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