+ getStepSpec(color, x, y) {
+ const allSpecs = this.pieces(color, x, y);
+ let stepSpec = allSpecs[piece];
+ if (stepSpec.moveas)
+ stepSpec = allSpecs[stepSpec.moveas];
+ return stepSpec;
+ }
+
+ // 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)