+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"
);
}
+ 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;
}
// 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;
// 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 (
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)
'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)
);
}
// 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 {
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));
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)
);
}
- // 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]);
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:
: ['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
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];
}
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))
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]);
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(' ') + " -"
);
}
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)
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 ||
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 :)
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++) {
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;
return moves;
}
- // TODO: wildebeest castle
-
getPotentialCamelMoves(sq) {
return this.getSlideNJumpMoves(sq, V.steps[V.CAMEL], "oneStep");
}