Checkered: fixing attempt (untested) + refactor/generalize oppCol oppTurn logic
[xogo.git] / variants / _SpecialCaptures / class.js
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.GetOppCol(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.GetOppCol(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.GetOppCol(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.GetOppCol(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 };