Small fixes
[vchess.git] / client / src / variants / Bario.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt } from "@/utils/alea";
4
5 export class BarioRules extends ChessRules {
6
7 static get Options() {
8 return null;
9 }
10
11 // Does not really seem necessary (although the author mention it)
12 // Instead, first move = pick a square for the king.
13 static get HasFlags() {
14 return false;
15 }
16
17 // Undetermined piece form:
18 static get UNDEFINED() {
19 return 'u';
20 }
21
22 static get PIECES() {
23 return ChessRules.PIECES.concat(V.UNDEFINED);
24 }
25
26 getPpath(b) {
27 if (b[1] == V.UNDEFINED) return "Bario/" + b;
28 return b;
29 }
30
31 canIplay(side, [x, y]) {
32 if (this.movesCount >= 2) return super.canIplay(side, [x, y]);
33 return (
34 this.turn == side &&
35 (
36 (side == 'w' && x == 7) ||
37 (side == 'b' && x == 0)
38 )
39 );
40 }
41
42 hoverHighlight([x, y]) {
43 const c = this.turn;
44 return (
45 this.movesCount <= 1 &&
46 (
47 (c == 'w' && x == 7) ||
48 (c == 'b' && x == 0)
49 )
50 );
51 }
52
53 onlyClick([x, y]) {
54 return (
55 this.movesCount <= 1 ||
56 // TODO: next line theoretically shouldn't be required...
57 (this.movesCount == 2 && this.getColor(x, y) != this.turn)
58 );
59 }
60
61 // Initiate the game by choosing a square for the king:
62 doClick(square) {
63 const c = this.turn;
64 if (
65 this.movesCount >= 2 ||
66 (
67 (c == 'w' && square[0] != 7) ||
68 (c == 'b' && square[0] != 0)
69 )
70 ) {
71 return null;
72 }
73 return new Move({
74 appear: [
75 new PiPo({ x: square[0], y: square[1], c: c, p: V.KING })
76 ],
77 vanish: [
78 new PiPo({ x: square[0], y: square[1], c: c, p: V.UNDEFINED })
79 ],
80 start: { x: -1, y: -1 }
81 });
82 }
83
84 // Do not check kings (TODO: something more subtle!)
85 static IsGoodPosition(position) {
86 if (position.length == 0) return false;
87 const rows = position.split("/");
88 if (rows.length != V.size.x) return false;
89 for (let row of rows) {
90 let sumElts = 0;
91 for (let i = 0; i < row.length; i++) {
92 if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
93 else {
94 const num = parseInt(row[i], 10);
95 if (isNaN(num) || num <= 0) return false;
96 sumElts += num;
97 }
98 }
99 if (sumElts != V.size.y) return false;
100 }
101 return true;
102 }
103
104 static IsGoodFen(fen) {
105 if (!ChessRules.IsGoodFen(fen)) return false;
106 const fenParsed = V.ParseFen(fen);
107 if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{8,8}$/))
108 if (!fenParsed.capture) return false;
109 return true;
110 }
111
112 static ParseFen(fen) {
113 const fenParts = fen.split(" ");
114 return Object.assign(
115 {
116 reserve: fenParts[4],
117 capture: fenParts[5]
118 },
119 ChessRules.ParseFen(fen)
120 );
121 }
122
123 getReserveFen() {
124 let counts = new Array(8);
125 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
126 counts[i] = this.reserve["w"][V.RESERVE_PIECES[i]];
127 counts[4 + i] = this.reserve["b"][V.RESERVE_PIECES[i]];
128 }
129 return counts.join("");
130 }
131
132 getCaptureFen() {
133 const L = this.captureUndefined.length;
134 const cu = this.captureUndefined[L-1];
135 return (!!cu ? V.CoordsToSquare(cu) : "-");
136 }
137
138 getFen() {
139 return (
140 super.getFen() + " " +
141 this.getReserveFen() + " " +
142 this.getCaptureFen()
143 );
144 }
145
146 getFenForRepeat() {
147 return (
148 super.getFenForRepeat() + "_" +
149 this.getReserveFen() + "_" +
150 this.getCaptureFen()
151 );
152 }
153
154 static GenRandInitFen() {
155 return "uuuuuuuu/pppppppp/8/8/8/8/PPPPPPPP/UUUUUUUU w 0 - 22212221 -";
156 }
157
158 setOtherVariables(fen) {
159 super.setOtherVariables(fen);
160 const reserve =
161 V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10));
162 this.reserve = {
163 w: {
164 [V.ROOK]: reserve[0],
165 [V.KNIGHT]: reserve[1],
166 [V.BISHOP]: reserve[2],
167 [V.QUEEN]: reserve[3]
168 },
169 b: {
170 [V.ROOK]: reserve[4],
171 [V.KNIGHT]: reserve[5],
172 [V.BISHOP]: reserve[6],
173 [V.QUEEN]: reserve[7]
174 }
175 };
176 const cu = V.ParseFen(fen).capture;
177 this.captureUndefined = [cu == '-' ? null : V.SquareToCoords(cu)];
178 this.subTurn = (cu == "-" ? 1 : 0);
179 // Local stack of pieces' definitions
180 this.definitions = [];
181 }
182
183 getColor(i, j) {
184 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
185 return this.board[i][j].charAt(0);
186 }
187
188 getPiece(i, j) {
189 if (i >= V.size.x) return V.RESERVE_PIECES[j];
190 return this.board[i][j].charAt(1);
191 }
192
193 getReservePpath(index, color) {
194 return color + V.RESERVE_PIECES[index];
195 }
196
197 static get RESERVE_PIECES() {
198 return [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
199 }
200
201 getReserveMoves([x, y]) {
202 const color = this.turn;
203 const p = V.RESERVE_PIECES[y];
204 if (this.reserve[color][p] == 0) return [];
205 // 2 cases, subTurn == 0 => target this.captureUndefined only (one square)
206 if (this.subTurn == 0) {
207 const L = this.captureUndefined.length;
208 const cu = this.captureUndefined[L-1];
209 return [
210 // Nothing changes on the board, just mark start.p for reserve update
211 new Move({
212 appear: [],
213 vanish: [],
214 start: { x: x, y: y, p: p },
215 end: { x: cu.x, y: cu.y }
216 })
217 ];
218 }
219 // or, subTurn == 1 => target any undefined piece that we own.
220 let moves = [];
221 for (let i = 0; i < V.size.x; i++) {
222 for (let j = 0; j < V.size.y; j++) {
223 if (
224 this.board[i][j] != V.EMPTY &&
225 this.getColor(i, j) == color &&
226 this.getPiece(i, j) == V.UNDEFINED
227 ) {
228 let mv = new Move({
229 appear: [
230 new PiPo({ x: i, y: j, c: color, p: p })
231 ],
232 vanish: [
233 new PiPo({ x: i, y: j, c: color, p: V.UNDEFINED })
234 ],
235 start: { x: x, y: y },
236 end: { x: i, y: j }
237 });
238 moves.push(mv);
239 }
240 }
241 }
242 return moves;
243 }
244
245 getPotentialMovesFrom([x, y]) {
246 if (this.subTurn == 0) {
247 if (x < V.size.x) return [];
248 return this.getReserveMoves([x, y]);
249 }
250 if (this.subTurn == 1) {
251 // Both normal move (from defined piece) and definition allowed
252 if (x >= V.size.x) return this.getReserveMoves([x, y]);
253 if (this.getPiece(x, y) == V.UNDEFINED) return [];
254 }
255 // subTurn == 1 and we move any piece, or
256 // subTurn == 2 and we can only move the just-defined piece
257 if (this.subTurn == 2) {
258 const L = this.definitions.length; //at least 1
259 const df = this.definitions[L-1];
260 if (x != df.x || y != df.y) return [];
261 }
262 return super.getPotentialMovesFrom([x, y]);
263 }
264
265 getAllPotentialMoves() {
266 const color = this.turn;
267 if (this.movesCount <= 1) {
268 // Just put the king on the board
269 const firstRank = (color == 'w' ? 7 : 0);
270 return [...Array(8)].map((x, j) => {
271 return new Move({
272 appear: [
273 new PiPo({ x: firstRank, y: j, c: color, p: V.KING })
274 ],
275 vanish: [
276 new PiPo({ x: firstRank, y: j, c: color, p: V.UNDEFINED })
277 ],
278 start: { x: -1, y: -1 }
279 });
280 });
281 }
282 const getAllReserveMoves = () => {
283 let moves = [];
284 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
285 moves = moves.concat(
286 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
287 );
288 }
289 return moves;
290 }
291 if (this.subTurn == 0) return getAllReserveMoves();
292 let moves = super.getAllPotentialMoves();
293 if (this.subTurn == 1)
294 moves = moves.concat(getAllReserveMoves());
295 return this.filterValid(moves);
296 }
297
298 filterValid(moves) {
299 if (this.movesCount <= 1) return moves;
300 const color = this.turn;
301 return moves.filter(m => {
302 if (m.vanish.length == 0) {
303 // subTurn == 0: need to check if a move exists at subTurn == 1
304 this.play(m);
305 const res = this.filterValid(this.getAllPotentialMoves()).length > 0;
306 this.undo(m);
307 return res;
308 }
309 const start = { x: m.vanish[0].x, y: m.vanish[0].y };
310 const end = { x: m.appear[0].x, y: m.appear[0].y };
311 if (start.x == end.x && start.y == end.y) {
312 // Unfinished turn: require careful check
313 this.play(m);
314 let res = false;
315 if (this.subTurn == 1)
316 // Can either play a move, or specialize a piece
317 res = this.filterValid(this.getAllPotentialMoves()).length > 0;
318 else {
319 // subTurn == 2: can only play a specialized piece
320 res = this.filterValid(
321 this.getPotentialMovesFrom([m.end.x, m.end.y])).length > 0;
322 }
323 this.undo(m);
324 return res;
325 }
326 this.play(m);
327 const res = !this.underCheck(color);
328 this.undo(m);
329 return res;
330 });
331 }
332
333 atLeastOneMove() {
334 const atLeastOneReserveMove = () => {
335 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
336 let moves = this.filterValid(
337 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
338 );
339 if (moves.length > 0) return true;
340 }
341 return false;
342 };
343 if (this.subTurn == 0) return atLeastOneReserveMove();
344 const canMoveSomething = super.atLeastOneMove();
345 if (this.subTurn == 2) return canMoveSomething;
346 return (canMoveSomething || atLeastOneReserveMove());
347 }
348
349 underCheck(color) {
350 if (super.underCheck(color)) return true;
351 // Aux func for piece attack on king (no pawn)
352 const pieceAttackOn = (p, [x1, y1], [x2, y2]) => {
353 const shift = [x2 - x1, y2 - y1];
354 const absShift = shift.map(Math.abs);
355 if (
356 (
357 p == V.KNIGHT &&
358 (absShift[0] + absShift[1] != 3 || shift[0] == 0 || shift[1] == 0)
359 ) ||
360 (p == V.ROOK && shift[0] != 0 && shift[1] != 0) ||
361 (p == V.BISHOP && absShift[0] != absShift[1]) ||
362 (
363 p == V.QUEEN &&
364 shift[0] != 0 && shift[1] != 0 && absShift[0] != absShift[1]
365 )
366 ) {
367 return false;
368 }
369 // Step is compatible with piece:
370 const step = [
371 shift[0] / Math.abs(shift[0]) || 0,
372 shift[1] / Math.abs(shift[1]) || 0
373 ];
374 let [i, j] = [x1 + step[0], y1 + step[1]];
375 while (i != x2 || j != y2) {
376 if (!V.OnBoard(i, j) || this.board[i][j] != V.EMPTY) return false;
377 i += step[0];
378 j += step[1];
379 }
380 return true;
381 };
382 // Check potential specializations of undefined using reserve:
383 const oppCol = V.GetOppCol(color);
384 for (let i=0; i<8; i++) {
385 for (let j=0; j<8; j++) {
386 if (
387 this.board[i][j] != V.EMPTY &&
388 this.getColor(i, j) == oppCol &&
389 this.getPiece(i, j) == V.UNDEFINED
390 ) {
391 for (let p of V.RESERVE_PIECES) {
392 if (
393 this.reserve[oppCol][p] >= 1 &&
394 pieceAttackOn(p, [i, j], this.kingPos[color])
395 ) {
396 return true;
397 }
398 }
399 }
400 }
401 }
402 return false;
403 }
404
405 getCheckSquares() {
406 if (this.movesCount <= 2) return [];
407 return super.getCheckSquares();
408 }
409
410 play(move) {
411 move.turn = [this.turn, this.subTurn]; //easier undo (TODO?)
412 const toNextPlayer = () => {
413 V.PlayOnBoard(this.board, move);
414 this.turn = V.GetOppCol(this.turn);
415 this.subTurn =
416 (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED ? 0 : 1);
417 this.movesCount++;
418 this.postPlay(move);
419 };
420 if (this.movesCount <= 1) toNextPlayer();
421 else if (move.vanish.length == 0) {
422 // Removal (subTurn == 0 --> 1)
423 this.reserve[this.turn][move.start.p]--;
424 this.subTurn++;
425 }
426 else {
427 const start = { x: move.vanish[0].x, y: move.vanish[0].y };
428 const end = { x: move.appear[0].x, y: move.appear[0].y };
429 if (start.x == end.x && start.y == end.y) {
430 // Specialisation (subTurn == 1 before 2)
431 this.reserve[this.turn][move.appear[0].p]--;
432 V.PlayOnBoard(this.board, move);
433 this.definitions.push(move.end);
434 this.subTurn++;
435 }
436 else {
437 // Normal move (subTurn 1 or 2: change turn)
438 this.epSquares.push(this.getEpSquare(move));
439 toNextPlayer();
440 }
441 }
442 }
443
444 postPlay(move) {
445 const color = V.GetOppCol(this.turn);
446 if (this.movesCount <= 2) this.kingPos[color] = [move.end.x, move.end.y];
447 else {
448 if (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED)
449 this.captureUndefined.push(move.end);
450 else this.captureUndefined.push(null);
451 if (move.appear[0].p == V.KING) super.postPlay(move);
452 else {
453 // If now all my pieces are defined, back to undefined state,
454 // only if at least two different kind of pieces on board!
455 // Store current state in move (cannot infer it after!)
456 if (
457 this.board.every(b => {
458 return b.every(cell => {
459 return (
460 cell == V.EMPTY ||
461 cell[0] != color ||
462 cell[1] != V.UNDEFINED
463 );
464 });
465 })
466 ) {
467 const piecesList = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
468 const oppCol = this.turn;
469 let definedPieces = { w: {}, b: {} };
470 for (let i=0; i<8; i++) {
471 for (let j=0; j<8; j++) {
472 if (this.board[i][j] != V.EMPTY) {
473 const p = this.getPiece(i, j);
474 const c = this.getColor(i, j);
475 if (piecesList.includes(p)) {
476 definedPieces[c][p] =
477 (!definedPieces[c][p] ? 1 : definedPieces[c][p] + 1);
478 }
479 }
480 }
481 }
482 const my_pk = Object.keys(definedPieces[color]);
483 const opp_pk = Object.keys(definedPieces[oppCol]);
484 const oppRevert = (
485 opp_pk.length >= 2 ||
486 (
487 // Only one opponent's piece is defined, but
488 // at least a different piece wait in reserve:
489 opp_pk.length == 1 &&
490 Object.keys(this.reserve[oppCol]).some(k => {
491 return (k != opp_pk[0] && this.reserve[oppCol][k] >= 1);
492 })
493 )
494 );
495 if (my_pk.length >= 2 || oppRevert) {
496 // NOTE: necessary HACK... because the move is played already.
497 V.UndoOnBoard(this.board, move);
498 move.position = this.getBaseFen();
499 move.reserve = JSON.parse(JSON.stringify(this.reserve));
500 V.PlayOnBoard(this.board, move);
501 for (
502 let cp of [{ c: color, pk: my_pk }, { c: oppCol, pk: opp_pk }]
503 ) {
504 if (cp.pk.length >= 2 || (cp.c == oppCol && oppRevert)) {
505 for (let p of cp.pk)
506 this.reserve[cp.c][p] += definedPieces[cp.c][p];
507 for (let i=0; i<8; i++) {
508 for (let j=0; j<8; j++) {
509 if (
510 this.board[i][j] != V.EMPTY &&
511 this.getColor(i, j) == cp.c &&
512 piecesList.includes(this.getPiece(i, j))
513 ) {
514 this.board[i][j] = cp.c + V.UNDEFINED;
515 }
516 }
517 }
518 }
519 }
520 }
521 }
522 }
523 }
524 }
525
526 undo(move) {
527 const toPrevPlayer = () => {
528 V.UndoOnBoard(this.board, move);
529 [this.turn, this.subTurn] = move.turn;
530 this.movesCount--;
531 this.postUndo(move);
532 };
533 if (this.movesCount <= 2 && move.appear[0].p == V.KING) toPrevPlayer();
534 else if (move.vanish.length == 0) {
535 this.reserve[this.turn][move.start.p]++;
536 this.subTurn = move.turn[1];
537 }
538 else {
539 const start = { x: move.vanish[0].x, y: move.vanish[0].y };
540 const end = { x: move.appear[0].x, y: move.appear[0].y };
541 if (start.x == end.x && start.y == end.y) {
542 this.reserve[this.turn][move.appear[0].p]++;
543 V.UndoOnBoard(this.board, move);
544 this.definitions.pop();
545 this.subTurn = move.turn[1];
546 }
547 else {
548 this.epSquares.pop();
549 toPrevPlayer();
550 }
551 }
552 }
553
554 postUndo(move) {
555 const color = this.turn;
556 if (this.movesCount <= 1) this.kingPos[color] = [-1, -1];
557 else {
558 this.captureUndefined.pop();
559 if (move.appear[0].p == V.KING) super.postUndo(move);
560 else {
561 if (!!move.position) {
562 this.board = V.GetBoard(move.position);
563 this.reserve = move.reserve;
564 }
565 }
566 }
567 }
568
569 getComputerMove() {
570 let initMoves = this.getAllValidMoves();
571 if (initMoves.length == 0) return null;
572 // Loop until valid move is found (no un-specifiable piece...)
573 const color = this.turn;
574 while (true) {
575 let moves = JSON.parse(JSON.stringify(initMoves));
576 let mvArray = [];
577 let mv = null;
578 // Just play random moves (for now at least. TODO?)
579 while (moves.length > 0) {
580 mv = moves[randInt(moves.length)];
581 mvArray.push(mv);
582 this.play(mv);
583 if (this.turn == color) {
584 if (this.subTurn == 1) moves = this.getAllValidMoves();
585 else {
586 // subTurn == 2
587 moves = this.filterValid(
588 this.getPotentialMovesFrom([mv.end.x, mv.end.y]));
589 }
590 }
591 else break;
592 }
593 const thisIsTheEnd = (this.turn != color);
594 for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
595 if (thisIsTheEnd) return (mvArray.length > 1 ? mvArray : mvArray[0]);
596 }
597 return null; //never reached
598 }
599
600 static get VALUES() {
601 return Object.assign({ u: 0 }, ChessRules.VALUES);
602 }
603
604 // NOTE: evalPosition is wrong, but unused (random mover)
605
606 getNotation(move) {
607 const end = { x: move.end.x, y: move.end.y };
608 const endSquare = V.CoordsToSquare(end);
609 if (move.appear.length == 0)
610 // Removal
611 return move.start.p.toUpperCase() + endSquare + "X";
612 if (move.vanish.length == 0) return "K@" + endSquare;
613 const start = { x: move.vanish[0].x, y: move.vanish[0].y };
614 if (start.x == end.x && start.y == end.y)
615 // Something is specialized
616 return move.appear[0].p.toUpperCase() + "@" + endSquare;
617 // Normal move
618 return super.getNotation(move);
619 }
620
621 };