Search code examples
assemblydosx86-16tasm

How do I ignore multiple keypresses?


I made a DOS program in assembly that makes a square jump when you press space.

If you hold space while the square is jumping, it continues to jump even after you stop pressing space. How can I make it only pay attention to space being pressed while the square is on the ground?

Code (compiled with TASM):

IDEAL
MODEL small
STACK 100h
DATASEG

X dw 0
Y dw 0
SquareX dw 20
SquareY dw 193
SquareSize dw 0
color db 1  ; default color is blue
FloorY dw 194
FloorX dw 0




CODESEG



    proc HalfSecondDelay
    push cx ;Backup
    push dx
    push ax
    
    MOV CX, 1H
    MOV DX, 3H
    MOV AH, 86H
    INT 15H
    
    pop ax ;Backup
    pop dx
    pop cx
    ret
    endp HalfSecondDelay

    
    proc WaitForSpace
    EnterSpace:  ;Wait until user presses Space
    mov ah,0
    int 16h
    cmp al,32
    jne EnterSpace
    ret
    endp WaitForSpace


    proc PlayerJump ;Makes The Square Jump
    push cx
    
    
    mov cx, 10
    MoveUp:
    Call HalfSecondDelay
    
    call RemovePlayerSquare
    sub [SquareY],4 ; Move Up
    call PaintPlayerSquare
    loop MoveUp
    
    mov cx, 10
    MoveDown:
    Call HalfSecondDelay
    call RemovePlayerSquare
    add [SquareY], 4 ; Move Down
    Call PaintPlayerSquare
    loop MoveDown

    pop cx
    ret
    endp PlayerJump


proc PaintFloorLine
    push bp
    mov bp,sp
    push cx


    
    mov cx, 320
LoopPaintFloorLine:
    call PaintPixel
    
    inc [X]
    loop loopPaintFloorLine
    

    pop cx
    pop bp
    ret
endp PaintFloorLine

proc PaintFloor ;Paints The Floor
    push bp
    mov bp,sp
    push cx
    
    mov [x], 0
    mov [y], 194
    mov [color], 0Ah
    
    mov cx, 7
    
loopPaintFloor:     
    call PaintFloorLine
    inc [Y]
    mov [x], 0
    loop loopPaintFloor
    
    
    pop cx
    pop bp
    ret
endp PaintFloor
    

proc PaintPixel
    push bp
    mov bp,sp

    push ax
    push bx
    push cx
    push dx 
    

    mov cx,[X]
    mov dx,[Y]
    mov al,[color]
    mov ah,0ch
    int 10h
    
    pop dx
    pop cx
    pop bx
    pop ax
    pop bp
    ret
endp PaintPixel

proc PaintLine
    push bp
    mov bp,sp
    push cx
    
    mov cx, [SquareSize]
    
loopPaintLine:  
    call PaintPixel
    inc [X]
    loop loopPaintLine
    
    mov cx, [SquareSize] ; Return The X
    sub [X], cx
    
    pop cx
    pop bp
    ret
endp PaintLine

    
proc PaintSquare ;Paints A Sqaure
    push bp
    mov bp,sp
    push cx
    
    mov cx, [SquareSize]
    
loopPrinSquare:     
    call PaintLine
    dec [Y]
    loop loopPrinSquare
    
    mov cx, [SquareSize] ; Return The Y
    add [Y], cx
    
    pop cx
    pop bp
    ret
endp PaintSquare

proc PaintPlayerSquare ; Paints The Player Square
    push bp
    mov bp,sp
    
    mov ax, [SquareX]
    mov [x], ax
    
    mov ax, [SquareY]
    mov [Y], ax
    
    mov [SquareSize], 25
    mov  [color], 1 ; blue color
    call PaintSquare ;  
    
    pop bp
    ret 
endp PaintPlayerSquare

proc RemovePlayerSquare ;Removes The Player Square
    push bp
    mov bp,sp
    
    push ax
    push bx
    push cx
    push dx 
    
    mov ax, [SquareX]
    mov [x], ax
    
    mov ax, [SquareY]
    mov [Y], ax
    
    mov  [color], 0 ; black color
    call PaintSquare ;  
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    pop bp
    ret 
endp RemovePlayerSquare

proc GraphicsScreen
    mov al, 13h
    mov ah, 0
    int 10h
    ret
endp GraphicsScreen



    
start:
    mov ax, @data
    mov ds, ax
    
    Call GraphicsScreen
    Call PaintFloor
    Call PaintPlayerSquare

    
    loopSquareJump:
    Call WaitForSpace
    Call PlayerJump
    jmp loopSquareJump

    

exit:
    mov ax, 4c00h
    int 21h
END start

Solution

  • Your problem comes from the Space keypresses remaining in the keyboard buffer when you don't want them to, causing them to be handled later. One solution is to clear the keyboard buffer just before you wait for Space to be pressed, like so:

    proc ClearKeyboardBuffer
    ClearKeyboardBuffer_loop:
    mov ah,01h
    int 16h                     ; is there a key pressed
    jz ClearKeyboardBuffer_ret  ; if not, return
    mov ah,00h
    int 16h                     ; "handle" the key
    jmp ClearKeyboardBuffer_loop
    
    ClearKeyboardBuffer_ret:
    ret
    endp ClearKeyboardBuffer
    

    Then simply amend your loop like so:

    loopSquareJump:
    Call ClearKeyboardBuffer
    Call WaitForSpace
    Call PlayerJump
    jmp loopSquareJump
    

    This is, however, not a great way to do it. According to Stephen Kitt's answer on Retrocomputing:

    If you’re using BIOS functions to read from the keyboard in your game, the quickest way to clear the buffer is to make its tail equal to its head: read the value at 0x0041A and write it to 0x0041C (with interrupts disabled):

    proc clearkeyboardbuffer
    ; AX is clobbered
        push ds
        mov ax, 0040h
        mov ds, ax
        mov ax, [001Ah]
        mov [001Ch], ax
        pop ds
        ret
    endp clearkeyboardbuffer
    

    (The BIOS keyboard buffer is a circular list starting at 0x0041E, and 0x0041A points at the buffer’s head, and 0x0041C at its tail. If both pointers are equal, the buffer is empty.)

    Read more on the original answer, if you're interested.