Various bug fixes
[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 = moves[0].vanish[0].c;
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 = moves[0].vanish[0].c;
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.getColor(x, y);
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.prototype.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.getLeaperCaptures([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 const [sx, sy] = [moves[0].start.x, moves[0].start.y];
158 const adjacentSteps = this.pieces()['r'].moves[0].steps;
159 let capturingPullDir = {};
160 const color = moves[0].vanish[0].c;
161 const oppCol = C.GetOppTurn(color);
162 if (type != "push") {
163 adjacentSteps.forEach(step => {
164 const [bi, bj] = [sx - step[0], this.getY(sy - step[1])];
165 if (
166 this.onBoard(bi, bj) &&
167 this.board[bi][bj] != "" &&
168 this.getColor(bi, bj) == oppCol &&
169 (!byChameleon || this.getPiece(bi, bj) == 'q')
170 ) {
171 capturingPullDir[step[0] + "." + step[1]] = true;
172 }
173 });
174 }
175 moves.forEach(m => {
176 const [ex, ey] = [m.end.x, m.end.y];
177 const step = [
178 ex != sx ? (ex - sx) / Math.abs(ex - sx) : 0,
179 ey != sy ? (ey - sy) / Math.abs(ey - sy) : 0
180 ];
181 let vanishPull, vanishPush;
182 if (type != "pull") {
183 const [fi, fj] = [ex + step[0], this.getY(ey + step[1])];
184 if (
185 this.onBoard(fi, fj) &&
186 this.board[fi][fj] != "" &&
187 this.getColor(bi, bj) == oppCol &&
188 (!byChameleon || this.getPiece(fi, fj) == 'q')
189 ) {
190 vanishPush =
191 new PiPo({x: fi, y: fj, p: this.getPiece(fi, fj), c: oppCol});
192 }
193 }
194 if (capturingPullDir[step[0] + "." + step[1]]) {
195 const [bi, bj] = [sx - step[0], this.getY(sy - step[1])];
196 vanishPull =
197 new PiPo({x: bi, y: bj, p: this.getPiece(bi, bj), c: oppCol});
198 }
199 if (vanishPull && vanishPush && type == "exclusive") {
200 // Create a new move for push action (cannot play both)
201 let newMove = JSON.parse(JSON.stringify(m));
202 newMove.vanish.push(vanishPush);
203 newMove.choice = '+';
204 moves.push(newMove);
205 m.vanish.push(vanishPull);
206 m.choice = '-';
207 }
208 else {
209 if (vanishPull)
210 m.vanish.push(vanishPull);
211 if (vanishPush)
212 m.vanish.push(vanishPush);
213 }
214 });
215 }
216
217 underAttack([x, y], oppCols) {
218 // Generate all potential opponent moves, check if king captured.
219 // TODO: do it more efficiently.
220 const color = this.getColor(x, y);
221 for (let i = 0; i < this.size.x; i++) {
222 for (let j = 0; j < this.size.y; j++) {
223 if (
224 this.board[i][j] != "" && oppCols.includes(this.getColor(i, j)) &&
225 this.getPotentialMovesFrom([i, j]).some(m => {
226 return (
227 m.vanish.length >= 2 &&
228 [1, m.vanish.length - 1].some(k => m.vanish[k].p == 'k')
229 );
230 })
231 ) {
232 return true;
233 }
234 }
235 }
236 return false;
237 }
238
239 };