在汇编中制作乒乓球游戏,如何一次输入多个击键?

Making a pong game in assembly, how do I get an input of multiple keystrokes at once?

我是初学者,所以这段代码可能没什么用,我用 int 16h 来做这个,但我对这个 int 了解不多。我刚刚发现你不能同时进行多次击键;有帮助吗?
这段代码的问题是一次只能移动一个板,我需要两个。如何检查多个输入?

这是供任何人使用的代码:

IDEAL
MODEL small
STACK 100h
DATASEG
; --------------------------
; Your variables here
; --------------------------
    line1X dw 80
  line1Y dw 120
  line1start dw 5
  line1end dw 10
  line2X dw 80
  line2Y dw 120
  line2start dw 310
  line2end dw 315
CODESEG
    proc startVideo ;creates video mode
      mov al,13h
      mov ah,0h
      int 10h
      ret
  endp startVideo
  proc clearScrean ;turns the screen black
      mov ah, 0ch
      xor al,al
      mov dx,200 
BlankLine:
      mov cx,320
BlankColumn:
        int 10h
        Loop BlankColumn
      dec dx
        cmp dx,0
        jne BlankLine     
      ret
  endp clearScrean
  
    proc drawboard ;creates the board
      push bp
      mov bp,sp
      mov al,0fh
      mov ah,0ch
      beginning equ [bp+10]
      fn equ [bp+8]
      X equ [bp+6] ;boards start
      Y equ [bp+4] ;boards end
      mov dx,Y
drawrow:
      mov cx,fn
drawcolumn:
        int 10h
      dec cx
        cmp cx,beginning
        jne drawcolumn
      dec dx
      cmp dx,X 
      jne drawrow
      pop bp
      ret 8
  endp drawboard
  proc drawall
      push [line1start]
      push [line1end]
      push [line1X]
      push [line1Y]
      call drawboard
      push [line2start]
      push [line2end]
      push [line2X]
      push [line2Y]
      call drawboard
      ret
  endp drawall
  proc boardup
      push bp
      mov bp,sp
      mov bx,[bp+4]
      mov si,[bp+6]
      cmp [word ptr bx],0 ;checks if board didnt get to border 
      je fn1
        call clearScrean
      sub [word ptr bx],5 ;3 pixels added to board
      sub [word ptr si],5
      call drawall ;prints both boards
fn1:
      pop bp
      ret 4
  endp boardup
  proc boarddown
      push bp
      mov bp,sp
      mov bx,[bp+4]
      mov si,[bp+6]
      cmp [word ptr si],200 ;checks if board didnt get to border 
      je fn2
        call clearScrean
      add [word ptr bx],5 ;3 pixels added to board
      add [word ptr si],5
      call drawall ;prints both boards
fn2:
      pop bp
      ret 4
  endp boarddown
start:
  mov ax, @data
  mov ds, ax
    mov bh,0
  call startVideo
  call clearScrean
  call drawall
checkskey: ;checks if key is being pressed
  mov ah,1
  int 16h
  jz checkskey ;jumps if key isnt pressed
  mov ah,0 ;checks which key is pressed
  int 16h
  cmp ah,11h ;if key pressed is w jump to upboard
  je upboard1
  cmp ah,01fh ;if key pressed is s jump to downboard
  je downboard1
  cmp ah,050h
  je downboard2
  cmp ah,048h
  je upboard2
  jmp checkskey ;if key isnt pressed jump to check key
upboard1: ;board 1 goes up
    push offset line1Y
  push offset line1X
  call boardup
  jmp checkskey
downboard1: ;board 1 goes down
    push offset line1Y
  push offset line1X
  call boarddown
  jmp checkskey
downboard2:
    push offset line2Y
  push offset line2X
  call boarddown
  jmp checkskey
upboard2:
    push offset line2Y
  push offset line2X
  call boardup
  jmp checkskey
exit:
  mov ax, 4c00h
  int 21h
END start

the problem with this code is that only one board can move at a time and i need both

同步感来自于快速,真正的快速。您计算机中的大多数东西都是串行工作的,但我们认为很多事情是并行发生的。

您的 checkskey 代码没问题。一块板使用 qs 键,另一块板使用 up向下键。
一旦某个键可用,键盘 BIOS 功能 00h 将立即检索它,您的程序将相应地更新图形。但是如果你的图形输出程序花的时间太长,那么玩家就会开始认为键盘呆滞了。

查看您的图形例程,我看到您使用视频 BIOS 功能 0Ch 将像素放在屏幕上。这很慢而且特别痛苦,因为你在最简单​​的图形屏幕上玩,你可以 MOV 一个字节来绘制一个像素。

在需要快速图形的程序中,将 ES 段寄存器永久指向视频缓冲区可能非常有利。

  mov  ax, 0A000h
  mov  es, ax
  cld             ; Because of the use of STOSB

这就是清除屏幕所需的全部:

ClearScreen:
  xor  di, di
  mov  cx, 64000
  mov  al, 0
  rep stosb
  ret

这是画水平线 (160,100)-(200,100) 的方法:

  mov  dl, 15    ; BrightWhite
  mov  cx, 51    ; 51 pixels from 160 to 200
  mov  bx, 160   ; X
  mov  ax, 100   ; Y
  call DrawLine

  ...

DrawLine:
  push dx
  mov  di, 320   ; BytesPerScanline
  mul  di
  add  ax, bx
  mov  di, ax    ; Address DI = (Y * BPS) + X
  pop  ax        ; Color AL
  rep stosb
  ret

另一个答案涉及多人游戏,其中 none 的玩家不断按下他们的专用键,从而占用键盘。尽管这种情况非常合理,但您可能希望让玩家按住按键的时间更长。为此,我们可以用我们自己的处理程序替换 BIOS/DOS 提供的键盘处理程序。

键盘上的每个键都关联一个唯一的 8 位数字,我们称之为扫描码。
每当按下一个键时,键盘都会在端口 60h 上提供相关键的扫描码。键盘也产生一个中断 09h。这个中断的处理程序可以检查扫描码并以任何它喜欢的方式处理它。这就是下面的演示程序所做的。
当按下一个键时,扫描码是一个最高位关闭的字节。当一个键被释放时,扫描码是一个字节,其最高位为开。其他 7 位在按下和释放时保持不变。

应该注意的是,虽然对于您的 乒乓球 游戏来说很好,但包含的替换处理程序是一个简约的处理程序。复杂的处理程序还会考虑以 E0h 或 E1h 代码为前缀的扩展扫描码。

该程序有额外的注释,因此您可以轻松了解正在发生的事情。该代码使用 FASM 语法。演示在真实DOS环境和DOSBox(0.74)下运行正常

; Multi-player Keyboard Input (c) 2021 Sep Roland

    ORG  256               ; Output will be a .COM program

    mov  ax, 3509h         ; DOS.GetInterruptVector
    int  21h               ; -> ES:BX
    push es bx             ; (1)

    mov  dx, Int09
    mov  ax, 2509h         ; DOS.SetInterruptVector
    int  21h

    mov  ax, 0013h         ; BIOS.SetVideoMode 320x200 (256 colors)
    int  10h
    mov  ax, 0A000h        ; Video buffer
    mov  es, ax
    cld                    ; So we can use the string primitive STOSB

Cont:
    mov  si, 160           ; Width
    mov  di, 100           ; Height

    mov  al, 0             ; Black
    cmp  [KeyList+48h], al ; Up
    je   .a
    mov  al, 2             ; Green
.a: mov  cx, 160           ; X
    mov  dx, 0             ; Y
    call Paint

    mov  al, 0             ; Black
    cmp  [KeyList+50h], al ; Down
    je   .b
    mov  al, 14            ; Yellow
.b: mov  cx, 160           ; X
    mov  dx, 100           ; Y
    call Paint

    mov  al, 0             ; Black
    cmp  [KeyList+11h], al ; aZerty / qWerty
    je   .c
    mov  al, 4             ; Red
.c: mov  cx, 0             ; X
    mov  dx, 0             ; Y
    call Paint

    mov  al, 0             ; Black
    cmp  [KeyList+1Fh], al ; S
    je   .d
    mov  al, 1             ; Blue
.d: mov  cx, 0             ; X
    mov  dx, 100           ; Y
    call Paint

    cmp  byte [KeyList+1], 0 ; ESC
    je   Cont

    pop  dx ds             ; (1)
    mov  ax, 2509h         ; DOS.SetInterruptVector
    int  21h

    mov  ax, 4C00h         ; DOS.Terminate
    int  21h
; --------------------------------------
Int09:
    push ax bx
    in   al, 60h
    mov  ah, 0
    mov  bx, ax
    and  bx, 127           ; 7-bit scancode goes to BX
    shl  ax, 1             ; 1-bit press/release goes to AH
    xor  ah, 1             ; -> AH=1 Press, AH=0 Release
    mov  [cs:KeyList+bx], ah
    mov  al, 20h           ; The non specific EOI (End Of Interrupt)
    out  20h, al
    pop  bx ax
    iret
; --------------------------------------
; IN (al,cx,dx,si,di)
Paint:
    push cx dx di          ; AL=Color CX=X DX=Y SI=Width DI=Height
    push ax                ; (1)
    mov  ax, 320           ; BytesPerScanline
    mul  dx
    add  ax, cx            ; (Y * BPS) + X
    mov  dx, di
    mov  di, ax
    pop  ax                ; (1)
.a: mov  cx, si
    rep  stosb
    sub  di, si
    add  di, 320
    dec  dx
    jnz  .a
    pop  di dx cx
    ret
; --------------------------------------
KeyList db 128 dup 0
KeyList db 128 dup 0

程序的KeyList记录了键盘上所有按键的当前状态。如果字节为 0,则表示未按下该键。如果字节为 1,则当前正在按下该键。

我把 Sep Roland 的精彩回答翻译成了 tasm:

; filename: dots.asm


;  Controls:
;
;  Up/Down Arrows  -  Move Purple Dot 
;  W/S Keys        -  Move Cyan Dot
;  Esc             -  Exit


IDEAL
MODEL small
STACK 100h

DATASEG

; postion of cyan dot
xCyanDot dw 107
yCyanDot dw 100

; position of purple dot
xPurpleDot dw 214
yPurpleDot dw 100

; keyboard scan codes we'll need
KeyEsc    equ 01h
KeyW      equ 11h
KeyS      equ 1Fh
UpArrow   equ 48h
DownArrow equ 50h

proc onKeyEvent  ; custom handler for int 09h
    push ax bx
    in   al, 60h
    mov  ah, 0
    mov  bx, ax
    and  bx, 127           ; 7-bit scancode goes to BX
    shl  ax, 1             ; 1-bit pressed/released goes to AH
    xor  ah, 1             ; -> AH=1 Pressed, AH=0 Released
    mov  [cs:KeyList+bx], ah
    mov  al, 20h           ; The non specific EOI (End Of Interrupt)
    out  20h, al
    pop  bx ax
    iret
endp

CODESEG

proc sleepSomeTime
    mov cx, 0
    mov dx, 50000  ; 50ms
    mov ah, 86h
    int 15h  ; param is cx:dx (in microseconds)
    ret
endp

proc drawPurpleDot
    mov al, 5
    mov cx, [xPurpleDot]
    mov dx, [yPurpleDot]
    mov bh, 0h
    mov ah, 0ch
    int 10h

    ret
endp

proc coverPurpleDot
    mov al, 0
    mov cx, [xPurpleDot]
    mov dx, [yPurpleDot]
    mov bh, 0h
    mov ah, 0ch
    int 10h

    ret
endp

proc drawCyanDot
    mov al, 3
    mov cx, [xCyanDot]
    mov dx, [yCyanDot]
    mov bh, 0h
    mov ah, 0ch
    int 10h

    ret
endp

proc coverCyanDot
    mov al, 0
    mov cx, [xCyanDot]
    mov dx, [yCyanDot]
    mov bh, 0h
    mov ah, 0ch
    int 10h

    ret
endp

KeyList db 128 dup (0)

proc if_Up_isPressedMoveDot
    mov bx, offset KeyList
    cmp [byte bx + UpArrow], 1
    jne handleUp_end
    
    call coverPurpleDot

    mov ax, [yPurpleDot]
    dec ax
    mov [yPurpleDot], ax

    call drawPurpleDot

    handleUp_end:
    ret
endp

proc if_Down_isPressedMoveDot
    mov bx, offset KeyList
    cmp [byte bx + DownArrow], 1
    jne handleDown_end
    
    call coverPurpleDot

    mov ax, [yPurpleDot]
    inc ax
    mov [yPurpleDot], ax

    call drawPurpleDot
    
    handleDown_end:
    ret
endp

proc if_W_isPressedMoveDot
    mov bx, offset KeyList
    cmp [byte bx + KeyW], 1
    jne handleW_end
    
    call coverCyanDot

    mov ax, [yCyanDot]
    dec ax
    mov [yCyanDot], ax

    call drawCyanDot

    handleW_end:
    ret
endp

proc if_S_isPressedMoveDot
    mov bx, offset KeyList
    cmp [byte bx + KeyS], 1
    jne handleS_end
    
    call coverCyanDot

    mov ax, [yCyanDot]
    inc ax
    mov [yCyanDot], ax

    call drawCyanDot

    handleS_end:
    ret
endp

proc main
    call drawPurpleDot
    call drawCyanDot
    
    mainLoop:
        call sleepSomeTime

        call if_Up_isPressedMoveDot
        call if_Down_isPressedMoveDot
        call if_W_isPressedMoveDot
        call if_S_isPressedMoveDot

    ; if Esc is not pressed, jump back to mainLoop
    mov bx, offset KeyList
    cmp [byte bx + KeyEsc], 1
    jne mainLoop
    
    ret
endp

start:
    mov ax, @data
    mov ds, ax

; enter graphic mode
    mov ax, 13h
    int 10h

; get the address of the existing int09h handler
    mov ax, 3509h ; Get Interrupt Vector
    int  21h ; -> ES:BX
    push es bx

; replace the existing int09h handler with ours
    mov dx, offset onKeyEvent
    mov ax, 2509h
    int 21h

call main

; return to text mode
    mov ah, 0
    mov al, 2
    int 10h

; restore the original int09h handler
    pop dx ds
    mov ax, 2509h
    int 21h

exit:
    mov ax, 4c00h
    int 21h

end start

编译 & 运行:

tasm /zi dots.asm
tlink /v dots.obj
dots

奖励部分 - 以下是 int 09h 使用的键盘扫描码:

 01h Esc          31h N
 02h 1 !          32h M
 03h 2 @          33h ,              63h F16   
 04h 3 #          34h .              64h F17
 05h 4 $          35h / ?            65h F18
 06h 5 %          36h RightShift     66h F19
 07h 6 ^          37h Grey*          67h F20
 08h 7 &          38h Alt            68h F21
 09h 8 *          39h SpaceBar       69h F22
 0Ah 9 (          3Ah CapsLock       6Ah F23
 0Bh 0 )          3Bh F1             6Bh F24
 0Ch - _          3Ch F2
 0Dh = +          3Dh F3             6Dh EraseEOF
 0Eh Backspace    3Eh F4
 0Fh Tab          3Fh F5             6Fh Copy/Play
 10h Q            40h F6
 11h W            41h F7
 12h E            42h F8             72h CrSel
 13h R            43h F9
 14h T            44h F10            74h ExSel
 15h Y            45h NumLock
 16h U            46h ScrollLock     76h Clear
 17h I            47h Home
 18h O            48h UpArrow
 19h P            49h PgUp
 1Ah [ {          4Ah Grey-
 1Bh ] }          4Bh LeftArrow
 1Ch Enter        4Ch Keypad 5
 1Dh Ctrl         4Dh RightArrow
 1Eh A            4Eh Grey+
 1Fh S            4Fh End
 20h D            50h DownArrow
 21h F            51h PgDn
 22h G            52h Ins
 23h H            53h Del
 24h J            54h SysReq
 25h K
 26h L            56h left | (102-key)
 27h ; :          57h F11
 28h ' "          58h F12            AAh self-test complete
 29h ` ~                             E0h prefix code
 2Ah LeftShift    5Ah PA1            E1h prefix code
 2Bh \ |          5Bh F13            EEh ECHO
 2Ch Z            5Ch F14            F0h prefix code (key break)
 2Dh X            5Dh F15            FAh ACK
 2Eh C                               FDh diagnostic failure
 2Fh V                               FEh RESEND
 30h B                               FFh kbd error/buffer full

source: http://muruganad.com/8086/8086-Interrupt-List.html