I've finished Snake! For this one, I wrote the game in BASIC first to get an idea of how it would work, which turned out to be a good choice since I struggled quite a bit to get the algorithm right (I've never made Snake and arrays aren't my strong suit). Having the solution massively helped since I knew any bugs were the fault of my implementation instead of the core functions, and I didn't have to tediously refactor to try new approaches all the time. It was still a pretty large undertaking though, no one part was particularly difficult, it was just a battle of attrition getting everything in and working properly together. I may start breaking up anything larger into separate files since it was annoying having to scroll all the way to the bottom to see my variables near the end. Overall, I'm pretty happy with how it turned out, everything seems to work without any major bugs, and I feel like I got a good amount of experience in dealing with ASM arrays. I'm considering remaking Space Invaders next, but I'm not 100% sure yet.
Code: .nolist
#include "ti83plus.inc"
.list
.org userMem-2
.db $BB,$6D
; Only run once at the start of the game, basic setup stuff
;------------------------
Start:
LD (Seed), DE
BCALL(_ClrLCDFull)
BCALL(_HomeUp)
LD HL, TitleScreenText
BCALL(_PutS)
TitleLoop
BCALL(_GetCSC)
CP 0
JR NZ, SetupGame
JR TitleLoop
SetupGame:
LD A, 0
LD B, 255
ClearArrayLoop:
LD HL, SnakeTailArray
LD D, 0
LD E, B
ADD HL, DE
LD (HL), A
DJNZ ClearArrayLoop
JR ContinueSetup
ContinueSetup:
LD A, 0
LD (DoesAppleExist), A
LD (SnakeHeadXPosition), A
LD (SnakeHeadYPosition), A
LD (TransitionToEnd), A
LD (SnakeTailArray+(1*ArrayElementSize)), A
LD A, 1
LD (SnakeTailArray+(0*ArrayElementSize)), A
LD (SnakeLength), A
LD A, 3
LD (SnakeDirection), A
;------------------------
; Primary game loop, basically just a list of CALLs to everything else - WORKING
;------------------------
GameLoop:
BCALL(_ClrLCDFull)
CALL ShiftArray
LD A, (DoesAppleExist)
CP 0
CALL Z, CreateApple ; checks if DoesAppleExist equals 0 and jumps to a procedure to create one if it does
LD A, (AppleYPosition)
LD (CurRow), A
LD A, (AppleXPosition)
LD (CurCol), A
LD A, 'X'
BCALL(_PutMap) ; Draws apple to screen
CALL InputHandling
CALL UpdateSnakeXYPosition ; Updates coordinates of head based on direction
LD A, (SnakeHeadYPosition)
LD (SnakeTailArray+(0*ArrayElementSize)), A
LD A, (SnakeHeadXPosition)
LD (SnakeTailArray+(1*ArrayElementSize)), A ; Loads Y and X positions into the first and second values of the array respectively
CALL OutputSnake ; Draws snake + tail to the screen
CALL TestSnakeAgainstTail ; Sees if the snake is colliding with itself
LD A, (TransitionToEnd)
CP 1
JR Z, EndGame
LD A, (SnakeLength)
CP 120 ; Going all the way to 128 is going to cause a killscreen due to only using A to store length, and 120 seems like a fair place to stop the game
JR Z, EndGame
CALL DetectAppleCollision
CALL WasteTime
JR GameLoop
RET
;------------------------
; Gets input - WORKING
;------------------------
InputHandling:
LD A, %11111110
OUT (1), A
NOP
NOP
IN A, (1)
CP %11111110
CALL Z, ChangeDirectionDown
CP %11111101
CALL Z, ChangeDirectionLeft
CP %11111011
CALL Z, ChangeDirectionRight
CP %11110111
CALL Z, ChangeDirectionUp ; Reads key input and updates snake direction
RET
;------------------------
; Slows down game to make playing practical - WORKING
;------------------------
WasteTime:
LD B, 30
WasteTimeLoop:
LD A, 0
WasteTimeLoop2:
PUSH AF
CALL InputHandling
POP AF
INC A
CP 255
JR NZ, WasteTimeLoop2
DJNZ WasteTimeLoop
RET
;------------------------
; Checks if the snake is eating an apple - WORKING
;------------------------
DetectAppleCollision:
LD A, (SnakeHeadYPosition)
LD C, A
LD A, (AppleYPosition)
CP C
JR NZ, NoCollision
LD A, (SnakeHeadXPosition)
LD C, A
LD A, (AppleXPosition)
CP C
JR Z, CollisionExists
JR NoCollision
CollisionExists:
LD A, 0
LD (DoesAppleExist), A
LD A, (SnakeLength)
INC A
LD (SnakeLength), A
NoCollision:
RET
;------------------------
; Displays score and exits program
;------------------------
EndGame:
BCALL(_ClrLCDFull)
BCALL(_HomeUp)
LD HL, EndScreenText
BCALL(_PutS)
LD A, (SnakeLength)
LD H, 0
LD L, A
BCALL(_DispHL)
BCALL(_NewLine)
RET
;------------------------
; Tests if the snake is colliding with any of its tail - WORKING
;------------------------
TestSnakeAgainstTail:
LD A, (SnakeLength)
LD B, A ; No doubling needed since both Y and X are handled in each loop
LD A, 2 ; Needs to count up, so I need to use a second counter - starts 2 because 0 and 1 are just the X and Y values
TestSnakeAgainstTailLoop
LD HL, SnakeTailArray ; loads in base address of array
LD D, 0
LD E, A ; Loads index into DE
ADD HL, DE ; Add index, HL now has value of address I need
PUSH AF ; Get rid of A for now
LD C, (HL)
LD A, (SnakeHeadYPosition)
CP C
JR NZ, ContinueTest ; No need to check further if the first value didn't match
SecondCheck
INC HL
LD C, (HL)
LD A, (SnakeHeadXPosition)
CP C
JR Z, PlayerHasLost ; Need to use variable to avoid a memory leak
ContinueTest
POP AF
INC A
INC A ; increase A by 2 since 2 values are needed for each set
DJNZ TestSnakeAgainstTailLoop
RET
PlayerHasLost
POP AF
LD A, 1
LD (TransitionToEnd), A
RET
;------------------------
; Draws snake and tail to the screen - WORKING
;------------------------
OutputSnake:
LD A, (SnakeLength)
LD B, A ; No doubling needed since both Y and X are handled in each loop
LD A, 0 ; Needs to count up, so I need to use a second counter
OutputSnakeLoop:
LD HL, SnakeTailArray ; loads in base address of array
LD D, 0
LD E, A ; Loads index into DE
ADD HL, DE ; Add index, HL now has value of address I need
PUSH AF ; Get rid of A for now
LD A, (HL)
LD (CurRow), A ; Fetch Y value and put it in (CurRow)
INC HL
LD A, (HL)
LD (CurCol), A ; Ditto for the X value
LD A, 'O'
BCALL(_PutMap)
POP AF
INC A
INC A ; increase A by 2 since 2 values are needed for each set
DJNZ OutputSnakeLoop
RET
;------------------------
; Updates the coordinates of the snake's head - WORKING
;------------------------
UpdateSnakeXYPosition:
LD A, (SnakeDirection)
CP 1
JR Z, MoveSnakeDown
CP 2
JR Z, MoveSnakeLeft
CP 3
JR Z, MoveSnakeRight
CP 4
JR Z, MoveSnakeUp
MoveSnakeDown:
LD A, (SnakeHeadYPosition)
INC A
LD (SnakeHeadYPosition), A
JR CheckForEdges
MoveSnakeLeft:
LD A, (SnakeHeadXPosition)
DEC A
LD (SnakeHeadXPosition), A
JR CheckForEdges
MoveSnakeRight:
LD A, (SnakeHeadXPosition)
INC A
LD (SnakeHeadXPosition), A
JR CheckForEdges
MoveSnakeUp:
LD A, (SnakeHeadYPosition)
DEC A
LD (SnakeHeadYPosition), A
CheckForEdges:
CheckVerticalEdge:
LD A, (SnakeHeadYPosition)
CP 8
JR Z, ResetYPositionTo7
CP 9
JR NC, ResetYPositionTo0 ; If the player tries going off the top of the screen, their position will wrap around from 0 to 255, ie: over 9
JR CheckHorizontalEdge
ResetYPositionTo7:
LD A, 7
LD (SnakeHeadYPosition), A
JR CheckHorizontalEdge
ResetYPositionTo0:
LD A, 0
LD (SnakeHeadYPosition), A
CheckHorizontalEdge:
LD A, (SnakeHeadXPosition)
CP 16
JR Z, ResetXPositionTo15
CP 17
JR NC, ResetXPositionTo0
RET
ResetXPositionTo15:
LD A, 15
LD (SnakeHeadXPosition), A
RET
ResetXPositionTo0:
LD A, 0
LD (SnakeHeadXPosition), A
RET
;------------------------
; Updates snake's direction - WORKING
;------------------------
ChangeDirectionDown:
LD A, 1
LD (SnakeDirection), A
RET
ChangeDirectionLeft:
LD A, 2
LD (SnakeDirection), A
RET
ChangeDirectionRight:
LD A, 3
LD (SnakeDirection), A
RET
ChangeDirectionUp:
LD A, 4
LD (SnakeDirection), A
RET
;------------------------
; Generates coordinates for apple and stores them in AppleYPosition and AppleXPosition - WORKING
;------------------------
CreateApple:
GenerateAppleYValue:
CALL RandomNumberGenerator
CP 0
JR Z, GenerateAppleYValue ; Ensures value is over 1
CP 31
JR C, SetAppleYValueTo0
CP 63
JR C, SetAppleYValueTo1
CP 95
JR C, SetAppleYValueTo2
CP 127
JR C, SetAppleYValueTo3
CP 159
JR C, SetAppleYValueTo4
CP 191
JR C, SetAppleYValueTo5
CP 223
JR C, SetAppleYValueTo6
JR SetAppleYValueTo7
SetAppleYValueTo0:
LD A, 0
JR LoadAppleYValue
SetAppleYValueTo1:
LD A, 1
JR LoadAppleYValue
SetAppleYValueTo2:
LD A, 2
JR LoadAppleYValue
SetAppleYValueTo3:
LD A, 3
JR LoadAppleYValue
SetAppleYValueTo4:
LD A, 4
JR LoadAppleYValue
SetAppleYValueTo5:
LD A, 5
JR LoadAppleYValue
SetAppleYValueTo6:
LD A, 6
JR LoadAppleYValue
SetAppleYValueTo7:
LD A, 7
LoadAppleYValue:
LD (AppleYPosition), A
GenerateAppleXValue:
CALL RandomNumberGenerator
CP 15
JR C, SetAppleXValueTo0
CP 31
JR C, SetAppleXValueTo1
CP 47
JR C, SetAppleXValueTo2
CP 63
JR C, SetAppleXValueTo3
CP 79
JR C, SetAppleXValueTo4
CP 95
JR C, SetAppleXValueTo5
CP 111
JR C, SetAppleXValueTo6
CP 127
JR C, SetAppleXValueTo7
CP 143
JR C, SetAppleXValueTo8
CP 159
JR C, SetAppleXValueTo9
CP 175
JR C, SetAppleXValueTo10
CP 191
JR C, SetAppleXValueTo11
CP 207
JR C, SetAppleXValueTo12
CP 223
JR C, SetAppleXValueTo13
CP 239
JR C, SetAppleXValueTo14
JR SetAppleXValueTo15
SetAppleXValueTo0:
LD A, 0
JR LoadAppleXValue
SetAppleXValueTo1:
LD A, 1
JR LoadAppleXValue
SetAppleXValueTo2:
LD A, 2
JR LoadAppleXValue
SetAppleXValueTo3:
LD A, 3
JR LoadAppleXValue
SetAppleXValueTo4:
LD A, 4
JR LoadAppleXValue
SetAppleXValueTo5:
LD A, 5
JR LoadAppleXValue
SetAppleXValueTo6:
LD A, 6
JR LoadAppleXValue
SetAppleXValueTo7:
LD A, 7
JR LoadAppleXValue
SetAppleXValueTo8:
LD A, 8
JR LoadAppleXValue
SetAppleXValueTo9:
LD A, 9
JR LoadAppleXValue
SetAppleXValueTo10:
LD A, 10
JR LoadAppleXValue
SetAppleXValueTo11:
LD A, 11
JR LoadAppleXValue
SetAppleXValueTo12:
LD A, 12
JR LoadAppleXValue
SetAppleXValueTo13:
LD A, 13
JR LoadAppleXValue
SetAppleXValueTo14:
LD A, 14
JR LoadAppleXValue
SetAppleXValueTo15:
LD A, 15
LoadAppleXValue:
LD (AppleXPosition), A
LD A, 1
LD (DoesAppleExist), A
RET
;------------------------
; Shifts all values in SnakeTailArray 2 spaces forward, starting from the end and moving towards the front - WORKING
;------------------------
ShiftArray:
LD A, (SnakeLength)
SLA A ; every 1 pixel needs 2 values, so we need to multiply SnakeLength by 2 to find the end of the list
LD B, A
ShiftArrayLoop
LD D, 0
PUSH BC
DEC B
LD E, B
POP BC
LD HL, SnakeTailArray ; Loads in base address for array
ADD HL, DE ; Loads in the current value, starts from end
LD A, (HL) ; Save current value in A
INC HL
INC HL ; Shift up 2 values
LD (HL), A ; Store saved value in address 2 above original address
DJNZ ShiftArrayLoop
RET
;------------------------
; Copy & pasted from WikiTI
;------------------------
RandomNumberGenerator:
PUSH HL
PUSH DE
LD DE,(Seed)
LD A, R
LD D, A
LD E, (HL)
ADD HL, DE
ADD A, L
XOR H
LD (Seed), HL
POP DE
POP HL
RET
;------------------------
;Data
;------------------------
TitleScreenText:
.DB " SNAKE "
.DB "BY: JOHN CAMOU "
.DB "PRESS ANY BUTTON",0
EndScreenText:
.DB "SCORE: ",0
SnakeDirection:
.EQU AppData ; 1 byte
SnakeHeadYPosition:
.EQU SnakeDirection + 1 ; 1 byte
SnakeHeadXPosition:
.EQU SnakeHeadYPosition + 1 ; 1 byte
SnakeLength:
.EQU SnakeHeadXPosition + 1 ; 1 byte
DoesAppleExist:
.EQU SnakeLength + 1 ; 1 bytes
AppleYPosition:
.EQU DoesAppleExist + 1 ; 1 bytes
AppleXPosition:
.EQU AppleYPosition + 1 ; 1 bytes
Seed:
.EQU AppleXPosition + 1 ; 2 bytes
TransitionToEnd:
.EQU Seed + 2 ; 1 byte
SnakeTailArray:
.EQU TransitionToEnd + 2 ; 255 bytes
ArrayElementSize
.EQU 1
;------------------------