1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
3 import { ArrayFun
} from "@/utils/array";
5 export class Pandemonium2Rules
extends ChessRules
{
7 static get PawnSpecs() {
11 { promotions: [V
.GILDING
] }
16 // If current side is under check: lost
17 return this.underCheck(this.turn
);
20 static get GILDING() {
24 static get SCEPTER() {
36 static get CARDINAL() {
44 static get MARSHAL() {
48 static get APRICOT() {
54 ChessRules
.PIECES
.concat([
55 V
.GILDING
, V
.SCEPTER
, V
.HORSE
, V
.DRAGON
,
56 V
.CARDINAL
, V
.WHOLE
, V
.MARSHAL
, V
.APRICOT
])
61 const prefix
= (ChessRules
.PIECES
.includes(b
[1]) ? "" : "Pandemonium/");
66 return { x: 8, y: 10};
70 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
71 return this.board
[i
][j
].charAt(0);
75 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
76 return this.board
[i
][j
].charAt(1);
79 setOtherVariables(fen
) {
80 super.setOtherVariables(fen
);
81 // Sub-turn is useful only at first move...
83 // Also init reserves (used by the interface to show landable pieces)
85 V
.ParseFen(fen
).reserve
.split("").map(x
=> parseInt(x
, 10));
90 [V
.KNIGHT
]: reserve
[2],
91 [V
.BISHOP
]: reserve
[3],
92 [V
.QUEEN
]: reserve
[4],
93 [V
.CARDINAL
]: reserve
[5],
94 [V
.MARSHAL
]: reserve
[6],
99 [V
.KNIGHT
]: reserve
[9],
100 [V
.BISHOP
]: reserve
[10],
101 [V
.QUEEN
]: reserve
[11],
102 [V
.CARDINAL
]: reserve
[12],
103 [V
.MARSHAL
]: reserve
[13]
108 static IsGoodFen(fen
) {
109 if (!ChessRules
.IsGoodFen(fen
)) return false;
110 const fenParsed
= V
.ParseFen(fen
);
112 if (!fenParsed
.reserve
|| !fenParsed
.reserve
.match(/^[0-9]{14,14}$/))
117 static ParseFen(fen
) {
118 const fenParts
= fen
.split(" ");
119 return Object
.assign(
120 ChessRules
.ParseFen(fen
),
121 { reserve: fenParts
[5] }
126 return super.getFen() + " " + this.getReserveFen();
130 return super.getFenForRepeat() + "_" + this.getReserveFen();
134 let counts
= new Array(14);
135 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
136 counts
[i
] = this.reserve
["w"][V
.RESERVE_PIECES
[i
]];
137 counts
[7 + i
] = this.reserve
["b"][V
.RESERVE_PIECES
[i
]];
139 return counts
.join("");
142 static GenRandInitFen(options
) {
143 if (options
.randomness
== 0) {
145 "rnbqkmcbnr/pppppppppp/91/91/91/91/PPPPPPPPPP/RNBQKMCBNR " +
146 "w 0 ajaj - 00000000000000"
150 let pieces
= { w: new Array(10), b: new Array(10) };
152 for (let c
of ["w", "b"]) {
153 if (c
== 'b' && options
.randomness
== 1) {
154 pieces
['b'] = pieces
['w'];
159 let positions
= ArrayFun
.range(10);
161 // Get random squares for bishops (different colors)
162 let randIndex
= 2 * randInt(5);
163 let bishop1Pos
= positions
[randIndex
];
164 let randIndex_tmp
= 2 * randInt(5) + 1;
165 let bishop2Pos
= positions
[randIndex_tmp
];
166 positions
.splice(Math
.max(randIndex
, randIndex_tmp
), 1);
167 positions
.splice(Math
.min(randIndex
, randIndex_tmp
), 1);
169 randIndex
= randInt(8);
170 let knight1Pos
= positions
[randIndex
];
171 positions
.splice(randIndex
, 1);
172 randIndex
= randInt(7);
173 let knight2Pos
= positions
[randIndex
];
174 positions
.splice(randIndex
, 1);
176 randIndex
= randInt(6);
177 let queenPos
= positions
[randIndex
];
178 positions
.splice(randIndex
, 1);
180 // Random squares for cardinal + marshal
181 randIndex
= randInt(5);
182 let cardinalPos
= positions
[randIndex
];
183 positions
.splice(randIndex
, 1);
184 randIndex
= randInt(4);
185 let marshalPos
= positions
[randIndex
];
186 positions
.splice(randIndex
, 1);
188 let rook1Pos
= positions
[0];
189 let kingPos
= positions
[1];
190 let rook2Pos
= positions
[2];
192 pieces
[c
][rook1Pos
] = "r";
193 pieces
[c
][knight1Pos
] = "n";
194 pieces
[c
][bishop1Pos
] = "b";
195 pieces
[c
][queenPos
] = "q";
196 pieces
[c
][kingPos
] = "k";
197 pieces
[c
][marshalPos
] = "m";
198 pieces
[c
][cardinalPos
] = "c";
199 pieces
[c
][bishop2Pos
] = "b";
200 pieces
[c
][knight2Pos
] = "n";
201 pieces
[c
][rook2Pos
] = "r";
202 flags
+= V
.CoordToColumn(rook1Pos
) + V
.CoordToColumn(rook2Pos
);
205 pieces
["b"].join("") +
206 "/pppppppppp/91/91/91/91/91/91/PPPPPPPPPP/" +
207 pieces
["w"].join("").toUpperCase() +
208 " w 0 " + flags
+ " - 00000000000000"
212 getReservePpath(index
, color
) {
213 const p
= V
.RESERVE_PIECES
[index
];
214 const prefix
= (ChessRules
.PIECES
.includes(p
) ? "" : "Pandemonium/");
215 return prefix
+ color
+ p
;;
218 // Ordering on reserve pieces
219 static get RESERVE_PIECES() {
221 [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.CARDINAL
, V
.MARSHAL
]
225 getReserveMoves([x
, y
]) {
226 const color
= this.turn
;
227 const oppCol
= V
.GetOppCol(color
);
228 const p
= V
.RESERVE_PIECES
[y
];
229 if (this.reserve
[color
][p
] == 0) return [];
230 const bounds
= (p
== V
.PAWN
? [1, V
.size
.x
- 1] : [0, V
.size
.x
]);
232 for (let i
= bounds
[0]; i
< bounds
[1]; i
++) {
233 for (let j
= 0; j
< V
.size
.y
; j
++) {
234 if (this.board
[i
][j
] == V
.EMPTY
) {
245 start: { x: x
, y: y
}, //a bit artificial...
249 // Do not drop on checkmate:
252 this.underCheck(oppCol
) && !this.atLeastOneMove("noReserve")
264 static get PromoteMap() {
274 applyPromotions(moves
, promoted
) {
275 const lastRank
= (this.turn
== 'w' ? 0 : V
.size
.x
- 1);
278 if ([m
.start
.x
, m
.end
.x
].includes(lastRank
)) {
279 let pMove
= JSON
.parse(JSON
.stringify(m
));
280 pMove
.appear
[0].p
= promoted
;
281 promotions
.push(pMove
);
284 Array
.prototype.push
.apply(moves
, promotions
);
287 getPotentialMovesFrom([x
, y
]) {
288 const c
= this.getColor(x
, y
);
289 const oppCol
= V
.GetOppCol(c
);
290 if (this.movesCount
<= 1) {
291 if (this.kingPos
[c
][0] == x
&& this.kingPos
[c
][1] == y
) {
292 // Pass (if setup is ok)
297 start: { x: this.kingPos
[c
][0], y: this.kingPos
[c
][1] },
298 end: { x: this.kingPos
[oppCol
][0], y: this.kingPos
[oppCol
][1] }
302 const firstRank
= (this.movesCount
== 0 ? V
.size
.x
- 1 : 0);
303 if (x
!= firstRank
|| this.getPiece(x
, y
) != V
.KNIGHT
) return [];
304 // Swap with who? search for matching bishop:
307 for (let i
= 0; i
< V
.size
.y
; i
++) {
308 const elt
= this.board
[x
][i
][1];
309 if (elt
== 'n') knights
.push(i
);
310 else if (elt
== 'b') bishops
.push(i
);
312 const destFile
= (knights
[0] == y
? bishops
[0] : bishops
[1]);
343 start: { x: x
, y: y
},
344 end: { x: x
, y: destFile
}
348 // Normal move (after initial setup)
349 if (x
>= V
.size
.x
) return this.getReserveMoves([x
, y
]);
350 const p
= this.getPiece(x
, y
);
353 if (ChessRules
.PIECES
.includes(p
))
354 moves
= super.getPotentialMovesFrom(sq
);
355 if ([V
.GILDING
, V
.APRICOT
, V
.WHOLE
].includes(p
))
356 moves
= super.getPotentialQueenMoves(sq
);
359 moves
= this.getPotentialScepterMoves(sq
);
362 moves
= this.getPotentialHorseMoves(sq
);
365 moves
= this.getPotentialDragonMoves(sq
);
368 moves
= this.getPotentialCardinalMoves(sq
);
371 moves
= this.getPotentialMarshalMoves(sq
);
374 // Maybe apply promotions:
375 if (Object
.keys(V
.PromoteMap
).includes(p
))
376 this.applyPromotions(moves
, V
.PromoteMap
[p
]);
380 getPotentialMarshalMoves(sq
) {
381 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
382 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], 1)
386 getPotentialCardinalMoves(sq
) {
387 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
388 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], 1)
392 getPotentialScepterMoves(sq
) {
394 V
.steps
[V
.KNIGHT
].concat(V
.steps
[V
.BISHOP
]).concat(V
.steps
[V
.ROOK
]);
395 return this.getSlideNJumpMoves(sq
, steps
, 1);
398 getPotentialHorseMoves(sq
) {
399 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
400 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
], 1));
403 getPotentialDragonMoves(sq
) {
404 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
405 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], 1));
408 getPotentialKingMoves(sq
) {
409 // Initialize with normal moves
410 let moves
= this.getSlideNJumpMoves(
411 sq
, V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), 1);
414 this.castleFlags
[c
][0] < V
.size
.y
||
415 this.castleFlags
[c
][1] < V
.size
.y
417 const finalSquares
= [
421 moves
= moves
.concat(super.getCastleMoves(sq
, finalSquares
));
426 isAttacked(sq
, color
) {
428 this.isAttackedByPawn(sq
, color
) ||
429 this.isAttackedByRook(sq
, color
) ||
430 this.isAttackedByKnight(sq
, color
) ||
431 this.isAttackedByBishop(sq
, color
) ||
432 this.isAttackedByKing(sq
, color
) ||
433 this.isAttackedByQueens(sq
, color
) ||
434 this.isAttackedByScepter(sq
, color
) ||
435 this.isAttackedByDragon(sq
, color
) ||
436 this.isAttackedByHorse(sq
, color
) ||
437 this.isAttackedByMarshal(sq
, color
) ||
438 this.isAttackedByCardinal(sq
, color
)
442 isAttackedByQueens([x
, y
], color
) {
443 // pieces: because queen = gilding = whole = apricot
444 const pieces
= [V
.QUEEN
, V
.GILDING
, V
.WHOLE
, V
.APRICOT
];
445 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
446 for (let step
of steps
) {
447 let rx
= x
+ step
[0],
449 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
) {
455 this.board
[rx
][ry
] != V
.EMPTY
&&
456 pieces
.includes(this.getPiece(rx
, ry
)) &&
457 this.getColor(rx
, ry
) == color
465 isAttackedByScepter(sq
, color
) {
467 V
.steps
[V
.KNIGHT
].concat(V
.steps
[V
.ROOK
]).concat(V
.steps
[V
.BISHOP
]);
469 super.isAttackedBySlideNJump(sq
, color
, V
.SCEPTER
, steps
, 1)
473 isAttackedByHorse(sq
, color
) {
475 super.isAttackedBySlideNJump(sq
, color
, V
.HORSE
, V
.steps
[V
.BISHOP
]) ||
476 super.isAttackedBySlideNJump(
477 sq
, color
, V
.HORSE
, V
.steps
[V
.ROOK
], 1)
481 isAttackedByDragon(sq
, color
) {
483 super.isAttackedBySlideNJump(sq
, color
, V
.DRAGON
, V
.steps
[V
.ROOK
]) ||
484 super.isAttackedBySlideNJump(
485 sq
, color
, V
.DRAGON
, V
.steps
[V
.BISHOP
], 1)
489 isAttackedByMarshal(sq
, color
) {
491 super.isAttackedBySlideNJump(sq
, color
, V
.MARSHAL
, V
.steps
[V
.ROOK
]) ||
492 super.isAttackedBySlideNJump(
493 sq
, color
, V
.MARSHAL
, V
.steps
[V
.KNIGHT
], 1)
497 isAttackedByCardinal(sq
, color
) {
499 super.isAttackedBySlideNJump(sq
, color
, V
.CARDINAL
, V
.steps
[V
.BISHOP
]) ||
500 super.isAttackedBySlideNJump(
501 sq
, color
, V
.CARDINAL
, V
.steps
[V
.KNIGHT
], 1)
506 let moves
= super.getAllPotentialMoves();
507 if (this.movesCount
>= 2) {
508 const color
= this.turn
;
509 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
510 moves
= moves
.concat(
511 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
515 return this.filterValid(moves
);
518 atLeastOneMove(noReserve
) {
519 if (!super.atLeastOneMove()) {
521 // Search one reserve move
522 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
523 let moves
= this.filterValid(
524 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
526 if (moves
.length
> 0) return true;
534 // Reverse 'PromoteMap'
535 static get P_CORRESPONDANCES() {
546 static MayDecode(piece
) {
547 if (Object
.keys(V
.P_CORRESPONDANCES
).includes(piece
))
548 return V
.P_CORRESPONDANCES
[piece
];
553 move.subTurn
= this.subTurn
; //much easier
554 if (this.movesCount
>= 2 || this.subTurn
== 2 || move.vanish
.length
== 0) {
555 this.turn
= V
.GetOppCol(this.turn
);
559 else this.subTurn
= 2;
560 move.flags
= JSON
.stringify(this.aggregateFlags());
561 this.epSquares
.push(this.getEpSquare(move));
562 V
.PlayOnBoard(this.board
, move);
567 if (move.vanish
.length
== 0 && move.appear
.length
== 0) return;
568 super.postPlay(move);
569 const color
= move.appear
[0].c
;
570 if (move.vanish
.length
== 0)
571 // Drop unpromoted piece:
572 this.reserve
[color
][move.appear
[0].p
]--;
573 else if (move.vanish
.length
== 2 && move.appear
.length
== 1)
574 // May capture a promoted piece:
575 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]++;
579 this.epSquares
.pop();
580 this.disaggregateFlags(JSON
.parse(move.flags
));
581 V
.UndoOnBoard(this.board
, move);
582 if (this.movesCount
>= 2 || this.subTurn
== 1 || move.vanish
.length
== 0) {
583 this.turn
= V
.GetOppCol(this.turn
);
586 this.subTurn
= move.subTurn
;
591 if (move.vanish
.length
== 0 && move.appear
.length
== 0) return;
592 super.postUndo(move);
593 const color
= move.appear
[0].c
;
594 if (move.vanish
.length
== 0)
595 this.reserve
[color
][move.appear
[0].p
]++;
596 else if (move.vanish
.length
== 2 && move.appear
.length
== 1)
597 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]--;
600 static get VALUES() {
601 return Object
.assign(
605 n: 2.5, //knight is weaker
618 static get SEARCH_DEPTH() {
623 if (this.movesCount
<= 1) {
624 // Special case: swap and pass at random
625 const moves1
= this.getAllValidMoves();
626 const m1
= moves1
[randInt(moves1
.length
)];
628 if (m1
.vanish
.length
== 0) {
632 const moves2
= this.getAllValidMoves();
633 const m2
= moves2
[randInt(moves2
.length
)];
637 return super.getComputerMove();
641 let evaluation
= super.evalPosition();
643 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
644 const p
= V
.RESERVE_PIECES
[i
];
645 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
646 evaluation
-= this.reserve
["b"][p
] * V
.VALUES
[p
];
652 if (move.vanish
.length
== 0) {
653 if (move.appear
.length
== 0) return "pass";
655 (move.appear
[0].p
== V
.PAWN
? "" : move.appear
[0].p
.toUpperCase());
656 return pieceName
+ "@" + V
.CoordsToSquare(move.end
);
658 if (move.appear
.length
== 2) {
659 if (move.appear
[0].p
!= V
.KING
)
660 return V
.CoordsToSquare(move.start
) + "S" + V
.CoordsToSquare(move.end
);
661 return (move.end
.y
< move.start
.y
? "0-0" : "0-0-0");
663 let notation
= super.getNotation(move);
664 if (move.vanish
[0].p
!= V
.PAWN
&& move.appear
[0].p
!= move.vanish
[0].p
)
665 // Add promotion indication:
666 notation
+= "=" + move.appear
[0].p
.toUpperCase();