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