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

; 贪吃蛇游戏 (Snake Game)

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

; 文本模式 80x25,验证汇编器功能

;

; 操作: 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 3             ; 蛇身长度

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         ; 随机数种子

saved_vmode DB 0             ; 保存的视频模式

 

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

; 程序入口

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

START:

    ; 保存视频模式

    MOV AH, 0FH

    INT 10H

    MOV [saved_vmode], AL

 

    ; 设置段寄存器

    MOV AX, CS

    MOV DS, AX

    MOV AX, CS

    MOV SS, AX

    MOV SP, 2000H

 

    ; 清屏(滚动窗口)

    MOV AX, 0600H

    MOV BH, 07H

    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

 

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

; 游戏结束

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

GAME_OVER_LBL:

    ; 在屏幕中央显示 "GAME OVER"

    MOV DL, 34

    MOV DH, 12

    MOV AH, 02H

    INT 10H

 

    MOV DX, MSG_OVER

    MOV AH, 09H

    INT 21H

 

    ; 显示分数

    MOV DL, 34

    MOV DH, 13

    MOV AH, 02H

    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

    INT 10H

 

    MOV DX, MSG_REPLAY

    MOV AH, 09H

    INT 21H

 

    ; 等待按键

WAIT_KEY:

    ; DOS 功能读取按键(比 INT 16H 更稳定)

    MOV AH, 07H

    INT 21H

    CMP AL, 1BH         ; ESC?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:

    ; 重新开始

    CALL InitGame

    JMP GAME_LOOP

 

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

; 退出程序

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

EXIT_PROG:

    ; DOS 退出(使用 AH=4CH 确保清理)

    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

 

    ; 蛇身第2节在左边 (39, 12)

    MOV WORD PTR [snake_x + 2], 39

    MOV WORD PTR [snake_y + 2], 12

 

    ; 蛇身第3节在左边 (38, 12)

    MOV WORD PTR [snake_x + 4], 38

    MOV WORD PTR [snake_y + 4], 12

 

    ; 生成食物

    CALL GenFood

 

    ; 清屏(滚动窗口)

    MOV AX, 0600H

    MOV BH, 07H

    MOV CX, 0000H

    MOV DX, 184FH

    INT 10H

 

    ; 绘制边框

    CALL DrawBorder

 

    RET

 

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

; 生成食物(避开蛇身)

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

GenFood:

    ; 生成随机位置

GEN_LOOP:

    CALL Random

    AND AX, 007FH        ; 0~127

    CMP AX, 1

    JL GEN_LOOP

    CMP AX, 78

    JG GEN_LOOP

    MOV WORD PTR [food_x], AX

 

    CALL Random

    AND AX, 001FH        ; 0~31

    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)

; 返回: AX = 伪随机数

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

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         ; ESC

    JZ INPUT_ESC

 

    CMP AL, 'W'

    JZ INPUT_UP

    CMP AL, 'w'

    JZ INPUT_UP

    CMP AH, 48H         ; 上箭头扫描码

    JZ INPUT_UP

 

    CMP AL, 'S'

    JZ INPUT_DOWN

    CMP AL, 's'

    JZ INPUT_DOWN

    CMP AH, 50H         ; 下箭头

    JZ INPUT_DOWN

 

    CMP AL, 'A'

    JZ INPUT_LEFT

    CMP AL, 'a'

    JZ INPUT_LEFT

    CMP AH, 4BH         ; 左箭头

    JZ INPUT_LEFT

 

    CMP AL, 'D'

    JZ INPUT_RIGHT

    CMP AL, 'd'

    JZ INPUT_RIGHT

    CMP AH, 4DH         ; 右箭头

    JZ INPUT_RIGHT

 

    JMP INPUT_DONE

 

INPUT_ESC:

    ; 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

    JMP INPUT_DONE

 

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

 

    ; 从尾部向头部移位: snake[i] = snake[i-1]

    PUSH CX

    PUSH SI

    PUSH DI

 

    MOV CX, [snake_len]

    DEC CX              ; len-1 次移位

    CMP CX, 0

    JZ MOVE_HEAD_ONLY

 

    ; SI 指向最后一个元素 (index = len-1)

    MOV SI, CX

    SHL SI, 1           ; SI = (len-1) * 2 (字大小)

 

SHIFT_LOOP:

    ; snake_x[i] = snake_x[i-1]; snake_y[i] = snake_y[i-1]

    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:

    ; 移动蛇头(index=0

    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

    JMP MOVE_DONE

 

MOVE_DONE:

    MOV [snake_x], AX

    MOV [snake_y], BX

 

    POP DI

    POP SI

    POP CX

    RET

 

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

; 碰撞检测

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

CheckCollision:

    PUSH CX

    PUSH SI

 

    ; 检查墙壁碰撞 (X=1~78, Y=2~23)

    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

 

    ; 检查自身碰撞(从第2节开始检查)

    MOV CX, [snake_len]

    DEC CX              ; len-1 往下到 1

    CMP CX, 0

    JZ CHECK_FOOD

    MOV SI, CX

    SHL SI, 1           ; SI = (len-1) * 2

 

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       ; SI >= 2 继续检查(跳过第1节即index=0

 

    ; 检查食物

    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

    INT 10H

    MOV AL, ' '

    MOV BH, 0

    MOV BL, 07H

    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

    INT 10H

 

    MOV AL, 0DBH        ; 蛇身字符

    MOV BH, 0

    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

    INT 10H

 

    MOV AL, 0DBH

    MOV BH, 0

    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

    INT 10H

 

    MOV AL, '*'

    MOV BH, 0

    MOV BL, COLOR_FOOD

    MOV CX, 1

    MOV AH, 09H

    INT 10H

 

    ; 显示分数

    MOV DL, 2

    MOV DH, 0

    MOV AH, 02H

    INT 10H

 

    MOV DX, MSG_SCORE_STR

    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 BH, 0

    MOV DL, 0

    MOV DH, 1

    MOV AH, 02H

    INT 10H

    MOV AL, '+'            ; 左上角

    MOV BH, 0

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

 

    MOV BH, 0

    MOV DL, 79

    MOV DH, 1

    MOV AH, 02H

    INT 10H

    MOV AL, '+'            ; 右上角

    MOV BH, 0

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

 

    MOV BH, 0

    MOV DL, 0

    MOV DH, 24

    MOV AH, 02H

    INT 10H

    MOV AL, '+'            ; 左下角

    MOV BH, 0

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

 

    MOV BH, 0

    MOV DL, 79

    MOV DH, 24

    MOV AH, 02H

    INT 10H

    MOV AL, '+'            ; 右下角

    MOV BH, 0

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

 

    ; 绘制上边界 ( 1, 1~78)

    MOV CX, 78

 

TOP_BORDER:

    MOV BH, 0

    MOV DL, CL

    MOV DH, 1

    MOV AH, 02H

    INT 10H

    PUSH CX

    MOV AL, '-'            ; 水平线

    MOV BH, 0

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    POP CX

    LOOP TOP_BORDER

 

    ; 绘制下边界 ( 24, 1~78)

    MOV CX, 78

 

BOT_BORDER:

    MOV BH, 0

    MOV DL, CL

    MOV DH, 24

    MOV AH, 02H

    INT 10H

    PUSH CX

    MOV AL, '-'

    MOV BH, 0

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    POP CX

    LOOP BOT_BORDER

 

    ; 绘制左边界 ( 2~23, 0)

    MOV CX, 22

 

LEFT_BORDER:

    MOV BH, 0

    MOV DH, CL

    INC DH

    MOV DL, 0

    MOV AH, 02H

    INT 10H

    PUSH CX

    MOV AL, '|'            ; 垂直线

    MOV BH, 0

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    POP CX

    LOOP LEFT_BORDER

 

    ; 绘制右边界 ( 2~23, 79)

    MOV CX, 22

 

RIGHT_BORDER:

    MOV BH, 0

    MOV DH, CL

    INC DH

    MOV DL, 79

    MOV AH, 02H

    INT 10H

    PUSH CX

    MOV AL, '|'

    MOV BH, 0

    MOV BL, COLOR_WALL

    MOV CX, 1

    MOV AH, 09H

    INT 10H

    POP CX

    LOOP RIGHT_BORDER

 

    POP DX

    POP CX

    RET

 

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

; 打印数字 (AX = 无符号整数)

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

PrintNumber:

    PUSH AX

    PUSH BX

    PUSH CX

    PUSH DX

 

    ; 将数字转换为字符串(倒序),余数入栈

    MOV CX, 0

    MOV BX, 10

 

DIV_LOOP:

    MOV DX, 0

    DIV BX

    PUSH DX             ; 余数入栈(8086 兼容,PUSH reg 替代 PUSH imm

    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_SCORE_STR   DB "Score: $"

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

 

; 颜色常量

COLOR_SNAKE EQU 0AH

COLOR_FOOD  EQU 0CH

COLOR_WALL  EQU 07H