1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class MusketeerRules
extends ChessRules
{
6 // Extra pieces get strange letters because many taken by combinations below
13 static get UNICORN() {
16 static get ELEPHANT() {
22 static get FORTRESS() {
29 static get RESERVE_PIECES() {
31 [V
.LEOPARD
, V
.CANNON
, V
.UNICORN
, V
.ELEPHANT
,
32 V
.HAWK
, V
.FORTRESS
, V
.SPIDER
]
37 return ChessRules
.PIECES
.concat(V
.RESERVE_PIECES
);
40 // Decode if normal piece, or + piece1 or piece2
42 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
43 const piece
= this.board
[i
][j
].charAt(1);
44 if (V
.PIECES
.includes(piece
)) return piece
;
66 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
67 return this.board
[i
][j
].charAt(0);
70 // Code: a/c = bishop + piece1/piece2 j/l for king,
71 // m/o for knight, s/t for queen, u/v for rook
72 static get AUGMENTED_PIECES() {
88 return (ChessRules
.PIECES
.includes(b
[1]) ? "" : "Musketeer/") + b
;
91 getReservePpath(index
, color
) {
92 return "Musketeer/" + color
+ V
.RESERVE_PIECES
[index
];
95 // Decode above notation into additional piece
96 getExtraPiece(symbol
) {
97 if (['a','j','m','s','u'].includes(symbol
))
98 return this.extraPieces
[0];
99 return this.extraPieces
[1];
102 // Inverse operation: augment piece
103 getAugmented(piece
) {
104 const p1
= [2, 3].includes(this.movesCount
);
106 case V
.ROOK: return (p1
? 'u' : 'v');
107 case V
.KNIGHT: return (p1
? 'm' : 'o');
108 case V
.BISHOP: return (p1
? 'a' : 'c');
109 case V
.QUEEN: return (p1
? 's' : 't');
110 case V
.KING: return (p1
? 'j' : 'l');
112 return '_'; //never reached
115 static IsGoodFen(fen
) {
116 if (!ChessRules
.IsGoodFen(fen
)) return false;
117 const fenParsed
= V
.ParseFen(fen
);
118 // 5) Check extra pieces
119 if (!fenParsed
.extraPieces
) return false;
120 // Not exact matching (would need to look at movesCount), but OK for now
121 if (!fenParsed
.extraPieces
.match(/^[dwxejfy-]{2,2}$/)) return false;
125 static IsGoodPosition(position
) {
126 if (position
.length
== 0) return false;
127 const rows
= position
.split("/");
128 if (rows
.length
!= V
.size
.x
) return false;
129 let kings
= { "w": 0, "b": 0 };
130 const allPiecesCodes
= V
.PIECES
.concat(V
.AUGMENTED_PIECES
);
131 const kingBlackCodes
= ['j','k','l'];
132 const kingWhiteCodes
= ['J','K','L'];
133 for (let row
of rows
) {
135 for (let i
= 0; i
< row
.length
; i
++) {
136 if (kingBlackCodes
.includes(row
[i
])) kings
['b']++;
137 else if (kingWhiteCodes
.includes(row
[i
])) kings
['w']++;
138 if (allPiecesCodes
.includes(row
[i
].toLowerCase())) sumElts
++;
140 const num
= parseInt(row
[i
], 10);
141 if (isNaN(num
)) return false;
145 if (sumElts
!= V
.size
.y
) return false;
147 // Both kings should be on board, only one of each color:
148 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
152 static ParseFen(fen
) {
153 const fenParts
= fen
.split(" ");
154 return Object
.assign(
155 ChessRules
.ParseFen(fen
),
156 { extraPieces: fenParts
[5] }
160 static GenRandInitFen(randomness
) {
161 return ChessRules
.GenRandInitFen(randomness
) + " --";
165 return super.getFen() + " " + this.extraPieces
.join("");
168 setOtherVariables(fen
) {
169 super.setOtherVariables(fen
);
170 // Extra pieces may not be defined yet (thus '-')
171 this.extraPieces
= V
.ParseFen(fen
).extraPieces
.split("");
172 // At early stages, also init reserves
173 if (this.movesCount
<= 5) {
174 const condShow
= (piece
) => {
175 if (this.movesCount
== 0) return true;
176 if (this.movesCount
== 1) return piece
!= this.extraPieces
[0];
177 if (this.movesCount
<= 3) return this.extraPiece
.includes(piece
);
178 return this.extraPiece
[1] == piece
;
180 this.reserve
= { w : {}, b: {} };
181 for (let c
of ['w', 'b']) {
182 V
.RESERVE_PIECES
.forEach(p
=>
183 this.reserve
[c
][p
] = condShow(p
) ? 1 : 0);
188 // Kings may be augmented:
190 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
191 const rows
= V
.ParseFen(fen
).position
.split("/");
192 for (let i
= 0; i
< rows
.length
; i
++) {
193 let k
= 0; //column index on board
194 for (let j
= 0; j
< rows
[i
].length
; j
++) {
195 const piece
= rows
[i
].charAt(j
);
196 if (['j','k','l'].includes(piece
.toLowerCase())) {
197 const color
= (piece
.charCodeAt(0) <= 90 ? 'w' : 'b');
198 this.kingPos
[color
] = [i
, k
];
201 const num
= parseInt(rows
[i
].charAt(j
), 10);
202 if (!isNaN(num
)) k
+= num
- 1;
209 getReserveMoves([x
, y
]) {
210 const color
= this.turn
;
211 const p
= V
.RESERVE_PIECES
[y
];
213 this.reserve
[color
][p
] == 0 ||
214 ([2, 3].includes(this.movesCount
) && p
!= this.extraPieces
[0]) ||
215 ([4, 5].includes(this.movesCount
) && p
!= this.extraPieces
[1])
221 (this.movesCount
<= 1 ? [2, 3, 4, 5] : [color
== 'w' ? 7 : 0]);
222 const mappingAppear
= [ [3, 4], [3, 3], [4, 4], [4, 3] ];
223 for (let i
of iIdx
) {
224 for (let j
= 0; j
< V
.size
.y
; j
++) {
226 (this.movesCount
<= 1 && this.board
[i
][j
] == V
.EMPTY
) ||
228 this.movesCount
>= 2 &&
229 ChessRules
.PIECES
.includes(this.board
[i
][j
].charAt(1))
232 const [appearX
, appearY
] =
234 ? mappingAppear
[this.movesCount
]
237 (this.movesCount
>= 2 ? this.board
[i
][j
].charAt(1) : '');
244 p: (this.movesCount
<= 1 ? p : this.getAugmented(pOnBoard
))
248 start: { x: x
, y: y
}, //a bit artificial...
251 if (this.movesCount
>= 2)
252 mv
.vanish
.push(new PiPo({ x: i
, y: j
, c: color
, p: pOnBoard
}))
260 // Assumption: movesCount >= 6
261 getPotentialMovesFrom([x
, y
]) {
262 // Standard moves. If piece not in usual list, new piece appears.
263 const initialPiece
= this.getPiece(x
, y
);
264 if (V
.RESERVE_PIECES
.includes(initialPiece
)) {
265 switch (initialPiece
) {
266 case V
.LEOPARD: return this.getPotentialLeopardMoves([x
, y
]);
267 case V
.CANNON: return this.getPotentialCannonMoves([x
, y
]);
268 case V
.UNICORN: return this.getPotentialUnicornMoves([x
, y
]);
269 case V
.ELEPHANT: return this.getPotentialElephantMoves([x
, y
]);
270 case V
.HAWK: return this.getPotentialHawkMoves([x
, y
]);
271 case V
.FORTRESS: return this.getPotentialFortressMoves([x
, y
]);
272 case V
.SPIDER: return this.getPotentialSpiderMoves([x
, y
]);
274 return []; //never reached
276 // Following is mostly copy-paste from Titan Chess (TODO?)
278 if (initialPiece
== V
.PAWN
) {
280 ChessRules
.PawnSpecs
.promotions
.concat(this.extraPieces
);
281 moves
= super.getPotentialPawnMoves([x
, y
], promotions
);
283 else moves
= super.getPotentialMovesFrom([x
, y
]);
284 const color
= this.turn
;
286 ((color
== 'w' && x
== 7) || (color
== "b" && x
== 0)) &&
287 V
.AUGMENTED_PIECES
.includes(this.board
[x
][y
][1])
289 const newPiece
= this.getExtraPiece(this.board
[x
][y
][1]);
291 m
.appear
[0].p
= initialPiece
;
302 if (m
.vanish
.length
<= 1) return;
303 const [vx
, vy
] = [m
.vanish
[1].x
, m
.vanish
[1].y
];
305 m
.appear
.length
>= 2 && //3 if the king was also augmented
306 m
.vanish
.length
== 2 &&
307 m
.vanish
[1].c
== color
&&
308 V
.AUGMENTED_PIECES
.includes(this.board
[vx
][vy
][1])
310 // Castle, rook is an "augmented piece"
311 m
.appear
[1].p
= V
.ROOK
;
314 p: this.getExtraPiece(this.board
[vx
][vy
][1]),
326 // All types of leaps used here:
327 static get Leap2Ortho() {
328 return [ [-2, 0], [0, -2], [2, 0], [0, 2] ];
330 static get Leap2Diago() {
331 return [ [-2, -2], [-2, 2], [2, -2], [2, 2] ];
333 static get Leap3Ortho() {
334 return [ [-3, 0], [0, -3], [3, 0], [0, 3] ];
336 static get Leap3Diago() {
337 return [ [-3, -3], [-3, 3], [3, -3], [3, 3] ];
339 static get CamelSteps() {
341 [-3, -1], [-3, 1], [-1, -3], [-1, 3],
342 [1, -3], [1, 3], [3, -1], [3, 1]
345 static get VerticalKnight() {
346 return [ [-2, -1], [-2, 1], [2, -1], [2, 1] ];
348 static get HorizontalKnight() {
349 return [ [-1, -2], [-1, 2], [1, -2], [1, 2] ];
352 getPotentialLeopardMoves(sq
) {
354 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], 2)
355 .concat(super.getPotentialKnightMoves(sq
))
359 getPotentialCannonMoves(sq
) {
361 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
362 .concat(V
.Leap2Ortho
).concat(V
.HorizontalKnight
);
363 return super.getSlideNJumpMoves(sq
, steps
, 1);
366 getPotentialUnicornMoves(sq
) {
368 super.getPotentialKnightMoves(sq
)
369 .concat(super.getSlideNJumpMoves(sq
, V
.CamelSteps
, 1))
373 getPotentialElephantMoves(sq
) {
375 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
376 .concat(V
.Leap2Ortho
)
377 .concat(V
.Leap2Diago
);
378 return super.getSlideNJumpMoves(sq
, steps
, 1);
381 getPotentialHawkMoves(sq
) {
383 V
.Leap2Ortho
.concat(V
.Leap2Diago
)
384 .concat(V
.Leap3Ortho
).concat(V
.Leap3Diago
);
385 return super.getSlideNJumpMoves(sq
, steps
, 1);
388 getPotentialFortressMoves(sq
) {
389 const steps
= V
.Leap2Ortho
.concat(V
.VerticalKnight
)
391 super.getSlideNJumpMoves(sq
, steps
, 1)
392 .concat(this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], 3))
396 getPotentialSpiderMoves(sq
) {
397 const steps
= V
.Leap2Ortho
.concat(V
.steps
[V
.KNIGHT
])
399 super.getSlideNJumpMoves(sq
, steps
, 1)
400 .concat(this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], 2))
404 getPossibleMovesFrom([x
, y
]) {
405 if (this.movesCount
<= 5)
406 return (x
>= V
.size
.x
? this.getReserveMoves([x
, y
]) : []);
407 return super.getPossibleMovesFrom([x
, y
]);
411 if (this.movesCount
>= 6) return super.getAllValidMoves();
413 const color
= this.turn
;
414 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
415 moves
= moves
.concat(
416 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
423 if (this.movesCount
<= 5) return true;
424 return super.atLeastOneMove();
427 isAttacked(sq
, color
) {
428 if (super.isAttacked(sq
, color
)) return true;
430 this.extraPieces
.includes(V
.LEOPARD
) &&
431 this.isAttackedByLeopard(sq
, color
)
436 this.extraPieces
.includes(V
.CANNON
) &&
437 this.isAttackedByCannon(sq
, color
)
442 this.extraPieces
.includes(V
.UNICORN
) &&
443 this.isAttackedByUnicorn(sq
, color
)
448 this.extraPieces
.includes(V
.ELEPHANT
) &&
449 this.isAttackedByElephant(sq
, color
)
454 this.extraPieces
.includes(V
.HAWK
) &&
455 this.isAttackedByHawk(sq
, color
)
460 this.extraPieces
.includes(V
.FORTRESS
) &&
461 this.isAttackedByFortress(sq
, color
)
466 this.extraPieces
.includes(V
.SPIDER
) &&
467 this.isAttackedBySpider(sq
, color
)
474 isAttackedByLeopard(sq
, color
) {
476 super.isAttackedBySlideNJump(
477 sq
, color
, V
.LEOPARD
, V
.steps
[V
.KNIGHT
], 1) ||
478 this.isAttackedBySlideNJump(sq
, color
, V
.LEOPARD
, V
.steps
[V
.BISHOP
], 2)
482 isAttackedByCannon(sq
, color
) {
484 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
485 .concat(V
.Leap2Ortho
).concat(V
.HorizontalKnight
);
486 return super.isAttackedBySlideNJump(sq
, color
, V
.CANNON
, steps
, 1);
489 isAttackedByUnicorn(sq
, color
) {
490 const steps
= V
.steps
[V
.KNIGHT
].concat(V
.CamelSteps
)
492 super.isAttackedBySlideNJump(sq
, color
, V
.UNICORN
, steps
, 1)
496 isAttackedByElephant(sq
, color
) {
498 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
499 .concat(V
.Leap2Ortho
)
500 .concat(V
.Leap2Diago
);
502 super.isAttackedBySlideNJump(sq
, color
, V
.ELEPHANT
, steps
, 1)
506 isAttackedByHawk(sq
, color
) {
508 V
.Leap2Ortho
.concat(V
.Leap2Diago
)
509 .concat(V
.Leap3Ortho
).concat(V
.Leap3Diago
);
510 return super.isAttackedBySlideNJump(sq
, color
, V
.HAWK
, steps
, 1);
513 isAttackedByFortress(sq
, color
) {
514 const steps
= V
.Leap2Ortho
.concat(V
.VerticalKnight
)
516 super.isAttackedBySlideNJump(sq
, color
, V
.FORTRESS
, steps
, 1) ||
517 this.isAttackedBySlideNJump(sq
, color
, V
.FORTRESS
, V
.steps
[V
.BISHOP
], 3)
521 isAttackedBySpider(sq
, color
) {
522 const steps
= V
.Leap2Ortho
.concat(V
.steps
[V
.KNIGHT
])
524 super.isAttackedBySlideNJump(sq
, color
, V
.SPIDER
, steps
, 1) ||
525 this.isAttackedBySlideNJump(sq
, color
, V
.SPIDER
, V
.steps
[V
.BISHOP
], 2)
530 if (this.movesCount
<= 6) return [];
531 return super.getCheckSquares();
534 // At movesCount == 0,1: show full reserves [minus chosen piece1]
535 // At movesCount == 2,3: show reserve with only 2 selected pieces
536 // At movesCount == 4,5: show reserve with only piece2
539 if (this.movesCount
> 6) super.postPlay(move);
541 switch (this.movesCount
) {
543 this.reserve
['w'][move.appear
[0].p
]--;
544 this.reserve
['b'][move.appear
[0].p
]--;
545 this.extraPieces
[0] = move.appear
[0].p
;
548 this.extraPieces
[1] = move.appear
[0].p
;
549 for (let p
of V
.RESERVE_PIECES
) {
550 const resVal
= (this.extraPieces
.includes(p
) ? 1 : 0);
551 this.reserve
['w'][p
] = resVal
;
552 this.reserve
['b'][p
] = resVal
;
556 this.reserve
['w'][this.extraPieces
[0]]--;
559 this.reserve
['b'][this.extraPieces
[0]]--;
562 this.reserve
['w'][this.extraPieces
[1]]--;
566 this.board
[3][3] = "";
567 this.board
[3][4] = "";
574 if (this.movesCount
>= 6) super.postUndo(move);
576 switch (this.movesCount
) {
578 this.reserve
['w'][move.appear
[0].p
]++;
579 this.reserve
['b'][move.appear
[0].p
]++;
580 this.extraPieces
[0] = '-';
583 this.extraPieces
[1] = '-';
584 for (let p
of V
.RESERVE_PIECES
) {
585 const resVal
= (p
!= this.extraPieces
[0] ? 1 : 0);
586 this.reserve
['w'][p
] = resVal
;
587 this.reserve
['b'][p
] = resVal
;
591 this.reserve
['w'][this.extraPieces
[0]]++;
594 this.reserve
['b'][this.extraPieces
[0]]++;
597 this.reserve
['w'][this.extraPieces
[1]]++;
600 this.reserve
= { w: {}, b: {} };
601 for (let c
of ['w', 'b'])
602 V
.RESERVE_PIECES
.forEach(p
=> this.reserve
[c
][p
] = 0);
603 this.reserve
['b'][this.extraPieces
[1]] = 1;
604 this.board
[3][3] = 'b' + this.extraPieces
[1];
605 this.board
[3][4] = 'w' + this.extraPieces
[0];
612 if (this.movesCount
>= 6) return super.getComputerMove();
613 // Choose a move at random
614 const moves
= this.getAllValidMoves();
615 return moves
[randInt(moves
.length
)];
618 static get SEARCH_DEPTH() {
624 for (let i
= 0; i
< V
.size
.x
; i
++) {
625 for (let j
= 0; j
< V
.size
.y
; j
++) {
626 if (this.board
[i
][j
] != V
.EMPTY
) {
627 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
628 const piece
= this.getPiece(i
, j
);
629 evaluation
+= sign
* V
.VALUES
[piece
];
630 const symbol
= this.board
[i
][j
][1];
631 if (V
.AUGMENTED_PIECES
.includes(symbol
)) {
632 const extraPiece
= this.getExtraPiece(symbol
);
633 evaluation
+= sign
* V
.VALUES
[extraPiece
]
641 static get VALUES() {
642 return Object
.assign(
656 static get ExtraDictionary() {
658 [V
.LEOPARD
]: { prefix: 'L', name: "Leopard" },
659 [V
.CANNON
]: { prefix: 'C', name: "Cannon" },
660 [V
.UNICORN
]: { prefix: 'U', name: "Unicorn" },
661 [V
.ELEPHANT
]: { prefix: 'E', name: "Elephant" },
662 [V
.HAWK
]: { prefix: 'H', name: "Hawk" },
663 [V
.FORTRESS
]: { prefix: 'F', name: "Fortress" },
664 [V
.SPIDER
]: { prefix: 'S', name: "Spider" }
669 if (this.movesCount
<= 5) {
670 if (this.movesCount
<= 1)
671 return V
.ExtraDictionary
[move.appear
[0].p
].name
;
672 // Put something on the board:
674 V
.ExtraDictionary
[V
.RESERVE_PIECES
[move.start
.y
]].prefix
+
675 "@" + V
.CoordsToSquare(move.end
)
680 V
.AUGMENTED_PIECES
.includes(move.vanish
[0].p
) ||
682 move.vanish
.length
>= 2 &&
683 V
.AUGMENTED_PIECES
.includes(move.vanish
[1].p
)
686 // Simplify move before calling super.getNotation()
687 let smove
= JSON
.parse(JSON
.stringify(move));
688 if (ChessRules
.PIECES
.includes(move.vanish
[0].p
)) {
689 // Castle with an augmented rook
691 smove
.vanish
[1].p
= smove
.appear
[1].p
;
694 // Moving an augmented piece
696 smove
.vanish
[0].p
= smove
.appear
[0].p
;
698 smove
.vanish
.length
== 2 &&
699 smove
.vanish
[0].c
== smove
.vanish
[1].c
&&
700 V
.AUGMENTED_PIECES
.includes(move.vanish
[1].p
)
702 // Castle with an augmented rook
704 smove
.vanish
[1].p
= smove
.appear
[1].p
;
707 notation
= super.getNotation(smove
);
709 // Else, more common case:
710 notation
= super.getNotation(move);
711 const pieceSymbol
= notation
.charAt(0).toLowerCase();
712 if (move.vanish
[0].p
!= V
.PAWN
&& V
.RESERVE_PIECES
.includes(pieceSymbol
))
713 notation
= V
.ExtraDictionary
[pieceSymbol
].prefix
+ notation
.substr(1);