#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "memory.h" #include "showmenu.h" #include "tcp.h" #define HOSTNAME "localhost" #define PORT 1423 #define GAMENAME "regexbattle" #define NUM_LEVELS (5) typedef struct ConnectionData{ int sock; i64 my_id; } ConnectionData; static char *die_message=NULL; __attribute__((noreturn, format (printf,1,2))) static void die(const char *format,...){ va_list ap; va_start(ap,format); char *buf; int ret=vasprintf(&buf,format,ap); va_end(ap); if(ret<0){ die_message=strdup("Double error roll"); } else { die_message=buf; } exit(1); } static void exit_cleanup(void){ endkeyboard(); endscreen(); if(die_message!=NULL){ printf("%s\n",die_message); } } //-1: error; 0: timeout; >=1: fd ready for reading static int wait_fds(const int *fds,i64 length,const struct timeval *timeout){ if(fds==NULL||length<=0)return -1; struct timeval timeout2; if(timeout!=NULL)memcpy(&timeout2,timeout,sizeof(struct timeval)); fd_set set; FD_ZERO(&set); int maxfd=-1; for(i64 i=0;imaxfd)maxfd=fds[i]; FD_SET(fds[i],&set); } int ret=select(maxfd+1,&set,NULL,NULL,timeout==NULL?NULL:&timeout2); if(ret<0)return -1; if(ret==0)return 0; for(i64 i=0;ingoods=10; level->nbads=10; level->goods=malloc(level->ngoods,char*); level->bads=malloc(level->nbads,char*); for(i64 i=0;ingoods;i++){ level->goods[i]=random_string(); } for(i64 i=0;inbads;i++){ level->bads[i]=random_string(); } return level; } static void destroy_level(Level *level){ for(i64 i=0;ingoods;i++)free(level->goods[i]); for(i64 i=0;inbads;i++)free(level->bads[i]); free(level->goods); free(level->bads); free(level); } // "LEVEL 005 005 002 ka 002 as ..." static char* serialise_level(const Level *level){ i64 allocsize=13+(level->ngoods+level->nbads)*5; for(i64 i=0;ingoods;i++)allocsize+=strlen(level->goods[i]); for(i64 i=0;inbads;i++)allocsize+=strlen(level->bads[i]); char *str=malloc(allocsize+1,char); char *ptr=str; #define WRITE(...) do {ptr+=sprintf(ptr,__VA_ARGS__);} while(0) WRITE("LEVEL %03" PRIi64 " %03" PRIi64,level->ngoods,level->nbads); for(i64 i=0;ingoods;i++)WRITE(" %lu %s",strlen(level->goods[i]),level->goods[i]); for(i64 i=0;inbads;i++)WRITE(" %lu %s",strlen(level->bads[i]),level->bads[i]); assert(ptr-str==allocsize); #undef WRITE return str; } static Level* deserialise_level(const char *str){ if(memcmp(str,"LEVEL ",6)!=0)die("Argument to deserialise_level not a level"); Level *level=malloc(1,Level); level->ngoods=strtoll(str+6,NULL,10); level->nbads=strtoll(str+10,NULL,10); level->goods=malloc(level->ngoods,char*); level->bads=malloc(level->nbads,char*); i64 offset=14; for(i64 i=0;ingoods;i++){ i64 len=strtoll(str+offset,NULL,10); offset+=4; level->goods[i]=memdup(str+offset,len); offset+=len+1; } for(i64 i=0;inbads;i++){ i64 len=strtoll(str+offset,NULL,10); offset+=4; level->bads[i]=memdup(str+offset,len); offset+=len+1; } return level; } static void game_start(ConnectionData cd,const char *roomname,i64 other_id,bool host){ if(host){ tcp_send_line_f(cd.sock,"room_message " GAMENAME " %s %" PRIi64 " 9",roomname,other_id); tcp_send_str(cd.sock,"regexping"); } else { TcpResponse *res=tcp_read_response(cd.sock,""); if(res==NULL)die("Protocol error"); if(res->type==TCP_PUSH_LEAVE){ tcp_response_destroy(res); moveto(1,7); tprintf("Other player left game."); return; } if(res->type!=TCP_PUSH_MESSAGE)die("Protocol error: push expected"); if(strcmp(res->mval.message,"regexping")!=0)die("Protocol error: regexping message expected"); tcp_response_destroy(res); } if(host){ TcpResponse *res=tcp_read_response(cd.sock,""); if(res==NULL)die("Protocol error"); if(res->type==TCP_PUSH_LEAVE){ tcp_response_destroy(res); moveto(1,7); tprintf("Other player left game."); return; } if(res->type!=TCP_PUSH_MESSAGE)die("Protocol error: push expected"); if(strcmp(res->mval.message,"regexpong")!=0)die("Protocol error: regexpong message expected"); tcp_response_destroy(res); } else { tcp_send_line_f(cd.sock,"room_message " GAMENAME " %s %" PRIi64 " 9",roomname,other_id); tcp_send_str(cd.sock,"regexpong"); } for(i64 lvidx=0;lvidxtype==TCP_PUSH_LEAVE){ tcp_response_destroy(res); moveto(1,7); tprintf("Other player left game."); return; } if(res->type!=TCP_PUSH_MESSAGE)die("Protocol error: message push expected"); level=deserialise_level(res->mval.message); tcp_response_destroy(res); if(level==NULL)die("Protocol error: invalid serialised level received"); } //TODO: present level destroy_level(level); } //TODO: end game properly } static void room_create(ConnectionData cd,const char *name){ tcp_send_line_f(cd.sock,"room_create " GAMENAME " %s 1 2",name); TcpResponse *res=tcp_read_response(cd.sock,"room_create"); if(res==NULL)die("Protocol error"); if(res->type==TCP_ERROR){ moveto(1,7); tprintf("Error: %s",res->eval); tcp_response_destroy(res); return; } if(res->type!=TCP_OK)die("Protocol error: ok or error expected on room_create"); tcp_response_destroy(res); moveto(1,7); tprintf("Waiting for other player to enter room '%s'...\n" "Press to cancel.",name); redraw(); int fds[2]={1,cd.sock}; i64 other_id; while(true){ int fd=wait_fds(fds,2,NULL); if(fd<=0)die("select: %s",strerror(errno)); if(fd==1){ int key=tgetkey(); if(key==KEY_ESC){ clearscreen(); return; } bel(); } else if(fd==cd.sock){ TcpResponse *res=tcp_read_response(cd.sock,""); if(res==NULL||res->type!=TCP_PUSH_JOIN)die("Protocol error: unexpected not-push-join %d",res->type); other_id=res->jval.id; tcp_response_destroy(res); break; } else assert(false); } game_start(cd,name,other_id,true); tcp_send_line_f(cd.sock,"room_leave " GAMENAME " %s",name); if(tcp_read_ok(cd.sock,"room_leave")==-1)die("Protocol error: ok expected after room_leave"); clearscreen(); } static void room_join(ConnectionData cd,const char *name){ tcp_send_line_f(cd.sock,"room_join " GAMENAME " %s",name); TcpResponse *res=tcp_read_response(cd.sock,"room_join"); if(res==NULL)die("Protocol error"); if(res->type==TCP_ERROR){ moveto(1,7); tprintf("Error: %s",res->eval); tcp_response_destroy(res); return; } if(res->type!=TCP_OK)die("Protocol error: expecting ok or error on room_join"); tcp_response_destroy(res); tcp_send_line_f(cd.sock,"room_player_list " GAMENAME " %s",name); res=tcp_read_response(cd.sock,"room_player_list"); if(res==NULL)die("Protocol error"); if(res->type==TCP_ERROR){ moveto(1,7); tprintf("Error: %s",res->eval); tcp_response_destroy(res); return; } if(res->type==TCP_PUSH_LEAVE){ tcp_response_destroy(res); moveto(1,7); tprintf("Other player already left the game."); return; } if(res->type!=TCP_LIST)die("Protocol error: expecting error or list on room_player_list"); i64 other_id=-1; for(i64 i=0;ilval->nitems;i++){ i64 id=strtoll(res->lval->items[i],NULL,10); if(id!=cd.my_id){ other_id=id; break; } } tcp_response_destroy(res); if(other_id==-1)die("Protocol error: self not found in own room list"); game_start(cd,name,other_id,false); tcp_send_line_f(cd.sock,"room_leave " GAMENAME " %s",name); if(tcp_read_ok(cd.sock,"room_leave")==-1)die("Protocol error: ok expected after room_leave"); clearscreen(); } int main(void){ struct timeval tv; gettimeofday(&tv,NULL); srand((unsigned)(tv.tv_sec*1000000L+tv.tv_usec)); int sock=tcp_connect(HOSTNAME,PORT); if(sock==-1){ printf("Could not connect to %s:%d\n",HOSTNAME,PORT); return 1; } char *recvbuf=NULL; i64 recvbufsz=0; tcp_send_line(sock,"ping"); tcp_read_line(sock,&recvbuf,&recvbufsz); if(strcmp(recvbuf,"pong")!=0){ printf("Protocol error; different server running on %s:%d?\n",HOSTNAME,PORT); return 1; } ConnectionData cdata; cdata.sock=sock; tcp_send_line(sock,"id"); TcpResponse *resp=tcp_read_response(sock,"id"); if(resp==NULL||resp->type!=TCP_INT){ printf("Protocol error; different server running on %s:%d?\n",HOSTNAME,PORT); return 1; } else { cdata.my_id=resp->ival; } tcp_response_destroy(resp); initscreen(); initkeyboard(false); installCLhandler(true); atexit(exit_cleanup); clearscreen(); while(true){ i64 sel=showmenu("REGEXBATTLE", "List open public games","Create a new game","Join an open game", "Quit",NULL); Size termsize=gettermsize(); fillrect(0,7,termsize.w,termsize.h-7,' '); switch(sel){ case 0: { tcp_send_line(sock,"room_list " GAMENAME); TcpList *list=tcp_read_list(sock,"room_list"); if(list==NULL)die("Protocol error: receiving list"); Size termsize=gettermsize(); fillrect(0,7,termsize.w,termsize.h-7,' '); moveto(0,7); for(i64 i=0;initems&&i+7items[i]); } tcp_list_destroy(list); break; } case 1: { char *line=show_prompt(2,8,20,"Game name to create"); Size termsize=gettermsize(); fillrect(0,7,termsize.w,termsize.h-7,' '); if(line!=NULL){ room_create(cdata,line); free(line); } break; } case 2: { char *line=show_prompt(2,8,20,"Game name to join"); Size termsize=gettermsize(); fillrect(0,7,termsize.w,termsize.h-7,' '); if(line!=NULL){ room_join(cdata,line); free(line); } break; } case 3: return 0; } } }