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
{
11 // Does not really seem necessary (although the author mention it)
12 // Instead, first move = pick a square for the king.
13 static get HasFlags() {
17 // Undetermined piece form:
18 static get UNDEFINED() {
23 return ChessRules
.PIECES
.concat(V
.UNDEFINED
);
27 if (b
[1] == V
.UNDEFINED
) return "Bario/" + b
;
31 canIplay(side
, [x
, y
]) {
32 if (this.movesCount
>= 2) return super.canIplay(side
, [x
, y
]);
36 (side
== 'w' && x
== 7) ||
37 (side
== 'b' && x
== 0)
42 hoverHighlight([x
, y
]) {
45 this.movesCount
<= 1 &&
47 (c
== 'w' && x
== 7) ||
55 this.movesCount
<= 1 ||
56 // TODO: next line theoretically shouldn't be required...
57 (this.movesCount
== 2 && this.getColor(x
, y
) != this.turn
)
61 // Initiate the game by choosing a square for the king:
65 this.movesCount
>= 2 ||
67 (c
== 'w' && square
[0] != 7) ||
68 (c
== 'b' && square
[0] != 0)
75 new PiPo({ x: square
[0], y: square
[1], c: c
, p: V
.KING
})
78 new PiPo({ x: square
[0], y: square
[1], c: c
, p: V
.UNDEFINED
})
80 start: { x: -1, y: -1 }
84 // Do not check kings (TODO: something more subtle!)
85 static IsGoodPosition(position
) {
86 if (position
.length
== 0) return false;
87 const rows
= position
.split("/");
88 if (rows
.length
!= V
.size
.x
) return false;
89 for (let row
of rows
) {
91 for (let i
= 0; i
< row
.length
; i
++) {
92 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
94 const num
= parseInt(row
[i
], 10);
95 if (isNaN(num
) || num
<= 0) return false;
99 if (sumElts
!= V
.size
.y
) return false;
104 static IsGoodFen(fen
) {
105 if (!ChessRules
.IsGoodFen(fen
)) return false;
106 const fenParsed
= V
.ParseFen(fen
);
107 if (!fenParsed
.reserve
|| !fenParsed
.reserve
.match(/^[0-9]{8,8}$/))
108 if (!fenParsed
.capture
) return false;
112 static ParseFen(fen
) {
113 const fenParts
= fen
.split(" ");
114 return Object
.assign(
116 reserve: fenParts
[4],
119 ChessRules
.ParseFen(fen
)
124 let counts
= new Array(8);
125 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
126 counts
[i
] = this.reserve
["w"][V
.RESERVE_PIECES
[i
]];
127 counts
[4 + i
] = this.reserve
["b"][V
.RESERVE_PIECES
[i
]];
129 return counts
.join("");
133 const L
= this.captureUndefined
.length
;
134 const cu
= this.captureUndefined
[L
-1];
135 return (!!cu
? V
.CoordsToSquare(cu
) : "-");
140 super.getFen() + " " +
141 this.getReserveFen() + " " +
148 super.getFenForRepeat() + "_" +
149 this.getReserveFen() + "_" +
154 static GenRandInitFen() {
155 return "uuuuuuuu/pppppppp/8/8/8/8/PPPPPPPP/UUUUUUUU w 0 - 22212221 -";
158 setOtherVariables(fen
) {
159 super.setOtherVariables(fen
);
161 V
.ParseFen(fen
).reserve
.split("").map(x
=> parseInt(x
, 10));
164 [V
.ROOK
]: reserve
[0],
165 [V
.KNIGHT
]: reserve
[1],
166 [V
.BISHOP
]: reserve
[2],
167 [V
.QUEEN
]: reserve
[3]
170 [V
.ROOK
]: reserve
[4],
171 [V
.KNIGHT
]: reserve
[5],
172 [V
.BISHOP
]: reserve
[6],
173 [V
.QUEEN
]: reserve
[7]
176 const cu
= V
.ParseFen(fen
).capture
;
177 this.captureUndefined
= [cu
== '-' ? null : V
.SquareToCoords(cu
)];
178 this.subTurn
= (cu
== "-" ? 1 : 0);
179 // Local stack of pieces' definitions
180 this.definitions
= [];
184 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
185 return this.board
[i
][j
].charAt(0);
189 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
190 return this.board
[i
][j
].charAt(1);
193 getReservePpath(index
, color
) {
194 return color
+ V
.RESERVE_PIECES
[index
];
197 static get RESERVE_PIECES() {
198 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
201 getReserveMoves([x
, y
]) {
202 const color
= this.turn
;
203 const p
= V
.RESERVE_PIECES
[y
];
204 if (this.reserve
[color
][p
] == 0) return [];
205 // 2 cases, subTurn == 0 => target this.captureUndefined only (one square)
206 if (this.subTurn
== 0) {
207 const L
= this.captureUndefined
.length
;
208 const cu
= this.captureUndefined
[L
-1];
210 // Nothing changes on the board, just mark start.p for reserve update
214 start: { x: x
, y: y
, p: p
},
215 end: { x: cu
.x
, y: cu
.y
}
219 // or, subTurn == 1 => target any undefined piece that we own.
221 for (let i
= 0; i
< V
.size
.x
; i
++) {
222 for (let j
= 0; j
< V
.size
.y
; j
++) {
224 this.board
[i
][j
] != V
.EMPTY
&&
225 this.getColor(i
, j
) == color
&&
226 this.getPiece(i
, j
) == V
.UNDEFINED
230 new PiPo({ x: i
, y: j
, c: color
, p: p
})
233 new PiPo({ x: i
, y: j
, c: color
, p: V
.UNDEFINED
})
235 start: { x: x
, y: y
},
245 getPotentialMovesFrom([x
, y
]) {
246 if (this.subTurn
== 0) {
247 if (x
< V
.size
.x
) return [];
248 return this.getReserveMoves([x
, y
]);
250 if (this.subTurn
== 1) {
251 // Both normal move (from defined piece) and definition allowed
252 if (x
>= V
.size
.x
) return this.getReserveMoves([x
, y
]);
253 if (this.getPiece(x
, y
) == V
.UNDEFINED
) return [];
255 // subTurn == 1 and we move any piece, or
256 // subTurn == 2 and we can only move the just-defined piece
257 if (this.subTurn
== 2) {
258 const L
= this.definitions
.length
; //at least 1
259 const df
= this.definitions
[L
-1];
260 if (x
!= df
.x
|| y
!= df
.y
) return [];
262 return super.getPotentialMovesFrom([x
, y
]);
265 getAllPotentialMoves() {
266 const color
= this.turn
;
267 if (this.movesCount
<= 1) {
268 // Just put the king on the board
269 const firstRank
= (color
== 'w' ? 7 : 0);
270 return [...Array(8)].map((x
, j
) => {
273 new PiPo({ x: firstRank
, y: j
, c: color
, p: V
.KING
})
276 new PiPo({ x: firstRank
, y: j
, c: color
, p: V
.UNDEFINED
})
278 start: { x: -1, y: -1 }
282 const getAllReserveMoves
= () => {
284 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
285 moves
= moves
.concat(
286 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
291 if (this.subTurn
== 0) return getAllReserveMoves();
292 let moves
= super.getAllPotentialMoves();
293 if (this.subTurn
== 1)
294 moves
= moves
.concat(getAllReserveMoves());
295 return this.filterValid(moves
);
299 if (this.movesCount
<= 1) return moves
;
300 const color
= this.turn
;
301 return moves
.filter(m
=> {
302 if (m
.vanish
.length
== 0) {
303 // subTurn == 0: need to check if a move exists at subTurn == 1
305 const res
= this.filterValid(this.getAllPotentialMoves()).length
> 0;
309 const start
= { x: m
.vanish
[0].x
, y: m
.vanish
[0].y
};
310 const end
= { x: m
.appear
[0].x
, y: m
.appear
[0].y
};
311 if (start
.x
== end
.x
&& start
.y
== end
.y
) {
312 // Unfinished turn: require careful check
315 if (this.subTurn
== 1)
316 // Can either play a move, or specialize a piece
317 res
= this.filterValid(this.getAllPotentialMoves()).length
> 0;
319 // subTurn == 2: can only play a specialized piece
320 res
= this.filterValid(
321 this.getPotentialMovesFrom([m
.end
.x
, m
.end
.y
])).length
> 0;
327 const res
= !this.underCheck(color
);
334 const atLeastOneReserveMove
= () => {
335 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
336 let moves
= this.filterValid(
337 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
339 if (moves
.length
> 0) return true;
343 if (this.subTurn
== 0) return atLeastOneReserveMove();
344 const canMoveSomething
= super.atLeastOneMove();
345 if (this.subTurn
== 2) return canMoveSomething
;
346 return (canMoveSomething
|| atLeastOneReserveMove());
350 if (super.underCheck(color
)) return true;
351 // Aux func for piece attack on king (no pawn)
352 const pieceAttackOn
= (p
, [x1
, y1
], [x2
, y2
]) => {
353 const shift
= [x2
- x1
, y2
- y1
];
354 const absShift
= shift
.map(Math
.abs
);
358 (absShift
[0] + absShift
[1] != 3 || shift
[0] == 0 || shift
[1] == 0)
360 (p
== V
.ROOK
&& shift
[0] != 0 && shift
[1] != 0) ||
361 (p
== V
.BISHOP
&& absShift
[0] != absShift
[1]) ||
364 shift
[0] != 0 && shift
[1] != 0 && absShift
[0] != absShift
[1]
369 // Step is compatible with piece:
371 shift
[0] / Math
.abs(shift
[0]) || 0,
372 shift
[1] / Math
.abs(shift
[1]) || 0
374 let [i
, j
] = [x1
+ step
[0], y1
+ step
[1]];
375 while (i
!= x2
|| j
!= y2
) {
376 if (!V
.OnBoard(i
, j
) || this.board
[i
][j
] != V
.EMPTY
) return false;
382 // Check potential specializations of undefined using reserve:
383 const oppCol
= V
.GetOppCol(color
);
384 for (let i
=0; i
<8; i
++) {
385 for (let j
=0; j
<8; j
++) {
387 this.board
[i
][j
] != V
.EMPTY
&&
388 this.getColor(i
, j
) == oppCol
&&
389 this.getPiece(i
, j
) == V
.UNDEFINED
391 for (let p
of V
.RESERVE_PIECES
) {
393 this.reserve
[oppCol
][p
] >= 1 &&
394 pieceAttackOn(p
, [i
, j
], this.kingPos
[color
])
406 if (this.movesCount
<= 2) return [];
407 return super.getCheckSquares();
411 move.turn
= [this.turn
, this.subTurn
]; //easier undo (TODO?)
412 const toNextPlayer
= () => {
413 V
.PlayOnBoard(this.board
, move);
414 this.turn
= V
.GetOppCol(this.turn
);
416 (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.UNDEFINED
? 0 : 1);
420 if (this.movesCount
<= 1) toNextPlayer();
421 else if (move.vanish
.length
== 0) {
422 // Removal (subTurn == 0 --> 1)
423 this.reserve
[this.turn
][move.start
.p
]--;
427 const start
= { x: move.vanish
[0].x
, y: move.vanish
[0].y
};
428 const end
= { x: move.appear
[0].x
, y: move.appear
[0].y
};
429 if (start
.x
== end
.x
&& start
.y
== end
.y
) {
430 // Specialisation (subTurn == 1 before 2)
431 this.reserve
[this.turn
][move.appear
[0].p
]--;
432 V
.PlayOnBoard(this.board
, move);
433 this.definitions
.push(move.end
);
437 // Normal move (subTurn 1 or 2: change turn)
438 this.epSquares
.push(this.getEpSquare(move));
445 const color
= V
.GetOppCol(this.turn
);
446 if (this.movesCount
<= 2) this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
448 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.UNDEFINED
)
449 this.captureUndefined
.push(move.end
);
450 else this.captureUndefined
.push(null);
451 if (move.appear
[0].p
== V
.KING
) super.postPlay(move);
453 // If now all my pieces are defined, back to undefined state,
454 // only if at least two different kind of pieces on board!
455 // Store current state in move (cannot infer it after!)
457 this.board
.every(b
=> {
458 return b
.every(cell
=> {
462 cell
[1] != V
.UNDEFINED
467 const piecesList
= [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
468 const oppCol
= this.turn
;
469 let definedPieces
= { w: {}, b: {} };
470 for (let i
=0; i
<8; i
++) {
471 for (let j
=0; j
<8; j
++) {
472 if (this.board
[i
][j
] != V
.EMPTY
) {
473 const p
= this.getPiece(i
, j
);
474 const c
= this.getColor(i
, j
);
475 if (piecesList
.includes(p
)) {
476 definedPieces
[c
][p
] =
477 (!definedPieces
[c
][p
] ? 1 : definedPieces
[c
][p
] + 1);
482 const my_pk
= Object
.keys(definedPieces
[color
]);
483 const opp_pk
= Object
.keys(definedPieces
[oppCol
]);
485 opp_pk
.length
>= 2 ||
487 // Only one opponent's piece is defined, but
488 // at least a different piece wait in reserve:
489 opp_pk
.length
== 1 &&
490 Object
.keys(this.reserve
[oppCol
]).some(k
=> {
491 return (k
!= opp_pk
[0] && this.reserve
[oppCol
][k
] >= 1);
495 if (my_pk
.length
>= 2 || oppRevert
) {
496 // NOTE: necessary HACK... because the move is played already.
497 V
.UndoOnBoard(this.board
, move);
498 move.position
= this.getBaseFen();
499 move.reserve
= JSON
.parse(JSON
.stringify(this.reserve
));
500 V
.PlayOnBoard(this.board
, move);
502 let cp
of [{ c: color
, pk: my_pk
}, { c: oppCol
, pk: opp_pk
}]
504 if (cp
.pk
.length
>= 2 || (cp
.c
== oppCol
&& oppRevert
)) {
506 this.reserve
[cp
.c
][p
] += definedPieces
[cp
.c
][p
];
507 for (let i
=0; i
<8; i
++) {
508 for (let j
=0; j
<8; j
++) {
510 this.board
[i
][j
] != V
.EMPTY
&&
511 this.getColor(i
, j
) == cp
.c
&&
512 piecesList
.includes(this.getPiece(i
, j
))
514 this.board
[i
][j
] = cp
.c
+ V
.UNDEFINED
;
527 const toPrevPlayer
= () => {
528 V
.UndoOnBoard(this.board
, move);
529 [this.turn
, this.subTurn
] = move.turn
;
533 if (this.movesCount
<= 2 && move.appear
[0].p
== V
.KING
) toPrevPlayer();
534 else if (move.vanish
.length
== 0) {
535 this.reserve
[this.turn
][move.start
.p
]++;
536 this.subTurn
= move.turn
[1];
539 const start
= { x: move.vanish
[0].x
, y: move.vanish
[0].y
};
540 const end
= { x: move.appear
[0].x
, y: move.appear
[0].y
};
541 if (start
.x
== end
.x
&& start
.y
== end
.y
) {
542 this.reserve
[this.turn
][move.appear
[0].p
]++;
543 V
.UndoOnBoard(this.board
, move);
544 this.definitions
.pop();
545 this.subTurn
= move.turn
[1];
548 this.epSquares
.pop();
555 const color
= this.turn
;
556 if (this.movesCount
<= 1) this.kingPos
[color
] = [-1, -1];
558 this.captureUndefined
.pop();
559 if (move.appear
[0].p
== V
.KING
) super.postUndo(move);
561 if (!!move.position
) {
562 this.board
= V
.GetBoard(move.position
);
563 this.reserve
= move.reserve
;
570 let initMoves
= this.getAllValidMoves();
571 if (initMoves
.length
== 0) return null;
572 // Loop until valid move is found (no un-specifiable piece...)
573 const color
= this.turn
;
575 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
578 // Just play random moves (for now at least. TODO?)
579 while (moves
.length
> 0) {
580 mv
= moves
[randInt(moves
.length
)];
583 if (this.turn
== color
) {
584 if (this.subTurn
== 1) moves
= this.getAllValidMoves();
587 moves
= this.filterValid(
588 this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]));
593 const thisIsTheEnd
= (this.turn
!= color
);
594 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
595 if (thisIsTheEnd
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
597 return null; //never reached
600 static get VALUES() {
601 return Object
.assign({ u: 0 }, ChessRules
.VALUES
);
604 // NOTE: evalPosition is wrong, but unused (random mover)
607 const end
= { x: move.end
.x
, y: move.end
.y
};
608 const endSquare
= V
.CoordsToSquare(end
);
609 if (move.appear
.length
== 0)
611 return move.start
.p
.toUpperCase() + endSquare
+ "X";
612 if (move.vanish
.length
== 0) return "K@" + endSquare
;
613 const start
= { x: move.vanish
[0].x
, y: move.vanish
[0].y
};
614 if (start
.x
== end
.x
&& start
.y
== end
.y
)
615 // Something is specialized
616 return move.appear
[0].p
.toUpperCase() + "@" + endSquare
;
618 return super.getNotation(move);