Created
September 17, 2020 22:31
-
-
Save edubart/4991c5dd51205288519419f7d438adcf to your computer and use it in GitHub Desktop.
Arduino Game of Life in Nelua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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