问题描述
这个棋盘游戏的目的是吃食物并成长.
在最基本的形式中,游戏仅使用三种颜色:一种用于蛇(一系列相互连接的瓷砖),一种用于食物(随机选择的瓷砖)和一种用于背景(无人使用的瓷砖).由于蛇一直在移动,因此随时可以清楚地看到蛇的头部在哪里.无需任何图形标记.玩家可以通过键盘上的箭头键控制蛇,瞄准食物.如果食物被吃掉,蛇将再长一段,然后将下面的食物放在板上.如果蛇撞到边界或撞到自己,游戏就结束了!
为使蛇移动,在头"侧添加了一个新的片段,并在尾巴"侧移除了一个现有的片段.在程序中,我们可以将头部"和尾部"的坐标存储在变量中.更新"head"变量很容易,但是我们如何明确知道新的"tail"将在哪里呢?那么,我们如何跟踪蛇呢?有必要发明一些数据结构吗?
要跟踪蛇,我们必须记录其所有段的位置.我们可以选择存储实际(X,Y)坐标或连续坐标之间变化的指示.我们可以将这些信息存储在视频缓冲区矩阵,我们自己的矩阵或循环缓冲区中.
从视频缓冲区矩阵读取游戏信息.
首先让我们假设使用文本视频模式,其中每个字符单元由一个字符字节(ASCII)和一个属性字节(颜色)表示,该属性字节使我们能够在16种前景色和16种背景色之间进行选择.如果前景色和背景色碰巧相等,那么我们存储在其中的字符代码就不再重要了.结果输出将始终形成单色实心矩形.我们可以进行设置,以便当前尾巴所在的图块的字符字节记录要移动的方向以便定位新尾巴.箭头键的扫描代码用于此目的.例如,如果当前尾巴位于(5,8)并且视频存储器中的字符字节保持值48h(),则新尾巴将位于(5,7)
我们也可以使用属性字节来代替使用字符字节来存储游戏信息.如果然后选择ASCII 32(空格),则视频硬件仅需要背景颜色,并且我们可以使用为前景色保留的4位空间来记录我们的游戏信息.同样,如果我们选择ASCII 219(全块),则视频硬件仅需要前景色,并且可以使用为背景色保留的4位空间来记录我们的游戏信息.
在下面的演示程序中,游戏板上的每个图块均由80x25文本视频模式的视频缓冲区中的2个字符单元组成.这将产生正方形图块.生成正方形瓦片的更简单方法是使用40x25文本视频模式,但事实证明,对于Microsoft Windows而言,40x25模式与使用80x25模式的左半部分相同.这不利于获得漂亮的方形瓷砖.
隐藏光标还完全是为了在Microsoft Windows中运行演示程序.
;蛇游戏-VRAM(c)2021年9月罗兰组织256模式= 03小时COLS = 80ROWS = 25SLEN = COLS/8;蛇的初始长度MIDP =((ROWS-1)/2)* 256 +(COLS/2);运动场中心BACKCOLOR = 66h;棕色的FOODCOLOR = 55h;品红SNAKECOLOR = 22h;绿色的定时器设备gs:046Ch;BIOS.TimerTickSTRUC Snake a,b,c,d,e{.头dw a尾巴dw b长度dw c.流数据库.速度数据库}cld异或斧mov gs,ax机斧,0B800hmov es,ax;虚拟RAMmov ax,[TIMER];种子mov [Rand],axmov ax,MODE;BIOS.SetVideoMode整数10hmov dx,ROWS * 256 + 0;隐藏光标mov bh,0mov ah,02h;BIOS设置向导整数10h;油漆运动场,画蛇和食物or迪mov cx,COLS *(ROWS-1)mov ax,BACKCOLOR * 256 + 0;0是免费的代表斯托mov di,((((ROWS-1)/2)* COLS +(COLS/2)-SLEN)* 2mov cx,SLEN * 2mov ax,SNAKECOLOR * 256 + 4Dh代表斯托叫NewFood;->(AX..DX);显示"GO"然后等待按键,然后开始mov dword [es:((ROWS-1)* COLS + 4)* 2],0A4F0A47hmov ah,00h;BIOS获取密钥int 16小时;->斧头通话状态;->(AX..DX)主要:mov ax,[TIMER];实时同步@@:cmp斧头,[计时器]je @b.kbd:mov ah,01h;BIOS测试键int 16小时;->AX ZF表演mov ah,00h;BIOS获取密钥int 16小时;->斧头cmp al,27岁;< ESC>吉戒烟cmp al,32;< SPC>jne .arrow速度:mov al,11111111b;快速利用每一刻cmp [S.Speed]等jne @f运动00010001b;慢用四分之一@@:mov [S.Speed],其他jmp .show.arrow:mov al,啊cmp al,4Dh;< RIGHT>je @fcmp al,48小时;< UP>je @fcmp al,4Bh;< LEFT>je @fcmp al,50小时;< DOWN>jne.显示@@:mov [S.Flow]等;AL = {4Dh = X +,48h = Y-,4Bh = X-,50h = Y +}.show:ror [S.Speed],1jnc主运动[S.Flow];{4Dh,48h,4Bh,50h}mov cx,[S.Head]呼叫NextXY;->CX调用ReadPlayfieldCell;->AL = {0,1,4Dh,48h,4Bh,50h}(BX)cmp al,1je.eat;0是免费的,1是食物杰DEAD;其他是蛇.move:致电NewHead;->(AX..CX)呼叫NewTail;->(AX..CX)jmp主.eat:致电NewHead;->(AX..CX)inc [S.Length]通话状态;->(AX..DX)叫NewFood;->(AX..DX)jmp主;----------------------------------;显示游戏结束"然后等待< ESC> ;,然后退出死亡:mov si,Msgmov di,((行-1)* COLS +(COLS/2)-4)* 2罗兹;第一个字符和颜色@@:stosw罗兹cmp al,0jne @b@@:mov ah,00h;BIOS获取密钥int 16小时;->斧头cmp al,27岁;< ESC>jne @b;-------退出:斧头,0003h;BIOS.SetVideoMode整数10hmov ax,4C00h;DOS终止整数21h;----------------------------------;输入(al,cx)输出(cx)NextXY:cmp al,4Dh;AL = {4Dh,48h,4Bh,50h}jne @f加cl,2;每个运动场单元2个字符单元cmp cl,COLS杰·迪德退回@@:cmp al,4Bh;AL = {48h,4Bh,50h}jae @f子频道1死了退回@@:ja @f子分类2死了退回@@:添加ch,1cmp ch,ROWS-1杰·迪德退回;----------------------------------;IN(cx)OUT()MOD(ax..cx)NewHead:mov al,[S.Flow]mov啊,蛇色调用WritePlayfieldCell;->(BX)xchg cx,[S.Head];-------;输入(ax,cx)输出()MOD(bx)WritePlayfieldCell:movzx bx,ch;CH是行imul bx,COLS加bl,cl;CL是列adc bh,0shl bx,1mov [es:bx],axmov [es:bx + 3],啊退回;----------------------------------;IN()OUT()MOD(ax..cx)NewTail:mov cx,[S.Tail]调用ReadPlayfieldCell;->AL = {4Dh,48h,4Bh,50h}(BX)呼叫NextXY;->CXxchg cx,[S.Tail]mov ax,BACKCOLOR * 256 + 0;0是免费的jmp WritePlayfieldCell;----------------------------------;IN()OUT()MOD(ax..dx)NewFood:移动斧头,[兰德]伊穆尔斧头,25173加斧头13849mov [Rand],axmov bx,ROWS-1xor dx,dxdiv bxmov ch,dlmov ax,[兰德]mov bx,COLS/2xor dx,dxdiv bxshl dl,1mov cl,dl调用ReadPlayfieldCell;->AL = {0,1,4Dh,48h,4Bh,50h}(BX)cmp al,0;0是免费的新食品杂志mov ax,FOODCOLOR * 256 + 1;1是食物jmp WritePlayfieldCell;----------------------------------;输入(cx)输出(al)MOD(bx)ReadPlayfieldCell:movzx bx,ch;CH是行imul bx,COLS加bl,cl;CL是列adc bh,0shl bx,1mov al,[es:bx]退回;----------------------------------;IN()OUT()MOD(ax..dx)状态:mov ax,[S.Length]mov bx,((行-1)* COLS + 5)* 2mov cx,10@@:xor dx,dxdiv cx加dx,0F00h +'0'mov [es:bx],dx子bx,2测试斧,斧头jnz @bmov字节[es:bx],''退回;----------------------------------Msg db'G',12,'AME OVER',0对齐2S蛇MIDP + SLEN-2,MIDP-SLEN,SLEN,4Dh,00010001b兰德dw?
从我们自己的矩阵中读取游戏信息.
此解决方案类似于读取视频缓冲区矩阵,但更快,更灵活.由于从VRAM读取要比从常规RAM读取慢,因此速度更快,并且由于屏幕可以继续显示所有字符和所有颜色组合,因此更加灵活.从某种角度讲更快":"MATRIX"程序运行一个典型的周期为1.1微秒,"VRAM"程序运行一个周期为2.6微秒.这有关系吗?并非如此,这两个程序都在必要的延迟循环中花费了99.99%的时间.
由于不存在内存不足,我们可以浪费一些内存并从中受益.即使游戏板的列数较少,我们也可以将矩阵设置为256列.然后,如果我们将X存储在地址
;蛇游戏-MATRIX(c)2021年9月罗兰组织256模式= 03小时COLS = 80ROWS = 25SLEN = COLS/8;蛇的初始长度MIDP =((ROWS-1)/2)* 256 +(COLS/2);运动场中心BACKCOLOR = 66h;棕色的FOODCOLOR = 55h;品红SNAKECOLOR = 22h;绿色的定时器设备gs:046Ch;BIOS.TimerTickSTRUC Snake a,b,c,d,e{.头dw a尾巴dw b长度dw c.流数据库.速度数据库}cld异或斧mov gs,ax机斧,0B800hmov es,ax;虚拟RAMmov ax,[TIMER];种子mov [Rand],axmov ax,MODE;BIOS.SetVideoMode整数10hmov dx,ROWS * 256 + 0;隐藏光标mov bh,0mov ah,02h;BIOS设置向导整数10h;绘制运动场和矩阵,绘制蛇和食物or迪mov cx,COLS *(ROWS-1)mov ax,BACKCOLOR * 256 + 0;0是免费的代表斯托mov bx,256 *(ROWS-1)@@:dec bxmov [Mat + bx]等jnz @bmov bx,MIDP-SLEN;尾XYmov ax,SNAKECOLOR * 256 + 4Dh@@:调用WritePlayfieldCell;->(CX)加bl,2;X +cmp bl,(COLS/2)+ SLEN-2;头Xjbe @b叫NewFood;->(AX..DX);显示"GO"然后等待按键,然后开始mov dword [es:((ROWS-1)* COLS + 4)* 2],0A4F0A47hmov ah,00h;BIOS获取密钥int 16小时;->斧头通话状态;->(AX..DX)主要:mov ax,[TIMER];实时同步@@:cmp斧头,[计时器]je @b.kbd:mov ah,01h;BIOS测试键int 16小时;->AX ZF表演mov ah,00h;BIOS获取密钥int 16小时;->斧头cmp al,27岁;< ESC>吉戒烟cmp al,32;< SPC>jne .arrow速度:mov al,11111111b;快速利用每一刻cmp [S.Speed]等jne @f运动00010001b;慢用四分之一@@:mov [S.Speed],其他jmp .show.arrow:mov al,啊cmp al,4Dh;< RIGHT>je @fcmp al,48小时;< UP>je @fcmp al,4Bh;< LEFT>je @fcmp al,50小时;< DOWN>jne.显示@@:mov [S.Flow]等;AL = {4Dh = X +,48h = Y-,4Bh = X-,50h = Y +}.show:ror [S.Speed],1jnc主运动[S.Flow];{4Dh,48h,4Bh,50h}mov bx,[S.Head]呼叫NextXY;->BXcmp字节[Mat + bx],1je.eat;0是免费的,1是食物杰DEAD;其他是蛇.move:致电NewHead;->(AX..CX)呼叫NewTail;->(AX..CX)jmp主.eat:致电NewHead;->(AX..CX)inc [S.Length]通话状态;->(AX..DX)叫NewFood;->(AX..DX)jmp主;----------------------------------;显示游戏结束"然后等待< ESC> ;,然后退出死亡:mov si,Msgmov di,((行-1)* COLS +(COLS/2)-4)* 2罗兹;第一个字符和颜色@@:stosw罗兹cmp al,0jne @b@@:mov ah,00h;BIOS获取密钥int 16小时;->斧头cmp al,27岁;< ESC>jne @b;-------退出:斧头,0003h;BIOS.SetVideoMode整数10hmov ax,4C00h;DOS终止整数21h;----------------------------------;输入(al,bx)输出(bx)NextXY:cmp al,4Dh;AL = {4Dh,48h,4Bh,50h}jne @f加bl,2;每个运动场单元2个字符单元cmp bl,COLS杰·迪德退回@@:cmp al,4Bh;AL = {48h,4Bh,50h}jae @f低于bh,1死了退回@@:ja @fsub bl,2死了退回@@:添加bh,1cmp bh,ROWS-1杰·迪德退回;----------------------------------;IN(al,bx)OUT()MOD(ax..cx)NewHead:xchg bx,[S.Head]mov [Mat + bx]等mov啊,蛇色mov bx,[S.Head];-------;输入(ax,bx)输出()MOD(cx)WritePlayfieldCell:mov [Mat + bx]等movzx cx,bh;BH是行imul cx,COLS加cl,bl;BL是专栏adc ch,0shl cx,1xchg bx,cxmov [es:bx + 1],啊mov [es:bx + 3],啊mov bx,cx退回;----------------------------------;IN()OUT()MOD(ax..cx)NewTail:mov bx,[S.Tail]运动[Mat + bx];->AL = {4Dh,48h,4Bh,50h}呼叫NextXY;->BXxchg bx,[S.Tail]mov ax,BACKCOLOR * 256 + 0;0是免费的jmp WritePlayfieldCell;----------------------------------;IN()OUT()MOD(ax..dx)NewFood:移动斧头,[兰德]伊穆尔斧头,25173加斧头13849mov [Rand],axmov cx,ROWS-1xor dx,dxdiv cxmov bh,dlmov ax,[兰德]mov cx,COLS/2xor dx,dxdiv cxshl dl,1mov bl,dlcmp字节[Mat + bx],0;0是免费的新食品杂志mov ax,FOODCOLOR * 256 + 1;1是食物jmp WritePlayfieldCell;----------------------------------;IN()OUT()MOD(ax..dx)状态:mov ax,[S.Length]mov bx,((行-1)* COLS + 5)* 2mov cx,10@@:xor dx,dxdiv cx加dx,0F00h +'0'mov [es:bx],dx子bx,2测试斧,斧头jnz @bmov字节[es:bx],''退回;----------------------------------Msg db'G',12,'AME OVER',0对齐2S蛇MIDP + SLEN-2,MIDP-SLEN,SLEN,4Dh,00010001b兰德rw 1垫rb 256 *(ROWS-1)
从环形缓冲区读取实际坐标.
在此圆形缓冲区中,我们记录了从头到尾的所有蛇段的坐标.缓冲区的大小必须能够容纳可能的最长蛇(或规则所允许的).该程序将指针存储到第一个记录( Head )并指向最后一个记录的指针记录( Tail ).对于新的蛇头,我们降低 Head 指针并插入新的坐标.对于新的蛇尾,我们只需降低 Tail 指针,丢弃最后一条记录.
由于我们需要保留在环形缓冲区内存的范围内,因此需要一种环绕机制.为环形缓冲区的内存选择2的幂很重要,因为这样我们就可以通过一条简单的 AND
指令进行环绕操作.而且,如果我们选择该2的幂为65536,那么我们可以完全放弃此 AND
操作,因为在实地址模式下,CPU已经自动绕回64KB.
搜索环形缓冲区需要花费时间,随着蛇的变长,这个时间将不可避免地增加.但是,在一个出于可玩性原因而设计的程序中,有超过99%的时间都花在延迟循环中,这没关系!
;蛇游戏-RINGBUFFER(c)2021年9月罗兰组织256模式= 03小时COLS = 80ROWS = 25SLEN = COLS/8;蛇的初始长度MIDP =((ROWS-1)/2)* 256 +(COLS/2);运动场中心BACKCOLOR = 66h;棕色的FOODCOLOR = 55h;品红SNAKECOLOR = 22h;绿色的定时器设备gs:046Ch;BIOS.TimerTickSTRUC Snake a,b,c,d,e{.头dw a尾巴dw b长度dw c.流数据库.速度数据库}cld异或斧mov gs,axmov斧头,cs加斧(EOF + 15)/16mov ss,ax;512字节堆栈mov sp,512加斧头512/16mov fs,ax;64KB环形缓冲区机斧,0B800hmov es,ax;虚拟RAMmov ax,[TIMER];种子mov [Rand],axmov ax,MODE;BIOS.SetVideoMode整数10hmov dx,ROWS * 256 + 0;隐藏光标mov bh,0mov ah,02h;BIOS设置向导整数10h;油漆运动场,画蛇和食物or迪mov cx,COLS *(ROWS-1)mov ax,BACKCOLOR * 256 + 0代表斯托mov cx,MIDP-SLEN;HeadXY == TailXY@@:致电NewHead;->(AX..BX)加cl,2;X +cmp cl,(COLS/2)+ SLEN-2;头Xjbe @b叫NewFood;->(AX..DX);显示"GO"然后等待按键,然后开始mov dword [es:((ROWS-1)* COLS + 4)* 2],0A4F0A47hmov ah,00h;BIOS获取密钥int 16小时;->斧头通话状态;->(AX..DX)主要:mov ax,[TIMER];实时同步@@:cmp斧头,[计时器]je @b.kbd:mov ah,01h;BIOS测试键int 16小时;->AX ZF表演mov ah,00h;BIOS获取密钥int 16小时;->斧头cmp al,27岁;< ESC>吉戒烟cmp al,32;< SPC>jne .arrow速度:mov al,11111111b;快速利用每一刻cmp [S.Speed]等jne @f运动00010001b;慢用四分之一@@:mov [S.Speed],其他jmp .show.arrow:mov al,啊cmp al,4Dh;< RIGHT>je @fcmp al,48小时;< UP>je @fcmp al,4Bh;< LEFT>je @fcmp al,50小时;< DOWN>jne.显示@@:mov [S.Flow]等;AL = {4Dh = X +,48h = Y-,4Bh = X-,50h = Y +}.show:ror [S.Speed],1jnc主运动[S.Flow];{4Dh,48h,4Bh,50h}mov bx,[S.Head]mov cx,[fs:bx]呼叫NextXY;->CXcmp cx,[FoodXY]je .eat调用ScanSnake;->采埃孚(BX)jnz DEAD;CX是某些蛇形部分的(X,Y).move:致电NewHead;->(AX BX)呼叫NewTail;->(AX..CX)jmp主.eat:致电NewHead;->(AX BX)inc [S.Length]通话状态;->(AX..DX)叫NewFood;->(AX..DX)jmp主;----------------------------------;显示游戏结束"然后等待< ESC> ;,然后退出死亡:mov si,Msgmov di,((行-1)* COLS +(COLS/2)-4)* 2罗兹;第一个字符和颜色@@:stosw罗兹cmp al,0jne @b@@:mov ah,00h;BIOS获取密钥int 16小时;->斧头cmp al,27岁;< ESC>jne @b;-------退出:斧头,0003h;BIOS.SetVideoMode整数10hmov ax,4C00h;DOS终止整数21h;----------------------------------;输入(al,cx)输出(cx)NextXY:cmp al,4Dh;AL = {4Dh,48h,4Bh,50h}jne @f加cl,2;每个运动场单元2个字符单元cmp cl,COLS杰·迪德退回@@:cmp al,4Bh;AL = {48h,4Bh,50h}jae @f子频道1死了退回@@:ja @f子分类2死了退回@@:添加ch,1cmp ch,ROWS-1杰·迪德退回;----------------------------------;输入(cx)输出(ZF)MOD(bx)ScanSnake:mov bx,[S.Tail]mov [fs:bx],cx;哨兵mov bx,[S.Head]子bx,2@@:添加bx,2cmp [fs:bx],cxjne @bcmp bx,[S.Tail]退回;----------------------------------;IN(cx)OUT()MOD(ax,bx)NewHead:mov bx,-2xadd [S.Head],bxmov [fs:bx-2],cxmov啊,蛇色;-------;IN(ah,cx)OUT()MOD(bx)WritePlayfieldCell:movzx bx,ch;CH是行imul bx,COLS加bl,cl;CL是列adc bh,0shl bx,1mov [es:bx + 1],啊mov [es:bx + 3],啊退回;----------------------------------;IN()OUT()MOD(ax..cx)NewTail:mov bx,-2xadd [S.Tail],bxmov cx,[fs:bx-2]mov啊,BACKCOLORjmp WritePlayfieldCell;----------------------------------;IN()OUT()MOD(ax..dx)NewFood:移动斧头,[兰德]伊穆尔斧头,25173加斧头13849mov [Rand],axmov bx,ROWS-1xor dx,dxdiv bxmov ch,dlmov ax,[兰德]mov bx,COLS/2xor dx,dxdiv bxshl dl,1mov cl,dl调用ScanSnake;->采埃孚(BX)jnz NewFood;CX是某些蛇形部分的(X,Y)mov [FoodXY],cxmov啊,FOODCOLORjmp WritePlayfieldCell;----------------------------------;IN()OUT()MOD(ax..dx)状态:mov ax,[S.Length]mov bx,((行-1)* COLS + 5)* 2mov cx,10@@:xor dx,dxdiv cx加dx,0F00h +'0'mov [es:bx],dx子bx,2测试斧,斧头jnz @bmov字节[es:bx],''退回;----------------------------------Msg db'G',12,'AME OVER',0对齐2S蛇SLEN * 2,SLEN * 2,SLEN,4Dh,00010001bFoodXY dw?兰德dw?EOF:
The objective of this board game is to eat the food and grow.
In its most basic form, the game only uses 3 colors: one for the snake (a series of interconnected tiles), one for the food (a randomly chosen tile), and one for the background (the unoccupied tiles).Because the snake is continuously on the move, it will be obvious enough where the head of the snake is at any one time. There's no need for any graphical markings.The player can control the snake through the arrow keys on the keyboard, aiming for the food. If the food is eaten, the snake will grow an additional one segment and a following food is placed on the board.If the snake crashes into the border or bumps into itself, the game is over!
To make the snake move, a new segment is added at the 'head' side and an existing segment is removed at the 'tail' side. In the program we can store the coordinates for both 'head' and 'tail' in variables. Updating the 'head' variable is easy, but how can we unambiguously know where the new 'tail' will be? So, how can we keep track of the snake? Will it be necessary to invent some data structure?
To keep track of the snake we have to record the position of all of its segments. We can choose to store the actual (X,Y) coordinates or an indication of the change between successive coordinates. We can store this information in the video buffer matrix, in a matrix of our own, or in a circular buffer.
Reading game information from the video buffer matrix.
First let us assume using a text video mode where each character cell is represented by a character byte (ASCII), and an attribute byte (color) that enables us to choose between 16 foreground colors and 16 background colors. If the foreground and background colors happen to be equal, it won't matter anymore what character code we have stored there. The resulting output will always form a single color, solid rectangle.We can set things up so that the character byte of the tile where the current tail is located, records the direction to move to in order to position the new tail. The scancodes of the arrow keys are used for this purpose. For example, if the current tail is at (5,8) and the character byte in the video memory holds the value 48h (), then the new tail will be positioned at (5,7).
Instead of using the character byte to store the game information, we could also use the attribute byte. If we then select ASCII 32 (space), the video hardware only needs the background color and we can use the 4-bit space reserved for the foreground color to record our game information. Similarly if we select ASCII 219 (full block), the video hardware only needs the foreground color and we can use the 4-bit space reserved for the background color to record our game information.
In the demonstration programs that follow, every tile on the game board is made up of 2 character cells in the video buffer of the 80x25 text video mode. This is what will produce square tiles. The simpler method to produce square tiles would have been to use the 40x25 text video mode, but as it turns out, for Microsoft Windows the 40x25 mode is the same as using the left half of the 80x25 mode. That does not help in getting nice square tiles.
Hiding the cursor is also solely for the benefit of running the demo's in Microsoft Windows.
; The Snake Game - VRAM (c) 2021 Sep Roland
ORG 256
MODE=03h
COLS=80
ROWS=25
SLEN=COLS/8 ; Initial length of snake
MIDP=((ROWS-1)/2)*256+(COLS/2) ; Center of playfield
BACKCOLOR=66h ; Brown
FOODCOLOR=55h ; Magenta
SNAKECOLOR=22h ; Green
TIMER equ gs:046Ch ; BIOS.TimerTick
STRUC Snake a, b, c, d, e
{
.Head dw a
.Tail dw b
.Length dw c
.Flow db d
.Speed db e
}
cld
xor ax, ax
mov gs, ax
mov ax, 0B800h
mov es, ax ; VRAM
mov ax, [TIMER] ; Seed
mov [Rand], ax
mov ax, MODE ; BIOS.SetVideoMode
int 10h
mov dx, ROWS*256+0 ; Hide cursor
mov bh, 0
mov ah, 02h ; BIOS.SetCursor
int 10h
; Paint the playfield, draw the snake and food
xor di, di
mov cx, COLS*(ROWS-1)
mov ax, BACKCOLOR*256+0 ; 0 is free
rep stosw
mov di, (((ROWS-1)/2)*COLS+(COLS/2)-SLEN)*2
mov cx, SLEN*2
mov ax, SNAKECOLOR*256+4Dh
rep stosw
call NewFood ; -> (AX..DX)
; Show "GO" and wait for a keypress, then begin
mov dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
call Status ; -> (AX..DX)
Main: mov ax, [TIMER] ; Sync with real time
@@: cmp ax, [TIMER]
je @b
.kbd: mov ah, 01h ; BIOS.TestKey
int 16h ; -> AX ZF
jz .show
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
je Quit
cmp al, 32 ; <SPC>
jne .arrow
.speed: mov al, 11111111b ; Fast uses every tick
cmp [S.Speed], al
jne @f
mov al, 00010001b ; Slow uses one out of four ticks
@@: mov [S.Speed], al
jmp .show
.arrow: mov al, ah
cmp al, 4Dh ; <RIGHT>
je @f
cmp al, 48h ; <UP>
je @f
cmp al, 4Bh ; <LEFT>
je @f
cmp al, 50h ; <DOWN>
jne .show
@@: mov [S.Flow], al ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}
.show: ror [S.Speed], 1
jnc Main
mov al, [S.Flow] ; {4Dh,48h,4Bh,50h}
mov cx, [S.Head]
call NextXY ; -> CX
call ReadPlayfieldCell ; -> AL={0,1,4Dh,48h,4Bh,50h} (BX)
cmp al, 1
je .eat ; 0 is free, 1 is food
ja DEAD ; other is snake
.move: call NewHead ; -> (AX..CX)
call NewTail ; -> (AX..CX)
jmp Main
.eat: call NewHead ; -> (AX..CX)
inc [S.Length]
call Status ; -> (AX..DX)
call NewFood ; -> (AX..DX)
jmp Main
; ----------------------------------
; Show "GAME OVER" and wait for <ESC>, then quit
DEAD: mov si, Msg
mov di, ((ROWS-1)*COLS+(COLS/2)-4)*2
lodsw ; First char and color
@@: stosw
lodsb
cmp al, 0
jne @b
@@: mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
jne @b
; --- --- --- --- --- ---
Quit: mov ax, 0003h ; BIOS.SetVideoMode
int 10h
mov ax, 4C00h ; DOS.Terminate
int 21h
; ----------------------------------
; IN (al,cx) OUT (cx)
NextXY: cmp al, 4Dh ; AL={4Dh,48h,4Bh,50h}
jne @f
add cl, 2 ; 2 character cells per playfield cell
cmp cl, COLS
je DEAD
ret
@@: cmp al, 4Bh ; AL={48h,4Bh,50h}
jae @f
sub ch, 1
jb DEAD
ret
@@: ja @f
sub cl, 2
jb DEAD
ret
@@: add ch, 1
cmp ch, ROWS-1
je DEAD
ret
; ----------------------------------
; IN (cx) OUT () MOD (ax..cx)
NewHead:mov al, [S.Flow]
mov ah, SNAKECOLOR
call WritePlayfieldCell ; -> (BX)
xchg cx, [S.Head]
; --- --- --- --- --- ---
; IN (ax,cx) OUT () MOD (bx)
WritePlayfieldCell:
movzx bx, ch ; CH is Row
imul bx, COLS
add bl, cl ; CL is Column
adc bh, 0
shl bx, 1
mov [es:bx], ax
mov [es:bx+3], ah
ret
; ----------------------------------
; IN () OUT () MOD (ax..cx)
NewTail:mov cx, [S.Tail]
call ReadPlayfieldCell ; -> AL={4Dh,48h,4Bh,50h} (BX)
call NextXY ; -> CX
xchg cx, [S.Tail]
mov ax, BACKCOLOR*256+0 ; 0 is free
jmp WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
NewFood:mov ax, [Rand]
imul ax, 25173
add ax, 13849
mov [Rand], ax
mov bx, ROWS-1
xor dx, dx
div bx
mov ch, dl
mov ax, [Rand]
mov bx, COLS/2
xor dx, dx
div bx
shl dl, 1
mov cl, dl
call ReadPlayfieldCell ; -> AL={0,1,4Dh,48h,4Bh,50h} (BX)
cmp al, 0 ; 0 is free
jne NewFood
mov ax, FOODCOLOR*256+1 ; 1 is food
jmp WritePlayfieldCell
; ----------------------------------
; IN (cx) OUT (al) MOD (bx)
ReadPlayfieldCell:
movzx bx, ch ; CH is Row
imul bx, COLS
add bl, cl ; CL is Column
adc bh, 0
shl bx, 1
mov al, [es:bx]
ret
; ----------------------------------
; IN () OUT () MOD (ax..dx)
Status: mov ax, [S.Length]
mov bx, ((ROWS-1)*COLS+5)*2
mov cx, 10
@@: xor dx, dx
div cx
add dx, 0F00h+'0'
mov [es:bx], dx
sub bx, 2
test ax, ax
jnz @b
mov byte [es:bx], ' '
ret
; ----------------------------------
Msg db 'G', 12, 'AME OVER', 0
ALIGN 2
S Snake MIDP+SLEN-2, MIDP-SLEN, SLEN, 4Dh, 00010001b
Rand dw ?
Reading game information from a matrix of our own.
This solution is similar to reading the video buffer matrix but is both faster and more flexible. Faster because reading from VRAM is slow compared to reading from regular RAM, and more flexible because the screen can keep displaying all of the characters and all of the color combinations. To put 'faster' in some perspective: the 'MATRIX' program runs a typical cycle in 1.1 µsec, and the 'VRAM' program runs a cycle in 2.6 µsec. Does this matter? Not really, both programs spent 99.99% of their time in the necessary delay loop.
Because there is no shortage of memory, we can waste some and benefit from it. Even though the game board has fewer columns, we can setup our matrix for 256 columns. If we then store X in the low byte of an addres register like BX
and Y in the high byte of that same address register, the reward will be that no conversion is needed to obtain the offset address BX
within the matrix.
; The Snake Game - MATRIX (c) 2021 Sep Roland
ORG 256
MODE=03h
COLS=80
ROWS=25
SLEN=COLS/8 ; Initial length of snake
MIDP=((ROWS-1)/2)*256+(COLS/2) ; Center of playfield
BACKCOLOR=66h ; Brown
FOODCOLOR=55h ; Magenta
SNAKECOLOR=22h ; Green
TIMER equ gs:046Ch ; BIOS.TimerTick
STRUC Snake a, b, c, d, e
{
.Head dw a
.Tail dw b
.Length dw c
.Flow db d
.Speed db e
}
cld
xor ax, ax
mov gs, ax
mov ax, 0B800h
mov es, ax ; VRAM
mov ax, [TIMER] ; Seed
mov [Rand], ax
mov ax, MODE ; BIOS.SetVideoMode
int 10h
mov dx, ROWS*256+0 ; Hide cursor
mov bh, 0
mov ah, 02h ; BIOS.SetCursor
int 10h
; Paint the playfield and matrix, draw the snake and food
xor di, di
mov cx, COLS*(ROWS-1)
mov ax, BACKCOLOR*256+0 ; 0 is free
rep stosw
mov bx, 256*(ROWS-1)
@@: dec bx
mov [Mat+bx], al
jnz @b
mov bx, MIDP-SLEN ; TailXY
mov ax, SNAKECOLOR*256+4Dh
@@: call WritePlayfieldCell ; -> (CX)
add bl, 2 ; X+
cmp bl, (COLS/2)+SLEN-2 ; HeadX
jbe @b
call NewFood ; -> (AX..DX)
; Show "GO" and wait for a keypress, then begin
mov dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
call Status ; -> (AX..DX)
Main: mov ax, [TIMER] ; Sync with real time
@@: cmp ax, [TIMER]
je @b
.kbd: mov ah, 01h ; BIOS.TestKey
int 16h ; -> AX ZF
jz .show
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
je Quit
cmp al, 32 ; <SPC>
jne .arrow
.speed: mov al, 11111111b ; Fast uses every tick
cmp [S.Speed], al
jne @f
mov al, 00010001b ; Slow uses one out of four ticks
@@: mov [S.Speed], al
jmp .show
.arrow: mov al, ah
cmp al, 4Dh ; <RIGHT>
je @f
cmp al, 48h ; <UP>
je @f
cmp al, 4Bh ; <LEFT>
je @f
cmp al, 50h ; <DOWN>
jne .show
@@: mov [S.Flow], al ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}
.show: ror [S.Speed], 1
jnc Main
mov al, [S.Flow] ; {4Dh,48h,4Bh,50h}
mov bx, [S.Head]
call NextXY ; -> BX
cmp byte [Mat+bx], 1
je .eat ; 0 is free, 1 is food
ja DEAD ; other is snake
.move: call NewHead ; -> (AX..CX)
call NewTail ; -> (AX..CX)
jmp Main
.eat: call NewHead ; -> (AX..CX)
inc [S.Length]
call Status ; -> (AX..DX)
call NewFood ; -> (AX..DX)
jmp Main
; ----------------------------------
; Show "GAME OVER" and wait for <ESC>, then quit
DEAD: mov si, Msg
mov di, ((ROWS-1)*COLS+(COLS/2)-4)*2
lodsw ; First char and color
@@: stosw
lodsb
cmp al, 0
jne @b
@@: mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
jne @b
; --- --- --- --- --- ---
Quit: mov ax, 0003h ; BIOS.SetVideoMode
int 10h
mov ax, 4C00h ; DOS.Terminate
int 21h
; ----------------------------------
; IN (al,bx) OUT (bx)
NextXY: cmp al, 4Dh ; AL={4Dh,48h,4Bh,50h}
jne @f
add bl, 2 ; 2 character cells per playfield cell
cmp bl, COLS
je DEAD
ret
@@: cmp al, 4Bh ; AL={48h,4Bh,50h}
jae @f
sub bh, 1
jb DEAD
ret
@@: ja @f
sub bl, 2
jb DEAD
ret
@@: add bh, 1
cmp bh, ROWS-1
je DEAD
ret
; ----------------------------------
; IN (al,bx) OUT () MOD (ax..cx)
NewHead:xchg bx, [S.Head]
mov [Mat+bx], al
mov ah, SNAKECOLOR
mov bx, [S.Head]
; --- --- --- --- --- ---
; IN (ax,bx) OUT () MOD (cx)
WritePlayfieldCell:
mov [Mat+bx], al
movzx cx, bh ; BH is Row
imul cx, COLS
add cl, bl ; BL is Column
adc ch, 0
shl cx, 1
xchg bx, cx
mov [es:bx+1], ah
mov [es:bx+3], ah
mov bx, cx
ret
; ----------------------------------
; IN () OUT () MOD (ax..cx)
NewTail:mov bx, [S.Tail]
mov al, [Mat+bx] ; -> AL={4Dh,48h,4Bh,50h}
call NextXY ; -> BX
xchg bx, [S.Tail]
mov ax, BACKCOLOR*256+0 ; 0 is free
jmp WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
NewFood:mov ax, [Rand]
imul ax, 25173
add ax, 13849
mov [Rand], ax
mov cx, ROWS-1
xor dx, dx
div cx
mov bh, dl
mov ax, [Rand]
mov cx, COLS/2
xor dx, dx
div cx
shl dl, 1
mov bl, dl
cmp byte [Mat+bx], 0 ; 0 is free
jne NewFood
mov ax, FOODCOLOR*256+1 ; 1 is food
jmp WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
Status: mov ax, [S.Length]
mov bx, ((ROWS-1)*COLS+5)*2
mov cx, 10
@@: xor dx, dx
div cx
add dx, 0F00h+'0'
mov [es:bx], dx
sub bx, 2
test ax, ax
jnz @b
mov byte [es:bx], ' '
ret
; ----------------------------------
Msg db 'G', 12, 'AME OVER', 0
ALIGN 2
S Snake MIDP+SLEN-2, MIDP-SLEN, SLEN, 4Dh, 00010001b
Rand rw 1
Mat rb 256*(ROWS-1)
Reading the actual coordinates from a ringbuffer.
In this circular buffer we record the coordinates of all the snake's segments going from the head to the tail. The buffer's size must be such that it can accomodate the longest snake possible (or allowed by the rules).The program stores pointers to the first record (Head) and to behind the lastrecord (Tail).For a new snake head, we lower the Head pointer and insert the new coordinates.For a new snake tail, we just lower the Tail pointer, discarding the last record.
Because we need to stay within the confines of the ringbuffer's memory, a wraparounding mechanism is needed. Choosing a power-of-two size for the ringbuffer's memory is important because then we can wraparound via a simple AND
instruction. And if we choose this power-of-two size to be 65536, then we can drop this AND
operation altogether since the CPU will already automatically wraparound at 64KB in the real address mode.
Searching the ringbuffer takes time, and this time will inevitably increase as the snake gets longer. However, in a program where, for playability reasons, more than 99% of the time is spent in a delay loop, it won't matter a bit!
; The Snake Game - RINGBUFFER (c) 2021 Sep Roland
ORG 256
MODE=03h
COLS=80
ROWS=25
SLEN=COLS/8 ; Initial length of snake
MIDP=((ROWS-1)/2)*256+(COLS/2) ; Center of playfield
BACKCOLOR=66h ; Brown
FOODCOLOR=55h ; Magenta
SNAKECOLOR=22h ; Green
TIMER equ gs:046Ch ; BIOS.TimerTick
STRUC Snake a, b, c, d, e
{
.Head dw a
.Tail dw b
.Length dw c
.Flow db d
.Speed db e
}
cld
xor ax, ax
mov gs, ax
mov ax, cs
add ax, (EOF+15)/16
mov ss, ax ; 512 bytes stack
mov sp, 512
add ax, 512/16
mov fs, ax ; 64KB ringbuffer
mov ax, 0B800h
mov es, ax ; VRAM
mov ax, [TIMER] ; Seed
mov [Rand], ax
mov ax, MODE ; BIOS.SetVideoMode
int 10h
mov dx, ROWS*256+0 ; Hide cursor
mov bh, 0
mov ah, 02h ; BIOS.SetCursor
int 10h
; Paint the playfield, draw the snake and food
xor di, di
mov cx, COLS*(ROWS-1)
mov ax, BACKCOLOR*256+0
rep stosw
mov cx, MIDP-SLEN ; HeadXY==TailXY
@@: call NewHead ; -> (AX..BX)
add cl, 2 ; X+
cmp cl, (COLS/2)+SLEN-2 ; HeadX
jbe @b
call NewFood ; -> (AX..DX)
; Show "GO" and wait for a keypress, then begin
mov dword [es:((ROWS-1)*COLS+4)*2], 0A4F0A47h
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
call Status ; -> (AX..DX)
Main: mov ax, [TIMER] ; Sync with real time
@@: cmp ax, [TIMER]
je @b
.kbd: mov ah, 01h ; BIOS.TestKey
int 16h ; -> AX ZF
jz .show
mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
je Quit
cmp al, 32 ; <SPC>
jne .arrow
.speed: mov al, 11111111b ; Fast uses every tick
cmp [S.Speed], al
jne @f
mov al, 00010001b ; Slow uses one out of four ticks
@@: mov [S.Speed], al
jmp .show
.arrow: mov al, ah
cmp al, 4Dh ; <RIGHT>
je @f
cmp al, 48h ; <UP>
je @f
cmp al, 4Bh ; <LEFT>
je @f
cmp al, 50h ; <DOWN>
jne .show
@@: mov [S.Flow], al ; AL={4Dh=X+, 48h=Y-, 4Bh=X-, 50h=Y+}
.show: ror [S.Speed], 1
jnc Main
mov al, [S.Flow] ; {4Dh,48h,4Bh,50h}
mov bx, [S.Head]
mov cx, [fs:bx]
call NextXY ; -> CX
cmp cx, [FoodXY]
je .eat
call ScanSnake ; -> ZF (BX)
jnz DEAD ; CX is (X,Y) of some snake part
.move: call NewHead ; -> (AX BX)
call NewTail ; -> (AX..CX)
jmp Main
.eat: call NewHead ; -> (AX BX)
inc [S.Length]
call Status ; -> (AX..DX)
call NewFood ; -> (AX..DX)
jmp Main
; ----------------------------------
; Show "GAME OVER" and wait for <ESC>, then quit
DEAD: mov si, Msg
mov di, ((ROWS-1)*COLS+(COLS/2)-4)*2
lodsw ; First char and color
@@: stosw
lodsb
cmp al, 0
jne @b
@@: mov ah, 00h ; BIOS.GetKey
int 16h ; -> AX
cmp al, 27 ; <ESC>
jne @b
; --- --- --- --- --- ---
Quit: mov ax, 0003h ; BIOS.SetVideoMode
int 10h
mov ax, 4C00h ; DOS.Terminate
int 21h
; ----------------------------------
; IN (al,cx) OUT (cx)
NextXY: cmp al, 4Dh ; AL={4Dh,48h,4Bh,50h}
jne @f
add cl, 2 ; 2 character cells per playfield cell
cmp cl, COLS
je DEAD
ret
@@: cmp al, 4Bh ; AL={48h,4Bh,50h}
jae @f
sub ch, 1
jb DEAD
ret
@@: ja @f
sub cl, 2
jb DEAD
ret
@@: add ch, 1
cmp ch, ROWS-1
je DEAD
ret
; ----------------------------------
; IN (cx) OUT (ZF) MOD (bx)
ScanSnake:
mov bx, [S.Tail]
mov [fs:bx], cx ; Sentinel
mov bx, [S.Head]
sub bx, 2
@@: add bx, 2
cmp [fs:bx], cx
jne @b
cmp bx, [S.Tail]
ret
; ----------------------------------
; IN (cx) OUT () MOD (ax,bx)
NewHead:mov bx, -2
xadd [S.Head], bx
mov [fs:bx-2], cx
mov ah, SNAKECOLOR
; --- --- --- --- --- ---
; IN (ah,cx) OUT () MOD (bx)
WritePlayfieldCell:
movzx bx, ch ; CH is Row
imul bx, COLS
add bl, cl ; CL is Column
adc bh, 0
shl bx, 1
mov [es:bx+1], ah
mov [es:bx+3], ah
ret
; ----------------------------------
; IN () OUT () MOD (ax..cx)
NewTail:mov bx, -2
xadd [S.Tail], bx
mov cx, [fs:bx-2]
mov ah, BACKCOLOR
jmp WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
NewFood:mov ax, [Rand]
imul ax, 25173
add ax, 13849
mov [Rand], ax
mov bx, ROWS-1
xor dx, dx
div bx
mov ch, dl
mov ax, [Rand]
mov bx, COLS/2
xor dx, dx
div bx
shl dl, 1
mov cl, dl
call ScanSnake ; -> ZF (BX)
jnz NewFood ; CX is (X,Y) of some snake part
mov [FoodXY], cx
mov ah, FOODCOLOR
jmp WritePlayfieldCell
; ----------------------------------
; IN () OUT () MOD (ax..dx)
Status: mov ax, [S.Length]
mov bx, ((ROWS-1)*COLS+5)*2
mov cx, 10
@@: xor dx, dx
div cx
add dx, 0F00h+'0'
mov [es:bx], dx
sub bx, 2
test ax, ax
jnz @b
mov byte [es:bx], ' '
ret
; ----------------------------------
Msg db 'G', 12, 'AME OVER', 0
ALIGN 2
S Snake SLEN*2, SLEN*2, SLEN, 4Dh, 00010001b
FoodXY dw ?
Rand dw ?
EOF:
这篇关于典型的蛇游戏.如何跟踪蛇?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!