From: Benjamin Auder Date: Sun, 24 Jul 2022 21:28:23 +0000 (+0200) Subject: Reorganize folders (untested Baroque). Draft Go X-Git-Url: https://git.auder.net/images/pieces/Cwda/doc/html/%24%7BgetWhatsApp%28link%29%7D?a=commitdiff_plain;h=a89fb4e9c92632a2e8295048a86d7a0ae0a7ec46;p=xogo.git Reorganize folders (untested Baroque). Draft Go --- diff --git a/pieces/Avalam/black_stack.svg b/pieces/Avalam/black_stack.svg new file mode 100644 index 0000000..1ee0e2b --- /dev/null +++ b/pieces/Avalam/black_stack.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/pieces/Avalam/black_stack2.svg b/pieces/Avalam/black_stack2.svg new file mode 100644 index 0000000..de29024 --- /dev/null +++ b/pieces/Avalam/black_stack2.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pieces/Avalam/black_stack3.svg b/pieces/Avalam/black_stack3.svg new file mode 100644 index 0000000..3518349 --- /dev/null +++ b/pieces/Avalam/black_stack3.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pieces/Avalam/black_stack4.svg b/pieces/Avalam/black_stack4.svg new file mode 100644 index 0000000..6d92f7c --- /dev/null +++ b/pieces/Avalam/black_stack4.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pieces/Avalam/black_stack5.svg b/pieces/Avalam/black_stack5.svg new file mode 100644 index 0000000..9ea4b0a --- /dev/null +++ b/pieces/Avalam/black_stack5.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pieces/Avalam/white_stack.svg b/pieces/Avalam/white_stack.svg new file mode 100644 index 0000000..d997ee1 --- /dev/null +++ b/pieces/Avalam/white_stack.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/pieces/Avalam/white_stack2.svg b/pieces/Avalam/white_stack2.svg new file mode 100644 index 0000000..b238b2d --- /dev/null +++ b/pieces/Avalam/white_stack2.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pieces/Avalam/white_stack3.svg b/pieces/Avalam/white_stack3.svg new file mode 100644 index 0000000..3a3df31 --- /dev/null +++ b/pieces/Avalam/white_stack3.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pieces/Avalam/white_stack4.svg b/pieces/Avalam/white_stack4.svg new file mode 100644 index 0000000..91a4df3 --- /dev/null +++ b/pieces/Avalam/white_stack4.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pieces/Avalam/white_stack5.svg b/pieces/Avalam/white_stack5.svg new file mode 100644 index 0000000..7e8cf11 --- /dev/null +++ b/pieces/Avalam/white_stack5.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/variants/Atarigo/class.js b/variants/Atarigo/class.js index 5337f71..2cf9048 100644 --- a/variants/Atarigo/class.js +++ b/variants/Atarigo/class.js @@ -1,164 +1,14 @@ -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 = ` - `; - 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 += ` - `; - } - } - // 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 += ` - `; - } - for (let i = 0; i < this.size.x; i++) { - const x = i * 10 + 5, maxY = this.size.x * 10 - 5; - board += ` - `; - } - board += ""; - 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) { diff --git a/variants/Atarigo/style.css b/variants/Atarigo/style.css index d38ff8e..a2579d2 100644 --- a/variants/Atarigo/style.css +++ b/variants/Atarigo/style.css @@ -1,11 +1 @@ -.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"); diff --git a/variants/Baroque/class.js b/variants/Baroque/class.js index 98ef1c7..edf1dd5 100644 --- a/variants/Baroque/class.js +++ b/variants/Baroque/class.js @@ -1,10 +1,11 @@ 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 { @@ -33,9 +34,6 @@ export default class BaroqueRules extends ChessRules { get hasFlags() { return false; } - get hasEnpassant() { - return false; - } genRandInitBaseFen() { if (this.options["randomness"] == 0) @@ -61,25 +59,18 @@ export default class BaroqueRules extends ChessRules { 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: [ @@ -90,20 +81,20 @@ export default class BaroqueRules extends ChessRules { ] }, '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' } } ); @@ -162,236 +153,23 @@ export default class BaroqueRules extends ChessRules { 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; - } - }; diff --git a/variants/Baroque/style.css b/variants/Baroque/style.css index 1fd6566..12550dc 100644 --- a/variants/Baroque/style.css +++ b/variants/Baroque/style.css @@ -1,4 +1,5 @@ @import url("/base_pieces.css"); +@import url("/variants/_SpecialCaptures/style.css"); piece.white.immobilizer { background-image: url('/variants/Baroque/pieces/white_immobilizer.svg'); diff --git a/variants/Berolina/class.js b/variants/Berolina/class.js index fdca8fb..beabdc6 100644 --- a/variants/Berolina/class.js +++ b/variants/Berolina/class.js @@ -1,25 +1,3 @@ -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 {}; diff --git a/variants/Berolina/rules.html b/variants/Berolina/rules.html new file mode 100644 index 0000000..c65158e --- /dev/null +++ b/variants/Berolina/rules.html @@ -0,0 +1 @@ +

TODO

diff --git a/variants/Berolina/style.css b/variants/Berolina/style.css new file mode 100644 index 0000000..a3550bc --- /dev/null +++ b/variants/Berolina/style.css @@ -0,0 +1 @@ +@import url("/base_pieces.css"); diff --git a/variants/Go/class.js b/variants/Go/class.js new file mode 100644 index 0000000..b8b7780 --- /dev/null +++ b/variants/Go/class.js @@ -0,0 +1,174 @@ +//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 = ` + `; + 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 += ` + `; + } + } + // 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 += ` + `; + } + for (let i = 0; i < this.size.x; i++) { + const x = i * 10 + 5, maxY = this.size.x * 10 - 5; + board += ` + `; + } + board += ""; + 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... + } + +}; diff --git a/variants/Go/rules.html b/variants/Go/rules.html new file mode 100644 index 0000000..c65158e --- /dev/null +++ b/variants/Go/rules.html @@ -0,0 +1 @@ +

TODO

diff --git a/variants/Go/style.css b/variants/Go/style.css new file mode 100644 index 0000000..233a3a5 --- /dev/null +++ b/variants/Go/style.css @@ -0,0 +1,10 @@ +.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'); +} diff --git a/variants/_Berolina/class.js b/variants/_Berolina/class.js new file mode 100644 index 0000000..6066e51 --- /dev/null +++ b/variants/_Berolina/class.js @@ -0,0 +1,23 @@ +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; + } + +}; diff --git a/variants/Berolina/pieces/CREDITS b/variants/_Berolina/pieces/CREDITS similarity index 100% rename from variants/Berolina/pieces/CREDITS rename to variants/_Berolina/pieces/CREDITS diff --git a/variants/Berolina/pieces/black_pawn.svg b/variants/_Berolina/pieces/black_pawn.svg similarity index 100% rename from variants/Berolina/pieces/black_pawn.svg rename to variants/_Berolina/pieces/black_pawn.svg diff --git a/variants/Berolina/pieces/white_pawn.svg b/variants/_Berolina/pieces/white_pawn.svg similarity index 100% rename from variants/Berolina/pieces/white_pawn.svg rename to variants/_Berolina/pieces/white_pawn.svg diff --git a/variants/_SpecialCaptures/class.js b/variants/_SpecialCaptures/class.js new file mode 100644 index 0000000..9b48266 --- /dev/null +++ b/variants/_SpecialCaptures/class.js @@ -0,0 +1,241 @@ +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; + } + +}; diff --git a/variants/_SpecialCaptures/pieces/CREDITS b/variants/_SpecialCaptures/pieces/CREDITS new file mode 100644 index 0000000..efa0731 --- /dev/null +++ b/variants/_SpecialCaptures/pieces/CREDITS @@ -0,0 +1,2 @@ +https://commons.wikimedia.org/wiki/File:Icon-push-object-3194184.svg +https://www.svgrepo.com/svg/323068/pull diff --git a/variants/_SpecialCaptures/pieces/capture_pull.svg b/variants/_SpecialCaptures/pieces/capture_pull.svg new file mode 100644 index 0000000..5c56398 --- /dev/null +++ b/variants/_SpecialCaptures/pieces/capture_pull.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/variants/_SpecialCaptures/pieces/capture_push.svg b/variants/_SpecialCaptures/pieces/capture_push.svg new file mode 100644 index 0000000..6e8d0fd --- /dev/null +++ b/variants/_SpecialCaptures/pieces/capture_push.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/variants/_SpecialCaptures/style.css b/variants/_SpecialCaptures/style.css new file mode 100644 index 0000000..3360267 --- /dev/null +++ b/variants/_SpecialCaptures/style.css @@ -0,0 +1,6 @@ +.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'); +}