From 1221ac47836806efb287b0323b92957d9129c653 Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Wed, 28 Nov 2018 03:07:29 +0100 Subject: [PATCH] Switching chess almost OK, Extinction seems OK, Crazyhouse advanced draft but to be debugged --- TODO | 2 + public/javascripts/base_rules.js | 30 ++++-- public/javascripts/components/game.js | 50 ++++++---- public/javascripts/variants/Crazyhouse.js | 106 ++++++++++++++++------ public/javascripts/variants/Extinction.js | 59 +++++++++++- public/javascripts/variants/Magnetic.js | 3 +- public/javascripts/variants/Switching.js | 73 ++++++++++++++- public/javascripts/variants/Zen.js | 40 ++++---- views/rules/Crazyhouse.pug | 33 +++++++ views/rules/Extinction.pug | 34 +++++++ views/rules/Switching.pug | 32 +++++++ 11 files changed, 380 insertions(+), 82 deletions(-) create mode 100644 views/rules/Crazyhouse.pug create mode 100644 views/rules/Extinction.pug create mode 100644 views/rules/Switching.pug diff --git a/TODO b/TODO index bcda2e84..fe7d8473 100644 --- a/TODO +++ b/TODO @@ -2,3 +2,5 @@ For animation, moves should contains "moving" and "fading" maybe... (But it's really just for Magnetic chess) setInterval "CRON" task in sockets.js to check connected clients (every 1hour maybe, or more) +Systematically show init+dest squares in PGN, maybe after short notation +(2 moves list, second for de-ambiguification) diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index f1188ab5..360f60c5 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -276,7 +276,8 @@ class ChessRules { let i = x + step[0]; let j = y + step[1]; - while (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j] == VariantRules.EMPTY) + while (i>=0 && i<sizeX && j>=0 && j<sizeY + && this.board[i][j] == VariantRules.EMPTY) { moves.push(this.getBasicMove([x,y], [i,j])); if (oneStep !== undefined) @@ -296,7 +297,7 @@ class ChessRules const color = this.turn; let moves = []; const V = VariantRules; - const [sizeX,sizeY] = VariantRules.size; + const [sizeX,sizeY] = V.size; const shift = (color == "w" ? -1 : 1); const firstRank = (color == 'w' ? sizeX-1 : 0); const startRank = (color == "w" ? sizeX-2 : 1); @@ -308,7 +309,7 @@ class ChessRules if (this.board[x+shift][y] == V.EMPTY) { moves.push(this.getBasicMove([x,y], [x+shift,y])); - // Next condition because variants with pawns on 1st rank generally allow them to jump + // Next condition because variants with pawns on 1st rank allow them to jump if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY) { // Two squares jump @@ -316,10 +317,16 @@ class ChessRules } } // Captures - if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY) + 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])); - if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY) + } + if (y<sizeY-1 && 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])); + } } if (x+shift == lastRank) @@ -331,10 +338,16 @@ class ChessRules if (this.board[x+shift][y] == V.EMPTY) moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p})); // Captures - if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY) + 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:p})); - if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY) + } + if (y<sizeY-1 && 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:p})); + } }); } @@ -603,7 +616,8 @@ class ChessRules V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); } - // Generic method for non-pawn pieces ("sliding or jumping"): is x,y attacked by piece != color ? + // Generic method for non-pawn pieces ("sliding or jumping"): + // is x,y attacked by piece !of color in colors? isAttackedBySlideNJump([x,y], colors, piece, steps, oneStep) { const [sizeX,sizeY] = VariantRules.size; diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js index 86f25210..2897b1cd 100644 --- a/public/javascripts/components/game.js +++ b/public/javascripts/components/game.js @@ -240,23 +240,39 @@ Vue.component('my-game', { ); } elementArray.push(gameDiv); - // if (!!vr.reserve) - // { - // let reserve = h('div', - // {'class':{'game':true}}, [ - // h('div', - // { 'class': { 'row': true }}, - // [ - // h('div', - // {'class':{'board':true}}, - // [h('img',{'class':{"piece":true},attrs:{"src":"/images/pieces/wb.svg"}})] - // ) - // ] - // ) - // ], - // ); - // elementArray.push(reserve); - // } + if (!!this.vr.reserve) //TODO: table, show counts for reserve pieces + //<tr style="padding:0"> + // <td style="padding:0;font-size:10px">3</td> + { + let reservePiecesArray = []; + for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++) + { + reservePiecesArray.push(h('img', + { + 'class': {"piece":true}, + attrs: { + "src": "/images/pieces/" + + this.vr.getReservePpath(this.mycolor,i) + ".svg", + id: this.getSquareId({x:sizeX,y:i}), + } + }) + ); + } + let reserve = h('div', + {'class':{'game':true}}, [ + h('div', + { 'class': { 'row': true }}, + [ + h('div', + {'class':{'board':true, ['board'+sizeY]:true}}, + reservePiecesArray + ) + ] + ) + ], + ); + elementArray.push(reserve); + } const eogMessage = this.getEndgameMessage(this.score); const modalEog = [ h('input', diff --git a/public/javascripts/variants/Crazyhouse.js b/public/javascripts/variants/Crazyhouse.js index 0ee4bd37..9cb07685 100644 --- a/public/javascripts/variants/Crazyhouse.js +++ b/public/javascripts/variants/Crazyhouse.js @@ -2,7 +2,7 @@ class CrazyhouseRules extends ChessRules { initVariables(fen) { - super.initVariables(); + super.initVariables(fen); // Also init reserves (used by the interface to show landing pieces) const V = VariantRules; this.reserve = @@ -24,59 +24,111 @@ class CrazyhouseRules extends ChessRules [V.QUEEN]: 0, } }; - // It may be a continuation: adjust numbers of pieces according to captures + rebirths - // TODO + // May be a continuation: adjust numbers of pieces according to captures + rebirths + this.moves.forEach(m => { + if (m.vanish.length == 2) + this.reserve[m.appear[0].c][m.vanish[1].p]++; + else if (m.vanish.length == 0) + this.reserve[m.appear[0].c][m.appear[0].p]--; + }); } // Used by the interface: - getReservePieces(color) + getReservePpath(color, index) { - return { - [color+V.PAWN]: this.reserve[color][V.PAWN], - [color+V.ROOK]: this.reserve[color][V.ROOK], - [color+V.KNIGHT]: this.reserve[color][V.KNIGHT], - [color+V.BISHOP]: this.reserve[color][V.BISHOP], - [color+V.QUEEN]: this.reserve[color][V.QUEEN], - }; + return color + VariantRules.RESERVE_PIECES[index]; } - getPotentialMovesFrom([x,y]) + // Put an ordering on reserve pieces + static get RESERVE_PIECES() { + const V = VariantRules; + return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]; + } + + getReserveMoves([x,y]) { - let moves = super.getPotentialMovesFrom([x,y]); - // Add landing moves: const color = this.turn; - Object.keys(this.reserve[color]).forEach(p => { - - moves.push(...); //concat... just appear - }); + const p = VariantRules.RESERVE_PIECES[y]; + if (this.reserve[color][p] == 0) + return []; + let moves = []; + for (let i=0; i<sizeX; i++) + { + for (let j=0; j<sizeY; j++) + { + if (this.board[i][j] != VariantRules.EMPTY) + { + let mv = new Move({ + appear: [ + new PiPo({ + x: i, + y: j, + c: color, + p: p + }) + ] + }); + moves.push(mv); + } + } + } return moves; } - // TODO: condition "if this is reserve" --> special square !!! coordinates ?? - getPossibleMovesFrom(sq) + getPotentialMovesFrom([x,y]) { - // Assuming color is right (already checked) - return this.filterValid( this.getPotentialMovesFrom(sq) ); + const sizeX = VariantRules.size[0]; + if (x < sizeX) + return super.getPotentialMovesFrom([x,y]); + // Reserves, outside of board: x == sizeX + return this.getReserveMoves([x,y]); } - // TODO: add reserve moves getAllValidMoves() { - + let moves = super.getAllValidMoves(); + const color = this.turn; + const sizeX = VariantRules.size[0]; + for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++) + moves = moves.concat(this.getReserveMoves([sizeX,i])); + return this.filterValid(moves); } - // TODO: also atLeastOneMove() { - + if (!super.atLeastOneMove()) + { + const sizeX = VariantRules.size[0]; + // Scan for reserve moves + for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++) + { + let moves = this.filterValid(this.getReserveMoves([sizeX,i])); + if (moves.length > 0) + return true; + } + return false; + } + return true; } - // TODO: update reserve updateVariables(move) { + super.updateVariables(move); + const color = this.turn; + if (move.vanish.length==2) + this.reserve[color][move.appear[0].p]++; + if (move.vanish.length==0) + this.reserve[color][move.appear[0].p]--; } + unupdateVariables(move) { + super.unupdateVariables(move); + const color = this.turn; + if (move.vanish.length==2) + this.reserve[color][move.appear[0].p]--; + if (move.vanish.length==0) + this.reserve[color][move.appear[0].p]++; } static get SEARCH_DEPTH() { return 2; } //high branching factor diff --git a/public/javascripts/variants/Extinction.js b/public/javascripts/variants/Extinction.js index db330d9e..9be4b0d7 100644 --- a/public/javascripts/variants/Extinction.js +++ b/public/javascripts/variants/Extinction.js @@ -27,6 +27,37 @@ class ExtinctionRules extends ChessRules }; } + getPotentialPawnMoves([x,y]) + { + let moves = super.getPotentialPawnMoves([x,y]); + // Add potential promotions into king + const color = this.turn; + const V = VariantRules; + const [sizeX,sizeY] = V.size; + const shift = (color == "w" ? -1 : 1); + const lastRank = (color == "w" ? 0 : sizeX-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})); + // 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<sizeY-1 && 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})); + } + } + + return moves; + } + // TODO: verify this assertion atLeastOneMove() { @@ -46,13 +77,24 @@ class ExtinctionRules extends ChessRules updateVariables(move) { super.updateVariables(move); - if (move.vanish.length==2 && move.appear.length==1) + // Treat the promotion case: (not the capture part) + if (move.appear[0].p != move.vanish[0].p) + { + this.material[move.appear[0].c][move.appear[0].p]++; + this.material[move.appear[0].c][VariantRules.PAWN]--; + } + if (move.vanish.length==2 && move.appear.length==1) //capture this.material[move.vanish[1].c][move.vanish[1].p]--; } unupdateVariables(move) { super.unupdateVariables(move); + if (move.appear[0].p != move.vanish[0].p) + { + this.material[move.appear[0].c][move.appear[0].p]--; + this.material[move.appear[0].c][VariantRules.PAWN]++; + } if (move.vanish.length==2 && move.appear.length==1) this.material[move.vanish[1].c][move.vanish[1].p]++; } @@ -73,14 +115,23 @@ class ExtinctionRules extends ChessRules return "*"; } - return this.checkGameEnd(); + return this.checkGameEnd(); //NOTE: currently unreachable... + } + + checkGameEnd() + { + return this.turn == "w" ? "0-1" : "1-0"; } // Very negative (resp. positive) if white (reps. black) pieces set is incomplete evalPosition() { - if (this.missAkind()) - return (this.turn=="w"?-1:1) * VariantRules.INFINITY; + const color = this.turn; + if (Object.keys(this.material[color]).some( + p => { return this.material[color][p] == 0; })) + { + return (color=="w"?-1:1) * VariantRules.INFINITY; + } return super.evalPosition(); } } diff --git a/public/javascripts/variants/Magnetic.js b/public/javascripts/variants/Magnetic.js index 844cc689..03912e7e 100644 --- a/public/javascripts/variants/Magnetic.js +++ b/public/javascripts/variants/Magnetic.js @@ -112,7 +112,8 @@ class MagneticRules extends ChessRules // Scan move for pawn (max 1) on 8th rank for (let i=1; i<move.appear.length; i++) { - if (move.appear[i].p==V.PAWN && move.appear[i].c==color && move.appear[i].x==lastRank) + if (move.appear[i].p==V.PAWN && move.appear[i].c==color + && move.appear[i].x==lastRank) { move.appear[i].p = V.ROOK; moves.push(move); diff --git a/public/javascripts/variants/Switching.js b/public/javascripts/variants/Switching.js index a7d67879..cc2febd1 100644 --- a/public/javascripts/variants/Switching.js +++ b/public/javascripts/variants/Switching.js @@ -1,10 +1,73 @@ -//https://www.chessvariants.com/diffmove.dir/switching.html class SwitchingRules extends ChessRules { - //TODO: - // Move completion: promote switched pawns (as in Magnetic) + // Build switch move between squares x1,y1 and x2,y2 + getSwitchMove_s([x1,y1],[x2,y2]) + { - // To every piece potential moves: add switchings + const c = this.getColor(x1,y1); //same as color at square 2 + const p1 = this.getPiece(x1,y1); + const p2 = this.getPiece(x2,y2); + let move = new Move({ + appear: [ + new PiPo({x:x2,y:y2,c:c,p:p1}), + new PiPo({x:x1,y:y1,c:c,p:p2}) + ], + vanish: [ + new PiPo({x:x1,y:y1,c:c,p:p1}), + new PiPo({x:x2,y:y2,c:c,p:p2}) + ], + start: {x:x1,y:y1}, + end: {x:x2,y:y2} + }); + // Move completion: promote switched pawns (as in Magnetic) + const sizeX = VariantRules.size[0]; + const lastRank = (c == "w" ? 0 : sizeX-1); + const V = VariantRules; + let moves = []; + if (p1==V.PAWN && x2==lastRank) //TODO: also the case p2==V.PAWN and x1==lastRank! see Magnetic chess + { + move.appear[0].p = V.ROOK; + moves.push(move); + for (let piece of [V.KNIGHT, V.BISHOP, V.QUEEN]) + { + let cmove = JSON.parse(JSON.stringify(move)); + cmove.appear[0].p = piece; + moves.push(cmove); + } + } + else //other cases + moves.push(move); + return moves; + } - // Prevent king switching if under check + getPotentialMovesFrom([x,y]) + { + let moves = super.getPotentialMovesFrom([x,y]); + // Add switches: + const V = VariantRules; + const color = this.turn; + const piece = this.getPiece(x,y); + const [sizeX,sizeY] = V.size; + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + const kp = this.kingPos[color]; + const oppCol = this.getOppCol(color); + for (let step of steps) + { + let [i,j] = [x+step[0],y+step[1]]; + if (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j]!=V.EMPTY + && this.getColor(i,j)==color && this.getPiece(i,j)!=piece + // No switching under check (theoretically non-king pieces could, but not) + && !this.isAttacked(kp, [oppCol])) + { + let switchMove_s = this.getSwitchMove_s([x,y],[i,j]); + if (switchMove_s.length == 1) + moves.push(switchMove_s[0]); + else //promotion + moves = moves.concat(switchMove_s); + } + } + return moves; + } + + static get SEARCH_DEPTH() { return 2; } //branching factor is quite high } diff --git a/public/javascripts/variants/Zen.js b/public/javascripts/variants/Zen.js index 17c922d2..31dfccd1 100644 --- a/public/javascripts/variants/Zen.js +++ b/public/javascripts/variants/Zen.js @@ -10,14 +10,14 @@ class ZenRules extends ChessRules getSlideNJumpMoves([x,y], steps, oneStep) { const color = this.getColor(x,y); - var moves = []; - let [sizeX,sizeY] = VariantRules.size; + let moves = []; + const [sizeX,sizeY] = VariantRules.size; outerLoop: - for (var loop=0; loop<steps.length; loop++) + for (let loop=0; loop<steps.length; loop++) { - var step = steps[loop]; - var i = x + step[0]; - var j = y + step[1]; + const step = steps[loop]; + let i = x + step[0]; + let j = y + step[1]; while (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j] == VariantRules.EMPTY) { @@ -38,22 +38,21 @@ class ZenRules extends ChessRules { const color = this.getColor(x,y); var moves = []; - var V = VariantRules; - var steps = asA != V.PAWN - ? V.steps[asA] + const V = VariantRules; + const steps = asA != V.PAWN + ? (asA==V.QUEEN ? V.steps[V.ROOK].concat(V.steps[V.BISHOP]) : V.steps[asA]) : color=='w' ? [[-1,-1],[-1,1]] : [[1,-1],[1,1]]; - var oneStep = (asA==V.KNIGHT || asA==V.PAWN); //we don't capture king - let [sizeX,sizeY] = V.size; - let lastRank = (color == 'w' ? 0 : sizeY-1); - let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]; + const oneStep = (asA==V.KNIGHT || asA==V.PAWN); //we don't capture king + const [sizeX,sizeY] = V.size; + const lastRank = (color == 'w' ? 0 : sizeY-1); + const promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]; outerLoop: - for (var loop=0; loop<steps.length; loop++) + for (let loop=0; loop<steps.length; loop++) { - var step = steps[loop]; - var i = x + step[0]; - var j = y + step[1]; - while (i>=0 && i<sizeX && j>=0 && j<sizeY - && this.board[i][j] == V.EMPTY) + const step = steps[loop]; + let i = x + step[0]; + let j = y + step[1]; + while (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j] == V.EMPTY) { if (oneStep) continue outerLoop; @@ -173,7 +172,8 @@ class ZenRules extends ChessRules getPotentialQueenMoves(sq) { const V = VariantRules; - let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.ROOK.concat(V.steps[V.BISHOP])]); + let noCaptures = + this.getSlideNJumpMoves(sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP])); let captures = this.findCaptures(sq); return noCaptures.concat(captures); } diff --git a/views/rules/Crazyhouse.pug b/views/rules/Crazyhouse.pug new file mode 100644 index 00000000..3928c309 --- /dev/null +++ b/views/rules/Crazyhouse.pug @@ -0,0 +1,33 @@ +p.boxed + | Every captured piece can be re-used by the capturer, landing it anywhere instead of moving a piece. + +h3 Specifications + +ul + li Chessboard: standard. + li Material: standard. + li Non-capturing moves: standard. + li Special moves: standard + rebirth. + li Captures: standard. + li End of game: standard. + +h3 Basics + +p + | Orthodox rules apply, with only one change: + | every time you capture a piece, your "reserve" of waiting pieces is augmented + | with this figure. At every move, you may choose to land one of your reserve + | pieces anywhere on the board (except last rank for pawns), + | instead of playing a regular move. + +p. + Note: when a promoted pawn is captured, capturing it put a pawn in the reserve, + not the promoted piece. This is to allow to gain material on last rank without + fear of giving a queen to the opponent. + +h3 Credits + +p + | This variant is very popular, a possible starting point is + a(href="https://www.chessvariants.com/other.dir/crazyhouse.html") lichess.org + | . diff --git a/views/rules/Extinction.pug b/views/rules/Extinction.pug new file mode 100644 index 00000000..56725864 --- /dev/null +++ b/views/rules/Extinction.pug @@ -0,0 +1,34 @@ +p.boxed + | Win by eliminating all opponent pieces of the same type. + +h3 Specifications + +ul + li Chessboard: standard. + li Material: standard. + li Non-capturing moves: standard. + li Special moves: standard. + li Captures: standard. + li End of game: pieces extinction. + +h3 Basics + +p + | Standard rules apply, but the game ends when all pieces of a kind disappeared. + | Kings are treated as normal pieces (no royal power), but may castle - + | without any concern about checks. + | Pawns may promote into king. + | If all pieces of a kind disappear, the game is lost; except it's a pawns extinction + | which results in a non-pawn extinction by capturing and promoting on the last rank. + +h3 End of game + +p. + Win by eliminating all enemy pawns, or rooks, or knights, or bishops, or queen(s), + or king(s) (there may be several if promotions happened). + +h3 Credits + +p + a(href="https://www.chessvariants.com/winning.dir/extinction.html") Extinction chess + | on chessvariants.com. diff --git a/views/rules/Switching.pug b/views/rules/Switching.pug new file mode 100644 index 00000000..3104734b --- /dev/null +++ b/views/rules/Switching.pug @@ -0,0 +1,32 @@ +p.boxed + | In addition to standard moves, a piece can be exchanged with an adjacent friendly unit. + +h3 Specifications + +ul + li Chessboard: standard. + li Material: standard. + li Non-capturing moves: standard + switch. + li Special moves: standard. + li Captures: standard. + li End of game: standard. + +h3 Basics + +p + | Instead of a normal move, a piece may be exchanged with an adjacent friendly one. + | Switching can move pawns until rank 1 or final rank. In the first case a 2-squares, + | jump is possible, and in the second a promotion occurs. + | Switching must involves two different units. + | Switching while the king is under check is not allowed. + +p. + Note: if the king and rook are on two adjacent squares, castling and switching + from the king are triggered in the same way. Castling takes priority: + if you wanna switch, use the rook. + +h3 Credits + +p + a(href="https://www.chessvariants.com/diffmove.dir/switching.html") Switching chess + | on chessvariants.com. -- 2.44.0