如何在16位x86 Intel汇编中实现贪吃蛇的转向功能
16位x86汇编文本模式贪吃蛇转向功能实现方案
我正在修读汇编课程,需要完成一个文本模式(使用字符而非像素)的贪吃蛇游戏项目。目前遇到转向功能实现难题,仅能实现无实际作用的90度转向,希望了解正确的转向实现方法。使用的是16位x86 Intel汇编,当前所有逻辑均为硬编码,未接入键盘输入。
以下是当前代码:
[org 0x0100] jmp _start string : db 'Score ' len: dw 6 Snake_X : dw 10 Snake_Y : dw 5 X_speed: dw 1 Y_speed: dw 1 snakeLen: dw 6 _start: call cls call playingField push 0x000A call Score mov cx,2 gameLoop: push word[snakeLen] ; length of snake call PrintsnakeX ; horizontal movement inc cx cmp cx,20 jl gameLoop push 0x000A call sleep call cls call playingField push 0x000A call Score mov cx,2 gameLoop1: mov ax,[snakeLen] push ax call PrintsnakeY ; vertical movement inc cx cmp cx,10 jl gameLoop1 end: mov ax,0x4c00 int 0x21 PrintsnakeX: push bp mov bp, sp push es push ax push cx push si push di mov ax, 0xb800 mov es, ax ; point es to video base mov cx, word[bp+4] ; load length of snake in cx mov ax, 80 ; load ax with columns per row mul word[Snake_Y] ; y position add ax, word[Snake_X] ; x position shl ax, 1 ; turn into byte offset add ax,cx mov di,ax ; point di to required location mov al, '*' ; point si to string mov ah, 40h ; load attribute in ah push di cld ; auto increment mode s1: cmp cx,0 jz break1 stosw dec cx jmp s1 break1: push 0x000F call sleep mov ah,0x00 pop di stosw mov ax,word[X_speed] add word[Snake_X],ax pop di pop si pop cx pop ax pop es pop bp ret 2 PrintsnakeY: push bp mov bp, sp push es push ax push cx push si push di mov ax, 0xb800 mov es, ax ; point es to video base mov cx, [bp+4] ; load length of string in cx mov ax, 80 ; load al with columns per row mul word[Snake_Y] ; multiply with y position add ax, word[Snake_X] ; add x position add ax,cx add ax,3 shl ax, 1 ; turn into byte offset mov di,ax ; point di to required location mov al, '*' ; point si to string mov ah, 40h ; load attribute in ah push di cld ; auto increment mode .s1: cmp cx,0 jz break stosw add di,158 dec cx jmp .s1 break: push 0x000F call sleep mov ah,0x00 pop di stosw mov ax,word[Y_speed] add word[Snake_Y],ax pop di pop si pop cx pop ax pop es pop bp ret 2 cls: push es push ax push cx push di mov ax, 0xb800 mov es, ax ; point es to video base xor di, di ; point di to top left column mov ax, 0x0720 ; space char in normal attribute mov cx, 2000 ; number of screen locations cld ; auto increment mode rep stosw ; clear the whole screen pop di pop cx pop ax pop es ret sleep: push bp mov bp,sp push cx mov cx,[bp+4] d1: push cx mov cx,0xFFFF delayLoop1: loop delayLoop1 pop cx loop d1 pop cx pop bp ret 2 Score: push bp mov bp,sp push ax mov ax,24 ; y position of score string push ax mov ax,2 ; x position of score string push ax mov ax, string ; point to score string push ax push word[len] ; len of score string call printstr mov ax,24 push ax mov ax,8 push ax mov ax,[bp+4] push ax call digits pop ax pop bp ret 2 playingField: push es push ax mov ax ,0xb800 mov es,ax mov ax,164 push ax mov ax,74 push ax call HLine mov ax,164 push ax mov ax,21 push ax call vLine mov ax,312 push ax mov ax,21 push ax call vLine mov ax,3524 push ax mov ax,75 push ax call HLine pop ax pop es ret HLine: push bp mov bp,sp mov di,[bp+6] mov ax,0x4020 mov cx,[bp+4] cld .lp2: stosw loop .lp2 pop bp ret 4 vLine: push bp mov bp,sp mov di,[bp+6] mov ax,0x4020 mov cx,[bp+4] cld .lp1: stosw add di,158 loop .lp1 pop bp ret 4 printstr: push bp mov bp, sp push es push ax push cx push si push di mov ax, 0xb800 mov es, ax ; point es to video base mov al, 80 ; load al with columns per row mul byte [bp+10] ; multiply with y position add ax, [bp+8] ; add x position shl ax, 1 ; turn into byte offset mov di,ax ; point di to required location mov si, [bp+6] ; point si to string mov cx, [bp+4] ; load length of string in cx mov ah, 09h ; load attribute in ah cld ; auto increment mode nextchar: lodsb ; load next char in al stosw ; print char/attribute pair loop nextchar ; repeat for the whole string pop di pop si pop cx pop ax pop es pop bp ret 8 digits: push bp mov bp,sp push cx push ax push bx push dx push di mov ax, 0xb800 mov es, ax mov al, 80 ; load al with columns per row mul byte [bp+8] ; multiply with y position add ax, [bp+6] ; add x position shl ax, 1 ; turn into byte offset mov di,ax mov ax,[bp+4] mov cx,0 mov bx,10 lp1: mov dx,0 div bx add dl,0x30 push dx inc cx cmp ax,0 jne lp1 cld lp2: pop ax mov ah,0x07 stosw loop lp2 pop di pop dx pop bx pop ax pop si pop bp ret 6
核心问题分析
当前代码的gameLoop和gameLoop1是硬编码的分段移动逻辑,没有基于方向状态的动态控制;且X_speed和Y_speed同时为1,导致蛇始终斜向移动,完全缺失转向的核心逻辑。
正确转向实现步骤
1. 引入方向状态变量
新增方向存储单元,用常量定义四个方向,统一管理蛇的移动方向:
UP equ 0 DOWN equ 1 LEFT equ 2 RIGHT equ 3 direction: db RIGHT ; 初始方向设为向右
2. 修正速度变量逻辑
蛇同一时间只能有一个方向的速度,不能同时存在水平和垂直速度:
- 初始化时,根据初始方向设置速度:比如向右时
X_speed=1,Y_speed=0 - 转向时,重置速度值:
- 转向UP:
Y_speed=-1,X_speed=0 - 转向DOWN:
Y_speed=1,X_speed=0 - 转向LEFT:
X_speed=-1,Y_speed=0 - 转向RIGHT:
X_speed=1,Y_speed=0
- 转向UP:
3. 实现键盘输入检测(替代硬编码)
使用DOS中断int 0x16实现非阻塞式键盘检测,读取按键后调整方向(禁止直接反向,比如向右时不能直接转向左):
check_key: mov ah, 0x01 int 0x16 ; 检测是否有按键按下 jz no_key_press ; 无按键则跳过 mov ah, 0x00 int 0x16 ; 读取按键ASCII码 cmp al, 'w' je set_up cmp al, 's' je set_down cmp al, 'a' je set_left cmp al, 'd' je set_right no_key_press: ret set_up: mov al, [direction] cmp al, DOWN je no_key_press ; 禁止反向 mov byte [direction], UP mov word [Y_speed], -1 mov word [X_speed], 0 ret set_down: mov al, [direction] cmp al, UP je no_key_press mov byte [direction], DOWN mov word [Y_speed], 1 mov word [X_speed], 0 ret set_left: mov al, [direction] cmp al, RIGHT je no_key_press mov byte [direction], LEFT mov word [X_speed], -1 mov word [Y_speed], 0 ret set_right: mov al, [direction] cmp al, LEFT je no_key_press mov byte [direction], RIGHT mov word [X_speed], 1 mov word [Y_speed], 0 ret
4. 统一蛇的绘制与移动逻辑
移除分离的PrintsnakeX和PrintsnakeY,改为通用的DrawSnake函数:
- 用数组存储蛇身每个节点的坐标,比如
snake_body: dw 10,5,9,5,8,5,7,5,6,5,5,5(每个节点占两个字,存X、Y) - 移动时,先计算新头部坐标(当前头部+速度),然后将所有蛇节坐标向后移位,最后更新头部坐标,再重新绘制整个蛇身。
5. 重构主循环
将硬编码的分段循环替换为单一主循环,每次循环先检测键盘调整方向,再更新蛇的位置并绘制:
_start: call cls call playingField push 0x000A call Score ; 初始化方向和速度 mov byte [direction], RIGHT mov word [X_speed], 1 mov word [Y_speed], 0 gameLoop: call check_key ; 检测键盘输入调整方向 push word[snakeLen] call DrawSnake ; 统一绘制蛇身 push 0x000F call sleep cmp word[Snake_X], 22 ; 碰撞左边界检测 jl end cmp word[Snake_X], 94 ; 碰撞右边界检测 jg end cmp word[Snake_Y], 2 ; 碰撞上边界检测 jl end cmp word[Snake_Y], 23 ; 碰撞下边界检测 jg end jmp gameLoop end: mov ax,0x4c00 int 0x21
内容的提问来源于stack exchange,提问作者SAAD ULLAH
相关产品推荐
相关产品推荐

