Files
GB-GameOfLife/Code/main.asm

676 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 pointer to buffer0
ld a, HIGH(Buffer0)
ldh [Old + 1], a
; set new pointer to buffer1
ld a, HIGH(Buffer1)
ldh [New + 1], a
; set rendered to buffer1 also
ldh [Rendered + 1], a
; set video pointer to second tilemap
ld a, HIGH(_SCRN1)
ldh [Video + 1], a
; set low byte of pointers to 0 (all buffers are aligned)
xor a
ldh [Old + 0], a
ldh [New + 0], a
ldh [Rendered + 0], a
ldh [Video + 0], 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 (-16, -8)
ld a, -16
ld [rSCX], a
ld a, -8
ld [rSCY], a
; clear screen (both buffers)
ld hl, _SCRN0
ld d, 17 ; empty tile
ld bc, 32 * 32 * 2
call MemorySet
; init buffer 0
ld hl, Buffer0
ld de, DefaultMap
ld bc, 32 * 32
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 bc, TopLeftCorner
call Conway
; 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, 30
.top
ld [XLoop], a
; handle top row cell
ld bc, TopRow
call Conway
; 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 bc, TopRightCorner
call Conway
; advance pointers to next row
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
ld a, 30
.leftcolumn
ld [YLoop], a
; handle first element in row
ld bc, LeftColumn
call Conway
; advance to next cell
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
ld a, 30
.inner
ld [XLoop], a
; handle element inside row
ld bc, Inner
call Conway
; advance to next cell
ld hl, New
inc [hl]
ld hl, Old
inc [hl]
; decrement x loop
ld a, [XLoop]
dec a
jr nz, .inner
; handle last element in row
.rightcolumn
ld bc, RightColumn
call Conway
; advance to next row
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 y loop
ld a, [YLoop]
dec a
jr nz, .leftcolumn
; handle bottom left element
.bottomleft
ld bc, BottomLeftCorner
call Conway
; 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, 30
.bottom
ld [XLoop], a
; handle top row cell
ld bc, BottomRow
call Conway
; 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 bc, BottomRightCorner
call Conway
; increment new pointer to first byte after buffer
ld hl, New
inc [hl]
inc hl
inc [hl]
; enable only v-blank interrupt
di
ld a, IEF_VBLANK
ld [rIE], a
ei
; wait end of rendering
.waitRender
halt
; compare high byte of rendered and new pointers
ldh a, [Rendered + 1]
ld b, a
ldh a, [New + 1]
cp a, b
jr nz, .waitRender
; compare low byte of rendered and new pointers
ldh a, [Rendered]
ld b, a
ldh a, [New]
cp a, b
jr nz, .waitRender
; swap pointers and display bg that has just been filled
ldh a, [New + 1]
cp a, HIGH(Buffer1)
jr z, .newToBuffer1
ld a, HIGH(Buffer0)
ldh [New + 1], a
ldh [Rendered + 1], a
ld a, HIGH(Buffer1)
ldh [Old + 1], a
ld a, HIGH(_SCRN0)
ldh [Video + 1], a
; display bg 9C00
ld a, LCDCF_ON | LCDCF_BGON | LCDCF_BG9C00
ld [rLCDC], a
jr .resetlow
.newToBuffer1
ld a, HIGH(Buffer1)
ldh [New + 1], a
ldh [Rendered + 1], a
ld a, HIGH(Buffer0)
ldh [Old + 1], a
ld a, HIGH(_SCRN1)
ldh [Video + 1], a
; display bg 9800
ld a, LCDCF_ON | LCDCF_BGON | LCDCF_BG9800
ld [rLCDC], a
.resetlow
; reset low bytes of pointers
xor a
ldh [New], a
ldh [Old], a
ldh [Video], a
ldh [Rendered], a
jp .mainloop
SECTION "Table based conway's game of life step for one cell", ROM0
; bc = pointer to neighbor offsets
; destroys all registers
Conway:
; reset alive counter
xor a
ld [Alive], a
.loop
; load offset into de
ld h, b
ld l, c
ld a, [hl+]
ld e, a
ld a, [hl+]
ld d, a
; check end of list
or a, e ; (a still contains d)
jr z, .decide
; advance bc to next neighbor
ld b, h
ld c, l
; load old pointer
ld hl, Old
ld a, [hl+]
ld h, [hl]
ld l, a
; add offset
add hl, de
; load neighbor
ld a, [hl]
; check neighbor is alive
or a, 0
jr z, .loop
; increment alive
ld hl, Alive
inc [hl]
; continue to next neighbor
jr .loop
.decide
; load old pointer
ld hl, Old
ld a, [hl+]
ld h, [hl]
ld l, a
; load status
ld a, [hl]
; check if alive
or a, 0
jr nz, .alive
.dead
; load live neighbor count
ld a, [Alive]
; check if there is 3 neighbors
cp a, 3
jr nz, .writedead
.writealive
; load new pointer
ld hl, New
ld a, [hl+]
ld h, [hl]
ld l, a
; write alive
ld a, 1
ld [hl], a
ret
.alive
; load live neighbor count
ld a, [Alive]
; check if there is 3 neighbors
cp a, 3
jr z, .writealive
; check if there is 2 neighbors
cp a, 2
jr z, .writealive
.writedead
; load new pointer
ld hl, New
ld a, [hl+]
ld h, [hl]
ld l, a
; write alive
xor a ; a = 0
ld [hl], a
ret
SECTION "V-Blank Interrupt Handler", ROM0[$40]
VBlankInterruptHandler:
; 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:
; 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 buffers", WRAM0[$C000]
Buffer0: ds 32 * 32
Buffer1: ds 32 * 32
SECTION "Update Memory", HRAM
; update
Old: ds 2
New: ds 2
Alive: ds 1
XLoop: ds 1
YLoop: ds 1
; render
RenderCount: ds 1
Video: ds 2
Rendered: ds 2
SECTION "Game of Life neighboring cells offset tables", ROM0
; for a looping grid of 32x32 cells
TopLeftCorner: dw 1, 33, 32, 63, 31, 1023, 992, 993, 0
TopRightCorner: dw -31, 1, 32, 31, -1, 991, 992, 961, 0
BottomLeftCorner: dw 1, -991, -992, -961, 31, -1, -32, -31, 0
BottomRightCorner: dw -31, -1023, -992, -993, -1, -33, -32, -63, 0
TopRow: dw 1, 33, 32, 31, -1, 991, 992, 993, 0
BottomRow: dw 1, -991, -992, -993, -1, -33, -32, -31, 0
LeftColumn: dw 1, 33, 32, 63, 31, -1, -32, -31, 0
RightColumn: dw -31, 1, 32, 31, -1, -33, -32, -63, 0
Inner: dw 1, 33, 32, 31, -1, -33, -32, -31, 0
SECTION "Default Map", ROM0
DefaultMap:
db 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 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, 1, 1, 0, 0, 0, 0, 0, 0, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 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, 1, 0, 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, 1, 1, 1, 0, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
SECTION "Graphics", ROM0
Tiles:
INCBIN "Tiles.bin"
TilesEnd: ds 0