--- /dev/null
+import ChessRules from "/base_rules.js";
+import PiPo from "/utils/PiPo.js";
+import Move from "/utils/Move.js";
+
+export default class ChainingRules extends ChessRules {
+
+ static get Options() {
+ return {
+ select: C.Options.select,
+ input: C.Options.input,
+ styles: ["atomic", "capture", "crazyhouse", "cylinder", "dark", "zen"]
+ };
+ }
+
+ get hasSelfCaptures() {
+ return true;
+ }
+
+ canSelfTake() {
+ return true; //self captures induce chaining
+ }
+
+ setOtherVariables(fenParsed, pieceArray) {
+ super.setOtherVariables(fenParsed, pieceArray);
+ // Stack of "last move" only for intermediate chaining
+ this.lastMoveEnd = [];
+ }
+
+ getBasicMove([sx, sy], [ex, ey], tr) {
+ const L = this.lastMoveEnd.length;
+ const piece = (L >= 1 ? this.lastMoveEnd[L-1].p : null);
+ if (
+ this.board[ex][ey] == "" ||
+ this.getColor(ex, ey) == C.GetOppTurn(this.turn)
+ ) {
+ if (piece && !tr)
+ tr = {c: this.turn, p: piece};
+ let mv = super.getBasicMove([sx, sy], [ex, ey], tr);
+ if (piece)
+ mv.vanish.pop(); //end of a chain: initial piece remains
+ return mv;
+ }
+ // (Self)Capture: initial, or inside a chain
+ const initPiece = (piece || this.getPiece(sx, sy)),
+ destPiece = this.getPiece(ex, ey);
+ let mv = new Move({
+ start: {x: sx, y: sy},
+ end: {x: ex, y: ey},
+ appear: [
+ new PiPo({
+ x: ex,
+ y: ey,
+ c: this.turn,
+ p: (!!tr ? tr.p : initPiece)
+ })
+ ],
+ vanish: [
+ new PiPo({
+ x: ex,
+ y: ey,
+ c: this.turn,
+ p: destPiece
+ })
+ ]
+ });
+ if (!piece) {
+ // Initial capture
+ mv.vanish.unshift(
+ new PiPo({
+ x: sx,
+ y: sy,
+ c: this.turn,
+ p: initPiece
+ })
+ );
+ }
+ mv.chained = destPiece; //easier (no need to detect it)
+ return mv;
+ }
+
+ getPiece(x, y) {
+ const L = this.lastMoveEnd.length;
+ if (L >= 1 && this.lastMoveEnd[L-1].x == x && this.lastMoveEnd[L-1].y == y)
+ return this.lastMoveEnd[L-1].p;
+ return super.getPiece(x, y);
+ }
+
+ getPotentialMovesFrom([x, y], color) {
+ const L = this.lastMoveEnd.length;
+ if (
+ L >= 1 &&
+ (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y)
+ ) {
+ // A self-capture was played: wrong square
+ return [];
+ }
+ return super.getPotentialMovesFrom([x, y], color);
+ }
+
+ isLastMove(move) {
+ return !move.chained;
+ }
+
+ postPlay(move) {
+ super.postPlay(move);
+ if (!!move.converted) {
+ this.lastMoveEnd.push({
+ x: move.end.x,
+ y: move.end.y,
+ p: move.chained
+ });
+ }
+ else
+ this.lastMoveEnd = [];
+ }
+
+};
export default class ConvertRules extends ChessRules {
- // TODO: options ? (balance progressive ok it seems?)
static get Options() {
return {
select: C.Options.select,
- input: C.Options.input,
- styles: [
- "atomic", "cannibal", "capture", "cylinder",
- "dark", "madrasi", "rifle", "teleport"
- ]
+ styles: ["cylinder", "dark", "recycle", "teleport"]
};
}
getBasicMove([sx, sy], [ex, ey], tr) {
const L = this.lastMoveEnd.length;
- const lm = this.lastMoveEnd[L-1];
- const piece = (!!lm ? lm.p : null);
- const c = this.turn;
+ const piece = (L >= 1 ? this.lastMoveEnd[L-1].p : null);
if (this.board[ex][ey] == "") {
if (piece && !tr)
- tr = {c: c, p: piece};
+ tr = {c: this.turn, p: piece};
let mv = super.getBasicMove([sx, sy], [ex, ey], tr);
if (piece)
- mv.vanish.pop();
+ mv.vanish.pop(); //end of a chain: initial piece remains
return mv;
}
// Capture: initial, or inside a chain
- const initPiece = (piece || this.getPiece(sx, sy));
- const oppCol = C.GetOppTurn(c);
- const oppPiece = this.getPiece(ex, ey);
+ const initPiece = (piece || this.getPiece(sx, sy)),
+ destPiece = this.getPiece(ex, ey);
let mv = new Move({
start: {x: sx, y: sy},
end: {x: ex, y: ey},
new PiPo({
x: ex,
y: ey,
- c: c,
+ c: this.turn,
p: (!!tr ? tr.p : initPiece)
})
],
new PiPo({
x: ex,
y: ey,
- c: oppCol,
- p: oppPiece
+ c: C.GetOppTurn(this.turn),
+ p: destPiece
})
]
});
new PiPo({
x: sx,
y: sy,
- c: c,
+ c: this.turn,
p: initPiece
})
);
}
+ mv.converted = destPiece; //easier (no need to detect it)
return mv;
}
return false;
}
- underAttack([x, y], color) {
+ underAttack([x, y], [color]) {
let explored = [];
return this.underAttack_aux([x, y], color, explored);
}
}
isLastMove(move) {
- return (
- super.isLastMove(move) ||
- move.vanish.length <= 1 ||
- move.vanish[1].c != move.vanish[0].c ||
- move.appear.length == 2 //castle!
- );
+ return !move.converted;
}
postPlay(move) {
super.postPlay(move);
- if (!this.isLastMove(move)) {
+ if (!!move.converted) {
this.lastMoveEnd.push({
x: move.end.x,
y: move.end.y,
- p: move.vanish[1].p //TODO: check this
+ p: move.converted
});
}
- }
-
-};
-
-// TODO: wrong rules! mismatch Convert (taking opponent pieces) and chaining (tend to be this) taking own units (with normal initial position).
-// Initial Convert:
-
-import { ChessRules, PiPo, Move } from "@/base_rules";
-import { randInt } from "@/utils/alea";
-
-export class ConvertRules extends ChessRules {
-
- static get HasEnpassant() {
- return false;
- }
-
- setOtherVariables(fen) {
- super.setOtherVariables(fen);
- // Stack of "last move" only for intermediate chaining
- this.lastMoveEnd = [null];
- }
-
- static GenRandInitFen(options) {
- const baseFen = ChessRules.GenRandInitFen(options);
- return (
- baseFen.substr(0, 8) +
- "/8/pppppppp/8/8/PPPPPPPP/8/" +
- baseFen.substr(35, 17)
- );
- }
-
- getBasicMove([sx, sy], [ex, ey], tr) {
- const L = this.lastMoveEnd.length;
- const lm = this.lastMoveEnd[L-1];
- const piece = (!!lm ? lm.p : null);
- const c = this.turn;
- if (this.board[ex][ey] == V.EMPTY) {
- if (!!piece && !tr) tr = { c: c, p: piece }
- let mv = super.getBasicMove([sx, sy], [ex, ey], tr);
- if (!!piece) mv.vanish.pop();
- return mv;
- }
- // Capture: initial, or inside a chain
- const initPiece = (piece || this.getPiece(sx, sy));
- const oppCol = V.GetOppCol(c);
- const oppPiece = this.getPiece(ex, ey);
- let mv = new Move({
- start: { x: sx, y: sy },
- end: { x: ex, y: ey },
- appear: [
- new PiPo({
- x: ex,
- y: ey,
- c: c,
- p: (!!tr ? tr.p : initPiece)
- })
- ],
- vanish: [
- new PiPo({
- x: ex,
- y: ey,
- c: oppCol,
- p: oppPiece
- })
- ]
- });
- if (!piece) {
- // Initial capture
- mv.vanish.unshift(
- new PiPo({
- x: sx,
- y: sy,
- c: c,
- p: initPiece
- })
- );
- }
- // TODO: This "converted" indication isn't needed in fact,
- // because it can be deduced from the move itself.
- mv.end.converted = oppPiece;
- return mv;
- }
-
- getPotentialMovesFrom([x, y], asA) {
- const L = this.lastMoveEnd.length;
- if (!!this.lastMoveEnd[L-1]) {
- if (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y)
- // A capture was played: wrong square
- return [];
- asA = this.lastMoveEnd[L-1].p;
- }
- switch (asA || this.getPiece(x, y)) {
- case V.PAWN: return super.getPotentialPawnMoves([x, y]);
- case V.ROOK: return super.getPotentialRookMoves([x, y]);
- case V.KNIGHT: return super.getPotentialKnightMoves([x, y]);
- case V.BISHOP: return super.getPotentialBishopMoves([x, y]);
- case V.QUEEN: return super.getPotentialQueenMoves([x, y]);
- case V.KING: return super.getPotentialKingMoves([x, y]);
- }
- return [];
- }
-
- getPossibleMovesFrom(sq) {
- const L = this.lastMoveEnd.length;
- let asA = undefined;
- if (!!this.lastMoveEnd[L-1]) {
- if (
- sq[0] != this.lastMoveEnd[L-1].x ||
- sq[1] != this.lastMoveEnd[L-1].y
- ) {
- return [];
- }
- asA = this.lastMoveEnd[L-1].p;
- }
- return this.filterValid(this.getPotentialMovesFrom(sq, asA));
- }
-
- isAttacked_aux([x, y], color, explored) {
- if (explored.some(sq => sq[0] == x && sq[1] == y))
- // Start of an infinite loop: exit
- return false;
- explored.push([x, y]);
- if (super.isAttacked([x, y], color)) return true;
- // Maybe indirect "chaining" attack:
- const myColor = this.turn
- let res = false;
- let toCheck = []; //check all but king (no need)
- // Pawns:
- const shiftToPawn = (myColor == 'w' ? -1 : 1);
- for (let yShift of [-1, 1]) {
- const [i, j] = [x + shiftToPawn, y + yShift];
- if (
- V.OnBoard(i, j) &&
- this.board[i][j] != V.EMPTY &&
- // NOTE: no need to check color (no enemy pawn can take directly)
- this.getPiece(i, j) == V.PAWN
- ) {
- toCheck.push([i, j]);
- }
- }
- // Knights:
- V.steps[V.KNIGHT].forEach(s => {
- const [i, j] = [x + s[0], y + s[1]];
- if (
- V.OnBoard(i, j) &&
- this.board[i][j] != V.EMPTY &&
- this.getPiece(i, j) == V.KNIGHT
- ) {
- toCheck.push([i, j]);
- }
- });
- // Sliders:
- V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
- let [i, j] = [x + s[0], y + s[1]];
- while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
- i += s[0];
- j += s[1];
- }
- if (!V.OnBoard(i, j)) return;
- const piece = this.getPiece(i, j);
- if (
- piece == V.QUEEN ||
- (piece == V.ROOK && (s[0] == 0 || s[1] == 0)) ||
- (piece == V.BISHOP && (s[0] != 0 && s[1] != 0))
- ) {
- toCheck.push([i, j]);
- }
- });
- for (let ij of toCheck) {
- if (this.isAttacked_aux(ij, color, explored)) return true;
- }
- return false;
- }
-
- isAttacked([x, y], color) {
- let explored = [];
- return this.isAttacked_aux([x, y], color, explored);
- }
-
- filterValid(moves) {
- // No "checks" (except to forbid castle)
- return moves;
- }
-
- getCheckSquares() {
- return [];
- }
-
- prePlay(move) {
- const c = this.turn;
- // Extra conditions to avoid tracking converted kings:
- if (
- move.appear[0].p == V.KING &&
- move.vanish.length >= 1 &&
- move.vanish[0].p == V.KING
- ) {
- this.kingPos[c][0] = move.appear[0].x;
- this.kingPos[c][1] = move.appear[0].y;
- }
- }
-
- play(move) {
- this.prePlay(move);
- const c = this.turn;
- move.flags = JSON.stringify(this.aggregateFlags());
- V.PlayOnBoard(this.board, move);
- if (!move.end.converted) {
- // Not a capture: change turn
- this.turn = V.GetOppCol(this.turn);
- this.movesCount++;
- this.lastMoveEnd.push(null);
- }
- else {
- this.lastMoveEnd.push(
- Object.assign({}, move.end, { p: move.end.converted })
- );
- }
- super.updateCastleFlags(move, move.appear[0].p, c);
- }
-
- undo(move) {
- this.disaggregateFlags(JSON.parse(move.flags));
- this.lastMoveEnd.pop();
- V.UndoOnBoard(this.board, move);
- if (!move.end.converted) {
- this.turn = V.GetOppCol(this.turn);
- this.movesCount--;
- }
- this.postUndo(move);
- }
-
- postUndo(move) {
- const c = this.getColor(move.start.x, move.start.y);
- if (
- move.appear[0].p == V.KING &&
- move.vanish.length >= 1 &&
- move.vanish[0].p == V.KING
- ) {
- this.kingPos[c] = [move.start.x, move.start.y];
- }
- }
-
- getCurrentScore() {
- const color = this.turn;
- const kp = this.kingPos[color];
- if (this.getColor(kp[0], kp[1]) != color)
- return (color == "w" ? "0-1" : "1-0");
- if (!super.atLeastOneMove()) return "1/2";
- return "*";
- }
-
- getComputerMove() {
- let initMoves = this.getAllValidMoves();
- if (initMoves.length == 0) return null;
- // Loop until valid move is found (no blocked pawn conversion...)
- while (true) {
- let moves = JSON.parse(JSON.stringify(initMoves));
- let mvArray = [];
- let mv = null;
- // Just play random moves (for now at least. TODO?)
- while (moves.length > 0) {
- mv = moves[randInt(moves.length)];
- mvArray.push(mv);
- this.play(mv);
- if (!!mv.end.converted)
- // A piece was just converted
- moves = this.getPotentialMovesFrom([mv.end.x, mv.end.y]);
- else break;
- }
- for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
- if (!mv.end.converted) return (mvArray.length > 1 ? mvArray : mvArray[0]);
- }
- return null; //never reached
- }
-
- getNotation(move) {
- if (move.appear.length == 2 && move.appear[0].p == V.KING)
- return (move.end.y < move.start.y ? "0-0-0" : "0-0");
- const c = this.turn;
- const L = this.lastMoveEnd.length;
- const lm = this.lastMoveEnd[L-1];
- const piece = (!lm ? move.appear[0].p : lm.p);
- // Basic move notation:
- let notation = piece.toUpperCase();
- if (
- this.board[move.end.x][move.end.y] != V.EMPTY ||
- (piece == V.PAWN && move.start.y != move.end.y)
- ) {
- notation += "x";
- }
- const finalSquare = V.CoordsToSquare(move.end);
- notation += finalSquare;
-
- // Add potential promotion indications:
- const firstLastRank = (c == 'w' ? [7, 0] : [0, 7]);
- if (move.end.x == firstLastRank[1] && piece == V.PAWN)
- notation += "=" + move.appear[0].p.toUpperCase();
- return notation;
+ else
+ this.lastMoveEnd = [];
}
};