Files
GB-GameOfLife/Code/main.asm
2018-12-29 01:51:33 +01:00

781 lines
13 KiB
NASM

INCLUDE "hardware.inc"
_VRAM_BG_TILES EQU $9000
SECTION "Memory Copy", ROM0
; hl = destination
; de = source
; bc = count
MemoryCopy:
ld a, [de]
ld [hl+], a
inc de
dec bc
ld a, b
or c
jr nz, MemoryCopy
ret
SECTION "Memory Set", ROM0
; hl = destination
; d = data
; bc = count
MemorySet:
ld a, d
ld [hl+], a
dec bc
ld a, b
or c
jr nz, MemorySet
ret
SECTION "Timer Interrupt Handler", ROM0[$50]
TimerInterruptHandler:
reti
SECTION "Serial Interrupt Handler", ROM0[$58]
SerialInterruptHandler:
reti
SECTION "Joypad Interrupt Handler", ROM0[$60]
JoypadInterruptHandler:
reti
SECTION "Header", ROM0[$100]
EntryPoint:
di
jp Start
REPT $150 - $104
db 0
ENDR
SECTION "Main", ROM0[$150]
Start:
; shut sound off
ld [rNR52], a
; set old and rendered pointers to buffer0
ld a, HIGH(Buffer0)
ldh [Old + 1], a
ldh [Rendered + 1], a
; set new pointer to buffer1
ld a, HIGH(Buffer1)
ldh [New + 1], a
; set video pointer to first tilemap
ld a, HIGH(_SCRN0)
ldh [Video + 1], a
; reset low bytes of pointers (all buffers are aligned)
xor a
ldh [New], a
ldh [Old], a
ldh [Rendered], a
ldh [Video], a
; enable v-blank interrupt
ld a, IEF_VBLANK
ld [rIE], a
; enable interrupts
ei
; wait for v-blank
halt
; disable screen
xor a
ld [rLCDC], a
; load bg palette [0=black, 1=dark gray, 2=light gray, 3=white]
ld a, %11100100
ld [rBGP], a
; load 18 tiles
; 0..15: 2x2 cell combinations
; 16: sprite selection tile
; 17: empty tile
ld hl, _VRAM_BG_TILES
ld de, Tiles
ld bc, TilesEnd - Tiles
call MemoryCopy
; set scrolling to (0, 0)
xor a
ld [rSCX], a
ld [rSCY], a
; clear screen (both buffers)
ld hl, _SCRN0
ld d, 17 ; empty tile
ld bc, 32 * 32 * 2
call MemorySet
; clear screen (both buffers)
ld hl, _SCRN1
ld d, 0 ; empty tile
ld bc, 32 * 32
call MemorySet
; init buffer 0
ld hl, Buffer0
ld de, DefaultMap
ld bc, 20 * 18
call MemoryCopy
; display bg 9800
ld a, LCDCF_ON | LCDCF_BGON | LCDCF_BG9800
ld [rLCDC], a
; enable h-blank interrupt in lcd stat
ld a, STATF_MODE00
ld [rSTAT], a
.mainloop
; enable v-blank and lcd stat interrupt for h-blank
di
ld a, IEF_VBLANK ;| IEF_LCDC
ld [rIE], a
ei
.topleft
; handle top left corner
ld hl, TopLeftCorner
call ConwayGroup
; advance to next cell in top row
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
; handle all cells in top row except corners
ld a, 18
.top
ld [XLoop], a
; handle top row cell
ld hl, TopRow
call ConwayGroup
; advance to next cell in top row
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
; decrement x loop
ld a, [XLoop]
dec a
jr nz, .top
; handle top right corner
.topright
ld hl, TopRightCorner
call ConwayGroup
; advance pointers to next row
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
ld a, 16
.leftcolumn
ld [YLoop], a
; handle first element in row
ld hl, LeftColumn
call ConwayGroup
; advance to next cell
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
ld a, 18
.inner
ld [XLoop], a
; handle element inside row
ld hl, Inner
call ConwayGroup
; advance to next cell
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
jr nz, .noCarry
inc hl ; old+1
inc [hl]
ld hl, New + 1
inc [hl]
.noCarry
; decrement x loop
ld a, [XLoop]
dec a
jr nz, .inner
; handle last element in row
.rightcolumn
ld hl, RightColumn
call ConwayGroup
; advance to next row
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
; decrement y loop
ld a, [YLoop]
dec a
jr nz, .leftcolumn
; handle bottom left element
.bottomleft
ld hl, BottomLeftCorner
call ConwayGroup
; advance to next cell in bottom row
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
; handle all cells in bottom row except corners
ld a, 18
.bottom
ld [XLoop], a
; handle top row cell
ld hl, BottomRow
call ConwayGroup
; advance to next cell in top row
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
; decrement x loop
ld a, [XLoop]
dec a
jr nz, .bottom
; handle last element
.bottomright
ld hl, BottomRightCorner
call ConwayGroup
; increment old pointer to first byte after buffer
ld hl, Old
inc [hl]
; wait end of rendering
;.waitRender
; halt
; ; compare high byte of rendered and old pointers
; ldh a, [Rendered + 1]
; ld b, a
; ldh a, [Old + 1]
; cp a, b
; jr nz, .waitRender
; ; compare low byte of rendered and old pointers
; ldh a, [Rendered]
; ld b, a
; ldh a, [Old]
; cp a, b
; jr nz, .waitRender
; enable only v-blank interrupt
di
ld a, IEF_VBLANK
ld [rIE], a
ei
; wait v-blank
halt
; check which buffer has just been rendered
ldh a, [Old + 1]
cp a, HIGH(Buffer1)
jr nc, .newToBuffer1
.newToBuffer0
; set old and rendered pointers to buffer1
ld a, HIGH(Buffer1)
ldh [Old + 1], a
ldh [Rendered + 1], a
; set new pointer to buffer0
ld a, HIGH(Buffer0)
ldh [New + 1], a
; set video pointer to first tilemap
ld a, HIGH(_SCRN1)
ldh [Video + 1], a
; display bg 9800 that has just been filled
ld a, LCDCF_ON | LCDCF_BGON | LCDCF_BG9800
ld [rLCDC], a
jr .resetLowBytes
.newToBuffer1
; set old and rendered pointers to buffer0
ld a, HIGH(Buffer0)
ldh [Old + 1], a
ldh [Rendered + 1], a
; set new pointer to buffer1
ld a, HIGH(Buffer1)
ldh [New + 1], a
; set video pointer to second tilemap
ld a, HIGH(_SCRN0)
ldh [Video + 1], a
; display bg 9C00 that has just been filled
ld a, LCDCF_ON | LCDCF_BGON | LCDCF_BG9C00
ld [rLCDC], a
.resetLowBytes
xor a
ldh [New], a
ldh [Old], a
ldh [Rendered], a
ldh [Video], a
jp .mainloop
SECTION "Load cell group and 8 neighbors to HRAM, then compute", ROM0
; hl = pointer to neighbor offsets
; destroys all registers
ConwayGroup:
; save pointer to neighbors
push hl
; pointer to HRAM
ld c, LOW(Cells)
; load old pointer into hl
ld hl, Old
ld a, [hl+]
ld h, [hl]
ld l, a
; load cell group
ld a, [hl]
; write cell group into hram
ld [$FF00+c], a
; increment hram pointer
inc c
; counter to 8
ld b, 8
.loop
; restore pointer to neighbors
pop hl
; load offset into de
ld a, [hl+]
ld e, a
ld a, [hl+]
ld d, a
; save incremented pointer to neighbors
push hl
; load old pointer into hl
ld hl, Old
ld a, [hl+]
ld h, [hl]
ld l, a
; add offset
add hl, de
; load neighbor
ld a, [hl]
; store neighbor into hram
ld [$FF00+c], a
; increment pointer into hram
inc c
; decrement counter
dec b
; continue to next neighbor if any
jr nz, .loop
.compute
; remove pointer to offsets from stack
pop hl
; reset result
xor a
ldh [Result], a
; compute all 4 cells
ld hl, TopLeftMask
call Conway
ld hl, TopRightMask
call Conway
ld hl, BottomLeftMask
call Conway
ld hl, BottomRightMask
call Conway
; load new pointer
ld hl, New
ld a, [hl+]
ld h, [hl]
ld l, a
; load result
ldh a, [Result]
; save result to new buffer
ld [hl], a
ret
SECTION "Conway cell compute", ROM0
; hl = pointer to cell group masks
Conway:
; reset alive counter
xor a
ldh [Alive], a
; load cells pointer to first neighbor
ld c, LOW(Cells + 1)
.loop
; load next mask
ld a, [hl+]
; check end of table
cp a, $FF
jr z, .decide
; move mask to d
ld d, a
; load data
ld a, [$FF00+c]
; mask data
and a, d
; count bits set
ld de, BitsSet
add a, e
ld e, a
ld a, [de]
ld b, a
; add to alive
ldh a, [Alive]
add a, b
ldh [Alive], a
; increment cells pointer
inc c
; loop over all neighbors
jr .loop
; load current group mask
.decide
ld b, [hl]
; load current cell group
ldh a, [Cells]
; mask data
and a, b
; load alive count
ldh a, [Alive]
jr z, .dead
.alive
; check if there is two or three neighbors
bit 1, a
ret z
.writealive
; add mask to result
ldh a, [Result]
or a, b
ldh [Result], a
ret
.dead
; check if there is two neighbors
cp a, 2
jr z, .writealive
.writedead
ret
SECTION "V-Blank Interrupt Handler", ROM0[$40]
VBlankInterruptHandler:
reti
; save A and flags
;push af
;
;; set max number of cells to render
;ld a, 10
;ldh [RenderCount], a
;
;; render
;jp Render
SECTION "LCD Stat Interrupt Handler", ROM0[$48]
LCDStatInterruptHandler:
reti
; save A and flags
;push af
;
;; set max number of cells to render
;ld a, 1
;ldh [RenderCount], a
;
;; render
;jp Render
SECTION "Render", ROM0
Render:
; save DE, BC, HL registers
push de
push bc
push hl
.loop
; compare rendered and new pointers
; compare high byte first
ldh a, [New + 1]
ld b, a
ldh a, [Rendered + 1]
cp a, b
jr c, .render
; compare low byte
ldh a, [Rendered + 0]
ld b, a
ldh a, [New + 0]
sub a, b
jr z, .exit
; check there are at least two bytes to render
dec a
jr z, .exit
; load rendered pointer
.render
ld hl, Rendered
ld a, [hl+]
ld h, [hl]
ld l, a
ld d, l
; read two bytes in bits 0 and 1 of C
ld a, [hl+]
ld b, a
ld a, [hl+]
sla a
or a, b
; store read data into C
ld c, a
; test bit 6 of address to determine if we're on odd or even row
bit 5, d
; store incremented rendered pointer
ld a, h
ld [Rendered + 1], a
ld a, l
ld [Rendered + 0], a
jr z, .even
.odd:
; move read data into bits 2 and 3
sla c
sla c
; load video pointer
ld hl, Video
ld a, [hl+]
ld h, [hl]
ld l, a
; load current data
ld a, [hl]
; add read data
or a, c
; write updated data into video ram
ld [hl], a
; increment video pointer by 1
ld hl, Video
inc [hl]
; check end of line
ld a, [Rendered]
and %11111
jr nz, .next
; increment video pointer by 16 to go to next line
ldh a, [Video]
add a, 16
ldh [Video], a
jr nc, .next
ld hl, Video + 1
inc [hl]
jr .next
.even:
; load video pointer
ld hl, Video
ld a, [hl+]
ld h, [hl]
ld l, a
; write data into video ram
ld [hl], c
; increment video pointer by 1
ld hl, Video
inc [hl]
; check end of line
ld a, [Rendered]
and %11111
jr nz, .next
; move back to beginning of video line
ldh a, [Video]
add a, -16
ldh [Video], a
; decrement render count
.next
ld hl, RenderCount
dec [hl]
jr nz, .loop
.exit
; restore DE, BC, HL registers
pop hl
pop bc
pop de
; restore A and flags, saved in interrupt handler
pop af
; return from v-blank or lcd interrupt
reti
SECTION "Automata buffer 0", WRAM0, ALIGN[9]
Buffer0: ds 20 * 18
SECTION "Automata buffer 1", WRAM0, ALIGN[9]
Buffer1: ds 20 * 18
SECTION "Compute Memory", HRAM
Old: ds 2 ; pointer to bufferX
New: ds 2 ; pointer to bufferX
Alive: ds 1
XLoop: ds 1
YLoop: ds 1
Cells: ds 9
Result: ds 1
SECTION "Render Memory", HRAM
RenderCount: ds 1 ; max number of tiles to render before leaving
Video: ds 2 ; pointer to tilemap
Rendered: ds 2 ; pointer to bufferX
SECTION "Game of Life neighboring cells offset tables", ROM0
; for a looping grid of 20x18 cells
; order matters R, BR, B, BL, L, TL, T, TR
TopLeftCorner: dw 1, 21, 20, 39, 19, 359, 340, 341
TopRightCorner: dw -19, 1, 20, 19, -1, 339, 340, 321
BottomLeftCorner: dw 1, -339, -340, -321, 19, -1, -20, -19
BottomRightCorner: dw -19, -359, -340, -341, -1, -21, -20, -39
TopRow: dw 1, 21, 20, 19, -1, 339, 340, 341
BottomRow: dw 1, -339, -340, -341, -1, -21, -20, -19
LeftColumn: dw 1, 21, 20, 39, 19, -1, -20, -19
RightColumn: dw -19, 1, 20, 19, -1, -21, -20, -39
Inner: dw 1, 21, 20, 19, -1, -21, -20, -19
SECTION "Game of Life neighboring masks", ROM0
; order matters I, R, BR, B, BL, L, TL, T, TR, SELF, END
TopLeftMask: db 14, 0, 0, 0, 0, 10, 8, 12, 0, 1, $FF
TopRightMask: db 13, 5, 0, 0, 0, 0, 0, 12, 4, 2, $FF
BottomLeftMask: db 11, 0, 0, 3, 2, 10, 0, 0, 0, 4, $FF
BottomRightMask: db 7, 5, 1, 3, 0, 0, 0, 0, 0, 8, $FF
SECTION "Bits Set", ROM0, ALIGN[4]
BitsSet:
db 0; 0 = 0000
db 1; 1 = 0001
db 1; 2 = 0010
db 2; 3 = 0011
db 1; 4 = 0100
db 2; 5 = 0101
db 2; 6 = 0110
db 3; 7 = 0111
db 1; 8 = 1000
db 2; 9 = 1001
db 2; 10 = 1010
db 3; 11 = 1011
db 2; 12 = 1100
db 3; 13 = 1101
db 3; 14 = 1110
db 4; 15 = 1111
SECTION "Default Map", ROM0
DefaultMap:
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
SECTION "Graphics", ROM0
Tiles:
INCBIN "Tiles.bin"
TilesEnd: ds 0