1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
3 import { ArrayFun
} from "@/utils/array";
5 export class PandemoniumRules
extends ChessRules
{
8 // If current side is under check: lost
9 return this.underCheck(this.turn
);
12 static get GILDING() {
16 static get SCEPTER() {
28 static get CARDINAL() {
36 static get MARSHAL() {
40 static get APRICOT() {
46 ChessRules
.PIECES
.concat([
47 V
.GILDING
, V
.SCEPTER
, V
.HORSE
, V
.DRAGON
,
48 V
.CARDINAL
, V
.WHOLE
, V
.MARSHAL
, V
.APRICOT
])
53 const prefix
= (ChessRules
.PIECES
.includes(b
[1]) ? "" : "Pandemonium/");
58 return { x: 10, y: 10};
62 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
63 return this.board
[i
][j
].charAt(0);
67 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
68 return this.board
[i
][j
].charAt(1);
71 setOtherVariables(fen
) {
72 super.setOtherVariables(fen
);
73 // Sub-turn is useful only at first move...
75 // Also init reserves (used by the interface to show landable pieces)
77 V
.ParseFen(fen
).reserve
.split("").map(x
=> parseInt(x
, 10));
82 [V
.KNIGHT
]: reserve
[2],
83 [V
.BISHOP
]: reserve
[3],
84 [V
.QUEEN
]: reserve
[4],
85 [V
.CARDINAL
]: reserve
[5],
86 [V
.MARSHAL
]: reserve
[6],
91 [V
.KNIGHT
]: reserve
[9],
92 [V
.BISHOP
]: reserve
[10],
93 [V
.QUEEN
]: reserve
[11],
94 [V
.CARDINAL
]: reserve
[12],
95 [V
.MARSHAL
]: reserve
[13]
100 static IsGoodEnpassant(enpassant
) {
101 if (enpassant
!= "-") {
102 const squares
= enpassant
.split(",");
103 if (squares
.length
> 2) return false;
104 for (let sq
of squares
) {
105 if (!sq
.match(/[a-j0-9]/)) return false;
111 static IsGoodFen(fen
) {
112 if (!ChessRules
.IsGoodFen(fen
)) return false;
113 const fenParsed
= V
.ParseFen(fen
);
115 if (!fenParsed
.reserve
|| !fenParsed
.reserve
.match(/^[0-9]{14,14}$/))
120 static ParseFen(fen
) {
121 const fenParts
= fen
.split(" ");
122 return Object
.assign(
123 ChessRules
.ParseFen(fen
),
124 { reserve: fenParts
[5] }
129 return super.getFen() + " " + this.getReserveFen();
133 return super.getFenForRepeat() + "_" + this.getReserveFen();
137 let counts
= new Array(14);
138 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
139 counts
[i
] = this.reserve
["w"][V
.RESERVE_PIECES
[i
]];
140 counts
[7 + i
] = this.reserve
["b"][V
.RESERVE_PIECES
[i
]];
142 return counts
.join("");
145 static GenRandInitFen(randomness
) {
146 if (randomness
== 0) {
148 "rnbqkmcbnr/pppppppppp/91/91/91/91/91/91/PPPPPPPPPP/RNBQKMCBNR " +
149 "w 0 ajaj - 00000000000000"
153 let pieces
= { w: new Array(10), b: new Array(10) };
155 for (let c
of ["w", "b"]) {
156 if (c
== 'b' && randomness
== 1) {
157 pieces
['b'] = pieces
['w'];
162 let positions
= ArrayFun
.range(10);
164 // Get random squares for bishops (different colors)
165 let randIndex
= 2 * randInt(5);
166 let bishop1Pos
= positions
[randIndex
];
167 let randIndex_tmp
= 2 * randInt(5) + 1;
168 let bishop2Pos
= positions
[randIndex_tmp
];
169 positions
.splice(Math
.max(randIndex
, randIndex_tmp
), 1);
170 positions
.splice(Math
.min(randIndex
, randIndex_tmp
), 1);
172 randIndex
= randInt(8);
173 let knight1Pos
= positions
[randIndex
];
174 positions
.splice(randIndex
, 1);
175 randIndex
= randInt(7);
176 let knight2Pos
= positions
[randIndex
];
177 positions
.splice(randIndex
, 1);
179 randIndex
= randInt(6);
180 let queenPos
= positions
[randIndex
];
181 positions
.splice(randIndex
, 1);
183 // Random squares for cardinal + marshal
184 randIndex
= randInt(5);
185 let cardinalPos
= positions
[randIndex
];
186 positions
.splice(randIndex
, 1);
187 randIndex
= randInt(4);
188 let marshalPos
= positions
[randIndex
];
189 positions
.splice(randIndex
, 1);
191 let rook1Pos
= positions
[0];
192 let kingPos
= positions
[1];
193 let rook2Pos
= positions
[2];
195 pieces
[c
][rook1Pos
] = "r";
196 pieces
[c
][knight1Pos
] = "n";
197 pieces
[c
][bishop1Pos
] = "b";
198 pieces
[c
][queenPos
] = "q";
199 pieces
[c
][kingPos
] = "k";
200 pieces
[c
][marshalPos
] = "m";
201 pieces
[c
][cardinalPos
] = "c";
202 pieces
[c
][bishop2Pos
] = "b";
203 pieces
[c
][knight2Pos
] = "n";
204 pieces
[c
][rook2Pos
] = "r";
205 flags
+= V
.CoordToColumn(rook1Pos
) + V
.CoordToColumn(rook2Pos
);
208 pieces
["b"].join("") +
209 "/pppppppppp/91/91/91/91/91/91/PPPPPPPPPP/" +
210 pieces
["w"].join("").toUpperCase() +
211 " w 0 " + flags
+ " - 00000000000000"
216 const L
= this.epSquares
.length
;
217 if (!this.epSquares
[L
- 1]) return "-"; //no en-passant
219 this.epSquares
[L
- 1].forEach(sq
=> {
220 res
+= V
.CoordsToSquare(sq
) + ",";
222 return res
.slice(0, -1); //remove last comma
225 getEpSquare(moveOrSquare
) {
226 if (!moveOrSquare
) return undefined;
227 if (typeof moveOrSquare
=== "string") {
228 const square
= moveOrSquare
;
229 if (square
== "-") return undefined;
231 square
.split(",").forEach(sq
=> {
232 res
.push(V
.SquareToCoords(sq
));
236 // Argument is a move:
237 const move = moveOrSquare
;
238 const [sx
, sy
, ex
] = [move.start
.x
, move.start
.y
, move.end
.x
];
239 if (this.getPiece(sx
, sy
) == V
.PAWN
&& Math
.abs(sx
- ex
) >= 2) {
240 const step
= (ex
- sx
) / Math
.abs(ex
- sx
);
245 if (sx
+ 2 * step
!= ex
) {
254 return undefined; //default
257 getReservePpath(index
, color
) {
258 const p
= V
.RESERVE_PIECES
[index
];
259 const prefix
= (ChessRules
.PIECES
.includes(p
) ? "" : "Pandemonium/");
260 return prefix
+ color
+ p
;;
263 // Ordering on reserve pieces
264 static get RESERVE_PIECES() {
266 [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.CARDINAL
, V
.MARSHAL
]
270 getReserveMoves([x
, y
]) {
271 const color
= this.turn
;
272 const oppCol
= V
.GetOppCol(color
);
273 const p
= V
.RESERVE_PIECES
[y
];
274 if (this.reserve
[color
][p
] == 0) return [];
275 const bounds
= (p
== V
.PAWN
? [1, V
.size
.x
- 1] : [0, V
.size
.x
]);
277 for (let i
= bounds
[0]; i
< bounds
[1]; i
++) {
278 for (let j
= 0; j
< V
.size
.y
; j
++) {
279 if (this.board
[i
][j
] == V
.EMPTY
) {
290 start: { x: x
, y: y
}, //a bit artificial...
294 // Do not drop on checkmate:
297 this.underCheck(oppCol
) && !this.atLeastOneMove("noReserve")
309 static get PromoteMap() {
319 getPotentialMovesFrom([x
, y
]) {
320 const c
= this.getColor(x
, y
);
321 const oppCol
= V
.GetOppCol(c
);
322 if (this.movesCount
<= 1) {
323 if (this.kingPos
[c
][0] == x
&& this.kingPos
[c
][1] == y
) {
324 // Pass (if setup is ok)
329 start: { x: this.kingPos
[c
][0], y: this.kingPos
[c
][1] },
330 end: { x: this.kingPos
[oppCol
][0], y: this.kingPos
[oppCol
][1] }
334 const firstRank
= (this.movesCount
== 0 ? 9 : 0);
335 if (x
!= firstRank
|| this.getPiece(x
, y
) != V
.KNIGHT
) return [];
336 // Swap with who? search for matching bishop:
339 for (let i
= 0; i
< 10; i
++) {
340 const elt
= this.board
[x
][i
][1];
341 if (elt
== 'n') knights
.push(i
);
342 else if (elt
== 'b') bishops
.push(i
);
344 const destFile
= (knights
[0] == y
? bishops
[0] : bishops
[1]);
375 start: { x: x
, y: y
},
376 end: { x: x
, y: destFile
}
380 // Normal move (after initial setup)
381 if (x
>= V
.size
.x
) return this.getReserveMoves([x
, y
]);
382 const p
= this.getPiece(x
, y
);
385 if (ChessRules
.PIECES
.includes(p
))
386 moves
= super.getPotentialMovesFrom(sq
);
387 if ([V
.GILDING
, V
.APRICOT
, V
.WHOLE
].includes(p
))
388 moves
= super.getPotentialQueenMoves(sq
);
391 moves
= this.getPotentialScepterMoves(sq
);
394 moves
= this.getPotentialHorseMoves(sq
);
397 moves
= this.getPotentialDragonMoves(sq
);
400 moves
= this.getPotentialCardinalMoves(sq
);
403 moves
= this.getPotentialMarshalMoves(sq
);
406 // Maybe apply promotions:
407 if (Object
.keys(V
.PromoteMap
).includes(p
)) {
408 const promoted
= V
.PromoteMap
[p
];
409 const lastRanks
= (c
== 'w' ? [0, 1] : [9, 8]);
412 if (lastRanks
.includes(m
.start
.x
) || lastRanks
.includes(m
.end
.x
)) {
413 let pMove
= JSON
.parse(JSON
.stringify(m
));
414 pMove
.appear
[0].p
= promoted
;
415 promotions
.push(pMove
);
418 Array
.prototype.push
.apply(moves
, promotions
);
423 addPawnMoves([x1
, y1
], [x2
, y2
], moves
) {
424 const color
= this.turn
;
425 const lastRanks
= (color
== "w" ? [0, 1] : [9, 8]);
426 if (!lastRanks
.includes(x2
)) {
427 moves
.push(this.getBasicMove([x1
, y1
], [x2
, y2
]));
430 let finalPieces
= [V
.GILDING
];
431 if (x2
== lastRanks
[1]) finalPieces
.push(V
.PAWN
);
432 for (let piece
of finalPieces
) {
433 const tr
= (piece
!= V
.PAWN
? { c: color
, p: piece
} : null);
434 moves
.push(this.getBasicMove([x1
, y1
], [x2
, y2
], tr
));
438 getPotentialPawnMoves([x
, y
]) {
439 const color
= this.turn
;
440 const shiftX
= (color
== 'w' ? -1 : 1);
442 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
) {
443 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
], moves
);
444 if ((color
== 'w' && x
>= V
.size
.x
- 3) || (color
== 'b' && x
<= 2)) {
445 if (this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
) {
446 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
449 (color
== 'w' && x
== V
.size
.x
- 2) ||
450 (color
== 'b' && x
== 1)
453 this.board
[x
+ 3 * shiftX
][y
] == V
.EMPTY
455 moves
.push(this.getBasicMove([x
, y
], [x
+ 3 * shiftX
, y
]));
460 for (let shiftY
of [-1, 1]) {
461 if (y
+ shiftY
>= 0 && y
+ shiftY
< V
.size
.y
) {
463 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
464 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
466 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
+ shiftY
], moves
);
470 Array
.prototype.push
.apply(
472 this.getEnpassantCaptures([x
, y
], shiftX
)
477 getPotentialMarshalMoves(sq
) {
478 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
479 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep")
483 getPotentialCardinalMoves(sq
) {
484 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
485 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep")
489 getPotentialScepterMoves(sq
) {
491 V
.steps
[V
.KNIGHT
].concat(V
.steps
[V
.BISHOP
]).concat(V
.steps
[V
.ROOK
]);
492 return this.getSlideNJumpMoves(sq
, steps
, "oneStep");
495 getPotentialHorseMoves(sq
) {
496 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
497 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
], "oneStep"));
500 getPotentialDragonMoves(sq
) {
501 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
502 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], "oneStep"));
505 getEnpassantCaptures([x
, y
], shiftX
) {
506 const Lep
= this.epSquares
.length
;
507 const epSquare
= this.epSquares
[Lep
- 1];
510 for (let epsq
of epSquare
) {
511 // TODO: some redundant checks
512 if (epsq
.x
== x
+ shiftX
&& Math
.abs(epsq
.y
- y
) == 1) {
513 let enpassantMove
= this.getBasicMove([x
, y
], [epsq
.x
, epsq
.y
]);
514 // WARNING: the captured pawn may be diagonally behind us,
515 // if it's a 3-squares jump and we take on 1st passing square
516 const px
= this.board
[x
][epsq
.y
] != V
.EMPTY
? x : x
- shiftX
;
517 enpassantMove
.vanish
.push({
521 c: this.getColor(px
, epsq
.y
)
523 moves
.push(enpassantMove
);
530 getPotentialKingMoves(sq
) {
531 // Initialize with normal moves
532 let moves
= this.getSlideNJumpMoves(
534 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
539 this.castleFlags
[c
][0] < V
.size
.y
||
540 this.castleFlags
[c
][1] < V
.size
.y
542 const finalSquares
= [
546 moves
= moves
.concat(super.getCastleMoves(sq
, finalSquares
));
551 isAttacked(sq
, color
) {
553 this.isAttackedByPawn(sq
, color
) ||
554 this.isAttackedByRook(sq
, color
) ||
555 this.isAttackedByKnight(sq
, color
) ||
556 this.isAttackedByBishop(sq
, color
) ||
557 this.isAttackedByKing(sq
, color
) ||
558 this.isAttackedByQueens(sq
, color
) ||
559 this.isAttackedByScepter(sq
, color
) ||
560 this.isAttackedByDragon(sq
, color
) ||
561 this.isAttackedByHorse(sq
, color
) ||
562 this.isAttackedByMarshal(sq
, color
) ||
563 this.isAttackedByCardinal(sq
, color
)
567 isAttackedByQueens([x
, y
], color
) {
568 // pieces: because queen = gilding = whole = apricot
569 const pieces
= [V
.QUEEN
, V
.GILDING
, V
.WHOLE
, V
.APRICOT
];
570 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
571 for (let step
of steps
) {
572 let rx
= x
+ step
[0],
574 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
) {
580 this.board
[rx
][ry
] != V
.EMPTY
&&
581 pieces
.includes(this.getPiece(rx
, ry
)) &&
582 this.getColor(rx
, ry
) == color
590 isAttackedByScepter(sq
, color
) {
592 V
.steps
[V
.KNIGHT
].concat(V
.steps
[V
.ROOK
]).concat(V
.steps
[V
.BISHOP
]);
594 super.isAttackedBySlideNJump(sq
, color
, V
.SCEPTER
, steps
, "oneStep")
598 isAttackedByHorse(sq
, color
) {
600 super.isAttackedBySlideNJump(sq
, color
, V
.HORSE
, V
.steps
[V
.BISHOP
]) ||
601 super.isAttackedBySlideNJump(
602 sq
, color
, V
.HORSE
, V
.steps
[V
.ROOK
], "oneStep")
606 isAttackedByDragon(sq
, color
) {
608 super.isAttackedBySlideNJump(sq
, color
, V
.DRAGON
, V
.steps
[V
.ROOK
]) ||
609 super.isAttackedBySlideNJump(
610 sq
, color
, V
.DRAGON
, V
.steps
[V
.BISHOP
], "oneStep")
614 isAttackedByMarshal(sq
, color
) {
616 super.isAttackedBySlideNJump(sq
, color
, V
.MARSHAL
, V
.steps
[V
.ROOK
]) ||
617 super.isAttackedBySlideNJump(
627 isAttackedByCardinal(sq
, color
) {
629 super.isAttackedBySlideNJump(sq
, color
, V
.CARDINAL
, V
.steps
[V
.BISHOP
]) ||
630 super.isAttackedBySlideNJump(
641 let moves
= super.getAllPotentialMoves();
642 if (this.movesCount
>= 2) {
643 const color
= this.turn
;
644 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
645 moves
= moves
.concat(
646 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
650 return this.filterValid(moves
);
653 atLeastOneMove(noReserve
) {
654 if (!super.atLeastOneMove()) {
656 // Search one reserve move
657 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
658 let moves
= this.filterValid(
659 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
661 if (moves
.length
> 0) return true;
669 // Reverse 'PromoteMap'
670 static get P_CORRESPONDANCES() {
681 static MayDecode(piece
) {
682 if (Object
.keys(V
.P_CORRESPONDANCES
).includes(piece
))
683 return V
.P_CORRESPONDANCES
[piece
];
688 move.subTurn
= this.subTurn
; //much easier
689 if (this.movesCount
>= 2 || this.subTurn
== 2 || move.vanish
.length
== 0) {
690 this.turn
= V
.GetOppCol(this.turn
);
694 else this.subTurn
= 2;
695 move.flags
= JSON
.stringify(this.aggregateFlags());
696 this.epSquares
.push(this.getEpSquare(move));
697 V
.PlayOnBoard(this.board
, move);
702 if (move.vanish
.length
== 0 && move.appear
.length
== 0) return;
703 super.postPlay(move);
704 const color
= move.appear
[0].c
;
705 if (move.vanish
.length
== 0)
706 // Drop unpromoted piece:
707 this.reserve
[color
][move.appear
[0].p
]--;
708 else if (move.vanish
.length
== 2 && move.appear
.length
== 1)
709 // May capture a promoted piece:
710 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]++;
714 this.epSquares
.pop();
715 this.disaggregateFlags(JSON
.parse(move.flags
));
716 V
.UndoOnBoard(this.board
, move);
717 if (this.movesCount
>= 2 || this.subTurn
== 1 || move.vanish
.length
== 0) {
718 this.turn
= V
.GetOppCol(this.turn
);
721 this.subTurn
= move.subTurn
;
726 if (move.vanish
.length
== 0 && move.appear
.length
== 0) return;
727 super.postUndo(move);
728 const color
= move.appear
[0].c
;
729 if (move.vanish
.length
== 0)
730 this.reserve
[color
][move.appear
[0].p
]++;
731 else if (move.vanish
.length
== 2 && move.appear
.length
== 1)
732 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]--;
735 static get VALUES() {
736 return Object
.assign(
740 n: 2.5, //knight is weaker
753 static get SEARCH_DEPTH() {
758 if (this.movesCount
<= 1) {
759 // Special case: swap and pass at random
760 const moves1
= this.getAllValidMoves();
761 const m1
= moves1
[randInt(moves1
.length
)];
763 if (m1
.vanish
.length
== 0) {
767 const moves2
= this.getAllValidMoves();
768 const m2
= moves2
[randInt(moves2
.length
)];
772 return super.getComputerMove();
776 let evaluation
= super.evalPosition();
778 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
779 const p
= V
.RESERVE_PIECES
[i
];
780 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
781 evaluation
-= this.reserve
["b"][p
] * V
.VALUES
[p
];
787 if (move.vanish
.length
== 0) {
788 if (move.appear
.length
== 0) return "pass";
790 (move.appear
[0].p
== V
.PAWN
? "" : move.appear
[0].p
.toUpperCase());
791 return pieceName
+ "@" + V
.CoordsToSquare(move.end
);
793 if (move.appear
.length
== 2) {
794 if (move.appear
[0].p
!= V
.KING
)
795 return V
.CoordsToSquare(move.start
) + "S" + V
.CoordsToSquare(move.end
);
796 return (move.end
.y
< move.start
.y
? "0-0" : "0-0-0");
798 let notation
= super.getNotation(move);
799 if (move.vanish
[0].p
!= V
.PAWN
&& move.appear
[0].p
!= move.vanish
[0].p
)
800 // Add promotion indication:
801 notation
+= "=" + move.appear
[0].p
.toUpperCase();