1
0
mirror of https://github.com/abakh/nbsdgames.git synced 2024-11-02 16:27:18 -04:00
nbsdgames/snakeduel.c
2021-04-18 13:21:53 +04:30

779 lines
15 KiB
C

/*
_ _
(_ | :
_)NAKE |.'UEL
Authored by abakh <abakh@tuta.io>
No rights are reserved and this software comes with no warranties of any kind to the extent permitted by law.
compile with -lm -lncurses
*/
#include <curses.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include "config.h"
#define SAVE_TO_NUM 10
#define MINLEN 10
#define MAXLEN 127
#define MINWID 40
#define MAXWID 127
#define LOSE -(MAXWID*MAXLEN)
#define WIN_LIMIT 5
//#define REPORT 0
#ifdef REPORT
#define reportif(x) if(x){fprintf(lol,#x" is true\n");fflush(lol);}
#define reportd(x) if(REPORT){fprintf(lol, #x": %ld\n",(long)x);fflush(lol);}
#define reports(x) if(REPORT){fprintf(lol, "line %d: %s\n",__LINE__,x);fflush(lol);}
#else
#define reportif(x)
#define reportd(x)
#define reports(x)
#endif
enum {UP=0,RIGHT,DOWN,LEFT};
enum {BLOCK=0,SURVIVAL,MIRROR,IMITATE};
typedef signed char byte;
/* The Plan9 compiler can not handle VLAs */
#ifdef NO_VLA
#define len 36
#define wid 80
#ifdef Plan9
int usleep(long usec) {
int second = usec/1000000;
long nano = usec*1000 - second*1000000;
struct timespec sleepy = {0};
sleepy.tv_sec = second;
sleepy.tv_nsec = nano;
nanosleep(&sleepy, (struct timespec *) NULL);
return 0;
}
#endif
#else
int len,wid;
#endif//NO_VLA
typedef struct snake{
int y;
int x;
byte direction;
byte fp;
byte strategy;
byte score;
chtype color;
} snake;
snake p;//player
snake c;//computer
byte pscore;
byte cscore;
chtype colors[6]={0};
byte constant_change={0};
bool must_win=0;
FILE* lol;
void logo(void){
mvaddstr(0,0," _ _");
mvaddstr(1,0,"(_ | : ");
mvaddstr(2,0," _)NAKE |.'UEL");
}
void rectangle(void){
for(int y=0;y<=len;++y){
mvaddch(3+y,0,ACS_VLINE);
mvaddch(4+y,1+wid,ACS_VLINE);
}
for(int x=0;x<=wid;++x){
mvaddch(3,x,ACS_HLINE);
mvaddch(4+len,x,ACS_HLINE);
}
mvaddch(3,0,ACS_ULCORNER);
mvaddch(4+len,0,ACS_LLCORNER);
mvaddch(3,1+wid,ACS_URCORNER);
mvaddch(4+len,1+wid,ACS_LRCORNER);
}
void swap(byte* a,byte* b){
byte s= *a;
*a=*b;
*b=s;
}
void swap_long(long* a,long* b){
long s= *a;
*a=*b;
*b=s;
}
byte opposite(byte direction){
switch(direction){
case UP:
return DOWN;
case DOWN:
return UP;
case LEFT:
return RIGHT;
case RIGHT:
return LEFT;
default:
abort();
}
}
snake fake_move(snake s){
switch(s.direction){
case UP:
s.y=s.y-1;
break;
case DOWN:
s.y=s.y+1;
break;
case LEFT:
s.x=s.x-1;
break;
case RIGHT:
s.x=s.x+1;
break;
}
return s;
}
bool blocked(byte board[len][wid],snake s){
s=fake_move(s);
return ( s.y<0 || s.y >=len || s.x<0 || s.x>=wid || board[s.y][s.x] );
}
bool better_change_way(byte board[len][wid],snake s){
if(blocked(board,s)){
return 1;
}
s=fake_move(s);
if(blocked(board,s)){
return 1;
}
return 0;
}
void putfp(byte board[len][wid],snake s){
if(s.x>=0 && s.y>=0 && s.x<wid && s.y<len){
board[s.y][s.x]=s.fp+opposite(s.direction);//putting direction for wiping
}
}
void move_snake(byte board[len][wid],snake *s){
assert(!blocked(board,*s));
*s=fake_move(*s);
putfp(board,*s);
}
void purs(snake me,int y,int x,byte directions[4]){
if(me.y<y){
directions[0]=DOWN;
directions[3]=UP;
}
else{
directions[0]=UP;
directions[3]=DOWN;
}
if(me.x<x){
directions[1]=RIGHT;
directions[2]=LEFT;
}
else{
directions[1]=LEFT;
directions[2]=RIGHT;
}
int x_dist=abs(x-me.x);
int y_dist=abs(y-me.y);
if(x_dist>y_dist){
swap(&directions[0],&directions[1]);
}
if(x_dist==y_dist && x_dist<3 && directions[0]==me.direction){
swap(&directions[0],&directions[1]);
}
}
void avoid(snake me,int y, int x, byte directions[4]){
purs(me,y,x,directions);
for(byte i=0;i<4;++i){
directions[i]=opposite(directions[i]);
}
}
void shuffle(byte directions[4]){
byte a=rand()%4;
byte b=rand()%4;
swap(&directions[a],&directions[b]);
}
void enemy_avoid(snake me,snake enemy,byte directions[4]){
avoid(me,enemy.y,enemy.x,directions);
}
void enemy_pursue(snake me,snake enemy,byte directions[4]){
purs(me,enemy.y,enemy.x,directions);
}
void enemy_block(byte board[len][wid],snake me, snake enemy,byte directions[4]){
snake ahead=enemy;
switch(enemy.direction){
case UP:
if(me.y>enemy.y)//me is to the down of the enemy, so cannot plan to block it's way in advance
goto JustPursue;
break;
case DOWN:
if(me.y<enemy.y)
goto JustPursue;
break;
case RIGHT:
if(me.x<enemy.x)
goto JustPursue;
break;
case LEFT:
if(me.x>enemy.x)
goto JustPursue;
break;
default:
abort();
}
for(byte i=0;i<10;++i){
if(blocked(board,ahead)||ahead.y==me.y||ahead.x==me.x){
purs(me,ahead.y,ahead.x,directions);
return;
}
ahead=fake_move(ahead);
}
JustPursue:
purs(me,ahead.y,ahead.x,directions);
}
void enemy_mirror(snake me,snake enemy,byte directions[4]){
int y,x;
y=len-1-enemy.y;
x=wid-1-enemy.x;
purs(me,y,x,directions);
}
void enemy_block_mirror(snake me,snake enemy,byte directions[4]){
int y_dist=abs(me.y-enemy.y);
int x_dist=abs(me.x-enemy.x);
if(y_dist>x_dist){
purs(me,len-1-enemy.y,enemy.x,directions);
}
else{
purs(me,enemy.y,wid-1-enemy.x,directions);
}
}
void move_to_top(byte array[4],byte index){
byte newtop=array[index];
for(byte i=index;i>0;--i){
array[i]=array[i-1];
}
array[0]=newtop;
}
void leave_escapes(byte board[len][wid],snake me,byte directions[4]){
byte s=3;
for(byte i=0;i<4;i++){
me.direction=directions[s];
if(!better_change_way(board,me)){
move_to_top(directions,s);
}
else{
--s;
}
}
}
long go_deep(byte board[len][wid],snake me,bool randomize){
reports("****go deep***");
if(randomize){
reports("randomize");
}
long m=0;
byte bumps=0;
static byte inc=1;
if(randomize){
inc=-inc;
}
while(!blocked(board,me)){
me=fake_move(me);
++m;
if(m>len+wid){
return m;
}
if(blocked(board,me)||(randomize&&!(rand()%10))){
snake f=me;
byte i;
if(randomize){
f.direction=rand()%4;
}
for(i=0;i<4;++i){
if(f.direction!=opposite(me.direction) || blocked(board,f)){
me=f;
break;
}
else{
f.direction+=4+inc;
f.direction%=4;
}
}
reports("***BUMP!***");
reportd(bumps);
reportd(m);
if(bumps==4){
return m;
}
else{
++bumps;
}
}
}
return m;
}
long mnvrblty(byte board[len][wid],snake me,byte depth){
long m=0;
long max=0,n,max_n;
while(m<=4 && !blocked(board,me)){
me=fake_move(me);
++m;
if(depth){
snake f=me;
max_n=0;
for(byte i=0;i<4;++i){
n=0;
if(i==opposite(me.direction)){
continue;
}
f.direction=i;
for(byte j=0;j<10;++j){
n=go_deep(board,f,j%2);
if(max_n<n){
max_n=n;
}
if(max_n>len+wid){
return max_n;
}
}
reports("Then the maximum became:");
reportd(max_n);
}
if(max<m+max_n){
max=m+max_n;
}
}
}
return max;
}
void sort_directions(long data[4],byte directions[4]){
bool not_sorted=1;
while(not_sorted){
not_sorted=0;
for(byte i=0;i<3;++i){
if(data[i]<data[i+1]){
swap_long(&data[i],&data[i+1]);
swap(&directions[i],&directions[i+1]);
not_sorted=1;
}
}
}
}
void rank_for_survival(byte board[len][wid],snake me,long advantages[4],byte directions[4]){
long max_adv,adv,sum,sum_positives;
for(byte i=0;i<4;++i){
reports("inspecting various directions");
reportd(i);
adv=sum=sum_positives=0;
max_adv=LONG_MIN;
me.direction= directions[i];
adv=mnvrblty(board,me,2);//advantage(board,*me,*enemy,depth-1);
reports("advantage is");
reportd(adv);
if(max_adv<adv){
max_adv=adv;
}
advantages[i]=max_adv;
reportd(advantages[i]);
}
sort_directions(advantages,directions);
reportd(advantages[0]);
reportd(directions[0]);
reportd(advantages[1]);
reportd(directions[1]);
reportd(advantages[2]);
reportd(directions[2]);
reportd(advantages[3]);
reportd(directions[3]);
}
void draw(byte board[len][wid]){
int y,x;
rectangle();
mvprintw(1,16,"Computer's wins: %d",c.score);
mvprintw(2,16,"Your wins: %d",p.score);
for(y=0;y<len;++y){
for(x=0;x<wid;++x){
switch(board[y][x]/4){
case 1:
mvaddch(4+y,x+1,' '|A_STANDOUT|c.color);
break;
case 2:
mvaddch(4+y,x+1,' '|A_STANDOUT|p.color);
break;
}
if(board[y][x]<0)
mvaddch(4+y,x+1,'0'-board[y][x]);
}
}
}
void help(void){
nocbreak();
cbreak();
erase();
logo();
attron(A_BOLD);
mvprintw(3,0," **** THE CONTROLS ****");
attroff(A_BOLD);
mvprintw(4,0,"hjkl/ARROW KEYS : Change direction");
mvprintw(5,0,"q : Quit");
mvprintw(6,0,"F1 & F2: Help on controls & gameplay");
mvprintw(8,0,"Press a key to continue");
refresh();
getch();
erase();
halfdelay(1);
}
void gameplay(void){
nocbreak();
cbreak();
erase();
logo();
attron(A_BOLD);
mvprintw(3,0," **** THE GAMEPLAY ****");
attroff(A_BOLD);
move(4,0);
printw("Don't hit the walls, the other snake and yourself. Kill the other snake.\n");
refresh();
getch();
erase();
halfdelay(1);
}
void sigint_handler(int x){
endwin();
puts("Quit.");
exit(x);
}
long decide(byte board[len][wid],snake *me,snake *enemy){
//do the move that gives the enemy least advantage (or move randomly at depth 0)
//return 0 if you fail to find a way out
snake f=*me;//f:future
static long turn=0;
reports(" **MOVE***********");
reportd(turn);
++turn;
reportd(me->direction);
int y_dist=(abs(me->y-enemy->y));
int x_dist=(abs(me->x-enemy->x));
int dist=(y_dist+x_dist);
long g=go_deep(board,*me,1);
reportd(g);
long max_survival;
byte directions[4]={0,1,2,3};
long advantages[4]={0};
if(me->strategy==IMITATE ){
if(abs(me->y-(len-1-enemy->y))+abs(me->x-(wid-1-enemy->x))>3){
me->strategy=SURVIVAL;
}
else{
me->strategy=IMITATE;
}
}
else if(g<20){
me->strategy=SURVIVAL;
}
else if( dist<20){
me->strategy=BLOCK;
}
else{
me->strategy=MIRROR;
}
bool change_path=0;
if(better_change_way(board,*me)){
change_path=1;
}
else if(me->strategy==IMITATE){
change_path=1;
}
else if(me->strategy==SURVIVAL){
reports("SURVIVAL!@#");
change_path=1;
}
else if(me->strategy==MIRROR){
change_path=better_change_way(board,*me) || ((me->x%2)&&(me->y%3==2)) || ((me->x%2==0)&&(me->y%3==0));
if(better_change_way(board,*me) && !change_path){
reports("fuck you");
}
}
else if(me->strategy==BLOCK){
reports("BLOCK!@#");
change_path= !(rand()%(dist+1)) || !(rand()%(x_dist+1)) || !(rand()%(y_dist+1));//this one wants to leave escapes
if(!change_path && dist<40 && !(rand()%(dist/2+1))){//this one wants to kill
change_path=1;
}
}
if(change_path){
if(me->strategy==IMITATE){
enemy_mirror(*me,*enemy,directions);
}
if(me->strategy==MIRROR){
enemy_mirror(*me,*enemy,directions);
//shuffle(directions);
leave_escapes(board,*me,directions);
reports("did the leave escapes shit");
reports("MIRROR");
}
else if(me->strategy==BLOCK){
if(dist<7){
enemy_pursue(*me,*enemy,directions);
}
/*else if(dist<20){
enemy_block(board,*me,*enemy,directions);
}*/
else{
enemy_block(board,*me,*enemy,directions);
}
leave_escapes(board,*me,directions);
reports("BLOCK");
}
else if(me->strategy==SURVIVAL){
rank_for_survival(board,*me,advantages,directions);
reports("SURVIVAL and I am acting upon it");
}
for(byte i=0;i<4;++i){//if one way is blocked, go for others
reportd(directions[i]);
f.direction=directions[i];
if(!blocked(board,f)){
if(better_change_way(board,f)){
reports("YET THIS MOTHER FUCKER CHOSE:");
reportd(i);
}
*me=f;
move_snake(board,me);
return 1;
}
else{
reports("this fucker didn't choose:");
reportd(directions[i]);
reports("because that way was supposedly blocked.");
}
}
return LOSE;
}
reports("went on");
move_snake(board,me);
return 1;
}
void init_game(byte board[len][wid]){
if(p.score>c.score+2 && rand()%2){
must_win=1;
}
if(must_win && p.score>c.score){
c.strategy=IMITATE;
}
else{
c.strategy=MIRROR;
}
c.direction=0;
c.y=len/2;
c.x=wid*9/20;
c.fp=4;
c.color=colors[rand()%6];
p.direction=0;
p.y=len/2;
p.x=wid*11/20;
p.fp=8;
do{
p.color=colors[rand()%6];
}while(p.color==c.color);
for(byte y=0;y<len;++y){
for(byte x=0;x<wid;++x){
board[y][x]=0;
}
}
}
int main(int argc, char** argv){
#ifdef REPORT
lol=fopen("lol","w");
fflush(lol);
#endif
bool autoset=0;
signal(SIGINT,sigint_handler);
#ifndef NO_VLA
if(argc>3 || (argc==2 && !strcmp("help",argv[1])) ){
printf("Usage: %s [len wid]\n",argv[0]);
return EXIT_FAILURE;
}
if(argc==2){
puts("Give both dimensions.");
return EXIT_FAILURE;
}
if(argc==3){
bool lool = sscanf(argv[1],"%d",&len) && sscanf(argv[2],"%d",&wid);
if(!lool){
puts("Invalid input.");
return EXIT_FAILURE;
}
if(len<MINLEN || wid<MINWID || len>500 || wid>500){
puts("At least one of your given dimensions is either too small or too big.");
return EXIT_FAILURE;
}
}
else{
autoset=1;
}
#endif
initscr();
#ifndef NO_VLA
if(autoset){
len=LINES-7;
if(len<MINLEN)
len=MINLEN;
else if(len>MAXLEN)
len=MAXLEN;
wid=COLS-5;
if(wid<MINWID)
wid=MINWID;
else if(wid>MAXWID)
wid=MAXWID;
}
#endif
srand(time(NULL)%UINT_MAX);
byte board[len][wid];
byte win_limit=WIN_LIMIT;
reportd(len);
reportd(wid);
noecho();
cbreak();
keypad(stdscr,1);
if(has_colors()){
start_color();
use_default_colors();
init_pair(1,COLOR_RED,-1);
init_pair(2,COLOR_YELLOW,-1);
init_pair(3,COLOR_GREEN,-1);
init_pair(4,COLOR_CYAN,-1);
init_pair(5,COLOR_MAGENTA,-1);
init_pair(6,COLOR_BLUE,-1);
for(byte b= 0;b<6;++b){
colors[b]=COLOR_PAIR(b+1);
}
colors[1]|=A_BOLD;
}
Start:
if(c.score==win_limit || p.score==win_limit){
win_limit=WIN_LIMIT;
c.score=p.score=0;
must_win=0;
}
if(c.score==p.score && p.score==win_limit-1){
++win_limit;
}
curs_set(0);
halfdelay(1);
init_game(board);
erase();
int preinput=0,input=0;
while(1){
logo();
draw(board);
refresh();
preinput=input;
input = getch();
if( input == KEY_F(1) || input=='?' )
help();
if( input==KEY_F(2) )
gameplay();
if( (input=='k' || input==KEY_UP) && p.y>0 && p.direction != DOWN ){
p.direction=UP;
}
if( (input=='j' || input==KEY_DOWN) && p.y<len-1 && p.direction != UP ){
p.direction=DOWN;
}
if( (input=='h' || input==KEY_LEFT) && p.x>0 && p.direction != RIGHT){
p.direction=LEFT;
}
if( (input=='l' || input==KEY_RIGHT) && p.x<wid-1 && p.direction != LEFT){
p.direction=RIGHT;
}
if( input=='q')
sigint_handler(0);
if( input=='p'){
nocbreak();
cbreak();
erase();
logo();
attron(A_BOLD);
mvaddstr(1,13,"PAUSED");
attroff(A_BOLD);
getch();
halfdelay(1);
}
if(input!=ERR){
if(preinput==input){//if it wasn't there, hitting two keys in less than 0.1 sec would not work
usleep(100000);
flushinp();
}
}
for(byte i=0;i<2;++i){
if(blocked(board,p)){
++c.score;
reports("player died");
goto Die;
}
else{
move_snake(board,&p);
}
/*if(decide(board,&p,&c) == LOSE){//move, if failed die.
++c.score;
reports("computer died");
goto Die;
}*/
if(decide(board,&c,&p) == LOSE){//move, if failed die.
++p.score;
reports("computer died");
goto Die;
}
}
refresh();
}
Die:
nocbreak();
cbreak();
draw(board);
refresh();
mvprintw(5+len,0,"Game over! Wanna play again?(y/n)");
curs_set(1);
input=getch();
if( input!= 'N' && input!= 'n' && input!='q')
goto Start;
endwin();
return EXIT_SUCCESS;
}