--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<circle cx="115" cy="115" r="100" fill="crimson" stroke="darkslategray"/>
+</svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<circle cx="115" cy="115" r="100" fill="crimson" stroke="darkslategray"/>
+<path d="M100,85 h30 v30 h-30 v30 h30" fill="none" stroke-width="5" stroke="black"/>
+</svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<circle cx="115" cy="115" r="100" fill="crimson" stroke="darkslategray"/>
+<path d="M100,85 h30 v30 h-30 M130,115 v30 h-30" fill="none" stroke-width="5" stroke="black"/>
+</svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<circle cx="115" cy="115" r="100" fill="crimson" stroke="darkslategray"/>
+<path d="M100,85 v30 h30 v30 M130,85 v30" fill="none" stroke-width="5" stroke="black"/>
+</svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<circle cx="115" cy="115" r="100" fill="crimson" stroke="darkslategray"/>
+<path d="M130,85 h-30 v30 h30 v30 h-30" fill="none" stroke-width="5" stroke="black"/>
+</svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<circle cx="115" cy="115" r="100" fill="gold" stroke="darkslategray"/>
+</svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<circle cx="115" cy="115" r="100" fill="gold" stroke="darkslategray"/>
+<path d="M100,85 h30 v30 h-30 v30 h30" fill="none" stroke-width="5" stroke="black"/>
+</svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<circle cx="115" cy="115" r="100" fill="gold" stroke="darkslategray"/>
+<path d="M100,85 h30 v30 h-30 M130,115 v30 h-30" fill="none" stroke-width="5" stroke="black"/>
+</svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<circle cx="115" cy="115" r="100" fill="gold" stroke="darkslategray"/>
+<path d="M100,85 v30 h30 v30 M130,85 v30" fill="none" stroke-width="5" stroke="black"/>
+</svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<circle cx="115" cy="115" r="100" fill="gold" stroke="darkslategray"/>
+<path d="M130,85 h-30 v30 h30 v30 h-30" fill="none" stroke-width="5" stroke="black"/>
+</svg>
\ No newline at end of file
-import ChessRules from "/base_rules.js";
+import GoRules from "/variants/Go/class.js";
import Move from "/utils/Move.js";
import PiPo from "/utils/PiPo.js";
import {ArrayFun} from "/utils/array.js";
-export default class AtarigoRules extends ChessRules {
+export default class AtarigoRules extends GoRules {
static get Options() {
- return {
- input: [
- {
- label: "Board size",
- variable: "bsize",
- type: "number",
- defaut: 11
- }
- ]
- };
- }
-
- get hasFlags() {
- return false;
- }
- get hasEnpassant() {
- return false;
- }
- get clickOnly() {
- return true;
- }
-
- getSvgChessboard() {
- const flipped = (this.playerColor == 'b');
- let board = `
- <svg
- viewBox="0 0 ${10*(this.size.y)} ${10*(this.size.x)}"
- class="chessboard_SVG">`;
- for (let i=0; i < this.size.x; i++) {
- for (let j=0; j < this.size.y; j++) {
- const ii = (flipped ? this.size.x - 1 - i : i);
- const jj = (flipped ? this.size.y - 1 - j : j);
- board += `
- <rect
- id="${this.coordsToId({x: ii, y: jj})}"
- width="10"
- height="10"
- x="${10*j}"
- y="${10*i}"
- fill="transparent"
- />`;
- }
- }
- // Add lines to delimitate "squares"
- for (let i = 0; i < this.size.x; i++) {
- const y = i * 10 + 5, maxX = this.size.y * 10 - 5;
- board += `
- <line x1="5" y1="${y}" x2="${maxX}" y2="${y}"
- stroke="black" stroke-width="0.2"/>`;
- }
- for (let i = 0; i < this.size.x; i++) {
- const x = i * 10 + 5, maxY = this.size.x * 10 - 5;
- board += `
- <line x1="${x}" y1="5" x2="${x}" y2="${maxY}"
- stroke="black" stroke-width="0.2"/>`;
- }
- board += "</svg>";
- return board;
- }
-
- get size() {
- return {
- x: this.options["bsize"],
- y: this.options["bsize"],
- };
- }
-
- genRandInitBaseFen() {
- const fenLine = C.FenEmptySquares(this.size.y);
- return {
- fen: (fenLine + '/').repeat(this.size.x - 1) + fenLine + " w 0",
- o: {}
- };
- }
-
- pieces(color, x, y) {
- return {
- 's': {
- "class": "stone",
- moves: []
- }
- };
- }
-
- doClick(coords) {
- const [x, y] = [coords.x, coords.y];
- if (this.board[x][y] != "")
- return null;
- const color = this.turn;
- const oppCol = C.GetOppCol(color);
- let move = new Move({
- appear: [ new PiPo({ x: x, y: y, c: color, p: 's' }) ],
- vanish: [],
- start: {x: x, y: y}
- });
- this.playOnBoard(move); //put the stone
- let noSuicide = false;
- let captures = [];
- for (let s of [[0, 1], [1, 0], [0, -1], [-1, 0]]) {
- const [i, j] = [x + s[0], y + s[1]];
- if (this.onBoard(i, j)) {
- if (this.board[i][j] == "")
- noSuicide = true; //clearly
- else if (this.getColor(i, j) == color) {
- // Free space for us = not a suicide
- if (!noSuicide) {
- let explored = ArrayFun.init(this.size.x, this.size.y, false);
- noSuicide = this.searchForEmptySpace([i, j], color, explored);
- }
- }
- else {
- // Free space for opponent = not a capture
- let explored = ArrayFun.init(this.size.x, this.size.y, false);
- const captureSomething =
- !this.searchForEmptySpace([i, j], oppCol, explored);
- if (captureSomething) {
- for (let ii = 0; ii < this.size.x; ii++) {
- for (let jj = 0; jj < this.size.y; jj++) {
- if (explored[ii][jj])
- captures.push(new PiPo({ x: ii, y: jj, c: oppCol, p: 's' }));
- }
- }
- }
- }
- }
- }
- this.undoOnBoard(move); //remove the stone
- if (!noSuicide && captures.length == 0)
- return null;
- Array.prototype.push.apply(move.vanish, captures);
- return move;
- }
-
- searchForEmptySpace([x, y], color, explored) {
- if (explored[x][y])
- return false; //didn't find empty space
- explored[x][y] = true;
- let res = false;
- for (let s of [[1, 0], [0, 1], [-1, 0], [0, -1]]) {
- const [i, j] = [x + s[0], y + s[1]];
- if (this.onBoard(i, j)) {
- if (this.board[i][j] == "")
- res = true;
- else if (this.getColor(i, j) == color)
- res = this.searchForEmptySpace([i, j], color, explored) || res;
- }
- }
- return res;
- }
-
- filterValid(moves) {
- // Suicide check not here, because side-computation of captures
- return moves;
+ let input = GoRules.Options.input;
+ input[0].defaut = 11;
+ return {input: input};
}
getCurrentScore(move_s) {
-.chessboard_SVG {
- background-color: #BA8C63;
-}
-
-piece.white.stone {
- background-image: url('/variants/Go/pieces/black_stone.svg');
-}
-
-piece.black.stone {
- background-image: url('/variants/Go/pieces/white_stone.svg');
-}
+@import url("/variants/Go/style.css");
import ChessRules from "/base_rules.js";
import GiveawayRules from "/variants/Giveaway/class.js";
+import AbstractSpecialCaptureRules from "/variants/_SpecialCaptures.js";
import {Random} from "/utils/alea.js";
import PiPo from "/utils/PiPo.js";
import Move from "/utils/Move.js";
-export default class BaroqueRules extends ChessRules {
+export default class BaroqueRules extends AbstractSpecialCaptureRules {
static get Options() {
return {
get hasFlags() {
return false;
}
- get hasEnpassant() {
- return false;
- }
genRandInitBaseFen() {
if (this.options["randomness"] == 0)
return res;
}
- // Although other pieces keep their names here for coding simplicity,
- // keep in mind that:
- // - a "rook" is a coordinator, capturing by coordinating with the king
- // - a "knight" is a long-leaper, capturing as in draughts
- // - a "bishop" is a chameleon, capturing as its prey
- // - a "queen" is a withdrawer, capturing by moving away from pieces
-
pieces() {
return Object.assign({},
super.pieces(),
{
'p': {
- "class": "pawn",
+ "class": "pawn", //pincer
moves: [
{steps: [[0, 1], [0, -1], [1, 0], [-1, 0]]}
]
},
'r': {
- "class": "rook",
+ "class": "rook", //coordinator
moves: [
{
steps: [
]
},
'n': {
- "class": "knight",
+ "class": "knight", //long-leaper
moveas: 'r'
},
'b': {
- "class": "bishop",
+ "class": "bishop", //chameleon
moveas: 'r'
},
'q': {
- "class": "queen",
+ "class": "queen", //withdrawer
moveas: 'r'
},
'i': {
"class": "immobilizer",
- moveas: 'q'
+ moveas: 'r'
}
}
);
return [];
switch (moves[0].vanish[0].p) {
case 'p':
- this.addPawnCaptures(moves);
+ this.addPincerCaptures(moves);
break;
case 'r':
- this.addRookCaptures(moves);
+ this.addCoordinatorCaptures(moves);
break;
case 'n':
const [x, y] = [moves[0].start.x, moves[0].start.y];
- moves = moves.concat(this.getKnightCaptures([x, y]));
+ moves = moves.concat(this.getLeaperCaptures([x, y]));
break;
case 'b':
- moves = this.getBishopCaptures(moves);
+ moves = this.getChameleonCaptures(moves, "pull");
break;
case 'q':
- this.addQueenCaptures(moves);
+ this.addPushmePullyouCaptures(moves, false, "pull");
break;
}
return moves;
}
- // Modify capturing moves among listed pawn moves
- addPawnCaptures(moves, byChameleon) {
- const steps = this.pieces()['p'].moves[0].steps;
- const color = this.turn;
- const oppCol = C.GetOppCol(color);
- moves.forEach(m => {
- if (byChameleon && m.start.x != m.end.x && m.start.y != m.end.y)
- // Chameleon not moving as pawn
- return;
- // Try capturing in every direction
- for (let step of steps) {
- const sq2 = [m.end.x + 2 * step[0], this.getY(m.end.y + 2 * step[1])];
- if (
- this.onBoard(sq2[0], sq2[1]) &&
- this.board[sq2[0]][sq2[1]] != "" &&
- this.getColor(sq2[0], sq2[1]) == color
- ) {
- // Potential capture
- const sq1 = [m.end.x + step[0], this.getY(m.end.y + step[1])];
- if (
- this.board[sq1[0]][sq1[1]] != "" &&
- this.getColor(sq1[0], sq1[1]) == oppCol
- ) {
- const piece1 = this.getPiece(sq1[0], sq1[1]);
- if (!byChameleon || piece1 == 'p') {
- m.vanish.push(
- new PiPo({
- x: sq1[0],
- y: sq1[1],
- c: oppCol,
- p: piece1
- })
- );
- }
- }
- }
- }
- });
- }
-
- addRookCaptures(moves, byChameleon) {
- const color = this.turn;
- const oppCol = V.GetOppCol(color);
- const kp = this.searchKingPos(color)[0];
- moves.forEach(m => {
- // Check piece-king rectangle (if any) corners for enemy pieces
- if (m.end.x == kp[0] || m.end.y == kp[1])
- return; //"flat rectangle"
- const corner1 = [m.end.x, kp[1]];
- const corner2 = [kp[0], m.end.y];
- for (let [i, j] of [corner1, corner2]) {
- if (this.board[i][j] != "" && this.getColor(i, j) == oppCol) {
- const piece = this.getPiece(i, j);
- if (!byChameleon || piece == 'r') {
- m.vanish.push(
- new PiPo({
- x: i,
- y: j,
- p: piece,
- c: oppCol
- })
- );
- }
- }
- }
- });
- }
-
- getKnightCaptures(startSquare, byChameleon) {
- // Look in every direction for captures
- const steps = this.pieces()['r'].moves[0].steps;
- const color = this.turn;
- const oppCol = C.GetOppCol(color);
- let moves = [];
- const [x, y] = [startSquare[0], startSquare[1]];
- const piece = this.getPiece(x, y); //might be a chameleon!
- outerLoop: for (let step of steps) {
- let [i, j] = [x + step[0], this.getY(y + step[1])];
- while (this.onBoard(i, j) && this.board[i][j] == "")
- [i, j] = [i + step[0], this.getY(j + step[1])];
- if (
- !this.onBoard(i, j) ||
- this.getColor(i, j) == color ||
- (byChameleon && this.getPiece(i, j) != 'n')
- ) {
- continue;
- }
- // last(thing), cur(thing) : stop if "cur" is our color,
- // or beyond board limits, or if "last" isn't empty and cur neither.
- // Otherwise, if cur is empty then add move until cur square;
- // if cur is occupied then stop if !!byChameleon and the square not
- // occupied by a leaper.
- let last = [i, j];
- let cur = [i + step[0], this.getY(j + step[1])];
- let vanished = [new PiPo({x: x, y: y, c: color, p: piece})];
- while (this.onBoard(cur[0], cur[1])) {
- if (this.board[last[0]][last[1]] != "") {
- const oppPiece = this.getPiece(last[0], last[1]);
- if (!!byChameleon && oppPiece != 'n')
- continue outerLoop;
- // Something to eat:
- vanished.push(
- new PiPo({x: last[0], y: last[1], c: oppCol, p: oppPiece})
- );
- }
- if (this.board[cur[0]][cur[1]] != "") {
- if (
- this.getColor(cur[0], cur[1]) == color ||
- this.board[last[0]][last[1]] != ""
- ) {
- //TODO: redundant test
- continue outerLoop;
- }
- }
- else {
- moves.push(
- new Move({
- appear: [new PiPo({x: cur[0], y: cur[1], c: color, p: piece})],
- vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
- start: {x: x, y: y},
- end: {x: cur[0], y: cur[1]}
- })
- );
- }
- last = [last[0] + step[0], this.getY(last[1] + step[1])];
- cur = [cur[0] + step[0], this.getY(cur[1] + step[1])];
- }
- }
- return moves;
- }
-
- // Chameleon
- getBishopCaptures(moves) {
- const [x, y] = [moves[0].start.x, moves[0].start.y];
- moves = moves.concat(this.getKnightCaptures([x, y], "asChameleon"));
- // No "king capture" because king cannot remain under check
- this.addPawnCaptures(moves, "asChameleon");
- this.addRookCaptures(moves, "asChameleon");
- this.addQueenCaptures(moves, "asChameleon");
- // Post-processing: merge similar moves, concatenating vanish arrays
- let mergedMoves = {};
- moves.forEach(m => {
- const key = m.end.x + this.size.x * m.end.y;
- if (!mergedMoves[key])
- mergedMoves[key] = m;
- else {
- for (let i = 1; i < m.vanish.length; i++)
- mergedMoves[key].vanish.push(m.vanish[i]);
- }
- });
- return Object.values(mergedMoves);
- }
-
- addQueenCaptures(moves, byChameleon) {
- if (moves.length == 0)
- return;
- const [x, y] = [moves[0].start.x, moves[0].start.y];
- const adjacentSteps = this.pieces()['r'].moves[0].steps;
- let capturingDirections = {};
- const color = this.turn;
- const oppCol = C.GetOppCol(color);
- adjacentSteps.forEach(step => {
- const [i, j] = [x - step[0], this.getY(y - step[1])];
- if (
- this.onBoard(i, j) &&
- this.board[i][j] != "" &&
- this.getColor(i, j) == oppCol &&
- (!byChameleon || this.getPiece(i, j) == 'q')
- ) {
- capturingDirections[step[0] + "." + step[1]] = true;
- }
- });
- moves.forEach(m => {
- const step = [
- m.end.x != x ? (m.end.x - x) / Math.abs(m.end.x - x) : 0,
- m.end.y != y ? (m.end.y - y) / Math.abs(m.end.y - y) : 0
- ];
- if (capturingDirections[step[0] + "." + step[1]]) {
- const [i, j] = [x - step[0], this.getY(y - step[1])];
- m.vanish.push(
- new PiPo({
- x: i,
- y: j,
- p: this.getPiece(i, j),
- c: oppCol
- })
- );
- }
- });
- }
-
- underAttack([x, y], oppCol) {
- // Generate all potential opponent moves, check if king captured.
- // TODO: do it more efficiently.
- const color = this.getColor(x, y);
- for (let i = 0; i < this.size.x; i++) {
- for (let j = 0; j < this.size.y; j++) {
- if (
- this.board[i][j] != "" && this.getColor(i, j) == oppCol &&
- this.getPotentialMovesFrom([i, j]).some(m => {
- return (
- m.vanish.length >= 2 &&
- [1, m.vanish.length - 1].some(k => m.vanish[k].p == 'k')
- );
- })
- ) {
- return true;
- }
- }
- }
- return false;
- }
-
};
@import url("/base_pieces.css");
+@import url("/variants/_SpecialCaptures/style.css");
piece.white.immobilizer {
background-image: url('/variants/Baroque/pieces/white_immobilizer.svg');
-import ChessRules from "/base_rules.js";
+import AbstractBerolinaRules from "/variants/_Berolina/class.js";
-export default class BerolinaRules extends ChessRules {
-
-//TODO: Berolina pawns in Utils, also captures for Baroque+Fugue+...
-
- pieces(color, x, y) {
- const pawnShift = (color == "w" ? -1 : 1);
- let res = super.pieces(color, x, y);
- res['p'].moves = [
- {
- steps: [[pawnShift, 1], [pawnShift, -1]],
- range: 1
- }
- ];
- res['p'].attack = [
- {
- steps: [[pawnShift, 0]],
- range: 1
- }
- ];
- return res;
- }
-
-};
+export default class BerolinaRules extends AbstractBerolinaRules {};
--- /dev/null
+<p>TODO</p>
--- /dev/null
+@import url("/base_pieces.css");
--- /dev/null
+//TODO:
+// - pass btn on top + message if opponent just passed
+// - do not count points: rely on players' ability to do that
+// - implement Ko rule (need something in fen: lastMove)
+
+import ChessRules from "/base_rules.js";
+import Move from "/utils/Move.js";
+import PiPo from "/utils/PiPo.js";
+import {ArrayFun} from "/utils/array.js";
+
+export default class GoRules extends ChessRules {
+
+ // TODO: option oneColor (just alter pieces class of white stones)
+ static get Options() {
+ return {
+ input: [
+ {
+ label: "Board size",
+ variable: "bsize",
+ type: "number",
+ defaut: 9
+ }
+ ]
+ };
+ }
+
+ get hasFlags() {
+ return false;
+ }
+ get hasEnpassant() {
+ return false;
+ }
+ get clickOnly() {
+ return true;
+ }
+
+ getSvgChessboard() {
+ const flipped = (this.playerColor == 'b');
+ let board = `
+ <svg
+ viewBox="0 0 ${10*(this.size.y)} ${10*(this.size.x)}"
+ class="chessboard_SVG">`;
+ for (let i=0; i < this.size.x; i++) {
+ for (let j=0; j < this.size.y; j++) {
+ const ii = (flipped ? this.size.x - 1 - i : i);
+ const jj = (flipped ? this.size.y - 1 - j : j);
+ board += `
+ <rect
+ id="${this.coordsToId({x: ii, y: jj})}"
+ width="10"
+ height="10"
+ x="${10*j}"
+ y="${10*i}"
+ fill="transparent"
+ />`;
+ }
+ }
+ // Add lines to delimitate "squares"
+ for (let i = 0; i < this.size.x; i++) {
+ const y = i * 10 + 5, maxX = this.size.y * 10 - 5;
+ board += `
+ <line x1="5" y1="${y}" x2="${maxX}" y2="${y}"
+ stroke="black" stroke-width="0.2"/>`;
+ }
+ for (let i = 0; i < this.size.x; i++) {
+ const x = i * 10 + 5, maxY = this.size.x * 10 - 5;
+ board += `
+ <line x1="${x}" y1="5" x2="${x}" y2="${maxY}"
+ stroke="black" stroke-width="0.2"/>`;
+ }
+ board += "</svg>";
+ return board;
+ }
+
+ get size() {
+ return {
+ x: this.options["bsize"],
+ y: this.options["bsize"],
+ };
+ }
+
+ genRandInitBaseFen() {
+ const fenLine = C.FenEmptySquares(this.size.y);
+ return {
+ fen: (fenLine + '/').repeat(this.size.x - 1) + fenLine + " w 0",
+ o: {}
+ };
+ }
+
+ pieces(color, x, y) {
+ return {
+ 's': {
+ "class": "stone",
+ moves: []
+ }
+ };
+ }
+
+ doClick(coords) {
+ const [x, y] = [coords.x, coords.y];
+ if (this.board[x][y] != "")
+ return null;
+ const color = this.turn;
+ const oppCol = C.GetOppCol(color);
+ let move = new Move({
+ appear: [ new PiPo({ x: x, y: y, c: color, p: 's' }) ],
+ vanish: [],
+ start: {x: x, y: y}
+ });
+ this.playOnBoard(move); //put the stone
+ let noSuicide = false;
+ let captures = [];
+ for (let s of [[0, 1], [1, 0], [0, -1], [-1, 0]]) {
+ const [i, j] = [x + s[0], y + s[1]];
+ if (this.onBoard(i, j)) {
+ if (this.board[i][j] == "")
+ noSuicide = true; //clearly
+ else if (this.getColor(i, j) == color) {
+ // Free space for us = not a suicide
+ if (!noSuicide) {
+ let explored = ArrayFun.init(this.size.x, this.size.y, false);
+ noSuicide = this.searchForEmptySpace([i, j], color, explored);
+ }
+ }
+ else {
+ // Free space for opponent = not a capture
+ let explored = ArrayFun.init(this.size.x, this.size.y, false);
+ const captureSomething =
+ !this.searchForEmptySpace([i, j], oppCol, explored);
+ if (captureSomething) {
+ for (let ii = 0; ii < this.size.x; ii++) {
+ for (let jj = 0; jj < this.size.y; jj++) {
+ if (explored[ii][jj])
+ captures.push(new PiPo({ x: ii, y: jj, c: oppCol, p: 's' }));
+ }
+ }
+ }
+ }
+ }
+ }
+ this.undoOnBoard(move); //remove the stone
+ if (!noSuicide && captures.length == 0)
+ return null;
+ Array.prototype.push.apply(move.vanish, captures);
+ return move;
+ }
+
+ searchForEmptySpace([x, y], color, explored) {
+ if (explored[x][y])
+ return false; //didn't find empty space
+ explored[x][y] = true;
+ let res = false;
+ for (let s of [[1, 0], [0, 1], [-1, 0], [0, -1]]) {
+ const [i, j] = [x + s[0], y + s[1]];
+ if (this.onBoard(i, j)) {
+ if (this.board[i][j] == "")
+ res = true;
+ else if (this.getColor(i, j) == color)
+ res = this.searchForEmptySpace([i, j], color, explored) || res;
+ }
+ }
+ return res;
+ }
+
+ filterValid(moves) {
+ // Suicide check not here, because side-computation of captures
+ return moves;
+ }
+
+ getCurrentScore() {
+ return "*"; //Go game is a little special...
+ }
+
+};
--- /dev/null
+<p>TODO</p>
--- /dev/null
+.chessboard_SVG {
+ background-color: #BA8C63;
+}
+
+piece.white.stone {
+ background-image: url('/variants/Go/pieces/black_stone.svg');
+}
+piece.black.stone {
+ background-image: url('/variants/Go/pieces/white_stone.svg');
+}
--- /dev/null
+import ChessRules from "/base_rules.js";
+
+export default class BerolinaRules extends ChessRules {
+
+ pieces(color, x, y) {
+ const pawnShift = (color == "w" ? -1 : 1);
+ let res = super.pieces(color, x, y);
+ res['p'].moves = [
+ {
+ steps: [[pawnShift, 1], [pawnShift, -1]],
+ range: 1
+ }
+ ];
+ res['p'].attack = [
+ {
+ steps: [[pawnShift, 0]],
+ range: 1
+ }
+ ];
+ return res;
+ }
+
+};
--- /dev/null
+import ChessRules from "/base_rules.js";
+import Move from "/utils/Move.js";
+import PiPo from "/utils/PiPo.js";
+
+export default class AbstractSpecialCaptureRules extends ChessRules {
+
+ // Wouldn't make sense:
+ get hasEnpassant() {
+ return false;
+ }
+
+ pieces() {
+ return Object.assign({},
+ super.pieces(),
+ {
+ '+': {"class": "push-action"},
+ '-': {"class": "pull-action"}
+ }
+ );
+ }
+
+ // Modify capturing moves among listed pincer moves
+ addPincerCaptures(moves, byChameleon) {
+ const steps = this.pieces()['p'].moves[0].steps;
+ const color = this.turn;
+ const oppCol = C.GetOppCol(color);
+ moves.forEach(m => {
+ if (byChameleon && m.start.x != m.end.x && m.start.y != m.end.y)
+ // Chameleon not moving as pawn
+ return;
+ // Try capturing in every direction
+ for (let step of steps) {
+ const sq2 = [m.end.x + 2 * step[0], this.getY(m.end.y + 2 * step[1])];
+ if (
+ this.onBoard(sq2[0], sq2[1]) &&
+ this.board[sq2[0]][sq2[1]] != "" &&
+ this.getColor(sq2[0], sq2[1]) == color
+ ) {
+ // Potential capture
+ const sq1 = [m.end.x + step[0], this.getY(m.end.y + step[1])];
+ if (
+ this.board[sq1[0]][sq1[1]] != "" &&
+ this.getColor(sq1[0], sq1[1]) == oppCol
+ ) {
+ const piece1 = this.getPiece(sq1[0], sq1[1]);
+ if (!byChameleon || piece1 == 'p') {
+ m.vanish.push(
+ new PiPo({
+ x: sq1[0],
+ y: sq1[1],
+ c: oppCol,
+ p: piece1
+ })
+ );
+ }
+ }
+ }
+ }
+ });
+ }
+
+ addCoordinatorCaptures(moves, byChameleon) {
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ const kp = this.searchKingPos(color)[0];
+ moves.forEach(m => {
+ // Check piece-king rectangle (if any) corners for enemy pieces
+ if (m.end.x == kp[0] || m.end.y == kp[1])
+ return; //"flat rectangle"
+ const corner1 = [m.end.x, kp[1]];
+ const corner2 = [kp[0], m.end.y];
+ for (let [i, j] of [corner1, corner2]) {
+ if (this.board[i][j] != "" && this.getColor(i, j) == oppCol) {
+ const piece = this.getPiece(i, j);
+ if (!byChameleon || piece == 'r') {
+ m.vanish.push(
+ new PiPo({
+ x: i,
+ y: j,
+ p: piece,
+ c: oppCol
+ })
+ );
+ }
+ }
+ }
+ });
+ }
+
+ getLeaperCaptures([x, y], byChameleon, onlyOne) {
+ // Look in every direction for captures
+ const steps = this.pieces()['r'].moves[0].steps;
+ const color = this.turn;
+ const oppCol = C.GetOppCol(color);
+ let moves = [];
+ outerLoop: for (let step of steps) {
+ let [i, j] = [x + step[0], this.getY(y + step[1])];
+ while (this.onBoard(i, j) && this.board[i][j] == "")
+ [i, j] = [i + step[0], this.getY(j + step[1])];
+ if (
+ !this.onBoard(i, j) ||
+ this.getColor(i, j) == color ||
+ (byChameleon && this.getPiece(i, j) != 'n')
+ ) {
+ continue; //nothing to eat
+ }
+ let vanished = [];
+ while (true) {
+ // Found something (more) to eat:
+ vanished.push(
+ new PiPo({x: i, y: j, c: oppCol, p: this.getPiece(i, j)}));
+ [i, j] = [i + step[0], this.getY(j + step[1])];
+ while (this.onBoard(i, j) && this.board[i][j] == "") {
+ let mv = this.getBasicMove([x, y], [i, j]);
+ Array.prorotype.push.apply(mv.vanish, vanished);
+ moves.push(mv);
+ [i, j] = [i + step[0], this.getY(j + step[1])];
+ }
+ if (
+ onlyOne ||
+ !this.onBoard(i, j) ||
+ this.getColor(i, j) == color ||
+ (byChameleon && this.getPiece(i, j) != 'n')
+ ) {
+ continue outerLoop;
+ }
+ }
+ }
+ return moves;
+ }
+
+ // Chameleon
+ getChameleonCaptures(moves, pushPullType, onlyOneJump) {
+ const [x, y] = [moves[0].start.x, moves[0].start.y];
+ moves = moves.concat(
+ this.getKnightCaptures([x, y], "asChameleon", onlyOneJump));
+ // No "king capture" because king cannot remain under check
+ this.addPincerCaptures(moves, "asChameleon");
+ this.addCoordinatorCaptures(moves, "asChameleon");
+ this.addPushmePullyouCaptures(moves, "asChameleon", pushPullType);
+ // Post-processing: merge similar moves, concatenating vanish arrays
+ let mergedMoves = {};
+ moves.forEach(m => {
+ const key = m.end.x + this.size.x * m.end.y;
+ if (!mergedMoves[key])
+ mergedMoves[key] = m;
+ else {
+ for (let i = 1; i < m.vanish.length; i++)
+ mergedMoves[key].vanish.push(m.vanish[i]);
+ }
+ });
+ return Object.values(mergedMoves);
+ }
+
+ // type: nothing (freely, capture all), or pull or push, or "exclusive"
+ addPushmePullyouCaptures(moves, byChameleon, type) {
+ if (moves.length == 0)
+ return;
+ const [sx, sy] = [moves[0].start.x, moves[0].start.y];
+ const adjacentSteps = this.pieces()['r'].moves[0].steps;
+ let capturingPullDir = {};
+ const color = this.turn;
+ const oppCol = C.GetOppCol(color);
+ if (type != "push") {
+ adjacentSteps.forEach(step => {
+ const [bi, bj] = [sx - step[0], this.getY(sy - step[1])];
+ if (
+ this.onBoard(bi, bj) &&
+ this.board[bi][bj] != "" &&
+ this.getColor(bi, bj) == oppCol &&
+ (!byChameleon || this.getPiece(bi, bj) == 'q')
+ ) {
+ capturingPullDir[step[0] + "." + step[1]] = true;
+ }
+ });
+ }
+ moves.forEach(m => {
+ const [ex, ey] = [m.end.x, m.end.y];
+ const step = [
+ ex != x ? (ex - x) / Math.abs(ex - x) : 0,
+ ey != y ? (ey - y) / Math.abs(ey - y) : 0
+ ];
+ let vanishPull, vanishPush;
+ if (type != "pull") {
+ const [fi, fj] = [ex + step[0], this.getY(ey + step[1])];
+ if (
+ this.onBoard(fi, fj) &&
+ this.board[fi][fj] != "" &&
+ this.getColor(bi, bj) == oppCol &&
+ (!byChameleon || this.getPiece(fi, fj) == 'q')
+ ) {
+ vanishPush =
+ new PiPo({x: fi, y: fj, p: this.getPiece(fi, fj), c: oppCol});
+ }
+ }
+ if (capturingPullDir[step[0] + "." + step[1]]) {
+ const [bi, bj] = [x - step[0], this.getY(y - step[1])];
+ vanishPull =
+ new PiPo({x: bi, y: bj, p: this.getPiece(bi, bj), c: oppCol});
+ }
+ if (vanishPull && vanishPush && type == "exclusive") {
+ // Create a new move for push action (cannot play both)
+ let newMove = JSON.parse(JSON.stringify(m));
+ newMove.vanish.push(vanishPush);
+ newMove.choice = '+';
+ moves.push(newMove);
+ m.vanish.push(vanishPull);
+ m.choice = '-';
+ }
+ else {
+ if (vanishPull)
+ m.vanish.push(vanishPull);
+ if (vanishPush)
+ m.vanish.push(vanishPush);
+ }
+ });
+ }
+
+ underAttack([x, y], oppCol) {
+ // Generate all potential opponent moves, check if king captured.
+ // TODO: do it more efficiently.
+ const color = this.getColor(x, y);
+ for (let i = 0; i < this.size.x; i++) {
+ for (let j = 0; j < this.size.y; j++) {
+ if (
+ this.board[i][j] != "" && this.getColor(i, j) == oppCol &&
+ this.getPotentialMovesFrom([i, j]).some(m => {
+ return (
+ m.vanish.length >= 2 &&
+ [1, m.vanish.length - 1].some(k => m.vanish[k].p == 'k')
+ );
+ })
+ ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+};
--- /dev/null
+https://commons.wikimedia.org/wiki/File:Icon-push-object-3194184.svg
+https://www.svgrepo.com/svg/323068/pull
--- /dev/null
+<svg width="512px" height="512px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="#000" d="M93.773 44.664L68.55 57.39l37.313 81.938-12.09-94.664zm90.24 22.76L143.274 150.3l65.317-63.21-24.58-19.666zM18.16 125.832l10.63 26.8 45.698 5.903-56.328-32.703zm91.897 27.463c-3.665.025-7.122.8-10.256 2.295-17.278 8.244-21.157 36.154-8.663 62.34 6.016 12.59 15.09 23.08 25.218 29.158-10.305 83.743 29.287 137.784 91.366 163.535-6.917 35.032-33.276 60.587-61.855 84.023l93.987 2.895-9.897-9.165-42.893-7.88c33.39-22.314 45.968-38.168 56.854-71.397-5.27-10.354-18.877-24.948-25.432-35.895 19.945 2.308 49.183 5.725 53.745 10.135 3.78 9.84 21.27 31.79 27.754 59.832l6.336 20.523 49.205-46.476-2.654-10.328-39.57 26.59c.868-28.203-11.48-65.273-22.79-77.613 0 0-28.852-17.656-78.207-24.197-23.798-16.76-36.016-42.392-45.87-60.483l51.965 3.803 80.844-9.424s2.82 2.165 6.457 4.72c5.99 9.605 16.65 16.048 28.718 16.048 15.646 0 28.932-10.82 32.732-25.334H486v-18H366.857c-4.145-13.994-17.165-24.31-32.44-24.31-10.23 0-19.447 4.632-25.667 11.894-1.853-.17-3.7-.344-5.45-.605l-9.023 13.026-75.072 6.48-63.6-9c7.833-12.96 7.088-33.54-1.896-52.412-9.92-20.788-27.617-34.888-43.653-34.78zm224.36 83.394c8.846 0 15.825 6.976 15.825 15.822 0 8.845-6.98 15.822-15.824 15.822-2.576 0-4.986-.606-7.12-1.664 2.146-10.544-.162-23.4-1.073-27.73a15.89 15.89 0 0 1 8.193-2.25zM384 384l-32 112h128V384h-96z"/></svg>
\ No newline at end of file
--- /dev/null
+<svg height='300px' width='300px' fill="#000000" xmlns:x="http://ns.adobe.com/Extensibility/1.0/" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" xmlns:graph="http://ns.adobe.com/Graphs/1.0/" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve"><switch><foreignObject requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/" x="0" y="0" width="1" height="1"></foreignObject><g i:extraneous="self"><g><path d="M22.5,65.4L4.7,87.9c-2,2.6-1.6,6.3,1,8.3c1.1,0.9,2.4,1.3,3.7,1.3c1.7,0,3.5-0.8,4.6-2.2l17.2-21.6l-8.3-7.8 C22.7,65.7,22.6,65.6,22.5,65.4z"></path><path d="M56.2,38l-8-6.2c-1.5-1.2-3.5-1.7-5.4-1.3c-1.9,0.4-3.5,1.5-4.5,3.2L25.5,54.4c-1.6,2.4-1.2,5.6,0.9,7.6l12.5,11.7 l-7,15.4c-1.4,3-0.1,6.5,2.9,7.9c0.8,0.4,1.6,0.5,2.5,0.5c2.2,0,4.4-1.3,5.4-3.5l8.8-19.3c1.1-2.3,0.5-5.1-1.3-6.8l-8-7.4 l10.4-13.3l12.6,3.6c0.6,0.1,1.1,0.2,1.7,0.2l14.4-1v-9.9l-14.2,1L56.2,38z"></path><ellipse transform="matrix(0.43 -0.9028 0.9028 0.43 12.8873 68.1666)" cx="60.4" cy="23.9" rx="10.4" ry="10.4"></ellipse><path d="M94.5,2.5h-5.6c-1.2,0-2.1,0.9-2.1,2.1v90.8c0,1.2,0.9,2.1,2.1,2.1h5.6c1.2,0,2.1-0.9,2.1-2.1V4.6 C96.6,3.4,95.6,2.5,94.5,2.5z"></path></g></g></switch></svg>
\ No newline at end of file
--- /dev/null
+.piece.white.push-action {
+ background-image: url('/variants/_SpecialCaptures/pieces/capture_push.svg');
+}
+.piece.white.pull-action {
+ background-image: url('/variants/_SpecialCaptures/pieces/capture_pull.svg');
+}