Skip to content

Instantly share code, notes, and snippets.

@edubart
Created September 17, 2020 22:31
Show Gist options
  • Save edubart/4991c5dd51205288519419f7d438adcf to your computer and use it in GitHub Desktop.
Save edubart/4991c5dd51205288519419f7d438adcf to your computer and use it in GitHub Desktop.
Arduino Game of Life in Nelua
require 'arduino'
-- user configurable defines
local CELL_SIZE: cint <comptime> = 4 -- the height and width of each cell
local DELAY: cint <comptime> = 50 -- number of milliseconds delay between the display of each generation
-- end of user configurable defines
local ROWS: cint <comptime> = (DISPLAY_HEIGHT // CELL_SIZE)
local COLUMNS: cint <comptime> = (DISPLAY_WIDTH // CELL_SIZE)
local BYTES_PER_COLUMN: cint <comptime> = ((COLUMNS + 7) // 8) --this is the number of bytes needed to hold a column
local MAX_ITERATIONS: cint <comptime> = 500 -- iterations are terminated if this number is exceeded
local STABLE_GENERATIONS: cint <comptime> = 4 -- must be at least 2, this is the number of back generations checked for stability
local MIN_OBJECTS: cint <comptime> = 2 -- minimum number of objects to be seeded by the random generator
local MAX_OBJECTS: cint <comptime> = 4 -- maximum number of objects for random seed generator
local MARGIN: cint <comptime> = 4 -- minimum distance from screen edge for randomly seeded objects
local OBJECT_COUNT: cint <comptime> = 7
local ALIVE: boolean <comptime> = true
local DEAD: boolean <comptime> = false
-- macro to get a bit field value
local function BITVAL(pos: byte) <inline>
return (1_b << (7_b - (pos % 8_b)))
end
-- structure template for life objects
local object_t = @record{
height: byte,
width: byte,
bitfield: [0]byte
}
local object_ptr_t = @*object_t
-- definitions and initialization for all defined objects here
local tble: record{
height: byte,
width: byte,
bitfield: [2]byte
} = {2,4, {
0b10010000, -- * *
0b11110000, -- ****
}}
local glider: record{
height: byte,
width: byte,
bitfield: [3]byte
} = {3,3, {
0b00100000, -- *
0b10100000, -- * *
0b01100000, -- **
}}
local loaf: record{
height: byte,
width: byte,
bitfield: [4]byte
} = {4,4, {
0b01100000, -- **
0b10010000, -- * *
0b01010000, -- * *
0b00100000, -- *
}}
local ship: record{
height: byte,
width: byte,
bitfield: [3]byte
} = {3,3, {
0b11000000, -- **
0b10100000, -- * *
0b01100000, -- **
}}
local behive: record{
height: byte,
width: byte,
bitfield: [4]byte
} = {4,3, {
0b01000000, -- *
0b10100000, -- * *
0b10100000, -- * *
0b01000000, -- *
}}
local blinker: record{
height: byte,
width: byte,
bitfield: [1]byte
} = {1,3, { 0b11100000 }} -- ***
local block: record{
height: byte,
width: byte,
bitfield: byte[2]
} = {2,2, {
0b11000000, -- **
0b11000000, -- **
}}
-- place all the objects in the object tble
local objectTable: [7]object_ptr_t = {
(@object_ptr_t)(&tble),
(@object_ptr_t)(&glider),
(@object_ptr_t)(&loaf),
(@object_ptr_t)(&ship),
(@object_ptr_t)(&behive),
(@object_ptr_t)(&blinker),
(@object_ptr_t)(&block)
};
-- enumerate all the objects, add any new objects to the end of this enum (note case difference )
-- the life program references all objects using the names in this list
local ObjectKind = @enum{
Table=0,Glider,Loaf,Ship,Behive,Blinker,Block
};
-- array used to calculate, draw and check if iterations are changing
local generations: [STABLE_GENERATIONS][ROWS][BYTES_PER_COLUMN]byte
local function isAlive(generation: byte, row: byte, column: byte): boolean
local b: byte = generations[generation][row][column // 8]
return (b & BITVAL(column)) ~= 0
end
local function setLife(generation: byte, row: byte, column: byte, state: boolean)
local elem: byte = column // 8
local b: byte = generations[generation][row][elem]
if state ~= DEAD then
b = b | BITVAL(column)
else
b = b & ~(BITVAL(column))
end
generations[generation][row][elem] = b
end
local function isNeighborAlive(generation: byte, row: byte, column: byte, dir: byte): boolean
local nrow: byte = row
local ncol: byte = column
if dir == 7 or dir == 0 or dir == 1 then
nrow = nrow - 1
end
if dir == 3 or dir == 4 or dir == 5 then
nrow = nrow + 1
end
if dir == 5 or dir == 6 or dir == 7 then
ncol = ncol - 1
end
if dir == 1 or dir == 2 or dir == 3 then
ncol = ncol + 1
end
if ncol == 255 then
ncol = COLUMNS - 1
end
if ncol == COLUMNS then
ncol = 0
end
if nrow == 255 then
nrow = ROWS - 1
end
if nrow == ROWS then
nrow = 0
end
return isAlive(generation,nrow,ncol)
end
local function getNeighborCount(generation: byte, row: byte, column: byte): byte
local count: byte = 0
for d:byte=0,<8 do
if isNeighborAlive(generation,row,column,d) then
count = count + 1
end
end
return count
end
local function isStable(thisGeneration: byte): boolean
-- returns true if any two captured generations are the same
for i:byte=0,<STABLE_GENERATIONS do
if i ~= thisGeneration then
if generations[thisGeneration] == generations[i] then
return true
end
end
end
return false
end
local function generate(): cuint
local thisGeneration: byte, nextGeneration: byte
-- display the initial array
for row:cint=0,<ROWS do
for column:cint=0,<COLUMNS do
if isAlive(thisGeneration,row,column) then
GLCD.DrawRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE//2, BLACK)
else
GLCD.DrawRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE//2, WHITE)
end
end
end
delay(1500) -- show the seeded condition a little longer
for iteration:cuint=0,<MAX_ITERATIONS do
thisGeneration = iteration % STABLE_GENERATIONS
nextGeneration = (thisGeneration+1) % STABLE_GENERATIONS
delay(DELAY)
generations[nextGeneration] = {} -- clear the array for the next generation
--see who lives and who dies
for row:cint = 0,<ROWS do
for column:cint = 0,<COLUMNS do
local cell: boolean = isAlive(thisGeneration,row,column)
local n: byte = getNeighborCount(thisGeneration,row,column)
if cell == DEAD then
if n==3 then
setLife(nextGeneration,row,column,ALIVE); -- birth
GLCD.DrawRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE//2, BLACK)
end
else
if n==2 or n==3 then
setLife(nextGeneration,row,column,ALIVE); --survival
-- No need to draw cell as it is already there.
else
setLife(nextGeneration,row,column,DEAD); --death
GLCD.DrawRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE//2, WHITE)
end
end
end
end
if isStable(thisGeneration) then
return iteration
end
end
return MAX_ITERATIONS
end
local function loadObject(object: byte, y: int8, x: int8)
-- object_ptr is the object to be loaded
-- y and x are offsets from the center of the display
local object_ptr: object_ptr_t = (@object_ptr_t)(objectTable[object])
local row: byte = (ROWS//2) + y
local column: byte = (COLUMNS//2) + x
for ys:byte = 0, < object_ptr.height do
for xs:byte = 0, < object_ptr.width do
if (row + ys < ROWS) and (column + xs < COLUMNS) then
-- this code only works on widths up to 8 bits
local value: boolean = (object_ptr.bitfield[ys] & BITVAL(xs)) ~= 0
setLife(0, row+ys, column+xs, value) -- objects always loaded into the first array
end
end
end
end
local function seed(seedValue: cint)
-- a hard coded seed is used if seedvalue is zero, otherwise a random seed is used
generations = {} -- clear all generation arrays
if seedValue == 0 then
-- load default objects, arguments are offsets from the center of the screen
loadObject(ObjectKind.Table, 2, -9) -- put a table object 2 down from the center and 9 columns left or center
loadObject(ObjectKind.Glider, -5, -9) -- put a glider 5 rows above the center and 9 columns left of center
else
-- load some random objects and place at random positions
local nbrObjects: byte = random2(MIN_OBJECTS,MAX_OBJECTS+1) -- make a random number of objects
for i:byte=0,<nbrObjects do
local obj: byte = random(OBJECT_COUNT)
local column: cint = random2(MARGIN - (COLUMNS//2), (COLUMNS//2) - MARGIN)
local row: cint = random2(MARGIN - (ROWS//2), (ROWS//2) - MARGIN)
loadObject(obj,row,column)
end
end
end
local function setup()
GLCD.Init() -- initialize the library, non inverted writes pixels onto a clear screen
GLCD.SelectFont(System5x7)
GLCD.DrawString("Life", gTextfmt_center, gTextfmt_center)
delay(3000)
seed(0)
end
local function loop()
GLCD.ClearScreen()
local i: cuint = generate()
GLCD.CursorTo(0,0)
GLCD.print1u(i)
GLCD.print(" iterations")
delay(2000)
seed(1) -- use random seed
end
setup()
while true do
loop()
end
-- Import Arduino
global function pinMode(a: cint, b: cint) <cimport,nodecl> end
global function digitalWrite(a: cint, b: cint) <cimport,nodecl> end
global function delay(a: cint) <cimport,nodecl> end
global function random(a: cint): cint <cimport,nodecl> end
global function random2(a: cint, b: cint): cint <cimport'random',nodecl> end
global LED_BUILTIN: cint <cimport,nodecl>
global OUTPUT: cint <cimport,nodecl>
global HIGH: cint <cimport,nodecl>
global LOW: cint <cimport,nodecl>
local function nelua_main(argc: cint, argv: *[0]cstring): cint <cimport,nodecl> end
local function setup() <entrypoint>
nelua_main(0, nilptr)
end
-- Import GLCD
## cinclude '<openGLCD.h>'
global GLCD <cimport,nodecl> = @record{}
global GLCDFont <cimport 'Font_t',nodecl> = @record{}
function GLCD.Init() <cimport 'GLCD.Init',nodecl> end
function GLCD.SelectFont(font: GLCDFont) <cimport 'GLCD.SelectFont',nodecl> end
function GLCD.ClearScreen() <cimport 'GLCD.ClearScreen',nodecl> end
function GLCD.CursorTo(x: cint, y: cint) <cimport 'GLCD.CursorTo',nodecl> end
function GLCD.DrawString(text: cstring, hpos: cint, vpos: cint) <cimport 'GLCD.DrawString',nodecl> end
function GLCD.print1u(text: cint) <cimport 'GLCD.print',nodecl> end
function GLCD.print(text: cstring) <cimport 'GLCD.print',nodecl> end
function GLCD.DrawRoundRect(x: byte, y: byte, w: byte, h: byte, radius: byte, col: byte) <cimport 'GLCD.DrawRoundRect',nodecl> end
global System5x7: GLCDFont <cimport,nodecl>
global gTextfmt_center: cint <cimport,nodecl>
global gTextfmt_center: cint <cimport,nodecl>
global DISPLAY_WIDTH: cint <comptime> = 128
global DISPLAY_HEIGHT: cint <comptime> = 64
global BLACK: byte <cimport,nodecl>
global WHITE: byte <cimport,nodecl>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment