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

Sep*_*and 0 windows assembly dos x86-16

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

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

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

Sep*_*and 5

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

\n

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

\n

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

\n

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

\n

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

\n

图1

\n
; The Snake Game - VRAM (c) 2021 Sep Roland\n\n        ORG     256\n\nMODE=03h\nCOLS=80\nROWS=25\nSLEN=COLS/8                         ; Initial length of snake\nMIDP=((ROWS-1)/2)*256+(COLS/2)      ; Center of playfield\nBACKCOLOR=66h                       ; Brown\nFOODCOLOR=55h                       ; Magenta\nSNAKECOLOR=22h                      ; Green\nTIMER equ gs:046Ch                  ; BIOS.TimerTick\n\nSTRUC Snake a, b, c, d, e\n {\n  .Head         dw      a\n  .Tail         dw      b\n  .Length       dw      c\n  .Flow         db      d\n  .Speed        db      e\n }\n\n        cld\n        xor     ax, ax\n        mov     gs, ax\n        mov     ax, 0B800h\n        mov     es, ax              ; VRAM\n\n        mov     ax, [TIMER]         ; Seed\n        mov     [Rand], ax\n\n        mov     ax, MODE            ; BIOS.SetVideoMode\n        int     10h\n        mov     dx, ROWS*256+0      ; Hide cursor\n        mov     bh, 0\n        mov     ah, 02h             ; BIOS.SetCursor\n        int     10h\n\n; Paint the playfield, draw the snake and food\n        xor     di, di\n        mov     cx, COLS*(ROWS-1)\n        mov     ax, BACKCOLOR*256+0 ; 0 is free\n        rep stosw\n\n        mov     di, (((ROWS-1)/2)*COLS+(COLS/2)-SLEN)*2\n        mov     cx, SLEN*2\n        mov     ax, SNAKECOLOR*256+4Dh\n        rep stosw\n\n        call    NewFood             ; -> (AX..DX)\n\n; Show "GO" and wait for a keypress, then begin\n        mov     dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h\n        mov     ah, 00h             ; BIOS.GetKey\n        int     16h                 ; -> AX\n        call    Status              ; -> (AX..DX)\n\nMain:   mov     ax, [TIMER]         ; Sync with real time\n@@:     cmp     ax, [TIMER]\n        je      @b\n\n.kbd:   mov     ah, 01h             ; BIOS.TestKey\n        int     16h                 ; -> AX ZF\n        jz      .show\n        mov     ah, 00h             ; BIOS.GetKey\n        int     16h                 ; -> AX\n        cmp     al, 27              ; <ESC>\n        je      Quit\n        cmp     al, 32              ; <SPC>\n        jne     .arrow\n\n.speed: mov     al, 11111111b       ; Fast uses every tick\n        cmp     [S.Speed], al\n        jne     @f\n        mov     al, 00010001b       ; Slow uses one out of four ticks\n@@:     mov     [S.Speed], al\n        jmp     .show\n\n.arrow: mov     al, ah\n        cmp     al, 4Dh             ; <RIGHT>\n        je      @f\n        cmp     al, 48h             ; <UP>\n        je      @f\n        cmp     al, 4Bh             ; <LEFT>\n        je      @f\n        cmp     al, 50h             ; <DOWN>\n        jne     .show\n@@:     mov     [S.Flow], al        ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}\n\n.show:  ror     [S.Speed], 1\n        jnc     Main\n\n        mov     al, [S.Flow]        ; {4Dh,48h,4Bh,50h}\n        mov     cx, [S.Head]\n        call    NextXY              ; -> CX\n        call    ReadPlayfieldCell   ; -> AL={0,1,4Dh,48h,4Bh,50h} (BX)\n        cmp     al, 1\n        je      .eat                ; 0 is free, 1 is food\n        ja      DEAD                ; other is snake\n\n.move:  call    NewHead             ; -> (AX..CX)\n        call    NewTail             ; -> (AX..CX)\n        jmp     Main\n\n.eat:   call    NewHead             ; -> (AX..CX)\n        inc     [S.Length]\n        call    Status              ; -> (AX..DX)\n        call    NewFood             ; -> (AX..DX)\n        jmp     Main\n; ----------------------------------\n; Show "GAME OVER" and wait for <ESC>, then quit\nDEAD:   mov     si, Msg\n        mov     di, ((ROWS-1)*COLS+(COLS/2)-4)*2\n        lodsw                       ; First char and color\n@@:     stosw\n        lodsb\n        cmp     al, 0\n        jne     @b\n\n@@:     mov     ah, 00h             ; BIOS.GetKey\n        int     16h                 ; -> AX\n        cmp     al, 27              ; <ESC>\n        jne     @b\n; ---   ---   ---   ---   ---   ---\nQuit:   mov     ax, 0003h           ; BIOS.SetVideoMode\n        int     10h\n        mov     ax, 4C00h           ; DOS.Terminate\n        int     21h\n; ----------------------------------\n; IN (al,cx) OUT (cx)\nNextXY: cmp     al, 4Dh             ; AL={4Dh,48h,4Bh,50h}\n        jne     @f\n        add     cl, 2               ; 2 character cells per playfield cell\n        cmp     cl, COLS\n        je      DEAD\n        ret\n@@:     cmp     al, 4Bh             ; AL={48h,4Bh,50h}\n        jae     @f\n        sub     ch, 1\n        jb      DEAD\n        ret\n@@:     ja      @f\n        sub     cl, 2\n        jb      DEAD\n        ret\n@@:     add     ch, 1\n        cmp     ch, ROWS-1\n        je      DEAD\n        ret\n; ----------------------------------\n; IN (cx) OUT () MOD (ax..cx)\nNewHead:mov     al, [S.Flow]\n        mov     ah, SNAKECOLOR\n        call    WritePlayfieldCell  ; -> (BX)\n        xchg    cx, [S.Head]\n; ---   ---   ---   ---   ---   ---\n; About this fall-thru: The old head needs to point at the new head,\n; therefore we update it with possibly new directional info held in [S.Flow].\n; ---   ---   ---   ---   ---   ---\n; IN (ax,cx) OUT () MOD (bx)\nWritePlayfieldCell:\n        movzx   bx, ch              ; CH is Row\n        imul    bx, COLS\n        add     bl, cl              ; CL is Column\n        adc     bh, 0\n        shl     bx, 1\n        mov     [es:bx], ax\n        mov     [es:bx+3], ah\n        ret\n; ----------------------------------\n; IN () OUT () MOD (ax..cx)\nNewTail:mov     cx, [S.Tail]\n        call    ReadPlayfieldCell   ; -> AL={4Dh,48h,4Bh,50h} (BX)\n        call    NextXY              ; -> CX\n        xchg    cx, [S.Tail]\n        mov     ax, BACKCOLOR*256+0 ; 0 is free\n        jmp     WritePlayfieldCell\n; ----------------------------------\n; IN () OUT () MOD (ax..dx)\nNewFood:mov     ax, [Rand]\n        imul    ax, 25173\n        add     ax, 13849\n        mov     [Rand], ax\n        mov     bx, ROWS-1\n        xor     dx, dx\n        div     bx\n        mov     ch, dl\n        mov     ax, [Rand]\n        mov     bx, COLS/2\n        xor     dx, dx\n        div     bx\n        shl     dl, 1\n        mov     cl, dl\n        call    ReadPlayfieldCell   ; -> AL={0,1,4Dh,48h,4Bh,50h} (BX)\n        cmp     al, 0               ; 0 is free\n        jne     NewFood\n        mov     ax, FOODCOLOR*256+1 ; 1 is food\n        jmp     WritePlayfieldCell\n; ----------------------------------\n; IN (cx) OUT (al) MOD (bx)\nReadPlayfieldCell:\n        movzx   bx, ch              ; CH is Row\n        imul    bx, COLS\n        add     bl, cl              ; CL is Column\n        adc     bh, 0\n        shl     bx, 1\n        mov     al, [es:bx]\n        ret\n; ----------------------------------\n; IN () OUT () MOD (ax..dx)\nStatus: mov     ax, [S.Length]\n        mov     bx, ((ROWS-1)*COLS+5)*2\n        mov     cx, 10\n@@:     xor     dx, dx\n        div     cx\n        add     dx, 0F00h+\'0\'\n        mov     [es:bx], dx\n        sub     bx, 2\n        test    ax, ax\n        jnz     @b\n        mov     byte [es:bx], \' \'\n        ret\n; ----------------------------------\nMsg     db      \'G\', 12, \'AME OVER\', 0\n\n        ALIGN   2\nS       Snake   MIDP+SLEN-2, MIDP-SLEN, SLEN, 4Dh, 00010001b\nRand    dw      ?\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

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

\n

图2

\n
; The Snake Game - MATRIX (c) 2021 Sep Roland\n\n        ORG     256\n\nMODE=03h\nCOLS=80\nROWS=25\nSLEN=COLS/8                         ; Initial length of snake\nMIDP=((ROWS-1)/2)*256+(COLS/2)      ; Center of playfield\nBACKCOLOR=66h                       ; Brown\nFOODCOLOR=55h                       ; Magenta\nSNAKECOLOR=22h                      ; Green\nTIMER equ gs:046Ch                  ; BIOS.TimerTick\n\nSTRUC Snake a, b, c, d, e\n {\n  .Head         dw      a\n  .Tail         dw      b\n  .Length       dw      c\n  .Flow         db      d\n  .Speed        db      e\n }\n\n        cld\n        xor     ax, ax\n        mov     gs, ax\n        mov     ax, 0B800h\n        mov     es, ax              ; VRAM\n\n        mov     ax, [TIMER]         ; Seed\n        mov     [Rand], ax\n\n        mov     ax, MODE            ; BIOS.SetVideoMode\n        int     10h\n        mov     dx, ROWS*256+0      ; Hide cursor\n        mov     bh, 0\n        mov     ah, 02h             ; BIOS.SetCursor\n        int     10h\n\n; Paint the playfield and matrix, draw the snake and food\n        xor     di, di\n        mov     cx, COLS*(ROWS-1)\n        mov     ax, BACKCOLOR*256+0 ; 0 is free\n        rep stosw\n\n        mov     bx, 256*(ROWS-1)\n@@:     dec     bx\n        mov     [Mat+bx], al\n        jnz     @b\n\n        mov     bx, MIDP-SLEN       ; TailXY\n        mov     ax, SNAKECOLOR*256+4Dh\n@@:     call    WritePlayfieldCell  ; -> (CX)\n        add     bl, 2               ; X+\n        cmp     bl, (COLS/2)+SLEN-2 ; HeadX\n        jbe     @b\n\n        call    NewFood             ; -> (AX..DX)\n\n; Show "GO" and wait for a keypress, then begin\n        mov     dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h\n        mov     ah, 00h             ; BIOS.GetKey\n        int     16h                 ; -> AX\n        call    Status              ; -> (AX..DX)\n\nMain:   mov     ax, [TIMER]         ; Sync with real time\n@@:     cmp     ax, [TIMER]\n        je      @b\n\n.kbd:   mov     ah, 01h             ; BIOS.TestKey\n        int     16h                 ; -> AX ZF\n        jz      .show\n        mov     ah, 00h             ; BIOS.GetKey\n        int     16h                 ; -> AX\n        cmp     al, 27              ; <ESC>\n        je      Quit\n        cmp     al, 32              ; <SPC>\n        jne     .arrow\n\n.speed: mov     al, 11111111b       ; Fast uses every tick\n        cmp     [S.Speed], al\n        jne     @f\n        mov     al, 00010001b       ; Slow uses one out of four ticks\n@@:     mov     [S.Speed], al\n        jmp     .show\n\n.arrow: mov     al, ah\n        cmp     al, 4Dh             ; <RIGHT>\n        je      @f\n        cmp     al, 48h             ; <UP>\n        je      @f\n        cmp     al, 4Bh             ; <LEFT>\n        je      @f\n        cmp     al, 50h             ; <DOWN>\n        jne     .show\n@@:     mov     [S.Flow], al        ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}\n\n.show:  ror     [S.Speed], 1\n        jnc     Main\n\n        mov     al, [S.Flow]        ; {4Dh,48h,4Bh,50h}\n        mov     bx, [S.Head]\n        call    NextXY              ; -> BX\n        cmp     byte [Mat+bx], 1\n        je      .eat                ; 0 is free, 1 is food\n        ja      DEAD                ; other is snake\n\n.move:  call    NewHead             ; -> (AX..CX)\n        call    NewTail             ; -> (AX..CX)\n        jmp     Main\n\n.eat:   call    NewHead             ; -> (AX..CX)\n        inc     [S.Length]\n        call    Status              ; -> (AX..DX)\n        call    NewFood             ; -> (AX..DX)\n        jmp     Main\n; ----------------------------------\n; Show "GAME OVER" and wait for <ESC>, then quit\nDEAD:   mov     si, Msg\n        mov     di, ((ROWS-1)*COLS+(COLS/2)-4)*2\n        lodsw                       ; First char and color\n@@:     stosw\n        lodsb\n        cmp     al, 0\n        jne     @b\n\n@@:     mov     ah, 00h             ; BIOS.GetKey\n        int     16h                 ; -> AX\n        cmp     al, 27              ; <ESC>\n        jne     @b\n; ---   ---   ---   ---   ---   ---\nQuit:   mov     ax, 0003h           ; BIOS.SetVideoMode\n        int     10h\n        mov     ax, 4C00h           ; DOS.Terminate\n        int     21h\n; ----------------------------------\n; IN (al,bx) OUT (bx)\nNextXY: cmp     al, 4Dh             ; AL={4Dh,48h,4Bh,50h}\n        jne     @f\n        add     bl, 2               ; 2 character cells per playfield cell\n        cmp     bl, COLS\n        je      DEAD\n        ret\n@@:     cmp     al, 4Bh             ; AL={48h,4Bh,50h}\n        jae     @f\n        sub     bh, 1\n        jb      DEAD\n        ret\n@@:     ja      @f\n        sub     bl, 2\n        jb      DEAD\n        ret\n@@:     add     bh, 1\n        cmp     bh, ROWS-1\n        je      DEAD\n        ret\n; ----------------------------------\n; IN (al,bx) OUT () MOD (ax..cx)\nNewHead:xchg    bx, [S.Head]\n        mov     [Mat+bx], al\n        mov     ah, SNAKECOLOR\n        mov     bx, [S.Head]\n; ---   ---   ---   ---   ---   ---\n; IN (ax,bx) OUT () MOD (cx)\nWritePlayfieldCell:\n        mov     [Mat+bx], al\n        movzx   cx, bh              ; BH is Row\n        imul    cx, COLS\n        add     cl, bl              ; BL is Column\n        adc     ch, 0\n        shl     cx, 1\n        xchg    bx, cx\n        mov     [es:bx+1], ah\n        mov     [es:bx+3], ah\n        mov     bx, cx\n        ret\n; ----------------------------------\n; IN () OUT () MOD (ax..cx)\nNewTail:mov     bx, [S.Tail]\n        mov     al, [Mat+bx]        ; -> AL={4Dh,48h,4Bh,50h}\n        call    NextXY              ; -> BX\n        xchg    bx, [S.Tail]\n        mov     ax, BACKCOLOR*256+0 ; 0 is free\n        jmp     WritePlayfieldCell\n; ----------------------------------\n; IN () OUT () MOD (ax..dx)\nNewFood:mov     ax, [Rand]\n        imul    ax, 25173\n        add     ax, 13849\n        mov     [Rand], ax\n        mov     cx, ROWS-1\n        xor     dx, dx\n        div     cx\n        mov     bh, dl\n        mov     ax, [Rand]\n        mov     cx, COLS/2\n        xor     dx, dx\n        div     cx\n        shl     dl, 1\n        mov     bl, dl\n        cmp     byte [Mat+bx], 0    ; 0 is free\n        jne     NewFood\n        mov     ax, FOODCOLOR*256+1 ; 1 is food\n        jmp     WritePlayfieldCell\n; ----------------------------------\n; IN () OUT () MOD (ax..dx)\nStatus: mov     ax, [S.Length]\n        mov     bx, ((ROWS-1)*COLS+5)*2\n        mov     cx, 10\n@@:     xor     dx, dx\n        div     cx\n        add     dx, 0F00h+\'0\'\n        mov     [es:bx], dx\n        sub     bx, 2\n        test    ax, ax\n        jnz     @b\n        mov     byte [es:bx], \' \'\n        ret\n; ----------------------------------\nMsg     db      \'G\', 12, \'AME OVER\', 0\n\n        ALIGN   2\nS       Snake   MIDP+SLEN-2, MIDP-SLEN, SLEN, 4Dh, 00010001b\nRand    rw      1\nMat     rb      256*(ROWS-1)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

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

\n

搜索环形缓冲区需要时间,并且随着蛇变长,这个时间不可避免地会增加。然而,在一个程序中,出于可玩性的原因,99%以上的时间都花在延迟循环上,这没什么关系!

\n

图3

\n
; The Snake Game - RINGBUFFER (c) 2021 Sep Roland\n\n        ORG     256\n\nMODE=03h\nCOLS=80\nROWS=25\nSLEN=COLS/8                         ; Initial length of snake\nMIDP=((ROWS-1)/2)*256+(COLS/2)      ; Center of playfield\nBACKCOLOR=66h                       ; Brown\nFOODCOLOR=55h                       ; Magenta\nSNAKECOLOR=22h                      ; Green\nTIMER equ gs:046Ch                  ; BIOS.TimerTick\n\nSTRUC Snake a, b, c, d, e\n {\n  .Head         dw      a\n  .Tail         dw      b\n  .Length       dw      c\n  .Flow         db      d\n  .Speed        db      e\n }\n\n        cld\n        xor     ax, ax\n        mov     gs, ax\n        mov     ax, cs\n        add     ax, (EOF+15)/16\n        mov     ss, ax              ; 512 bytes stack\n        mov     sp, 512\n        add     ax, 512/16\n        mov     fs, ax              ; 64KB ringbuffer\n        mov     ax, 0B800h\n        mov     es, ax              ; VRAM\n\n        mov     ax, [TIMER]         ; Seed\n        mov     [Rand], ax\n\n        mov     ax, MODE            ; BIOS.SetVideoMode\n        int     10h\n        mov     dx, ROWS*256+0      ; Hide cursor\n        mov     bh, 0\n        mov     ah, 02h             ; BIOS.SetCursor\n        int     10h\n\n; Paint the playfield, draw the snake and food\n        xor     di, di\n        mov     cx, COLS*(ROWS-1)\n        mov     ax, BACKCOLOR*256+0\n        rep stosw\n\n        mov     cx, MIDP-SLEN       ; HeadXY==TailXY\n@@:     call    NewHead             ; -> (AX..BX)\n        add     cl, 2               ; X+\n        cmp     cl, (COLS/2)+SLEN-2 ; HeadX\n        jbe     @b\n\n        call    NewFood             ; -> (AX..DX)\n\n; Show "GO" and wait for a keypress, then begin\n        mov     dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h\n        mov     ah, 00h             ; BIOS.GetKey\n        int     16h                 ; -> AX\n        call    Status              ; -> (AX..DX)\n\nMain:   mov     ax, [TIMER]         ; Sync with real time\n@@:     cmp     ax, [TIMER]\n        je      @b\n\n.kbd:   mov     ah, 01h             ; BIOS.TestKey\n        int     16h                 ; -> AX ZF\n        jz      .show\n        mov     ah, 00h             ; BIOS.GetKey\n        int     16h                 ; -> AX\n        cmp     al, 27              ; <ESC>\n        je      Quit\n        cmp     al, 32              ; <SPC>\n        jne     .arrow\n\n.speed: mov     al, 11111111b       ; Fast uses every tick\n        cmp     [S.Speed], al\n        jne     @f\n        mov     al, 00010001b       ; Slow uses one out of four ticks\n@@:     mov     [S.Speed], al\n        jmp     .show\n\n.arrow: mov     al, ah\n        cmp     al, 4Dh             ; <RIGHT>\n        je      @f\n        cmp     al, 48h             ; <UP>\n        je      @f\n        cmp     al, 4Bh             ; <LEFT>\n        je      @f\n        cmp     al, 50h             ; <DOWN>\n        jne     .show\n@@:     mov     [S.Flow], al        ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}\n\n.show:  ror     [S.Speed], 1\n        jnc     Main\n\n        mov     al, [S.Flow]        ; {4Dh,48h,4Bh,50h}\n        mov     bx, [S.Head]\n        mov     cx, [fs:bx]\n        call    NextXY              ; -> CX\n        cmp     cx, [FoodXY]\n        je      .eat\n        call    ScanSnake           ; -> ZF (BX)\n        jnz     DEAD                ; CX is (X,Y