this code is for snake game. the proc detect _ direction calls mov_snake proc that does many things and ends up with calling detect_direction . when I run the code in DosBox (the most updated version) the snake location is being updated twice and the the code stoprun (not via the endgame proc, the line c:tasm/bin/ shows up again)
If you have any idea please comment it.
ideal
model small
stack 100h
dataseg
message db 'game over, to play again press y$'
object_location dw 2
clock equ es:6ch
longer db 0
snake dw 2000 dup (0)
codeseg
proc cleanscreen ; cleans the screen
push cx
push bx
mov cx,4000d
mov bx,0
clean: mov [byte ptr es:bx],0
inc bx
loop clean
pop bx
pop cx
ret
endp cleanscreen
proc startgame
mov ax, 0b800h
mov es, ax
call cleanscreen
mov dl, '*'
mov dh,200
mov di,2000d;snake head first position
mov [es:di], dx
mov [es:di-2], dx
mov [es:di-4], dx
mov [word ptr snake],1996d
mov [word ptr snake 2], 1998d
mov [word ptr snake 4], 2000d
mov cx, 3
call random
mov ah,0
int 16h
call detect_direction
ret
endp startgame
proc delay
push cx
push bx
mov cx, 0ffffh ;delay loop
lopa:
mov bx, 20d
lopb:
dec bx
cmp bx, 0
jnz lopb
loop lopa
pop bx
pop cx
ret
endp delay
proc buffer
mov ah,1
int 16h
jz nothingchanged
mov ah,0
int 16h
nothingchanged:
ret
endp buffer
proc detect_direction
cmp al, 'w'
je up
cmp al, 'a'
je left
cmp al, 'd'
je right
cmp al, 's'
je down
cmp al, 'q'
je gameover
up: sub di,160
jmp move_on
down: add di,160d
jmp move_on
right: add di,2
jmp move_on
left: sub di,2
jmp move_on
gameover:call endgame
move_on:
call move_func
ret
endp detect_direction
proc updatesnake
push bx
push si
mov bx, offset snake
cmp [longer],1 ;if longer is one , increase snake length
je increa
mov si, [bx]
mov [word ptr es:si], 0 ; delete tail
sub bx,2
increa:
mov [es:di], dx
add bx,2 ; prevent tail value from being deleted when being increased
updateloop:
mov si, [bx 2]
mov [bx], si
inc bx
loop updateloop
mov [bx], di
cmp [longer],1
jne reg
inc cx ; this prevent inseting a junk value when increasing snake's length
reg:
pop si
pop bx
ret
endp updatesnake
proc move_func
call updatesnake
call delay
call boundries
call check_if_eaten
call buffer
call detect_direction
ret
endp move_func
proc boundries
push ax
push dx
cmp di,160d
ja keep_playing
cmp di,3840d
jb keep_playing
mov ax,di
mov dl,160d
div dl
cmp ah,0
jne keep_playing
mov ax,di
inc ax
div dl
cmp ah,0
jne keep_playing
call endgame
keep_playing:
pop dx
pop ax
ret
endp boundries
proc random
push ax
push bx
push cx
push si
push dx
mov ax, [clock] ; read timer counter
mov cx, [word cs:bx] ; read one byte from memory
xor si, cx ; xor memory and counter
and si, 2000d ; leaves value between 0-2000
sal si,1 ; mult value
mov [object_location], si ; save in memory for future checking
mov dl,'@'
mov dh, 150
mov [es:si],dx
pop dx
pop si
pop cx
pop bx
pop ax
ret
endp random
proc check_if_eaten
mov [longer],0
cmp di, [object_location]
jne skip_increase
call random
mov [longer], 1
skip_increase:
ret
endp check_if_eaten
proc endgame
push dx
call cleanscreen
mov dx, offset message
mov ah,9h
int 21h
mov ah,0
int 16
cmp al, 'y'
jne line
call startgame
line:
pop dx
ret
endp endgame
start:
mov ax, @data
mov ds, ax
call startgame
exit:
mov ax, 4c00h
int 21h
end start
CodePudding user response:
Your code has much improved since your previous question, but because of the many errors that are still in there, I doubt it if someone could fully answer your "WHY???"
I will focus on one of your procedures, boundries, where you want to end the game if the snake collides with the top- or bottom rows or left- or right columns
The first test, cmp di,160
ja keep_playing
, means that you keep playing provided the snake does not touch the top row of the screen. None of the other borders is even verified!
What you need if inverting the conditional jump like in cmp di, 160
jb stop_playing
.
In the fourth test (for the right side border) you divide an odd number (because of the inc ax
) by 160. This will always yield a non-zero remainder. So, not a useful test. To test for left- and right borders, you can do with just one division and interpret the remainder twice: 0 is a left side collision and 158 is a right side collision.
proc boundries
push ax
push dx
cmp di, 160 ; Top row
jb stop_playing
cmp di, 3840 ; Bottom row
ja stop_playing
mov ax, di
mov dl, 160
div dl
cmp ah, 0 ; Left column
je stop_playing
cmp ah, 158 ; Right column
jne keep_playing ; All 4 tests passed!
stop_playing:
call endgame
keep_playing:
pop dx
pop ax
ret
endp boundries
As a bonus the random procedure, where you read the clock in the AX
register, but then suddenly you don't use AX
and start using SI
instead!
The
mov ax, [clock] ; read timer counter
instruction will read the true clock as defined by yourclock equ es:6ch
equate if you temporarily set theES
segment register to the BIOS data page at 0040h:push es mov ax, 0040h mov es, ax mov ax, [clock] ; read timer counter pop es
An
AND
instruction likeand si, 2000 ; leaves value between 0-2000
will not limit the value the way you expect! Restricting to that particular range requires dividing by 2001 and using the remainder.
This is my version of the new random procedure:
proc random
push ax ; \
push bx ; | (i)
push dx ; /
push ds ; (ii)
xor dx, dx ; The word-sized `DIV` division requires this
mov ds, dx
mov ax, [046Ch] ; (iii) read timer counter
pop ds
xor ax, [cs:bx] ; XOR counter and a (randomly chosen?) WORD from memory
mov bx, 2000 ; (iiii)
div bx ; DX:AX / BX -> Remainder in DX is [0,1999]
shl dx, 1 ; Convert into text video offset
mov bx, dx ; Move to an address register
mov [object_location], bx
mov word [es:bx], 9640h ; Character @ with attribute 96h (150)
pop dx
pop bx
pop ax
ret
endp random
(i) Less registers used == Less registers that need preserving!
(ii) Using DS
gives shorter code.
(iii) Stop using the 'confusing' clock equ that has a es:
segment override in it.
(iiii) To stay on the screen, the object_location should be restricted to {0, 2, 4, 6, ... , 3998].
I recently posted a Q/A The quintessential Snake Game. How to keep track of the snake?. Maybe that you could get one or two ideas from it...