/* 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 . */ #include #include #include #include #include #include #include #include "bolixo.h" #include "proto/bod_client.protodef" #include "documentd.h" #include "bolixo.m" using namespace std; #include "proto/documentd_checkers.protoh" #define documentd_checkers_chat_NOTNEED #include "proto/documentd_checkers.protoch" struct CHECKER_PLAYER{ unsigned col=11; unsigned line=11; bool onemove=false; // The player has done one move over another player coin // and may stop there or not. std::string name; void reset(){ onemove = false; col = line = 11; } bool has_selected(){ // Has the player select the piece he wants to move return col < 10 && line < 10; } }; class CHECKERS: public GAME{ unsigned nbcol=8; static const unsigned MAX_GRID_SIZE = 10; unsigned char grid[MAX_GRID_SIZE][MAX_GRID_SIZE]; CHECKER_PLAYER player1,player2; bool player1_playing = true; std::string message; std::map sessions; // Display mode (reverse) per session void update_msg (bool to_all, PARAM_STRING msg, const char *color, std::vector &res); public: const char *getclass() const{ return "CHEC"; } void save(DOC_WRITER &w, bool save_session_info); void load(DOC_READER &r, std::string &msg); void resetgame(); void testwin(std::vector &res); void exec (const char *var, const char *val, const DOC_CONTEXT &ctx, const DOC_UI_SPECS_receive &sp, std::vector &res,std::vector &unotifies); }; GAME_P make_CHECKERS() { return make_shared(); } void CHECKERS::save(DOC_WRITER &w, bool save_session_info) { documentd_checkers_header (&w,revision,player1_playing,nbcol); PLAYER p1,p2; p1.name = player1.name; p1.line = player1.line; p1.col = player1.col; p1.onemove = player1.onemove; p2.name = player2.name; p2.line = player2.line; p2.col = player2.col; p2.onemove = player2.onemove; documentd_checkers_players(&w,p1,p2); vector cells; for (auto &g:grid) for (auto &gg:g) cells.push_back(gg); documentd_checkers_cells(&w,cells); vector schat; documentd_copychat (schat,chat); documentd_checkers_chat2(&w,schat); if (save_session_info){ for (auto &s:sessions){ documentd_checkers_session (&w,s.first,s.second); } } } void CHECKERS::load(DOC_READER &r, string &msg) { glocal msg; glocal revision; glocal nbcol; glocal player1_playing; glocal grid; glocal player1; glocal player2; glocal sessions; glocal unsigned max_grid_size = MAX_GRID_SIZE; glocal chat; chat.clear(); resetgame(); sessions.clear(); (&r); glocal.revision = revision; glocal.nbcol = nbcol; glocal.player1_playing = player1_playing; glocal.player1.name = player1.name; glocal.player1.line = player1.line; glocal.player1.col = player1.col; glocal.player1.onemove = player1.onemove; glocal.player2.name = player2.name; glocal.player2.line = player2.line; glocal.player2.col = player2.col; glocal.player2.onemove = player2.onemove; unsigned line = 0; unsigned col = 0; for (auto &c:cells){ glocal.grid[line][col] = c; col++; if (col == glocal.max_grid_size){ line++; col = 0; } } for (auto l:lines) glocal.chat.emplace_back(0,l); for (auto l:lines) glocal.chat.emplace_back(l.time,l.line); glocal.sessions[session] = reverse; glocal.msg = "Invalid format for sudoku file"; } enum CELLSTATE { NOTUSED,PLAYER1,PLAYER2,PLAYER1_KING,PLAYER2_KING,WHITE}; void CHECKERS::resetgame() { player1_playing = true; player1.reset(); player2.reset(); for (auto &g:grid) for (auto &gg:g) gg=NOTUSED; // We block the white cells for (unsigned i=0; i &res) { unsigned nbcell1=0,nbcell2=0; unsigned line = 0; for (auto &g:grid){ unsigned col = 0; for (auto &gg:g){ if (is_any_of(gg,PLAYER1,PLAYER1_KING)){ nbcell1++; }else if (is_any_of(gg,PLAYER2,PLAYER2_KING)){ nbcell2++; } col++; } line++; } if (nbcell1 == 0 || nbcell2 == 0){ update_msg (true,MSG_R(I_WON),"blue",res); } } void CHECKERS::update_msg( bool to_all, // The message will be shown to all player or not PARAM_STRING msg, const char *color, vector &res) { VARVAL mvar; mvar.var = to_all ? VAR_NOTIFY : VAR_SCRIPT; js_find_set (mvar.val,"msg","style.color",color,"innerHTML",documentd_escape(msg).c_str()); res.emplace_back(mvar); } static void print_player(string &lines, PARAM_STRING gameid, PARAM_STRING name, unsigned playernum, const char *player_color, bool playing, bool mobile) { const char *color = playing ? "lightblue" : "white"; unsigned margin = 5; unsigned width=150; if (mobile){ width = 250; margin = 10; } lines += string_f("
\n" ,playernum,gameid.ptr,playernum,width,color,player_color,margin,margin,margin); lines += string_f("%s\n",name.ptr); lines += "
\n"; } void CHECKERS::exec ( const char *var, const char *val, const DOC_CONTEXT &ctx, const DOC_UI_SPECS_receive &sp, vector &res, std::vector &unotifies) { string error; setactivity(); VARVAL notify_var; notify_var.var = VAR_NOTIFY; if (ctx.maywrite){ if (player1.name.size() == 0){ player1.name = ctx.username; js_find_set(notify_var.val,"player0","innerHTML",ctx.username); }else if (player2.name.size() == 0){ player2.name = ctx.username; js_find_set(notify_var.val,"player1","innerHTML",ctx.username); } } bool reverse = sessions[ctx.session]; if (strcmp(var,"print")==0){ string lines; if (strcmp(val,"console")==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 *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'; }else{ // tlmp_warning ("sp w=%u h=%u cw=%u ch=%u",sp.width,sp.height,sp.content_width,sp.content_height); // The layout is a square, constrained by the width unsigned reverse_width = sp.mobile ? 45 : 25; unsigned reverse_margin = 25; unsigned waiting_users_width = sp.mobile ? 62 : 42; unsigned limited_width = sp.content_width - (reverse_width+reverse_margin+waiting_users_width); unsigned dim = limited_width < sp.content_height ? limited_width : sp.content_height; unsigned width = dim - dim % nbcol; unsigned w8 = width/(nbcol); unsigned h8 = w8; unsigned grid_width = w8*nbcol; unsigned grid_height = w8*nbcol; lines += "\n"; lines += "
\n"; lines += "
\n"; documentd_button_start(lines,gameid); if (!sp.mobile) documentd_button_label(lines,MSG_R(I_NEWGAME)); documentd_button (lines,0,MSG_U(I_8_8,"8 X 8"),nbcol==8); documentd_button (lines,1,MSG_U(I_10_10,"10 X 10"),nbcol==10); documentd_button_end(lines); lines += "
\n"; print_player(lines,gameid,player1.name,0,"black",player1_playing,sp.mobile); print_player(lines,gameid,player2.name,1,"red",!player1_playing,sp.mobile); lines += "
\n"; lines += string_f("
\n",val); lines += string_f("
\n",reverse_margin); lines += string_f("\n",reverse_width,grid_height,reverse_width,grid_height); lines += string_f("\n" ,reverse_width,grid_height); { // Draw a vertical arrow unsigned h1 = reverse_width/2; unsigned v1 = grid_height/4; unsigned vlen = grid_height/2; lines += string_f("\n" ,h1,v1,vlen); lines += string_f("\n" ,h1-5,v1+5,h1,v1,h1+5,v1+5); lines += string_f("\n" ,h1-5,v1+vlen-5,h1,v1+vlen,h1+5,v1+vlen-5); } lines += "\n"; lines += "
\n"; lines += "
\n"; lines += string_f("\n" ,gameid.c_str(),grid_width,grid_height,grid_width,grid_height); unsigned w8_2 = w8/2; unsigned h8_2 = h8/2; unsigned circle_radius = h8_2 - h8/10; for (unsigned vline=0; vline\n" ,w8*col,h8*vline,w8,h8,w8); } unsigned cell = grid[line][col]; const char *fill_color = "none"; const char *stroke_color = "none"; const char *select_color = "none"; const char *king_color = "none"; if (is_any_of(cell,PLAYER1,PLAYER2,PLAYER1_KING,PLAYER2_KING)){ if (is_any_of(cell,PLAYER1,PLAYER1_KING)){ fill_color = "black"; if (cell == PLAYER1_KING) king_color = "gold"; }else{ fill_color = "red"; if (cell == PLAYER2_KING) king_color = "gold"; } stroke_color = "black"; if (player1.col == col && player1.line == line){ select_color = "blue"; }else if (player2.col == col && player2.line == line){ select_color = "blue"; } } lines += string_f("\n" ,line,col,w8_col+w8_2,h8_line+h8_2,circle_radius,stroke_color,fill_color); lines += string_f("\n" ,line,col,w8_col+w8_2,h8_line+h8_2,circle_radius-2,king_color,king_color); lines += string_f("\n" ,line,col,w8_col+w8_2,h8_line+h8_2,circle_radius-8,fill_color,fill_color); lines += string_f("\n" ,line,col,w8_col+w8_2,h8_line+h8_2,circle_radius-18,select_color,select_color); } } lines += string_f("\n" ,1,1,grid_width-1,grid_height-1,1); for (unsigned vline=0; vline\n",linecolor ,0,h8*vline,grid_width); for (unsigned col=0; col\n",linecolor ,w8*col,0,grid_height); } } lines += "\n"; lines += "
\n"; draw_waiting_users(lines,waiting_users_width,grid_height,"flex:0 0 auto;"); lines += "
\n"; lines += "
\n"; lines += string_f("
 
\n",gameid.c_str()); documentd_chat (lines,ctx.username,sp.mobile,chat,sp.width-20,sp.mobile ? 200 : 100); lines += "
\n"; } VARVAL v; v.var = VAR_CONTENT; v.val = lines; res.push_back(v); }else if (strcmp(var,"reverse")==0){ sessions[ctx.session] = !reverse; documentd_forcerefresh(res); }else if (strcmp(var,REQ_CHAT)==0){ appendchat(val,notify_var.val,res,ctx); }else if (ctx.maywrite){ if (strcmp(var,"place")==0){ unsigned lo,co,button; if (splitline(val,',',limits(lo,0u,nbcol-1),limits(co,0u,nbcol-1),enums(button,{1u,2u}))){ if (reverse) lo = nbcol -1 - lo; CHECKER_PLAYER *player = &player2; unsigned cell_val = PLAYER2; unsigned cell_val_king = PLAYER2_KING; unsigned other_player = PLAYER1; unsigned other_player_king = PLAYER1_KING; unsigned king_line = 0; // Line to reach to turn a coin into a king int vmove = -1; if (player1_playing){ player = &player1; cell_val = PLAYER1; cell_val_king = PLAYER1_KING; other_player = PLAYER2; other_player_king = PLAYER2_KING; king_line = nbcol-1; vmove = 1; } if (player->has_selected()){ if (player->line == lo && player->col == co){ // The player is unselecting the cell if (player->onemove){ player1_playing = !player1_playing; js_find_set(notify_var.val,"player0","style.backgroundColor",player1_playing ? "lightblue" : "white"); js_find_set(notify_var.val,"player1","style.backgroundColor",!player1_playing ? "lightblue" : "white"); } string id = string_f("cs%u,%u",lo,co); js_find_loop_set (notify_var.val,"grid","circle",id,"style.fill","none","style.stroke","none"); player->reset(); }else{ int left_col = player->col - 1; int right_col = player->col + 1; int next_line = player->line + vmove; int left_col2 = player->col - 2; int right_col2 = player->col + 2; int next_line2 = player->line + vmove + vmove; // for king moves int prev_line = player->line - vmove; int prev_line2 = prev_line - vmove; unsigned char &cell = grid[lo][co]; unsigned char sel_cell = grid[player->line][player->col]; bool move_ok = false; bool keep_playing = false; // tlmp_warning ("sel_cell=%u cell=%u lo=%u co=%u prev_line=%u",sel_cell,cell,lo,co,prev_line); if (cell != 0){ if (cell == WHITE){ error = MSG_U(E_CANTGOTHERE,"You are not allowed to move to white cell"); }else if (is_any_of(cell,cell_val,cell_val_king)){ error = MSG_U(E_NOJUMPONYOU,"You can't stack coins"); }else{ error = MSG_U(E_NOJUMPONOTHER,"You can't jump on the other player coin"); } }else if (!player->onemove && (int)lo == next_line && is_any_of((int)co,left_col,right_col)){ // Move the an available next cell move_ok = true; }else if (!player->onemove && is_any_of(sel_cell,PLAYER1_KING,PLAYER2_KING) && (int)lo == prev_line && is_any_of((int)co,left_col,right_col)){ // Move the an available next cell move_ok = true; }else if ((int)lo == next_line2 || ((int)lo == prev_line2 && is_any_of(sel_cell,PLAYER1_KING,PLAYER2_KING))){ // Move over a player coin int inter_line = (int)lo==next_line2 ? next_line : prev_line; unsigned erase_col = 0; if ((int)co == left_col2 && is_any_of(grid[inter_line][left_col],other_player,other_player_king)){ move_ok = true; grid[inter_line][left_col] = NOTUSED; erase_col = left_col; }else if ((int)co == right_col2 && is_any_of(grid[inter_line][right_col],other_player,other_player_king)){ move_ok = true; grid[inter_line][right_col] = NOTUSED; erase_col = right_col; } if (move_ok){ // && (sp.mobile || button == 2)){ // Ok, we jumped over a coin, it has to be erased for (auto c:{"c","ck","cc","cs"}){ string id = string_f("%s%u,%u",c,inter_line,erase_col); js_find_loop_set (notify_var.val,"grid","circle",id,"style.fill","none","style.stroke","none"); } keep_playing = true; } } if (move_ok){ // Erase last selected position for (auto c:{"c","ck","cc","cs"}){ string id = string_f("%s%u,%u",c,player->line,player->col); js_find_loop_set (notify_var.val,"grid","circle",id,"style.fill","none","style.stroke","none"); } // Draw the new position const char *kcolor = is_any_of(sel_cell,PLAYER1_KING,PLAYER2_KING) ? "gold" : "none"; const char *color = player1_playing ? "black" : "red"; string id = string_f("c%u,%u",lo,co); js_find_loop_set (notify_var.val,"grid","circle",id,"style.fill",color,"style.stroke","black"); id = string_f("ck%u,%u",lo,co); js_find_loop_set (notify_var.val,"grid","circle",id,"style.fill",kcolor,"style.stroke",kcolor); id = string_f("cc%u,%u",lo,co); js_find_loop_set (notify_var.val,"grid","circle",id,"style.fill",color,"style.stroke",color); grid[player->line][player->col] = NOTUSED; cell = sel_cell; if (lo == king_line){ if (cell == PLAYER1){ cell = PLAYER1_KING; }else if (cell == PLAYER2){ cell = PLAYER2_KING; } string id = string_f("ck%u,%u",lo,co); js_find_loop_set (notify_var.val,"grid","circle",id,"style.fill","gold","style.stroke","gold"); } setmodified (ctx.username); documentd_setchanges(res); if (keep_playing){ player->line = lo; player->col = co; player->onemove = true; id = string_f("cs%u,%u",lo,co); const char *color = player1_playing ? "blue" : "blue"; js_find_loop_set (notify_var.val,"grid","circle",id,"style.fill",color,"style.stroke",color); }else{ player->reset(); player1_playing = !player1_playing; js_find_set(notify_var.val,"player0","style.backgroundColor",player1_playing ? "lightblue" : "white"); js_find_set(notify_var.val,"player1","style.backgroundColor",!player1_playing ? "lightblue" : "white"); } }else if (error.size() == 0){ error = MSG_U(E_IVLDMOVE,"Invalid move"); } } }else if (is_any_of(grid[lo][co],cell_val,cell_val_king)){ // The player is about to move that piece player->col = co; player->line = lo; const char *color = player1_playing ? "blue" : "blue"; string id = string_f("cs%u,%u",lo,co); js_find_loop_set (notify_var.val,"grid","circle",id,"style.fill",color,"style.stroke",color); }else{ documentd_error (res,"Can't select this cell"); } }else{ tlmp_error ("checkers, invalid place command: %s",val); } }else if (strcmp(var,"newgame")==0){ int uval = atoi(val); if (is_any_of(uval,0,1)){ nbcol = uval == 0 ? 8 : 10; resetgame(); js_find_loop_start (notify_var.val,"grid","circle"); unsigned item = 0; for (unsigned i=0; iname = ctx.username; js_find_set(notify_var.val,"player0","innerHTML",player1.name.c_str()); js_find_set(notify_var.val,"player1","innerHTML",player2.name.c_str()); } }else{ error = MSG_R(E_READONLY); } if (notify_var.val.size() > 0) res.emplace_back(notify_var); if (error.size() > 0){ update_msg(false,error,"red",res); }else{ update_msg(false," ","white",res); } }