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