X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FEmergo.js;h=a1a98488b23bb989950327137631f7312499a570;hb=665a8844aa49422692f184703944978510106aa7;hp=0d81759cb8ef9c46c6c5c90574a305179b7882b8;hpb=d2af3400944331ffd0c770f83857257c2f48e487;p=vchess.git diff --git a/client/src/variants/Emergo.js b/client/src/variants/Emergo.js index 0d81759c..a1a98488 100644 --- a/client/src/variants/Emergo.js +++ b/client/src/variants/Emergo.js @@ -1,7 +1,572 @@ -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 + // 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: null, b: null }; + if (reserve[0] > 0) this.reserve['w'] = { [V.PAWN]: reserve[0] }; + if (reserve[1] > 0) this.reserve['b'] = { [V.PAWN]: reserve[1] }; + // Local stack of captures during a turn (squares + directions) + this.captures = [ [] ]; + } + + atLeastOneCaptureFrom([x, y], color, forbiddenStep) { + for (let s of V.steps[V.BISHOP]) { + if ( + !forbiddenStep || + (s[0] != -forbiddenStep[0] || s[1] != -forbiddenStep[1]) + ) { + 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, captures[L-1].step) + ); + } + 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; + }; + + getLongestCaptures_aux([x, y], color, locSteps) { + 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 nextRes = + this.getLongestCaptures_aux([i + s[0], j + s[1]], color, locSteps); + res.push(1 + nextRes); + locSteps.pop(); + V.UndoOnBoard(this.board, move); + } + } + if (res.length == 0) return 0; + return Math.max(...res); + } + + getLongestCapturesFrom([x, y], color, locSteps) { + 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 stepRes = + this.getLongestCaptures_aux([i + s[0], j + s[1]], color, locSteps); + res.push({ step: s, length: 1 + stepRes }); + 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; + let caps = []; + if (L > 0) { + let locSteps = [ captures[L-1].step ]; + let res = + this.getLongestCapturesFrom(captures[L-1].square, color, locSteps); + Array.prototype.push.apply( + caps, + res.map(r => Object.assign({ square: captures[L-1].square }, r)) + ); + } + else { + 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)) + ); + } + } + } + } + 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 color = this.getColor(x1, y1); + const firstCodes = (color == 'w' ? [65, 97] : [97, 65]); + const cpCapt = this.board[capt[0]][capt[1]]; + let count1 = [cp1.charCodeAt(0) - firstCodes[0], -1]; + if (cp1[1] != '@') count1[1] = cp1.charCodeAt(1) - firstCodes[0]; + let countC = [cpCapt.charCodeAt(0) - firstCodes[1], -1]; + if (cpCapt[1] != '@') countC[1] = cpCapt.charCodeAt(1) - firstCodes[1]; + count1[1]++; + countC[0]--; + let colorChange = false, + captVanish = false; + if (countC[0] < 0) { + if (countC[1] >= 0) { + colorChange = true; + countC = [countC[1], -1]; + } + else captVanish = true; + } + const incPrisoners = String.fromCharCode(firstCodes[0] + count1[1]); + let mv = new Move({ + appear: [ + new PiPo({ + x: x2, + y: y2, + c: cp1[0], + p: incPrisoners + }) + ], + 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 (!captVanish) { + mv.appear.push( + new PiPo({ + x: capt[0], + y: capt[1], + c: String.fromCharCode( + firstCodes[(colorChange ? 0 : 1)] + countC[0]), + p: (colorChange ? '@' : 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; + if (!!this.reserve[color] && !this.atLeastOneCapture(color)) return []; + 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.end.x, move.end.y], + step: [(move.end.x - move.start.x)/2, (move.end.y - move.start.y)/2] + }); + if (this.atLeastOneCapture(color)) + // There could be other captures (mandatory) + move.notTheEnd = true; + } + else if (move.vanish == 0) { + const firstCode = (color == 'w' ? 65 : 97); + // Generally, reserveCount == 1 (except for shadow piece) + const reserveCount = move.appear[0].c.charCodeAt() - firstCode + 1; + this.reserve[color][V.PAWN] -= reserveCount; + 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'); + const firstCode = (color == 'w' ? 65 : 97); + const reserveCount = move.appear[0].c.charCodeAt() - firstCode + 1; + if (!this.reserve[color]) this.reserve[color] = { [V.PAWN]: 0 }; + this.reserve[color][V.PAWN] += reserveCount; + } + else if (move.vanish.length == 2) { + const L0 = this.captures.length; + let captures = this.captures[L0 - 1]; + captures.pop(); + } + } + + atLeastOneMove() { + const color = this.turn; + if (this.atLeastOneCapture(color)) return true; + 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]) return "*"; + let atLeastOnePiece = false; + outerLoop: 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) { + atLeastOnePiece = true; + break outerLoop; + } + } + } + if (!atLeastOnePiece) 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); + const L0 = this.captures.length; + if (this.captures[L0 - 1].length > 0) return V.CoordsToSquare(move.end); + return V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end); + } };