Add temporary patch for preset challenges
[vchess.git] / client / src / variants / Allmate.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2
3 export class AllmateRules extends ChessRules {
4
5 static get HasEnpassant() {
6 return false;
7 }
8
9 getCheckSquares() {
10 // No notion of check
11 return [];
12 }
13
14 static GenRandInitFen(randomness) {
15 return ChessRules.GenRandInitFen(randomness).slice(0, -2);
16 }
17
18 getPotentialMovesFrom([x, y]) {
19 let moves = super.getPotentialMovesFrom([x, y]);
20 // Remove standard captures (without removing castling):
21 moves = moves.filter(m => {
22 return m.vanish.length == 1 || m.appear.length == 2;
23 });
24
25 // Augment moves with "mate-captures":
26 // TODO: this is coded in a highly inefficient way...
27 const color = this.turn;
28 const oppCol = V.GetOppCol(this.turn);
29 moves.forEach(m => {
30 this.play(m);
31 let allAttacks = [];
32
33 // While something has been captured:
34 // remove it, and keep looking for captures
35 outerLoop: while (true) {
36
37 // 1) What is attacked?
38 let attacked = {};
39 for (let i=0; i<V.size.x; i++) {
40 for (let j=0; j<V.size.y; j++) {
41 if (this.getColor(i,j) == oppCol && this.isAttacked([i,j], color))
42 attacked[i+"_"+j] = [i,j];
43 }
44 }
45 if (Object.keys(attacked).length == 0) break outerLoop;
46
47 // 2) Among attacked pieces, which cannot escape capture?
48 // Avoid "oppMoves = this.getAllValidMoves();" => infinite recursion
49 for (let i=0; i<V.size.x; i++) {
50 for (let j=0; j<V.size.y; j++) {
51 if (this.getColor(i,j) == oppCol) {
52 let oppMoves = [];
53 switch (this.getPiece(i, j)) {
54 case V.PAWN:
55 oppMoves = this.getPotentialPawnMoves([i, j]);
56 break;
57 case V.ROOK:
58 oppMoves = this.getPotentialRookMoves([i, j]);
59 break;
60 case V.KNIGHT:
61 oppMoves = this.getPotentialKnightMoves([i, j]);
62 break;
63 case V.BISHOP:
64 oppMoves = this.getPotentialBishopMoves([i, j]);
65 break;
66 case V.QUEEN:
67 oppMoves = this.getPotentialQueenMoves([i, j]);
68 break;
69 case V.KING:
70 // Do not allow castling to escape from check
71 oppMoves = super.getSlideNJumpMoves(
72 [i, j],
73 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
74 "oneStep"
75 );
76 break;
77 }
78 for (let om of oppMoves) {
79 V.PlayOnBoard(this.board, om);
80 Object.values(attacked).forEach(sq => {
81 const origSq = [sq[0], sq[1]];
82 if (om.start.x == sq[0] && om.start.y == sq[1])
83 // Piece moved:
84 sq = [om.appear[0].x, om.appear[0].y];
85 if (!this.isAttacked(sq, color))
86 delete attacked[origSq[0]+"_"+origSq[1]];
87 });
88 V.UndoOnBoard(this.board, om);
89 if (Object.keys(attacked).length == 0)
90 // No need to explore more moves
91 break outerLoop;
92 }
93 }
94 }
95 }
96
97 // 3) Add mate-captures and remove pieces from board:
98 Object.keys(attacked).forEach(k => {
99 allAttacks.push({
100 sq: attacked[k],
101 p: this.getPiece(attacked[k][0], attacked[k][1])
102 });
103 this.board[attacked[k][0]][attacked[k][1]] = V.EMPTY;
104 });
105 }
106
107 // Put removed pieces back on board:
108 allAttacks.forEach(v => {
109 this.board[v.sq[0]][v.sq[1]] = oppCol + v.p;
110 });
111 this.undo(m);
112 allAttacks.forEach(v => {
113 m.vanish.push(
114 new PiPo({
115 x: v.sq[0],
116 y: v.sq[1],
117 c: oppCol,
118 p: v.p
119 })
120 );
121 });
122 });
123
124 return moves;
125 }
126
127 // No "under check" conditions in castling
128 getCastleMoves(sq) {
129 return super.getCastleMoves(sq, null, "castleInCheck");
130 }
131
132 // TODO: allow pieces to "commit suicide"? (Currently yes except king)
133 filterValid(moves) {
134 // Remove moves which let the king mate-captured:
135 if (moves.length == 0) return [];
136 const color = this.turn;
137 const oppCol = V.GetOppCol(color);
138 return moves.filter(m => {
139 let res = true;
140 this.play(m);
141 if (this.underCheck(color)) {
142 res = false;
143 const attacked = this.kingPos[color];
144 // Try to find a move to escape check
145 // TODO: very inefficient method.
146 outerLoop: for (let i=0; i<V.size.x; i++) {
147 for (let j=0; j<V.size.y; j++) {
148 if (this.getColor(i,j) == color) {
149 let emoves = [];
150 // Artficial turn change to "play twice":
151 this.turn = color;
152 switch (this.getPiece(i, j)) {
153 case V.PAWN:
154 emoves = this.getPotentialPawnMoves([i, j]);
155 break;
156 case V.ROOK:
157 emoves = this.getPotentialRookMoves([i, j]);
158 break;
159 case V.KNIGHT:
160 emoves = this.getPotentialKnightMoves([i, j]);
161 break;
162 case V.BISHOP:
163 emoves = this.getPotentialBishopMoves([i, j]);
164 break;
165 case V.QUEEN:
166 emoves = this.getPotentialQueenMoves([i, j]);
167 break;
168 case V.KING:
169 emoves = this.getPotentialKingMoves([i, j]);
170 break;
171 }
172 this.turn = oppCol;
173 for (let em of emoves) {
174 V.PlayOnBoard(this.board, em);
175 let sq = attacked;
176 if (em.start.x == attacked[0] && em.start.y == attacked[1])
177 // King moved:
178 sq = [em.appear[0].x, em.appear[0].y];
179 if (!this.isAttacked(sq, oppCol))
180 res = true;
181 V.UndoOnBoard(this.board, em);
182 if (res)
183 // No need to explore more moves
184 break outerLoop;
185 }
186 }
187 }
188 }
189 }
190 this.undo(m);
191 return res;
192 });
193 }
194
195 postPlay(move) {
196 super.postPlay(move);
197 if (move.vanish.length >= 2 && move.appear.length == 1) {
198 for (let i = 1; i<move.vanish.length; i++) {
199 const v = move.vanish[i];
200 // Did opponent king disappeared?
201 if (v.p == V.KING)
202 this.kingPos[this.turn] = [-1, -1];
203 // Or maybe a rook?
204 else if (v.p == V.ROOK) {
205 if (v.y < this.kingPos[v.c][1])
206 this.castleFlags[v.c][0] = 8;
207 else
208 // v.y > this.kingPos[v.c][1]
209 this.castleFlags[v.c][1] = 8;
210 }
211 }
212 }
213 }
214
215 preUndo(move) {
216 super.preUndo(move);
217 const oppCol = this.turn;
218 if (move.vanish.length >= 2 && move.appear.length == 1) {
219 // Did opponent king disappeared?
220 const psq = move.vanish.find(v => v.p == V.KING && v.c == oppCol)
221 if (psq)
222 this.kingPos[psq.c] = [psq.x, psq.y];
223 }
224 }
225
226 getCurrentScore() {
227 const color = this.turn;
228 const kp = this.kingPos[color];
229 if (kp[0] < 0)
230 // King disappeared
231 return color == "w" ? "0-1" : "1-0";
232 if (this.atLeastOneMove()) return "*";
233 // Kings still there, no moves:
234 return "1/2";
235 }
236
237 static get SEARCH_DEPTH() {
238 return 1;
239 }
240
241 getNotation(move) {
242 let notation = super.getNotation(move);
243 // Add a capture mark (not describing what is captured...):
244 if (move.vanish.length > 1 && move.appear.length == 1) {
245 if (!!(notation.match(/^[a-h]x/)))
246 // Pawn capture: remove initial "b" in bxc4 for example
247 notation = notation.substr(1);
248 notation = notation.replace("x","") + "X";
249 }
250 return notation;
251 }
252
253 };