X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FBall.js;h=47689757d5e78c7d18ee1e7fae1f0e0d5724ca88;hb=4313762da3237b04f204e121a20cab3ba7bb5dd2;hp=cf50a8096522883621c72287140fe584d9e394fe;hpb=6f2f94374f1e73c375edf732d9425e575e81fff7;p=vchess.git diff --git a/client/src/variants/Ball.js b/client/src/variants/Ball.js index cf50a809..47689757 100644 --- a/client/src/variants/Ball.js +++ b/client/src/variants/Ball.js @@ -1,26 +1,38 @@ import { ChessRules, Move, PiPo } from "@/base_rules"; -import { WildebeestRules } from "@/variants/Wildebeest"; import { ArrayFun } from "@/utils/array"; import { shuffle } from "@/utils/alea"; export class BallRules extends ChessRules { + + static get Lines() { + return [ + // White goal: + [[0, 3], [0, 6]], + [[0, 6], [1, 6]], + [[1, 6], [1, 3]], + [[1, 3], [0, 3]], + // Black goal: + [[9, 3], [9, 6]], + [[9, 6], [8, 6]], + [[8, 6], [8, 3]], + [[8, 3], [9, 3]] + ]; + } + static get PawnSpecs() { return Object.assign( {}, ChessRules.PawnSpecs, - { promotions: ChessRules.PawnSpecs.promotions.concat([V.WILDEBEEST]) } + { promotions: ChessRules.PawnSpecs.promotions.concat([V.PHOENIX]) } ); } static get HasFlags() { return false; } - static get HasCastle() { - return false; - } - static get WILDEBEEST() { - return 'w'; + static get PHOENIX() { + return 'h'; } static get BALL() { @@ -28,12 +40,6 @@ export class BallRules extends ChessRules { return "aa"; } - // Special code for "something to fill space" (around goals) - // --> If goal is outside the board (current prototype: it's inside) -// static get FILL() { -// return "ff"; -// } - static get HAS_BALL_CODE() { return { 'p': 's', @@ -42,7 +48,7 @@ export class BallRules extends ChessRules { 'b': 'c', 'q': 't', 'k': 'l', - 'w': 'y' + 'h': 'i' }; } @@ -54,13 +60,13 @@ export class BallRules extends ChessRules { 'c': 'b', 't': 'q', 'l': 'k', - 'y': 'w' + 'i': 'h' }; } static get PIECES() { return ChessRules.PIECES - .concat([V.WILDEBEEST]) + .concat([V.PHOENIX]) .concat(Object.keys(V.HAS_BALL_DECODE)) .concat(['a']); } @@ -75,6 +81,13 @@ export class BallRules extends ChessRules { return ChessRules.fen2board(f); } + static ParseFen(fen) { + return Object.assign( + ChessRules.ParseFen(fen), + { pmove: fen.split(" ")[4] } + ); + } + // Check that exactly one ball is on the board // + at least one piece per color. static IsGoodPosition(position) { @@ -82,18 +95,19 @@ export class BallRules extends ChessRules { const rows = position.split("/"); if (rows.length != V.size.x) return false; let pieces = { "w": 0, "b": 0 }; - const withBall = Object.keys(V.HAS_BALL_DECODE).concat([V.BALL]); + const withBall = Object.keys(V.HAS_BALL_DECODE).concat(['a']); let ballCount = 0; for (let row of rows) { let sumElts = 0; for (let i = 0; i < row.length; i++) { const lowerRi = row[i].toLowerCase(); if (V.PIECES.includes(lowerRi)) { - if (lowerRi != V.BALL) pieces[row[i] == lowerRi ? "b" : "w"]++; + if (lowerRi != 'a') pieces[row[i] == lowerRi ? "b" : "w"]++; if (withBall.includes(lowerRi)) ballCount++; sumElts++; - } else { - const num = parseInt(row[i]); + } + else { + const num = parseInt(row[i], 10); if (isNaN(num)) return false; sumElts += num; } @@ -105,48 +119,104 @@ export class BallRules extends ChessRules { return true; } + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParts = fen.split(" "); + if (fenParts.length != 5) return false; + if ( + fenParts[4] != "-" && + !fenParts[4].match(/^([a-i][1-9]){2,2}$/) + ) { + return false; + } + return true; + } + getPpath(b) { let prefix = ""; const withPrefix = Object.keys(V.HAS_BALL_DECODE) - .concat([V.WILDEBEEST]) - .concat(['a']); + .concat([V.PHOENIX]) + .concat(['a', 'w']); //TODO: 'w' for backward compatibility - to remove if (withPrefix.includes(b[1])) prefix = "Ball/"; return prefix + b; } + getPPpath(m) { + if ( + m.vanish.length == 2 && + m.appear.length == 2 && + m.appear[0].c != m.appear[1].c + ) { + // Take ball in place (from opponent) + return "Ball/inplace"; + } + return super.getPPpath(m); + } + canTake([x1, y1], [x2, y2]) { - // Capture enemy or pass ball to friendly pieces + if (this.getColor(x1, y1) !== this.getColor(x2, y2)) { + // The piece holding the ball cannot capture: + return ( + !(Object.keys(V.HAS_BALL_DECODE) + .includes(this.board[x1][y1].charAt(1))) + ); + } + // Pass: possible only if one of the friendly pieces has the ball return ( - this.getColor(x1, y1) !== this.getColor(x2, y2) || - Object.keys(V.HAS_BALL_DECODE).includes(this.board[x1][y1].charAt(1)) + Object.keys(V.HAS_BALL_DECODE).includes(this.board[x1][y1].charAt(1)) || + Object.keys(V.HAS_BALL_DECODE).includes(this.board[x2][y2].charAt(1)) ); } - getCheckSquares(color) { - return []; + getFen() { + return super.getFen() + " " + this.getPmoveFen(); + } + + getFenForRepeat() { + return super.getFenForRepeat() + "_" + this.getPmoveFen(); } - static GenRandInitFen(randomness) { - if (randomness == 0) - return "rnbqkwbnr/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/RNBQKWBNR w 0 -"; + getPmoveFen() { + const L = this.pmoves.length; + if (!this.pmoves[L-1]) return "-"; + return ( + V.CoordsToSquare(this.pmoves[L-1].start) + + V.CoordsToSquare(this.pmoves[L-1].end) + ); + } + + static GenRandInitFen(options) { + if (options.randomness == 0) + return "hbnrqrnhb/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/HBNRQRNHB w 0 - -"; let pieces = { w: new Array(9), b: new Array(9) }; for (let c of ["w", "b"]) { - if (c == 'b' && randomness == 1) { + if (c == 'b' && options.randomness == 1) { pieces['b'] = pieces['w']; break; } - // Get random squares for every piece, totally freely + // Get random squares for every piece, with bishops and phoenixes + // on different colors: let positions = shuffle(ArrayFun.range(9)); - const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q', 'w']; - const rem2 = positions[0] % 2; + const composition = ['b', 'b', 'h', 'h', 'n', 'n', 'r', 'r', 'q']; + let rem2 = positions[0] % 2; if (rem2 == positions[1] % 2) { // Fix bishops (on different colors) - for (let i=2; i<9; i++) { - if (positions[i] % 2 != rem2) + for (let i=4; i<9; i++) { + if (positions[i] % 2 != rem2) { [positions[1], positions[i]] = [positions[i], positions[1]]; + break; + } + } + } + rem2 = positions[2] % 2; + if (rem2 == positions[3] % 2) { + // Fix phoenixes too: + for (let i=4; i<9; i++) { + if (positions[i] % 2 != rem2) + [positions[3], positions[i]] = [positions[i], positions[3]]; } } for (let i = 0; i < 9; i++) pieces[c][positions[i]] = composition[i]; @@ -155,12 +225,26 @@ export class BallRules extends ChessRules { pieces["b"].join("") + "/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/" + pieces["w"].join("").toUpperCase() + - // En-passant allowed, but no flags - " w 0 -" + " w 0 - -" ); } - scanKings(fen) {} + scanKings() {} + + setOtherVariables(fen) { + super.setOtherVariables(fen); + const pmove = V.ParseFen(fen).pmove; + // Local stack of "pass moves" (no need for appear & vanish) + this.pmoves = [ + pmove != "-" + ? + { + start: V.SquareToCoords(pmove.substr(0, 2)), + end: V.SquareToCoords(pmove.substr(2)) + } + : null + ]; + } static get size() { return { x: 9, y: 9 }; @@ -174,7 +258,23 @@ export class BallRules extends ChessRules { } static get steps() { - return WildebeestRules.steps; + return Object.assign( + {}, + ChessRules.steps, + // Add phoenix moves + { + h: [ + [-2, -2], + [-2, 2], + [2, -2], + [2, 2], + [-1, 0], + [1, 0], + [0, -1], + [0, 1] + ] + } + ); } // Because of the ball, getPiece() could be wrong: @@ -217,16 +317,31 @@ export class BallRules extends ChessRules { ); } - // Post-processing: maybe the ball was taken, or a piece + ball + // Post-processing: maybe the ball was taken, or a piece + ball, + // or maybe a pass (ball <--> piece) if (mv.vanish.length == 2) { if ( // Take the ball? mv.vanish[1].c == 'a' || - // Capture a ball-holding piece? + // Capture a ball-holding piece? If friendly one, then adjust Object.keys(V.HAS_BALL_DECODE).includes(mv.vanish[1].p) ) { mv.appear[0].p = V.HAS_BALL_CODE[mv.appear[0].p]; - } else if (mv.vanish[1].c == mv.vanish[0].c) { + if (mv.vanish[1].c == mv.vanish[0].c) { + // "Capturing" self => pass + mv.appear[0].x = mv.start.x; + mv.appear[0].y = mv.start.y; + mv.appear.push( + new PiPo({ + x: mv.end.x, + y: mv.end.y, + p: V.HAS_BALL_DECODE[mv.vanish[1].p], + c: mv.vanish[0].c + }) + ); + } + } + else if (mv.vanish[1].c == mv.vanish[0].c) { // Pass the ball: the passing unit does not disappear mv.appear.push(JSON.parse(JSON.stringify(mv.vanish[0]))); mv.appear[0].p = V.HAS_BALL_CODE[mv.vanish[1].p]; @@ -238,31 +353,124 @@ export class BallRules extends ChessRules { return mv; } - // NOTE: if a pawn is captured en-passant, he doesn't hold the ball + // NOTE: if a pawn captures en-passant, he doesn't hold the ball // So base implementation is fine. getPotentialMovesFrom([x, y]) { - if (this.getPiece(x, y) == V.WILDEBEEST) - return this.getPotentialWildebeestMoves([x, y]); - return super.getPotentialMovesFrom([x, y]); + let moves = undefined; + const piece = this.getPiece(x, y); + if (piece == V.PHOENIX) + moves = this.getPotentialPhoenixMoves([x, y]); + else moves = super.getPotentialMovesFrom([x, y]); + // Add "taking ball in place" move (at most one in list) + for (let m of moves) { + if ( + m.vanish.length == 2 && + m.vanish[1].p != 'a' && + m.vanish[0].c != m.vanish[1].c && + Object.keys(V.HAS_BALL_DECODE).includes(m.appear[0].p) + ) { + const color = this.turn; + const oppCol = V.GetOppCol(color); + moves.push( + new Move({ + appear: [ + new PiPo({ + x: x, + y: y, + c: color, + p: m.appear[0].p + }), + new PiPo({ + x: m.vanish[1].x, + y: m.vanish[1].y, + c: oppCol, + p: V.HAS_BALL_DECODE[m.vanish[1].p] + }) + ], + vanish: [ + new PiPo({ + x: x, + y: y, + c: color, + p: piece + }), + new PiPo({ + x: m.vanish[1].x, + y: m.vanish[1].y, + c: oppCol, + p: m.vanish[1].p + }) + ], + end: { x: m.end.x, y: m.end.y } + }) + ); + break; + } + } + return moves; + } + + getSlideNJumpMoves(sq, steps, nbSteps) { + // "Sliders": at most 3 steps + return super.getSlideNJumpMoves(sq, steps, !nbSteps ? 3 : 1); } - getPotentialWildebeestMoves(sq) { - return this.getSlideNJumpMoves( - sq, - V.steps[V.KNIGHT].concat(V.steps[WildebeestRules.CAMEL]), - "oneStep" + getPotentialPhoenixMoves(sq) { + return super.getSlideNJumpMoves(sq, V.steps[V.PHOENIX], 1); + } + + getPmove(move) { + if ( + move.vanish.length == 2 && + move.appear.length == 2 && + move.appear[0].c != move.appear[1].c + ) { + // In-place pass: + return { + start: move.start, + end: move.end + }; + } + return null; + } + + oppositePasses(m1, m2) { + return ( + m1.start.x == m2.end.x && + m1.start.y == m2.end.y && + m1.end.x == m2.start.x && + m1.end.y == m2.start.y ); } filterValid(moves) { - return moves; + const L = this.pmoves.length; + const lp = this.pmoves[L-1]; + if (!lp) return moves; + return moves.filter(m => { + return ( + m.vanish.length == 1 || + m.appear.length == 1 || + m.appear[0].c == m.appear[1].c || + !this.oppositePasses(lp, m) + ); + }); } // isAttacked: unused here (no checks) - postPlay() {} - postUndo() {} + postPlay(move) { + this.pmoves.push(this.getPmove(move)); + } + + postUndo() { + this.pmoves.pop(); + } + + getCheckSquares() { + return []; + } getCurrentScore() { // Turn has changed: @@ -288,12 +496,11 @@ export class BallRules extends ChessRules { static get VALUES() { return { p: 1, - r: 5, + r: 3, n: 3, - b: 3, - q: 9, - w: 7, - k: 5, + b: 2, + q: 5, + h: 3, a: 0 //ball: neutral }; } @@ -348,4 +555,5 @@ export class BallRules extends ChessRules { finalSquare ); } + };