典型的贪吃蛇游戏。如何追踪蛇?

时间:2021-03-26 23:56:32

标签: windows assembly dos x86-16

这个棋盘游戏的目标是吃食物和成长。

在最基本的形式中,游戏只使用 3 种颜色:一种用于蛇(一系列相互连接的图块),一种用于食物(随机选择的图块),一种用于背景(未占用的图块)。 因为蛇一直在移动,所以在任何时候蛇的头在哪里都是很明显的。不需要任何图形标记。 玩家可以通过键盘上的方向键控制蛇,瞄准食物。如果食物被吃掉,蛇会再长出一个节段,然后在棋盘上放置下一个食物。 如果蛇撞到边界或撞到自己,游戏就结束了!

为了让蛇移动,在“头部”一侧添加了一个新的部分,并在“尾部”一侧删除了一个现有的部分。在程序中,我们可以将“头”和“尾”的坐标存储在变量中。更新 'head' 变量很容易,但我们如何才能清楚地知道新的 'tail' 会在哪里呢?那么,我们如何追踪蛇呢?是否有必要发明一些数据结构?

1 个答案:

答案 0 :(得分:4)

为了跟踪蛇,我们必须记录它所有片段的位置。我们可以选择存储实际的 (X,Y) 坐标或连续坐标之间变化的指示。我们可以将这些信息存储在视频缓冲区矩阵、我们自己的矩阵或循环缓冲区中。

从视频缓冲矩阵中读取游戏信息。

首先让我们假设使用文本视频模式,其中每个字符单元由一个字符字节 (ASCII) 和一个属性字节(颜色)表示,使我们能够在 16 种前景色和 16 种背景色之间进行选择。如果前景色和背景色恰好相等,那么我们在那里存储什么字符代码就不再重要了。结果输出将始终形成一个单一颜色的实心矩形。 我们可以进行设置,以便当前尾部所在的 tile 的字符字节记录移动的方向,以便定位新的尾部。箭头键的扫描码用于此目的。例如,如果当前尾部位于 (5,8) 且显存中的字符字节保存值为 48h (up),则新尾部将位于 (5,7) .

除了使用字符字节来存储游戏信息之外,我们还可以使用属性字节。如果我们再选择 ASCII 32(空格),视频硬件只需要背景色,我们就可以使用为前景色保留的 4 位空间来记录我们的游戏信息。同样,如果我们选择ASCII 219(全块),视频硬件只需要前景色,我们可以使用为背景色保留的4位空间来记录我们的游戏信息。

在随后的演示程序中,游戏板上的每个图块都由 80x25 文本视频模式的视频缓冲区中的 2 个字符单元组成。这将产生方形瓷砖。生成方形图块的更简单方法是使用 40x25 文本视频模式,但事实证明,对于 Microsoft Windows,40x25 模式与使用 80x25 模式的左半部分相同。这无助于获得漂亮的方形瓷砖。
隐藏光标也仅仅是为了在 Microsoft Windows 中运行演示。

figure 1

; The Snake Game - VRAM (c) 2021 Sep Roland

        ORG     256

MODE=03h
COLS=80
ROWS=25
SLEN=COLS/8                         ; Initial length of snake
MIDP=((ROWS-1)/2)*256+(COLS/2)      ; Center of playfield
BACKCOLOR=66h                       ; Brown
FOODCOLOR=55h                       ; Magenta
SNAKECOLOR=22h                      ; Green
TIMER equ gs:046Ch                  ; BIOS.TimerTick

STRUC Snake a, b, c, d, e
 {
  .Head         dw      a
  .Tail         dw      b
  .Length       dw      c
  .Flow         db      d
  .Speed        db      e
 }

        cld
        xor     ax, ax
        mov     gs, ax
        mov     ax, 0B800h
        mov     es, ax              ; VRAM

        mov     ax, [TIMER]         ; Seed
        mov     [Rand], ax

        mov     ax, MODE            ; BIOS.SetVideoMode
        int     10h
        mov     dx, ROWS*256+0      ; Hide cursor
        mov     bh, 0
        mov     ah, 02h             ; BIOS.SetCursor
        int     10h

; Paint the playfield, draw the snake and food
        xor     di, di
        mov     cx, COLS*(ROWS-1)
        mov     ax, BACKCOLOR*256+0 ; 0 is free
        rep stosw

        mov     di, (((ROWS-1)/2)*COLS+(COLS/2)-SLEN)*2
        mov     cx, SLEN*2
        mov     ax, SNAKECOLOR*256+4Dh
        rep stosw

        call    NewFood             ; -> (AX..DX)

; Show "GO" and wait for a keypress, then begin
        mov     dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h
        mov     ah, 00h             ; BIOS.GetKey
        int     16h                 ; -> AX
        call    Status              ; -> (AX..DX)

Main:   mov     ax, [TIMER]         ; Sync with real time
@@:     cmp     ax, [TIMER]
        je      @b

.kbd:   mov     ah, 01h             ; BIOS.TestKey
        int     16h                 ; -> AX ZF
        jz      .show
        mov     ah, 00h             ; BIOS.GetKey
        int     16h                 ; -> AX
        cmp     al, 27              ; <ESC>
        je      Quit
        cmp     al, 32              ; <SPC>
        jne     .arrow

.speed: mov     al, 11111111b       ; Fast uses every tick
        cmp     [S.Speed], al
        jne     @f
        mov     al, 00010001b       ; Slow uses one out of four ticks
@@:     mov     [S.Speed], al
        jmp     .show

.arrow: mov     al, ah
        cmp     al, 4Dh             ; <RIGHT>
        je      @f
        cmp     al, 48h             ; <UP>
        je      @f
        cmp     al, 4Bh             ; <LEFT>
        je      @f
        cmp     al, 50h             ; <DOWN>
        jne     .show
@@:     mov     [S.Flow], al        ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}

.show:  ror     [S.Speed], 1
        jnc     Main

        mov     al, [S.Flow]        ; {4Dh,48h,4Bh,50h}
        mov     cx, [S.Head]
        call    NextXY              ; -> CX
        call    ReadPlayfieldCell   ; -> AL={0,1,4Dh,48h,4Bh,50h} (BX)
        cmp     al, 1
        je      .eat                ; 0 is free, 1 is food
        ja      DEAD                ; other is snake

.move:  call    NewHead             ; -> (AX..CX)
        call    NewTail             ; -> (AX..CX)
        jmp     Main

.eat:   call    NewHead             ; -> (AX..CX)
        inc     [S.Length]
        call    Status              ; -> (AX..DX)
        call    NewFood             ; -> (AX..DX)
        jmp     Main
; ----------------------------------
; Show "GAME OVER" and wait for <ESC>, then quit
DEAD:   mov     si, Msg
        mov     di, ((ROWS-1)*COLS+(COLS/2)-4)*2
        lodsw                       ; First char and color
@@:     stosw
        lodsb
        cmp     al, 0
        jne     @b

@@:     mov     ah, 00h             ; BIOS.GetKey
        int     16h                 ; -> AX
        cmp     al, 27              ; <ESC>
        jne     @b
; ---   ---   ---   ---   ---   ---
Quit:   mov     ax, 0003h           ; BIOS.SetVideoMode
        int     10h
        mov     ax, 4C00h           ; DOS.Terminate
        int     21h
; ----------------------------------
; IN (al,cx) OUT (cx)
NextXY: cmp     al, 4Dh             ; AL={4Dh,48h,4Bh,50h}
        jne     @f
        add     cl, 2               ; 2 character cells per playfield cell
        cmp     cl, COLS
        je      DEAD
        ret
@@:     cmp     al, 4Bh             ; AL={48h,4Bh,50h}
        jae     @f
        sub     ch, 1
        jb      DEAD
        ret
@@:     ja      @f
        sub     cl, 2
        jb      DEAD
        ret
@@:     add     ch, 1
        cmp     ch, ROWS-1
        je      DEAD
        ret
; ----------------------------------
; IN (cx) OUT () MOD (ax..cx)
NewHead:mov     al, [S.Flow]
        mov     ah, SNAKECOLOR
        call    WritePlayfieldCell  ; -> (BX)
        xchg    cx, [S.Head]
; ---   ---   ---   ---   ---   ---
; IN (ax,cx) OUT () MOD (bx)
WritePlayfieldCell:
        movzx   bx, ch              ; CH is Row
        imul    bx, COLS
        add     bl, cl              ; CL is Column
        adc     bh, 0
        shl     bx, 1
        mov     [es:bx], ax
        mov     [es:bx+3], ah
        ret
; ----------------------------------
; IN () OUT () MOD (ax..cx)
NewTail:mov     cx, [S.Tail]
        call    ReadPlayfieldCell   ; -> AL={4Dh,48h,4Bh,50h} (BX)
        call    NextXY              ; -> CX
        xchg    cx, [S.Tail]
        mov     ax, BACKCOLOR*256+0 ; 0 is free
        jmp     WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
NewFood:mov     ax, [Rand]
        imul    ax, 25173
        add     ax, 13849
        mov     [Rand], ax
        mov     bx, ROWS-1
        xor     dx, dx
        div     bx
        mov     ch, dl
        mov     ax, [Rand]
        mov     bx, COLS/2
        xor     dx, dx
        div     bx
        shl     dl, 1
        mov     cl, dl
        call    ReadPlayfieldCell   ; -> AL={0,1,4Dh,48h,4Bh,50h} (BX)
        cmp     al, 0               ; 0 is free
        jne     NewFood
        mov     ax, FOODCOLOR*256+1 ; 1 is food
        jmp     WritePlayfieldCell
; ----------------------------------
; IN (cx) OUT (al) MOD (bx)
ReadPlayfieldCell:
        movzx   bx, ch              ; CH is Row
        imul    bx, COLS
        add     bl, cl              ; CL is Column
        adc     bh, 0
        shl     bx, 1
        mov     al, [es:bx]
        ret
; ----------------------------------
; IN () OUT () MOD (ax..dx)
Status: mov     ax, [S.Length]
        mov     bx, ((ROWS-1)*COLS+5)*2
        mov     cx, 10
@@:     xor     dx, dx
        div     cx
        add     dx, 0F00h+'0'
        mov     [es:bx], dx
        sub     bx, 2
        test    ax, ax
        jnz     @b
        mov     byte [es:bx], ' '
        ret
; ----------------------------------
Msg     db      'G', 12, 'AME OVER', 0

        ALIGN   2
S       Snake   MIDP+SLEN-2, MIDP-SLEN, SLEN, 4Dh, 00010001b
Rand    dw      ?

从我们自己的矩阵中读取游戏信息。

此解决方案类似于读取视频缓冲区矩阵,但速度更快且更灵活。速度更快,因为与从常规 RAM 读取相比,从 VRAM 读取要慢,并且更灵活,因为屏幕可以保持显示所有字符和所有颜色组合。从某些角度来看“更快”:“MATRIX”程序以 1.1 微秒运行一个典型周期,“VRAM”程序以 2.6 微秒运行一个周期。这有关系吗?并非如此,这两个程序在必要的延迟循环中花费了 99.99% 的时间。

因为内存并不短缺,我们可以浪费一些并从中受益。即使游戏板的列数较少,我们也可以将矩阵设置为 256 列。如果我们然后将 X 存储在地址寄存器的低字节中,例如 BX 并将 Y 存储在同一地址寄存器的高字节中,则奖励将是无需转换即可获得偏移地址 BX矩阵内。

figure 2

; The Snake Game - MATRIX (c) 2021 Sep Roland

        ORG     256

MODE=03h
COLS=80
ROWS=25
SLEN=COLS/8                         ; Initial length of snake
MIDP=((ROWS-1)/2)*256+(COLS/2)      ; Center of playfield
BACKCOLOR=66h                       ; Brown
FOODCOLOR=55h                       ; Magenta
SNAKECOLOR=22h                      ; Green
TIMER equ gs:046Ch                  ; BIOS.TimerTick

STRUC Snake a, b, c, d, e
 {
  .Head         dw      a
  .Tail         dw      b
  .Length       dw      c
  .Flow         db      d
  .Speed        db      e
 }

        cld
        xor     ax, ax
        mov     gs, ax
        mov     ax, 0B800h
        mov     es, ax              ; VRAM

        mov     ax, [TIMER]         ; Seed
        mov     [Rand], ax

        mov     ax, MODE            ; BIOS.SetVideoMode
        int     10h
        mov     dx, ROWS*256+0      ; Hide cursor
        mov     bh, 0
        mov     ah, 02h             ; BIOS.SetCursor
        int     10h

; Paint the playfield and matrix, draw the snake and food
        xor     di, di
        mov     cx, COLS*(ROWS-1)
        mov     ax, BACKCOLOR*256+0 ; 0 is free
        rep stosw

        mov     bx, 256*(ROWS-1)
@@:     dec     bx
        mov     [Mat+bx], al
        jnz     @b

        mov     bx, MIDP-SLEN       ; TailXY
        mov     ax, SNAKECOLOR*256+4Dh
@@:     call    WritePlayfieldCell  ; -> (CX)
        add     bl, 2               ; X+
        cmp     bl, (COLS/2)+SLEN-2 ; HeadX
        jbe     @b

        call    NewFood             ; -> (AX..DX)

; Show "GO" and wait for a keypress, then begin
        mov     dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h
        mov     ah, 00h             ; BIOS.GetKey
        int     16h                 ; -> AX
        call    Status              ; -> (AX..DX)

Main:   mov     ax, [TIMER]         ; Sync with real time
@@:     cmp     ax, [TIMER]
        je      @b

.kbd:   mov     ah, 01h             ; BIOS.TestKey
        int     16h                 ; -> AX ZF
        jz      .show
        mov     ah, 00h             ; BIOS.GetKey
        int     16h                 ; -> AX
        cmp     al, 27              ; <ESC>
        je      Quit
        cmp     al, 32              ; <SPC>
        jne     .arrow

.speed: mov     al, 11111111b       ; Fast uses every tick
        cmp     [S.Speed], al
        jne     @f
        mov     al, 00010001b       ; Slow uses one out of four ticks
@@:     mov     [S.Speed], al
        jmp     .show

.arrow: mov     al, ah
        cmp     al, 4Dh             ; <RIGHT>
        je      @f
        cmp     al, 48h             ; <UP>
        je      @f
        cmp     al, 4Bh             ; <LEFT>
        je      @f
        cmp     al, 50h             ; <DOWN>
        jne     .show
@@:     mov     [S.Flow], al        ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}

.show:  ror     [S.Speed], 1
        jnc     Main

        mov     al, [S.Flow]        ; {4Dh,48h,4Bh,50h}
        mov     bx, [S.Head]
        call    NextXY              ; -> BX
        cmp     byte [Mat+bx], 1
        je      .eat                ; 0 is free, 1 is food
        ja      DEAD                ; other is snake

.move:  call    NewHead             ; -> (AX..CX)
        call    NewTail             ; -> (AX..CX)
        jmp     Main

.eat:   call    NewHead             ; -> (AX..CX)
        inc     [S.Length]
        call    Status              ; -> (AX..DX)
        call    NewFood             ; -> (AX..DX)
        jmp     Main
; ----------------------------------
; Show "GAME OVER" and wait for <ESC>, then quit
DEAD:   mov     si, Msg
        mov     di, ((ROWS-1)*COLS+(COLS/2)-4)*2
        lodsw                       ; First char and color
@@:     stosw
        lodsb
        cmp     al, 0
        jne     @b

@@:     mov     ah, 00h             ; BIOS.GetKey
        int     16h                 ; -> AX
        cmp     al, 27              ; <ESC>
        jne     @b
; ---   ---   ---   ---   ---   ---
Quit:   mov     ax, 0003h           ; BIOS.SetVideoMode
        int     10h
        mov     ax, 4C00h           ; DOS.Terminate
        int     21h
; ----------------------------------
; IN (al,bx) OUT (bx)
NextXY: cmp     al, 4Dh             ; AL={4Dh,48h,4Bh,50h}
        jne     @f
        add     bl, 2               ; 2 character cells per playfield cell
        cmp     bl, COLS
        je      DEAD
        ret
@@:     cmp     al, 4Bh             ; AL={48h,4Bh,50h}
        jae     @f
        sub     bh, 1
        jb      DEAD
        ret
@@:     ja      @f
        sub     bl, 2
        jb      DEAD
        ret
@@:     add     bh, 1
        cmp     bh, ROWS-1
        je      DEAD
        ret
; ----------------------------------
; IN (al,bx) OUT () MOD (ax..cx)
NewHead:xchg    bx, [S.Head]
        mov     [Mat+bx], al
        mov     ah, SNAKECOLOR
        mov     bx, [S.Head]
; ---   ---   ---   ---   ---   ---
; IN (ax,bx) OUT () MOD (cx)
WritePlayfieldCell:
        mov     [Mat+bx], al
        movzx   cx, bh              ; BH is Row
        imul    cx, COLS
        add     cl, bl              ; BL is Column
        adc     ch, 0
        shl     cx, 1
        xchg    bx, cx
        mov     [es:bx+1], ah
        mov     [es:bx+3], ah
        mov     bx, cx
        ret
; ----------------------------------
; IN () OUT () MOD (ax..cx)
NewTail:mov     bx, [S.Tail]
        mov     al, [Mat+bx]        ; -> AL={4Dh,48h,4Bh,50h}
        call    NextXY              ; -> BX
        xchg    bx, [S.Tail]
        mov     ax, BACKCOLOR*256+0 ; 0 is free
        jmp     WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
NewFood:mov     ax, [Rand]
        imul    ax, 25173
        add     ax, 13849
        mov     [Rand], ax
        mov     cx, ROWS-1
        xor     dx, dx
        div     cx
        mov     bh, dl
        mov     ax, [Rand]
        mov     cx, COLS/2
        xor     dx, dx
        div     cx
        shl     dl, 1
        mov     bl, dl
        cmp     byte [Mat+bx], 0    ; 0 is free
        jne     NewFood
        mov     ax, FOODCOLOR*256+1 ; 1 is food
        jmp     WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
Status: mov     ax, [S.Length]
        mov     bx, ((ROWS-1)*COLS+5)*2
        mov     cx, 10
@@:     xor     dx, dx
        div     cx
        add     dx, 0F00h+'0'
        mov     [es:bx], dx
        sub     bx, 2
        test    ax, ax
        jnz     @b
        mov     byte [es:bx], ' '
        ret
; ----------------------------------
Msg     db      'G', 12, 'AME OVER', 0

        ALIGN   2
S       Snake   MIDP+SLEN-2, MIDP-SLEN, SLEN, 4Dh, 00010001b
Rand    rw      1
Mat     rb      256*(ROWS-1)

从环形缓冲区读取实际坐标。

在这个循环缓冲区中,我们记录了从头到尾的所有蛇段的坐标。缓冲区的大小必须能够容纳最长的蛇(或规则允许)。 程序存储指向第一条记录(Head)和最后一条记录后面的指针 记录()。 对于新的蛇头,我们降低 Head 指针并插入新坐标。 对于新的蛇尾,我们只需降低 Tail 指针,丢弃最后一条记录。

因为我们需要保持在环形缓冲区内存的范围内,所以需要一个环绕机制。为环形缓冲区的内存选择二次幂的大小很重要,因为这样我们就可以通过简单的 AND 指令进行环绕。如果我们选择这个 2 的幂大小为 65536,那么我们可以完全放弃这个 AND 操作,因为 CPU 已经在实地址模式下自动回绕到 64KB。

搜索 ringbuffer 需要时间,随着蛇变长,这个时间不可避免地会增加。然而,在一个程序中,出于可玩性的原因,超过 99% 的时间都花在了延迟循环中,这无关紧要!

figure 3

; The Snake Game - RINGBUFFER (c) 2021 Sep Roland

        ORG     256

MODE=03h
COLS=80
ROWS=25
SLEN=COLS/8                         ; Initial length of snake
MIDP=((ROWS-1)/2)*256+(COLS/2)      ; Center of playfield
BACKCOLOR=66h                       ; Brown
FOODCOLOR=55h                       ; Magenta
SNAKECOLOR=22h                      ; Green
TIMER equ gs:046Ch                  ; BIOS.TimerTick

STRUC Snake a, b, c, d, e
 {
  .Head         dw      a
  .Tail         dw      b
  .Length       dw      c
  .Flow         db      d
  .Speed        db      e
 }

        cld
        xor     ax, ax
        mov     gs, ax
        mov     ax, cs
        add     ax, (EOF+15)/16
        mov     ss, ax              ; 512 bytes stack
        mov     sp, 512
        add     ax, 512/16
        mov     fs, ax              ; 64KB ringbuffer
        mov     ax, 0B800h
        mov     es, ax              ; VRAM

        mov     ax, [TIMER]         ; Seed
        mov     [Rand], ax

        mov     ax, MODE            ; BIOS.SetVideoMode
        int     10h
        mov     dx, ROWS*256+0      ; Hide cursor
        mov     bh, 0
        mov     ah, 02h             ; BIOS.SetCursor
        int     10h

; Paint the playfield, draw the snake and food
        xor     di, di
        mov     cx, COLS*(ROWS-1)
        mov     ax, BACKCOLOR*256+0
        rep stosw

        mov     cx, MIDP-SLEN       ; HeadXY==TailXY
@@:     call    NewHead             ; -> (AX..BX)
        add     cl, 2               ; X+
        cmp     cl, (COLS/2)+SLEN-2 ; HeadX
        jbe     @b

        call    NewFood             ; -> (AX..DX)

; Show "GO" and wait for a keypress, then begin
        mov     dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h
        mov     ah, 00h             ; BIOS.GetKey
        int     16h                 ; -> AX
        call    Status              ; -> (AX..DX)

Main:   mov     ax, [TIMER]         ; Sync with real time
@@:     cmp     ax, [TIMER]
        je      @b

.kbd:   mov     ah, 01h             ; BIOS.TestKey
        int     16h                 ; -> AX ZF
        jz      .show
        mov     ah, 00h             ; BIOS.GetKey
        int     16h                 ; -> AX
        cmp     al, 27              ; <ESC>
        je      Quit
        cmp     al, 32              ; <SPC>
        jne     .arrow

.speed: mov     al, 11111111b       ; Fast uses every tick
        cmp     [S.Speed], al
        jne     @f
        mov     al, 00010001b       ; Slow uses one out of four ticks
@@:     mov     [S.Speed], al
        jmp     .show

.arrow: mov     al, ah
        cmp     al, 4Dh             ; <RIGHT>
        je      @f
        cmp     al, 48h             ; <UP>
        je      @f
        cmp     al, 4Bh             ; <LEFT>
        je      @f
        cmp     al, 50h             ; <DOWN>
        jne     .show
@@:     mov     [S.Flow], al        ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}

.show:  ror     [S.Speed], 1
        jnc     Main

        mov     al, [S.Flow]        ; {4Dh,48h,4Bh,50h}
        mov     bx, [S.Head]
        mov     cx, [fs:bx]
        call    NextXY              ; -> CX
        cmp     cx, [FoodXY]
        je      .eat
        call    ScanSnake           ; -> ZF (BX)
        jnz     DEAD                ; CX is (X,Y) of some snake part

.move:  call    NewHead             ; -> (AX BX)
        call    NewTail             ; -> (AX..CX)
        jmp     Main

.eat:   call    NewHead             ; -> (AX BX)
        inc     [S.Length]
        call    Status              ; -> (AX..DX)
        call    NewFood             ; -> (AX..DX)
        jmp     Main
; ----------------------------------
; Show "GAME OVER" and wait for <ESC>, then quit
DEAD:   mov     si, Msg
        mov     di, ((ROWS-1)*COLS+(COLS/2)-4)*2
        lodsw                       ; First char and color
@@:     stosw
        lodsb
        cmp     al, 0
        jne     @b

@@:     mov     ah, 00h             ; BIOS.GetKey
        int     16h                 ; -> AX
        cmp     al, 27              ; <ESC>
        jne     @b
; ---   ---   ---   ---   ---   ---
Quit:   mov     ax, 0003h           ; BIOS.SetVideoMode
        int     10h
        mov     ax, 4C00h           ; DOS.Terminate
        int     21h
; ----------------------------------
; IN (al,cx) OUT (cx)
NextXY: cmp     al, 4Dh             ; AL={4Dh,48h,4Bh,50h}
        jne     @f
        add     cl, 2               ; 2 character cells per playfield cell
        cmp     cl, COLS
        je      DEAD
        ret
@@:     cmp     al, 4Bh             ; AL={48h,4Bh,50h}
        jae     @f
        sub     ch, 1
        jb      DEAD
        ret
@@:     ja      @f
        sub     cl, 2
        jb      DEAD
        ret
@@:     add     ch, 1
        cmp     ch, ROWS-1
        je      DEAD
        ret
; ----------------------------------
; IN (cx) OUT (ZF) MOD (bx)
ScanSnake:
        mov     bx, [S.Tail]
        mov     [fs:bx], cx         ; Sentinel
        mov     bx, [S.Head]
        sub     bx, 2
@@:     add     bx, 2
        cmp     [fs:bx], cx
        jne     @b
        cmp     bx, [S.Tail]
        ret
; ----------------------------------
; IN (cx) OUT () MOD (ax,bx)
NewHead:mov     bx, -2
        xadd    [S.Head], bx
        mov     [fs:bx-2], cx
        mov     ah, SNAKECOLOR
; ---   ---   ---   ---   ---   ---
; IN (ah,cx) OUT () MOD (bx)
WritePlayfieldCell:
        movzx   bx, ch              ; CH is Row
        imul    bx, COLS
        add     bl, cl              ; CL is Column
        adc     bh, 0
        shl     bx, 1
        mov     [es:bx+1], ah
        mov     [es:bx+3], ah
        ret
; ----------------------------------
; IN () OUT () MOD (ax..cx)
NewTail:mov     bx, -2
        xadd    [S.Tail], bx
        mov     cx, [fs:bx-2]
        mov     ah, BACKCOLOR
        jmp     WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
NewFood:mov     ax, [Rand]
        imul    ax, 25173
        add     ax, 13849
        mov     [Rand], ax
        mov     bx, ROWS-1
        xor     dx, dx
        div     bx
        mov     ch, dl
        mov     ax, [Rand]
        mov     bx, COLS/2
        xor     dx, dx
        div     bx
        shl     dl, 1
        mov     cl, dl
        call    ScanSnake           ; -> ZF (BX)
        jnz     NewFood             ; CX is (X,Y) of some snake part
        mov     [FoodXY], cx
        mov     ah, FOODCOLOR
        jmp     WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
Status: mov     ax, [S.Length]
        mov     bx, ((ROWS-1)*COLS+5)*2
        mov     cx, 10
@@:     xor     dx, dx
        div     cx
        add     dx, 0F00h+'0'
        mov     [es:bx], dx
        sub     bx, 2
        test    ax, ax
        jnz     @b
        mov     byte [es:bx], ' '
        ret
; ----------------------------------
Msg     db      'G', 12, 'AME OVER', 0

        ALIGN   2
S       Snake   SLEN*2, SLEN*2, SLEN, 4Dh, 00010001b
FoodXY  dw      ?
Rand    dw      ?

EOF:
相关问题