3

用四个整数编写一个贪吃蛇游戏

 1 year ago
source link: https://blog.csdn.net/csdnnews/article/details/125093329
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

用四个整数编写一个贪吃蛇游戏

作者 | Andrei Cioban

译者 | 弯月

出品 | CSDN(ID:CSDNnews)

b63844a16c01216d4011bc0891ee5423.gif

记得上次编写贪吃蛇游戏还是很多年以前的事,如今我打算尽己所能,在一些很特别的方面做到极致:

  • 将游戏的地图保存到一个uint32_t中,其中的1表示蛇的身体。因此整个地图包括4x8个位置。

  • 用另一个unit64_t作为方向数组,这样可以实现蛇的移动,还可以保持不断增长的身体的位置。

  • 在另一个uint32_t中使用几个5比特数据来保存head(蛇头)、tail(蛇尾)、apple(苹果)和length(当前长度)。还有两个比特用来保存键盘输入。

  • 用一个8比特变量(uint8_t)作为循环变量。

因为标准C没有提供键盘交互功能,因此必须依赖于curses,所以如果你想编译该程序,请确保计算机上安装了该库。如果你使用的是正确的操作系统,很可能curses已经存在了。如若不然,你可以使用任何包管理器进行安装。

不幸的是,curses本身需要消耗内存,但毕竟处理各种转义字符和底层函数很麻烦,我不想自己实现。这种做法也许有点算作弊。

在阅读本文之前,请记住文中的代码仅供娱乐,只是一个练习。出于前面提到的限制,本文会编写大量晦涩的宏来进行位操作,还会使用全局变量、重复使用同一个计数器,等等。这些都不是易读代码的最佳实践。

代码

完整的代码,请参见GitHub:

git clone [email protected]:nomemory/integers-snake.git 

编译和运行:

gcc -Wall snake.c -lcurses && ./a.out

内存布局

首先定义4个整数,用于保存所有游戏数据:

uint32_t map = ...;

uint32_t vars = ...;

uint64_t shape = ...;

int8_t i = ...;    

map

map变量负责屏幕显示。map变量有32比特,利用curses渲染成4x8的方格:

8af78723012ed017664a43adaab62d78.png

访问每个比特并设置0或1,需要使用下面的宏:

vars

vars是一个32位整数,用于保存下面的数据:

17a623f54e72461551f76a0fb77056b1.png
  • hpos (比特0~4)表示蛇头的位置,表示为从map的最低位开始的偏移量;

  • tpos(比特5~9)表示蛇尾的位置,表示为从map的最低位开始的偏移量;

  • len(比特10~14)表示蛇的长度;

  • apos(比特15~19)表示苹果的位置,表示为从map的最低位开始的偏移量;

  • chdir(比特20~21)表示表示最后一次按下的键,2个比特足够了,因为只需要四个方向键;

  • 其余的比特没有使用。我们也可以把循环计数器的uint8_t放在这儿,但为了简单起见,我还是使用了单独的变量。

我们定义了以下的宏来访问hpos、hpos等。这些宏就像是针对每个段的getter/setter一样。

更多有关宏背后的技巧,请参见这篇文章:https://www.coranac.com/documents/working-with-bits-and-bitfields/

shape

shape用来保存蛇的每一节的方向。每个方向2比特就足够了,所以一共可以保存32个方向:

e39502992bca6d6d677cca34eb62b81f.png

方向的意义用下面的宏表示:

每次蛇在map的方格中移动时,我们需要使用下述宏循环这些方向:

当蛇移动且没有吃掉苹果时,我们调用s_shape_rot宏,删除最后一个方向,然后添加一个新的蛇头(根据s_chdir)。

这么看来,蛇的行为有点像队列:

58dc1f573e31c8d3f5c3222a7ea8d64d.png

当蛇移动并吃掉一个苹果时,我们调用s_shape_add,仅增加长度,并添加一个新的蛇尾s_tdir。

主循环

主循环如下所示。

每当某个键按下时,就展开s_key_press,检查移动是否允许,然后更新s_chdir(使用s_chdir_set)。

s_key_press有两个输入参数的作用是去除相反方向。例如,如果蛇当前向右移动(SR),那么SL就是不可能的输入,从而中断switch语句。

移动蛇的函数

move_snake()中实现了大部分逻辑:

为了验证蛇是否可以在方格中移动,我们实现了check_*()函数:

  • check_l()检查蛇的X坐标(s_hpos % 8)是否大于上一个位置的X坐标;

  • check_r()检查蛇的X坐标(s_hpos % 8)是否小于上一个位置的X坐标;

  • check_u()和check_d()的原理相同,检查增加s_hpos是否会导致溢出。如果溢出,表明移动超出了方格边界。这里溢出当做一个特性使用。

显示蛇的函数

这是需要实现的最后一个函数:

宏展开之后

所有宏展开之后,代码如下所示:

上述代码非常难懂,上下滚动屏幕甚至会感到头晕。

感想

这个练习很有趣。完整的代码在此(https://github.com/nomemory/integers-snake/blob/main/snake.c),大约100行,只用了四个整数。

如果在你的终端上蛇跑得太快,可以尝试增加s_napms。

*本文由CSDN翻译,未经授权,禁止转载。

原文链接:https://www.andreinc.net/2022/05/01/4-integers-are-enough-to-write-a-snake-game


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK