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
265 const firstRank
= (color
== 'w' ? 7 : 0);
266 return [...Array(8)].map((x
, j
) => {
269 new PiPo({ x: firstRank
, y: j
, c: color
, p: V
.KING
})
272 new PiPo({ x: firstRank
, y: j
, c: color
, p: V
.UNDEFINED
})
274 start: { x: -1, y: -1 }
278 const getAllReserveMoves
= () => {
280 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
281 moves
= moves
.concat(
282 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
287 if (this.subTurn
== 0) return getAllReserveMoves();
288 let moves
= super.getAllPotentialMoves();
289 if (this.subTurn
== 1)
290 moves
= moves
.concat(getAllReserveMoves());
291 return this.filterValid(moves
);
295 if (this.movesCount
<= 1) return moves
;
296 const color
= this.turn
;
297 return moves
.filter(m
=> {
298 if (m
.vanish
.length
== 0) {
299 // subTurn == 0: need to check if a move exists at subTurn == 1
301 const res
= this.filterValid(this.getAllPotentialMoves()).length
> 0;
305 const start
= { x: m
.vanish
[0].x
, y: m
.vanish
[0].y
};
306 const end
= { x: m
.appear
[0].x
, y: m
.appear
[0].y
};
307 if (start
.x
== end
.x
&& start
.y
== end
.y
) {
308 // Unfinished turn: require careful check
311 if (this.subTurn
== 1)
312 // Can either play a move, or specialize a piece
313 res
= this.filterValid(this.getAllPotentialMoves()).length
> 0;
315 // subTurn == 2: can only play a specialized piece
316 res
= this.filterValid(
317 this.getPotentialMovesFrom([m
.end
.x
, m
.end
.y
])).length
> 0;
323 const res
= !this.underCheck(color
);
330 const atLeastOneReserveMove
= () => {
331 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
332 let moves
= this.filterValid(
333 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
335 if (moves
.length
> 0) return true;
339 if (this.subTurn
== 0) return atLeastOneReserveMove();
340 const canMoveSomething
= super.atLeastOneMove();
341 if (this.subTurn
== 2) return canMoveSomething
;
342 return (canMoveSomething
|| atLeastOneReserveMove());
346 if (super.underCheck(color
)) return true;
347 // Aux func for piece attack on king (no pawn)
348 const pieceAttackOn
= (p
, [x1
, y1
], [x2
, y2
]) => {
349 const shift
= [x2
- x1
, y2
- y1
];
350 const absShift
= shift
.map(Math
.abs
);
354 (absShift
[0] + absShift
[1] != 3 || shift
[0] == 0 || shift
[1] == 0)
356 (p
== V
.ROOK
&& shift
[0] != 0 && shift
[1] != 0) ||
357 (p
== V
.BISHOP
&& absShift
[0] != absShift
[1]) ||
360 shift
[0] != 0 && shift
[1] != 0 && absShift
[0] != absShift
[1]
365 // Step is compatible with piece:
367 shift
[0] / Math
.abs(shift
[0]) || 0,
368 shift
[1] / Math
.abs(shift
[1]) || 0
370 let [i
, j
] = [x1
+ step
[0], y1
+ step
[1]];
371 while (i
!= x2
|| j
!= y2
) {
372 if (!V
.OnBoard(i
, j
) || this.board
[i
][j
] != V
.EMPTY
) return false;
378 // Check potential specializations of undefined using reserve:
379 const oppCol
= V
.GetOppCol(color
);
380 for (let i
=0; i
<8; i
++) {
381 for (let j
=0; j
<8; j
++) {
383 this.board
[i
][j
] != V
.EMPTY
&&
384 this.getColor(i
, j
) == oppCol
&&
385 this.getPiece(i
, j
) == V
.UNDEFINED
387 for (let p
of V
.RESERVE_PIECES
) {
389 this.reserve
[oppCol
][p
] >= 1 &&
390 pieceAttackOn(p
, [i
, j
], this.kingPos
[color
])
402 if (this.movesCount
<= 2) return [];
403 return super.getCheckSquares();
407 move.turn
= [this.turn
, this.subTurn
]; //easier undo (TODO?)
408 const toNextPlayer
= () => {
409 V
.PlayOnBoard(this.board
, move);
410 this.turn
= V
.GetOppCol(this.turn
);
412 (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.UNDEFINED
? 0 : 1);
416 if (this.movesCount
<= 1) toNextPlayer();
417 else if (move.vanish
.length
== 0) {
418 // Removal (subTurn == 0 --> 1)
419 this.reserve
[this.turn
][move.start
.p
]--;
423 const start
= { x: move.vanish
[0].x
, y: move.vanish
[0].y
};
424 const end
= { x: move.appear
[0].x
, y: move.appear
[0].y
};
425 if (start
.x
== end
.x
&& start
.y
== end
.y
) {
426 // Specialisation (subTurn == 1 before 2)
427 this.reserve
[this.turn
][move.appear
[0].p
]--;
428 V
.PlayOnBoard(this.board
, move);
429 this.definitions
.push(move.end
);
433 // Normal move (subTurn 1 or 2: change turn)
434 this.epSquares
.push(this.getEpSquare(move));
441 const color
= V
.GetOppCol(this.turn
);
442 if (this.movesCount
<= 2) this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
444 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.UNDEFINED
)
445 this.captureUndefined
.push(move.end
);
446 else this.captureUndefined
.push(null);
447 if (move.appear
[0].p
== V
.KING
) super.postPlay(move);
449 // If now all my pieces are defined, back to undefined state,
450 // only if at least two different kind of pieces on board!
451 // Store current state in move (cannot infer it after!)
453 this.board
.every(b
=> {
454 return b
.every(cell
=> {
458 cell
[1] != V
.UNDEFINED
463 const piecesList
= [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
464 const oppCol
= this.turn
;
465 let definedPieces
= { w: {}, b: {} };
466 for (let i
=0; i
<8; i
++) {
467 for (let j
=0; j
<8; j
++) {
468 if (this.board
[i
][j
] != V
.EMPTY
) {
469 const p
= this.getPiece(i
, j
);
470 const c
= this.getColor(i
, j
);
471 if (piecesList
.includes(p
)) {
472 definedPieces
[c
][p
] =
473 (!definedPieces
[c
][p
] ? 1 : definedPieces
[c
][p
] + 1);
478 const my_pk
= Object
.keys(definedPieces
[color
]);
479 const opp_pk
= Object
.keys(definedPieces
[oppCol
]);
481 opp_pk
.length
>= 2 ||
483 // Only one opponent's piece is defined, but
484 // at least a different piece wait in reserve:
485 opp_pk
.length
== 1 &&
486 Object
.keys(this.reserve
[oppCol
]).some(k
=> {
487 return (k
!= opp_pk
[0] && this.reserve
[oppCol
][k
] >= 1);
491 if (my_pk
.length
>= 2 || oppRevert
) {
492 // NOTE: necessary HACK... because the move is played already.
493 V
.UndoOnBoard(this.board
, move);
494 move.position
= this.getBaseFen();
495 move.reserve
= JSON
.parse(JSON
.stringify(this.reserve
));
496 V
.PlayOnBoard(this.board
, move);
498 let cp
of [{ c: color
, pk: my_pk
}, { c: oppCol
, pk: opp_pk
}]
500 if (cp
.pk
.length
>= 2 || (cp
.c
== oppCol
&& oppRevert
)) {
502 this.reserve
[cp
.c
][p
] += definedPieces
[cp
.c
][p
];
503 for (let i
=0; i
<8; i
++) {
504 for (let j
=0; j
<8; j
++) {
506 this.board
[i
][j
] != V
.EMPTY
&&
507 this.getColor(i
, j
) == cp
.c
&&
508 piecesList
.includes(this.getPiece(i
, j
))
510 this.board
[i
][j
] = cp
.c
+ V
.UNDEFINED
;
523 const toPrevPlayer
= () => {
524 V
.UndoOnBoard(this.board
, move);
525 [this.turn
, this.subTurn
] = move.turn
;
529 if (this.movesCount
<= 2 && move.appear
[0].p
== V
.KING
) toPrevPlayer();
530 else if (move.vanish
.length
== 0) {
531 this.reserve
[this.turn
][move.start
.p
]++;
532 this.subTurn
= move.turn
[1];
535 const start
= { x: move.vanish
[0].x
, y: move.vanish
[0].y
};
536 const end
= { x: move.appear
[0].x
, y: move.appear
[0].y
};
537 if (start
.x
== end
.x
&& start
.y
== end
.y
) {
538 this.reserve
[this.turn
][move.appear
[0].p
]++;
539 V
.UndoOnBoard(this.board
, move);
540 this.definitions
.pop();
541 this.subTurn
= move.turn
[1];
544 this.epSquares
.pop();
551 const color
= this.turn
;
552 if (this.movesCount
<= 1) this.kingPos
[color
] = [-1, -1];
554 this.captureUndefined
.pop();
555 if (move.appear
[0].p
== V
.KING
) super.postUndo(move);
557 if (!!move.position
) {
558 this.board
= V
.GetBoard(move.position
);
559 this.reserve
= move.reserve
;
566 let initMoves
= this.getAllValidMoves();
567 if (initMoves
.length
== 0) return null;
568 // Loop until valid move is found (no un-specifiable piece...)
569 const color
= this.turn
;
571 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
574 // Just play random moves (for now at least. TODO?)
575 while (moves
.length
> 0) {
576 mv
= moves
[randInt(moves
.length
)];
579 if (this.turn
== color
) {
580 if (this.subTurn
== 1) moves
= this.getAllValidMoves();
583 moves
= this.filterValid(
584 this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]));
589 const thisIsTheEnd
= (this.turn
!= color
);
590 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
591 if (thisIsTheEnd
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
593 return null; //never reached
596 static get VALUES() {
597 return Object
.assign({ u: 0 }, ChessRules
.VALUES
);
600 // NOTE: evalPosition is wrong, but unused (random mover)
603 const end
= { x: move.end
.x
, y: move.end
.y
};
604 const endSquare
= V
.CoordsToSquare(end
);
605 if (move.appear
.length
== 0)
607 return move.start
.p
.toUpperCase() + endSquare
+ "X";
608 if (move.vanish
.length
== 0) return "K@" + endSquare
;
609 const start
= { x: move.vanish
[0].x
, y: move.vanish
[0].y
};
610 if (start
.x
== end
.x
&& start
.y
== end
.y
)
611 // Something is specialized
612 return move.appear
[0].p
.toUpperCase() + "@" + endSquare
;
614 return super.getNotation(move);