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