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