Commit: a1f89bae158d656ae6ee19f2453722c3b17b78be Author: Vi Grey Date: 2020-04-23 07:03 UTC Summary: Initial commit .gitignore | 1 + CHANGELOG.md | 6 ++ Makefile | 35 ++++++++ README.md | 71 ++++++++++++++++ bin/NESGameOfLife.nes | Bin 0 -> 40976 bytes resources/asm6.zip | Bin 0 -> 47675 bytes src/NESGameOfLife.asm | 48 +++++++++++ src/cells.asm | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/controllers.asm | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/cursor.asm | 359 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/defs.asm | 24 ++++++ src/game.asm | 195 +++++++++++++++++++++++++++++++++++++++++++ src/graphics/tileset.chr | Bin 0 -> 8192 bytes src/prg.asm | 336 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ram.asm | 70 ++++++++++++++++ src/rng.asm | 50 +++++++++++ 16 files changed, 1704 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe0a4f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/*.deb diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6c21b78 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Change Log +All notable changes to this project will be documented in this file. + +## 1.0.0 - 2020-04-23 +### Added +- Initial release diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e8bbe15 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +# Copyright (C) 2020, Vi Grey +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +PKG_NAME := NESGameOfLife +CURRENTDIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +all: + mkdir -p $(CURRENTDIR)bin; \ + cd $(CURRENTDIR)src; \ + asm $(PKG_NAME).asm ../bin/$(PKG_NAME).nes; \ + +clean: + rm -f -- $(CURRENTDIR)bin/$(PKG_NAME).nes*; \ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3754989 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# NES Conway's Game of Life + +An NES implementation of Conway's Game of Life, albeit a slow implementation. + +**_NES Conway's Game of Life was created by Vi Grey (https://vigrey.com) and is licensed under the BSD 2-Clause License._** + +### Description: + +This is an 40x40 grid implementation of Conway's Game of Life - https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life - written from scratch in 6502 Assembly to run on the Nintendo Entertainment System (NES). + +Because of the 40x40 grid size in this implementation along with likely nonoptimal code, it takes about 1.25 to 1.5 seconds to calculate the next board state. I would like to apologize for how slow this program is and may look into improving the computation speed in the future. + +### "MAKE" Platforms: +- Linux + +### Build Dependencies: +- asm6 _(You'll probably have to build asm6 from source. Make sure the asm6 binary is named **asm** and that the binary is executable and accessible in your PATH. The source code can be found at http://3dscapture.com/NES/asm6.zip or in **resources/asm6.zip** included in this repository)_ + +### Build NES ROM File: + +From a terminal, go to the the main directory of this project (the directory this README.md file exists in), you can then build the file with the following command. + + $ make + +The resulting file will be located in at **bin/NESGameOfLife.nes**. + +### Cleaning Build Environment: + +If you used `make` to build the NES ROM, you can run the following command to clean up the build environment. + + $ make clean + +### Playing NES ROM File: + +This NES ROM file can be played on an NES emulator or be put on a cartridge like an Everdrive or burned onto an NES-NROM-256 cartridge. + +### Burning NES ROM to a Cartridge: + +For anyone interested in burning this NES ROM to a cartridge, this NES ROM is built for the NES-NROM-256 cartridge board. + +### Special Thanks + +In Memory of John Horton Conway (1937-2020) + +Sorry my tribute to you is so cliche. Rest in peace. + +### License: + Copyright (C) 2020, Vi Grey + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS \`\`AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. diff --git a/bin/NESGameOfLife.nes b/bin/NESGameOfLife.nes new file mode 100644 index 0000000..222f6df Binary files /dev/null and b/bin/NESGameOfLife.nes differ diff --git a/resources/asm6.zip b/resources/asm6.zip new file mode 100644 index 0000000..c983b54 Binary files /dev/null and b/resources/asm6.zip differ diff --git a/src/NESGameOfLife.asm b/src/NESGameOfLife.asm new file mode 100644 index 0000000..ee2213a --- /dev/null +++ b/src/NESGameOfLife.asm @@ -0,0 +1,48 @@ +; Copyright (C) 2020, Vi Grey +; All rights reserved. +; +; Redistribution and use in source and binary forms, with or without +; modification, are permitted provided that the following conditions +; are met: +; +; 1. Redistributions of source code must retain the above copyright +; notice, this list of conditions and the following disclaimer. +; 2. Redistributions in binary form must reproduce the above copyright +; notice, this list of conditions and the following disclaimer in the +; documentation and/or other materials provided with the distribution. +; +; THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +; ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +; OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +; LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +; SUCH DAMAGE. + + .db "NES", $1A + .db $02 + .db $01 + .db $00 + .db $00 + .db 0, 0, 0, 0, 0, 0, 0, 0 + + +.include "ram.asm" +.include "defs.asm" + + +.base $8000 + +.include "prg.asm" + + .pad CALLBACK, #$FF + .dw NMI + .dw RESET + .dw 0 + +.base $0000 + .incbin "graphics/tileset.chr" diff --git a/src/cells.asm b/src/cells.asm new file mode 100644 index 0000000..fc04fda --- /dev/null +++ b/src/cells.asm @@ -0,0 +1,247 @@ +ReadCurrentState: + lda #$00 + sta cursorXTmp + sta cursorYTmp +ReadCurrentStateLoop: + jsr GetCursorTmpCellByteOffset + jsr GetNeighborCount + lda cellNeighbors + cmp #$03 + beq ReadCurrentStateNextStateAlive + ; Neighbors not 3 + cmp #$02 + bne ReadCurrentStateNextStateDead + ; Neighbors are 2 + ldx cellOffsetByte + lda currentState, X + and cellOffsetBitLittleEndian + beq ReadCurrentStateNextStateDead + ; State was alive + jmp ReadCurrentStateNextStateAlive +ReadCurrentStateNextStateDead: + lda #$FF + eor cellOffsetBitLittleEndian + sta cellOffsetBitLittleEndianNOT + ldx cellOffsetByte + lda nextState, X + and cellOffsetBitLittleEndianNOT + sta nextState, X + jmp ReadCurrentStateIncCursorTmp +ReadCurrentStateNextStateAlive: + lda #$FF + eor cellOffsetBitLittleEndian + sta cellOffsetBitLittleEndianNOT + ldx cellOffsetByte + lda nextState, X + and cellOffsetBitLittleEndianNOT + clc + adc cellOffsetBitLittleEndian + sta nextState, X +ReadCurrentStateIncCursorTmp: + inc cursorXTmp + lda cursorXTmp + cmp #40 + bcc ReadCurrentStateIncCursorXTmpContinue: + jsr PollController + jsr CheckStartNMIDisabled + cmp #$01 + beq ReadCurrentStateStartPress + lda #$00 + sta cursorXTmp + inc cursorYTmp + lda cursorYTmp + cmp #40 + bcs ReadCurrentStateDone + jmp ReadCurrentStateLoop +ReadCurrentStateStartPress: + lda #$00 + sta running + rts +ReadCurrentStateIncCursorXTmpContinue: + jmp ReadCurrentStateLoop +ReadCurrentStateDone: + jsr NextStateToCurrentState + rts + + + +NextStateToCurrentState: + ldx #$00 +NextStateToCurrentStateLoop + lda nextState, X + sta currentState, X + inx + cpx #200 + bne NextStateToCurrentStateLoop + rts + + +GetNeighborCount: + lda #$00 + sta cellNeighbors +GetNeighborCountTopRow: + lda cellOffsetByte + sec + sbc #$05 + sta cellOffsetByte + tax + dex + lda currentState, X + sta currentCellBuffer + inx + lda currentState, X + sta (currentCellBuffer + 1) + inx + lda currentState, X + sta (currentCellBuffer + 2) + ldx #$07 +GetNeighborCountTopRowLoop: + asl (currentCellBuffer + 2) + rol (currentCellBuffer + 1) + rol currentCellBuffer + dex + bne GetNeighborCountTopRowLoop + ldx cellOffsetBit +GetNeighborCountTopRowOffsetBitLoop: + cpx #$00 + beq GetNeighborCountTopRowContinue + asl (currentCellBuffer + 2) + rol (currentCellBuffer + 1) + rol currentCellBuffer + dex + jmp GetNeighborCountTopRowOffsetBitLoop +GetNeighborCountTopRowContinue: + lda currentCellBuffer + and #%11100000 + ora cellNeighbors + sta cellNeighbors +GetNeighborCountMiddleRow: + lda cellOffsetByte + clc + adc #$05 + sta cellOffsetByte + ldx cellOffsetByte + dex + lda currentState, X + sta currentCellBuffer + inx + lda currentState, X + sta (currentCellBuffer + 1) + inx + lda currentState, X + sta (currentCellBuffer + 2) + ldx #$07 +GetNeighborCountMiddleRowLoop: + asl (currentCellBuffer + 2) + rol (currentCellBuffer + 1) + rol currentCellBuffer + dex + bne GetNeighborCountMiddleRowLoop + ldx cellOffsetBit +GetNeighborCountMiddleRowOffsetBitLoop: + cpx #$00 + beq GetNeighborCountMiddleRowContinue + asl (currentCellBuffer + 2) + rol (currentCellBuffer + 1) + rol currentCellBuffer + dex + jmp GetNeighborCountMiddleRowOffsetBitLoop +GetNeighborCountMiddleRowContinue: + lda currentCellBuffer + and #%10000000 + lsr + lsr + lsr + ora cellNeighbors + sta cellNeighbors + lda currentCellBuffer + and #%00100000 + lsr + lsr + ora cellNeighbors + sta cellNeighbors +GetNeighborCountBottomRow: + lda cellOffsetByte + clc + adc #$05 + sta cellOffsetByte + tax + dex + lda currentState, X + sta currentCellBuffer + inx + lda currentState, X + sta (currentCellBuffer + 1) + inx + lda currentState, X + sta (currentCellBuffer + 2) + ldx #$07 +GetNeighborCountBottomRowLoop: + asl (currentCellBuffer + 2) + rol (currentCellBuffer + 1) + rol currentCellBuffer + dex + bne GetNeighborCountBottomRowLoop + ldx cellOffsetBit +GetNeighborCountBottomOffsetBitLoop: + cpx #$00 + beq GetNeighborCountBottomRowContinue + asl (currentCellBuffer + 2) + rol (currentCellBuffer + 1) + rol currentCellBuffer + dex + jmp GetNeighborCountBottomOffsetBitLoop +GetNeighborCountBottomRowContinue: + lda currentCellBuffer + and #%11100000 + lsr + lsr + lsr + lsr + lsr + ora cellNeighbors + sta cellNeighbors +GetNeighborCountCheckBorder: + lda cursorXTmp + bne GetNeighborCountCheckBorderNotX0 + lda cellNeighbors + and #%01101011 + sta cellNeighbors +GetNeighborCountCheckBorderNotX0: + lda cursorXTmp + cmp #39 + bne GetNeighborCountCheckBorderNotX39 + lda cellNeighbors + and #%11010110 + sta cellNeighbors +GetNeighborCountCheckBorderNotX39: + lda cursorYTmp + bne GetNeighborCountCheckBorderNotY0 + lda cellNeighbors + and #%00011111 + sta cellNeighbors +GetNeighborCountCheckBorderNotY0: + lda cursorYTmp + cmp #39 + bne GetNeighborCountCheckBorderNotY39 + lda cellNeighbors + and #%11111000 + sta cellNeighbors +GetNeighborCountCheckBorderNotY39: + lda cellNeighbors + ldy #$08 + ldx #$00 +GetNeighborCountBitsToCellNeighborsLoop: + rol cellNeighbors + bcc GetNeighborCountBitsToCellNeighborsLoopContinue + inx +GetNeighborCountBitsToCellNeighborsLoopContinue: + dey + bne GetNeighborCountBitsToCellNeighborsLoop + stx cellNeighbors +GetNeighborCountDone: + lda cellOffsetByte + sec + sbc #$05 + sta cellOffsetByte + rts diff --git a/src/controllers.asm b/src/controllers.asm new file mode 100644 index 0000000..bdd32da --- /dev/null +++ b/src/controllers.asm @@ -0,0 +1,262 @@ +;;;;;;;;;; +;; +;; Controller Polling +;; +;;;;;;;;;; + +PollController: + lda controller1 + sta controller1LastFrame + ldx #$01 + stx CONTROLLER1 + dex + stx CONTROLLER1 +PollControllerLoop: + ; $4016 Read (Player 1 and 3) + lda CONTROLLER1 + lsr + rol controller1 + lsr + rol controller1D1 +PollControllerEnd: + inx + cpx #$08 + bne PollControllerLoop + lda controller1 + ora controller1D1 + sta controller1 + rts + + +;;;;;;;;;; +;; +;; Controller Checking +;; +;;;;;;;;;; + +CheckUp: + lda controller1 + and #(BUTTON_DOWN) + bne CheckUpDone + lda controller1 + and #(BUTTON_UP) + beq CheckUpDone + jsr HandleUp +CheckUpDone: + rts + + +CheckDown: + lda controller1 + and #(BUTTON_UP) + bne CheckDownDone + lda controller1 + and #(BUTTON_DOWN) + beq CheckDownDone + jsr HandleDown +CheckDownDone: + rts + + +CheckLeft: + lda controller1 + and #(BUTTON_RIGHT) + bne CheckLeftDone + lda controller1 + and #(BUTTON_LEFT) + beq CheckLeftDone + jsr HandleLeft +CheckLeftDone: + rts + + +CheckRight: + lda controller1 + and #(BUTTON_LEFT) + bne CheckRightDone + lda controller1 + and #(BUTTON_RIGHT) + beq CheckRightDone + jsr HandleRight +CheckRightDone: + rts + + +CheckA: + lda controller1 + and #(BUTTON_A) + beq CheckADone + lda controller1LastFrame + and #(BUTTON_A) + bne CheckADone + ;jsr Blank + jsr FlipCell + ;jsr EnableNMI +CheckADone: + rts + + +CheckStart: + lda controller1 + and #(BUTTON_START) + beq CheckStartDone + lda controller1LastFrame + and #(BUTTON_START) + bne CheckStartDone + lda screen + bne CheckStartNotTitleScreen + lda #$01 + sta screen + lda #$00 + sta running + jsr Blank + jsr ClearPPURAM + jsr SetPalette + jsr DrawBlankBoard + jsr DrawGameOfLifeTitle + jsr DrawJohnConway + jsr SetCursor + jsr EnableNMI + rts +CheckStartNotTitleScreen: + lda #$01 + sta running + jsr DisableSpritesNMI + jsr ReadCurrentState + jsr DrawCurrentState + jsr EnableNMI +CheckStartDone: + rts + + +CheckStartNMIDisabled: + ldy #$00 + lda controller1 + and #(BUTTON_START) + beq CheckStartNMIDisabledDone + lda controller1LastFrame + and #(BUTTON_START) + bne CheckStartNMIDisabledDone + iny +CheckStartNMIDisabledDone: + tya + rts + + +CheckSelect: + lda controller1 + and #(BUTTON_SELECT) + beq CheckSelectDone + lda controller1LastFrame + and #(BUTTON_SELECT) + bne CheckSelectDone + lda randomSet + bne RandomIsSet + jsr Blank + jsr SetRandomCurrentState + jsr DrawCurrentState + jsr EnableNMI + jmp CheckSelectDone +RandomIsSet: + jsr Blank + jsr SetZeroCurrentState + jsr DrawCurrentState + jsr EnableNMI +CheckSelectDone: + rts + + +HandleUp: + lda controller1LastFrame + and #(BUTTON_UP) + bne HandleUpPressedLastFrame + ; Up not pressed last frame + lda #$00 + sta upHold + jmp DoUp +HandleUpPressedLastFrame: + inc upHold + lda upHold + asl + cmp fps + bne HandleUpDone + lda upHold + sec + sbc fpsDiv10 + sta upHold +DoUp: + jsr CursorUp +HandleUpDone: + rts + + +HandleDown: + lda controller1LastFrame + and #(BUTTON_DOWN) + bne HandleDownPressedLastFrame + ; Down not pressed last frame + lda #$00 + sta downHold + jmp DoDown +HandleDownPressedLastFrame: + inc downHold + lda downHold + asl + cmp fps + bne HandleDownDone + lda downHold + sec + sbc fpsDiv10 + sta downHold +DoDown: + jsr CursorDown +HandleDownDone: + rts + + +HandleLeft: + lda controller1LastFrame + and #(BUTTON_LEFT) + bne HandleLeftPressedLastFrame + ; Left not pressed last frame + lda #$00 + sta leftHold + jmp DoLeft +HandleLeftPressedLastFrame: + inc leftHold + lda leftHold + asl + cmp fps + bne HandleLeftDone + lda leftHold + sec + sbc fpsDiv10 + sta leftHold +DoLeft: + jsr CursorLeft +HandleLeftDone: + rts + + +HandleRight: + lda controller1LastFrame + and #(BUTTON_RIGHT) + bne HandleRightPressedLastFrame + ; Right not pressed last frame + lda #$00 + sta rightHold + jmp DoRight +HandleRightPressedLastFrame: + inc rightHold + lda rightHold + asl + cmp fps + bne HandleRightDone + lda rightHold + sec + sbc fpsDiv10 + sta rightHold +DoRight: + jsr CursorRight +HandleRightDone: + rts diff --git a/src/cursor.asm b/src/cursor.asm new file mode 100644 index 0000000..c372cb1 --- /dev/null +++ b/src/cursor.asm @@ -0,0 +1,359 @@ +;;;;;;;;;; +;; +;; Cursor Management +;; +;;;;;;;;;; + +CursorUp: + lda cursorY + beq CursorUpDone + ldx #$00 + ldy #$04 +CursorUpLoop: + lda $200, X + sec + sbc #$04 + sta $200, X + inx + inx + inx + inx + dey + bne CursorUpLoop + dec cursorY +CursorUpDone: + rts + + +CursorDown: + lda cursorY + cmp #39 + beq CursorDownDone + ldx #$00 + ldy #$04 +CursorDownLoop: + lda $200, X + clc + adc #$04 + sta $200, X + inx + inx + inx + inx + dey + bne CursorDownLoop + inc cursorY +CursorDownDone: + rts + + +CursorLeft: + lda cursorX + beq CursorLeftDone + ldx #$00 + ldy #$04 +CursorLeftLoop: + lda $203, X + sec + sbc #$04 + sta $203, X + inx + inx + inx + inx + dey + bne CursorLeftLoop + dec cursorX +CursorLeftDone: + rts + + +CursorRight: + lda cursorX + cmp #39 + beq CursorRightDone + ldx #$00 + ldy #$04 +CursorRightLoop: + lda $203, X + clc + adc #$04 + sta $203, X + inx + inx + inx + inx + dey + bne CursorRightLoop + inc cursorX +CursorRightDone: + rts + + +DisableCursor: + lda #$FE + sta $200 + sta $201 + sta $202 + sta $203 + sta $204 + sta $205 + sta $206 + sta $207 + sta $208 + sta $209 + sta $20A + sta $20B + sta $20C + sta $20D + sta $20E + sta $20F + rts + + +SetCursor: + lda #$71 + sta $200 + lda #$01 + sta $201 + lda #$00 + sta $202 + lda #$7A + sta $203 + lda #$71 + sta $204 + lda #$02 + sta $205 + lda #$00 + sta $206 + lda #$82 + sta $207 + lda #$79 + sta $208 + lda #$11 + sta $209 + lda #$00 + sta $20A + lda #$7A + sta $20B + lda #$79 + sta $20C + lda #$12 + sta $20D + lda #$00 + sta $20E + lda #$82 + sta $20F + lda #20 + sta cursorX + sta cursorY + rts + + +;;;;;;;;;; +;; +;; Cell Management +;; +;;;;;;;;;; + +FlipCell: + ; TODO - Implement + jsr GetCursorCellByteOffset + ldx cellOffsetByte + lda cellOffsetBitLittleEndian + eor currentState, X + sta currentState, X + lda cellOffsetBitLittleEndian + sta cellOffsetBitXOR + lda cursorX + sta cursorXTmp + lda cursorY + sta cursorYTmp + jsr GetPPUTile + rts + + +;;;;;;;;;; +; +; Set cursor offset +GetCursorTmpCellByteOffset: + lda cursorXTmp + and #%00000111 + sta cellOffsetBit + tax + inx + lda #$00 + sec +GetCursorTmpCellOffsetBitLittleEndianLoop: + ror + dex + bne GetCursorTmpCellOffsetBitLittleEndianLoop + sta cellOffsetBitLittleEndian + lda cursorXTmp + ; Divide cursorXTmp by 8 + lsr + lsr + lsr + ldx cursorYTmp +GetCursorYTmpCellByteOffsetLoop: + cpx #$00 + beq GetCursorYTmpCellByteOffsetLoopDone + clc + adc #$05 + dex + jmp GetCursorYTmpCellByteOffsetLoop +GetCursorYTmpCellByteOffsetLoopDone: + tax + stx cellOffsetByte + rts + + +; Set cursor offset +GetCursorCellByteOffset: + lda cursorX + and #%00000111 + sta cellOffsetBit + tax + inx + lda #$00 + sec +GetCursorCellOffsetBitLittleEndianLoop: + ror + dex + bne GetCursorCellOffsetBitLittleEndianLoop + sta cellOffsetBitLittleEndian + lda cursorX + ; Divide cursor X by 8 + lsr + lsr + lsr + ldx cursorY +GetCursorYCellByteOffsetLoop: + cpx #$00 + beq GetCursorYCellByteOffsetLoopDone + clc + adc #$05 + dex + jmp GetCursorYCellByteOffsetLoop +GetCursorYCellByteOffsetLoopDone: + tax + stx cellOffsetByte + rts + + + +GetPPUTile: + lda #$01 + sta cellTileValue + ldx cellOffsetByte + lda cursorYTmp + and #%00000001 + beq GetPPUTileCursorYEven + ; cursorYTmp is odd + ; Go back 1 row + dex + dex + dex + dex + dex +GetPPUTileCursorYEven: + ; cursorYTmp is even + lda cursorXTmp + and #$01 + beq GetPPUTileCursorXEven + ; cursorXTmp is odd + ; Set cellOffsetBitLittleEndian left 1 % 8 bits + asl cellOffsetBitLittleEndian +GetPPUTileCursorXEven: + ; cursorXTmp is even + ; Start tile identification + lda currentState, X + clc + and cellOffsetBitLittleEndian + beq GetPPUTileCellTLIsZero + ; Top Left cell is on + sec +GetPPUTileCellTLIsZero: + ; Top Left cell is off + ; Top Left Tile of 2x2 grid section is 0 + rol cellTileValue + lsr cellOffsetBitLittleEndian + lda currentState, X + clc + and cellOffsetBitLittleEndian + beq GetPPUTileCellTRIsZero + ; Top Right cell is on + sec +GetPPUTileCellTRIsZero: + ; Top Right cell is off + ; Bottom Row of 2x2 grid section + rol cellTileValue + asl cellOffsetBitLittleEndian + inx + inx + inx + inx + inx + lda currentState, X + clc + and cellOffsetBitLittleEndian + beq GetPPUTileCellBLIsZero + ; Bottom Left cell is on + sec +GetPPUTileCellBLIsZero: + ; Bottom Left cell is off + rol cellTileValue + lsr cellOffsetBitLittleEndian + lda currentState, X + clc + and cellOffsetBitLittleEndian + beq GetPPUTileCellBRIsZero + sec +GetPPUTileCellBRIsZero: + rol cellTileValue + jsr GetPPUTileLocation + lda PPU_STATUS + lda ppuAddr + sta PPU_ADDR + lda (ppuAddr + 1) + sta PPU_ADDR + lda cellTileValue + sta PPU_DATA + jsr ResetScroll + rts +GetPPUTileLocation: + lda #$20 + sta ppuAddr + lda #$A6 + sta (ppuAddr + 1) + lda cursorXTmp + lsr + clc + adc (ppuAddr + 1) + sta (ppuAddr + 1) + lda #$00 + adc ppuAddr + sta ppuAddr + lda cursorYTmp + and #%11111110 + lsr + lsr + lsr + lsr + clc + adc ppuAddr + sta ppuAddr + lda cursorYTmp + and #%11111110 + asl + asl + asl + asl + clc + adc (ppuAddr + 1) + sta (ppuAddr + 1) + lda #$00 + adc ppuAddr + sta ppuAddr +GetPPUTileLocationDone: + rts diff --git a/src/defs.asm b/src/defs.asm new file mode 100644 index 0000000..566f345 --- /dev/null +++ b/src/defs.asm @@ -0,0 +1,24 @@ +BUTTON_A = 1 << 7 +BUTTON_B = 1 << 6 +BUTTON_SELECT = 1 << 5 +BUTTON_START = 1 << 4 +BUTTON_UP = 1 << 3 +BUTTON_DOWN = 1 << 2 +BUTTON_LEFT = 1 << 1 +BUTTON_RIGHT = 1 << 0 + +PPU_CTRL = $2000 +PPU_MASK = $2001 +PPU_STATUS = $2002 +PPU_OAM_ADDR = $2003 +PPU_OAM_DATA = $2004 +PPU_SCROLL = $2005 +PPU_ADDR = $2006 +PPU_DATA = $2007 + +OAM_DMA = $4014 +APU_FRAME_COUNTER = $4017 + +CONTROLLER1 = $4016 + +CALLBACK = $FFFA diff --git a/src/game.asm b/src/game.asm new file mode 100644 index 0000000..bd48268 --- /dev/null +++ b/src/game.asm @@ -0,0 +1,195 @@ +DrawBlankBoard: + lda #$20 + sta ppuAddr + lda #$A6 + sta (ppuAddr + 1) + ldx #20 + ldy #20 +DrawBlankBoardLoopPPUADDR: + lda PPU_STATUS + lda ppuAddr + sta PPU_ADDR + lda (ppuAddr + 1) + sta PPU_ADDR + lda #$10 +DrawBlankBoardLoop: + sta PPU_DATA + dey + bne DrawBlankBoardLoop + ldy #20 + jsr IncPPUAddrLine + dex + bne DrawBlankBoardLoopPPUADDR + rts + + +DrawCurrentState: + jsr Blank + lda #$20 + sta ppuAddr + lda #$A6 + sta (ppuAddr + 1) + lda #$00 + sta cursorXTmp + sta cursorYTmp + tax +DrawCurrentStateLoopPPUADDR: + lda PPU_STATUS + lda ppuAddr + sta PPU_ADDR + lda (ppuAddr + 1) + sta PPU_ADDR + ldy #$00 +DrawCurrentStateLoop: +DrawCurrentStateBlock1: + lda #$01 + sta cellTileValue + lda currentState, X + rol + rol cellTileValue + rol + rol cellTileValue + inx + inx + inx + inx + inx + lda currentState, X + rol + rol cellTileValue + rol + rol cellTileValue + lda cellTileValue + sta PPU_DATA + dex + dex + dex + dex + dex +DrawCurrentStateBlock2: + lda #$01 + sta cellTileValue + lda currentState, X + rol + rol + rol + rol cellTileValue + rol + rol cellTileValue + inx + inx + inx + inx + inx + lda currentState, X + rol + rol + rol + rol cellTileValue + rol + rol cellTileValue + lda cellTileValue + sta PPU_DATA + dex + dex + dex + dex + dex +DrawCurrentStateBlock3: + lda #$01 + sta cellTileValue + lda currentState, X + rol + rol + rol + rol + rol + rol cellTileValue + rol + rol cellTileValue + inx + inx + inx + inx + inx + lda currentState, X + rol + rol + rol + rol + rol + rol cellTileValue + rol + rol cellTileValue + lda cellTileValue + sta PPU_DATA + dex + dex + dex + dex + dex +DrawCurrentStateBlock4: + lda #$01 + sta cellTileValue + lda currentState, X + rol + rol + rol + rol + rol + rol + rol + rol cellTileValue + rol + rol cellTileValue + inx + inx + inx + inx + inx + lda currentState, X + rol + rol + rol + rol + rol + rol + rol + rol cellTileValue + rol + rol cellTileValue + dex + dex + dex + dex + dex + lda cellTileValue + sta PPU_DATA + inx + iny + cpy #$05 + bne DrawCurrentStateLine + txa + clc + adc #$05 + tax + cpx #200 + beq DrawCurrentStateDone + ldy #$00 + ; Byte number in row is not 5 + lda (ppuAddr + 1) + clc + adc #$20 + sta (ppuAddr + 1) + lda ppuAddr + adc #$00 + sta ppuAddr + jmp DrawCurrentStateLoopPPUADDR +DrawCurrentStateLine: + jmp DrawCurrentStateLoop +DrawCurrentStateDone: + rts + + + + diff --git a/src/graphics/tileset.chr b/src/graphics/tileset.chr new file mode 100644 index 0000000..0d9ea48 Binary files /dev/null and b/src/graphics/tileset.chr differ diff --git a/src/prg.asm b/src/prg.asm new file mode 100644 index 0000000..a446337 --- /dev/null +++ b/src/prg.asm @@ -0,0 +1,336 @@ +RESET: + sei + cld + ldx #$40 + stx APU_FRAME_COUNTER + ldx #$FF + txs + inx + lda #%00000110 + sta PPU_MASK + lda #$00 + sta PPU_CTRL + stx $4010 + ldy #$00 + + +InitialVWait: + lda PPU_STATUS + bpl InitialVWait +InitialVWait2: + inx + bne InitialVWait2NotIncY + iny +InitialVWait2NotIncY: + lda PPU_STATUS + bpl InitialVWait2 + ldx #$00 + cpy #$09 + bne NotNTSC + lda #60 + inx + jmp InitialVWaitDone +NotNTSC: + lda #50 + cpy #$0A + bne NotPAL + jmp InitialVWaitDone +NotPAL: + ldx #$02 +InitialVWaitDone: + sta fps + stx region + +GetFPSDiv10: + lda #5 + sta fpsDiv10 + lda fps + cmp #50 + beq GetFPSDiv10Done + inc fpsDiv10 +GetFPSDiv10Done: + + +InitializeRAM: + ldx #$00 +InitializeRAMLoop: + lda #$00 + cpx #fps + beq InitializeRAMSkipZeroPage + cpx #fpsDiv10 + beq InitializeRAMSkipZeroPage + cpx #region + beq InitializeRAMSkipZeroPage + sta $0000, x +InitializeRAMSkipZeroPage: + sta $0100, x + sta $0300, x + sta $0400, x + sta $0500, x + sta $0600, x + sta $0700, x + lda #$FE + sta $0200, x + inx + bne InitializeRAMLoop + jsr ClearPPURAM + jsr SetPalette + lda #$00 + sta screen + jsr DisableCursor + jsr DrawTitleScreen + lda #$A6 + ora (lfsr + 1) + sta (lfsr + 1) + jsr LFSR + jsr ResetScroll + + +Forever: + jmp Forever + + +NMI: + lda #$00 + sta PPU_OAM_ADDR + lda #$02 + sta OAM_DMA + lda PPU_STATUS + jsr Draw + ;jsr DrawFrame + jsr ResetScroll + jsr Update +NMIDone: + rti + + +ResetScroll: + lda #$00 + sta PPU_SCROLL + sta PPU_SCROLL + jsr EnableNMI + rts + + +Draw: + lda #%00011110 + sta PPU_MASK + rts + + +DisableNMI: + lda #$00 + sta PPU_CTRL + rts + + +EnableNMI: + lda #%10001000 + sta PPU_CTRL + rts + + +Blank: + lda #%00000110 + sta PPU_MASK + jsr DisableNMI + rts + +DisableSpritesNMI: + lda #%00001110 + sta PPU_MASK + lda #%00001000 + sta PPU_CTRL + rts + +Update: + jsr LFSR + lda screen + beq UpdateTitleScreen + inc $50 + lda running + beq UpdateContinue + jsr DisableSpritesNMI + jsr ReadCurrentState + jsr DrawCurrentState + jsr EnableNMI + rts +UpdateContinue: + jsr PollController + jsr CheckUp + jsr CheckDown + jsr CheckLeft + jsr CheckRight + jsr CheckA + jsr CheckStart + jsr CheckSelect + rts +UpdateTitleScreen: + jsr PollController + jsr CheckStart + rts + + +IncPPUAddrLine: + lda (ppuAddr + 1) + clc + adc #$20 + sta (ppuAddr + 1) + lda ppuAddr + adc #$00 + sta ppuAddr + rts + + +ClearPPURAM: + lda PPU_STATUS + lda #$20 + sta PPU_ADDR + lda #$00 + sta PPU_ADDR + ldy #$04 + ldx #$00 + txa +ClearPPURAMLoop: + sta PPU_DATA + dex + bne ClearPPURAMLoop + ldx #$00 + dey + bne ClearPPURAMLoop + rts + + +SetPalette: + lda PPU_STATUS + lda #$3F + sta PPU_ADDR + lda #$00 + sta PPU_ADDR + ldy #$00 +SetPaletteLoop: + lda #$0F + sta PPU_DATA + lda #$11 + sta PPU_DATA + lda #$03 + sta PPU_DATA + lda #$30 + sta PPU_DATA + iny + cpy #$14 + bne SetPaletteLoop + rts + + +DrawGameOfLifeTitle: + lda PPU_STATUS + lda #$20 + sta PPU_ADDR + lda #$65 + sta PPU_ADDR + lda #<(Title) + sta addr + lda #>(Title) + sta (addr + 1) + ldy #$00 +DrawGameOfLifeTitleLoop: + lda (addr), y + sta PPU_DATA + iny + cpy #21 + bne DrawGameOfLifeTitleLoop + rts + + +DrawJohnConway: + lda PPU_STATUS + lda #$23 + sta PPU_ADDR + lda #$45 + sta PPU_ADDR + lda #<(JohnConway) + sta addr + lda #>(JohnConway) + sta (addr + 1) + ldy #$00 +DrawJohnConwayLoop: + lda (addr), y + sta PPU_DATA + iny + cpy #22 + bne DrawJohnConwayLoop + rts + +DrawTitleScreen: + lda PPU_STATUS + lda #$20 + sta PPU_ADDR + lda #$60 + sta PPU_ADDR + lda #<(TitleScreen) + sta addr + lda #>(TitleScreen) + sta (addr + 1) + ldy #$00 +DrawTitleScreenLoop: + lda addr + cmp #<(TitleScreenDone) + bne DrawTitleScreenNotEndOfTitleScreenText + lda (addr + 1) + cmp #>(TitleScreenDone) + bne DrawTitleScreenNotEndOfTitleScreenText + rts +DrawTitleScreenNotEndOfTitleScreenText: + lda (addr), Y + sta PPU_DATA + lda addr + clc + adc #$01 + sta addr + lda (addr + 1) + adc #$00 + sta (addr + 1) + jmp DrawTitleScreenLoop + rts + + +Title: + .byte "Conway's Game of Life" + +JohnConway: + .byte "John Conway 1937-2020" + + + +TitleScreen: + .byte " Conway's Game of Life " + .byte " " + .byte " Controls " + .byte " " + .byte " * Up/Down/Left/Right: Move " + .byte " Cursor " + .byte " * A: Flip Selected Cell " + .byte " * Select: Randomize/Clear " + .byte " Game Grid " + .byte " * Start: Run/Pause Game " + .byte " * RESET: Go Back to This " + .byte " Screen " + .byte " " + .byte " " + .byte " " + .byte " PRESS START to Start Game " + .byte " " + .byte " " + .byte " " + .byte " NES ROM Programmed in 2020 " + .byte " by Vi Grey @ViGreyTech " + +TitleScreenDone: + + +.include "controllers.asm" +.include "cursor.asm" +.include "cells.asm" +.include "rng.asm" +.include "game.asm" diff --git a/src/ram.asm b/src/ram.asm new file mode 100644 index 0000000..7a77fa6 --- /dev/null +++ b/src/ram.asm @@ -0,0 +1,70 @@ +.enum $0000 + cellTileValue dsb 1 + cellOffsetByte dsb 1 + cellOffsetBit dsb 1 + running dsb 1 + randomSet dsb 1 + + cellOffsetBitLittleEndian dsb 1 + + + cellOffsetBitLittleEndianNOT dsb 1 + cellOffsetBitXOR dsb 1 + cellPPUPosition dsb 1 + + cellNeighbors dsb 1 + cellOffsetByteTmp dsb 1 + cellOffsetBitTmp dsb 1 + + currentCellOffsetByte dsb 1 + currentCellOffsetBit dsb 1 + currentCellBuffer dsb 3 + currentCellValue dsb 1 + + screen dsb 1 + lfsr dsb 4 + fps dsb 1 + fpsDiv10 dsb 1 + region dsb 1 + addr dsb 2 + addrTmp dsb 2 +.ende + +.enum $0300 +CurrentState: + currentState dsb 200 +.ende + +.enum $0400 +NextState: + nextState dsb 200 +.ende + +.enum $0500 + drawBufferOffset dsb 1 + drawBuffer dsb 200 + draw dsb 1 + resumeDrawAddr dsb 2 + ppuAddr dsb 2 + ppuAddrTmp dsb 2 +.ende + +.enum $0600 + ; Controller Data + controller1 dsb 1 + controller1D1 dsb 1 + controller1LastFrame dsb 1 + + cursorX dsb 1 + cursorY dsb 1 + + cursorXTmp dsb 1 + cursorYTmp dsb 1 + + + + upHold dsb 1 + downHold dsb 1 + leftHold dsb 1 + rightHold dsb 1 +.ende diff --git a/src/rng.asm b/src/rng.asm new file mode 100644 index 0000000..68b4253 --- /dev/null +++ b/src/rng.asm @@ -0,0 +1,50 @@ +;;;;;;;;;; +;; +;; MATH +;; +;;;;;;;;;; + +LFSR: + clc + ldx #$08 + lda lfsr +LFSRLoop: + asl + rol (lfsr + 1) + rol (lfsr + 2) + rol (lfsr + 3) + bcc LFSRNot1Shifted + eor #$C5 +LFSRNot1Shifted: + dex + bne LFSRLoop + sta lfsr + rts + + +SetZeroCurrentState: + ldx #$00 + lda #$00 +SetZeroCurrentStateLoop: + sta currentState, X + inx + cpx #200 + bne SetZeroCurrentStateLoop + lda #$00 + sta randomSet + rts + + +SetRandomCurrentState: + ldx #$00 +SetRandomCurrentStateLoop: + stx cellOffsetByteTmp + jsr LFSR + ldx cellOffsetByteTmp + sta currentState, X + inx + cpx #200 + bne SetRandomCurrentStateLoop + lda #$01 + sta randomSet + rts