我正在为univesity工作,我们需要创建一个简单的breakout/arkanoid克隆,它很顺利但是我发现了一个会删除屏幕上所有内容的bug,这个bug是随机的,但我怀疑它与我的DrawPaddle功能.也许你可以发现错误或了解为什么视频内存会这样做.
游戏必须使用16位ms-dos程序集完成,我使用NASM + VAL + Dosbox创建它,我用以下代码编译它:
nasm -f obj test.asm val test.obj
游戏只是使用键盘箭头在固定屏幕上移动球拍,您也可以通过按下退出退出游戏.
这是一切都还好:https://puu.sh/yeKtG/affc912d4b.png,当程序溢出时它看起来像这样:http://puu.sh/yeKEy/caeef089d1.png或http:// puu.sh/yeKJH/1106e1e823.png
我注意到奇怪的行为只发生在我移动桨时它会随机发生,例如现在我从程序中删除了几乎所有其他东西,它可能需要几次尝试来获取bug.
这是DrawPaddle代码:
DrawPaddle: push di mov di, [paddleposition] mov cx, 5 ;the paddle will be 5 pixels tall .p0: push cx mov cx, paddlesize .p1: mov byte [es:di], bl inc di loop .p1 add di, screenweight - paddlesize pop cx loop .p0 pop di ret
这是完整的代码,它使用键盘处理程序读取输入,并使用320x200x256直接写入视频内存.
BITS 16 stacksize EQU 0200h ;Constantes ;Direccion de inicio de la memoria de video videobase EQU 0a000h ;Definicion de colores black EQU 0 green EQU 00110000b ;Screen data screenweight EQU 320 ;Paddle data startx EQU 140 starty EQU 170 paddlesize EQU 40 paddlecolor EQU 00101010b ;Paddle movement limits leftlimit EQU starty * screenweight + 1 + 10 + 1 rightlimit EQU ((starty + 1) * screenweight) - paddlesize - 10 - 1 segment mystack stack resb stacksize stacktop: segment mydata data ;Variables escpressed dw 0 leftpressed dw 0 rightpressed dw 0 oldintseg resw 1 oldintoff resw 1 originalVideoMode resb 1 paddleposition resw 1 segment mycode code ;Subrutinas KeybInt: push ds ;guardamos ds:ax push ax mov ax, mydata ;los re-inicializamos mov ds, ax cli .getstatus: in al, 64h test al, 02h loopnz .getstatus ;esperando a que el puerto esté listo in al,60h ;obtenemos el codigo make o break de la tecla leida cmp al, 01h ;revisamos si es escape jne .revEsc mov word [escpressed], 1 jmp .kbread .revEsc: cmp al, 81h ;revisamos si el escape fue soltado jne .revIzq mov word [escpressed], 0 jmp .kbread .revIzq: cmp al, 4bh ;revisamos si es la flecha izquierda jne .revDer mov word [leftpressed], 1 jmp .kbread .revDer: cmp al, 4dh ;revisamos si es la flecha derecha jne .revIzq2 mov word [rightpressed], 1 jmp .kbread .revIzq2: cmp al, 0cbh ;si se solto la flecha izquierda jne .revDer2 mov word [leftpressed], 0 jmp .kbread .revDer2: cmp al, 0cdh ;o la derecha jne .kbread mov word [rightpressed], 0 jmp .kbread .kbread: in al, 61h or al, 10000000b out 61h, al and al, 01111111b out 61h, al mov al, 20h out 20h, al sti pop ax ;recuperamos ds:ax pop ds iret DrawStage: push di push bx ;movemos el cursor a la posicion 10,10 ;que seria en realidad 10*320+10 mov di, (10 * screenweight) + 10 ;ahora repetiremos esto 320-20 veces mov cx, 300 .h1: mov byte [es:di], green inc di loop .h1 mov di, (190 * screenweight) + 10 ;ahora repetiremos esto 320-20 veces mov cx, 301 .h2: mov byte [es:di], green inc di loop .h2 ;ahora volveremos al primer punto ;y dibujaremos hacia abajo mov di, (10 * screenweight) + 10 ;y lo repetiremos 200-20 veces mov cx, 180 .v1: mov byte [es:di], green add di, screenweight loop .v1 mov di, (10 * screenweight) + 310 mov cx, 180 .v2: mov byte [es:di], green add di, screenweight loop .v2 pop bx pop di ret ;Rutina para dibujar el palo ;Recibe en bl el color del mismo DrawPaddle: push di mov di, [paddleposition] mov cx, 5 ;the paddle will be 5 pixels tall .p0: push cx mov cx, paddlesize .p1: mov byte [es:di], bl inc di loop .p1 add di, screenweight - paddlesize pop cx loop .p0 pop di ret Delay1: mov dx, 4 sub dx, 3 .pause1: mov cx, 6000 .pause2: dec cx jne .pause2 dec dx jne .pause1 ret ..start: mov ax, mydata mov ds, ax mov ax, mystack mov ss, ax mov sp, stacktop ;guardando el manejador actual mov ah, 35h mov al, 9h int 21h mov [oldintseg], es mov [oldintoff], bx ;instalando el manejador nuevo mov ax, mycode mov es, ax mov dx, KeybInt mov ax, cs mov ds, ax mov ah, 25h mov al, 9h int 21h ;restaurando el segmento de datos mov ax, mydata mov ds, ax ;guardando el modo de video y aplicando el nuevo xor ax, ax mov ah, 0fh int 10h mov [originalVideoMode], al mov ah, 00h mov al, 13h int 10h ;coordenada de inicio para el palo mov ax, (screenweight * starty) + startx mov word [paddleposition], ax mov ax, videobase mov es, ax call DrawStage mov bl, paddlecolor call DrawPaddle jmp .main .main: call Delay1 ;leemos las entradas cmp word [escpressed], 1 je .dosexit cmp word [rightpressed], 1 je .movRight cmp word [leftpressed], 1 je .movLeft jmp .main .movRight: mov bl, black call DrawPaddle cmp word [paddleposition], rightlimit je .ending inc word [paddleposition] jmp .ending .movLeft: mov bl, black call DrawPaddle cmp word [paddleposition], leftlimit je .ending dec word [paddleposition] jmp .ending .ending: mov bl, paddlecolor call DrawPaddle jmp .main .dosexit: ;restaurando el modo de video original mov ah, 00h mov byte al, [originalVideoMode] int 10h ;restaurando el manejador de teclado original mov dx, [oldintoff] mov ax, [oldintseg] mov ds, ax mov ah, 25h mov al, 9h int 21h mov al, 0 mov ah, 4ch int 21h
谢谢阅读!
您可以修改cx
键盘中断而不保留它.
^^^这是答案(导致你的bug的原因),而不仅仅是一些建议
这里有一些建议如下:
在中断中有任何循环(动态延迟)也是错误的,中断应该尽可能快地进行.
我无法从头部记得什么是读取键盘的0x6X端口正确方法(我回想起刚才这是一个有点棘手,把它完全正确的),所以我不会去检查特定的in/out
序列及其正确性.
但是,如果您将XXXpressed
通过实际当前状态设置中断,并且主循环将太慢,则可能看不到非常短的按键(因为输入未被缓冲).对于像arkanoid clone一样的简单游戏,这是可以的,我根本不会被这个打扰,听起来像是正确的行为(你需要实际上非常快地把钥匙保持得这么短).
您还可以ds
通过在中断代码处理程序附近保留一些数据空间(后移入escpressed dw 0
代码部分iret
),然后在任何地方使用mov word [cs:escpressed], 1
等来避免设置中断.使用cs:
内部寻址的总惩罚将低于ds
设置,如果你实际上会以更有效的方式设置内存标志和短暂的中断代码(可以简化很多).
你对所有主循环使用慢速loop
指令有多广泛,但是在delay
子程序中你做了更快的dec cx
jnz ...
选择.
我确实检查了如何编写DOS键盘处理程序,所以这是我的建议(不幸的是我没有测试它,如果它工作):
segment mycode code escpressed db 0 leftpressed db 0 rightpressed db 0 KeybInt: cli push ax ;guardamos ax ; when IRQ1 is fired, int 9 is called to handle it and the input ; already waits on port 0x60, no need to validate IBF flag on 0x64 in al,60h ;obtenemos el codigo make o break de la tecla leida mov ah,al and al,0x7F ; AL = scan code without pressed/released flag shr ah,7 xor ah,1 ; AH = 1/0 pressed/released cmp al, 01h ;revisamos si es escape jne .checkLeft mov [cs:escpressed], ah jmp .kbread .checkLeft: cmp al, 4bh ;revisamos si es la flecha izquierda jne .checkRight mov [cs:leftpressed], ah jmp .kbread .checkRight: cmp al, 4dh ;revisamos si es la flecha derecha jne .kbread mov [cs:rightpressed], ah .kbread: in al, 61h mov ah, al ; store original value or al, 10000000b out 61h, al ; set "enable kbd" bit mov al, ah out 61h, al ; set original value back mov al, 20h out 20h, al ; send end-of-interrupt signal to 8259 IC pop ax ;recuperamos ax sti ; not needed in real x86 real mode, IRET restores flags iret ; but explicit STI paired with CLI may help some VMs
...然后在游戏代码中,要检查密钥的状态,您也必须使用cs
:
... cmp byte [cs:escpressed], 1 ...