From 616561273f216debfeab7f5fc532d0b0a8bc8e2d Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sat, 28 Mar 2020 19:49:09 +0100 Subject: [PATCH] Some fixes + work on Dynamo draft. Also listen for clicks in Board.vue --- client/src/base_rules.js | 11 +- client/src/components/BaseGame.vue | 6 + client/src/components/Board.vue | 10 +- client/src/translations/about/en.pug | 1 + client/src/translations/about/es.pug | 1 + client/src/translations/about/fr.pug | 1 + client/src/translations/rules/Ball/en.pug | 6 +- client/src/translations/rules/Ball/es.pug | 6 +- client/src/translations/rules/Ball/fr.pug | 6 +- client/src/variants/Ball.js | 4 +- client/src/variants/Checkered.js | 2 +- client/src/variants/Dynamo.js | 299 ++++++++++++++++++---- client/src/variants/Eightpieces.js | 8 +- client/src/variants/Suction.js | 6 +- 14 files changed, 294 insertions(+), 73 deletions(-) diff --git a/client/src/base_rules.js b/client/src/base_rules.js index d1348e71..d35a9186 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -86,6 +86,11 @@ export const ChessRules = class ChessRules { return V.CanFlip; } + // Some variants use click infos: + doClick() { + return null; + } + static get IMAGE_EXTENSION() { // All pieces should be in the SVG format return ".svg"; @@ -846,12 +851,12 @@ export const ChessRules = class ChessRules { // NOTE: in some variants this is not a rook const rookPos = this.castleFlags[c][castleSide]; - const castlingPiece = this.getPiece(x, rookPos); - if (this.getColor(x, rookPos) != c) - // Rook is here but changed color (see Benedict) + if (this.board[x][rookPos] == V.EMPTY || this.getColor(x, rookPos) != c) + // Rook is not here, or changed color (see Benedict) continue; // Nothing on the path of the king ? (and no checks) + const castlingPiece = this.getPiece(x, rookPos); const finDist = finalSquares[castleSide][0] - y; let step = finDist / Math.max(1, Math.abs(finDist)); i = y; diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index 0ed908a5..51d087a3 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -21,6 +21,7 @@ div#baseGame :vname="game.vname" :incheck="incheck" @play-move="play" + @click-square="clickSquare" ) #turnIndicator(v-if="showTurn") {{ turn }} #controls.button-group @@ -364,6 +365,11 @@ export default { ); } }, + clickSquare: function(square) { + // Some variants make use of a single click at specific times: + const move = this.vr.doClick(square); + if (!!move) this.play(move); + }, // "light": if gotoMove() or gotoEnd() play: function(move, received, light, noemit) { // Freeze while choices are shown: diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index 71cac153..80a79a4a 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -388,9 +388,15 @@ export default { mousedown: function(e) { e.preventDefault(); if (!this.start) { - // Start square must contain a piece. // NOTE: classList[0] is enough: 'piece' is the first assigned class - if (e.target.classList[0] != "piece") return; + const withPiece = e.target.classList[0] == "piece"; + // Emit the click event which could be used by some variants + this.$emit( + "click-square", + getSquareFromId(withPiece ? e.target.parentNode.id : e.target.id) + ); + // Start square must contain a piece. + if (!withPiece) return; let parent = e.target.parentNode; //surrounding square // Show possible moves if current player allowed to play const startSquare = getSquareFromId(parent.id); diff --git a/client/src/translations/about/en.pug b/client/src/translations/about/en.pug index 6e888b9b..6d5472c2 100644 --- a/client/src/translations/about/en.pug +++ b/client/src/translations/about/en.pug @@ -52,3 +52,4 @@ h3 Related links span  (in French) a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net a(href="https://brainking.com/") brainking.com + a(href="https://www.facebook.com/groups/592562551198628") A Facebook group diff --git a/client/src/translations/about/es.pug b/client/src/translations/about/es.pug index 0986e016..c9036c80 100644 --- a/client/src/translations/about/es.pug +++ b/client/src/translations/about/es.pug @@ -49,3 +49,4 @@ h3 Enlaces relacionados span  (en francés) a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net a(href="https://brainking.com/") brainking.com + a(href="https://www.facebook.com/groups/592562551198628") Un grupo Facebook diff --git a/client/src/translations/about/fr.pug b/client/src/translations/about/fr.pug index 849aad13..af12edaa 100644 --- a/client/src/translations/about/fr.pug +++ b/client/src/translations/about/fr.pug @@ -48,3 +48,4 @@ h3 Liens connexes a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net a(href="https://brainking.com/") brainking.com + a(href="https://www.facebook.com/groups/592562551198628") Un groupe Facebook diff --git a/client/src/translations/rules/Ball/en.pug b/client/src/translations/rules/Ball/en.pug index d1d750cc..63859cf3 100644 --- a/client/src/translations/rules/Ball/en.pug +++ b/client/src/translations/rules/Ball/en.pug @@ -18,13 +18,13 @@ p. figure.diagram-container .diagram.diag12 - | fen:rnbhq1nbr/ppppppppp/3h5/9/4a4/5P3/9/PPPPP1PPP/RNBHQHNBR: + | fen:1bnrqrnhb/ppppppppp/2h6/9/4a4/5P3/9/PPPPP1PPP/HBNRQRNHB: .diagram.diag22 - | fen:rnbhq1nbr/ppppppppp/3h5/9/4S4/9/9/PPPPP1PPP/RNBHQHNBR: + | fen:1bnrqrnhb/ppppppppp/2h6/9/4S4/9/9/PPPPP1PPP/HBNRQRNHB: figcaption Left: before fxe5 (taking ball). Right: after fxe5. p - | The piece sitting next to the queen is a + | The new piece represented by an upside-down knight is a a(href="https://en.wikipedia.org/wiki/Phoenix_(chess)") Phoenix (H) | . It moves by jumping two squares diagonally (potentially over pieces), | or one square orthogonally. diff --git a/client/src/translations/rules/Ball/es.pug b/client/src/translations/rules/Ball/es.pug index 763b8fd8..69c18c04 100644 --- a/client/src/translations/rules/Ball/es.pug +++ b/client/src/translations/rules/Ball/es.pug @@ -18,15 +18,15 @@ p. figure.diagram-container .diagram.diag12 - | fen:rnbhq1nbr/ppppppppp/3h5/9/4a4/5P3/9/PPPPP1PPP/RNBHQHNBR: + | fen:1bnrqrnhb/ppppppppp/2h6/9/4a4/5P3/9/PPPPP1PPP/HBNRQRNHB: .diagram.diag22 - | fen:rnbhq1nbr/ppppppppp/3h5/9/4S4/9/9/PPPPP1PPP/RNBHQHNBR: + | fen:1bnrqrnhb/ppppppppp/2h6/9/4S4/9/9/PPPPP1PPP/HBNRQRNHB: figcaption. Izquierda: antes de fxe5 (tomando la pelota). Derecha: después de fxe5. p - | La pieza al lado de la dama es un + | La nueva pieza representada por un caballo volcado es un a(href="https://en.wikipedia.org/wiki/Phoenix_(chess)") Phoenix (H) | . Se mueve saltando dos casillas en diagonal | (potencialmente sobre piezas), o una casilla ortogonalmente. diff --git a/client/src/translations/rules/Ball/fr.pug b/client/src/translations/rules/Ball/fr.pug index 42acab5d..141cd09f 100644 --- a/client/src/translations/rules/Ball/fr.pug +++ b/client/src/translations/rules/Ball/fr.pug @@ -19,13 +19,13 @@ p. figure.diagram-container .diagram.diag12 - | fen:rnbhq1nbr/ppppppppp/3h5/9/4a4/5P3/9/PPPPP1PPP/RNBHQHNBR: + | fen:1bnrqrnhb/ppppppppp/2h6/9/4a4/5P3/9/PPPPP1PPP/HBNRQRNHB: .diagram.diag22 - | fen:rnbhq1nbr/ppppppppp/3h5/9/4S4/9/9/PPPPP1PPP/RNBHQHNBR: + | fen:1bnrqrnhb/ppppppppp/2h6/9/4S4/9/9/PPPPP1PPP/HBNRQRNHB: figcaption Gauche : avant fxe5 (prenant le ballon). Droite : après fxe5. p - | La pièce située à côté de la dame est un + | La nouvelle pièce représentée par un cavalier retourné est un a(href="https://en.wikipedia.org/wiki/Phoenix_(chess)") Phoenix (H) | . Il se déplace en effectuant des sauts de deux cases en diagonale | (potentiellement par dessus des pièces), ou d'une case orthogonalement. diff --git a/client/src/variants/Ball.js b/client/src/variants/Ball.js index 0f84bae3..19b81ede 100644 --- a/client/src/variants/Ball.js +++ b/client/src/variants/Ball.js @@ -125,7 +125,7 @@ export class BallRules extends ChessRules { static GenRandInitFen(randomness) { if (randomness == 0) - return "rnbcqcnbr/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/RNBCQCNBR w 0 -"; + return "hbnrqrnhb/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/HBNRQRNHB w 0 -"; let pieces = { w: new Array(9), b: new Array(9) }; for (let c of ["w", "b"]) { @@ -136,7 +136,7 @@ export class BallRules extends ChessRules { // Get random squares for every piece, totally freely let positions = shuffle(ArrayFun.range(9)); - const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'c', 'c', 'q']; + const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'h', 'h', 'q']; const rem2 = positions[0] % 2; if (rem2 == positions[1] % 2) { // Fix bishops (on different colors) diff --git a/client/src/variants/Checkered.js b/client/src/variants/Checkered.js index 79ea63c4..ecf25817 100644 --- a/client/src/variants/Checkered.js +++ b/client/src/variants/Checkered.js @@ -177,7 +177,7 @@ export class CheckeredRules extends ChessRules { // Does m2 un-do m1 ? (to disallow undoing checkered moves) oppositeMoves(m1, m2) { return ( - m1 && + !!m1 && m2.appear[0].c == "c" && m2.appear.length == 1 && m2.vanish.length == 1 && diff --git a/client/src/variants/Dynamo.js b/client/src/variants/Dynamo.js index 7a794ea2..c600b8ae 100644 --- a/client/src/variants/Dynamo.js +++ b/client/src/variants/Dynamo.js @@ -1,46 +1,124 @@ import { ChessRules, Move, PiPo } from "@/base_rules"; -// TODO: need FEN lastmove pour interdire défaire dernière poussée -// --> check appear et vanish totally reversed. - -// TODO: pawn promotions by push (en + des promotions standard) -// --> similar to Zen promotions. - export class DynamoRules extends ChessRules { + // TODO: later, allow to push out pawns on a and h files? + static get HasEnpassant() { + return false; + } + canIplay(side, [x, y]) { // Sometimes opponent's pieces can be moved directly return true; } - getPPpath(m) { - let imgName = ""; - if (m.vanish.length == 1) imgName = "empty"; + setOtherVariables(fen) { + super.setOtherVariables(fen); + this.subTurn = 1; + // Local stack of "action moves" + this.amoves = []; + const amove = V.ParseFen(fen).amove; + if (cmove == "-") this.amoves.push(null); else { - // Something is pushed or pull: count by how many squares - if (m.appear.length == 1) - // It just exit the board - imgName = "raus"; - else { - const deltaX = Math.abs(m.appear[1].x - m.vanish[1].x); - const deltaY = Math.abs(m.appear[1].y - m.vanish[1].y); - if (deltaX == 0) imgName = "shift_" + deltaY; - else if (deltaY == 0) imgName = "shift_" + deltaX; - else - // Special knight push/pull: just print "P" - imgName = "pstep"; - } + const amoveParts = amove.split("/"); + let amove = { + // No need for start & end + appear: [], + vanish: [] + }; + [0, 1].map(i => { + amoveParts[0].split(".").forEach(av => { + // Format is "bpe3" + const xy = V.SquareToCoords(av.substr(2)); + move[i == 0 ? "appear" : "vanish"].push( + new PiPo({ + x: xy.x, + y: xy.y, + c: av[0], + p: av[1] + }) + ); + }); + }); + this.amoves.push(move); } - return "Dynamo/" + imgName; } - getPactions(sq, by, color) { + static ParseFen(fen) { + return Object.assign( + ChessRules.ParseFen(fen), + { cmove: fen.split(" ")[4] } + ); + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParts = fen.split(" "); + if (fenParts.length != 6) return false; + if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/)) + return false; + return true; + } + + getAmove(move) { + if (move.appear.length == 2 && move.vanish.length == 2) + return { appear: move.appear, vanish: move.vanish }; + return null; + } + + // TODO: this.firstMove + rooks location in setOtherVariables + // only rooks location in FEN (firstMove is forgotten if quit game and come back) + doClick(square) { + // If subTurn == 2 && square is the final square of last move, + // then return an empty move + if ( + this.subTurn == 2 && + square.x == this.firstMove.end.x && + square.y == this.firstMove.end.y) + ) { + return { + appear: [], + vanish: [] + }; + } + return null; + } + + canTake() { + // Captures don't occur (only pulls & pushes) + return false; + } + + // "pa" : piece (as a square) doing this push/pull action + getActionMoves([sx, sy], [ex, ey], pa) { + const color = this.getColor(sx, sy); + const lastRank = (color == 'w' ? 0 : 7); + const piece = this.getPiece(sx, sy); + let moves = []; + if (ex == lastRank && piece == V.PAWN) { + // Promotion by push or pull + V.PawnSpecs.promotions.forEach(p => { + let move = super.getBasicMove([sx, sy], [ex, ey], { c: color, p: p }); + moves.push(move); + }); + } else moves.push(super.getBasicMove([sx, sy], [ex, ey])); + const actionType = + ( + Math.abs(pa[0] - sx) < Math.abs(pa[0] - ex) || + Math.abs(pa[1] - sy) < Math.abs(pa[1] - ey) + ) + ? "push" + : "pull"; + moves.forEach(m => m.action = [{ by: pa, type: actionType }]); + return moves; + } + + // TODO: if type is given, consider only actions of this type + getPactions(sq, color, type) { const [x, y] = sq; let moves = []; let squares = {}; -// const lineAdd = (allowedPieces) = { -// // attacking piece must be of the allowed types -// }; if (!by) { + const oppCol = V.GetOppCol(color); // Look in all directions for a "color" piece for (let step of V.steps[V.KNIGHT]) { const xx = x + step[0], @@ -52,11 +130,18 @@ export class DynamoRules extends ChessRules { ) { const px = x - step[0], py = y - step[1]; - if (V.OnBoard(px, py) && this.board[px][py] == V.EMPTY) { - const hash = "s" + px + py; - if (!squares[hash]) { - squares[hash] = true; - moves.push(this.getBasicMove([x, y], [px, py])); + if (V.OnBoard(px, py)) { + if (this.board[px][py] == V.EMPTY) { + const hash = "s" + px + py; + if (!squares[hash]) { + squares[hash] = true; + Array.prototype.push.apply( + moves, + this.getActionMoves([x, y], [px, py], [xx, yy]) + ); + } + else { //add piece doing action + } } } else { const hash = "s" + xx + yy; @@ -82,35 +167,97 @@ export class DynamoRules extends ChessRules { } } for (let step in V.steps[V.ROOK]) { - // color is enemy, so no pawn pushes: king, rook and queen + // (+ if color is ours, pawn pushes) king, rook and queen + // --> pawns special case can push from a little distance if on 2nd rank (or 1st rank) } for (let step in V.steps[V.BISHOP]) { - // King, bishop, queen, and possibly pawns attacks + // King, bishop, queen, and possibly pawns attacks (if color is enemy) } } -// else { -// // TODO: probably in a different function for now. -// } return moves; } // NOTE: to push a piece out of the board, make it slide until our piece // (doing the action, moving or not) + // TODO: for pushes, play the pushed piece first. + // for pulls: play the piece doing the action first getPotentialMovesFrom([x, y]) { const color = this.turn; - const oppCol = V.GetOppCol(color); - if (this.getColor(x, y) != color) { - // Look in every direction for a friendly pusher/puller. - // This means that the action is done without moving. - return this.getPactions([x, y], null, color); - } else { - // Playing my pieces: do they attack an enemy? - // If yes ... TODO - //this.getPattacks(sq, [x, y]); - // Temporary: - return super.getPotentialMovesFrom([x, y]); + if (this.getColor(x, y) != color) + // The only moves possible with enemy pieces are pulls and pushes: + return this.getPactions([x, y], color); + else { + // Playing my pieces: either on their own, or pushed by another + // If subTurn == 2 then we should have a first move, + // TODO = use it to allow some type of action + if (this.subTurn == 2) { + return ( + this.moveOnSubturn1.isAnAction + ? super.getPotentialMovesFrom([x, y]) + : this.getPactions([x, y], color, TODO_arg) + ); + } else { + // Both options are possible at subTurn1: normal move, or push + moves = + super.getPotentialMovesFrom([x, y]) + .concat(this.getPactions([x, y], color, "push"); + // TODO: discard moves that let the king underCheck, and no second + // move can counter check. Example: pinned queen pushes pinned pawn. + .filter(m => { + this.play(m); + const res = this.filterMoves(this.getPotentialMoves(/* TODO: args? */)).length > 0; + this.undo(m); + return res; + }); + } } - return []; //never reached + return moves; + } + + // TODO: track rooks locations, should be a field in FEN, in castleflags? + // --> only useful if castleFlags is still ON + getCastleMoves(sq) { + // TODO: if rook1 isn't at its place (with castleFlags ON), set it off + // same for rook2. + let moves = super.getCastleMoves(sq); + // TODO: restore castleFlags + } + + // Does m2 un-do m1 ? (to disallow undoing actions) + oppositeMoves(m1, m2) { + const isEqual = (av1, av2) => { + // Precondition: av1 and av2 length = 2 + for (let av of av1) { + const avInAv2 = av2.find(elt => { + return ( + elt.x == av.x && + elt.y == av.y && + elt.c == av.c && + elt.p == av.p + ); + }); + if (!avInAv2) return false; + } + return true; + }; + return ( + !!m1 && + m1.appear.length == 2 && + m2.appear.length == 2 && + m1.vanish.length == 2 && + m2.vanish.length == 2 && + isEqual(m1.appear, m2.vanish) && + isEqual(m1.vanish, m2.appear) + ); + } + + filterValid(moves) { + if (moves.length == 0) return []; + const color = this.turn; + return moves.filter(m => { + const L = this.amoves.length; //at least 1: init from FEN + return !this.oppositeMoves(this.amoves[L - 1], m); + }); } isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) { @@ -161,4 +308,58 @@ export class DynamoRules extends ChessRules { } return false; } + + getCurrentScore() { + if (this.subTurn == 2) + // Move not over + return "*"; + return super.getCurrentScore(); + } + + play(move) { + move.flags = JSON.stringify(this.aggregateFlags()); + V.PlayOnBoard(this.board, move); + if (this.subTurn == 1) { + // TODO: is there a second move possible? + // (if the first move is a normal one, there may be no actions available) + // --> If not, just change turn as ion the else {} section + this.subTurn = 2; + this.movesCount++; + } else { + // subTurn == 2 + this.turn = V.GetOppCol(this.turn); + this.subTurn = 1; + } + this.postPlay(move); + } + + updateCastleFlags(move, piece) { + const c = V.GetOppCol(this.turn); + const firstRank = (c == "w" ? V.size.x - 1 : 0); + // Update castling flags if rooks are moved (only) + if (piece == V.KING && move.appear.length > 0) + this.castleFlags[c] = [V.size.y, V.size.y]; + else if ( + move.start.x == firstRank && + this.castleFlags[c].includes(move.start.y) + ) { + const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); + this.castleFlags[c][flagIdx] = V.size.y; + } + } + + undo(move) { + this.disaggregateFlags(JSON.parse(move.flags)); + V.UndoOnBoard(this.board, move); + if (this.subTurn == 2) { + this.subTurn = 1; + this.movesCount--; + } + else { + // subTurn == 1 (after a move played) + this.turn = V.GetOppCol(this.turn); + this.subTurn = 2; + } + this.postUndo(move); + } }; diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js index a05ba1e2..8cf8747a 100644 --- a/client/src/variants/Eightpieces.js +++ b/client/src/variants/Eightpieces.js @@ -108,7 +108,7 @@ export class EightpiecesRules extends ChessRules { // 5) Check sentry push (if any) if ( fenParsed.sentrypush != "-" && - !fenParsed.sentrypush.match(/^([a-h][1-8],?)+$/) + !fenParsed.sentrypush.match(/^([a-h][1-8]){2,2}$/) ) { return false; } @@ -131,7 +131,7 @@ export class EightpiecesRules extends ChessRules { // Condensate path: just need initial and final squares: return [0, spL - 1] .map(i => V.CoordsToSquare(this.sentryPush[L-1][i])) - .join(","); + .join(""); } setOtherVariables(fen) { @@ -145,8 +145,8 @@ export class EightpiecesRules extends ChessRules { if (parsedFen.sentrypush == "-") this.sentryPush = [null]; else { // Expand init + dest squares into a full path: - const [init, dest] = - parsedFen.sentrypush.split(",").map(sq => V.SquareToCoords(sq)); + const init = V.SquareToCoords(parsedFen.sentrypush.substr(0, 2)), + dest = V.SquareToCoords(parsedFen.sentrypush.substr(2)); let newPath = [init]; const delta = ['x', 'y'].map(i => Math.abs(dest[i] - init[i])); // Check that it's not a knight movement: diff --git a/client/src/variants/Suction.js b/client/src/variants/Suction.js index aa28f596..aa34f6c7 100644 --- a/client/src/variants/Suction.js +++ b/client/src/variants/Suction.js @@ -29,8 +29,8 @@ export class SuctionRules extends ChessRules { static IsGoodFen(fen) { if (!ChessRules.IsGoodFen(fen)) return false; const fenParts = fen.split(" "); - if (fenParts.length != 6) return false; - if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/)) + if (fenParts.length != 5) return false; + if (fenParts[4] != "-" && !fenParts[4].match(/^([a-h][1-8]){2}$/)) return false; return true; } @@ -121,7 +121,7 @@ export class SuctionRules extends ChessRules { // Does m2 un-do m1 ? (to disallow undoing captures) oppositeMoves(m1, m2) { return ( - m1 && + !!m1 && m2.vanish.length == 2 && m1.start.x == m2.start.x && m1.end.x == m2.end.x && -- 2.44.0