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