我对组装完全陌生,不管你信不信,我们的第一个任务是在装配中创造蛇。我应该如何储存蛇?我应该把它放在堆栈中,还是应该把它放到一些寄存器中?我已经对这种“糟糕”的语言做了大约3天的研究,但是无法找到一个好的开始方法。我可能会在c ++中使用某种链接列表,但遗憾的是这不是c ++。
非常感谢任何帮助
答案 0 :(得分:1)
我希望6年后不再是问题,但就我而言,我基于像素。例如,有一个像素被指定为蛇的头部。我总是保存它的位置(x轴和y轴),并在循环中检查蛇的方向上是否有任何绘制的像素:
如果是黑色,我什么也没做,继续移动蛇。黑色是背景
如果是红色-我知道那条蛇要吃一个苹果。
如果是蓝色,我就知道那条蛇将要撞到墙壁,依此类推。
答案 1 :(得分:1)
可以使用一对x,y坐标跟踪蛇的头部。当然,您还需要头部的当前方向。(尾部需要的不仅仅是x,y对,最好是x,y对的圆形缓冲区。那么您实际上并不需要与历史记录缓冲区分开的尾部x,y。)
正如@Daniel的答案所解释的那样,看着随着头部移动您将要绘制的下一个像素会告诉您蛇吃什么:什么,苹果,墙壁或自身。 >
(如果您具有对视频RAM的有效读取访问权限,则可以简单地读取像素而不是保留阴影板阵列。但是大多数现代系统没有都具有有效的video-RAM读取访问权限;通常x86上的VGA内存无法通过写合并进行缓存。但是,如果您假装要为真正的8086进行写操作,则VGA RAM位于ISA总线的另一侧,因此速度也很慢,但是8086没有任何缓存。 。甚至更早的系统可能会将视频RAM作为主内存IDK的一部分。)
另一种选择是搜索苹果和蛇形方块的列表,但这比检查一个数组条目要耗费更多的工作。编写游戏(或其他实时软件)的关键因素是在最坏情况性能上设定严格的上限。当上面有蛇的正方形很多,并且步伐时间很短(蛇快速移动)时,您不希望游戏变得笨拙并且某些步伐比其他步伐花费的时间更长,因为搜索花费了太长时间。很快就无法播放。
因此,您可能想要一个rows * cols
个字节的数组(进行2D索引),在其中存储诸如0
的空白,1
的墙壁等的代码,< / strong>。如果您使用的是调色板视频模式,并且直接绘制到VRAM中,或者使用现代视频API,屏幕缓冲区或OpenGL纹理,则可能使用与视频RAM中的像素相同的代码。否则,状态数组中将只有代码,屏幕缓冲区中将只有24或32位像素。
为简化索引编制,您可以使存储格式使用2步的幂,即使板宽不是。因此,每行的末尾可能会有填充列。但这可能会浪费大量内存,通常您只需要相对于当前位置进行索引(例如,左/右下一列为+ -1字节,或者向下/上一行为+ -row_stride)。
头/尾x,y坐标可以只是进入平板阵列的单个索引或指针,但我们还需要实际的x,y坐标才能分别绘制图形。
每一步之后,您还需要在尾部清理一个正方形(除非蛇仍从最近的苹果中生长出来;您需要向下计数那里有多少未决的生长是)。我们知道在哪里将屏幕涂成黑色,因为我们有尾巴x,y。
但是我们如何更新尾部x,y,以便下一步步骤将遵循头部的实际路径?您可能希望通过查看尾巴周围的正方形,您可以找出哪个是倒数第二个。我们可以用两个不同的轨迹导致具有相同的头和尾巴的相同板位置的例子来证明情况并非如此。播放器可以通过水平或垂直Zig字形来创建此板布局。
H123 H167 H is the head, T is the tail
654 258 snake segments are 1..8 in order of moves.
78T 34T
在没有1..8历史记录的情况下,所有方块都只是“蛇形”,没有算法能够明确确定在擦除后尾巴应该以哪种方式移动。 (即使是一种慢速算法,也可能会影响整个电路板。)
对于合理的算法,还有其他模棱两可的情况,例如仅查看尾巴周围的8个正方形。
54 H12 # defeating a "local" algorithm
T321H 34
T5
所以我们必须以某种方式记录历史。我认为最好和最简单的选择是:
您的蛇将非常类似于蛇在屏幕上的爬行方式,通过内存地址进行爬行,因此,这种数据结构导致简单的实现(在每个步骤中只需完成很少的工作)是适当的,并非巧合)
此缓冲区的大小将设置我们可以支持的最大蛇形长度,这时我们将重用缓冲区条目以在 读取尾部后记录右头。您可以将其设置为大于行*列,因此,如果需要,游戏没有施加任何实际限制。
这可能是许多真正的经典Snake游戏完全具有最大蛇长的技术原因。 (除了游戏玩法原因。)由于您的游戏是唯一在CPU上运行的游戏,因此没有理由不总是使用相同大小的循环缓冲区,即静态分配的缓冲区大小与您所需的大小一样。这样一来,您就不会再因为游戏增长或其他原因而停滞不前。
您可以编写类似于此C的asm:
typedef struct xy {uint8_t x, y;} xy_t;
static const unsigned max_snakelen = 512;
struct xy snakepath[max_snakelen];
// uint16_t [] board array offsets is great if we don't also need x,y for graphics
enum board_entry { SQUARE_EMPTY=0, SQUARE_APPLE, SQUARE_SNAKE, SQUARE_WALL };
static const unsigned rows = 40;
static const unsigned cols = 80; // including walls
board_entry board[rows * cols]; // we'll do 2D indexing manually because asm has to
static inline unsigned dir_to_board_byte_offset(enum cur_direction) { ... }
// top bit maps to +- rows, or bottom bit to +- 1
static inline xy_t dir_to_xy_offset(enum directions cur_direction) { ... }
// map 0..3 to {+1, 0}, {-1,0}, {0,+1}, {0,-1} in some order.
void step(enum directions cur_direction)
{
static unsigned tailidx = 0; // maybe kept in a register
static unsigned headidx = 5; // and arrange for the initial snake to be in the buffer somehow
if (!growth) // tail stays still while snake grows
--growth;
xy_t tail = snakepath[tailidx++]; // movzx edx, byte [snakepath + rbx*2] // movzx ecx, byte [snakepath + rbx*2 + 1]
tailidx &= max_snakelen - 1; // wrap the circular buffer idx. AND ebx, MAX_SNAKELEN - 1
board[tail.y * cols + tail.x] = SQUARE_EMPTY; // imul stuff
// and update graphics
}
// Most of the registers used for the above stuff are dead now, and can be reused below
// Tail segments disappear *before* the head moves: snake can be head-to-tail
// and go full Ouroboros without crashing.
xy_t headxy = snakepath[headidx++]; // or track head separately, maybe in a reg so we can more quickly like our function arg.
headidx &= max_snakelen - 1;
headxy += dir_to_xy_offset(cur_direction); // maybe use a 16-bit add to do both components in parallel, except that `-1` will carry into the upper element. So that only works with SIMD `paddb` or something.
// pretend that's a C++ overload and do the component adds separately
enum board_entry headtype = board[headxy.y * cols + headxy.x];
if (headtype != SQUARE_EMPTY) {
apple or death;
}
board[headxy.y * cols + headxy.x] = SQUARE_SNAKE;
// ADD NEW HEAD to circular buffer
snakepath[headidx] = headxy; // mov [snakepath + 2*rbx], ax
// and draw graphics for head, using its x,y.
}
这只是为了给您一个大致的概念,它很笨拙,并且不是很好的C风格。 (而不是预期的。)我知道并非所有内容都已声明。由您决定在等待按键和计时器事件的事件循环的asm版本中寄存器中保留多少状态。调用的函数必须保存/恢复reg,但是如果您使用标准的调用约定并始终执行该操作,那么让最外层的循环将其状态保持在reg中就没有什么害处。
以前,乘法运算很慢,因此您可以考虑在蛇形结构中保留x,y 和一维数组索引或实际指针。但是现代CPU通常具有快速乘法器。 (从Core 2左右开始,像3周期延迟那样在Intel上已完全流水线化,而在非流水线8086上则超过了100。)
尤其是在16位计算机上,将绝对地址嵌入到多个指令中不会花费很多代码字节。在32位代码中情况更糟。 x86-64可以在Linux上的位置相关代码中使用32位绝对地址,但仍允许使用[snakepath + rbx*2]
之类的寻址模式,否则,您可能会喜欢RISC并在寄存器中获取至少一个基地址并引用静态相关数据。
根据目标ISA,您可能会在寄存器中保留更多或更少的指针。
一种替代方法是每次玩家转过身来记录。但是在最坏的情况下,播放器每步旋转一次,因此您仍然需要与蛇的长度一样多的缓冲区条目。 (或者,如果您的游戏机数量较少,则您有高度的不良游戏玩法:玩家可能会因为过去转向过多而无法转弯,并因此丧命。)
每个条目至少需要与x,y对一样多的空间,并且需要更多的解码工作。 (每转一圈只记录x,y对,通过查看尾巴x,y与最后一圈,就可以提供足够的信息来重建路径。)
前向+长度实际上可以更紧凑,尽管:每条记录1个字节。如果将位打包到位域中,方向将占用2位,并且即使板子宽度大于或高于63 = 2 6 -1,我们也可以选择尽早添加新条目以将长度保持在6位方块。因此,每个条目可以轻松地为1个字节。 (用dir = x & 3
和len = x >> 2
解码)。
您可以使用这种dir + len格式,方法是减少尾部条目的长度,直到达到0(这意味着尾部已经转弯,应该开始查看下一个缓冲区条目)。为了提高效率,您可以将正在处理的当前文件解开,或者使用sub byte [rsi], 4
并检查进位(以发现递减后长度是否已经为零,然后将其包装起来) )。
答案 2 :(得分:0)
我应该如何存放蛇?我应该把它放在堆栈中,还是应该把它放到一个寄存器中?
假设您正在谈论Snake动画/游戏,答案可能都不是。您最有可能使用二维数组来表示“屏幕”上的细胞,并将蛇的身体表示为给定“颜色”的细胞。
我可能首先要用最简单的方式来实现C或C ++中的代码...而不使用任何数据结构库...然后在汇编程序中重新编码算法。