Search code examples
assemblybufferx86-16tasmdosbox

how to use mov ah, 0 int 16h so that the snake moves continuously


I'm making the snake game and ran into a problem. For now, I used int 21h, so it only moved when I pressed a key. Now I want it to move continuously so it isn't waiting for me to press a key but just checks if there's something in the buffer. To do that I'm using ah 0h and int 16h however, I don't know how to do that it'll always move. I think I need to add a loop somewhere that checks what is in dir and based on that it goes to where it's supposed to go but I just don't know what the loop is supposed to look like (I also think I should use delay if I'm not wrong). I think the relevant parts are:

    mov ah, 0h
    int 16h
    mov [byte ptr saveal], al
    
    cmp [byte ptr saveal], 'w'
    jz w
    cmp [byte ptr saveal], 'a'
    jz a
    cmp [byte ptr saveal], 's'
    jz s
    cmp [byte ptr saveal], 'd'
    jz d
    cmp [byte ptr saveal], 'q'
    jmp exit
    
w:
    mov [byte ptr dir], 1
    call up
    jmp wasd
s:
    mov [byte ptr dir], 2
    call down
    jmp wasd    
a:
    mov [byte ptr di], 3
    call left
    jmp wasd
d:
    mov [byte ptr dir], 4
    call right
    jmp wasd

The whole code is:

MODEL small
STACK 100h
DATASEG
; --------------------------
; Your variables here
; --------------------------
saveal db ' '
dir db 0       ;not used

app dw 0       ;place of the apple
st_am dw 3
stars dw 0, 0, 0  ;places of the *

CODESEG
proc black
body:
    mov [es:si], ax
    add si, 2
    cmp si, 25*80*2
    jnz body
  ret
endp black

proc up
    mov di, 80*2
    cmp si, di
    jb not_move_up
    
    cmp si, [app]
    jnz move_up
    call apple
    
move_up:
    call delete
    call replace_stars

    sub si, 80*2
    mov ah, 156
    mov al, '*'
    mov [es:si], ax
    mov [stars], si
    
not_move_up:
  ret
endp up
proc down

    mov di, (24*80*2)-1
    cmp si, di
    jg not_move_down
    
    cmp si, [app]
    jnz move_down
    call apple
    
move_down:
    call delete
    call replace_stars
    
    add si, 80*2
    mov ah, 156
    mov al, '*'
    mov [es:si], ax
    mov [stars], si
    
not_move_down:
  ret
endp down
proc left

    mov dx, 0
    mov bx, si
    mov ax, si
    mov si, 80*2
    div si
    mov si, bx
    cmp dx,0
    jz not_move_left
    
    cmp si, [app]
    jnz move_left
    call apple
    
move_left:
    call delete
    call replace_stars
    
    sub si, 2
    mov ah, 156
    mov al, '*'
    mov [es:si], ax
    mov [stars], si
    
not_move_left:
  ret
endp left
proc right

    mov dx, 0
    mov bx, si
    mov ax, si
    mov si, 80*2
    div si
    mov si, bx
    cmp dx,158
    jz not_move_right
    
    cmp si, [app]
    jnz move_right
    call apple
    
move_right:
    call delete
    call replace_stars
    
    add si, 2
    mov ah, 156
    mov al, '*'
    mov [es:si], ax
    mov [stars], si
    
not_move_right:
  ret
endp right

proc apple
    mov ax, 40h
    mov es, ax
    mov ax, [es:6ch]
    and ax, 0000001111111110b
    mov di,ax
    mov [app], di
    mov ax, 0b800h
    mov es, ax
    
    mov al, '@'
    mov ah, 154
    mov [es:di], ax
  ret 
endp apple
    
proc delete
    mov bx, offset stars
    mov di, [st_am]
    dec di
    shl di, 1
    mov di, [bx+di]
    mov ax, 0b800h
    mov es, ax
    
    mov al, ' '
    mov ah, 0
    
    mov [es:di], ax
  ret
endp delete

proc replace_stars
    mov  bx, [st_am]   ; The amount of stars (3 or more)
    dec  bx
    shl  bx, 1         ; Offset to the last star
replace:
    mov  ax, [stars+bx-2]
    mov  [stars+bx], ax
    sub  bx, 2
    jnz  replace
  ret
endp replace_stars

proc first_3_dots
    mov bx, offset stars
    mov si, ((12*80+40)*2)-2
    mov al, '*'
    mov ah, 156
    mov [es:si], ax
    mov [bx], si
    mov si, (12*80+40)*2
    mov al, '*'
    mov ah, 156
    mov [es:si], ax
    mov [bx+2], si
    mov si, ((12*80+40)*2)+2
    mov al, '*'
    mov ah, 156
    mov [es:si], ax
    mov [bx+4], si
  ret
endp first_3_dots

proc delay
    mov cx, 0FFFFh
delay1:
    mov ax, 300
delay2:
    dec ax
    jnz delay2
    loop delay1
  ret
endp delay

proc incS
    
  ret
endp incS

start:
    mov ax, @data
    mov ds, ax
; --------------------------
; Your code here
; --------------------------
    mov ax, 0b800h
    mov es, ax
    
    mov si,0
    mov al, ' '
    mov ah, 0
    call black
    
    call first_3_dots
    mov si, ((12*80+40)*2)-2    

    call apple
    
wasd:
    mov ah, 0h
    int 16h
    mov [byte ptr saveal], al
    
    cmp [byte ptr saveal], 'w'
    jz w
    cmp [byte ptr saveal], 'a'
    jz a
    cmp [byte ptr saveal], 's'
    jz s
    cmp [byte ptr saveal], 'd'
    jz d
    cmp [byte ptr saveal], 'q'
    jmp exit
    
w:
    mov [byte ptr dir], 1
    call up
    jmp wasd
s:
    mov [byte ptr dir], 2
    call down
    jmp wasd    
a:
    mov [byte ptr di], 3
    call left
    jmp wasd
d:
    mov [byte ptr dir], 4
    call right
    jmp wasd
    
exit:
    mov ax, 4c00h
    int 21h
END start

Solution

  • I think I need to add a loop somewhere that checks what is in dir and based on that it goes to where it's supposed to go but I just don't know what the loop is supposed to look like

    You don't need an additional loop. The wasd main loop is fine. But instead of waiting (blocking) for the user to press a key, simply check (non-blocking) if the user has already pressed a key. If a key is indeed available then store its info in the dir variable and use it normally. And if no key is available right now, then use whatever is in the dir variable as if it had been pressed right now.

    Instead of using values {1,2,3,4}, I would use the ASCII codes themselves, so {'w','a','s','d'}.
    Seeing how you have initialized the snake, I also suggest you initialize the dir variable to point to the left (dir db 'a').

    wasd:
      mov  ah, 01h   ; BIOS.CheckKeystroke
      int  16h       ; -> AX ZF
      mov  al, [dir]
      jz   .key      ; No key available, continue in the same direction
      mov  ah, 00h   ; BIOS.GetKeystroke
      int  16h       ; -> AX
    .key:
      mov  [saveal], al
      cmp  al, 'w'
      je   .w
      cmp  al, 'a'
      je   .a
      cmp  al, 's'
      je   .s
      cmp  al, 'd'
      je   .d
      cmp  al, 'q'
      je   exit
      jmp  wasd      ; Invalid key
        
    .w:
      mov  [dir], al
      call up
      jmp  wasd
    .s:
      mov  [dir], al
      call down
      jmp  wasd    
    .a:
      mov  [dir], al ; You had a typo in `mov [byte ptr di], 3` !!!
      call left      ;                                    ^ 
      jmp  wasd
    .d:
      mov  [dir], al
      call right
      jmp  wasd
    
    exit:
      mov  ax, 4C00h
      int  21h
    

    (I also think I should use delay if I'm not wrong).

    Correct. With the above changes everything will become much too fast. You need to add a suitable delay to the main loop. Waiting for one tick on the BIOS timer is already a good solution:

    wasd:
      xor  ax, ax               ; Equivalent to `mov ax, 0`
      mov  ds, ax               ; This sets DS=0
      mov  ax, [word ptr 046Ch] ; Reads the BIOS timer at address DS:046Ch
    
    .wait:                      ; For as long as the timer hasn't changed,
      cmp  ax, [word ptr 046Ch] ; we stay in this tight loop
      je   .wait                ; Should never take longer than 1/18 sec
    
      mov  ax, @data            ; This restores DS to what you have set
      mov  ds, ax               ; at program start
    
      mov  ah, 01h     ; BIOS.CheckKeystroke
      int  16h         ; -> AX ZF
      mov  al, [dir]
      jz   .key        ; No key available, continue the same direction
    
      ...
    
    

    If it still doesn't work, then try next code that is similar to your very own apple procedure:

    wasd:
      mov  ax, 0040h
      mov  es, ax
      mov  ax, [es:6Ch]
    .wait:
      cmp  ax, [es:6Ch]
      je   .wait
      mov  ax, 0B800h
      mov  es, ax
    
      mov  ah, 01h     ; BIOS.CheckKeystroke
      int  16h         ; -> AX ZF
      mov  al, [dir]
      jz   .key        ; No key available, continue the same direction
    
      ...
    
    

    And in case TASM is a really stupid assembler, then also change:

    mov al, [dir]      -->    mov al, [byte ptr dir]
    mov [saveal], al   -->    mov [byte ptr saveal], al
    mov [dir], al      -->    mov [byte ptr dir], al
    mov [dir], al      -->    mov [byte ptr dir], al
    mov [dir], al      -->    mov [byte ptr dir], al
    mov [dir], al      -->    mov [byte ptr dir], al
    

    [EDIT]

    I assume what you wanted me to is: ```wasd: xor ax, ax mov ds, ax mov ax, [word ptr 046Ch] waitt: ;cant do .wait cmp ax, [word ptr 046Ch] ; je waitt mov ax, @data mov ds, ax mov ah, 0h int 16h mov al, [dir] jz key mov ah, 0h int 16h key: mov [byte ptr saveal], al cmp [byte ptr saveal], 'q' jz exit... ```` it's still takes a long time to respond to a pressed key.

    Your recent comment contains a typo that could explain why it still takes a long time to respond. The first time that you wrote mov ah, 0h int 16h, it needs to be mov ah, 01h int 16h.

    As I was curious whether it would actually work, I have translated your complete program for the FASM assembler. I believe FASM is superior to TASM, so if you're not forced to use TASM, you could consider becoming a FASM user. FASM is a modern assembler that is being actively maintainded by its author, supported by a community, and that comes with an easy to use IDE with no need for any external linker.

    I'm happy to report that it all works! One change that I needed to make though, is slowing the program down even more, by turning the simple delay loop into a pair of nested loops that wait for about 3/18 sec. For your convenience, I have refrained from most other changes...

      ORG  256         ; For a program with the .COM extension
    
    ; *** SETUP ***
      mov  ax, 0B800h  ; CONST ES = 0B800h
      mov  es, ax
      mov  si, 0
      mov  al, ' '
      mov  ah, 0
      call black
      call first_3_dots
      mov  si, ((12*80+40)*2)-2
      call apple
    
    ; *** MAIN LOOP ***
    wasd:
      push ds          ; (1)
      xor  ax, ax      ; Equivalent to `mov ax, 0`
      mov  ds, ax      ; This sets DS=0
      mov  cx, 3       ; Wait about 3/18 sec
    .w1:
      mov  ax, [046Ch] ; Reads the BIOS timer at address DS:046Ch
    .w2:               ; For as long as the timer hasn't changed,
      cmp  ax, [046Ch] ; we stay in this tight loop
      je   .w2         ; Should never take longer than 1/18 sec
      loop .w1
      pop  ds          ; (1) This restores DS
    
      mov  ah, 01h     ; BIOS.CheckKeystroke
      int  16h         ; -> AX ZF
      mov  al, [dir]
      jz   .key        ; No key available, continue the same direction
      mov  ah, 00h     ; BIOS.GetKeystroke
      int  16h         ; -> AX
    .key:
      mov  [saveal], al
      cmp  al, 'w'
      je   .w
      cmp  al, 'a'
      je   .a
      cmp  al, 's'
      je   .s
      cmp  al, 'd'
      je   .d
      cmp  al, 'q'
      je   exit
      jmp  wasd      ; Invalid key
        
    .w:
      mov  [dir], al
      call up
      jmp  wasd
    .s:
      mov  [dir], al
      call down
      jmp  wasd    
    .a:
      mov  [dir], al
      call left
      jmp  wasd
    .d:
      mov  [dir], al
      call right
      jmp  wasd
    
    exit:
      mov  ax, 4C00h
      int  21h
    
    ; *** PROCEDURES ***
    black:
      mov  [es:si], ax
      add  si, 2
      cmp  si, 25*80*2
      jnz  black
      ret
    
    up:
      mov  di, 80*2
      cmp  si, di
      jb   not_move_up
      cmp  si, [app]
      jnz  move_up
      call apple
    move_up:
      call delete
      call replace_stars
      sub  si, 80*2
      mov  ah, 156
      mov  al, '*'
      mov  [es:si], ax
      mov  [stars], si
    not_move_up:
      ret
    
    down:
      mov  di, (24*80*2)-1
      cmp  si, di
      jg   not_move_down
      cmp  si, [app]
      jnz  move_down
      call apple
    move_down:
      call delete
      call replace_stars
      add  si, 80*2
      mov  ah, 156
      mov  al, '*'
      mov  [es:si], ax
      mov  [stars], si
    not_move_down:
      ret
    
    left:
      mov  dx, 0
      mov  bx, si
      mov  ax, si
      mov  si, 80*2
      div  si
      mov  si, bx
      cmp  dx, 0
      jz   not_move_left
      cmp  si, [app]
      jnz  move_left
      call apple
    move_left:
      call delete
      call replace_stars
      sub  si, 2
      mov  ah, 156
      mov  al, '*'
      mov  [es:si], ax
      mov  [stars], si
    not_move_left:
      ret
    
    right:
      mov  dx, 0
      mov  bx, si
      mov  ax, si
      mov  si, 80*2
      div  si
      mov  si, bx
      cmp  dx, 158
      jz   not_move_right
      cmp  si, [app]
      jnz  move_right
      call apple
    move_right:
      call delete
      call replace_stars
      add  si, 2
      mov  ah, 156
      mov  al, '*'
      mov  [es:si], ax
      mov  [stars], si
    not_move_right:
      ret
    
    apple:
      mov  ax, 40h
      mov  es, ax
      mov  ax, [es:6ch]
      and  ax, 0000001111111110b
      mov  di, ax
      mov  [app], di
      mov  ax, 0b800h
      mov  es, ax
      mov  al, '@'
      mov  ah, 154
      mov  [es:di], ax
      ret 
            
    delete:
      mov  bx, stars
      mov  di, [st_am]
      dec  di
      shl  di, 1
      mov  di, [bx+di]
      mov  ax, 0b800h
      mov  es, ax
      mov  al, ' '
      mov  ah, 0
      mov  [es:di], ax
      ret
    
    replace_stars:
      mov  bx, [st_am]   ; The amount of stars (3 or more)
      dec  bx
      shl  bx, 1         ; Offset to the last star
    replace:
      mov  ax, [stars+bx-2]
      mov  [stars+bx], ax
      sub  bx, 2
      jnz  replace
      ret
    
    first_3_dots:
      mov bx, stars
      mov al, '*'
      mov ah, 156
      mov si, ((12*80+40)*2)-2
      mov [es:si], ax
      mov [bx], si
      mov si, (12*80+40)*2
      mov [es:si], ax
      mov [bx+2], si
      mov si, ((12*80+40)*2)+2
      mov [es:si], ax
      mov [bx+4], si
      ret
    ; ------------------------------
    saveal db 0
    dir    db 0
    
    app    dw 0        ; place of the apple
    st_am  dw 3
    stars  dw 0, 0, 0  ; places of the *