import { shuffle } from "@/utils/alea";
export class BallRules extends ChessRules {
+
+ static get Lines() {
+ return [
+ // White goal:
+ [[0, 3], [0, 6]],
+ [[0, 6], [1, 6]],
+ [[1, 6], [1, 3]],
+ [[1, 3], [0, 3]],
+ // Black goal:
+ [[9, 3], [9, 6]],
+ [[9, 6], [8, 6]],
+ [[8, 6], [8, 3]],
+ [[8, 3], [9, 3]]
+ ];
+ }
+
static get PawnSpecs() {
return Object.assign(
{},
ChessRules.PawnSpecs,
- { promotions: ChessRules.PawnSpecs.promotions.concat([V.CHAMPION]) }
+ { promotions: ChessRules.PawnSpecs.promotions.concat([V.PHOENIX]) }
);
}
static get HasFlags() {
return false;
}
- static get HasCastle() {
- return false;
- }
- static get CHAMPION() {
+ static get PHOENIX() {
return 'h';
}
return "aa";
}
- // Special code for "something to fill space" (around goals)
- // --> If goal is outside the board (current prototype: it's inside)
-// static get FILL() {
-// return "ff";
-// }
-
static get HAS_BALL_CODE() {
return {
'p': 's',
'b': 'c',
'q': 't',
'k': 'l',
- 'h': 'd'
+ 'h': 'i'
};
}
'c': 'b',
't': 'q',
'l': 'k',
- 'd': 'h'
+ 'i': 'h'
};
}
static get PIECES() {
return ChessRules.PIECES
- .concat([V.CHAMPION])
+ .concat([V.PHOENIX])
.concat(Object.keys(V.HAS_BALL_DECODE))
.concat(['a']);
}
return ChessRules.fen2board(f);
}
+ static ParseFen(fen) {
+ return Object.assign(
+ ChessRules.ParseFen(fen),
+ { pmove: fen.split(" ")[4] }
+ );
+ }
+
// Check that exactly one ball is on the board
// + at least one piece per color.
static IsGoodPosition(position) {
const rows = position.split("/");
if (rows.length != V.size.x) return false;
let pieces = { "w": 0, "b": 0 };
- const withBall = Object.keys(V.HAS_BALL_DECODE).concat([V.BALL]);
+ const withBall = Object.keys(V.HAS_BALL_DECODE).concat(['a']);
let ballCount = 0;
for (let row of rows) {
let sumElts = 0;
for (let i = 0; i < row.length; i++) {
const lowerRi = row[i].toLowerCase();
if (V.PIECES.includes(lowerRi)) {
- if (lowerRi != V.BALL) pieces[row[i] == lowerRi ? "b" : "w"]++;
+ if (lowerRi != 'a') pieces[row[i] == lowerRi ? "b" : "w"]++;
if (withBall.includes(lowerRi)) ballCount++;
sumElts++;
- } else {
- const num = parseInt(row[i]);
+ }
+ else {
+ const num = parseInt(row[i], 10);
if (isNaN(num)) return false;
sumElts += num;
}
return true;
}
+ static IsGoodFen(fen) {
+ if (!ChessRules.IsGoodFen(fen)) return false;
+ const fenParts = fen.split(" ");
+ if (fenParts.length != 5) return false;
+ if (
+ fenParts[4] != "-" &&
+ !fenParts[4].match(/^([a-i][1-9]){2,2}$/)
+ ) {
+ return false;
+ }
+ return true;
+ }
+
getPpath(b) {
let prefix = "";
const withPrefix =
Object.keys(V.HAS_BALL_DECODE)
- .concat([V.CHAMPION])
- .concat(['a']);
+ .concat([V.PHOENIX])
+ .concat(['a', 'w']); //TODO: 'w' for backward compatibility - to remove
if (withPrefix.includes(b[1])) prefix = "Ball/";
return prefix + b;
}
+ getPPpath(m) {
+ if (
+ m.vanish.length == 2 &&
+ m.appear.length == 2 &&
+ m.appear[0].c != m.appear[1].c
+ ) {
+ // Take ball in place (from opponent)
+ return "Ball/inplace";
+ }
+ return super.getPPpath(m);
+ }
+
canTake([x1, y1], [x2, y2]) {
- // Capture enemy or pass ball to friendly pieces
+ if (this.getColor(x1, y1) !== this.getColor(x2, y2)) {
+ // The piece holding the ball cannot capture:
+ return (
+ !(Object.keys(V.HAS_BALL_DECODE)
+ .includes(this.board[x1][y1].charAt(1)))
+ );
+ }
+ // Pass: possible only if one of the friendly pieces has the ball
return (
- this.getColor(x1, y1) !== this.getColor(x2, y2) ||
- Object.keys(V.HAS_BALL_DECODE).includes(this.board[x1][y1].charAt(1))
+ Object.keys(V.HAS_BALL_DECODE).includes(this.board[x1][y1].charAt(1)) ||
+ Object.keys(V.HAS_BALL_DECODE).includes(this.board[x2][y2].charAt(1))
);
}
- getCheckSquares(color) {
- return [];
+ getFen() {
+ return super.getFen() + " " + this.getPmoveFen();
}
- static GenRandInitFen(randomness) {
- if (randomness == 0)
- return "rnbhqhnbr/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/RNBHQHNBR w 0 -";
+ getFenForRepeat() {
+ return super.getFenForRepeat() + "_" + this.getPmoveFen();
+ }
+
+ getPmoveFen() {
+ const L = this.pmoves.length;
+ if (!this.pmoves[L-1]) return "-";
+ return (
+ V.CoordsToSquare(this.pmoves[L-1].start) +
+ V.CoordsToSquare(this.pmoves[L-1].end)
+ );
+ }
+
+ static GenRandInitFen(options) {
+ if (options.randomness == 0)
+ return "hbnrqrnhb/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/HBNRQRNHB w 0 - -";
let pieces = { w: new Array(9), b: new Array(9) };
for (let c of ["w", "b"]) {
- if (c == 'b' && randomness == 1) {
+ if (c == 'b' && options.randomness == 1) {
pieces['b'] = pieces['w'];
break;
}
- // Get random squares for every piece, totally freely
+ // Get random squares for every piece, with bishops and phoenixes
+ // on different colors:
let positions = shuffle(ArrayFun.range(9));
- const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'h', 'h', 'q'];
- const rem2 = positions[0] % 2;
+ const composition = ['b', 'b', 'h', 'h', 'n', 'n', 'r', 'r', 'q'];
+ let rem2 = positions[0] % 2;
if (rem2 == positions[1] % 2) {
// Fix bishops (on different colors)
- for (let i=2; i<9; i++) {
- if (positions[i] % 2 != rem2)
+ for (let i=4; i<9; i++) {
+ if (positions[i] % 2 != rem2) {
[positions[1], positions[i]] = [positions[i], positions[1]];
+ break;
+ }
+ }
+ }
+ rem2 = positions[2] % 2;
+ if (rem2 == positions[3] % 2) {
+ // Fix phoenixes too:
+ for (let i=4; i<9; i++) {
+ if (positions[i] % 2 != rem2)
+ [positions[3], positions[i]] = [positions[i], positions[3]];
}
}
for (let i = 0; i < 9; i++) pieces[c][positions[i]] = composition[i];
pieces["b"].join("") +
"/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/" +
pieces["w"].join("").toUpperCase() +
- // En-passant allowed, but no flags
- " w 0 -"
+ " w 0 - -"
);
}
scanKings() {}
+ setOtherVariables(fen) {
+ super.setOtherVariables(fen);
+ const pmove = V.ParseFen(fen).pmove;
+ // Local stack of "pass moves" (no need for appear & vanish)
+ this.pmoves = [
+ pmove != "-"
+ ?
+ {
+ start: V.SquareToCoords(pmove.substr(0, 2)),
+ end: V.SquareToCoords(pmove.substr(2))
+ }
+ : null
+ ];
+ }
+
static get size() {
return { x: 9, y: 9 };
}
return Object.assign(
{},
ChessRules.steps,
- // Add champion moves
+ // Add phoenix moves
{
h: [
[-2, -2],
- [-2, 0],
[-2, 2],
- [0, -2],
- [0, 2],
[2, -2],
- [2, 0],
[2, 2],
[-1, 0],
[1, 0],
);
}
- // Post-processing: maybe the ball was taken, or a piece + ball
+ // Post-processing: maybe the ball was taken, or a piece + ball,
+ // or maybe a pass (ball <--> piece)
if (mv.vanish.length == 2) {
if (
// Take the ball?
mv.vanish[1].c == 'a' ||
- // Capture a ball-holding piece?
+ // Capture a ball-holding piece? If friendly one, then adjust
Object.keys(V.HAS_BALL_DECODE).includes(mv.vanish[1].p)
) {
mv.appear[0].p = V.HAS_BALL_CODE[mv.appear[0].p];
- } else if (mv.vanish[1].c == mv.vanish[0].c) {
+ if (mv.vanish[1].c == mv.vanish[0].c) {
+ // "Capturing" self => pass
+ mv.appear[0].x = mv.start.x;
+ mv.appear[0].y = mv.start.y;
+ mv.appear.push(
+ new PiPo({
+ x: mv.end.x,
+ y: mv.end.y,
+ p: V.HAS_BALL_DECODE[mv.vanish[1].p],
+ c: mv.vanish[0].c
+ })
+ );
+ }
+ }
+ else if (mv.vanish[1].c == mv.vanish[0].c) {
// Pass the ball: the passing unit does not disappear
mv.appear.push(JSON.parse(JSON.stringify(mv.vanish[0])));
mv.appear[0].p = V.HAS_BALL_CODE[mv.vanish[1].p];
return mv;
}
- // NOTE: if a pawn is captured en-passant, he doesn't hold the ball
+ // NOTE: if a pawn captures en-passant, he doesn't hold the ball
// So base implementation is fine.
getPotentialMovesFrom([x, y]) {
- if (this.getPiece(x, y) == V.CHAMPION)
- return this.getPotentialChampionMoves([x, y]);
- return super.getPotentialMovesFrom([x, y]);
- }
-
- // "Sliders": at most 2 steps
- getSlideNJumpMoves([x, y], steps, oneStep) {
- let moves = [];
- outerLoop: for (let step of steps) {
- let i = x + step[0];
- let j = y + step[1];
- let stepCount = 1;
- while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
- moves.push(this.getBasicMove([x, y], [i, j]));
- if (oneStep || stepCount == 2) continue outerLoop;
- i += step[0];
- j += step[1];
- stepCount++;
+ let moves = undefined;
+ const piece = this.getPiece(x, y);
+ if (piece == V.PHOENIX)
+ moves = this.getPotentialPhoenixMoves([x, y]);
+ else moves = super.getPotentialMovesFrom([x, y]);
+ // Add "taking ball in place" move (at most one in list)
+ for (let m of moves) {
+ if (
+ m.vanish.length == 2 &&
+ m.vanish[1].p != 'a' &&
+ m.vanish[0].c != m.vanish[1].c &&
+ Object.keys(V.HAS_BALL_DECODE).includes(m.appear[0].p)
+ ) {
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ moves.push(
+ new Move({
+ appear: [
+ new PiPo({
+ x: x,
+ y: y,
+ c: color,
+ p: m.appear[0].p
+ }),
+ new PiPo({
+ x: m.vanish[1].x,
+ y: m.vanish[1].y,
+ c: oppCol,
+ p: V.HAS_BALL_DECODE[m.vanish[1].p]
+ })
+ ],
+ vanish: [
+ new PiPo({
+ x: x,
+ y: y,
+ c: color,
+ p: piece
+ }),
+ new PiPo({
+ x: m.vanish[1].x,
+ y: m.vanish[1].y,
+ c: oppCol,
+ p: m.vanish[1].p
+ })
+ ],
+ end: { x: m.end.x, y: m.end.y }
+ })
+ );
+ break;
}
- if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]))
- moves.push(this.getBasicMove([x, y], [i, j]));
}
return moves;
}
- getPotentialChampionMoves(sq) {
- return this.getSlideNJumpMoves(sq, V.steps[V.CHAMPION], "oneStep");
+ getSlideNJumpMoves(sq, steps, nbSteps) {
+ // "Sliders": at most 3 steps
+ return super.getSlideNJumpMoves(sq, steps, !nbSteps ? 3 : 1);
+ }
+
+ getPotentialPhoenixMoves(sq) {
+ return super.getSlideNJumpMoves(sq, V.steps[V.PHOENIX], 1);
+ }
+
+ getPmove(move) {
+ if (
+ move.vanish.length == 2 &&
+ move.appear.length == 2 &&
+ move.appear[0].c != move.appear[1].c
+ ) {
+ // In-place pass:
+ return {
+ start: move.start,
+ end: move.end
+ };
+ }
+ return null;
+ }
+
+ oppositePasses(m1, m2) {
+ return (
+ m1.start.x == m2.end.x &&
+ m1.start.y == m2.end.y &&
+ m1.end.x == m2.start.x &&
+ m1.end.y == m2.start.y
+ );
}
filterValid(moves) {
- return moves;
+ const L = this.pmoves.length;
+ const lp = this.pmoves[L-1];
+ if (!lp) return moves;
+ return moves.filter(m => {
+ return (
+ m.vanish.length == 1 ||
+ m.appear.length == 1 ||
+ m.appear[0].c == m.appear[1].c ||
+ !this.oppositePasses(lp, m)
+ );
+ });
}
// isAttacked: unused here (no checks)
- postPlay() {}
- postUndo() {}
+ postPlay(move) {
+ this.pmoves.push(this.getPmove(move));
+ }
+
+ postUndo() {
+ this.pmoves.pop();
+ }
+
+ getCheckSquares() {
+ return [];
+ }
getCurrentScore() {
// Turn has changed:
static get VALUES() {
return {
p: 1,
- r: 5,
+ r: 3,
n: 3,
- b: 3,
- q: 9,
- h: 4,
+ b: 2,
+ q: 5,
+ h: 3,
a: 0 //ball: neutral
};
}
finalSquare
);
}
+
};