' =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
' File...... WallLife.SXB
' Purpose... Digital Life Program
' Author.... Sean Hillmeyer
' Based on the work of Jon Williams
' Copyright (c) 2008 Sean Hillmeyer
' Some Rights Reserved
' -- see http://creativecommons.org/licenses/by/2.5/
' E-mail.... hillmeyer@gmail.com
' Started... 1 APR 2008
'
' Program Description:
'
' Conway's Game of Life simulation.
' -- see: http://en.wikipedia.org/wiki/Conway's_Game_of_Life
' Runs on a 8x26 martix of LEDs. 
'
' =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
'
' -------------------------------------------------------------------------
' Device Settings
' -------------------------------------------------------------------------
DEVICE SX28, OSCXT2, TURBO, STACKX, OPTIONX
FREQ   20_000_000
ID	   "LIFE"

' -------------------------------------------------------------------------
' IO Pins
' -------------------------------------------------------------------------
Anodes PIN RA OUTPUT		'Anodes are controled by BCD on port RA
Dout PIN RB.0 OUTPUT		'Cathodes are controled by a serial shift to MAX69XX LED drivers
Cout PIN RB.1 OUTPUT
Latch PIN RB.2 OUTPUT
OE	 PIN RB.3 OUTPUT

' -------------------------------------------------------------------------
' Constants
' -------------------------------------------------------------------------
Yes CON 1
No CON 0
Alive CON 1
Dead CON 0

' -------------------------------------------------------------------------
' Variables
' -------------------------------------------------------------------------
' The following four bytes contain the display buffer for the matrix.
' Pixel 0,0 (upper left) is the highest bit in ColHH,Byte(0)

' 0 | ColHH, ColHL, ColLH, ColLL | 0
' 1 | ColHH, ColHL, ColLH, ColLL | 1
' 2 | ColHH, ColHL, ColLH, ColLL | 2
' 3 | ColHH, ColHL, ColLH, ColLL | 3
' 4 | ColHH, ColHL, ColLH, ColLL | 4
' 5 | ColHH, ColHL, ColLH, ColLL | 5
' 6 | ColHH, ColHL, ColLH, ColLL | 6
' 7 | ColHH, ColHL, ColLH, ColLL | 7

ColHH           VAR             Byte(8)
ColHL           VAR             Byte(8)
ColLH           VAR             Byte(8)
ColLL           VAR             Byte(8)

newGenHH        VAR             Byte(8)
newGenHL        VAR             Byte(8)
newGenLH        VAR             Byte(8)
newGenLL        VAR             Byte(8)

genCount VAR Byte
row VAR Byte ' current display row (ISR)
ms VAR Word ' for delay timing (ISR)
seed VAR Byte ' running random value (ISR)

idxCol VAR Byte
idxRow VAR Byte
neighbors VAR Byte
chkRow VAR Byte
chkCol VAR Byte
colTmp VAR Byte
cell VAR Byte
tmpB1 VAR Byte ' work variables
tmpB2 VAR Byte
tmpB3 VAR Byte

' -------------------------------------------------------------------------
INTERRUPT 1000 ' run every 2 milliseconds
' -------------------------------------------------------------------------
GOTO INT_HANDLER

' =========================================================================
PROGRAM Start
' =========================================================================

' -------------------------------------------------------------------------
' Subroutine Declarations
' -------------------------------------------------------------------------
DELAY SUB 1, 2 ' delay in 1 ms units
COUNT_NEIGHBORS SUB 0 ' counts cell neighbors
GET_BIT FUNC 1, 2 ' get bit value
SET_BIT FUNC 1, 2 ' sets a bit in a byte
CLR_BIT FUNC 1, 2 ' clears a bit in a byte
PUT_BIT FUNC 1, 3 ' writes bitVal into byte
GET_CELL FUNC 1, 0 ' get cell status

' -------------------------------------------------------------------------
' Program Code
' -------------------------------------------------------------------------

Start:

Display_Test: ' fill the display and clear for test
FOR idxRow = 0 TO 7
       ColHH(idxRow) = $FF
       ColHL(idxRow) = $FF
       ColLH(idxRow) = $FF
       ColLL(idxRow) = $FF
NEXT
DELAY 5000
FOR idxRow = 0 to 7
       ColHH(idxRow) = 0
       ColHL(idxRow) = 0
	   ColLH(idxRow) = 0
	   ColLL(idxRow) = 0
NEXT

Randomize_Cells:  ' fill the display buffer with a random initial state
FOR idxRow = 0 TO 7
	   ColHH(idxRow) = seed
					       ColHL(idxRow) = seed
										       ColLH(idxRow) = seed
															       ColLL(idxRow) = seed
	   DELAY 5 ' let rand gen run
NEXT
DELAY 10

Main:

Game_of_Life:
FOR idxCol = 0 TO 31
	FOR idxRow = 0 TO 7
		COUNT_NEIGHBORS
		IF neighbors <= 1 THEN ' alone... dies
			IF idxCol <= 7 THEN
				newGenLL(idxRow) = CLR_BIT newGenLL(idxRow), idxCol
			ELSEIF idxCol <= 15 THEN
				colTmp = idxCol - 8
				newGenLH(idxRow) = CLR_BIT newGenLH(idxRow), colTmp
			ELSEIF idxCol <= 23 THEN
				colTmp = idxCol - 16
				newGenHL(idxRow) = CLR_BIT newGenHL(idxRow), colTmp
			ELSEIF idxCol <= 31 THEN
				colTmp = idxCol - 24
				newGenHH(idxRow) = CLR_BIT newGenHH(idxRow), colTmp
			ENDIF
		ENDIF
		IF neighbors = 2 THEN ' no change
			IF idxCol <= 7 THEN
				cell = GET_BIT ColLL(idxRow), idxCol
				newGenLL(idxRow) = PUT_BIT newGenLL(idxRow), idxCol, cell
			ELSEIF idxCol <= 15 THEN
				colTmp = idxCol - 8
				cell = GET_BIT ColLH(idxRow), colTmp
				newGenLH(idxRow) = PUT_BIT newGenLH(idxRow), colTmp, cell
			ELSEIF idxCol <= 23 THEN
				colTmp = idxCol - 16
				cell = GET_BIT ColHL(idxRow), colTmp
				newGenHL(idxRow) = PUT_BIT newGenHL(idxRow), colTmp, cell
			ELSEIF idxCol <= 31 THEN
				colTmp = idxCol - 24
				cell = GET_BIT ColHH(idxRow), colTmp
				newGenHH(idxRow) = PUT_BIT newGenHH(idxRow), colTmp, cell
			ENDIF
		ENDIF
		IF neighbors = 3 THEN ' lives!
			IF idxCol <= 7 THEN
				newGenLL(idxRow) = SET_BIT newGenLL(idxRow), idxCol
			ELSEIF idxCol <= 15 THEN
				colTmp = idxCol - 8
				newGenLH(idxRow) = SET_BIT newGenLH(idxRow), colTmp
			ELSEIF idxCol <= 23 THEN
				colTmp = idxCol - 16
				newGenHL(idxRow) = SET_BIT newGenHL(idxRow), colTmp
			ELSEIF idxCol <= 31 THEN
				colTmp = idxCol - 24
				newGenHH(idxRow) = SET_BIT newGenHH(idxRow), colTmp
			ENDIF
		ENDIF
		IF neighbors >= 4 THEN ' crowded... dies
			IF idxCol <= 7 THEN
				newGenLL(idxRow) = CLR_BIT newGenLL(idxRow), idxCol
			ELSEIF idxCol <= 15 THEN
				colTmp = idxCol - 8
				newGenLH(idxRow) = CLR_BIT newGenLH(idxRow), colTmp
			ELSEIF idxCol <= 23 THEN
				colTmp = idxCol - 16
				newGenHL(idxRow) = CLR_BIT newGenHL(idxRow), colTmp
			ELSEIF idxCol <= 31 THEN
				colTmp = idxCol - 24
				newGenHH(idxRow) = CLR_BIT newGenHH(idxRow), colTmp
			ENDIF
		ENDIF
	NEXT
NEXT

FOR idxRow = 0 TO 7 ' update display
	ColLL(idxRow) = newGenLL(idxRow)
	ColLH(idxRow) = newGenLH(idxRow)
	ColHL(idxRow) = newGenHL(idxRow)
	ColHH(idxRow) = newGenHH(idxRow)
NEXT

INC genCount
DELAY 1000 ' inter-generation timing

IF genCount = 150 THEN
	genCount = 0
	Goto Randomize_Cells
ENDIF

GOTO Game_of_Life



' -------------------------------------------------------------------------
' Interrupt handler
' -------------------------------------------------------------------------
INT_HANDLER:
' Disable the outputs and shift out the current row
HIGH OE
Anodes = row
SHIFTOUT Dout, Cout, 0, ColLL(row)\8
SHIFTOUT Dout, Cout, 0, ColLH(row)\8
SHIFTOUT Dout, Cout, 0, ColHL(row)\8
SHIFTOUT Dout, Cout, 0, ColHH(row)\8
PULSOUT Latch, 1
' Switch to the displayed row and re-enable the outputs
LOW OE

INC row ' point to next column in the sequence

IF row = 8 THEN
	row = 0 ' yes, reset
ENDIF

Update_Timer:
IF ms > 0 THEN ' delay timer running?
	DEC ms ' yes, decrement
ENDIF

LFSR: ' randomize "seed"
IF seed = 0 THEN
	seed = 24
ENDIF
ASM
	MOV W, #$1D
	CLRB C
	RL seed
	SNB C
	XOR seed, W
ENDASM

ISR_Exit:
RETURNINT

' -------------------------------------------------------------------------
' Subroutine Code
' -------------------------------------------------------------------------
' Use: DELAY msec
DELAY:
IF __PARAMCNT = 1 THEN
	ms = __PARAM1 ' get byte parameter
ELSE
	ms = __WPARAM12 ' get word parameter
ENDIF
DO
	' wait for timer to expire
LOOP UNTIL ms = 0
RETURN

' -------------------------------------------------------------------------
' Use: COUNT_NEIGHBORS
' -- counts live neighbors of cell in dispBuf
' -- location of cell in idxCol/idxRow
COUNT_NEIGHBORS:
neighbors = 0 ' reset neighbors count
chkCol = idxCol - 1 ' SW
chkRow = idxRow - 1
cell = GET_CELL
neighbors = neighbors + cell
chkCol = idxCol - 1 ' W
chkRow = idxRow + 0
cell = GET_CELL
neighbors = neighbors + cell
chkCol = idxCol - 1 ' NW
chkRow = idxRow + 1
cell = GET_CELL
neighbors = neighbors + cell
chkCol = idxCol + 0 ' N
chkRow = idxRow + 1
cell = GET_CELL
neighbors = neighbors + cell
chkCol = idxCol + 1 ' NE
chkRow = idxRow + 1
cell = GET_CELL
neighbors = neighbors + cell
chkCol = idxCol + 1 ' E
chkRow = idxRow + 0
cell = GET_CELL
neighbors = neighbors + cell
chkCol = idxCol + 1 ' SE
chkRow = idxRow - 1
cell = GET_CELL
neighbors = neighbors + cell
chkCol = idxCol + 0 ' S
chkRow = idxRow - 1
cell = GET_CELL
neighbors = neighbors + cell
RETURN

' -------------------------------------------------------------------------
' Use: result = GET_CELL
' -- returns value of cell (0 or 1) from dispBuf array
' -- uses bound checking for "chkCol" and "chkRow"
GET_CELL:
tmpB1 = 0
IF chkCol >= 0 THEN
	IF chkCol <= 32 THEN
		IF chkRow >= 0 THEN
			IF chkRow <= 8 THEN
				IF chkCol <= 7 THEN
					tmpB1 = GET_BIT ColLL(chkRow), chkCol
				ELSEIF chkCol <= 15 THEN
					colTmp = chkCol - 8
					tmpB1 = GET_BIT ColLH(chkRow), colTmp
				ELSEIF chkCol <= 23 THEN
					colTmp = chkCol - 16
					tmpB1 = GET_BIT ColHL(chkRow), colTmp
				ELSEIF chkCol <= 31 THEN
					colTmp = chkCol - 24
					tmpB1 = GET_BIT ColHH(chkRow), colTmp
				ENDIF
			ENDIF
		ENDIF
	ENDIF
ENDIF
RETURN tmpB1

' -------------------------------------------------------------------------
' Use: result = GET_BIT value, position
' -- returns 1 or 0
GET_BIT:
tmpB1 = __PARAM1 ' save value
tmpB2 = __PARAM2 ' save position
tmpB2 = 1 << tmpB2 ' create mask
tmpB1 = tmpB1 & tmpB2 ' isolate bit
IF tmpB1 > 0 THEN
	tmpB1 = 1
ENDIF
RETURN tmpB1

' -------------------------------------------------------------------------
' Use: result = SET_BIT value, position
' -- returns copy of value with position bit set
SET_BIT:
tmpB1 = __PARAM1 ' save value
tmpB2 = __PARAM2 ' save position
tmpB2 = 1 << tmpB2 ' create mask
tmpB1 = tmpB1 | tmpB2 ' set the bit
RETURN tmpB1

' -------------------------------------------------------------------------
' Use: result = CLR_BIT value, position
' -- returns value with position bit cleared
CLR_BIT:
tmpB1 = __PARAM1 ' save value
tmpB2 = __PARAM2 ' save position
tmpB2 = 1 << tmpB2 ' create mask
tmpB2 = tmpB2 ^ %11111111 ' invert the mask
tmpB1 = tmpB1 & tmpB2 ' clear the bit
RETURN tmpB1

' -------------------------------------------------------------------------
' Use: result = PUT_BIT value, position, bitVal
' -- writes bitVal to value.position
PUT_BIT:
tmpB1 = __PARAM1 ' save value
tmpB2 = __PARAM2 ' save position
tmpB3 = __PARAM3.0 ' save bit value
tmpB2 = 1 << tmpB2 ' create mask
IF tmpB3 = 1 THEN
	tmpB1 = tmpB1 | tmpB2 ' set the bit
ELSE
	tmpB2 = tmpB2 ^ %11111111 ' invert the mask
	tmpB1 = tmpB1 & tmpB2 ' clear the bit
ENDIF
RETURN tmpB1