Search code examples
assemblymasm32

Making a Wrong Turn Makes the Snake Die (assembly)


My snake game is almost close to running smoothly. There is just this one bug where my snake dies if I made a 'wrong' turn. For example, when my snake is going right then I press up then left, it dies. However, when I press up then right, it lives. My approach in checking where the parts are to be printed depends on the previous character read on the previous page. If, for example, the previous character read on the previous page is '>', it will read the character on its left next then print the read character on the alternate page. Here's my code:

.model small
.data
mode db                 03h
startPosX db            39
startPosY db            12
myChar db               14
wdir db                 119, 48h, 87
sdir db                 115, 50h, 83
adir db                 97, 4bh, 65
ddir db                 100, 4dh, 68
curColor db             12h
cycle db                0
startPrompt db          'Press any key to start the game n___n', '$'
index dw              0
recycleTime EQU         100
maxX dw                 80
maxY dw                 25
food db                 01h
prevkey    db           ?
input db                ?
xpos db                 ?
ypos db                 ?
foodPosX db             ?
foodPosY db             ?
cloneXpos db            ?
cloneYpos db            ?
snakeLength db          5
partCount   db          1
curPage db              0
altPage db              1
cloneAltPage db         ?
boolCopied db           0
upChar db               '^'
downChar db             'v'
leftChar db             '<'
rightChar db            '>'
promptRetry db 'Want to play again?', '$'
choices db 'Yes       No', '$'
promptTY db 'Thank you for playing! n___n', '$'
arrowSelect db 16
mychoice db 'y'
.stack 100h
.code

loadPage macro page
    mov al, page
    mov ah, 05h
    int 10h
endm

setCursorPos macro x, y, page
    mov dh, y
    mov dl, x
    mov bh, page
    mov ah, 02h
    int 10h
endm

printChar macro char, color, page
    mov al, char
    mov bh, page
    mov bl, color
    xor cx, cx
    mov cx, 1
    mov ah, 09h
    int 10h
endm

loadPrompts proc
    call clearScreen
    call clearRegisters
    setCursorPos 30, 12, curPage
    lea dx, promptRetry
    mov ah, 09h
    int 21h

    setCursorPos 34, 13, curPage
    lea dx, choices
    mov ah, 09h
    int 21h
    ret
loadPrompts endp

printArrow proc
    setCursorPos xpos, ypos, curPage
    printChar arrowSelect, 0fh, curPage
    ret
printArrow endp

loadTYPrompt proc
    call clearscreen
    call clearRegisters
    mov xpos, 26

    setCursorPos xpos, 12, curPage
    lea dx, promptTY
    mov ah, 09h
    int 21h

    mov ah, 00h
    int 16h

    call clearScreen

    mov ax, 4c00h
    int 21h
    ret
loadTYPrompt endp

copy macro dest, source
    mov cl, source
    mov dest, cl
endm

gameover proc
    call clearScreen
    call loadPrompts
    setCursorPos 32, 13, curPage
    printChar arrowSelect, 0fh, curPage

    choose:
    mov ah, 00h
    int 16h
    mov input, al
    cmp al, 'a'
    je chooseYes
    cmp al, 'd'
    je chooseNo

    jmp done

    chooseYes:
    mov mychoice, 'y'
    mov xpos, 32
    mov ypos, 13
    jmp done

    chooseNo:
    mov mychoice, 'n'
    mov xpos, 42
    mov ypos, 13

    done:
    call clearScreen
    call loadPrompts
    call printArrow
    cmp input, 13
    jne choose

    cmp mychoice, 'y'
    je playagain

    call loadTYPrompt

    playagain:
    call clearRegisters
    mov snakeLength, 5
    mov partCount, 1
    loadPage altPage
    copy cloneAltPage, altPage
    copy altPage, curPage
    copy curPage, cloneAltPage
    mov boolCopied, 0
    mov input, 'd'
    mov prevkey, 'd'
    ret
gameover endp

random macro maxCoor
    mov ah, 00h     
    int 1ah         

    mov  ax, dx
    xor  dx, dx
    mov  cx, maxCoor     
    div  cx         
endm

scanChar proc
    mov ah, 08h
    int 10h
    ret
scanChar endp

keystroke proc
    mov ah, 01h
    int 16h
    jz nopress

    mov ah, 00h
    int 16h

    cmp al, 00h
    jne wsadInput

    mov input, ah
    jmp nopress

    wsadInput:
    mov input, al
    nopress:
    ret
keystroke endp

spawnfood proc
    cmp cycle, 0
    jne setFood

    setFoodPos:
    random maxX
    mov foodPosX, dl
    random maxY
    mov foodPosY, dl

    setFood:
    setCursorPos foodPosX, foodPosY, curPage
    mov ah, 08h
    int 10h

    cmp ah, 12h
    je setFoodPos

    spawn:
    printChar food, 0fh, curPage
    ret
spawnfood endp

waitAmillisec proc
    mov cx, 1h
    mov dx, 0f4h
    mov ah, 86h
    int 15h
    inc cycle
    cmp cycle, recycleTime
    jle proceed

    mov cycle, 0

    proceed:
    ret
waitAmillisec endp

clearRegisters proc
    xor ax, ax
    xor bx, bx
    xor cx, cx
    xor dx, dx
    ret
clearRegisters endp

clearScreen proc
    mov ax, 0600h
    mov bh, 07h
    xor cx, cx
    mov dx, 184fh
    int 10h
    ret
clearScreen endp

initgraphics proc
    mov al,mode         
    mov ah,00                 
    int 10h              
    ret
initgraphics endp

closegraphics proc
    mov ax, 0003h      
    int 10h
    ret
closegraphics endp

main    proc

mov ax, @data
mov ds, ax

call initgraphics

mov cx, 3200h
mov ah, 01h
int 10h

setCursorPos 22, 12, curPage
lea dx, startPrompt
mov ah, 09h
int 21h

mov ah, 00h
int 16h

mov input, 'd'
mov prevkey, 'd'
call clearScreen

gameStart:
setCursorPos startPosX, startPosY, curPage
mov xpos, dl
mov ypos, dh

start:
printChar myChar, curColor, curPage
inc xpos
setCursorPos xpos, ypos, curPage
inc partCount
mov cl, partCount
cmp cl, snakeLength
jle start

dec xpos
mov partCount, 1

readchar:
call keystroke

mov index, 0
call clearRegisters

cmpDir:
mov bx, index
mov al, input
cmp al, [ddir+bx]
je moveright
cmp al, [wdir+bx]
je moveup
cmp al, [sdir+bx]
je movedown
cmp al, [adir+bx]
je moveleft
inc index
cmp index, 3
jl cmpDir

mov index, 0
copy input, prevkey
jmp cmpDir

moveup:
    cmp prevkey, 's'
    je movedown

    mov prevkey, 'w'
    copy mychar, upChar
    cmp ypos, 0
    jne up

    mov ypos, 24
    copy cloneYPos, snakeLength
    jmp keepmoving

    up:
    dec ypos
    jmp keepmoving

movedown:
    cmp prevkey, 'w'
    je moveup

    mov prevkey, 's'
    copy mychar, downChar
    cmp ypos, 24
    jne down

    mov ypos, 0
    copy cloneYPos, snakeLength
    jmp keepmoving

    down:
    inc ypos
    jmp keepmoving

moveleft:
    cmp prevkey, 'd'
    je moveright

    mov prevkey, 'a'
    copy mychar, leftChar
    cmp xpos, 0
    jne left

    mov xpos, 79
    mov cl, snakeLength
    copy cloneXPos, xpos
    sub cloneXPos, cl
    jmp keepmoving

    left:
    dec xpos
    jmp keepmoving

moveright:
    cmp prevkey, 'a'
    je moveleft

    mov prevkey, 'd'
    copy mychar, rightChar
    cmp xpos, 79
    jne right

    mov xpos, 0
    copy cloneXPos, snakeLength
    jmp keepmoving

    right:
    inc xpos

keepmoving:
    call spawnfood
    setCursorPos xpos, ypos, altPage

    call scanChar
    cmp ah, curColor
    jne notDeads

    call gameOver
    jmp gameStart

    notDeads:
    printChar myChar, curColor, altPage

    mov cl, xpos
    cmp cl, foodPosX
    jne copyHeadPos

    mov cl, ypos
    cmp cl, foodPosY
    jne copyHeadPos

    add snakeLength, 2
    mov cycle, 0

    copyHeadPos:
    cmp boolCopied, 0
    jne keeplooking

    copy cloneXPos, xpos
    copy cloneYPos, ypos
    mov boolCopied, 1

    keepLooking:
    mov cl, partCount
    printbody:
        cmp cl, snakeLength
        je move
        call scanChar
        cmp al, rightChar
        je checkForRightPart
        cmp al, upChar
        je checkForUpPart
        cmp al, downChar
        je checkForDownPart
        cmp al, leftChar
        je checkForLeftPart

        checkForUpPart:
            cmp ypos, 24
            jne goUp

            mov ypos, 0
            jmp checkPrevChar

            goUp:
            inc ypos
        jmp checkPrevChar

        checkForDownPart:
            cmp ypos, 0
            jne goDown

            mov ypos, 24
            jmp checkPrevChar

            goDown:
            dec ypos
        jmp checkPrevChar

        checkForLeftPart:
            cmp xpos, 79
            jne goLeft

            mov xpos, 0
            jmp checkPrevChar

            goLeft:
            inc xpos
        jmp checkPrevChar

        checkForRightPart:
            cmp xpos, 0
            jne goRight

            mov xpos, 79
            jmp checkPrevChar

            goRight:
            dec xpos

        checkPrevChar:
        setCursorPos xpos, ypos, curPage
        mov ah, 08h
        int 10h
        inc partCount
        mov cl, partCount
        cmp cl, snakeLength
        jg move 
        cmp al, rightChar
        je printRight
        cmp al, upChar
        je printUp
        cmp al, downChar
        je printDown
        cmp al, leftChar
        je printLeft

        jmp move

        printUp:
        inc ypos
        jmp moveup

        printDown:
        dec ypos
        jmp movedown

        printLeft:
        inc xpos
        jmp moveleft

        printRight:
        dec xpos
        jmp moveRight
move:
call clearScreen
call spawnfood
copy xpos, cloneXPos
copy ypos, cloneYPos
loadPage altPage
copy cloneAltPage, altPage
copy altPage, curPage
copy curPage, cloneAltPage
mov partCount, 1
mov boolCopied, 0
call waitamillisec
jmp readchar

mov ax, 4c00h
int 21h

main    endp
end main

Your help will be very much appreciated n___n

EDIT:

I did some trial and error process in looking for the bug in the code and it turns out... the one that's causing the bug is something of these lines (pointed by the arrow):

move(insert direction here):
    cmp prevkey, 'a' <-
    je moveleft <-

When I tried it, the snake already moves properly O.O I didn't think that those are the lines causing the bug. Btw, what those lines do is that it checks whether the key pressed would make the snake go opposite the direction it's currently going. (like if I press left when the snake is going right).

Anyway, to those who helped me in finding the bug... thank you so much ;___;

I guess the only problem now is checking if the user inputs a key that would make the snake go in a direction opposing the direction it's currently going (like if the snake is going left and the user pressed the 'right' key).


Solution

  • I solved your problem. Long live the snake!

    Each of the four labels moveup, movedown, moveleft, and moveright is reached in 2 distinct ways in your program. Firstly when the user pushes a key and secondly due to your internal logic to actually draw the snake. The checks you placed at the forementionned labels need only be executed in the first case. Just add four extra labels and adjust four jumps. I'll show it for the up direction only.

       ...
    moveup:
       cmp prevkey, 's'
       je movedown
       mov prevkey, 'w'
    moveup_draw:
       copy mychar, upChar
       ...
    

    and further down in the program

       ...
       jmp move
    printUp:
       inc ypos
       jmp moveup_draw
       ...
    

    I ran your program with these changes applied and the snake works beautifully.

    ps The GameOver routine does exhibit a flaw when the user pushes a key other than 'a', 'd', or CR. The selection arrow appears at the wrong place. Make sure xpos and ypos have a value according to the current setting of mychoice.

    ...
    cmp al, 'd'
    je chooseNo
                 ; Here is something missing!
    jmp done