为什么 proc 运行两次而不是(几乎)无限循环?
Why a proc runs twice instead of an (almost) infinite loop?
此代码适用于贪吃蛇游戏。 proc detect _ direction 调用 mov_snake proc
很多事情,最后调用 detect_direction 。当我 运行 DosBox(最新版本)中的代码时,蛇位置被更新两次并且代码停止 运行(不是通过残局过程,c:tasm/bin 行/ 再次出现)
有什么想法欢迎评论。
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
您的代码自 your previous question 以来有了很大改进,但由于仍然存在许多错误,我怀疑是否有人可以完全回答您的“为什么???”
我将重点介绍您的一个程序,boundries,如果蛇与顶行或底行或左列或右列发生碰撞,您希望在其中结束游戏
第一个测试,cmp di,160
ja keep_playing
,意味着你继续玩,前提是蛇没有碰到屏幕的第一行。 None 其他边界甚至被验证!
如果像 cmp di, 160
jb stop_playing
.
那样反转条件跳转,你需要什么
在第四个测试(右侧边框)中,您将奇数(因为 inc ax
)除以 160。这将始终产生非零余数。所以,不是一个有用的测试。要测试左右边界,您可以只进行一次除法并将余数解释两次:0 是左侧碰撞,158 是右侧碰撞。
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
作为奖励 random 程序,您在其中读取 AX
寄存器中的 clock,但突然间您不要使用 AX
而是开始使用 SI
!
如果您暂时将 ES
段寄存器设置为 BIOS 数据页,mov ax, [clock] ; read timer counter
指令将读取您的 clock equ es:6ch
定义的真实时钟0040h:
push es
mov ax, 0040h
mov es, ax
mov ax, [clock] ; read timer counter
pop es
像 and si, 2000 ; leaves value between 0-2000
这样的 AND
指令不会像您期望的那样限制值!限制到该特定范围需要除以 2001 并使用余数。
这是我的新随机程序的版本:
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) 使用的寄存器更少 == 需要保留的寄存器更少!
(ii) 使用 DS
给出更短的代码。
(iii) 停止使用其中包含 es:
段覆盖的 'confusing' clock equ。
(iiii) 要留在屏幕上,object_location 应限制为 {0, 2, 4, 6, ... , 3998]。
我最近发了一篇Q/AThe quintessential Snake Game. How to keep track of the snake?。也许你可以从中得到一两个想法...
此代码适用于贪吃蛇游戏。 proc detect _ direction 调用 mov_snake proc 很多事情,最后调用 detect_direction 。当我 运行 DosBox(最新版本)中的代码时,蛇位置被更新两次并且代码停止 运行(不是通过残局过程,c:tasm/bin 行/ 再次出现)
有什么想法欢迎评论。
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
您的代码自 your previous question 以来有了很大改进,但由于仍然存在许多错误,我怀疑是否有人可以完全回答您的“为什么???”
我将重点介绍您的一个程序,boundries,如果蛇与顶行或底行或左列或右列发生碰撞,您希望在其中结束游戏
第一个测试,cmp di,160
ja keep_playing
,意味着你继续玩,前提是蛇没有碰到屏幕的第一行。 None 其他边界甚至被验证!
如果像 cmp di, 160
jb stop_playing
.
在第四个测试(右侧边框)中,您将奇数(因为 inc ax
)除以 160。这将始终产生非零余数。所以,不是一个有用的测试。要测试左右边界,您可以只进行一次除法并将余数解释两次:0 是左侧碰撞,158 是右侧碰撞。
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
作为奖励 random 程序,您在其中读取 AX
寄存器中的 clock,但突然间您不要使用 AX
而是开始使用 SI
!
如果您暂时将
ES
段寄存器设置为 BIOS 数据页,mov ax, [clock] ; read timer counter
指令将读取您的clock equ es:6ch
定义的真实时钟0040h:push es mov ax, 0040h mov es, ax mov ax, [clock] ; read timer counter pop es
像
and si, 2000 ; leaves value between 0-2000
这样的AND
指令不会像您期望的那样限制值!限制到该特定范围需要除以 2001 并使用余数。
这是我的新随机程序的版本:
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) 使用的寄存器更少 == 需要保留的寄存器更少!
(ii) 使用 DS
给出更短的代码。
(iii) 停止使用其中包含 es:
段覆盖的 'confusing' clock equ。
(iiii) 要留在屏幕上,object_location 应限制为 {0, 2, 4, 6, ... , 3998]。
我最近发了一篇Q/AThe quintessential Snake Game. How to keep track of the snake?。也许你可以从中得到一两个想法...