ba5730e6fdbc731202dcd11e7599312ffb86942b
[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)], //pawns can move 2 squares?
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]),
123 e: parseInt(fenParsed.pocket[1])
124 },
125 "b": {
126 h: parseInt(fenParsed.pocket[2]),
127 e: parseInt(fenParsed.pocket[3])
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].includes(m.vanish[0].y)
176 )
177 ) {
178 let pMove = JSON.parse(JSON.stringify(m));
179 if (shift == 1) pMove.appear.shift();
180 // NOTE: unshift instead of push, for choices presentation
181 pMove.appear.unshift(new PiPo({
182 p: pp,
183 c: color,
184 x: x,
185 y: y
186 }));
187 validMoves.push(pMove);
188 if (shift == 0) unshiftNothing(m);
189 }
190 shift = (m.appear[0].p == V.NOTHING ? 1 : 0);
191 if (
192 m.appear.length >= 2 + shift &&
193 m.vanish.length == 2 &&
194 ![m.appear[shift].y, m.appear[shift+1].y].includes(m.vanish[1].y)
195 ) {
196 // Special castle case: rook flag was necessarily on
197 let pMove = JSON.parse(JSON.stringify(m));
198 if (shift == 1) pMove.appear.shift();
199 pMove.appear.unshift(new PiPo({
200 p: pp,
201 c: color,
202 x: m.vanish[1].x,
203 y: m.vanish[1].y
204 }));
205 validMoves.push(pMove);
206 if (shift == 0) unshiftNothing(m);
207 }
208 }
209 }
210 // Unshift, to show the empty square on the left:
211 validMoves.unshift(m);
212 }
213 });
214 moves = validMoves;
215 }
216 return moves;
217 }
218
219 getPotentialHawkMoves(sq) {
220 return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
221 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
222 );
223 }
224
225 getPotentialElephantMoves(sq) {
226 return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
227 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
228 );
229 }
230
231 isAttacked(sq, color) {
232 return (
233 super.isAttacked(sq, color) ||
234 this.isAttackedByHawk(sq, color) ||
235 this.isAttackedByElephant(sq, color)
236 );
237 }
238
239 isAttackedByHawk(sq, color) {
240 return (
241 this.isAttackedBySlideNJump(sq, color, V.HAWK, V.steps[V.BISHOP]) ||
242 this.isAttackedBySlideNJump(
243 sq,
244 color,
245 V.HAWK,
246 V.steps[V.KNIGHT],
247 "oneStep"
248 )
249 );
250 }
251
252 isAttackedByElephant(sq, color) {
253 return (
254 this.isAttackedBySlideNJump(sq, color, V.ELEPHANT, V.steps[V.ROOK]) ||
255 this.isAttackedBySlideNJump(
256 sq,
257 color,
258 V.ELEPHANT,
259 V.steps[V.KNIGHT],
260 "oneStep"
261 )
262 );
263 }
264
265 filterValid(moves) {
266 if (Object.values(this.pocket[this.turn]).some(v => v > 0))
267 // Undercheck tests done in getPotentialMovesFrom()
268 return moves;
269 return super.filterValid(moves);
270 }
271
272 prePlay(move) {
273 super.prePlay(move);
274 if (move.appear.length >= 2) {
275 if ([V.HAWK, V.ELEPHANT].includes(move.appear[0].p)) {
276 // A pocket piece is used
277 const color = this.turn;
278 this.pocket[color][move.appear[0].p] = 0;
279 }
280 }
281 }
282
283 postPlay(move) {
284 const color = move.vanish[0].c;
285 const piece = move.vanish[0].p;
286 // Update king position + flags
287 if (piece == V.KING) {
288 const shift =
289 ([V.HAWK, V.ELEPHANT, V.NOTHING].includes(move.appear[0].p) ? 1 : 0);
290 this.kingPos[color][0] = move.appear[shift].x;
291 this.kingPos[color][1] = move.appear[shift].y;
292 return;
293 }
294 this.updateCastleFlags(move, piece);
295
296 const oppCol = V.GetOppCol(color);
297 const firstRank = (color == 'w' ? 7 : 0);
298 const oppFirstRank = 7 - firstRank;
299 // Does this move turn off a piece init square flag?
300 if (move.start.x == firstRank) {
301 if (this.pieceFlags[color][move.start.y])
302 this.pieceFlags[color][move.start.y] = false;
303 // Special castle case:
304 if (move.appear.length >= 2 && move.vanish.length == 2) {
305 const L = move.appear.length;
306 if (move.appear[L-1].p == V.ROOK)
307 this.pieceFlags[color][move.vanish[1].y] = false;
308 }
309 }
310 if (move.end.x == oppFirstRank && this.pieceFlags[oppCol][move.end.y])
311 this.pieceFlags[oppCol][move.end.y] = false;
312 }
313
314 postUndo(move) {
315 super.postUndo(move);
316 if (move.appear.length >= 2) {
317 if ([V.HAWK, V.ELEPHANT].includes(move.appear[0].p)) {
318 // A pocket piece was used
319 const color = this.turn;
320 this.pocket[color][move.appear[0].p] = 1;
321 }
322 }
323 }
324
325 static get SEARCH_DEPTH() {
326 return 2;
327 }
328
329 static get VALUES() {
330 return Object.assign(
331 {},
332 ChessRules.VALUES,
333 { 'h': 5, 'e': 7 }
334 );
335 }
336
337 getNotation(move) {
338 if (move.appear.length >= 2) {
339 const pPieceAppear = [V.HAWK, V.ELEPHANT].includes(move.appear[0].p);
340 const nothingAppear = (move.appear[0].p == V.NOTHING);
341 if (pPieceAppear || nothingAppear) {
342 let suffix = "";
343 if (pPieceAppear) suffix = "/" + move.appear[0].p.toUpperCase();
344 let cmove = JSON.parse(JSON.stringify(move));
345 cmove.appear.shift();
346 return super.getNotation(cmove) + suffix;
347 }
348 }
349 return super.getNotation(move);
350 }
351 };