From: Benjamin Auder <benjamin.auder@somewhere> Date: Wed, 12 Dec 2018 15:55:59 +0000 (+0100) Subject: Implementation of pieces movements + captures in Ultima X-Git-Url: https://git.auder.net/doc/html/css/scripts/pieces/img/%7B%7B%20pkg.url%20%7D%7D?a=commitdiff_plain;h=a3c86ec9b60326a8ae3d8f237493fb09627aed95;p=vchess.git Implementation of pieces movements + captures in Ultima --- diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 8de0176c..5020094c 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -729,6 +729,10 @@ class ChessRules play(move, ingame) { + // DEBUG: +// if (!this.states) this.states = []; +// if (!ingame) this.states.push(JSON.stringify(this.board)); + if (!!ingame) move.notation = [this.getNotation(move), this.getLongNotation(move)]; @@ -746,6 +750,11 @@ class ChessRules this.moves.pop(); this.unupdateVariables(move); this.parseFlags(JSON.parse(move.flags)); + + // DEBUG: +// if (JSON.stringify(this.board) != this.states[this.states.length-1]) +// debugger; +// this.states.pop(); } ////////////// diff --git a/public/javascripts/variants/Ultima.js b/public/javascripts/variants/Ultima.js index 18afe402..d6676436 100644 --- a/public/javascripts/variants/Ultima.js +++ b/public/javascripts/variants/Ultima.js @@ -72,7 +72,7 @@ class UltimaRules extends ChessRules { return [ new Move({ appear: [], - vanish: [{x:x,y:y,p:piece,c:color}], + vanish: [new PiPo({x:x,y:y,p:piece,c:color})], start: {x:x,y:y}, end: {x:i,y:j} }) ]; @@ -118,23 +118,58 @@ 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]<sizeX && sq2[1]>=0 && sq2[1]<sizeY + && this.board[sq2[0]][sq2[1]] != VariantRules.EMPTY + && this.getColor(sq2[0],sq2[1]) == color) + { + // Potential capture + const sq1 = [m.end.x+step[0],m.end.y+step[1]]; + if (this.board[sq1[0]][sq1[1]] != VariantRules.EMPTY + && this.getColor(sq1[0],sq1[1]) == oppCol) + { + const piece1 = this.getPiece(sq1[0],sq1[1]); + if (!byChameleon || piece1 == VariantRules.PAWN) + { + m.vanish.push(new PiPo({ + x:sq1[0], + y:sq1[1], + c:oppCol, + p:piece1 + })); + } + } + } + } + }); + } + // "Pincher" getPotentialPawnMoves([x,y]) { let moves = super.getPotentialRookMoves([x,y]); - // Add captures - moves.forEach(m => { - if (m - }); + this.addPawnCaptures(moves); + return moves; } - // Coordinator - getPotentialRookMoves(sq) + addRookCaptures(moves, byChameleon) { - const color = this.getColor(sq); + const color = this.turn; const oppCol = this.getOppCol(color); const kp = this.kingPos[color]; - let moves = super.getPotentialQueenMoves(sq); moves.forEach(m => { // Check piece-king rectangle (if any) corners for enemy pieces if (m.end.x == kp[0] || m.end.y == kp[1]) @@ -145,27 +180,42 @@ class UltimaRules extends ChessRules { if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) == oppCol) { - m.vanish.push( new PiPo({ - x:i, - y:j, - p:this.getPiece(i,j), - c: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 + }) ); + } } } }); + } + + // Coordinator + getPotentialRookMoves(sq) + { + let moves = super.getPotentialQueenMoves(sq); + this.addRookCaptures(moves); return moves; } // Long-leaper - getPotentialKnightMoves([x,y]) + getKnightCaptures(startSquare, byChameleon) { - let moves = 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.getColor(x,y); + 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]]; @@ -174,44 +224,115 @@ class UltimaRules extends ChessRules i += step[0]; j += step[1]; } - if (i<0 && i>=sizeX || j<0 || j>=sizeY || this.getColor(i,j)==color) + if (i<0 || i>=sizeX || j<0 || j>=sizeY || this.getColor(i,j)==color + || (!!byChameleon && this.getPiece(i,j)!=V.KNIGHT)) + { continue; - // Found an enemy piece: potential capture (if empty space behind) - // So, while we find enemy pieces + space in this direction, add captures! - i += step[0]; - j += step[1]; - while ( ) //TODO: finish........ + } + // 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]<sizeX && cur[1]>=0 && cur[1]<sizeY) + { + if (this.board[last[0]][last[1]] != V.EMPTY) + { + const oppPiece = this.getPiece(last[0],last[1]); + if (!!byChameleon && oppPiece != V.KNIGHT) + continue outerLoop; + // Something to eat: + vanished.push( new PiPo({x:last[0],y:last[1],c:oppCol,p:oppPiece}) ); + } + if (this.board[cur[0]][cur[1]] != V.EMPTY) + { + if (this.getColor(cur[0],cur[1]) == color + || this.board[last[0]][last[1]] != V.EMPTY) //TODO: redundant test + { + continue outerLoop; + } + } + else + { + moves.push(new Move({ + appear: [ new PiPo({x:cur[0],y:cur[1],c:color,p:piece}) ], + vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required? + start: {x:x,y:y}, + end: {x:cur[0],y:cur[1]} + })); + } + last = [last[0]+step[0],last[1]+step[1]]; + cur = [cur[0]+step[0],cur[1]+step[1]]; + } } return moves; } + // Long-leaper + getPotentialKnightMoves(sq) + { + return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq)); + } + getPotentialBishopMoves(sq) { - return super.getPotentialQueenMoves(sq); - // TODO: add captures of coordinators,pinchers,withdrawers... by re-using code + let moves = super.getPotentialQueenMoves(sq) + .concat(this.getKnightCaptures(sq,"asChameleon")); + // NOTE: no "addKingCaptures" because the king isn't captured + this.addPawnCaptures(moves, "asChameleon"); + this.addRookCaptures(moves, "asChameleon"); + this.addQueenCaptures(moves, "asChameleon"); + // Post-processing: merge similar moves, concatenating vanish arrays + let mergedMoves = {}; + const [sizeX,sizeY] = VariantRules.size; + moves.forEach(m => { + const key = m.end.x + sizeX * m.end.y; + if (!mergedMoves[key]) + mergedMoves[key] = m; + else + { + for (let i=1; i<m.vanish.length; i++) + mergedMoves[key].vanish.push(m.vanish[i]); + } + }); + // Finally return an array + moves = []; + Object.keys(mergedMoves).forEach(k => { moves.push(mergedMoves[k]); }); + return moves; } - getPotentialQueenMoves([x,y]) + // Withdrawer + addQueenCaptures(moves, byChameleon) { - let moves = super.getPotentialQueenMoves(sq); + if (moves.length == 0) + return; + const [x,y] = [moves[0].start.x,moves[0].start.y]; const V = VariantRules; const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); let capturingDirections = []; - const color = this.getColor(x,y); + 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 (this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol) + if (i>=0 && i<sizeX && j>=0 && j<sizeY + && this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol + && (!byChameleon || this.getPiece(i,j) == V.QUEEN)) + { capturingDirections.push(step); + } }); moves.forEach(m => { 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() function does not work on complex array elements + // NOTE: includes() and even _.isEqual() functions fail... // TODO: this test should be done only once per direction - if (capturingDirection.some(dir => _.isEqual(dir, step))) + 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({ @@ -224,8 +345,16 @@ class UltimaRules extends ChessRules }); } + getPotentialQueenMoves(sq) + { + let moves = super.getPotentialQueenMoves(sq); + this.addQueenCaptures(moves); + return moves; + } + getPotentialImmobilizerMoves(sq) { + // Immobilizer doesn't capture return super.getPotentialQueenMoves(sq); } @@ -248,12 +377,14 @@ class UltimaRules extends ChessRules isAttackedByRook(sq, colors) { // Enemy king must be on same file and a rook on same row (or reverse) + return false; } isAttackedByKnight(sq, colors) { // Square (x,y) must be on same line as a knight, // and there must be empty square(s) behind. + return false; } isAttackedByBishop(sq, colors) @@ -261,12 +392,14 @@ class UltimaRules extends ChessRules // 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; } isAttackedByQueen(sq, colors) { // Square (x,y) must be adjacent to a queen, and the queen must have // some free space in the opposite direction from (x,y) + return false; } updateVariables(move) @@ -281,6 +414,12 @@ class UltimaRules extends ChessRules } } + checkGameEnd() + { + // No valid move: game is lost (stalemate is a win) + return this.turn == "w" ? "0-1" : "1-0"; + } + static get VALUES() { //TODO: totally experimental! return { 'p': 1, @@ -352,4 +491,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); + } }