4

I'm a beginner so this code probably isn't any good, I used int 16h for this but I don't know a lot about this int. I just found out you can't have multiple keystrokes at once; any help?
The problem with this code is that only one board can move at a time and I need both. How do I check for multiple inputs?

Here's the code for anyone who wants it:

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
Sep Roland
  • 33,889
  • 7
  • 43
  • 76
orraz1
  • 43
  • 5

3 Answers3

4

The other answer deals with a multi-player game where none of the players keep pushing their dedicated keys and thus hogging the keyboard. Although this scenario is quite reasonable, you might want to allow the players to hold their key down for a longer period. To this effect we can substitute the keyboard handler that BIOS/DOS provides by a handler of our own.

With each key on the keyboard is associated a unique 8-bit number we call the scancode.
Whenever a key is pressed the keyboard makes the scancode of the concerned key available at port 60h. The keyboard also generates an interrupt 09h. A handler for this interrupt can examine the scancode and process it in any way it likes. That's what the below demonstration program does.
When a key is pressed the scancode is a byte with its highest bit off. When a key is released the scancode is a byte with its highest bit on. The other 7 bits remain the same for both presses and releases.

It should be noted that, although fine for the purpose of your pong game, the included substitution handler is a minimalistic one. A sophisticated handler would also take into account the extended scancodes that are prefixed with an E0h or E1h code.

The program has additional comments so you can easily understand what's happening. The code uses FASM syntax. The demo runs OK in the real DOS environment and in the 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

The program's KeyList records the current state of all of the keys on the keyboard. If a byte is 0, the key is not being pressed. If a byte is 1, that key is currently being pressed.

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
  • that's nuts, thanks, ill try translating this code to tasm and put it in my project, tysm. – orraz1 May 16 '21 at 07:16
  • Sry for the questions but what exactly is "DOS.GetInterruptVector", and "DOS.GetInterruptVector"? Also what exactly are you doing in this code? – orraz1 Jun 08 '21 at 04:40
  • Nvm i understand now omfg this is genius – orraz1 Jun 08 '21 at 05:01
  • do you have any articles on how do i use int 21h 25th interrupt? I know how it works just not how you use it. Btw where do I put the implement interrupt code? – orraz1 Jun 08 '21 at 16:48
2

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

The sense of simultaneousness comes from being fast, real fast. Most everything in your computer works serially but we percieve many things as happening in parallel.

Your checkskey code is fine. One board uses the q and s keys, and the other board uses the up and down keys.
As soon as a key is available, the Keyboard BIOS function 00h will retrieve it immediately and your program will update the graphics accordingly. But if your graphical output routines take too long, then the players will start thinking that the keyboard is sluggish.

Looking at your graphics routines, I see that you use the Video BIOS function 0Ch to put pixels on the screen. This is slow and especially painful since you're playing on the easiest of graphics screen where you can just MOV a byte to draw a pixel.

In a program that needs fast graphics it can be very advantageous to have the ES segment register point at the video buffer permanently.

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

This is all it takes to clear the screen:

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

This is how you draw the horizontal line (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
Sep Roland
  • 33,889
  • 7
  • 43
  • 76
  • tysm for that! But the problem is that two keys can't be pressed at once to move both boards at the same time, so idk if this will fix it. Thanks tho I'm going to use your code to make my project feel 20x better. Btw how do i change the width of the board? Also i don't really know anything about the es or rep stosb, will this be a problem? – orraz1 May 05 '21 at 05:17
  • @orraz1 If you *don't really know anything about the `es` or `rep stosb`*, then get to know the full instruction set of the 8086. It's not too elaborate; it comprises some 50 instructions which is nothing compared to the instruction set of current x86. I believe my second answer will fix your problem of simultaneously pressed keys! – Sep Roland May 07 '21 at 09:03
1

I translated Sep Roland's amazing answer into 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

KeyList db 128 dup (0)

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  [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, 20000  ; 20ms
    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

proc if_Up_isPressedMoveDot
    cmp [byte KeyList + UpArrow], 1
    jne handleUp_end
    
    call coverPurpleDot
    dec [yPurpleDot]
    call drawPurpleDot

    handleUp_end:
    ret
endp

proc if_Down_isPressedMoveDot
    cmp [byte KeyList + DownArrow], 1
    jne handleDown_end
    
    call coverPurpleDot
    inc [yPurpleDot]
    call drawPurpleDot
    
    handleDown_end:
    ret
endp

proc if_W_isPressedMoveDot
    cmp [byte KeyList + KeyW], 1
    jne handleW_end
    
    call coverCyanDot
    dec [yCyanDot]
    call drawCyanDot

    handleW_end:
    ret
endp

proc if_S_isPressedMoveDot
    cmp [byte KeyList + KeyS], 1
    jne handleS_end
    
    call coverCyanDot
    inc [yCyanDot]
    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
    cmp [byte KeyList + 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

To Compile & Run:

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

Bonus Part - Here are the keyboard scan codes that int 09h uses:

 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
  • 2
    Seeing that *UpArrow* is an `equ`, so basically just an immediate, shouldn't you be able to combine `mov bx, offset KeyList` `cmp [byte bx + UpArrow], 1` into the single instruction `cmp [byte KeyList + UpArrow], 1`? – Sep Roland May 15 '22 at 19:49
  • 1
    Doesn't TASM allow to write `dec [word yPurpleDot]` as a short replacement for `mov ax, [yPurpleDot]` `dec ax` `mov [yPurpleDot], ax`? – Sep Roland May 15 '22 at 19:52
  • You're right about `dec [word yPurpleDot]` :) – Nitsan BenHanoch May 17 '22 at 08:59
  • Regarding the other point, `cmp [byte KeyList + UpArrow], 1` doesn't work for me, but `cmp [byte offset KeyList + UpArrow], 1` does. However, although it runs, the latter makes tlink display a "Fixup overflow" error. Maybe it's a bug in the version of tlink that I'm using – Nitsan BenHanoch May 17 '22 at 09:04
  • Thank you so much for reading my code and giving input! – Nitsan BenHanoch May 17 '22 at 10:45
  • UPDATE: tlink is okay with`cmp [byte KeyList + UpArrow], 1` (with or without the offset), once `KeyList` is moved into `DATASEG`. I edited my code accordingly – Nitsan BenHanoch Jun 11 '22 at 20:00
  • `proc onKeyEvent` is still in DATASEG; that seems weird. If that's intentional and necessary, it deserves a comment to explain why it's there. – Peter Cordes Jun 11 '22 at 22:01