/* This file is part of Bolixo. Bolixo is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bolixo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bolixo. If not, see . */ /* Documents and games manager. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filesystem.h" #include "bolixo.h" #include "bolixo.m" #define INSTRUMENT_DONOTOPEN #include "instrument.h" static DEBUG_KEY D_SUDOKU("sudoku","sudoku game"); using namespace std; static DEBUG_KEY D_PROTO ("proto","Protocol information"); enum CONNECT_TYPE { TYPE_NONE, TYPE_CONTROL, TYPE_CLIENT, TYPE_WORKER}; struct HANDLE_INFO: public ARRAY_OBJ{ CONNECT_TYPE type; REQUEST_INFO req; HANDLE_INFO(){ type = TYPE_NONE; } }; #include "proto/documentd_control.protoh" #include "proto/documentd_client.protoh" static string documentd_path (const char *name) { return string_f("%s/bo-games/%s",getenv("HOME"),name); } static void documentd_error (vector &res, PARAM_STRING s) { VARVAL v; v.var = "error"; v.val = s.ptr; res.push_back(v); } class GAME{ public: virtual const char *getclass()=0; virtual void save(FILE *fout)=0; virtual void load(FILE *fin)=0; virtual void resetgame() = 0; virtual void testwin(vector &res) = 0; virtual void exec (const char *var, const char *val, vector &res) = 0; virtual ~GAME(){}; }; using GAME_P = shared_ptr; class TICTACTO: public GAME{ unsigned char grid[3][3]; public: void save(FILE *fout){ for (auto &g:grid){ fprintf (fout,"%u %u %u\n",g[0],g[1],g[2]); } } void load(FILE *fin){ for (auto &g:grid){ unsigned v0,v1,v2; if (fscanf(fin,"%u %u %u\n",&v0,&v1,&v2)==3){ g[0] = v0; g[1] = v1; g[2] = v2; } } } void resetgame(){ memset (grid,0,sizeof(grid)); } TICTACTO(){ resetgame(); } const char *getclass(){ return "tictacto"; } void testwin(vector &res){ bool won = false; for (auto g:grid){ unsigned char v = g[0]; if (v != 0){ bool found = true; for (unsigned j=1; j<3; j++){ if (v != g[j]){ found = false; break; } } if (found){ won = true; break; } } } if (!won){ for (unsigned i=0; i<3; i++){ unsigned char v = grid[0][i]; if (v != 0){ bool found = true; for (unsigned j=1; j<3; j++){ if (v != grid[j][i]){ found = false; break; } } if (found){ won = true; break; } } } if (!won){ if (grid[0][0] != 0 && grid[0][0] == grid[1][1] && grid[0][0] == grid[2][2]){ won = true; }else if (grid[0][2] != 0 && grid[0][2] == grid[1][1] && grid[0][2] == grid[2][0]){ won = true; } } } if (won){ VARVAL v; v.var = "result"; v.val = "won"; res.push_back(v); } } void exec (const char *var, const char *val, vector &res){ if (strcmp(var,"place")==0){ bool ok = false; unsigned i=atoi(val); const char *pt = str_skipdig(val); const char *msg = "Invalid syntax"; if (*pt == ','){ pt++; unsigned j=atoi(pt); pt = str_skipdig(pt); if (*pt == ','){ pt++; unsigned value=atoi(pt); msg = "Out of range"; if (i < 3 && j < 3){ msg = "Invalid value"; if (value > 0 && value < 3){ unsigned char &g = grid[i][j]; if (g == 0){ grid[i][j] = value; ok = true; }else{ msg = "Location already played"; } } } } } if (!ok){ documentd_error (res,string_f("Invalid command %s=%s: %s",var,val,msg)); } }else if (strcmp(var,"print")==0){ string lines; const char *linesep = NULL; for (auto &g:grid){ if (linesep != NULL) lines += linesep; linesep = "-----------\n"; char sep = '\0'; for (auto &gg:g){ static char tbcar[]={' ','X','O'}; if (sep != '\0') lines += sep; lines += ' '; lines += tbcar[gg]; lines += ' '; sep = '|'; } lines += '\n'; } VARVAL v; v.var = "content"; v.val = lines; res.push_back(v); } } }; struct SUDOKU_CELL{ unsigned char visible; unsigned char value; unsigned char user_value; void reset(){ visible = value = user_value = 0; } SUDOKU_CELL(){ reset(); } }; class SUDOKU: public GAME{ SUDOKU_CELL grid[9][9]; unsigned line,column; // Currently selected 3x3 area public: const char *getclass(){ return "sudoku"; } SUDOKU(){ resetgame(); } void save(FILE *fout); void load(FILE *fin); void resetgame(); void testwin(vector &res); void exec (const char *var, const char *val, vector &res); }; void SUDOKU::save (FILE *fout) { for (auto &g:grid){ for (auto &gg:g) fprintf (fout,"%u,%u,%u\n",gg.visible,gg.value,gg.user_value); } } void SUDOKU::load (FILE *fin) { resetgame(); for (auto &g:grid){ for (auto &gg:g){ unsigned visible,value,user_value; if (fscanf(fin,"%u,%u,%u\n",&visible,&value,&user_value)==3){ gg.visible = visible; gg.value = value; gg.user_value = user_value; } } } } void SUDOKU::resetgame() { line = column = 0; for (auto &g:grid){ for (auto &gg:g){ gg.reset(); } } } void SUDOKU::testwin(vector &res) { unsigned nbok = 0; for (auto &g:grid){ for (auto &gg:g){ if (gg.visible || gg.value == gg.user_value) nbok++; } } if (nbok == 9*9){ VARVAL v; v.var = "result"; v.val = "won"; res.push_back(v); } } void SUDOKU::exec (const char *var, const char *val, vector &res) { if (strcmp(var,"place")==0){ unsigned lo,co,v; int n = sscanf(val,"%u,%u,%u",&lo,&co,&v); if (n == 2){ if (lo < 3 && co < 3){ line=lo; column = co; }else{ documentd_error (res,"You can't select this area"); } }else if (n!=3){ documentd_error (res,"Invalid place command (need 5 value)"); }else if (lo < 3 && co < 3 && v >= 0 && v < 10){ auto &gg = grid[line*3+lo][column*3+co]; if (gg.visible){ documentd_error (res,"You can't set this cell"); }else{ gg.user_value = v; } }else{ documentd_error (res,string_f("Invalid coordinate [%u,%u] [%u,%u]",line,column,lo,co)); } }else if (strcmp(var,"print")==0){ string lines; static const char *dashes = "\t+---+---+---+---+---+---+---+---+---+\n"; unsigned nol = 0; static const char *white = "\033[01;37m"; static const char *green = "\033[01;32m"; static const char *blue = "\033[01;34m"; static const char *red = "\033[01;31m"; static const char *bgblue = "\033[01;44m"; static const char *normal = "\033[00m"; for (auto &g:grid){ if (nol % 3 == 0){ lines += blue; lines += dashes; lines += normal; }else{ lines += '\t'; for (unsigned i=0; i<9; i++){ if (i % 3 == 0){ lines += blue; }else{ lines += green; } lines += '+'; lines += green; lines += "---"; } lines += blue; lines += '+'; lines += normal; lines += '\n'; } lines += "\t"; unsigned pos = 0; for (auto &gg:g){ if (pos % 3 == 0){ lines += blue; lines += '|'; }else{ lines += green; lines += '|'; } lines += normal; if (nol / 3 == line && pos / 3 == column) lines += bgblue; if (gg.visible){ lines += string_f("%s %c %s",white,gg.value+'0',normal); }else if (gg.user_value != 0){ if (gg.user_value != gg.value){ lines += red; } lines += string_f(" %c ",gg.user_value+'0'); lines += normal; }else{ lines += " "; } lines += normal; pos++; } lines += blue; lines += "|\n"; lines += normal; nol++; } lines += blue; lines += dashes; lines += normal; VARVAL v; v.var = "content"; v.val = lines; res.push_back(v); }else if (strcmp(var,"newgame")==0){ glocal SUDOKU_CELL (*grid)[9][9] = &grid; unsigned difficulty = atoi(val); if (difficulty > 3){ documentd_error (res,"Difficulty from 0 to 3"); }else{ static const char *tbdiff[]={"simple", "easy", "intermediate", "expert"}; (string_f("qqwing --generate 1 --compact --solution --difficulty %s",tbdiff[difficulty]),10); debug_printf (D_SUDOKU,"read qqwing %s\n",line); if (noline < 9){ auto &g = (*glocal.grid)[noline]; for (unsigned i=0; i<9; i++){ char car = line[i]; auto &gg = g[i]; gg.reset(); if (car != '.'){ gg.visible = 1; gg.value = car - '0'; } } } if (noline >= 10 && noline < 19){ auto &g = (*glocal.grid)[noline-10]; for (unsigned i=0; i<9; i++){ g[i].value = line[i]-'0'; } } return 0; } } } class WORDPROC: public GAME{ vector lines; public: const char *getclass(){ return "wordproc"; } void save(FILE *fout); void load(FILE *fin); void resetgame(); void testwin(vector &res); void exec (const char *var, const char *val, vector &res); }; void WORDPROC::save(FILE *fout) { } void WORDPROC::load(FILE *fin) { } void WORDPROC::resetgame() { lines.clear(); } void WORDPROC::testwin(vector &res) { } void WORDPROC::exec (const char *var, const char *val, vector &res) { } class CHECKERS: public GAME{ unsigned char grid[8][8]; public: const char *getclass(){ return "checkers"; } void save(FILE *fout); void load(FILE *fin); void resetgame(); void testwin(vector &res); void exec (const char *var, const char *val, vector &res); }; void CHECKERS::save(FILE *fout) { for (auto &g:grid){ for (auto &gg:g){ fprintf (fout," %u",gg); } fprintf (fout,"\n"); } } void CHECKERS::load(FILE *fin) { resetgame(); for (auto &g:grid){ unsigned v[8]; if (fscanf(fin,"%u %u %u %u %u %u %u %u\n",&v[0],&v[1],&v[2],&v[3],&v[4],&v[5],&v[6],&v[7])==8){ for (unsigned i=0; i<8; i++) g[i] = v[i]; } } } void CHECKERS::resetgame() { for (auto &g:grid) for (auto &gg:g) gg=0; for (unsigned i=0; i<3; i++){ unsigned start = (i+1)&1; for (unsigned j=start; j<8; j+=2){ grid[i][j] = 1; } unsigned ii=5+i; start = i&1; for (unsigned j=start; j<8; j+=2){ grid[ii][j] = 2; } } } void CHECKERS::testwin(vector &res) { } static const char *cnv (const char *val, unsigned &posx, unsigned &posy) { val = str_skip(val); if (islower(*val)){ posy = *val - 'a'; }else if (isupper(*val)){ posy = *val - 'A'; } posx = 8-atoi(val+1); return val+2; } void CHECKERS::exec (const char *var, const char *val, vector &res) { if (strcmp(var,"print")==0){ string lines; //static const char *white = "\033[01;37m"; //static const char *green = "\033[01;32m"; //static const char *blue = "\033[01;34m"; //static const char *red = "\033[01;31m"; static const char *bgblue = "\033[01;44m"; static const char *bggreen = "\033[01;42m"; static const char *bgred = "\033[01;41m"; static const char *bgblack = "\033[01;40m"; static const char *normal = "\033[00m"; static const char *tbcolor[]={bgblack,bgblue}; unsigned nol = 0; for (auto &g:grid){ static const char *tbl[]={ "%s %s %s ", "%s %s %s ", "%s %s %s ", "%s %s %s ", "%s %s %s ", //"%s ", }; for (unsigned i=0; i<5; i++){ unsigned color = nol & 1; if (i == 2){ lines += string_f("\t%d ",8-nol); }else{ lines += "\t "; } for (auto &gg:g){ const char *bg = tbcolor[color]; color = (color+1)&1; const char *bg1 = ""; if (gg == 0){ bg1 = bg; }else if (gg == 1){ bg1 = bgred; }else if (gg == 2){ bg1 = bggreen; } lines += string_f(tbl[i],bg,bg1,bg); } lines += normal; lines += '\n'; } nol++; } lines += "\t "; for (unsigned i=0; i<8; i++) lines += string_f(" %c ",i+'A'); lines += '\n'; VARVAL v; v.var = "content"; v.val = lines; res.push_back(v); }else if (strcmp(var,"place")==0){ unsigned fromx,fromy,tox,toy; const char *pt = cnv(val,fromx,fromy); cnv(pt,tox,toy); // printf ("from = %u %u to %u %u\n",fromx,fromy,tox,toy); unsigned char value = grid[fromx][fromy]; if (value == 0){ documentd_error (res,"Invalid move"); }else if (grid[tox][toy] != 0){ documentd_error (res,"Destination used"); }else{ grid[fromx][fromy] = 0; grid[tox][toy] = value; } } } int main (int argc, char *argv[]) { glocal int ret = -1; glocal const char *control = "/var/run/documentd.sock"; glocal const char *clientsock = "/tmp/documentd_client.sock"; glocal const char *user = "bolixo"; glocal bool daemon = false; glocal const char *admin_secretfile = "/etc/bolixo/secrets.client"; glocal const char *pidfile = "/var/run/documentd.pid"; glocal const char *hostname = NULL; static const char *tbdic[]={"bolixo",NULL}; glocal.ret = (argc,argv,tbdic); setproginfo ("documentd",VERSION,"Process document content"); setgrouparg ("Networking"); setarg ('c',"control","Unix socket for documentd-control",glocal.control,false); setarg ('C',"clientsock","Unix socket for documentd-client",glocal.clientsock,false); setgrouparg ("Misc."); setarg (' ',"user","Run the program as this user",glocal.user,false); setarg (' ',"daemon","Run in background",glocal.daemon,false); setarg (' ',"pidfile","File holding the PID of the process",glocal.pidfile,false); setarg (' ',"admin-secrets","File holding admin secrets for communication",glocal.admin_secretfile,false); if (glocal.daemon){ syslog (LOG_ERR,"%s",msg); }else{ fprintf (stderr,"%s",msg); } if (glocal.daemon){ syslog (LOG_WARNING,"%s",msg); }else{ fprintf (stderr,"%s",msg); } int ret = -1; glocal map games; glocal unsigned messages_sent = 0; glocal string controlport = string_f("unix:%s",glocal.control); glocal string clientport = string_f("unix:%s",glocal.clientsock); glocal map admin_secrets; glocal pid_t pid = (pid_t)-1; fdpass_readsecrets (glocal.admin_secretfile,glocal.admin_secrets); signal (SIGCHLD,SIG_IGN); (glocal.clientport,5); HANDLE_INFO *n = new HANDLE_INFO; info.data = n; // tlmp_error ("port=%s control=%s client=%s\n",info.port,glocal.controlport.c_str(),glocal.clientport.c_str()); if (string_cmp(info.port,glocal.controlport)==0){ n->type = TYPE_CONTROL; }else if (string_cmp(info.port,glocal.clientport)==0){ n->req.secret = fdpass_findsecret (glocal.admin_secrets,info.port); n->type = TYPE_CLIENT; } debug_printf (D_PROTO,"receive line: %s\n",line); HANDLE_INFO *c = (HANDLE_INFO*)info.data; static const char *tbtype[]={"none","control request","client request"}; ERROR_PREFIX prefix ("%s: ",tbtype[c->type]); if (c->type == TYPE_CONTROL){ (this,c->req,line, info.linelen,endserver, endclient, no,c); vector tb; tb.push_back(string_f ("Version %s",VERSION)); instrument_status(tb); for (auto g:glocal.games){ tb.push_back(string_f("gameid: %s",g.first.c_str())); } rep_status(tb); toggle_instrument_file(on); endserver = true; if (on){ debug_seton(); }else{ debug_setoff(); } debug_setfdebug (filename); // gamename gameid = success:b msg string msg; bool success = false; auto g = glocal.games.find(gameid); if (g != glocal.games.end()){ msg = "Game already exist"; }else{ GAME_P p; if (strcmp(gamename,"tictacto")==0){ p = make_shared(); }else if (strcmp(gamename,"sudoku")==0){ p = make_shared(); }else if (strcmp(gamename,"wordproc")==0){ p = make_shared(); }else if (strcmp(gamename,"checkers")==0){ p = make_shared(); } if (p != NULL){ glocal.games[gameid] = p; glocal.games[gameid]->resetgame(); success = true; }else{ msg = "unknown game"; } } rep_startgame(success,msg); // gameid = success:b msg bool success = false; string msg; auto g = glocal.games.find(gameid); if (g != glocal.games.end()){ success = true; glocal.games.erase (g); }else{ msg = "Unknown game id"; } rep_endgame (success,msg); // gameid = success:b msg bool success = false; string msg; auto g = glocal.games.find(gameid); if (g != glocal.games.end()){ success = true; g->second->resetgame(); }else{ msg = "Unknown game id"; } rep_endgame (success,msg); // gameid steps:U{VARVAL}v = success:b msg res:U{VARVAL}v vector res; bool success = true; string msg; auto g = glocal.games.find(gameid); if (g != glocal.games.end()){ success = true; for (auto &v:steps){ g->second->exec(v.var,v.val,res); } g->second->testwin(res); }else{ msg = "Unknown game id"; } rep_playstep(success,msg,res); // gameid = success:b msg bool success = false; string msg; auto g = glocal.games.find(gameid); if (g != glocal.games.end()){ glocal GAME_P game = g->second; success = true; (documentd_path(gameid),false); fprintf (fout,"%s\n",glocal.game->getclass()); glocal.game->save(fout); return 0; }else{ msg = "Unknown game id"; } rep_save(success,msg); // gameid = success:b msg bool success = false; string msg; auto g = glocal.games.find(gameid); if (g != glocal.games.end()){ glocal.games.erase (g); } string tmp = documentd_path(gameid); FILE *fin = fopen (tmp.c_str(),"r"); if (fin == NULL){ msg = "Gameid file does not exist"; }else{ char buf[100]; if (fgets(buf,sizeof(buf)-1,fin)!=NULL){ GAME_P p; if (strcmp(buf,"sudoku\n")==0){ p = GAME_P(new SUDOKU); }else if (strcmp(buf,"tictacto\n")==0){ p = GAME_P(new TICTACTO); }else if (strcmp(buf,"wordproc\n")==0){ p = GAME_P(new WORDPROC); }else if (strcmp(buf,"checkers\n")==0){ p = GAME_P(new CHECKERS); }else{ msg = string_f("Unknown game type %s",buf); } if (p != NULL){ glocal.games[gameid] = p; p->load (fin); success = true; } } fclose (fin); } rep_save(success,msg); tlmp_error ("Invalid command: %s\n",line); endclient = true; }else if (c->type == TYPE_CLIENT){ (this,c->req,line,info.linelen, endserver, endclient,no,c); rep_test(false); tlmp_error ("Invalid command: %s\n",line); endclient = true; } bool some_errors = false; if (fdpass_setcontrol(s,glocal.control,glocal.user)==-1){ some_errors = true; } if (!some_errors && s.is_ok()){ chmod (glocal.clientsock,0666); s.setrawmode(true); if (glocal.daemon){ daemon_init(glocal.pidfile,glocal.user); } open_instrument_file(); s.loop(); ret = 0; } return ret; return glocal.ret; }