【问题标题】:How to manipulate array in emu8086如何在emu8086中操作数组
【发布时间】:2017-08-18 09:19:59
【问题描述】:

我目前正在emu8086 中创建一个简单的游戏程序。使用箭头键,我可以在我想要的每个方向上打印星号。问题是有一个要求,如果我已经打印了 5 个星号,则应该移除星号的尾部,这样我的星号主体将在每次打印时保持 5 个。

根据我的研究,主要使用数组,但我还不知道如何在数组中操作它,因为我不知道如何跟踪数组的尾部和位置,以便我能够打印空间以删除尾部。

喜欢:

1. *
2. *
3. *
4. *
5. *

点击一个方向后,它应该看起来像.. 在这种情况下,它只是向下方向.. 其他方向的组合如何?

1. 
2.*
3.*
4.*
5.*
6.*

这是我的打印代码

continue:

mov ah, 2   ;print asterisk
mov dl, "*"
int 21h 

continue2:
mov ch, 32  ;hide blinking cursor
mov ah, 1
int 10h 

mov ah, 00h ;input direction
int 16h  

cmp ah, up
je  up        
cmp ah, left
je  left
cmp ah, down
je  down 
cmp ah, right
je  right

【问题讨论】:

  • 如何设置下一个星号的位置? int 21h,2 确实在默认的下一个位置(到 STDOUT)打印它,而不是通过某些键输入向上/向下/向右/向左打印。同样是DOS,你为什么不直接写到VRAM,因为你在做“游戏”,在8086 PC XT时代,如果你想写一个游戏,你必须砍掉每个中间人,包括非常慢的 BIOS/DOS 调用。 BTW,你懂 C/C++,所以我可以用 C 的例子来回答吗?
  • 我正在考虑回答这个问题,但我实际上尝试使用emu8086,它默默地将mov [di+bx*2],ax 编译为mov [di+bx],ax,没有任何警告或错误。我不愿意支持这种低劣的工具(这不是我第一次遇到 emu8086 的一些奇怪问题)。
  • 我们实际上需要使用emu8086。它就像一个蛇游戏,但它由用户一个一个打印身体(星号)
  • 他们给了我们一个线索,我们可以使用数组,但我很难理解它是如何工作的。
  • 另外,当我检查 emu8086 时,我发现第一个“Hello world”示例是通过将数据直接写入视频 RAM 来工作,跳过慢速 BIOS/DOS int 10h/int 21h服务。也许您应该阅读 DOS 下的 VGA 文本模式视频内存组织,并查看该示例,了解它是如何工作的。虽然要读取键盘状态 BIOS int 16h 对于简单的蛇游戏来说是可以的(我只是希望我能正确记住这些中断),但直接从端口 60h(或 61h?)读取键盘会与 BIOS 处理程序发生冲突,你不应该蛇游戏需要它。

标签: assembly x86 emu8086


【解决方案1】:

如果你知道蛇的最大长度,你可以使用所谓的"ring buffer"

汇编中的“数组”只是内存的连续区域,其结构由您的代码定义。

假设您最多有 5 个星号长蛇,那么我将使用 6 个 [x, y] 位置的缓冲区来定义,5 个位置是从头到尾的星号,第 6 个位置是前一个尾部位置,即打印空间以“擦除”尾部。

我实际上会在代码中重新安排一下,有这样的缓冲区,位置长一些 2 的幂(6 之后的下一个 2 的幂是 8,如果你的蛇会长大,填满屏幕,那么你需要 80 *25=最多2000个位置,下一个2的幂是2048)。而且我会保留 head 元素的索引(然后根据需要选择朝向下一个身体部位的方向,我可能会在初始化期间将 head 放在最后,因此 buffer[0] 将包含“空间”尾部,buffer[1] 最后asterisk "tail", ... buffer[5] 将是 "head" 星号,我将保留该 headIndex=5(或者直接将内存偏移量设为headOffset = 10,因为每个元素都是 2 个字节([x,y] ) 和 5*2 = 10)。

所以你有一些“环形缓冲区”用于数据部分中的身体部位位置:

BUFFER_SIZE  EQU  2*8  ; or 2*2048 for growing snake
    ; everything will be "2*", because each position is 2 bytes big

snakeLength:
    dw      ?
headOffset:
    dw      ?
positions:
    db      BUFFER_SIZE DUP (?)

还有一些代码,它将把蛇的长度初始化为 5,然后将 positions 中的前 6 对字节作为蛇的开始位置(包括第一个位置的“空格”尾部!),并将头部偏移到 @987654326 @(缓冲区中的第 5 位字节对)。

现在要打印蛇,您将加载headOffsetsnakeLength,并多次执行“snakeLength”:

  • positions + offset获取两个字节,作为[x,y]
  • 在此处打印星号
  • 将偏移量减少 2
  • and <register with offset>,(BUFFER_SIZE-2),这将使它指向内存的最后一个元素,以防偏移量已经为 0(这就是为什么二的幂,允许我通过单个 and 指令环绕“环形缓冲区”和位掩码操作)。

最后再进行一次迭代,但这次打印的是空格,而不是星号(用于最终元素)。

这就是关于如何绘制这种数组中定义的蛇。

如何向某个方向“移动”它,比如 (-1, 0)(向左):

  • 读取headOffset
  • [positions + headOffset]读取磁头的当前位置
  • 将偏移量增加 2
  • and <register with offset>,(BUFFER_SIZE-2) 使其指向缓冲区中的下一个元素
  • 将 (-1, 0) 添加到当前头部位置
  • 将更新后的位置存储到[positions + offset]
  • 将偏移存储为新的headOffset(这将使最后一个星号尾部位置成为新的“空格”尾部,而之前的“空格”尾部在相同的蛇形长度下无法触及)。

绘制完这个新的数据状态后,蛇会向左“移动”一步。

如何长蛇:和move一样,但是你也将snakeLength更新了+1,所以buffer的最后两个元素仍然作为之前的星号+空格tail,保持tail在同一位置,而head前进了。 (使用这种方法,您只能在每个步骤中将蛇长出 +1,因为您不会在尾巴上添加新的身体部位......但这正是古老的蛇游戏的工作方式,当您吃一些更大的食物时蛇增长了 N 个部分,它是在 N 个后续步骤中完成的,而不是立即完成)。

所以你有 8 个位置的数组,其中只有 6 个被使用。使用哪个 6 由headOffset 定义,它环绕并环绕(从偏移量 0 开始“向下”返回到偏移量 14 (8*2-2),从偏移量 14 开始向上意味着在偏移量 0 处着陆,所以“环形缓冲区”像“环形”一样扭曲,没有任何开始或结束)。


另一种方法是有 6 个位置的数组,当你移动蛇时,你实际上将数组中的数据从位置 i 移动到位置 i-1,并且然后将新的头部位置写入最后一个数组元素。这可能在第一次尝试时更容易理解,但是“环形缓冲区”解决方案更优越,因为您不必在每次蛇移动时移动内存块,您只需添加新头并调整偏移/长度数据,而不是移动在内存中的位置,您移动访问数据的偏移量。 (有 1500 部分长的蛇,这意味着只需设置一个新元素并更新偏移量就可以节省近 3000 个字节memmove)。这样做的代价是每次从一个元素移动到下一个元素时都会引入偏移量扭曲逻辑(从偏移量 14 到 0 和从 0 到 14),但这是通过单个 and 指令完成的,所以它将是仍然比复制缓冲区的内存快得多。


emu8086 较差,您的讲师应该更新工具和课程(NASM + dosbox + 一些简洁的调试器怎么样,大多数大学很可能仍然有一些 199x 时代左右的涡轮调试器学生许可证)。

【讨论】:

  • 顺便说一句,这样你会得到闪烁的身体部位,当你穿过最后一个空间被绘制的位置时(特别是如果蛇正在生长,所以蛇的几个部分正在穿过)。为避免这种情况,您应该从末端向头部绘制,先绘制空间,然后是身体。实际上,如果您知道屏幕内容是完整的,您可以只绘制空间以擦除最后一个身体部分,当蛇移动(不生长)时,只绘制头部。身体的其余部分是星号,因此重新使用屏幕上已有的内容是可以的,而无需每次都重新绘制它们。但这是关于绘制逻辑技巧
  • 感谢您的努力,先生。我会试着理解你的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多