游戏必须使用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],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,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,ax ;guardando el modo de video y aplicando el nuevo xor ax,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],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,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,9h int 21h mov al,0 mov ah,4ch int 21h
谢谢阅读!
^^^这是答案(导致你的错误的原因),而不仅仅是一些建议
这里有一些建议如下:
在中断中有任何循环(动态延迟)也是错误的,中断应该尽可能快地进行.
我不记得从头部读取键盘的0x6X端口的正确方法(我只记得它有点棘手,让它完全正确),所以我不打算检查特定的输入/输出序列及其正确性.
但是如果你将在实际当前状态中设置XXXpressed中断,并且主循环将太慢,则可能看不到非常短的按键(因为输入没有被缓冲).对于像arkanoid clone一样的简单游戏,这是可以的,我根本不会被这个打扰,听起来像是正确的行为(你需要实际上非常快地把钥匙保持得这么短).
您还可以通过在中断代码处理程序附近保留一些数据空间(在iret之后将escpressed dw 0移动到代码部分中)来避免中断中的ds设置,然后将其用作mov word [cs:escpressed],1等.如果您实际上以更有效的方式设置内存标志和短中断代码(可以简化很多),那么使用cs的代价:内部中断寻址将低于ds设置.
你有多广泛地将slow loop
instruction用于所有主循环,但是在延迟子程序中你做了更快的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,al ; set "enable kbd" bit mov al,ah out 61h,al ; set original value back mov al,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 ...