From: Benjamin Auder Date: Wed, 18 Mar 2020 13:30:03 +0000 (+0100) Subject: Fix Cannibal variant. TODO: better kings images X-Git-Url: https://git.auder.net/%7B%7B%20asset%28%27mixstore/css/store/table.css%27%29%20%7D%7D?a=commitdiff_plain;h=1c58eb76b86d89b9aad29920240b12451f77ab95;p=vchess.git Fix Cannibal variant. TODO: better kings images --- diff --git a/client/public/images/pieces/Cannibal/bc.svg b/client/public/images/pieces/Cannibal/bc.svg new file mode 100644 index 00000000..03c42738 --- /dev/null +++ b/client/public/images/pieces/Cannibal/bc.svg @@ -0,0 +1,94 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/bl.svg b/client/public/images/pieces/Cannibal/bl.svg new file mode 100644 index 00000000..965d8dec --- /dev/null +++ b/client/public/images/pieces/Cannibal/bl.svg @@ -0,0 +1,119 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/bo.svg b/client/public/images/pieces/Cannibal/bo.svg new file mode 100644 index 00000000..a3e425a5 --- /dev/null +++ b/client/public/images/pieces/Cannibal/bo.svg @@ -0,0 +1,99 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/bs.svg b/client/public/images/pieces/Cannibal/bs.svg new file mode 100644 index 00000000..fab7c213 --- /dev/null +++ b/client/public/images/pieces/Cannibal/bs.svg @@ -0,0 +1,62 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/bt.svg b/client/public/images/pieces/Cannibal/bt.svg new file mode 100644 index 00000000..311d5fd7 --- /dev/null +++ b/client/public/images/pieces/Cannibal/bt.svg @@ -0,0 +1,109 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/bu.svg b/client/public/images/pieces/Cannibal/bu.svg new file mode 100644 index 00000000..bce3eed1 --- /dev/null +++ b/client/public/images/pieces/Cannibal/bu.svg @@ -0,0 +1,94 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/wc.svg b/client/public/images/pieces/Cannibal/wc.svg new file mode 100644 index 00000000..b51dea5a --- /dev/null +++ b/client/public/images/pieces/Cannibal/wc.svg @@ -0,0 +1,126 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/wl.svg b/client/public/images/pieces/Cannibal/wl.svg new file mode 100644 index 00000000..322c54c3 --- /dev/null +++ b/client/public/images/pieces/Cannibal/wl.svg @@ -0,0 +1,124 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/wo.svg b/client/public/images/pieces/Cannibal/wo.svg new file mode 100644 index 00000000..0e3bd47e --- /dev/null +++ b/client/public/images/pieces/Cannibal/wo.svg @@ -0,0 +1,71 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/ws.svg b/client/public/images/pieces/Cannibal/ws.svg new file mode 100644 index 00000000..dfcd791e --- /dev/null +++ b/client/public/images/pieces/Cannibal/ws.svg @@ -0,0 +1,71 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/wt.svg b/client/public/images/pieces/Cannibal/wt.svg new file mode 100644 index 00000000..e2a5a230 --- /dev/null +++ b/client/public/images/pieces/Cannibal/wt.svg @@ -0,0 +1,204 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Cannibal/wu.svg b/client/public/images/pieces/Cannibal/wu.svg new file mode 100644 index 00000000..b2bfd221 --- /dev/null +++ b/client/public/images/pieces/Cannibal/wu.svg @@ -0,0 +1,128 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 46064758..cd4bb778 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -605,21 +605,23 @@ export const ChessRules = class ChessRules { // Build a regular move from its initial and destination squares. // tr: transformation getBasicMove([sx, sy], [ex, ey], tr) { + const initColor = this.getColor(sx, sy); + const initPiece = this.getPiece(sx, sy); let mv = new Move({ appear: [ new PiPo({ x: ex, y: ey, - c: tr ? tr.c : this.getColor(sx, sy), - p: tr ? tr.p : this.getPiece(sx, sy) + c: tr ? tr.c : initColor, + p: tr ? tr.p : initPiece }) ], vanish: [ new PiPo({ x: sx, y: sy, - c: this.getColor(sx, sy), - p: this.getPiece(sx, sy) + c: initColor, + p: initPiece }) ] }); @@ -679,33 +681,31 @@ export const ChessRules = class ChessRules { return !!enpassantMove ? [enpassantMove] : []; } + // Consider all potential promotions: + addPawnMoves([x1, y1], [x2, y2], moves, promotions) { + let finalPieces = [V.PAWN]; + const color = this.turn; + const lastRank = (color == "w" ? 0 : V.size.x - 1); + if (x2 == lastRank) { + // promotions arg: special override for Hiddenqueen variant + if (!!promotions) finalPieces = promotions; + else if (!!V.PawnSpecs.promotions) + finalPieces = V.PawnSpecs.promotions; + } + let tr = null; + for (let piece of finalPieces) { + tr = (piece != V.PAWN ? { c: color, p: piece } : null); + moves.push(this.getBasicMove([x1, y1], [x2, y2], tr)); + } + } + // What are the pawn moves from square x,y ? getPotentialPawnMoves([x, y], promotions) { const color = this.turn; const [sizeX, sizeY] = [V.size.x, V.size.y]; const pawnShiftX = V.PawnSpecs.directions[color]; - const firstRank = color == "w" ? sizeX - 1 : 0; - const startRank = color == "w" ? sizeX - 2 : 1; - const lastRank = color == "w" ? 0 : sizeX - 1; - - // Consider all potential promotions: - const addMoves = ([x1, y1], [x2, y2], moves) => { - let finalPieces = [V.PAWN]; - if (x2 == lastRank) { - // promotions arg: special override for Hiddenqueen variant - if (!!promotions) finalPieces = promotions; - else if (!!V.PawnSpecs.promotions) - finalPieces = V.PawnSpecs.promotions; - } - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x1, y1], [x2, y2], { - c: color, - p: piece - }) - ); - } - } + const firstRank = (color == "w" ? sizeX - 1 : 0); + const startRank = (color == "w" ? sizeX - 2 : 1); // Pawn movements in shiftX direction: const getPawnMoves = (shiftX) => { @@ -714,7 +714,7 @@ export const ChessRules = class ChessRules { if (x + shiftX >= 0 && x + shiftX < sizeX) { if (this.board[x + shiftX][y] == V.EMPTY) { // One square forward - addMoves([x, y], [x + shiftX, y], moves); + this.addPawnMoves([x, y], [x + shiftX, y], moves, promotions); // Next condition because pawns on 1st rank can generally jump if ( V.PawnSpecs.twoSquares && @@ -736,7 +736,10 @@ export const ChessRules = class ChessRules { this.board[x + shiftX][y + shiftY] != V.EMPTY && this.canTake([x, y], [x + shiftX, y + shiftY]) ) { - addMoves([x, y], [x + shiftX, y + shiftY], moves); + this.addPawnMoves( + [x, y], [x + shiftX, y + shiftY], + moves, promotions + ); } if ( V.PawnSpecs.captureBackward && @@ -744,7 +747,10 @@ export const ChessRules = class ChessRules { this.board[x - shiftX][y + shiftY] != V.EMPTY && this.canTake([x, y], [x - shiftX, y + shiftY]) ) { - addMoves([x, y], [x + shiftX, y + shiftY], moves); + this.addPawnMoves( + [x, y], [x + shiftX, y + shiftY], + moves, promotions + ); } } } @@ -1038,7 +1044,7 @@ export const ChessRules = class ChessRules { // Is color under check after his move ? underCheck(color) { - return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]); + return this.isAttacked(this.kingPos[color], V.GetOppCol(color)); } ///////////////// @@ -1060,7 +1066,7 @@ export const ChessRules = class ChessRules { play(move) { // DEBUG: // if (!this.states) this.states = []; -// const stateFen = this.getBaseFen() + this.getTurnFen();// + this.getFlagsFen(); +// const stateFen = this.getFen() + JSON.stringify(this.kingPos); // this.states.push(stateFen); this.prePlay(move); @@ -1072,6 +1078,27 @@ export const ChessRules = class ChessRules { this.postPlay(move); } + updateCastleFlags(move) { + const c = V.GetOppCol(this.turn); + const firstRank = (c == "w" ? V.size.x - 1 : 0); + // 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.castleFlags[c].includes(move.start.y) + ) { + const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); + this.castleFlags[c][flagIdx] = V.size.y; + } else if ( + move.end.x == oppFirstRank && //we took opponent rook? + this.castleFlags[oppCol].includes(move.end.y) + ) { + const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1); + this.castleFlags[oppCol][flagIdx] = V.size.y; + } + } + // After move is played, update variables + flags postPlay(move) { const c = V.GetOppCol(this.turn); @@ -1082,7 +1109,6 @@ export const ChessRules = class ChessRules { else // Crazyhouse-like variants piece = move.appear[0].p; - const firstRank = c == "w" ? V.size.x - 1 : 0; // Update king position + flags if (piece == V.KING && move.appear.length > 0) { @@ -1091,24 +1117,7 @@ export const ChessRules = class ChessRules { if (V.HasCastle) this.castleFlags[c] = [V.size.y, V.size.y]; return; } - if (V.HasCastle) { - // 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.castleFlags[c].includes(move.start.y) - ) { - const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); - this.castleFlags[c][flagIdx] = V.size.y; - } else if ( - move.end.x == oppFirstRank && //we took opponent rook? - this.castleFlags[oppCol].includes(move.end.y) - ) { - const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1); - this.castleFlags[oppCol][flagIdx] = V.size.y; - } - } + if (V.HasCastle) this.updateCastleFlags(move); } preUndo() {} @@ -1123,7 +1132,7 @@ export const ChessRules = class ChessRules { this.postUndo(move); // DEBUG: -// const stateFen = this.getBaseFen() + this.getTurnFen();// + this.getFlagsFen(); +// const stateFen = this.getFen() + JSON.stringify(this.kingPos); // if (stateFen != this.states[this.states.length-1]) debugger; // this.states.pop(); } diff --git a/client/src/translations/rules/Cannibal/en.pug b/client/src/translations/rules/Cannibal/en.pug index 5335038a..b3da21ca 100644 --- a/client/src/translations/rules/Cannibal/en.pug +++ b/client/src/translations/rules/Cannibal/en.pug @@ -12,7 +12,7 @@ p. figure.diagram-container .diagram - | fen:rnbqkbnr/1ppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR a6: + | fen:rnbqkbnr/1ppppppp/p7/8/4P3/8/PPPP1PPP/RNBQKBNR a6: figcaption After 1.e4 a6, 2.Bxa6 turns the bishop into a pawn. p If the king captures, it transforms as well but stays royal. diff --git a/client/src/translations/rules/Cannibal/es.pug b/client/src/translations/rules/Cannibal/es.pug index 60b75a5b..9fd771de 100644 --- a/client/src/translations/rules/Cannibal/es.pug +++ b/client/src/translations/rules/Cannibal/es.pug @@ -12,7 +12,7 @@ p. figure.diagram-container .diagram - | fen:rnbqkbnr/pppp1pp1/7p/4P3/8/8/PPP1PPPP/RNBQKBNR: + | fen:rnbqkbnr/1ppppppp/p7/8/4P3/8/PPPP1PPP/RNBQKBNR a6: figcaption Después de 1.e4 a6, 2.Bxa6 transforma el alfil en un peón. p Si el rey captura, también se transforma pero permanece real. diff --git a/client/src/translations/rules/Cannibal/fr.pug b/client/src/translations/rules/Cannibal/fr.pug index 344a7f8e..03b6497b 100644 --- a/client/src/translations/rules/Cannibal/fr.pug +++ b/client/src/translations/rules/Cannibal/fr.pug @@ -12,7 +12,7 @@ p. figure.diagram-container .diagram - | fen:rnbqkbnr/1ppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR a6: + | fen:rnbqkbnr/1ppppppp/p7/8/4P3/8/PPPP1PPP/RNBQKBNR a6: figcaption Après 1.e4 a6, 2.Bxa6 transforme le fou en un pion. p Si le roi capture, il se transforme aussi mais reste royal. diff --git a/client/src/variants/Cannibal.js b/client/src/variants/Cannibal.js index fc0ef0b5..1508f672 100644 --- a/client/src/variants/Cannibal.js +++ b/client/src/variants/Cannibal.js @@ -1,6 +1,60 @@ -import { ChessRules } from "@/base_rules"; +import { ChessRules, Move, PiPo } from "@/base_rules"; export class CannibalRules extends ChessRules { + static get KING_CODE() { + return { + 'p': 's', + 'r': 'u', + 'n': 'o', + 'b': 'c', + 'q': 't' + }; + } + + static get KING_DECODE() { + return { + 's': 'p', + 'u': 'r', + 'o': 'n', + 'c': 'b', + 't': 'q' + }; + } + + // Kings may be disguised: + getPiece(x, y) { + const piece = this.board[x][y].charAt(1); + if (Object.keys(V.KING_DECODE).includes(piece)) + return V.KING_DECODE[piece]; + return piece; + } + + getPpath(b) { + return (Object.keys(V.KING_DECODE).includes(b[1]) ? "Cannibal/" : "") + b; + } + + // Kings may be disguised: + setOtherVariables(fen) { + super.setOtherVariables(fen); + const rows = V.ParseFen(fen).position.split("/"); + if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0) { + for (let i = 0; i < rows.length; i++) { + let k = 0; //column index on board + for (let j = 0; j < rows[i].length; j++) { + const piece = rows[i].charAt(j); + if (Object.keys(V.KING_DECODE).includes(piece.toLowerCase())) { + const color = (piece.charCodeAt(0) <= 90 ? 'w' : 'b'); + this.kingPos[color] = [i, k]; + } else { + const num = parseInt(rows[i].charAt(j)); + if (!isNaN(num)) k += num - 1; + } + k++; + } + } + } + } + // Trim all non-capturing moves static KeepCaptures(moves) { return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1); @@ -26,20 +80,80 @@ export class CannibalRules extends ChessRules { return false; } - getPotentialMovesFrom([x, y]) { - let moves = super.getPotentialMovesFrom([x, y]); - // Transform capturers, except for the king - moves.forEach(m => { - if ( - m.appear[0].p != V.KING && - m.vanish.length == 2 && - m.appear.length == 1 && - m.vanish[0].p != m.vanish[1].p - ) { - m.appear[0].p = m.vanish[1].p; - } + // Because of the disguised kings, getPiece() could be wrong: + // use board[x][y][1] instead (always valid). + getBasicMove([sx, sy], [ex, ey], tr) { + const initColor = this.getColor(sx, sy); + const initPiece = this.board[sx][sy].charAt(1); + let mv = new Move({ + appear: [ + new PiPo({ + x: ex, + y: ey, + c: tr ? tr.c : initColor, + p: tr ? tr.p : initPiece + }) + ], + vanish: [ + new PiPo({ + x: sx, + y: sy, + c: initColor, + p: initPiece + }) + ] }); - return moves; + + // The opponent piece disappears if we take it + if (this.board[ex][ey] != V.EMPTY) { + mv.vanish.push( + new PiPo({ + x: ex, + y: ey, + c: this.getColor(ex, ey), + p: this.board[ex][ey].charAt(1) + }) + ); + + // If the captured piece has a different nature: take it as well + if (mv.vanish[0].p != mv.vanish[1].p) { + if ( + mv.vanish[0].p == V.KING || + Object.keys(V.KING_DECODE).includes(mv.vanish[0].p) + ) { + mv.appear[0].p = V.KING_CODE[mv.vanish[1].p]; + } else mv.appear[0].p = mv.vanish[1].p; + } + } + else if (!!tr && mv.vanish[0].p != V.PAWN) + // Special case of a non-capturing king-as-pawn promotion + mv.appear[0].p = V.KING_CODE[tr.p]; + + return mv; + } + + getPotentialMovesFrom([x, y]) { + const piece = this.board[x][y].charAt(1); + if (Object.keys(V.KING_DECODE).includes(piece)) + return super.getPotentialMovesFrom([x, y], V.KING_DECODE[piece]); + return super.getPotentialMovesFrom([x, y], piece); + } + + addPawnMoves([x1, y1], [x2, y2], moves) { + let finalPieces = [V.PAWN]; + const color = this.turn; + const lastRank = (color == "w" ? 0 : V.size.x - 1); + if (x2 == lastRank) { + if (this.board[x2][y2] != V.EMPTY) + // Cannibal rules: no choice if capture + finalPieces = [this.getPiece(x2, y2)]; + else finalPieces = V.PawnSpecs.promotions; + } + let tr = null; + for (let piece of finalPieces) { + tr = (piece != V.PAWN ? { c: color, p: piece } : null); + moves.push(this.getBasicMove([x1, y1], [x2, y2], tr)); + } } getPossibleMovesFrom(sq) { @@ -56,6 +170,38 @@ export class CannibalRules extends ChessRules { return moves; } + postPlay(move) { + const c = V.GetOppCol(this.turn); + const piece = move.appear[0].p; + // Update king position + flags + if (piece == V.KING || Object.keys(V.KING_DECODE).includes(piece)) { + this.kingPos[c][0] = move.appear[0].x; + this.kingPos[c][1] = move.appear[0].y; + this.castleFlags[c] = [V.size.y, V.size.y]; + return; + } + super.updateCastleFlags(move); + } + + postUndo(move) { + // (Potentially) Reset king position + const c = this.getColor(move.start.x, move.start.y); + const piece = move.appear[0].p; + if (piece == V.KING || Object.keys(V.KING_DECODE).includes(piece)) + this.kingPos[c] = [move.start.x, move.start.y]; + } + + static get VALUES() { + return { + p: 1, + r: 5, + n: 3, + b: 3, + q: 9, + k: 5 + }; + } + static get SEARCH_DEPTH() { return 4; } diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js index fe36ab24..bea261a3 100644 --- a/client/src/variants/Eightpieces.js +++ b/client/src/variants/Eightpieces.js @@ -247,21 +247,23 @@ export class EightpiecesRules extends ChessRules { // Because of the lancers, getPiece() could be wrong: // use board[x][y][1] instead (always valid). getBasicMove([sx, sy], [ex, ey], tr) { + const initColor = this.getColor(sx, sy); + const initPiece = this.board[sx][sy].charAt(1); let mv = new Move({ appear: [ new PiPo({ x: ex, y: ey, - c: tr ? tr.c : this.getColor(sx, sy), - p: tr ? tr.p : this.board[sx][sy].charAt(1) + c: tr ? tr.c : initColor, + p: tr ? tr.p : initPiece }) ], vanish: [ new PiPo({ x: sx, y: sy, - c: this.getColor(sx, sy), - p: this.board[sx][sy].charAt(1) + c: initColor, + p: initPiece }) ] });