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