Code archives/Algorithms/Generalized Cellular Automata Handler

This code has been declared by its author to be Public Domain code.

Download source code

Generalized Cellular Automata Handler by Pineapple2013
Rundown of the different classes involved:

Automata : contains a grid of cells, a buffer, and a reference to a Ruleset object.

Ruleset : contains general details like neighborhood and composition of borders, and an actual set of Rulesetrule objects.

Rulesetrule: contains things like the original cell and the result, and a set of Rulesetrulereq objects that have to all be met for the result to take the original's place.

Rulesetrulereq: defined in a format where the requirement is satisfied if the number of neighbors of a specified kind is at least X and/or/nand/nor no more than Y.

The example is fabulously thorough and everything is pretty well-commented, so it shouldn't be too difficult to figure out.
' 	--+-----------------------------------------------------------------------------------------+--
'	  |   This code was originally written by Sophie Kirschner (sophiek@pineapplemachine.com)   |  
' 	  | It is released as public domain. Please don't interpret that as liberty to claim credit |  
' 	  |   that isn't yours, or to sell this code when it could otherwise be obtained for free   |  
'	  |                because that would be a really shitty thing of you to do.                |
' 	--+-----------------------------------------------------------------------------------------+--



SuperStrict

Import brl.retro ' Only used for tostring() methods, feel free to get rid of them if you don't need to use them.

' Example program

Rem

' Controls:
'	LMB draws "on" cells, RMB draws "off".
'	Number keys 1-5 choose ruleset: Conway's Life, HighLife, Fredkin's Automata, Seeds, Brian's Brain
'	X clears the grid
'	Pressing period or holding space simulates a generation

' Consts for relating the cell grid to the graphics window
Const gw%=512,gh%=512
Const cw%=8,ch%=8

' Define some general rules that'll be used in the various example rulesets
Global B1rule		:rulesetrule=rulesetrule.Create(0,1,[rulesetrulereq.Create(1,1,1,rulesetrulereq.CONDITIONAND)])	' Turn on with 1 on neighbor
Global B2rule		:rulesetrule=rulesetrule.Create(0,1,[rulesetrulereq.Create(1,2,2,rulesetrulereq.CONDITIONAND)])	' Turn on with 2 on neighbors
Global B3rule		:rulesetrule=rulesetrule.Create(0,1,[rulesetrulereq.Create(1,3,3,rulesetrulereq.CONDITIONAND)])	' Turn on with 3 on neighbors
Global B5rule		:rulesetrule=rulesetrule.Create(0,1,[rulesetrulereq.Create(1,5,5,rulesetrulereq.CONDITIONAND)])	' Turn on with 5 on neighbors
Global B6rule		:rulesetrule=rulesetrule.Create(0,1,[rulesetrulereq.Create(1,6,6,rulesetrulereq.CONDITIONAND)])	' Turn on with 6 on neighbors
Global B7rule		:rulesetrule=rulesetrule.Create(0,1,[rulesetrulereq.Create(1,7,7,rulesetrulereq.CONDITIONAND)])	' Turn on with 7 on neighbors
Global S23rule	:rulesetrule=rulesetrule.Create(1,0,[rulesetrulereq.Create(1,2,3,rulesetrulereq.CONDITIONNAND)])	' Turn off with anything but 2 or 3 neighbors
Global Srule		:rulesetrule=rulesetrule.Create(1,0,Null)									' Turn off regardless of neighbors

Global border0%[]=[0,0,0,0]

' John Conway's Game of Life - http://en.wikipedia.org/wiki/Conway's_Game_of_Life
Global ConwaysGOL:ruleset=ruleset.Create(2,border0,ruleset.MooresNeighborhood) ' B3-S23
ConwaysGOL.addrule(B3rule)
ConwaysGOL.addrule(S23rule)

' HighLife - http://en.wikipedia.org/wiki/HighLife
Global HighLife:ruleset=ruleset.Create(2,border0,ruleset.MooresNeighborhood) ' B36-S23
HighLife.addrule(B3rule);HighLife.addrule(B6rule)
HighLife.addrule(S23rule)

' Fredkin's Automata - http://www.rennard.org/alife/english/acintrogb01.html
Global FredkinsAutomata:ruleset=ruleset.Create(2,border0,ruleset.MooresNeighborhood) ' B1357-S02468
FredkinsAutomata.addrule(B1rule);FredkinsAutomata.addrule(B3rule);FredkinsAutomata.addrule(B5rule);FredkinsAutomata.addrule(B7rule)
FredkinsAutomata.addrule(rulesetrule.Create(1,0,[	rulesetrulereq.Create(1,0,0,rulesetrulereq.CONDITIONNAND), ..	' Turn off with anything but 0, 2, 4, 6, or 8 neighbors
								rulesetrulereq.Create(1,2,2,rulesetrulereq.CONDITIONNAND), ..
								rulesetrulereq.Create(1,4,4,rulesetrulereq.CONDITIONNAND), ..
								rulesetrulereq.Create(1,6,6,rulesetrulereq.CONDITIONNAND), ..
								rulesetrulereq.Create(1,8,8,rulesetrulereq.CONDITIONNAND)  ]))

' Seeds - http://en.wikipedia.org/wiki/Seeds_(cellular_automaton)
Global Seeds:ruleset=ruleset.Create(2,border0,ruleset.MooresNeighborhood) ' B2-S
Seeds.addrule(B2rule)
Seeds.addrule(Srule)

' Brian's Brain - http://en.wikipedia.org/wiki/Brian's_Brain
Global BriansBrain:ruleset=ruleset.Create(3,border0,ruleset.MooresNeighborhood) ' B2-S with a third "refractory" state
BriansBrain.addrule(B2rule)
BriansBrain.addrule(rulesetrule.Create(1,2,Null))											' Turn on to refractory unconditionally
BriansBrain.addrule(rulesetrule.Create(2,0,Null))											' Turn refractory to off unconditionally

' graphics window
Graphics gw,gh

' render the cells with these colors
Global cellcolors%[][]=[[0,0,0],[255,255,255],[0,0,255]]

' cellular automata object containing a grid of cells
Global cells:automata=automata.Create(gw/cw,gh/ch,ConwaysGOL)

' main loop
Repeat
	Cls
	
	' draw on grid with mouse - left mouse turns on, right mouse turns off
	Local mx%=MouseX()/cw,my%=MouseY()/ch
	mx=Max(0,Min(cells.width-1,mx));my=Max(0,Min(cells.height-1,my))
	If MouseDown(1) Then
		cells.setcell(mx,my,1)
	ElseIf MouseDown(2)
		cells.setcell(mx,my,0)
	EndIf
	
	' clear grid on pressing x
	If KeyHit(key_x) Then cells.clearcells
	
	' switch ruleset with number keys
	If KeyHit(key_1) Then cells.rules=ConwaysGOL
	If KeyHit(key_2) Then cells.rules=HighLife
	If KeyHit(key_3) Then cells.rules=FredkinsAutomata
	If KeyHit(key_4) Then cells.rules=Seeds
	If KeyHit(key_5) Then cells.rules=BriansBrain
	
	' draw it
	SetColor 255,255,0
	For Local i%=0 Until cells.width
		For Local j%=0 Until cells.height
			Local c%=cells.getcell(i,j)
			SetColor cellcolors[c][0],cellcolors[c][1],cellcolors[c][2]
			DrawRect i*cw,j*ch,cw,ch
		Next
	Next
	
	' update when pressing period or holding down space
	If KeyDown(key_space) Or KeyHit(key_period) Then cells.update
	
	Flip
	If KeyDown(27) Or AppTerminate() Then End
Forever

EndRem



' Cellular automata grid type
Type automata
	Field width%,height%	' Dimensions of the cell grid
	Field grid%[][]		' Actual cell grid
	Field buffer%[][]		' Buffer for the cell grid, gets important when updating
	Field rules:ruleset		' Defines the actual ruleset to be used
	' Create a new automata object
	Function Create:automata(w%,h%,rules:ruleset)
		Local n:automata=New automata
		n.setsize w,h
		n.rules=rules
		Return n
	End Function
	' Set cell grid size
	Method setsize(w%,h%)
		width=w;height=h
		grid=New Int[][w]
		buffer=New Int[][w]
		For Local i%=0 Until w
			grid[i]=New Int[h]
			buffer[i]=New Int[h]
		Next
	End Method
	' Get the cell at a coord
	Method getcell%(x%,y%)
		Assert grid
		Assert x>=0 And y>=0 And x<width And y<height
		Return grid[x][y]
	End Method
	' Set the cell at a coord
	Method setcell(x%,y%,element%)
		Assert grid
		Assert x>=0 And y>=0 And x<width And y<height
		grid[x][y]=element
	End Method
	' Clear all cells to a value
	Method clearcells(element%=0)
		Assert grid
		For Local i%=0 Until width
			For Local j%=0 Until height
				grid[i][j]=element
			Next
		Next
	End Method
	' Update the simulation
	Method update()
		Assert grid And buffer
		' Iterate through all cells
		For Local i%=0 Until width
			Assert i<grid.length
			For Local j%=0 Until height
				Assert j<grid[i].length
				Assert grid[i][j]<rules.rules.length
				Local rulesarray:rulesetrule[]=rules.rules[grid[i][j]]
				buffer[i][j]=grid[i][j]
				' Do nothing if no rules apply directly to this cell state
				If rulesarray And rulesarray.length Then
					Local neighbors%[rules.elements]
					Assert rules.neighborhood
					' Iterate through all the cells in this one's neighborhood and tally up the numbers of each cell state
					For Local coord%[]=EachIn rules.neighborhood
						Local nx%=i+coord[0]
						Local ny%=j+coord[1]
						If nx<0
							neighbors[rules.border[rules.BORDERWEST]]:+1
						ElseIf nx>=width
							neighbors[rules.border[rules.BORDEREAST]]:+1
						ElseIf ny<0
							neighbors[rules.border[rules.BORDERNORTH]]:+1
						ElseIf ny>=height
							neighbors[rules.border[rules.BORDERSOUTH]]:+1
						Else
							neighbors[grid[nx][ny]]:+1
						EndIf
					Next
					' Iterate through all rules which apply to this cell state
					For Local r:rulesetrule=EachIn rulesarray
						Local allreqs%=1
						Assert r.reqs
						' Iterate through all the rule's rulereqs
						For Local req:rulesetrulereq=EachIn r.reqs
							Local atleast%=(req.at_least=-1) Or (neighbors[req.element]>=req.at_least)
							Local atmost%=(req.at_most=-1) Or (neighbors[req.element]<=req.at_most)
							If Not	((req.condition=rulesetrulereq.CONDITIONAND And (atleast And atmost)) Or ..		' AND conditional
								 (req.condition=rulesetrulereq.CONDITIONNAND And Not (atleast And atmost)) Or ..	' NAND conditional
								 (req.condition=rulesetrulereq.CONDITIONOR And (atleast Or atmost)) Or ..			' OR conditional
								 (req.condition=rulesetrulereq.CONDITIONNOR And Not (atleast Or atmost)) Or ..		' NOR conditional
								 (req.condition=rulesetrulereq.UNCONDITIONAL)) Then						' unconditional
								allreqs=0;Exit
							EndIf
						Next
						' Only apply the rule (change the cell to the result) if all of the rulereqs are satisfied
						If allreqs Then
							buffer[i][j]=r.result
							Exit
						EndIf
					Next
				EndIf
			Next
		Next
		' Swap the main grid of cells for the new one that just got put in the buffer
		Local t%[][]=grid
		grid=buffer;buffer=t
	End Method
End Type

' Cellular automata ruleset type
Type ruleset
	' Frequently-used neighborhoods for convenience
	Global VonNeumannNeighborhood%[][]=[[-1,0],[1,0],[0,-1],[0,1]]
	Global MooresNeighborhood%[][]=[[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]]
	Field elements%=2								' Number of elements the ruleset deals with
	Field border%[] 								' Composition of borders when they get considered as neighbors
	Field neighborhood%[][]							' Neighborhood; see this type's globals for examples
	Field rules:rulesetrule[][]						' Array containing an actual set of rules
	Const BORDEREAST%=0,BORDERSOUTH%=1,BORDERWEST%=2,BORDERNORTH%=3	' Constants for use with the border[] array
	' Create a new ruleset object
	Function Create:ruleset(elements%,border%[],neighborhood%[][])
		Local n:ruleset=New ruleset
		n.elements=elements
		n.border=border
		n.neighborhood=neighborhood
		n.rules=New rulesetrule[][n.elements]
		Return n
	End Function
	' Add a rulesetrule to the ruleset's array of rules
	Method addrule(r:rulesetrule)
		Local length%
		If Not rules[r.element] Then
			length=0
			rules[r.element]=New rulesetrule[1]
		Else
			length=rules[r.element].length
			rules[r.element]=rules[r.element][..length+1]
		EndIf
		rules[r.element][length]=r
	End Method
	' Useful for debugging, since rulesets can be a little esoteric
	Method tostring$() 
		Local str$=""
		For Local rulesarray:rulesetrule[]=EachIn rules
			Local rstr$=""
			For Local rule:rulesetrule=EachIn rulesarray
				rstr:+rule.tostring()+"; "
			Next
			rstr=Left(rstr,rstr.length-2)
			If rstr Then str:+rstr+"~n"
		Next
		Return Left(str,str.length-1)
	End Method
End Type

' Cellular automata rule type; rulesets contain these
Type rulesetrule
	Field element%			' Cell state that the rule gets applied to
	Field result%				' State that the cell becomes if all the rule's requirements ("reqs") are met
	Field reqs:rulesetrulereq[]	' Array containing a set of requirements ("reqs") to be met
	' Creates a new rulesetrule object
	Function Create:rulesetrule(element%,result%,reqs:rulesetrulereq[])
		Local n:rulesetrule=New rulesetrule
		n.element=element
		n.result=result
		n.reqs=reqs
		Return n
	End Function
	' Useful for debugging, since rulesets can be a little esoteric
	Method tostring$() 
		Local str$="Element: "+element+", Result: "+result+": "
		For Local req:rulesetrulereq=EachIn reqs
			str:+"("+req.tostring()+") AND "
		Next
		If reqs.length Then str=Left(str,str.length-5)
		Return str
	End Method
End Type

' Cellular automata rule requirement type; rulesetrules contain these, and for a rule to take effect all of its rulereqs must be satisfied
Type rulesetrulereq
	Field element%			' Which cell state is being counted from one cell's neighbors
	Field at_least%=-1			' The minimum number of neighboring cells of the specified state; -1 is analogous to "At least zero"
	Field at_most%=-1			' The maximum number of neighboring cells of the specified state; -1 is analogous to "At most [highest possible number of neighbors]"
	Field condition%=CONDITIONAND	' Important! Logical operators for whether the min AND max should be met, min OR max, min NAND max, min NOR max, or whether the requirement is just bollocks and "UNCONDITIONAL"
	Const CONDITIONAND%=0,CONDITIONOR%=1,CONDITIONNAND%=2,CONDITIONNOR%=3,UNCONDITIONAL%=4
	' Creates a new rulesetrulereq object
	Function Create:rulesetrulereq(element%,at_least%=-1,at_most%=-1,condition%=CONDITIONAND)
		Local n:rulesetrulereq=New rulesetrulereq
		n.element=element
		n.at_least=at_least
		n.at_most=at_most
		n.condition=condition
		Return n
	End Function
	' Useful for debugging, since rulesets can be a little esoteric
	Method tostring$() 
		If condition=UNCONDITIONAL Then Return "UNCONDITIONAL"
		Local cond$[]=["AND","OR","NAND","NOR"]
		Local least$="[1]"
		If at_least>=0 Then least="At least "+at_least
		Local most$="[1]"
		If at_most>=0 Then most="At most "+at_most
		Return least+" "+cond[condition]+" "+most
	End Method
End Type

Comments

None.

Code Archives Forum