From: Benjamin Auder Date: Mon, 7 Dec 2020 23:21:06 +0000 (+0100) Subject: Some simplifactions, a few fixes, update TODO X-Git-Url: https://git.auder.net/images/js/assets/pieces/cb.svg?a=commitdiff_plain;h=f52671e5b5b50b1421474f27dc9c18f701b559f3;p=vchess.git Some simplifactions, a few fixes, update TODO --- diff --git a/TODO b/TODO index 0212575d..190667cb 100644 --- a/TODO +++ b/TODO @@ -1,15 +1,8 @@ +Implement Wildebeest castle rules +=> (1, 2, 3 or 4 squares slide; randomized: may be impossible >1, but possible >4...) getRepetitionStatus() lose or draw... (for some variants) Chakart: king remote capture, is an option if short range -WON'T IMPLEMENT: -Simultaneous games: view Game + Simultaneous, using component (One)Game -Storage: just key ID => IDs of actual games (in indexedDB) -In Hall challenge: acceptation on sender side (who launch the game when ready --> left click [which just delete if nobody registered, with confirm box]) - -Tournaments: merge with tournament.auder.net code. At the beginning, still admin / users. Later : admin / supervisors / users -=> recurrent tournament, eg. every sunday (late?) afternoon + wednesday evening, Discord-vote for variant? -(Can be replaced by still separated tournament.audeR.net, using vchess credentials...) - Embedded rules language not updated when language is set (in Analyse, Game and Problems) 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: option "confirm moves on touch screen" diff --git a/client/src/variants/Berolina.js b/client/src/variants/Berolina.js index 0765d4b9..15a567ce 100644 --- a/client/src/variants/Berolina.js +++ b/client/src/variants/Berolina.js @@ -54,6 +54,26 @@ export class BerolinaRules extends ChessRules { ); } + getEnpassantCaptures([x, y], shift) { + const Lep = this.epSquares.length; + const epSquare = this.epSquares[Lep - 1]; //always at least one element + if ( + !!epSquare && + epSquare[0].x == x + shift && + epSquare[0].y == y + ) { + let enpassantMove = this.getBasicMove([x, y], [x + shift, y]); + enpassantMove.vanish.push({ + x: x, + y: epSquare[1], + p: "p", + c: this.getColor(x, epSquare[1]) + }); + return [enpassantMove]; + } + return []; + } + // Special pawns movements getPotentialPawnMoves([x, y]) { const color = this.turn; @@ -105,24 +125,13 @@ export class BerolinaRules extends ChessRules { } // Next condition so that other variants could inherit from this class - if (V.PawnSpecs.enPassant) { - // En passant - const Lep = this.epSquares.length; - const epSquare = this.epSquares[Lep - 1]; //always at least one element - if ( - !!epSquare && - epSquare[0].x == x + shiftX && - epSquare[0].y == y - ) { - let enpassantMove = this.getBasicMove([x, y], [x + shiftX, y]); - enpassantMove.vanish.push({ - x: x, - y: epSquare[1], - p: "p", - c: this.getColor(x, epSquare[1]) - }); - moves.push(enpassantMove); - } + if (V.HasEnpassant) { + // NOTE: backward en-passant captures are not considered + // because no rules define them (for now). + Array.prototype.push.apply( + moves, + this.getEnpassantCaptures([x, y], shiftX) + ); } return moves; diff --git a/client/src/variants/Cannibal.js b/client/src/variants/Cannibal.js index 1d8469e2..a0da0fd6 100644 --- a/client/src/variants/Cannibal.js +++ b/client/src/variants/Cannibal.js @@ -112,38 +112,9 @@ export class CannibalRules extends ChessRules { // 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 - }) - ] - }); - - // 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) - }) - ); + let mv = super.getBasicMove([sx, sy], [ex, ey], tr); + if (this.board[ex][ey] != V.EMPTY) { // If the captured piece has a different nature: take it as well if (mv.vanish[0].p != mv.vanish[1].p) { if ( @@ -151,7 +122,8 @@ export class CannibalRules extends ChessRules { 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 mv.appear[0].p = mv.vanish[1].p; } } else if (!!tr && mv.vanish[0].p != V.PAWN) diff --git a/client/src/variants/Colorbound.js b/client/src/variants/Colorbound.js index b806184c..85f81ef3 100644 --- a/client/src/variants/Colorbound.js +++ b/client/src/variants/Colorbound.js @@ -35,59 +35,10 @@ export class ColorboundRules extends ChessRules { 'k': 'k' }; - let pieces = { w: new Array(8), b: new Array(8) }; - let flags = ""; - // Shuffle pieces on first (and last rank if randomness == 2) - for (let c of ["w", "b"]) { - if (c == 'b' && randomness == 1) { - pieces['b'] = pieces['w'].map(p => piecesMap[p]); - flags += flags; - break; - } - - // TODO: same code as in base_rules. Should extract and factorize? - - let positions = ArrayFun.range(8); - - let randIndex = 2 * randInt(4); - const bishop1Pos = positions[randIndex]; - let randIndex_tmp = 2 * randInt(4) + 1; - const bishop2Pos = positions[randIndex_tmp]; - positions.splice(Math.max(randIndex, randIndex_tmp), 1); - positions.splice(Math.min(randIndex, randIndex_tmp), 1); - - randIndex = randInt(6); - const knight1Pos = positions[randIndex]; - positions.splice(randIndex, 1); - randIndex = randInt(5); - const knight2Pos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(4); - const queenPos = positions[randIndex]; - positions.splice(randIndex, 1); - - const rook1Pos = positions[0]; - const kingPos = positions[1]; - const rook2Pos = positions[2]; - - pieces[c][rook1Pos] = "r"; - pieces[c][knight1Pos] = "n"; - pieces[c][bishop1Pos] = "b"; - pieces[c][queenPos] = "q"; - pieces[c][kingPos] = "k"; - pieces[c][bishop2Pos] = "b"; - pieces[c][knight2Pos] = "n"; - pieces[c][rook2Pos] = "r"; - if (c == 'b') pieces[c] = pieces[c].map(p => piecesMap[p]); - flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos); - } - // Add turn + flags + enpassant + const baseFen = ChessRules.GenRandInitFen(randomness); return ( - pieces["b"].join("") + - "/pppppppp/8/8/8/8/PPPPPPPP/" + - pieces["w"].join("").toUpperCase() + - " w 0 " + flags + " -" + baseFen.substr(0, 8).split('').map(p => piecesMap[p]).join('') + + baseFen.substr(8) ); } diff --git a/client/src/variants/Doublearmy.js b/client/src/variants/Doublearmy.js index 1846aa3c..8a6c806f 100644 --- a/client/src/variants/Doublearmy.js +++ b/client/src/variants/Doublearmy.js @@ -3,7 +3,7 @@ import { ChessRules } from "@/base_rules"; // Ideas with 2 kings: // Stage 1 {w, b} : 2 kings on board, value 5. // Stage 2: only one, get mated and all that, value 1000 -// ...But the middle king will get captured quickly... +// ...But the middle king will be captured quickly... export class DoublearmyRules extends ChessRules { diff --git a/client/src/variants/Dynamo.js b/client/src/variants/Dynamo.js index 67065d7c..35c41c58 100644 --- a/client/src/variants/Dynamo.js +++ b/client/src/variants/Dynamo.js @@ -767,6 +767,8 @@ export class DynamoRules extends ChessRules { play(move) { move.flags = JSON.stringify(this.aggregateFlags()); V.PlayOnBoard(this.board, move); + // NOTE; if subTurn == 1, there may be no available moves at subTurn == 2. + // However, it's quite easier to wait for a user click. if (this.subTurn == 2) { const L = this.firstMove.length; this.amoves.push(this.getAmove(this.firstMove[L-1], move)); diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js index 1fbc5d0b..c380610c 100644 --- a/client/src/variants/Eightpieces.js +++ b/client/src/variants/Eightpieces.js @@ -246,45 +246,6 @@ export class EightpiecesRules extends ChessRules { return null; } - // 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 : initColor, - p: tr ? tr.p : initPiece - }) - ], - vanish: [ - new PiPo({ - x: sx, - y: sy, - c: initColor, - p: initPiece - }) - ] - }); - - // 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) - }) - ); - } - - return mv; - } - canIplay(side, [x, y]) { return ( (this.subTurn == 1 && this.turn == side && this.getColor(x, y) == side) diff --git a/client/src/variants/Fullcavalry.js b/client/src/variants/Fullcavalry.js index 98549db4..0eb53f1f 100644 --- a/client/src/variants/Fullcavalry.js +++ b/client/src/variants/Fullcavalry.js @@ -114,46 +114,6 @@ export class FullcavalryRules extends ChessRules { ); } - // Because of the lancers, getPiece() could be wrong: - // use board[x][y][1] instead (always valid). - // TODO: base implementation now uses this too (no?) - 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 - }) - ] - }); - - // 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) - }) - ); - } - - return mv; - } - getPotentialMovesFrom([x, y]) { if (this.getPiece(x, y) == V.LANCER) return this.getPotentialLancerMoves([x, y]); @@ -162,12 +122,8 @@ export class FullcavalryRules extends ChessRules { getPotentialPawnMoves([x, y]) { const color = this.getColor(x, y); - let moves = []; - const [sizeX, sizeY] = [V.size.x, V.size.y]; let shiftX = (color == "w" ? -1 : 1); - const startRank = color == "w" ? sizeX - 2 : 1; - const lastRank = color == "w" ? 0 : sizeX - 1; - + const lastRank = (color == "w" ? 0 : 7); let finalPieces = [V.PAWN]; if (x + shiftX == lastRank) { // Only allow direction facing inside board: @@ -177,46 +133,7 @@ export class FullcavalryRules extends ChessRules { : ['c', 'd', 'e', 'm', 'o']; finalPieces = allowedLancerDirs.concat([V.KNIGHT, V.BISHOP, V.QUEEN]); } - if (this.board[x + shiftX][y] == V.EMPTY) { - // One square forward - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y], { - c: color, - p: piece - }) - ); - } - if (x == startRank && this.board[x + 2 * shiftX][y] == V.EMPTY) - // Two squares jump - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); - } - // Captures - for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - for (let piece of finalPieces) { - moves.push( - this.getBasicMove([x, y], [x + shiftX, y + shiftY], { - c: color, - p: piece - }) - ); - } - } - } - - // Add en-passant captures - Array.prototype.push.apply( - moves, - this.getEnpassantCaptures([x, y], shiftX) - ); - - return moves; + return super.getPotentialPawnMoves([x, y], finalPieces); } // Obtain all lancer moves in "step" direction @@ -375,12 +292,8 @@ export class FullcavalryRules extends ChessRules { this.getColor(coord.x, coord.y) == color ) ) { - if ( - this.getPiece(coord.x, coord.y) == V.LANCER && - !this.isImmobilized([coord.x, coord.y]) - ) { + if (this.getPiece(coord.x, coord.y) == V.LANCER) lancerPos.push({x: coord.x, y: coord.y}); - } coord.x += step[0]; coord.y += step[1]; } @@ -419,6 +332,10 @@ export class FullcavalryRules extends ChessRules { return moves.filter(m => m.vanish.length == 1); } + static get SEARCH_DEPTH() { + return 2; + } + getNotation(move) { let notation = super.getNotation(move); if (Object.keys(V.LANCER_DIRNAMES).includes(move.vanish[0].p)) diff --git a/client/src/variants/Knightrelay1.js b/client/src/variants/Knightrelay1.js index 1e7bfabb..aba95290 100644 --- a/client/src/variants/Knightrelay1.js +++ b/client/src/variants/Knightrelay1.js @@ -6,7 +6,19 @@ export class Knightrelay1Rules extends ChessRules { return false; } - // TODO: IsGoodPosition to check that 2 knights are on the board... + static IsGoodPosition(position) { + if (!ChessRules.IsGoodPosition(position)) return false; + // Check that (at least) 2 knights per side are on the board + const rows = position.split("/"); + let knights = { 'N': 0, 'n': 0 }; + for (let row of rows) { + for (let i = 0; i < row.length; i++) { + if (['N','n'].includes(row[i])) knights[row[i]]++; + } + } + if (Object.values(knights).some(v => v < 2)) return false; + return true; + } getPotentialMovesFrom([x, y]) { let moves = super.getPotentialMovesFrom([x, y]); diff --git a/client/src/variants/Perfect.js b/client/src/variants/Perfect.js index 4bd2ac92..0c5e6e11 100644 --- a/client/src/variants/Perfect.js +++ b/client/src/variants/Perfect.js @@ -138,67 +138,50 @@ export class PerfectRules extends ChessRules { if (randomness == 0) return "esqakbnr/pppppppp/8/8/8/8/PPPPPPPP/ESQAKBNR w 0 ahah -"; - let pieces = { w: new Array(8), b: new Array(8) }; - let flags = ""; - let whiteBishopPos = -1; - for (let c of ["w", "b"]) { - if (c == 'b' && randomness == 1) { - pieces['b'] = pieces['w']; - flags += flags; - break; + const baseFen = ChessRules.GenRandInitFen(randomness); + const fenParts = baseFen.split(' '); + const posParts = fenParts[0].split('/'); + + // Replace a random rook per side by an empress, + // a random knight by a princess, and a bishop by an amazon + // (Constraint: the two remaining bishops on different colors). + + let newPos = { 0: "", 7: "" }; + let amazonOddity = -1; + for (let rank of [0, 7]) { + let replaced = { 'b': -2, 'n': -2, 'r': -2 }; + for (let i = 0; i < 8; i++) { + const curChar = posParts[rank].charAt(i).toLowerCase(); + if (['b', 'n', 'r'].includes(curChar)) { + if ( + replaced[curChar] == -1 || + (curChar == 'b' && rank == 7 && i % 2 == amazonOddity) || + ( + (curChar != 'b' || rank == 0) && + replaced[curChar] == -2 && + randInt(2) == 0 + ) + ) { + replaced[curChar] = i; + if (curChar == 'b') { + if (amazonOddity < 0) amazonOddity = i % 2; + newPos[rank] += 'a'; + } + else if (curChar == 'r') newPos[rank] += 'e'; + else newPos[rank] += 's'; + } + else { + if (replaced[curChar] == -2) replaced[curChar]++; + newPos[rank] += curChar; + } + } + else newPos[rank] += curChar; } - - let positions = ArrayFun.range(8); - - // Get random squares for bishop: if black, pick a different color - // than where the white one stands. - let randIndex = - c == 'w' - ? randInt(8) - : 2 * randInt(4) + (1 - whiteBishopPos % 2); - if (c == 'w') whiteBishopPos = randIndex; - const bishopPos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(7); - const knightPos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(6); - const queenPos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(5); - const amazonPos = positions[randIndex]; - positions.splice(randIndex, 1); - - randIndex = randInt(4); - const princessPos = positions[randIndex]; - positions.splice(randIndex, 1); - - // Rook, empress and king positions are now almost fixed, - // only the ordering rook->empress or empress->rook must be decided. - let rookPos = positions[0]; - let empressPos = positions[2]; - const kingPos = positions[1]; - flags += V.CoordToColumn(rookPos) + V.CoordToColumn(empressPos); - if (Math.random() < 0.5) [rookPos, empressPos] = [empressPos, rookPos]; - - pieces[c][rookPos] = "r"; - pieces[c][knightPos] = "n"; - pieces[c][bishopPos] = "b"; - pieces[c][queenPos] = "q"; - pieces[c][kingPos] = "k"; - pieces[c][amazonPos] = "a"; - pieces[c][princessPos] = "s"; - pieces[c][empressPos] = "e"; } - // Add turn + flags + enpassant + return ( - pieces["b"].join("") + - "/pppppppp/8/8/8/8/PPPPPPPP/" + - pieces["w"].join("").toUpperCase() + - " w 0 " + flags + " -" + newPos[0] + "/" + posParts.slice(1, 7).join('/') + "/" + + newPos[7].toUpperCase() + " " + fenParts.slice(1, 5).join(' ') + " -" ); } diff --git a/client/src/variants/Swap.js b/client/src/variants/Swap.js index 9c3407b7..167adeae 100644 --- a/client/src/variants/Swap.js +++ b/client/src/variants/Swap.js @@ -270,28 +270,8 @@ export class SwapRules extends ChessRules { const color = this.turn; const oppCol = V.GetOppCol(this.turn); - // Search best (half) move for opponent turn (TODO: a bit too slow) -// const getBestMoveEval = () => { -// let score = this.getCurrentScore(); -// if (score != "*") return maxeval * (score == "1-0" ? 1 : -1); -// let moves = this.getAllValidMoves(); -// let res = (oppCol == "w" ? -maxeval : maxeval); -// for (let m of moves) { -// this.play(m); -// score = this.getCurrentScore(); -// // Now turn is oppCol,2 if m allow a swap and movesCount >= 2 -// // Otherwise it's color,1. In both cases the next test makes sense -// if (score != "*") { -// // Game over -// this.undo(m); -// return maxeval * (score == "1-0" ? 1 : -1); -// } -// const evalPos = this.evalPosition(); -// res = oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos); -// this.undo(m); -// } -// return res; -// }; + // NOTE: searching best (half) move for opponent turn is a bit too slow. + // => Only 2 half moves depth here. const moves11 = this.getAllValidMoves(); if (this.movesCount == 0) @@ -312,7 +292,6 @@ export class SwapRules extends ChessRules { let moves12 = this.getAllValidMoves(); for (let j = 0; j < moves12.length; j++) { this.play(moves12[j]); -// const evalMove = getBestMoveEval() + 0.05 - Math.random() / 10; const evalMove = this.evalPosition() + 0.05 - Math.random() / 10; if ( !bestMove || diff --git a/client/src/variants/Takenmake.js b/client/src/variants/Takenmake.js index 5c0ac08e..9549c2a9 100644 --- a/client/src/variants/Takenmake.js +++ b/client/src/variants/Takenmake.js @@ -23,7 +23,7 @@ export class TakenmakeRules extends ChessRules { switch (asA || piece) { case V.PAWN: if (!asA || piece == V.PAWN) - moves = this.getPotentialPawnMoves([x, y]); + moves = super.getPotentialPawnMoves([x, y]); else { // Special case: we don't want promotion, since just moving like // a pawn, but I'm in fact not a pawn :) diff --git a/client/src/variants/Twokings.js b/client/src/variants/Twokings.js index a5bd76af..6606da58 100644 --- a/client/src/variants/Twokings.js +++ b/client/src/variants/Twokings.js @@ -15,7 +15,7 @@ export class TwokingsRules extends CoregalRules { if (position.length == 0) return false; const rows = position.split("/"); if (rows.length != V.size.x) return false; - let kings = { "w": 0, "b": 0 }; + let kings = { 'K': 0, 'k': 0 }; for (let row of rows) { let sumElts = 0; for (let i = 0; i < row.length; i++) { diff --git a/client/src/variants/Wildebeest.js b/client/src/variants/Wildebeest.js index 6317c4bb..7a9e35c4 100644 --- a/client/src/variants/Wildebeest.js +++ b/client/src/variants/Wildebeest.js @@ -160,7 +160,7 @@ export class WildebeestRules extends ChessRules { for (let epsq of epSquare) { // TODO: some redundant checks if (epsq.x == x + shiftX && Math.abs(epsq.y - y) == 1) { - var enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]); + let enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]); // WARNING: the captured pawn may be diagonally behind us, // if it's a 3-squares jump and we take on 1st passing square const px = this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX; @@ -178,8 +178,6 @@ export class WildebeestRules extends ChessRules { return moves; } - // TODO: wildebeest castle - getPotentialCamelMoves(sq) { return this.getSlideNJumpMoves(sq, V.steps[V.CAMEL], "oneStep"); }