1 /*
2 _ _
3 (_ | :
4 _)NAKE |.'UEL
5
6
7 Authored by abakh <abakh@tuta.io>
8 To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
9
10 You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
11
12 */
13 #include <curses.h>
14 #include <string.h>
15 #include <stdlib.h>
16 #include <stdbool.h>
17 #include <limits.h>
18 #include <time.h>
19 #include <signal.h>
20 #include <unistd.h>
21 #include <assert.h>
22 #include "config.h"
23 #define SAVE_TO_NUM 10
24 #define MINLEN 10
25 #define MAXLEN 127
26 #define MINWID 40
27 #define MAXWID 127
28 #define LOSE -(MAXWID*MAXLEN)
29 #define WIN_LIMIT 5
30 //#define REPORT 0
31 #ifdef REPORT
32 #define reportif(x) if(x){fprintf(lol,#x" is true\n");fflush(lol);}
33 #define reportd(x) if(REPORT){fprintf(lol, #x": %ld\n",(long)x);fflush(lol);}
34 #define reports(x) if(REPORT){fprintf(lol, "line %d: %s\n",__LINE__,x);fflush(lol);}
35 #else
36 #define reportif(x)
37 #define reportd(x)
38 #define reports(x)
39 #endif
40
41 enum {UP=0,RIGHT,DOWN,LEFT};
42 enum {BLOCK=0,SURVIVAL,MIRROR,IMITATE};
43 /* The Plan9 compiler can not handle VLAs */
44 #ifdef NO_VLA
45 #define len 36
46 #define wid 80
47
48 #ifdef Plan9
usleep(long usec)49 int usleep(long usec) {
50 int second = usec/1000000;
51 long nano = usec*1000 - second*1000000;
52 struct timespec sleepy = {0};
53 sleepy.tv_sec = second;
54 sleepy.tv_nsec = nano;
55 nanosleep(&sleepy, (struct timespec *) NULL);
56 return 0;
57 }
58 #endif
59
60
61 #else
62 int len,wid;
63 #endif//NO_VLA
64 typedef struct snake{
65 int y;
66 int x;
67 byte direction;
68 byte fp;
69 byte strategy;
70 byte score;
71 chtype color;
72 } snake;
73 snake p;//player
74 snake c;//computer
75 byte pscore;
76 byte cscore;
77 chtype colors[6]={0};
78 byte constant_change={0};
79
80 bool must_win=0;
81 FILE* lol;
82
logo(void)83 void logo(void){
84 mvaddstr(0,0," _ _");
85 mvaddstr(1,0,"(_ | : ");
86 mvaddstr(2,0," _)NAKE |.'UEL");
87 }
rectangle(void)88 void rectangle(void){
89 for(int y=0;y<=len;++y){
90 mvaddch(3+y,0,ACS_VLINE);
91 mvaddch(4+y,1+wid,ACS_VLINE);
92 }
93 for(int x=0;x<=wid;++x){
94 mvaddch(3,x,ACS_HLINE);
95 mvaddch(4+len,x,ACS_HLINE);
96 }
97 mvaddch(3,0,ACS_ULCORNER);
98 mvaddch(4+len,0,ACS_LLCORNER);
99 mvaddch(3,1+wid,ACS_URCORNER);
100 mvaddch(4+len,1+wid,ACS_LRCORNER);
101 }
102
swap(byte * a,byte * b)103 void swap(byte* a,byte* b){
104 byte s= *a;
105 *a=*b;
106 *b=s;
107 }
swap_long(long * a,long * b)108 void swap_long(long* a,long* b){
109 long s= *a;
110 *a=*b;
111 *b=s;
112 }
opposite(byte direction)113 byte opposite(byte direction){
114 switch(direction){
115 case UP:
116 return DOWN;
117 case DOWN:
118 return UP;
119 case LEFT:
120 return RIGHT;
121 case RIGHT:
122 return LEFT;
123 default:
124 abort();
125 }
126 }
fake_move(snake s)127 snake fake_move(snake s){
128 switch(s.direction){
129 case UP:
130 s.y=s.y-1;
131 break;
132 case DOWN:
133 s.y=s.y+1;
134 break;
135 case LEFT:
136 s.x=s.x-1;
137 break;
138 case RIGHT:
139 s.x=s.x+1;
140 break;
141 }
142 return s;
143 }
144
blocked(byte board[len][wid],snake s)145 bool blocked(byte board[len][wid],snake s){
146 s=fake_move(s);
147 return ( s.y<0 || s.y >=len || s.x<0 || s.x>=wid || board[s.y][s.x] );
148 }
better_change_way(byte board[len][wid],snake s)149 bool better_change_way(byte board[len][wid],snake s){
150 if(blocked(board,s)){
151 return 1;
152 }
153 s=fake_move(s);
154 if(blocked(board,s)){
155 return 1;
156 }
157 return 0;
158 }
putfp(byte board[len][wid],snake s)159 void putfp(byte board[len][wid],snake s){
160 if(s.x>=0 && s.y>=0 && s.x<wid && s.y<len){
161 board[s.y][s.x]=s.fp+opposite(s.direction);//putting direction for wiping
162 }
163 }
move_snake(byte board[len][wid],snake * s)164 void move_snake(byte board[len][wid],snake *s){
165 assert(!blocked(board,*s));
166 *s=fake_move(*s);
167 putfp(board,*s);
168 }
purs(snake me,int y,int x,byte directions[4])169 void purs(snake me,int y,int x,byte directions[4]){
170 if(me.y<y){
171 directions[0]=DOWN;
172 directions[3]=UP;
173 }
174 else{
175 directions[0]=UP;
176 directions[3]=DOWN;
177 }
178
179 if(me.x<x){
180 directions[1]=RIGHT;
181 directions[2]=LEFT;
182 }
183 else{
184 directions[1]=LEFT;
185 directions[2]=RIGHT;
186 }
187 int x_dist=abs(x-me.x);
188 int y_dist=abs(y-me.y);
189
190 if(x_dist>y_dist){
191 swap(&directions[0],&directions[1]);
192 }
193 if(x_dist==y_dist && x_dist<3 && directions[0]==me.direction){
194 swap(&directions[0],&directions[1]);
195 }
196
197 }
avoid(snake me,int y,int x,byte directions[4])198 void avoid(snake me,int y, int x, byte directions[4]){
199 purs(me,y,x,directions);
200 for(byte i=0;i<4;++i){
201 directions[i]=opposite(directions[i]);
202 }
203 }
shuffle(byte directions[4])204 void shuffle(byte directions[4]){
205 byte a=rand()%4;
206 byte b=rand()%4;
207 swap(&directions[a],&directions[b]);
208 }
enemy_avoid(snake me,snake enemy,byte directions[4])209 void enemy_avoid(snake me,snake enemy,byte directions[4]){
210 avoid(me,enemy.y,enemy.x,directions);
211 }
enemy_pursue(snake me,snake enemy,byte directions[4])212 void enemy_pursue(snake me,snake enemy,byte directions[4]){
213 purs(me,enemy.y,enemy.x,directions);
214 }
enemy_block(byte board[len][wid],snake me,snake enemy,byte directions[4])215 void enemy_block(byte board[len][wid],snake me, snake enemy,byte directions[4]){
216 snake ahead=enemy;
217 switch(enemy.direction){
218 case UP:
219 if(me.y>enemy.y)//me is to the down of the enemy, so cannot plan to block it's way in advance
220 goto JustPursue;
221 break;
222 case DOWN:
223 if(me.y<enemy.y)
224 goto JustPursue;
225 break;
226 case RIGHT:
227 if(me.x<enemy.x)
228 goto JustPursue;
229 break;
230 case LEFT:
231 if(me.x>enemy.x)
232 goto JustPursue;
233 break;
234 default:
235 abort();
236 }
237
238 for(byte i=0;i<10;++i){
239 if(blocked(board,ahead)||ahead.y==me.y||ahead.x==me.x){
240 purs(me,ahead.y,ahead.x,directions);
241 return;
242 }
243 ahead=fake_move(ahead);
244 }
245 JustPursue:
246 purs(me,ahead.y,ahead.x,directions);
247 }
enemy_mirror(snake me,snake enemy,byte directions[4])248 void enemy_mirror(snake me,snake enemy,byte directions[4]){
249 int y,x;
250 y=len-1-enemy.y;
251 x=wid-1-enemy.x;
252 purs(me,y,x,directions);
253 }
enemy_block_mirror(snake me,snake enemy,byte directions[4])254 void enemy_block_mirror(snake me,snake enemy,byte directions[4]){
255 int y_dist=abs(me.y-enemy.y);
256 int x_dist=abs(me.x-enemy.x);
257
258 if(y_dist>x_dist){
259 purs(me,len-1-enemy.y,enemy.x,directions);
260 }
261 else{
262 purs(me,enemy.y,wid-1-enemy.x,directions);
263 }
264 }
move_to_top(byte array[4],byte index)265 void move_to_top(byte array[4],byte index){
266 byte newtop=array[index];
267 for(byte i=index;i>0;--i){
268 array[i]=array[i-1];
269 }
270 array[0]=newtop;
271 }
leave_escapes(byte board[len][wid],snake me,byte directions[4])272 void leave_escapes(byte board[len][wid],snake me,byte directions[4]){
273 byte s=3;
274 for(byte i=0;i<4;i++){
275 me.direction=directions[s];
276 if(!better_change_way(board,me)){
277 move_to_top(directions,s);
278 }
279 else{
280 --s;
281 }
282 }
283 }
go_deep(byte board[len][wid],snake me,bool randomize)284 long go_deep(byte board[len][wid],snake me,bool randomize){
285 reports("****go deep***");
286 if(randomize){
287 reports("randomize");
288 }
289 long m=0;
290 byte bumps=0;
291 static byte inc=1;
292 if(randomize){
293 inc=-inc;
294 }
295 while(!blocked(board,me)){
296 me=fake_move(me);
297 ++m;
298 if(m>len+wid){
299 return m;
300 }
301 if(blocked(board,me)||(randomize&&!(rand()%10))){
302 snake f=me;
303 byte i;
304
305 if(randomize){
306 f.direction=rand()%4;
307 }
308
309 for(i=0;i<4;++i){
310 if(f.direction!=opposite(me.direction) || blocked(board,f)){
311 me=f;
312 break;
313 }
314 else{
315 f.direction+=4+inc;
316 f.direction%=4;
317 }
318 }
319
320 reports("***BUMP!***");
321 reportd(bumps);
322 reportd(m);
323
324 if(bumps==4){
325 return m;
326 }
327 else{
328 ++bumps;
329 }
330
331 }
332 }
333 return m;
334
335 }
mnvrblty(byte board[len][wid],snake me,byte depth)336 long mnvrblty(byte board[len][wid],snake me,byte depth){
337 long m=0;
338 long max=0,n,max_n;
339 while(m<=4 && !blocked(board,me)){
340 me=fake_move(me);
341 ++m;
342 if(depth){
343 snake f=me;
344 max_n=0;
345 for(byte i=0;i<4;++i){
346 n=0;
347 if(i==opposite(me.direction)){
348 continue;
349 }
350 f.direction=i;
351 for(byte j=0;j<10;++j){
352 n=go_deep(board,f,j%2);
353 if(max_n<n){
354 max_n=n;
355 }
356 if(max_n>len+wid){
357 return max_n;
358 }
359 }
360 reports("Then the maximum became:");
361 reportd(max_n);
362 }
363 if(max<m+max_n){
364 max=m+max_n;
365 }
366 }
367 }
368 return max;
369 }
sort_directions(long data[4],byte directions[4])370 void sort_directions(long data[4],byte directions[4]){
371 bool not_sorted=1;
372 while(not_sorted){
373 not_sorted=0;
374 for(byte i=0;i<3;++i){
375 if(data[i]<data[i+1]){
376 swap_long(&data[i],&data[i+1]);
377 swap(&directions[i],&directions[i+1]);
378 not_sorted=1;
379 }
380 }
381 }
382 }
rank_for_survival(byte board[len][wid],snake me,long advantages[4],byte directions[4])383 void rank_for_survival(byte board[len][wid],snake me,long advantages[4],byte directions[4]){
384 long max_adv,adv,sum,sum_positives;
385 for(byte i=0;i<4;++i){
386 reports("inspecting various directions");
387 reportd(i);
388 adv=sum=sum_positives=0;
389 max_adv=LONG_MIN;
390 me.direction= directions[i];
391 adv=mnvrblty(board,me,2);//advantage(board,*me,*enemy,depth-1);
392 reports("advantage is");
393 reportd(adv);
394 if(max_adv<adv){
395 max_adv=adv;
396 }
397 advantages[i]=max_adv;
398
399 reportd(advantages[i]);
400 }
401
402 sort_directions(advantages,directions);
403 reportd(advantages[0]);
404 reportd(directions[0]);
405 reportd(advantages[1]);
406 reportd(directions[1]);
407 reportd(advantages[2]);
408 reportd(directions[2]);
409 reportd(advantages[3]);
410 reportd(directions[3]);
411 }
draw(byte board[len][wid])412 void draw(byte board[len][wid]){
413 int y,x;
414 rectangle();
415 mvprintw(1,16,"Computer's wins: %d",c.score);
416 mvprintw(2,16,"Your wins: %d",p.score);
417 for(y=0;y<len;++y){
418 for(x=0;x<wid;++x){
419 switch(board[y][x]/4){
420 case 1:
421 mvaddch(4+y,x+1,' '|A_STANDOUT|c.color);
422 break;
423 case 2:
424 mvaddch(4+y,x+1,' '|A_STANDOUT|p.color);
425 break;
426 }
427 if(board[y][x]<0)
428 mvaddch(4+y,x+1,'0'-board[y][x]);
429 }
430 }
431 }
help(void)432 void help(void){
433 nocbreak();
434 cbreak();
435 erase();
436 logo();
437 attron(A_BOLD);
438 mvprintw(3,0," **** THE CONTROLS ****");
439 attroff(A_BOLD);
440 mvprintw(4,0,"hjkl/ARROW KEYS : Change direction");
441 mvprintw(5,0,"q : Quit");
442 mvprintw(6,0,"F1 & F2: Help on controls & gameplay");
443 mvprintw(8,0,"Press a key to continue");
444 refresh();
445 getch();
446 erase();
447 halfdelay(1);
448 }
gameplay(void)449 void gameplay(void){
450 nocbreak();
451 cbreak();
452 erase();
453 logo();
454 attron(A_BOLD);
455 mvprintw(3,0," **** THE GAMEPLAY ****");
456 attroff(A_BOLD);
457 move(4,0);
458 printw("Don't hit the walls, the other snake and yourself. Kill the other snake.\n");
459 refresh();
460 getch();
461 erase();
462 halfdelay(1);
463 }
sigint_handler(int x)464 void sigint_handler(int x){
465 endwin();
466 puts("Quit.");
467 exit(x);
468 }
decide(byte board[len][wid],snake * me,snake * enemy)469 long decide(byte board[len][wid],snake *me,snake *enemy){
470 //do the move that gives the enemy least advantage (or move randomly at depth 0)
471 //return 0 if you fail to find a way out
472 snake f=*me;//f:future
473 static long turn=0;
474 reports(" **MOVE***********");
475 reportd(turn);
476 ++turn;
477 reportd(me->direction);
478 int y_dist=(abs(me->y-enemy->y));
479 int x_dist=(abs(me->x-enemy->x));
480 int dist=(y_dist+x_dist);
481 long g=go_deep(board,*me,1);
482 reportd(g);
483 byte directions[4]={0,1,2,3};
484 long advantages[4]={0};
485 if(me->strategy==IMITATE ){
486 if(abs(me->y-(len-1-enemy->y))+abs(me->x-(wid-1-enemy->x))>3){
487 me->strategy=SURVIVAL;
488 }
489 else{
490 me->strategy=IMITATE;
491 }
492 }
493 else if(g<20){
494 me->strategy=SURVIVAL;
495 }
496 else if( dist<20){
497 me->strategy=BLOCK;
498 }
499 else{
500 me->strategy=MIRROR;
501 }
502 bool change_path=0;
503 if(better_change_way(board,*me)){
504 change_path=1;
505 }
506 else if(me->strategy==IMITATE){
507 change_path=1;
508 }
509 else if(me->strategy==SURVIVAL){
510 reports("SURVIVAL!@#");
511 change_path=1;
512 }
513 else if(me->strategy==MIRROR){
514 change_path=better_change_way(board,*me) || ((me->x%2)&&(me->y%3==2)) || ((me->x%2==0)&&(me->y%3==0));
515 if(better_change_way(board,*me) && !change_path){
516 reports("fuck you");
517 }
518 }
519 else if(me->strategy==BLOCK){
520 reports("BLOCK!@#");
521 change_path= !(rand()%(dist+1)) || !(rand()%(x_dist+1)) || !(rand()%(y_dist+1));//this one wants to leave escapes
522
523 if(!change_path && dist<40 && !(rand()%(dist/2+1))){//this one wants to kill
524 change_path=1;
525 }
526 }
527
528 if(change_path){
529 if(me->strategy==IMITATE){
530 enemy_mirror(*me,*enemy,directions);
531 }
532 if(me->strategy==MIRROR){
533 enemy_mirror(*me,*enemy,directions);
534 //shuffle(directions);
535 leave_escapes(board,*me,directions);
536 reports("did the leave escapes shit");
537 reports("MIRROR");
538 }
539 else if(me->strategy==BLOCK){
540 if(dist<7){
541 enemy_pursue(*me,*enemy,directions);
542 }
543 /*else if(dist<20){
544 enemy_block(board,*me,*enemy,directions);
545 }*/
546 else{
547 enemy_block(board,*me,*enemy,directions);
548 }
549 leave_escapes(board,*me,directions);
550 reports("BLOCK");
551 }
552
553 else if(me->strategy==SURVIVAL){
554 rank_for_survival(board,*me,advantages,directions);
555 reports("SURVIVAL and I am acting upon it");
556
557 }
558
559 for(byte i=0;i<4;++i){//if one way is blocked, go for others
560 reportd(directions[i]);
561 f.direction=directions[i];
562 if(!blocked(board,f)){
563 if(better_change_way(board,f)){
564 reports("YET THIS MOTHER FUCKER CHOSE:");
565 reportd(i);
566 }
567 *me=f;
568 move_snake(board,me);
569 return 1;
570 }
571 else{
572 reports("this fucker didn't choose:");
573 reportd(directions[i]);
574 reports("because that way was supposedly blocked.");
575 }
576 }
577 return LOSE;
578 }
579
580 reports("went on");
581 move_snake(board,me);
582 return 1;
583 }
init_game(byte board[len][wid])584 void init_game(byte board[len][wid]){
585 if(p.score>c.score+2 && rand()%2){
586 must_win=1;
587 }
588 if(must_win && p.score>c.score){
589 c.strategy=IMITATE;
590 }
591 else{
592 c.strategy=MIRROR;
593 }
594
595 c.direction=0;
596 c.y=len/2;
597 c.x=wid*9/20;
598 c.fp=4;
599 c.color=colors[rand()%6];
600 p.direction=0;
601 p.y=len/2;
602 p.x=wid*11/20;
603 p.fp=8;
604
605 do{
606 p.color=colors[rand()%6];
607 }while(p.color==c.color);
608
609 for(byte y=0;y<len;++y){
610 for(byte x=0;x<wid;++x){
611 board[y][x]=0;
612 }
613 }
614 }
main(int argc,char ** argv)615 int main(int argc, char** argv){
616 #ifdef REPORT
617 lol=fopen("lol","w");
618 fflush(lol);
619 #endif
620 bool autoset=0;
621 signal(SIGINT,sigint_handler);
622 #ifndef NO_VLA
623 if(argc>3 || (argc==2 && !strcmp("help",argv[1])) ){
624 printf("Usage: %s [len wid]\n",argv[0]);
625 return EXIT_FAILURE;
626 }
627 if(argc==2){
628 puts("Give both dimensions.");
629 return EXIT_FAILURE;
630 }
631 if(argc==3){
632 bool lool = sscanf(argv[1],"%d",&len) && sscanf(argv[2],"%d",&wid);
633 if(!lool){
634 puts("Invalid input.");
635 return EXIT_FAILURE;
636 }
637 if(len<MINLEN || wid<MINWID || len>500 || wid>500){
638 puts("At least one of your given dimensions is either too small or too big.");
639 return EXIT_FAILURE;
640 }
641
642 }
643 else{
644 autoset=1;
645 }
646 #endif
647 initscr();
648 #ifndef NO_VLA
649 if(autoset){
650 len=LINES-7;
651 if(len<MINLEN)
652 len=MINLEN;
653 else if(len>MAXLEN)
654 len=MAXLEN;
655
656 wid=COLS-5;
657 if(wid<MINWID)
658 wid=MINWID;
659 else if(wid>MAXWID)
660 wid=MAXWID;
661 }
662 #endif
663 srand(time(NULL)%UINT_MAX);
664 byte board[len][wid];
665 byte win_limit=WIN_LIMIT;
666
667 reportd(len);
668 reportd(wid);
669 noecho();
670 cbreak();
671 keypad(stdscr,1);
672 if(has_colors()){
673 start_color();
674 use_default_colors();
675 init_pair(1,COLOR_RED,-1);
676 init_pair(2,COLOR_YELLOW,-1);
677 init_pair(3,COLOR_GREEN,-1);
678 init_pair(4,COLOR_CYAN,-1);
679 init_pair(5,COLOR_MAGENTA,-1);
680 init_pair(6,COLOR_BLUE,-1);
681 for(byte b= 0;b<6;++b){
682 colors[b]=COLOR_PAIR(b+1);
683 }
684 colors[1]|=A_BOLD;
685 }
686 Start:
687 if(c.score==win_limit || p.score==win_limit){
688 win_limit=WIN_LIMIT;
689 c.score=p.score=0;
690 must_win=0;
691 }
692 if(c.score==p.score && p.score==win_limit-1){
693 ++win_limit;
694 }
695 curs_set(0);
696 halfdelay(1);
697 init_game(board);
698 erase();
699 int preinput=0,input=0;
700 while(1){
701 logo();
702 draw(board);
703 refresh();
704 preinput=input;
705 input = getch();
706 if( input == KEY_F(1) || input=='?' )
707 help();
708 if( input==KEY_F(2) )
709 gameplay();
710 if( (input=='k' || input==KEY_UP) && p.y>0 && p.direction != DOWN ){
711 p.direction=UP;
712 }
713 if( (input=='j' || input==KEY_DOWN) && p.y<len-1 && p.direction != UP ){
714 p.direction=DOWN;
715 }
716 if( (input=='h' || input==KEY_LEFT) && p.x>0 && p.direction != RIGHT){
717 p.direction=LEFT;
718 }
719 if( (input=='l' || input==KEY_RIGHT) && p.x<wid-1 && p.direction != LEFT){
720 p.direction=RIGHT;
721 }
722 if( input=='q')
723 sigint_handler(0);
724 if( input=='p'){
725 nocbreak();
726 cbreak();
727 erase();
728 logo();
729 attron(A_BOLD);
730 mvaddstr(1,13,"PAUSED");
731 attroff(A_BOLD);
732 getch();
733 halfdelay(1);
734 }
735 if(input!=ERR){
736 if(preinput==input){//if it wasn't there, hitting two keys in less than 0.1 sec would not work
737 usleep(100000);
738 flushinp();
739 }
740 }
741
742
743 for(byte i=0;i<2;++i){
744 if(blocked(board,p)){
745 ++c.score;
746 reports("player died");
747 goto Die;
748 }
749 else{
750 move_snake(board,&p);
751 }
752
753 /*if(decide(board,&p,&c) == LOSE){//move, if failed die.
754 ++c.score;
755 reports("computer died");
756 goto Die;
757 }*/
758 if(decide(board,&c,&p) == LOSE){//move, if failed die.
759 ++p.score;
760 reports("computer died");
761 goto Die;
762 }
763 }
764 refresh();
765 }
766 Die:
767 nocbreak();
768 cbreak();
769 draw(board);
770 refresh();
771 mvprintw(5+len,0,"Game over! Wanna play again?(y/n)");
772 curs_set(1);
773 input=getch();
774 if( input!= 'N' && input!= 'n' && input!='q')
775 goto Start;
776 endwin();
777 return EXIT_SUCCESS;
778 }
779