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) ||
49 // Initiate the game by choosing a square for the king:
53 this.movesCount
>= 2 ||
55 (c
== 'w' && square
[0] != 7) ||
56 (c
== 'b' && square
[0] != 0)
63 new PiPo({ x: square
[0], y: square
[1], c: c
, p: V
.KING
})
66 start: { x: -1, y: -1 },
70 // Do not check kings (TODO: something more subtle!)
71 static IsGoodPosition(position
) {
72 if (position
.length
== 0) return false;
73 const rows
= position
.split("/");
74 if (rows
.length
!= V
.size
.x
) return false;
75 for (let row
of rows
) {
77 for (let i
= 0; i
< row
.length
; i
++) {
78 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
80 const num
= parseInt(row
[i
], 10);
81 if (isNaN(num
) || num
<= 0) return false;
85 if (sumElts
!= V
.size
.y
) return false;
90 static IsGoodFen(fen
) {
91 if (!ChessRules
.IsGoodFen(fen
)) return false;
92 const fenParsed
= V
.ParseFen(fen
);
93 if (!fenParsed
.reserve
|| !fenParsed
.reserve
.match(/^[0-9]{8,8}$/))
94 if (!fenParsed
.capture
) return false;
98 static ParseFen(fen
) {
99 const fenParts
= fen
.split(" ");
100 return Object
.assign(
102 reserve: fenParts
[4],
105 ChessRules
.ParseFen(fen
)
110 let counts
= new Array(8);
111 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
112 counts
[i
] = this.reserve
["w"][V
.RESERVE_PIECES
[i
]];
113 counts
[4 + i
] = this.reserve
["b"][V
.RESERVE_PIECES
[i
]];
115 return counts
.join("");
119 const L
= this.captureUndefined
.length
;
120 const cu
= this.captureUndefined
[L
-1];
121 return (!!cu
? V
.CoordsToSquare(cu
) : "-");
126 super.getFen() + " " +
127 this.getReserveFen() + " " +
134 super.getFenForRepeat() + "_" +
135 this.getReserveFen() + "_" +
140 static GenRandInitFen() {
141 return "8/pppppppp/8/8/8/8/PPPPPPPP/8 w 0 - 22212221 -";
144 setOtherVariables(fen
) {
145 super.setOtherVariables(fen
);
147 V
.ParseFen(fen
).reserve
.split("").map(x
=> parseInt(x
, 10));
150 [V
.ROOK
]: reserve
[0],
151 [V
.KNIGHT
]: reserve
[1],
152 [V
.BISHOP
]: reserve
[2],
153 [V
.QUEEN
]: reserve
[3]
156 [V
.ROOK
]: reserve
[4],
157 [V
.KNIGHT
]: reserve
[5],
158 [V
.BISHOP
]: reserve
[6],
159 [V
.QUEEN
]: reserve
[7]
162 const cu
= V
.ParseFen(fen
).capture
;
163 this.captureUndefined
= [cu
== '-' ? null : V
.SquareToCoords(cu
)];
164 this.subTurn
= (cu
== "-" ? 1 : 0);
165 // Local stack of pieces' definitions
166 this.definitions
= [];
170 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
171 return this.board
[i
][j
].charAt(0);
175 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
176 return this.board
[i
][j
].charAt(1);
179 getReservePpath(index
, color
) {
180 return color
+ V
.RESERVE_PIECES
[index
];
183 static get RESERVE_PIECES() {
184 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
187 getReserveMoves([x
, y
]) {
188 const color
= this.turn
;
189 const p
= V
.RESERVE_PIECES
[y
];
190 if (this.reserve
[color
][p
] == 0) return [];
191 // 2 cases, subTurn == 0 => target this.captureUndefined only (one square)
192 if (this.subTurn
== 0) {
193 const L
= this.captureUndefined
.length
;
194 const cu
= this.captureUndefined
[L
-1];
196 // Nothing changes on the board, just mark start.p for reserve update
200 start: { x: x
, y: y
, p: p
},
201 end: { x: cu
.x
, y: cu
.y
}
205 // or, subTurn == 1 => target any undefined piece that we own.
207 for (let i
= 0; i
< V
.size
.x
; i
++) {
208 for (let j
= 0; j
< V
.size
.y
; j
++) {
210 this.board
[i
][j
] != V
.EMPTY
&&
211 this.getColor(i
, j
) == color
&&
212 this.getPiece(i
, j
) == V
.UNDEFINED
216 new PiPo({ x: i
, y: j
, c: color
, p: p
})
219 new PiPo({ x: i
, y: j
, c: color
, p: V
.UNDEFINED
})
221 start: { x: x
, y: y
},
231 getPotentialMovesFrom([x
, y
]) {
232 if (this.subTurn
== 0) {
233 if (x
< V
.size
.x
) return [];
234 return this.getReserveMoves([x
, y
]);
236 if (this.subTurn
== 1) {
237 // Both normal move (from defined piece) and definition allowed
238 if (x
>= V
.size
.x
) return this.getReserveMoves([x
, y
]);
239 if (this.getPiece(x
, y
) == V
.UNDEFINED
) return [];
241 // subTurn == 1 and we move any piece, or
242 // subTurn == 2 and we can only move the just-defined piece
243 if (this.subTurn
== 2) {
244 const L
= this.definitions
.length
; //at least 1
245 const df
= this.definitions
[L
-1];
246 if (x
!= df
.x
|| y
!= df
.y
) return [];
248 return super.getPotentialMovesFrom([x
, y
]);
251 getAllPotentialMoves() {
252 const color
= this.turn
;
253 if (this.movesCount
<= 1) {
254 // Just put the king on the board
256 const firstRank
= (color
== 'w' ? 7 : 0);
257 return [...Array(8)].map((x
, j
) => {
260 new PiPo({ x: firstRank
, y: j
, c: color
, p: V
.KING
})
263 start: { x: -1, y: -1 }
267 const getAllReserveMoves
= () => {
269 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
270 moves
= moves
.concat(
271 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
276 if (this.subTurn
== 0) return getAllReserveMoves();
277 let moves
= super.getAllPotentialMoves();
278 if (this.subTurn
== 1)
279 moves
= moves
.concat(getAllReserveMoves());
280 return this.filterValid(moves
);
284 const color
= this.turn
;
285 return moves
.filter(m
=> {
286 if (m
.vanish
.length
== 0) return true;
287 const start
= { x: m
.vanish
[0].x
, y: m
.vanish
[0].y
};
288 const end
= { x: m
.appear
[0].x
, y: m
.appear
[0].y
};
289 if (start
.x
== end
.x
&& start
.y
== end
.y
) {
290 // Unfinished turn: require careful check
293 if (this.subTurn
== 1)
294 // Can either play a move, or specialize a piece
295 res
= this.filterValid(this.getAllPotentialMoves()).length
> 0;
297 // subTurn == 2: can only play a specialized piece
298 res
= this.filterValid(
299 this.getPotentialMovesFrom([m
.end
.x
, m
.end
.y
])).length
> 0;
305 const res
= !this.underCheck(color
);
312 const atLeastOneReserveMove
= () => {
313 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
314 let moves
= this.filterValid(
315 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
317 if (moves
.length
> 0) return true;
321 if (this.subTurn
== 0) return true; //always one reserve for an undefined
322 if (!super.atLeastOneMove()) return atLeastOneReserveMove();
327 if (super.underCheck(color
)) return true;
328 // Aux func for piece attack on king (no pawn)
329 const pieceAttackOn
= (p
, [x1
, y1
], [x2
, y2
]) => {
330 const shift
= [x2
- x1
, y2
- y1
];
331 const absShift
= shift
.map(Math
.abs
);
335 (absShift
[0] + absShift
[1] != 3 || shift
[0] == 0 || shift
[1] == 0)
337 (p
== V
.ROOK
&& shift
[0] != 0 && shift
[1] != 0) ||
338 (p
== V
.BISHOP
&& absShift
[0] != absShift
[1]) ||
341 shift
[0] != 0 && shift
[1] != 0 && absShift
[0] != absShift
[1]
346 // Step is compatible with piece:
348 shift
[0] / Math
.abs(shift
[0]) || 0,
349 shift
[1] / Math
.abs(shift
[1]) || 0
351 let [i
, j
] = [x1
+ step
[0], y1
+ step
[1]];
352 while (i
!= x2
|| j
!= y2
) {
353 if (!V
.OnBoard(i
, j
) || this.board
[i
][j
] != V
.EMPTY
) return false;
359 // Check potential specializations of undefined using reserve:
360 const oppCol
= V
.GetOppCol(color
);
361 for (let i
=0; i
<8; i
++) {
362 for (let j
=0; j
<8; j
++) {
364 this.board
[i
][j
] != V
.EMPTY
&&
365 this.getColor(i
, j
) == oppCol
&&
366 this.getPiece(i
, j
) == V
.UNDEFINED
368 for (let p
of V
.RESERVE_PIECES
) {
370 this.reserve
[oppCol
][p
] >= 1 &&
371 pieceAttackOn(p
, [i
, j
], this.kingPos
[color
])
383 move.turn
= [this.turn
, this.subTurn
]; //easier undo (TODO?)
384 const toNextPlayer
= () => {
385 V
.PlayOnBoard(this.board
, move);
386 this.turn
= V
.GetOppCol(this.turn
);
388 (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.UNDEFINED
? 0 : 1);
392 if (move.vanish
.length
== 0) {
393 if (move.appear
.length
== 1) toNextPlayer();
395 // Removal (subTurn == 0 --> 1)
396 this.reserve
[this.turn
][move.start
.p
]--;
401 const start
= { x: move.vanish
[0].x
, y: move.vanish
[0].y
};
402 const end
= { x: move.appear
[0].x
, y: move.appear
[0].y
};
403 if (start
.x
== end
.x
&& start
.y
== end
.y
) {
404 // Specialisation (subTurn == 1 before 2)
405 this.reserve
[this.turn
][move.appear
[0].p
]--;
406 V
.PlayOnBoard(this.board
, move);
407 this.definitions
.push(move.end
);
411 // Normal move (subTurn 1 or 2: change turn)
412 this.epSquares
.push(this.getEpSquare(move));
418 const color
= V
.GetOppCol(this.turn
);
419 if (move.vanish
.length
== 0) {
420 this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
421 const firstRank
= (color
== 'w' ? 7 : 0);
422 for (let j
= 0; j
< 8; j
++) {
423 if (j
!= move.end
.y
) this.board
[firstRank
][j
] = color
+ V
.UNDEFINED
;
427 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.UNDEFINED
)
428 this.captureUndefined
.push(move.end
);
429 else this.captureUndefined
.push(null);
430 if (move.appear
[0].p
== V
.KING
) super.postPlay(move);
432 // If now all my pieces are defined, back to undefined state,
433 // only if at least two different kind of pieces on board!
434 // Store current state in move (cannot infer it after!)
436 this.board
.every(b
=> {
437 return b
.every(cell
=> {
441 cell
[1] != V
.UNDEFINED
446 const piecesList
= [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
447 const oppCol
= this.turn
;
448 let definedPieces
= { w: {}, b: {} };
449 for (let i
=0; i
<8; i
++) {
450 for (let j
=0; j
<8; j
++) {
451 if (this.board
[i
][j
] != V
.EMPTY
) {
452 const p
= this.getPiece(i
, j
);
453 const c
= this.getColor(i
, j
);
454 if (piecesList
.includes(p
)) {
455 definedPieces
[c
][p
] =
456 (!definedPieces
[c
][p
] ? 1 : definedPieces
[c
][p
] + 1);
461 const my_pk
= Object
.keys(definedPieces
[color
]);
462 const opp_pk
= Object
.keys(definedPieces
[oppCol
]);
464 opp_pk
.length
>= 2 ||
466 // Only one opponent's piece is defined, but
467 // at least a different piece wait in reserve:
468 opp_pk
.length
== 1 &&
469 Object
.keys(this.reserve
[oppCol
]).some(k
=> {
470 return (k
!= opp_pk
[0] && this.reserve
[oppCol
][k
] >= 1);
474 if (my_pk
.length
>= 2 || oppRevert
) {
475 // NOTE: necessary HACK... because the move is played already.
476 V
.UndoOnBoard(this.board
, move);
477 move.position
= this.getBaseFen();
478 move.reserve
= JSON
.parse(JSON
.stringify(this.reserve
));
479 V
.PlayOnBoard(this.board
, move);
481 let cp
of [{ c: color
, pk: my_pk
}, { c: oppCol
, pk: opp_pk
}]
483 if (cp
.pk
.length
>= 2 || (cp
.c
== oppCol
&& oppRevert
)) {
485 this.reserve
[cp
.c
][p
] += definedPieces
[cp
.c
][p
];
486 for (let i
=0; i
<8; i
++) {
487 for (let j
=0; j
<8; j
++) {
489 this.board
[i
][j
] != V
.EMPTY
&&
490 this.getColor(i
, j
) == cp
.c
&&
491 piecesList
.includes(this.getPiece(i
, j
))
493 this.board
[i
][j
] = cp
.c
+ V
.UNDEFINED
;
506 const toPrevPlayer
= () => {
507 V
.UndoOnBoard(this.board
, move);
508 [this.turn
, this.subTurn
] = move.turn
;
512 if (move.vanish
.length
== 0) {
513 if (move.appear
.length
== 1) toPrevPlayer();
515 this.reserve
[this.turn
][move.start
.p
]++;
516 this.subTurn
= move.turn
[1];
520 const start
= { x: move.vanish
[0].x
, y: move.vanish
[0].y
};
521 const end
= { x: move.appear
[0].x
, y: move.appear
[0].y
};
522 if (start
.x
== end
.x
&& start
.y
== end
.y
) {
523 this.reserve
[this.turn
][move.appear
[0].p
]++;
524 V
.UndoOnBoard(this.board
, move);
525 this.definitions
.pop();
526 this.subTurn
= move.turn
[1];
529 this.epSquares
.pop();
535 const color
= this.turn
;
536 if (move.vanish
.length
== 0) {
537 this.kingPos
[color
] = [-1, -1];
538 const firstRank
= (color
== 'w' ? 7 : 0);
539 for (let j
= 0; j
< 8; j
++) this.board
[firstRank
][j
] = "";
542 this.captureUndefined
.pop();
543 if (move.appear
[0].p
== V
.KING
) super.postUndo(move);
545 if (!!move.position
) {
546 this.board
= V
.GetBoard(move.position
);
547 this.reserve
= move.reserve
;
554 let initMoves
= this.getAllValidMoves();
555 if (initMoves
.length
== 0) return null;
556 // Loop until valid move is found (no un-specifiable piece...)
557 const color
= this.turn
;
559 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
562 // Just play random moves (for now at least. TODO?)
563 while (moves
.length
> 0) {
564 mv
= moves
[randInt(moves
.length
)];
567 if (this.turn
== color
) {
568 if (this.subTurn
== 1) moves
= this.getAllValidMoves();
571 moves
= this.filterValid(
572 this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]));
577 const thisIsTheEnd
= (this.turn
!= color
);
578 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
579 if (thisIsTheEnd
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
581 return null; //never reached
584 static get VALUES() {
585 return Object
.assign({ u: 0 }, ChessRules
.VALUES
);
588 // NOTE: evalPosition is wrong, but unused (random mover)
591 const end
= { x: move.end
.x
, y: move.end
.y
};
592 const endSquare
= V
.CoordsToSquare(end
);
593 if (move.appear
.length
== 0)
595 return move.start
.p
.toUpperCase() + endSquare
+ "X";
596 if (move.vanish
.length
== 0) return "K@" + endSquare
;
597 const start
= { x: move.vanish
[0].x
, y: move.vanish
[0].y
};
598 if (start
.x
== end
.x
&& start
.y
== end
.y
)
599 // Something is specialized
600 return move.appear
[0].p
.toUpperCase() + "@" + endSquare
;
602 return super.getNotation(move);