From 6808d7a16ec1e761c6a2dffec2281c96953e4d89 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 18 Feb 2020 15:30:28 +0100 Subject: [PATCH] Started code review + some fixes (unfinished) --- client/package.json | 31 +- client/src/App.vue | 13 +- client/src/base_rules.js | 1203 +++++++++++------------ client/src/components/BaseGame.vue | 322 +++--- client/src/components/Board.vue | 403 ++++---- client/src/components/ChallengeList.vue | 23 +- client/src/components/Chat.vue | 24 +- client/src/components/ComputerGame.vue | 85 +- client/src/components/ContactForm.vue | 31 +- client/src/components/GameList.vue | 78 +- client/src/components/Language.vue | 9 +- client/src/components/MoveList.vue | 116 +-- client/src/components/Settings.vue | 18 +- client/src/components/UpsertUser.vue | 71 +- client/src/data/challengeCheck.js | 29 +- client/src/data/problemCheck.js | 11 +- client/src/data/userCheck.js | 22 +- client/src/main.js | 14 +- client/src/playCompMove.js | 17 +- client/src/router.js | 30 +- client/src/store.js | 39 +- client/src/translations/en.js | 115 +-- client/src/translations/es.js | 119 +-- client/src/translations/fr.js | 128 +-- client/src/utils/ajax.js | 53 +- client/src/utils/alea.js | 28 +- client/src/utils/array.js | 28 +- client/src/utils/cookie.js | 20 +- client/src/utils/datetime.js | 55 +- client/src/utils/gameStorage.js | 141 ++- client/src/utils/modalClick.js | 6 +- client/src/utils/printDiagram.js | 78 +- client/src/utils/scoring.js | 3 +- client/src/utils/squareId.js | 10 +- client/src/utils/timeControl.js | 49 +- client/src/variants/Alice.js | 320 +++--- client/src/variants/Antiking.js | 218 ++-- client/src/variants/Atomic.js | 168 ++-- client/src/variants/Baroque.js | 618 ++++++------ client/src/variants/Berolina.js | 137 ++- client/src/variants/Checkered.js | 359 ++++--- client/src/variants/Chess960.js | 5 +- client/src/variants/Crazyhouse.js | 243 ++--- client/src/variants/Dark.js | 223 ++--- client/src/variants/Extinction.js | 117 ++- client/src/variants/Grand.js | 352 ++++--- client/src/variants/Losers.js | 186 ++-- client/src/variants/Magnetic.js | 186 ++-- client/src/variants/Marseille.js | 264 +++-- client/src/variants/Upsidedown.js | 66 +- client/src/variants/Wildebeest.js | 307 +++--- client/src/variants/Zen.js | 211 ++-- client/src/views/About.vue | 17 +- client/src/views/Analyse.vue | 29 +- client/src/views/Auth.vue | 38 +- client/src/views/Game.vue | 566 ++++++----- client/src/views/Hall.vue | 579 ++++++----- client/src/views/Logout.vue | 6 +- client/src/views/MyGames.vue | 30 +- client/src/views/News.vue | 94 +- client/src/views/Problems.vue | 163 ++- client/src/views/Rules.vue | 54 +- client/src/views/Variants.vue | 38 +- client/vue.config.js | 6 +- 64 files changed, 4338 insertions(+), 4684 deletions(-) diff --git a/client/package.json b/client/package.json index 242acdc2..88feedb9 100644 --- a/client/package.json +++ b/client/package.json @@ -15,7 +15,6 @@ "devDependencies": { "@vue/cli-plugin-eslint": "^3.12.1", "@vue/cli-service": "^4.2.2", - "@vue/eslint-config-prettier": "^4.0.1", "ajv": "^6.11.0", "apply-loader": "^2.0.0", "babel-eslint": "^10.0.3", @@ -39,11 +38,39 @@ }, "extends": [ "plugin:vue/essential", - "@vue/prettier" + "eslint:recommended" ], "rules": {}, "parserOptions": { "parser": "babel-eslint" + }, + "globals": { + "V": "readonly" + }, + "rules": { + "consistent-return": 2, + "indent": [ + "error", + 2, + { + "SwitchCase": 1, + "VariableDeclarator": "first", + "FunctionExpression": { + "parameters": "first" + }, + "CallExpression": { + "arguments": "first" + }, + "flatTernaryExpressions": true + } + ], + "no-else-return" : [ + 1, + { + "allowElseIf": false + } + ], + "semi" : [1, "always"] } }, "postcss": { diff --git a/client/src/App.vue b/client/src/App.vue index 012b1c2b..9879e275 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -53,17 +53,17 @@ export default { ContactForm, Language, Settings, - UpsertUser, + UpsertUser }, data: function() { return { - st: store.state, + st: store.state }; }, computed: { flagImage: function() { return `/images/flags/${this.st.lang}.svg`; - }, + } }, mounted: function() { let dialogs = document.querySelectorAll("div[role='dialog']"); @@ -73,12 +73,11 @@ export default { }, methods: { hideDrawer: function(e) { - if (e.target.innerText == "Forum") - return; //external link + if (e.target.innerText == "Forum") return; //external link e.preventDefault(); //TODO: why is this needed? document.getElementsByClassName("drawer")[0].checked = false; - }, - }, + } + } }; diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 924f7374..056f4774 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -4,71 +4,65 @@ import { ArrayFun } from "@/utils/array"; import { randInt, shuffle } from "@/utils/alea"; -export const PiPo = class PiPo //Piece+Position -{ +export const PiPo = class PiPo { + //Piece+Position // o: {piece[p], color[c], posX[x], posY[y]} - constructor(o) - { + constructor(o) { this.p = o.p; this.c = o.c; this.x = o.x; this.y = o.y; } -} +}; // TODO: for animation, moves should contains "moving" and "fading" maybe... -export const Move = class Move -{ +export const Move = class Move { // o: {appear, vanish, [start,] [end,]} // appear,vanish = arrays of PiPo // start,end = coordinates to apply to trigger move visually (think castle) - constructor(o) - { + constructor(o) { this.appear = o.appear; this.vanish = o.vanish; - this.start = !!o.start ? o.start : {x:o.vanish[0].x, y:o.vanish[0].y}; - this.end = !!o.end ? o.end : {x:o.appear[0].x, y:o.appear[0].y}; + this.start = o.start ? o.start : { x: o.vanish[0].x, y: o.vanish[0].y }; + this.end = o.end ? o.end : { x: o.appear[0].x, y: o.appear[0].y }; } -} +}; // NOTE: x coords = top to bottom; y = left to right (from white player perspective) -export const ChessRules = class ChessRules -{ +export const ChessRules = class ChessRules { ////////////// // MISC UTILS - static get HasFlags() { return true; } //some variants don't have flags + static get HasFlags() { + return true; + } //some variants don't have flags - static get HasEnpassant() { return true; } //some variants don't have ep. + static get HasEnpassant() { + return true; + } //some variants don't have ep. // Path to pieces - static getPpath(b) - { + static getPpath(b) { return b; //usual pieces in pieces/ folder } // Turn "wb" into "B" (for FEN) - static board2fen(b) - { - return (b[0]=='w' ? b[1].toUpperCase() : b[1]); + static board2fen(b) { + return b[0] == "w" ? b[1].toUpperCase() : b[1]; } // Turn "p" into "bp" (for board) - static fen2board(f) - { - return (f.charCodeAt()<=90 ? "w"+f.toLowerCase() : "b"+f); + static fen2board(f) { + return f.charCodeAt() <= 90 ? "w" + f.toLowerCase() : "b" + f; } // Check if FEN describe a board situation correctly - static IsGoodFen(fen) - { + static IsGoodFen(fen) { const fenParsed = V.ParseFen(fen); // 1) Check position - if (!V.IsGoodPosition(fenParsed.position)) - return false; + if (!V.IsGoodPosition(fenParsed.position)) return false; // 2) Check turn - if (!fenParsed.turn || !V.IsGoodTurn(fenParsed.turn)) - return false; + if (!fenParsed.turn || !V.IsGoodTurn(fenParsed.turn)) return false; // 3) Check moves count if (!fenParsed.movesCount || !(parseInt(fenParsed.movesCount) >= 0)) return false; @@ -76,81 +70,65 @@ export const ChessRules = class ChessRules if (V.HasFlags && (!fenParsed.flags || !V.IsGoodFlags(fenParsed.flags))) return false; // 5) Check enpassant - if (V.HasEnpassant && - (!fenParsed.enpassant || !V.IsGoodEnpassant(fenParsed.enpassant))) - { + if ( + V.HasEnpassant && + (!fenParsed.enpassant || !V.IsGoodEnpassant(fenParsed.enpassant)) + ) { return false; } return true; } // Is position part of the FEN a priori correct? - static IsGoodPosition(position) - { - if (position.length == 0) - return false; + static IsGoodPosition(position) { + if (position.length == 0) return false; const rows = position.split("/"); - if (rows.length != V.size.x) - return false; - for (let row of rows) - { + if (rows.length != V.size.x) return false; + for (let row of rows) { let sumElts = 0; - for (let i=0; i d (column number to letter) - static CoordToColumn(colnum) - { + static CoordToColumn(colnum) { return String.fromCharCode(97 + colnum); } // d --> 3 (column letter to number) - static ColumnToCoord(column) - { + static ColumnToCoord(column) { return column.charCodeAt(0) - 97; } // a4 --> {x:3,y:0} - static SquareToCoords(sq) - { + static SquareToCoords(sq) { return { // NOTE: column is always one char => max 26 columns // row is counted from black side => subtraction @@ -160,44 +138,40 @@ export const ChessRules = class ChessRules } // {x:0,y:4} --> e8 - static CoordsToSquare(coords) - { + static CoordsToSquare(coords) { return V.CoordToColumn(coords.y) + (V.size.x - coords.x); } // Aggregates flags into one object - aggregateFlags() - { + aggregateFlags() { return this.castleFlags; } // Reverse operation - disaggregateFlags(flags) - { + disaggregateFlags(flags) { this.castleFlags = flags; } // En-passant square, if any - getEpSquare(moveOrSquare) - { - if (!moveOrSquare) - return undefined; - if (typeof moveOrSquare === "string") - { + getEpSquare(moveOrSquare) { + if (!moveOrSquare) return undefined; + if (typeof moveOrSquare === "string") { const square = moveOrSquare; - if (square == "-") - return undefined; + if (square == "-") return undefined; return V.SquareToCoords(square); } // Argument is a move: const move = moveOrSquare; - const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x]; + const [sx, sy, ex] = [move.start.x, move.start.y, move.end.x]; // NOTE: next conditions are first for Atomic, and last for Checkered - if (move.appear.length > 0 && Math.abs(sx - ex) == 2 - && move.appear[0].p == V.PAWN && ["w","b"].includes(move.appear[0].c)) - { + if ( + move.appear.length > 0 && + Math.abs(sx - ex) == 2 && + move.appear[0].p == V.PAWN && + ["w", "b"].includes(move.appear[0].c) + ) { return { - x: (sx + ex)/2, + x: (sx + ex) / 2, y: sy }; } @@ -205,26 +179,22 @@ export const ChessRules = class ChessRules } // Can thing on square1 take thing on square2 - canTake([x1,y1], [x2,y2]) - { - return this.getColor(x1,y1) !== this.getColor(x2,y2); + canTake([x1, y1], [x2, y2]) { + return this.getColor(x1, y1) !== this.getColor(x2, y2); } // Is (x,y) on the chessboard? - static OnBoard(x,y) - { - return (x>=0 && x=0 && y= 0 && x < V.size.x && y >= 0 && y < V.size.y; } // Used in interface: 'side' arg == player color - canIplay(side, [x,y]) - { - return (this.turn == side && this.getColor(x,y) == side); + canIplay(side, [x, y]) { + return this.turn == side && this.getColor(x, y) == side; } // On which squares is color under check ? (for interface) - getCheckSquares(color) - { + getCheckSquares(color) { return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]) ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate! : []; @@ -234,12 +204,10 @@ export const ChessRules = class ChessRules // FEN UTILS // Setup the initial random (assymetric) position - static GenRandInitFen() - { - let pieces = { "w": new Array(8), "b": new Array(8) }; + static GenRandInitFen() { + let pieces = { w: new Array(8), b: new Array(8) }; // Shuffle pieces on first and last rank - for (let c of ["w","b"]) - { + for (let c of ["w", "b"]) { let positions = ArrayFun.range(8); // Get random squares for bishops @@ -249,8 +217,8 @@ export const ChessRules = class ChessRules let randIndex_tmp = 2 * randInt(4) + 1; const bishop2Pos = positions[randIndex_tmp]; // Remove chosen squares - positions.splice(Math.max(randIndex,randIndex_tmp), 1); - positions.splice(Math.min(randIndex,randIndex_tmp), 1); + positions.splice(Math.max(randIndex, randIndex_tmp), 1); + positions.splice(Math.min(randIndex, randIndex_tmp), 1); // Get random squares for knights randIndex = randInt(6); @@ -272,63 +240,59 @@ export const ChessRules = class ChessRules const rook2Pos = positions[2]; // Finally put the shuffled pieces in the board array - pieces[c][rook1Pos] = 'r'; - pieces[c][knight1Pos] = 'n'; - pieces[c][bishop1Pos] = 'b'; - pieces[c][queenPos] = 'q'; - pieces[c][kingPos] = 'k'; - pieces[c][bishop2Pos] = 'b'; - pieces[c][knight2Pos] = 'n'; - pieces[c][rook2Pos] = 'r'; + pieces[c][rook1Pos] = "r"; + pieces[c][knight1Pos] = "n"; + pieces[c][bishop1Pos] = "b"; + pieces[c][queenPos] = "q"; + pieces[c][kingPos] = "k"; + pieces[c][bishop2Pos] = "b"; + pieces[c][knight2Pos] = "n"; + pieces[c][rook2Pos] = "r"; } - return pieces["b"].join("") + + return ( + pieces["b"].join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" + pieces["w"].join("").toUpperCase() + - " w 0 1111 -"; //add turn + flags + enpassant + " w 0 1111 -" + ); //add turn + flags + enpassant } // "Parse" FEN: just return untransformed string data - static ParseFen(fen) - { + static ParseFen(fen) { const fenParts = fen.split(" "); - let res = - { + let res = { position: fenParts[0], turn: fenParts[1], - movesCount: fenParts[2], + movesCount: fenParts[2] }; let nextIdx = 3; - if (V.HasFlags) - Object.assign(res, {flags: fenParts[nextIdx++]}); - if (V.HasEnpassant) - Object.assign(res, {enpassant: fenParts[nextIdx]}); + if (V.HasFlags) Object.assign(res, { flags: fenParts[nextIdx++] }); + if (V.HasEnpassant) Object.assign(res, { enpassant: fenParts[nextIdx] }); return res; } // Return current fen (game state) - getFen() - { - return this.getBaseFen() + " " + - this.getTurnFen() + " " + this.movesCount + - (V.HasFlags ? (" " + this.getFlagsFen()) : "") + - (V.HasEnpassant ? (" " + this.getEnpassantFen()) : ""); + getFen() { + return ( + this.getBaseFen() + + " " + + this.getTurnFen() + + " " + + this.movesCount + + (V.HasFlags ? " " + this.getFlagsFen() : "") + + (V.HasEnpassant ? " " + this.getEnpassantFen() : "") + ); } // Position part of the FEN string - getBaseFen() - { + getBaseFen() { let position = ""; - for (let i=0; i 0) - { + for (let j = 0; j < V.size.y; j++) { + if (this.board[i][j] == V.EMPTY) emptyCount++; + else { + if (emptyCount > 0) { // Add empty squares in-between position += emptyCount; emptyCount = 0; @@ -336,87 +300,72 @@ export const ChessRules = class ChessRules position += V.board2fen(this.board[i][j]); } } - if (emptyCount > 0) - { + if (emptyCount > 0) { // "Flush remainder" position += emptyCount; } - if (i < V.size.x - 1) - position += "/"; //separate rows + if (i < V.size.x - 1) position += "/"; //separate rows } return position; } - getTurnFen() - { + getTurnFen() { return this.turn; } // Flags part of the FEN string - getFlagsFen() - { + getFlagsFen() { let flags = ""; // Add castling flags - for (let i of ['w','b']) - { - for (let j=0; j<2; j++) - flags += (this.castleFlags[i][j] ? '1' : '0'); + for (let i of ["w", "b"]) { + for (let j = 0; j < 2; j++) flags += this.castleFlags[i][j] ? "1" : "0"; } return flags; } // Enpassant part of the FEN string - getEnpassantFen() - { + getEnpassantFen() { const L = this.epSquares.length; - if (!this.epSquares[L-1]) - return "-"; //no en-passant - return V.CoordsToSquare(this.epSquares[L-1]); + if (!this.epSquares[L - 1]) return "-"; //no en-passant + return V.CoordsToSquare(this.epSquares[L - 1]); } // Turn position fen into double array ["wb","wp","bk",...] - static GetBoard(position) - { + static GetBoard(position) { const rows = position.split("/"); let board = ArrayFun.init(V.size.x, V.size.y, ""); - for (let i=0; i= 0 && x+shiftX < sizeX) - { - const finalPieces = x + shiftX == lastRank - ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN] - : [V.PAWN] + if (x + shiftX >= 0 && x + shiftX < sizeX) { + const finalPieces = + x + shiftX == lastRank + ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] + : [V.PAWN]; // One square forward - if (this.board[x+shiftX][y] == V.EMPTY) - { - for (let piece of finalPieces) - { - moves.push(this.getBasicMove([x,y], [x+shiftX,y], - {c:pawnColor,p:piece})); + if (this.board[x + shiftX][y] == V.EMPTY) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y], { + c: pawnColor, + p: piece + }) + ); } // Next condition because pawns on 1st rank can generally jump - if ([startRank,firstRank].includes(x) - && this.board[x+2*shiftX][y] == V.EMPTY) - { + if ( + [startRank, firstRank].includes(x) && + this.board[x + 2 * shiftX][y] == V.EMPTY + ) { // Two squares jump - moves.push(this.getBasicMove([x,y], [x+2*shiftX,y])); + moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); } } // Captures - for (let shiftY of [-1,1]) - { - if (y + shiftY >= 0 && y + shiftY < sizeY - && this.board[x+shiftX][y+shiftY] != V.EMPTY - && this.canTake([x,y], [x+shiftX,y+shiftY])) - { - for (let piece of finalPieces) - { - moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY], - {c:pawnColor,p:piece})); + for (let shiftY of [-1, 1]) { + if ( + y + shiftY >= 0 && + y + shiftY < sizeY && + this.board[x + shiftX][y + shiftY] != V.EMPTY && + this.canTake([x, y], [x + shiftX, y + shiftY]) + ) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y + shiftY], { + c: pawnColor, + p: piece + }) + ); } } } } - if (V.HasEnpassant) - { + if (V.HasEnpassant) { // En passant const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep-1]; //always at least one element - if (!!epSquare && epSquare.x == x+shiftX && Math.abs(epSquare.y - y) == 1) - { - let enpassantMove = this.getBasicMove([x,y], [epSquare.x,epSquare.y]); + const epSquare = this.epSquares[Lep - 1]; //always at least one element + if ( + !!epSquare && + epSquare.x == x + shiftX && + Math.abs(epSquare.y - y) == 1 + ) { + let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]); enpassantMove.vanish.push({ x: x, y: epSquare.y, - p: 'p', - c: this.getColor(x,epSquare.y) + p: "p", + c: this.getColor(x, epSquare.y) }); moves.push(enpassantMove); } @@ -697,106 +662,112 @@ export const ChessRules = class ChessRules } // What are the rook moves from square x,y ? - getPotentialRookMoves(sq) - { + getPotentialRookMoves(sq) { return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]); } // What are the knight moves from square x,y ? - getPotentialKnightMoves(sq) - { + getPotentialKnightMoves(sq) { return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"); } // What are the bishop moves from square x,y ? - getPotentialBishopMoves(sq) - { + getPotentialBishopMoves(sq) { return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]); } // What are the queen moves from square x,y ? - getPotentialQueenMoves(sq) - { - return this.getSlideNJumpMoves(sq, - V.steps[V.ROOK].concat(V.steps[V.BISHOP])); + getPotentialQueenMoves(sq) { + return this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + ); } // What are the king moves from square x,y ? - getPotentialKingMoves(sq) - { + getPotentialKingMoves(sq) { // Initialize with normal moves - let moves = this.getSlideNJumpMoves(sq, - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); + let moves = this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + "oneStep" + ); return moves.concat(this.getCastleMoves(sq)); } - getCastleMoves([x,y]) - { - const c = this.getColor(x,y); - if (x != (c=="w" ? V.size.x-1 : 0) || y != this.INIT_COL_KING[c]) + getCastleMoves([x, y]) { + const c = this.getColor(x, y); + if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c]) return []; //x isn't first rank, or king has moved (shortcut) // Castling ? const oppCol = V.GetOppCol(c); let moves = []; let i = 0; - const finalSquares = [ [2,3], [V.size.y-2,V.size.y-3] ]; //king, then rook - castlingCheck: - for (let castleSide=0; castleSide < 2; castleSide++) //large, then small - { - if (!this.castleFlags[c][castleSide]) - continue; + const finalSquares = [ + [2, 3], + [V.size.y - 2, V.size.y - 3] + ]; //king, then rook + castlingCheck: for ( + let castleSide = 0; + castleSide < 2; + castleSide++ //large, then small + ) { + if (!this.castleFlags[c][castleSide]) continue; // If this code is reached, rooks and king are on initial position // Nothing on the path of the king ? (and no checks) const finDist = finalSquares[castleSide][0] - y; let step = finDist / Math.max(1, Math.abs(finDist)); i = y; - do - { - if (this.isAttacked([x,i], [oppCol]) || (this.board[x][i] != V.EMPTY && - // NOTE: next check is enough, because of chessboard constraints - (this.getColor(x,i) != c - || ![V.KING,V.ROOK].includes(this.getPiece(x,i))))) - { + do { + if ( + this.isAttacked([x, i], [oppCol]) || + (this.board[x][i] != V.EMPTY && + // NOTE: next check is enough, because of chessboard constraints + (this.getColor(x, i) != c || + ![V.KING, V.ROOK].includes(this.getPiece(x, i)))) + ) { continue castlingCheck; } i += step; - } - while (i!=finalSquares[castleSide][0]); + } while (i != finalSquares[castleSide][0]); // Nothing on the path to the rook? - step = (castleSide == 0 ? -1 : 1); - for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) - { - if (this.board[x][i] != V.EMPTY) - continue castlingCheck; + step = castleSide == 0 ? -1 : 1; + for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) { + if (this.board[x][i] != V.EMPTY) continue castlingCheck; } const rookPos = this.INIT_COL_ROOK[c][castleSide]; // Nothing on final squares, except maybe king and castling rook? - for (i=0; i<2; i++) - { - if (this.board[x][finalSquares[castleSide][i]] != V.EMPTY && - this.getPiece(x,finalSquares[castleSide][i]) != V.KING && - finalSquares[castleSide][i] != rookPos) - { + for (i = 0; i < 2; i++) { + if ( + this.board[x][finalSquares[castleSide][i]] != V.EMPTY && + this.getPiece(x, finalSquares[castleSide][i]) != V.KING && + finalSquares[castleSide][i] != rookPos + ) { continue castlingCheck; } } // If this code is reached, castle is valid - moves.push( new Move({ - appear: [ - new PiPo({x:x,y:finalSquares[castleSide][0],p:V.KING,c:c}), - new PiPo({x:x,y:finalSquares[castleSide][1],p:V.ROOK,c:c})], - vanish: [ - new PiPo({x:x,y:y,p:V.KING,c:c}), - new PiPo({x:x,y:rookPos,p:V.ROOK,c:c})], - end: Math.abs(y - rookPos) <= 2 - ? {x:x, y:rookPos} - : {x:x, y:y + 2 * (castleSide==0 ? -1 : 1)} - }) ); + moves.push( + new Move({ + appear: [ + new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }), + new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c }) + ], + vanish: [ + new PiPo({ x: x, y: y, p: V.KING, c: c }), + new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c }) + ], + end: + Math.abs(y - rookPos) <= 2 + ? { x: x, y: rookPos } + : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) } + }) + ); } return moves; @@ -806,16 +777,13 @@ export const ChessRules = class ChessRules // MOVES VALIDATION // For the interface: possible moves for the current turn from square sq - getPossibleMovesFrom(sq) - { - return this.filterValid( this.getPotentialMovesFrom(sq) ); + getPossibleMovesFrom(sq) { + return this.filterValid(this.getPotentialMovesFrom(sq)); } // TODO: promotions (into R,B,N,Q) should be filtered only once - filterValid(moves) - { - if (moves.length == 0) - return []; + filterValid(moves) { + if (moves.length == 0) return []; const color = this.turn; return moves.filter(m => { this.play(m); @@ -827,20 +795,18 @@ export const ChessRules = class ChessRules // Search for all valid moves considering current turn // (for engine and game end) - getAllValidMoves() - { + getAllValidMoves() { const color = this.turn; const oppCol = V.GetOppCol(color); let potentialMoves = []; - for (let i=0; i 0) - { - for (let k=0; k 0) - return true; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { + const moves = this.getPotentialMovesFrom([i, j]); + if (moves.length > 0) { + for (let k = 0; k < moves.length; k++) { + if (this.filterValid([moves[k]]).length > 0) return true; } } } @@ -874,29 +833,29 @@ export const ChessRules = class ChessRules } // Check if pieces of color in 'colors' are attacking (king) on square x,y - isAttacked(sq, colors) - { - return (this.isAttackedByPawn(sq, colors) - || this.isAttackedByRook(sq, colors) - || this.isAttackedByKnight(sq, colors) - || this.isAttackedByBishop(sq, colors) - || this.isAttackedByQueen(sq, colors) - || this.isAttackedByKing(sq, colors)); + isAttacked(sq, colors) { + return ( + this.isAttackedByPawn(sq, colors) || + this.isAttackedByRook(sq, colors) || + this.isAttackedByKnight(sq, colors) || + this.isAttackedByBishop(sq, colors) || + this.isAttackedByQueen(sq, colors) || + this.isAttackedByKing(sq, colors) + ); } // Is square x,y attacked by 'colors' pawns ? - isAttackedByPawn([x,y], colors) - { - for (let c of colors) - { - let pawnShift = (c=="w" ? 1 : -1); - if (x+pawnShift>=0 && x+pawnShift=0 && y+i= 0 && x + pawnShift < V.size.x) { + for (let i of [-1, 1]) { + if ( + y + i >= 0 && + y + i < V.size.y && + this.getPiece(x + pawnShift, y + i) == V.PAWN && + this.getColor(x + pawnShift, y + i) == c + ) { return true; } } @@ -906,53 +865,62 @@ export const ChessRules = class ChessRules } // Is square x,y attacked by 'colors' rooks ? - isAttackedByRook(sq, colors) - { + isAttackedByRook(sq, colors) { return this.isAttackedBySlideNJump(sq, colors, V.ROOK, V.steps[V.ROOK]); } // Is square x,y attacked by 'colors' knights ? - isAttackedByKnight(sq, colors) - { - return this.isAttackedBySlideNJump(sq, colors, - V.KNIGHT, V.steps[V.KNIGHT], "oneStep"); + isAttackedByKnight(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.KNIGHT, + V.steps[V.KNIGHT], + "oneStep" + ); } // Is square x,y attacked by 'colors' bishops ? - isAttackedByBishop(sq, colors) - { + isAttackedByBishop(sq, colors) { return this.isAttackedBySlideNJump(sq, colors, V.BISHOP, V.steps[V.BISHOP]); } // Is square x,y attacked by 'colors' queens ? - isAttackedByQueen(sq, colors) - { - return this.isAttackedBySlideNJump(sq, colors, V.QUEEN, - V.steps[V.ROOK].concat(V.steps[V.BISHOP])); + isAttackedByQueen(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.QUEEN, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + ); } // Is square x,y attacked by 'colors' king(s) ? - isAttackedByKing(sq, colors) - { - return this.isAttackedBySlideNJump(sq, colors, V.KING, - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); + isAttackedByKing(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.KING, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + "oneStep" + ); } // Generic method for non-pawn pieces ("sliding or jumping"): // is x,y attacked by a piece of color in array 'colors' ? - isAttackedBySlideNJump([x,y], colors, piece, steps, oneStep) - { - for (let step of steps) - { - let rx = x+step[0], ry = y+step[1]; - while (V.OnBoard(rx,ry) && this.board[rx][ry] == V.EMPTY && !oneStep) - { + isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { + for (let step of steps) { + let rx = x + step[0], + ry = y + step[1]; + while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) { rx += step[0]; ry += step[1]; } - if (V.OnBoard(rx,ry) && this.getPiece(rx,ry) === piece - && colors.includes(this.getColor(rx,ry))) - { + if ( + V.OnBoard(rx, ry) && + this.getPiece(rx, ry) === piece && + colors.includes(this.getColor(rx, ry)) + ) { return true; } } @@ -960,8 +928,7 @@ export const ChessRules = class ChessRules } // Is color under check after his move ? - underCheck(color) - { + underCheck(color) { return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]); } @@ -969,70 +936,58 @@ export const ChessRules = class ChessRules // MOVES PLAYING // Apply a move on board - static PlayOnBoard(board, move) - { - for (let psq of move.vanish) - board[psq.x][psq.y] = V.EMPTY; - for (let psq of move.appear) - board[psq.x][psq.y] = psq.c + psq.p; + static PlayOnBoard(board, move) { + for (let psq of move.vanish) board[psq.x][psq.y] = V.EMPTY; + for (let psq of move.appear) board[psq.x][psq.y] = psq.c + psq.p; } // Un-apply the played move - static UndoOnBoard(board, move) - { - for (let psq of move.appear) - board[psq.x][psq.y] = V.EMPTY; - for (let psq of move.vanish) - board[psq.x][psq.y] = psq.c + psq.p; + static UndoOnBoard(board, move) { + for (let psq of move.appear) board[psq.x][psq.y] = V.EMPTY; + for (let psq of move.vanish) board[psq.x][psq.y] = psq.c + psq.p; } // After move is played, update variables + flags - updateVariables(move) - { + updateVariables(move) { let piece = undefined; let c = undefined; - if (move.vanish.length >= 1) - { + if (move.vanish.length >= 1) { // Usual case, something is moved piece = move.vanish[0].p; c = move.vanish[0].c; - } - else - { + } else { // Crazyhouse-like variants piece = move.appear[0].p; c = move.appear[0].c; } - if (c == "c") //if (!["w","b"].includes(c)) - { + if (c == "c") { + //if (!["w","b"].includes(c)) // 'c = move.vanish[0].c' doesn't work for Checkered c = V.GetOppCol(this.turn); } - const firstRank = (c == "w" ? V.size.x-1 : 0); + const firstRank = c == "w" ? V.size.x - 1 : 0; // Update king position + flags - if (piece == V.KING && move.appear.length > 0) - { + if (piece == V.KING && move.appear.length > 0) { this.kingPos[c][0] = move.appear[0].x; this.kingPos[c][1] = move.appear[0].y; - if (V.HasFlags) - this.castleFlags[c] = [false,false]; + if (V.HasFlags) this.castleFlags[c] = [false, false]; return; } - if (V.HasFlags) - { + if (V.HasFlags) { // Update castling flags if rooks are moved const oppCol = V.GetOppCol(c); - const oppFirstRank = (V.size.x-1) - firstRank; - if (move.start.x == firstRank //our rook moves? - && this.INIT_COL_ROOK[c].includes(move.start.y)) - { - const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1); + const oppFirstRank = V.size.x - 1 - firstRank; + if ( + move.start.x == firstRank && //our rook moves? + this.INIT_COL_ROOK[c].includes(move.start.y) + ) { + const flagIdx = move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1; this.castleFlags[c][flagIdx] = false; - } - else if (move.end.x == oppFirstRank //we took opponent rook? - && this.INIT_COL_ROOK[oppCol].includes(move.end.y)) - { - const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1); + } else if ( + move.end.x == oppFirstRank && //we took opponent rook? + this.INIT_COL_ROOK[oppCol].includes(move.end.y) + ) { + const flagIdx = move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1; this.castleFlags[oppCol][flagIdx] = false; } } @@ -1040,55 +995,48 @@ export const ChessRules = class ChessRules // After move is undo-ed *and flags resetted*, un-update other variables // TODO: more symmetry, by storing flags increment in move (?!) - unupdateVariables(move) - { + unupdateVariables(move) { // (Potentially) Reset king position - const c = this.getColor(move.start.x,move.start.y); - if (this.getPiece(move.start.x,move.start.y) == V.KING) + const c = this.getColor(move.start.x, move.start.y); + if (this.getPiece(move.start.x, move.start.y) == V.KING) this.kingPos[c] = [move.start.x, move.start.y]; } - play(move) - { + play(move) { // DEBUG: -// if (!this.states) this.states = []; -// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); -// this.states.push(stateFen); - - if (V.HasFlags) - move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo) - if (V.HasEnpassant) - this.epSquares.push( this.getEpSquare(move) ); + // if (!this.states) this.states = []; + // const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); + // this.states.push(stateFen); + + if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo) + if (V.HasEnpassant) this.epSquares.push(this.getEpSquare(move)); V.PlayOnBoard(this.board, move); this.turn = V.GetOppCol(this.turn); this.movesCount++; this.updateVariables(move); } - undo(move) - { - if (V.HasEnpassant) - this.epSquares.pop(); - if (V.HasFlags) - this.disaggregateFlags(JSON.parse(move.flags)); + undo(move) { + if (V.HasEnpassant) this.epSquares.pop(); + if (V.HasFlags) this.disaggregateFlags(JSON.parse(move.flags)); V.UndoOnBoard(this.board, move); this.turn = V.GetOppCol(this.turn); this.movesCount--; this.unupdateVariables(move); // DEBUG: -// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); -// if (stateFen != this.states[this.states.length-1]) debugger; -// this.states.pop(); + // const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); + // if (stateFen != this.states[this.states.length-1]) debugger; + // this.states.pop(); } /////////////// // END OF GAME // What is the score ? (Interesting if game is over) - getCurrentScore() - { - if (this.atLeastOneMove()) // game not over + getCurrentScore() { + if (this.atLeastOneMove()) + // game not over return "*"; // Game over @@ -1097,184 +1045,180 @@ export const ChessRules = class ChessRules if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])) return "1/2"; // OK, checkmate - return (color == "w" ? "0-1" : "1-0"); + return color == "w" ? "0-1" : "1-0"; } /////////////// // ENGINE PLAY // Pieces values - static get VALUES() - { + static get VALUES() { return { - 'p': 1, - 'r': 5, - 'n': 3, - 'b': 3, - 'q': 9, - 'k': 1000 + p: 1, + r: 5, + n: 3, + b: 3, + q: 9, + k: 1000 }; } // "Checkmate" (unreachable eval) - static get INFINITY() { return 9999; } + static get INFINITY() { + return 9999; + } // At this value or above, the game is over - static get THRESHOLD_MATE() { return V.INFINITY; } + static get THRESHOLD_MATE() { + return V.INFINITY; + } // Search depth: 2 for high branching factor, 4 for small (Loser chess, eg.) - static get SEARCH_DEPTH() { return 3; } + static get SEARCH_DEPTH() { + return 3; + } // NOTE: works also for extinction chess because depth is 3... - getComputerMove() - { + getComputerMove() { const maxeval = V.INFINITY; const color = this.turn; // Some variants may show a bigger moves list to the human (Switching), // thus the argument "computer" below (which is generally ignored) let moves1 = this.getAllValidMoves("computer"); - if (moves1.length == 0) //TODO: this situation should not happen + if (moves1.length == 0) + //TODO: this situation should not happen return null; // Can I mate in 1 ? (for Magnetic & Extinction) - for (let i of shuffle(ArrayFun.range(moves1.length))) - { + for (let i of shuffle(ArrayFun.range(moves1.length))) { this.play(moves1[i]); - let finish = (Math.abs(this.evalPosition()) >= V.THRESHOLD_MATE); - if (!finish) - { + let finish = Math.abs(this.evalPosition()) >= V.THRESHOLD_MATE; + if (!finish) { const score = this.getCurrentScore(); - if (["1-0","0-1"].includes(score)) - finish = true; + if (["1-0", "0-1"].includes(score)) finish = true; } this.undo(moves1[i]); - if (finish) - return moves1[i]; + if (finish) return moves1[i]; } // Rank moves using a min-max at depth 2 - for (let i=0; i eval2)) - { + let evalPos = 0; //1/2 value + switch (score2) { + case "*": + evalPos = this.evalPosition(); + break; + case "1-0": + evalPos = maxeval; + break; + case "0-1": + evalPos = -maxeval; + break; + } + if ( + (color == "w" && evalPos < eval2) || + (color == "b" && evalPos > eval2) + ) { eval2 = evalPos; } this.undo(moves2[j]); } - } - else - eval2 = (score1=="1/2" ? 0 : (score1=="1-0" ? 1 : -1) * maxeval); - if ((color=="w" && eval2 > moves1[i].eval) - || (color=="b" && eval2 < moves1[i].eval)) - { + } else eval2 = score1 == "1/2" ? 0 : (score1 == "1-0" ? 1 : -1) * maxeval; + if ( + (color == "w" && eval2 > moves1[i].eval) || + (color == "b" && eval2 < moves1[i].eval) + ) { moves1[i].eval = eval2; } this.undo(moves1[i]); } - moves1.sort( (a,b) => { return (color=="w" ? 1 : -1) * (b.eval - a.eval); }); + moves1.sort((a, b) => { + return (color == "w" ? 1 : -1) * (b.eval - a.eval); + }); let candidates = [0]; //indices of candidates moves - for (let j=1; j= 3 && Math.abs(moves1[0].eval) < V.THRESHOLD_MATE) - { + if (V.SEARCH_DEPTH >= 3 && Math.abs(moves1[0].eval) < V.THRESHOLD_MATE) { // From here, depth >= 3: may take a while, so we control time const timeStart = Date.now(); - for (let i=0; i= 5000) //more than 5 seconds + for (let i = 0; i < moves1.length; i++) { + if (Date.now() - timeStart >= 5000) + //more than 5 seconds return currentBest; //depth 2 at least this.play(moves1[i]); // 0.1 * oldEval : heuristic to avoid some bad moves (not all...) - moves1[i].eval = 0.1*moves1[i].eval + - this.alphabeta(V.SEARCH_DEPTH-1, -maxeval, maxeval); + moves1[i].eval = + 0.1 * moves1[i].eval + + this.alphabeta(V.SEARCH_DEPTH - 1, -maxeval, maxeval); this.undo(moves1[i]); } - moves1.sort( (a,b) => { - return (color=="w" ? 1 : -1) * (b.eval - a.eval); }); - } - else - return currentBest; -// console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; })); + moves1.sort((a, b) => { + return (color == "w" ? 1 : -1) * (b.eval - a.eval); + }); + } else return currentBest; + // console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; })); candidates = [0]; - for (let j=1; j= beta) - break; //beta cutoff + if (alpha >= beta) break; //beta cutoff } - } - else //color=="b" - { - for (let i=0; i= beta) - break; //alpha cutoff + if (alpha >= beta) break; //alpha cutoff } } return v; } - evalPosition() - { + evalPosition() { let evaluation = 0; // Just count material for now - for (let i=0; i move.appear.length) - { + if (move.vanish.length > move.appear.length) { // Capture const startColumn = V.CoordToColumn(move.start.y); notation = startColumn + "x" + finalSquare; - } - else //no capture - notation = finalSquare; - if (move.appear.length > 0 && move.appear[0].p != V.PAWN) //promotion + } //no capture + else notation = finalSquare; + if (move.appear.length > 0 && move.appear[0].p != V.PAWN) + //promotion notation += "=" + move.appear[0].p.toUpperCase(); return notation; } - - else - { - // Piece movement - return piece.toUpperCase() + - (move.vanish.length > move.appear.length ? "x" : "") + finalSquare; - } - } -} + // Piece movement + return ( + piece.toUpperCase() + + (move.vanish.length > move.appear.length ? "x" : "") + + finalSquare + ); + } +}; diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index ef6ef01e..b2dd4a72 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -1,25 +1,47 @@ @@ -53,13 +86,13 @@ import { getDate } from "@/utils/datetime"; import { processModalClick } from "@/utils/modalClick"; import { getScoreMessage } from "@/utils/scoring"; export default { - name: 'my-base-game', + name: "my-base-game", components: { Board, - MoveList, + MoveList }, // "vr": VariantRules object, describing the game state + rules - props: ["vr","game"], + props: ["vr", "game"], data: function() { return { st: store.state, @@ -71,7 +104,7 @@ export default { cursor: -1, //index of the move just played lastMove: null, firstMoveNumber: 0, //for printing - incheck: [], //for Board + incheck: [] //for Board }; }, watch: { @@ -80,62 +113,54 @@ export default { this.re_setVariables(); }, // Received a new move to play: - "game.moveToPlay": function(newMove) { - if (!!newMove) //if stop + launch new game, get undefined move - this.play(newMove, "receive"); + "game.moveToPlay": function(move) { + if (move) this.play(move, "receive"); }, // ...Or to undo (corr game, move not validated) "game.moveToUndo": function(move) { - if (!!move) - this.undo(move); - }, + if (move) this.undo(move); + } }, computed: { showMoves: function() { return this.game.vname != "Dark" || this.game.score != "*"; }, - turn: function() { - let color = ""; - const L = this.moves.length; - if (L == 0 || this.moves[L-1].color == "b") - color = "White"; - else //if (this.moves[L-1].color == "w") - color = "Black"; - return this.st.tr[color + " to move"]; - }, analyze: function() { - return this.game.mode=="analyze" || + return ( + this.game.mode == "analyze" || // From Board viewpoint, a finished Dark game == analyze (TODO: unclear) - (this.game.vname == "Dark" && this.game.score != "*"); - }, + (this.game.vname == "Dark" && this.game.score != "*") + ); + } }, created: function() { - if (!!this.game.fenStart) - this.re_setVariables(); + if (this.game.fenStart) this.re_setVariables(); }, mounted: function() { - [document.getElementById("eogDiv"),document.getElementById("adjuster")] - .forEach(elt => elt.addEventListener("click", processModalClick)); + [ + document.getElementById("eogDiv"), + document.getElementById("adjuster") + ].forEach(elt => elt.addEventListener("click", processModalClick)); // Take full width on small screens: let boardSize = parseInt(localStorage.getItem("boardSize")); - if (!boardSize) - { - boardSize = (window.innerWidth >= 768 - ? 0.75 * Math.min(window.innerWidth, window.innerHeight) - : window.innerWidth); + if (!boardSize) { + boardSize = + window.innerWidth >= 768 + ? 0.75 * Math.min(window.innerWidth, window.innerHeight) + : window.innerWidth; } - const movesWidth = (window.innerWidth >= 768 ? 280 : 0); + const movesWidth = window.innerWidth >= 768 ? 280 : 0; document.getElementById("boardContainer").style.width = boardSize + "px"; let gameContainer = document.getElementById("gameContainer"); - gameContainer.style.width = (boardSize + movesWidth) + "px"; - document.getElementById("boardSize").value = (boardSize * 100) / (window.innerWidth - movesWidth); + gameContainer.style.width = boardSize + movesWidth + "px"; + document.getElementById("boardSize").value = + (boardSize * 100) / (window.innerWidth - movesWidth); // timeout to avoid calling too many time the adjust method let timeoutLaunched = false; - window.addEventListener("resize", (e) => { - if (!timeoutLaunched) - { + window.addEventListener("resize", () => { + if (!timeoutLaunched) { timeoutLaunched = true; - setTimeout( () => { + setTimeout(() => { this.adjustBoard(); timeoutLaunched = false; }, 500); @@ -149,24 +174,22 @@ export default { }, adjustBoard: function() { const boardContainer = document.getElementById("boardContainer"); - if (!boardContainer) - return; //no board on page + if (!boardContainer) return; //no board on page const k = document.getElementById("boardSize").value; - const movesWidth = (window.innerWidth >= 768 ? 280 : 0); + const movesWidth = window.innerWidth >= 768 ? 280 : 0; const minBoardWidth = 240; //TODO: these 240 and 280 are arbitrary... // Value of 0 is board min size; 100 is window.width [- movesWidth] - const boardSize = minBoardWidth + - k * (window.innerWidth - (movesWidth+minBoardWidth)) / 100; + const boardSize = + minBoardWidth + + (k * (window.innerWidth - (movesWidth + minBoardWidth))) / 100; localStorage.setItem("boardSize", boardSize); boardContainer.style.width = boardSize + "px"; document.getElementById("gameContainer").style.width = - (boardSize + movesWidth) + "px"; + boardSize + movesWidth + "px"; }, handleKeys: function(e) { - if ([32,37,38,39,40].includes(e.keyCode)) - e.preventDefault(); - switch (e.keyCode) - { + if ([32, 37, 38, 39, 40].includes(e.keyCode)) e.preventDefault(); + switch (e.keyCode) { case 37: this.undo(); break; @@ -186,13 +209,10 @@ export default { }, handleScroll: function(e) { // NOTE: since game.mode=="analyze" => no score, next condition is enough - if (this.game.score != "*") - { + if (this.game.score != "*") { e.preventDefault(); - if (e.deltaY < 0) - this.undo(); - else if (e.deltaY > 0) - this.play(); + if (e.deltaY < 0) this.undo(); + else if (e.deltaY > 0) this.play(); } }, showRules: function() { @@ -206,8 +226,9 @@ export default { // Post-processing: decorate each move with color + current FEN: // (to be able to jump to any position quickly) let vr_tmp = new V(this.game.fenStart); //vr is already at end of game - this.firstMoveNumber = - Math.floor(V.ParseFen(this.game.fenStart).movesCount / 2); + this.firstMoveNumber = Math.floor( + V.ParseFen(this.game.fenStart).movesCount / 2 + ); this.moves.forEach(move => { // NOTE: this is doing manually what play() function below achieve, // but in a lighter "fast-forward" way @@ -216,31 +237,39 @@ export default { vr_tmp.play(move); move.fen = vr_tmp.getFen(); }); - if ((this.moves.length > 0 && this.moves[0].color == "b") || - (this.moves.length == 0 && this.vr_tmp.turn == "b")) - { + if ( + (this.moves.length > 0 && this.moves[0].color == "b") || + (this.moves.length == 0 && vr_tmp.turn == "b") + ) { // 'end' is required for Board component to check lastMove for e.p. - this.moves.unshift({color: "w", notation: "...", end: {x:-1,y:-1}}); + this.moves.unshift({ + color: "w", + notation: "...", + end: { x: -1, y: -1 } + }); } const L = this.moves.length; - this.cursor = L-1; - this.lastMove = (L > 0 ? this.moves[L-1] : null); + this.cursor = L - 1; + this.lastMove = L > 0 ? this.moves[L - 1] : null; this.incheck = this.vr.getCheckSquares(this.vr.turn); }, analyzePosition: function() { - const newUrl = "/analyse/" + this.game.vname + - "/?fen=" + this.vr.getFen().replace(/ /g, "_"); - if (this.game.type == "live") - this.$router.push(newUrl); //open in same tab: against cheating... - else - window.open("#" + newUrl); //open in a new tab: more comfortable + const newUrl = + "/analyse/" + + this.game.vname + + "/?fen=" + + this.vr.getFen().replace(/ /g, "_"); + if (this.game.type == "live") this.$router.push(newUrl); + //open in same tab: against cheating... + else window.open("#" + newUrl); //open in a new tab: more comfortable }, download: function() { const content = this.getPgn(); // Prepare and trigger download link let downloadAnchor = document.getElementById("download"); downloadAnchor.setAttribute("download", "game.pgn"); - downloadAnchor.href = "data:text/plain;charset=utf-8," + encodeURIComponent(content); + downloadAnchor.href = + "data:text/plain;charset=utf-8," + encodeURIComponent(content); downloadAnchor.click(); }, getPgn: function() { @@ -254,15 +283,13 @@ export default { pgn += '[Result "' + this.game.score + '"]\n\n'; let counter = 1; let i = 0; - while (i < this.moves.length) - { - pgn += (counter++) + "."; - for (let color of ["w","b"]) - { + while (i < this.moves.length) { + pgn += counter++ + "."; + for (let color of ["w", "b"]) { let move = ""; while (i < this.moves.length && this.moves[i].color == color) move += this.moves[i++].notation + ","; - move = move.slice(0,-1); //remove last comma + move = move.slice(0, -1); //remove last comma pgn += move + (i < this.moves.length ? " " : ""); } } @@ -272,37 +299,41 @@ export default { this.endgameMessage = message; let modalBox = document.getElementById("modalEog"); modalBox.checked = true; - setTimeout(() => { modalBox.checked = false; }, 2000); + setTimeout(() => { + modalBox.checked = false; + }, 2000); }, animateMove: function(move, callback) { let startSquare = document.getElementById(getSquareId(move.start)); // TODO: error "flush nextTick callbacks" when observer reloads page: // this late check is not a fix! - if (!startSquare) - return; + if (!startSquare) return; let endSquare = document.getElementById(getSquareId(move.end)); let rectStart = startSquare.getBoundingClientRect(); let rectEnd = endSquare.getBoundingClientRect(); - let translation = {x:rectEnd.x-rectStart.x, y:rectEnd.y-rectStart.y}; - let movingPiece = - document.querySelector("#" + getSquareId(move.start) + " > img.piece"); - if (!movingPiece) //TODO: shouldn't happen + let translation = { + x: rectEnd.x - rectStart.x, + y: rectEnd.y - rectStart.y + }; + let movingPiece = document.querySelector( + "#" + getSquareId(move.start) + " > img.piece" + ); + if (!movingPiece) + //TODO: shouldn't happen return; // HACK for animation (with positive translate, image slides "under background") // Possible improvement: just alter squares on the piece's way... const squares = document.getElementsByClassName("board"); - for (let i=0; i { - for (let i=0; i { + for (let i = 0; i < squares.length; i++) squares.item(i).style.zIndex = "auto"; movingPiece.style = {}; //required e.g. for 0-0 with KR swap callback(); @@ -313,22 +344,20 @@ export default { const navigate = !move; // Forbid playing outside analyze mode, except if move is received. // Sufficient condition because Board already knows which turn it is. - if (!navigate && this.game.mode!="analyze" && !receive - && (this.game.score != "*" || this.cursor < this.moves.length-1)) - { + if ( + !navigate && + this.game.mode != "analyze" && + !receive && + (this.game.score != "*" || this.cursor < this.moves.length - 1) + ) { return; } const doPlayMove = () => { - if (!!receive && this.cursor < this.moves.length-1) - this.gotoEnd(); //required to play the move - if (navigate) - { - if (this.cursor == this.moves.length-1) - return; //no more moves - move = this.moves[this.cursor+1]; - } - else - { + if (!!receive && this.cursor < this.moves.length - 1) this.gotoEnd(); //required to play the move + if (navigate) { + if (this.cursor == this.moves.length - 1) return; //no more moves + move = this.moves[this.cursor + 1]; + } else { move.color = this.vr.turn; move.notation = this.vr.getNotation(move); } @@ -336,51 +365,43 @@ export default { this.cursor++; this.lastMove = move; if (this.st.settings.sound == 2) - new Audio("/sounds/move.mp3").play().catch(err => {}); - if (!navigate) - { + new Audio("/sounds/move.mp3").play().catch(() => {}); + if (!navigate) { move.fen = this.vr.getFen(); // Stack move on movesList at current cursor - if (this.cursor == this.moves.length) - this.moves.push(move); - else - this.moves = this.moves.slice(0,this.cursor).concat([move]); + if (this.cursor == this.moves.length) this.moves.push(move); + else this.moves = this.moves.slice(0, this.cursor).concat([move]); } // Is opponent in check? this.incheck = this.vr.getCheckSquares(this.vr.turn); const score = this.vr.getCurrentScore(); - if (score != "*") - { + if (score != "*") { const message = getScoreMessage(score); if (this.game.mode != "analyze") this.$emit("gameover", score, message); - else //just show score on screen (allow undo) - this.showEndgameMsg(score + " . " + message); + //just show score on screen (allow undo) + else this.showEndgameMsg(score + " . " + message); } - if (!navigate && this.game.mode!="analyze") + if (!navigate && this.game.mode != "analyze") this.$emit("newmove", move); //post-processing (e.g. computer play) }; if (!!receive && this.game.vname != "Dark") this.animateMove(move, doPlayMove); - else - doPlayMove(); + else doPlayMove(); }, undo: function(move) { const navigate = !move; - if (navigate) - { - if (this.cursor < 0) - return; //no more moves + if (navigate) { + if (this.cursor < 0) return; //no more moves move = this.moves[this.cursor]; } this.vr.undo(move); this.cursor--; - this.lastMove = (this.cursor >= 0 ? this.moves[this.cursor] : undefined); + this.lastMove = this.cursor >= 0 ? this.moves[this.cursor] : undefined; if (this.st.settings.sound == 2) - new Audio("/sounds/undo.mp3").play().catch(err => {}); + new Audio("/sounds/undo.mp3").play().catch(() => {}); this.incheck = this.vr.getCheckSquares(this.vr.turn); - if (!navigate) - this.moves.pop(); + if (!navigate) this.moves.pop(); }, gotoMove: function(index) { this.vr.re_init(this.moves[index].fen); @@ -388,29 +409,24 @@ export default { this.lastMove = this.moves[index]; }, gotoBegin: function() { - if (this.cursor == -1) - return; + if (this.cursor == -1) return; this.vr.re_init(this.game.fenStart); - if (this.moves.length > 0 && this.moves[0].notation == "...") - { + if (this.moves.length > 0 && this.moves[0].notation == "...") { this.cursor = 0; this.lastMove = this.moves[0]; - } - else - { + } else { this.cursor = -1; this.lastMove = null; } }, gotoEnd: function() { - if (this.cursor == this.moves.length - 1) - return; - this.gotoMove(this.moves.length-1); + if (this.cursor == this.moves.length - 1) return; + this.gotoMove(this.moves.length - 1); }, flip: function() { this.orientation = V.GetOppCol(this.orientation); - }, - }, + } + } }; diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index a7920d35..45c7293b 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -3,118 +3,130 @@ import { getSquareId, getSquareFromId } from "@/utils/squareId"; import { ArrayFun } from "@/utils/array"; import { store } from "@/store"; export default { - name: 'my-board', + name: "my-board", // Last move cannot be guessed from here, and is required to highlight squares // vr: object to check moves, print board... // userColor is left undefined for an external observer - props: ["vr","lastMove","analyze","incheck","orientation","userColor","vname"], - data: function () { + props: [ + "vr", + "lastMove", + "analyze", + "incheck", + "orientation", + "userColor", + "vname" + ], + data: function() { return { possibleMoves: [], //filled after each valid click/dragstart choices: [], //promotion pieces, or checkered captures... (as moves) selectedPiece: null, //moving piece (or clicked piece) start: {}, //pixels coordinates + id of starting square (click or drag) - settings: store.state.settings, + settings: store.state.settings }; }, render(h) { - if (!this.vr) - { + if (!this.vr) { // Return empty div of class 'game' to avoid error when setting size - return h("div", - { - "class": { - "game": true, - }, - }); + return h("div", { + class: { + game: true + } + }); } - const [sizeX,sizeY] = [V.size.x,V.size.y]; + const [sizeX, sizeY] = [V.size.x, V.size.y]; // Precompute hints squares to facilitate rendering let hintSquares = ArrayFun.init(sizeX, sizeY, false); - this.possibleMoves.forEach(m => { hintSquares[m.end.x][m.end.y] = true; }); + this.possibleMoves.forEach(m => { + hintSquares[m.end.x][m.end.y] = true; + }); // Also precompute in-check squares let incheckSq = ArrayFun.init(sizeX, sizeY, false); - this.incheck.forEach(sq => { incheckSq[sq[0]][sq[1]] = true; }); + this.incheck.forEach(sq => { + incheckSq[sq[0]][sq[1]] = true; + }); // Create board element (+ reserves if needed by variant or mode) const lm = this.lastMove; const showLight = this.settings.highlight && this.vname != "Dark"; const gameDiv = h( - 'div', + "div", { - 'class': { - 'game': true, - 'clearer': true, - }, + class: { + game: true, + clearer: true + } }, [...Array(sizeX).keys()].map(i => { - let ci = (this.orientation=='w' ? i : sizeX-i-1); + let ci = this.orientation == "w" ? i : sizeX - i - 1; return h( - 'div', + "div", { - 'class': { - 'row': true, + class: { + row: true }, - style: { 'opacity': this.choices.length>0?"0.5":"1" }, + style: { opacity: this.choices.length > 0 ? "0.5" : "1" } }, [...Array(sizeY).keys()].map(j => { - let cj = (this.orientation=='w' ? j : sizeY-j-1); + let cj = this.orientation == "w" ? j : sizeY - j - 1; let elems = []; - if (this.vr.board[ci][cj] != V.EMPTY && (this.vname!="Dark" - || this.analyze || (!!this.userColor - && this.vr.enlightened[this.userColor][ci][cj]))) - { + if ( + this.vr.board[ci][cj] != V.EMPTY && + (this.vname != "Dark" || + this.analyze || + (!!this.userColor && + this.vr.enlightened[this.userColor][ci][cj])) + ) { elems.push( - h( - 'img', - { - 'class': { - 'piece': true, - 'ghost': !!this.selectedPiece - && this.selectedPiece.parentNode.id == "sq-"+ci+"-"+cj, - }, - attrs: { - src: "/images/pieces/" + - V.getPpath(this.vr.board[ci][cj]) + ".svg", - }, + h("img", { + class: { + piece: true, + ghost: + !!this.selectedPiece && + this.selectedPiece.parentNode.id == "sq-" + ci + "-" + cj + }, + attrs: { + src: + "/images/pieces/" + + V.getPpath(this.vr.board[ci][cj]) + + ".svg" } - ) + }) ); } - if (this.settings.hints && hintSquares[ci][cj]) - { + if (this.settings.hints && hintSquares[ci][cj]) { elems.push( - h( - 'img', - { - 'class': { - 'mark-square': true, - }, - attrs: { - src: "/images/mark.svg", - }, + h("img", { + class: { + "mark-square": true + }, + attrs: { + src: "/images/mark.svg" } - ) + }) ); } return h( - 'div', + "div", { - 'class': { - 'board': true, - ['board'+sizeY]: true, - 'light-square': (i+j)%2==0, - 'dark-square': (i+j)%2==1, + class: { + board: true, + ["board" + sizeY]: true, + "light-square": (i + j) % 2 == 0, + "dark-square": (i + j) % 2 == 1, [this.settings.bcolor]: true, - 'in-shadow': this.vname=="Dark" && !this.analyze - && (!this.userColor - || !this.vr.enlightened[this.userColor][ci][cj]), - 'highlight': showLight && !!lm && lm.end.x == ci && lm.end.y == cj, - 'incheck': showLight && incheckSq[ci][cj], + "in-shadow": + this.vname == "Dark" && + !this.analyze && + (!this.userColor || + !this.vr.enlightened[this.userColor][ci][cj]), + highlight: + showLight && !!lm && lm.end.x == ci && lm.end.y == cj, + incheck: showLight && incheckSq[ci][cj] }, attrs: { - id: getSquareId({x:ci,y:cj}), - }, + id: getSquareId({ x: ci, y: cj }) + } }, elems ); @@ -124,118 +136,134 @@ export default { ); let elementArray = [gameDiv]; const playingColor = this.userColor || "w"; //default for an observer - if (!!this.vr.reserve) - { - const shiftIdx = (playingColor=="w" ? 0 : 1); + if (this.vr.reserve) { + const shiftIdx = playingColor == "w" ? 0 : 1; let myReservePiecesArray = []; - for (let i=0; i 0 && !!boardElt) //no choices to show at first drawing - { + if (this.choices.length > 0 && !!boardElt) { + //no choices to show at first drawing const squareWidth = boardElt.offsetWidth / sizeY; const offset = [boardElt.offsetTop, boardElt.offsetLeft]; const choices = h( - 'div', + "div", { - attrs: { "id": "choices" }, - 'class': { 'row': true }, + attrs: { id: "choices" }, + class: { row: true }, style: { - "top": (offset[0] + (sizeY/2)*squareWidth-squareWidth/2) + "px", - "left": (offset[1] + squareWidth*(sizeY - this.choices.length)/2) + "px", - "width": (this.choices.length * squareWidth) + "px", - "height": squareWidth + "px", - }, + top: offset[0] + (sizeY / 2) * squareWidth - squareWidth / 2 + "px", + left: + offset[1] + + (squareWidth * (sizeY - this.choices.length)) / 2 + + "px", + width: this.choices.length * squareWidth + "px", + height: squareWidth + "px" + } }, - this.choices.map(m => { //a "choice" is a move - return h('div', + this.choices.map(m => { + //a "choice" is a move + return h( + "div", { - 'class': { - 'board': true, - ['board'+sizeY]: true, + class: { + board: true, + ["board" + sizeY]: true }, style: { - 'width': (100/this.choices.length) + "%", - 'padding-bottom': (100/this.choices.length) + "%", - }, + width: 100 / this.choices.length + "%", + "padding-bottom": 100 / this.choices.length + "%" + } }, - [h('img', - { - attrs: { "src": '/images/pieces/' + - V.getPpath(m.appear[0].c+m.appear[0].p) + '.svg' }, - 'class': { 'choice-piece': true }, - on: { - "click": e => { this.play(m); this.choices=[]; }, + [ + h("img", { + attrs: { + src: + "/images/pieces/" + + V.getPpath(m.appear[0].c + m.appear[0].p) + + ".svg" }, + class: { "choice-piece": true }, + on: { + click: () => { + this.play(m); + this.choices = []; + } + } }) ] ); @@ -245,95 +273,83 @@ export default { } let onEvents = {}; // NOTE: click = mousedown + mouseup - if ('ontouchstart' in window) - { + if ("ontouchstart" in window) { onEvents = { on: { touchstart: this.mousedown, touchmove: this.mousemove, - touchend: this.mouseup, - }, + touchend: this.mouseup + } }; - } - else - { + } else { onEvents = { on: { mousedown: this.mousedown, mousemove: this.mousemove, - mouseup: this.mouseup, - }, + mouseup: this.mouseup + } }; } - return h( - 'div', - onEvents, - elementArray - ); + return h("div", onEvents, elementArray); }, methods: { mousedown: function(e) { // Abort if a piece is already being processed, or target is not a piece. // NOTE: just looking at classList[0] because piece is the first assigned class - if (!!this.selectedPiece || e.target.classList[0] != "piece") - return; + if (!!this.selectedPiece || e.target.classList[0] != "piece") return; e.preventDefault(); //disable native drag & drop let parent = e.target.parentNode; //the surrounding square // Next few lines to center the piece on mouse cursor let rect = parent.getBoundingClientRect(); this.start = { - x: rect.x + rect.width/2, - y: rect.y + rect.width/2, - id: parent.id, + x: rect.x + rect.width / 2, + y: rect.y + rect.width / 2, + id: parent.id }; this.selectedPiece = e.target.cloneNode(); - let spStyle = this.selectedPiece.style + let spStyle = this.selectedPiece.style; spStyle.position = "absolute"; spStyle.top = 0; spStyle.display = "inline-block"; spStyle.zIndex = 3000; const startSquare = getSquareFromId(parent.id); this.possibleMoves = []; - const color = (this.analyze ? this.vr.turn : this.userColor); - if (this.vr.canIplay(color,startSquare)) + const color = this.analyze ? this.vr.turn : this.userColor; + if (this.vr.canIplay(color, startSquare)) this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare); // Next line add moving piece just after current image // (required for Crazyhouse reserve) parent.insertBefore(this.selectedPiece, e.target.nextSibling); }, mousemove: function(e) { - if (!this.selectedPiece) - return; + if (!this.selectedPiece) return; // There is an active element: move it around - const [offsetX,offsetY] = !!e.clientX - ? [e.clientX,e.clientY] //desktop browser + const [offsetX, offsetY] = e.clientX + ? [e.clientX, e.clientY] //desktop browser : [e.changedTouches[0].pageX, e.changedTouches[0].pageY]; //smartphone - this.selectedPiece.style.left = (offsetX-this.start.x) + "px"; - this.selectedPiece.style.top = (offsetY-this.start.y) + "px"; + this.selectedPiece.style.left = offsetX - this.start.x + "px"; + this.selectedPiece.style.top = offsetY - this.start.y + "px"; }, mouseup: function(e) { - if (!this.selectedPiece) - return; + if (!this.selectedPiece) return; // There is an active element: obtain the move from start and end squares this.selectedPiece.style.zIndex = -3000; //HACK to find square from final coords - const [offsetX,offsetY] = !!e.clientX - ? [e.clientX,e.clientY] + const [offsetX, offsetY] = e.clientX + ? [e.clientX, e.clientY] : [e.changedTouches[0].pageX, e.changedTouches[0].pageY]; let landing = document.elementFromPoint(offsetX, offsetY); this.selectedPiece.style.zIndex = 3000; // Next condition: classList.contains(piece) fails because of marks - while (landing.tagName == "IMG") - landing = landing.parentNode; - if (this.start.id == landing.id) //one or multi clicks on same piece + while (landing.tagName == "IMG") landing = landing.parentNode; + if (this.start.id == landing.id) + //one or multi clicks on same piece return; // OK: process move attempt, landing is a square node let endSquare = getSquareFromId(landing.id); let moves = this.findMatchingMoves(endSquare); this.possibleMoves = []; - if (moves.length > 1) - this.choices = moves; - else if (moves.length==1) - this.play(moves[0]); + if (moves.length > 1) this.choices = moves; + else if (moves.length == 1) this.play(moves[0]); // Else: impossible move this.selectedPiece.parentNode.removeChild(this.selectedPiece); delete this.selectedPiece; @@ -343,15 +359,14 @@ export default { // Run through moves list and return the matching set (if promotions...) let moves = []; this.possibleMoves.forEach(function(m) { - if (endSquare[0] == m.end.x && endSquare[1] == m.end.y) - moves.push(m); + if (endSquare[0] == m.end.x && endSquare[1] == m.end.y) moves.push(m); }); return moves; }, play: function(move) { - this.$emit('play-move', move); - }, - }, + this.$emit("play-move", move); + } + } }; diff --git a/client/src/components/ChallengeList.vue b/client/src/components/ChallengeList.vue index f6d3090e..92c42f7c 100644 --- a/client/src/components/ChallengeList.vue +++ b/client/src/components/ChallengeList.vue @@ -20,32 +20,29 @@ export default { props: ["challenges"], data: function() { return { - st: store.state, + st: store.state }; }, computed: { sortedChallenges: function() { // Show in order: challenges I sent, challenges I received, other challenges - let minAdded = Number.MAX_SAFE_INTEGER - let maxAdded = 0 + let minAdded = Number.MAX_SAFE_INTEGER; + let maxAdded = 0; let augmentedChalls = this.challenges.map(c => { let priority = 0; - if (!!c.to && c.to == this.st.user.name) - priority = 1; + if (!!c.to && c.to == this.st.user.name) priority = 1; else if (c.from.sid == this.st.user.sid || c.from.id == this.st.user.id) priority = 2; - if (c.added < minAdded) - minAdded = c.added; - if (c.added > maxAdded) - maxAdded = c.added - return Object.assign({}, c, {priority: priority}); + if (c.added < minAdded) minAdded = c.added; + if (c.added > maxAdded) maxAdded = c.added; + return Object.assign({}, c, { priority: priority }); }); const deltaAdded = maxAdded - minAdded; - return augmentedChalls.sort((c1,c2) => { + return augmentedChalls.sort((c1, c2) => { return c2.priority - c1.priority + (c2.added - c1.added) / deltaAdded; }); - }, - }, + } + } }; diff --git a/client/src/components/Chat.vue b/client/src/components/Chat.vue index ae85e736..7205f3b7 100644 --- a/client/src/components/Chat.vue +++ b/client/src/components/Chat.vue @@ -13,38 +13,40 @@ import { store } from "@/store"; export default { name: "my-chat", // Prop 'pastChats' for corr games where chats are on server - props: ["players","pastChats","newChat"], + props: ["players", "pastChats", "newChat"], data: function() { return { st: store.state, - chats: [], //chat messages after human game + chats: [] //chat messages after human game }; }, watch: { newChat: function(chat) { if (chat.msg != "") - this.chats.unshift({msg:chat.msg, name:chat.name || "@nonymous"}); - }, + this.chats.unshift({ msg: chat.msg, name: chat.name || "@nonymous" }); + } }, methods: { classObject: function(chat) { return { "my-chatmsg": chat.name == this.st.user.name, - "opp-chatmsg": !!this.players && this.players.some( - p => p.name == chat.name && p.name != this.st.user.name) + "opp-chatmsg": + !!this.players && + this.players.some( + p => p.name == chat.name && p.name != this.st.user.name + ) }; }, sendChat: function() { let chatInput = document.getElementById("inputChat"); const chatTxt = chatInput.value.trim(); - if (chatTxt == "") - return; //nothing to send + if (chatTxt == "") return; //nothing to send chatInput.value = ""; - const chat = {msg:chatTxt, name: this.st.user.name || "@nonymous"}; + const chat = { msg: chatTxt, name: this.st.user.name || "@nonymous" }; this.$emit("mychat", chat); this.chats.unshift(chat); - }, - }, + } + } }; diff --git a/client/src/components/ComputerGame.vue b/client/src/components/ComputerGame.vue index 3bbdc046..402bc7fa 100644 --- a/client/src/components/ComputerGame.vue +++ b/client/src/components/ComputerGame.vue @@ -9,7 +9,7 @@ import Worker from "worker-loader!@/playCompMove"; export default { name: "my-computer-game", components: { - BaseGame, + BaseGame }, // gameInfo: fen + mode + vname // mode: "auto" (game comp vs comp) or "versus" (normal) @@ -22,7 +22,7 @@ export default { // Web worker to play computer moves without freezing interface: timeStart: undefined, //time when computer starts thinking compThink: false, //avoid asking a new move while one is being searched - compWorker: null, + compWorker: null }; }, watch: { @@ -30,13 +30,11 @@ export default { this.launchGame(); }, "gameInfo.score": function(newScore) { - if (newScore != "*") - { + if (newScore != "*") { this.game.score = newScore; //user action - if (!this.compThink) - this.$emit("game-stopped"); //otherwise wait for comp + if (!this.compThink) this.$emit("game-stopped"); //otherwise wait for comp } - }, + } }, // Modal end of game, and then sub-components created: function() { @@ -44,60 +42,51 @@ export default { this.compWorker = new Worker(); this.compWorker.onmessage = e => { let compMove = e.data; - if (!compMove) - { + if (!compMove) { this.compThink = false; this.$emit("game-stopped"); //no more moves: mate or stalemate return; //after game ends, no more moves, nothing to do } - if (!Array.isArray(compMove)) - compMove = [compMove]; //to deal with MarseilleRules + if (!Array.isArray(compMove)) compMove = [compMove]; //to deal with MarseilleRules // Small delay for the bot to appear "more human" - const delay = Math.max(500-(Date.now()-this.timeStart), 0); + const delay = Math.max(500 - (Date.now() - this.timeStart), 0); setTimeout(() => { - if (this.currentUrl != document.location.href) - return; //page change + if (this.currentUrl != document.location.href) return; //page change // NOTE: Dark and 2-moves are incompatible - const animate = (this.gameInfo.vname != "Dark"); - const animDelay = (animate ? 250 : 0); + const animate = this.gameInfo.vname != "Dark"; + const animDelay = animate ? 250 : 0; let moveIdx = 0; let self = this; (function executeMove() { self.$set(self.game, "moveToPlay", compMove[moveIdx++]); - if (moveIdx >= compMove.length) - { + if (moveIdx >= compMove.length) { self.compThink = false; - if (self.game.score != "*") //user action + if (self.game.score != "*") + //user action self.$emit("game-stopped"); - } - else - setTimeout(executeMove, 500 + animDelay); + } else setTimeout(executeMove, 500 + animDelay); })(); }, delay); - } - if (!!this.gameInfo.fen) - this.launchGame(); + }; + if (this.gameInfo.fen) this.launchGame(); }, methods: { launchGame: function() { - this.compWorker.postMessage(["scripts",this.gameInfo.vname]); - this.compWorker.postMessage(["init",this.gameInfo.fen]); + this.compWorker.postMessage(["scripts", this.gameInfo.vname]); + this.compWorker.postMessage(["init", this.gameInfo.fen]); this.vr = new V(this.gameInfo.fen); - const mycolor = (Math.random() < 0.5 ? "w" : "b"); - let players = [{name:"Myself"},{name:"Computer"}]; - if (mycolor == "b") - players = players.reverse(); + const mycolor = Math.random() < 0.5 ? "w" : "b"; + let players = [{ name: "Myself" }, { name: "Computer" }]; + if (mycolor == "b") players = players.reverse(); this.currentUrl = document.location.href; //to avoid playing outside page // NOTE: fen and fenStart are redundant in game object - this.game = Object.assign({}, - this.gameInfo, - { - fenStart: this.gameInfo.fen, - players: players, - mycolor: mycolor, - score: "*", - }); - this.compWorker.postMessage(["init",this.gameInfo.fen]); + this.game = Object.assign({}, this.gameInfo, { + fenStart: this.gameInfo.fen, + players: players, + mycolor: mycolor, + score: "*" + }); + this.compWorker.postMessage(["init", this.gameInfo.fen]); if (mycolor != "w" || this.gameInfo.mode == "auto") this.playComputerMove(); }, @@ -107,14 +96,14 @@ export default { this.compWorker.postMessage(["askmove"]); }, processMove: function(move) { - if (this.game.score != "*") - return; + if (this.game.score != "*") return; // Send the move to web worker (including his own moves) - this.compWorker.postMessage(["newmove",move]); + this.compWorker.postMessage(["newmove", move]); // subTurn condition for Marseille (and Avalanche) rules - if ((!this.vr.subTurn || this.vr.subTurn <= 1) - && (this.gameInfo.mode == "auto" || this.vr.turn != this.game.mycolor)) - { + if ( + (!this.vr.subTurn || this.vr.subTurn <= 1) && + (this.gameInfo.mode == "auto" || this.vr.turn != this.game.mycolor) + ) { this.playComputerMove(); } }, @@ -122,7 +111,7 @@ export default { this.game.score = score; this.game.scoreMsg = scoreMsg; this.$emit("game-over", score); //bubble up to Rules.vue - }, - }, + } + } }; diff --git a/client/src/components/ContactForm.vue b/client/src/components/ContactForm.vue index 61796bfa..9c70a559 100644 --- a/client/src/components/ContactForm.vue +++ b/client/src/components/ContactForm.vue @@ -27,13 +27,12 @@ export default { return { enterTime: Number.MAX_SAFE_INTEGER, //for a basic anti-bot strategy st: store.state, - infoMsg: "", + infoMsg: "" }; }, methods: { trySetEnterTime: function(event) { - if (!!event.target.checked) - { + if (event.target.checked) { this.enterTime = Date.now(); this.infoMsg = ""; } @@ -41,17 +40,21 @@ export default { trySendMessage: function() { // Basic anti-bot strategy: const exitTime = Date.now(); - if (exitTime - this.enterTime < 5000) - return; + if (exitTime - this.enterTime < 5000) return; let email = document.getElementById("userEmail"); let subject = document.getElementById("mailSubject"); let content = document.getElementById("mailContent"); - const error = checkNameEmail({email: email}); - if (!!error) - return alert(error); - if (content.value.trim().length == 0) - return alert(this.st.tr["Empty message"]); - if (subject.value.trim().length == 0 && !confirm(this.st.tr["No subject. Send anyway?"])) + let error = checkNameEmail({ email: email }); + if (!error && content.value.trim().length == 0) + error = this.st.tr["Empty message"]; + if (error) { + alert(error); + return; + } + if ( + subject.value.trim().length == 0 && + !confirm(this.st.tr["No subject. Send anyway?"]) + ) return; // Message sending: @@ -61,7 +64,7 @@ export default { { email: email.value, subject: subject.value, - content: content.value, + content: content.value }, () => { this.infoMsg = "Email sent!"; @@ -69,8 +72,8 @@ export default { content.value = ""; } ); - }, - }, + } + } }; diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue index 2a4b0239..7125fde8 100644 --- a/client/src/components/GameList.vue +++ b/client/src/components/GameList.vue @@ -22,21 +22,20 @@ import { store } from "@/store"; import { GameStorage } from "@/utils/gameStorage"; export default { name: "my-game-list", - props: ["games","showBoth"], + props: ["games", "showBoth"], data: function() { return { st: store.state, - showCadence: true, + showCadence: true }; }, mounted: function() { // timeout to avoid calling too many time the adjust method let timeoutLaunched = false; - window.addEventListener("resize", (e) => { - if (!timeoutLaunched) - { + window.addEventListener("resize", () => { + if (!timeoutLaunched) { timeoutLaunched = true; - setTimeout( () => { + setTimeout(() => { this.showCadence = window.innerWidth >= 425; //TODO: arbitrary timeoutLaunched = false; }, 500); @@ -46,55 +45,64 @@ export default { computed: { sortedGames: function() { // Show in order: games where it's my turn, my running games, my games, other games - let minCreated = Number.MAX_SAFE_INTEGER - let maxCreated = 0 + let minCreated = Number.MAX_SAFE_INTEGER; + let maxCreated = 0; let augmentedGames = this.games.map(g => { let priority = 0; - if (g.players.some(p => p.uid == this.st.user.id || p.sid == this.st.user.sid)) - { + if ( + g.players.some( + p => p.uid == this.st.user.id || p.sid == this.st.user.sid + ) + ) { priority++; - if (g.score == "*") - { + if (g.score == "*") { priority++; - const myColor = g.players[0].uid == this.st.user.id - || g.players[0].sid == this.st.user.sid - ? "w" - : "b"; + const myColor = + g.players[0].uid == this.st.user.id || + g.players[0].sid == this.st.user.sid + ? "w" + : "b"; // I play in this game, so g.fen will be defined - if (!!g.fen.match(" " + myColor + " ")) - priority++; + if (g.fen.match(" " + myColor + " ")) priority++; } } - if (g.created < minCreated) - minCreated = g.created; - if (g.created > maxCreated) - maxCreated = g.created; - return Object.assign({}, g, {priority: priority, myTurn: priority==3}); + if (g.created < minCreated) minCreated = g.created; + if (g.created > maxCreated) maxCreated = g.created; + return Object.assign({}, g, { + priority: priority, + myTurn: priority == 3 + }); }); const deltaCreated = maxCreated - minCreated; - return augmentedGames.sort((g1,g2) => { - return g2.priority - g1.priority + - (g2.created - g1.created) / deltaCreated; + return augmentedGames.sort((g1, g2) => { + return ( + g2.priority - g1.priority + (g2.created - g1.created) / deltaCreated + ); }); - }, + } }, methods: { player_s: function(g) { if (this.showBoth) - return (g.players[0].name || "@nonymous") + " - " + (g.players[1].name || "@nonymous"); - if (this.st.user.sid == g.players[0].sid || this.st.user.id == g.players[0].uid) + return ( + (g.players[0].name || "@nonymous") + + " - " + + (g.players[1].name || "@nonymous") + ); + if ( + this.st.user.sid == g.players[0].sid || + this.st.user.id == g.players[0].uid + ) return g.players[1].name || "@nonymous"; return g.players[0].name || "@nonymous"; }, deleteGame: function(game, e) { - if (game.score != "*") - { - if (confirm(this.st.tr["Remove game?"])) - GameStorage.remove(game.id); + if (game.score != "*") { + if (confirm(this.st.tr["Remove game?"])) GameStorage.remove(game.id); e.stopPropagation(); } - }, - }, + } + } }; diff --git a/client/src/components/Language.vue b/client/src/components/Language.vue index e7243555..bdfd3417 100644 --- a/client/src/components/Language.vue +++ b/client/src/components/Language.vue @@ -25,21 +25,20 @@ export default { name: "my-language", data: function() { return { - st: store.state, + st: store.state }; }, mounted: function() { // NOTE: better style would be in pug directly, but how? document.querySelectorAll("#langSelect > option").forEach(opt => { - if (opt.value == this.st.lang) - opt.selected = true; + if (opt.value == this.st.lang) opt.selected = true; }); }, methods: { setLanguage: function(e) { localStorage["lang"] = e.target.value; store.setLanguage(e.target.value); - }, - }, + } + } }; diff --git a/client/src/components/MoveList.vue b/client/src/components/MoveList.vue index 0dede660..29e9a9fe 100644 --- a/client/src/components/MoveList.vue +++ b/client/src/components/MoveList.vue @@ -1,120 +1,97 @@ diff --git a/client/src/components/Settings.vue b/client/src/components/Settings.vue index f1031df8..e5a9d9c7 100644 --- a/client/src/components/Settings.vue +++ b/client/src/components/Settings.vue @@ -32,21 +32,21 @@ export default { name: "my-settings", data: function() { return { - st: store.state, + st: store.state }; }, methods: { updateSettings: function(event) { - const propName = - event.target.id.substr(3).replace(/^\w/, c => c.toLowerCase()) - let value = (["bcolor","sound"].includes(propName) + const propName = event.target.id + .substr(3) + .replace(/^\w/, c => c.toLowerCase()); + let value = ["bcolor", "sound"].includes(propName) ? event.target.value - : event.target.checked); - if (propName == "sound") - value = parseInt(value); + : event.target.checked; + if (propName == "sound") value = parseInt(value); store.updateSetting(propName, value); - }, - }, + } + } }; diff --git a/client/src/components/UpsertUser.vue b/client/src/components/UpsertUser.vue index eb94dedd..222a8625 100644 --- a/client/src/components/UpsertUser.vue +++ b/client/src/components/UpsertUser.vue @@ -35,34 +35,30 @@ import { store } from "@/store"; import { checkNameEmail } from "@/data/userCheck"; import { ajax } from "@/utils/ajax"; export default { - name: 'my-upsert-user', + name: "my-upsert-user", data: function() { return { nameOrEmail: "", //for login logStage: "Login", //or Register infoMsg: "", enterTime: Number.MAX_SAFE_INTEGER, //for a basic anti-bot strategy - st: store.state, + st: store.state }; }, watch: { nameOrEmail: function(newValue) { - if (newValue.indexOf('@') >= 0) - { + if (newValue.indexOf("@") >= 0) { this.st.user.email = newValue; this.st.user.name = ""; - } - else - { + } else { this.st.user.name = newValue; this.st.user.email = ""; } - }, + } }, computed: { submitMessage: function() { - switch (this.stage) - { + switch (this.stage) { case "Login": return "Go"; case "Register": @@ -70,26 +66,25 @@ export default { case "Update": return "Apply"; } + return "Never reached"; }, stage: function() { return this.st.user.id > 0 ? "Update" : this.logStage; - }, + } }, methods: { trySetEnterTime: function(event) { - if (!!event.target.checked) - { + if (event.target.checked) { this.infoMsg = ""; this.enterTime = Date.now(); } }, toggleStage: function() { // Loop login <--> register (update is for logged-in users) - this.logStage = (this.logStage == "Login" ? "Register" : "Login"); + this.logStage = this.logStage == "Login" ? "Register" : "Login"; }, ajaxUrl: function() { - switch (this.stage) - { + switch (this.stage) { case "Login": return "/sendtoken"; case "Register": @@ -97,10 +92,10 @@ export default { case "Update": return "/update"; } + return "Never reached"; }, ajaxMethod: function() { - switch (this.stage) - { + switch (this.stage) { case "Login": return "GET"; case "Register": @@ -108,10 +103,10 @@ export default { case "Update": return "PUT"; } + return "Never reached"; }, infoMessage: function() { - switch (this.stage) - { + switch (this.stage) { case "Login": return "Connection token sent. Check your emails!"; case "Register": @@ -119,29 +114,31 @@ export default { case "Update": return "Modifications applied!"; } + return "Never reached"; }, onSubmit: function() { // Basic anti-bot strategy: const exitTime = Date.now(); - if (this.stage == "Register" && exitTime - this.enterTime < 5000) - return; + if (this.stage == "Register" && exitTime - this.enterTime < 5000) return; let error = undefined; - if (this.stage == 'Login') - { - const type = (this.nameOrEmail.indexOf('@') >= 0 ? "email" : "name"); - error = checkNameEmail({[type]: this.nameOrEmail}); + if (this.stage == "Login") { + const type = this.nameOrEmail.indexOf("@") >= 0 ? "email" : "name"; + error = checkNameEmail({ [type]: this.nameOrEmail }); + } else error = checkNameEmail(this.st.user); + if (error) { + alert(error); + return; } - else - error = checkNameEmail(this.st.user); - if (!!error) - return alert(error); this.infoMsg = "Processing... Please wait"; - ajax(this.ajaxUrl(), this.ajaxMethod(), - this.stage == "Login" ? { nameOrEmail: this.nameOrEmail } : this.st.user, - res => { + ajax( + this.ajaxUrl(), + this.ajaxMethod(), + this.stage == "Login" + ? { nameOrEmail: this.nameOrEmail } + : this.st.user, + () => { this.infoMsg = this.infoMessage(); - if (this.stage != "Update") - this.nameOrEmail = ""; + if (this.stage != "Update") this.nameOrEmail = ""; }, err => { this.infoMsg = ""; @@ -152,8 +149,8 @@ export default { doLogout: function() { document.getElementById("modalUser").checked = false; this.$router.push("/logout"); - }, - }, + } + } }; diff --git a/client/src/data/challengeCheck.js b/client/src/data/challengeCheck.js index 626b7cf6..789952f8 100644 --- a/client/src/data/challengeCheck.js +++ b/client/src/data/challengeCheck.js @@ -1,29 +1,22 @@ import { extractTime } from "@/utils/timeControl"; -export function checkChallenge(c) -{ +export function checkChallenge(c) { const vid = parseInt(c.vid); - if (isNaN(vid) || vid <= 0) - return "Please select a variant"; + if (isNaN(vid) || vid <= 0) return "Please select a variant"; const tc = extractTime(c.cadence); - if (!tc) - return "Wrong time control"; + if (!tc) return "Wrong time control"; // Basic alphanumeric check for opponent name - if (!!c.to) - { - // NOTE: slightly redundant (see data/userCheck.js) - if (!c.to.match(/^[\w]+$/)) - return "Wrong characters in opponent name"; + if (c.to) { + // NOTE: slightly redundant (see data/userCheck.js) + if (!c.to.match(/^[\w]+$/)) return "Wrong characters in opponent name"; } // Allow custom FEN (and check it) only for individual challenges - if (c.fen.length > 0 && !!c.to) - { - if (!V.IsGoodFen(c.fen)) - return "Bad FEN string"; - } - else - c.fen = ""; + if (c.fen.length > 0 && !!c.to) { + if (!V.IsGoodFen(c.fen)) return "Bad FEN string"; + } else c.fen = ""; + + return ""; } diff --git a/client/src/data/problemCheck.js b/client/src/data/problemCheck.js index 3b9ccb46..652daaed 100644 --- a/client/src/data/problemCheck.js +++ b/client/src/data/problemCheck.js @@ -1,9 +1,8 @@ -export function checkProblem(p) -{ +export function checkProblem(p) { const vid = parseInt(p.vid); - if (isNaN(vid) || vid <= 0) - return "Please select a variant"; + if (isNaN(vid) || vid <= 0) return "Please select a variant"; - if (!V.IsGoodFen(p.fen)) - return "Bad FEN string"; + if (!V.IsGoodFen(p.fen)) return "Bad FEN string"; + + return ""; } diff --git a/client/src/data/userCheck.js b/client/src/data/userCheck.js index 9eb85622..dac8ef6f 100644 --- a/client/src/data/userCheck.js +++ b/client/src/data/userCheck.js @@ -1,17 +1,11 @@ -export function checkNameEmail(o) -{ - if (typeof o.name === "string") - { - if (o.name.length == 0) - return "Empty name"; - if (!o.name.match(/^[\w]+$/)) - return "Bad characters in name"; +export function checkNameEmail(o) { + if (typeof o.name === "string") { + if (o.name.length == 0) return "Empty name"; + if (!o.name.match(/^[\w]+$/)) return "Bad characters in name"; } - if (typeof o.email === "string") - { - if (o.email.length == 0) - return "Empty email"; - if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/)) - return "Bad characters in email"; + if (typeof o.email === "string") { + if (o.email.length == 0) return "Empty email"; + if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/)) return "Bad characters in email"; } + return ""; } diff --git a/client/src/main.js b/client/src/main.js index f1e23cbd..db70c3e1 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -11,17 +11,17 @@ new Vue({ return h(App); }, created: function() { - window.doClick = (elemId) => { document.getElementById(elemId).click() }; - document.addEventListener("keydown", (e) => { - if (e.code === "Escape") - { + window.doClick = elemId => { + document.getElementById(elemId).click(); + }; + document.addEventListener("keydown", e => { + if (e.code === "Escape") { let modalBoxes = document.querySelectorAll("[id^='modal']"); modalBoxes.forEach(m => { - if (m.checked && m.id != "modalWelcome") - m.checked = false; + if (m.checked && m.id != "modalWelcome") m.checked = false; }); } }); store.initialize(); - }, + } }).$mount("#app"); diff --git a/client/src/playCompMove.js b/client/src/playCompMove.js index b93a8d1e..d8dd2d47 100644 --- a/client/src/playCompMove.js +++ b/client/src/playCompMove.js @@ -1,22 +1,23 @@ // Logic to play a computer move in a web worker -onmessage = async function(e) -{ - switch (e.data[0]) - { - case "scripts": +onmessage = async function(e) { + switch (e.data[0]) { + case "scripts": { const vModule = await import("@/variants/" + e.data[1] + ".js"); self.V = vModule.VariantRules; break; - case "init": + } + case "init": { const fen = e.data[1]; self.vr = new self.V(fen); break; + } case "newmove": self.vr.play(e.data[1]); break; - case "askmove": + case "askmove": { const compMove = self.vr.getComputerMove(); postMessage(compMove); break; + } } -} +}; diff --git a/client/src/router.js b/client/src/router.js index daaefce8..c5afb17d 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -5,69 +5,67 @@ import Hall from "./views/Hall.vue"; Vue.use(Router); function loadView(view) { - return () => import(/* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`) + return () => + import(/* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`); } -import { ajax } from "@/utils/ajax"; -import { store } from "@/store"; - const router = new Router({ routes: [ { path: "/", name: "hall", - component: Hall, + component: Hall }, { path: "/variants", name: "variants", - component: loadView("Variants"), + component: loadView("Variants") }, { path: "/variants/:vname([a-zA-Z0-9]+)", name: "rules", - component: loadView("Rules"), + component: loadView("Rules") }, { path: "/authenticate/:token", name: "authenticate", - component: loadView("Auth"), + component: loadView("Auth") }, { path: "/logout", name: "logout", - component: loadView("Logout"), + component: loadView("Logout") }, { path: "/problems", name: "myproblems", - component: loadView("Problems"), + component: loadView("Problems") }, { path: "/mygames", name: "mygames", - component: loadView("MyGames"), + component: loadView("MyGames") }, { path: "/game/:id([a-zA-Z0-9]+)", name: "game", - component: loadView("Game"), + component: loadView("Game") }, { path: "/analyse/:vname([a-zA-Z0-9]+)", name: "analyse", - component: loadView("Analyse"), + component: loadView("Analyse") }, { path: "/about", name: "about", - component: loadView("About"), + component: loadView("About") }, { path: "/news", name: "news", - component: loadView("News"), - }, + component: loadView("News") + } ] }); diff --git a/client/src/store.js b/client/src/store.js index 52a5247b..373aa288 100644 --- a/client/src/store.js +++ b/client/src/store.js @@ -2,21 +2,21 @@ import { ajax } from "./utils/ajax"; import { getRandString } from "./utils/alea"; // Global store: see https://medium.com/fullstackio/managing-state-in-vue-js-23a0352b1c87 -export const store = -{ +export const store = { state: { variants: [], tr: {}, user: {}, settings: {}, - lang: "", + lang: "" }, socketCloseListener: null, initialize() { - ajax("/variants", "GET", res => { this.state.variants = res.variantArray; }); + ajax("/variants", "GET", res => { + this.state.variants = res.variantArray; + }); let mysid = localStorage.getItem("mysid"); - if (!mysid) - { + if (!mysid) { mysid = getRandString(); localStorage.setItem("mysid", mysid); //done only once (unless user clear browser data) } @@ -26,22 +26,26 @@ export const store = name: localStorage.getItem("myname") || "", //"" for "anonymous" email: "", //unknown yet notify: false, //email notifications - sid: mysid, + sid: mysid }; // Slow verification through the server: // NOTE: still superficial identity usurpation possible, but difficult. ajax("/whoami", "GET", res => { this.state.user.id = res.id; const storedId = localStorage.getItem("myid"); - if (res.id > 0 && !storedId) //user cleared localStorage + if (res.id > 0 && !storedId) + //user cleared localStorage localStorage.setItem("myid", res.id); - else if (res.id == 0 && !!storedId) //user cleared cookie + else if (res.id == 0 && !!storedId) + //user cleared cookie localStorage.removeItem("myid"); this.state.user.name = res.name; const storedName = localStorage.getItem("myname"); - if (!!res.name && !storedName) //user cleared localStorage + if (!!res.name && !storedName) + //user cleared localStorage localStorage.setItem("myname", res.name); - else if (!res.name && !!storedName) //user cleared cookie + else if (!res.name && !!storedName) + //user cleared cookie localStorage.removeItem("myname"); this.state.user.email = res.email; this.state.user.notify = res.notify; @@ -51,13 +55,12 @@ export const store = bcolor: localStorage.getItem("bcolor") || "lichess", sound: parseInt(localStorage.getItem("sound")) || 1, hints: localStorage.getItem("hints") == "true", - highlight: localStorage.getItem("highlight") == "true", + highlight: localStorage.getItem("highlight") == "true" }; - const supportedLangs = ["en","es","fr"]; - this.state.lang = localStorage["lang"] || - (supportedLangs.includes(navigator.language) - ? navigator.language - : "en"); + const supportedLangs = ["en", "es", "fr"]; + this.state.lang = + localStorage["lang"] || + (supportedLangs.includes(navigator.language) ? navigator.language : "en"); this.setTranslations(); }, updateSetting: function(propName, value) { @@ -72,5 +75,5 @@ export const store = setLanguage(lang) { this.state.lang = lang; this.setTranslations(); - }, + } }; diff --git a/client/src/translations/en.js b/client/src/translations/en.js index c9653358..7fd40fc5 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -1,54 +1,55 @@ -export const translations = -{ - "Abort": "Abort", - "About": "About", +export const translations = { + Abort: "Abort", + About: "About", "Accept draw?": "Accept draw?", - "All": "All", - "Analyse": "Analyse", - "Analyse in Dark mode makes no sense!": "Analyse in Dark mode makes no sense!", + All: "All", + Analyse: "Analyse", + "Analyse in Dark mode makes no sense!": + "Analyse in Dark mode makes no sense!", "Are you sure?": "Are you sure?", "Authentication successful!": "Authentication successful!", - "Apply": "Apply", + Apply: "Apply", "Back to list": "Back to list", "Black to move": "Black to move", "Black win": "Black win", "Board colors": "Board colors", "Board size": "Board size", - "blue": "blue", - "brown": "brown", - "Cadence": "Cadence", - "Challenge": "Challenge", + blue: "blue", + brown: "brown", + Cadence: "Cadence", + Challenge: "Challenge", "Challenge declined": "Challenge declined", "Chat here": "Chat here", - "Connection token sent. Check your emails!": "Connection token sent. Check your emails!", - "Contact": "Contact", + "Connection token sent. Check your emails!": + "Connection token sent. Check your emails!", + Contact: "Contact", "Correspondance challenges": "Correspondance challenges", "Correspondance games": "Correspondance games", "Database error:": "Database error:", - "Delete": "Delete", - "Download": "Download", - "Draw": "Draw", + Delete: "Delete", + Download: "Download", + Draw: "Draw", "Draw offer only in your turn": "Draw offer only in your turn", - "Edit": "Edit", - "Email": "Email", + Edit: "Edit", + Email: "Email", "Email sent!": "Email sent!", "Empty message": "Empty message", "Error while loading database:": "Error while loading database:", "Example game": "Example game", - "From": "From", + From: "From", "Game retrieval failed:": "Game retrieval failed:", "Game removal failed:": "Game removal failed:", - "Go": "Go", - "green": "green", - "Hall": "Hall", + Go: "Go", + green: "green", + Hall: "Hall", "Highlight last move and checks?": "Highlight last move and checks?", - "Instructions": "Instructions", - "Language": "Language", + Instructions: "Instructions", + Language: "Language", "Live challenges": "Live challenges", "Live games": "Live games", "Load more": "Load more", - "Login": "Login", - "Logout": "Logout", + Login: "Login", + Logout: "Logout", "Logout successful!": "Logout successful!", "Modifications applied!": "Modifications applied!", "Move played:": "Move played:", @@ -56,56 +57,60 @@ export const translations = "My games": "My games", "My problems": "My problems", "Name or Email": "Name or Email", - "New connexion detected: tab now offline": "New connexion detected: tab now offline", + "New connexion detected: tab now offline": + "New connexion detected: tab now offline", "New correspondance game:": "New correspondance game:", "New game": "New game", "New problem": "New problem", - "News": "News", + News: "News", "No subject. Send anyway?": "No subject. Send anyway?", - "None": "None", + None: "None", "Notifications by email": "Notifications by email", - "Number": "Number", - "Observe": "Observe", + Number: "Number", + Observe: "Observe", "Offer draw?": "Offer draw?", "Opponent action": "Opponent action", "Play sounds?": "Play sounds?", "Play with?": "Play with?", - "Players": "Players", - "Please log in to accept corr challenges": "Please log in to accept corr challenges", - "Please log in to play correspondance games": "Please log in to play correspondance games", + Players: "Players", + "Please log in to accept corr challenges": + "Please log in to accept corr challenges", + "Please log in to play correspondance games": + "Please log in to play correspondance games", "Please select a variant": "Please select a variant", - "Practice": "Practice", + Practice: "Practice", "Prefix?": "Prefix?", "Processing... Please wait": "Processing... Please wait", - "Problems": "Problems", + Problems: "Problems", "participant(s):": "participant(s):", - "Register": "Register", - "Registration complete! Please check your emails": "Registration complete! Please check your emails", + Register: "Register", + "Registration complete! Please check your emails": + "Registration complete! Please check your emails", "Remove game?": "Remove game?", - "Resign": "Resign", + Resign: "Resign", "Resign the game?": "Resign the game?", - "Result": "Result", - "Rules": "Rules", - "Send": "Send", + Result: "Result", + Rules: "Rules", + Send: "Send", "Self-challenge is forbidden": "Self-challenge is forbidden", "Send challenge": "Send challenge", - "Settings": "Settings", + Settings: "Settings", "Show possible moves?": "Show possible moves?", "Show solution": "Show solution", - "Social": "Social", - "Solution": "Solution", + Social: "Social", + Solution: "Solution", "Stop game": "Stop game", - "Subject": "Subject", + Subject: "Subject", "Terminate game?": "Terminate game?", "Three repetitions": "Three repetitions", - "Time": "Time", - "To": "To", - "Unknown": "Unknown", - "Update": "Update", + Time: "Time", + To: "To", + Unknown: "Unknown", + Update: "Update", "User name": "User name", - "Variant": "Variant", - "Variants": "Variants", - "Versus": "Versus", + Variant: "Variant", + Variants: "Variants", + Versus: "Versus", "White to move": "White to move", "White win": "White win", "Who's there?": "Who's there?", @@ -130,5 +135,5 @@ export const translations = "Pawns move diagonally": "Pawns move diagonally", "Reverse captures": "Reverse captures", "Shared pieces": "Shared pieces", - "Standard rules": "Standard rules", + "Standard rules": "Standard rules" }; diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 78f85854..44203b9a 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -1,55 +1,56 @@ -export const translations = -{ - "Abort": "Terminar", - "About": "Acerca de", +export const translations = { + Abort: "Terminar", + About: "Acerca de", "Accept draw?": "¿Acceptar tablas?", - "All": "Todos", - "Analyse": "Analizar", - "Analyse in Dark mode makes no sense!": "¡Analizar en modo Dark no tiene sentido!", - "Apply": "Aplicar", + All: "Todos", + Analyse: "Analizar", + "Analyse in Dark mode makes no sense!": + "¡Analizar en modo Dark no tiene sentido!", + Apply: "Aplicar", "Are you sure?": "¿Está usted seguro?", "Authentication successful!": "¡Autenticación exitosa!", "Back to list": "Volver a la lista", - "Black": "Negras", + Black: "Negras", "Black to move": "Juegan las negras", "Black win": "Las negras gagnan", "Board colors": "Colores del tablero", "Board size": "Tamaño del tablero", - "blue": "azul", - "brown": "marrón", - "Cadence": "Cadencia", - "Challenge": "Desafiar", + blue: "azul", + brown: "marrón", + Cadence: "Cadencia", + Challenge: "Desafiar", "Challenge declined": "Desafío rechazado", "Chat here": "Chat aquí", - "Connection token sent. Check your emails!": "Token de conexión enviado. ¡Revisa tus correos!", - "Contact": "Contacto", + "Connection token sent. Check your emails!": + "Token de conexión enviado. ¡Revisa tus correos!", + Contact: "Contacto", "Correspondance challenges": "Desafíos por correspondencia", "Correspondance games": "Partidas por correspondencia", "Database error:": "Error de la base de datos:", - "Delete": "Borrar", - "Download": "Descargar", - "Draw": "Tablas", + Delete: "Borrar", + Download: "Descargar", + Draw: "Tablas", "Draw offer only in your turn": "Oferta de tablas solo en tu turno", - "Edit": "Editar", - "Email": "Email", + Edit: "Editar", + Email: "Email", "Email sent!": "¡Email enviado!", "Empty message": "Mensaje vacio", "Error while loading database:": "Error al cargar la base de datos:", "Example game": "Ejemplo de partida", - "From": "De", + From: "De", "Game retrieval failed:": "La recuperación de la partida falló:", "Game removal failed:": "La eliminación de la partida falló:", - "Go": "Go", - "green": "verde", - "Hall": "Salón", + Go: "Go", + green: "verde", + Hall: "Salón", "Highlight last move and checks?": "¿Resaltar el último movimiento y jaques?", - "Instructions": "Instrucciones", - "Language": "Idioma", + Instructions: "Instrucciones", + Language: "Idioma", "Live challenges": "Desafíos en vivo", "Live games": "Partidas en vivo", "Load more": "Cargar más", - "Login": "Login", - "Logout": "Logout", + Login: "Login", + Logout: "Logout", "Logout successful!": "¡Desconexión exitosa!", "Modifications applied!": "¡Modificaciones aplicadas!", "Move played:": "Movimiento jugado:", @@ -57,57 +58,61 @@ export const translations = "My games": "Mis partidas", "My problems": "Mis problemas", "Name or Email": "Nombre o Email", - "New connexion detected: tab now offline": "Nueva conexión detectada: pestaña ahora desconectada", + "New connexion detected: tab now offline": + "Nueva conexión detectada: pestaña ahora desconectada", "New correspondance game:": "Nueva partida por correspondencia:", "New game": "Nueva partida", "New problem": "Nuevo problema", - "News": "Noticias", + News: "Noticias", "No subject. Send anyway?": "Sin asunto. ¿Enviar sin embargo?", - "None": "Ninguno", + None: "Ninguno", "Notifications by email": "Notificaciones por email", - "Number": "Número", + Number: "Número", "Offer draw?": "¿Ofrecer tablas?", - "Observe": "Observar", + Observe: "Observar", "Opponent action": "Acción del adversario", "Play sounds?": "¿Permitir sonidos?", "Play with?": "¿Jugar con?", - "Players": "Jugadores", - "Please log in to accept corr challenges": "Inicia sesión para aceptar los desafíos por correspondencia", - "Please log in to play correspondance games": "Inicia sesión para jugar partidas por correspondancia", + Players: "Jugadores", + "Please log in to accept corr challenges": + "Inicia sesión para aceptar los desafíos por correspondencia", + "Please log in to play correspondance games": + "Inicia sesión para jugar partidas por correspondancia", "Please select a variant": "Por favor seleccione una variante", - "Practice": "Práctica", + Practice: "Práctica", "Prefix?": "¿Prefijo?", "Processing... Please wait": "Procesando... por favor espere", - "Problems": "Problemas", + Problems: "Problemas", "participant(s):": "participante(s):", - "Register": "Registrarse", - "Registration complete! Please check your emails": "¡Registro completo! Por favor revise sus correos electrónicos", + Register: "Registrarse", + "Registration complete! Please check your emails": + "¡Registro completo! Por favor revise sus correos electrónicos", "Remove game?": "¿Eliminar la partida?", - "Resign": "Abandonar", + Resign: "Abandonar", "Resign the game?": "¿Abandonar la partida?", - "Result": "Resultado", - "Rules": "Reglas", - "Send": "Enviar", + Result: "Resultado", + Rules: "Reglas", + Send: "Enviar", "Self-challenge is forbidden": "Auto desafío está prohibido", "Send challenge": "Enviar desafío", - "Settings": "Configuraciones", + Settings: "Configuraciones", "Show possible moves?": "¿Mostrar posibles movimientos?", "Show solution": "Mostrar la solución", - "Social": "Social", - "Solution": "Solución", + Social: "Social", + Solution: "Solución", "Stop game": "Terminar la partida", - "Subject": "Asunto", + Subject: "Asunto", "Terminate game?": "¿Terminar la partida?", "Three repetitions": "Tres repeticiones", - "Time": "Tiempo", - "To": "A", - "Unknown": "Desconocido", - "Update": "Actualización", + Time: "Tiempo", + To: "A", + Unknown: "Desconocido", + Update: "Actualización", "User name": "Nombre de usuario", - "Variant": "Variante", - "Variants": "Variantes", - "Versus": "Contra", - "White": "Blancas", + Variant: "Variante", + Variants: "Variantes", + Versus: "Contra", + White: "Blancas", "White to move": "Juegan las blancas", "White win": "Las blancas gagnan", "Who's there?": "¿Quién está ahí?", @@ -132,5 +137,5 @@ export const translations = "Pawns move diagonally": "Peones se mueven en diagonal", "Reverse captures": "Capturas invertidas", "Shared pieces": "Piezas compartidas", - "Standard rules": "Reglas estandar", + "Standard rules": "Reglas estandar" }; diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index e5322eb7..d0877147 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -1,55 +1,59 @@ -export const translations = -{ - "Abort": "Arrêter", - "About": "À propos", +export const translations = { + Abort: "Arrêter", + About: "À propos", "Accept draw?": "Accepter la nulle ?", - "All": "Tous", - "Analyse": "Analyser", - "Analyse in Dark mode makes no sense!": "Analyser en mode Dark n'a pas de sens !", - "Apply": "Appliquer", + All: "Tous", + Analyse: "Analyser", + "Analyse in Dark mode makes no sense!": + "Analyser en mode Dark n'a pas de sens !", + Apply: "Appliquer", "Authentication successful!": "Authentification réussie !", "Are you sure?": "Étes vous sûr?", "Back to list": "Retour à la liste", - "Black": "Noirs", + Black: "Noirs", "Black to move": "Trait aux noirs", "Black win": "Les noirs gagnent", "Board colors": "Couleurs de l'échiquier", "Board size": "Taille de l'échiquier", - "blue": "bleu", - "brown": "marron", - "Cadence": "Cadence", - "Challenge": "Défier", + blue: "bleu", + brown: "marron", + Cadence: "Cadence", + Challenge: "Défier", "Challenge declined": "Défi refusé", "Chat here": "Chattez ici", - "Connection token sent. Check your emails!": "Token de connection envoyé. Allez voir vos emails !", - "Contact": "Contact", + "Connection token sent. Check your emails!": + "Token de connection envoyé. Allez voir vos emails !", + Contact: "Contact", "Correspondance challenges": "Défis par correspondance", "Correspondance games": "Parties par correspondance", "Database error:": "Erreur de base de données :", - "Delete": "Supprimer", - "Download": "Télécharger", - "Draw": "Nulle", - "Draw offer only in your turn": "Proposition de nulle seulement sur votre temps", - "Edit": "Éditer", - "Email": "Email", + Delete: "Supprimer", + Download: "Télécharger", + Draw: "Nulle", + "Draw offer only in your turn": + "Proposition de nulle seulement sur votre temps", + Edit: "Éditer", + Email: "Email", "Email sent!": "Email envoyé !", "Empty message": "Message vide", - "Error while loading database:": "Erreur lors du chargement de la base de données :", + "Error while loading database:": + "Erreur lors du chargement de la base de données :", "Example game": "Partie exemple", - "From": "De", + From: "De", "Game retrieval failed:": "Échec de la récupération de la partie :", "Game removal failed:": "Échec de la suppresion de la partie :", - "Go": "Go", - "green": "vert", - "Hall": "Salon", - "Highlight last move and checks?": "Mettre en valeur le dernier coup et les échecs ?", - "Instructions": "Instructions", - "Language": "Langue", + Go: "Go", + green: "vert", + Hall: "Salon", + "Highlight last move and checks?": + "Mettre en valeur le dernier coup et les échecs ?", + Instructions: "Instructions", + Language: "Langue", "Live challenges": "Défis en direct", "Live games": "Parties en direct", "Load more": "Charger plus", - "Login": "Login", - "Logout": "Logout", + Login: "Login", + Logout: "Logout", "Logout successful!": "Déconnection réussie !", "Modifications applied!": "Modifications effectuées !", "Move played:": "Coup joué :", @@ -57,57 +61,61 @@ export const translations = "My games": "Mes parties", "My problems": "Mes problèmes", "Name or Email": "Nom ou Email", - "New connexion detected: tab now offline": "Nouvelle connexion détectée : onglet désormais hors ligne", + "New connexion detected: tab now offline": + "Nouvelle connexion détectée : onglet désormais hors ligne", "New correspondance game:": "Nouvelle partie par corespondance :", "New game": "Nouvelle partie", "New problem": "Nouveau problème", - "News": "Nouvelles", + News: "Nouvelles", "No subject. Send anyway?": "Pas de sujet. Envoyer quand-même ??", - "None": "Aucun", + None: "Aucun", "Notifications by email": "Notifications par email", - "Number": "Numéro", + Number: "Numéro", "Offer draw?": "Proposer nulle ?", - "Observe": "Observer", + Observe: "Observer", "Opponent action": "Action de l'adversaire", "Play sounds?": "Jouer les sons ?", "Play with?": "Jouer avec ?", - "Players": "Joueurs", - "Please log in to accept corr challenges": "Identifiez vous pour accepter des défis par correspondance", - "Please log in to play correspondance games": "Identifiez vous pour jouer des parties par correspondance", + Players: "Joueurs", + "Please log in to accept corr challenges": + "Identifiez vous pour accepter des défis par correspondance", + "Please log in to play correspondance games": + "Identifiez vous pour jouer des parties par correspondance", "Please select a variant": "Sélectionnez une variante SVP", - "Practice": "Pratiquer", + Practice: "Pratiquer", "Prefix?": "Préfixe ?", "Processing... Please wait": "Traitement en cours... Attendez SVP", - "Problems": "Problèmes", + Problems: "Problèmes", "participant(s):": "participant(s) :", - "Register": "S'enregistrer", - "Registration complete! Please check your emails": "Enregistrement terminé ! Allez voir vos emails", + Register: "S'enregistrer", + "Registration complete! Please check your emails": + "Enregistrement terminé ! Allez voir vos emails", "Remove game?": "Supprimer la partie ?", - "Resign": "Abandonner", + Resign: "Abandonner", "Resign the game?": "Abandonner la partie ?", - "Result": "Résultat", - "Rules": "Règles", - "Send": "Envoyer", + Result: "Résultat", + Rules: "Règles", + Send: "Envoyer", "Self-challenge is forbidden": "Interdit de s'auto-défier", "Send challenge": "Envoyer défi", - "Settings": "Réglages", + Settings: "Réglages", "Show possible moves?": "Montrer les coups possibles ?", "Show solution": "Montrer la solution", - "Social": "Social", - "Solution": "Solution", + Social: "Social", + Solution: "Solution", "Stop game": "Arrêter la partie", - "Subject": "Sujet", + Subject: "Sujet", "Terminate game?": "Stopper la partie ?", "Three repetitions": "Triple répétition", - "Time": "Temps", - "To": "À", - "Unknown": "Inconnu", - "Update": "Mise à jour", + Time: "Temps", + To: "À", + Unknown: "Inconnu", + Update: "Mise à jour", "User name": "Nom d'utilisateur", - "Variant": "Variante", - "Variants": "Variantes", - "Versus": "Contre", - "White": "Blancs", + Variant: "Variante", + Variants: "Variantes", + Versus: "Contre", + White: "Blancs", "White to move": "Trait aux blancs", "White win": "Les blancs gagnent", "Who's there?": "Qui est là ?", @@ -132,5 +140,5 @@ export const translations = "Pawns move diagonally": "Les pions vont en diagonale", "Reverse captures": "Captures inversées", "Shared pieces": "Pièces partagées", - "Standard rules": "Règles usuelles", + "Standard rules": "Règles usuelles" }; diff --git a/client/src/utils/ajax.js b/client/src/utils/ajax.js index 86925b03..e539f468 100644 --- a/client/src/utils/ajax.js +++ b/client/src/utils/ajax.js @@ -5,8 +5,7 @@ import params from "../parameters"; //for server URL // Problem: fetch() does not set req.xhr... see access/ajax() security especially for /whoami // From JSON (encoded string values!) to "arg1=...&arg2=..." -function toQueryString(data) -{ +function toQueryString(data) { let data_str = ""; Object.keys(data).forEach(k => { data_str += k + "=" + encodeURIComponent(data[k]) + "&"; @@ -15,56 +14,48 @@ function toQueryString(data) } // data, error: optional -export function ajax(url, method, data, success, error) -{ +export function ajax(url, method, data, success, error) { let xhr = new XMLHttpRequest(); - if (data === undefined || typeof(data) === "function") //no data - { + if (data === undefined || typeof data === "function") { + //no data error = success; success = data; data = {}; } - if (!success) - success = () => {}; //by default, do nothing + if (!success) success = () => {}; //by default, do nothing if (!error) - error = errmsg => { alert(errmsg); }; + error = errmsg => { + alert(errmsg); + }; xhr.onreadystatechange = function() { - if (this.readyState == 4 && this.status == 200) - { + if (this.readyState == 4 && this.status == 200) { let res_json = ""; try { res_json = JSON.parse(xhr.responseText); } catch (e) { - // Plain text (e.g. for rules retrieval) - return success(xhr.responseText); + // Plain text (e.g. for rules retrieval) (TODO: no more plain text in current version) + success(xhr.responseText); } - if (!res_json.errmsg && !res_json.errno) - success(res_json); - else - { - if (!!res_json.errmsg) - error(res_json.errmsg); - else - error(res_json.code + ". errno = " + res_json.errno); + if (res_json) { + if (!res_json.errmsg && !res_json.errno) success(res_json); + else { + if (res_json.errmsg) error(res_json.errmsg); + else error(res_json.code + ". errno = " + res_json.errno); + } } } }; - if (["GET","DELETE"].includes(method) && !!data) - { + if (["GET", "DELETE"].includes(method) && !!data) { // Append query params to URL url += "/?" + toQueryString(data); } xhr.open(method, params.serverUrl + url, true); - xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest"); + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); // Next line to allow cross-domain cookies in dev mode - if (params.cors) - xhr.withCredentials = true; - if (["POST","PUT"].includes(method)) - { + if (params.cors) xhr.withCredentials = true; + if (["POST", "PUT"].includes(method)) { xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); xhr.send(JSON.stringify(data)); - } - else - xhr.send(); + } else xhr.send(); } diff --git a/client/src/utils/alea.js b/client/src/utils/alea.js index 04563938..9ff11593 100644 --- a/client/src/utils/alea.js +++ b/client/src/utils/alea.js @@ -1,27 +1,26 @@ // Random (enough) string for socket and game IDs -export function getRandString() -{ - return (Date.now().toString(36) + Math.random().toString(36).substr(2, 7)) - .toUpperCase(); +export function getRandString() { + return ( + Date.now().toString(36) + + Math.random() + .toString(36) + .substr(2, 7) + ).toUpperCase(); } -export function randInt(min, max) -{ - if (!max) - { +export function randInt(min, max) { + if (!max) { max = min; min = 0; } - return Math.floor(Math.random() * (max - min) ) + min; + return Math.floor(Math.random() * (max - min)) + min; } // Inspired by https://github.com/jashkenas/underscore/blob/master/underscore.js -export function sample (arr, n) -{ +export function sample(arr, n) { n = n || 1; let cpArr = arr.map(e => e); - for (let index = 0; index < n; index++) - { + for (let index = 0; index < n; index++) { const rand = randInt(index, arr.length); const temp = cpArr[index]; cpArr[index] = cpArr[rand]; @@ -30,7 +29,6 @@ export function sample (arr, n) return cpArr.slice(0, n); } -export function shuffle(arr) -{ +export function shuffle(arr) { return sample(arr, arr.length); } diff --git a/client/src/utils/array.js b/client/src/utils/array.js index fae4ce91..4d44db02 100644 --- a/client/src/utils/array.js +++ b/client/src/utils/array.js @@ -1,32 +1,24 @@ // Remove item(s) in array (if present) -export const ArrayFun = -{ - remove: function(arr, rfun, all) - { +export const ArrayFun = { + remove: function(arr, rfun, all) { const index = arr.findIndex(rfun); - if (index >= 0) - { + if (index >= 0) { arr.splice(index, 1); - if (!!all) - { + if (all) { // Reverse loop because of the splice below - for (let i=arr.length-1; i>=index; i--) - { - if (rfun(arr[i])) - arr.splice(i, 1); + for (let i = arr.length - 1; i >= index; i--) { + if (rfun(arr[i])) arr.splice(i, 1); } } } }, // Double array intialization - init: function(size1, size2, initElem) - { - return [...Array(size1)].map(e => Array(size2).fill(initElem)); + init: function(size1, size2, initElem) { + return [...Array(size1)].map(() => Array(size2).fill(initElem)); }, - range: function(max) - { + range: function(max) { return [...Array(max).keys()]; - }, + } }; diff --git a/client/src/utils/cookie.js b/client/src/utils/cookie.js index b0518442..78551a29 100644 --- a/client/src/utils/cookie.js +++ b/client/src/utils/cookie.js @@ -1,22 +1,18 @@ // Source: https://www.quirksmode.org/js/cookies.html -export function setCookie(name, value) -{ +export function setCookie(name, value) { var date = new Date(); - date.setTime(date.getTime()+(183*24*60*60*1000)); //6 months - var expires = "; expires="+date.toGMTString(); - document.cookie = name+"="+value+expires+"; path=/"; + date.setTime(date.getTime() + 183 * 24 * 60 * 60 * 1000); //6 months + var expires = "; expires=" + date.toGMTString(); + document.cookie = name + "=" + value + expires + "; path=/"; } export function getCookie(name, defaut) { var nameEQ = name + "="; - var ca = document.cookie.split(';'); - for (var i=0;i < ca.length;i++) - { + var ca = document.cookie.split(";"); + for (var i = 0; i < ca.length; i++) { var c = ca[i]; - while (c.charAt(0)==' ') - c = c.substring(1,c.length); - if (c.indexOf(nameEQ) == 0) - return c.substring(nameEQ.length,c.length); + while (c.charAt(0) == " ") c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); } return defaut; //cookie not found } diff --git a/client/src/utils/datetime.js b/client/src/utils/datetime.js index 5addd255..75e85d71 100644 --- a/client/src/utils/datetime.js +++ b/client/src/utils/datetime.js @@ -1,47 +1,50 @@ -function zeroPad(x) -{ - return (x<10 ? "0" : "") + x; +function zeroPad(x) { + return (x < 10 ? "0" : "") + x; } -export function getDate(d) -{ - return d.getFullYear() + '-' + zeroPad(d.getMonth()+1) + '-' + zeroPad(d.getDate()); +export function getDate(d) { + return ( + d.getFullYear() + + "-" + + zeroPad(d.getMonth() + 1) + + "-" + + zeroPad(d.getDate()) + ); } -export function getTime(d) -{ - return zeroPad(d.getHours()) + ":" + zeroPad(d.getMinutes()) + ":" + - zeroPad(d.getSeconds()); +export function getTime(d) { + return ( + zeroPad(d.getHours()) + + ":" + + zeroPad(d.getMinutes()) + + ":" + + zeroPad(d.getSeconds()) + ); } -function padDigits(x) -{ - if (x < 10) - return "0" + x; +function padDigits(x) { + if (x < 10) return "0" + x; return x; } -export function ppt(t) -{ +export function ppt(t) { // "Pretty print" an amount of time given in seconds const dayInSeconds = 60 * 60 * 24; const hourInSeconds = 60 * 60; const days = Math.floor(t / dayInSeconds); - const hours = Math.floor(t%dayInSeconds / hourInSeconds); - const minutes = Math.floor(t%hourInSeconds / 60); + const hours = Math.floor((t % dayInSeconds) / hourInSeconds); + const minutes = Math.floor((t % hourInSeconds) / 60); const seconds = Math.floor(t % 60); let res = ""; - if (days > 0) - res += days + "d "; - if (days <= 3 && hours > 0) //NOTE: 3 is arbitrary + if (days > 0) res += days + "d "; + if (days <= 3 && hours > 0) + //NOTE: 3 is arbitrary res += hours + "h "; if (days == 0 && minutes > 0) - res += (hours > 0 ? padDigits(minutes) + "m " : minutes + ":"); - if (days == 0 && hours == 0) - { + res += hours > 0 ? padDigits(minutes) + "m " : minutes + ":"; + if (days == 0 && hours == 0) { res += padDigits(seconds); - if (minutes == 0) - res += "s"; //seconds indicator, since this is the only number printed + if (minutes == 0) res += "s"; //seconds indicator, since this is the only number printed } return res.trim(); //remove potential last space } diff --git a/client/src/utils/gameStorage.js b/client/src/utils/gameStorage.js index f995828d..453388ed 100644 --- a/client/src/utils/gameStorage.js +++ b/client/src/utils/gameStorage.js @@ -19,8 +19,7 @@ import { ajax } from "@/utils/ajax"; import { store } from "@/store"; -function dbOperation(callback) -{ +function dbOperation(callback) { let db = null; let DBOpenRequest = window.indexedDB.open("vchess", 4); @@ -28,7 +27,7 @@ function dbOperation(callback) alert(store.state.tr["Database error:"] + " " + event.target.errorCode); }; - DBOpenRequest.onsuccess = function(event) { + DBOpenRequest.onsuccess = function() { db = DBOpenRequest.result; callback(db); db.close(); @@ -37,28 +36,32 @@ function dbOperation(callback) DBOpenRequest.onupgradeneeded = function(event) { let db = event.target.result; db.onerror = function(event) { - alert(store.state.tr["Error while loading database:"] + " " + event.target.errorCode); + alert( + store.state.tr["Error while loading database:"] + + " " + + event.target.errorCode + ); }; // Create objectStore for vchess->games let objectStore = db.createObjectStore("games", { keyPath: "id" }); objectStore.createIndex("score", "score"); //to search by game result - } + }; } -export const GameStorage = -{ +export const GameStorage = { // Optional callback to get error status - add: function(game, callback) - { - dbOperation((db) => { + add: function(game, callback) { + dbOperation(db => { let transaction = db.transaction("games", "readwrite"); - if (callback) - { + if (callback) { transaction.oncomplete = function() { callback({}); //everything's fine - } + }; transaction.onerror = function() { - callback({errmsg: store.state.tr["Game retrieval failed:"] + " " + transaction.error}); + callback({ + errmsg: + store.state.tr["Game retrieval failed:"] + " " + transaction.error + }); }; } let objectStore = transaction.objectStore("games"); @@ -68,98 +71,81 @@ export const GameStorage = // TODO: also option to takeback a move ? // obj: chat, move, fen, clocks, score[Msg], initime, ... - update: function(gameId, obj) - { - if (Number.isInteger(gameId) || !isNaN(parseInt(gameId))) - { + update: function(gameId, obj) { + if (Number.isInteger(gameId) || !isNaN(parseInt(gameId))) { // corr: only move, fen and score - ajax( - "/games", - "PUT", - { - gid: gameId, - newObj: - { - // Some fields may be undefined: - chat: obj.chat, - move: obj.move, - fen: obj.fen, - score: obj.score, - scoreMsg: obj.scoreMsg, - drawOffer: obj.drawOffer, - } + ajax("/games", "PUT", { + gid: gameId, + newObj: { + // Some fields may be undefined: + chat: obj.chat, + move: obj.move, + fen: obj.fen, + score: obj.score, + scoreMsg: obj.scoreMsg, + drawOffer: obj.drawOffer } - ); - } - else - { + }); + } else { // live - dbOperation((db) => { - let objectStore = db.transaction("games", "readwrite").objectStore("games"); + dbOperation(db => { + let objectStore = db + .transaction("games", "readwrite") + .objectStore("games"); objectStore.get(gameId).onsuccess = function(event) { const game = event.target.result; Object.keys(obj).forEach(k => { - if (k == "move") - game.moves.push(obj[k]); - else - game[k] = obj[k]; + if (k == "move") game.moves.push(obj[k]); + else game[k] = obj[k]; }); objectStore.put(game); //save updated data - } + }; }); } }, // Retrieve all local games (running, completed, imported...) - getAll: function(callback) - { - dbOperation((db) => { - let objectStore = db.transaction('games').objectStore('games'); + getAll: function(callback) { + dbOperation(db => { + let objectStore = db.transaction("games").objectStore("games"); let games = []; objectStore.openCursor().onsuccess = function(event) { let cursor = event.target.result; // if there is still another cursor to go, keep running this code - if (cursor) - { + if (cursor) { games.push(cursor.value); cursor.continue(); - } - else - callback(games); - } + } else callback(games); + }; }); }, // Retrieve any game from its identifiers (locally or on server) // NOTE: need callback because result is obtained asynchronously - get: function(gameId, callback) - { + get: function(gameId, callback) { // corr games identifiers are integers - if (Number.isInteger(gameId) || !isNaN(parseInt(gameId))) - { - ajax("/games", "GET", {gid:gameId}, res => { + if (Number.isInteger(gameId) || !isNaN(parseInt(gameId))) { + ajax("/games", "GET", { gid: gameId }, res => { let game = res.game; game.moves.forEach(m => { m.squares = JSON.parse(m.squares); }); callback(game); }); - } - else //local game - { - dbOperation((db) => { - let objectStore = db.transaction('games').objectStore('games'); + } //local game + else { + dbOperation(db => { + let objectStore = db.transaction("games").objectStore("games"); objectStore.get(gameId).onsuccess = function(event) { callback(event.target.result); - } + }; }); } }, - getCurrent: function(callback) - { - dbOperation((db) => { - let objectStore = db.transaction('games').objectStore('games'); + getCurrent: function(callback) { + dbOperation(db => { + let objectStore = db.transaction("games").objectStore("games"); objectStore.get("*").onsuccess = function(event) { callback(event.target.result); }; @@ -167,20 +153,21 @@ export const GameStorage = }, // Delete a game in indexedDB - remove: function(gameId, callback) - { - dbOperation((db) => { + remove: function(gameId, callback) { + dbOperation(db => { let transaction = db.transaction(["games"], "readwrite"); - if (callback) - { + if (callback) { transaction.oncomplete = function() { callback({}); //everything's fine - } + }; transaction.onerror = function() { - callback({errmsg: store.state.tr["Game removal failed:"] + " " + transaction.error}); + callback({ + errmsg: + store.state.tr["Game removal failed:"] + " " + transaction.error + }); }; } transaction.objectStore("games").delete(gameId); }); - }, + } }; diff --git a/client/src/utils/modalClick.js b/client/src/utils/modalClick.js index 8e0dca7f..96935787 100644 --- a/client/src/utils/modalClick.js +++ b/client/src/utils/modalClick.js @@ -1,7 +1,5 @@ -export function processModalClick(e) -{ +export function processModalClick(e) { // Close a modal when click on it but outside focused element const data = e.target.dataset; - if (!!data.checkbox) - document.getElementById(data.checkbox).checked = false; + if (data.checkbox) document.getElementById(data.checkbox).checked = false; } diff --git a/client/src/utils/printDiagram.js b/client/src/utils/printDiagram.js index bf078cee..aebdc247 100644 --- a/client/src/utils/printDiagram.js +++ b/client/src/utils/printDiagram.js @@ -1,14 +1,11 @@ import { ArrayFun } from "@/utils/array"; // Turn (human) marks into coordinates -function getMarkArray(marks) -{ - if (!marks || marks == "-") - return []; +function getMarkArray(marks) { + if (!marks || marks == "-") return []; let markArray = ArrayFun.init(V.size.x, V.size.y, false); const squares = marks.split(","); - for (let i=0; i= 0) - { + if (squares[i].indexOf("-") >= 0) { // Shadow a range of squares, horizontally or vertically const firstLastSq = squares[i].split("-"); - const range = - [ + const range = [ V.SquareToCoords(firstLastSq[0]), V.SquareToCoords(firstLastSq[1]) ]; - const step = - [ + const step = [ range[1].x == range[0].x ? 0 : (range[1].x - range[0].x) / Math.abs(range[1].x - range[0].x), @@ -59,9 +46,11 @@ function getShadowArray(shadow) : (range[1].y - range[0].y) / Math.abs(range[1].y - range[0].y) ]; // Convention: range always from smaller to larger number - for (let x=range[0].x, y=range[0].y; x <= range[1].x && y <= range[1].y; - x += step[0], y += step[1]) - { + for ( + let x = range[0].x, y = range[0].y; + x <= range[1].x && y <= range[1].y; + x += step[0], y += step[1] + ) { shadowArray[x][y] = true; } continue; @@ -75,30 +64,31 @@ function getShadowArray(shadow) // args: object with position (mandatory), and // orientation, marks, shadow (optional) -export function getDiagram(args) -{ +export function getDiagram(args) { // Obtain the array of pieces images names: const board = V.GetBoard(args.position); const orientation = args.orientation || "w"; const markArray = getMarkArray(args.marks); const shadowArray = getShadowArray(args.shadow); let boardDiv = ""; - const [startX,startY,inc] = orientation == 'w' - ? [0, 0, 1] - : [V.size.x-1, V.size.y-1, -1]; - for (let i=startX; i>=0 && i= 0 && i < V.size.x; i += inc) { boardDiv += "
"; - for (let j=startY; j>=0 && j= 0 && j < V.size.y; j += inc) { + boardDiv += + "
"; - if (board[i][j] != V.EMPTY) - { - boardDiv += ""; } if (markArray.length > 0 && markArray[i][j]) diff --git a/client/src/utils/scoring.js b/client/src/utils/scoring.js index 841de4bf..9c6d25fa 100644 --- a/client/src/utils/scoring.js +++ b/client/src/utils/scoring.js @@ -1,8 +1,7 @@ // Default score message if none provided export function getScoreMessage(score) { let eogMessage = "Undefined"; //not translated: unused - switch (score) - { + switch (score) { case "1-0": eogMessage = "White win"; break; diff --git a/client/src/utils/squareId.js b/client/src/utils/squareId.js index 6469bc3f..58ca0862 100644 --- a/client/src/utils/squareId.js +++ b/client/src/utils/squareId.js @@ -1,13 +1,11 @@ // Get the identifier of a HTML square from its numeric coordinates o.x,o.y. -export function getSquareId(o) -{ +export function getSquareId(o) { // NOTE: a separator is required to allow any size of board - return "sq-" + o.x + "-" + o.y; + return "sq-" + o.x + "-" + o.y; } // Inverse function -export function getSquareFromId(id) -{ - const idParts = id.split('-'); +export function getSquareFromId(id) { + const idParts = id.split("-"); return [parseInt(idParts[1]), parseInt(idParts[2])]; } diff --git a/client/src/utils/timeControl.js b/client/src/utils/timeControl.js index 72d5557f..9106b96c 100644 --- a/client/src/utils/timeControl.js +++ b/client/src/utils/timeControl.js @@ -1,49 +1,46 @@ -function timeUnitToSeconds(value, unit) -{ +function timeUnitToSeconds(value, unit) { let seconds = value; - switch (unit) - { - case 'd': - seconds *= 24; - case 'h': - seconds *= 60; - case 'm': + switch (unit) { + case "d": + seconds *= 86400; //24*60*60 + break; + case "h": + seconds *= 3600; + break; + case "m": seconds *= 60; + break; } return seconds; } -function isLargerUnit(unit1, unit2) -{ - return (unit1 == 'd' && unit2 != 'd') - || (unit1 == 'h' && ['s','m'].includes(unit2)) - || (unit1 == 'm' && unit2 == 's'); +function isLargerUnit(unit1, unit2) { + return ( + (unit1 == "d" && unit2 != "d") || + (unit1 == "h" && ["s", "m"].includes(unit2)) || + (unit1 == "m" && unit2 == "s") + ); } -export function extractTime(cadence) -{ - let tcParts = cadence.replace(/ /g,"").split('+'); +export function extractTime(cadence) { + let tcParts = cadence.replace(/ /g, "").split("+"); // Concatenate usual time control suffixes, in case of none is provided tcParts[0] += "m"; tcParts[1] += "s"; const mainTimeArray = tcParts[0].match(/^([0-9]+)([smhd]+)$/); - if (!mainTimeArray) - return null; + if (!mainTimeArray) return null; const mainTimeValue = parseInt(mainTimeArray[1]); const mainTimeUnit = mainTimeArray[2][0]; const mainTime = timeUnitToSeconds(mainTimeValue, mainTimeUnit); let increment = 0; - if (tcParts.length >= 2) - { + if (tcParts.length >= 2) { const incrementArray = tcParts[1].match(/^([0-9]+)([smhd]+)$/); - if (!incrementArray) - return null; + if (!incrementArray) return null; const incrementValue = parseInt(incrementArray[1]); const incrementUnit = incrementArray[2][0]; // Increment unit cannot be larger than main unit: - if (isLargerUnit(incrementUnit, mainTimeUnit)) - return null; + if (isLargerUnit(incrementUnit, mainTimeUnit)) return null; increment = timeUnitToSeconds(incrementValue, incrementUnit); } - return {mainTime:mainTime, increment:increment}; + return { mainTime: mainTime, increment: increment }; } diff --git a/client/src/variants/Alice.js b/client/src/variants/Alice.js index d7e775cf..e7daf7f8 100644 --- a/client/src/variants/Alice.js +++ b/client/src/variants/Alice.js @@ -1,67 +1,57 @@ import { ChessRules } from "@/base_rules"; -import { ArrayFun} from "@/utils/array"; +import { ArrayFun } from "@/utils/array"; // NOTE: alternative implementation, probably cleaner = use only 1 board // TODO? atLeastOneMove() would be more efficient if rewritten here (less sideBoard computations) -export const VariantRules = class AliceRules extends ChessRules -{ - static get ALICE_PIECES() - { +export const VariantRules = class AliceRules extends ChessRules { + static get ALICE_PIECES() { return { - 's': 'p', - 't': 'q', - 'u': 'r', - 'c': 'b', - 'o': 'n', - 'l': 'k', + s: "p", + t: "q", + u: "r", + c: "b", + o: "n", + l: "k" }; } - static get ALICE_CODES() - { + static get ALICE_CODES() { return { - 'p': 's', - 'q': 't', - 'r': 'u', - 'b': 'c', - 'n': 'o', - 'k': 'l', + p: "s", + q: "t", + r: "u", + b: "c", + n: "o", + k: "l" }; } - static getPpath(b) - { + static getPpath(b) { return (Object.keys(this.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b; } - static get PIECES() - { + static get PIECES() { return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES)); } - setOtherVariables(fen) - { + setOtherVariables(fen) { super.setOtherVariables(fen); const rows = V.ParseFen(fen).position.split("/"); - if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0) - { + if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0) { // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved') - for (let i=0; i { - // Filter out king moves which result in under-check position on - // current board (before mirror traversing) - let aprioriValid = true; - if (m.appear[0].p == V.KING) - { - this.play(m); - if (this.underCheck(color, sideBoard)) - aprioriValid = false; - this.undo(m); - } - return aprioriValid; - }); + this.board = sideBoard[mirrorSide - 1]; + const moves = super.getPotentialMovesFrom([x, y]).filter(m => { + // Filter out king moves which result in under-check position on + // current board (before mirror traversing) + let aprioriValid = true; + if (m.appear[0].p == V.KING) { + this.play(m); + if (this.underCheck(color, sideBoard)) aprioriValid = false; + this.undo(m); + } + return aprioriValid; + }); this.board = saveBoard; // Finally filter impossible moves const res = moves.filter(m => { - if (m.appear.length == 2) //castle - { + if (m.appear.length == 2) { + //castle // appear[i] must be an empty square on the other board - for (let psq of m.appear) - { - if (this.getSquareOccupation(psq.x,psq.y,3-mirrorSide) != V.EMPTY) + for (let psq of m.appear) { + if (this.getSquareOccupation(psq.x, psq.y, 3 - mirrorSide) != V.EMPTY) return false; } - } - else if (this.board[m.end.x][m.end.y] != V.EMPTY) - { + } else if (this.board[m.end.x][m.end.y] != V.EMPTY) { // Attempt to capture - const piece = this.getPiece(m.end.x,m.end.y); - if ((mirrorSide==1 && codes.includes(piece)) - || (mirrorSide==2 && pieces.includes(piece))) - { + const piece = this.getPiece(m.end.x, m.end.y); + if ( + (mirrorSide == 1 && codes.includes(piece)) || + (mirrorSide == 2 && pieces.includes(piece)) + ) { return false; } } // If the move is computed on board1, m.appear change for Alice pieces. - if (mirrorSide==1) - { - m.appear.forEach(psq => { //forEach: castling taken into account + if (mirrorSide == 1) { + m.appear.forEach(psq => { + //forEach: castling taken into account psq.p = V.ALICE_CODES[psq.p]; //goto board2 }); - } - else //move on board2: mark vanishing pieces as Alice - { + } //move on board2: mark vanishing pieces as Alice + else { m.vanish.forEach(psq => { psq.p = V.ALICE_CODES[psq.p]; }); } // Fix en-passant captures - if (m.vanish[0].p == V.PAWN && m.vanish.length == 2 - && this.board[m.end.x][m.end.y] == V.EMPTY) - { - m.vanish[1].c = V.GetOppCol(this.getColor(x,y)); + if ( + m.vanish[0].p == V.PAWN && + m.vanish.length == 2 && + this.board[m.end.x][m.end.y] == V.EMPTY + ) { + m.vanish[1].c = V.GetOppCol(this.getColor(x, y)); // In the special case of en-passant, if // - board1 takes board2 : vanish[1] --> Alice // - board2 takes board1 : vanish[1] --> normal let van = m.vanish[1]; - if (mirrorSide==1 && codes.includes(this.getPiece(van.x,van.y))) + if (mirrorSide == 1 && codes.includes(this.getPiece(van.x, van.y))) van.p = V.ALICE_CODES[van.p]; - else if (mirrorSide==2 && pieces.includes(this.getPiece(van.x,van.y))) + else if ( + mirrorSide == 2 && + pieces.includes(this.getPiece(van.x, van.y)) + ) van.p = V.ALICE_PIECES[van.p]; } return true; @@ -175,12 +159,9 @@ export const VariantRules = class AliceRules extends ChessRules return res; } - filterValid(moves, sideBoard) - { - if (moves.length == 0) - return []; - if (!sideBoard) - sideBoard = [this.getSideBoard(1), this.getSideBoard(2)]; + filterValid(moves, sideBoard) { + if (moves.length == 0) return []; + if (!sideBoard) sideBoard = [this.getSideBoard(1), this.getSideBoard(2)]; const color = this.turn; return moves.filter(m => { this.playSide(m, sideBoard); //no need to track flags @@ -190,20 +171,17 @@ export const VariantRules = class AliceRules extends ChessRules }); } - getAllValidMoves() - { + getAllValidMoves() { const color = this.turn; - const oppCol = V.GetOppCol(color); let potentialMoves = []; const sideBoard = [this.getSideBoard(1), this.getSideBoard(2)]; - for (var i=0; i { - const mirrorSide = (pieces.includes(psq.p) ? 1 : 2); - sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY; + const mirrorSide = pieces.includes(psq.p) ? 1 : 2; + sideBoard[mirrorSide - 1][psq.x][psq.y] = V.EMPTY; }); move.appear.forEach(psq => { - const mirrorSide = (pieces.includes(psq.p) ? 1 : 2); - const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]); - sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece; - if (piece == V.KING) - this.kingPos[psq.c] = [psq.x,psq.y]; + const mirrorSide = pieces.includes(psq.p) ? 1 : 2; + const piece = mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]; + sideBoard[mirrorSide - 1][psq.x][psq.y] = psq.c + piece; + if (piece == V.KING) this.kingPos[psq.c] = [psq.x, psq.y]; }); } // Undo on sideboards - undoSide(move, sideBoard) - { + undoSide(move, sideBoard) { const pieces = Object.keys(V.ALICE_CODES); move.appear.forEach(psq => { - const mirrorSide = (pieces.includes(psq.p) ? 1 : 2); - sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY; + const mirrorSide = pieces.includes(psq.p) ? 1 : 2; + sideBoard[mirrorSide - 1][psq.x][psq.y] = V.EMPTY; }); move.vanish.forEach(psq => { - const mirrorSide = (pieces.includes(psq.p) ? 1 : 2); - const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]); - sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece; - if (piece == V.KING) - this.kingPos[psq.c] = [psq.x,psq.y]; + const mirrorSide = pieces.includes(psq.p) ? 1 : 2; + const piece = mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]; + sideBoard[mirrorSide - 1][psq.x][psq.y] = psq.c + piece; + if (piece == V.KING) this.kingPos[psq.c] = [psq.x, psq.y]; }); } // sideBoard: arg containing both boards (see getAllValidMoves()) - underCheck(color, sideBoard) - { + underCheck(color, sideBoard) { const kp = this.kingPos[color]; - const mirrorSide = (sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2); + const mirrorSide = sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2; let saveBoard = this.board; - this.board = sideBoard[mirrorSide-1]; + this.board = sideBoard[mirrorSide - 1]; let res = this.isAttacked(kp, [V.GetOppCol(color)]); this.board = saveBoard; return res; } - getCheckSquares(color) - { + getCheckSquares(color) { const pieces = Object.keys(V.ALICE_CODES); const kp = this.kingPos[color]; - const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2); + const mirrorSide = pieces.includes(this.getPiece(kp[0], kp[1])) ? 1 : 2; let sideBoard = this.getSideBoard(mirrorSide); let saveBoard = this.board; this.board = sideBoard; let res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]) - ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ] - : [ ]; + ? [JSON.parse(JSON.stringify(this.kingPos[color]))] + : []; this.board = saveBoard; return res; } - updateVariables(move) - { + updateVariables(move) { super.updateVariables(move); //standard king const piece = move.vanish[0].p; const c = move.vanish[0].c; // "l" = Alice king - if (piece == "l") - { + if (piece == "l") { this.kingPos[c][0] = move.appear[0].x; this.kingPos[c][1] = move.appear[0].y; - this.castleFlags[c] = [false,false]; + this.castleFlags[c] = [false, false]; } } - unupdateVariables(move) - { + unupdateVariables(move) { super.unupdateVariables(move); const c = move.vanish[0].c; - if (move.vanish[0].p == "l") - this.kingPos[c] = [move.start.x, move.start.y]; + if (move.vanish[0].p == "l") this.kingPos[c] = [move.start.x, move.start.y]; } - getCurrentScore() - { - if (this.atLeastOneMove()) // game not over + getCurrentScore() { + if (this.atLeastOneMove()) + // game not over return "*"; const pieces = Object.keys(V.ALICE_CODES); const color = this.turn; const kp = this.kingPos[color]; - const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2); + const mirrorSide = pieces.includes(this.getPiece(kp[0], kp[1])) ? 1 : 2; let sideBoard = this.getSideBoard(mirrorSide); let saveBoard = this.board; this.board = sideBoard; let res = "*"; if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])) res = "1/2"; - else - res = (color == "w" ? "0-1" : "1-0"); + else res = color == "w" ? "0-1" : "1-0"; this.board = saveBoard; return res; } - static get VALUES() - { - return Object.assign( - ChessRules.VALUES, - { - 's': 1, - 'u': 5, - 'o': 3, - 'c': 3, - 't': 9, - 'l': 1000, - } - ); + static get VALUES() { + return Object.assign(ChessRules.VALUES, { + s: 1, + u: 5, + o: 3, + c: 3, + t: 9, + l: 1000 + }); } - getNotation(move) - { - if (move.appear.length == 2 && move.appear[0].p == V.KING) - { - if (move.end.y < move.start.y) - return "0-0-0"; - else - return "0-0"; + getNotation(move) { + if (move.appear.length == 2 && move.appear[0].p == V.KING) { + if (move.end.y < move.start.y) return "0-0-0"; + return "0-0"; } const finalSquare = V.CoordsToSquare(move.end); const piece = this.getPiece(move.start.x, move.start.y); - const captureMark = (move.vanish.length > move.appear.length ? "x" : ""); + const captureMark = move.vanish.length > move.appear.length ? "x" : ""; let pawnMark = ""; - if (["p","s"].includes(piece) && captureMark.length == 1) + if (["p", "s"].includes(piece) && captureMark.length == 1) pawnMark = V.CoordToColumn(move.start.y); //start column // Piece or pawn movement let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare; - if (['s','p'].includes(piece) && !['s','p'].includes(move.appear[0].p)) - { + if (["s", "p"].includes(piece) && !["s", "p"].includes(move.appear[0].p)) { // Promotion notation += "=" + move.appear[0].p.toUpperCase(); } return notation; } -} +}; diff --git a/client/src/variants/Antiking.js b/client/src/variants/Antiking.js index 610dd257..a13247a1 100644 --- a/client/src/variants/Antiking.js +++ b/client/src/variants/Antiking.js @@ -1,172 +1,168 @@ import { ChessRules } from "@/base_rules"; -import { ArrayFun} from "@/utils/array"; +import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class AntikingRules extends ChessRules -{ - static getPpath(b) - { - return b[1]=='a' ? "Antiking/"+b : b; +export const VariantRules = class AntikingRules extends ChessRules { + static getPpath(b) { + return b[1] == "a" ? "Antiking/" + b : b; } - static get ANTIKING() { return 'a'; } + static get ANTIKING() { + return "a"; + } - static get PIECES() - { + static get PIECES() { return ChessRules.PIECES.concat([V.ANTIKING]); } - setOtherVariables(fen) - { + setOtherVariables(fen) { super.setOtherVariables(fen); - this.antikingPos = {'w':[-1,-1], 'b':[-1,-1]}; + this.antikingPos = { w: [-1, -1], b: [-1, -1] }; const rows = V.ParseFen(fen).position.split("/"); - for (let i=0; i0?antikingPos["w"]:"") - + "A" + (antikingPos["w"]<7?7-antikingPos["w"]:""); - const ranks23_white = (antikingPos["b"]>0?antikingPos["b"]:"") + "a" - + (antikingPos["b"]<7?7-antikingPos["b"]:"") + "/PPPPPPPP"; - return pieces["b"].join("") + "/" + ranks23_black + + const ranks23_black = + "pppppppp/" + + (antikingPos["w"] > 0 ? antikingPos["w"] : "") + + "A" + + (antikingPos["w"] < 7 ? 7 - antikingPos["w"] : ""); + const ranks23_white = + (antikingPos["b"] > 0 ? antikingPos["b"] : "") + + "a" + + (antikingPos["b"] < 7 ? 7 - antikingPos["b"] : "") + + "/PPPPPPPP"; + return ( + pieces["b"].join("") + + "/" + + ranks23_black + "/8/8/" + - ranks23_white + "/" + pieces["w"].join("").toUpperCase() + - " w 0 1111 -"; + ranks23_white + + "/" + + pieces["w"].join("").toUpperCase() + + " w 0 1111 -" + ); } -} +}; diff --git a/client/src/variants/Atomic.js b/client/src/variants/Atomic.js index 3d4f541f..996f2d1a 100644 --- a/client/src/variants/Atomic.js +++ b/client/src/variants/Atomic.js @@ -1,29 +1,43 @@ import { ChessRules, PiPo } from "@/base_rules"; -export const VariantRules = class AtomicRules extends ChessRules -{ - getPotentialMovesFrom([x,y]) - { - let moves = super.getPotentialMovesFrom([x,y]); +export const VariantRules = class AtomicRules extends ChessRules { + getPotentialMovesFrom([x, y]) { + let moves = super.getPotentialMovesFrom([x, y]); // Handle explosions moves.forEach(m => { - if (m.vanish.length > 1 && m.appear.length <= 1) //avoid castles - { + if (m.vanish.length > 1 && m.appear.length <= 1) { + //avoid castles // Explosion! OPTION (TODO?): drop moves which explode our king here - let steps = [ [-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1] ]; - for (let step of steps) - { + let steps = [ + [-1, -1], + [-1, 0], + [-1, 1], + [0, -1], + [0, 1], + [1, -1], + [1, 0], + [1, 1] + ]; + for (let step of steps) { let x = m.end.x + step[0]; let y = m.end.y + step[1]; - if (V.OnBoard(x,y) && this.board[x][y] != V.EMPTY - && this.getPiece(x,y) != V.PAWN) - { + if ( + V.OnBoard(x, y) && + this.board[x][y] != V.EMPTY && + this.getPiece(x, y) != V.PAWN + ) { m.vanish.push( - new PiPo({p:this.getPiece(x,y),c:this.getColor(x,y),x:x,y:y})); + new PiPo({ + p: this.getPiece(x, y), + c: this.getColor(x, y), + x: x, + y: y + }) + ); } } - m.end = {x:m.appear[0].x, y:m.appear[0].y}; + m.end = { x: m.appear[0].x, y: m.appear[0].y }; m.appear.pop(); //Nothin appears in this case } }); @@ -31,56 +45,53 @@ export const VariantRules = class AtomicRules extends ChessRules return moves; } - getPotentialKingMoves([x,y]) - { + getPotentialKingMoves([x, y]) { // King cannot capture: let moves = []; const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); - for (let step of steps) - { + for (let step of steps) { const i = x + step[0]; const j = y + step[1]; - if (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY) - moves.push(this.getBasicMove([x,y], [i,j])); + if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) + moves.push(this.getBasicMove([x, y], [i, j])); } - return moves.concat(this.getCastleMoves([x,y])); + return moves.concat(this.getCastleMoves([x, y])); } - isAttacked(sq, colors) - { - if (this.getPiece(sq[0],sq[1]) == V.KING && this.isAttackedByKing(sq, colors)) + isAttacked(sq, colors) { + if ( + this.getPiece(sq[0], sq[1]) == V.KING && + this.isAttackedByKing(sq, colors) + ) return false; //king cannot take... - return (this.isAttackedByPawn(sq, colors) - || this.isAttackedByRook(sq, colors) - || this.isAttackedByKnight(sq, colors) - || this.isAttackedByBishop(sq, colors) - || this.isAttackedByQueen(sq, colors)); + return ( + this.isAttackedByPawn(sq, colors) || + this.isAttackedByRook(sq, colors) || + this.isAttackedByKnight(sq, colors) || + this.isAttackedByBishop(sq, colors) || + this.isAttackedByQueen(sq, colors) + ); } - updateVariables(move) - { + updateVariables(move) { super.updateVariables(move); - const color = move.vanish[0].c; - if (move.appear.length == 0) //capture - { - const firstRank = {"w": 7, "b": 0}; - for (let c of ["w","b"]) - { + if (move.appear.length == 0) { + //capture + const firstRank = { w: 7, b: 0 }; + for (let c of ["w", "b"]) { // Did we explode king of color c ? (TODO: remove move earlier) - if (Math.abs(this.kingPos[c][0]-move.end.x) <= 1 - && Math.abs(this.kingPos[c][1]-move.end.y) <= 1) - { - this.kingPos[c] = [-1,-1]; - this.castleFlags[c] = [false,false]; - } - else - { + if ( + Math.abs(this.kingPos[c][0] - move.end.x) <= 1 && + Math.abs(this.kingPos[c][1] - move.end.y) <= 1 + ) { + this.kingPos[c] = [-1, -1]; + this.castleFlags[c] = [false, false]; + } else { // Now check if init rook(s) exploded - if (Math.abs(move.end.x-firstRank[c]) <= 1) - { - if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][0]) <= 1) + if (Math.abs(move.end.x - firstRank[c]) <= 1) { + if (Math.abs(move.end.y - this.INIT_COL_ROOK[c][0]) <= 1) this.castleFlags[c][0] = false; - if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][1]) <= 1) + if (Math.abs(move.end.y - this.INIT_COL_ROOK[c][1]) <= 1) this.castleFlags[c][1] = false; } } @@ -88,59 +99,56 @@ export const VariantRules = class AtomicRules extends ChessRules } } - unupdateVariables(move) - { + unupdateVariables(move) { super.unupdateVariables(move); const c = move.vanish[0].c; const oppCol = V.GetOppCol(c); - if ([this.kingPos[c][0],this.kingPos[oppCol][0]].some(e => { return e < 0; })) - { + if ( + [this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => { + return e < 0; + }) + ) { // There is a chance that last move blowed some king away.. - for (let psq of move.vanish) - { - if (psq.p == 'k') - this.kingPos[psq.c==c ? c : oppCol] = [psq.x, psq.y]; + for (let psq of move.vanish) { + if (psq.p == "k") + this.kingPos[psq.c == c ? c : oppCol] = [psq.x, psq.y]; } } } - underCheck(color) - { + underCheck(color) { const oppCol = V.GetOppCol(color); let res = undefined; // If our king disappeared, move is not valid - if (this.kingPos[color][0] < 0) - res = true; + if (this.kingPos[color][0] < 0) res = true; // If opponent king disappeared, move is valid - else if (this.kingPos[oppCol][0] < 0) - res = false; + else if (this.kingPos[oppCol][0] < 0) res = false; // Otherwise, if we remain under check, move is not valid - else - res = this.isAttacked(this.kingPos[color], [oppCol]); + else res = this.isAttacked(this.kingPos[color], [oppCol]); return res; } - getCheckSquares(color) - { - let res = [ ]; - if (this.kingPos[color][0] >= 0 //king might have exploded - && this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])) - { - res = [ JSON.parse(JSON.stringify(this.kingPos[color])) ] + getCheckSquares(color) { + let res = []; + if ( + this.kingPos[color][0] >= 0 && //king might have exploded + this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]) + ) { + res = [JSON.parse(JSON.stringify(this.kingPos[color]))]; } return res; } - getCurrentScore() - { + getCurrentScore() { const color = this.turn; const kp = this.kingPos[color]; - if (kp[0] < 0) //king disappeared + if (kp[0] < 0) + //king disappeared return color == "w" ? "0-1" : "1-0"; - if (this.atLeastOneMove()) // game not over + if (this.atLeastOneMove()) + // game not over return "*"; - if (!this.isAttacked(kp, [V.GetOppCol(color)])) - return "1/2"; + if (!this.isAttacked(kp, [V.GetOppCol(color)])) return "1/2"; return color == "w" ? "0-1" : "1-0"; //checkmate } -} +}; diff --git a/client/src/variants/Baroque.js b/client/src/variants/Baroque.js index 5ac9a011..79fc73ce 100644 --- a/client/src/variants/Baroque.js +++ b/client/src/variants/Baroque.js @@ -2,54 +2,54 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class BaroqueRules extends ChessRules -{ - static get HasFlags() { return false; } +export const VariantRules = class BaroqueRules extends ChessRules { + static get HasFlags() { + return false; + } - static get HasEnpassant() { return false; } + static get HasEnpassant() { + return false; + } - static getPpath(b) - { - if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1) + static getPpath(b) { + if (b[1] == "m") + //'m' for Immobilizer (I is too similar to 1) return "Baroque/" + b; return b; //usual piece } - static get PIECES() - { + static get PIECES() { return ChessRules.PIECES.concat([V.IMMOBILIZER]); } // No castling, but checks, so keep track of kings - setOtherVariables(fen) - { - this.kingPos = {'w':[-1,-1], 'b':[-1,-1]}; + setOtherVariables(fen) { + this.kingPos = { w: [-1, -1], b: [-1, -1] }; const fenParts = fen.split(" "); const position = fenParts[0].split("/"); - for (let i=0; i { - if (!!byChameleon && m.start.x!=m.end.x && m.start.y!=m.end.y) - return; //chameleon not moving as pawn + if (!!byChameleon && m.start.x != m.end.x && m.start.y != m.end.y) return; //chameleon not moving as pawn // Try capturing in every direction - for (let step of steps) - { - const sq2 = [m.end.x+2*step[0],m.end.y+2*step[1]]; - if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] != V.EMPTY - && this.getColor(sq2[0],sq2[1]) == color) - { + for (let step of steps) { + const sq2 = [m.end.x + 2 * step[0], m.end.y + 2 * step[1]]; + if ( + V.OnBoard(sq2[0], sq2[1]) && + this.board[sq2[0]][sq2[1]] != V.EMPTY && + this.getColor(sq2[0], sq2[1]) == color + ) { // Potential capture - const sq1 = [m.end.x+step[0],m.end.y+step[1]]; - if (this.board[sq1[0]][sq1[1]] != V.EMPTY - && this.getColor(sq1[0],sq1[1]) == oppCol) - { - const piece1 = this.getPiece(sq1[0],sq1[1]); - if (!byChameleon || piece1 == V.PAWN) - { - m.vanish.push(new PiPo({ - x:sq1[0], - y:sq1[1], - c:oppCol, - p:piece1 - })); + const sq1 = [m.end.x + step[0], m.end.y + step[1]]; + if ( + this.board[sq1[0]][sq1[1]] != V.EMPTY && + this.getColor(sq1[0], sq1[1]) == oppCol + ) { + const piece1 = this.getPiece(sq1[0], sq1[1]); + if (!byChameleon || piece1 == V.PAWN) { + m.vanish.push( + new PiPo({ + x: sq1[0], + y: sq1[1], + c: oppCol, + p: piece1 + }) + ); } } } @@ -174,37 +163,33 @@ export const VariantRules = class BaroqueRules extends ChessRules } // "Pincer" - getPotentialPawnMoves([x,y]) - { - let moves = super.getPotentialRookMoves([x,y]); + getPotentialPawnMoves([x, y]) { + let moves = super.getPotentialRookMoves([x, y]); this.addPawnCaptures(moves); return moves; } - addRookCaptures(moves, byChameleon) - { + addRookCaptures(moves, byChameleon) { const color = this.turn; const oppCol = V.GetOppCol(color); const kp = this.kingPos[color]; moves.forEach(m => { // Check piece-king rectangle (if any) corners for enemy pieces - if (m.end.x == kp[0] || m.end.y == kp[1]) - return; //"flat rectangle" + if (m.end.x == kp[0] || m.end.y == kp[1]) return; //"flat rectangle" const corner1 = [m.end.x, kp[1]]; const corner2 = [kp[0], m.end.y]; - for (let [i,j] of [corner1,corner2]) - { - if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol) - { - const piece = this.getPiece(i,j); - if (!byChameleon || piece == V.ROOK) - { - m.vanish.push( new PiPo({ - x:i, - y:j, - p:piece, - c:oppCol - }) ); + for (let [i, j] of [corner1, corner2]) { + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == oppCol) { + const piece = this.getPiece(i, j); + if (!byChameleon || piece == V.ROOK) { + m.vanish.push( + new PiPo({ + x: i, + y: j, + p: piece, + c: oppCol + }) + ); } } } @@ -212,88 +197,84 @@ export const VariantRules = class BaroqueRules extends ChessRules } // Coordinator - getPotentialRookMoves(sq) - { + getPotentialRookMoves(sq) { let moves = super.getPotentialQueenMoves(sq); this.addRookCaptures(moves); return moves; } // Long-leaper - getKnightCaptures(startSquare, byChameleon) - { + getKnightCaptures(startSquare, byChameleon) { // Look in every direction for captures const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); const color = this.turn; const oppCol = V.GetOppCol(color); let moves = []; - const [x,y] = [startSquare[0],startSquare[1]]; - const piece = this.getPiece(x,y); //might be a chameleon! - outerLoop: - for (let step of steps) - { - let [i,j] = [x+step[0], y+step[1]]; - while (V.OnBoard(i,j) && this.board[i][j]==V.EMPTY) - { + const [x, y] = [startSquare[0], startSquare[1]]; + const piece = this.getPiece(x, y); //might be a chameleon! + outerLoop: for (let step of steps) { + let [i, j] = [x + step[0], y + step[1]]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { i += step[0]; j += step[1]; } - if (!V.OnBoard(i,j) || this.getColor(i,j)==color - || (!!byChameleon && this.getPiece(i,j)!=V.KNIGHT)) - { + if ( + !V.OnBoard(i, j) || + this.getColor(i, j) == color || + (!!byChameleon && this.getPiece(i, j) != V.KNIGHT) + ) { continue; } // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits, // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then // add move until cur square; if cur is occupied then stop if !!byChameleon and // the square not occupied by a leaper. - let last = [i,j]; - let cur = [i+step[0],j+step[1]]; - let vanished = [ new PiPo({x:x,y:y,c:color,p:piece}) ]; - while (V.OnBoard(cur[0],cur[1])) - { - if (this.board[last[0]][last[1]] != V.EMPTY) - { - const oppPiece = this.getPiece(last[0],last[1]); - if (!!byChameleon && oppPiece != V.KNIGHT) - continue outerLoop; + let last = [i, j]; + let cur = [i + step[0], j + step[1]]; + let vanished = [new PiPo({ x: x, y: y, c: color, p: piece })]; + while (V.OnBoard(cur[0], cur[1])) { + if (this.board[last[0]][last[1]] != V.EMPTY) { + const oppPiece = this.getPiece(last[0], last[1]); + if (!!byChameleon && oppPiece != V.KNIGHT) continue outerLoop; // Something to eat: - vanished.push( new PiPo({x:last[0],y:last[1],c:oppCol,p:oppPiece}) ); + vanished.push( + new PiPo({ x: last[0], y: last[1], c: oppCol, p: oppPiece }) + ); } - if (this.board[cur[0]][cur[1]] != V.EMPTY) - { - if (this.getColor(cur[0],cur[1]) == color - || this.board[last[0]][last[1]] != V.EMPTY) //TODO: redundant test - { + if (this.board[cur[0]][cur[1]] != V.EMPTY) { + if ( + this.getColor(cur[0], cur[1]) == color || + this.board[last[0]][last[1]] != V.EMPTY + ) { + //TODO: redundant test continue outerLoop; } + } else { + moves.push( + new Move({ + appear: [new PiPo({ x: cur[0], y: cur[1], c: color, p: piece })], + vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required? + start: { x: x, y: y }, + end: { x: cur[0], y: cur[1] } + }) + ); } - else - { - moves.push(new Move({ - appear: [ new PiPo({x:cur[0],y:cur[1],c:color,p:piece}) ], - vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required? - start: {x:x,y:y}, - end: {x:cur[0],y:cur[1]} - })); - } - last = [last[0]+step[0],last[1]+step[1]]; - cur = [cur[0]+step[0],cur[1]+step[1]]; + last = [last[0] + step[0], last[1] + step[1]]; + cur = [cur[0] + step[0], cur[1] + step[1]]; } } return moves; } // Long-leaper - getPotentialKnightMoves(sq) - { + getPotentialKnightMoves(sq) { return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq)); } - getPotentialBishopMoves([x,y]) - { - let moves = super.getPotentialQueenMoves([x,y]) - .concat(this.getKnightCaptures([x,y],"asChameleon")); + getPotentialBishopMoves([x, y]) { + let moves = super + .getPotentialQueenMoves([x, y]) + .concat(this.getKnightCaptures([x, y], "asChameleon")); // No "king capture" because king cannot remain under check this.addPawnCaptures(moves, "asChameleon"); this.addRookCaptures(moves, "asChameleon"); @@ -302,111 +283,119 @@ export const VariantRules = class BaroqueRules extends ChessRules let mergedMoves = {}; moves.forEach(m => { const key = m.end.x + V.size.x * m.end.y; - if (!mergedMoves[key]) - mergedMoves[key] = m; - else - { - for (let i=1; i { moves.push(mergedMoves[k]); }); + Object.keys(mergedMoves).forEach(k => { + moves.push(mergedMoves[k]); + }); return moves; } // Withdrawer - addQueenCaptures(moves, byChameleon) - { - if (moves.length == 0) - return; - const [x,y] = [moves[0].start.x,moves[0].start.y]; + addQueenCaptures(moves, byChameleon) { + if (moves.length == 0) return; + const [x, y] = [moves[0].start.x, moves[0].start.y]; const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); let capturingDirections = []; const color = this.turn; const oppCol = V.GetOppCol(color); adjacentSteps.forEach(step => { - const [i,j] = [x+step[0],y+step[1]]; - if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol - && (!byChameleon || this.getPiece(i,j) == V.QUEEN)) - { + const [i, j] = [x + step[0], y + step[1]]; + if ( + V.OnBoard(i, j) && + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == oppCol && + (!byChameleon || this.getPiece(i, j) == V.QUEEN) + ) { capturingDirections.push(step); } }); moves.forEach(m => { const step = [ - m.end.x!=x ? (m.end.x-x)/Math.abs(m.end.x-x) : 0, - m.end.y!=y ? (m.end.y-y)/Math.abs(m.end.y-y) : 0 + m.end.x != x ? (m.end.x - x) / Math.abs(m.end.x - x) : 0, + m.end.y != y ? (m.end.y - y) / Math.abs(m.end.y - y) : 0 ]; // NOTE: includes() and even _.isEqual() functions fail... // TODO: this test should be done only once per direction - if (capturingDirections.some(dir => - { return (dir[0]==-step[0] && dir[1]==-step[1]); })) - { - const [i,j] = [x-step[0],y-step[1]]; - m.vanish.push(new PiPo({ - x:i, - y:j, - p:this.getPiece(i,j), - c:oppCol - })); + if ( + capturingDirections.some(dir => { + return dir[0] == -step[0] && dir[1] == -step[1]; + }) + ) { + const [i, j] = [x - step[0], y - step[1]]; + m.vanish.push( + new PiPo({ + x: i, + y: j, + p: this.getPiece(i, j), + c: oppCol + }) + ); } }); } - getPotentialQueenMoves(sq) - { + getPotentialQueenMoves(sq) { let moves = super.getPotentialQueenMoves(sq); this.addQueenCaptures(moves); return moves; } - getPotentialImmobilizerMoves(sq) - { + getPotentialImmobilizerMoves(sq) { // Immobilizer doesn't capture return super.getPotentialQueenMoves(sq); } - getPotentialKingMoves(sq) - { - return this.getSlideNJumpMoves(sq, - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); + getPotentialKingMoves(sq) { + return this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + "oneStep" + ); } // isAttacked() is OK because the immobilizer doesn't take - isAttackedByPawn([x,y], colors) - { + isAttackedByPawn([x, y], colors) { // Square (x,y) must be surroundable by two enemy pieces, // and one of them at least should be a pawn (moving). - const dirs = [ [1,0],[0,1] ]; + const dirs = [ + [1, 0], + [0, 1] + ]; const steps = V.steps[V.ROOK]; - for (let dir of dirs) - { - const [i1,j1] = [x-dir[0],y-dir[1]]; //"before" - const [i2,j2] = [x+dir[0],y+dir[1]]; //"after" - if (V.OnBoard(i1,j1) && V.OnBoard(i2,j2)) - { - if ((this.board[i1][j1]!=V.EMPTY && colors.includes(this.getColor(i1,j1)) - && this.board[i2][j2]==V.EMPTY) - || - (this.board[i2][j2]!=V.EMPTY && colors.includes(this.getColor(i2,j2)) - && this.board[i1][j1]==V.EMPTY)) - { + for (let dir of dirs) { + const [i1, j1] = [x - dir[0], y - dir[1]]; //"before" + const [i2, j2] = [x + dir[0], y + dir[1]]; //"after" + if (V.OnBoard(i1, j1) && V.OnBoard(i2, j2)) { + if ( + (this.board[i1][j1] != V.EMPTY && + colors.includes(this.getColor(i1, j1)) && + this.board[i2][j2] == V.EMPTY) || + (this.board[i2][j2] != V.EMPTY && + colors.includes(this.getColor(i2, j2)) && + this.board[i1][j1] == V.EMPTY) + ) { // Search a movable enemy pawn landing on the empty square - for (let step of steps) - { - let [ii,jj] = (this.board[i1][j1]==V.EMPTY ? [i1,j1] : [i2,j2]); - let [i3,j3] = [ii+step[0],jj+step[1]]; - while (V.OnBoard(i3,j3) && this.board[i3][j3]==V.EMPTY) - { + for (let step of steps) { + let [ii, jj] = this.board[i1][j1] == V.EMPTY ? [i1, j1] : [i2, j2]; + let [i3, j3] = [ii + step[0], jj + step[1]]; + while (V.OnBoard(i3, j3) && this.board[i3][j3] == V.EMPTY) { i3 += step[0]; j3 += step[1]; } - if (V.OnBoard(i3,j3) && colors.includes(this.getColor(i3,j3)) - && this.getPiece(i3,j3) == V.PAWN && !this.isImmobilized([i3,j3])) - { + if ( + V.OnBoard(i3, j3) && + colors.includes(this.getColor(i3, j3)) && + this.getPiece(i3, j3) == V.PAWN && + !this.isImmobilized([i3, j3]) + ) { return true; } } @@ -416,31 +405,30 @@ export const VariantRules = class BaroqueRules extends ChessRules return false; } - isAttackedByRook([x,y], colors) - { + isAttackedByRook([x, y], colors) { // King must be on same column or row, // and a rook should be able to reach a capturing square // colors contains only one element, giving the oppCol and thus king position - const sameRow = (x == this.kingPos[colors[0]][0]); - const sameColumn = (y == this.kingPos[colors[0]][1]); - if (sameRow || sameColumn) - { + const sameRow = x == this.kingPos[colors[0]][0]; + const sameColumn = y == this.kingPos[colors[0]][1]; + if (sameRow || sameColumn) { // Look for the enemy rook (maximum 1) - for (let i=0; i1 ? "x" : "") + finalSquare; - else - notation = move.appear[0].p.toUpperCase() + finalSquare; - if (move.vanish.length > 1 && move.appear[0].p != V.KING) - notation += "X"; //capture mark (not describing what is captured...) + } else if (move.appear[0].p == V.KING) + notation = "K" + (move.vanish.length > 1 ? "x" : "") + finalSquare; + else notation = move.appear[0].p.toUpperCase() + finalSquare; + if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X"; //capture mark (not describing what is captured...) return notation; } -} +}; diff --git a/client/src/variants/Berolina.js b/client/src/variants/Berolina.js index 592b25a6..03b1b3b2 100644 --- a/client/src/variants/Berolina.js +++ b/client/src/variants/Berolina.js @@ -1,17 +1,12 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class BerolinaRules extends ChessRules -{ +export const VariantRules = class BerolinaRules extends ChessRules { // En-passant after 2-sq jump - getEpSquare(moveOrSquare) - { - if (!moveOrSquare) - return undefined; - if (typeof moveOrSquare === "string") - { + getEpSquare(moveOrSquare) { + if (!moveOrSquare) return undefined; + if (typeof moveOrSquare === "string") { const square = moveOrSquare; - if (square == "-") - return undefined; + if (square == "-") return undefined; // Enemy pawn initial column must be given too: let res = []; const epParts = square.split(","); @@ -21,14 +16,12 @@ export const VariantRules = class BerolinaRules extends ChessRules } // Argument is a move: const move = moveOrSquare; - const [sx,ex,sy] = [move.start.x,move.end.x,move.start.y]; - if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2) - { - return - [ + const [sx, ex, sy] = [move.start.x, move.end.x, move.start.y]; + if (this.getPiece(sx, sy) == V.PAWN && Math.abs(sx - ex) == 2) { + return [ { - x: (ex + sx)/2, - y: (move.end.y + sy)/2 + x: (ex + sx) / 2, + y: (move.end.y + sy) / 2 }, move.end.y ]; @@ -37,57 +30,66 @@ export const VariantRules = class BerolinaRules extends ChessRules } // Special pawns movements - getPotentialPawnMoves([x,y]) - { + getPotentialPawnMoves([x, y]) { const color = this.turn; let moves = []; - const [sizeX,sizeY] = [V.size.x,V.size.y]; - const shiftX = (color == "w" ? -1 : 1); - const firstRank = (color == 'w' ? sizeX-1 : 0); - const startRank = (color == "w" ? sizeX-2 : 1); - const lastRank = (color == "w" ? 0 : sizeX-1); - const finalPieces = x + shiftX == lastRank - ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN] - : [V.PAWN]; + const [sizeX, sizeY] = [V.size.x, V.size.y]; + const shiftX = color == "w" ? -1 : 1; + const startRank = color == "w" ? sizeX - 2 : 1; + const lastRank = color == "w" ? 0 : sizeX - 1; + const finalPieces = + x + shiftX == lastRank ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] : [V.PAWN]; // One square diagonally - for (let shiftY of [-1,1]) - { - if (this.board[x+shiftX][y+shiftY] == V.EMPTY) - { - for (let piece of finalPieces) - { - moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY], - {c:color,p:piece})); + for (let shiftY of [-1, 1]) { + if (this.board[x + shiftX][y + shiftY] == V.EMPTY) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y + shiftY], { + c: color, + p: piece + }) + ); } - if (x == startRank && y+2*shiftY>=0 && y+2*shiftY= 0 && + y + 2 * shiftY < sizeY && + this.board[x + 2 * shiftX][y + 2 * shiftY] == V.EMPTY + ) { // Two squares jump - moves.push(this.getBasicMove([x,y], [x+2*shiftX,y+2*shiftY])); + moves.push( + this.getBasicMove([x, y], [x + 2 * shiftX, y + 2 * shiftY]) + ); } } } // Capture - if (this.board[x+shiftX][y] != V.EMPTY - && this.canTake([x,y], [x+shiftX,y])) - { + if ( + this.board[x + shiftX][y] != V.EMPTY && + this.canTake([x, y], [x + shiftX, y]) + ) { for (let piece of finalPieces) - moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece})); + moves.push( + this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece }) + ); } // En passant const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep-1]; //always at least one element - if (!!epSquare && epSquare[0].x == x+shiftX && epSquare[0].y == y - && Math.abs(epSquare[1] - y) == 1) - { - let enpassantMove = this.getBasicMove([x,y], [x+shiftX,y]); + const epSquare = this.epSquares[Lep - 1]; //always at least one element + if ( + !!epSquare && + epSquare[0].x == x + shiftX && + epSquare[0].y == y && + Math.abs(epSquare[1] - y) == 1 + ) { + let enpassantMove = this.getBasicMove([x, y], [x + shiftX, y]); enpassantMove.vanish.push({ x: x, y: epSquare[1], - p: 'p', - c: this.getColor(x,epSquare[1]) + p: "p", + c: this.getColor(x, epSquare[1]) }); moves.push(enpassantMove); } @@ -95,16 +97,14 @@ export const VariantRules = class BerolinaRules extends ChessRules return moves; } - isAttackedByPawn([x,y], colors) - { - for (let c of colors) - { - let pawnShift = (c=="w" ? 1 : -1); - if (x+pawnShift>=0 && x+pawnShift= 0 && x + pawnShift < V.size.x) { + if ( + this.getPiece(x + pawnShift, y) == V.PAWN && + this.getColor(x + pawnShift, y) == c + ) { return true; } } @@ -112,26 +112,25 @@ export const VariantRules = class BerolinaRules extends ChessRules return false; } - getNotation(move) - { + getNotation(move) { const piece = this.getPiece(move.start.x, move.start.y); - if (piece == V.PAWN) - { + if (piece == V.PAWN) { // Pawn move const finalSquare = V.CoordsToSquare(move.end); let notation = ""; - if (move.vanish.length == 2) //capture + if (move.vanish.length == 2) + //capture notation = "Px" + finalSquare; - else - { + else { // No capture: indicate the initial square for potential ambiguity const startSquare = V.CoordsToSquare(move.start); notation = startSquare + finalSquare; } - if (move.appear[0].p != V.PAWN) //promotion + if (move.appear[0].p != V.PAWN) + //promotion notation += "=" + move.appear[0].p.toUpperCase(); return notation; } return super.getNotation(move); //all other pieces are orthodox } -} +}; diff --git a/client/src/variants/Checkered.js b/client/src/variants/Checkered.js index afbf21e2..0e4f0a0d 100644 --- a/client/src/variants/Checkered.js +++ b/client/src/variants/Checkered.js @@ -1,159 +1,144 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class CheckeredRules extends ChessRules -{ - static getPpath(b) - { - return b[0]=='c' ? "Checkered/"+b : b; +export const VariantRules = class CheckeredRules extends ChessRules { + static getPpath(b) { + return b[0] == "c" ? "Checkered/" + b : b; } - static board2fen(b) - { + static board2fen(b) { const checkered_codes = { - 'p': 's', - 'q': 't', - 'r': 'u', - 'b': 'c', - 'n': 'o', + p: "s", + q: "t", + r: "u", + b: "c", + n: "o" }; - if (b[0]=="c") - return checkered_codes[b[1]]; + if (b[0] == "c") return checkered_codes[b[1]]; return ChessRules.board2fen(b); } - static fen2board(f) - { + static fen2board(f) { // Tolerate upper-case versions of checkered pieces (why not?) const checkered_pieces = { - 's': 'p', - 'S': 'p', - 't': 'q', - 'T': 'q', - 'u': 'r', - 'U': 'r', - 'c': 'b', - 'C': 'b', - 'o': 'n', - 'O': 'n', + s: "p", + S: "p", + t: "q", + T: "q", + u: "r", + U: "r", + c: "b", + C: "b", + o: "n", + O: "n" }; if (Object.keys(checkered_pieces).includes(f)) - return 'c'+checkered_pieces[f]; + return "c" + checkered_pieces[f]; return ChessRules.fen2board(f); } - static get PIECES() - { - return ChessRules.PIECES.concat(['s','t','u','c','o']); + static get PIECES() { + return ChessRules.PIECES.concat(["s", "t", "u", "c", "o"]); } - setOtherVariables(fen) - { + setOtherVariables(fen) { super.setOtherVariables(fen); // Local stack of non-capturing checkered moves: this.cmoves = []; const cmove = fen.split(" ")[5]; - if (cmove == "-") - this.cmoves.push(null); - else - { + if (cmove == "-") this.cmoves.push(null); + else { this.cmoves.push({ - start: ChessRules.SquareToCoords(cmove.substr(0,2)), - end: ChessRules.SquareToCoords(cmove.substr(2)), + start: ChessRules.SquareToCoords(cmove.substr(0, 2)), + end: ChessRules.SquareToCoords(cmove.substr(2)) }); } } - static IsGoodFen(fen) - { - if (!ChessRules.IsGoodFen(fen)) - return false; + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; const fenParts = fen.split(" "); - if (fenParts.length != 6) - return false; + if (fenParts.length != 6) return false; if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/)) return false; return true; } - static IsGoodFlags(flags) - { + static IsGoodFlags(flags) { // 4 for castle + 16 for pawns return !!flags.match(/^[01]{20,20}$/); } - setFlags(fenflags) - { + setFlags(fenflags) { super.setFlags(fenflags); //castleFlags - this.pawnFlags = - { - "w": [...Array(8).fill(true)], //pawns can move 2 squares? - "b": [...Array(8).fill(true)], + this.pawnFlags = { + w: [...Array(8).fill(true)], //pawns can move 2 squares? + b: [...Array(8).fill(true)] }; - if (!fenflags) - return; + if (!fenflags) return; const flags = fenflags.substr(4); //skip first 4 digits, for castle - for (let c of ['w','b']) - { - for (let i=0; i<8; i++) - this.pawnFlags[c][i] = (flags.charAt((c=='w'?0:8)+i) == '1'); + for (let c of ["w", "b"]) { + for (let i = 0; i < 8; i++) + this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1"; } } - aggregateFlags() - { + aggregateFlags() { return [this.castleFlags, this.pawnFlags]; } - disaggregateFlags(flags) - { + disaggregateFlags(flags) { this.castleFlags = flags[0]; this.pawnFlags = flags[1]; } - getCmove(move) - { - if (move.appear[0].c == 'c' && move.vanish.length == 1) - return {start: move.start, end: move.end}; + getCmove(move) { + if (move.appear[0].c == "c" && move.vanish.length == 1) + return { start: move.start, end: move.end }; return null; } - canTake([x1,y1], [x2,y2]) - { - const color1 = this.getColor(x1,y1); - const color2 = this.getColor(x2,y2); + canTake([x1, y1], [x2, y2]) { + const color1 = this.getColor(x1, y1); + const color2 = this.getColor(x2, y2); // Checkered aren't captured - return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn); + return ( + color1 != color2 && + color2 != "c" && + (color1 != "c" || color2 != this.turn) + ); } // Post-processing: apply "checkerization" of standard moves - getPotentialMovesFrom([x,y]) - { - let standardMoves = super.getPotentialMovesFrom([x,y]); + getPotentialMovesFrom([x, y]) { + let standardMoves = super.getPotentialMovesFrom([x, y]); const lastRank = this.turn == "w" ? 0 : 7; - if (this.getPiece(x,y) == V.KING) - return standardMoves; //king has to be treated differently (for castles) + if (this.getPiece(x, y) == V.KING) return standardMoves; //king has to be treated differently (for castles) let moves = []; standardMoves.forEach(m => { - if (m.vanish[0].p == V.PAWN) - { - if (Math.abs(m.end.x-m.start.x)==2 && !this.pawnFlags[this.turn][m.start.y]) + if (m.vanish[0].p == V.PAWN) { + if ( + Math.abs(m.end.x - m.start.x) == 2 && + !this.pawnFlags[this.turn][m.start.y] + ) return; //skip forbidden 2-squares jumps - if (this.board[m.end.x][m.end.y] == V.EMPTY && m.vanish.length==2 - && this.getColor(m.start.x,m.start.y) == 'c') - { + if ( + this.board[m.end.x][m.end.y] == V.EMPTY && + m.vanish.length == 2 && + this.getColor(m.start.x, m.start.y) == "c" + ) { return; //checkered pawns cannot take en-passant } } - if (m.vanish.length == 1) - moves.push(m); //no capture - else - { + if (m.vanish.length == 1) moves.push(m); + //no capture + else { // A capture occured (m.vanish.length == 2) m.appear[0].c = "c"; moves.push(m); - if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated): - && (m.vanish[0].p != V.PAWN || m.end.x != lastRank)) - { + if ( + m.appear[0].p != m.vanish[1].p && //avoid promotions (already treated): + (m.vanish[0].p != V.PAWN || m.end.x != lastRank) + ) { // Add transformation into captured piece let m2 = JSON.parse(JSON.stringify(m)); m2.appear[0].p = m.vanish[1].p; @@ -164,29 +149,30 @@ export const VariantRules = class CheckeredRules extends ChessRules return moves; } - canIplay(side, [x,y]) - { - return (side == this.turn && [side,'c'].includes(this.getColor(x,y))); + canIplay(side, [x, y]) { + return side == this.turn && [side, "c"].includes(this.getColor(x, y)); } // Does m2 un-do m1 ? (to disallow undoing checkered moves) - oppositeMoves(m1, m2) - { - return (!!m1 && m2.appear[0].c == 'c' - && m2.appear.length == 1 && m2.vanish.length == 1 - && m1.start.x == m2.end.x && m1.end.x == m2.start.x - && m1.start.y == m2.end.y && m1.end.y == m2.start.y); + oppositeMoves(m1, m2) { + return ( + !!m1 && + m2.appear[0].c == "c" && + m2.appear.length == 1 && + m2.vanish.length == 1 && + m1.start.x == m2.end.x && + m1.end.x == m2.start.x && + m1.start.y == m2.end.y && + m1.end.y == m2.start.y + ); } - filterValid(moves) - { - if (moves.length == 0) - return []; + filterValid(moves) { + if (moves.length == 0) return []; const color = this.turn; return moves.filter(m => { const L = this.cmoves.length; //at least 1: init from FEN - if (this.oppositeMoves(this.cmoves[L-1], m)) - return false; + if (this.oppositeMoves(this.cmoves[L - 1], m)) return false; this.play(m); const res = !this.underCheck(color); this.undo(m); @@ -194,19 +180,18 @@ export const VariantRules = class CheckeredRules extends ChessRules }); } - isAttackedByPawn([x,y], colors) - { - for (let c of colors) - { - const color = (c=="c" ? this.turn : c); - let pawnShift = (color=="w" ? 1 : -1); - if (x+pawnShift>=0 && x+pawnShift<8) - { - for (let i of [-1,1]) - { - if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN - && this.getColor(x+pawnShift,y+i)==c) - { + isAttackedByPawn([x, y], colors) { + for (let c of colors) { + const color = c == "c" ? this.turn : c; + let pawnShift = color == "w" ? 1 : -1; + if (x + pawnShift >= 0 && x + pawnShift < 8) { + for (let i of [-1, 1]) { + if ( + y + i >= 0 && + y + i < 8 && + this.getPiece(x + pawnShift, y + i) == V.PAWN && + this.getColor(x + pawnShift, y + i) == c + ) { return true; } } @@ -215,17 +200,17 @@ export const VariantRules = class CheckeredRules extends ChessRules return false; } - underCheck(color) - { - return this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c']); + underCheck(color) { + return this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]); } - getCheckSquares(color) - { + getCheckSquares(color) { // Artifically change turn, for checkered pawns this.turn = V.GetOppCol(color); - const kingAttacked = this.isAttacked( - this.kingPos[color], [V.GetOppCol(color),'c']); + const kingAttacked = this.isAttacked(this.kingPos[color], [ + V.GetOppCol(color), + "c" + ]); let res = kingAttacked ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate! : []; @@ -233,139 +218,125 @@ export const VariantRules = class CheckeredRules extends ChessRules return res; } - updateVariables(move) - { + updateVariables(move) { super.updateVariables(move); // Does this move turn off a 2-squares pawn flag? - const secondRank = [1,6]; + const secondRank = [1, 6]; if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN) - this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false; + this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false; } - getCurrentScore() - { - if (this.atLeastOneMove()) // game not over + getCurrentScore() { + if (this.atLeastOneMove()) + // game not over return "*"; const color = this.turn; // Artifically change turn, for checkered pawns this.turn = V.GetOppCol(this.turn); - const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c']) - ? (color == "w" ? "0-1" : "1-0") + const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]) + ? color == "w" + ? "0-1" + : "1-0" : "1/2"; this.turn = V.GetOppCol(this.turn); return res; } - evalPosition() - { + evalPosition() { let evaluation = 0; //Just count material for now, considering checkered neutral (...) - for (let i=0; i 1) - { + if (move.vanish.length > 1) { // Capture const startColumn = V.CoordToColumn(move.start.y); - notation = startColumn + "x" + finalSquare + - "=" + move.appear[0].p.toUpperCase(); - } - else //no capture - { + notation = + startColumn + + "x" + + finalSquare + + "=" + + move.appear[0].p.toUpperCase(); + } //no capture + else { notation = finalSquare; - if (move.appear.length > 0 && piece != move.appear[0].p) //promotion + if (move.appear.length > 0 && piece != move.appear[0].p) + //promotion notation += "=" + move.appear[0].p.toUpperCase(); } return notation; } - - else - { - // Piece movement - return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare - + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : ""); - } + // Piece movement + return ( + piece.toUpperCase() + + (move.vanish.length > 1 ? "x" : "") + + finalSquare + + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "") + ); } -} +}; diff --git a/client/src/variants/Chess960.js b/client/src/variants/Chess960.js index 74f4d1d5..7e10e2a5 100644 --- a/client/src/variants/Chess960.js +++ b/client/src/variants/Chess960.js @@ -1,5 +1,4 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class Chess960Rules extends ChessRules -{ +export const VariantRules = class Chess960Rules extends ChessRules { // Standard rules -} +}; diff --git a/client/src/variants/Crazyhouse.js b/client/src/variants/Crazyhouse.js index 82997274..89b6f830 100644 --- a/client/src/variants/Crazyhouse.js +++ b/client/src/variants/Crazyhouse.js @@ -1,24 +1,18 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; -import { ArrayFun} from "@/utils/array"; +import { ArrayFun } from "@/utils/array"; -export const VariantRules = class CrazyhouseRules extends ChessRules -{ - static IsGoodFen(fen) - { - if (!ChessRules.IsGoodFen(fen)) - return false; +export const VariantRules = class CrazyhouseRules extends ChessRules { + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; const fenParsed = V.ParseFen(fen); // 5) Check reserves if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/)) return false; // 6) Check promoted array - if (!fenParsed.promoted) - return false; - if (fenParsed.promoted == "-") - return true; //no promoted piece on board + if (!fenParsed.promoted) return false; + if (fenParsed.promoted == "-") return true; //no promoted piece on board const squares = fenParsed.promoted.split(","); - for (let square of squares) - { + for (let square of squares) { const c = V.SquareToCoords(square); if (c.y < 0 || c.y > V.size.y || isNaN(c.x) || c.x < 0 || c.x > V.size.x) return false; @@ -26,132 +20,108 @@ export const VariantRules = class CrazyhouseRules extends ChessRules return true; } - static ParseFen(fen) - { + static ParseFen(fen) { const fenParts = fen.split(" "); - return Object.assign( - ChessRules.ParseFen(fen), - { - reserve: fenParts[5], - promoted: fenParts[6], - } - ); + return Object.assign(ChessRules.ParseFen(fen), { + reserve: fenParts[5], + promoted: fenParts[6] + }); } - static GenRandInitFen() - { + static GenRandInitFen() { return ChessRules.GenRandInitFen() + " 0000000000 -"; } - getFen() - { - return super.getFen() + " " + this.getReserveFen() + " " + this.getPromotedFen(); + getFen() { + return ( + super.getFen() + " " + this.getReserveFen() + " " + this.getPromotedFen() + ); } - getReserveFen() - { + getReserveFen() { let counts = new Array(10); - for (let i=0; i 0) - res = res.slice(0,-1); //remove last comma - else - res = "-"; + if (res.length > 0) res = res.slice(0, -1); + //remove last comma + else res = "-"; return res; } - setOtherVariables(fen) - { + setOtherVariables(fen) { super.setOtherVariables(fen); const fenParsed = V.ParseFen(fen); // Also init reserves (used by the interface to show landable pieces) - this.reserve = - { - "w": - { + this.reserve = { + w: { [V.PAWN]: parseInt(fenParsed.reserve[0]), [V.ROOK]: parseInt(fenParsed.reserve[1]), [V.KNIGHT]: parseInt(fenParsed.reserve[2]), [V.BISHOP]: parseInt(fenParsed.reserve[3]), - [V.QUEEN]: parseInt(fenParsed.reserve[4]), + [V.QUEEN]: parseInt(fenParsed.reserve[4]) }, - "b": - { + b: { [V.PAWN]: parseInt(fenParsed.reserve[5]), [V.ROOK]: parseInt(fenParsed.reserve[6]), [V.KNIGHT]: parseInt(fenParsed.reserve[7]), [V.BISHOP]: parseInt(fenParsed.reserve[8]), - [V.QUEEN]: parseInt(fenParsed.reserve[9]), + [V.QUEEN]: parseInt(fenParsed.reserve[9]) } }; this.promoted = ArrayFun.init(V.size.x, V.size.y, false); - if (fenParsed.promoted != "-") - { - for (let square of fenParsed.promoted.split(",")) - { - const [x,y] = V.SquareToCoords(square); - promoted[x][y] = true; + if (fenParsed.promoted != "-") { + for (let square of fenParsed.promoted.split(",")) { + const [x, y] = V.SquareToCoords(square); + this.promoted[x][y] = true; } } } - getColor(i,j) - { - if (i >= V.size.x) - return (i==V.size.x ? "w" : "b"); + getColor(i, j) { + if (i >= V.size.x) return i == V.size.x ? "w" : "b"; return this.board[i][j].charAt(0); } - getPiece(i,j) - { - if (i >= V.size.x) - return V.RESERVE_PIECES[j]; + getPiece(i, j) { + if (i >= V.size.x) return V.RESERVE_PIECES[j]; return this.board[i][j].charAt(1); } // Used by the interface: - getReservePpath(color, index) - { + getReservePpath(color, index) { return color + V.RESERVE_PIECES[index]; } // Ordering on reserve pieces - static get RESERVE_PIECES() - { - return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]; + static get RESERVE_PIECES() { + return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]; } - getReserveMoves([x,y]) - { + getReserveMoves([x, y]) { const color = this.turn; const p = V.RESERVE_PIECES[y]; - if (this.reserve[color][p] == 0) - return []; + if (this.reserve[color][p] == 0) return []; let moves = []; - const pawnShift = (p==V.PAWN ? 1 : 0); - for (let i=pawnShift; i= V.size.x) - { + getPotentialMovesFrom([x, y]) { + if (x >= V.size.x) { // Reserves, outside of board: x == sizeX(+1) - return this.getReserveMoves([x,y]); + return this.getReserveMoves([x, y]); } // Standard moves - return super.getPotentialMovesFrom([x,y]); + return super.getPotentialMovesFrom([x, y]); } - getAllValidMoves() - { + getAllValidMoves() { let moves = super.getAllValidMoves(); const color = this.turn; - for (let i=0; i 0) - return true; + this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i]) + ); + if (moves.length > 0) return true; } return false; } return true; } - updateVariables(move) - { + updateVariables(move) { super.updateVariables(move); - if (move.vanish.length == 2 && move.appear.length == 2) - return; //skip castle + if (move.vanish.length == 2 && move.appear.length == 2) return; //skip castle const color = move.appear[0].c; - if (move.vanish.length == 0) - { + if (move.vanish.length == 0) { this.reserve[color][move.appear[0].p]--; return; } move.movePromoted = this.promoted[move.start.x][move.start.y]; - move.capturePromoted = this.promoted[move.end.x][move.end.y] + move.capturePromoted = this.promoted[move.end.x][move.end.y]; this.promoted[move.start.x][move.start.y] = false; - this.promoted[move.end.x][move.end.y] = move.movePromoted - || (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN); - if (move.capturePromoted) - this.reserve[color][V.PAWN]++; - else if (move.vanish.length == 2) - this.reserve[color][move.vanish[1].p]++; + this.promoted[move.end.x][move.end.y] = + move.movePromoted || + (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN); + if (move.capturePromoted) this.reserve[color][V.PAWN]++; + else if (move.vanish.length == 2) this.reserve[color][move.vanish[1].p]++; } - unupdateVariables(move) - { + unupdateVariables(move) { super.unupdateVariables(move); - if (move.vanish.length == 2 && move.appear.length == 2) - return; + if (move.vanish.length == 2 && move.appear.length == 2) return; const color = this.turn; - if (move.vanish.length == 0) - { + if (move.vanish.length == 0) { this.reserve[color][move.appear[0].p]++; return; } - if (move.movePromoted) - this.promoted[move.start.x][move.start.y] = true; + if (move.movePromoted) this.promoted[move.start.x][move.start.y] = true; this.promoted[move.end.x][move.end.y] = move.capturePromoted; - if (move.capturePromoted) - this.reserve[color][V.PAWN]--; - else if (move.vanish.length == 2) - this.reserve[color][move.vanish[1].p]--; + if (move.capturePromoted) this.reserve[color][V.PAWN]--; + else if (move.vanish.length == 2) this.reserve[color][move.vanish[1].p]--; } - static get SEARCH_DEPTH() { return 2; } //high branching factor + static get SEARCH_DEPTH() { + return 2; + } //high branching factor - evalPosition() - { + evalPosition() { let evaluation = super.evalPosition(); // Add reserves: - for (let i=0; i 0) - return super.getNotation(move); + getNotation(move) { + if (move.vanish.length > 0) return super.getNotation(move); // Rebirth: const piece = - (move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""); + move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""; return piece + "@" + V.CoordsToSquare(move.end); } - getLongNotation(move) - { - if (move.vanish.length > 0) - return super.getLongNotation(move); + getLongNotation(move) { + if (move.vanish.length > 0) return super.getLongNotation(move); return "@" + V.CoordsToSquare(move.end); } -} +}; diff --git a/client/src/variants/Dark.js b/client/src/variants/Dark.js index c0bfb09c..358362c7 100644 --- a/client/src/variants/Dark.js +++ b/client/src/variants/Dark.js @@ -1,52 +1,45 @@ import { ChessRules } from "@/base_rules"; -import { ArrayFun} from "@/utils/array"; +import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class DarkRules extends ChessRules -{ +export const VariantRules = class DarkRules extends ChessRules { // Standard rules, in the shadow - setOtherVariables(fen) - { + setOtherVariables(fen) { super.setOtherVariables(fen); - const [sizeX,sizeY] = [V.size.x,V.size.y]; + const [sizeX, sizeY] = [V.size.x, V.size.y]; this.enlightened = { - "w": ArrayFun.init(sizeX,sizeY), - "b": ArrayFun.init(sizeX,sizeY) + w: ArrayFun.init(sizeX, sizeY), + b: ArrayFun.init(sizeX, sizeY) }; // Setup enlightened: squares reachable by each side // (TODO: one side would be enough ?) this.updateEnlightened(); } - updateEnlightened() - { - for (let i=0; i= 2 && move.vanish[1].p == V.KING) - { + if (move.vanish.length >= 2 && move.vanish[1].p == V.KING) { // We took opponent king ! (because if castle vanish[1] is a rook) - this.kingPos[this.turn] = [-1,-1]; + this.kingPos[this.turn] = [-1, -1]; } // Update lights for both colors: this.updateEnlightened(); } - unupdateVariables(move) - { + unupdateVariables(move) { super.unupdateVariables(move); const c = move.vanish[0].c; const oppCol = V.GetOppCol(c); - if (this.kingPos[oppCol][0] < 0) - { + if (this.kingPos[oppCol][0] < 0) { // Last move took opponent's king - for (let psq of move.vanish) - { - if (psq.p == 'k') - { + for (let psq of move.vanish) { + if (psq.p == "k") { this.kingPos[oppCol] = [psq.x, psq.y]; break; } @@ -134,96 +116,85 @@ export const VariantRules = class DarkRules extends ChessRules this.updateEnlightened(); } - getCurrentScore() - { + getCurrentScore() { const color = this.turn; const kp = this.kingPos[color]; - if (kp[0] < 0) //king disappeared - return (color == "w" ? "0-1" : "1-0"); - if (this.atLeastOneMove()) // game not over + if (kp[0] < 0) + //king disappeared + return color == "w" ? "0-1" : "1-0"; + if (this.atLeastOneMove()) + // game not over return "*"; return "1/2"; //no moves but kings still there (seems impossible) } - static get THRESHOLD_MATE() - { + static get THRESHOLD_MATE() { return 500; //checkmates evals may be slightly below 1000 } // In this special situation, we just look 1 half move ahead - getComputerMove() - { + getComputerMove() { const maxeval = V.INFINITY; const color = this.turn; const oppCol = V.GetOppCol(color); - const pawnShift = (color == "w" ? -1 : 1); + const pawnShift = color == "w" ? -1 : 1; // Do not cheat: the current enlightment is all we can see const myLight = JSON.parse(JSON.stringify(this.enlightened[color])); // Can a slider on (i,j) apparently take my king? // NOTE: inaccurate because assume yes if some squares are shadowed - const sliderTake = ([i,j], piece) => { + const sliderTake = ([i, j], piece) => { const kp = this.kingPos[color]; let step = undefined; - if (piece == V.BISHOP) - { - if (Math.abs(kp[0] - i) == Math.abs(kp[1] - j)) - { - step = - [ - (i-kp[0]) / Math.abs(i-kp[0]), - (j-kp[1]) / Math.abs(j-kp[1]) + if (piece == V.BISHOP) { + if (Math.abs(kp[0] - i) == Math.abs(kp[1] - j)) { + step = [ + (i - kp[0]) / Math.abs(i - kp[0]), + (j - kp[1]) / Math.abs(j - kp[1]) ]; } + } else if (piece == V.ROOK) { + if (kp[0] == i) step = [0, (j - kp[1]) / Math.abs(j - kp[1])]; + else if (kp[1] == j) step = [(i - kp[0]) / Math.abs(i - kp[0]), 0]; } - else if (piece == V.ROOK) - { - if (kp[0] == i) - step = [0, (j-kp[1]) / Math.abs(j-kp[1])]; - else if (kp[1] == j) - step = [(i-kp[0]) / Math.abs(i-kp[0]), 0]; - } - if (!step) - return false; + if (!step) return false; // Check for obstacles let obstacle = false; for ( - let x=kp[0]+step[0], y=kp[1]+step[1]; + let x = kp[0] + step[0], y = kp[1] + step[1]; x != i && y != j; - x += step[0], y += step[1]) - { - if (myLight[x][y] && this.board[x][y] != V.EMPTY) - { + x += step[0], y += step[1] + ) { + if (myLight[x][y] && this.board[x][y] != V.EMPTY) { obstacle = true; break; } } - if (!obstacle) - return true; + if (!obstacle) return true; return false; }; // Do I see something which can take my king ? const kingThreats = () => { const kp = this.kingPos[color]; - for (let i=0; i= 0 && kingThreats()) - { + if (this.kingPos[oppCol][0] >= 0 && kingThreats()) { // We didn't take opponent king, and our king will be captured: bad move.eval = -maxeval; } this.undo(move); - if (!!move.eval) - continue; + if (move.eval) continue; move.eval = 0; //a priori... // Can I take something ? If yes, do it if it seems good... - if (move.vanish.length == 2 && move.vanish[1].c != color) //avoid castle - { + if (move.vanish.length == 2 && move.vanish[1].c != color) { + //avoid castle const myPieceVal = V.VALUES[move.appear[0].p]; const hisPieceVal = V.VALUES[move.vanish[1].p]; - if (myPieceVal <= hisPieceVal) - move.eval = hisPieceVal - myPieceVal + 2; //favor captures - else - { + if (myPieceVal <= hisPieceVal) move.eval = hisPieceVal - myPieceVal + 2; + //favor captures + else { // Taking a pawn with minor piece, // or minor piece or pawn with a rook, // or anything but a queen with a queen, // or anything with a king. // ==> Do it at random, although // this is clearly inferior to what a human can deduce... - move.eval = (Math.random() < 0.5 ? 1 : -1); + move.eval = Math.random() < 0.5 ? 1 : -1; } } } @@ -289,10 +254,10 @@ export const VariantRules = class DarkRules extends ChessRules // TODO: also need to implement the case when an opponent piece (in light) // is threatening something - maybe not the king, but e.g. pawn takes rook. - moves.sort((a,b) => b.eval - a.eval); + moves.sort((a, b) => b.eval - a.eval); let candidates = [0]; - for (let j=1; j0 && this.board[x+shift][y-1] != V.EMPTY - && this.canTake([x,y], [x+shift,y-1])) - { - moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING})); + if ( + y > 0 && + this.board[x + shift][y - 1] != V.EMPTY && + this.canTake([x, y], [x + shift, y - 1]) + ) { + moves.push( + this.getBasicMove([x, y], [x + shift, y - 1], { c: color, p: V.KING }) + ); } - if (y { return this.material[color][p] == 0; })) - { - return (this.turn == "w" ? "0-1" : "1-0"); + if ( + Object.keys(this.material[color]).some(p => { + return this.material[color][p] == 0; + }) + ) { + return this.turn == "w" ? "0-1" : "1-0"; } return "*"; } - return (this.turn == "w" ? "0-1" : "1-0"); //NOTE: currently unreachable... + return this.turn == "w" ? "0-1" : "1-0"; //NOTE: currently unreachable... } - evalPosition() - { + evalPosition() { const color = this.turn; - if (Object.keys(this.material[color]).some( - p => { return this.material[color][p] == 0; })) - { + if ( + Object.keys(this.material[color]).some(p => { + return this.material[color][p] == 0; + }) + ) { // Very negative (resp. positive) if white (reps. black) pieces set is incomplete - return (color=="w"?-1:1) * V.INFINITY; + return (color == "w" ? -1 : 1) * V.INFINITY; } return super.evalPosition(); } -} +}; diff --git a/client/src/variants/Grand.js b/client/src/variants/Grand.js index a53f59e9..d28f639f 100644 --- a/client/src/variants/Grand.js +++ b/client/src/variants/Grand.js @@ -4,17 +4,13 @@ import { randInt } from "@/utils/alea"; // NOTE: initial setup differs from the original; see // https://www.chessvariants.com/large.dir/freeling.html -export const VariantRules = class GrandRules extends ChessRules -{ - static getPpath(b) - { - return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b; +export const VariantRules = class GrandRules extends ChessRules { + static getPpath(b) { + return ([V.MARSHALL, V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b; } - static IsGoodFen(fen) - { - if (!ChessRules.IsGoodFen(fen)) - return false; + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; const fenParsed = V.ParseFen(fen); // 5) Check captures if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{14,14}$/)) @@ -22,115 +18,99 @@ export const VariantRules = class GrandRules extends ChessRules return true; } - static IsGoodEnpassant(enpassant) - { - if (enpassant != "-") - { + static IsGoodEnpassant(enpassant) { + if (enpassant != "-") { const squares = enpassant.split(","); - if (squares.length > 2) - return false; - for (let sq of squares) - { + if (squares.length > 2) return false; + for (let sq of squares) { const ep = V.SquareToCoords(sq); - if (isNaN(ep.x) || !V.OnBoard(ep)) - return false; + if (isNaN(ep.x) || !V.OnBoard(ep)) return false; } } return true; } - static ParseFen(fen) - { + static ParseFen(fen) { const fenParts = fen.split(" "); - return Object.assign( - ChessRules.ParseFen(fen), - { captured: fenParts[5] } - ); + return Object.assign(ChessRules.ParseFen(fen), { captured: fenParts[5] }); } - getFen() - { + getFen() { return super.getFen() + " " + this.getCapturedFen(); } - getCapturedFen() - { + getCapturedFen() { let counts = [...Array(14).fill(0)]; let i = 0; - for (let j=0; j { + this.epSquares[L - 1].forEach(sq => { res += V.CoordsToSquare(sq) + ","; }); - return res.slice(0,-1); //remove last comma + return res.slice(0, -1); //remove last comma } // En-passant after 2-sq or 3-sq jumps - getEpSquare(moveOrSquare) - { - if (!moveOrSquare) - return undefined; - if (typeof moveOrSquare === "string") - { + getEpSquare(moveOrSquare) { + if (!moveOrSquare) return undefined; + if (typeof moveOrSquare === "string") { const square = moveOrSquare; - if (square == "-") - return undefined; + if (square == "-") return undefined; let res = []; square.split(",").forEach(sq => { res.push(V.SquareToCoords(sq)); @@ -139,18 +119,19 @@ export const VariantRules = class GrandRules extends ChessRules } // Argument is a move: const move = moveOrSquare; - const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x]; - if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2) - { - const step = (ex-sx) / Math.abs(ex-sx); - let res = [{ - x: sx + step, - y: sy - }]; - if (sx + 2*step != ex) //3-squares move - { + const [sx, sy, ex] = [move.start.x, move.start.y, move.end.x]; + if (this.getPiece(sx, sy) == V.PAWN && Math.abs(sx - ex) >= 2) { + const step = (ex - sx) / Math.abs(ex - sx); + let res = [ + { + x: sx + step, + y: sy + } + ]; + if (sx + 2 * step != ex) { + //3-squares move res.push({ - x: sx + 2*step, + x: sx + 2 * step, y: sy }); } @@ -159,95 +140,94 @@ export const VariantRules = class GrandRules extends ChessRules return undefined; //default } - getPotentialMovesFrom([x,y]) - { - switch (this.getPiece(x,y)) - { + getPotentialMovesFrom([x, y]) { + switch (this.getPiece(x, y)) { case V.MARSHALL: - return this.getPotentialMarshallMoves([x,y]); + return this.getPotentialMarshallMoves([x, y]); case V.CARDINAL: - return this.getPotentialCardinalMoves([x,y]); + return this.getPotentialCardinalMoves([x, y]); default: - return super.getPotentialMovesFrom([x,y]) + return super.getPotentialMovesFrom([x, y]); } } // Special pawn rules: promotions to captured friendly pieces, // optional on ranks 8-9 and mandatory on rank 10. - getPotentialPawnMoves([x,y]) - { + getPotentialPawnMoves([x, y]) { const color = this.turn; let moves = []; - const [sizeX,sizeY] = [V.size.x,V.size.y]; - const shiftX = (color == "w" ? -1 : 1); - const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]); - const lastRanks = (color == "w" ? [0,1,2] : [sizeX-1,sizeX-2,sizeX-3]); - const promotionPieces = - [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL]; + const [sizeX, sizeY] = [V.size.x, V.size.y]; + const shiftX = color == "w" ? -1 : 1; + const startRanks = color == "w" ? [sizeX - 2, sizeX - 3] : [1, 2]; + const lastRanks = + color == "w" ? [0, 1, 2] : [sizeX - 1, sizeX - 2, sizeX - 3]; + const promotionPieces = [ + V.ROOK, + V.KNIGHT, + V.BISHOP, + V.QUEEN, + V.MARSHALL, + V.CARDINAL + ]; // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank let finalPieces = undefined; - if (lastRanks.includes(x + shiftX)) - { + if (lastRanks.includes(x + shiftX)) { finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0); - if (x + shiftX != lastRanks[0]) - finalPieces.push(V.PAWN); - } - else - finalPieces = [V.PAWN]; - if (this.board[x+shiftX][y] == V.EMPTY) - { + if (x + shiftX != lastRanks[0]) finalPieces.push(V.PAWN); + } else finalPieces = [V.PAWN]; + if (this.board[x + shiftX][y] == V.EMPTY) { // One square forward for (let piece of finalPieces) - moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece})); - if (startRanks.includes(x)) - { - if (this.board[x+2*shiftX][y] == V.EMPTY) - { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece }) + ); + if (startRanks.includes(x)) { + if (this.board[x + 2 * shiftX][y] == V.EMPTY) { // Two squares jump - moves.push(this.getBasicMove([x,y], [x+2*shiftX,y])); - if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY) - { + moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); + if (x == startRanks[0] && this.board[x + 3 * shiftX][y] == V.EMPTY) { // Three squares jump - moves.push(this.getBasicMove([x,y], [x+3*shiftX,y])); + moves.push(this.getBasicMove([x, y], [x + 3 * shiftX, y])); } } } } // Captures - for (let shiftY of [-1,1]) - { - if (y + shiftY >= 0 && y + shiftY < sizeY - && this.board[x+shiftX][y+shiftY] != V.EMPTY - && this.canTake([x,y], [x+shiftX,y+shiftY])) - { - for (let piece of finalPieces) - { - moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY], - {c:color,p:piece})); + for (let shiftY of [-1, 1]) { + if ( + y + shiftY >= 0 && + y + shiftY < sizeY && + this.board[x + shiftX][y + shiftY] != V.EMPTY && + this.canTake([x, y], [x + shiftX, y + shiftY]) + ) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y + shiftY], { + c: color, + p: piece + }) + ); } } } // En passant const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep-1]; - if (!!epSquare) - { - for (let epsq of epSquare) - { + const epSquare = this.epSquares[Lep - 1]; + if (epSquare) { + for (let epsq of epSquare) { // TODO: some redundant checks - if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1) - { - var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]); + if (epsq.x == x + shiftX && Math.abs(epsq.y - y) == 1) { + var enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]); // WARNING: the captured pawn may be diagonally behind us, // if it's a 3-squares jump and we take on 1st passing square - const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX); + const px = this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX; enpassantMove.vanish.push({ x: px, y: epsq.y, - p: 'p', - c: this.getColor(px,epsq.y) + p: "p", + c: this.getColor(px, epsq.y) }); moves.push(enpassantMove); } @@ -259,56 +239,65 @@ export const VariantRules = class GrandRules extends ChessRules // TODO: different castle? - getPotentialMarshallMoves(sq) - { + getPotentialMarshallMoves(sq) { return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat( - this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")); + this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") + ); } - getPotentialCardinalMoves(sq) - { + getPotentialCardinalMoves(sq) { return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat( - this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")); + this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") + ); } - isAttacked(sq, colors) - { - return super.isAttacked(sq, colors) - || this.isAttackedByMarshall(sq, colors) - || this.isAttackedByCardinal(sq, colors); + isAttacked(sq, colors) { + return ( + super.isAttacked(sq, colors) || + this.isAttackedByMarshall(sq, colors) || + this.isAttackedByCardinal(sq, colors) + ); } - isAttackedByMarshall(sq, colors) - { - return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK]) - || this.isAttackedBySlideNJump( - sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep"); + isAttackedByMarshall(sq, colors) { + return ( + this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK]) || + this.isAttackedBySlideNJump( + sq, + colors, + V.MARSHALL, + V.steps[V.KNIGHT], + "oneStep" + ) + ); } - isAttackedByCardinal(sq, colors) - { - return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP]) - || this.isAttackedBySlideNJump( - sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep"); + isAttackedByCardinal(sq, colors) { + return ( + this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP]) || + this.isAttackedBySlideNJump( + sq, + colors, + V.CARDINAL, + V.steps[V.KNIGHT], + "oneStep" + ) + ); } - updateVariables(move) - { + updateVariables(move) { super.updateVariables(move); - if (move.vanish.length == 2 && move.appear.length == 1) - { + if (move.vanish.length == 2 && move.appear.length == 1) { // Capture: update this.captured this.captured[move.vanish[1].c][move.vanish[1].p]++; } - if (move.vanish[0].p != move.appear[0].p) - { + if (move.vanish[0].p != move.appear[0].p) { // Promotion: update this.captured this.captured[move.vanish[0].c][move.appear[0].p]--; } } - unupdateVariables(move) - { + unupdateVariables(move) { super.unupdateVariables(move); if (move.vanish.length == 2 && move.appear.length == 1) this.captured[move.vanish[1].c][move.vanish[1].p]--; @@ -316,23 +305,22 @@ export const VariantRules = class GrandRules extends ChessRules this.captured[move.vanish[0].c][move.appear[0].p]++; } - static get VALUES() - { + static get VALUES() { return Object.assign( ChessRules.VALUES, - {'c': 5, 'm': 7} //experimental + { c: 5, m: 7 } //experimental ); } - static get SEARCH_DEPTH() { return 2; } + static get SEARCH_DEPTH() { + return 2; + } // TODO: this function could be generalized and shared better (how ?!...) - static GenRandInitFen() - { - let pieces = { "w": new Array(10), "b": new Array(10) }; + static GenRandInitFen() { + let pieces = { w: new Array(10), b: new Array(10) }; // Shuffle pieces on first and last rank - for (let c of ["w","b"]) - { + for (let c of ["w", "b"]) { let positions = ArrayFun.range(10); // Get random squares for bishops @@ -342,8 +330,8 @@ export const VariantRules = class GrandRules extends ChessRules let randIndex_tmp = 2 * randInt(5) + 1; let bishop2Pos = positions[randIndex_tmp]; // Remove chosen squares - positions.splice(Math.max(randIndex,randIndex_tmp), 1); - positions.splice(Math.min(randIndex,randIndex_tmp), 1); + positions.splice(Math.max(randIndex, randIndex_tmp), 1); + positions.splice(Math.min(randIndex, randIndex_tmp), 1); // Get random squares for knights randIndex = randInt(8); @@ -374,20 +362,22 @@ export const VariantRules = class GrandRules extends ChessRules let rook2Pos = positions[2]; // Finally put the shuffled pieces in the board array - pieces[c][rook1Pos] = 'r'; - pieces[c][knight1Pos] = 'n'; - pieces[c][bishop1Pos] = 'b'; - pieces[c][queenPos] = 'q'; - pieces[c][marshallPos] = 'm'; - pieces[c][cardinalPos] = 'c'; - pieces[c][kingPos] = 'k'; - pieces[c][bishop2Pos] = 'b'; - pieces[c][knight2Pos] = 'n'; - pieces[c][rook2Pos] = 'r'; + pieces[c][rook1Pos] = "r"; + pieces[c][knight1Pos] = "n"; + pieces[c][bishop1Pos] = "b"; + pieces[c][queenPos] = "q"; + pieces[c][marshallPos] = "m"; + pieces[c][cardinalPos] = "c"; + pieces[c][kingPos] = "k"; + pieces[c][bishop2Pos] = "b"; + pieces[c][knight2Pos] = "n"; + pieces[c][rook2Pos] = "r"; } - return pieces["b"].join("") + + return ( + pieces["b"].join("") + "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" + pieces["w"].join("").toUpperCase() + - " w 0 1111 - 00000000000000"; + " w 0 1111 - 00000000000000" + ); } -} +}; diff --git a/client/src/variants/Losers.js b/client/src/variants/Losers.js index d815ee7a..81d2730a 100644 --- a/client/src/variants/Losers.js +++ b/client/src/variants/Losers.js @@ -2,63 +2,71 @@ import { ChessRules } from "@/base_rules"; import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; -export const VariantRules = class LosersRules extends ChessRules -{ - static get HasFlags() { return false; } +export const VariantRules = class LosersRules extends ChessRules { + static get HasFlags() { + return false; + } - getPotentialPawnMoves([x,y]) - { - let moves = super.getPotentialPawnMoves([x,y]); + getPotentialPawnMoves([x, y]) { + let moves = super.getPotentialPawnMoves([x, y]); // Complete with promotion(s) into king, if possible const color = this.turn; - const shift = (color == "w" ? -1 : 1); - const lastRank = (color == "w" ? 0 : V.size.x-1); - if (x+shift == lastRank) - { + const shift = color == "w" ? -1 : 1; + const lastRank = color == "w" ? 0 : V.size.x - 1; + if (x + shift == lastRank) { // Normal move - if (this.board[x+shift][y] == V.EMPTY) - moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING})); + if (this.board[x + shift][y] == V.EMPTY) + moves.push( + this.getBasicMove([x, y], [x + shift, y], { c: color, p: V.KING }) + ); // Captures - if (y>0 && this.canTake([x,y], [x+shift,y-1]) - && this.board[x+shift][y-1] != V.EMPTY) - { - moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING})); + if ( + y > 0 && + this.canTake([x, y], [x + shift, y - 1]) && + this.board[x + shift][y - 1] != V.EMPTY + ) { + moves.push( + this.getBasicMove([x, y], [x + shift, y - 1], { c: color, p: V.KING }) + ); } - if (y 0) - { - for (let k=0; k 0) + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { + const moves = this.getPotentialMovesFrom([i, j]); + if (moves.length > 0) { + for (let k = 0; k < moves.length; k++) { + if ( + moves[k].vanish.length == 2 && + this.filterValid([moves[k]]).length > 0 + ) return true; } } @@ -69,77 +77,75 @@ export const VariantRules = class LosersRules extends ChessRules } // Trim all non-capturing moves - static KeepCaptures(moves) - { - return moves.filter(m => { return m.vanish.length == 2; }); + static KeepCaptures(moves) { + return moves.filter(m => { + return m.vanish.length == 2; + }); } - getPossibleMovesFrom(sq) - { - let moves = this.filterValid( this.getPotentialMovesFrom(sq) ); + getPossibleMovesFrom(sq) { + let moves = this.filterValid(this.getPotentialMovesFrom(sq)); // This is called from interface: we need to know if a capture is possible - if (this.atLeastOneCapture()) - moves = V.KeepCaptures(moves); + if (this.atLeastOneCapture()) moves = V.KeepCaptures(moves); return moves; } - getAllValidMoves() - { + getAllValidMoves() { let moves = super.getAllValidMoves(); - if (moves.some(m => { return m.vanish.length == 2; })) + if ( + moves.some(m => { + return m.vanish.length == 2; + }) + ) moves = V.KeepCaptures(moves); return moves; } - underCheck(color) - { + underCheck() { return false; //No notion of check } - getCheckSquares(move) - { + getCheckSquares() { return []; } // No variables update because no royal king + no castling - updateVariables(move) { } - unupdateVariables(move) { } + updateVariables() {} + unupdateVariables() {} - getCurrentScore() - { - if (this.atLeastOneMove()) // game not over + getCurrentScore() { + if (this.atLeastOneMove()) + // game not over return "*"; // No valid move: the side who cannot move wins - return (this.turn == "w" ? "1-0" : "0-1"); + return this.turn == "w" ? "1-0" : "0-1"; } - static get VALUES() - { + static get VALUES() { // Experimental... return { - 'p': 1, - 'r': 7, - 'n': 3, - 'b': 3, - 'q': 5, - 'k': 5 + p: 1, + r: 7, + n: 3, + b: 3, + q: 5, + k: 5 }; } - static get SEARCH_DEPTH() { return 4; } + static get SEARCH_DEPTH() { + return 4; + } - evalPosition() - { - return - super.evalPosition(); //better with less material + evalPosition() { + return -super.evalPosition(); //better with less material } - static GenRandInitFen() - { - let pieces = { "w": new Array(8), "b": new Array(8) }; + static GenRandInitFen() { + let pieces = { w: new Array(8), b: new Array(8) }; // Shuffle pieces on first and last rank - for (let c of ["w","b"]) - { + for (let c of ["w", "b"]) { let positions = ArrayFun.range(8); // Get random squares for bishops @@ -149,8 +155,8 @@ export const VariantRules = class LosersRules extends ChessRules let randIndex_tmp = 2 * randInt(4) + 1; let bishop2Pos = positions[randIndex_tmp]; // Remove chosen squares - positions.splice(Math.max(randIndex,randIndex_tmp), 1); - positions.splice(Math.min(randIndex,randIndex_tmp), 1); + positions.splice(Math.max(randIndex, randIndex_tmp), 1); + positions.splice(Math.min(randIndex, randIndex_tmp), 1); // Get random squares for knights randIndex = randInt(6); @@ -175,18 +181,20 @@ export const VariantRules = class LosersRules extends ChessRules let rook2Pos = positions[1]; // Finally put the shuffled pieces in the board array - pieces[c][rook1Pos] = 'r'; - pieces[c][knight1Pos] = 'n'; - pieces[c][bishop1Pos] = 'b'; - pieces[c][queenPos] = 'q'; - pieces[c][kingPos] = 'k'; - pieces[c][bishop2Pos] = 'b'; - pieces[c][knight2Pos] = 'n'; - pieces[c][rook2Pos] = 'r'; + pieces[c][rook1Pos] = "r"; + pieces[c][knight1Pos] = "n"; + pieces[c][bishop1Pos] = "b"; + pieces[c][queenPos] = "q"; + pieces[c][kingPos] = "k"; + pieces[c][bishop2Pos] = "b"; + pieces[c][knight2Pos] = "n"; + pieces[c][rook2Pos] = "r"; } - return pieces["b"].join("") + + return ( + pieces["b"].join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" + pieces["w"].join("").toUpperCase() + - " w 0 -"; //en-passant allowed, but no flags + " w 0 -" + ); //en-passant allowed, but no flags } -} +}; diff --git a/client/src/variants/Magnetic.js b/client/src/variants/Magnetic.js index 3789de38..0110c49e 100644 --- a/client/src/variants/Magnetic.js +++ b/client/src/variants/Magnetic.js @@ -1,98 +1,92 @@ import { ChessRules, PiPo } from "@/base_rules"; -export const VariantRules = class MagneticRules extends ChessRules -{ - static get HasEnpassant() { return false; } +export const VariantRules = class MagneticRules extends ChessRules { + static get HasEnpassant() { + return false; + } - getPotentialMovesFrom([x,y]) - { - let standardMoves = super.getPotentialMovesFrom([x,y]); + getPotentialMovesFrom([x, y]) { + let standardMoves = super.getPotentialMovesFrom([x, y]); let moves = []; standardMoves.forEach(m => { let newMove_s = this.applyMagneticLaws(m); - if (newMove_s.length == 1) - moves.push(newMove_s[0]); - else //promotion - moves = moves.concat(newMove_s); + if (newMove_s.length == 1) moves.push(newMove_s[0]); + //promotion + else moves = moves.concat(newMove_s); }); return moves; } // Complete a move with magnetic actions // TODO: job is done multiple times for (normal) promotions. - applyMagneticLaws(move) - { - if (move.appear[0].p == V.KING && move.appear.length==1) - return [move]; //kings are not charged - const aIdx = (move.appear[0].p != V.KING ? 0 : 1); //if castling, rook is charged - const [x,y] = [move.appear[aIdx].x, move.appear[aIdx].y]; + applyMagneticLaws(move) { + if (move.appear[0].p == V.KING && move.appear.length == 1) return [move]; //kings are not charged + const aIdx = move.appear[0].p != V.KING ? 0 : 1; //if castling, rook is charged + const [x, y] = [move.appear[aIdx].x, move.appear[aIdx].y]; const color = this.turn; - const lastRank = (color=="w" ? 0 : 7); + const lastRank = color == "w" ? 0 : 7; const standardMove = JSON.parse(JSON.stringify(move)); this.play(standardMove); - for (let step of [[-1,0],[1,0],[0,-1],[0,1]]) - { - let [i,j] = [x+step[0],y+step[1]]; - while (V.OnBoard(i,j)) - { - if (this.board[i][j] != V.EMPTY) - { + for (let step of [ + [-1, 0], + [1, 0], + [0, -1], + [0, 1] + ]) { + let [i, j] = [x + step[0], y + step[1]]; + while (V.OnBoard(i, j)) { + if (this.board[i][j] != V.EMPTY) { // Found something. Same color or not? - if (this.getColor(i,j) != color) - { + if (this.getColor(i, j) != color) { // Attraction - if ((Math.abs(i-x)>=2 || Math.abs(j-y)>=2) && this.getPiece(i,j) != V.KING) - { + if ( + (Math.abs(i - x) >= 2 || Math.abs(j - y) >= 2) && + this.getPiece(i, j) != V.KING + ) { move.vanish.push( new PiPo({ - p:this.getPiece(i,j), - c:this.getColor(i,j), - x:i, - y:j + p: this.getPiece(i, j), + c: this.getColor(i, j), + x: i, + y: j }) ); move.appear.push( new PiPo({ - p:this.getPiece(i,j), - c:this.getColor(i,j), - x:x+step[0], - y:y+step[1] + p: this.getPiece(i, j), + c: this.getColor(i, j), + x: x + step[0], + y: y + step[1] }) ); } - } - else - { + } else { // Repulsion - if (this.getPiece(i,j) != V.KING) - { + if (this.getPiece(i, j) != V.KING) { // Push it until we meet an obstacle or edge of the board - let [ii,jj] = [i+step[0],j+step[1]]; - while (V.OnBoard(ii,jj)) - { - if (this.board[ii][jj] != V.EMPTY) - break; + let [ii, jj] = [i + step[0], j + step[1]]; + while (V.OnBoard(ii, jj)) { + if (this.board[ii][jj] != V.EMPTY) break; ii += step[0]; jj += step[1]; } ii -= step[0]; jj -= step[1]; - if (Math.abs(ii-i)>=1 || Math.abs(jj-j)>=1) - { + if (Math.abs(ii - i) >= 1 || Math.abs(jj - j) >= 1) { move.vanish.push( new PiPo({ - p:this.getPiece(i,j), - c:this.getColor(i,j), - x:i, - y:j + p: this.getPiece(i, j), + c: this.getColor(i, j), + x: i, + y: j }) ); move.appear.push( new PiPo({ - p:this.getPiece(i,j), - c:this.getColor(i,j), - x:ii, - y:jj + p: this.getPiece(i, j), + c: this.getColor(i, j), + x: ii, + y: jj }) ); } @@ -107,15 +101,15 @@ export const VariantRules = class MagneticRules extends ChessRules this.undo(standardMove); let moves = []; // Scan move for pawn (max 1) on 8th rank - for (let i=1; i= 2 && move.vanish[1].p == V.KING) - { + if (move.vanish.length >= 2 && move.vanish[1].p == V.KING) { // We took opponent king ! const oppCol = V.GetOppCol(c); - this.kingPos[oppCol] = [-1,-1]; - this.castleFlags[oppCol] = [false,false]; + this.kingPos[oppCol] = [-1, -1]; + this.castleFlags[oppCol] = [false, false]; } // Did we magnetically move our (init) rooks or opponents' ones ? - const firstRank = (c == "w" ? 7 : 0); + const firstRank = c == "w" ? 7 : 0; const oppFirstRank = 7 - firstRank; const oppCol = V.GetOppCol(c); move.vanish.forEach(psq => { if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y)) - this.castleFlags[c][psq.y==this.INIT_COL_ROOK[c][0] ? 0 : 1] = false; - else if (psq.x == oppFirstRank && this.INIT_COL_ROOK[oppCol].includes(psq.y)) - this.castleFlags[oppCol][psq.y==this.INIT_COL_ROOK[oppCol][0] ? 0 : 1] = false; + this.castleFlags[c][psq.y == this.INIT_COL_ROOK[c][0] ? 0 : 1] = false; + else if ( + psq.x == oppFirstRank && + this.INIT_COL_ROOK[oppCol].includes(psq.y) + ) + this.castleFlags[oppCol][ + psq.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1 + ] = false; }); } - unupdateVariables(move) - { + unupdateVariables(move) { super.unupdateVariables(move); const c = move.vanish[0].c; const oppCol = V.GetOppCol(c); - if (this.kingPos[oppCol][0] < 0) - { + if (this.kingPos[oppCol][0] < 0) { // Last move took opponent's king - for (let psq of move.vanish) - { - if (psq.p == 'k') - { + for (let psq of move.vanish) { + if (psq.p == "k") { this.kingPos[oppCol] = [psq.x, psq.y]; break; } @@ -193,19 +183,19 @@ export const VariantRules = class MagneticRules extends ChessRules } } - getCurrentScore() - { + getCurrentScore() { const color = this.turn; const kp = this.kingPos[color]; - if (kp[0] < 0) //king disappeared - return (color == "w" ? "0-1" : "1-0"); - if (this.atLeastOneMove()) // game not over + if (kp[0] < 0) + //king disappeared + return color == "w" ? "0-1" : "1-0"; + if (this.atLeastOneMove()) + // game not over return "*"; return "1/2"; //no moves but kings still there } - static get THRESHOLD_MATE() - { + static get THRESHOLD_MATE() { return 500; //checkmates evals may be slightly below 1000 } -} +}; diff --git a/client/src/variants/Marseille.js b/client/src/variants/Marseille.js index 31c363d2..19d35a63 100644 --- a/client/src/variants/Marseille.js +++ b/client/src/variants/Marseille.js @@ -1,106 +1,94 @@ import { ChessRules } from "@/base_rules"; import { randInt } from "@/utils/alea"; -export const VariantRules = class MarseilleRules extends ChessRules -{ - static IsGoodEnpassant(enpassant) - { - if (enpassant != "-") - { +export const VariantRules = class MarseilleRules extends ChessRules { + static IsGoodEnpassant(enpassant) { + if (enpassant != "-") { const squares = enpassant.split(","); - if (squares.length > 2) - return false; - for (let sq of squares) - { + if (squares.length > 2) return false; + for (let sq of squares) { const ep = V.SquareToCoords(sq); - if (isNaN(ep.x) || !V.OnBoard(ep)) - return false; + if (isNaN(ep.x) || !V.OnBoard(ep)) return false; } } return true; } - getTurnFen() - { + getTurnFen() { return this.turn + this.subTurn; } // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn) - getEnpassantFen() - { + getEnpassantFen() { const L = this.epSquares.length; - if (this.epSquares[L-1].every(epsq => epsq === undefined)) - return "-"; //no en-passant + if (this.epSquares[L - 1].every(epsq => epsq === undefined)) return "-"; //no en-passant let res = ""; - this.epSquares[L-1].forEach(epsq => { - if (!!epsq) - res += V.CoordsToSquare(epsq) + ","; + this.epSquares[L - 1].forEach(epsq => { + if (epsq) res += V.CoordsToSquare(epsq) + ","; }); - return res.slice(0,-1); //remove last comma + return res.slice(0, -1); //remove last comma } - setOtherVariables(fen) - { + setOtherVariables(fen) { const parsedFen = V.ParseFen(fen); this.setFlags(parsedFen.flags); - if (parsedFen.enpassant == "-") - this.epSquares = [ [undefined] ]; - else - { + if (parsedFen.enpassant == "-") this.epSquares = [[undefined]]; + else { let res = []; const squares = parsedFen.enpassant.split(","); - for (let sq of squares) - res.push(V.SquareToCoords(sq)); - this.epSquares = [ res ]; + for (let sq of squares) res.push(V.SquareToCoords(sq)); + this.epSquares = [res]; } this.scanKingsRooks(fen); // Extract subTurn from turn indicator: "w" (first move), or // "w1" or "w2" white subturn 1 or 2, and same for black const fullTurn = V.ParseFen(fen).turn; this.turn = fullTurn[0]; - this.subTurn = (fullTurn[1] || 0); //"w0" = special code for first move in game + this.subTurn = fullTurn[1] || 0; //"w0" = special code for first move in game } - getPotentialPawnMoves([x,y]) - { + getPotentialPawnMoves([x, y]) { const color = this.turn; let moves = []; - const [sizeX,sizeY] = [V.size.x,V.size.y]; - const shiftX = (color == "w" ? -1 : 1); - const firstRank = (color == 'w' ? sizeX-1 : 0); - const startRank = (color == "w" ? sizeX-2 : 1); - const lastRank = (color == "w" ? 0 : sizeX-1); - const finalPieces = x + shiftX == lastRank - ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN] - : [V.PAWN]; + const [sizeX, sizeY] = [V.size.x, V.size.y]; + const shiftX = color == "w" ? -1 : 1; + const firstRank = color == "w" ? sizeX - 1 : 0; + const startRank = color == "w" ? sizeX - 2 : 1; + const lastRank = color == "w" ? 0 : sizeX - 1; + const finalPieces = + x + shiftX == lastRank ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] : [V.PAWN]; // One square forward - if (this.board[x+shiftX][y] == V.EMPTY) - { - for (let piece of finalPieces) - { - moves.push(this.getBasicMove([x,y], [x+shiftX,y], - {c:color,p:piece})); + if (this.board[x + shiftX][y] == V.EMPTY) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece }) + ); } // Next condition because pawns on 1st rank can generally jump - if ([startRank,firstRank].includes(x) - && this.board[x+2*shiftX][y] == V.EMPTY) - { + if ( + [startRank, firstRank].includes(x) && + this.board[x + 2 * shiftX][y] == V.EMPTY + ) { // Two squares jump - moves.push(this.getBasicMove([x,y], [x+2*shiftX,y])); + moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); } } // Captures - for (let shiftY of [-1,1]) - { - if (y + shiftY >= 0 && y + shiftY < sizeY - && this.board[x+shiftX][y+shiftY] != V.EMPTY - && this.canTake([x,y], [x+shiftX,y+shiftY])) - { - for (let piece of finalPieces) - { - moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY], - {c:color,p:piece})); + for (let shiftY of [-1, 1]) { + if ( + y + shiftY >= 0 && + y + shiftY < sizeY && + this.board[x + shiftX][y + shiftY] != V.EMPTY && + this.canTake([x, y], [x + shiftX, y + shiftY]) + ) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y + shiftY], { + c: color, + p: piece + }) + ); } } } @@ -109,31 +97,33 @@ export const VariantRules = class MarseilleRules extends ChessRules // OK on subturn 2 only if enPassant was played at subturn 1 // (and if there are two e.p. squares available). const Lep = this.epSquares.length; - const epSquares = this.epSquares[Lep-1]; //always at least one element + const epSquares = this.epSquares[Lep - 1]; //always at least one element let epSqs = []; epSquares.forEach(sq => { - if (!!sq) - epSqs.push(sq); + if (sq) epSqs.push(sq); }); - if (epSqs.length == 0) - return moves; + if (epSqs.length == 0) return moves; const oppCol = V.GetOppCol(color); - for (let sq of epSqs) - { - if (this.subTurn == 1 || (epSqs.length == 2 && - // Was this en-passant capture already played at subturn 1 ? - // (Or maybe the opponent filled the en-passant square with a piece) - this.board[epSqs[0].x][epSqs[0].y] != V.EMPTY)) - { - if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1 + for (let sq of epSqs) { + if ( + this.subTurn == 1 || + (epSqs.length == 2 && + // Was this en-passant capture already played at subturn 1 ? + // (Or maybe the opponent filled the en-passant square with a piece) + this.board[epSqs[0].x][epSqs[0].y] != V.EMPTY) + ) { + if ( + sq.x == x + shiftX && + Math.abs(sq.y - y) == 1 && // Add condition "enemy pawn must be present" - && this.getPiece(x,sq.y) == V.PAWN && this.getColor(x,sq.y) == oppCol) - { - let epMove = this.getBasicMove([x,y], [sq.x,sq.y]); + this.getPiece(x, sq.y) == V.PAWN && + this.getColor(x, sq.y) == oppCol + ) { + let epMove = this.getBasicMove([x, y], [sq.x, sq.y]); epMove.vanish.push({ x: x, y: sq.y, - p: 'p', + p: "p", c: oppCol }); moves.push(epMove); @@ -144,49 +134,41 @@ export const VariantRules = class MarseilleRules extends ChessRules return moves; } - play(move) - { + play(move) { move.flags = JSON.stringify(this.aggregateFlags()); move.turn = this.turn + this.subTurn; V.PlayOnBoard(this.board, move); const epSq = this.getEpSquare(move); - if (this.subTurn == 0) //first move in game - { + if (this.subTurn == 0) { + //first move in game this.turn = "b"; this.subTurn = 1; this.epSquares.push([epSq]); } // Does this move give check on subturn 1? If yes, skip subturn 2 - else if (this.subTurn==1 && this.underCheck(V.GetOppCol(this.turn))) - { + else if (this.subTurn == 1 && this.underCheck(V.GetOppCol(this.turn))) { this.turn = V.GetOppCol(this.turn); this.epSquares.push([epSq]); move.checkOnSubturn1 = true; - } - else - { - if (this.subTurn == 2) - { + } else { + if (this.subTurn == 2) { this.turn = V.GetOppCol(this.turn); - let lastEpsq = this.epSquares[this.epSquares.length-1]; + let lastEpsq = this.epSquares[this.epSquares.length - 1]; lastEpsq.push(epSq); - } - else - this.epSquares.push([epSq]); + } else this.epSquares.push([epSq]); this.subTurn = 3 - this.subTurn; } this.updateVariables(move); } - undo(move) - { + undo(move) { this.disaggregateFlags(JSON.parse(move.flags)); V.UndoOnBoard(this.board, move); - if (move.turn[1] == '0' || move.checkOnSubturn1 || this.subTurn == 2) + if (move.turn[1] == "0" || move.checkOnSubturn1 || this.subTurn == 2) this.epSquares.pop(); - else //this.subTurn == 1 - { - let lastEpsq = this.epSquares[this.epSquares.length-1]; + //this.subTurn == 1 + else { + let lastEpsq = this.epSquares[this.epSquares.length - 1]; lastEpsq.pop(); } this.turn = move.turn[0]; @@ -197,23 +179,20 @@ export const VariantRules = class MarseilleRules extends ChessRules // NOTE: GenRandInitFen() is OK, // since at first move turn indicator is just "w" - static get VALUES() - { + static get VALUES() { return { - 'p': 1, - 'r': 5, - 'n': 3, - 'b': 3, - 'q': 7, //slightly less than in orthodox game - 'k': 1000 + p: 1, + r: 5, + n: 3, + b: 3, + q: 7, //slightly less than in orthodox game + k: 1000 }; } // No alpha-beta here, just adapted min-max at depth 2(+1) - getComputerMove() - { - if (this.subTurn == 2) - return null; //TODO: imperfect interface setup + getComputerMove() { + if (this.subTurn == 2) return null; //TODO: imperfect interface setup const maxeval = V.INFINITY; const color = this.turn; @@ -221,35 +200,29 @@ export const VariantRules = class MarseilleRules extends ChessRules // Search best (half) move for opponent turn const getBestMoveEval = () => { - const turnBefore = this.turn + this.subTurn; let score = this.getCurrentScore(); - if (score != "*") - { - if (score == "1/2") - return 0; + if (score != "*") { + if (score == "1/2") return 0; return maxeval * (score == "1-0" ? 1 : -1); } let moves = this.getAllValidMoves(); - let res = (oppCol == "w" ? -maxeval : maxeval); - for (let m of moves) - { + let res = oppCol == "w" ? -maxeval : maxeval; + for (let m of moves) { this.play(m); score = this.getCurrentScore(); // Now turn is oppCol,2 if m doesn't give check // Otherwise it's color,1. In both cases the next test makes sense - if (score != "*") - { + if (score != "*") { if (score == "1/2") - res = (oppCol == "w" ? Math.max(res, 0) : Math.min(res, 0)); - else - { + res = oppCol == "w" ? Math.max(res, 0) : Math.min(res, 0); + else { // Found a mate this.undo(m); return maxeval * (score == "1-0" ? 1 : -1); } } const evalPos = this.evalPosition(); - res = (oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos)); + res = oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos); this.undo(m); } return res; @@ -258,42 +231,39 @@ export const VariantRules = class MarseilleRules extends ChessRules let moves11 = this.getAllValidMoves(); let doubleMoves = []; // Rank moves using a min-max at depth 2 - for (let i=0; i { - return (color=="w" ? 1 : -1) * (b.eval - a.eval); }); + doubleMoves.sort((a, b) => { + return (color == "w" ? 1 : -1) * (b.eval - a.eval); + }); let candidates = [0]; //indices of candidates moves - for (let i=1; - i 2) - return false; - for (let sq of squares) - { + if (squares.length > 2) return false; + for (let sq of squares) { const ep = V.SquareToCoords(sq); - if (isNaN(ep.x) || !V.OnBoard(ep)) - return false; + if (isNaN(ep.x) || !V.OnBoard(ep)) return false; } } return true; } // There may be 2 enPassant squares (if pawn jump 3 squares) - getEnpassantFen() - { + getEnpassantFen() { const L = this.epSquares.length; - if (!this.epSquares[L-1]) - return "-"; //no en-passant + if (!this.epSquares[L - 1]) return "-"; //no en-passant let res = ""; - this.epSquares[L-1].forEach(sq => { + this.epSquares[L - 1].forEach(sq => { res += V.CoordsToSquare(sq) + ","; }); - return res.slice(0,-1); //remove last comma + return res.slice(0, -1); //remove last comma } // En-passant after 2-sq or 3-sq jumps - getEpSquare(moveOrSquare) - { - if (!moveOrSquare) - return undefined; - if (typeof moveOrSquare === "string") - { + getEpSquare(moveOrSquare) { + if (!moveOrSquare) return undefined; + if (typeof moveOrSquare === "string") { const square = moveOrSquare; - if (square == "-") - return undefined; + if (square == "-") return undefined; let res = []; square.split(",").forEach(sq => { res.push(V.SquareToCoords(sq)); @@ -75,18 +77,19 @@ export const VariantRules = class WildebeestRules extends ChessRules } // Argument is a move: const move = moveOrSquare; - const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x]; - if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2) - { - const step = (ex-sx) / Math.abs(ex-sx); - let res = [{ - x: sx + step, - y: sy - }]; - if (sx + 2*step != ex) //3-squares move - { + const [sx, sy, ex] = [move.start.x, move.start.y, move.end.x]; + if (this.getPiece(sx, sy) == V.PAWN && Math.abs(sx - ex) >= 2) { + const step = (ex - sx) / Math.abs(ex - sx); + let res = [ + { + x: sx + step, + y: sy + } + ]; + if (sx + 2 * step != ex) { + //3-squares move res.push({ - x: sx + 2*step, + x: sx + 2 * step, y: sy }); } @@ -95,85 +98,80 @@ export const VariantRules = class WildebeestRules extends ChessRules return undefined; //default } - getPotentialMovesFrom([x,y]) - { - switch (this.getPiece(x,y)) - { + getPotentialMovesFrom([x, y]) { + switch (this.getPiece(x, y)) { case V.CAMEL: - return this.getPotentialCamelMoves([x,y]); + return this.getPotentialCamelMoves([x, y]); case V.WILDEBEEST: - return this.getPotentialWildebeestMoves([x,y]); + return this.getPotentialWildebeestMoves([x, y]); default: - return super.getPotentialMovesFrom([x,y]) + return super.getPotentialMovesFrom([x, y]); } } // Pawns jump 2 or 3 squares, and promote to queen or wildebeest - getPotentialPawnMoves([x,y]) - { + getPotentialPawnMoves([x, y]) { const color = this.turn; let moves = []; - const [sizeX,sizeY] = [V.size.x,V.size.y]; - const shiftX = (color == "w" ? -1 : 1); - const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]); - const lastRank = (color == "w" ? 0 : sizeX-1); - const finalPieces = x + shiftX == lastRank - ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN] - : [V.PAWN]; + const [sizeX, sizeY] = [V.size.x, V.size.y]; + const shiftX = color == "w" ? -1 : 1; + const startRanks = color == "w" ? [sizeX - 2, sizeX - 3] : [1, 2]; + const lastRank = color == "w" ? 0 : sizeX - 1; + const finalPieces = + x + shiftX == lastRank ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] : [V.PAWN]; - if (this.board[x+shiftX][y] == V.EMPTY) - { + if (this.board[x + shiftX][y] == V.EMPTY) { // One square forward for (let piece of finalPieces) - moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece})); - if (startRanks.includes(x)) - { - if (this.board[x+2*shiftX][y] == V.EMPTY) - { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece }) + ); + if (startRanks.includes(x)) { + if (this.board[x + 2 * shiftX][y] == V.EMPTY) { // Two squares jump - moves.push(this.getBasicMove([x,y], [x+2*shiftX,y])); - if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY) - { + moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); + if (x == startRanks[0] && this.board[x + 3 * shiftX][y] == V.EMPTY) { // Three squares jump - moves.push(this.getBasicMove([x,y], [x+3*shiftX,y])); + moves.push(this.getBasicMove([x, y], [x + 3 * shiftX, y])); } } } } // Captures - for (let shiftY of [-1,1]) - { - if (y + shiftY >= 0 && y + shiftY < sizeY - && this.board[x+shiftX][y+shiftY] != V.EMPTY - && this.canTake([x,y], [x+shiftX,y+shiftY])) - { - for (let piece of finalPieces) - { - moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY], - {c:color,p:piece})); + for (let shiftY of [-1, 1]) { + if ( + y + shiftY >= 0 && + y + shiftY < sizeY && + this.board[x + shiftX][y + shiftY] != V.EMPTY && + this.canTake([x, y], [x + shiftX, y + shiftY]) + ) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y + shiftY], { + c: color, + p: piece + }) + ); } } } // En passant const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep-1]; - if (!!epSquare) - { - for (let epsq of epSquare) - { + const epSquare = this.epSquares[Lep - 1]; + if (epSquare) { + for (let epsq of epSquare) { // TODO: some redundant checks - if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1) - { - var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]); + if (epsq.x == x + shiftX && Math.abs(epsq.y - y) == 1) { + var enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]); // WARNING: the captured pawn may be diagonally behind us, // if it's a 3-squares jump and we take on 1st passing square - const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX); + const px = this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX; enpassantMove.vanish.push({ x: px, y: epsq.y, - p: 'p', - c: this.getColor(px,epsq.y) + p: "p", + c: this.getColor(px, epsq.y) }); moves.push(enpassantMove); } @@ -185,74 +183,87 @@ export const VariantRules = class WildebeestRules extends ChessRules // TODO: wildebeest castle - getPotentialCamelMoves(sq) - { + getPotentialCamelMoves(sq) { return this.getSlideNJumpMoves(sq, V.steps[V.CAMEL], "oneStep"); } - getPotentialWildebeestMoves(sq) - { + getPotentialWildebeestMoves(sq) { return this.getSlideNJumpMoves( - sq, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep"); + sq, + V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), + "oneStep" + ); } - isAttacked(sq, colors) - { - return super.isAttacked(sq, colors) - || this.isAttackedByCamel(sq, colors) - || this.isAttackedByWildebeest(sq, colors); + isAttacked(sq, colors) { + return ( + super.isAttacked(sq, colors) || + this.isAttackedByCamel(sq, colors) || + this.isAttackedByWildebeest(sq, colors) + ); } - isAttackedByCamel(sq, colors) - { - return this.isAttackedBySlideNJump(sq, colors, - V.CAMEL, V.steps[V.CAMEL], "oneStep"); + isAttackedByCamel(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.CAMEL, + V.steps[V.CAMEL], + "oneStep" + ); } - isAttackedByWildebeest(sq, colors) - { - return this.isAttackedBySlideNJump(sq, colors, V.WILDEBEEST, - V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep"); + isAttackedByWildebeest(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.WILDEBEEST, + V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), + "oneStep" + ); } - getCurrentScore() - { - if (this.atLeastOneMove()) // game not over + getCurrentScore() { + if (this.atLeastOneMove()) + // game not over return "*"; // No valid move: game is lost (stalemate is a win) - return (this.turn == "w" ? "0-1" : "1-0"); + return this.turn == "w" ? "0-1" : "1-0"; } static get VALUES() { return Object.assign( ChessRules.VALUES, - {'c': 3, 'w': 7} //experimental + { c: 3, w: 7 } //experimental ); } - static get SEARCH_DEPTH() { return 2; } + static get SEARCH_DEPTH() { + return 2; + } - static GenRandInitFen() - { - let pieces = { "w": new Array(10), "b": new Array(10) }; - for (let c of ["w","b"]) - { + static GenRandInitFen() { + let pieces = { w: new Array(10), b: new Array(10) }; + for (let c of ["w", "b"]) { let positions = ArrayFun.range(11); // Get random squares for bishops + camels (different colors) - let randIndexes = sample(ArrayFun.range(6), 2) - .map(i => { return 2*i; }); + let randIndexes = sample(ArrayFun.range(6), 2).map(i => { + return 2 * i; + }); let bishop1Pos = positions[randIndexes[0]]; let camel1Pos = positions[randIndexes[1]]; // The second bishop (camel) must be on a square of different color - let randIndexes_tmp = sample(ArrayFun.range(5), 2) - .map(i => { return 2*i+1; }); + let randIndexes_tmp = sample(ArrayFun.range(5), 2).map(i => { + return 2 * i + 1; + }); let bishop2Pos = positions[randIndexes_tmp[0]]; let camel2Pos = positions[randIndexes_tmp[1]]; - for (let idx of randIndexes.concat(randIndexes_tmp) - .sort((a,b) => { return b-a; })) //largest indices first - { + for (let idx of randIndexes.concat(randIndexes_tmp).sort((a, b) => { + return b - a; + })) { + //largest indices first positions.splice(idx, 1); } @@ -276,21 +287,23 @@ export const VariantRules = class WildebeestRules extends ChessRules let kingPos = positions[1]; let rook2Pos = positions[2]; - pieces[c][rook1Pos] = 'r'; - pieces[c][knight1Pos] = 'n'; - pieces[c][bishop1Pos] = 'b'; - pieces[c][queenPos] = 'q'; - pieces[c][camel1Pos] = 'c'; - pieces[c][camel2Pos] = 'c'; - pieces[c][wildebeestPos] = 'w'; - pieces[c][kingPos] = 'k'; - pieces[c][bishop2Pos] = 'b'; - pieces[c][knight2Pos] = 'n'; - pieces[c][rook2Pos] = 'r'; + pieces[c][rook1Pos] = "r"; + pieces[c][knight1Pos] = "n"; + pieces[c][bishop1Pos] = "b"; + pieces[c][queenPos] = "q"; + pieces[c][camel1Pos] = "c"; + pieces[c][camel2Pos] = "c"; + pieces[c][wildebeestPos] = "w"; + pieces[c][kingPos] = "k"; + pieces[c][bishop2Pos] = "b"; + pieces[c][knight2Pos] = "n"; + pieces[c][rook2Pos] = "r"; } - return pieces["b"].join("") + + return ( + pieces["b"].join("") + "/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" + pieces["w"].join("").toUpperCase() + - " w 0 1111 -"; + " w 0 1111 -" + ); } -} +}; diff --git a/client/src/variants/Zen.js b/client/src/variants/Zen.js index 256b80dc..f5bff8ff 100644 --- a/client/src/variants/Zen.js +++ b/client/src/variants/Zen.js @@ -1,26 +1,21 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class ZenRules extends ChessRules -{ +export const VariantRules = class ZenRules extends ChessRules { // NOTE: enPassant, if enabled, would need to redefine carefully getEpSquare - static get HasEnpassant() { return false; } + static get HasEnpassant() { + return false; + } // TODO(?): some duplicated code in 2 next functions - getSlideNJumpMoves([x,y], steps, oneStep) - { - const color = this.getColor(x,y); + getSlideNJumpMoves([x, y], steps, oneStep) { let moves = []; - outerLoop: - for (let loop=0; loop { - moves.push(this.getBasicMove([x,y], [i,j], {c:color,p:p})); + moves.push(this.getBasicMove([x, y], [i, j], { c: color, p: p })); }); - } - else - { + } else { // All other cases - moves.push(this.getBasicMove([x,y], [i,j])); + moves.push(this.getBasicMove([x, y], [i, j])); } } } @@ -76,8 +76,7 @@ export const VariantRules = class ZenRules extends ChessRules } // Find possible captures from a square: look in every direction! - findCaptures(sq) - { + findCaptures(sq) { let moves = []; Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.PAWN)); @@ -89,96 +88,91 @@ export const VariantRules = class ZenRules extends ChessRules return moves; } - getPotentialPawnMoves([x,y]) - { - const color = this.getColor(x,y); + getPotentialPawnMoves([x, y]) { + const color = this.getColor(x, y); let moves = []; - const [sizeX,sizeY] = [V.size.x,V.size.y]; - const shift = (color == 'w' ? -1 : 1); - const startRank = (color == 'w' ? sizeY-2 : 1); - const firstRank = (color == 'w' ? sizeY-1 : 0); - const lastRank = (color == "w" ? 0 : sizeY-1); - - if (x+shift != lastRank) - { + const sizeY = V.size.y; + const shift = color == "w" ? -1 : 1; + const startRank = color == "w" ? sizeY - 2 : 1; + const firstRank = color == "w" ? sizeY - 1 : 0; + const lastRank = color == "w" ? 0 : sizeY - 1; + + if (x + shift != lastRank) { // Normal moves - if (this.board[x+shift][y] == V.EMPTY) - { - moves.push(this.getBasicMove([x,y], [x+shift,y])); - if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY) - { + if (this.board[x + shift][y] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [x + shift, y])); + if ( + [startRank, firstRank].includes(x) && + this.board[x + 2 * shift][y] == V.EMPTY + ) { //two squares jump - moves.push(this.getBasicMove([x,y], [x+2*shift,y])); + moves.push(this.getBasicMove([x, y], [x + 2 * shift, y])); } } - } - - else //promotion - { - let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]; + } //promotion + else { + let promotionPieces = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]; promotionPieces.forEach(p => { // Normal move - if (this.board[x+shift][y] == V.EMPTY) - moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p})); + if (this.board[x + shift][y] == V.EMPTY) + moves.push( + this.getBasicMove([x, y], [x + shift, y], { c: color, p: p }) + ); }); } // No en passant here // Add "zen" captures - Array.prototype.push.apply(moves, this.findCaptures([x,y])); + Array.prototype.push.apply(moves, this.findCaptures([x, y])); return moves; } - getPotentialRookMoves(sq) - { + getPotentialRookMoves(sq) { let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.ROOK]); let captures = this.findCaptures(sq); return noCaptures.concat(captures); } - getPotentialKnightMoves(sq) - { + getPotentialKnightMoves(sq) { let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"); let captures = this.findCaptures(sq); return noCaptures.concat(captures); } - getPotentialBishopMoves(sq) - { + getPotentialBishopMoves(sq) { let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]); let captures = this.findCaptures(sq); return noCaptures.concat(captures); } - getPotentialQueenMoves(sq) - { + getPotentialQueenMoves(sq) { let noCaptures = this.getSlideNJumpMoves( - sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP])); + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + ); let captures = this.findCaptures(sq); return noCaptures.concat(captures); } - getPotentialKingMoves(sq) - { + getPotentialKingMoves(sq) { // Initialize with normal moves - let noCaptures = this.getSlideNJumpMoves(sq, - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); + let noCaptures = this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + "oneStep" + ); let captures = this.findCaptures(sq); return noCaptures.concat(captures).concat(this.getCastleMoves(sq)); } - getNotation(move) - { + getNotation(move) { // Recognize special moves first - if (move.appear.length == 2) - { + if (move.appear.length == 2) { // castle - if (move.end.y < move.start.y) - return "0-0-0"; - else - return "0-0"; + if (move.end.y < move.start.y) return "0-0-0"; + return "0-0"; } // Translate initial square (because pieces may fly unusually in this variant!) @@ -189,40 +183,33 @@ export const VariantRules = class ZenRules extends ChessRules let notation = ""; const piece = this.getPiece(move.start.x, move.start.y); - if (piece == V.PAWN) - { + if (piece == V.PAWN) { // pawn move (TODO: enPassant indication) - if (move.vanish.length > 1) - { + if (move.vanish.length > 1) { // capture notation = initialSquare + "x" + finalSquare; - } - else //no capture - notation = finalSquare; - if (piece != move.appear[0].p) //promotion + } //no capture + else notation = finalSquare; + if (piece != move.appear[0].p) + //promotion notation += "=" + move.appear[0].p.toUpperCase(); - } - - else - { + } else { // Piece movement notation = piece.toUpperCase(); - if (move.vanish.length > 1) - notation += initialSquare + "x"; + if (move.vanish.length > 1) notation += initialSquare + "x"; notation += finalSquare; } return notation; } - static get VALUES() - { + static get VALUES() { return { - 'p': 1, - 'r': 3, - 'n': 2, - 'b': 2, - 'q': 5, - 'k': 1000 - } + p: 1, + r: 3, + n: 2, + b: 2, + q: 5, + k: 1000 + }; } -} +}; diff --git a/client/src/views/About.vue b/client/src/views/About.vue index a685f3c8..711cc0b0 100644 --- a/client/src/views/About.vue +++ b/client/src/views/About.vue @@ -8,15 +8,18 @@ main diff --git a/client/src/views/Analyse.vue b/client/src/views/Analyse.vue index 035cd089..5d22306e 100644 --- a/client/src/views/Analyse.vue +++ b/client/src/views/Analyse.vue @@ -11,46 +11,43 @@ main diff --git a/client/src/views/Auth.vue b/client/src/views/Auth.vue index a750b3e3..a44687c1 100644 --- a/client/src/views/Auth.vue +++ b/client/src/views/Auth.vue @@ -10,33 +10,31 @@ main import { store } from "@/store"; import { ajax } from "@/utils/ajax"; export default { - name: 'my-auth', + name: "my-auth", data: function() { return { st: store.state, - errmsg: "", + errmsg: "" }; }, created: function() { - ajax( - "/authenticate", - "GET", - {token: this.$route.params["token"]}, - (res) => { - if (!res.errmsg) //if not already logged in - { - this.st.user.id = res.id; - this.st.user.name = res.name; - this.st.user.email = res.email; - this.st.user.notify = res.notify; - localStorage["myname"] = res.name; - localStorage["myid"] = res.id; - } - else - this.errmsg = res.errmsg; - } + ajax( + "/authenticate", + "GET", + { token: this.$route.params["token"] }, + res => { + if (!res.errmsg) { + //if not already logged in + this.st.user.id = res.id; + this.st.user.name = res.name; + this.st.user.email = res.email; + this.st.user.notify = res.notify; + localStorage["myname"] = res.name; + localStorage["myid"] = res.id; + } else this.errmsg = res.errmsg; + } ); - }, + } }; diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 5255cf43..a0e264ed 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -42,28 +42,29 @@ import { GameStorage } from "@/utils/gameStorage"; import { ppt } from "@/utils/datetime"; import { extractTime } from "@/utils/timeControl"; import { getRandString } from "@/utils/alea"; -import { ArrayFun } from "@/utils/array"; import { processModalClick } from "@/utils/modalClick"; import { getScoreMessage } from "@/utils/scoring"; import params from "@/parameters"; export default { - name: 'my-game', + name: "my-game", components: { BaseGame, - Chat, + Chat }, // gameRef: to find the game in (potentially remote) storage data: function() { return { st: store.state, - gameRef: { //given in URL (rid = remote ID) + gameRef: { + //given in URL (rid = remote ID) id: "", rid: "" }, - game: { //passed to BaseGame - players:[{name:""},{name:""}], + game: { + //passed to BaseGame + players: [{ name: "" }, { name: "" }], chats: [], - rendered: false, + rendered: false }, virtualClocks: [0, 0], //initialized with true game.clocks vr: null, //"variant rules" object initialized from FEN @@ -76,46 +77,54 @@ export default { connexionString: "", // Related to (killing of) self multi-connects: newConnect: {}, - killed: {}, + killed: {} }; }, watch: { - "$route": function(to, from) { + $route: function(to) { this.gameRef.id = to.params["id"]; this.gameRef.rid = to.query["rid"]; this.loadGame(); - }, + } }, // NOTE: some redundant code with Hall.vue (mostly related to people array) created: function() { // Always add myself to players' list const my = this.st.user; - this.$set(this.people, my.sid, {id:my.id, name:my.name}); + this.$set(this.people, my.sid, { id: my.id, name: my.name }); this.gameRef.id = this.$route.params["id"]; this.gameRef.rid = this.$route.query["rid"]; //may be undefined // Initialize connection - this.connexionString = params.socketUrl + - "/?sid=" + this.st.user.sid + - "&tmpId=" + getRandString() + - "&page=" + encodeURIComponent(this.$route.path); + this.connexionString = + params.socketUrl + + "/?sid=" + + this.st.user.sid + + "&tmpId=" + + getRandString() + + "&page=" + + encodeURIComponent(this.$route.path); this.conn = new WebSocket(this.connexionString); this.conn.onmessage = this.socketMessageListener; this.conn.onclose = this.socketCloseListener; // Socket init required before loading remote game: - const socketInit = (callback) => { - if (!!this.conn && this.conn.readyState == 1) //1 == OPEN state + const socketInit = callback => { + if (!!this.conn && this.conn.readyState == 1) + //1 == OPEN state callback(); - else //socket not ready yet (initial loading) - { + //socket not ready yet (initial loading) + else { // NOTE: it's important to call callback without arguments, // otherwise first arg is Websocket object and loadGame fails. - this.conn.onopen = () => { return callback() }; + this.conn.onopen = () => { + return callback(); + }; } }; - if (!this.gameRef.rid) //game stored locally or on server + if (!this.gameRef.rid) + //game stored locally or on server this.loadGame(null, () => socketInit(this.roomInit)); - else //game stored remotely: need socket to retrieve it - { + //game stored remotely: need socket to retrieve it + else { // NOTE: the callback "roomInit" will be lost, so we don't provide it. // --> It will be given when receiving "fullgame" socket event. // A more general approach would be to store it somewhere. @@ -123,8 +132,9 @@ export default { } }, mounted: function() { - document.getElementById("chatWrap").addEventListener( - "click", processModalClick); + document + .getElementById("chatWrap") + .addEventListener("click", processModalClick); }, beforeDestroy: function() { this.send("disconnect"); @@ -137,14 +147,8 @@ export default { this.send("pollclients"); }, send: function(code, obj) { - if (!!this.conn) - { - this.conn.send(JSON.stringify( - Object.assign( - {code: code}, - obj, - ) - )); + if (this.conn) { + this.conn.send(JSON.stringify(Object.assign({ code: code }, obj))); } }, isConnected: function(index) { @@ -153,38 +157,38 @@ export default { if (this.st.user.sid == player.sid || this.st.user.id == player.uid) return true; // Try to find a match in people: - return Object.keys(this.people).some(sid => sid == player.sid) || - Object.values(this.people).some(p => p.id == player.uid); + return ( + Object.keys(this.people).some(sid => sid == player.sid) || + Object.values(this.people).some(p => p.id == player.uid) + ); }, socketMessageListener: function(msg) { - if (!this.conn) - return; + if (!this.conn) return; const data = JSON.parse(msg.data); - switch (data.code) - { + switch (data.code) { case "pollclients": data.sockIds.forEach(sid => { - this.$set(this.people, sid, {id:0, name:""}); - if (sid != this.st.user.sid) - { - this.send("askidentity", {target:sid}); + this.$set(this.people, sid, { id: 0, name: "" }); + if (sid != this.st.user.sid) { + this.send("askidentity", { target: sid }); // Ask potentially missed last state, if opponent and I play - if (!!this.game.mycolor - && this.game.type == "live" && this.game.score == "*" - && this.game.players.some(p => p.sid == sid)) - { - this.send("asklastate", {target:sid}); + if ( + !!this.game.mycolor && + this.game.type == "live" && + this.game.score == "*" && + this.game.players.some(p => p.sid == sid) + ) { + this.send("asklastate", { target: sid }); } } }); break; case "connect": if (!this.people[data.from]) - this.$set(this.people, data.from, {name:"", id:0}); - if (!this.people[data.from].name) - { + this.$set(this.people, data.from, { name: "", id: 0 }); + if (!this.people[data.from].name) { this.newConnect[data.from] = true; //for self multi-connects tests - this.send("askidentity", {target:data.from}); + this.send("askidentity", { target: data.from }); } break; case "disconnect": @@ -199,40 +203,39 @@ export default { //this.conn.close(); this.conn = null; break; - case "askidentity": - { + case "askidentity": { // Request for identification (TODO: anonymous shouldn't need to reply) const me = { // Decompose to avoid revealing email name: this.st.user.name, sid: this.st.user.sid, - id: this.st.user.id, + id: this.st.user.id }; - this.send("identity", {data:me, target:data.from}); + this.send("identity", { data: me, target: data.from }); break; } - case "identity": - { + case "identity": { const user = data.data; - if (!!user.name) //otherwise anonymous - { + if (user.name) { + //otherwise anonymous // If I multi-connect, kill current connexion if no mark (I'm older) - if (this.newConnect[user.sid] && user.id > 0 - && user.id == this.st.user.id && user.sid != this.st.user.sid) - { - if (!this.killed[this.st.user.sid]) - { - this.send("killme", {sid:this.st.user.sid}); + if ( + this.newConnect[user.sid] && + user.id > 0 && + user.id == this.st.user.id && + user.sid != this.st.user.sid + ) { + if (!this.killed[this.st.user.sid]) { + this.send("killme", { sid: this.st.user.sid }); this.killed[this.st.user.sid] = true; } } - if (user.sid != this.st.user.sid) //I already know my identity... - { - this.$set(this.people, user.sid, - { - id: user.id, - name: user.name, - }); + if (user.sid != this.st.user.sid) { + //I already know my identity... + this.$set(this.people, user.sid, { + id: user.id, + name: user.name + }); } } delete this.newConnect[user.sid]; @@ -240,9 +243,10 @@ export default { } case "askgame": // Send current (live) game if not asked by any of the players - if (this.game.type == "live" - && this.game.players.every(p => p.sid != data.from[0])) - { + if ( + this.game.type == "live" && + this.game.players.every(p => p.sid != data.from[0]) + ) { const myGame = { id: this.game.id, fen: this.game.fen, @@ -250,13 +254,13 @@ export default { vid: this.game.vid, cadence: this.game.cadence, score: this.game.score, - rid: this.st.user.sid, //useful in Hall if I'm an observer + rid: this.st.user.sid //useful in Hall if I'm an observer }; - this.send("game", {data:myGame, target:data.from}); + this.send("game", { data: myGame, target: data.from }); } break; case "askfullgame": - this.send("fullgame", {data:this.game, target:data.from}); + this.send("fullgame", { data: this.game, target: data.from }); break; case "fullgame": // Callback "roomInit" to poll clients only after game is loaded @@ -264,46 +268,48 @@ export default { break; case "asklastate": // Sending last state if I played a move or score != "*" - if ((this.game.moves.length > 0 && this.vr.turn != this.game.mycolor) - || this.game.score != "*" || this.drawOffer == "sent") - { + if ( + (this.game.moves.length > 0 && this.vr.turn != this.game.mycolor) || + this.game.score != "*" || + this.drawOffer == "sent" + ) { // Send our "last state" informations to opponent const L = this.game.moves.length; - const myIdx = ["w","b"].indexOf(this.game.mycolor); + const myIdx = ["w", "b"].indexOf(this.game.mycolor); const myLastate = { // NOTE: lastMove (when defined) includes addTime - lastMove: (L>0 ? this.game.moves[L-1] : undefined), + lastMove: L > 0 ? this.game.moves[L - 1] : undefined, // Since we played a move (or abort or resign), // only drawOffer=="sent" is possible drawSent: this.drawOffer == "sent", score: this.game.score, movesCount: L, - initime: this.game.initime[1-myIdx], //relevant only if I played + initime: this.game.initime[1 - myIdx] //relevant only if I played }; - this.send("lastate", {data:myLastate, target:data.from}); + this.send("lastate", { data: myLastate, target: data.from }); } break; case "lastate": //got opponent infos about last move this.lastate = data.data; - if (this.game.rendered) //game is rendered (Board component) + if (this.game.rendered) + //game is rendered (Board component) this.processLastate(); //else: will be processed when game is ready break; - case "newmove": - { + case "newmove": { const move = data.data; - if (!!move.cancelDrawOffer) //opponent refuses draw - { + if (move.cancelDrawOffer) { + //opponent refuses draw this.drawOffer = ""; // NOTE for corr games: drawOffer reset by player in turn if (this.game.type == "live" && !!this.game.mycolor) - GameStorage.update(this.gameRef.id, {drawOffer: ""}); + GameStorage.update(this.gameRef.id, { drawOffer: "" }); } this.$set(this.game, "moveToPlay", move); break; } case "resign": - this.gameOver(data.side=="b" ? "1-0" : "0-1", "Resign"); + this.gameOver(data.side == "b" ? "1-0" : "0-1", "Resign"); break; case "abort": this.gameOver("?", "Abort"); @@ -324,138 +330,129 @@ export default { }, socketCloseListener: function() { this.conn = new WebSocket(this.connexionString); - this.conn.addEventListener('message', this.socketMessageListener); - this.conn.addEventListener('close', this.socketCloseListener); + this.conn.addEventListener("message", this.socketMessageListener); + this.conn.addEventListener("close", this.socketCloseListener); }, // lastate was received, but maybe game wasn't ready yet: processLastate: function() { const data = this.lastate; this.lastate = undefined; //security... const L = this.game.moves.length; - if (data.movesCount > L) - { + if (data.movesCount > L) { // Just got last move from him - this.$set(this.game, "moveToPlay", Object.assign({initime: data.initime}, data.lastMove)); + this.$set( + this.game, + "moveToPlay", + Object.assign({ initime: data.initime }, data.lastMove) + ); } - if (data.drawSent) - this.drawOffer = "received"; - if (data.score != "*") - { + if (data.drawSent) this.drawOffer = "received"; + if (data.score != "*") { this.drawOffer = ""; - if (this.game.score == "*") - this.gameOver(data.score); + if (this.game.score == "*") this.gameOver(data.score); } }, clickDraw: function() { - if (!this.game.mycolor) - return; //I'm just spectator - if (["received","threerep"].includes(this.drawOffer)) - { - if (!confirm(this.st.tr["Accept draw?"])) - return; - const message = (this.drawOffer == "received" - ? "Mutual agreement" - : "Three repetitions"); - this.send("draw", {data:message}); + if (!this.game.mycolor) return; //I'm just spectator + if (["received", "threerep"].includes(this.drawOffer)) { + if (!confirm(this.st.tr["Accept draw?"])) return; + const message = + this.drawOffer == "received" + ? "Mutual agreement" + : "Three repetitions"; + this.send("draw", { data: message }); this.gameOver("1/2", message); - } - else if (this.drawOffer == "") //no effect if drawOffer == "sent" - { - if (this.game.mycolor != this.vr.turn) - return alert(this.st.tr["Draw offer only in your turn"]); - if (!confirm(this.st.tr["Offer draw?"])) + } else if (this.drawOffer == "") { + //no effect if drawOffer == "sent" + if (this.game.mycolor != this.vr.turn) { + alert(this.st.tr["Draw offer only in your turn"]); return; + } + if (!confirm(this.st.tr["Offer draw?"])) return; this.drawOffer = "sent"; this.send("drawoffer"); - GameStorage.update(this.gameRef.id, {drawOffer: this.game.mycolor}); + GameStorage.update(this.gameRef.id, { drawOffer: this.game.mycolor }); } }, abortGame: function() { - if (!this.game.mycolor || !confirm(this.st.tr["Terminate game?"])) - return; + if (!this.game.mycolor || !confirm(this.st.tr["Terminate game?"])) return; this.gameOver("?", "Abort"); this.send("abort"); }, - resign: function(e) { + resign: function() { if (!this.game.mycolor || !confirm(this.st.tr["Resign the game?"])) return; - this.send("resign", {data:this.game.mycolor}); - this.gameOver(this.game.mycolor=="w" ? "0-1" : "1-0", "Resign"); + this.send("resign", { data: this.game.mycolor }); + this.gameOver(this.game.mycolor == "w" ? "0-1" : "1-0", "Resign"); }, // 3 cases for loading a game: // - from indexedDB (running or completed live game I play) // - from server (one correspondance game I play[ed] or not) // - from remote peer (one live game I don't play, finished or not) loadGame: function(game, callback) { - const afterRetrieval = async (game) => { + const afterRetrieval = async game => { const vModule = await import("@/variants/" + game.vname + ".js"); window.V = vModule.VariantRules; this.vr = new V(game.fen); - const gtype = (game.cadence.indexOf('d') >= 0 ? "corr" : "live"); + const gtype = game.cadence.indexOf("d") >= 0 ? "corr" : "live"; const tc = extractTime(game.cadence); const myIdx = game.players.findIndex(p => { return p.sid == this.st.user.sid || p.uid == this.st.user.id; }); - const mycolor = [undefined,"w","b"][myIdx+1]; //undefined for observers - if (!game.chats) - game.chats = []; //live games don't have chat history - if (gtype == "corr") - { - if (game.players[0].color == "b") - { + const mycolor = [undefined, "w", "b"][myIdx + 1]; //undefined for observers + if (!game.chats) game.chats = []; //live games don't have chat history + if (gtype == "corr") { + if (game.players[0].color == "b") { // Adopt the same convention for live and corr games: [0] = white - [ game.players[0], game.players[1] ] = - [ game.players[1], game.players[0] ]; + [game.players[0], game.players[1]] = [ + game.players[1], + game.players[0] + ]; } // corr game: needs to compute the clocks + initime // NOTE: clocks in seconds, initime in milliseconds game.clocks = [tc.mainTime, tc.mainTime]; - game.moves.sort((m1,m2) => m1.idx - m2.idx); //in case of - if (game.score == "*") //otherwise no need to bother with time - { + game.moves.sort((m1, m2) => m1.idx - m2.idx); //in case of + if (game.score == "*") { + //otherwise no need to bother with time game.initime = [0, 0]; const L = game.moves.length; - if (L >= 3) - { + if (L >= 3) { let addTime = [0, 0]; - for (let i=2; i= 1) - game.initime[L%2] = game.moves[L-1].played; + if (L >= 1) game.initime[L % 2] = game.moves[L - 1].played; } - const reformattedMoves = game.moves.map( (m) => { + const reformattedMoves = game.moves.map(m => { const s = m.squares; return { appear: s.appear, vanish: s.vanish, start: s.start, - end: s.end, + end: s.end }; }); // Sort chat messages from newest to oldest - game.chats.sort( (c1,c2) => { return c2.added - c1.added; }); - if (myIdx >= 0 && game.chats.length > 0) - { + game.chats.sort((c1, c2) => { + return c2.added - c1.added; + }); + if (myIdx >= 0 && game.chats.length > 0) { // TODO: group multi-moves into an array, to deduce color from index // and not need this (also repeated in BaseGame::re_setVariables()) let vr_tmp = new V(game.fenStart); //vr is already at end of game - for (let i=0; i= 0; midx--) - { - if (game.moves[midx].color == mycolor) - { + for (let midx = game.moves.length - 1; midx >= 0; midx--) { + if (game.moves[midx].color == mycolor) { dtLastMove = game.moves[midx].played; break; } @@ -466,44 +463,42 @@ export default { // Now that we used idx and played, re-format moves as for live games game.moves = reformattedMoves; } - if (gtype == "live" && game.clocks[0] < 0) //game unstarted - { + if (gtype == "live" && game.clocks[0] < 0) { + //game unstarted game.clocks = [tc.mainTime, tc.mainTime]; - if (game.score == "*") - { + if (game.score == "*") { game.initime[0] = Date.now(); - if (myIdx >= 0) - { + if (myIdx >= 0) { // I play in this live game; corr games don't have clocks+initime - GameStorage.update(game.id, - { + GameStorage.update(game.id, { clocks: game.clocks, - initime: game.initime, + initime: game.initime }); } } } - if (!!game.drawOffer) - { - if (game.drawOffer == "t") //three repetitions + if (game.drawOffer) { + if (game.drawOffer == "t") + //three repetitions this.drawOffer = "threerep"; - else - { - if (myIdx < 0) - this.drawOffer = "received"; //by any of the players - else - { + else { + if (myIdx < 0) this.drawOffer = "received"; + //by any of the players + else { // I play in this game: - if ((game.drawOffer == "w" && myIdx==0) || (game.drawOffer=="b" && myIdx==1)) + if ( + (game.drawOffer == "w" && myIdx == 0) || + (game.drawOffer == "b" && myIdx == 1) + ) this.drawOffer = "sent"; - else //all other cases - this.drawOffer = "received"; + //all other cases + else this.drawOffer = "received"; } } } - if (!!game.scoreMsg) - game.scoreMsg = this.st.tr[game.scoreMsg]; //stored in english - this.game = Object.assign({}, + if (game.scoreMsg) game.scoreMsg = this.st.tr[game.scoreMsg]; //stored in english + this.game = Object.assign( + {}, game, // NOTE: assign mycolor here, since BaseGame could also be VS computer { @@ -512,16 +507,15 @@ export default { mycolor: mycolor, // opponent sid not strictly required (or available), but easier // at least oppsid or oppid is available anyway: - oppsid: (myIdx < 0 ? undefined : game.players[1-myIdx].sid), - oppid: (myIdx < 0 ? undefined : game.players[1-myIdx].uid), + oppsid: myIdx < 0 ? undefined : game.players[1 - myIdx].sid, + oppid: myIdx < 0 ? undefined : game.players[1 - myIdx].uid } ); this.re_setClocks(); this.$nextTick(() => { this.game.rendered = true; // Did lastate arrive before game was rendered? - if (!!this.lastate) - this.processLastate(); + if (this.lastate) this.processLastate(); }); this.repeat = {}; //reset: scan past moves' FEN: let repIdx = 0; @@ -529,74 +523,84 @@ export default { let vr_tmp = new V(game.fenStart); game.moves.forEach(m => { vr_tmp.play(m); - const fenObj = V.ParseFen( vr_tmp.getFen() ); + const fenObj = V.ParseFen(vr_tmp.getFen()); repIdx = fenObj.position + "_" + fenObj.turn; - if (!!fenObj.flags) - repIdx += "_" + fenObj.flags; - this.repeat[repIdx] = (!!this.repeat[repIdx] - ? this.repeat[repIdx]+1 - : 1); + if (fenObj.flags) repIdx += "_" + fenObj.flags; + this.repeat[repIdx] = this.repeat[repIdx] + ? this.repeat[repIdx] + 1 + : 1; }); - if (this.repeat[repIdx] >= 3) - this.drawOffer = "threerep"; - if (!!callback) - callback(); + if (this.repeat[repIdx] >= 3) this.drawOffer = "threerep"; + if (callback) callback(); }; - if (!!game) - return afterRetrieval(game); - if (!!this.gameRef.rid) - { - // Remote live game: forgetting about callback func... (TODO: design) - this.send("askfullgame", {target:this.gameRef.rid}); + if (game) { + afterRetrieval(game); + return; } - else - { + if (this.gameRef.rid) { + // Remote live game: forgetting about callback func... (TODO: design) + this.send("askfullgame", { target: this.gameRef.rid }); + } else { // Local or corr game GameStorage.get(this.gameRef.id, afterRetrieval); } }, re_setClocks: function() { - if (this.game.moves.length < 2 || this.game.score != "*") - { + if (this.game.moves.length < 2 || this.game.score != "*") { // 1st move not completed yet, or game over: freeze time this.virtualClocks = this.game.clocks.map(s => ppt(s)); return; } const currentTurn = this.vr.turn; - const colorIdx = ["w","b"].indexOf(currentTurn); - let countdown = this.game.clocks[colorIdx] - - (Date.now() - this.game.initime[colorIdx])/1000; - this.virtualClocks = [0,1].map(i => { - const removeTime = i == colorIdx - ? (Date.now() - this.game.initime[colorIdx])/1000 - : 0; + const colorIdx = ["w", "b"].indexOf(currentTurn); + let countdown = + this.game.clocks[colorIdx] - + (Date.now() - this.game.initime[colorIdx]) / 1000; + this.virtualClocks = [0, 1].map(i => { + const removeTime = + i == colorIdx ? (Date.now() - this.game.initime[colorIdx]) / 1000 : 0; return ppt(this.game.clocks[i] - removeTime); }); let clockUpdate = setInterval(() => { - if (countdown < 0 || this.vr.turn != currentTurn || this.game.score != "*") - { + if ( + countdown < 0 || + this.vr.turn != currentTurn || + this.game.score != "*" + ) { clearInterval(clockUpdate); if (countdown < 0) - this.gameOver(this.vr.turn=="w" ? "0-1" : "1-0", this.st.tr["Time"]); - } - else - this.$set(this.virtualClocks, colorIdx, ppt(Math.max(0, --countdown))); + this.gameOver( + this.vr.turn == "w" ? "0-1" : "1-0", + this.st.tr["Time"] + ); + } else + this.$set( + this.virtualClocks, + colorIdx, + ppt(Math.max(0, --countdown)) + ); }, 1000); }, // Post-process a move (which was just played in BaseGame) processMove: function(move) { - if (this.game.type == "corr" && move.color == this.game.mycolor) - { - if (!confirm(this.st.tr["Move played:"] + " " + move.notation + "\n" + this.st.tr["Are you sure?"])) - { - return this.$set(this.game, "moveToUndo", move); + if (this.game.type == "corr" && move.color == this.game.mycolor) { + if ( + !confirm( + this.st.tr["Move played:"] + + " " + + move.notation + + "\n" + + this.st.tr["Are you sure?"] + ) + ) { + this.$set(this.game, "moveToUndo", move); + return; } } - const colorIdx = ["w","b"].indexOf(move.color); - const nextIdx = ["w","b"].indexOf(this.vr.turn); + const colorIdx = ["w", "b"].indexOf(move.color); + const nextIdx = ["w", "b"].indexOf(this.vr.turn); // https://stackoverflow.com/a/38750895 - if (!!this.game.mycolor) - { + if (this.game.mycolor) { const allowed_fields = ["appear", "vanish", "start", "end"]; // NOTE: 'var' to see this variable outside this block var filtered_move = Object.keys(move) @@ -608,28 +612,24 @@ export default { } // Send move ("newmove" event) to people in the room (if our turn) let addTime = 0; - if (move.color == this.game.mycolor) - { - if (this.drawOffer == "received") //I refuse draw + if (move.color == this.game.mycolor) { + if (this.drawOffer == "received") + //I refuse draw this.drawOffer = ""; - if (this.game.moves.length >= 2) //after first move - { + if (this.game.moves.length >= 2) { + //after first move const elapsed = Date.now() - this.game.initime[colorIdx]; // elapsed time is measured in milliseconds - addTime = this.game.increment - elapsed/1000; + addTime = this.game.increment - elapsed / 1000; } - const sendMove = Object.assign({}, - filtered_move, - { - addTime: addTime, - cancelDrawOffer: this.drawOffer=="", - }); - this.send("newmove", {data: sendMove}); + const sendMove = Object.assign({}, filtered_move, { + addTime: addTime, + cancelDrawOffer: this.drawOffer == "" + }); + this.send("newmove", { data: sendMove }); // (Add)Time indication: useful in case of lastate infos requested move.addTime = addTime; - } - else - addTime = move.addTime; //supposed transmitted + } else addTime = move.addTime; //supposed transmitted // Update current game object: this.game.moves.push(move); this.game.fen = move.fen; @@ -640,23 +640,18 @@ export default { // If repetition detected, consider that a draw offer was received: const fenObj = V.ParseFen(move.fen); let repIdx = fenObj.position + "_" + fenObj.turn; - if (!!fenObj.flags) - repIdx += "_" + fenObj.flags; - this.repeat[repIdx] = (!!this.repeat[repIdx] - ? this.repeat[repIdx]+1 - : 1); - if (this.repeat[repIdx] >= 3) - this.drawOffer = "threerep"; - else if (this.drawOffer == "threerep") - this.drawOffer = ""; + if (fenObj.flags) repIdx += "_" + fenObj.flags; + this.repeat[repIdx] = this.repeat[repIdx] ? this.repeat[repIdx] + 1 : 1; + if (this.repeat[repIdx] >= 3) this.drawOffer = "threerep"; + else if (this.drawOffer == "threerep") this.drawOffer = ""; // Since corr games are stored at only one location, update should be // done only by one player for each move: - if (!!this.game.mycolor && - (this.game.type == "live" || move.color == this.game.mycolor)) - { + if ( + !!this.game.mycolor && + (this.game.type == "live" || move.color == this.game.mycolor) + ) { let drawCode = ""; - switch (this.drawOffer) - { + switch (this.drawOffer) { case "threerep": drawCode = "t"; break; @@ -667,29 +662,24 @@ export default { drawCode = this.vr.turn; break; } - if (this.game.type == "corr") - { - GameStorage.update(this.gameRef.id, - { + if (this.game.type == "corr") { + GameStorage.update(this.gameRef.id, { fen: move.fen, - move: - { + move: { squares: filtered_move, played: Date.now(), - idx: this.game.moves.length - 1, + idx: this.game.moves.length - 1 }, - drawOffer: drawCode || "n", //"n" for "None" to force reset (otherwise it's ignored) + drawOffer: drawCode || "n" //"n" for "None" to force reset (otherwise it's ignored) }); - } - else //live - { - GameStorage.update(this.gameRef.id, - { + } //live + else { + GameStorage.update(this.gameRef.id, { fen: move.fen, move: filtered_move, clocks: this.game.clocks, initime: this.game.initime, - drawOffer: drawCode, + drawOffer: drawCode }); } } @@ -699,28 +689,30 @@ export default { document.getElementById("chatBtn").classList.remove("somethingnew"); }, processChat: function(chat) { - this.send("newchat", {data:chat}); + this.send("newchat", { data: chat }); // NOTE: anonymous chats in corr games are not stored on server (TODO?) if (this.game.type == "corr" && this.st.user.id > 0) - GameStorage.update(this.gameRef.id, {chat: chat}); + GameStorage.update(this.gameRef.id, { chat: chat }); }, gameOver: function(score, scoreMsg) { this.game.score = score; - this.game.scoreMsg = this.st.tr[(!!scoreMsg - ? scoreMsg - : getScoreMessage(score))]; + this.game.scoreMsg = this.st.tr[ + scoreMsg ? scoreMsg : getScoreMessage(score) + ]; const myIdx = this.game.players.findIndex(p => { return p.sid == this.st.user.sid || p.uid == this.st.user.id; }); - if (myIdx >= 0) //OK, I play in this game - { - GameStorage.update(this.gameRef.id, - {score: score, scoreMsg: scoreMsg}); + if (myIdx >= 0) { + //OK, I play in this game + GameStorage.update(this.gameRef.id, { + score: score, + scoreMsg: scoreMsg + }); // Notify the score to main Hall. TODO: only one player (currently double send) - this.send("result", {gid:this.game.id, score:score}); + this.send("result", { gid: this.game.id, score: score }); } - }, - }, + } + } }; diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 6f2dfeab..190717d0 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -91,9 +91,9 @@ export default { components: { Chat, GameList, - ChallengeList, + ChallengeList }, - data: function () { + data: function() { return { st: store.state, cdisplay: "live", //or corr @@ -106,125 +106,129 @@ export default { fen: "", vid: localStorage.getItem("vid") || "", to: "", //name of challenged player (if any) - cadence: localStorage.getItem("cadence") || "", + cadence: localStorage.getItem("cadence") || "" }, newChat: "", conn: null, connexionString: "", // Related to (killing of) self multi-connects: newConnect: {}, - killed: {}, + killed: {} }; }, watch: { // st.variants changes only once, at loading from [] to [...] - "st.variants": function(variantArray) { + "st.variants": function() { // Set potential challenges and games variant names: this.challenges.concat(this.games).forEach(o => { - if (o.vname == "") - o.vname = this.getVname(o.vid); + if (o.vname == "") o.vname = this.getVname(o.vid); }); - }, + } }, computed: { anonymousCount: function() { let count = 0; - Object.values(this.people).forEach(p => { count += (!p.name ? 1 : 0); }); + Object.values(this.people).forEach(p => { + count += !p.name ? 1 : 0; + }); return count; - }, + } }, created: function() { const my = this.st.user; - this.$set(this.people, my.sid, {id:my.id, name:my.name, pages:["/"]}); + this.$set(this.people, my.sid, { id: my.id, name: my.name, pages: ["/"] }); // Ask server for current corr games (all but mines) ajax( "/games", "GET", - {uid: this.st.user.id, excluded: true}, + { uid: this.st.user.id, excluded: true }, response => { - this.games = this.games.concat(response.games.map(g => { - const type = this.classifyObject(g); - const vname = this.getVname(g.vid); - return Object.assign({}, g, {type: type, vname: vname}); - })); + this.games = this.games.concat( + response.games.map(g => { + const type = this.classifyObject(g); + const vname = this.getVname(g.vid); + return Object.assign({}, g, { type: type, vname: vname }); + }) + ); } ); // Also ask for corr challenges (open + sent by/to me) - ajax( - "/challenges", - "GET", - {uid: this.st.user.id}, - response => { - // Gather all senders names, and then retrieve full identity: - // (TODO [perf]: some might be online...) - let names = {}; - response.challenges.forEach(c => { - if (c.uid != this.st.user.id) - names[c.uid] = ""; //unknwon for now - else if (!!c.target && c.target != this.st.user.id) - names[c.target] = ""; - }); - const addChallenges = (newChalls) => { - names[this.st.user.id] = this.st.user.name; //in case of - this.challenges = this.challenges.concat( - response.challenges.map(c => { - const from = {name: names[c.uid], id: c.uid}; //or just name - const type = this.classifyObject(c); - const vname = this.getVname(c.vid); - return Object.assign({}, - { - type: type, - vname: vname, - from: from, - to: (!!c.target ? names[c.target] : ""), - }, - c); - }) - ); - }; - if (Object.keys(names).length > 0) - { - ajax("/users", - "GET", - { ids: Object.keys(names).join(",") }, - response2 => { - response2.users.forEach(u => {names[u.id] = u.name}); - addChallenges(); - } - ); - } - else - addChallenges(); - } - ); + ajax("/challenges", "GET", { uid: this.st.user.id }, response => { + // Gather all senders names, and then retrieve full identity: + // (TODO [perf]: some might be online...) + let names = {}; + response.challenges.forEach(c => { + if (c.uid != this.st.user.id) names[c.uid] = ""; + //unknwon for now + else if (!!c.target && c.target != this.st.user.id) + names[c.target] = ""; + }); + const addChallenges = () => { + names[this.st.user.id] = this.st.user.name; //in case of + this.challenges = this.challenges.concat( + response.challenges.map(c => { + const from = { name: names[c.uid], id: c.uid }; //or just name + const type = this.classifyObject(c); + const vname = this.getVname(c.vid); + return Object.assign( + {}, + { + type: type, + vname: vname, + from: from, + to: c.target ? names[c.target] : "" + }, + c + ); + }) + ); + }; + if (Object.keys(names).length > 0) { + ajax( + "/users", + "GET", + { ids: Object.keys(names).join(",") }, + response2 => { + response2.users.forEach(u => { + names[u.id] = u.name; + }); + addChallenges(); + } + ); + } else addChallenges(); + }); const connectAndPoll = () => { this.send("connect"); this.send("pollclientsandgamers"); }; // Initialize connection - this.connexionString = params.socketUrl + - "/?sid=" + this.st.user.sid + - "&tmpId=" + getRandString() + - "&page=" + encodeURIComponent(this.$route.path); + this.connexionString = + params.socketUrl + + "/?sid=" + + this.st.user.sid + + "&tmpId=" + + getRandString() + + "&page=" + + encodeURIComponent(this.$route.path); this.conn = new WebSocket(this.connexionString); this.conn.onopen = connectAndPoll; this.conn.onmessage = this.socketMessageListener; this.conn.onclose = this.socketCloseListener; }, mounted: function() { - ["peopleWrap","infoDiv","newgameDiv"].forEach(eltName => { + ["peopleWrap", "infoDiv", "newgameDiv"].forEach(eltName => { let elt = document.getElementById(eltName); elt.addEventListener("click", processModalClick); }); - document.querySelectorAll("#predefinedCadences > button").forEach( - (b) => { b.addEventListener("click", - () => { this.newchallenge.cadence = b.innerHTML; } - )} - ); + document.querySelectorAll("#predefinedCadences > button").forEach(b => { + b.addEventListener("click", () => { + this.newchallenge.cadence = b.innerHTML; + }); + }); const showCtype = localStorage.getItem("type-challenges") || "live"; const showGtype = localStorage.getItem("type-games") || "live"; - this.setDisplay('c', showCtype); - this.setDisplay('g', showGtype); + this.setDisplay("c", showCtype); + this.setDisplay("g", showGtype); }, beforeDestroy: function() { this.send("disconnect"); @@ -232,20 +236,14 @@ export default { methods: { // Helpers: send: function(code, obj) { - if (!!this.conn) - { - this.conn.send(JSON.stringify( - Object.assign( - {code: code}, - obj, - ) - )); + if (this.conn) { + this.conn.send(JSON.stringify(Object.assign({ code: code }, obj))); } }, getVname: function(vid) { const variant = this.st.variants.find(v => v.id == vid); // this.st.variants might be uninitialized (variant == null) - return (!!variant ? variant.name : ""); + return variant ? variant.name : ""; }, filterChallenges: function(type) { return this.challenges.filter(c => c.type == type); @@ -253,21 +251,24 @@ export default { filterGames: function(type) { return this.games.filter(g => g.type == type); }, - classifyObject: function(o) { //challenge or game - return (o.cadence.indexOf('d') === -1 ? "live" : "corr"); + classifyObject: function(o) { + //challenge or game + return o.cadence.indexOf("d") === -1 ? "live" : "corr"; }, setDisplay: function(letter, type, e) { this[letter + "display"] = type; - localStorage.setItem("type-" + (letter == 'c' ? "challenges" : "games"), type); - let elt = !!e + localStorage.setItem( + "type-" + (letter == "c" ? "challenges" : "games"), + type + ); + let elt = e ? e.target : document.getElementById("btn" + letter.toUpperCase() + type); elt.classList.add("active"); elt.classList.remove("somethingnew"); //in case of - if (!!elt.previousElementSibling) + if (elt.previousElementSibling) elt.previousElementSibling.classList.remove("active"); - else - elt.nextElementSibling.classList.remove("active"); + else elt.nextElementSibling.classList.remove("active"); }, isGamer: function(sid) { return this.people[sid].pages.some(p => p.indexOf("/game/") >= 0); @@ -278,21 +279,17 @@ export default { : "Observe"; }, challOrWatch: function(sid) { - if (this.people[sid].pages.some(p => p == "/")) - { + if (this.people[sid].pages.some(p => p == "/")) { // Available, in Hall this.newchallenge.to = this.people[sid].name; document.getElementById("modalPeople").checked = false; - doClick("modalNewgame"); - } - else - { + window.doClick("modalNewgame"); + } else { // In some game, maybe playing maybe not: show a random one let gids = []; this.people[sid].pages.forEach(p => { const matchGid = p.match(/[a-zA-Z0-9]+$/); - if (!!matchGid) - gids.push(matchGid[0]); + if (matchGid) gids.push(matchGid[0]); }); const gid = gids[Math.floor(Math.random() * gids.length)]; this.showGame(this.games.find(g => g.id == gid)); @@ -311,90 +308,82 @@ export default { document.getElementById("peopleBtn").classList.remove("somethingnew"); }, processChat: function(chat) { - this.send("newchat", {data:chat}); + this.send("newchat", { data: chat }); }, // Messaging center: socketMessageListener: function(msg) { - if (!this.conn) - return; + if (!this.conn) return; const data = JSON.parse(msg.data); - switch (data.code) - { - case "pollclientsandgamers": - { + switch (data.code) { + case "pollclientsandgamers": { // Since people can be both in Hall and Game, // need to track "askIdentity" requests: let identityAsked = {}; data.sockIds.forEach(s => { const page = s.page || "/"; - if (s.sid != this.st.user.sid && !identityAsked[s.sid]) - { + if (s.sid != this.st.user.sid && !identityAsked[s.sid]) { identityAsked[s.sid] = true; - this.send("askidentity", {target:s.sid, page:page}); + this.send("askidentity", { target: s.sid, page: page }); } if (!this.people[s.sid]) - this.$set(this.people, s.sid, {id:0, name:"", pages:[page]}); + this.$set(this.people, s.sid, { id: 0, name: "", pages: [page] }); else if (this.people[s.sid].pages.indexOf(page) < 0) this.people[s.sid].pages.push(page); - if (!s.page) //peer is in Hall - this.send("askchallenge", {target:s.sid}); - else //peer is in Game - this.send("askgame", {target:s.sid, page:page}); + if (!s.page) + //peer is in Hall + this.send("askchallenge", { target: s.sid }); + //peer is in Game + else this.send("askgame", { target: s.sid, page: page }); }); break; } case "connect": - case "gconnect": - { + case "gconnect": { const page = data.page || "/"; // NOTE: player could have been polled earlier, but might have logged in then // So it's a good idea to ask identity if he was anonymous. // But only ask game / challenge if currently disconnected. - if (!this.people[data.from]) - { - this.$set(this.people, data.from, {name:"", id:0, pages:[page]}); + if (!this.people[data.from]) { + this.$set(this.people, data.from, { + name: "", + id: 0, + pages: [page] + }); if (data.code == "connect") - this.send("askchallenge", {target:data.from}); - else - this.send("askgame", {target:data.from, page:page}); - } - else - { + this.send("askchallenge", { target: data.from }); + else this.send("askgame", { target: data.from, page: page }); + } else { // append page if not already in list if (this.people[data.from].pages.indexOf(page) < 0) this.people[data.from].pages.push(page); } - if (this.people[data.from].id == 0) - { + if (this.people[data.from].id == 0) { this.newConnect[data.from] = true; //for self multi-connects tests - this.send("askidentity", {target:data.from, page:page}); + this.send("askidentity", { target: data.from, page: page }); } break; } case "disconnect": - case "gdisconnect": + case "gdisconnect": { // If the user reloads the page twice very quickly (experienced with Firefox), // the first reload won't have time to connect but will trigger a "close" event anyway. // ==> Next check is required. - if (!this.people[data.from]) - return; + if (!this.people[data.from]) return; // Disconnect means no more tmpIds: - if (data.code == "disconnect") - { + if (data.code == "disconnect") { // Remove the live challenge sent by this player: ArrayFun.remove(this.challenges, c => c.from.sid == data.from); - } - else - { + } else { // Remove the matching live game if now unreachable const gid = data.page.match(/[a-zA-Z0-9]+$/)[0]; const gidx = this.games.findIndex(g => g.id == gid); - if (gidx >= 0) - { + if (gidx >= 0) { const game = this.games[gidx]; - if (game.type == "live" && - game.rids.length == 1 && game.rids[0] == data.from) - { + if ( + game.type == "live" && + game.rids.length == 1 && + game.rids[0] == data.from + ) { this.games.splice(gidx, 1); } } @@ -404,6 +393,7 @@ export default { if (this.people[data.from].pages.length == 0) this.$delete(this.people, data.from); break; + } case "killed": // I logged in elsewhere: alert(this.st.tr["New connexion detected: tab now offline"]); @@ -413,157 +403,156 @@ export default { //this.conn.close(); this.conn = null; break; - case "askidentity": - { + case "askidentity": { // Request for identification (TODO: anonymous shouldn't need to reply) const me = { // Decompose to avoid revealing email name: this.st.user.name, sid: this.st.user.sid, - id: this.st.user.id, + id: this.st.user.id }; - this.send("identity", {data:me, target:data.from}); + this.send("identity", { data: me, target: data.from }); break; } - case "identity": - { + case "identity": { const user = data.data; - if (!!user.name) //otherwise anonymous - { + if (user.name) { + //otherwise anonymous // If I multi-connect, kill current connexion if no mark (I'm older) - if (this.newConnect[user.sid] && user.id > 0 - && user.id == this.st.user.id && user.sid != this.st.user.sid) - { - if (!this.killed[this.st.user.sid]) - { - this.send("killme", {sid:this.st.user.sid}); + if ( + this.newConnect[user.sid] && + user.id > 0 && + user.id == this.st.user.id && + user.sid != this.st.user.sid + ) { + if (!this.killed[this.st.user.sid]) { + this.send("killme", { sid: this.st.user.sid }); this.killed[this.st.user.sid] = true; } } - if (user.sid != this.st.user.sid) //I already know my identity... - { - this.$set(this.people, user.sid, - { - id: user.id, - name: user.name, - pages: this.people[user.sid].pages, - }); + if (user.sid != this.st.user.sid) { + //I already know my identity... + this.$set(this.people, user.sid, { + id: user.id, + name: user.name, + pages: this.people[user.sid].pages + }); } } delete this.newConnect[user.sid]; break; } - case "askchallenge": - { + case "askchallenge": { // Send my current live challenge (if any) - const cIdx = this.challenges.findIndex(c => - c.from.sid == this.st.user.sid && c.type == "live"); - if (cIdx >= 0) - { + const cIdx = this.challenges.findIndex( + c => c.from.sid == this.st.user.sid && c.type == "live" + ); + if (cIdx >= 0) { const c = this.challenges[cIdx]; // NOTE: in principle, should only send targeted challenge to the target. // But we may not know yet the identity of the target (just name), // so cannot decide if data.from is the target or not. - const myChallenge = - { + const myChallenge = { id: c.id, from: this.st.user.sid, to: c.to, fen: c.fen, vid: c.vid, cadence: c.cadence, - added: c.added, + added: c.added }; - this.send("challenge", {data:myChallenge, target:data.from}); + this.send("challenge", { data: myChallenge, target: data.from }); } break; } case "challenge": //after "askchallenge" - case "newchallenge": - { + case "newchallenge": { // NOTE about next condition: see "askchallenge" case. const chall = data.data; - if (!chall.to || (this.people[chall.from].id > 0 && - (chall.from == this.st.user.sid || chall.to == this.st.user.name))) - { + if ( + !chall.to || + (this.people[chall.from].id > 0 && + (chall.from == this.st.user.sid || chall.to == this.st.user.name)) + ) { let newChall = Object.assign({}, chall); newChall.type = this.classifyObject(chall); newChall.added = Date.now(); let fromValues = Object.assign({}, this.people[chall.from]); delete fromValues["pages"]; //irrelevant in this context - newChall.from = Object.assign({sid:chall.from}, fromValues); + newChall.from = Object.assign({ sid: chall.from }, fromValues); newChall.vname = this.getVname(newChall.vid); this.challenges.push(newChall); - if ((newChall.type == "live" && this.cdisplay == "corr") || - (newChall.type == "corr" && this.cdisplay == "live")) - { - document.getElementById("btnC" + newChall.type).classList.add("somethingnew"); + if ( + (newChall.type == "live" && this.cdisplay == "corr") || + (newChall.type == "corr" && this.cdisplay == "live") + ) { + document + .getElementById("btnC" + newChall.type) + .classList.add("somethingnew"); } } break; } - case "refusechallenge": - { + case "refusechallenge": { const cid = data.data; ArrayFun.remove(this.challenges, c => c.id == cid); alert(this.st.tr["Challenge declined"]); break; } - case "deletechallenge": - { + case "deletechallenge": { // NOTE: the challenge may be already removed const cid = data.data; ArrayFun.remove(this.challenges, c => c.id == cid); break; } case "game": //individual request - case "newgame": - { + case "newgame": { // NOTE: it may be live or correspondance const game = data.data; let locGame = this.games.find(g => g.id == game.id); - if (!locGame) - { + if (!locGame) { let newGame = game; newGame.type = this.classifyObject(game); newGame.vname = this.getVname(game.vid); - if (!game.score) //if new game from Hall + if (!game.score) + //if new game from Hall newGame.score = "*"; newGame.rids = [game.rid]; delete newGame["rid"]; this.games.push(newGame); - if ((newGame.type == "live" && this.gdisplay == "corr") || - (newGame.type == "corr" && this.gdisplay == "live")) - { - document.getElementById("btnG" + newGame.type).classList.add("somethingnew"); + if ( + (newGame.type == "live" && this.gdisplay == "corr") || + (newGame.type == "corr" && this.gdisplay == "live") + ) { + document + .getElementById("btnG" + newGame.type) + .classList.add("somethingnew"); } - } - else - { + } else { // Append rid (if not already in list) - if (!locGame.rids.includes(game.rid)) - locGame.rids.push(game.rid); + if (!locGame.rids.includes(game.rid)) locGame.rids.push(game.rid); } break; } - case "result": - { + case "result": { let g = this.games.find(g => g.id == data.gid); - if (!!g) - g.score = data.score; + if (g) g.score = data.score; break; } - case "startgame": - { + case "startgame": { // New game just started: data contain all information const gameInfo = data.data; if (this.classifyObject(gameInfo) == "live") this.startNewGame(gameInfo); - else - { - this.infoMessage = this.st.tr["New correspondance game:"] + - " " + - "#/game/" + gameInfo.id + ""; + else { + this.infoMessage = + this.st.tr["New correspondance game:"] + + " " + + "#/game/" + + gameInfo.id + + ""; let modalBox = document.getElementById("modalInfo"); modalBox.checked = true; } @@ -577,56 +566,62 @@ export default { } }, socketCloseListener: function() { - if (!this.conn) - return; + if (!this.conn) return; this.conn = new WebSocket(this.connexionString); this.conn.addEventListener("message", this.socketMessageListener); this.conn.addEventListener("close", this.socketCloseListener); }, // Challenge lifecycle: newChallenge: async function() { + let error = ""; if (this.newchallenge.vid == "") - return alert(this.st.tr["Please select a variant"]); - if (!!this.newchallenge.to && this.newchallenge.to == this.st.user.name) - return alert(this.st.tr["Self-challenge is forbidden"]); + error = this.st.tr["Please select a variant"]; + else if (!!this.newchallenge.to && this.newchallenge.to == this.st.user.name) + error = this.st.tr["Self-challenge is forbidden"]; + if (error) { + alert(error); + return; + } const vname = this.getVname(this.newchallenge.vid); const vModule = await import("@/variants/" + vname + ".js"); window.V = vModule.VariantRules; - if (!!this.newchallenge.cadence.match(/^[0-9]+$/)) + if (this.newchallenge.cadence.match(/^[0-9]+$/)) this.newchallenge.cadence += "+0"; //assume minutes, no increment - const error = checkChallenge(this.newchallenge); - if (!!error) - return alert(error); const ctype = this.classifyObject(this.newchallenge); - if (ctype == "corr" && this.st.user.id <= 0) - return alert(this.st.tr["Please log in to play correspondance games"]); + error = checkChallenge(this.newchallenge); + if (!error && ctype == "corr" && this.st.user.id <= 0) + error = this.st.tr["Please log in to play correspondance games"]; + if (error) { + alert(error); + return; + } // NOTE: "from" information is not required here let chall = Object.assign({}, this.newchallenge); - const finishAddChallenge = (cid) => { + const finishAddChallenge = cid => { chall.id = cid || "c" + getRandString(); // Remove old challenge if any (only one at a time of a given type): - const cIdx = this.challenges.findIndex(c => - (c.from.sid == this.st.user.sid || c.from.id == this.st.user.id) && c.type == ctype); - if (cIdx >= 0) - { + const cIdx = this.challenges.findIndex( + c => + (c.from.sid == this.st.user.sid || c.from.id == this.st.user.id) && + c.type == ctype + ); + if (cIdx >= 0) { // Delete current challenge (will be replaced now) - this.send("deletechallenge", {data:this.challenges[cIdx].id}); - if (ctype == "corr") - { - ajax( - "/challenges", - "DELETE", - {id: this.challenges[cIdx].id} - ); + this.send("deletechallenge", { data: this.challenges[cIdx].id }); + if (ctype == "corr") { + ajax("/challenges", "DELETE", { id: this.challenges[cIdx].id }); } this.challenges.splice(cIdx, 1); } - this.send("newchallenge", {data:Object.assign({from:this.st.user.sid}, chall)}); + this.send("newchallenge", { + data: Object.assign({ from: this.st.user.sid }, chall) + }); // Add new challenge: - chall.from = { //decompose to avoid revealing email + chall.from = { + //decompose to avoid revealing email sid: this.st.user.sid, id: this.st.user.id, - name: this.st.user.name, + name: this.st.user.name }; chall.added = Date.now(); // NOTE: vname and type are redundant (can be deduced from cadence + vid) @@ -638,61 +633,49 @@ export default { localStorage.setItem("vid", chall.vid); document.getElementById("modalNewgame").checked = false; }; - if (ctype == "live") - { + if (ctype == "live") { // Live challenges have a random ID finishAddChallenge(null); - } - else - { + } else { // Correspondance game: send challenge to server - ajax( - "/challenges", - "POST", - { chall: chall }, - response => { finishAddChallenge(response.cid); } - ); + ajax("/challenges", "POST", { chall: chall }, response => { + finishAddChallenge(response.cid); + }); } }, clickChallenge: function(c) { - const myChallenge = (c.from.sid == this.st.user.sid //live - || (this.st.user.id > 0 && c.from.id == this.st.user.id)); //corr - if (!myChallenge) - { - if (c.type == "corr" && this.st.user.id <= 0) - return alert(this.st.tr["Please log in to accept corr challenges"]); + const myChallenge = + c.from.sid == this.st.user.sid || //live + (this.st.user.id > 0 && c.from.id == this.st.user.id); //corr + if (!myChallenge) { + if (c.type == "corr" && this.st.user.id <= 0) { + alert(this.st.tr["Please log in to accept corr challenges"]); + return; + } c.accepted = true; - if (!!c.to) //c.to == this.st.user.name (connected) - { + if (c.to) { + //c.to == this.st.user.name (connected) // TODO: if special FEN, show diagram after loading variant c.accepted = confirm("Accept challenge?"); } - if (c.accepted) - { - c.seat = { //again, avoid c.seat = st.user to not reveal email + if (c.accepted) { + c.seat = { + //again, avoid c.seat = st.user to not reveal email sid: this.st.user.sid, id: this.st.user.id, - name: this.st.user.name, + name: this.st.user.name }; this.launchGame(c); + } else { + this.send("refusechallenge", { data: c.id, target: c.from.sid }); } - else - { - this.send("refusechallenge", {data:c.id, target:c.from.sid}); + this.send("deletechallenge", { data: c.id }); + } //my challenge + else { + if (c.type == "corr") { + ajax("/challenges", "DELETE", { id: c.id }); } - this.send("deletechallenge", {data:c.id}); - } - else //my challenge - { - if (c.type == "corr") - { - ajax( - "/challenges", - "DELETE", - {id: c.id} - ); - } - this.send("deletechallenge", {data:c.id}); + this.send("deletechallenge", { data: c.id }); } // In all cases, the challenge is consumed: ArrayFun.remove(this.challenges, ch => ch.id == c.id); @@ -702,37 +685,35 @@ export default { const vModule = await import("@/variants/" + c.vname + ".js"); window.V = vModule.VariantRules; // These game informations will be shared - let gameInfo = - { + let gameInfo = { id: getRandString(), fen: c.fen || V.GenRandInitFen(), players: shuffle([c.from, c.seat]), //white then black vid: c.vid, - cadence: c.cadence, + cadence: c.cadence }; let oppsid = c.from.sid; //may not be defined if corr + offline opp - if (!oppsid) - { - oppsid = Object.keys(this.people).find(sid => - this.people[sid].id == c.from.id); + if (!oppsid) { + oppsid = Object.keys(this.people).find( + sid => this.people[sid].id == c.from.id + ); } const notifyNewgame = () => { - if (!!oppsid) //opponent is online - this.send("startgame", {data:gameInfo, target:oppsid}); + if (oppsid) + //opponent is online + this.send("startgame", { data: gameInfo, target: oppsid }); // Send game info (only if live) to everyone except me in this tab - this.send("newgame", {data:gameInfo}); + this.send("newgame", { data: gameInfo }); }; - if (c.type == "live") - { + if (c.type == "live") { notifyNewgame(); this.startNewGame(gameInfo); - } - else //corr: game only on server - { + } //corr: game only on server + else { ajax( "/games", "POST", - {gameInfo: gameInfo, cid: c.id}, //cid useful to delete challenge + { gameInfo: gameInfo, cid: c.id }, //cid useful to delete challenge response => { gameInfo.id = response.gameId; notifyNewgame(); @@ -752,14 +733,14 @@ export default { moves: [], clocks: [-1, -1], //-1 = unstarted initime: [0, 0], //initialized later - score: "*", + score: "*" }); GameStorage.add(game); if (this.st.settings.sound >= 1) - new Audio("/sounds/newgame.mp3").play().catch(err => {}); + new Audio("/sounds/newgame.mp3").play().catch(() => {}); this.$router.push("/game/" + gameInfo.id); - }, - }, + } + } }; diff --git a/client/src/views/Logout.vue b/client/src/views/Logout.vue index 89f9a399..34dbb49b 100644 --- a/client/src/views/Logout.vue +++ b/client/src/views/Logout.vue @@ -10,11 +10,11 @@ main import { store } from "@/store"; import { ajax } from "@/utils/ajax"; export default { - name: 'my-logout', + name: "my-logout", data: function() { return { st: store.state, - errmsg: "", + errmsg: "" }; }, created: function() { @@ -29,7 +29,7 @@ export default { localStorage.removeItem("myid"); localStorage.removeItem("myname"); ajax("/logout", "GET"); //TODO: listen for errors? - }, + } }; diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue index 863d17fc..01b5264d 100644 --- a/client/src/views/MyGames.vue +++ b/client/src/views/MyGames.vue @@ -19,25 +19,24 @@ import GameList from "@/components/GameList.vue"; export default { name: "my-my-games", components: { - GameList, + GameList }, data: function() { return { st: store.state, display: "live", liveGames: [], - corrGames: [], + corrGames: [] }; }, created: function() { - GameStorage.getAll((localGames) => { - localGames.forEach((g) => g.type = this.classifyObject(g)); + GameStorage.getAll(localGames => { + localGames.forEach(g => (g.type = this.classifyObject(g))); this.liveGames = localGames; }); - if (this.st.user.id > 0) - { - ajax("/games", "GET", {uid: this.st.user.id}, (res) => { - res.games.forEach((g) => g.type = this.classifyObject(g)); + if (this.st.user.id > 0) { + ajax("/games", "GET", { uid: this.st.user.id }, res => { + res.games.forEach(g => (g.type = this.classifyObject(g))); this.corrGames = res.games; }); } @@ -50,23 +49,20 @@ export default { setDisplay: function(type, e) { this.display = type; localStorage.setItem("type-myGames", type); - let elt = !!e - ? e.target - : document.getElementById(type + "Games"); + let elt = e ? e.target : document.getElementById(type + "Games"); elt.classList.add("active"); - if (!!elt.previousElementSibling) + if (elt.previousElementSibling) elt.previousElementSibling.classList.remove("active"); - else - elt.nextElementSibling.classList.remove("active"); + else elt.nextElementSibling.classList.remove("active"); }, // TODO: classifyObject is redundant (see Hall.vue) classifyObject: function(o) { - return (o.cadence.indexOf('d') === -1 ? "live" : "corr"); + return o.cadence.indexOf("d") === -1 ? "live" : "corr"; }, showGame: function(g) { this.$router.push("/game/" + g.id); - }, - }, + } + } }; diff --git a/client/src/views/News.vue b/client/src/views/News.vue index 8f03b3af..2686552c 100644 --- a/client/src/views/News.vue +++ b/client/src/views/News.vue @@ -18,7 +18,7 @@ main @click="showModalNews" ) | {{ st.tr["Write news"] }} - .news(v-for="n,idx in sortedNewsList" :class="{margintop:idx>0}") + .news(v-for="n,idx in newsList" :class="{margintop:idx>0}") span.ndt {{ formatDatetime(n.added) }} div(v-if="devs.includes(st.user.id)") button(@click="editNews(n)") {{ st.tr["Edit"] }} @@ -41,33 +41,31 @@ export default { st: store.state, cursor: 0, //ID of last showed news hasMore: true, //a priori there could be more news to load - curnews: {id:0, content:""}, + curnews: { id: 0, content: "" }, newsList: [], - infoMsg: "", + infoMsg: "" }; }, created: function() { - ajax("/news", "GET", {cursor:this.cursor}, (res) => { - this.newsList = res.newsList; + ajax("/news", "GET", { cursor: this.cursor }, res => { + this.newsList = res.newsList.sort((n1, n2) => n1.added - n2.added); const L = res.newsList.length; - if (L > 0) - this.cursor = res.newsList[L-1].id; + if (L > 0) this.cursor = this.newsList[0].id; }); }, mounted: function() { - document.getElementById("newnewsDiv").addEventListener("click", processModalClick); - }, - computed: { - sortedNewsList: function() { - return this.newsList.sort( (n1,n2) => n1.added - n2.added ); - }, + document + .getElementById("newnewsDiv") + .addEventListener("click", processModalClick); }, methods: { formatDatetime: function(dt) { const dtObj = new Date(dt); const timePart = getTime(dtObj); // Show minutes but not seconds: - return getDate(dtObj) + " " + timePart.substr(0,timePart.lastIndexOf(":")); + return ( + getDate(dtObj) + " " + timePart.substr(0, timePart.lastIndexOf(":")) + ); }, parseHtml: function(txt) { return !txt.match(/<[/a-zA-Z]+>/) @@ -78,7 +76,7 @@ export default { const newsContent = document.getElementById("newsContent"); // https://stackoverflow.com/questions/995168/textarea-to-resize-based-on-content-length newsContent.style.height = "1px"; - newsContent.style.height = (10+newsContent.scrollHeight)+"px"; + newsContent.style.height = 10 + newsContent.scrollHeight + "px"; }, resetCurnews: function() { this.curnews.id = 0; @@ -87,49 +85,39 @@ export default { }, showModalNews: function() { this.resetCurnews(); - doClick('modalNews'); + window.doClick("modalNews"); }, sendNews: function() { const edit = this.curnews.id > 0; this.infoMsg = "Processing... Please wait"; - ajax( - "/news", - edit ? "PUT" : "POST", - {news: this.curnews}, - (res) => { - if (edit) - { - let n = this.newsList.find(n => n.id == this.curnews.id); - if (!!n) - n.content = this.curnews.content; - } - else - { - const newNews = { - content:this.curnews.content, - added:Date.now(), - uid: this.st.user.id, - id: res.id - }; - this.newsList = this.newsList.concat([newNews]); - } - document.getElementById("modalNews").checked = false; - this.infoMsg = ""; - this.resetCurnews(); + ajax("/news", edit ? "PUT" : "POST", { news: this.curnews }, res => { + if (edit) { + let n = this.newsList.find(n => n.id == this.curnews.id); + if (n) n.content = this.curnews.content; + } else { + const newNews = { + content: this.curnews.content, + added: Date.now(), + uid: this.st.user.id, + id: res.id + }; + this.newsList = [newNews].concat(this.newsList); } - ); + document.getElementById("modalNews").checked = false; + this.infoMsg = ""; + this.resetCurnews(); + }); }, editNews: function(n) { this.curnews.content = n.content; this.curnews.id = n.id; // No need for added and uid fields: never updated - doClick('modalNews'); + window.doClick("modalNews"); }, deleteNews: function(n) { - if (confirm(this.st.tr["Are you sure?"])) - { + if (confirm(this.st.tr["Are you sure?"])) { this.infoMsg = "Processing... Please wait"; - ajax("/news", "DELETE", {id:n.id}, () => { + ajax("/news", "DELETE", { id: n.id }, () => { const nIdx = this.newsList.findIndex(nw => nw.id == n.id); this.newsList.splice(nIdx, 1); this.infoMsg = ""; @@ -138,19 +126,15 @@ export default { } }, loadMore: function() { - ajax("/news", "GET", {cursor:this.cursor}, (res) => { - if (res.newsList.length > 0) - { + ajax("/news", "GET", { cursor: this.cursor }, res => { + if (res.newsList.length > 0) { this.newsList = this.newsList.concat(res.newsList); const L = res.newsList.length; - if (L > 0) - this.cursor = res.newsList[L-1].id; - } - else - this.hasMore = false; + if (L > 0) this.cursor = res.newsList[L - 1].id; + } else this.hasMore = false; }); - }, - }, + } + } }; diff --git a/client/src/views/Problems.vue b/client/src/views/Problems.vue index 9fa2d796..a9f9fcb6 100644 --- a/client/src/views/Problems.vue +++ b/client/src/views/Problems.vue @@ -66,7 +66,7 @@ main .row(v-else) .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 #controls - button#newProblem(onClick="doClick('modalNewprob')") + button#newProblem(onClick="window.doClick('modalNewprob')") | {{ st.tr["New problem"] }} label(for="checkboxMine") {{ st.tr["My problems"] }} input#checkboxMine( @@ -86,7 +86,7 @@ main th {{ st.tr["Instructions"] }} th {{ st.tr["Number"] }} tr( - v-for="p in sortedProblems" + v-for="p in problems" v-show="displayProblem(p)" @click="setHrefPid(p)" ) @@ -107,14 +107,14 @@ import BaseGame from "@/components/BaseGame.vue"; export default { name: "my-problems", components: { - BaseGame, + BaseGame }, data: function() { return { st: store.state, emptyVar: { vid: 0, - vname: "", + vname: "" }, // Problem currently showed, or edited: curproblem: { @@ -124,7 +124,7 @@ export default { diag: "", instruction: "", solution: "", - showSolution: false, + showSolution: false }, loadedVar: 0, //corresponding to loaded V selectedVar: 0, //to filter problems based on variant @@ -134,68 +134,56 @@ export default { infoMsg: "", vr: null, //"variant rules" object initialized from FEN game: { - players:[{name:"Problem"},{name:"Problem"}], - mode: "analyze", - }, + players: [{ name: "Problem" }, { name: "Problem" }], + mode: "analyze" + } }; }, created: function() { - ajax("/problems", "GET", (res) => { - this.problems = res.problems; + ajax("/problems", "GET", res => { + // Show newest problem first: + this.problems = res.problems.sort((p1, p2) => p2.added - p1.added); if (this.st.variants.length > 0) this.problems.forEach(p => this.setVname(p)); // Retrieve all problems' authors' names let names = {}; this.problems.forEach(p => { - if (p.uid != this.st.user.id) - names[p.uid] = ""; //unknwon for now - else - p.uname = this.st.user.name; + if (p.uid != this.st.user.id) names[p.uid] = ""; + //unknwon for now + else p.uname = this.st.user.name; }); const showOneIfPid = () => { const pid = this.$route.query["id"]; - if (!!pid) - this.showProblem(this.problems.find(p => p.id == pid)); + if (pid) this.showProblem(this.problems.find(p => p.id == pid)); }; - if (Object.keys(names).length > 0) - { - ajax("/users", - "GET", - { ids: Object.keys(names).join(",") }, - res2 => { - res2.users.forEach(u => {names[u.id] = u.name}); - this.problems.forEach(p => p.uname = names[p.uid]); - showOneIfPid(); - } - ); - } - else - showOneIfPid(); + if (Object.keys(names).length > 0) { + ajax("/users", "GET", { ids: Object.keys(names).join(",") }, res2 => { + res2.users.forEach(u => { + names[u.id] = u.name; + }); + this.problems.forEach(p => (p.uname = names[p.uid])); + showOneIfPid(); + }); + } else showOneIfPid(); }); }, mounted: function() { - document.getElementById("newprobDiv").addEventListener("click", processModalClick); + document + .getElementById("newprobDiv") + .addEventListener("click", processModalClick); }, watch: { // st.variants changes only once, at loading from [] to [...] - "st.variants": function(variantArray) { + "st.variants": function() { // Set problems vname (either all are set or none) if (this.problems.length > 0 && this.problems[0].vname == "") this.problems.forEach(p => this.setVname(p)); }, - "$route": function(to, from) { + $route: function(to) { const pid = to.query["id"]; - if (!!pid) - this.showProblem(this.problems.find(p => p.id == pid)); - else - this.showOne = false - }, - }, - computed: { - sortedProblems: function() { - // Newest first: - return this.problems.sort( (p1,p2) => p2.added - p1.added); - }, + if (pid) this.showProblem(this.problems.find(p => p.id == pid)); + else this.showOne = false; + } }, methods: { setVname: function(prob) { @@ -204,19 +192,18 @@ export default { firstChars: function(text) { let preparedText = text // Replace line jumps and
by spaces - .replace(/\n/g, " " ) - .replace(//g, " " ) + .replace(/\n/g, " ") + .replace(//g, " ") .replace(/<[^>]+>/g, "") //remove remaining HTML tags .replace(/[ ]+/g, " ") //remove series of spaces by only one .trim(); const maxLength = 32; //arbitrary... if (preparedText.length > maxLength) - return preparedText.substr(0,32) + "..."; + return preparedText.substr(0, 32) + "..."; return preparedText; }, copyProblem: function(p1, p2) { - for (let key in p1) - p2[key] = p1[key]; + for (let key in p1) p2[key] = p1[key]; }, setHrefPid: function(p) { // Change href => $route changes, watcher notices, call showProblem @@ -245,14 +232,10 @@ export default { }, changeVariant: function(prob) { this.setVname(prob); - this.loadVariant( - prob.vid, - () => { - // Set FEN if possible (might not be correct yet) - if (V.IsGoodFen(prob.fen)) - this.setDiagram(prob); - } - ); + this.loadVariant(prob.vid, () => { + // Set FEN if possible (might not be correct yet) + if (V.IsGoodFen(prob.fen)) this.setDiagram(prob); + }); }, loadVariant: async function(vid, cb) { // Condition: vid is a valid variant ID @@ -274,48 +257,47 @@ export default { const parsedFen = V.ParseFen(prob.fen); const args = { position: parsedFen.position, - orientation: parsedFen.turn, + orientation: parsedFen.turn }; prob.diag = getDiagram(args); }, displayProblem: function(p) { - return ((this.selectedVar == 0 || p.vid == this.selectedVar) && - ((this.onlyMines && p.uid == this.st.user.id) - || (!this.onlyMines && p.uid != this.st.user.id))); + return ( + (this.selectedVar == 0 || p.vid == this.selectedVar) && + ((this.onlyMines && p.uid == this.st.user.id) || + (!this.onlyMines && p.uid != this.st.user.id)) + ); }, showProblem: function(p) { - this.loadVariant( - p.vid, - () => { - // The FEN is already checked at this stage: - this.vr = new V(p.fen); - this.game.vname = p.vname; - this.game.mycolor = this.vr.turn; //diagram orientation - this.game.fen = p.fen; - this.$set(this.game, "fenStart", p.fen); - this.copyProblem(p, this.curproblem); - this.showOne = true; - } - ); + this.loadVariant(p.vid, () => { + // The FEN is already checked at this stage: + this.vr = new V(p.fen); + this.game.vname = p.vname; + this.game.mycolor = this.vr.turn; //diagram orientation + this.game.fen = p.fen; + this.$set(this.game, "fenStart", p.fen); + this.copyProblem(p, this.curproblem); + this.showOne = true; + }); }, sendProblem: function() { const error = checkProblem(this.curproblem); - if (!!error) - return alert(error); + if (error) { + alert(error); + return; + } const edit = this.curproblem.id > 0; this.infoMsg = "Processing... Please wait"; ajax( "/problems", edit ? "PUT" : "POST", - {prob: this.curproblem}, - (ret) => { - if (edit) - { + { prob: this.curproblem }, + ret => { + if (edit) { let editedP = this.problems.find(p => p.id == this.curproblem.id); this.copyProblem(this.curproblem, editedP); - } - else //new problem - { + } //new problem + else { let newProblem = Object.assign({}, this.curproblem); newProblem.id = ret.id; newProblem.uid = this.st.user.id; @@ -328,21 +310,19 @@ export default { ); }, editProblem: function(prob) { - if (!prob.diag) - this.setDiagram(prob); //possible because V is loaded at this stage + if (!prob.diag) this.setDiagram(prob); //possible because V is loaded at this stage this.copyProblem(prob, this.curproblem); - doClick('modalNewprob'); + window.doClick("modalNewprob"); }, deleteProblem: function(prob) { - if (confirm(this.st.tr["Are you sure?"])) - { - ajax("/problems", "DELETE", {id:prob.id}, () => { + if (confirm(this.st.tr["Are you sure?"])) { + ajax("/problems", "DELETE", { id: prob.id }, () => { ArrayFun.remove(this.problems, p => p.id == prob.id); this.backToList(); }); } - }, - }, + } + } }; @@ -379,5 +359,4 @@ textarea @media screen and (max-width: 767px) #topPage text-align: center - diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue index d06ae269..4daebf9d 100644 --- a/client/src/views/Rules.vue +++ b/client/src/views/Rules.vue @@ -23,9 +23,9 @@ import ComputerGame from "@/components/ComputerGame.vue"; import { store } from "@/store"; import { getDiagram } from "@/utils/printDiagram"; export default { - name: 'my-rules', + name: "my-rules", components: { - ComputerGame, + ComputerGame }, data: function() { return { @@ -37,14 +37,14 @@ export default { vname: "", mode: "versus", fen: "", - score: "*", + score: "*" } }; }, watch: { - "$route": function(newRoute) { + $route: function(newRoute) { this.re_setVariant(newRoute.params["vname"]); - }, + } }, created: function() { // NOTE: variant cannot be set before store is initialized @@ -52,23 +52,27 @@ export default { }, computed: { content: function() { - if (!this.gameInfo.vname) - return ""; //variant not set yet + if (!this.gameInfo.vname) return ""; //variant not set yet // (AJAX) Request to get rules content (plain text, HTML) - return require("raw-loader!@/translations/rules/" + - this.gameInfo.vname + "/" + this.st.lang + ".pug") - // Next two lines fix a weird issue after last update (2019-11) - .replace(/\\n/g, " ").replace(/\\"/g, '"') - .replace('module.exports = "', '').replace(/"$/, "") - .replace(/(fen:)([^:]*):/g, this.replaceByDiag); - }, + return ( + require("raw-loader!@/translations/rules/" + + this.gameInfo.vname + + "/" + + this.st.lang + + ".pug") + // Next two lines fix a weird issue after last update (2019-11) + .replace(/\\n/g, " ") + .replace(/\\"/g, '"') + .replace('module.exports = "', "") + .replace(/"$/, "") + .replace(/(fen:)([^:]*):/g, this.replaceByDiag) + ); + } }, methods: { clickReadRules: function() { - if (this.display != "rules") - this.display = "rules"; - else if (this.gameInProgress) - this.display = "computer"; + if (this.display != "rules") this.display = "rules"; + else if (this.gameInProgress) this.display = "computer"; }, parseFen(fen) { const fenParts = fen.split(" "); @@ -76,7 +80,7 @@ export default { position: fenParts[0], marks: fenParts[1], orientation: fenParts[2], - shadow: fenParts[3], + shadow: fenParts[3] }; }, // Method to replace diagrams in loaded HTML @@ -90,8 +94,7 @@ export default { this.gameInfo.vname = vname; }, startGame: function(mode) { - if (this.gameInProgress) - return; + if (this.gameInProgress) return; this.gameInProgress = true; this.display = "computer"; this.gameInfo.mode = mode; @@ -107,10 +110,11 @@ export default { this.gameInProgress = false; }, gotoAnalyze: function() { - this.$router.push("/analyse/" + this.gameInfo.vname - + "/?fen=" + V.GenRandInitFen()); - }, - }, + this.$router.push( + "/analyse/" + this.gameInfo.vname + "/?fen=" + V.GenRandInitFen() + ); + } + } }; diff --git a/client/src/views/Variants.vue b/client/src/views/Variants.vue index 7f78f040..31061158 100644 --- a/client/src/views/Variants.vue +++ b/client/src/views/Variants.vue @@ -19,33 +19,35 @@ export default { data: function() { return { curPrefix: "", - st: store.state, + st: store.state }; }, computed: { - filteredVariants: function () { - const capitalizedPrefix = this.curPrefix.replace(/^\w/, c => c.toUpperCase()); + filteredVariants: function() { + const capitalizedPrefix = this.curPrefix.replace(/^\w/, c => + c.toUpperCase() + ); const variants = this.st.variants - .filter( v => { - return v.name.startsWith(capitalizedPrefix); - }) - .map( v => { - return { - name: v.name, - desc: v.description, - }; - }) - .sort((a,b) => { - return a.name.localeCompare(b.name); - }); + .filter(v => { + return v.name.startsWith(capitalizedPrefix); + }) + .map(v => { + return { + name: v.name, + desc: v.description + }; + }) + .sort((a, b) => { + return a.name.localeCompare(b.name); + }); return variants; - }, + } }, methods: { getLink: function(vname) { return "/variants/" + vname; - }, - }, + } + } }; diff --git a/client/vue.config.js b/client/vue.config.js index a98dd2dc..63b0e4bc 100644 --- a/client/vue.config.js +++ b/client/vue.config.js @@ -4,7 +4,7 @@ module.exports = { output: { // Fix "window is not defined" issues with web worker. // https://github.com/webpack/webpack/issues/6642 - globalObject: 'this', - }, - }, + globalObject: "this" + } + } }; -- 2.44.0