; ============================================

; 贪吃蛇游戏 (Snake Game)

; 8086 汇编语言,COM 文件格式

; 80x25文本字符模式,无任何图形代码

; 修复:撞自身后按ESC死机、键盘缓冲区残留卡死问题

; 优先使用DOS中断,修复右下角边框加号色彩错误

;

; 操作: WASD 或方向键控制方向

;       ESC 退出游戏

; ============================================

ORG 0100H

jmp START

 

; ============================================

; 数据定义

; ============================================

snake_x     DW 200 DUP(0)   ; 蛇身各节 X 坐标 (1~78)

snake_y     DW 200 DUP(0)   ; 蛇身各节 Y 坐标 (1~23)

snake_len   DW 6             ; 蛇身长度

direction   DB 0             ; 当前方向: 0=, 1=, 2=, 3=

new_dir     DB 0             ; 缓冲的新方向

game_over   DB 0             ; 0=运行中, 1=结束

food_x      DW 0             ; 食物 X

food_y      DW 0             ; 食物 Y

score       DW 0             ; 分数

old_tail_x  DW 0             ; 前一帧蛇尾 X(擦除用)

old_tail_y  DW 0             ; 前一帧蛇尾 Y

delay_val   DW 3000          ; 延迟值(越小越快)

rnd_seed    DW 12345         ; 随机数种子

 

; 颜色常量(文本属性,无图形相关)

COLOR_SNAKE EQU 0AH  ; 亮绿蛇身

COLOR_FOOD  EQU 0CH  ; 亮红食物

COLOR_WALL  EQU 07H  ; 灰白边框

 

; ============================================

; 程序入口

; ============================================

START:

    ; COM段设置

    MOV AX, CS

    MOV DS, AX

    MOV SS, AX

    MOV SP, 2000H

 

    ; 清屏

    MOV AX, 0600H

    MOV BH, COLOR_WALL

    MOV CX, 0000H

    MOV DX, 184FH

    INT 10H

 

    CALL InitGame

 

    ; === 主游戏循环 ===

GAME_LOOP:

    CMP BYTE PTR [game_over], 1

    JZ GAME_OVER_LBL

 

    CALL HandleInput

    CALL MoveSnake

    CALL CheckCollision

    CALL DrawFrame

    CALL Delay

    JMP GAME_LOOP

 

; ============================================

; 游戏结束界面(修复ESC死机核心改动)

; ============================================

GAME_OVER_LBL:

    ; 【修复1】清空键盘缓冲区,消除残留按键导致死循环

    CALL ClearKeyBuffer

 

    ; 光标定位输出GAME OVER

    MOV DL, 34

    MOV DH, 12

    MOV AH, 02H

    MOV BH, 0

    INT 10H

 

    MOV DX, MSG_OVER

    MOV AH, 09H

    INT 21H

 

    ; 分数

    MOV DL, 34

    MOV DH, 13

    MOV AH, 02H

    MOV BH, 0

    INT 10H

 

    MOV DX, MSG_SCORE

    MOV AH, 09H

    INT 21H

 

    MOV AX, [score]

    CALL PrintNumber

 

    ; 重玩提示

    MOV DL, 30

    MOV DH, 14

    MOV AH, 02H

    MOV BH, 0

    INT 10H

 

    MOV DX, MSG_REPLAY

    MOV AH, 09H

    INT 21H

 

; 阻塞等待按键(每次读取前清空缓冲,杜绝ESC残留卡死)

WAIT_KEY:

    CALL ClearKeyBuffer

    MOV AH, 07H        ; DOS无回显读键,不破坏视频状态

    INT 21H

 

    CMP AL, 1BH         ; ESC退出

    JZ EXIT_PROG

 

    CMP AL, 'Y'

    JZ WAIT_KEY_R

    CMP AL, 'y'

    JZ WAIT_KEY_R

    CMP AL, 'N'

    JZ WAIT_KEY_N

    CMP AL, 'n'

    JZ WAIT_KEY_N

    JMP WAIT_KEY

 

WAIT_KEY_N:

    JMP EXIT_PROG

 

WAIT_KEY_R:

    ; 【修复2】重置死亡标记再初始化,防止残留game_over=1

    MOV BYTE PTR [game_over], 0

    CALL InitGame

    JMP GAME_LOOP

 

; ============================================

; 清空键盘缓冲区 【新增修复子程序】

; 解决INT16残留扫描码导致ESC死机

; ============================================

ClearKeyBuffer:

    PUSH AX

CLEAR_LOOP:

    MOV AH, 01H

    INT 16H

    JZ CLEAR_END

    MOV AH, 00H

    INT 16H

    JMP CLEAR_LOOP

CLEAR_END:

    POP AX

    RET

 

; ============================================

; 退出程序

; ============================================

EXIT_PROG:

    MOV AX, 4C00H

    INT 21H

 

; ============================================

; 初始化游戏

; ============================================

InitGame:

    MOV WORD PTR [snake_len], 3

    MOV BYTE PTR [direction], 0

    MOV BYTE PTR [new_dir], 0

    MOV BYTE PTR [game_over], 0

    MOV WORD PTR [score], 0

    MOV WORD PTR [delay_val], 3000

 

    ; 蛇头中央 (40,12)

    MOV WORD PTR [snake_x], 40

    MOV WORD PTR [snake_y], 12

    MOV WORD PTR [snake_x + 2], 39

    MOV WORD PTR [snake_y + 2], 12

    MOV WORD PTR [snake_x + 4], 38

    MOV WORD PTR [snake_y + 4], 12

 

    CALL GenFood

 

    ; 清屏

    MOV AX, 0600H

    MOV BH, COLOR_WALL

    MOV CX, 0000H

    MOV DX, 184FH

    INT 10H

 

    CALL DrawBorder

    RET

 

; ============================================

; 生成食物

; ============================================

GenFood:

GEN_LOOP:

    CALL Random

    AND AX, 007FH

    CMP AX, 1

    JL GEN_LOOP

    CMP AX, 78

    JG GEN_LOOP

    MOV WORD PTR [food_x], AX

 

    CALL Random

    AND AX, 001FH

    CMP AX, 2

    JL GEN_LOOP

    CMP AX, 23

    JG GEN_LOOP

    MOV WORD PTR [food_y], AX

 

    PUSH CX

    PUSH SI

    MOV CX, [snake_len]

    MOV SI, 0

CHECK_OVERLAP:

    CMP CX, 0

    JZ CHECK_DONE

    MOV AX, [snake_x + SI]

    CMP AX, [food_x]

    JNZ CHECK_NEXT

    MOV AX, [snake_y + SI]

    CMP AX, [food_y]

    JZ GEN_LOOP

CHECK_NEXT:

    ADD SI, 2

    DEC CX

    JMP CHECK_OVERLAP

CHECK_DONE:

    POP SI

    POP CX

    RET

 

; ============================================

; LCG随机数

; ============================================

Random:

    PUSH DX

    PUSH BX

    MOV AX, [rnd_seed]

    MOV DX, 0

    MOV BX, 8405H

    MUL BX

    INC AX

    MOV [rnd_seed], AX

    POP BX

    POP DX

    RET

 

; ============================================

; 键盘输入处理

; ============================================

HandleInput:

    MOV AH, 01H

    INT 16H

    JZ INPUT_DONE

 

    MOV AH, 00H

    INT 16H

 

    CMP AL, 1BH

    JZ INPUT_ESC

 

    ; W/

    CMP AL, 'W'

    JZ INPUT_UP

    CMP AL, 'w'

    JZ INPUT_UP

    CMP AH, 48H

    JZ INPUT_UP

 

    ; S/

    CMP AL, 'S'

    JZ INPUT_DOWN

    CMP AL, 's'

    JZ INPUT_DOWN

    CMP AH, 50H

    JZ INPUT_DOWN

 

    ; A/

    CMP AL, 'A'

    JZ INPUT_LEFT

    CMP AL, 'a'

    JZ INPUT_LEFT

    CMP AH, 4BH

    JZ INPUT_LEFT

 

    ; D/

    CMP AL, 'D'

    JZ INPUT_RIGHT

    CMP AL, 'd'

    JZ INPUT_RIGHT

    CMP AH, 4DH

    JZ INPUT_RIGHT

 

    JMP INPUT_DONE

 

INPUT_ESC:

    MOV BYTE PTR [game_over], 1

    JMP INPUT_DONE

 

INPUT_UP:

    CMP BYTE PTR [direction], 3

    JZ INPUT_DONE

    MOV BYTE PTR [new_dir], 1

    JMP INPUT_DONE

INPUT_DOWN:

    CMP BYTE PTR [direction], 1

    JZ INPUT_DONE

    MOV BYTE PTR [new_dir], 3

    JMP INPUT_DONE

INPUT_LEFT:

    CMP BYTE PTR [direction], 0

    JZ INPUT_DONE

    MOV BYTE PTR [new_dir], 2

    JMP INPUT_DONE

INPUT_RIGHT:

    CMP BYTE PTR [direction], 2

    JZ INPUT_DONE

    MOV BYTE PTR [new_dir], 0

INPUT_DONE:

    RET

 

; ============================================

; 移动蛇

; ============================================

MoveSnake:

    PUSH SI

    MOV SI, [snake_len]

    DEC SI

    SHL SI, 1

    MOV AX, [snake_x + SI]

    MOV [old_tail_x], AX

    MOV AX, [snake_y + SI]

    MOV [old_tail_y], AX

    POP SI

 

    MOV AL, [new_dir]

    MOV [direction], AL

 

    PUSH CX

    PUSH SI

    PUSH DI

    MOV CX, [snake_len]

    DEC CX

    CMP CX, 0

    JZ MOVE_HEAD_ONLY

    MOV SI, CX

    SHL SI, 1

SHIFT_LOOP:

    MOV AX, [snake_x + SI - 2]

    MOV [snake_x + SI], AX

    MOV AX, [snake_y + SI - 2]

    MOV [snake_y + SI], AX

    SUB SI, 2

    DEC CX

    CMP CX, 0

    JNZ SHIFT_LOOP

 

MOVE_HEAD_ONLY:

    MOV AX, [snake_x]

    MOV BX, [snake_y]

    CMP BYTE PTR [direction], 0

    JZ MOVE_RIGHT

    CMP BYTE PTR [direction], 1

    JZ MOVE_UP

    CMP BYTE PTR [direction], 2

    JZ MOVE_LEFT

    INC BX

    JMP MOVE_DONE

MOVE_RIGHT:

    INC AX

    JMP MOVE_DONE

MOVE_UP:

    DEC BX

    JMP MOVE_DONE

MOVE_LEFT:

    DEC AX

MOVE_DONE:

    MOV [snake_x], AX

    MOV [snake_y], BX

    POP DI

    POP SI

    POP CX

    RET

 

; ============================================

; 碰撞检测

; ============================================

CheckCollision:

    PUSH CX

    PUSH SI

    ; 墙碰撞

    MOV AX, [snake_x]

    CMP AX, 1

    JL COLLIDE_WALL

    CMP AX, 78

    JG COLLIDE_WALL

    MOV AX, [snake_y]

    CMP AX, 2

    JL COLLIDE_WALL

    CMP AX, 23

    JG COLLIDE_WALL

 

    ; 自身碰撞

    MOV CX, [snake_len]

    DEC CX

    CMP CX, 0

    JZ CHECK_FOOD

    MOV SI, CX

    SHL SI, 1

SELF_LOOP:

    MOV AX, [snake_x]

    CMP AX, [snake_x + SI]

    JNZ SELF_NEXT

    MOV AX, [snake_y]

    CMP AX, [snake_y + SI]

    JZ COLLIDE_SELF

SELF_NEXT:

    SUB SI, 2

    CMP SI, 2

    JGE SELF_LOOP

    JMP CHECK_FOOD

 

COLLIDE_WALL:

COLLIDE_SELF:

    MOV BYTE PTR [game_over], 1

    POP SI

    POP CX

    RET

 

CHECK_FOOD:

    MOV AX, [snake_x]

    CMP AX, [food_x]

    JNZ COLL_DONE

    MOV AX, [snake_y]

    CMP AX, [food_y]

    JNZ COLL_DONE

 

    ; 吃食物加长

    MOV AX, [snake_len]

    INC AX

    MOV [snake_len], AX

    MOV AX, [score]

    ADD AX, 10

    MOV [score], AX

    MOV AX, [delay_val]

    CMP AX, 500

    JLE SPEED_DONE

    SUB AX, 50

    MOV [delay_val], AX

SPEED_DONE:

    CALL GenFood

COLL_DONE:

    POP SI

    POP CX

    RET

 

; ============================================

; 绘制画面

; ============================================

DrawFrame:

    PUSH AX

    PUSH BX

    PUSH CX

    PUSH DX

    PUSH SI

 

    ; 擦除蛇尾

    MOV DL, BYTE PTR [old_tail_x]

    MOV DH, BYTE PTR [old_tail_y]

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    MOV AL, ' '

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

 

    ; 绘制蛇身

    MOV CX, [snake_len]

    DEC CX

    CMP CX, 0

    JL DRAW_HEAD

    MOV SI, CX

    SHL SI, 1

DRAW_BODY_LOOP:

    MOV DL, BYTE PTR [snake_x + SI]

    MOV DH, BYTE PTR [snake_y + SI]

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    MOV AL, 0DBH

    MOV BL, COLOR_SNAKE

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    SUB SI, 2

    CMP SI, 0

    JGE DRAW_BODY_LOOP

 

DRAW_HEAD:

    MOV DL, BYTE PTR [snake_x]

    MOV DH, BYTE PTR [snake_y]

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    MOV AL, 0DBH

    MOV BL, 0AH

    MOV CX, 1

    MOV AH, 09H

    INT 10H

 

    ; 食物

    MOV DL, BYTE PTR [food_x]

    MOV DH, BYTE PTR [food_y]

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    MOV AL, '*'

    MOV BL, COLOR_FOOD

    MOV CX, 1

    MOV AH, 09H

    INT 10H

 

    ; 分数栏

    MOV DL, 2

    MOV DH, 0

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    MOV DX, MSG_SCORE

    MOV AH, 09H

    INT 21H

    MOV AX, [score]

    CALL PrintNumber

 

    POP SI

    POP DX

    POP CX

    POP BX

    POP AX

    RET

 

; ============================================

; 绘制边框(四角颜色统一)

; ============================================

DrawBorder:

    PUSH CX

    PUSH DX

    ; 四角 +

    ; 左上

    MOV DL, 0

    MOV DH, 1

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    MOV AL, '+'

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    ; 右上

    MOV DL, 79

    MOV DH, 1

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    MOV AL, '+'

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    ; 左下

    MOV DL, 0

    MOV DH, 24

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    MOV AL, '+'

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    ; 右下(修复异色)

    MOV DL, 79

    MOV DH, 24

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    MOV AL, '+'

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

 

    ; 上横线

    MOV CX, 78

TOP_BORDER:

    MOV DL, CL

    MOV DH, 1

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    PUSH CX

    MOV AL, '-'

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    POP CX

    LOOP TOP_BORDER

    ; 下横线

    MOV CX, 78

BOT_BORDER:

    MOV DL, CL

    MOV DH, 24

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    PUSH CX

    MOV AL, '-'

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    POP CX

    LOOP BOT_BORDER

    ; 左竖线

    MOV CX, 22

LEFT_BORDER:

    MOV DH, CL

    INC DH

    MOV DL, 0

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    PUSH CX

    MOV AL, '|'

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    POP CX

    LOOP LEFT_BORDER

    ; 右竖线

    MOV CX, 22

RIGHT_BORDER:

    MOV DH, CL

    INC DH

    MOV DL, 79

    MOV AH, 02H

    MOV BH, 0

    INT 10H

    PUSH CX

    MOV AL, '|'

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    POP CX

    LOOP RIGHT_BORDER

    POP DX

    POP CX

    RET

 

; ============================================

; 数字打印

; ============================================

PrintNumber:

    PUSH AX

    PUSH BX

    PUSH CX

    PUSH DX

    MOV CX, 0

    MOV BX, 10

DIV_LOOP:

    MOV DX, 0

    DIV BX

    PUSH DX

    INC CX

    CMP AX, 0

    JNZ DIV_LOOP

PRINT_DIGIT:

    POP DX

    ADD DL, '0'

    MOV AH, 02H

    INT 21H

    LOOP PRINT_DIGIT

    POP DX

    POP CX

    POP BX

    POP AX

    RET

 

; ============================================

; 延迟

; ============================================

Delay:

    PUSH CX

    PUSH DX

    MOV CX, [delay_val]

DELAY_OUTER:

    MOV DX, 100

DELAY_INNER:

    DEC DX

    CMP DX, 0

    JNZ DELAY_INNER

    LOOP DELAY_OUTER

    POP DX

    POP CX

    RET

 

; ============================================

; 字符串

; ============================================

MSG_OVER        DB "GAME OVER!$"

MSG_SCORE       DB "Score: $"

MSG_REPLAY      DB "Play again? [Y]es / [N]o / ESC-Exit$"