Conway's Game of Life

Share your advanced PureBasic knowledge/code with the community.
chris_b
Enthusiast
Enthusiast
Posts: 103
Joined: Sun Apr 27, 2003 1:54 am

Conway's Game of Life

Post by chris_b »

Here's a little code to show Conway's Life on a 256x256 grid:

Code: Select all

DefType.l x,y,i,col,frame,oldframe

Dim LifeArray.b(1,255,255)

InitKeyboard()
InitSprite()
InitSprite3D()
OpenScreen(1024, 768, 16, "Life")
TransparentSpriteColor(-1, 255, 0, 255)
CreateSprite(0,256,256,#PB_Sprite_Texture)
CreateSprite3D(0, 0)
ZoomSprite3D(0, 768,768)
Sprite3DQuality(0)

col=RGB(240,240,255)

frame=0
oldframe=0

Procedure RandomLife()

For y=0 To 255
  For x=0 To 255

    LifeArray(0,x,y)=Random(1)

  Next
Next

EndProcedure

randomlife()

Repeat

  FlipBuffers()

  oldframe=frame
  frame=1-frame

  For y=0 To 255 
    For x=0 To 255 

      i=LifeArray(oldframe,(x-1)&255,(y-1)&255)
      i=i+LifeArray(oldframe,x,(y-1)&255)
      i=i+LifeArray(oldframe,(x+1)&255,(y-1)&255)
      i=i+LifeArray(oldframe,(x-1)&255,y)
      i=i+LifeArray(oldframe,(x+1)&255,y)
      i=i+LifeArray(oldframe,(x-1)&255,(y+1)&255)
      i=i+LifeArray(oldframe,x,(y+1)&255)
      i=i+LifeArray(oldframe,(x+1)&255,(y+1)&255)

      If i=2 And LifeArray(oldframe,x,y)=1
        LifeArray(frame,x,y)=1
      ElseIf i=3
        LifeArray(frame,x,y)=1
      Else
        LifeArray(frame,x,y)=0
      EndIf

    Next
  Next

  StartDrawing(SpriteOutput(0))

  Box(0, 0, 256, 256 ,0) 

  For y=0 To 255
    For x=0 To 255
      If LifeArray(frame,x,y)
        Plot(x,y,col)
      endif
    Next
  Next

  StopDrawing()

  ClearScreen(0,0,0)

  Start3D()
  DisplaySprite3D(0,128,0,255) 
  Stop3D()

  ExamineKeyboard()

  If KeyboardPushed(#PB_Key_Space)
    randomlife()
  endif

Until KeyboardPushed(#PB_Key_Escape)

End
It runs pretty fast on my system, but there's plenty of scope to speed it up. For example the bitwise ANDs used to ensure that the world wraps around are not necessary - really the outside edges should be processed in seperate loops. But I left it like this so the code is easier to understand.

Don't forget to disable debugger for best speed.
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

Post by GPI »

chris_b
Enthusiast
Enthusiast
Posts: 103
Joined: Sun Apr 27, 2003 1:54 am

Post by chris_b »

heh... your version makes my code snippet look somewhat minimalistic in comparison
User avatar
kenmo
Addict
Addict
Posts: 2045
Joined: Tue Dec 23, 2003 3:54 am

Post by kenmo »

Nice job, both of you. I recently did this in DBPro, and it also worked surprisingly fast. By the way GPI, yours says "Warp" instead of "Wrap"...
infratec
Always Here
Always Here
Posts: 7619
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Conway's Game of Life

Post by infratec »

I brought it back to Life :mrgreen:

Code: Select all

EnableExplicit

Global Dim LifeArray.a(1, 255, 255)


Procedure RandomLife()
  
  Protected.i x, y
  
  For y = 0 To 255
    For x = 0 To 255
      LifeArray(0, x, y) = Random(1)
    Next x
  Next y
  
EndProcedure




Define.i x, y, i, col, frame, oldframe, Event


InitKeyboard()
InitSprite()

OpenWindow(0, 0, 0, 768, 768, "Life", #PB_Window_MinimizeGadget|#PB_Window_ScreenCentered)
OpenWindowedScreen(WindowID(0), 0, 0, 1024, 768)

CreateSprite(0,256,256)
ZoomSprite(0, 768,768)

col = RGB(240,240,255)

randomlife()

Repeat
  
  Repeat
    Event = WindowEvent()
  Until Event = 0 Or Event = #PB_Event_CloseWindow
  
  FlipBuffers()
  
  oldframe = frame
  frame = 1 - frame
  
  If StartDrawing(SpriteOutput(0))
    Box(0, 0, 256, 256 ,0) 
    
    For y = 0 To 255 
      For x = 0 To 255 
        
        i =     LifeArray(oldframe, (x-1)&255, (y-1)&255)
        i = i + LifeArray(oldframe, x        , (y-1)&255)
        i = i + LifeArray(oldframe, (x+1)&255, (y-1)&255)
        i = i + LifeArray(oldframe, (x-1)&255, y)
        i = i + LifeArray(oldframe, (x+1)&255, y)
        i = i + LifeArray(oldframe, (x-1)&255, (y+1)&255)
        i = i + LifeArray(oldframe, x        , (y+1)&255)
        i = i + LifeArray(oldframe, (x+1)&255,(y+1)&255)
        
        If i = 2 And LifeArray(oldframe, x, y) = 1
          LifeArray(frame, x, y) = 1
          Plot(x, y, col)
        ElseIf i = 3
          LifeArray(frame, x, y) = 1
          Plot(x, y, col)
        Else
          LifeArray(frame, x, y) = 0
        EndIf
        
      Next
    Next
    
    StopDrawing()
  EndIf
  
  ClearScreen(0)
  
  DisplaySprite(0, 0, 0)
  
  ExamineKeyboard()
  
  If KeyboardPushed(#PB_Key_Space)
    randomlife()
  EndIf
  
Until KeyboardPushed(#PB_Key_Escape) Or Event = #PB_Event_CloseWindow
Compiles now with the latest PB version (6.02) without errors.
User avatar
Caronte3D
Addict
Addict
Posts: 1361
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: Conway's Game of Life

Post by Caronte3D »

Nice! 8)
benubi
Enthusiast
Enthusiast
Posts: 220
Joined: Tue Mar 29, 2005 4:01 pm

Re: Conway's Game of Life

Post by benubi »

Nice!
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Conway's Game of Life

Post by Kwai chang caine »

Very nice 8)
it seem a Petri dish :lol:

Image
ImageThe happiness is a road...
Not a destination
User avatar
jacdelad
Addict
Addict
Posts: 2010
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: Conway's Game of Life

Post by jacdelad »

They always die after some time...
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
Mr.L
Enthusiast
Enthusiast
Posts: 146
Joined: Sun Oct 09, 2011 7:39 am

Re: Conway's Game of Life

Post by Mr.L »

You like the game of life? :D
Here is my PureBasic implementation of the Hashlife algorithm.
The original code was written in Python script by Eric Burnett (https://github.com/EricBurnett/GameOfLife)
I have ported it to PureBasic and added many features.
- navigate with the mouse, zoom in/out with the mousewheel
- add/delete pixels with the number keys
- supported rules are Game-Of-Life and WireWorld
- supported cell-file-types: *.cells, *.lif, *.life, *.mc, *.mcl and *.rle

Code: Select all

; originally written in Python script by Eric Burnett (https://github.com/EricBurnett/GameOfLife)
; ported to PureBasic, rewritten and enhanced by Mr.L (2016)

EnableExplicit

#MAXLEVEL = 60
#MAXMEM = 500 << 20

Enumeration
	#Gadget_Canvas
	#Gadget_Start
	#Gadget_Step
	#Gadget_Speed
	#Gadget_Left
	#Gadget_Right
	#Gadget_Up
	#Gadget_Down
	#Gadget_Center
	#Menu_Open
	#Menu_Quit
	#Menu_ShowQuadTree
	#Menu_ShowDescription
	#Menu_RuleGameOfLife
	#Menu_RuleWireWorld
EndEnumeration

Structure POINT2Q
	x.q
	y.q
EndStructure

Structure POINT2D
	x.d
	y.d
EndStructure

Structure BOUNDS
	b.q[4]
EndStructure

Structure Node
	level.q
	*hash.Node
	*n.Node[4]
	*next.Node
	nextLevel.q
	usage.b
EndStructure

Structure World
	*root.Node
	view_center.POINT2D
	view_size.d
	view_bounds.BOUNDS
	cursor.POINT2Q
	offset.POINT2Q
EndStructure

Structure Game
	canvas.i
	width.i
	height.i
	generations_per_update.q
	delay_per_update.i
	next_update_Time.i
	redrawTime.i
	iteration_count.q
	drawTree.b
	paused.b
	description.s
	*ruleProc
	*world.World
EndStructure

Global NewList Nodes.Node()
Global Dim *zero.Node(#MAXLEVEL)
Global World.World
Global Game.Game
Global *FreeNodes, FreeNodeCount
Global Dim *hashTab.Node(1)
Global hashPrime.q, hashLimit.q, hashPop.q, hashFound.q
Global Dim Pow2.q(#MAXLEVEL)
Global Dim Rule(255, 255)
Global countPixel, countBox

LoadFont(0, "Consolas", 8, #PB_Font_Bold)

Declare Game_Draw()
Declare ParseFile(name.s)
Declare Game_OpenFile(path.s = "", filename.s = "", speed = 2)

; class Node
; A Node represents a square 2^N x 2^N cluster of cells.

; The Node class is based on the description of the HashLife algorithm found
; at http://drdobbs.com/high-performance-computing/184406478. It is a hash tree
; with agressive caching and de-duplication. In particular:
; * Nodes are defined recursively, with nw, ne, sw, and se being Nodes
; representing the 2^(N-1) x 2^(N-1) cells in a particular corner ([0|1] at
; the leaves).
; * Nodes are immutable. Once a Node is returned from Node.CanonicalNode(), the
; cells represented are guaranteed not to change.
; * Nodes are unique. They are constructed in such a way that no two Node
; objects can represent the same configuration of cells. In particular, this
; means that Nodes can be compared by equality by doing id(a)==id(b). This
; means that most node hierarchies take far less than 2^(2N) space to store.
; * One key operation on a Node is to return the inner core 2^(N-1) x 2^(N-1)
; cells forward a number of generations (usually 2^(N-2)). This is cached
; wherever possible, and, along with identical nodes being shared due to their
; uniqueness, means calculating the future inner core of a Node is usually far
; cheaper than the worst Case 2^(2N) operation.

Procedure.q min(a.q, b.q)
	If a <= b
		ProcedureReturn a
	EndIf
	ProcedureReturn b
EndProcedure

Procedure.q max(a.q, b.q)
	If a >= b
		ProcedureReturn a
	EndIf
	ProcedureReturn b
EndProcedure

Global Dim NodeColor(255)

;- -----------------------------------

Procedure.s DebugNode(*node.Node, level = 0)
	If *node <= 0 : ProcedureReturn : EndIf
	Protected text.s = "(" + Str(*node\level)
	
	Debug RSet(Hex(*node), 12) + RSet(" ", (4 + level * 4), "_") + Hex(*node\n[0]) + "','" + Hex(*node\n[1]) + "','" + Hex(*node\n[2]) + "','" + Hex(*node\n[3])
	If *node\level = 1
		text + ", '" + Str(*node\n[0]) + "','" + Str(*node\n[1]) + "','" + Str(*node\n[2]) + "','" + Str(*node\n[3]) + "'"
	ElseIf *node\level > 1
		If *node\n[0]: text + DebugNode(*node\n[0], level + 1) : EndIf
		If *node\n[1]: text + DebugNode(*node\n[1], level + 1) : EndIf
		If *node\n[2]: text + DebugNode(*node\n[2], level + 1) : EndIf
		If *node\n[3]: text + DebugNode(*node\n[3], level + 1) : EndIf
	EndIf
	ProcedureReturn text + ")"
EndProcedure

Macro SetBounds(b_, b0_, b1_, b2_, b3_)
	b_\b[0] = (b0_)
	b_\b[1] = (b1_)
	b_\b[2] = (b2_)
	b_\b[3] = (b3_)
EndMacro

; Various sum functions, for counting portions of a level-1 node.

Macro Node_Sum(node_, index_)
	; index is the value To skip (i.e. count 3 of 4 cells).
	(node_\n[0] + node_\n[1] + node_\n[2] + node_\n[3] - node_\n[index_])
EndMacro

Macro Node_SumLeft(node_)
	(node_\n[0] + node_\n[2])
EndMacro

Macro Node_SumTop(node_)
	(node_\n[0] + node_\n[1])
EndMacro

Macro Node_SumRight(node_)
	(node_\n[1] + node_\n[3])
EndMacro

Macro Node_SumBottom(node_)
	(node_\n[2] + node_\n[3])
EndMacro

Macro Node_Zero(level_)
	*zero(level_)
EndMacro

Macro Node_IsZero(node_)
	Bool(Node_Zero(node_\level) = node_)
EndMacro

Macro Node_MergeHorizontal(l_, r_)
	Node_Canonical(l_\level, l_\n[1], r_\n[0], l_\n[3], r_\n[2])
EndMacro

Macro Node_MergeVertical(t_, b_)
	Node_Canonical(t_\level, t_\n[2], t_\n[3], b_\n[0], b_\n[1])
EndMacro

Macro Node_MergeCenter(nw_, ne_, sw_, se_)
	Node_Canonical(nw_\level, nw_\n[3], ne_\n[2], sw_\n[1], se_\n[0])
EndMacro

Macro Hash_Key(nw_, ne_, sw_, se_)
	(5 * (nw_) + 17 * (ne_) + 257 * (sw_) + 65537 * (se_))
EndMacro

Procedure.q Hash_NextPrime(i.q)
	i | 1
	Protected j.q = 3
	Repeat
		i + 2
		While (j * j < i) And (i % j)
			j + 2
		Wend
	Until (j * j > i)
	ProcedureReturn i
EndProcedure

Procedure Hash_Resize()
	Protected nHashPrime.q = Hash_NextPrime(2 * hashPrime)
	Protected Dim *nHashTab.Node(nHashPrime)
	Protected h.q, i, q, *p.Node
	
	For i = 0 To hashPrime - 1
		*p = *hashTab(i)
		While *p
			Protected *np.Node = *p\hash
			Protected nw = *p\n[0]
			Protected ne = *p\n[1]
			Protected sw = *p\n[2]
			Protected se = *p\n[3]
			h = Hash_Key(nw, ne, sw, se) % nHashPrime
			*p\hash = *nHashTab(h)
			*nHashTab(h) = *p
			*p = *np
		Wend
	Next
	
	CopyArray(*nHashTab(), *hashTab())
	
	hashPrime = nHashPrime
	hashLimit = hashPrime
EndProcedure

Procedure Node_Canonical(level, nw, ne, sw, se)
	; Returns a canonical version of a new node.
	
	Static h.q
	Static.Node *canonical, *pred
	
	h = Hash_Key(nw, ne, sw, se) % hashPrime
	*pred = #Null
	
	*canonical = *hashTab(h)
	If *canonical
		hashFound + 1
		If *canonical\n[0] = nw And *canonical\n[1] = ne And *canonical\n[2] = sw And *canonical\n[3] = se
			ProcedureReturn *canonical
		Else
			While *canonical\hash
				*canonical = *canonical\hash
				If *canonical\n[0] = nw And *canonical\n[1] = ne And *canonical\n[2] = sw And *canonical\n[3] = se
					If *pred
						*pred\hash = *canonical\hash
						*canonical\hash = *hashTab(h)
						*hashTab(h) = *canonical
					EndIf
					ProcedureReturn *canonical
				EndIf
				*pred = *canonical
			Wend
		EndIf
	EndIf
	
	*canonical = AddElement(Nodes())
	*canonical\level = level
	*canonical\n[0] = nw
	*canonical\n[1] = ne
	*canonical\n[2] = sw
	*canonical\n[3] = se
	*canonical\hash = *hashTab(h)
	
	*hashTab(h) = *canonical
	hashPop + 1
	If hashpop > hashLimit
		Hash_Resize()
	EndIf
	
	ProcedureReturn *canonical
EndProcedure

Procedure Node_Expand(*node.Node)
	; Returns a node one level deeper, with the center being this node.
	Static *zero.Node
	
	*zero = Node_Zero(*node\level - 1)
	ProcedureReturn Node_Canonical(*node\level + 1,
	                               Node_Canonical(*node\level, *zero, *zero, *zero, *node\n[0]),
	                               Node_Canonical(*node\level, *zero, *zero, *node\n[1], *zero),
	                               Node_Canonical(*node\level, *zero, *node\n[2], *zero, *zero),
	                               Node_Canonical(*node\level, *node\n[3], *zero, *zero, *zero))
EndProcedure

Procedure Node_Compact(*node.Node)
	; Returns the smallest node (level >= 1) that will contain all the cells
	; (without shifting the center).
	
	If *node\level < 1
		ProcedureReturn *node
	EndIf
	
	Static *zero.Node
	
	*zero = Node_Zero(*node\level - 2)
	While (*node\level > 2 And
	       (*node\n[0]\n[0] = *zero) And (*node\n[0]\n[1] = *zero) And (*node\n[0]\n[2] = *zero) And
	       (*node\n[1]\n[0] = *zero) And (*node\n[1]\n[1] = *zero) And (*node\n[1]\n[3] = *zero) And
	       (*node\n[2]\n[0] = *zero) And (*node\n[2]\n[2] = *zero) And (*node\n[2]\n[3] = *zero) And
	       (*node\n[3]\n[1] = *zero) And (*node\n[3]\n[2] = *zero) And (*node\n[3]\n[3] = *zero))
		*node = Node_Canonical(*node\level - 1, *node\n[0]\n[3], *node\n[1]\n[2], *node\n[2]\n[1], *node\n[3]\n[0])
		*zero = Node_Zero(*node\level - 2)
	Wend
	
	ProcedureReturn *node
EndProcedure

Procedure Node_Rule_WireWorld(*node.Node)
	Static s0, s1, s2, s3, h
	
	s0 = *node\n[0]\n[3]
	s1 = *node\n[1]\n[2]
	s2 = *node\n[2]\n[1]
	s3 = *node\n[3]\n[0]
	
	If s0 = 3
		h = Bool(*node\n[0]\n[0] = 1) + Bool(*node\n[0]\n[1] = 1) + Bool(*node\n[0]\n[2] = 1) +
		    Bool(*node\n[1]\n[0] = 1) + Bool(*node\n[1]\n[2] = 1) +
		    Bool(*node\n[2]\n[0] = 1) + Bool(*node\n[2]\n[1] = 1) +
		    Bool(*node\n[3]\n[0] = 1)
		If h = 1 Or h = 2 : s0 = 1 : EndIf
	ElseIf s0
		s0 + 1
	EndIf
	
	If s1 = 3
		h = Bool(*node\n[0]\n[1] = 1) + Bool(*node\n[0]\n[3] = 1) + Bool(*node\n[1]\n[0] = 1) +
		    Bool(*node\n[1]\n[1] = 1) + Bool(*node\n[1]\n[3] = 1) +
		    Bool(*node\n[2]\n[1] = 1) +
		    Bool(*node\n[3]\n[0] = 1) + Bool(*node\n[3]\n[1] = 1)
		If h = 1 Or h = 2 : s1 = 1 : EndIf
	ElseIf s1
		s1 + 1
	EndIf
	
	If s2 = 3
		h = Bool(*node\n[0]\n[2] = 1) + Bool(*node\n[0]\n[3] = 1) +
		    Bool(*node\n[1]\n[2] = 1) +
		    Bool(*node\n[2]\n[0] = 1) + Bool(*node\n[2]\n[2] = 1) + Bool(*node\n[2]\n[3] = 1) +
		    Bool(*node\n[3]\n[0] = 1) + Bool(*node\n[3]\n[2] = 1)
		If h = 1 Or h = 2 : s2 = 1 : EndIf
	ElseIf s2
		s2 + 1
	EndIf
	
	If s3 = 3
		h = Bool(*node\n[0]\n[3] = 1) +
		    Bool(*node\n[1]\n[2] = 1) + Bool(*node\n[1]\n[3] = 1) +
		    Bool(*node\n[2]\n[1] = 1) + Bool(*node\n[2]\n[3] = 1) +
		    Bool(*node\n[3]\n[1] = 1) + Bool(*node\n[3]\n[2] = 1) + Bool(*node\n[3]\n[3] = 1)
		If h = 1 Or h = 2 : s3 = 1 : EndIf
	ElseIf s3
		s3 + 1
	EndIf
	
	ProcedureReturn Node_Canonical(1, s0, s1, s2, s3)
EndProcedure

Procedure Node_Rule_GameOfLife(*node.Node)
	Static countNW, countNE, countSW, countSE, i
	
	countNW = (Node_Sum(*node\n[0], 3) + Node_SumLeft (*node\n[1]) + Node_SumTop (*node\n[2]) + *node\n[3]\n[0])
	countNE = (Node_Sum(*node\n[1], 2) + Node_SumRight(*node\n[0]) + Node_SumTop (*node\n[3]) + *node\n[2]\n[1])
	countSW = (Node_Sum(*node\n[2], 1) + Node_SumLeft (*node\n[3]) + Node_SumBottom(*node\n[0]) + *node\n[1]\n[2])
	countSE = (Node_Sum(*node\n[3], 0) + Node_SumRight(*node\n[2]) + Node_SumBottom(*node\n[1]) + *node\n[0]\n[3])

	i = *node\n[0]\n[3]
	countNW = Rule(countNW, i)
	i = *node\n[1]\n[2]
	countNE = Rule(countNE, i)
	i = *node\n[2]\n[1]
	countSW = Rule(countSW, i)
	i = *node\n[3]\n[0]
	countSE = Rule(countSE, i)
	
	ProcedureReturn Node_Canonical(1, countNW, countNE, countSW, countSE)
EndProcedure

Procedure Node_Forward(*node.Node, atLevel = #Null)
	; Returns the inner 2^(level-1) x 2^(level-1) core of this node, forward in
	; time. The number of generations will be 2^(atLevel-2), by calling Node_Forward
	; twice at every level <= atLevel, And once For higher levels. This causes the
	; exponential speedup to start at the specified level, being linear up till
	; that point.
	
	Protected.Node *n00, *n01, *n02, *n10, *n11, *n12, *n20, *n21, *n22
	Protected.Node *nw, *ne, *sw, *se
	
	If (atLevel = #Null) Or (atLevel > *node\level)
		atLevel = *node\level
	EndIf
	
	If *node\next And (*node\nextLevel <> atLevel)
		; Wipe the cache for now.
		*node\next = #Null
		*node\nextLevel = #Null
	EndIf
	
	If *node\next <> #Null
		ProcedureReturn *node\next
	EndIf
	
	If *node\level = 2
		*node\next = CallFunctionFast(Game\ruleProc, *node)
		*node\nextLevel = atLevel
	Else
		*n00 = Node_Forward(*node\n[0], atLevel)
		*n01 = Node_Forward(Node_MergeHorizontal(*node\n[0], *node\n[1]), atLevel)
		*n02 = Node_Forward(*node\n[1], atLevel)
		*n10 = Node_Forward(Node_MergeVertical(*node\n[0], *node\n[2]), atLevel)
		*n11 = Node_Forward(Node_MergeCenter(*node\n[0], *node\n[1], *node\n[2], *node\n[3]), atLevel)
		*n12 = Node_Forward(Node_MergeVertical(*node\n[1], *node\n[3]), atLevel)
		*n20 = Node_Forward(*node\n[2], atLevel)
		*n21 = Node_Forward(Node_MergeHorizontal(*node\n[2], *node\n[3]), atLevel)
		*n22 = Node_Forward(*node\n[3], atLevel)
		
		If atLevel <> *node\level
			; Just merge the result from the lower levels without adding another
			; forward phase - this takes out a factor of two in the recursion and
			; allows us to step some number of generations forward < 2^(level-2).
			*nw = Node_MergeCenter(*n00, *n01, *n10, *n11)
			*ne = Node_MergeCenter(*n01, *n02, *n11, *n12)
			*sw = Node_MergeCenter(*n10, *n11, *n20, *n21)
			*se = Node_MergeCenter(*n11, *n12, *n21, *n22)
		Else
			*nw = Node_Forward(Node_Canonical(*node\level - 1, *n00, *n01, *n10, *n11))
			*ne = Node_Forward(Node_Canonical(*node\level - 1, *n01, *n02, *n11, *n12))
			*sw = Node_Forward(Node_Canonical(*node\level - 1, *n10, *n11, *n20, *n21))
			*se = Node_Forward(Node_Canonical(*node\level - 1, *n11, *n12, *n21, *n22))
		EndIf
		
		*node\next = Node_Canonical(*node\level - 1, *nw, *ne, *sw, *se)
		*node\nextLevel = atLevel
	EndIf
	
	ProcedureReturn *node\next
EndProcedure

Procedure Node_ForwardN(*node.Node, n.q)
	If *node = #Null
		ProcedureReturn 0
	EndIf
	
	; Returns a Node pointer, representing these cells forward n generations.
	; It will automatically expand to be big enough to fit all cells.
	; The most compact node centered at the appropriate location that contains
	; all the cells is returned.
	
	Static atLevel.q
	
	atLevel = 2
	
	While (n > 0)
		If (n & 1)
			While *node\level < (atLevel - 2)
				*node = Node_Expand(*node)
			Wend
			; Expand twice extra To ensure the expanded cells will fit within the
			; center forward one.
			*node = Node_Expand(*node)
			*node = Node_Expand(*node)
			*node = Node_Forward(*node, atLevel)
		EndIf
		
		n >> 1
		atLevel + 1
	Wend
	
	ProcedureReturn Node_Compact(*node)
EndProcedure

Procedure Node_SetPixel(*node.Node, size.q, xp.q, yp.q, state, x.q = 0, y.q = 0)
	If (xp < x - size) Or (xp > x + size) Or (yp < y - size) Or (yp > y + size)
		ProcedureReturn *node
	EndIf
	
	Protected a = *node\n[0]
	Protected b = *node\n[1]
	Protected c = *node\n[2]
	Protected d = *node\n[3]
	
	If (*node\level = 1)
		If (x - 1 = xp) And (y - 1 = yp) : a = state : EndIf
		If (x = xp) And (y - 1 = yp) : b = state : EndIf
		If (x - 1 = xp) And (y = yp) : c = state : EndIf
		If (x = xp) And (y = yp) : d = state : EndIf
	Else
		size >> 1
		a = Node_SetPixel(a, size, xp, yp, state, x - size, y - size)
		b = Node_SetPixel(b, size, xp, yp, state, x + size, y - size)
		c = Node_SetPixel(c, size, xp, yp, state, x - size, y + size)
		d = Node_SetPixel(d, size, xp, yp, state, x + size, y + size)
	EndIf
	
	ProcedureReturn Node_Canonical(*node\level, a, b, c, d)
EndProcedure

Procedure Node_GetPixel(*node.Node, x.q, y.q)
	Protected.q xp, yp, size = Pow2(World\root\level - 1)
	Repeat
		size >> 1
		If y < yp
			If x < xp
				*node = *node\n[0]
				xp - size
			Else
				*node = *node\n[1]
				xp + size
			EndIf
			yp - size
		Else
			If x < xp
				*node = *node\n[2]
				xp - size
			Else
				*node = *node\n[3]
				xp + size
			EndIf
			yp + size
		EndIf
	Until size = 0
	
	ProcedureReturn *node
EndProcedure

Procedure Node_SetPixelState(x, y, state)
	While (World\root\level < #MAXLEVEL) And max(Abs(x), Abs(y)) > Pow2(World\root\level - 1)
		World\root = Node_Expand(World\root)
	Wend
	World\root = Node_SetPixel(World\root, Pow2(World\root\level - 1), x, y, state)
	; 	World_Center(World\root, #True)
EndProcedure

Procedure Node_GarbageCollectionMark(*node.Node)
	If *node = #Null Or *node\usage Or *node\level < 3 : ProcedureReturn #False : EndIf
	*node\usage = 1
	Node_GarbageCollectionMark(*node\n[0])
	Node_GarbageCollectionMark(*node\n[1])
	Node_GarbageCollectionMark(*node\n[3])
	Node_GarbageCollectionMark(*node\n[2])
	Node_GarbageCollectionMark(*node\next)
EndProcedure

Procedure Node_GarbageCollection()
	StartDrawing(CanvasOutput(Game\canvas))
	DrawText(10, OutputHeight() - 50, "Garbage collection", RGB(255, 0, 0), 0)
	StopDrawing()
	
	ForEach Nodes()
		Nodes()\usage = 0
	Next
	
	hashPop = 0
	FillMemory(@ *hashTab(), (ArraySize(*hashTab()) + 1) * SizeOf(Integer))
	
	Node_GarbageCollectionMark(World\root)
	ForEach Nodes()
		If (Node_IsZero(Nodes()) = 0) And (Nodes()\usage = 0) And (Nodes()\level > 2)
			DeleteElement(Nodes())
		Else
			Protected nw = Nodes()\n[0]
			Protected ne = Nodes()\n[1]
			Protected sw = Nodes()\n[2]
			Protected se = Nodes()\n[3]
			Protected h.q = Hash_Key(nw, ne, sw, se) % hashPrime
			Nodes()\hash = *hashTab(h)
			*hashTab(h) = Nodes()
			hashPop + 1
		EndIf
	Next
EndProcedure

Procedure Node_Draw(*node.Node, size.q, x.d, y.d)
	If *node = 0 Or Node_IsZero(*node) : ProcedureReturn : EndIf
	Static s.d, xo.q, yo.q, i
	s = World\view_size * size
	xo = (x - World\view_center\x) * World\view_size
	yo = (y - World\view_center\y) * World\view_size
	If s <= 0.5
		countPixel + 1
		Box(xo, yo, 1, 1, RGB(255, 255, 255))
		ProcedureReturn
	ElseIf (*node\level = 1)
		i = *node\n[0]
		If *node\n[0]: countBox + 1 : Box(xo - s, yo - s, s, s, NodeColor(i)): EndIf
		i = *node\n[1]
		If *node\n[1]: countBox + 1 : Box(xo, yo - s, s, s, NodeColor(i)): EndIf
		i = *node\n[2]
		If *node\n[2]: countBox + 1 : Box(xo - s, yo, s, s, NodeColor(i)): EndIf
		i = *node\n[3]
		If *node\n[3]: countBox + 1 : Box(xo, yo, s, s, NodeColor(i)): EndIf
		ProcedureReturn
	ElseIf (xo + s < 0) Or (xo - s > OutputWidth()) Or (yo + s < 0) Or (yo - s > OutputHeight())
		ProcedureReturn
	EndIf
	size >> 1
	Node_Draw(*node\n[0], size, x - size, y - size)
	Node_Draw(*node\n[1], size, x + size, y - size)
	Node_Draw(*node\n[2], size, x - size, y + size)
	Node_Draw(*node\n[3], size, x + size, y + size)
EndProcedure

Procedure Box2(x.q, y.q, w.q, h.q, col)
	; draw a box that is clipped to the output size
	If x > 0 Or x + w < OutputWidth() Or y > 0 Or y + h < OutputHeight()
		If x < 0 : w + x + 1 : x = -1 : ElseIf x > OutputWidth() : w - (OutputWidth() - x) : x = OutputWidth() + 1 : EndIf
		If y < 0 : h + y + 1 : y = -1 : ElseIf y > OutputHeight() : h - (OutputHeight() - y) : y = OutputHeight() + 1 : EndIf
		w = min(w, OutputWidth())
		h = min(h, OutputHeight())
		Box(x, y, w, h, col)
	EndIf
EndProcedure


Procedure Node_DrawQuadTree(*node.Node, size.q, x.d, y.d)
	Protected s.d = World\view_size * size * 2
	If (s < 8) : ProcedureReturn : EndIf
	Protected xo.q = (x - World\view_center\x - size) * World\view_size
	Protected yo.q = (y - World\view_center\y - size) * World\view_size
	Box2(xo, yo, s, s, RGB(125, 150, 225))
	If (*node\level < 2) Or Node_IsZero(*node) Or (xo + s < 0) Or (xo - s > OutputWidth()) Or (yo + s < 0) Or (yo - s > OutputHeight())
		ProcedureReturn
	EndIf
	size >> 1
	Node_DrawQuadTree(*node\n[0], size, x - size, y - size)
	Node_DrawQuadTree(*node\n[2], size, x - size, y + size)
	Node_DrawQuadTree(*node\n[1], size, x + size, y - size)
	Node_DrawQuadTree(*node\n[3], size, x + size, y + size)
EndProcedure


;- -----------------------------------

; Manages the world of cells, infinite in size.
; 
; Handles drawing, iteration, and tracking various statistics about what is happening.

Procedure World_Iterate(num_generations.q)
	; Updates the state of the current world by n generations.
	
	hashFound = 0
	
	Game\iteration_count + num_generations
	World\root = Node_ForwardN(World\root, num_generations)
	
	If (ListSize(Nodes()) * SizeOf(Node)) > #MAXMEM
		Node_GarbageCollection()
	EndIf
EndProcedure

Procedure World_ShiftView(direction, step_size)
	; Shifts the current view by a number of screen pixels.
	; view_center is in terms of cells, so shift by the corresponding number of
	; cells instead.
	
	Protected cells.q = max(1, step_size / World\view_size)
	If direction = #PB_Shortcut_Up Or direction = #Gadget_Up
		World\view_center\y - cells
	ElseIf direction = #PB_Shortcut_Down Or direction = #Gadget_Down
		World\view_center\y + cells
	ElseIf direction = #PB_Shortcut_Right Or direction = #Gadget_Right
		World\view_center\x + cells
	ElseIf direction = #PB_Shortcut_Left Or direction = #Gadget_Left
		World\view_center\x - cells
	EndIf
EndProcedure

Procedure World_GetBounds(*node.Node, i, *bounds.BOUNDS, size.q, x.q = 0, y.q = 0)
	If Node_IsZero(*node) : ProcedureReturn : EndIf
	
	If *node\level = 1
		If i = 0
			If (*node\n[0] Or *node\n[1]) : *bounds\b[0] = min(*bounds\b[0], y - 1) : EndIf
			If (*node\n[2] Or *node\n[3]) : *bounds\b[0] = min(*bounds\b[0], y) : EndIf
		EndIf
		If i = 1
			If (*node\n[0] Or *node\n[1]) : *bounds\b[1] = max(*bounds\b[1], y - 1) : EndIf
			If (*node\n[2] Or *node\n[3]) : *bounds\b[1] = max(*bounds\b[1], y) : EndIf
		EndIf
		If i = 2
			If (*node\n[0] Or *node\n[2]) : *bounds\b[2] = min(*bounds\b[2], x - 1) : EndIf
			If (*node\n[1] Or *node\n[3]) : *bounds\b[2] = min(*bounds\b[2], x) : EndIf
		EndIf
		If i = 3
			If (*node\n[0] Or *node\n[2]) : *bounds\b[3] = max(*bounds\b[3], x - 1) : EndIf
			If (*node\n[1] Or *node\n[3]) : *bounds\b[3] = max(*bounds\b[3], x) : EndIf
		EndIf
		ProcedureReturn
	ElseIf (i = 0 And y - size >= *bounds\b[0]) Or
	       (i = 1 And y + size <= *bounds\b[1]) Or
	       (i = 2 And x - size >= *bounds\b[2]) Or
	       (i = 3 And x + size <= *bounds\b[3])
		ProcedureReturn
	EndIf
	
	size >> 1
	World_GetBounds(*node\n[0], i, *bounds, size, x - size, y - size)
	World_GetBounds(*node\n[1], i, *bounds, size, x + size, y - size)
	World_GetBounds(*node\n[2], i, *bounds, size, x - size, y + size)
	World_GetBounds(*node\n[3], i, *bounds, size, x + size, y + size)
EndProcedure

Procedure World_Center(*node.Node, dontMove = #False)
	If *node\level < 1
		ProcedureReturn
	EndIf
	
	Protected size.q = Pow2(*node\level - 1)
	SetBounds(World\view_bounds, size, -size, size, -size)
	World_GetBounds(*node, 0, World\view_bounds, size, 0, 0)
	World_GetBounds(*node, 1, World\view_bounds, size, 0, 0)
	World_GetBounds(*node, 2, World\view_bounds, size, 0, 0)
	World_GetBounds(*node, 3, World\view_bounds, size, 0, 0)
	World\view_bounds\b[1] + 1
	World\view_bounds\b[3] + 1
	If dontMove = #False
		Protected width.d = Abs(World\view_bounds\b[3] - World\view_bounds\b[2])
		Protected height.d = Abs(World\view_bounds\b[1] - World\view_bounds\b[0])
		
		If width = 0 Or height = 0
			ProcedureReturn
		EndIf
		
		If (Game\width / width) < (Game\height / height)
			World\view_size = (Game\width - 100) / width
		Else
			World\view_size = (Game\height - 100) / height
		EndIf
		
		World\view_center\x = World\view_bounds\b[2] + (width / 2)
		World\view_center\y = World\view_bounds\b[0] + (height / 2)
	EndIf
EndProcedure

;- -----------------------------------

Procedure Game_SetRule(rule.s)
	FreeArray(NodeColor())
	Global Dim NodeColor(255)
	
	Select UCase(rule)
		Case "GAMEOFLIFE"
			NodeColor(0) = RGB(255, 255, 255)
			NodeColor(1) = RGB(255, 255, 255)
			Game\ruleProc = @Node_Rule_GameOfLife()
		Case "WIREWORLD"
			NodeColor(0) = RGB(128, 128, 128)
			NodeColor(1) = RGB(0, 128, 255)
			NodeColor(2) = RGB(255, 255, 255)
			NodeColor(3) = RGB(255, 128, 0)
			Game\ruleProc = @Node_Rule_WireWorld()
	EndSelect
EndProcedure

Procedure Game_SetSpeed(value)
	Game\delay_per_update = 0
	Game\generations_per_update = 0
	
	Select value
		Case 0 : Game\delay_per_update = 1000
		Case 1 : Game\delay_per_update = 500
		Case 2 : Game\delay_per_update = 250
		Case 3 : Game\delay_per_update = 125
		Case 4 : Game\generations_per_update = 1
		Case 5 : Game\generations_per_update = 2
		Case 6 : Game\generations_per_update = 3
		Case 7 : Game\generations_per_update = 4
		Case 8 : Game\generations_per_update = 5
		Case 9 : Game\generations_per_update = 6
		Case 10 : Game\generations_per_update = 7
	EndSelect
	
	SetGadgetState(#Gadget_Speed, value)
EndProcedure

Procedure Game_Draw()
	If (World\root) = #Null Or (World\root\level < 1)
		ProcedureReturn
	EndIf
	
	StartDrawing(CanvasOutput(#Gadget_Canvas))
	DrawingFont(FontID(0))
	Box(0, 0, OutputWidth(), OutputHeight(), RGB(0, 0, 0))
	countPixel = 0
	countBox = 0
	Node_Draw(World\root, Pow2(World\root\level - 1), (Game\width / 2) / World\view_size, (Game\height / 2) / World\view_size)
	Protected s.d = World\view_size
	If s > 4
		Protected x.d = Mod( - World\view_center\x * s + Game\width / 2, s)
		Protected y.d = Mod( - World\view_center\y * s + Game\height / 2, s)
		While y < OutputHeight()
			LineXY(0, y, OutputWidth(), y, RGB(32, 32, 32))
			y + s
		Wend
		While x < OutputWidth()
			LineXY(x, 0, x, OutputHeight(), RGB(32, 32, 32))
			x + s
		Wend
	EndIf
	
	
	If Game\drawTree
		DrawingMode(#PB_2DDrawing_Outlined)
		Node_DrawQuadTree(World\root, Pow2(World\root\level - 1), (Game\width / 2) / World\view_size, (Game\height / 2) / World\view_size)
		DrawingMode(#PB_2DDrawing_Default)
	EndIf
	
	Protected py = OutputHeight() - 200
	DrawingMode(#PB_2DDrawing_Transparent)
	If Game\ruleProc = @Node_Rule_GameOfLife()
		DrawText(10, py, "Rule:                Game of Life", RGB(128, 128, 0))
	Else
		DrawText(10, py, "Rule:                Wireworld", RGB(128, 128, 0))
	EndIf
	DrawText(10, py + 20, "iteration count :    " + Str(Game\iteration_count), RGB(128, 128, 128))
	If Game\delay_per_update > 0
		DrawText(10, py + 40, "generations/update : " + Str(Game\delay_per_update) + " ms", RGB(128, 128, 128))
	Else
		DrawText(10, py + 40, "generations/update : 8^" + Str(Game\generations_per_update), RGB(128, 128, 128))
	EndIf
	DrawText(10, py + 60, "Hash size :          " + Str((hashPop * SizeOf(Node)) >> 20) + " / " + Str((hashLimit * SizeOf(Node)) >> 20) + " MB", RGB(128, 128, 128))
	DrawText(10, py + 80, "hash usage :         " + Str(hashFound), RGB(128, 128, 128))
	DrawText(10, py + 100, "width / height :     " + Str(World\view_bounds\b[3] - World\view_bounds\b[2]) + " / " + Str(World\view_bounds\b[1] - World\view_bounds\b[0]) + "  level:" + Str(World\root\level), RGB(128, 128, 128))
	DrawText(10, py + 120, "x / y :              " + Str(World\cursor\x) + " / " + Str(World\cursor\y), RGB(128, 128, 128))
	DrawText(10, OutputHeight() - 25, "box: " + Str(countBox) + "  pixel:" + Str(countPixel), RGB(0, 255, 0))
	StopDrawing ()
EndProcedure

Procedure Game_Tick()
	If Game\paused
		ProcedureReturn
	EndIf
	Static time
	time = ElapsedMilliseconds()
	If time > Game\next_update_Time
		World_Iterate(Pow(8, Game\generations_per_update))
		Game\next_update_Time = time + Game\delay_per_update
	EndIf
EndProcedure

Procedure Game_Run()
	Protected event
	
	Repeat
		; Update anything that happens over time.
		
		Game_Tick()
		
		If ElapsedMilliseconds() > Game\redrawTime
			; Re-draw the screen.
			Game_Draw()
			Game\redrawTime = ElapsedMilliseconds() + 125
		EndIf
		
		Repeat
			event = WindowEvent()
			If event = #PB_Event_CloseWindow
				End
			EndIf
		Until event = 0
	ForEver
EndProcedure

Procedure Game_Pause(state)
	Game\paused = state
	If Game\paused
		SetGadgetText(#Gadget_Start, "START")
	Else
		SetGadgetText(#Gadget_Start, "PAUSE")
	EndIf
EndProcedure

Procedure Game_Quit()
	End
EndProcedure

;- -----------------------------------

Procedure ReadRule(line.s)
	Protected ruleFound = #False
	line = LCase(line)
	
	If Left(line, 5) = "#rule"
		line = RemoveString(Mid(line, 6), " ")
		ruleFound = #True
	ElseIf Left(line, 3) = "#r "
		line = RemoveString(Mid(line, 4), " ")
		ruleFound = #True
	ElseIf FindString(line, "rule")
		line = StringField(RemoveString(line, " "), 2, "rule=")
		ruleFound = #True
	EndIf
	
	If ruleFound
		If FindString(line, "b") = 0 And FindString(line, "s") = 0 And FindString(line, "/")
			line = "s" + StringField(line, 1, "/") + "/b" + StringField(line, 2, "/")
		EndIf
		
		FreeArray(Rule())
		Dim Rule(255, 255)
		
		Protected *c.Character = @line, rIndex
		While *c\c
			If *c\c = 'b'
				rIndex = 1
			ElseIf *c\c = 's'
				rIndex = 2
			EndIf
			*c + SizeOf(Character)
			While rIndex And (*c\c >= '0' And *c\c <= '9')
				Rule(Val(Chr(*c\c)), rIndex - 1) = 1
				*c + SizeOf(Character)
			Wend
		Wend
	EndIf
	
	ProcedureReturn ruleFound
EndProcedure

Procedure ParseFileCells(name.s)
	Protected file = ReadFile(#PB_Any, name)
	Protected x.q, y.q
	
	If IsFile(file)
		ReadRule("#R B3/S23")
		Game\description = ""
		
		World\root = Node_Zero(#MAXLEVEL)
		
		While Not Eof(file)
			Protected line.s = Trim(ReadString(file))
			If Left(line, 1) = "!"
				Game\description + line + #CRLF$
			Else
				Protected *c.Character = @line
				While *c\c
					If *c\c = '.'
						x + 1
					ElseIf *c\c = 'O'
						World\root = Node_SetPixel(World\root, Pow2(World\root\level - 1), x, y, 1)
						x + 1
					EndIf
					*c + SizeOf(Character)
				Wend
				y + 1
				x = 0
			EndIf
		Wend
		CloseFile(file)
		World\root = Node_Compact(World\root)
		ProcedureReturn #True
	EndIf
	
	ProcedureReturn #False
EndProcedure

Procedure ParseFileLIF(name.s)
	Protected file = ReadFile(#PB_Any, name)
	Protected.q ofsX, x, y
	
	ReadRule("#R B3/S23")
	
	Game\description = ""
	
	If IsFile(file)
		World\root = Node_Zero(#MAXLEVEL)
		
		Protected line.s = Trim(ReadString(file))
		
		If Trim(LCase(line)) = "#life 1.05"
			While Not Eof(file)
				line.s = Trim(ReadString(file))
				
				If Left(line, 1) = "#"
					Game\description + line + #CRLF$
				EndIf
				
				Protected ruleFound
				If ruleFound = #False
					ruleFound = ReadRule(line)
				EndIf
				
				Protected *c.Character = @line
				
				If line = "" Or *c\c = '!'
					Continue
				ElseIf *c\c = '#'
					If FindString(line, "#P")
						ofsX = Val(StringField(line, 2, " "))
						y = Val(StringField(line, 3, " "))
					EndIf
				Else
					x = ofsX
					While *c\c
						If *c\c = 10 Or *c\c = 13
							Break
						ElseIf *c\c <> '.'
							World\root = Node_SetPixel(World\root, Pow2(World\root\level - 1), x, y, 1)
						EndIf
						x + 1
						*c + SizeOf(Character)
					Wend
					y + 1
				EndIf
			Wend
		ElseIf Trim(LCase(line)) = "#life 1.06"
			While Not Eof(file)
				line.s = Trim(ReadString(file))
				If ruleFound = #False
					ruleFound = ReadRule(line)
				EndIf
				
				If Left(line, 1) = "#" : Continue : EndIf
				x = Val(StringField(line, 1, " "))
				y = -Val(StringField(line, 2, " "))
				World\root = Node_SetPixel(World\root, Pow2(World\root\level - 1), x, y, 1)
			Wend
		EndIf
		CloseFile(file)
		World\root = Node_Compact(World\root)
		ProcedureReturn #True
	EndIf
	ProcedureReturn #False
EndProcedure

Procedure ParseFileRLE(name.s)
	Protected file = ReadFile(#PB_Any, name)
	Protected.q x, y, i, count
	Protected l.s
	
	If IsFile(file)
		ReadRule("#R B3/S23")
		Game\description = ""
		World\root = Node_Zero(#MAXLEVEL)
		
		While Not Eof(file)
			Protected line.s = Trim(ReadString(file))
			Protected *c.Character = @line
			If Left(line, 1) = "#"
				Game\description + line + #CRLF$
			EndIf
			If *c\c = '#' : Continue : EndIf
			
			Protected ruleFound
			If ruleFound = #False
				ruleFound = ReadRule(line)
			EndIf
			
			If *c\c = 'x' : Continue : EndIf
			
			While *c\c
				l = ""
				While (*c\c >= '0' And *c\c <= '9')
					l + Chr(*c\c)
					*c + SizeOf(Character)
				Wend
				count = max(1, Val(l))
				Select *c\c
					Case '!'
						Break
					Case 'b'
						x + count
					Case '$'
						y + count
						x = 0
					Case 'o'
						For i = 1 To count
							World\root = Node_SetPixel(World\root, Pow2(World\root\level - 1), x, y, 1)
							x + 1
						Next
				EndSelect
				*c + SizeOf(Character)
			Wend
		Wend
		CloseFile(file)
		World\root = Node_Compact(World\root)
		ProcedureReturn #True
	EndIf
	ProcedureReturn #False
EndProcedure

Procedure ParseFileMCL(name.s)
	Protected file = ReadFile(#PB_Any, name)
	Protected.q x, y, i, count
	Protected l.s
	
	If IsFile(file)
		ReadRule("#R B3/S23")
		Game\description = ""
		
		World\root = Node_Zero(#MAXLEVEL)
		While Not Eof(file)
			Protected line.s = Trim(ReadString(file))
			If Left(line, 1) = "#"
				Game\description + line + #CRLF$
				
				If FindString(UCase(line), "WIREWORLD")
					Game_SetRule("WIREWORLD")
				EndIf
			EndIf
			
			Protected ruleFound
			If ruleFound = #False
				ruleFound = ReadRule(line)
			EndIf
			
			If Left(Line, 2) <> "#L"
				Continue
			EndIf
			
			line = Mid(line, 4)
			
			Protected *c.Character = @line
			While *c\c
				l = ""
				While (*c\c >= '0' And *c\c <= '9')
					l + Chr(*c\c)
					*c + SizeOf(Character)
				Wend
				count = max(1, Val(l))
				Select *c\c
					Case '.'
						x + count
					Case '$'
						y + count
						x = 0
					Case 'A' To 'X'
						i = 1
						While i <= count
							World\root = Node_SetPixel(World\root, Pow2(World\root\level - 1), x, y, *c\c - 'A' + 1)
							x + 1
							i + 1
						Wend
				EndSelect
				*c + SizeOf(Character)
			Wend
			
		Wend
		CloseFile(file)
		World\root = Node_Compact(World\root)
		ProcedureReturn #True
	EndIf
	ProcedureReturn #False
EndProcedure

Procedure ParseFileMC(name.s)
	Protected file = ReadFile(#PB_Any, name)
	Protected.q x, y, l, n
	Dim *pts(32000)
	
	If IsFile(file)
		ReadRule("#R B3/S23")
		Game\description = ""
		
		Dim pts(7, 7)
		
		While Not Eof(file)
			Protected line.s = Trim(ReadString(file))
			
			If Left(line, 1) = "#"
				Game\description + line + #CRLF$
				
				If FindString(UCase(line), "WIREWORLD")
					Game_SetRule("WIREWORLD")
				EndIf
			EndIf
			
			Protected *c.Character = @line
			If *c\c = '[' Or *c\c = '#'
				Continue
			EndIf
			
			If *c\c = '$' Or *c\c = '*' Or *c\c = '.'
				FreeArray(pts())
				Dim pts(7, 7)
				x = 0
				y = 0
				
				While *c\c
					If *c\c = '$'
						y + 1
						x = 0
					Else
						pts(x, y) = Bool(*c\c = '*')
						x + 1
					EndIf
					*c + SizeOf(Character)
				Wend
				
				;    0  1  2  3
				;    01 23 45 67			
				;0 0 **|**|**|**
				;  1 **|**|**|**
				;    --+--+--+--			
				;1 2 **|**|**|**
				;  3 **|**|**|**
				;    --+--+--+--
				;2 4 **|**|**|**
				;  5 **|**|**|**
				;    --+--+--+--
				;4 6 **|**|**|**
				;  7 **|**|**|**	
				
				Protected lv, *n1, *n2, *n3, *n4
				Dim *n(3, 3)
				y = 0
				While y < 4
					x = 0
					While x < 4
						*n(x, y) = Node_Canonical(1, pts(x * 2, y * 2), pts(x * 2 + 1, y * 2), pts(x * 2, y * 2 + 1), pts(x * 2 + 1, y * 2 + 1))
						x + 1
					Wend
					y + 1
				Wend
				
				lv = 3
				*n1 = Node_Canonical(2, *n(0, 0), *n(1, 0), *n(0, 1), *n(1, 1))
				*n2 = Node_Canonical(2, *n(2, 0), *n(3, 0), *n(2, 1), *n(3, 1))
				*n3 = Node_Canonical(2, *n(0, 2), *n(1, 2), *n(0, 3), *n(1, 3))
				*n4 = Node_Canonical(2, *n(2, 2), *n(3, 2), *n(2, 3), *n(3, 3))
			Else
				lv = Val(StringField(line, 1, " "))
				*n1 = Val(StringField(line, 2, " "))
				*n2 = Val(StringField(line, 3, " "))
				*n3 = Val(StringField(line, 4, " "))
				*n4 = Val(StringField(line, 5, " "))
				
				If lv > 1
					*n1 = *pts(*n1)
					*n2 = *pts(*n2)
					*n3 = *pts(*n3)
					*n4 = *pts(*n4)
					If *n1 = 0 : *n1 = Node_Zero(lv - 1): EndIf
					If *n2 = 0 : *n2 = Node_Zero(lv - 1): EndIf
					If *n3 = 0 : *n3 = Node_Zero(lv - 1): EndIf
					If *n4 = 0 : *n4 = Node_Zero(lv - 1): EndIf
				EndIf
			EndIf
			
			n + 1
			If n > ArraySize(*pts())
				ReDim *pts(ArraySize(*pts()) + 1000)
			EndIf
			*pts(n) = Node_Canonical(lv, *n1, *n2, *n3, *n4)
			
			World\root = *pts(n)
			
		Wend
		CloseFile(file)
		ProcedureReturn #True
	EndIf
	ProcedureReturn #False
EndProcedure

Procedure ParseFile(name.s)
	Protected res
	
	Select LCase(GetExtensionPart(name))
		Case "cells"
			res = ParseFileCells(name)
		Case "lif", "life"
			res = ParseFileLIF(name)
		Case "mc"
			res = ParseFileMC(name)
		Case "rle"
			res = ParseFileRLE(name)
		Case "mcl"
			res = ParseFileMCL(name)
	EndSelect
	
	If res
		SetWindowTitle(0, GetFilePart(name))
		ProcedureReturn res
	EndIf
	
	ProcedureReturn #False
EndProcedure

Procedure Game_OpenFile(path.s = "", filename.s = "", speed = 2)
	If path And filename
		If ReceiveHTTPFile(path, filename) = 0
			MessageRequester("", "failed to download file: " + #CRLF$ + path)
		EndIf
	ElseIf FileSize(filename) < 0
		ProcedureReturn
	EndIf
	
	ClearList(Nodes())

	Game\width = GadgetWidth(#Gadget_Canvas)
	Game\height = GadgetHeight(#Gadget_Canvas)
	Game\iteration_count = 0
	Game\world = @World
		
	Game_Pause(1)
	Game_SetSpeed(speed)
	Game_SetRule("GAMEOFLIFE")
	
	hashPop = 0
	hashPrime = Hash_NextPrime(1000)
	hashLimit = hashPrime
	FreeArray(*hashTab())
	Dim *hashTab(hashPrime)
	
	Define l
	Pow2(0) = Pow(2, 0)
	For l = 1 To #MAXLEVEL
		Pow2(l) = Pow(2, l)
		*zero(l) = Node_Canonical(l, *zero(l - 1), *zero(l - 1), *zero(l - 1), *zero(l - 1))
	Next
	
	If ParseFile(filename) = 0
		ReadRule("#R B3/S23")
		World\root = Node_Zero(#MAXLEVEL)
		Protected x, y
		Restore xypoints
		Repeat
			Read.i x
			Read.i y
			If x = -999
				Break
			Else
				World\root = Node_SetPixel(World\root, Pow2(World\root\level - 1), x, y, 1)
			EndIf
		ForEver
	EndIf
	
	; Initialize the world. xypoints is a list of coordinates in the world
	; that should be set to true, as (x,y) tuples.
	
	If World\root
		World\root = Node_Compact(World\root)
		World_Center(World\root)
	EndIf
	
	DataSection
		xypoints:
		Data.i - 2, -2, -2, -1, -2, 2, -1, -2, -1, 1, 0, -2, 0, 1, 0, 2, 1, 0, 2, -2, 2, 0, 2, 1, 2, 2
		Data.i - 999, -999
	EndDataSection
EndProcedure

Procedure Event_Gadget()
	Select EventGadget()
		Case #Gadget_Start
			If Game\paused
				Game_Pause(0)
			Else
				Game_Pause(1)
			EndIf
		Case #Gadget_Step
			Game_Pause(1)
			World_Iterate(1)
			Game_Draw()
		Case #Gadget_Speed
			Game_SetSpeed(GetGadgetState(#Gadget_Speed))
		Case #Gadget_Center
			World_Center(World\root)
		Case #Gadget_Left, #Gadget_Right, #Gadget_Up, #Gadget_Down
			World_ShiftView(EventGadget(), 50)
			
		Case #Gadget_Canvas
			Protected mx = GetGadgetAttribute(#Gadget_Canvas, #PB_Canvas_MouseX)
			Protected my = GetGadgetAttribute(#Gadget_Canvas, #PB_Canvas_MouseY)
			Protected scale.d = 0.0
			
			World\cursor\x = ((mx - (Game\width + World\view_size) * 0.5) / World\view_size) + World\view_center\x
			World\cursor\y = ((my - (Game\height + World\view_size) * 0.5) / World\view_size) + World\view_center\y
			
			If EventType() = #PB_EventType_MouseWheel
				
				If GetGadgetAttribute(#Gadget_Canvas, #PB_Canvas_WheelDelta) > 0
					scale = 1 / 0.75
				Else
					scale = 1 * 0.75
				EndIf
				If World\view_size * scale < 256
					World\view_size * scale
				Else
					scale = 0
				EndIf
				
			ElseIf EventType() = #PB_EventType_KeyDown
				
				Protected key = GetGadgetAttribute(#Gadget_Canvas, #PB_Canvas_Key)
				Select key
					Case #PB_Shortcut_F
						World_Center(World\root)
					Case #PB_Shortcut_G
						Node_GarbageCollection()
					Case #PB_Shortcut_1
						Node_SetPixelState(World\cursor\x, World\cursor\y, 0)
					Case #PB_Shortcut_2
						Node_SetPixelState(World\cursor\x, World\cursor\y, 1)
					Case #PB_Shortcut_3
						Node_SetPixelState(World\cursor\x, World\cursor\y, 2)
					Case #PB_Shortcut_4
						Node_SetPixelState(World\cursor\x, World\cursor\y, 3)
					Case #PB_Shortcut_Down, #PB_Shortcut_Up, #PB_Shortcut_Left, #PB_Shortcut_Right
						World_ShiftView(key, 50)
					Case #PB_Shortcut_PageDown, #PB_Shortcut_A
						; Zoom in.
						scale = 1 / 0.75
						If World\view_size * scale < 256
							World\view_size * scale
						Else
							scale = 0
						EndIf
					Case #PB_Shortcut_PageUp, #PB_Shortcut_Y
						; Zoom out.
						scale = 1 * 0.75
						If World\view_size * scale > 0
							World\view_size * scale
						Else
							scale = 0
						EndIf
				EndSelect
				
				Game\redrawTime = 0
			EndIf
			
			If scale
				mx - Game\width / 2
				my - Game\height / 2
				World\view_center\x - ((mx - mx * scale) / World\view_size)
				World\view_center\y - ((my - my * scale) / World\view_size)
				Game\redrawTime = 0
			EndIf
			
	EndSelect
	
	If EventGadget() <> #Gadget_Canvas
		SetActiveGadget(#Gadget_Canvas)
	EndIf
EndProcedure

Procedure Event_Menu()
	Select EventMenu()
		Case #Menu_Open
			Game_OpenFile("", OpenFileRequester("", #PB_Compiler_FilePath, "Cell-Files|*.cells;*.lif;*.life;*.mc;*.rle;*.mcl", 0))
		Case #Menu_Quit
			Game_Quit()
		Case #Menu_RuleGameOfLife
			Game_SetRule("GAMEOFLIFE")
			SetMenuItemState(0, #Menu_RuleGameOfLife, 1)
			SetMenuItemState(0, #Menu_RuleWireWorld, 0)
		Case #Menu_RuleWireWorld
			Game_SetRule("WIREWORLD")
			SetMenuItemState(0, #Menu_RuleGameOfLife, 0)
			SetMenuItemState(0, #Menu_RuleWireWorld, 1)
		Case #Menu_ShowQuadTree
			If GetMenuItemState(0, #Menu_ShowQuadTree) = 0
				SetMenuItemState(0, #Menu_ShowQuadTree, 1)
			Else
				SetMenuItemState(0, #Menu_ShowQuadTree, 0)
			EndIf
			Game\drawTree = GetMenuItemState(0, #Menu_ShowQuadTree)
		Case #Menu_ShowDescription
			If game\description
				MessageRequester("Description", game\description)
			Else
				MessageRequester("Description", "No Description")
			EndIf
	EndSelect
	
	SetActiveGadget(#Gadget_Canvas)
EndProcedure

OpenWindow(0, 0, 0, 800, 600, "HashLife", #PB_Window_SystemMenu | #PB_Window_MaximizeGadget | #PB_Window_Maximize)
CreateMenu(0, WindowID(0))
MenuTitle("File")
MenuItem(#Menu_Open, "Open")
MenuItem(#Menu_Quit, "Quit")
MenuTitle("Options")
OpenSubMenu("Rule")
MenuItem(#Menu_RuleGameOfLife, "Game of life")
MenuItem(#Menu_RuleWireWorld, "Wireworld")
CloseSubMenu()
MenuItem(#Menu_ShowQuadTree, "Draw QuadTree")
MenuItem(#Menu_ShowDescription, "Show Description")
SetMenuItemState(0, #Menu_RuleGameOfLife, 1)
SetMenuItemState(0, #Menu_ShowQuadTree, 0)
CanvasGadget(#Gadget_Canvas, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 40, #PB_Canvas_Keyboard | #PB_Canvas_Border | #PB_Canvas_Container)
TrackBarGadget(#Gadget_Speed, 10, 10, 100, 25, 0, 10, #PB_TrackBar_Ticks)
ButtonGadget(#Gadget_Step, 10, 40, 100, 25, "Step")
ButtonGadget(#Gadget_Start, 10, 70, 100, 35, "")
ButtonGadget(#Gadget_Up, 50, 120, 20, 20, "↑")
ButtonGadget(#Gadget_Center, 35, 140, 50, 50, "Center")
ButtonGadget(#Gadget_Down, 50, 190, 20, 20, "↓")
ButtonGadget(#Gadget_Left, 15, 155, 20, 20, "←")
ButtonGadget(#Gadget_Right, 85, 155, 20, 20, "→")
CloseGadgetList()
SetActiveGadget(#Gadget_Canvas)

BindGadgetEvent(#Gadget_Start, @Event_Gadget())
BindGadgetEvent(#Gadget_Step, @Event_Gadget())
BindGadgetEvent(#Gadget_Canvas, @Event_Gadget())
BindGadgetEvent(#Gadget_Speed, @Event_Gadget())
BindGadgetEvent(#Gadget_Center, @Event_Gadget())
BindGadgetEvent(#Gadget_Left, @Event_Gadget())
BindGadgetEvent(#Gadget_Right, @Event_Gadget())
BindGadgetEvent(#Gadget_Up, @Event_Gadget())
BindGadgetEvent(#Gadget_Down, @Event_Gadget())

BindMenuEvent(0, #Menu_Open, @Event_Menu())
BindMenuEvent(0, #Menu_Quit, @Event_Menu())
BindMenuEvent(0, #Menu_RuleGameOfLife, @Event_Menu())
BindMenuEvent(0, #Menu_RuleWireWorld, @Event_Menu())
BindMenuEvent(0, #Menu_ShowQuadTree, @Event_Menu())
BindMenuEvent(0, #Menu_ShowDescription, @Event_Menu())

Game_OpenFile()
Game_OpenFile("https://github.com/ngmsoftware/hashlife/raw/master/metapixel-galaxy.mc", GetTemporaryDirectory() + "metapixel-galaxy.mc" , 5)
;Game_OpenFile("https://github.com/jimblandy/golly/raw/master/src/Patterns/WireWorld/primes.mc", GetTemporaryDirectory() + "primes.mc", 5)

Game_Run()
benubi
Enthusiast
Enthusiast
Posts: 220
Joined: Tue Mar 29, 2005 4:01 pm

Re: Conway's Game of Life

Post by benubi »

Based on the code of the opening thread I wrote a colored version with a few gimmicks. I fear to have spent too much time with it :lol:
You can get sometimes funny result when playing with the "new" constants, especially when you change to a higher reach - but this is more intense calculations...

On random generation the pixels are pure one channel pixels i.e. only red or green or blue, but not mixed. This happens later when they reproduce. Because of color entropy when mixing, they tend to become darker or gray. To prevent them from turning too dark there's a minimum color value (which I found to work best with values between 32-96), from which point the color pixels get "normalized" (scaled up to have at least one r g b channel with a value of 255)- or they get always normalized every turn which makes them look very bright.

Space: generate new random field
F1: Statistics fn/off
F2: Always normalize colors on/off
PageUp/PageDown: Change updates per second
KeyPad 8/2: Change motion blur
KeyPad +/-: Change reach
Pause: Pause life
F12: load seed image (open file requester)

When a loop is detected a statistics "window" will popup and display the update number of the loop start.

I found on my monitor the sizes 512x256 and 256x128 to be most beautiful and efficient, but you can use any resolution size based on power of 2 with the current version. 1024x512 is already too slow for my PC to be work at 60/120 FPS, and you can't see well what is on the screen. There's also the possibility to load a seed image, which should also only use pure black,red,green,blue if you want more beautiful gradients (I tried with things with geometric patterns done in mspaint).

Edit: I forgot to mention you can use the mouse to click life.

Code: Select all

EnableExplicit


#LIFE_WIDTH  = 512
#LIFE_HEIGHT = #LIFE_WIDTH / 2

#LIFE_WMASK       = #LIFE_WIDTH - 1
#LIFE_HMASK       = #LIFE_HEIGHT - 1
#MAX_FPS          = 120
#MIN_COLOR        = 48
#MIN_POPULATION   = 2 ; 2 ; default 2
#REP_POPULATION   = 3 ; 3 ; default 3
#MAX_POPULATION   = 3 ; 3 ; default 3
#MAX_REACH        = 6  ; 1 ; default 1
#MAX_FADESPRITES  = 32
#MAX_MD5_SIZE     = 50
#MAX_FADE_OPACITY = 128

Structure golrgba
  r.a
  g.a
  b.a
  a.a
EndStructure
Structure point2i
  x.i
  y.i
EndStructure

Global Dim LifeArray.golrgba(1, #LIFE_WIDTH, #LIFE_HEIGHT)

Global Dim Reach.point2i(9)
Global ReachCount, ReachMax, t_reach

NewList OSprite.i()
NewList MD5List.s()

Define.i x, y, i, col, frame, oldframe, Event, FPS
Define r, g, b, pcount, max, ShowStats, NormalizeColors, em_last, em_start, next_flip, t_maxfades
Define avg_fps, ldate, t_fps, paused
Define tt_life, tt_red, tt_blue, tt_green, tt_frame, d, z, StabilityFrame,tt_updates
Define *DrawBuff, pitch, format, pixbytes, *PIX.golrgba, upside_down, height, *O.golrgba, *F.golrgba, width, *T.golrgba, *R.point2i, img_fps, timg_fps

UseGIFImageDecoder()
UseTGAImageDecoder()
UsePNGImageDecoder()
UseJPEGImageDecoder()
UseJPEG2000ImageDecoder()
UseTIFFImageDecoder()

UseMD5Fingerprint()


Procedure MakeReach(ReachW, ReachH)
  Protected total_dots = (1 + (ReachW * 2)) * (1 + (ReachH * 2))
  Protected x, y, i
  ReDim Reach(total_dots)
  ReachCount = total_dots - 1
  ReachMax   = total_dots - 2
  Debug "total dots " + total_dots
  For y = - ReachH To ReachH
    For x = - ReachW To ReachW
      If x Or y
        Reach(i)\x = x
        Reach(i)\y = y
        i + 1
      EndIf
    Next
  Next
EndProcedure

Procedure RandomLife()
  
  Protected.i x, y
  
  Protected rmax = t_reach
  Debug rmax
  If rmax > 1
    rmax = (rmax - 1) * 3
  EndIf
  
  
  For y = 0 To #LIFE_HMASK
    For x = 0 To #LIFE_WMASK
      LifeArray(0, x, y)\r = 0
      LifeArray(0, x, y)\g = 0
      LifeArray(0, x, y)\b = 0
      
      LifeArray(0, x, y)\a = Bool(Random(rmax) = 1)
      If LifeArray(0, x, y)\a
        
        
        
        Select Random(2)
          Case 0
            LifeArray(0, x, y)\r = 255
          Case 1
            LifeArray(0, x, y)\g = 255
          Case 2
            LifeArray(0, x, y)\b = 255
          Case 3
            LifeArray(0, x, y)\r = Random(255)
            LifeArray(0, x, y)\g = Random(255)
            LifeArray(0, x, y)\b = Random(255)
        EndSelect
        LifeArray(1, x, y)\r = LifeArray(0, x, y)\r
        LifeArray(1, x, y)\g = LifeArray(0, x, y)\g
        LifeArray(1, x, y)\b = LifeArray(0, x, y)\b
      EndIf
      LifeArray(1, x, y)\r = LifeArray(0, x, y)\r
      LifeArray(1, x, y)\g = LifeArray(0, x, y)\g
      LifeArray(1, x, y)\b = LifeArray(0, x, y)\b
    Next x
  Next y
  
EndProcedure

Procedure LoadMapImage(targetSprite, filename$, rnd=-1)
  Shared oldframe, frame, LifeArray()
  Protected rmax = t_reach
  Protected x, y, col
  RandomSeed(rnd)
  Debug rmax
  If rmax > 1
    rmax = (rmax - 1) * 3
  EndIf
  oldframe = 1
  frame    = 0
  If IsSprite(targetSprite)
    Debug "Sprite OK"
    If LoadImage(1, filename$)
      Debug "Image loaded"
      If StartDrawing(SpriteOutput(targetSprite))
        DrawImage(ImageID(1), 0, 0, OutputWidth(), OutputHeight())
        Debug "draw image to sprite"
        For y = 0 To OutputHeight() - 1
          For x = 0 To OutputWidth() - 1
            col                  = Point(x, y)
            If rnd<>-1
              LifeArray(0, x, y)\a = Bool(Random(rmax) = 1)
            Else 
              LifeArray(0, x, y)\a = Bool(col)
            EndIf 
            LifeArray(0, x, y)\r = Red(col)
            LifeArray(0, x, y)\g = Green(col)
            LifeArray(0, x, y)\b = Blue(col)
;             LifeArray(1, x, y)\a = LifeArray(1, x, y)\a
;             LifeArray(1, x, y)\r = Red(col)
;             LifeArray(1, x, y)\g = Green(col)
;             LifeArray(1, x, y)\b = Blue(col)
          Next
        Next
        
        StopDrawing()
      Else 
        Debug "StartDrawing failed"
      EndIf
      
      FreeImage(1)
      ProcedureReturn #True
    Else 
      Debug "Load image failed"
    EndIf
  Else 
    Debug "Not a sprite"
  EndIf
  ProcedureReturn #False
EndProcedure




InitMouse()
InitKeyboard()
InitSprite()
ExamineDesktops()
;OpenWindow(0, 0, 0, DesktopWidth(0), DesktopHeight(0), "Life", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered | #PB_Window_MaximizeGadget | #PB_Window_Maximize)
OpenWindow(0, 0, 0, DesktopWidth(0), DesktopHeight(0), "Life", #PB_Window_BorderLess)
;OpenScreen(DesktopWidth(0),DesktopHeight(0),DesktopDepth(0),"Life")
OpenWindowedScreen(WindowID(0), 0, 0, DesktopWidth(0), DesktopHeight(0), 1, 0, 0, #PB_Screen_NoSynchronization)
For i = 1 To #MAX_FADESPRITES Step 1
  AddElement(OSprite())
  OSprite() = CreateSprite( - 1, #LIFE_WIDTH, #LIFE_HEIGHT)
  ZoomSprite(OSprite(), ScreenWidth(), ScreenHeight())
Next

CreateSprite(0, 16, 16)
If StartDrawing(SpriteOutput(0))
  DrawingMode(#PB_2DDrawing_Outlined)
  Circle(8, 8, 3, $FFFFFF)
  StopDrawing()
EndIf

CreateSprite(1, 280, 216, #PB_Sprite_AlphaBlending) ; info
ZoomSprite(1, ScreenWidth() * 0.8, ScreenHeight() * 0.8)

col     = RGB(110, 240, 255)
t_reach = 1
MakeReach(t_reach, t_reach)
FirstElement(OSprite())
If LoadMapImage( OSprite(), GetPathPart(ProgramFilename())+"Unbenannt.png") = #False
  randomlife()
EndIf
t_maxfades = 0; #MAX_FADESPRITES / 2


FPS = 120

Repeat
  
  Repeat
    Event = WindowEvent()
    
  Until Event = 0 Or Event = #PB_Event_CloseWindow
  FlipBuffers()
  Delay(0)
  
  If ldate <> Date()
    avg_fps  = t_fps
    ldate    = Date()
    t_fps    = 1
    img_fps  = timg_fps
    timg_fps = 0
  Else
    t_fps = t_fps + 1
  EndIf
  
  If WindowMouseX(0) <= 2 Or WindowMouseX(0) >= WindowWidth(0) - 3 Or WindowMouseY(0) <= 2 Or WindowMouseY(0) >= WindowHeight(0) - 3
    ReleaseMouse(1)
  Else
    ReleaseMouse(0)
  EndIf
  
  
  If ElapsedMilliseconds() >= next_flip And Not paused 
    next_flip = ElapsedMilliseconds() + (1000 / FPS) 
    LastElement(OSprite())
    MoveElement(OSprite(), #PB_List_First)
    
    
    If StartDrawing(SpriteOutput(OSprite()))
      
      oldframe  = frame
      frame     = 1 - frame
   
      
      em_last = ElapsedMilliseconds()
      tt_frame + 1
      tt_updates = tt_updates + 1
      Box(0, 0, #LIFE_WIDTH, #LIFE_HEIGHT, 0)
      
      *DrawBuff   = DrawingBuffer()
      pitch       = DrawingBufferPitch()
      format      = DrawingBufferPixelFormat()
      pixbytes    = OutputDepth() / 8
      height      = OutputHeight()
      width       = OutputWidth()
      upside_down = format & #PB_PixelFormat_ReversedY
      
      tt_life  = 0
      tt_red   = 0
      tt_green = 0
      tt_blue  = 0
      
      For y = 0 To #LIFE_HMASK
        If upside_down
          *PIX = *DrawBuff + ((height - (y + 1)) * pitch)
        Else
          *PIX = *DrawBuff + ( y * pitch)
        EndIf
        For x = 0 To #LIFE_WMASK
          
          pcount = 0
          r      = 0
          g      = 0
          b      = 0
          max    = 0
          
          Macro chkColor(_frame, _X_, _Y_)
            *T = LifeArray(_frame, (_x_) & #LIFE_WMASK, (_Y_) & #LIFE_HMASK)
            If *T\a
              r      = r + *T\r
              g      = g + *T\g
              b      = b + *T\b
              pcount = pcount + 1
            EndIf
          EndMacro
          Macro IsSet(_frame, _X_ , _Y_)
            Bool(LifeArray(_frame, (_X_) & #LIFE_WMASK, (_Y_) & #LIFE_HMASK)\a) : chkColor(frame, _X_, _Y_)
          EndMacro
          
          
          i  = 0
          *R = @Reach(0)
          For z = 0 To ReachMax
            
            i  = i + IsSet(oldframe, (x + *R\x ) , (y + *R\y))
            *R = *R + SizeOf(point2i)
          Next
          
          Define upc.f, upn.f, max
          
          *O = @LifeArray(oldframe, x , y)
          
          If *O \a
            pcount + 1
            r = r + *O \r
            g = g + *O \g
            b = b + *O \b
          EndIf
          
          
          If pcount > 1
            upc = 1.0 / pcount
            r   = r * upc
            g   = g * upc
            b   = b * upc
          Else
            col = 0
          EndIf
          
          max = r
          If g > max
            max = g
          EndIf
          If b > max
            max = b
          EndIf
          If NormalizeColors Or (max > 0 And max <= #MIN_COLOR)
            If max And max <> 255
              upn = 255.0 / max
              r * upn
              g * upn
              b * upn
            EndIf
          EndIf
          
          
          *F = @LifeArray(frame, x, y)
          
          If i >= #MIN_POPULATION And *O\a = 1 And i < #REP_POPULATION
            
            If *F\a = 1
              
              r = *F\r
              g = *F\g
              b = *F\b
            Else
              *F\a = 1
              *F\r = r
              *F\g = g
              *F\b = b
            EndIf
            *PIX\r = r
            *PIX\g = g
            *PIX\b = b
            
            tt_life + 1
            tt_red + r
            tt_green + g
            tt_blue + b
            
          ElseIf i <= #MAX_POPULATION And i >= #REP_POPULATION
            
            *F \a = 1
            *F \r = r
            *F \g = g
            *F \b = b
            
            *PIX\r = r
            *PIX\g = g
            *PIX\b = b
            
            tt_life + 1
            tt_red + r
            tt_green + g
            tt_blue + b
            
          Else
            
            
            *F \a = 0
            *F\r  = *O\r
            *F\g  = *O\g
            *F\b  = *O\b

          EndIf
          *PIX = *PIX + pixbytes
        Next
        
        
      Next
      
      
      Define rx, ry
      
      
      
      timg_fps = timg_fps + 1
      
      StopDrawing()
      FirstElement(MD5List())
      InsertElement(MD5List())
      MD5List() = Fingerprint(@LifeArray(frame, 0, 0), @LifeArray(0 , #LIFE_WIDTH, #LIFE_HEIGHT) - @LifeArray(0, 0, 0), #PB_Cipher_MD5)
      
      While ListSize(MD5List()) > #MAX_MD5_SIZE
        LastElement(MD5List())
        DeleteElement(MD5List())
      Wend
      
      Define tmd5.s
      If StabilityFrame = 0
        If FirstElement(MD5List())
          tmd5 = MD5List()
          While NextElement(MD5List())
            If MD5List() = tmd5
              StabilityFrame = tt_updates - ListIndex(MD5List())
              ShowStats = 1
              Break
            EndIf
          Wend
        EndIf
        
      EndIf
      
    EndIf
  EndIf
  
  
  
  
  ClearScreen(0)
  Define Opacity, of.f
  Opacity = 255
  If t_maxfades > 1
    of = #MAX_FADE_OPACITY / (t_maxfades - 1)
  Else
    of = 255
  EndIf
  
  Opacity = 255
  ForEach OSprite()
    If ListIndex(OSprite()) >= t_maxfades
      Break
    EndIf
    If Opacity = 255
      DisplaySprite(OSprite(), 0, 0)
      Opacity = #MAX_FADE_OPACITY
    ElseIf Opacity > 0
      DisplayTransparentSprite(OSprite(), 0, 0, Opacity)
      Opacity - (of * ListIndex(OSprite()))
    EndIf
    
    ;Opacity = #MAX_FADE_OPACITY - (#MAX_FADE_OPACITY * ((1+ListIndex(OSprite()))/(t_maxfades+1)))
  Next
  
  
  FirstElement(OSprite())
  DisplayTransparentSprite(OSprite(), 0, 0)
  
  
  SpriteQuality(#PB_Sprite_NoFiltering)
  
  
  ExamineMouse()
  
  
  rx = WindowMouseX(0)
  ry = WindowMouseY(0)
  
  If rx >= 0 And ry >= 0
    rx = rx * ScreenWidth() / WindowWidth(0)
    ry = ry * ScreenHeight() / WindowHeight(0)
  EndIf
  
  DisplayTransparentSprite(0, rx, ry)
  
  If MouseButton(1)
    rx = WindowMouseX(0)
    ry = WindowMouseY(0)
    rx = rx * #LIFE_WIDTH / WindowWidth(0)
    ry = ry * #LIFE_HEIGHT / WindowHeight(0)
    
    If rx >= 0 And ry >= 0
      
      LifeArray(frame, rx, ry)\a = 1
      If LifeArray(frame, rx, ry)\a
           ; Plot(rx, ry, col)
        LifeArray(frame, rx, ry)\r = Red(col)
        LifeArray(frame, rx, ry)\g = Green(col)
        LifeArray(frame, rx, ry)\b = Blue(col)
      EndIf
    EndIf
    
  EndIf
  
  
  If ShowStats Or paused
    
    
    If StartDrawing(SpriteOutput(1))
      
      DrawingMode(#PB_2DDrawing_AllChannels)
      Box(0, 0, OutputWidth(), OutputHeight(), $0)
      Define M = TextHeight("M")
      Define txt$,txtcolor 
      txtcolor = $FFFFFF
      If paused = 0
        If StabilityFrame
          txtcolor = ElapsedMilliseconds() % 512
          If txtcolor > 255
            txtcolor = 512 - txtcolor
          EndIf 
          If txtcolor<#MIN_COLOR
            txtcolor=#MIN_COLOR
          EndIf 
          txtcolor = RGB(txtcolor,0,0)
          
          txt$ = "(F1) Loop detected @ frame #" + FormatNumber(StabilityFrame, 0)
        Else
          txt$ = "(F1)  S t a t i s t i c s"
        EndIf
      Else
        txt$ = "P a u s e d"
      EndIf
      ; window
      RoundBox(0, 0, OutputWidth(), OutputHeight(), M, M, $C0040202)
      DrawingMode(#PB_2DDrawing_Outlined | #PB_2DDrawing_AllChannels)
      RoundBox(0, 0, OutputWidth(), OutputHeight(), M, M, $A0807060)
      RoundBox(1, 1, OutputWidth() - 2, OutputHeight() - 2, M, M, $A0FFEECC)
      RoundBox(2, 2, OutputWidth() - 4, OutputHeight() - 4, M, M, $A0807060)
      DrawingMode(1)
      DrawingFont( - 1)
      y = M + 12
 
      ; title + bar
      DrawText((OutputWidth() / 2) - (TextWidth(txt$) / 2), 4, txt$, txtcolor)
      LineXY(4, M + 8, OutputWidth() - 8, M + 8, $FFFFFF)
      
      width = OutputWidth()
      ;DrawingMode(1)
      ; draw pairs
      Macro NPAIR(_L_, _N_, _W_ = (width * 0.5))
        DrawText(5, y + 1, LSet(_L_, 20), $202020)
        DrawText(4, y, LSet(_L_, 20))
        DrawText(_W_ + 9, y + 1, FormatNumber(_N_, 0), $202020)
        DrawText(_W_ + 8, y, FormatNumber(_N_, 0))
        y + M + 4
      EndMacro
      
      NPAIR("Frame #:", tt_frame)
      NPAIR("Life #:", tt_life)
      NPAIR("R #:", tt_red)
      NPAIR("G #:", tt_green)
      NPAIR("B #:", tt_blue)
      NPAIR("Motion blur", t_maxfades)
      NPAIR("Max Upd/s:", FPS)
      NPAIR("Img Upd/s:", img_fps)
      NPAIR("Avg FPS:", avg_fps)
      
      StopDrawing()
      
    EndIf
    
  ;  SpriteQuality(#PB_Sprite_BilinearFiltering)
    
    DisplayTransparentSprite(1, ScreenWidth() * 0.1, ScreenHeight() * 0.1)
  EndIf
  
 ; SpriteQuality(#PB_Sprite_NoFiltering)
  
  ExamineKeyboard()
  
  If KeyboardPushed(#PB_Key_Space)
    randomlife()
    frame    = 0
    oldframe = 1
    tt_frame = 0
    em_start = ElapsedMilliseconds()
    StabilityFrame=0  
    tt_updates = 1
  EndIf
  
  If KeyboardReleased(#PB_Key_Space)
    ForEach oSprite()
      If StartDrawing(SpriteOutput(oSprite()))
        Box(0, 0, OutputWidth(), OutputHeight(), $0)
        StopDrawing()
      EndIf
      
    Next
    
    
  EndIf
  
  
  If KeyboardReleased(#PB_Key_Pause)
    Paused ! 1
  EndIf
  
  
  If KeyboardReleased(#PB_Key_F1)
    ShowStats ! 1
  EndIf
  If KeyboardReleased(#PB_Key_F2)
    NormalizeColors ! 1
  EndIf
  
  If KeyboardReleased(#PB_Key_PageDown)
    FPS = FPS - 1
    If FPS < 1
      FPS = 1
    EndIf
  ElseIf KeyboardReleased(#PB_Key_PageUp)
    FPS = FPS + 1
    If FPS > #MAX_FPS
      FPS = #MAX_FPS
    EndIf
  EndIf
  
  If KeyboardReleased(#PB_Key_0) Or KeyboardReleased(#PB_Key_Pad8)
    t_maxfades + 1
    If t_maxfades > ListSize(OSprite())
      t_maxfades = ListSize(OSprite())
    EndIf
  ElseIf KeyboardReleased(#PB_Key_9) Or KeyboardReleased(#PB_Key_Pad2)
    t_maxfades - 1
    If t_maxfades < 1
      t_maxfades = 1
    EndIf
  EndIf
  
  If KeyboardReleased(#PB_Key_Add)
    t_Reach = t_reach + 1
    If t_reach > #MAX_REACH
      t_reach = #MAX_REACH
    EndIf
    
    MakeReach(t_reach, t_reach)
  ElseIf KeyboardReleased(#PB_Key_Subtract)
    t_reach = t_reach - 1
    If t_reach < 1
      t_reach = 1
    EndIf
    MakeReach(t_reach, t_reach)
  EndIf
  
  If KeyboardReleased(#PB_Key_F12)
    
    If FirstElement(OSprite())
      StabilityFrame = 0
      LoadMapImage(OSprite(), OpenFileRequester("Select image file for seed", "", "", 0))
    EndIf
    
  EndIf
Until KeyboardPushed(#PB_Key_Escape) Or Event = #PB_Event_CloseWindow
Post Reply