First draft of Shinobi (unworking)
[vchess.git] / client / src / variants / Shinobi.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3
4 export class ShinobiRules extends ChessRules {
5
6 /* Would be unused:
7 static get PawnSpecs() {
8 return Object.assign(
9 { promotions: [V.PAWN] },
10 ChessRules.PawnSpecs
11 );
12 } */
13
14 static get CAPTAIN() {
15 return 'c';
16 }
17 static get NINJA() {
18 return 'j';
19 }
20 static get SAMURAI() {
21 return 's';
22 }
23 static get MONK() {
24 return 'm';
25 }
26 static get HORSE() {
27 return 'h';
28 }
29 static get LANCE() {
30 return 'l';
31 }
32
33 static get PIECES() {
34 return (
35 ChessRules.PIECES
36 .concat([V.CAPTAIN, V.NINJA, V.SAMURAI, V.MONK, V.HORSE, V.LANCE])
37 );
38 }
39
40 getPpath(b) {
41 if (b[0] == 'b' && b[1] != 'c') return b;
42 return "Shinobi/" + b;
43 }
44
45 getReservePpath(index, color) {
46 return "Shinobi/" + color + V.RESERVE_PIECES[index];
47 }
48
49 static IsGoodFen(fen) {
50 if (!ChessRules.IsGoodFen(fen)) return false;
51 const fenParsed = V.ParseFen(fen);
52 // 5) Check reserve
53 if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{6,6}$/))
54 return false;
55 return true;
56 }
57
58 static ParseFen(fen) {
59 const fenParts = fen.split(" ");
60 return Object.assign(
61 ChessRules.ParseFen(fen),
62 { reserve: fenParts[5] }
63 );
64 }
65
66 // In hand initially: another captain, a ninja + a samurai,
67 // and 2 x monk, horse, lance (TODO)
68 static GenRandInitFen(randomness) {
69 const baseFen = ChessRules.GenRandInitFen(Math.min(randomness, 1));
70 return (
71 baseFen.substr(0, 33) + "3CK3 " +
72 "w 0 " + baseFen.substr(38, 2) + " - 111222"
73 );
74 }
75
76 getFen() {
77 return super.getFen() + " " + this.getReserveFen();
78 }
79
80 getFenForRepeat() {
81 return super.getFenForRepeat() + "_" + this.getReserveFen();
82 }
83
84 getReserveFen() {
85 // TODO: can simplify other drops variants with this code:
86 return Object.values(this.reserve['w']).join("");
87 }
88
89 setOtherVariables(fen) {
90 super.setOtherVariables(fen);
91 const reserve =
92 V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10));
93 this.reserve = {
94 w: {
95 [V.CAPTAIN]: reserve[0],
96 [V.NINJA]: reserve[1],
97 [V.SAMURAI]: reserve[2],
98 [V.MONK]: reserve[3],
99 [V.HORSE]: reserve[4]
100 [V.LANCE]: reserve[5]
101 }
102 };
103 }
104
105 getColor(i, j) {
106 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
107 return this.board[i][j].charAt(0);
108 }
109
110 getPiece(i, j) {
111 if (i >= V.size.x) return V.RESERVE_PIECES[j];
112 return this.board[i][j].charAt(1);
113 }
114
115 // Ordering on reserve pieces
116 static get RESERVE_PIECES() {
117 return [V.CAPTAIN, V.NINJA, V.SAMURAI, V.MONK, V.HORSE, V.LANCE];
118 }
119
120 getReserveMoves([x, y]) {
121 // color == 'w', no drops for black.
122 const p = V.RESERVE_PIECES[y];
123 if (this.reserve['w'][p] == 0) return [];
124 let moves = [];
125 for (let i of [4, 5, 6, 7]) {
126 for (let j = 0; j < V.size.y; j++) {
127 if (this.board[i][j] == V.EMPTY) {
128 let mv = new Move({
129 appear: [
130 new PiPo({
131 x: i,
132 y: j,
133 c: color,
134 p: p
135 })
136 ],
137 vanish: [],
138 start: { x: x, y: y },
139 end: { x: i, y: j }
140 });
141 moves.push(mv);
142 }
143 }
144 }
145 return moves;
146 }
147
148 static get MapUnpromoted() {
149 return {
150 m: 'b',
151 h: 'n',
152 l: 'r',
153 p: 'c'
154 };
155 }
156
157 getPotentialMovesFrom([x, y]) {
158 if (x >= V.size.x) {
159 // Reserves, outside of board: x == sizeX(+1)
160 if (this.turn == 'b') return [];
161 return this.getReserveMoves([x, y]);
162 }
163 // Standard moves
164 const piece = this.getPiece(x, y);
165 const sq = [x, y];
166 if (ChessRules.includes(piece)) return super.getPotentialMovesFrom(sq);
167 switch (piece) {
168 case V.KING: return super.getPotentialKingMoves(sq);
169 case V.CAPTAIN: return this.getPotentialCaptainMoves(sq);
170 case V.NINJA: return this.getPotentialNinjaMoves(sq);
171 case V.SAMURAI: return this.getPotentialSamuraiMoves(sq);
172 }
173 let moves = [];
174 switch (piece) {
175 // Unpromoted
176 case V.PAWN:
177 moves = super.getPotentialPawnMoves(sq);
178 case V.MONK:
179 moves = this.getPotentialMonkMoves(sq);
180 break;
181 case V.HORSE:
182 moves = this.getPotentialHorseMoves(sq);
183 break;
184 case V.LANCE:
185 moves = this.getPotentialLanceMoves(sq);
186 break;
187 }
188 const promotionZone = (this.turn == 'w' ? [0, 1, 2] : [5, 6, 7]);
189 const promotedForm = V.MapUnpromoted[piece];
190 moves.forEach(m => {
191 if (promotionZone.includes(m.end.x)) move.appear[0].p = promotedForm;
192 });
193 return moves;
194 }
195
196 getPotentialCaptainMoves([x, y]) {
197 }
198
199 // TODO: adapt...
200 getPotentialNinjaMoves(sq) {
201 return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep");
202 }
203
204 getPotentialSamuraiMoves(sq) {
205 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
206 return super.getSlideNJumpMoves(sq, steps, "oneStep");
207 }
208
209 getPotentialMonkMoves(sq) {
210 return (
211 super.getSlideNJumpMoves(sq, V.steps[V.ROOK])
212 .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"))
213 );
214 }
215
216 getPotentialHorseMoves(sq) {
217 const steps =
218 V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]);
219 return super.getSlideNJumpMoves(sq, steps, "oneStep");
220 }
221
222 getPotentialLanceMoves(sq) {
223 return (
224 super.getSlideNJumpMoves(sq, V.steps[V.BISHOP])
225 .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"))
226 );
227 }
228
229 isAttacked(sq, color) {
230 if (color == 'b')
231 return (super.isAttacked(sq, 'b') || this.isAttackedByCaptain(sq, 'b'));
232 // Attacked by white:
233 return (
234 super.isAttackedByKing(sq, 'w') ||
235 this.isAttackedByCaptain(sq, 'w') ||
236 this.isAttackedByNinja(sq, 'w')
237 this.isAttackedBySamurai(sq, 'w')
238 this.isAttackedByMonk(sq, 'w') ||
239 this.isAttackedByHorse(sq, 'w') ||
240 this.isAttackedByLance(sq, 'w') ||
241 super.isAttackedByBishop(sq, 'w') ||
242 super.isAttackedByKnight(sq, 'w') ||
243 super.isAttackedByRook(sq, 'w')
244 );
245 }
246
247 isAttackedByCaptain(sq, color) {
248 const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]);
249 return (
250 super.isAttackedBySlideNJump(sq, color, V.DUCHESS, steps, "oneStep")
251 );
252 }
253
254 isAttackedByNinja(sq, color) {
255 return (
256 super.isAttackedBySlideNJump(
257 sq, color, V.DUCHESS, V.steps[V.BISHOP], "oneStep")
258 );
259 }
260
261 isAttackedBySamurai(sq, color) {
262 return (
263 super.isAttackedBySlideNJump(sq, color, V.MORTAR, V.steps[V.ROOK]) ||
264 super.isAttackedBySlideNJump(
265 sq, color, V.MORTAR, V.steps[V.KNIGHT], "oneStep")
266 );
267 }
268
269 isAttackedByMonk(sq, color) {
270 const steps =
271 V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]);
272 return (
273 super.isAttackedBySlideNJump(sq, color, V.GENERAL, steps, "oneStep")
274 );
275 }
276
277 isAttackedByHorse(sq, color) {
278 return (
279 super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP])
280 ||
281 super.isAttackedBySlideNJump(
282 sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], "oneStep")
283 );
284 }
285
286 isAttackedByLance(sq, color) {
287 return (
288 super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP])
289 ||
290 super.isAttackedBySlideNJump(
291 sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], "oneStep")
292 );
293 }
294
295 getAllValidMoves() {
296 let moves = super.getAllPotentialMoves();
297 const color = this.turn;
298 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
299 moves = moves.concat(
300 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
301 );
302 }
303 return this.filterValid(moves);
304 }
305
306 atLeastOneMove() {
307 if (!super.atLeastOneMove()) {
308 // Search one reserve move
309 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
310 let moves = this.filterValid(
311 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
312 );
313 if (moves.length > 0) return true;
314 }
315 return false;
316 }
317 return true;
318 }
319
320 // TODO: only black can castle (see Orda)
321
322 postPlay(move) {
323 super.postPlay(move);
324 // Skip castle:
325 if (move.vanish.length == 2 && move.appear.length == 2) return;
326 const color = move.appear[0].c;
327 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--;
328 }
329
330 postUndo(move) {
331 super.postUndo(move);
332 if (move.vanish.length == 2 && move.appear.length == 2) return;
333 const color = this.turn;
334 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++;
335 }
336
337 /*
338 static get SEARCH_DEPTH() {
339 return 2;
340 } */
341
342 // TODO:
343 static get VALUES() {
344 return (
345 Object.assign(
346 {
347 c: 4,
348 g: 5,
349 a: 7,
350 m: 7,
351 f: 2
352 },
353 ChessRules.VALUES
354 )
355 );
356 }
357
358 evalPosition() {
359 let evaluation = super.evalPosition();
360 // Add reserves:
361 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
362 const p = V.RESERVE_PIECES[i];
363 evaluation += this.reserve["w"][p] * V.VALUES[p];
364 evaluation -= this.reserve["b"][p] * V.VALUES[p];
365 }
366 return evaluation;
367 }
368
369 getNotation(move) {
370 if (move.vanish.length > 0) return super.getNotation(move);
371 // Drop:
372 const piece =
373 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
374 return piece + "@" + V.CoordsToSquare(move.end);
375 }
376
377 };