Fix Eightpieces
[vchess.git] / client / src / variants / Schess.js
1 import { ChessRules, PiPo } from "@/base_rules";
2
3 export class SchessRules extends ChessRules {
4
5 static get PawnSpecs() {
6 return Object.assign(
7 {},
8 ChessRules.PawnSpecs,
9 {
10 promotions:
11 ChessRules.PawnSpecs.promotions.concat([V.HAWK, V.ELEPHANT])
12 }
13 );
14 }
15
16 static get HAWK() {
17 return 'h';
18 }
19
20 static get ELEPHANT() {
21 return 'e';
22 }
23
24 static get NOTHING() {
25 return 'o';
26 }
27
28 static get PIECES() {
29 return ChessRules.PIECES.concat([V.HAWK, V.ELEPHANT]);
30 }
31
32 getPpath(b) {
33 if ([V.HAWK, V.ELEPHANT, V.NOTHING].includes(b[1])) return "Schess/" + b;
34 return b;
35 }
36
37 static IsGoodFen(fen) {
38 if (!ChessRules.IsGoodFen(fen)) return false;
39 const fenParsed = V.ParseFen(fen);
40 // Check pocket state
41 if (!fenParsed.pocket || !fenParsed.pocket.match(/^[0-1]{4,4}$/))
42 return false;
43 return true;
44 }
45
46 static IsGoodFlags(flags) {
47 // 4 for castle + 16 for generators
48 return !!flags.match(/^[a-z]{4,4}[01]{16,16}$/);
49 }
50
51 setFlags(fenflags) {
52 super.setFlags(fenflags); //castleFlags
53 this.pieceFlags = {
54 w: [...Array(8)], //pieces can generate Hawk or Elephant?
55 b: [...Array(8)]
56 };
57 const flags = fenflags.substr(4); //skip first 4 letters, for castle
58 for (let c of ["w", "b"]) {
59 for (let i = 0; i < 8; i++)
60 this.pieceFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1";
61 }
62 }
63
64 aggregateFlags() {
65 return [this.castleFlags, this.pieceFlags];
66 }
67
68 disaggregateFlags(flags) {
69 this.castleFlags = flags[0];
70 this.pieceFlags = flags[1];
71 }
72
73 static ParseFen(fen) {
74 const fenParts = fen.split(" ");
75 return Object.assign(
76 ChessRules.ParseFen(fen),
77 { pocket: fenParts[5] }
78 );
79 }
80
81 static GenRandInitFen(options) {
82 return (
83 ChessRules.GenRandInitFen(options).slice(0, -2) +
84 // Add pieceFlags + pocket
85 "1111111111111111 - 1111"
86 );
87 }
88
89 getFen() {
90 return (
91 super.getFen() + " " +
92 this.getPocketFen()
93 );
94 }
95
96 getFenForRepeat() {
97 return (
98 super.getFenForRepeat() + "_" +
99 this.getPocketFen()
100 );
101 }
102
103 getFlagsFen() {
104 let fen = super.getFlagsFen();
105 // Add pieces flags
106 for (let c of ["w", "b"])
107 for (let i = 0; i < 8; i++) fen += (this.pieceFlags[c][i] ? "1" : "0");
108 return fen;
109 }
110
111 getPocketFen() {
112 let res = "";
113 for (let c of ["w", "b"])
114 res += this.pocket[c][V.HAWK] + this.pocket[c][V.ELEPHANT];
115 return res;
116 }
117
118 setOtherVariables(fen) {
119 super.setOtherVariables(fen);
120 const fenParsed = V.ParseFen(fen);
121 this.pocket = {
122 "w": {
123 h: parseInt(fenParsed.pocket[0], 10),
124 e: parseInt(fenParsed.pocket[1], 10)
125 },
126 "b": {
127 h: parseInt(fenParsed.pocket[2], 10),
128 e: parseInt(fenParsed.pocket[3], 10)
129 }
130 };
131 }
132
133 getPotentialMovesFrom([x, y]) {
134 let moves = undefined;
135 switch (this.getPiece(x, y)) {
136 case V.HAWK:
137 moves = this.getPotentialHawkMoves([x, y]);
138 break;
139 case V.ELEPHANT:
140 moves = this.getPotentialElephantMoves([x, y]);
141 break;
142 default:
143 moves = super.getPotentialMovesFrom([x, y]);
144 }
145 // For moves presentation when choices:
146 const unshiftNothing = (m) => {
147 const a = m.appear[0];
148 m.appear.unshift(new PiPo({
149 p: V.NOTHING,
150 c: 'o',
151 x: a.x,
152 y: a.y
153 }));
154 };
155 // Post-processing: add choices for hawk and elephant,
156 // except for moves letting the king under check.
157 const color = this.turn;
158 if (Object.values(this.pocket[color]).some(v => v > 0)) {
159 const firstRank = (color == "w" ? 7 : 0);
160 let validMoves = [];
161 moves.forEach(m => {
162 let inCheckAfter = false;
163 this.play(m);
164 if (this.underCheck(color)) inCheckAfter = true;
165 this.undo(m);
166 if (!inCheckAfter) {
167 for (let pp of ['h', 'e']) {
168 if (this.pocket[color][pp] > 0) {
169 let shift = (m.appear[0].p == V.NOTHING ? 1 : 0);
170 if (
171 m.start.x == firstRank &&
172 this.pieceFlags[color][m.start.y] &&
173 (
174 m.appear.length == shift+1 ||
175 // Special castle case: is initial king square free?
176 ![m.appear[shift].y, m.appear[shift+1].y]
177 .includes(m.vanish[0].y)
178 )
179 ) {
180 let pMove = JSON.parse(JSON.stringify(m));
181 if (shift == 1) pMove.appear.shift();
182 // NOTE: unshift instead of push, for choices presentation
183 pMove.appear.unshift(new PiPo({
184 p: pp,
185 c: color,
186 x: x,
187 y: y
188 }));
189 validMoves.push(pMove);
190 if (shift == 0) unshiftNothing(m);
191 }
192 shift = (m.appear[0].p == V.NOTHING ? 1 : 0);
193 if (
194 m.appear.length >= 2 + shift &&
195 m.vanish.length == 2 &&
196 ![m.appear[shift].y, m.appear[shift+1].y]
197 .includes(m.vanish[1].y)
198 ) {
199 // Special castle case: rook flag was necessarily on
200 let pMove = JSON.parse(JSON.stringify(m));
201 if (shift == 1) pMove.appear.shift();
202 pMove.appear.unshift(new PiPo({
203 p: pp,
204 c: color,
205 x: m.vanish[1].x,
206 y: m.vanish[1].y
207 }));
208 validMoves.push(pMove);
209 if (shift == 0) unshiftNothing(m);
210 }
211 }
212 }
213 // Unshift, to show the empty square on the left:
214 validMoves.unshift(m);
215 }
216 });
217 moves = validMoves;
218 }
219 return moves;
220 }
221
222 getPotentialHawkMoves(sq) {
223 return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
224 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], 1)
225 );
226 }
227
228 getPotentialElephantMoves(sq) {
229 return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
230 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], 1)
231 );
232 }
233
234 isAttacked(sq, color) {
235 return (
236 super.isAttacked(sq, color) ||
237 this.isAttackedByHawk(sq, color) ||
238 this.isAttackedByElephant(sq, color)
239 );
240 }
241
242 isAttackedByHawk(sq, color) {
243 return (
244 this.isAttackedBySlideNJump(sq, color, V.HAWK, V.steps[V.BISHOP]) ||
245 this.isAttackedBySlideNJump(sq, color, V.HAWK, V.steps[V.KNIGHT], 1)
246 );
247 }
248
249 isAttackedByElephant(sq, color) {
250 return (
251 this.isAttackedBySlideNJump(sq, color, V.ELEPHANT, V.steps[V.ROOK]) ||
252 this.isAttackedBySlideNJump(sq, color, V.ELEPHANT, V.steps[V.KNIGHT], 1)
253 );
254 }
255
256 filterValid(moves) {
257 if (Object.values(this.pocket[this.turn]).some(v => v > 0))
258 // Undercheck tests done in getPotentialMovesFrom()
259 return moves;
260 return super.filterValid(moves);
261 }
262
263 prePlay(move) {
264 super.prePlay(move);
265 if (move.appear.length >= 2) {
266 if ([V.HAWK, V.ELEPHANT].includes(move.appear[0].p)) {
267 // A pocket piece is used
268 const color = this.turn;
269 this.pocket[color][move.appear[0].p] = 0;
270 }
271 }
272 }
273
274 postPlay(move) {
275 const color = move.vanish[0].c;
276 const piece = move.vanish[0].p;
277 // Update king position + flags
278 if (piece == V.KING) {
279 const shift =
280 ([V.HAWK, V.ELEPHANT, V.NOTHING].includes(move.appear[0].p) ? 1 : 0);
281 this.kingPos[color][0] = move.appear[shift].x;
282 this.kingPos[color][1] = move.appear[shift].y;
283 }
284 this.updateCastleFlags(move, piece);
285
286 const oppCol = this.turn;
287 const firstRank = (color == 'w' ? 7 : 0);
288 const oppFirstRank = 7 - firstRank;
289 // Does this move turn off a piece init square flag?
290 if (move.start.x == firstRank) {
291 if (this.pieceFlags[color][move.start.y])
292 this.pieceFlags[color][move.start.y] = false;
293 // Special castle case:
294 if (move.appear.length >= 2 && move.vanish.length == 2) {
295 const L = move.appear.length;
296 if (move.appear[L-1].p == V.ROOK)
297 this.pieceFlags[color][move.vanish[1].y] = false;
298 }
299 }
300 if (move.end.x == oppFirstRank && this.pieceFlags[oppCol][move.end.y])
301 this.pieceFlags[oppCol][move.end.y] = false;
302 }
303
304 postUndo(move) {
305 super.postUndo(move);
306 if (move.appear.length >= 2) {
307 if ([V.HAWK, V.ELEPHANT].includes(move.appear[0].p)) {
308 // A pocket piece was used
309 const color = this.turn;
310 this.pocket[color][move.appear[0].p] = 1;
311 }
312 }
313 }
314
315 static get SEARCH_DEPTH() {
316 return 2;
317 }
318
319 static get VALUES() {
320 return Object.assign(
321 {
322 'h': 5,
323 'e': 7
324 },
325 ChessRules.VALUES
326 );
327 }
328
329 getNotation(move) {
330 if (move.appear.length >= 2) {
331 const pPieceAppear = [V.HAWK, V.ELEPHANT].includes(move.appear[0].p);
332 const nothingAppear = (move.appear[0].p == V.NOTHING);
333 if (pPieceAppear || nothingAppear) {
334 let suffix = "";
335 if (pPieceAppear) {
336 suffix = "/" + move.appear[0].p.toUpperCase();
337 if (move.appear.length == 3) {
338 // Castling; indicate square
339 suffix +=
340 V.CoordsToSquare({ x: move.appear[0].x, y: move.appear[0].y });
341 }
342 }
343 let cmove = JSON.parse(JSON.stringify(move));
344 cmove.appear.shift();
345 return super.getNotation(cmove) + suffix;
346 }
347 }
348 return super.getNotation(move);
349 }
350
351 };