移动互联网课程(移动互联网课程设计贪吃蛇)

Mark wiens

发布时间:2022-08-16

移动互联网课程(移动互联网课程设计贪吃蛇)

 

互联网小常识:无线网络设备及时更新到最新固件。设置高强度的无线密码,各单位的认证机制建议釆取实名方式。

作者 | Andrei Cioban

译者 | 弯月

出品 | CSDN(ID:CSDNnews)

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

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

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

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

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

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

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

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

代码

完整的代码,请参见GitHub:

git clone git@github.com: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的方格:

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

defines_is_set(b) ((map&(1<<(b)))!=0)// checks if the b bit from the map is set to 1define s_tog(b) (map^=(1<<(b))) // toggles the b bit of the map (currently not used)define s_set_0(b) (map&=~(1<define s_set_1(b) (map|=(1<

vars

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

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

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

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

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

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

互联网小常识:子网划分方法(根据IP地址划分子网):依然要根据子网个数和主机个数确定子网号和主机号位数,然后计算子网掩码,然后列出子网地址段。

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

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

define s_mask(start,len) (s_ls_bits(len)<<(start)) // creates a bitmask oflenstarting from position startdefine s_prep(y,start,len) (((y)&s_ls_bits(len))<<(start)) // prepares the mask// Gets the thelennumber of bits, starting from positionstartofydefine s_get(y,start,len) (((y)>>(start))&s_ls_bits(len))// Sets the thelennumber of bits, starting from positionstartofyto the valuebfdefine s_set(x,bf,start,len) (x=((x)&~s_mask(start,len))|s_prep(bf,start,len))define s_hpos s_get(vars,0,5) // gets the last5bits ofvars, which corresponds to s_hposdefine s_tpos s_get(vars,5,5) // sets the last5bits ofvars, which corresonds to s_hposdefine s_len s_get(vars,10,5)define s_apos s_get(vars,15,5)define s_chdir s_get(vars,20,2)define s_hpos_set(pos) s_set(vars,pos,0,5)define s_tpos_set(pos) s_set(vars,pos,5,5)define s_len_set(len) s_set(vars,len,10,5)define s_apos_set(app) s_set(vars,app,15,5)define s_chdir_set(cdir) s_set(vars,cdir,20,2)define s_len_inc s_len_set(s_len+1)

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

shape

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

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

defineSU 0 //UPdefineSD 1 //DOWNdefineSL 2 //LEFTdefineSR 3 //RIGHT

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

defines_hdir ((shape>>(s_len*2)&3))// retrieves the head direction (based on s_slen)define s_tdir (shape&3) // retrieves the last 2 bits which corresponds to the taildefine s_hdir_set(d) s_set(shape,d,s_len*2,2) // sets the head directiondefine s_tdir_set(d) s_set(shape,d,0,2) // sets the tail direction// Macros for changing the shape each time the snake movesdefine s_shape_rot(nd) do { shape>>=2; s_hdir_set(nd); } while(0);define s_shape_add(nd) do { s_len_inc; shape<<=2; s_tdir_set(nd); } while(0);

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

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

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

主循环

主循环如下所示。

// Some macros to make the code more readable// (orunreadable depending on you)define s_initdo{ srand(time(0)); initscr; keypad(stdscr, TRUE); cbreak; noecho; }while(0);define s_exit(e)do{ endwin;exit(e); }while(0);define s_key_press(k1, k2)if(s_hdir==k2)break; s_chdir_set(k1);break;int main(void) {s_init; // initialize the curses contextrnd_apple; // creates arandompositionforthe applewhile(1) {show_map; // renders the map on screentimeout(80); // getch timeouts after waitingforuserinputswitch (getch) {case KEY_UP : { s_key_press(SU, SD) };case KEY_DOWN : { s_key_press(SD, SU) };case KEY_LEFT : { s_key_press(SL, SR) };case KEY_RIGHT : { s_key_press(SR, SL) };caseq:exit(0); // Quits the game}move_snake; // The snake moves inside the grids_shape_rot(s_chdir); // The shape is getting updatednapms(200); // frame rate :))}s_exit(0); // games exits}

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

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

移动蛇的函数

move_snake中实现了大部分逻辑:

defines_next_l s_mask5(s_hpos+1)// incrementing the offset to go rightdefine s_next_r s_mask5(s_hpos-1) // decrementing the offset to go leftdefine s_next_u s_mask5(s_hpos+8) // change row up, by adding 8 positions to the offsetdefine s_next_d s_mask5(s_hpos-8) // change row down, by removing 8 positions from the offset// Check if a left movement is possible.static void check_l { if ((s_mod_p2(s_next_l,8) < s_mod_p2(s_hpos,8)) || s_is_set(s_next_l)) s_exit(-1); }// Check if a right movement is possible.static void check_r { if ((s_mod_p2(s_next_r,8) > s_mod_p2(s_hpos,8)) || s_is_set(s_next_r)) s_exit(-1); }// Check if a up movement is possiblestatic void check_u { if ((s_next_u < s_hpos) || s_is_set(s_next_u)) s_exit(-1); }// Check if a down movement is possiblestatic void check_d { if ((s_next_d > s_hpos) || s_is_set(s_next_d)) s_exit(-1); }static void move_snake {if (s_hdir==SL) { check_l; s_hpos_set(s_hpos+1); }else if (s_hdir==SR) { check_r; s_hpos_set(s_hpos-1); }else if (s_hdir==SU) { check_u; s_hpos_set(s_hpos+8); }else if (s_hdir==SD) { check_d; s_hpos_set(s_hpos-8); }// Sets the bit based on the current s_hdir and s_hposs_set_1(s_hpos);// If an apple is eatenif (s_apos==s_hpos) {// We generate another apple so we dont starvernd_apple;// Append to the tails_shape_add(s_tdir);// We stop clearning the tail bitreturn;}// Clear the tail bits_set_0(s_tpos);// Update the t_pos so we can clear the next tail bit when the snake movesif (s_tdir==SL) { s_tpos_set(s_tpos+1); }else if (s_tdir==SR) { s_tpos_set(s_tpos-1); }else if (s_tdir==SU) { s_tpos_set(s_tpos+8); }else if (s_tdir==SD) { s_tpos_set(s_tpos-8); }}

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

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

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

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

显示蛇的函数

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

static void show_map {clear;i=32;while(i-->0) {//!! Trigger warningforsensitive people, incoming-->0//If the bitisan apple, we render the apple@if(i==s_apos) { addch(@); addch(); }//We draw either the snake bit ()orthe empty bit (.)else{ addch(s_is_set(i) ?:.); addch(); }//We construct the gridbyinserting anewlineif(!s_mod_p2(i,8)) { addch(\n); }};}

宏展开之后

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

uint32_tmap=0x700;uint32_tvars =0x20090a;uint64_tshape =0x2a;int8_ti =0;staticvoidrnd_apple {i = (rand&(32-1));while(((map&(1<<(i)))!=0)) i = (rand&(32-1));(vars=((vars)&~(((1<<(5))-1)<<(15)))|(((i)&((1<<(5))-1))<<(15)));}staticvoidshow_map {wclear(stdscr);i=32;while(i-->0) {if(i==(((vars)>>(15))&((1<<(5))-1))) { waddch(stdscr,@); waddch(stdscr,); }else{ waddch(stdscr,((map&(1<<(i)))!=0) ?:.); waddch(stdscr,); }if(!(i&(8-1))) { waddch(stdscr,\n); }};}staticvoidcheck_l {if((((((((vars)>>(0))&((1<<(5))-1))+1)&0x1f)&(8-1)) < ((((vars)>>(0))&((1<<(5))-1))&(8-1))) || ((map&(1<<((((((vars)>>(0))&((1<<(5))-1))+1)&0x1f))))!=0))do{ endwin;exit(-1); }while(0);; }staticvoidcheck_r {if((((((((vars)>>(0))&((1<<(5))-1))-1)&0x1f)&(8-1)) > ((((vars)>>(0))&((1<<(5))-1))&(8-1))) || ((map&(1<<((((((vars)>>(0))&((1<<(5))-1))-1)&0x1f))))!=0))do{ endwin;exit(-1); }while(0);; }staticvoidcheck_u {if(((((((vars)>>(0))&((1<<(5))-1))+8)&0x1f) < (((vars)>>(0))&((1<<(5))-1))) || ((map&(1<<((((((vars)>>(0))&((1<<(5))-1))+8)&0x1f))))!=0))do{ endwin;exit(-1); }while(0);; }staticvoidcheck_d {if(((((((vars)>>(0))&((1<<(5))-1))-8)&0x1f) > (((vars)>>(0))&((1<<(5))-1))) || ((map&(1<<((((((vars)>>(0))&((1<<(5))-1))-8)&0x1f))))!=0))do{ endwin;exit(-1); }while(0);; }staticvoidmove_snake {if(((shape>>((((vars)>>(10))&((1<<(5))-1))*2)&3))==2) { check_l; (vars=((vars)&~(((1<<(5))-1)<<(0)))|((((((vars)>>(0))&((1<<(5))-1))+1)&((1<<(5))-1))<<(0))); }elseif(((shape>>((((vars)>>(10))&((1<<(5))-1))*2)&3))==3) { check_r; (vars=((vars)&~(((1<<(5))-1)<<(0)))|((((((vars)>>(0))&((1<<(5))-1))-1)&((1<<(5))-1))<<(0))); }elseif(((shape>>((((vars)>>(10))&((1<<(5))-1))*2)&3))==0) { check_u; (vars=((vars)&~(((1<<(5))-1)<<(0)))|((((((vars)>>(0))&((1<<(5))-1))+8)&((1<<(5))-1))<<(0))); }elseif(((shape>>((((vars)>>(10))&((1<<(5))-1))*2)&3))==1) { check_d; (vars=((vars)&~(((1<<(5))-1)<<(0)))|((((((vars)>>(0))&((1<<(5))-1))-8)&((1<<(5))-1))<<(0))); }(map|=(1<<(((vars)>>(0))&((1<<(5))-1))));if((((vars)>>(15))&((1<<(5))-1))==(((vars)>>(0))&((1<<(5))-1))) {rnd_apple;do{ (vars=((vars)&~(((1<<(5))-1)<<(10)))|((((((vars)>>(10))&((1<<(5))-1))+1)&((1<<(5))-1))<<(10))); shape<<=2; (shape=((shape)&~(((1<<(2))-1)<<(0)))|((((shape&3))&((1<<(2))-1))<<(0))); }while(0);;return;}(map&=~(1<<(((vars)>>(5))&((1<<(5))-1))));if((shape&3)==2) { (vars=((vars)&~(((1<<(5))-1)<<(5)))|((((((vars)>>(5))&((1<<(5))-1))+1)&((1<<(5))-1))<<(5))); }elseif((shape&3)==3) { (vars=((vars)&~(((1<<(5))-1)<<(5)))|((((((vars)>>(5))&((1<<(5))-1))-1)&((1<<(5))-1))<<(5))); }elseif((shape&3)==0) { (vars=((vars)&~(((1<<(5))-1)<<(5)))|((((((vars)>>(5))&((1<<(5))-1))+8)&((1<<(5))-1))<<(5))); }elseif((shape&3)==1) { (vars=((vars)&~(((1<<(5))-1)<<(5)))|((((((vars)>>(5))&((1<<(5))-1))-8)&((1<<(5))-1))<<(5))); }}intmain(void){do{ srand(time(0)); initscr; keypad(stdscr,1); cbreak; noecho; }while(0);;rnd_apple;while(1) {show_map;wtimeout(stdscr,80);switch(wgetch(stdscr)) {case0403: {if(((shape>>((((vars)>>(10))&((1<<(5))-1))*2)&3))==1)break; (vars=((vars)&~(((1<<(2))-1)<<(20)))|(((0)&((1<<(2))-1))<<(20)));break; };case0402: {if(((shape>>((((vars)>>(10))&((1<<(5))-1))*2)&3))==0)break; (vars=((vars)&~(((1<<(2))-1)<<(20)))|(((1)&((1<<(2))-1))<<(20)));break; };case0404: {if(((shape>>((((vars)>>(10))&((1<<(5))-1))*2)&3))==3)break; (vars=((vars)&~(((1<<(2))-1)<<(20)))|(((2)&((1<<(2))-1))<<(20)));break; };case0405: {if(((shape>>((((vars)>>(10))&((1<<(5))-1))*2)&3))==2)break; (vars=((vars)&~(((1<<(2))-1)<<(20)))|(((3)&((1<<(2))-1))<<(20)));break; };caseq:exit(0);}move_snake;do{ shape>>=2; (shape=((shape)&~(((1<<(2))-1)<<((((vars)>>(10))&((1<<(5))-1))*2)))|((((((vars)>>(20))&((1<<(2))-1)))&((1<<(2))-1))<<((((vars)>>(10))&((1<<(5))-1))*2))); }while(0);;napms(200);}do{ endwin;exit(0); }while(0);;}

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

感想

互联网小常识:异常检测主要包括基于统计异常检测、基于数据挖掘的异常检测、基于神经网络入侵检测等。

这个练习很有趣。完整的代码在此(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

成就一亿技术人

互联网小常识:STP的配置任务包括:打开或关闭交换机的STP,设置STP的根网桥和备份网桥,配置生成树优先级、配置路径代价和配置STP可选功能等。

免责声明:本站所有信息均搜集自互联网,并不代表本站观点,本站不对其真实合法性负责。如有信息侵犯了您的权益,请告知,本站将立刻处理。联系QQ:1640731186