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