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