1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
} from "@/utils/alea";
5 // TODO: issue with undo of specialisation to cover check, subTurn decremented to 0
7 export class BarioRules
extends ChessRules
{
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() {
15 // Undetermined piece form:
16 static get UNDEFINED() {
21 return ChessRules
.PIECES
.concat(V
.UNDEFINED
);
25 if (b
[1] == V
.UNDEFINED
) return "Bario/" + b
;
29 canIplay(side
, [x
, y
]) {
30 if (this.movesCount
>= 2) return super.canIplay(side
, [x
, y
]);
34 (side
== 'w' && x
== 7) ||
35 (side
== 'b' && x
== 0)
40 hoverHighlight(x
, y
) {
43 this.movesCount
<= 1 &&
45 (c
== 'w' && x
== 7) ||
51 // Initiate the game by choosing a square for the king:
55 this.movesCount
>= 2 ||
57 (c
== 'w' && square
[0] != 7) ||
58 (c
== 'b' && square
[0] != 0)
65 new PiPo({ x: square
[0], y: square
[1], c: c
, p: V
.KING
})
68 start: { x: -1, y: -1 },
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
) {
79 for (let i
= 0; i
< row
.length
; i
++) {
80 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
82 const num
= parseInt(row
[i
], 10);
83 if (isNaN(num
) || num
<= 0) return false;
87 if (sumElts
!= V
.size
.y
) return false;
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;
100 static ParseFen(fen
) {
101 const fenParts
= fen
.split(" ");
102 return Object
.assign(
104 reserve: fenParts
[4],
107 ChessRules
.ParseFen(fen
)
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
]];
117 return counts
.join("");
121 const L
= this.captureUndefined
.length
;
122 const cu
= this.captureUndefined
[L
-1];
123 return (!!cu
? V
.CoordsToSquare(cu
) : "-");
128 super.getFen() + " " +
129 this.getReserveFen() + " " +
136 super.getFenForRepeat() + "_" +
137 this.getReserveFen() + "_" +
142 static GenRandInitFen() {
143 return "8/pppppppp/8/8/8/8/PPPPPPPP/8 w 0 - 22212221 -";
146 setOtherVariables(fen
) {
147 super.setOtherVariables(fen
);
149 V
.ParseFen(fen
).reserve
.split("").map(x
=> parseInt(x
, 10));
152 [V
.ROOK
]: reserve
[0],
153 [V
.KNIGHT
]: reserve
[1],
154 [V
.BISHOP
]: reserve
[2],
155 [V
.QUEEN
]: reserve
[3]
158 [V
.ROOK
]: reserve
[4],
159 [V
.KNIGHT
]: reserve
[5],
160 [V
.BISHOP
]: reserve
[6],
161 [V
.QUEEN
]: reserve
[7]
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
= [];
172 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
173 return this.board
[i
][j
].charAt(0);
177 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
178 return this.board
[i
][j
].charAt(1);
181 getReservePpath(index
, color
) {
182 return color
+ V
.RESERVE_PIECES
[index
];
185 static get RESERVE_PIECES() {
186 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
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];
200 new PiPo({ x: cu
.x
, y: cu
.y
, c: color
, p: p
})
203 new PiPo({ x: cu
.x
, y: cu
.y
, c: color
, p: V
.UNDEFINED
})
205 start: { x: x
, y: y
}
209 // or, subTurn == 1 => target any undefined piece that we own.
211 for (let i
= 0; i
< V
.size
.x
; i
++) {
212 for (let j
= 0; j
< V
.size
.y
; j
++) {
214 this.board
[i
][j
] != V
.EMPTY
&&
215 this.getColor(i
, j
) == color
&&
216 this.getPiece(i
, j
) == V
.UNDEFINED
220 new PiPo({ x: i
, y: j
, c: color
, p: p
})
223 new PiPo({ x: i
, y: j
, c: color
, p: V
.UNDEFINED
})
225 start: { x: x
, y: y
},
235 getPotentialMovesFrom([x
, y
]) {
236 if (this.subTurn
== 0) {
237 if (x
< V
.size
.x
) return [];
238 return this.getReserveMoves([x
, y
]);
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 [];
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 [];
252 return super.getPotentialMovesFrom([x
, y
]);
256 const getAllReserveMoves
= () => {
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
])
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
);
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
281 const res
= !this.underCheck(color
);
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
])
293 if (moves
.length
> 0) return true;
297 if (this.subTurn
== 0) return true; //always one reserve for an undefined
298 if (!super.atLeastOneMove()) return atLeastOneReserveMove();
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
);
311 (absShift
[0] + absShift
[1] != 3 || shift
[0] == 0 || shift
[1] == 0)
313 (p
== V
.ROOK
&& shift
[0] != 0 && shift
[1] != 0) ||
314 (p
== V
.BISHOP
&& absShift
[0] != absShift
[1]) ||
317 shift
[0] != 0 && shift
[1] != 0 && absShift
[0] != absShift
[1]
322 // Step is compatible with piece:
324 shift
[0] / Math
.abs(shift
[0]) || 0,
325 shift
[1] / Math
.abs(shift
[1]) || 0
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;
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
++) {
340 this.board
[i
][j
] != V
.EMPTY
&&
341 this.getColor(i
, j
) == oppCol
&&
342 this.getPiece(i
, j
) == V
.UNDEFINED
344 for (let p
of V
.RESERVE_PIECES
) {
346 this.reserve
[oppCol
][p
] >= 1 &&
347 pieceAttackOn(p
, [i
, j
], this.kingPos
[color
])
359 const toNextPlayer
= () => {
360 V
.PlayOnBoard(this.board
, move);
361 this.turn
= V
.GetOppCol(this.turn
);
363 (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.UNDEFINED
? 0 : 1);
367 if (move.vanish
.length
== 0) {
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
);
385 // Normal move (subTurn 1 or 2: change turn)
386 this.epSquares
.push(this.getEpSquare(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
;
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);
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!)
409 this.board
.every(b
=> {
410 return b
.every(cell
=> {
414 cell
[1] != V
.UNDEFINED
419 const piecesList
= [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
421 for (let i
=0; i
<8; i
++) {
422 for (let j
=0; j
<8; j
++) {
424 this.board
[i
][j
] != V
.EMPTY
&&
425 this.getColor(i
, j
) == color
427 const p
= this.getPiece(i
, j
);
428 if (piecesList
.includes(p
))
429 myPieces
[p
] = (!myPieces
[p
] ? 1 : myPieces
[p
] + 1);
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
++) {
440 this.board
[i
][j
] != V
.EMPTY
&&
441 this.getColor(i
, j
) == color
&&
442 piecesList
.includes(this.getPiece(i
, j
))
444 this.board
[i
][j
] = color
+ V
.UNDEFINED
;
455 const toPrevPlayer
= () => {
456 V
.UndoOnBoard(this.board
, move);
457 this.turn
= V
.GetOppCol(this.turn
);
461 if (move.vanish
.length
== 0) {
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();
476 this.epSquares
.pop();
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
] = "";
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);
493 if (!!move.position
) {
494 this.board
= V
.GetBoard(move.position
);
495 this.reserve
[color
] = {
507 const color
= this.turn
;
508 // Just play at random for now...
510 while (this.turn
== color
) {
511 const moves
= this.getAllValidMoves();
512 const choice
= moves
[randInt(moves
.length
)];
513 mvArray
.push(choice
);
516 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
517 return (mvArray
.length
== 1? mvArray
[0] : mvArray
);
520 static get VALUES() {
521 return Object
.assign({ u: 0 }, ChessRules
.VALUES
);
524 // NOTE: evalPosition is wrong, but unused (random mover)
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
)
536 return symbol
+ "@" + endSquare
;
538 return symbol
+ endSquare
+ "X";
541 return super.getNotation(move);