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

77 Views Asked by At

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
2

There are 2 best solutions below

11
Sep Roland On BEST ANSWER

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 *
1
Weather Vane On

You can use Int 16h with function 01h (non-blocking) to check if a key has been pressed, and if so, function 00h to read the key.

These are probably used by MSVC's _kbhit() and _getch(), where some key presses will provide more than one character: if the first is an 'escape' value such as 0 or 224 for function and cursor control keys, then another value is immediately available. This is shown in a C code answer

That may be the way those BIOS functions work too. You don't seem to use cursor control keys, but maybe you can now!