From: Benjamin Auder <benjamin.auder@somewhere> Date: Thu, 13 Dec 2018 02:29:11 +0000 (+0100) Subject: About to finish completed Ultima rules X-Git-Url: https://git.auder.net/variants/Chakart/css/assets/current/pieces/common.css?a=commitdiff_plain;h=2f3c845159670ec8bfba8a3999571d6ee1e45320;p=vchess.git About to finish completed Ultima rules --- diff --git a/TODO b/TODO index b3568538..bb4d6e16 100644 --- a/TODO +++ b/TODO @@ -1,5 +1 @@ Full detection of repeated positions (including turn) -In UltimaChess, consider these rules modifications: http://www.inference.org.uk/mackay/ultima/ultima.html -Reintroduce isAttackedBy in Ultima, and fix it for pawns --> underCheck + stalemate ---> take into account that an immobilized piece does not give check. -(chameleons cannot be immobilized) diff --git a/public/javascripts/variants/Ultima.js b/public/javascripts/variants/Ultima.js index c98822dc..57f3e25c 100644 --- a/public/javascripts/variants/Ultima.js +++ b/public/javascripts/variants/Ultima.js @@ -50,9 +50,39 @@ class UltimaRules extends ChessRules // - a "bishop" is a chameleon, capturing as its prey // - a "queen" is a withdrawer, capturing by moving away from pieces - getPotentialMovesFrom([x,y]) + // Is piece on square (x,y) immobilized? + isImmobilized([x,y]) { - // Pre-check: is thing on this square immobilized? + // Final check: is this knight immobilized? + let foundImmobilizer = false; + let neutralized = false; + outerLoop: + for (let step of steps) + { + const [i2,j2] = [i+step[0],j+step[1]]; + if (i2>=0 && i2<sizeX && j2>=0 && j2<sizeY + && this.board[i2][j2] != V.EMPTY + && this.getColor(i2,j2) == oppCol + && this.getPiece(i2,j2) == V.IMMOBILIZER) + { + foundImmobilizer = true; + // Moving is possible only if this immobilizer is neutralized + for (let step2 of steps) + { + const [i3,j3] = [i2+step2[0],j2+step2[1]]; + if (i3>=0 && i3<sizeX && j3>=0 && j3<sizeY + && this.board[i3][j3] != V.EMPTY && this.getColor(i3,j3) == color + && [V.BISHOP,V.IMMOBILIZER].includes(this.getPiece(i3,j3))) + { + neutralized = true; + break outerLoop; + } + } + } + } + if (!foundImmobilizer || neutralized) + return false; + const piece = this.getPiece(x,y); const color = this.getColor(x,y); const oppCol = this.getOppCol(color); @@ -87,6 +117,13 @@ class UltimaRules extends ChessRules } } } + } + + getPotentialMovesFrom([x,y]) + { + // Pre-check: is thing on this square immobilized? + if (this.isImmobilized([x,y])) + return []; switch (this.getPiece(x,y)) { case VariantRules.IMMOBILIZER: @@ -288,13 +325,10 @@ class UltimaRules extends ChessRules { let moves = super.getPotentialQueenMoves([x,y]) .concat(this.getKnightCaptures([x,y],"asChameleon")); + // No "king capture" because king cannot remain under check this.addPawnCaptures(moves, "asChameleon"); this.addRookCaptures(moves, "asChameleon"); this.addQueenCaptures(moves, "asChameleon"); - // Add king capture if it's within range - const oppKp = this.kingPos[this.getOppCol(this.turn)]; - if (Math.abs(x-oppKp[0]) <= 1 && Math.abs(y-oppKp[1]) <= 1) - moves.push(this.getBasicMove([x,y],oppKp)); // Post-processing: merge similar moves, concatenating vanish arrays let mergedMoves = {}; const [sizeX,sizeY] = VariantRules.size; @@ -376,77 +410,153 @@ class UltimaRules extends ChessRules V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); } - atLeastOneMove() - { - if (this.kingPos[this.turn][0] < 0) - return false; - return super.atLeastOneMove(); - } + // isAttacked() is OK because the immobilizer doesn't take - underCheck(move) + // TODO: check if any pawn can reach capturing square + !immobilized + isAttackedByPawn([x,y], colors) { - return false; //there is no check + // Square (x,y) must be surrounded by two enemy pieces, + // and one of them at least should be a pawn. + const dirs = [ [1,0],[0,1],[1,1],[-1,1] ]; + const [sizeX,sizeY] = VariantRules.size; + for (let dir of dirs) + { + const [i1,j1] = [x-dir[0],y-dir[1]]; //"before" + const [i2,j2] = [x+dir[0],y+dir[1]]; //"after" + if (i1>=0 && i1<sizeX && i2>=0 && i2<sizeX + && j1>=0 && j1<sizeY && j2>=0 && j2<sizeY + && this.board[i1][j1]!=VariantRules.EMPTY + && this.board[i2][j2]!=VariantRules.EMPTY + && colors.includes(this.getColor(i1,j1)) + && colors.includes(this.getColor(i2,j2)) + && [this.getPiece(i1,j1),this.getPiece(i2,j2)].includes(VariantRules.PAWN)) + { + return true; + } + } + return false; } - getCheckSquares(move) + // TODO: check if enemy's rook can reach capturing squares + !immobilized + isAttackedByRook([x,y], colors) { - 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; + const [sizeX,sizeY] = VariantRules.size; + // King must be on same column and a rook on same row (or reverse) + if (x == this.kingPos[colors[0]][0]) //using colors[0], only element in this case + { + // Look for enemy rook on this column + for (let i=0; i<sizeY; i++) + { + if (this.board[x][i] != VariantRules.EMPTY + && colors.includes(this.getColor(x,i)) + && this.getPiece(x,i) == VariantRules.ROOK) + { + return true; + } + } + } + else if (y == this.kingPos[colors[0]][1]) + { + // Look for enemy rook on this row + for (let i=0; i<sizeX; i++) + { + if (this.board[i][y] != VariantRules.EMPTY + && colors.includes(this.getColor(i,y)) + && this.getPiece(i,y) == VariantRules.ROOK) + { + return true; + } + } + } + return false; } - updateVariables(move) + isAttackedByKnight([x,y], colors) { - // 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) + // Square (x,y) must be on same line as a knight, + // and there must be empty square(s) behind. + const V = VariantRules; + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + const [sizeX,sizeY] = V.size; + outerLoop: + for (let step of steps) { - this.kingPos[c][0] = move.appear[0].x; - this.kingPos[c][1] = move.appear[0].y; + const [i0,j0] = [x+step[0],y+step[1]]; + if (i0>=0 && i0<sizeX && j0>=0 && j0<sizeY && this.board[i0][j0] == V.EMPTY) + { + // Try in opposite direction: + let [i,j] = [x-step[0],y-step[1]]; + while (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j] == V.EMPTY) + { + i -= step[0]; + j -= step[1]; + } + if (i>=0 && i<sizeX && j>=0 && j<sizeY && colors.includes(this.getColor(i,j)) + && this.getPiece(i,j) == V.KNIGHT) + { + if (!this.isImmobilized([i,j])) + return true; + } + } } - // Does this move takes opponent's king? - const oppCol = this.getOppCol(c); - for (let i=1; i<move.vanish.length; i++) + return false; + } + + isAttackedByBishop([x,y], colors) + { + // We cheat a little here: since this function is used exclusively for king, + // it's enough to check the immediate surrounding of the square. + const V = VariantRules; + const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + const [sizeX,sizeY] = V.size; + for (let step of adjacentSteps) { - if (move.vanish[i].p == VariantRules.KING) + const [i,j] = [x+step[0],y+step[1]]; + if (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j]!=V.EMPTY + && colors.includes(this.getColor(i,j)) && this.getPiece(i,j) == V.BISHOP) { - this.kingPos[oppCol] = [-1,-1]; - break; + return true; //bishops are never immobilized } } + return false; } - unupdateVariables(move) + isAttackedByQueen([x,y], colors) { - super.unupdateVariables(move); - const c = this.getColor(move.start.x,move.start.y); - const oppCol = this.getOppCol(c); - if (this.kingPos[oppCol][0] < 0) + // 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 V = VariantRules; + const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + const [sizeX,sizeY] = V.size; + for (let step of adjacentSteps) { - // Last move took opponent's king - for (let i=1; i<move.vanish.length; i++) + const sq2 = [x+2*step[0],y+2*step[1]]; + if (sq2[0]>=0 && sq2[0]<sizeX && sq2[1]>=0 && sq2[1]<sizeY + && this.board[sq2[0]][sq2[1]] == V.EMPTY) { - const psq = move.vanish[i]; - if (psq.p == 'k') + const sq1 = [x+step[0],y+step[1]]; + if (this.board[sq1[0]][sq1[1]] != V.EMPTY + && colors.includes(this.getColor(sq1[0],sq1[1])) + && this.getPiece(sq1[0],sq1[1]) == V.QUEEN + && !this.isImmobilized(sq1)) { - this.kingPos[oppCol] = [psq.x, psq.y]; - break; + return true; } } } + return false; } - checkGameEnd() + updateVariables(move) { - // Stalemate, or our king disappeared - return this.turn == "w" ? "0-1" : "1-0"; + // 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) + { + this.kingPos[c][0] = move.appear[0].x; + this.kingPos[c][1] = move.appear[0].y; + } } static get VALUES() { //TODO: totally experimental! @@ -463,10 +573,6 @@ 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) }; diff --git a/views/rules/Ultima.pug b/views/rules/Ultima.pug index df8571f4..423ba5d6 100644 --- a/views/rules/Ultima.pug +++ b/views/rules/Ultima.pug @@ -1,5 +1,5 @@ p.boxed - | Pieces look the same but behave very differently. + | Most pieces look the same but behave very differently. | They generally move like an orthodox queen, | but capturing rules are complex. @@ -11,21 +11,20 @@ ul li Non-capturing moves: often like queen. li Special moves: none. li Captures: very special. - li End of game: capture the king. + li End of game: standard; see below. h3 Non-capturing moves // TODO: short paragraph, only the king moves like an orthodox king +// Consider these rules modifications: http://www.inference.org.uk/mackay/ultima/ultima.html h3 Capturing moves // TODO... -// TODO: dire comment suicider une pièce immobilisée (self-capture) h3 End of the game // TODO: show the situation from Wikipedia page -// tell that it's much simpler to go until king capture and not very different in this case h3 Credits