+ getPpath(b) {
+ if (b == V.BALL) return "Football/ball";
+ return b;
+ }
+
+ canIplay(side, [x, y]) {
+ return (
+ side == this.turn &&
+ (this.board[x][y] == V.BALL || this.getColor(x, y) == side)
+ );
+ }
+
+ // No checks or king tracking etc. But, track ball
+ setOtherVariables() {
+ // Stack of "kicked by" coordinates, to avoid infinite loops
+ this.kickedBy = [ {} ];
+ this.subTurn = 1;
+ this.ballPos = [-1, -1];
+ for (let i=0; i < V.size.x; i++) {
+ for (let j=0; j< V.size.y; j++) {
+ if (this.board[i][j] == V.BALL) {
+ this.ballPos = [i, j];
+ return;
+ }
+ }
+ }
+ }
+
+ static GenRandInitFen(options) {
+ if (options.randomness == 0)
+ return "rnbq1knbr/9/9/9/4a4/9/9/9/RNBQ1KNBR w 0";
+
+ let pieces = { w: new Array(8), b: new Array(8) };
+ for (let c of ["w", "b"]) {
+ if (c == 'b' && options.randomness == 1) {
+ pieces['b'] = pieces['w'];
+ break;
+ }
+
+ // Get random squares for every piece, totally freely
+ let positions = shuffle(ArrayFun.range(8));
+ const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q'];
+ // Fix bishops (on different colors)
+ const realOddity =
+ (pos) => { return (pos <= 3 ? pos % 2 : (pos + 1) % 2); };
+ const rem2 = realOddity(positions[0]);
+ if (rem2 == realOddity(positions[1])) {
+ for (let i=2; i<8; i++) {
+ if (realOddity(positions[i]) != rem2) {
+ [positions[1], positions[i]] = [positions[i], positions[1]];
+ break;
+ }
+ }
+ }
+ for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i];
+ }
+ const piecesB = pieces["b"].join("") ;
+ const piecesW = pieces["w"].join("").toUpperCase();
+ return (
+ piecesB.substr(0, 4) + "1" + piecesB.substr(4) +
+ "/9/9/9/4a4/9/9/9/" +
+ piecesW.substr(0, 4) + "1" + piecesW.substr(4) +
+ " w 0"
+ );
+ }
+
+ tryKickFrom([x, y]) {
+ const bp = this.ballPos;
+ const emptySquare = (i, j) => {
+ return V.OnBoard(i, j) && this.board[i][j] == V.EMPTY;
+ };
+ // Kick the (adjacent) ball from x, y with current turn:
+ const step = [bp[0] - x, bp[1] - y];
+ const piece = this.getPiece(x, y);
+ let moves = [];
+ if (piece == V.KNIGHT) {
+ // The knight case is particular
+ V.steps[V.KNIGHT].forEach(s => {
+ const [i, j] = [bp[0] + s[0], bp[1] + s[1]];
+ if (
+ V.OnBoard(i, j) &&
+ this.board[i][j] == V.EMPTY &&
+ (
+ // In a corner? Then, allow all ball moves
+ ([0, 8].includes(bp[0]) && [0, 8].includes(bp[1])) ||
+ // Do not end near the knight
+ (Math.abs(i - x) >= 2 || Math.abs(j - y) >= 2)
+ )
+ ) {
+ moves.push(super.getBasicMove(bp, [i, j]));
+ }
+ });
+ }
+ else {
+ let compatible = false,
+ oneStep = false;
+ switch (piece) {
+ case V.ROOK:
+ compatible = (step[0] == 0 || step[1] == 0);
+ break;
+ case V.BISHOP:
+ compatible = (step[0] != 0 && step[1] != 0);
+ break;
+ case V.QUEEN:
+ compatible = true;
+ break;
+ case V.KING:
+ compatible = true;
+ oneStep = true;
+ break;
+ }
+ if (!compatible) return [];
+ let [i, j] = [bp[0] + step[0], bp[1] + step[1]];
+ const horizontalStepOnGoalRow =
+ ([0, 8].includes(bp[0]) && step.some(s => s == 0));
+ if (
+ emptySquare(i, j) &&
+ (this.movesCount >= 2 || j != 4 || ![0, 8].includes(i)) &&
+ (!horizontalStepOnGoalRow || j != 4)
+ ) {
+ moves.push(super.getBasicMove(bp, [i, j]));
+ if (!oneStep) {
+ do {
+ i += step[0];
+ j += step[1];
+ if (!emptySquare(i, j)) break;
+ if (
+ (this.movesCount >= 2 || j != 4 || ![0, 8].includes(i)) &&
+ (!horizontalStepOnGoalRow || j != 4)
+ ) {
+ moves.push(super.getBasicMove(bp, [i, j]));
+ }
+ } while (true);
+ }
+ }
+ // Try the other direction (TODO: experimental)
+ [i, j] = [bp[0] - 2*step[0], bp[1] - 2*step[1]];
+ if (
+ emptySquare(i, j) &&
+ (this.movesCount >= 2 || j != 4 || ![0, 8].includes(i)) &&
+ (!horizontalStepOnGoalRow || j != 4)
+ ) {
+ moves.push(super.getBasicMove(bp, [i, j]));
+ if (!oneStep) {
+ do {
+ i -= step[0];
+ j -= step[1];
+ if (!emptySquare(i, j)) break;
+ if (
+ (this.movesCount >= 2 || j != 4 || ![0, 8].includes(i)) &&
+ (!horizontalStepOnGoalRow || j != 4)
+ ) {
+ moves.push(super.getBasicMove(bp, [i, j]));
+ }
+ } while (true);
+ }
+ }
+ }
+ const kickedFrom = x + "-" + y;
+ moves.forEach(m => m.start.by = kickedFrom)
+ return moves;
+ }
+
+ getPotentialMovesFrom([x, y], computer) {
+ const piece = this.getPiece(x, y);
+ if (V.PIECES.includes(piece)) {
+ if (this.subTurn > 1) return [];
+ const moves = super.getPotentialMovesFrom([x, y])
+ .filter(m => m.end.y != 4 || ![0, 8].includes(m.end.x));
+ // If bishop stuck in a corner: allow to jump over the next obstacle
+ if (
+ moves.length == 0 && piece == V.BISHOP &&
+ [0, 8].includes(x) && [0, 8].includes(y)
+ ) {
+ const indX = x == 0 ? [1, 2] : [7, 6];
+ const indY = y == 0 ? [1, 2] : [7, 6];
+ if (
+ this.board[indX[0]][indY[0]] != V.EMPTY &&
+ this.board[indX[1]][indY[1]] == V.EMPTY
+ ) {
+ return [super.getBasicMove([x, y], [indX[1], indY[1]])];
+ }
+ }
+ return moves;
+ }
+ // Kicking the ball: look for adjacent pieces.
+ const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+ const c = this.turn;
+ let moves = [];
+ let kicks = {};
+ let adjacentPieces = false;
+ for (let s of steps) {
+ const [i, j] = [x + s[0], y + s[1]];
+ if (
+ V.OnBoard(i, j) &&
+ this.board[i][j] != V.EMPTY &&
+ this.getColor(i, j) == c
+ ) {
+ const kmoves = this.tryKickFrom([i, j]);
+ kmoves.forEach(km => {
+ const key = V.CoordsToSquare(km.start) + V.CoordsToSquare(km.end);
+ if (!kicks[key]) {
+ moves.push(km);
+ kicks[key] = true;
+ }
+ });
+ if (!adjacentPieces) adjacentPieces = true;
+ }
+ }
+ if (adjacentPieces) {
+ // Add the "end" move (even if no valid kicks)
+ outerLoop: for (let i=0; i < V.size.x; i++) {
+ for (let j=0; j < V.size.y; j++) {
+ if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == c) {
+ moves.push({
+ appear: [], vanish: [],
+ start: { x: x, y: y }, end: { x: i, y: j }
+ });
+ if (computer) break outerLoop; //no choice for computer
+ }
+ }
+ }
+ }