From: Benjamin Auder Date: Thu, 21 Jan 2021 02:22:20 +0000 (+0100) Subject: First draft of Emergo - still buggish. Fix Fanorona, avoid underscore in FEN and... X-Git-Url: https://git.auder.net/doc/html/packages.html?a=commitdiff_plain;h=d982fffc441a0443be90ba6f57a94d1d60b702c8;p=vchess.git First draft of Emergo - still buggish. Fix Fanorona, avoid underscore in FEN and file names --- diff --git a/client/public/images/pieces/Emergo/generateSVG_simple.py b/client/public/images/pieces/Emergo/generateSVG_simple.py index 1437626c..5cb53c9e 100755 --- a/client/public/images/pieces/Emergo/generateSVG_simple.py +++ b/client/public/images/pieces/Emergo/generateSVG_simple.py @@ -43,7 +43,7 @@ final = "" for color in ["white", "black"]: chrShift = 0 if color == "white" else 32 for number in range(12): - filename = chr(65 + number + chrShift) + "_.svg" + filename = chr(65 + number + chrShift) + "@.svg" f = open(filename, "w") f.write(preamble) f.write("\n"); diff --git a/client/public/images/pieces/Otage/b_.png b/client/public/images/pieces/Otage/b@.png similarity index 100% rename from client/public/images/pieces/Otage/b_.png rename to client/public/images/pieces/Otage/b@.png diff --git a/client/public/images/pieces/Otage/w_.png b/client/public/images/pieces/Otage/w@.png similarity index 100% rename from client/public/images/pieces/Otage/w_.png rename to client/public/images/pieces/Otage/w@.png diff --git a/client/public/images/pieces/Pacosako/w_.png b/client/public/images/pieces/Pacosako/w@.png similarity index 100% rename from client/public/images/pieces/Pacosako/w_.png rename to client/public/images/pieces/Pacosako/w@.png diff --git a/client/src/translations/rules/Emergo/en.pug b/client/src/translations/rules/Emergo/en.pug index 3a33838b..8c24bd6a 100644 --- a/client/src/translations/rules/Emergo/en.pug +++ b/client/src/translations/rules/Emergo/en.pug @@ -1,2 +1,62 @@ p.boxed - | TODO + | Similar to Checkers, with prisoners stacked below capturers. + +p + | The 9x9 board is initially empty. + | Each player receives 12 stackable pieces, "in hand". + | At each turn, a player must either + ul + li. + Enter a new piece on the board such that the opponent cannot capture it. + However, if a capture is already possible before the move, then + the piece can be dropped anywhere. + White cannot place a piece in the center at move 1. + li Play a move on the board, along diagonals. + +p. + Simple moves are Ferz moves: one step diagonally. + Captures work exactly as in Checkers: by jumping over a diagonally adjacent + piece to land on a free square just behind. + However, the resulting situation is more complex. See below. + If a capture is possible, then it must be played; in this case no piece can + be introduced on the board. + +p TODO: diagram + +p. + Let us consider each unit as a compound entity containing W white pieces + and B black ones (initially W = 1 and B = 0 for white units, + and vice-versa for black). + Captures can then be described formally as follows. + +p. + As white: + If W1/B1 jumps over W2/B2 at square S2 to land on S1', then + W1/(B1+1) arrives on S1' and W2/(B2-1) remains on S2. + If W2 = B2 - 1 = 0, nothing remains at the captured unit location. + As black: exchange W and B above. + +p. + In other words, each unit is a stack of friendly and enemy pieces, with + friendly pieces on top. After each capture, the prisoners part of the + stack is incremented, while the "jailers" counterpart at the captured + location decreases by one. + +p. + When several capturing chains are available, + the player has to select one of the longest (as in Checkers). + +p TODO: diagram (from mindsports.nl) + +h3 More information + +p + | See the + a(href="https://www.mindsports.nl/index.php/arena/emergo/88-rules") + | Emergo page + |  on the author's website. + | Rules are also described on + a(href="http://www.iggamecenter.com/info/en/emergo.html") iggamecenter + | , where you can also play this game. + +p Inventors: Christian Freeling and Ed van Zon (1986) diff --git a/client/src/variants/Crazyhouse.js b/client/src/variants/Crazyhouse.js index 64ce48b3..7fa47b72 100644 --- a/client/src/variants/Crazyhouse.js +++ b/client/src/variants/Crazyhouse.js @@ -185,17 +185,15 @@ export class CrazyhouseRules extends ChessRules { } atLeastOneMove() { - if (!super.atLeastOneMove()) { - // Search one reserve move - for (let i = 0; i < V.RESERVE_PIECES.length; i++) { - const moves = this.filterValid( - this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i]) - ); - if (moves.length > 0) return true; - } - return false; + if (super.atLeastOneMove()) return true; + // Search one reserve move + for (let i = 0; i < V.RESERVE_PIECES.length; i++) { + const moves = this.filterValid( + this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i]) + ); + if (moves.length > 0) return true; } - return true; + return false; } postPlay(move) { diff --git a/client/src/variants/Emergo.js b/client/src/variants/Emergo.js index 264186b3..caa2e982 100644 --- a/client/src/variants/Emergo.js +++ b/client/src/variants/Emergo.js @@ -1,13 +1,535 @@ -import { ChessRules } from "@/base_rules"; +import { ChessRules, Move, PiPo } from "@/base_rules"; +import { randInt } from "@/utils/alea"; +import { ArrayFun } from "@/utils/array"; -export class YoteRules extends ChessRules { +export class EmergoRules extends ChessRules { - // TODO - //If (as white) a pile W1/B1 jumps over another pile W2/B2, it lets on the intermediate square exactly W2 men, to end as W1/(B1+B2). - //In the first case in the video, W1=1, B1=0, W2=0, B2=1 ==> 1/1 and finally 1/2 with nothing on intermediate squares since W2 is always 0. - //In the second case, W1=1, B1=0, W2=1, B2=1 ==> 1 man left on intermediate square, end as 1/1. - //...I think it's that (?). Not very well explained either on Wikipedia or mindsports.nl :/ - //Found this link: http://www.iggamecenter.com/info/en/emergo.html - so it's all clear now ! I'll add the game soon. - //Btw, I'm not a big fan of this naming "men" for pieces, but, won't contradict the author on that  + // Simple encoding: A to L = 1 to 12, from left to right, if white controls. + // Lowercase if black controls. + // Single piece (no prisoners): A@ to L@ (+ lowercase) + + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + static get DarkBottomRight() { + return true; + } + + // board element == file name: + static board2fen(b) { + return b; + } + static fen2board(f) { + return f; + } + + static IsGoodPosition(position) { + if (position.length == 0) return false; + const rows = position.split("/"); + if (rows.length != V.size.x) return false; + for (let row of rows) { + let sumElts = 0; + for (let i = 0; i < row.length; i++) { + // Add only 0.5 per symbol because 2 per piece + if (row[i].toLowerCase().match(/^[a-lA-L@]$/)) sumElts += 0.5; + else { + const num = parseInt(row[i], 10); + if (isNaN(num) || num <= 0) return false; + sumElts += num; + } + } + if (sumElts != V.size.y) return false; + } + return true; + } + + static GetBoard(position) { + const rows = position.split("/"); + let board = ArrayFun.init(V.size.x, V.size.y, ""); + for (let i = 0; i < rows.length; i++) { + let j = 0; + for (let indexInRow = 0; indexInRow < rows[i].length; indexInRow++) { + const character = rows[i][indexInRow]; + const num = parseInt(character, 10); + // If num is a number, just shift j: + if (!isNaN(num)) j += num; + else + // Something at position i,j + board[i][j++] = V.fen2board(character + rows[i][++indexInRow]); + } + } + return board; + } + + getPpath(b) { + return "Emergo/" + b; + } + + getColor(x, y) { + if (x >= V.size.x) return x == V.size.x ? "w" : "b"; + if (this.board[x][y].charCodeAt(0) < 97) return 'w'; + return 'b'; + } + + getPiece() { + return V.PAWN; //unused + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParsed = V.ParseFen(fen); + // 3) Check reserves + if ( + !fenParsed.reserve || + !fenParsed.reserve.match(/^([0-9]{1,2},?){2,2}$/) + ) { + return false; + } + return true; + } + + static ParseFen(fen) { + const fenParts = fen.split(" "); + return Object.assign( + ChessRules.ParseFen(fen), + { reserve: fenParts[3] } + ); + } + + static get size() { + return { x: 9, y: 9 }; + } + + static GenRandInitFen(randomness) { + return "9/9/9/9/9/9/9/9/9 w 0 12,12"; + } + + getFen() { + return super.getFen() + " " + this.getReserveFen(); + } + + getFenForRepeat() { + return super.getFenForRepeat() + "_" + this.getReserveFen(); + } + + getReserveFen() { + return ( + (!this.reserve["w"] ? 0 : this.reserve["w"][V.PAWN]) + "," + + (!this.reserve["b"] ? 0 : this.reserve["b"][V.PAWN]) + ); + } + + getReservePpath(index, color) { + return "Emergo/" + (color == 'w' ? 'A' : 'a') + '@'; + } + + static get RESERVE_PIECES() { + return [V.PAWN]; //only array length matters + } + + setOtherVariables(fen) { + const reserve = + V.ParseFen(fen).reserve.split(",").map(x => parseInt(x, 10)); + this.reserve = { + w: { [V.PAWN]: reserve[0] }, + b: { [V.PAWN]: reserve[1] } + }; + // Local stack of captures during a turn (squares + directions) + this.captures = [ [] ]; + } + + atLeastOneCaptureFrom([x, y], color) { + for (let s of V.steps[V.BISHOP]) { + const [i, j] = [x + s[0], y + s[1]]; + if ( + V.OnBoard(i + s[0], j + s[1]) && + this.board[i][j] != V.EMPTY && + this.getColor(i, j) != color && + this.board[i + s[0]][j + s[1]] == V.EMPTY + ) { + return true; + } + } + return false; + } + + atLeastOneCapture(color) { + const L0 = this.captures.length; + const captures = this.captures[L0 - 1]; + const L = captures.length; + if (L > 0) return this.atLeastOneCaptureFrom(captures[L-1].square, color); + for (let i = 0; i < V.size.x; i++) { + for (let j=0; j< V.size.y; j++) { + if ( + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == color && + this.atLeastOneCaptureFrom([i, j], color) + ) { + return true; + } + } + } + return false; + } + + maxLengthIndices(caps) { + let maxLength = 0; + let res = []; + for (let i = 0; i < caps.length; i++) { + if (caps[i].length > maxLength) { + res = [i]; + maxLength = caps[i].length; + } + else if (caps[i].length == maxLength) res.push(i); + } + return res; + }; + + getLongestCapturesFrom([x, y], color, locSteps) { + // + // TODO: debug here, from + // 9/9/2a@1a@4/5A@3/9/3aa1A@3/9/9/8A@ w 10 8,9 + // White to move, double capture. + // + let res = []; + const L = locSteps.length; + const lastStep = (L > 0 ? locSteps[L-1] : null); + for (let s of V.steps[V.BISHOP]) { + if (!!lastStep && s[0] == -lastStep[0] && s[1] == -lastStep[1]) continue; + const [i, j] = [x + s[0], y + s[1]]; + if ( + V.OnBoard(i + s[0], j + s[1]) && + this.board[i + s[0]][j + s[1]] == V.EMPTY && + this.board[i][j] != V.EMPTY && + this.getColor(i, j) != color + ) { + const move = this.getBasicMove([x, y], [i + s[0], j + s[1]], [i, j]); + locSteps.push(s); + V.PlayOnBoard(this.board, move); + const sRes = this.getLongestCapturesFrom( + [i + s[0], j + s[1]], color, locSteps); + res.push({ + step: s, + length: 1 + (sRes.length == 0 ? 0 : sRes[0].length) + }); + locSteps.pop(); + V.UndoOnBoard(this.board, move); + } + } + return this.maxLengthIndices(res).map(i => res[i]); + } + + getAllLongestCaptures(color) { + const L0 = this.captures.length; + const captures = this.captures[L0 - 1]; + const L = captures.length; + if (L > 0) { + let locSteps = []; + const caps = Object.assign( + { square: captures[L-1].square }, + this.getLongestCapturesFrom(captures[L-1].square, color, locSteps) + ); + return this.maxLengthIndices(caps).map(i => caps[i]); + } + let caps = []; + for (let i = 0; i < V.size.x; i++) { + for (let j=0; j < V.size.y; j++) { + if ( + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == color + ) { + let locSteps = []; + let res = this.getLongestCapturesFrom([i, j], color, locSteps); + Array.prototype.push.apply( + caps, + res.map(r => Object.assign({ square: [i, j] }, r)) + ); + } + } + } + +console.log(caps); + + return this.maxLengthIndices(caps).map(i => caps[i]); + } + + getBasicMove([x1, y1], [x2, y2], capt) { + const cp1 = this.board[x1][y1]; + if (!capt) { + return new Move({ + appear: [ new PiPo({ x: x2, y: y2, c: cp1[0], p: cp1[1] }) ], + vanish: [ new PiPo({ x: x1, y: y1, c: cp1[0], p: cp1[1] }) ] + }); + } + // Compute resulting types based on jumped + jumping pieces + const cpCapt = this.board[capt[0]][capt[1]]; + const newAtCapt = cpCapt.charCodeAt(0) - 1; + const newAtDest = + cp1[1] == '@' + ? (cp1.charCodeAt(0) < 97 ? 65 : 97) + : (cp1.charCodeAt(1) + 1); + const color = this.turn; + let mv = new Move({ + appear: [ + new PiPo({ + x: x2, + y: y2, + c: cp1[0], + p: String.fromCharCode(newAtDest) + }) + ], + vanish: [ + new PiPo({ x: x1, y: y1, c: cp1[0], p: cp1[1] }), + new PiPo({ x: capt[0], y: capt[1], c: cpCapt[0], p: cpCapt[1] }) + ] + }); + if ([64, 96].includes(newAtCapt)) { + // Enemy units vanish from capturing square + if (cpCapt.charAt(1) != '@') { + // Out units remain: + mv.appear.push( + new PiPo({ + x: capt[0], + y: capt[1], + c: cpCapt[0], + p: '@' + }) + ); + } + } + else { + mv.appear.push( + new PiPo({ + x: capt[0], + y: capt[1], + c: String.fromCharCode(newAtCapt), + p: cpCapt[1] + }) + ); + } + return mv; + } + + getReserveMoves(x) { + const color = this.turn; + if (!this.reserve[color] || this.atLeastOneCapture(color)) return []; + let moves = []; + const shadowPiece = + this.reserve[V.GetOppCol(color)] == null + ? this.reserve[color][V.PAWN] - 1 + : 0; + const appearColor = String.fromCharCode( + (color == 'w' ? 'A' : 'a').charCodeAt(0) + shadowPiece); + const addMove = ([i, j]) => { + moves.push( + new Move({ + appear: [ new PiPo({ x: i, y: j, c: appearColor, p: '@' }) ], + vanish: [], + start: { x: V.size.x + (color == 'w' ? 0 : 1), y: 0 } + }) + ); + }; + const oppCol = V.GetOppCol(color); + const opponentCanCapture = this.atLeastOneCapture(oppCol); + for (let i = 0; i < V.size.x; i++) { + for (let j = i % 2; j < V.size.y; j += 2) { + if ( + this.board[i][j] == V.EMPTY && + // prevent playing on central square at move 1: + (this.movesCount >= 1 || i != 4 || j != 4) + ) { + if (opponentCanCapture) addMove([i, j]); + else { + let canAddMove = true; + for (let s of V.steps[V.BISHOP]) { + if ( + V.OnBoard(i + s[0], j + s[1]) && + V.OnBoard(i - s[0], j - s[1]) && + this.board[i + s[0]][j + s[1]] != V.EMPTY && + this.board[i - s[0]][j - s[1]] == V.EMPTY && + this.getColor(i + s[0], j + s[1]) == oppCol + ) { + canAddMove = false; + break; + } + } + if (canAddMove) addMove([i, j]); + } + } + } + } + return moves; + } + + getPotentialMovesFrom([x, y], longestCaptures) { + if (x >= V.size.x) { + if (longestCaptures.length == 0) return this.getReserveMoves(x); + return []; + } + const color = this.turn; + const L0 = this.captures.length; + const captures = this.captures[L0 - 1]; + const L = captures.length; + let moves = []; + if (longestCaptures.length > 0) { + if ( + L > 0 && + (x != captures[L-1].square[0] || y != captures[L-1].square[1]) + ) { + return []; + } + longestCaptures.forEach(lc => { + if (lc.square[0] == x && lc.square[1] == y) { + const s = lc.step; + const [i, j] = [x + s[0], y + s[1]]; + moves.push(this.getBasicMove([x, y], [i + s[0], j + s[1]], [i, j])); + } + }); + return moves; + } + // Just search simple moves: + for (let s of V.steps[V.BISHOP]) { + const [i, j] = [x + s[0], y + s[1]]; + if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) + moves.push(this.getBasicMove([x, y], [i, j])); + } + return moves; + } + + getAllValidMoves() { + const color = this.turn; + const longestCaptures = this.getAllLongestCaptures(color); + let potentialMoves = []; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) { + Array.prototype.push.apply( + potentialMoves, + this.getPotentialMovesFrom([i, j], longestCaptures) + ); + } + } + } + // Add reserve moves + potentialMoves = potentialMoves.concat( + this.getReserveMoves(V.size.x + (color == "w" ? 0 : 1)) + ); + return potentialMoves; + } + + getPossibleMovesFrom([x, y]) { + const longestCaptures = this.getAllLongestCaptures(this.getColor(x, y)); + return this.getPotentialMovesFrom([x, y], longestCaptures); + } + + filterValid(moves) { + return moves; + } + + getCheckSquares() { + return []; + } + + play(move) { + const color = this.turn; + move.turn = color; //for undo + V.PlayOnBoard(this.board, move); + if (move.vanish.length == 2) { + const L0 = this.captures.length; + let captures = this.captures[L0 - 1]; + captures.push({ + square: [move.start.x, move.start.y], + step: [move.end.x - move.start.x, move.end.y - move.start.y] + }); + if (this.atLeastOneCapture()) + // There could be other captures (optional) + move.notTheEnd = true; + } + else if (move.vanish == 0) { + if (--this.reserve[color][V.PAWN] == 0) this.reserve[color] = null; + } + if (!move.notTheEnd) { + this.turn = V.GetOppCol(color); + this.movesCount++; + this.captures.push([]); + } + } + + undo(move) { + V.UndoOnBoard(this.board, move); + if (!move.notTheEnd) { + this.turn = move.turn; + this.movesCount--; + this.captures.pop(); + } + if (move.vanish.length == 0) { + const color = (move.appear[0].c == 'A' ? 'w' : 'b'); + if (!this.reserve[color]) this.reserve[color] = { [V.PAWN]: 1 }; + else this.reserve[color][V.PAWN]++; + } + else if (move.vanish.length == 2) { + const L0 = this.captures.length; + let captures = this.captures[L0 - 1]; + captures.pop(); + } + } + + atLeastOneMove() { + if (this.atLeastOneCapture()) return true; + const color = this.turn; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) { + const moves = this.getPotentialMovesFrom([i, j], []); + if (moves.length > 0) return true; + } + } + } + const reserveMoves = + this.getReserveMoves(V.size.x + (this.turn == "w" ? 0 : 1)); + return (reserveMoves.length > 0); + } + + getCurrentScore() { + const color = this.turn; + // If no pieces on board + reserve, I lose + if ( + !this.reserve[color] && + this.board.every(b => { + return b.every(cell => { + return (cell == "" || cell[0] != color); + }); + }) + ) { + return (color == 'w' ? "0-1" : "1-0"); + } + if (!this.atLeastOneMove()) return "1/2"; + return "*"; + } + + getComputerMove() { + // Random mover for now (TODO) + const color = this.turn; + let mvArray = []; + let mv = null; + while (this.turn == color) { + const moves = this.getAllValidMoves(); + mv = moves[randInt(moves.length)]; + mvArray.push(mv); + this.play(mv); + } + for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]); + return (mvArray.length > 1 ? mvArray : mvArray[0]); + } + + getNotation(move) { + if (move.vanish.length == 0) return "@" + V.CoordsToSquare(move.end); + return V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end); + } }; diff --git a/client/src/variants/Fanorona.js b/client/src/variants/Fanorona.js index 04eea2c3..39911635 100644 --- a/client/src/variants/Fanorona.js +++ b/client/src/variants/Fanorona.js @@ -246,18 +246,16 @@ export class FanoronaRules extends ChessRules { const color = this.turn; move.turn = color; //for undo V.PlayOnBoard(this.board, move); - const L0 = this.captures.length; - let captures = this.captures[L0 - 1]; if (move.vanish.length >= 2) { + const L0 = this.captures.length; + let captures = this.captures[L0 - 1]; captures.push({ square: move.start, step: [move.end.x - move.start.x, move.end.y - move.start.y] }); if (this.atLeastOneCapture()) // There could be other captures (optional) - // This field is mostly useful for computer play. move.notTheEnd = true; - else captures.pop(); //useless now } if (!move.notTheEnd) { this.turn = V.GetOppCol(color); @@ -268,12 +266,12 @@ export class FanoronaRules extends ChessRules { undo(move) { V.UndoOnBoard(this.board, move); - if (move.turn != this.turn) { + if (!move.notTheEnd) { this.turn = move.turn; this.movesCount--; this.captures.pop(); } - else { + if (move.vanish.length >= 2) { const L0 = this.captures.length; let captures = this.captures[L0 - 1]; captures.pop(); diff --git a/client/src/variants/Otage.js b/client/src/variants/Otage.js index 9eb900a4..2a8891ff 100644 --- a/client/src/variants/Otage.js +++ b/client/src/variants/Otage.js @@ -36,7 +36,7 @@ export class OtageRules extends ChessRules { x: ['b', 'k'], y: ['q', 'q'], z: ['q', 'k'], - '_': ['k', 'k'] + '@': ['k', 'k'] }; } @@ -61,7 +61,7 @@ export class OtageRules extends ChessRules { for (let i = 0; i < row.length; i++) { const lowR = row[i].toLowerCase(); const readNext = !(ChessRules.PIECES.includes(lowR)); - if (!!(lowR.match(/[a-z_]/))) { + if (!!(lowR.match(/[a-z@]/))) { sumElts++; if (lowR == 'k') kings[row[i]]++; else if (readNext) { @@ -146,7 +146,7 @@ export class OtageRules extends ChessRules { const c = fenRows[i].charAt(j); const lowR = c.toLowerCase(); const readNext = !(ChessRules.PIECES.includes(lowR)); - if (!!(lowR.match(/[a-z_]/))) { + if (!!(lowR.match(/[a-z@]/))) { if (lowR == 'k') this.kingPos[c == 'k' ? 'b' : 'w'] = [i, k]; else if (readNext) { const up = this.getUnionPieces(fenRows[i][++j], lowR); diff --git a/client/src/variants/Pacosako.js b/client/src/variants/Pacosako.js index b5f703f8..158abf22 100644 --- a/client/src/variants/Pacosako.js +++ b/client/src/variants/Pacosako.js @@ -30,25 +30,25 @@ export class PacosakoRules extends ChessRules { x: ['b', 'k'], y: ['q', 'q'], z: ['q', 'k'], - '_': ['k', 'k'] + '@': ['k', 'k'] }; } static fen2board(f) { - // Underscore is character 95, in file w_ - return f.charCodeAt() <= 95 ? "w" + f.toLowerCase() : "b" + f; + // Arobase is character 64 + return f.charCodeAt() <= 90 ? "w" + f.toLowerCase() : "b" + f; } static IsGoodPosition(position) { if (position.length == 0) return false; const rows = position.split("/"); if (rows.length != V.size.x) return false; - let kingSymb = ['k', 'g', 'm', 'u', 'x', 'z', '_']; + let kingSymb = ['k', 'g', 'm', 'u', 'x', 'z', '@']; let kings = { 'k': 0, 'K': 0 }; for (let row of rows) { let sumElts = 0; for (let i = 0; i < row.length; i++) { - if (!!(row[i].toLowerCase().match(/[a-z_]/))) { + if (!!(row[i].toLowerCase().match(/[a-z@]/))) { sumElts++; if (kingSymb.includes(row[i])) kings['k']++; // Not "else if", if two kings dancing together @@ -104,12 +104,12 @@ export class PacosakoRules extends ChessRules { this.kingPos = { w: [-1, -1], b: [-1, -1] }; const fenRows = V.ParseFen(fen).position.split("/"); const startRow = { 'w': V.size.x - 1, 'b': 0 }; - const kingSymb = ['k', 'g', 'm', 'u', 'x', 'z', '_']; + const kingSymb = ['k', 'g', 'm', 'u', 'x', 'z', '@']; for (let i = 0; i < fenRows.length; i++) { let k = 0; for (let j = 0; j < fenRows[i].length; j++) { const c = fenRows[i].charAt(j); - if (!!(c.toLowerCase().match(/[a-z_]/))) { + if (!!(c.toLowerCase().match(/[a-z@]/))) { if (kingSymb.includes(c)) this.kingPos["b"] = [i, k]; // Not "else if", in case of two kings dancing together