1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
} from "@/utils/alea";
5 export class BarioRules
extends ChessRules
{
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() {
13 // Undetermined piece form:
14 static get UNDEFINED() {
19 return ChessRules
.PIECES
.concat(V
.UNDEFINED
);
23 if (b
[1] == V
.UNDEFINED
) return "Bario/" + b
;
27 canIplay(side
, [x
, y
]) {
28 if (this.movesCount
>= 2) return super.canIplay(side
, [x
, y
]);
32 (side
== 'w' && x
== 7) ||
33 (side
== 'b' && x
== 0)
38 hoverHighlight(x
, y
) {
41 this.movesCount
<= 1 &&
43 (c
== 'w' && x
== 7) ||
51 this.movesCount
<= 1 ||
52 // TODO: next line theoretically shouldn't be required...
53 (this.movesCount
== 2 && this.getColor(x
, y
) != this.turn
)
57 // Initiate the game by choosing a square for the king:
61 this.movesCount
>= 2 ||
63 (c
== 'w' && square
[0] != 7) ||
64 (c
== 'b' && square
[0] != 0)
71 new PiPo({ x: square
[0], y: square
[1], c: c
, p: V
.KING
})
74 new PiPo({ x: square
[0], y: square
[1], c: c
, p: V
.UNDEFINED
})
76 start: { x: -1, y: -1 },
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
) {
87 for (let i
= 0; i
< row
.length
; i
++) {
88 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
90 const num
= parseInt(row
[i
], 10);
91 if (isNaN(num
) || num
<= 0) return false;
95 if (sumElts
!= V
.size
.y
) return false;
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;
108 static ParseFen(fen
) {
109 const fenParts
= fen
.split(" ");
110 return Object
.assign(
112 reserve: fenParts
[4],
115 ChessRules
.ParseFen(fen
)
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
]];
125 return counts
.join("");
129 const L
= this.captureUndefined
.length
;
130 const cu
= this.captureUndefined
[L
-1];
131 return (!!cu
? V
.CoordsToSquare(cu
) : "-");
136 super.getFen() + " " +
137 this.getReserveFen() + " " +
144 super.getFenForRepeat() + "_" +
145 this.getReserveFen() + "_" +
150 static GenRandInitFen() {
151 return "uuuuuuuu/pppppppp/8/8/8/8/PPPPPPPP/UUUUUUUU w 0 - 22212221 -";
154 setOtherVariables(fen
) {
155 super.setOtherVariables(fen
);
157 V
.ParseFen(fen
).reserve
.split("").map(x
=> parseInt(x
, 10));
160 [V
.ROOK
]: reserve
[0],
161 [V
.KNIGHT
]: reserve
[1],
162 [V
.BISHOP
]: reserve
[2],
163 [V
.QUEEN
]: reserve
[3]
166 [V
.ROOK
]: reserve
[4],
167 [V
.KNIGHT
]: reserve
[5],
168 [V
.BISHOP
]: reserve
[6],
169 [V
.QUEEN
]: reserve
[7]
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
= [];
180 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
181 return this.board
[i
][j
].charAt(0);
185 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
186 return this.board
[i
][j
].charAt(1);
189 getReservePpath(index
, color
) {
190 return color
+ V
.RESERVE_PIECES
[index
];
193 static get RESERVE_PIECES() {
194 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
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];
206 // Nothing changes on the board, just mark start.p for reserve update
210 start: { x: x
, y: y
, p: p
},
211 end: { x: cu
.x
, y: cu
.y
}
215 // or, subTurn == 1 => target any undefined piece that we own.
217 for (let i
= 0; i
< V
.size
.x
; i
++) {
218 for (let j
= 0; j
< V
.size
.y
; j
++) {
220 this.board
[i
][j
] != V
.EMPTY
&&
221 this.getColor(i
, j
) == color
&&
222 this.getPiece(i
, j
) == V
.UNDEFINED
226 new PiPo({ x: i
, y: j
, c: color
, p: p
})
229 new PiPo({ x: i
, y: j
, c: color
, p: V
.UNDEFINED
})
231 start: { x: x
, y: y
},
241 getPotentialMovesFrom([x
, y
]) {
242 if (this.subTurn
== 0) {
243 if (x
< V
.size
.x
) return [];
244 return this.getReserveMoves([x
, y
]);
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 [];
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 [];
258 return super.getPotentialMovesFrom([x
, y
]);
261 getAllPotentialMoves() {
262 const color
= this.turn
;
263 if (this.movesCount
<= 1) {
264 // Just put the king on the board
266 const firstRank
= (color
== 'w' ? 7 : 0);
267 return [...Array(8)].map((x
, j
) => {
270 new PiPo({ x: firstRank
, y: j
, c: color
, p: V
.KING
})
273 new PiPo({ x: firstRank
, y: j
, c: color
, p: V
.UNDEFINED
})
275 start: { x: -1, y: -1 }
279 const getAllReserveMoves
= () => {
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
])
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
);
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
305 if (this.subTurn
== 1)
306 // Can either play a move, or specialize a piece
307 res
= this.filterValid(this.getAllPotentialMoves()).length
> 0;
309 // subTurn == 2: can only play a specialized piece
310 res
= this.filterValid(
311 this.getPotentialMovesFrom([m
.end
.x
, m
.end
.y
])).length
> 0;
317 const res
= !this.underCheck(color
);
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
])
329 if (moves
.length
> 0) return true;
333 if (this.subTurn
== 0) return true; //always one reserve for an undefined
334 if (!super.atLeastOneMove()) return atLeastOneReserveMove();
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
);
347 (absShift
[0] + absShift
[1] != 3 || shift
[0] == 0 || shift
[1] == 0)
349 (p
== V
.ROOK
&& shift
[0] != 0 && shift
[1] != 0) ||
350 (p
== V
.BISHOP
&& absShift
[0] != absShift
[1]) ||
353 shift
[0] != 0 && shift
[1] != 0 && absShift
[0] != absShift
[1]
358 // Step is compatible with piece:
360 shift
[0] / Math
.abs(shift
[0]) || 0,
361 shift
[1] / Math
.abs(shift
[1]) || 0
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;
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
++) {
376 this.board
[i
][j
] != V
.EMPTY
&&
377 this.getColor(i
, j
) == oppCol
&&
378 this.getPiece(i
, j
) == V
.UNDEFINED
380 for (let p
of V
.RESERVE_PIECES
) {
382 this.reserve
[oppCol
][p
] >= 1 &&
383 pieceAttackOn(p
, [i
, j
], this.kingPos
[color
])
395 if (this.movesCount
<= 2) return [];
396 return super.getCheckSquares();
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
);
405 (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.UNDEFINED
? 0 : 1);
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
]--;
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
);
426 // Normal move (subTurn 1 or 2: change turn)
427 this.epSquares
.push(this.getEpSquare(move));
434 const color
= V
.GetOppCol(this.turn
);
435 if (this.movesCount
<= 2) this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
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);
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!)
446 this.board
.every(b
=> {
447 return b
.every(cell
=> {
451 cell
[1] != V
.UNDEFINED
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);
471 const my_pk
= Object
.keys(definedPieces
[color
]);
472 const opp_pk
= Object
.keys(definedPieces
[oppCol
]);
474 opp_pk
.length
>= 2 ||
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);
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);
491 let cp
of [{ c: color
, pk: my_pk
}, { c: oppCol
, pk: opp_pk
}]
493 if (cp
.pk
.length
>= 2 || (cp
.c
== oppCol
&& oppRevert
)) {
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
++) {
499 this.board
[i
][j
] != V
.EMPTY
&&
500 this.getColor(i
, j
) == cp
.c
&&
501 piecesList
.includes(this.getPiece(i
, j
))
503 this.board
[i
][j
] = cp
.c
+ V
.UNDEFINED
;
516 const toPrevPlayer
= () => {
517 V
.UndoOnBoard(this.board
, move);
518 [this.turn
, this.subTurn
] = move.turn
;
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];
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];
537 this.epSquares
.pop();
544 const color
= this.turn
;
545 if (this.movesCount
<= 1) this.kingPos
[color
] = [-1, -1];
547 this.captureUndefined
.pop();
548 if (move.appear
[0].p
== V
.KING
) super.postUndo(move);
550 if (!!move.position
) {
551 this.board
= V
.GetBoard(move.position
);
552 this.reserve
= move.reserve
;
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
;
564 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
567 // Just play random moves (for now at least. TODO?)
568 while (moves
.length
> 0) {
569 mv
= moves
[randInt(moves
.length
)];
572 if (this.turn
== color
) {
573 if (this.subTurn
== 1) moves
= this.getAllValidMoves();
576 moves
= this.filterValid(
577 this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]));
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]);
586 return null; //never reached
589 static get VALUES() {
590 return Object
.assign({ u: 0 }, ChessRules
.VALUES
);
593 // NOTE: evalPosition is wrong, but unused (random mover)
596 const end
= { x: move.end
.x
, y: move.end
.y
};
597 const endSquare
= V
.CoordsToSquare(end
);
598 if (move.appear
.length
== 0)
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
;
607 return super.getNotation(move);