| 1 | import ChessRules from "/base_rules.js"; |
| 2 | import Move from "/utils/Move.js"; |
| 3 | import PiPo from "/utils/PiPo.js"; |
| 4 | |
| 5 | export default class AbstractSpecialCaptureRules extends ChessRules { |
| 6 | |
| 7 | // Wouldn't make sense: |
| 8 | get hasEnpassant() { |
| 9 | return false; |
| 10 | } |
| 11 | |
| 12 | pieces() { |
| 13 | return Object.assign({}, |
| 14 | super.pieces(), |
| 15 | { |
| 16 | '+': {"class": "push-action"}, |
| 17 | '-': {"class": "pull-action"} |
| 18 | } |
| 19 | ); |
| 20 | } |
| 21 | |
| 22 | // Modify capturing moves among listed pincer moves |
| 23 | addPincerCaptures(moves, byChameleon) { |
| 24 | const steps = this.pieces()['p'].moves[0].steps; |
| 25 | const color = this.turn; |
| 26 | const oppCol = C.GetOppTurn(color); |
| 27 | moves.forEach(m => { |
| 28 | if (byChameleon && m.start.x != m.end.x && m.start.y != m.end.y) |
| 29 | // Chameleon not moving as pawn |
| 30 | return; |
| 31 | // Try capturing in every direction |
| 32 | for (let step of steps) { |
| 33 | const sq2 = [m.end.x + 2 * step[0], this.getY(m.end.y + 2 * step[1])]; |
| 34 | if ( |
| 35 | this.onBoard(sq2[0], sq2[1]) && |
| 36 | this.board[sq2[0]][sq2[1]] != "" && |
| 37 | this.getColor(sq2[0], sq2[1]) == color |
| 38 | ) { |
| 39 | // Potential capture |
| 40 | const sq1 = [m.end.x + step[0], this.getY(m.end.y + step[1])]; |
| 41 | if ( |
| 42 | this.board[sq1[0]][sq1[1]] != "" && |
| 43 | this.getColor(sq1[0], sq1[1]) == oppCol |
| 44 | ) { |
| 45 | const piece1 = this.getPiece(sq1[0], sq1[1]); |
| 46 | if (!byChameleon || piece1 == 'p') { |
| 47 | m.vanish.push( |
| 48 | new PiPo({ |
| 49 | x: sq1[0], |
| 50 | y: sq1[1], |
| 51 | c: oppCol, |
| 52 | p: piece1 |
| 53 | }) |
| 54 | ); |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | } |
| 59 | }); |
| 60 | } |
| 61 | |
| 62 | addCoordinatorCaptures(moves, byChameleon) { |
| 63 | const color = this.turn; |
| 64 | const oppCol = V.GetOppTurn(color); |
| 65 | const kp = this.searchKingPos(color)[0]; |
| 66 | moves.forEach(m => { |
| 67 | // Check piece-king rectangle (if any) corners for enemy pieces |
| 68 | if (m.end.x == kp[0] || m.end.y == kp[1]) |
| 69 | return; //"flat rectangle" |
| 70 | const corner1 = [m.end.x, kp[1]]; |
| 71 | const corner2 = [kp[0], m.end.y]; |
| 72 | for (let [i, j] of [corner1, corner2]) { |
| 73 | if (this.board[i][j] != "" && this.getColor(i, j) == oppCol) { |
| 74 | const piece = this.getPiece(i, j); |
| 75 | if (!byChameleon || piece == 'r') { |
| 76 | m.vanish.push( |
| 77 | new PiPo({ |
| 78 | x: i, |
| 79 | y: j, |
| 80 | p: piece, |
| 81 | c: oppCol |
| 82 | }) |
| 83 | ); |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | }); |
| 88 | } |
| 89 | |
| 90 | getLeaperCaptures([x, y], byChameleon, onlyOne) { |
| 91 | // Look in every direction for captures |
| 92 | const steps = this.pieces()['r'].moves[0].steps; |
| 93 | const color = this.turn; |
| 94 | const oppCol = C.GetOppTurn(color); |
| 95 | let moves = []; |
| 96 | outerLoop: for (let step of steps) { |
| 97 | let [i, j] = [x + step[0], this.getY(y + step[1])]; |
| 98 | while (this.onBoard(i, j) && this.board[i][j] == "") |
| 99 | [i, j] = [i + step[0], this.getY(j + step[1])]; |
| 100 | if ( |
| 101 | !this.onBoard(i, j) || |
| 102 | this.getColor(i, j) == color || |
| 103 | (byChameleon && this.getPiece(i, j) != 'n') |
| 104 | ) { |
| 105 | continue; //nothing to eat |
| 106 | } |
| 107 | let vanished = []; |
| 108 | while (true) { |
| 109 | // Found something (more) to eat: |
| 110 | vanished.push( |
| 111 | new PiPo({x: i, y: j, c: oppCol, p: this.getPiece(i, j)})); |
| 112 | [i, j] = [i + step[0], this.getY(j + step[1])]; |
| 113 | while (this.onBoard(i, j) && this.board[i][j] == "") { |
| 114 | let mv = this.getBasicMove([x, y], [i, j]); |
| 115 | Array.prorotype.push.apply(mv.vanish, vanished); |
| 116 | moves.push(mv); |
| 117 | [i, j] = [i + step[0], this.getY(j + step[1])]; |
| 118 | } |
| 119 | if ( |
| 120 | onlyOne || |
| 121 | !this.onBoard(i, j) || |
| 122 | this.getColor(i, j) == color || |
| 123 | (byChameleon && this.getPiece(i, j) != 'n') |
| 124 | ) { |
| 125 | continue outerLoop; |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | return moves; |
| 130 | } |
| 131 | |
| 132 | // Chameleon |
| 133 | getChameleonCaptures(moves, pushPullType, onlyOneJump) { |
| 134 | const [x, y] = [moves[0].start.x, moves[0].start.y]; |
| 135 | moves = moves.concat( |
| 136 | this.getKnightCaptures([x, y], "asChameleon", onlyOneJump)); |
| 137 | // No "king capture" because king cannot remain under check |
| 138 | this.addPincerCaptures(moves, "asChameleon"); |
| 139 | this.addCoordinatorCaptures(moves, "asChameleon"); |
| 140 | this.addPushmePullyouCaptures(moves, "asChameleon", pushPullType); |
| 141 | // Post-processing: merge similar moves, concatenating vanish arrays |
| 142 | let mergedMoves = {}; |
| 143 | moves.forEach(m => { |
| 144 | const key = m.end.x + this.size.x * m.end.y; |
| 145 | if (!mergedMoves[key]) |
| 146 | mergedMoves[key] = m; |
| 147 | else { |
| 148 | for (let i = 1; i < m.vanish.length; i++) |
| 149 | mergedMoves[key].vanish.push(m.vanish[i]); |
| 150 | } |
| 151 | }); |
| 152 | return Object.values(mergedMoves); |
| 153 | } |
| 154 | |
| 155 | // type: nothing (freely, capture all), or pull or push, or "exclusive" |
| 156 | addPushmePullyouCaptures(moves, byChameleon, type) { |
| 157 | if (moves.length == 0) |
| 158 | return; |
| 159 | const [sx, sy] = [moves[0].start.x, moves[0].start.y]; |
| 160 | const adjacentSteps = this.pieces()['r'].moves[0].steps; |
| 161 | let capturingPullDir = {}; |
| 162 | const color = this.turn; |
| 163 | const oppCol = C.GetOppTurn(color); |
| 164 | if (type != "push") { |
| 165 | adjacentSteps.forEach(step => { |
| 166 | const [bi, bj] = [sx - step[0], this.getY(sy - step[1])]; |
| 167 | if ( |
| 168 | this.onBoard(bi, bj) && |
| 169 | this.board[bi][bj] != "" && |
| 170 | this.getColor(bi, bj) == oppCol && |
| 171 | (!byChameleon || this.getPiece(bi, bj) == 'q') |
| 172 | ) { |
| 173 | capturingPullDir[step[0] + "." + step[1]] = true; |
| 174 | } |
| 175 | }); |
| 176 | } |
| 177 | moves.forEach(m => { |
| 178 | const [ex, ey] = [m.end.x, m.end.y]; |
| 179 | const step = [ |
| 180 | ex != x ? (ex - x) / Math.abs(ex - x) : 0, |
| 181 | ey != y ? (ey - y) / Math.abs(ey - y) : 0 |
| 182 | ]; |
| 183 | let vanishPull, vanishPush; |
| 184 | if (type != "pull") { |
| 185 | const [fi, fj] = [ex + step[0], this.getY(ey + step[1])]; |
| 186 | if ( |
| 187 | this.onBoard(fi, fj) && |
| 188 | this.board[fi][fj] != "" && |
| 189 | this.getColor(bi, bj) == oppCol && |
| 190 | (!byChameleon || this.getPiece(fi, fj) == 'q') |
| 191 | ) { |
| 192 | vanishPush = |
| 193 | new PiPo({x: fi, y: fj, p: this.getPiece(fi, fj), c: oppCol}); |
| 194 | } |
| 195 | } |
| 196 | if (capturingPullDir[step[0] + "." + step[1]]) { |
| 197 | const [bi, bj] = [x - step[0], this.getY(y - step[1])]; |
| 198 | vanishPull = |
| 199 | new PiPo({x: bi, y: bj, p: this.getPiece(bi, bj), c: oppCol}); |
| 200 | } |
| 201 | if (vanishPull && vanishPush && type == "exclusive") { |
| 202 | // Create a new move for push action (cannot play both) |
| 203 | let newMove = JSON.parse(JSON.stringify(m)); |
| 204 | newMove.vanish.push(vanishPush); |
| 205 | newMove.choice = '+'; |
| 206 | moves.push(newMove); |
| 207 | m.vanish.push(vanishPull); |
| 208 | m.choice = '-'; |
| 209 | } |
| 210 | else { |
| 211 | if (vanishPull) |
| 212 | m.vanish.push(vanishPull); |
| 213 | if (vanishPush) |
| 214 | m.vanish.push(vanishPush); |
| 215 | } |
| 216 | }); |
| 217 | } |
| 218 | |
| 219 | underAttack([x, y], oppCol) { |
| 220 | // Generate all potential opponent moves, check if king captured. |
| 221 | // TODO: do it more efficiently. |
| 222 | const color = this.getColor(x, y); |
| 223 | for (let i = 0; i < this.size.x; i++) { |
| 224 | for (let j = 0; j < this.size.y; j++) { |
| 225 | if ( |
| 226 | this.board[i][j] != "" && this.getColor(i, j) == oppCol && |
| 227 | this.getPotentialMovesFrom([i, j]).some(m => { |
| 228 | return ( |
| 229 | m.vanish.length >= 2 && |
| 230 | [1, m.vanish.length - 1].some(k => m.vanish[k].p == 'k') |
| 231 | ); |
| 232 | }) |
| 233 | ) { |
| 234 | return true; |
| 235 | } |
| 236 | } |
| 237 | } |
| 238 | return false; |
| 239 | } |
| 240 | |
| 241 | }; |