X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FYote.js;h=dc1ef684800da1aa4ad8d684a28b1c7ea6d28223;hb=cbe9537881e68f50c43f48d3699c4b248690fb4d;hp=0d81759cb8ef9c46c6c5c90574a305179b7882b8;hpb=1328e7dd5a73fd4a4eee85d5b86aea73b9a74f9f;p=vchess.git diff --git a/client/src/variants/Yote.js b/client/src/variants/Yote.js index 0d81759c..dc1ef684 100644 --- a/client/src/variants/Yote.js +++ b/client/src/variants/Yote.js @@ -1,7 +1,398 @@ -import { ChessRules } from "@/base_rules"; +import { ChessRules, Move, PiPo } from "@/base_rules"; export class YoteRules extends ChessRules { - // TODO + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + static get Monochrome() { + return true; + } + + static get Notoodark() { + return true; + } + + static get ReverseColors() { + return true; + } + + 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; + } + // 4) Check lastMove + if (!fenParsed.lastMove) return false; + const lmParts = fenParsed.lastMove.split(","); + for (lp of lmParts) { + if (lp != "-" && !lp.match(/^([a-f][1-5]){2,2}$/)) return false; + } + return true; + } + + static ParseFen(fen) { + const fenParts = fen.split(" "); + return Object.assign( + ChessRules.ParseFen(fen), + { + reserve: fenParts[3], + lastMove: fenParts[4] + } + ); + } + + static GenRandInitFen(randomness) { + return "6/6/6/6/6 w 0 12,12 -,-"; + } + + getFen() { + return ( + super.getFen() + " " + + this.getReserveFen() + " " + + this.getLastmoveFen() + ); + } + + 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]) + ); + } + + getLastmoveFen() { + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + return ( + ( + !lm['w'] + ? '-' + : V.CoordsToSquare(lm['w'].start) + V.CoordsToSquare(lm['w'].end) + ) + + "," + + ( + !lm['b'] + ? '-' + : V.CoordsToSquare(lm['b'].start) + V.CoordsToSquare(lm['b'].end) + ) + ); + } + + setOtherVariables(fen) { + const fenParsed = V.ParseFen(fen); + const reserve = fenParsed.reserve.split(",").map(x => parseInt(x, 10)); + this.reserve = { + w: { [V.PAWN]: reserve[0] }, + b: { [V.PAWN]: reserve[1] } + }; + // And last moves (to avoid undoing your last move) + const lmParts = fenParsed.lastMove.split(","); + this.lastMove = [{ w: null, b: null }]; + ['w', 'b'].forEach((c, i) => { + if (lmParts[i] != '-') { + this.lastMove[0][c] = { + start: V.SquareToCoords(lmParts[i].substr(0, 2)), + end: V.SquareToCoords(lmParts[i].substr(2)) + }; + } + }); + // Local stack to know if (current) last move captured something + this.captures = [false]; + } + + static get size() { + return { x: 5, y: 6 }; + } + + static get PIECES() { + return [V.PAWN]; + } + + getColor(i, j) { + if (i >= V.size.x) return i == V.size.x ? "w" : "b"; + return this.board[i][j].charAt(0); + } + + getPiece() { + return V.PAWN; + } + + getPpath(b) { + return "Yote/" + b; + } + + getReservePpath(index, color) { + return "Yote/" + color + V.PAWN; + } + + static get RESERVE_PIECES() { + return [V.PAWN]; + } + + canIplay(side, [x, y]) { + if (this.turn != side) return false; + const L = this.captures.length; + if (!this.captures[L-1]) return this.getColor(x, y) == side; + return (x < V.size.x && this.getColor(x, y) != side); + } + + hoverHighlight(x, y) { + const L = this.captures.length; + if (!this.captures[L-1]) return false; + const oppCol = V.GetOppCol(this.turn); + return (this.board[x][y] != V.EMPTY && this.getColor(x, y) == oppCol); + } + + // TODO: onlyClick() doesn't fulfill exactly its role. + // Seems that there is some lag... TOFIX + onlyClick([x, y]) { + const L = this.captures.length; + return (this.captures[L-1] && this.getColor(x, y) != this.turn); + } + + // PATCH related to above TO-DO: + getPossibleMovesFrom([x, y]) { + if (x < V.size.x && this.board[x][y] == V.EMPTY) return []; + return super.getPossibleMovesFrom([x, y]); + } + + doClick([x, y]) { + const L = this.captures.length; + if (!this.captures[L-1]) return null; + const oppCol = V.GetOppCol(this.turn); + if (this.board[x][y] == V.EMPTY || this.getColor(x, y) != oppCol) + return null; + return new Move({ + appear: [], + vanish: [ new PiPo({ x: x, y: y, c: oppCol, p: V.PAWN }) ], + end: { x: x, y: y } + }); + } + + getReserveMoves(x) { + const color = this.turn; + if (this.reserve[color][V.PAWN] == 0) return []; + let moves = []; + 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) { + let mv = new Move({ + appear: [ + new PiPo({ + x: i, + y: j, + c: color, + p: V.PAWN + }) + ], + vanish: [], + start: { x: x, y: 0 }, //a bit artificial... + end: { x: i, y: j } + }); + moves.push(mv); + } + } + } + return moves; + } + + getPotentialMovesFrom([x, y]) { + const L = this.captures.length; + if (this.captures[L-1]) { + if (x >= V.size.x) return []; + const mv = this.doClick([x, y]); + return (!!mv ? [mv] : []); + } + if (x >= V.size.x) + return this.getReserveMoves([x, y]); + return this.getPotentialPawnMoves([x, y]); + } + + getPotentialPawnMoves([x, y]) { + let moves = []; + const color = this.turn; + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + let forbiddenStep = null; + if (!!lm[color]) { + forbiddenStep = [ + lm[color].start.x - lm[color].end.x, + lm[color].start.y - lm[color].end.y + ]; + } + const oppCol = V.GetOppCol(color); + for (let s of V.steps[V.ROOK]) { + if ( + !!forbiddenStep && + s[0] == forbiddenStep[0] && s[1] == forbiddenStep[1] + ) { + continue; + } + const [i1, j1] = [x + s[0], y + s[1]]; + if (V.OnBoard(i1, j1)) { + if (this.board[i1][j1] == V.EMPTY) + moves.push(super.getBasicMove([x, y], [i1, j1])); + else if (this.getColor(i1, j1) == oppCol) { + const [i2, j2] = [i1 + s[0], j1 + s[1]]; + if (V.OnBoard(i2, j2) && this.board[i2][j2] == V.EMPTY) { + let mv = new Move({ + appear: [ + new PiPo({ x: i2, y: j2, c: color, p: V.PAWN }) + ], + vanish: [ + new PiPo({ x: x, y: y, c: color, p: V.PAWN }), + new PiPo({ x: i1, y: j1, c: oppCol, p: V.PAWN }) + ] + }); + moves.push(mv); + } + } + } + } + return moves; + } + + getAllPotentialMoves() { + let moves = super.getAllPotentialMoves(); + const color = this.turn; + moves = moves.concat( + this.getReserveMoves(V.size.x + (color == "w" ? 0 : 1))); + return moves; + } + + filterValid(moves) { + return moves; + } + + getCheckSquares() { + return []; + } + + atLeastOneMove() { + if (!super.atLeastOneMove()) { + // Search one reserve move + const moves = + this.getReserveMoves(V.size.x + (this.turn == "w" ? 0 : 1)); + if (moves.length > 0) return true; + return false; + } + return true; + } + + play(move) { + const color = this.turn; + move.turn = color; //for undo + const L = this.lastMove.length; + if (color == 'w') + this.lastMove.push({ w: null, b: this.lastMove[L-1]['b'] }); + if (move.appear.length == move.vanish.length) { //== 1 + // Normal move (non-capturing, non-dropping, non-removal) + let lm = this.lastMove[L - (color == 'w' ? 0 : 1)]; + if (!lm[color]) lm[color] = {}; + lm[color].start = move.start; + lm[color].end = move.end; + } + const oppCol = V.GetOppCol(color); + V.PlayOnBoard(this.board, move); + const captureNotEnding = ( + move.vanish.length == 2 && + this.board.some(b => b.some(cell => cell != "" && cell[0] == oppCol)) + ); + this.captures.push(captureNotEnding); + // Change turn unless I just captured something, + // and an opponent stone can be removed from board. + if (!captureNotEnding) { + this.turn = oppCol; + this.movesCount++; + } + this.postPlay(move); + } + + undo(move) { + V.UndoOnBoard(this.board, move); + if (this.turn == 'b') this.lastMove.pop(); + else this.lastMove['b'] = null; + this.captures.pop(); + if (move.turn != this.turn) { + this.turn = move.turn; + this.movesCount--; + } + this.postUndo(move); + } + + postPlay(move) { + if (move.vanish.length == 0) { + const color = move.appear[0].c; + this.reserve[color][V.PAWN]--; + if (this.reserve[color][V.PAWN] == 0) delete this.reserve[color]; + } + } + + postUndo(move) { + if (move.vanish.length == 0) { + const color = move.appear[0].c; + if (!this.reserve[color]) this.reserve[color] = { [V.PAWN]: 0 }; + this.reserve[color][V.PAWN]++; + } + } + + getCurrentScore() { + if (this.movesCount <= 2) return "*"; + const color = this.turn; + // If no stones on board, or no move available, I lose + if ( + this.board.every(b => { + return b.every(cell => { + return (cell == "" || cell[0] != color); + }); + }) + || + !this.atLeastOneMove() + ) { + return (color == 'w' ? "0-1" : "1-0"); + } + return "*"; + } + + static get SEARCH_DEPTH() { + return 4; + } + + evalPosition() { + let evaluation = super.evalPosition(); + // Add reserves: + evaluation += this.reserve["w"][V.PAWN]; + evaluation -= this.reserve["b"][V.PAWN]; + return evaluation; + } + + getNotation(move) { + if (move.vanish.length == 0) + // Placement: + return "@" + V.CoordsToSquare(move.end); + if (move.appear.length == 0) + // Removal after capture: + return V.CoordsToSquare(move.start) + "X"; + return ( + V.CoordsToSquare(move.start) + + (move.vanish.length == 2 ? "x" : "") + + V.CoordsToSquare(move.end) + ); + } };