; ============================================
; 贪吃蛇游戏 (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