+ // Can thing on square1 capture thing on square2?
+ canTake([x1, y1], [x2, y2]) {
+ return this.getColor(x1, y1) !== this.getColor(x2, y2);
+ }
+
+ canStepOver(i, j, p) {
+ // In some variants, objects on boards don't stop movement (Chakart)
+ return this.board[i][j] == "";
+ }
+
+ canDrop([c, p], [i, j]) {
+ return (
+ this.board[i][j] == "" &&
+ (!this.enlightened || this.enlightened[i][j]) &&
+ (
+ p != "p" ||
+ (c == 'w' && i < this.size.x - 1) ||
+ (c == 'b' && i > 0)
+ )
+ );
+ }
+
+ // For Madrasi:
+ // (redefined in Baroque etc, where Madrasi condition doesn't make sense)
+ isImmobilized([x, y]) {
+ if (!this.options["madrasi"])
+ return false;
+ const color = this.getColor(x, y);
+ const oppCol = C.GetOppCol(color);
+ const piece = this.getPieceType(x, y); //ok not cannibal king
+ const stepSpec = this.getStepSpec(color, x, y);
+ const attacks = stepSpec.attack || stepSpec.moves;
+ for (let a of attacks) {
+ outerLoop: for (let step of a.steps) {
+ let [i, j] = [x + step[0], y + step[1]];
+ let stepCounter = 1;
+ while (this.onBoard(i, j) && this.board[i][j] == "") {
+ if (a.range <= stepCounter++)
+ continue outerLoop;
+ i += step[0];
+ j = this.getY(j + step[1]);
+ }
+ if (
+ this.onBoard(i, j) &&
+ this.getColor(i, j) == oppCol &&
+ this.getPieceType(i, j) == piece
+ ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // Stop at the first capture found
+ atLeastOneCapture(color) {
+ const oppCol = C.GetOppCol(color);
+ const allowed = ([x, y]) => {
+ this.getColor(x, y) == oppCol &&
+ this.filterValid([this.getBasicMove([i, j], [x, y])]).length >= 1
+ };
+ for (let i=0; i<this.size.x; i++) {
+ for (let j=0; j<this.size.y; j++) {
+ if (this.getColor(i, j) == color) {
+ if (
+ (!this.options["zen"] && this.findDestSquares(
+ [i, j], {attackOnly: true, one: true}, allowed).length == 1) ||
+ (this.options["zen"] && this.findCapturesOn(
+ [i, j], {one: true}, allowed).length == 1)
+ ) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ compatibleStep([x1, y1], [x2, y2], step, range) {
+ let shifts = [0];
+ if (this.options["cylinder"])
+ Array.prototype.push.apply(shifts, [-this.size.y, this.size.y]);
+ for (let sh of shifts) {
+ const rx = (x2 - x1) / step[0],
+ ry = (y2 + sh - y1) / step[1];
+ if (
+ (!Number.isFinite(rx) && !Number.isNaN(rx)) ||
+ (!Number.isFinite(ry) && !Number.isNaN(ry))
+ ) {
+ continue;
+ }
+ let distance = (Number.isNaN(rx) ? ry : rx);
+ // TODO: 1e-7 here is totally arbitrary
+ if (Math.abs(distance - Math.round(distance)) > 1e-7)
+ continue;
+ distance = Math.round(distance); //in case of (numerical...)
+ if (range >= distance)
+ return true;
+ }
+ return false;
+ }
+
+ ////////////////////
+ // MOVES GENERATION
+
+ getDropMovesFrom([c, p]) {
+ // NOTE: by design, this.reserve[c][p] >= 1 on user click
+ // (but not necessarily otherwise: atLeastOneMove() etc)
+ if (this.reserve[c][p] == 0)
+ return [];
+ let moves = [];
+ for (let i=0; i<this.size.x; i++) {
+ for (let j=0; j<this.size.y; j++) {
+ if (this.canDrop([c, p], [i, j])) {
+ let mv = new Move({
+ start: {x: c, y: p},
+ end: {x: i, y: j},
+ appear: [new PiPo({x: i, y: j, c: c, p: p})],
+ vanish: []
+ });
+ if (this.board[i][j] != "") {
+ mv.vanish.push(new PiPo({
+ x: i,
+ y: j,
+ c: this.getColor(i, j),
+ p: this.getPiece(i, j)
+ }));
+ }
+ moves.push(mv);
+ }
+ }
+ }
+ return moves;
+ }
+
+ // All possible moves from selected square
+ getPotentialMovesFrom([x, y], color) {
+ if (this.subTurnTeleport == 2)
+ return [];
+ if (typeof x == "string")
+ return this.getDropMovesFrom([x, y]);
+ if (this.isImmobilized([x, y]))
+ return [];
+ const piece = this.getPieceType(x, y);
+ let moves = this.getPotentialMovesOf(piece, [x, y]);
+ if (piece == "p" && this.hasEnpassant && this.epSquare)
+ Array.prototype.push.apply(moves, this.getEnpassantCaptures([x, y]));
+ if (
+ piece == "k" && this.hasCastle &&