Several small improvements + integrate options + first working draft of Cwda
[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(options) {
15 return ChessRules.GenRandInitFen(options).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], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1);
73 break;
74 }
75 for (let om of oppMoves) {
76 V.PlayOnBoard(this.board, om);
77 Object.values(attacked).forEach(sq => {
78 const origSq = [sq[0], sq[1]];
79 if (om.start.x == sq[0] && om.start.y == sq[1])
80 // Piece moved:
81 sq = [om.appear[0].x, om.appear[0].y];
82 if (!this.isAttacked(sq, color))
83 delete attacked[origSq[0]+"_"+origSq[1]];
84 });
85 V.UndoOnBoard(this.board, om);
86 if (Object.keys(attacked).length == 0)
87 // No need to explore more moves
88 break outerLoop;
89 }
90 }
91 }
92 }
93
94 // 3) Add mate-captures and remove pieces from board:
95 Object.keys(attacked).forEach(k => {
96 allAttacks.push({
97 sq: attacked[k],
98 p: this.getPiece(attacked[k][0], attacked[k][1])
99 });
100 this.board[attacked[k][0]][attacked[k][1]] = V.EMPTY;
101 });
102 }
103
104 // Put removed pieces back on board:
105 allAttacks.forEach(v => {
106 this.board[v.sq[0]][v.sq[1]] = oppCol + v.p;
107 });
108 this.undo(m);
109 allAttacks.forEach(v => {
110 m.vanish.push(
111 new PiPo({
112 x: v.sq[0],
113 y: v.sq[1],
114 c: oppCol,
115 p: v.p
116 })
117 );
118 });
119 });
120
121 return moves;
122 }
123
124 // No "under check" conditions in castling
125 getCastleMoves(sq) {
126 return super.getCastleMoves(sq, null, "castleInCheck");
127 }
128
129 // TODO: allow pieces to "commit suicide"? (Currently yes except king)
130 filterValid(moves) {
131 // Remove moves which let the king mate-captured:
132 if (moves.length == 0) return [];
133 const color = this.turn;
134 const oppCol = V.GetOppCol(color);
135 return moves.filter(m => {
136 let res = true;
137 this.play(m);
138 if (this.underCheck(color)) {
139 res = false;
140 const attacked = this.kingPos[color];
141 // Try to find a move to escape check
142 // TODO: very inefficient method.
143 outerLoop: for (let i=0; i<V.size.x; i++) {
144 for (let j=0; j<V.size.y; j++) {
145 if (this.getColor(i,j) == color) {
146 let emoves = [];
147 // Artficial turn change to "play twice":
148 this.turn = color;
149 switch (this.getPiece(i, j)) {
150 case V.PAWN:
151 emoves = this.getPotentialPawnMoves([i, j]);
152 break;
153 case V.ROOK:
154 emoves = this.getPotentialRookMoves([i, j]);
155 break;
156 case V.KNIGHT:
157 emoves = this.getPotentialKnightMoves([i, j]);
158 break;
159 case V.BISHOP:
160 emoves = this.getPotentialBishopMoves([i, j]);
161 break;
162 case V.QUEEN:
163 emoves = this.getPotentialQueenMoves([i, j]);
164 break;
165 case V.KING:
166 emoves = this.getPotentialKingMoves([i, j]);
167 break;
168 }
169 this.turn = oppCol;
170 for (let em of emoves) {
171 V.PlayOnBoard(this.board, em);
172 let sq = attacked;
173 if (em.start.x == attacked[0] && em.start.y == attacked[1])
174 // King moved:
175 sq = [em.appear[0].x, em.appear[0].y];
176 if (!this.isAttacked(sq, oppCol))
177 res = true;
178 V.UndoOnBoard(this.board, em);
179 if (res)
180 // No need to explore more moves
181 break outerLoop;
182 }
183 }
184 }
185 }
186 }
187 this.undo(m);
188 return res;
189 });
190 }
191
192 postPlay(move) {
193 super.postPlay(move);
194 if (move.vanish.length >= 2 && move.appear.length == 1) {
195 for (let i = 1; i<move.vanish.length; i++) {
196 const v = move.vanish[i];
197 // Did opponent king disappeared?
198 if (v.p == V.KING)
199 this.kingPos[this.turn] = [-1, -1];
200 // Or maybe a rook?
201 else if (v.p == V.ROOK) {
202 if (v.y < this.kingPos[v.c][1])
203 this.castleFlags[v.c][0] = 8;
204 else
205 // v.y > this.kingPos[v.c][1]
206 this.castleFlags[v.c][1] = 8;
207 }
208 }
209 }
210 }
211
212 preUndo(move) {
213 super.preUndo(move);
214 const oppCol = this.turn;
215 if (move.vanish.length >= 2 && move.appear.length == 1) {
216 // Did opponent king disappeared?
217 const psq = move.vanish.find(v => v.p == V.KING && v.c == oppCol)
218 if (psq)
219 this.kingPos[psq.c] = [psq.x, psq.y];
220 }
221 }
222
223 getCurrentScore() {
224 const color = this.turn;
225 const kp = this.kingPos[color];
226 if (kp[0] < 0)
227 // King disappeared
228 return color == "w" ? "0-1" : "1-0";
229 if (this.atLeastOneMove()) return "*";
230 // Kings still there, no moves:
231 return "1/2";
232 }
233
234 static get SEARCH_DEPTH() {
235 return 1;
236 }
237
238 getNotation(move) {
239 let notation = super.getNotation(move);
240 // Add a capture mark (not describing what is captured...):
241 if (move.vanish.length > 1 && move.appear.length == 1) {
242 if (!!(notation.match(/^[a-h]x/)))
243 // Pawn capture: remove initial "b" in bxc4 for example
244 notation = notation.substr(1);
245 notation = notation.replace("x","") + "X";
246 }
247 return notation;
248 }
249
250 };