ebc2c7c26f2d289053d6404bab805ebf23c42e33
[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 PIECES() {
24 return ChessRules.PIECES.concat([V.HAWK, V.ELEPHANT]);
25 }
26
27 getPpath(b) {
28 if ([V.HAWK, V.ELEPHANT].includes(b[1])) return "Schess/" + b;
29 return b;
30 }
31
32 // TODO: maybe changes could be done to this method to show "empty"
33 // instead of a piece to not use a pocket piece...
34 // getPPpath(b) { }
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 // Post-processing: add choices for hawk and elephant,
145 // except for moves letting the king under check.
146 const color = this.turn;
147 if (Object.values(this.pocket[color]).some(v => v > 0)) {
148 const firstRank = (color == "w" ? 7 : 0);
149 let pocketMoves = [];
150 moves.forEach(m => {
151 let inCheckAfter = false;
152 this.play(m);
153 if (this.underCheck(color)) inCheckAfter = true;
154 this.undo(m);
155 if (!inCheckAfter) {
156 for (let pp of ['h', 'e']) {
157 if (this.pocket[color][pp] > 0) {
158 if (
159 m.start.x == firstRank &&
160 this.pieceFlags[color][m.start.y] &&
161 (
162 m.appear.length == 1 ||
163 // Special castle case: is initial king square free?
164 ![m.appear[0].y, m.appear[1].y].includes(m.vanish[0].y)
165 )
166 ) {
167 let pMove = JSON.parse(JSON.stringify(m));
168 // NOTE: unshift instead of push, for choices presentation
169 pMove.appear.unshift(new PiPo({
170 p: pp,
171 c: color,
172 x: x,
173 y: y
174 }));
175 pocketMoves.push(pMove);
176 }
177 if (
178 m.appear.length == 2 &&
179 ![m.appear[0].y, m.appear[1].y].includes(m.vanish[1].y)
180 ) {
181 // Special castle case: rook flag was necessarily on
182 let pMove = JSON.parse(JSON.stringify(m));
183 pMove.appear.unshift(new PiPo({
184 p: pp,
185 c: color,
186 x: m.vanish[1].x,
187 y: m.vanish[1].y
188 }));
189 pocketMoves.push(pMove);
190 }
191 }
192 }
193 }
194 });
195 // NOTE: the order matter, for presentation on screen
196 moves = moves.concat(pocketMoves);
197 }
198 return moves;
199 }
200
201 getPotentialHawkMoves(sq) {
202 return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
203 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
204 );
205 }
206
207 getPotentialElephantMoves(sq) {
208 return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
209 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
210 );
211 }
212
213 isAttacked(sq, color) {
214 return (
215 super.isAttacked(sq, color) ||
216 this.isAttackedByHawk(sq, color) ||
217 this.isAttackedByElephant(sq, color)
218 );
219 }
220
221 isAttackedByHawk(sq, color) {
222 return (
223 this.isAttackedBySlideNJump(sq, color, V.HAWK, V.steps[V.BISHOP]) ||
224 this.isAttackedBySlideNJump(
225 sq,
226 color,
227 V.HAWK,
228 V.steps[V.KNIGHT],
229 "oneStep"
230 )
231 );
232 }
233
234 isAttackedByElephant(sq, color) {
235 return (
236 this.isAttackedBySlideNJump(sq, color, V.ELEPHANT, V.steps[V.ROOK]) ||
237 this.isAttackedBySlideNJump(
238 sq,
239 color,
240 V.ELEPHANT,
241 V.steps[V.KNIGHT],
242 "oneStep"
243 )
244 );
245 }
246
247 prePlay(move) {
248 super.prePlay(move);
249 if (move.appear.length >= 2) {
250 if ([V.HAWK, V.ELEPHANT].includes(move.appear[0].p)) {
251 // A pocket piece is used
252 const color = this.turn;
253 this.pocket[color][move.appear[0].p] = 0;
254 }
255 }
256 }
257
258 postPlay(move) {
259 super.postPlay(move);
260 const color = move.vanish[0].c;
261 const oppCol = V.GetOppCol(color);
262 const firstRank = (color == 'w' ? 7 : 0);
263 const oppFirstRank = 7 - firstRank;
264 // Does this move turn off a piece init square flag?
265 if (move.start.x == firstRank) {
266 if (this.pieceFlags[color][move.start.y])
267 this.pieceFlags[color][move.start.y] = false;
268 // Special castle case:
269 if (move.appear.length >= 2) {
270 const L = move.appear.length;
271 if (move.appear[L-1].p == V.ROOK)
272 this.pieceFlags[color][move.vanish[1].y] = false;
273 }
274 }
275 if (move.end.x == oppFirstRank && this.pieceFlags[oppCol][move.end.y])
276 this.pieceFlags[oppCol][move.end.y] = false;
277 }
278
279 postUndo(move) {
280 super.postUndo(move);
281 if (move.appear.length >= 2) {
282 if ([V.HAWK, V.ELEPHANT].includes(move.appear[0].p)) {
283 // A pocket piece was used
284 const color = this.turn;
285 this.pocket[color][move.appear[0].p] = 1;
286 }
287 }
288 }
289
290 static get SEARCH_DEPTH() {
291 return 2;
292 }
293
294 static get VALUES() {
295 return Object.assign(
296 {},
297 ChessRules.VALUES,
298 { 'h': 5, 'e': 7 }
299 );
300 }
301
302 getNotation(move) {
303 if (
304 move.appear.length >= 2 &&
305 [V.HAWK, V.ELEPHANT].includes(move.appear[0].p)
306 ) {
307 const suffix = "/" + move.appear[0].p.toUpperCase();
308 let cmove = JSON.parse(JSON.stringify(move));
309 cmove.appear.shift();
310 return super.getNotation(cmove) + suffix;
311 }
312 return super.getNotation(move);
313 }
314 };