import ChessRules from "/base_rules.js";
-import GiveawayRules from "/variants/Giveaway/class.js";
-import { ArrayFun } from "/utils/array.js";
-import { Random } from "/utils/alea.js";
+import {ArrayFun} from "/utils/array.js";
+import {Random} from "/utils/alea.js";
+import {FenUtil} from "/utils/setupPieces.js";
import PiPo from "/utils/PiPo.js";
import Move from "/utils/Move.js";
't': {"class": ["immobilized", "queen"]},
'l': {"class": ["immobilized", "king"]}
};
- return Object.assign({}, specials, bowsered, super.pieces(color, x, y));
+ return Object.assign(
+ {
+ 'y': {
+ // Virtual piece for "king remote shell captures"
+ attack: [
+ {
+ steps: [
+ [0, 1], [0, -1], [1, 0], [-1, 0],
+ [1, 1], [1, -1], [-1, 1], [-1, -1]
+ ]
+ }
+ ]
+ }
+ },
+ specials, bowsered, super.pieces(color, x, y)
+ );
}
- genRandInitFen(seed) {
- const gr = new GiveawayRules(
- {mode: "suicide", options: {}, genFenOnly: true});
- // Add Peach + mario flags
- return gr.genRandInitFen(seed).slice(0, -17) + '{"flags":"1111"}';
+ genRandInitBaseFen() {
+ const s = FenUtil.setupPieces(
+ ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'],
+ {
+ randomness: this.options["randomness"],
+ diffCol: ['b']
+ }
+ );
+ return {
+ fen: s.b.join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" +
+ s.w.join("").toUpperCase(),
+ o: {flags: "1111"} //Peach + Mario
+ };
}
fen2board(f) {
}
setOtherVariables(fenParsed) {
- this.setFlags(fenParsed.flags);
- this.reserve = {}; //to be filled later
+ super.setOtherVariables(fenParsed);
this.egg = null;
- this.moveStack = [];
// Change seed (after FEN generation!!)
// so that further calls differ between players:
- Random.setSeed(Math.floor(10000 * Math.random()));
+ Random.setSeed(Math.floor(19840 * Math.random()));
+ }
+
+ initReserves() {
+ this.reserve = {}; //to be filled later
+ }
+
+ canStepOver(i, j) {
+ return (
+ this.board[i][j] == "" ||
+ ['i', V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
+ );
}
// For Toadette bonus
- getDropMovesFrom([c, p]) {
- if (typeof c != "string" || this.reserve[c][p] == 0)
- return [];
- let moves = [];
- const start = (c == 'w' && p == 'p' ? 1 : 0);
- const end = (c == 'b' && p == 'p' ? 7 : 8);
- for (let i = start; i < end; i++) {
- for (let j = 0; j < this.size.y; j++) {
- const pieceIJ = this.getPiece(i, j);
- const colIJ = this.getColor(i, j);
- if (this.board[i][j] == "" || colIJ == 'a' || pieceIJ == 'i') {
- let m = new Move({
- start: {x: c, y: p},
- appear: [new PiPo({x: i, y: j, c: c, p: p})],
- vanish: []
- });
- // A drop move may remove a bonus (or hidden queen!)
- if (this.board[i][j] != "")
- m.vanish.push(new PiPo({x: i, y: j, c: colIJ, p: pieceIJ}));
- moves.push(m);
- }
- }
- }
- return moves;
+ canDrop([c, p], [i, j]) {
+ return (
+ (
+ this.board[i][j] == "" ||
+ this.getColor(i, j) == 'a' ||
+ this.getPiece(i, j) == 'i'
+ )
+ &&
+ (p != "p" || (c == 'w' && i < this.size.x - 1) || (c == 'b' && i > 0))
+ );
}
getPotentialMovesFrom([x, y]) {
moves = this.getDropMovesFrom([x, y]);
else if (this.egg == "kingboo") {
const color = this.turn;
- const oppCol = C.GetOppCol(color);
+ const oppCol = C.GetOppTurn(color);
// Only allow to swap (non-immobilized!) pieces
for (let i=0; i<this.size.x; i++) {
for (let j=0; j<this.size.y; j++) {
return moves;
}
- canStepOver(i, j) {
- return (
- this.board[i][j] == "" ||
- ['i', V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
- );
- }
-
getPawnMovesFrom([x, y]) {
const color = this.turn;
- const oppCol = C.GetOppCol(color);
+ const oppCol = C.GetOppTurn(color);
const shiftX = (color == 'w' ? -1 : 1);
const firstRank = (color == "w" ? this.size.x - 1 : 0);
let moves = [];
}
}
for (let shiftY of [-1, 1]) {
+ const nextY = this.getY(y + shiftY);
if (
- y + shiftY >= 0 &&
- y + shiftY < this.size.y &&
- this.board[x + shiftX][y + shiftY] != "" &&
+ nextY >= 0 &&
+ nextY < this.size.y &&
+ this.board[x + shiftX][nextY] != "" &&
// Pawns cannot capture invisible queen this way!
- this.getPiece(x + shiftX, y + shiftY) != 'i' &&
- ['a', oppCol].includes(this.getColor(x + shiftX, y + shiftY))
+ this.getPiece(x + shiftX, nextY) != 'i' &&
+ ['a', oppCol].includes(this.getColor(x + shiftX, nextY))
) {
- moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY]));
+ moves.push(this.getBasicMove([x, y], [x + shiftX, nextY]));
}
}
- super.pawnPostProcess(moves, color, oppCol);
- // Add mushroom on before-last square
+ moves = super.pawnPostProcess(moves, color, oppCol);
+ // Add mushroom on before-last square (+ potential segments)
moves.forEach(m => {
- let revStep = [m.start.x - m.end.x, m.start.y - m.end.y];
- for (let i of [0, 1])
- revStep[i] = revStep[i] / Math.abs(revStep[i]) || 0;
- const [blx, bly] = [m.end.x + revStep[0], m.end.y + revStep[1]];
- m.appear.push(new PiPo({x: blx, y: bly, c: 'a', p: 'm'}));
- if (blx != x && this.board[blx][bly] != "") {
+ let [mx, my] = [x, y];
+ if (Math.abs(m.end.x - m.start.x) == 2)
+ mx = (m.start.x + m.end.x) / 2;
+ m.appear.push(new PiPo({x: mx, y: my, c: 'a', p: 'm'}));
+ if (mx != x && this.board[mx][my] != "") {
m.vanish.push(new PiPo({
- x: blx,
- y: bly,
- c: this.getColor(blx, bly),
- p: this.getPiece(blx, bly)
+ x: mx,
+ y: my,
+ c: this.getColor(mx, my),
+ p: this.getPiece(mx, my)
}));
}
+ if (Math.abs(m.end.y - m.start.y) > 1) {
+ m.segments = [
+ [[x, y], [x, y]],
+ [[m.end.x, m.end.y], [m.end.x, m.end.y]]
+ ];
+ }
});
return moves;
}
getKnightMovesFrom([x, y]) {
// Add egg on initial square:
- return this.getPotentialMovesOf('n', [x, y]).map(m => {
+ return super.getPotentialMovesOf('n', [x, y]).map(m => {
m.appear.push(new PiPo({p: "e", c: "a", x: x, y: y}));
return m;
});
}
getQueenMovesFrom(sq) {
- const normalMoves = this.getPotentialMovesOf('q', sq);
+ const normalMoves = super.getPotentialMovesOf('q', sq);
// If flag allows it, add 'invisible movements'
let invisibleMoves = [];
if (this.powerFlags[this.turn]['q']) {
}
getKingMovesFrom([x, y]) {
- let moves = this.getPotentialMovesOf('k', [x, y]);
+ let moves = super.getPotentialMovesOf('k', [x, y]);
// If flag allows it, add 'remote shell captures'
if (this.powerFlags[this.turn]['k']) {
- super.pieces()['k'].moves[0].steps.forEach(step => {
- let [i, j] = [x + step[0], y + step[1]];
- while (this.onBoard(i, j) && this.canStepOver(i, j)) {
- i += step[0];
- j += step[1];
- }
- if (this.onBoard(i, j)) {
- const colIJ = this.getColor(i, j);
- if (colIJ != this.turn) {
- // May just destroy a bomb or banana:
- let shellCapture = new Move({
- start: {x: x, y: y},
- end: {x: i, y: j},
- appear: [],
- vanish: [
- new PiPo({x: i, y: j, c: colIJ, p: this.getPiece(i, j)})
- ]
- });
- shellCapture.shell = true; //easier play()
- shellCapture.choice = 'z'; //to display in showChoices()
- moves.push(shellCapture);
- }
- }
+ let shellCaptures = super.getPotentialMovesOf('y', [x, y]);
+ shellCaptures.forEach(sc => {
+ sc.shell = true; //easier play()
+ sc.choice = 'z'; //to display in showChoices()
+ // Fix move (Rifle style):
+ sc.vanish.shift();
+ sc.appear.shift();
});
+ Array.prototype.push.apply(moves, shellCaptures);
}
return moves;
}
play(move) {
const color = this.turn;
- const oppCol = C.GetOppCol(color);
- if (
- move.appear.length > 0 &&
- move.appear[0].p == 'p' &&
- (
- (color == 'w' && move.end.x == 0) ||
- (color == 'b' && move.end.x == this.size.x - 1)
- )
- ) {
- // "Forgotten" promotion, which occurred after some effect
- let moves = [move];
- super.pawnPostProcess(moves, color, oppCol);
- super.showChoices(moves);
- return false;
- }
- if (!move.nextComputed) {
- // Set potential random effects, so that play() is deterministic
- // from opponent viewpoint:
- const endPiece = this.getPiece(move.end.x, move.end.y);
- switch (endPiece) {
- case V.EGG:
- move.egg = Random.sample(V.EGG_SURPRISE);
- move.next = this.getEggEffect(move);
- break;
- case V.MUSHROOM:
- move.next = this.getMushroomEffect(move);
- break;
- case V.BANANA:
- case V.BOMB:
- move.next = this.getBombBananaEffect(move, endPiece);
- break;
- }
- if (!move.next && move.appear.length > 0 && !move.kingboo) {
- const movingPiece = move.appear[0].p;
- if (['b', 'r'].includes(movingPiece)) {
- // Drop a banana or bomb:
- const bs =
- this.getRandomSquare([move.end.x, move.end.y],
- movingPiece == 'r'
- ? [[1, 1], [1, -1], [-1, 1], [-1, -1]]
- : [[1, 0], [-1, 0], [0, 1], [0, -1]],
- "freeSquare");
- if (bs) {
- move.appear.push(
- new PiPo({
- x: bs[0],
- y: bs[1],
- c: 'a',
- p: movingPiece == 'r' ? 'd' : 'w'
- })
- );
- if (this.board[bs[0]][bs[1]] != "") {
- move.vanish.push(
- new PiPo({
- x: bs[0],
- y: bs[1],
- c: this.getColor(bs[0], bs[1]),
- p: this.getPiece(bs[0], bs[1])
- })
- );
- }
- }
- }
- }
- move.nextComputed = true;
- }
+ const oppCol = C.GetOppTurn(color);
this.egg = move.egg;
if (move.egg == "toadette") {
this.reserve = { w: {}, b: {} };
this.getColor(i, j) == oppCol
) {
const pieceIJ = this.getPiece(i, j);
- if (pieceIJ == 'i') {
+ if (
+ pieceIJ == 'i' &&
+ // Ensure that current move doesn't erase invisible queen
+ move.appear.every(a => a.x != i || a.y != j)
+ ) {
move.vanish.push(new PiPo({x: i, y: j, c: oppCol, p: 'i'}));
move.appear.push(new PiPo({x: i, y: j, c: oppCol, p: 'q'}));
}
}
}
}
- if (!move.next && !["daisy", "toadette", "kingboo"].includes(move.egg)) {
- this.turn = oppCol;
- this.movesCount++;
- }
+ this.playOnBoard(move);
+ super.postPlay(move);
+ }
+
+ playVisual(move, r) {
+ super.playVisual(move, r);
if (move.egg)
this.displayBonus(move);
- this.playOnBoard(move);
- this.nextMove = move.next;
- return true;
+ }
+
+ buildMoveStack(move, r) {
+ const color = this.turn;
+ if (
+ move.appear.length > 0 &&
+ move.appear[0].p == 'p' &&
+ (
+ (color == 'w' && move.end.x == 0) ||
+ (color == 'b' && move.end.x == this.size.x - 1)
+ )
+ ) {
+ // "Forgotten" promotion, which occurred after some effect
+ let moves = super.pawnPostProcess([move], color, C.GetOppTurn(color));
+ super.showChoices(moves, r);
+ }
+ else
+ super.buildMoveStack(move, r);
+ }
+
+ computeNextMove(move) {
+ if (move.koopa)
+ return null;
+ // Set potential random effects, so that play() is deterministic
+ // from opponent viewpoint:
+ const endPiece = this.getPiece(move.end.x, move.end.y);
+ switch (endPiece) {
+ case V.EGG:
+ move.egg = Random.sample(V.EGG_SURPRISE);
+ move.next = this.getEggEffect(move);
+ break;
+ case V.MUSHROOM:
+ move.next = this.getMushroomEffect(move);
+ break;
+ case V.BANANA:
+ case V.BOMB:
+ move.next = this.getBombBananaEffect(move, endPiece);
+ break;
+ }
+ // NOTE: Chakart has also some side-effects:
+ if (
+ !move.next && move.appear.length > 0 &&
+ !move.kingboo && !move.luigiEffect
+ ) {
+ const movingPiece = move.appear[0].p;
+ if (['b', 'r'].includes(movingPiece)) {
+ // Drop a banana or bomb:
+ const bs =
+ this.getRandomSquare([move.end.x, move.end.y],
+ movingPiece == 'r'
+ ? [[1, 1], [1, -1], [-1, 1], [-1, -1]]
+ : [[1, 0], [-1, 0], [0, 1], [0, -1]],
+ "freeSquare");
+ if (bs) {
+ move.appear.push(
+ new PiPo({
+ x: bs[0],
+ y: bs[1],
+ c: 'a',
+ p: movingPiece == 'r' ? 'd' : 'w'
+ })
+ );
+ if (this.board[bs[0]][bs[1]] != "") {
+ move.vanish.push(
+ new PiPo({
+ x: bs[0],
+ y: bs[1],
+ c: this.getColor(bs[0], bs[1]),
+ p: this.getPiece(bs[0], bs[1])
+ })
+ );
+ }
+ }
+ }
+ }
+ }
+
+ isLastMove(move) {
+ return !move.next && !["daisy", "toadette", "kingboo"].includes(move.egg);
}
// Helper to set and apply banana/bomb effect
let bagOfPieces = [];
for (let i=0; i<this.size.x; i++) {
for (let j=0; j<this.size.y; j++) {
- if (this.getColor(i, j) == c && this.getPiece(i, j) != 'k')
+ const pieceIJ = this.getPiece(i, j);
+ if (
+ this.getColor(i, j) == c && pieceIJ != 'k' &&
+ (
+ // The color will change, so pawns on first rank are ineligible
+ pieceIJ != 'p' ||
+ (c == 'w' && i < this.size.x - 1) || (c == 'b' && i > 0)
+ )
+ ) {
bagOfPieces.push([i, j]);
+ }
}
}
if (bagOfPieces.length >= 1)
case "luigi":
case "waluigi":
// Change color of friendly or enemy piece, king excepted
- const oldColor = (move.egg == "waluigi" ? color : C.GetOppCol(color));
- const newColor = C.GetOppCol(oldColor);
+ const oldColor = (move.egg == "waluigi" ? color : C.GetOppTurn(color));
+ const newColor = C.GetOppTurn(oldColor);
const coords = getRandomPiece(oldColor);
if (coords) {
const piece = this.getPiece(coords[0], coords[1]);
new PiPo({x: coords[0], y: coords[1], c: oldColor, p: piece})
]
});
+ em.luigiEffect = true; //avoid dropping bomb/banana by mistake
}
break;
case "bowser":
p: this.getPiece(move.start.x, move.start.y)
}));
}
- em.koopa = true; //to cancel mushroom effect
+ em.koopa = true; //avoid applying effect
break;
case "chomp":
// Eat piece
}
getMushroomEffect(move) {
- if (move.koopa)
+ if (
+ typeof move.start.x == "string" || //drop move (toadette)
+ ['b', 'r', 'q'].includes(move.vanish[0].p) //slider
+ ) {
return null;
- let step = [move.end.x - move.start.x, move.end.y - move.start.y];
- if ([0, 1].some(i => Math.abs(step[i]) >= 2 && Math.abs(step[1-i]) != 1)) {
- // Slider, multi-squares: normalize step
- for (let j of [0, 1])
- step[j] = step[j] / Math.abs(step[j]) || 0;
}
+ let step = [move.end.x - move.start.x, move.end.y - move.start.y];
+ if (Math.abs(step[0]) == 2 && Math.abs(step[1]) == 0)
+ // Pawn initial 2-squares move: normalize step
+ step[0] /= 2;
const nextSquare = [move.end.x + step[0], move.end.y + step[1]];
- const afterSquare =
- [nextSquare[0] + step[0], nextSquare[1] + step[1]];
let nextMove = null;
- this.playOnBoard(move); //HACK for getBasicMove() below
if (
this.onBoard(nextSquare[0], nextSquare[1]) &&
- ['k', 'p', 'n'].includes(move.vanish[0].p) &&
- !['w', 'b'].includes(this.getColor(nextSquare[0], nextSquare[1]))
+ (
+ this.board[nextSquare[0]][nextSquare[1]] == "" ||
+ this.getColor(nextSquare[0], nextSquare[1]) == 'a'
+ )
) {
- // Speed up non-sliders
+ this.playOnBoard(move); //HACK for getBasicMove()
nextMove = this.getBasicMove([move.end.x, move.end.y], nextSquare);
+ this.undoOnBoard(move);
}
- else if (
- this.onBoard(afterSquare[0], afterSquare[1]) &&
- this.board[nextSquare[0]][nextSquare[1]] != "" &&
- this.getColor(nextSquare[0], nextSquare[1]) != 'a' &&
- this.getColor(afterSquare[0], afterSquare[1]) != this.turn
- ) {
- nextMove = this.getBasicMove([move.end.x, move.end.y], afterSquare);
- }
- this.undoOnBoard(move);
return nextMove;
}
}
displayBonus(move) {
- let divBonus = document.createElement("div");
- divBonus.classList.add("bonus-text");
- divBonus.innerHTML = move.egg;
- let container = document.getElementById(this.containerId);
- container.appendChild(divBonus);
- setTimeout(() => container.removeChild(divBonus), 2000);
+ super.displayMessage(null, move.egg, "bonus-text", 2000);
}
atLeastOneMove() {
return moves;
}
- playPlusVisual(move, r) {
- const nextLines = () => {
- if (!this.play(move))
- return;
- this.moveStack.push(move);
- this.playVisual(move, r);
- if (this.nextMove)
- this.playPlusVisual(this.nextMove, r);
- else {
- this.afterPlay(this.moveStack);
- this.moveStack = [];
- }
- };
- if (this.moveStack.length == 0)
- nextLines();
- else
- this.animate(move, nextLines);
+ // Kingboo bonus can be animated better:
+ customAnimate(move, segments, cb) {
+ if (!move.kingboo)
+ return 0;
+ super.animateMoving(move.end, move.start, null,
+ segments.reverse().map(s => s.reverse()), cb);
+ return 1;
}
};