From 6752407b88b6d7678b9b19df4ffe1224d17625d7 Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Thu, 29 Nov 2018 04:30:27 +0100 Subject: [PATCH] Improve Crazyhouse; still not debugged --- TODO | 8 +- public/javascripts/base_rules.js | 24 ++++- public/javascripts/components/game.js | 7 +- public/javascripts/variants/Crazyhouse.js | 124 ++++++++++++++++++---- public/javascripts/variants/Loser.js | 22 ++-- public/javascripts/variants/Switching.js | 4 +- views/rules/Checkered.pug | 19 +++- 7 files changed, 156 insertions(+), 52 deletions(-) diff --git a/TODO b/TODO index 27b3f8db..d491a90b 100644 --- a/TODO +++ b/TODO @@ -2,11 +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) Button to show all pieces that can move (next to expert mode; change icons) - -Bugs, limitations: - - switching chess when castling: show 2 promotion kings (??) - - Crazyhouse: TODO = keep track of promoted pawns. - - Checkered: implement stage 2 ?! +Style (crazyhouse): ghost image of reserve pieces are initially translated diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 360f60c5..e8862736 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -726,7 +726,7 @@ class ChessRules // if (!ingame) this.states.push(JSON.stringify(this.board)); if (!!ingame) - move.notation = this.getNotation(move); + move.notation = [this.getNotation(move), this.getLongNotation(move)]; move.flags = JSON.stringify(this.flags); //save flags (for undo) this.updateVariables(move); @@ -1110,6 +1110,16 @@ class ChessRules } } + // Complete the usual notation, may be required for de-ambiguification + getLongNotation(move) + { + const startSquare = + String.fromCharCode(97 + move.start.y) + (VariantRules.size[0]-move.start.x); + const finalSquare = + String.fromCharCode(97 + move.end.y) + (VariantRules.size[0]-move.end.x); + return startSquare + finalSquare; //not encoding move. But short+long is enough + } + // The score is already computed when calling this function getPGN(mycolor, score, fenStart, mode) { @@ -1124,14 +1134,24 @@ class ChessRules pgn += '[Fen "' + fenStart + '"]<br>'; pgn += '[Result "' + score + '"]<br><br>'; + // Standard PGN for (let i=0; i<this.moves.length; i++) { if (i % 2 == 0) pgn += ((i/2)+1) + "."; - pgn += this.moves[i].notation + " "; + pgn += this.moves[i].notation[0] + " "; } + pgn += score + "<br><br>"; + // "Complete moves" PGN (helping in ambiguous cases) + for (let i=0; i<this.moves.length; i++) + { + if (i % 2 == 0) + pgn += ((i/2)+1) + "."; + pgn += this.moves[i].notation[1] + " "; + } pgn += score; + return pgn; } } diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js index f5bef8ef..6d05a2a6 100644 --- a/public/javascripts/components/game.js +++ b/public/javascripts/components/game.js @@ -242,13 +242,14 @@ Vue.component('my-game', { elementArray.push(gameDiv); if (!!this.vr.reserve) { + const shiftIdx = (this.mycolor=="w" ? 0 : 1); let myReservePiecesArray = []; for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++) { myReservePiecesArray.push(h('div', { 'class': {'board':true, ['board'+sizeY]:true}, - attrs: { id: this.getSquareId({x:sizeX,y:i}) } + attrs: { id: this.getSquareId({x:sizeX+shiftIdx,y:i}) } }, [ h('img', @@ -272,7 +273,7 @@ Vue.component('my-game', { oppReservePiecesArray.push(h('div', { 'class': {'board':true, ['board'+sizeY]:true}, - attrs: { id: this.getSquareId({x:sizeX,y:i}) } + attrs: { id: this.getSquareId({x:sizeX+(1-shiftIdx),y:i}) } }, [ h('img', @@ -754,6 +755,8 @@ Vue.component('my-game', { this.possibleMoves = this.mode!="idle" && this.vr.canIplay(this.mycolor,startSquare) ? this.vr.getPossibleMovesFrom(startSquare) : []; + console.log(this.possibleMoves); + console.log(this.vr.promoted); e.target.parentNode.appendChild(this.selectedPiece); } }, diff --git a/public/javascripts/variants/Crazyhouse.js b/public/javascripts/variants/Crazyhouse.js index 9558fe4b..37337650 100644 --- a/public/javascripts/variants/Crazyhouse.js +++ b/public/javascripts/variants/Crazyhouse.js @@ -24,14 +24,10 @@ class CrazyhouseRules extends ChessRules [V.QUEEN]: 0, } }; - // 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]--; - }); - // TODO: keep track of promoted pawns ==> give a pawn if captured. + const [sizeX,sizeY] = VariantRules.size; + this.promoted = doubleArray(sizeX, sizeY, false); + // May be a continuation: adjust numbers of pieces in reserve + promoted pieces + this.moves.forEach(m => { this.updateVariables(m); }); } getColor(i,j) @@ -86,7 +82,7 @@ class CrazyhouseRules extends ChessRules }) ], vanish: [], - start: {x:sizeX, y:y}, //a bit artificial... + start: {x:x, y:y}, //a bit artificial... end: {x:i, y:j} }); moves.push(mv); @@ -99,10 +95,13 @@ class CrazyhouseRules extends ChessRules getPotentialMovesFrom([x,y]) { const sizeX = VariantRules.size[0]; - if (x < sizeX) - return super.getPotentialMovesFrom([x,y]); - // Reserves, outside of board: x == sizeX - return this.getReserveMoves([x,y]); + if (x >= sizeX) + { + // Reserves, outside of board: x == sizeX + return this.getReserveMoves([x,y]); + } + // Standard moves + return super.getPotentialMovesFrom([x,y]); } getAllValidMoves() @@ -111,7 +110,7 @@ class CrazyhouseRules extends ChessRules 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])); + moves = moves.concat(this.getReserveMoves([sizeX+(color=="w"?0:1),i])); return this.filterValid(moves); } @@ -135,25 +134,101 @@ class CrazyhouseRules extends ChessRules updateVariables(move) { super.updateVariables(move); + if (move.vanish.length == 2 && move.appear.length == 2) + return; //skip castle const color = this.turn; - if (move.vanish.length==2) - this.reserve[color][move.vanish[1].p]++; - if (move.vanish.length==0) + const V = VariantRules; + // Three types of move: + // 1. Rebirth: just update material + // 2. Standard move: + // a. check if a promoted piece is moving + // b. check if it's a promotion (mutually exclusive) + // 3. Capture: + // a. check if a promoted piece is captured (and mark move) + // b. check if a promoted piece is moving + // c. check if it's a promotion (mutually exclusive with b) + if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--; + else if (move.vanish.length == 1) + { + if (this.promoted[move.start.x][move.start.y]) + { + this.promoted[move.start.x][move.start.y] = false; + this.promoted[move.end.x][move.end.y] = true; + } + else if (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN) + this.promoted[move.end.x][move.end.y] = true; + } + else //capture + { + if (this.promoted[move.end.x][move.end.y]) + { + move.capturePromoted = true; //required for undo + this.reserve[color][VariantRules.PAWN]++; + this.promoted[move.end.x][move.end.y] = false; + } + else + this.reserve[color][move.vanish[1].p]++; + if (this.promoted[move.start.x][move.start.y]) + { + this.promoted[move.start.x][move.start.y] = false; + this.promoted[move.end.x][move.end.y] = true; + } + else if (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN) + this.promoted[move.end.x][move.end.y] = true; + } } unupdateVariables(move) { super.unupdateVariables(move); const color = this.turn; - if (move.vanish.length==2) - this.reserve[color][move.vanish[1].p]--; - if (move.vanish.length==0) + const V = VariantRules; + if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++; + else if (move.vanish.length == 1) + { + if (this.promoted[move.end.x][move.end.y]) + { + this.promoted[move.end.x][move.end.y] = false; + if (move.vanish[0].p != V.PAWN || move.appear[0].p == V.PAWN) + { + // Not a promotion (= promoted piece creation) + this.promoted[move.start.x][move.start.y] = true; + } + } + } + else //capture + { + if (this.promoted[move.end.x][move.end.y]) + { + this.promoted[move.end.x][move.end.y] = !!move.capturePromoted; + if (move.vanish[0].p != V.PAWN || move.appear[0].p == V.PAWN) + this.promoted[move.start.x][move.start.y] = true; + } + // Un-update material: + if (move.capturePromoted) + this.reserve[color][VariantRules.PAWN]--; + else + this.reserve[color][move.vanish[1].p]--; + } } static get SEARCH_DEPTH() { return 2; } //high branching factor + evalPosition() + { + let evaluation = super.evalPosition(); + // Add reserves: + for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++) + { + const p = VariantRules.RESERVE_PIECES[i]; + evaluation += this.reserve["w"][p] * VariantRules.VALUES[p]; + evaluation -= this.reserve["b"][p] * VariantRules.VALUES[p]; + } + return evaluation; + } + getNotation(move) { if (move.vanish.length > 0) @@ -165,4 +240,13 @@ class CrazyhouseRules extends ChessRules String.fromCharCode(97 + move.end.y) + (VariantRules.size[0]-move.end.x); return piece + "@" + finalSquare; } + + getLongNotation(move) + { + if (move.vanish.length > 0) + return super.getLongNotation(move); + const finalSquare = + String.fromCharCode(97 + move.end.y) + (VariantRules.size[0]-move.end.x); + return "@" + finalSquare; + } } diff --git a/public/javascripts/variants/Loser.js b/public/javascripts/variants/Loser.js index 0c484c12..0ed3edc2 100644 --- a/public/javascripts/variants/Loser.js +++ b/public/javascripts/variants/Loser.js @@ -2,7 +2,8 @@ class LoserRules extends ChessRules { initVariables(fen) { - // No castling, hence no flags + // No castling, hence no flags; but flags defined for compatibility + this.flags = "-"; const epSq = this.moves.length > 0 ? this.getEpSquare(this.lastMove) : undefined; this.epSquares = [ epSq ]; } @@ -102,21 +103,10 @@ class LoserRules extends ChessRules return []; } - play(move, ingame) - { - if (!!ingame) - move.notation = this.getNotation(move); - this.moves.push(move); - this.epSquares.push( this.getEpSquare(move) ); - VariantRules.PlayOnBoard(this.board, move); - } - - undo(move) - { - VariantRules.UndoOnBoard(this.board, move); - this.epSquares.pop(); - this.moves.pop(); - } + // Unused: + updateVariables(move) { } + unupdateVariables(move) { } + parseFlags(flags) { } checkGameEnd() { diff --git a/public/javascripts/variants/Switching.js b/public/javascripts/variants/Switching.js index 3ddf5032..af934f32 100644 --- a/public/javascripts/variants/Switching.js +++ b/public/javascripts/variants/Switching.js @@ -6,6 +6,9 @@ class SwitchingRules extends ChessRules const c = this.getColor(x1,y1); //same as color at square 2 const p1 = this.getPiece(x1,y1); const p2 = this.getPiece(x2,y2); + const V = VariantRules; + if (p1 == V.KING && p2 == V.ROOK) + return []; //avoid duplicate moves (potential conflict with castle) let move = new Move({ appear: [ new PiPo({x:x2,y:y2,c:c,p:p1}), @@ -21,7 +24,6 @@ class SwitchingRules extends ChessRules // 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) || (p2==V.PAWN && x1==lastRank)) { diff --git a/views/rules/Checkered.pug b/views/rules/Checkered.pug index 23ea0f0e..cf347665 100644 --- a/views/rules/Checkered.pug +++ b/views/rules/Checkered.pug @@ -50,14 +50,21 @@ ul h2.stageDelimiter Stage 2 -p.warn This stage is unimplemented for now. +p.warn This stage is not (and probably will never be) implemented. p. During the game one of the two players can decide to take control of the checkered pieces. They thus become autonomous and vulnerable to being captured - stage 2 begins. - The other player is in charge of both the white and black pieces, and tries to eliminate checkered pieces. + The other player is in charge of both the white and black pieces, and tries to + eliminate checkered pieces. The checkered side wins by checkmating either the white or black king. +h4 Variant of stage 2 +p. + An observer could decide to join the game by taking the checkered pieces at any moment. + It then becomes a chess game with three players, with some subtelties to be resolved. + It was tested in some (real life) games organised by the variant creator. + h3 Special moves span Checkered pawns can... @@ -69,5 +76,9 @@ ul h3 Credits ul - li The rules of Checkered Chess were thought up by Patrick Bernier and developed with the help of Benjamin Auder. - li Thanks also to Olive Martin, Christian Poisson, Bevis Martin, Laurent Nouhaud and Frédéric Fradet. + li. + The rules of Checkered Chess were thought up by Patrick Bernier and developed + with the help of Benjamin Auder. + li. + Thanks also to Olive Martin, Christian Poisson, Bevis Martin, Laurent Nouhaud + and Frédéric Fradet. -- 2.44.0