X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Fvariants%2FUltima.js;h=7f9531eb82ef281a9f5a899161f8e34c7d05b46e;hb=0279ac93197aa072991bce8f643ca68e99a54bc7;hp=4612da1004e273d10c5ee298b9fc103a0fb9836f;hpb=45338cdd7f037ba7b8c9e25d000ce351d86567a6;p=vchess.git diff --git a/public/javascripts/variants/Ultima.js b/public/javascripts/variants/Ultima.js index 4612da10..7f9531eb 100644 --- a/public/javascripts/variants/Ultima.js +++ b/public/javascripts/variants/Ultima.js @@ -52,7 +52,33 @@ class UltimaRules extends ChessRules getPotentialMovesFrom([x,y]) { - // TODO: pre-check: is thing on this square immobilized? If yes, return [] + // Pre-check: is thing on this square immobilized? + // In this case add potential suicide as a move "taking the immobilizer" + const piece = this.getPiece(x,y); + const color = this.getColor(x,y); + const oppCol = this.getOppCol(color); + const V = VariantRules; + const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + const [sizeX,sizeY] = V.size; + for (let step of adjacentSteps) + { + const [i,j] = [x+step[0],y+step[1]]; + if (i>=0 && i=0 && j filter directly in functions below } getSlideNJumpMoves([x,y], steps, oneStep) @@ -97,79 +118,277 @@ class UltimaRules extends ChessRules return moves; } + // Modify capturing moves among listed pawn moves + addPawnCaptures(moves, byChameleon) + { + const steps = VariantRules.steps[VariantRules.ROOK]; + const [sizeX,sizeY] = VariantRules.size; + const color = this.turn; + const oppCol = this.getOppCol(color); + moves.forEach(m => { + if (!!byChameleon && m.start.x!=m.end.x && m.start.y!=m.end.y) + return; //chameleon not moving as pawn + // Try capturing in every direction + for (let step of steps) + { + const sq2 = [m.end.x+2*step[0],m.end.y+2*step[1]]; + if (sq2[0]>=0 && sq2[0]=0 && sq2[1] { + // 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 = [Math.max(m.end.x,kp[0]), Math.min(m.end.y,kp[1])]; + const corner2 = [Math.min(m.end.x,kp[0]), Math.max(m.end.y,kp[1])]; + for (let [i,j] of [corner1,corner2]) + { + if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) == oppCol) + { + const piece = this.getPiece(i,j); + if (!byChameleon || piece == VariantRules.ROOK) + { + m.vanish.push( new PiPo({ + x:i, + y:j, + p:piece, + c:oppCol + }) ); + } + } + } + }); } - getPotentialKnightMoves(sq) + // Coordinator + getPotentialRookMoves(sq) { - return super.getPotentialQueenMoves(sq); + let moves = super.getPotentialQueenMoves(sq); + this.addRookCaptures(moves); + return moves; } - getPotentialBishopMoves(sq) + // Long-leaper + getKnightCaptures(startSquare, byChameleon) { - return super.getPotentialQueenMoves(sq); + // Look in every direction for captures + const V = VariantRules; + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + const [sizeX,sizeY] = V.size; + const color = this.turn; + const oppCol = this.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], y+step[1]]; + while (i>=0 && i=0 && j=sizeX || j<0 || j>=sizeY || this.getColor(i,j)==color + || (!!byChameleon && this.getPiece(i,j)!=V.KNIGHT)) + { + 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],j+step[1]]; + let vanished = [ new PiPo({x:x,y:y,c:color,p:piece}) ]; + while (cur[0]>=0 && cur[0]=0 && cur[1] { + const key = m.end.x + sizeX * m.end.y; + if (!mergedMoves[key]) + mergedMoves[key] = m; + else + { + for (let i=1; i { moves.push(mergedMoves[k]); }); + return moves; } - getPotentialKingMoves(sq) + // Withdrawer + addQueenCaptures(moves, byChameleon) { + if (moves.length == 0) + return; + const [x,y] = [moves[0].start.x,moves[0].start.y]; const V = VariantRules; - return this.getSlideNJumpMoves(sq, - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); + const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + let capturingDirections = []; + const color = this.turn; + const oppCol = this.getOppCol(color); + const [sizeX,sizeY] = V.size; + adjacentSteps.forEach(step => { + const [i,j] = [x+step[0],y+step[1]]; + if (i>=0 && i=0 && j { + 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 + ]; + // NOTE: includes() and even _.isEqual() functions fail... + // TODO: this test should be done only once per direction + if (capturingDirections.some(dir => + { return (dir[0]==-step[0] && dir[1]==-step[1]); })) + { + const [i,j] = [x-step[0],y-step[1]]; + m.vanish.push(new PiPo({ + x:i, + y:j, + p:this.getPiece(i,j), + c:oppCol + })); + } + }); } - // isAttacked() is OK because the immobilizer doesn't take - - isAttackedByPawn([x,y], colors) + getPotentialQueenMoves(sq) { - // Square (x,y) must be surrounded by two enemy pieces, - // and one of them at least should be a pawn - return false; + let moves = super.getPotentialQueenMoves(sq); + this.addQueenCaptures(moves); + return moves; } - isAttackedByRook(sq, colors) + getPotentialImmobilizerMoves(sq) { - // Enemy king must be on same file and a rook on same row (or reverse) + // Immobilizer doesn't capture + return super.getPotentialQueenMoves(sq); } - isAttackedByKnight(sq, colors) + getPotentialKingMoves(sq) { - // Square (x,y) must be on same line as a knight, - // and there must be empty square(s) behind. + const V = VariantRules; + return this.getSlideNJumpMoves(sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); } - isAttackedByBishop(sq, colors) + underCheck(move) { - // switch on piece nature on square sq: a chameleon attack as this piece - // ==> call the appropriate isAttackedBy... (exception of immobilizers) - // Other exception: a chameleon cannot attack a chameleon (seemingly...) + return false; //there is no check } - isAttackedByQueen(sq, colors) + getCheckSquares(move) { - // Square (x,y) must be adjacent to a queen, and the queen must have - // some free space in the opposite direction from (x,y) + const c = this.getOppCol(this.turn); //opponent + const saveKingPos = this.kingPos[c]; //king might be taken + this.play(move); + // The only way to be "under check" is to have lost the king (thus game over) + let res = this.kingPos[c][0] < 0 + ? [ JSON.parse(JSON.stringify(saveKingPos)) ] + : [ ]; + this.undo(move); + return res; } updateVariables(move) { - // Just update king position + // Just update king(s) position(s) const piece = this.getPiece(move.start.x,move.start.y); const c = this.getColor(move.start.x,move.start.y); if (piece == VariantRules.KING && move.appear.length > 0) @@ -177,6 +396,54 @@ class UltimaRules extends ChessRules this.kingPos[c][0] = move.appear[0].x; this.kingPos[c][1] = move.appear[0].y; } + // Does this move takes opponent's king? + const oppCol = this.getOppCol(c); + for (let i=1; i= 0) + return "*"; + + return this.checkGameEnd(); + } + + checkGameEnd() + { + // Stalemate, or our king disappeared + return this.turn == "w" ? "0-1" : "1-0"; } static get VALUES() { //TODO: totally experimental! @@ -193,6 +460,10 @@ class UltimaRules extends ChessRules static get SEARCH_DEPTH() { return 2; } //TODO? + static get THRESHOLD_MATE() { + return 500; //checkmates evals may be slightly below 1000 + } + static GenRandInitFen() { let pieces = { "w": new Array(8), "b": new Array(8) }; @@ -250,4 +521,15 @@ class UltimaRules extends ChessRules { return "0000"; //TODO: or "-" ? } + + getNotation(move) + { + if (move.appear.length == 0) + { + const startSquare = + String.fromCharCode(97 + move.start.y) + (VariantRules.size[0]-move.start.x); + return "^" + startSquare; //suicide + } + return super.getNotation(move); + } }