From ad030c7d24804fbfa06158e93d89a3f101d2c8b3 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Fri, 24 Apr 2020 20:55:30 +0200 Subject: [PATCH] Simplify Game logic a bit + some advances on Chakart --- TODO | 3 +- client/src/base_rules.js | 5 + client/src/components/BaseGame.vue | 2 +- client/src/translations/faq/en.pug | 2 +- client/src/variants/Chakart.js | 94 +++++++++++++--- client/src/variants/Football.js | 2 + client/src/variants/Monochrome.js | 4 + client/src/variants/Teleport.js | 23 +++- client/src/views/Game.vue | 173 +++++++++++------------------ 9 files changed, 182 insertions(+), 126 deletions(-) diff --git a/TODO b/TODO index ec7d9756..b2fd2150 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,7 @@ Issue: embedded rules language not updated when language is set (in Analyse, Game and Problems) Also: if new live game starts in background, "new game" notify OK but not first move (not too serious however) -On smartphone for Teleport, Chakart, Weiqi and some others: wait re-click to confirm "touch" move (in Board.vue) +On smartphone for Teleport, Chakart, Weiqi and some others: option "confirm moves on touch screen" +(=> comme pour corr) + option "confirm moves in corr games"? https://www.chessvariants.com/crossover.dir/koopachess.html --> Can a stunned piece capture? Maybe not. ...recover? After 5 moves? Never? diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 1795b5fa..cfb8f841 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -80,6 +80,11 @@ export const ChessRules = class ChessRules { return V.ShowMoves; } + // Generally true, unless the variant includes random effects + static get CorrConfirm() { + return true; + } + // Used for Monochrome variant (TODO: harmonize: !canFlip ==> showFirstTurn) get showFirstTurn() { return false; diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index 70a304b8..f4760649 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -461,7 +461,7 @@ export default { // To play a received move, cursor must be at the end of the game: this.gotoEnd(); } - // The board may show some the possible moves: (TODO: bad solution) + // The board may show some possible moves: (TODO: bad solution) this.$refs["board"].resetCurrentAttempt(); const playSubmove = (smove) => { smove.notation = this.vr.getNotation(smove); diff --git a/client/src/translations/faq/en.pug b/client/src/translations/faq/en.pug index 0d5ae9ed..2f744627 100644 --- a/client/src/translations/faq/en.pug +++ b/client/src/translations/faq/en.pug @@ -122,7 +122,7 @@ | long live games, like maybe 1h30 + 30s. .question. - I bookmarked a fascinating observed game, but the URL no longer works. + I bookmarked a fascinating game, but the URL no longer works. .answer | Live games are stored locally, on browsers only. | If you are only an observer, your browser doesn't even store it, so later diff --git a/client/src/variants/Chakart.js b/client/src/variants/Chakart.js index 5d79f4d0..12a84bca 100644 --- a/client/src/variants/Chakart.js +++ b/client/src/variants/Chakart.js @@ -1,23 +1,38 @@ import { ChessRules } from "@/base_rules"; export class ChakartRules extends ChessRules { - // NOTE: getBasicMove, ajouter les bonus à vanish array - // + déterminer leur effet (si cavalier) ou case (si banane ou bombe) - // (L'effet doit être caché au joueur : devrait être OK) - // - // Saut possible par dessus bonus ou champis mais pas bananes ou bombes -//==> redefinir isAttackedBySlide et getPotentialSlide... - -// keep track of captured pieces: comme Grand; pieces can get back to board with toadette bonus. -// --> pour ce bonus, passer "capture" temporairement en "reserve" pour permettre de jouer le coup. + static get CorrConfirm() { + // Because of bonus effects + return false; + } - // "pièces" supplémentaires : bananes, bombes, champis, bonus --> + couleur ? - // (Semble mieux sans couleur => couleur spéciale indiquant que c'est pas jouable) - // (Attention: pas jouables cf. getPotentialMoves...) + static get CanAnalyze() { + return false; + } hoverHighlight(x, y) { - // TODO: exact squares - return this.subTurn == 2; //&& this.firstMove.donkey or wario or bonus roi boo + if ( + this.firstMove.appear.length == 0 || + this.firstMove.vanish.length == 0 || + this.board[x][y] != V.EMPTY + ) { + return false; + } + const deltaX = Math.abs(this.firstMove.end.x - x); + const deltaY = Math.abs(this.firstMove.end.y - y); + return ( + this.subTurn == 2 && + // Condition: rook or bishop move, may capture, but no bonus move + [V.ROOK, V.BISHOP].includes(this.firstMove.vanish[0].p) && + ( + this.firstMove.vanish.length == 1 || + ['w', 'b'].includes(this.firstMove.vanish[1].c) + ) && + ( + this.firstMove.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1 || + this.firstMove.vanish[0].p == V.BISHOP && deltaX + deltaY == 1 + ) + ); } static get IMMOBILIZE_CODE() { @@ -46,9 +61,33 @@ export class ChakartRules extends ChessRules { return 'i'; } + // Fictive color 'a', bomb banana mushroom egg + static get BOMB() { + // Doesn't collide with bishop because color 'a' + return 'b'; + } + static get BANANA() { + return 'n'; + } + static get EGG() { + return 'e'; + } + static get MUSHROOM() { + return 'm'; + } + + static get PIECES() { + return ( + ChessRules.PIECES.concat( + Object.keys(V.IMMOBILIZE_DECODE)).concat( + [V.BANANA, V.BOMB, V.EGG, V.MUSHROOM, V.INVISIBLE_QUEEN]) + ); + } + getPpath(b) { let prefix = ""; if ( + b[0] == 'a' || b[1] == V.INVISIBLE_QUEEN || Object.keys(V.IMMOBILIZE_DECODE).includes(b[1]) ) { @@ -97,7 +136,7 @@ export class ChakartRules extends ChessRules { setFlags(fenflags) { super.setFlags(fenflags); //castleFlags this.powerFlags = { - w: [...Array(2)], //king can send red shell? Queen can be invisible? + w: [...Array(2)], //king can send shell? Queen can be invisible? b: [...Array(2)] }; const flags = fenflags.substr(4); //skip first 4 letters, for castle @@ -166,7 +205,13 @@ export class ChakartRules extends ChessRules { return fen; } + addBonusYoshi() { + // TODO +// --> pour bonus toadette, passer "capture" temporairement en "reserve" pour permettre de jouer le coup. + } + getPotentialMovesFrom([x, y]) { + // TODO: si banane ou bombe ou... alors return [] ? // TODO: bananes et bombes limitent les déplacements (agissent comme un mur "capturable") // bananes jaunes et rouges ?! (agissant sur une seule couleur ?) --> mauvaise idée. if (this.subTurn == 2) { @@ -177,6 +222,17 @@ export class ChakartRules extends ChessRules { // TODO: un-immobilize my immobilized piece at the end of this turn, if any } + getBasicMove([x1, y1], [x2, y2]) { + // NOTE: getBasicMove, ajouter les bonus à vanish array + // + déterminer leur effet (si cavalier) ou case (si banane ou bombe) + // (L'effet doit être caché au joueur : devrait être OK) + } + + getSlideNJumpMpves(sq, steps, oneStep) { + // Saut possible par dessus bonus ou champis mais pas bananes ou bombes +//==> redefinir isAttackedBySlide et getPotentialSlide... + } + getPotentialPawnMoves(sq) { //Toad: pion // laisse sur sa case de départ un champi turbo permettant à Peach et cavalier et autres pions d'aller @@ -230,11 +286,19 @@ export class ChakartRules extends ChessRules { // Profite des accélérateurs posés par les pions (+ 1 case : obligatoire). } + isAttackedBySlideNJump() { + // TODO: + } + atLeastOneMove() { // TODO: check that return true; } + getAllPotentialMoves() { + // (Attention: objets pas jouables cf. getPotentialMoves...) + } + play(move) { // TODO: subTurn passe à 2 si arrivée sur bonus cavalier // potentiellement pose (tour, fou) ou si choix (reconnaître i (ok), ii (ok) et iii (si coup normal + pas immobilisé) ?) diff --git a/client/src/variants/Football.js b/client/src/variants/Football.js index b0678e16..e7e65138 100644 --- a/client/src/variants/Football.js +++ b/client/src/variants/Football.js @@ -1,4 +1,6 @@ import { ChessRules } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { shuffle } from "@/utils/alea"; export class FootballRules extends ChessRules { static get HasFlags() { diff --git a/client/src/variants/Monochrome.js b/client/src/variants/Monochrome.js index 5bd2ce00..b17b6f1f 100644 --- a/client/src/variants/Monochrome.js +++ b/client/src/variants/Monochrome.js @@ -6,6 +6,10 @@ export class MonochromeRules extends ChessRules { return false; } + static get Lines() { + return [ [[4, 0], [4, 8]] ]; + } + get showFirstTurn() { return true; } diff --git a/client/src/variants/Teleport.js b/client/src/variants/Teleport.js index 93648c48..f5422c73 100644 --- a/client/src/variants/Teleport.js +++ b/client/src/variants/Teleport.js @@ -3,8 +3,27 @@ import { randInt } from "@/utils/alea"; export class TeleportRules extends ChessRules { hoverHighlight(x, y) { - // TODO: only highlight if the move is legal - return (this.subTurn == 2 && this.board[x][y] == V.EMPTY); + if (this.subTurn == 1 || this.board[x][y] != V.EMPTY) + return false; + // Only highlight if the move is legal + const color = this.turn; + const tMove = new Move({ + appear: [ + new PiPo({ + x: x, + y: y, + c: color, + // The dropped piece nature has no importance: + p: V.KNIGHT + }) + ], + vanish: [], + start: { x: -1, y: -1 } + }); + this.play(tMove); + const moveOk = !this.underCheck(color); + this.undo(tMove); + return moveOk; } setOtherVariables(fen) { diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 2beb10cb..0735b76b 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -212,9 +212,6 @@ export default { curDiag: "", //for corr moves confirmation conn: null, roomInitialized: false, - // If newmove has wrong index: ask fullgame again: - askGameTime: 0, - gameIsLoading: false, // If asklastate got no reply, ask again: gotLastate: false, gotMoveIdx: -1, //last move index received @@ -357,8 +354,6 @@ export default { this.rematchOffer = ""; this.lastate = undefined; this.roomInitialized = false; - this.askGameTime = 0; - this.gameIsLoading = false; this.gotLastate = false; this.gotMoveIdx = -1; this.opponentGotMove = false; @@ -536,27 +531,6 @@ export default { this.$router.push( "/game/" + nextGid + "/?next=" + JSON.stringify(this.nextIds)); }, - askGameAgain: function() { - this.gameIsLoading = true; - const currentUrl = document.location.href; - const doAskGame = () => { - if (document.location.href != currentUrl) return; //page change - this.fetchGame((game) => { - if (!!game) - // This is my game: just reload. - this.loadGame(game); - else - // Just ask fullgame again (once!), this is much simpler. - // If this fails, the user could just reload page :/ - this.send("askfullgame"); - }); - }; - // Delay of at least 2s between two game requests - const now = Date.now(); - const delay = Math.max(2000 - (now - this.askGameTime), 0); - this.askGameTime = now; - setTimeout(doAskGame, delay); - }, socketMessageListener: function(msg) { if (!this.conn) return; const data = JSON.parse(msg.data); @@ -770,76 +744,69 @@ console.log(data.data); const movePlus = data.data; const movesCount = this.game.moves.length; - if (movePlus.index > movesCount) { - // This can only happen if I'm an observer and missed a move. - if (this.gotMoveIdx < movePlus.index) - this.gotMoveIdx = movePlus.index; - if (!this.gameIsLoading) this.askGameAgain(); + if ( + movePlus.index < movesCount || + this.gotMoveIdx >= movePlus.index + ) { + // Opponent re-send but we already have the move: + // (maybe he didn't receive our pingback...) + this.send("gotmove", {data: movePlus.index, target: data.from}); } else { - if ( - movePlus.index < movesCount || - this.gotMoveIdx >= movePlus.index - ) { - // Opponent re-send but we already have the move: - // (maybe he didn't receive our pingback...) - this.send("gotmove", {data: movePlus.index, target: data.from}); - } else { - this.gotMoveIdx = movePlus.index; - const receiveMyMove = (movePlus.color == this.game.mycolor); - const moveColIdx = ["w", "b"].indexOf(movePlus.color); - if (!receiveMyMove && !!this.game.mycolor) { - // Notify opponent that I got the move: - this.send( - "gotmove", - { data: movePlus.index, target: data.from } + this.gotMoveIdx = movePlus.index; + const receiveMyMove = (movePlus.color == this.game.mycolor); + const moveColIdx = ["w", "b"].indexOf(movePlus.color); + if (!receiveMyMove && !!this.game.mycolor) { + // Notify opponent that I got the move: + this.send( + "gotmove", + { data: movePlus.index, target: data.from } + ); + // And myself if I'm elsewhere: + if (!this.focus) { + notify( + "New move", + { + body: + (this.game.players[moveColIdx].name || "@nonymous") + + " just played." + } ); - // And myself if I'm elsewhere: - if (!this.focus) { - notify( - "New move", - { - body: - (this.game.players[moveColIdx].name || "@nonymous") + - " just played." - } - ); - } } - if (movePlus.cancelDrawOffer) { - // Opponent refuses draw - this.drawOffer = ""; - // NOTE for corr games: drawOffer reset by player in turn - if ( - this.game.type == "live" && - !!this.game.mycolor && - !receiveMyMove - ) { - GameStorage.update(this.gameRef, { drawOffer: "" }); - } + } + if (movePlus.cancelDrawOffer) { + // Opponent refuses draw + this.drawOffer = ""; + // NOTE for corr games: drawOffer reset by player in turn + if ( + this.game.type == "live" && + !!this.game.mycolor && + !receiveMyMove + ) { + GameStorage.update(this.gameRef, { drawOffer: "" }); } - this.$refs["basegame"].play(movePlus.move, "received"); - // Freeze time while the move is being play - // (TODO: a callback would be cleaner here) - clearInterval(this.clockUpdate); - this.clockUpdate = null; - const freezeDuration = ["all", "highlight"].includes(V.ShowMoves) - // 250 = length of animation, 500 = delay between sub-moves - ? 250 + 750 * - (Array.isArray(movePlus.move) ? movePlus.move.length - 1 : 0) - // Incomplete information: no move animation - : 0; - setTimeout( - () => { - this.game.clocks[moveColIdx] = movePlus.clock; - this.processMove( - movePlus.move, - { receiveMyMove: receiveMyMove } - ); - }, - freezeDuration - ); } + this.$refs["basegame"].play(movePlus.move, "received"); + // Freeze time while the move is being play + // (TODO: a callback would be cleaner here) + clearInterval(this.clockUpdate); + this.clockUpdate = null; + const freezeDuration = ["all", "highlight"].includes(V.ShowMoves) + // 250 = length of animation, 500 = delay between sub-moves + ? 250 + 750 * + (Array.isArray(movePlus.move) ? movePlus.move.length - 1 : 0) + // Incomplete information: no move animation + : 0; + setTimeout( + () => { + this.game.clocks[moveColIdx] = movePlus.clock; + this.processMove( + movePlus.move, + { receiveMyMove: receiveMyMove } + ); + }, + freezeDuration + ); } break; } @@ -1261,14 +1228,12 @@ console.log(data.data); game ); this.$refs["basegame"].re_setVariables(this.game); - if (!this.gameIsLoading) { - // Initial loading: - this.gotMoveIdx = game.moves.length - 1; - // If we arrive here after 'nextGame' action, the board might be hidden - let boardDiv = document.querySelector(".game"); - if (!!boardDiv && boardDiv.style.visibility == "hidden") - boardDiv.style.visibility = "visible"; - } + // Initial loading: + this.gotMoveIdx = game.moves.length - 1; + // If we arrive here after 'nextGame' action, the board might be hidden + let boardDiv = document.querySelector(".game"); + if (!!boardDiv && boardDiv.style.visibility == "hidden") + boardDiv.style.visibility = "visible"; this.re_setClocks(); this.$nextTick(() => { this.game.rendered = true; @@ -1279,12 +1244,6 @@ console.log(data.data); this.lastateAsked = false; this.sendLastate(game.oppsid); } - if (this.gameIsLoading) { - this.gameIsLoading = false; - if (this.gotMoveIdx >= game.moves.length) - // Some moves arrived meanwhile... - this.askGameAgain(); - } if (!!callback) callback(); }, loadVariantThenGame: async function(game, callback) { @@ -1512,7 +1471,7 @@ console.log(data.data); this.opponentGotMove = false; this.send("newmove", {data: sendMove}); // If the opponent doesn't reply gotmove soon enough, re-send move: - // Do this at most 2 times, because mpore would mean network issues, + // Do this at most 2 times, because more would mean network issues, // opponent would then be expected to disconnect/reconnect. let counter = 1; const currentUrl = document.location.href; @@ -1544,6 +1503,7 @@ console.log(data.data); }; if ( this.game.type == "corr" && + V.CorrConfirm && moveCol == this.game.mycolor && !data.receiveMyMove ) { @@ -1587,7 +1547,8 @@ console.log(data.data); }); document.querySelector("#confirmDiv > .card").style.width = boardDiv.offsetWidth + "px"; - } else { + } + else { // Incomplete information: just ask confirmation // Hide the board, because otherwise it could reveal infos boardDiv.style.visibility = "hidden"; -- 2.44.0