/* 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 #include #include "bolixo.h" #include "proto/bod_client.protodef" #include "documentd.h" #include "bolixo.m" using namespace std; struct CHESS_PLAYER{ bool robot = false; unsigned col=11; unsigned line=11; std::string name; bool king_moved = false; bool left_rook_moved = false; bool right_rook_moved = false; struct{ unsigned line=10; unsigned col=10; }lastmove; struct{ unsigned line=10; unsigned col=10; }en_passant; void reset(){ king_moved = left_rook_moved = right_rook_moved = false; reset_sel(); reset_en_passant(); reset_lastmove(); } void reset_sel(){ col = line = 11; } void reset_en_passant(){ en_passant.line = en_passant.col = 10; } bool has_en_passant() const { return en_passant.line != 10; } bool has_lastmove() const { return lastmove.line != 10; } void reset_lastmove(){ lastmove.line = lastmove.col = 10; } bool has_selected(){ // Has the player select the piece he wants to move return col < 8 && line < 8; } std::string dump() const; bool is_robot() const { return robot; } }; class CHESSMOVE_EFFECTS; struct CHESS_COOR{ unsigned line; unsigned col; CHESS_COOR(unsigned _line, unsigned _col){ line = _line; col = _col; } }; enum CHESS_UNDO_TYPE { CHESS_UNDO_MOVE, CHESS_UNDO_KING_MOVED, CHESS_UNDO_LEFT_ROOK_MOVED, CHESS_UNDO_RIGHT_ROOK_MOVED, CHESS_UNDO_EN_PASSANT,CHESS_UNDO_LASTMOVE }; struct CHESS_UNDO{ bool player1_playing = false; unsigned line=10; unsigned col=10; char cell=' '; CHESS_UNDO_TYPE type; }; enum ROBOT_TYPE{ ROBOT_NONE, ROBOT_GNUCHESS, ROBOT_STOCKFISH}; class CHESS: public GAME{ std::string gamename; // User selected name for the current game std::string timer; // Timer specification ROBOT_TYPE robot_type = ROBOT_NONE; char grid[8][8]; CHESS_PLAYER player1,player2; bool player1_playing = true; std::string message; std::vector undos; std::vector marked_pieces; unsigned robotskill=0; std::map sessions; // Display mode (reverse) per session bool checkmove (CHESS_PLAYER *player, unsigned to_line, unsigned to_col, CHESS_PLAYER *other_player, CHESSMOVE_EFFECTS &, std::string &error); void save_lastmove (CHESS_PLAYER *player); void execmove (CHESS_PLAYER *player, CHESS_PLAYER *other_player, unsigned to_line, unsigned to_col, const CHESSMOVE_EFFECTS &); bool check_expose(unsigned line, unsigned col, bool king_is_white, std::vector &pieces); bool check_expose(bool king_is_white, std::vector &pieces); void undoone(VARVAL ¬ify_var); void show_marked_pieces (VARVAL ¬ify_var, const char *color); std::string format_fen(); void robot_request (std::vector &res); void update_players(std::string ¬ify); void appendmove2chat(const char *username,bool is_human,char oldcell, char newcell, PARAM_STRING chessmove, std::string ¬ify); std::string define_styles(bool mobile); std::string define_functions(const DOC_CONTEXT &ctx, bool reverse, bool mobile); std::string draw_board (bool reverse, bool mobile, unsigned docnum, bool editmode, std::string &script); void refreshboard(VARVAL ¬ify_var); public: CHESS(); const char *getclass() const{ return CLASS_CHESS; } 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); void engine_reply(const char *line, std::string ¬ify, bool &done); }; GAME_P make_CHESS() { return make_shared(); } #include "proto/documentd_chess.protoh" #define documentd_chess_players_NOTNEED #define documentd_chess_chat_NOTNEED #define documentd_chess_gameinfo_NOTNEED #include "proto/documentd_chess.protoch" static unsigned maxskill=3; // Max skill level offered to users void chess_setmaxskill (unsigned _maxskill) { if (_maxskill > 0 && _maxskill <= 5) maxskill = _maxskill; } unsigned chess_getmaxskill() { return maxskill; } inline bool chs_is_king(char cell){ return is_any_of(cell,'k','K'); } inline bool chs_is_queen(char cell){ return is_any_of(cell,'q','Q'); } inline bool chs_is_rook(char cell){ return is_any_of(cell,'r','R'); } inline bool chs_is_knight(char cell){ return is_any_of(cell,'n','N'); } inline bool chs_is_bishop(char cell){ return is_any_of(cell,'b','B'); } inline bool chs_is_pawn(char cell){ return is_any_of(cell,'p','P'); } inline bool chs_is_black(char cell){ return islower(cell); } inline bool chs_is_white(char cell){ return isupper(cell); } struct CHESSMOVE_EFFECTS{ bool king_moved = false; bool left_rook_moved = false; bool right_rook_moved = false; // The following two field are there to support the "en passant" move. bool pawn_moved_2steps = false; // A pawn has done its first move, moving 2 cell at once bool clear_en_passant = false; // Clear from the grid the other player en_passant pawn bool reset_en_passant = false; // Forget the en passant setting from the other player bool pawn_queen = false; // The pawn must be turned into a queen bool left_castling = false; bool right_castling = false; string dump() const { string ret=string_f("king_moved=%d left_rook_moved=%d right_rook_moved=%d pawn_moved_2steps=%d clear_en_passant=%d pawn_queen=%d" ,king_moved,left_rook_moved,right_rook_moved,pawn_moved_2steps,clear_en_passant,pawn_queen); return ret; } }; string CHESS_PLAYER::dump() const { string ret=string_f("king_moved=%d left_rook_moved=%d right_rook_moved=%d en_passant.line=%u en_passant.col=%u" ,king_moved,left_rook_moved,right_rook_moved,en_passant.line,en_passant.col); return ret; } CHESS::CHESS() { resetgame(); robot_type = ROBOT_NONE; if (file_type("/usr/bin/stockfish")!=-1){ robot_type = ROBOT_STOCKFISH; }else if (file_type("/usr/bin/gnuchess")!=-1){ robot_type = ROBOT_GNUCHESS; } } /* Check if a move is valid. Return true if the move is valid. */ bool CHESS::checkmove( CHESS_PLAYER *player, // Current player. CHeck if a move from the selected cell on the board unsigned to_line, // is valid to this cell on the board unsigned to_col, CHESS_PLAYER *other_player, CHESSMOVE_EFFECTS &effects, // Note side effects of the move string &error) // Will contain any errors. { unsigned from_line = player->line; // CHeck if a move from this cell on the board unsigned from_col = player->col; bool ret = false; bool valid = true; bool vertical = false; bool horizontal = false; bool diagonal = false; int distance = 0; char from_cell = grid[from_line][from_col]; char to_cell = grid[to_line][to_col]; // First we qualify the move and after, we see if it fits the piece if (from_line == to_line){ horizontal = true; distance = to_col-from_col; if (to_col > from_col){ for (unsigned col=from_col+1; col < to_col; col++){ if (grid[from_line][col] != ' '){ valid = false; break; } } }else{ for (unsigned col=from_col-1; col > to_col; col--){ if (grid[from_line][col] != ' '){ valid = false; break; } } } }else if (from_col == to_col){ vertical = true; distance = to_line-from_line; if (to_line > from_line){ for (unsigned line=from_line+1; line < to_line; line++){ if (grid[line][from_col] != ' '){ valid = false; break; } } }else{ for (unsigned line=from_line-1; line > to_line; line--){ if (grid[line][from_col] != ' '){ valid = false; break; } } } }else if (abs((int)to_line-(int)from_line) == abs((int)to_col-(int)from_col)){ diagonal = true; distance = to_line - from_line; int hmove = 1, vmove = 1; if (from_line > to_line) vmove = -1; if (from_col > to_col) hmove = -1; unsigned line = from_line + vmove; unsigned col = from_col + hmove; while (line != to_line && col != to_col){ if (grid[line][col] != ' '){ valid = false; break; } line += vmove; col += hmove; } } if (valid && to_cell != ' '){ if (isupper(from_cell) == isupper(to_cell)){ error = MSG_U(E_CANTTAKEYOURPIECE,"Can't move on your piece"); valid = false; } } if (valid){ if (chs_is_pawn(from_cell)){ if (vertical && to_cell == ' '){ if (chs_is_black(from_cell)){ if (distance == 1){ if (to_line == 7) effects.pawn_queen = true; ret = true; }else if (distance == 2 && from_line == 1){ effects.pawn_moved_2steps = true; ret = true; } }else{ if (distance == -1){ if (to_line == 0) effects.pawn_queen = true; ret = true; }else if (distance == -2 && from_line == 6){ effects.pawn_moved_2steps = true; ret = true; } } }else if (diagonal){ if (chs_is_black(from_cell)){ if (distance == 1){ if (chs_is_white(to_cell)){ ret = true; }else if (other_player->en_passant.line == from_line && other_player->en_passant.col == to_col){ effects.clear_en_passant = true; ret = true; } } }else{ if (distance == -1){ if (chs_is_black(to_cell)){ ret = true; }else if (other_player->en_passant.line == from_line && other_player->en_passant.col == to_col){ effects.clear_en_passant = true; ret = true; } } } } }else if (chs_is_rook(from_cell)){ if (horizontal || vertical){ if (from_line == 0 || from_line == 7){ if (from_col == 0){ effects.left_rook_moved = true; }else if (from_col == 7){ effects.right_rook_moved = true; } } ret = true; } }else if (chs_is_bishop(from_cell)){ if (diagonal) ret = true; // tlmp_warning ("bishop %d %d %d ret=%d",horizontal,vertical,diagonal,ret); }else if (chs_is_queen(from_cell)){ if (horizontal || vertical || diagonal) ret = true; }else if (chs_is_king(from_cell)){ if ((distance == 1 || distance == -1) && (horizontal || vertical || diagonal)){ effects.king_moved = true; ret = true; }else if (horizontal && !player->king_moved){ if (distance == -2 && !player->left_rook_moved && grid[from_line][1] == ' ' && grid[from_line][2] == ' ' && grid[from_line][3] == ' '){ effects.king_moved = true; effects.left_rook_moved = true; effects.left_castling = true; ret = true; }else if (distance == 2 && !player->right_rook_moved && grid[from_line][5] == ' ' && grid[from_line][6] == ' '){ effects.king_moved = true; effects.right_rook_moved = true; effects.right_castling = true; ret = true; } } }else if (chs_is_knight(from_cell)){ // Compute all the possible moves (including one outside the board) unsigned tb[][2]={ {from_line+2,from_col-1}, {from_line+2,from_col+1}, {from_line+1,from_col-2}, {from_line+1,from_col+2}, {from_line-2,from_col-1}, {from_line-2,from_col+1}, {from_line-1,from_col-2}, {from_line-1,from_col+2} }; for (auto &t:tb){ if (t[0] == to_line && t[1] == to_col){ ret = true; break; } } } } if (ret){ if (other_player->has_en_passant()){ effects.reset_en_passant = true; } }else if (error.size() == 0){ error = MSG_R(E_IVLDMOVE); } return ret; } /* Record the current lastmove in the undos This has to be called before execmove() is called and before any other side effects are stored in the undo table. */ void CHESS::save_lastmove (CHESS_PLAYER *player) { if (player->has_lastmove()){ CHESS_UNDO undo; undo.player1_playing = player1_playing; undo.line = player->lastmove.line; undo.col = player->lastmove.col; undo.cell = ' '; undo.type = CHESS_UNDO_LASTMOVE; undos.push_back(undo); } } /* Complete a move */ void CHESS::execmove (CHESS_PLAYER *player, CHESS_PLAYER *other_player, unsigned to_line, unsigned to_col, const CHESSMOVE_EFFECTS &effects) { char &old_cell = grid[player->line][player->col]; char &new_cell = grid[to_line][to_col]; CHESS_UNDO undo; undo.player1_playing = player1_playing; undo.line = player->line; undo.col = player->col; undo.cell = old_cell; undo.type = CHESS_UNDO_MOVE; undos.push_back(undo); undo.line = to_line; undo.col = to_col; undo.cell = new_cell; undos.push_back(undo); if (effects.pawn_queen){ new_cell = isupper(old_cell) ? 'Q' : 'q'; }else{ new_cell = old_cell; } old_cell = ' '; if (effects.pawn_moved_2steps){ player->en_passant.line = to_line; player->en_passant.col = to_col; } if (effects.reset_en_passant){ undo.line = other_player->en_passant.line; undo.col = other_player->en_passant.col; undo.type = CHESS_UNDO_EN_PASSANT; undos.push_back(undo); } if (effects.king_moved){ if (!player->king_moved){ player->king_moved = true; undo.type = CHESS_UNDO_KING_MOVED; undos.push_back(undo); } } if (effects.left_rook_moved){ if (!player->left_rook_moved){ player->left_rook_moved = true; undo.type = CHESS_UNDO_LEFT_ROOK_MOVED; undos.push_back(undo); } } if (effects.right_rook_moved){ if (!player->right_rook_moved){ player->right_rook_moved = true; undo.type = CHESS_UNDO_RIGHT_ROOK_MOVED; undos.push_back(undo); } } player1_playing = !player1_playing; player->reset_sel(); other_player->reset_en_passant(); } /* Validate if the king is currently exposed (may be taken by the opponent at the next move) line,col is the possible position of the king. Return true if there at least one opponent which may attack the king */ bool CHESS::check_expose( unsigned line, unsigned col, bool king_is_white, vector &pieces) // Will contain all the coordinates of the pieces // which may touch the king { pieces.clear(); unsigned old_line=0,old_col=0; // Original position of the king char king = king_is_white ? 'K' : 'k'; char cur_cell = grid[line][col]; if (cur_cell == king){ old_line = line; old_col = col; }else{ // Note the current position of the king in old_line,old_col and erase the king // Then we place the kind at position line,col bool found = false; for (unsigned i=0; i<8; i++){ for (unsigned j=0; !found && j<8; j++){ if (grid[i][j] == king){ old_line = i; old_col = j; grid[i][j] = ' '; found = true; break; } } } grid[line][col] = king; } CHESS_PLAYER *player = &player1; CHESS_PLAYER *other_player = &player2; if (king_is_white){ player = &player2; other_player = &player1; } unsigned old_player_line = player->line; unsigned old_player_col = player->col; for (unsigned i=0; i<8; i++){ for (unsigned j=0; j<8; j++){ char cell = grid[i][j]; if ((king_is_white && islower(cell)) || isupper(cell)){ CHESSMOVE_EFFECTS effects; player->line = i; player->col = j; string error; if (checkmove(player,line,col,other_player,effects,error)){ pieces.emplace_back(i,j); } } } } if (old_line != line || old_col != col){ grid[old_line][old_col] = king; grid[line][col] = cur_cell; } player->line = old_player_line; player->col = old_player_col; return pieces.size() > 0; } /* Find the current position of the king and check if it is exposed */ bool CHESS::check_expose( bool king_is_white, vector &pieces) // Will contain all the coordinates of the pieces // which may touch the king { bool ret = false; char king = king_is_white ? 'K' : 'k'; bool found = false; for (unsigned i=0; !found && i<8; i++){ for (unsigned j=0; j<8; j++){ if (grid[i][j] == king){ ret = check_expose (i,j,king_is_white,pieces); found = true; break; } } } return ret; } static void copy_player (const CHESS_PLAYER &player, CHESS_FILE_PLAYER2 &fplayer) { fplayer.name = player.name; fplayer.line = player.line; fplayer.col = player.col; fplayer.king_moved = player.king_moved; fplayer.left_rook_moved = player.left_rook_moved; fplayer.right_rook_moved = player.right_rook_moved; fplayer.en_passant_line = player.en_passant.line; fplayer.en_passant_col = player.en_passant.col; fplayer.lastmove_line = player.lastmove.line; fplayer.lastmove_col = player.lastmove.col; } static void copy_player (const CHESS_FILE_PLAYER_receive &fplayer, CHESS_PLAYER &player) { player.name = fplayer.name; player.line = fplayer.line; player.col = fplayer.col; player.king_moved = fplayer.king_moved; player.left_rook_moved = fplayer.left_rook_moved; player.right_rook_moved = fplayer.right_rook_moved; player.en_passant.line = fplayer.en_passant_line; player.en_passant.col = fplayer.en_passant_col; } static void copy_player (const CHESS_FILE_PLAYER2_receive &fplayer, CHESS_PLAYER &player) { player.name = fplayer.name; player.robot = player.name == CHESS_ROBOT; player.line = fplayer.line; player.col = fplayer.col; player.king_moved = fplayer.king_moved; player.left_rook_moved = fplayer.left_rook_moved; player.right_rook_moved = fplayer.right_rook_moved; player.en_passant.line = fplayer.en_passant_line; player.en_passant.col = fplayer.en_passant_col; player.lastmove.line = fplayer.lastmove_line; player.lastmove.col = fplayer.lastmove_col; } void CHESS::save(DOC_WRITER &w, bool save_session_info) { documentd_chess_header (&w,revision,player1_playing); CHESS_FILE_PLAYER2 p1,p2; copy_player (player1,p1); copy_player (player2,p2); documentd_chess_players2(&w,p1,p2); vector cells; for (auto &g:grid) for (auto &gg:g) cells.push_back(gg); documentd_chess_cells(&w,cells); for (auto &s:sessions){ documentd_chess_session (&w,s.first,s.second); } vector uns; for (auto &u:undos){ CHESS_FILE_UNDO un; un.p1_playing = u.player1_playing; un.line = u.line; un.col = u.col; un.cell = u.cell; un.type = u.type; uns.push_back(un); } documentd_chess_undo(&w,uns); vector schat; documentd_copychat (schat,chat); documentd_chess_chat2(&w,schat); documentd_chess_gameinfo2(&w,gamename,timer,robotskill); } void CHESS::load(DOC_READER &r, string &msg) { glocal msg; glocal revision; glocal player1_playing; glocal grid; glocal player1; glocal player2; glocal sessions; glocal undos; glocal chat; glocal gamename; glocal timer; glocal robotskill; chat.clear(); resetgame(); sessions.clear(); (&r); glocal.revision = revision; glocal.player1_playing = player1_playing; copy_player (player1,glocal.player1); copy_player (player2,glocal.player2); copy_player (player1,glocal.player1); copy_player (player2,glocal.player2); unsigned line = 0; unsigned col = 0; for (auto &c:cells){ glocal.grid[line][col] = (char)c; col++; if (col == 8){ line++; col = 0; } } glocal.sessions[session] = reverse; for (auto &u:undos){ CHESS_UNDO undo; undo.player1_playing = u.p1_playing; undo.line = u.line; undo.col = u.col; undo.cell = u.cell; undo.type = u.type; glocal.undos.push_back(undo); } for (auto l:lines) glocal.chat.emplace_back(0,l); for (auto l:lines) glocal.chat.emplace_back(l.time,l.line); glocal.gamename = gamename; glocal.timer = timer; glocal.robotskill = 0; glocal.gamename = gamename; glocal.timer = timer; glocal.robotskill = robotskill; glocal.msg = "Invalid format for sudoku file"; } void CHESS::resetgame() { player1_playing = true; player1.reset(); player2.reset(); undos.clear(); marked_pieces.clear(); for (auto &g:grid) for (auto &gg:g) gg=' '; for (unsigned i=0; i<8; i++){ const char *lowerp = "rnbqkbnr"; const char *upperp = "RNBQKBNR"; grid[0][i] = lowerp[i]; grid[1][i] = 'p'; grid[6][i] = 'P'; grid[7][i] = upperp[i]; } } struct CELL_SPEC{ const char *utf8; const char *stroke_color; const char *fill_color; }; /* We convert a cell letter into HTML and we include the proper stroke and fill color. We use a trick. On the desktop, we use the HTML corresponding to the lower case piece and play with the stroke and fill color. This looks much nicer. On mobile (Android at least), a different font is used and the trick fails. So on mobile, we use both lower and upper case letter. We do the same trick in the javascript function showpiece() below. */ static void cnv_cell_html (char cell, CELL_SPEC &spec, bool mobile) { spec.utf8 = " "; if (isupper(cell)){ spec.fill_color = "white"; spec.stroke_color = "black"; }else if (islower(cell)){ spec.fill_color = "black"; spec.stroke_color = "white"; }else{ spec.fill_color = "none"; spec.stroke_color = "none"; } if (!mobile){ cell = tolower (cell); // We are using the same chars, but using a different fill color. Looks nicer } if (cell == 'K'){ spec.utf8 = "♔"; }else if (cell == 'Q'){ spec.utf8 = "♕"; }else if (cell == 'R'){ spec.utf8 = "♖"; }else if (cell == 'B'){ spec.utf8 = "♗"; }else if (cell == 'N'){ spec.utf8 = "♘"; }else if (cell == 'P'){ spec.utf8 = "♙"; }else if (cell == 'k'){ spec.utf8 = "♚"; }else if (cell == 'q'){ spec.utf8 = "♛"; }else if (cell == 'r'){ spec.utf8 = "♜"; }else if (cell == 'b'){ spec.utf8 = "♝"; }else if (cell == 'n'){ spec.utf8 = "♞"; }else if (cell == 'p'){ spec.utf8 = "♟"; } } inline void update_cell (VARVAL &var, PARAM_STRING id, char cell) { var.val += string_f("findupdpiece('%s','%c');\n",id.ptr,cell); } inline void update_circle (VARVAL &var, PARAM_STRING id, const char *fill, const char *stroke) { var.val += string_f("findupdcircle('%s','%s','%s');\n",id.ptr,fill,stroke); } inline void update_player_bg (VARVAL &var, unsigned player, const char *color) { var.val += string_f("updplayerbg('%u','%s');\n",player,color); } /* Undo one step */ void CHESS::undoone(VARVAL ¬ify_var) { if (undos.size() > 0){ size_t last = undos.size() -1; bool last_playing = undos[last].player1_playing; if (player1.has_selected()){ string sel_id = string_f("cs%u,%u",player1.line,player1.col); update_circle(notify_var,sel_id,"none","none"); player1.reset_sel(); }else if (player2.has_selected()){ string sel_id = string_f("cs%u,%u",player2.line,player2.col); update_circle(notify_var,sel_id,"none","none"); player2.reset_sel(); } while (undos.size() > 0){ last = undos.size() -1; CHESS_UNDO &u = undos[last]; if (u.player1_playing != last_playing){ break; }else{ CHESS_PLAYER *player = &player1; CHESS_PLAYER *other_player = &player2; if (!last_playing){ player = &player2; other_player = &player1; } if (u.type == CHESS_UNDO_MOVE){ if (player->has_lastmove()){ string last_id = string_f("cl%u,%u",player->lastmove.line,player->lastmove.col); update_circle(notify_var,last_id,"none","none"); player->reset_lastmove(); } string text_id = string_f("c%u,%u",u.line,u.col); update_cell(notify_var,text_id,u.cell); grid[u.line][u.col] = u.cell; }else if (u.type == CHESS_UNDO_LASTMOVE){ // tlmp_warning ("undo lastmove %u %u",u.line,u.col); player->lastmove.line = u.line; player->lastmove.col = u.col; string last_id = string_f("cl%u,%u",u.line,u.col); update_circle(notify_var,last_id,"none","black"); }else if (u.type == CHESS_UNDO_KING_MOVED){ player->king_moved = false; }else if (u.type == CHESS_UNDO_LEFT_ROOK_MOVED){ player->left_rook_moved = false; }else if (u.type == CHESS_UNDO_RIGHT_ROOK_MOVED){ player->right_rook_moved = false; }else if (u.type >= CHESS_UNDO_EN_PASSANT){ other_player->en_passant.line = u.line; other_player->en_passant.col = u.col; } undos.erase(undos.begin()+last); } } player1_playing = !player1_playing; update_player_bg (notify_var,0,player1_playing ? "lightblue" : "white"); update_player_bg (notify_var,1,!player1_playing ? "lightblue" : "white"); } } void CHESS::testwin(vector &res) { // update_msg (true,MSG_R(I_WON),"blue",res); } const unsigned BOX_PLAYER_WIDTH=150; const unsigned BOX_PLAYER_HEIGHT=20; const unsigned BOX_PLAYER_WIDTH_MOBILE=300; const unsigned BOX_PLAYER_HEIGHT_MOBILE=40; static const char *svg_robot = "" "" "" "" "" "" "" "" ""; static const char *svg_robot_mobile = "" "" "" "" "" "" "" "" ""; static void print_player(string &lines, PARAM_STRING gameid, PARAM_STRING name, unsigned playernum, bool playing, bool mobile) { const char *color = playing ? "lightblue" : "white"; unsigned margin = 5; unsigned width=BOX_PLAYER_WIDTH; unsigned height=BOX_PLAYER_HEIGHT; const char *svg = svg_robot; if (mobile){ width = BOX_PLAYER_WIDTH_MOBILE; height=BOX_PLAYER_HEIGHT_MOBILE; svg = svg_robot_mobile; margin = 10; } lines += string_f("
\n" ,playernum,playernum,width,margin,margin,margin); const char *player_color = "white"; const char *bgcolor = "#f0dab5"; const char *msg = MSG_U(I_WHITE,"WHITES"); if (playernum == 1){ player_color = "black"; msg = MSG_U(I_BLACK,"BLACKS"); } lines += string_f("
\n",bgcolor); lines += string_f("%s\n",player_color,msg); lines += "
\n"; lines += string_f("
\n" ,playernum,gameid.ptr,width,height,color); const char *namept = name.ptr; if(strcmp(namept,CHESS_ROBOT)==0) namept = svg; if (namept[0] == '\0') namept = " "; lines += string_f("%s\n",namept); lines += "
\n"; lines += "
\n"; } void CHESS::show_marked_pieces ( VARVAL ¬ify_var, const char *color) { for (auto &m:marked_pieces){ string id=string_f("cm%u,%u",m.line,m.col); update_circle(notify_var,id,color,color); } } string CHESS::format_fen() { string lines; for (unsigned i=0; i<8; i++){ if (i > 0) lines += '/'; unsigned nbspaces=0; for (auto c:grid[i]){ if (c == ' '){ nbspaces++; }else{ if (nbspaces > 0){ lines += (char)('0'+nbspaces); nbspaces=0; } lines += c; } } if (nbspaces > 0) lines += (char)('0'+nbspaces); } // Who must play lines += ' '; lines += player1_playing ? 'w' : 'b'; lines += ' '; // castling size_t curlen = lines.size(); if (!player1.king_moved){ if (!player1.right_rook_moved) lines += 'K'; if (!player1.left_rook_moved) lines += 'Q'; } if (!player2.king_moved){ if (!player2.right_rook_moved) lines += 'k'; if (!player2.left_rook_moved) lines += 'q'; } if (lines.size()==curlen) lines += '-'; lines += ' '; if (player1_playing){ if (player1.has_en_passant()){ lines += (char)('a'+player1.en_passant.col); lines += (char)('8'-player1.en_passant.line); }else{ lines += '-'; } }else if (player2.has_en_passant()){ lines += (char)('a'+player2.en_passant.col); lines += (char)('8'-player2.en_passant.line); }else{ lines += '-'; } lines += string_f(" 0 %zu",undos.size()/2); return lines; } // If the next player is a robot, we send it some work void CHESS::robot_request(vector &res) { CHESS_PLAYER *next_player = &player2; if (player1_playing) next_player = &player1; if (next_player->is_robot()){ VARVAL robot; robot.var = VAR_ENGINE; string fen = format_fen(); if (robot_type == ROBOT_GNUCHESS){ static unsigned tbskill[]={4,8}; unsigned gnuskill = robotskill < 2 ? robotskill : 1; robot.val = string_f("position fen %s\ngo searchmoves depth %u\n" ,fen.c_str(),tbskill[gnuskill]); }else if (robot_type == ROBOT_STOCKFISH){ static unsigned char tbskill[]={0,5,10,15,20}; static unsigned char tbdepth[]={4,10,20,30,200}; //static unsigned char tbnodes[]={4,10,20,30,50}; static unsigned short tbmovetime[]={100,300,500,1000,10000}; unsigned stockskill = robotskill < maxskill ? robotskill : 4; robot.val = string_f( "setoption name Skill Level value %u\n" "position fen %s\n" "go depth %u movetime %u\n" ,tbskill[stockskill] ,fen.c_str() ,tbdepth[stockskill],tbmovetime[stockskill]); } res.push_back(robot); } } void CHESS::update_players(string ¬ify) { const char *player = player1.name == CHESS_ROBOT ? svg_robot : player1.name.c_str(); js_find_set(notify,"player0","innerHTML",player); player = player2.name == CHESS_ROBOT ? svg_robot : player2.name.c_str(); js_find_set(notify,"player1","innerHTML",player); } const unsigned SVG_CHESS_GRID_HEIGHT=1000; const unsigned SVG_CHESS_GRID_WIDTH=1000; static unsigned chess_font_base() { return 4*(SVG_CHESS_GRID_HEIGHT/8)/5; // Vertical position for the font. Not perfect. } string CHESS::define_styles(bool mobile) { const unsigned h8 = SVG_CHESS_GRID_HEIGHT/8; unsigned font_size = h8*9/10; if (mobile) font_size -= 15; string lines; lines += ".piece{\n"; lines += "\ttext-anchor:middle;\n"; lines += string_f("\tfont-size:%u;\n",font_size); lines += "\tfont-family: Times New Roman;\n"; lines += "}\n"; return lines; } string CHESS::define_functions(const DOC_CONTEXT &ctx, bool reverse, bool mobile) { const unsigned w8 = SVG_CHESS_GRID_WIDTH/8; const unsigned w8_2 = w8/2; unsigned font_base = chess_font_base(); string lines; lines = documentd_js_loop_function("grid","chs",ctx.docnum); lines += "window.chs_gameplace = function(gameid,docnum,event){\n"; lines += "\tvar elm = document.getElementById('grid-'+gameid+','+docnum);\n"; lines += "\tvar rect = elm.getBoundingClientRect();\n"; lines += "\tvar h8=(rect.bottom-rect.top)/8;\n"; lines += "\tconsole.log('h8='+h8);\n"; lines += "\tgameaction(event,'place:'+Math.floor((event.clientY-rect.top)/h8)+','+Math.floor((event.clientX-rect.left)/h8)+ ','+event.which);\n"; lines += "\tevent.stopPropagation();\n"; lines += "}\n"; lines += "window.playersel = function(event,num,ctx){\n"; lines += "\tvar which = event.which;\n"; lines += "\tif (ctx==1) which = 2;\n"; // Trick with oncontextmenu lines += "\tgameaction(event,'playersel:'+num+','+which);\n"; lines += "\tevent.stopPropagation();\n"; lines += "\treturn false;\n"; lines += "}\n"; lines += "window.gamereverse = function(event){\n"; lines += "\tgameaction(event,'reverse:');\n"; lines += "\tevent.stopPropagation();\n"; lines += "}\n"; // Convert chess piece into HTML lines += "window.tbhtml=[];\n"; lines += "tbhtml['K'] = '♔';\n"; lines += "tbhtml['Q'] = '♕';\n"; lines += "tbhtml['R'] = '♖';\n"; lines += "tbhtml['B'] = '♗';\n"; lines += "tbhtml['N'] = '♘';\n"; lines += "tbhtml['P'] = '♙';\n"; lines += "tbhtml['k'] = '♚';\n"; lines += "tbhtml['q'] = '♛';\n"; lines += "tbhtml['r'] = '♜';\n"; lines += "tbhtml['b'] = '♝';\n"; lines += "tbhtml['n'] = '♞';\n"; lines += "tbhtml['p'] = '♟';\n"; lines += "tbhtml[' '] = ' ';\n"; lines += "window.showpiece = function(elm,piece){\n"; lines += "\tvar fill_color='none';\n"; lines += "\tvar stroke_color='none';\n"; lines += "\tif (piece===piece.toUpperCase()){\n"; lines += "\t\tfill_color = 'white';\n"; lines += "\t\tstroke_color = 'black';\n"; lines += "\t}else if (piece===piece.toLowerCase()){\n"; lines += "\t\tfill_color = 'black';\n"; lines += "\t\tstroke_color = 'white';\n"; lines += "\t}\n"; if (mobile){ // See comment in cnv_ lines += "\telm.innerHTML=tbhtml[piece];\n"; }else{ lines += "\telm.innerHTML=tbhtml[piece.toLowerCase()];\n"; } lines += "\telm.style.stroke=stroke_color;\n"; lines += "\telm.style.fill=fill_color;\n"; lines += "}\n"; lines += "window.findupdpiece = function(id,piece){\n"; lines += "\tchs_loop('text',id,function(e){\n"; lines += "\t\tshowpiece(e,piece);\n"; lines += "\t});\n"; lines += "}\n"; lines += "window.findupdcircle = function(id,fill,stroke){\n"; lines += "\tchs_loop('circle',id,function(e){\n"; lines += "\t\te.style.fill=fill;\n"; lines += "\t\te.style.stroke=stroke;\n"; lines += "\t});\n"; lines += "}\n"; lines += "window.updplayerbg = function(player,color){\n"; lines += string_f("\tvar elm = document.getElementById('player'+player+'-%s');\n",gameid.c_str()); lines += "\tif (elm != null){\n"; lines += "\t\telm.style.backgroundColor=color;\n"; lines += "\t}\n"; lines += "}\n"; lines += "window.CHES_updmsg = function(color,msg){\n"; lines += string_f("\tvar elm = document.getElementById('msg-%s');\n",gameid.c_str()); lines += "\tif (elm != null){\n"; lines += "\t\telm.style.color=color;\n"; lines += "\t\telm.innerHTML=msg;\n"; lines += "\t}\n"; lines += "}\n"; // fctanimate receive a starting cell position and perform an animation on the grid // tbanim[] records animation to execute. At the end of fctanimate, the next animation // is started. lines += "window.tbanim=[];\n"; lines += "window.fctanimate = function(x0,y0,x1,y1,nostep,steps,duration,piece,fctend){\n"; if (reverse){ lines += "\tvar ly0=7-y0;\n"; lines += "\tvar ly1=7-y1;\n"; }else{ lines += "\tvar ly0=y0;\n"; lines += "\tvar ly1=y1;\n"; } lines += "\tvar txt = document.getElementById('txtanim');\n"; lines += "\tshowpiece(txt,piece);\n"; //lines += "\tconsole.log ('txt='+' nostep='+nostep+' steps='+steps+' x0='+x0+' y0='+y0+' x1='+x1+' y1='+y1+' utf8='+utf8);\n"; lines += string_f ("\tvar stepx = (x1-x0)*%u/steps;\n",w8); lines += string_f ("\tvar stepy = (ly1-ly0)*%u/steps;\n",w8); //lines += "\tconsole.log ('stepx='+stepx+' stepy='+stepy);\n"; lines += string_f("\ttxt.setAttribute('x',x0*%u+stepx*nostep+%u);\n",w8,w8_2); lines += string_f("\ttxt.setAttribute('y',ly0*%u+stepy*nostep+%u);\n",w8,font_base); lines += "\tnostep++;\n"; lines += "\tif (nostep < steps){\n"; //lines += "\t\tconsole.log ('timer='+duration/steps);\n"; lines += "\t\twindow.setTimeout(function(){\n"; lines += "\t\t\tfctanimate(x0,y0,x1,y1,nostep,steps,duration,piece,fctend);\n"; lines += "\t\t\t},duration/steps);\n"; lines += "\t}else{\n"; lines += "\t\ttxt.style.fill='none';\n"; lines += "\t\ttxt.style.stroke='none';\n"; lines += "\t\tfctend();\n"; lines += "\t\ttbanim.shift();\n"; lines += "\t\tif (tbanim.length > 0) tbanim[0]();\n"; lines += "\t}\n"; lines += "}\n"; return lines; } string CHESS::draw_board( bool reverse, bool mobile, unsigned docnum, bool editmode, // Enable onXXXX function string &script) { string lines; const unsigned grid_width = SVG_CHESS_GRID_HEIGHT; const unsigned grid_height = SVG_CHESS_GRID_HEIGHT; unsigned font_base = chess_font_base (); script = string_f( "var chs_%u={\n" "\tgameid:'%s',docnum:%u\n" "};\n" "chs_%u.gameplace=function(event){\n" "\tchs_gameplace(this.gameid,this.docnum,event);\n" "};\n" ,docnum ,gameid.c_str(),docnum ,docnum); script += string_f("doc_lst.push(chs_%u);\n",docnum); string onmouse; if (editmode) onmouse = string_f("onmousedown='chs_%u.gameplace(event); return false;'",docnum); lines += string_f("\n" ,gameid.c_str(),docnum,grid_width,grid_height,onmouse.c_str()); unsigned w8 = grid_width/8; unsigned h8 = grid_height/8; unsigned w8_2 = w8/2; unsigned h8_2 = h8/2; unsigned circle_x = w8/4; unsigned circle_y = h8*5/6; unsigned circle_radius = w8/12; unsigned last_radius = w8_2-3; unsigned marked_radius = w8/12; for (unsigned vline=0; vline<8; vline++){ lines += string_f("\n","lightgray" ,0,h8*vline,grid_width); unsigned line = reverse ? (8-1-vline) : vline; unsigned l1 = line & 1; for (unsigned col=0; col<8; col++){ unsigned w8_col = w8*col; unsigned h8_line = h8*vline; lines += string_f("\n","lightgray" ,w8*col,0,grid_height); const char *cellcolor = (l1+col)&1 ? "#b58763" : "#f0dab5"; lines += string_f("\n" ,w8*col,h8*vline,w8,h8,w8,cellcolor); char cell = grid[line][col]; const char *select_color = "none"; if (isalpha(cell)){ if (player1.col == col && player1.line == line){ select_color = "blue"; }else if (player2.col == col && player2.line == line){ select_color = "blue"; } } // Circle for last move const char *last_color = "none"; if (player1.lastmove.line == line && player1.lastmove.col == col){ last_color = "black"; }else if (player2.lastmove.line == line && player2.lastmove.col == col){ last_color = "black"; } lines += string_f("\n" ,line,col,w8_col+w8_2,h8_line+h8_2,last_radius,last_color); // Piece CELL_SPEC spec; cnv_cell_html (cell,spec,mobile); lines += string_f("%s\n" ,line,col,w8_col+w8_2,h8_line+font_base,spec.stroke_color,spec.fill_color,spec.utf8); // Circle to the left to show a selected piece lines += string_f("\n" ,line,col,w8_col+circle_x,h8_line+circle_y,circle_radius,select_color,select_color); // small circle to the right to show marked pieces const char *marked_color = "none"; for (auto &m:marked_pieces){ if (m.line == line && m.col == col){ marked_color = "red"; break; } } lines += string_f("\n" ,line,col,w8_col+w8-circle_x,h8_line+circle_y,marked_radius,marked_color,marked_color); } } // Draw the lines over the grid lines += string_f("\n" ,1,1,grid_width-1,grid_height-1,1); for (unsigned vline=0; vline<8; vline++){ static const char *linecolor="darkgray"; lines += string_f("\n",linecolor ,0,h8*vline,grid_width); for (unsigned col=0; col<8; col++){ lines += string_f("\n",linecolor ,w8*col,0,grid_height); } } lines += "XX\n"; lines += "\n"; return lines; } inline auto lim8(unsigned &val) { return limits(val,0u,7u); } /* Send all the javascript to users refreshing the content of all cells Normally used by resetgame. */ void CHESS::refreshboard(VARVAL ¬ify_var) { notify_var.val += "chs_loop_board(function(svg){\n"; notify_var.val += "\tvar elms = svg.getElementsByTagName('text');\n"; unsigned item = 0; for (unsigned i=0; i<8; i++){ for (unsigned j=0; j<8; j++){ char cell = grid[i][j]; notify_var.val += string_f("\tshowpiece(elms[%u],\"%c\");\n",item,cell); item++; } } // Clear all circle/markers notify_var.val += "\tvar elms = svg.getElementsByTagName('circle');\n"; notify_var.val += "\tfor (var i=0; i &res, std::vector &unotifies) { string error,status,api_error; setactivity(); VARVAL notify_var; notify_var.var = VAR_NOTIFY; notify_var.val = string_f("doc_cur_gameid='%s';\n",gameid.c_str()); if (ctx.maywrite){ bool robot = strcmp(ctx.username,CHESS_ROBOT)==0; if (player1.name.size() == 0 && player2.name != ctx.username){ player1.name = ctx.username; player1.robot = robot; js_find_set(notify_var.val,"player0","innerHTML",ctx.username); }else if (player2.name.size() == 0 && player1.name != ctx.username){ player2.name = ctx.username; player2.robot = robot; js_find_set(notify_var.val,"player1","innerHTML",ctx.username); } } bool reverse = sessions[ctx.username]; if (strcmp(var,REQ_PRINT)==0){ string lines; if (strcmp(val,"console")==0){ lines += " a b c d e f g h\n"; lines += " ----------------\n"; unsigned nol=8; for (auto &g:grid){ lines += string_f("%u|",nol); for (auto c:g){ lines += ' '; lines += c; } lines += '\n'; nol--; } lines += " ----------------\n"; lines += " a b c d e f g h\n"; }else if (strcmp(val,"fen")==0){ // Single line chess notation lines = format_fen(); }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 waiting_users_width = sp.mobile ? 62 : 42; unsigned reverse_width = sp.mobile ? 45 : 25; unsigned reverse_margin = 25; 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 % 8; unsigned w8 = width/8; unsigned grid_height = w8*8; lines += "\n"; lines += "\n"; // We center the players, knowing the content_width; { lines += string_f("
\n",sp.mobile ? 30 : 0,sp.content_width); unsigned player_width = sp.mobile ? BOX_PLAYER_WIDTH_MOBILE : BOX_PLAYER_WIDTH; unsigned extra_width = (sp.content_width-2*player_width)/2; { unsigned side_width = sp.mobile ? sp.content_width/2 : extra_width; lines += string_f("
\n",side_width); documentd_button_start(lines,gameid); documentd_button (lines,0,MSG_R(I_NEWGAME),false); documentd_button_end(lines); lines += string_f("
" ,gameid.c_str(),side_width-30-5-5); lines += string_f("%s\n",gamename.c_str()); lines += "
"; lines += "
\n"; if (!sp.mobile){ print_player(lines,gameid,player1.name,0,player1_playing,sp.mobile); print_player(lines,gameid,player2.name,1,!player1_playing,sp.mobile); } lines += string_f("
\n",side_width); documentd_button_start(lines,gameid); documentd_button (lines,1,MSG_U(I_UNDO,"Undo"),false); documentd_button (lines,2,MSG_U(I_CONFIG,"Config"),false); documentd_button_end(lines); lines += string_f("
",gameid.c_str()); lines += timer; lines += "
\n"; lines += "
\n"; } lines += "
\n"; if (sp.mobile){ lines += string_f("
\n",sp.mobile ? 30 : 0,sp.content_width); lines += string_f("
\n",extra_width); print_player(lines,gameid,player1.name,0,player1_playing,sp.mobile); print_player(lines,gameid,player2.name,1,!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 += string_f("
\n",width,grid_height); string script; lines += draw_board(reverse,sp.mobile,ctx.docnum,true,script); lines += "
\n"; if (script.size() > 0){ 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 = move(lines); res.emplace_back(move(v)); }else if (strcmp(var,REQ_FUNCTIONS)==0){ VARVAL var; var.var = VAR_DEFSCRIPT; var.val = define_functions (ctx,false,sp.mobile); res.emplace_back(move(var)); }else if (strcmp(var,REQ_STYLES)==0){ VARVAL var; var.var = VAR_STYLES; var.val += define_styles(sp.mobile); res.emplace_back(move(var)); }else if (strcmp(var,REQ_REGION)==0){ // For embedding VARVAL var,var_script; var.var = VAR_CONTENT; var_script.var = VAR_DEFSCRIPT; var.val = draw_board(false,sp.mobile,ctx.docnum,false,var_script.val); res.emplace_back(move(var)); res.emplace_back(move(var_script)); }else if (strcmp(var,"reverse")==0){ sessions[ctx.username] = !reverse; documentd_forcerefresh(res); if (ctx.maywrite) setmodified(ctx.username); }else if (strcmp(var,REQ_CHAT)==0){ appendchat(val,notify_var.val,res,ctx); }else if (strcmp(var,REQ_GETFIELDS)==0){ VARVAL var; var.var = VAR_FIELDS; if (strcmp(val,DIALOG_CHESS_CONFIG)==0){ var.val = string_f("player1:%s\n",player1.name.c_str()); var.val += string_f("player2:%s\n",player2.name.c_str()); var.val += string_f("name:%s\n",gamename.c_str()); var.val += string_f("timer:%s\n",timer.c_str()); var.val += string_f("skill:%u\n",robotskill); var.val += string_f("maxskill:%u\n",maxskill); } res.emplace_back(var); }else if (ctx.maywrite){ if (strcmp(var,"place")==0){ unsigned lo,co,button; if (splitline(val,',',lim8(lo),lim8(co),enums(button,{1u,2u}))){ if (reverse) lo = 8 -1 - lo; CHESS_PLAYER *player = &player2; CHESS_PLAYER *other_player = &player1; if (player1_playing){ player = &player1; other_player = &player2; } // tlmp_warning ("has_selected %d player1 %d isupper %d islower %d",player->has_selected(),player1_playing,isupper(grid[lo][co]),islower(grid[lo][co])); char &new_cell = grid[lo][co]; if (player->has_selected()){ string sel_id = string_f("cs%u,%u",player->line,player->col); string new_id = string_f("cs%u,%u",lo,co); if (player->line == lo && player->col == co){ // The player is unselecting the cell update_circle (notify_var,new_id,"none","none"); player->reset_sel(); setmodified(ctx.username); }else{ // The player is doing a move CHESSMOVE_EFFECTS effects; bool move_ok = checkmove (player,lo,co,other_player,effects,error); if (move_ok){ save_lastmove(player); // We record the animation. We create a function in tbanim[] // Inside this function, we create fctend. All the javascript generated below // ends up in fctend. // At the end of fctend, there is the call to fctanimate notify_var.val += "tbanim[tbanim.length]=function(){\n"; notify_var.val += "\tvar fctend=function(){\n"; if (effects.clear_en_passant){ CHESS_UNDO undo; undo.player1_playing = player1_playing; undo.line = other_player->en_passant.line; undo.col = other_player->en_passant.col; undo.cell = grid[undo.line][undo.col]; undo.type = CHESS_UNDO_MOVE; undos.push_back(undo); string id = string_f("c%u,%u",other_player->en_passant.line,other_player->en_passant.col); update_cell (notify_var,id,' '); grid[undo.line][undo.col] = ' '; } if (effects.left_castling){ unsigned from_line = player->line; char rook = grid[from_line][0]; CHESS_UNDO undo; undo.player1_playing = player1_playing; undo.line = from_line; undo.col = 0; undo.cell = rook; undo.type = CHESS_UNDO_MOVE; undos.push_back(undo); undo.col = 3; undo.cell = ' '; undos.push_back(undo); string id = string_f("c%u,%u",from_line,0); update_cell (notify_var,id,' '); id = string_f("c%u,%u",from_line,3); update_cell (notify_var,id,rook); grid[from_line][0] = ' '; grid[from_line][3] = rook; } if (effects.right_castling){ unsigned from_line = player->line; char rook = grid[from_line][7]; CHESS_UNDO undo; undo.player1_playing = player1_playing; undo.line = from_line; undo.col = 7; undo.cell = rook; undo.type = CHESS_UNDO_MOVE; undos.push_back(undo); undo.col = 5; undo.cell = ' '; undos.push_back(undo); string id = string_f("c%u,%u",from_line,7); update_cell (notify_var,id,' '); id = string_f("c%u,%u",from_line,5); update_cell (notify_var,id,rook); grid[from_line][7] = ' '; grid[from_line][5] = rook; } string old_text_id = string_f("c%u,%u",player->line,player->col); update_cell(notify_var,old_text_id,' '); unsigned player_line=player->line; // Save player action for the fctanimate call unsigned player_col=player->col; char old_cell = new_cell; // Keep a copy of the target cell execmove (player,other_player,lo,co,effects); if (!player->is_robot()){ setmodified(ctx.username); documentd_setchanges(res); } string new_text_id = string_f("c%u,%u",lo,co); update_cell (notify_var,new_text_id,new_cell); update_circle(notify_var,sel_id,"none","none"); show_marked_pieces (notify_var,"none"); if (check_expose (player1_playing,marked_pieces)){ status = MSG_U(I_KINGCHECK,"Check"); } update_player_bg(notify_var,0,player1_playing ? "lightblue" : "white"); update_player_bg(notify_var,1,player1_playing ? "white" : "lightblue"); if (player->has_lastmove()){ string last_id = string_f("cl%u,%u",player->lastmove.line,player->lastmove.col); update_circle (notify_var,last_id,"none","none"); } player->lastmove.line = lo; player->lastmove.col = co; string last_id = string_f("cl%u,%u",lo,co); update_circle(notify_var,last_id,"none","black"); vector exposing_pieces; if (check_expose (!player1_playing,exposing_pieces)){ marked_pieces = exposing_pieces; error = MSG_U(I_KINGEXPOSED,"Your king becomes in check. Illegal move."); undoone(notify_var); } notify_var.val += "};\n"; // End of fctend // Compute the duration based on distance float dist = sqrt(pow((int)(co-player_col),2)+pow((int)(lo-player_line),2)); unsigned duration = 1000/8*dist; // Distance maximum is 8. Max duration is 1000ms // tlmp_warning ("dist=%f duration=%u",dist,duration); notify_var.val += string_f("fctanimate(%u,%u,%u,%u,0,100,%u,\"%c\",fctend);\n" ,player_col,player_line,co,lo,duration,new_cell); notify_var.val += "};\n"; notify_var.val += "if (tbanim.length==1) tbanim[0]();\n"; show_marked_pieces (notify_var,"red"); robot_request(res); if (!player->is_robot()){ unsigned rlo=8-lo; unsigned rplayer_line=8-player_line; string chessmove = string_f("%c%u%c%u",'a'+player_col,rplayer_line,'a'+co,rlo); appendmove2chat (ctx.username,true,old_cell,new_cell,chessmove,notify_var.val); setmodified(ctx.username); } } } }else if (new_cell == ' '){ error = MSG_U(E_MUSTSELECTONEPIECE,"You must select one piece"); }else if ((player1_playing && isupper(new_cell)) || (!player1_playing && islower(new_cell))){ // 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); update_circle(notify_var,id,color,color); }else{ error = MSG_U(E_CANTSELECT,"You can't select an opponent piece"); } }else{ tlmp_warning ("chess, invalid place command: %s",val); } }else if (strcmp(var,"newgame")==0){ int uval = atoi(val); if (uval == 0){ VARVAL var; var.var = VAR_DIALOG; var.val = DIALOG_CHESS_NEWGAME; res.emplace_back(var); }else if (uval == 1){ // Not really a new game, but an undo if (undos.size() > 0){ undoone(notify_var); show_marked_pieces (notify_var,"none"); if (check_expose (player1_playing,marked_pieces)){ error = MSG_R(I_KINGCHECK); show_marked_pieces (notify_var,"red"); } setmodified(ctx.username); }else{ error = MSG_U(E_NOUNDO,"No step to undo"); } }else if (uval == 2){ // Config VARVAL var; var.var = VAR_DIALOG; var.val = DIALOG_CHESS_CONFIG; res.emplace_back(var); }else{ tlmp_error ("checkers newgame=%d",uval); } }else if (strcmp(var,"resetgame")==0){ resetgame(); refreshboard(notify_var); setmodified(ctx.username); robot_request(res); documentd_setchanges(res); }else if (strcmp(var,"config")==0){ vector fields; documentd_parsefields(val,fields); for (auto &f:fields){ if (f.var == "player1"){ player1.name = f.val; }else if (f.var == "player2"){ player2.name = f.val; }else if (f.var == "name"){ gamename = f.val; }else if (f.var == "timer"){ timer = f.val; }else if (f.var == "skill"){ robotskill = atoi(f.val.c_str()); } } player1.robot = player1.name == CHESS_ROBOT; player2.robot = player2.name == CHESS_ROBOT; setmodified(ctx.username); update_players(notify_var.val); js_find_set(notify_var.val,"gamename","innerHTML",gamename.c_str()); js_find_set(notify_var.val,"timer","innerHTML",timer.c_str()); robot_request(res); }else if (strcmp(var,"playersel")==0){ unsigned playernum,button; if (splitline(val,',',limits(playernum,0u,1u),limits(button,1u,2u))){ // One player has selected a color (white or black). // If the player name is already in the other color, we do a swap. // For example, player A is playing the whites and B, the blacks. // Player A selects the blacks. Player B will be playing the whites. // If this is done with button 2, then robot mode (play against the computer). // The same rule applies. // Player A is playing the whites and robot is playing the blacks. // If anyone select the whites using button 2, a swap is applied. CHESS_PLAYER *player = &player1; CHESS_PLAYER *other_player = &player2; if (playernum==1){ player = &player2; other_player = &player1; } if (button == 2){ if (robot_type == ROBOT_NONE){ error = MSG_U(E_NOGNUCHESS,"No chess engine installed on this server (gnuchess,stockfish)"); }else if (other_player->is_robot()){ swap(other_player->name,player->name); player->robot = true; other_player->robot = false; }else{ player->name = CHESS_ROBOT; player->robot = true; } }else if (other_player->name == ctx.username){ swap(other_player->name,player->name); swap(other_player->robot,player->robot); }else{ player->name = ctx.username; player->robot = false; } setmodified(ctx.username); update_players(notify_var.val); robot_request(res); } }else if (is_eq(var,"loadline")){ // For automated tests purpose // val=line:line_content unsigned line; string content; if (splitline(val,':',lim8(line),content) && content.size()==8){ unsigned col = 0; for (auto cell:content){ if (cell == '_') cell = ' '; grid[line][col] = cell; col++; } refreshboard(notify_var); }else{ api_error = string_f(MSG_U(E_IVLDCMDARGS,"Command %s: invalid parameters"),"loadline"); } }else if (is_eq(var,"clear")){ for (auto &g:grid) for (auto &gg:g) gg=' '; refreshboard(notify_var); }else if (is_eq(var,"checkmove")){ // For automated tests purpose // It is permisive. It will find who is playing by lookup at the from_line,from_col cell. // This helps create test sequences. unsigned from_line,from_col,to_line,to_col,do_it; if (splitline(val,',',lim8(from_line),lim8(from_col),lim8(to_line),lim8(to_col),enums(do_it,{0u,1u}))){ CHESSMOVE_EFFECTS eff; char cell = grid[from_line][from_col]; if (cell == ' '){ }else{ CHESS_PLAYER *player = &player1; CHESS_PLAYER *other_player = &player2; player1_playing = true; if (islower(cell)){ player = &player2; other_player = &player1; player1_playing = false; } player->line = from_line; player->col = from_col; bool move_ok = checkmove (player,to_line,to_col,other_player,eff,error); VARVAL var; var.var = "move_ok"; var.val = string_f("%d",move_ok); res.emplace_back(var); var.var = "effects"; var.val = eff.dump(); res.emplace_back(var); if (do_it) execmove (player,other_player,to_line,to_col,eff); var.var = "player1"; var.val = player1.dump(); res.emplace_back(var); var.var = "player2"; var.val = player2.dump(); res.emplace_back(var); player->reset_sel(); } } }else if (is_eq(var,"setplayer")){ unsigned player_num,en_passant_line,en_passant_col; bool king_moved,left_rook_moved,right_rook_moved; if (splitline(val,limits(player_num,1u,2u) ,king_moved,left_rook_moved,right_rook_moved ,en_passant_line,en_passant_col)){ auto &p = player_num == 1 ? player1 : player2; p.king_moved = king_moved; p.left_rook_moved = left_rook_moved; p.right_rook_moved = right_rook_moved; p.en_passant.line = en_passant_line; p.en_passant.col = en_passant_col; }else{ api_error = string_f(MSG_R(E_IVLDCMDARGS),"setplayer"); } }else if (is_eq(var,"dump")){ VARVAL var; var.var = "grid"; unsigned line = 0; var.val = '\n'; for (auto &g:grid){ var.val += string_f("%u:",line); for (auto gg:g) var.val += gg; var.val += '\n'; line++; } res.emplace_back(var); var.var = "player1"; var.val = player1.dump(); res.emplace_back(var); var.var = "player2"; var.val = player2.dump(); res.emplace_back(var); } }else{ error = MSG_R(E_READONLY); } if (api_error.size() > 0){ VARVAL var; var.var = VAR_ERROR; var.val = move(api_error); res.emplace_back(move(var)); } if (notify_var.val.size() > 0) res.emplace_back(move(notify_var)); if (error.size() > 0){ update_msg(false,error,"red",res); }else if (status.size() > 0){ update_msg(true,status,"red",res); }else{ update_msg(false," ","white",res); } } inline const char *chs_sel_name(bool took, bool is_human, const char *took_str, const char *human_str, const char *computer_str) { if (took) return took_str; if (is_human) return human_str; return computer_str; } /* Convert a piece (letter) into a readable string. This string represent a piece owned by a human or the computer. Further, the string represent the piece who made the action, or the piece which was taken. The distinction between the 3 cases hopefully will be enough for all languages. */ static const char *piece2name(char cellval, bool is_human, bool took) { char cell = tolower(cellval); const char *piece = ""; if (chs_is_pawn(cell)){ piece = chs_sel_name(took,is_human,MSG_U(I_THE_PAWN,"the pawn"),MSG_U(I_A_PAWN,"a pawn"),MSG_R(I_A_PAWN)); }else if (chs_is_rook(cell)){ piece = chs_sel_name(took,is_human,MSG_U(I_THE_ROOK,"the rook"),MSG_U(I_HIS_ROOK,"his rook"),MSG_U(I_ITS_ROOK,"its rook")); }else if (chs_is_knight(cell)){ piece = chs_sel_name(took,is_human,MSG_U(I_THE_KNIGHT,"the knight"),MSG_U(I_HIS_KNIGHT,"his knight"),MSG_U(I_ITS_KNIGHT,"its knight")); }else if (chs_is_bishop(cell)){ piece = chs_sel_name(took,is_human,MSG_U(I_THE_BISHOP,"the bishop"),MSG_U(I_HIS_BISHOP,"his bishop"),MSG_U(I_ITS_BISHOP,"its bishop")); }else if (chs_is_queen(cell)){ piece = chs_sel_name(took,is_human,MSG_U(I_THE_QUEEN,"the queen"),MSG_U(I_HIS_QUEEN,"his queen"),MSG_U(I_ITS_QUEEN,"its queen")); }else if (chs_is_king(cell)){ piece = chs_sel_name(took,is_human,MSG_U(I_THE_KING,"the king"),MSG_U(I_HIS_KING,"his king"),MSG_U(I_ITS_KING,"its king")); } return piece; } /* Append a message in the chat about a chess move. Try to make this readable */ void CHESS::appendmove2chat(const char *username,bool is_human, char oldcell, char newcell, PARAM_STRING chessmove, string ¬ify) { const char *piece = piece2name(newcell,is_human,false); string msg; if (oldcell == ' '){ msg = string_f(MSG_U(I_USERPLAYED,"%s moved %s: %s"),username,piece,chessmove.ptr); }else{ const char *oldpiece = piece2name(oldcell,is_human,true); msg = string_f(MSG_U(I_USERPLAYEDTOOK,"%s moved %s and took %s: %s"),username,piece,oldpiece,chessmove.ptr); } appendchat (msg,notify); } void CHESS::engine_reply (const char *line, string ¬ify, bool &done) { const char *pt; done = false; //tlmp_warning ("engine_reply: %s",line); if (is_start_any_of(line,pt,"bestmove ")){ pt = str_skip(pt); if (strlen(pt) >= 4 && isalpha(pt[0]) && isdigit(pt[1]) && isalpha(pt[2]) && isdigit(pt[3]) && is_any_of(pt[4],' ','\0')){ DOC_UI_SPECS_receive sp; documentd_init_specs (sp); vector res; unsigned col1 = pt[0] - 'a'; unsigned line1 = 8-(pt[1] - '0'); unsigned col2 = pt[2] - 'a'; unsigned line2 = 8-(pt[3] - '0'); char oldcell = grid[line2][col2]; // Value of the target cell before the move string cmd1 = string_f("%u,%u,1",line1,col1); string cmd2 = string_f("%u,%u,1",line2,col2); vector steps; VARVAL_receive st; st.var = "place"; st.val = cmd1.c_str(); steps.emplace_back(st); st.val = cmd2.c_str(); steps.emplace_back(st); DOC_CONTEXT ctx; ctx.session = "session"; ctx.username = CHESS_ROBOT; ctx.maywrite = true; vector unotifies; manyexec (steps,ctx,sp,res,unotifies); for (auto &r:res){ if (r.var == VAR_NOTIFY){ notify += r.val; } } string chessmove(pt,4); appendmove2chat(MSG_U(I_USERCOMPUTER,"Computer"),false,oldcell,grid[line2][col2],chessmove,notify); }else{ const char *msg = MSG_U(I_COMPCANTDO,"The computer does not know what to do"); js_find_set (notify,"msg","style.color","red","innerHTML",documentd_escape(msg).c_str()); } done = true; } }