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 getSlideNJumpMoves([x
, y
], steps
, nbSteps
) {
328 outerLoop: for (let step
of steps
) {
332 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
333 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
336 // Next condition to remain compatible with super method
337 (isNaN(parseInt(nbSteps
, 10)) || nbSteps
>= stepCounter
)
345 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]))
346 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
351 // All types of leaps used here:
352 static get Leap2Ortho() {
353 return [ [-2, 0], [0, -2], [2, 0], [0, 2] ];
355 static get Leap2Diago() {
356 return [ [-2, -2], [-2, 2], [2, -2], [2, 2] ];
358 static get Leap3Ortho() {
359 return [ [-3, 0], [0, -3], [3, 0], [0, 3] ];
361 static get Leap3Diago() {
362 return [ [-3, -3], [-3, 3], [3, -3], [3, 3] ];
364 static get CamelSteps() {
366 [-3, -1], [-3, 1], [-1, -3], [-1, 3],
367 [1, -3], [1, 3], [3, -1], [3, 1]
370 static get VerticalKnight() {
371 return [ [-2, -1], [-2, 1], [2, -1], [2, 1] ];
373 static get HorizontalKnight() {
374 return [ [-1, -2], [-1, 2], [1, -2], [1, 2] ];
377 getPotentialLeopardMoves(sq
) {
379 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], 2)
380 .concat(super.getPotentialKnightMoves(sq
))
384 getPotentialCannonMoves(sq
) {
386 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
387 .concat(V
.Leap2Ortho
).concat(V
.HorizontalKnight
);
388 return super.getSlideNJumpMoves(sq
, steps
, "oneStep");
391 getPotentialUnicornMoves(sq
) {
393 super.getPotentialKnightMoves(sq
)
394 .concat(super.getSlideNJumpMoves(sq
, V
.CamelSteps
, "oneStep"))
398 getPotentialElephantMoves(sq
) {
400 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
401 .concat(V
.Leap2Ortho
)
402 .concat(V
.Leap2Diago
);
403 return super.getSlideNJumpMoves(sq
, steps
, "oneStep");
406 getPotentialHawkMoves(sq
) {
408 V
.Leap2Ortho
.concat(V
.Leap2Diago
)
409 .concat(V
.Leap3Ortho
).concat(V
.Leap3Diago
);
410 return super.getSlideNJumpMoves(sq
, steps
, "oneStep");
413 getPotentialFortressMoves(sq
) {
414 const steps
= V
.Leap2Ortho
.concat(V
.VerticalKnight
)
416 super.getSlideNJumpMoves(sq
, steps
, "oneStep")
417 .concat(this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], 3))
421 getPotentialSpiderMoves(sq
) {
422 const steps
= V
.Leap2Ortho
.concat(V
.steps
[V
.KNIGHT
])
424 super.getSlideNJumpMoves(sq
, steps
, "oneStep")
425 .concat(this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], 2))
429 getPossibleMovesFrom([x
, y
]) {
430 if (this.movesCount
<= 5)
431 return (x
>= V
.size
.x
? this.getReserveMoves([x
, y
]) : []);
432 return super.getPossibleMovesFrom([x
, y
]);
436 if (this.movesCount
>= 6) return super.getAllValidMoves();
438 const color
= this.turn
;
439 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
440 moves
= moves
.concat(
441 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
448 if (this.movesCount
<= 5) return true;
449 return super.atLeastOneMove();
452 isAttacked(sq
, color
) {
453 if (super.isAttacked(sq
, color
)) return true;
455 this.extraPieces
.includes(V
.LEOPARD
) &&
456 this.isAttackedByLeopard(sq
, color
)
461 this.extraPieces
.includes(V
.CANNON
) &&
462 this.isAttackedByCannon(sq
, color
)
467 this.extraPieces
.includes(V
.UNICORN
) &&
468 this.isAttackedByUnicorn(sq
, color
)
473 this.extraPieces
.includes(V
.ELEPHANT
) &&
474 this.isAttackedByElephant(sq
, color
)
479 this.extraPieces
.includes(V
.HAWK
) &&
480 this.isAttackedByHawk(sq
, color
)
485 this.extraPieces
.includes(V
.FORTRESS
) &&
486 this.isAttackedByFortress(sq
, color
)
491 this.extraPieces
.includes(V
.SPIDER
) &&
492 this.isAttackedBySpider(sq
, color
)
499 // Modify because of the limiyted steps options of some of the pieces here
500 isAttackedBySlideNJump([x
, y
], color
, piece
, steps
, nbSteps
) {
501 if (!!nbSteps
&& isNaN(parseInt(nbSteps
, 10))) nbSteps
= 1;
502 for (let step
of steps
) {
503 let rx
= x
+ step
[0],
507 V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&&
508 (!nbSteps
|| stepCounter
< nbSteps
)
516 this.board
[rx
][ry
] != V
.EMPTY
&&
517 this.getPiece(rx
, ry
) == piece
&&
518 this.getColor(rx
, ry
) == color
526 isAttackedByLeopard(sq
, color
) {
528 super.isAttackedBySlideNJump(
529 sq
, color
, V
.LEOPARD
, V
.steps
[V
.KNIGHT
], "oneStep") ||
530 this.isAttackedBySlideNJump(sq
, color
, V
.LEOPARD
, V
.steps
[V
.BISHOP
], 2)
534 isAttackedByCannon(sq
, color
) {
536 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
537 .concat(V
.Leap2Ortho
).concat(V
.HorizontalKnight
);
538 return super.isAttackedBySlideNJump(sq
, color
, V
.CANNON
, steps
, "oneStep");
541 isAttackedByUnicorn(sq
, color
) {
542 const steps
= V
.steps
[V
.KNIGHT
].concat(V
.CamelSteps
)
544 super.isAttackedBySlideNJump(sq
, color
, V
.UNICORN
, steps
, "oneStep")
548 isAttackedByElephant(sq
, color
) {
550 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
551 .concat(V
.Leap2Ortho
)
552 .concat(V
.Leap2Diago
);
554 super.isAttackedBySlideNJump(sq
, color
, V
.ELEPHANT
, steps
, "oneStep")
558 isAttackedByHawk(sq
, color
) {
560 V
.Leap2Ortho
.concat(V
.Leap2Diago
)
561 .concat(V
.Leap3Ortho
).concat(V
.Leap3Diago
);
562 return super.isAttackedBySlideNJump(sq
, color
, V
.HAWK
, steps
, "oneStep");
565 isAttackedByFortress(sq
, color
) {
566 const steps
= V
.Leap2Ortho
.concat(V
.VerticalKnight
)
568 super.isAttackedBySlideNJump(sq
, color
, V
.FORTRESS
, steps
, "oneStep") ||
569 this.isAttackedBySlideNJump(sq
, color
, V
.FORTRESS
, V
.steps
[V
.BISHOP
], 3)
573 isAttackedBySpider(sq
, color
) {
574 const steps
= V
.Leap2Ortho
.concat(V
.steps
[V
.KNIGHT
])
576 super.isAttackedBySlideNJump(sq
, color
, V
.SPIDER
, steps
, "oneStep") ||
577 this.isAttackedBySlideNJump(sq
, color
, V
.SPIDER
, V
.steps
[V
.BISHOP
], 2)
582 if (this.movesCount
<= 6) return [];
583 return super.getCheckSquares();
586 // At movesCount == 0,1: show full reserves [minus chosen piece1]
587 // At movesCount == 2,3: show reserve with only 2 selected pieces
588 // At movesCount == 4,5: show reserve with only piece2
591 if (this.movesCount
> 6) super.postPlay(move);
593 switch (this.movesCount
) {
595 this.reserve
['w'][move.appear
[0].p
]--;
596 this.reserve
['b'][move.appear
[0].p
]--;
597 this.extraPieces
[0] = move.appear
[0].p
;
600 this.extraPieces
[1] = move.appear
[0].p
;
601 for (let p
of V
.RESERVE_PIECES
) {
602 const resVal
= (this.extraPieces
.includes(p
) ? 1 : 0);
603 this.reserve
['w'][p
] = resVal
;
604 this.reserve
['b'][p
] = resVal
;
608 this.reserve
['w'][this.extraPieces
[0]]--;
611 this.reserve
['b'][this.extraPieces
[0]]--;
614 this.reserve
['w'][this.extraPieces
[1]]--;
618 this.board
[3][3] = "";
619 this.board
[3][4] = "";
626 if (this.movesCount
>= 6) super.postUndo(move);
628 switch (this.movesCount
) {
630 this.reserve
['w'][move.appear
[0].p
]++;
631 this.reserve
['b'][move.appear
[0].p
]++;
632 this.extraPieces
[0] = '-';
635 this.extraPieces
[1] = '-';
636 for (let p
of V
.RESERVE_PIECES
) {
637 const resVal
= (p
!= this.extraPieces
[0] ? 1 : 0);
638 this.reserve
['w'][p
] = resVal
;
639 this.reserve
['b'][p
] = resVal
;
643 this.reserve
['w'][this.extraPieces
[0]]++;
646 this.reserve
['b'][this.extraPieces
[0]]++;
649 this.reserve
['w'][this.extraPieces
[1]]++;
652 this.reserve
= { w: {}, b: {} };
653 for (let c
of ['w', 'b'])
654 V
.RESERVE_PIECES
.forEach(p
=> this.reserve
[c
][p
] = 0);
655 this.reserve
['b'][this.extraPieces
[1]] = 1;
656 this.board
[3][3] = 'b' + this.extraPieces
[1];
657 this.board
[3][4] = 'w' + this.extraPieces
[0];
664 if (this.movesCount
>= 6) return super.getComputerMove();
665 // Choose a move at random
666 const moves
= this.getAllValidMoves();
667 return moves
[randInt(moves
.length
)];
670 static get SEARCH_DEPTH() {
676 for (let i
= 0; i
< V
.size
.x
; i
++) {
677 for (let j
= 0; j
< V
.size
.y
; j
++) {
678 if (this.board
[i
][j
] != V
.EMPTY
) {
679 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
680 const piece
= this.getPiece(i
, j
);
681 evaluation
+= sign
* V
.VALUES
[piece
];
682 const symbol
= this.board
[i
][j
][1];
683 if (V
.AUGMENTED_PIECES
.includes(symbol
)) {
684 const extraPiece
= this.getExtraPiece(symbol
);
685 evaluation
+= sign
* V
.VALUES
[extraPiece
]
693 static get VALUES() {
694 return Object
.assign(
708 static get ExtraDictionary() {
710 [V
.LEOPARD
]: { prefix: 'L', name: "Leopard" },
711 [V
.CANNON
]: { prefix: 'C', name: "Cannon" },
712 [V
.UNICORN
]: { prefix: 'U', name: "Unicorn" },
713 [V
.ELEPHANT
]: { prefix: 'E', name: "Elephant" },
714 [V
.HAWK
]: { prefix: 'H', name: "Hawk" },
715 [V
.FORTRESS
]: { prefix: 'F', name: "Fortress" },
716 [V
.SPIDER
]: { prefix: 'S', name: "Spider" }
721 if (this.movesCount
<= 5) {
722 if (this.movesCount
<= 1)
723 return V
.ExtraDictionary
[move.appear
[0].p
].name
;
724 // Put something on the board:
726 V
.ExtraDictionary
[V
.RESERVE_PIECES
[move.start
.y
]].prefix
+
727 "@" + V
.CoordsToSquare(move.end
)
732 V
.AUGMENTED_PIECES
.includes(move.vanish
[0].p
) ||
734 move.vanish
.length
>= 2 &&
735 V
.AUGMENTED_PIECES
.includes(move.vanish
[1].p
)
738 // Simplify move before calling super.getNotation()
739 let smove
= JSON
.parse(JSON
.stringify(move));
740 if (ChessRules
.PIECES
.includes(move.vanish
[0].p
)) {
741 // Castle with an augmented rook
743 smove
.vanish
[1].p
= smove
.appear
[1].p
;
746 // Moving an augmented piece
748 smove
.vanish
[0].p
= smove
.appear
[0].p
;
750 smove
.vanish
.length
== 2 &&
751 smove
.vanish
[0].c
== smove
.vanish
[1].c
&&
752 V
.AUGMENTED_PIECES
.includes(move.vanish
[1].p
)
754 // Castle with an augmented rook
756 smove
.vanish
[1].p
= smove
.appear
[1].p
;
759 notation
= super.getNotation(smove
);
761 // Else, more common case:
762 notation
= super.getNotation(move);
763 const pieceSymbol
= notation
.charAt(0).toLowerCase();
764 if (move.vanish
[0].p
!= V
.PAWN
&& V
.RESERVE_PIECES
.includes(pieceSymbol
))
765 notation
= V
.ExtraDictionary
[pieceSymbol
].prefix
+ notation
.substr(1);