最近堆叠的事情比较多,返回到这一层时知乎问题已经不能修改了,只好开一篇文章说。
用C语言,能在100行之内实现贪吃蛇吗? - duangsuse的回答 - 知乎 https://www.zhihu.com/question/360814879/answer/1601486959
不说无关技术的语文错误(如把使用写成适用),那个压行代码的部分应该也有部分逻辑缺失的问题(比如 cursEnable
和 putWall
的逻辑,对程序正确性影响大)
我在此给一个完整的压行版(10行,但行数已无意义):
//gcc rm.c -DW=24 -DH=18 -lncurses -include curses.h #-include stdlib.h -include unistd.h
// gobals
int w;int h;int nM;int nBlk;typedef int*map2D;map2D m;map2D que;int p;int iHead=0;useconds_t delayUsec;void init(int m_w,int m_h,int dt){w=m_w;h=m_h;nM=w*h;delayUsec=dt*1000;map2D*maps[]={&m,&que};for(int i=0;i<2;i++)*maps[i]=malloc(sizeof(int)*nM);}
// data model
static inline int pYX(int y,int x){return y*w+x;}static inline void yxP(int p,int*y,int*x){*y=p/w,*x=p%w;}static inline int cycledInc(int*p){*p=(*p+1)%nM;return*p;}int isZeroOr(int n,int x){return(x%n)==0;}int curUse(int(op)(WINDOW*,bool)){return op(stdscr,1);}
//putXX
void putWall(){int y,x;for(int i=0;i<nM;i++){yxP(i,&y,&x);if (isZeroOr(w-1, x)||isZeroOr(h-1, y)) m[i]=1;}}void putCell(){que[cycledInc(&iHead)]=p;m[p]=1;}void putFruit(){int pA;do{pA=rand()%nM;}while(m[pA]!=0);m[pA]=2;}
//game
void game(char*style[]){noecho();curUse(nodelay);curUse(keypad);int ch,y,x,d=1,iTail=0;putWall();putFruit();p=pYX(h/2,w/2);putCell();while((ch=wgetch(stdscr))!='q'){if(ch==(-1)){}else if(ch==259&&d!=w)d=-w;else if(ch==258&&d!=-w)d=w;else if(ch==260&&d!=1)d=-1;else if(ch==261&&d!=-1)d=1;p+=d;if(m[p]==1){break;}if(m[p]==2){putFruit();}else{m[que[cycledInc(&iTail)]]=0;}putCell();for(int i=0;i<nM;i++){yxP(i,&y,&x);mvprintw(y,x*nBlk,style[m[i]]);}wrefresh(stdscr);usleep(delayUsec);}}
//call init,game
int main(){char*deftStyle[]={" ","[]","()"};nBlk=2;initscr();curs_set(0);init(W,H,100);game(deftStyle);wgetch(stdscr);return endwin();}
第一行编译命令 # 号后的头文件是选择性包含(.h 里只用了函数的 extern),可以 -Wno-everything
替换。
![ed08ad256973bd51655c7dcf2daadcfd.png](https://i-blog.csdnimg.cn/blog_migrate/55ebdd4deb641c9cb4991cb9dd3eab9a.jpeg)
关于这个程序以代码行数评优劣,的确十分不妥(不过不当人面我说句心里话,我重写的那个大佬也该试着多重写几次看看,总会有进步;还有,许多其它都只实现了命令行前端的答主,你们大几百行已经够我用 clean C + asprintf 写一个 multiplayer 的贪吃蛇了,要加油啊)
至于B站那个18行的,不得不承认是“很有创新性”(我也不知道该不该这么说),反正我复刻了一遍(修改了IO方式消除“闪烁”,为此也对后端数据解释做了点改动,毕竟不要闪烁是不可能区别m
和pApple
各自渲染的)
// Windows.h, conio.h, limits.h, stdlib/stdio/time
// 当然你要和他一样短,C预处理器 gcc -E 过一遍内联所有常量就行
define w 50
#define h 40
#define speed 100
const char *sNone=" ", *sBody="[]", *sFruit="()";
const int nM=w*h, nBlk=2; int *m;
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
void printp(int p, const char* s) {
COORD cd = {p%w*nBlk, p/w};
SetConsoleCursorPosition(hStdout, cd);
printf("%s", s);
}
int randP() { int pt; do { pt = rand()%nM; } while (m[pt]!=0); return pt; }
#define isZeroOr(n, x) ((x%n) == 0)
int main(void) {
srand(time(NULL));
m = malloc(nM);
char* cmd; asprintf(&cmd, "mode con cols=%d lines=%d", w*nBlk, 1+h); system(cmd);
for (int i=0; i<nM; i++) m[i] = (isZeroOr(h-1, i/w) || isZeroOr(w-1, i%w))? 1 : 0;
int p=(h/2)*w + w/2, d=1, nLen=1, pA=randP();
unsigned t0 = time(NULL);
char ch; while (!kbhit() || (ch=getch()) != 'q') {
d =
(ch=='a')? -1 :
(ch=='d')?+1 :
(ch=='w')? -w :
(ch=='s')? +w : d;
p+=d; if (m[p]==1) break;
m[p] = nLen+1/*decred later*/;
if (p==pA) { nLen++; pA = randP(); continue; }
int oldMpA = m[pA]; m[pA] = INT_MAX-1;
for (int i=0; i<nM; i++) { m[i]--; printp(i, (m[i]<=0)? sNone : (m[p]!=INT_MAX-1-1)? sBody : sFruit); }
m[pA] = oldMpA;
unsigned t1 = time(NULL);
Sleep((t1-t0)/speed); t0 = t1;
}
}
注意,我这个版本没有“闪烁”而且没注意压行,长几倍。当然性能一样很垃圾(每次渲染都要递减整个 map 数组,思量是不是要 SIMD <xmmintrin>
优化,啧啧)
至于其它两个超短(C/JavaScript,惯常一维的存储模式)蛇,虽然还好(也不至于按一键更一帧,之前听说有人只会这么写),但行为上要么直接朝运动反向击键会挂(我也写了这种版本),要么完全不会挂(没有墙、撞自己也不会),所以说,最小化设计也不是说一定有多短。
就好像算法的时间复杂度一样,好的代码功能越多越好维护、实现行数开销越少,烂代码,即便刚开始看起来很短,如果要加一个功能,要么然你痛苦要么然电脑痛苦。