1 import { ArrayFun
} from "@/utils/array";
2 import { randInt
, shuffle
} from "@/utils/alea";
3 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
5 export const VariantRules
= class EightpiecesRules
extends ChessRules
{
17 return ChessRules
.PIECES
.concat([V
.JAILER
, V
.SENTRY
, V
.LANCER
]);
20 static get LANCER_DIRS() {
34 const piece
= this.board
[i
][j
].charAt(1);
35 // Special lancer case: 8 possible orientations
36 if (Object
.keys(V
.LANCER_DIRS
).includes(piece
)) return V
.LANCER
;
41 if ([V
.JAILER
, V
.SENTRY
].concat(Object
.keys(V
.LANCER_DIRS
)).includes(b
[1]))
42 return "Eightpieces/" + b
;
46 static ParseFen(fen
) {
47 const fenParts
= fen
.split(" ");
48 return Object
.assign(ChessRules
.ParseFen(fen
), {
49 sentrypush: fenParts
[5]
54 return super.getFen() + " " + this.getSentrypushFen();
58 return super.getFenForRepeat() + "_" + this.getSentrypushFen();
62 const L
= this.sentryPush
.length
;
63 if (!this.sentryPush
[L
-1]) return "-";
65 this.sentryPush
[L
-1].forEach(coords
=>
66 res
+= V
.CoordsToSquare(coords
) + ",");
67 return res
.slice(0, -1);
70 setOtherVariables(fen
) {
71 super.setOtherVariables(fen
);
72 // subTurn == 2 only when a sentry moved, and is about to push something
74 // Stack pieces' forbidden squares after a sentry move at each turn
75 const parsedFen
= V
.ParseFen(fen
);
76 if (parsedFen
.sentrypush
== "-") this.sentryPush
= [null];
79 parsedFen
.sentrypush
.split(",").map(sq
=> {
80 return V
.SquareToCoords(sq
);
86 canTake([x1
,y1
], [x2
, y2
]) {
87 if (this.subTurn
== 2)
88 // Sentry push: pieces can capture own color (only)
89 return this.getColor(x1
, y1
) == this.getColor(x2
, y2
);
90 return super.canTake([x1
,y1
], [x2
, y2
]);
93 static GenRandInitFen(randomness
) {
94 // TODO: special conditions for 960
95 return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 1111 - -";
98 // Scan kings, rooks and jailers
100 this.INIT_COL_KING
= { w: -1, b: -1 };
101 this.INIT_COL_ROOK
= { w: -1, b: -1 };
102 this.INIT_COL_JAILER
= { w: -1, b: -1 };
103 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
104 const fenRows
= V
.ParseFen(fen
).position
.split("/");
105 const startRow
= { 'w': V
.size
.x
- 1, 'b': 0 };
106 for (let i
= 0; i
< fenRows
.length
; i
++) {
108 for (let j
= 0; j
< fenRows
[i
].length
; j
++) {
109 switch (fenRows
[i
].charAt(j
)) {
111 this.kingPos
["b"] = [i
, k
];
112 this.INIT_COL_KING
["b"] = k
;
115 this.kingPos
["w"] = [i
, k
];
116 this.INIT_COL_KING
["w"] = k
;
119 if (i
== startRow
['b'] && this.INIT_COL_ROOK
["b"] < 0)
120 this.INIT_COL_ROOK
["b"] = k
;
123 if (i
== startRow
['w'] && this.INIT_COL_ROOK
["w"] < 0)
124 this.INIT_COL_ROOK
["w"] = k
;
127 if (i
== startRow
['b'] && this.INIT_COL_JAILER
["b"] < 0)
128 this.INIT_COL_JAILER
["b"] = k
;
131 if (i
== startRow
['w'] && this.INIT_COL_JAILER
["w"] < 0)
132 this.INIT_COL_JAILER
["w"] = k
;
135 const num
= parseInt(fenRows
[i
].charAt(j
));
136 if (!isNaN(num
)) k
+= num
- 1;
144 // Is piece on square (x,y) immobilized?
145 isImmobilized([x
, y
]) {
146 const color
= this.getColor(x
, y
);
147 const oppCol
= V
.GetOppCol(color
);
148 for (let step
of V
.steps
[V
.ROOK
]) {
149 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
152 this.board
[i
][j
] != V
.EMPTY
&&
153 this.getColor(i
, j
) == oppCol
155 const oppPiece
= this.getPiece(i
, j
);
156 if (oppPiece
== V
.JAILER
) return [i
, j
];
162 getPotentialMovesFrom_aux([x
, y
]) {
163 switch (this.getPiece(x
, y
)) {
165 return this.getPotentialJailerMoves([x
, y
]);
167 return this.getPotentialSentryMoves([x
, y
]);
169 return this.getPotentialLancerMoves([x
, y
]);
171 return super.getPotentialMovesFrom([x
, y
]);
175 getPotentialMovesFrom([x
,y
]) {
176 if (this.subTurn
== 1) {
177 if (!!this.isImmobilized([x
, y
])) return [];
178 return this.getPotentialMovesFrom_aux([x
, y
]);
180 // subTurn == 2: only the piece pushed by the sentry is allowed to move,
181 // as if the sentry didn't exist
182 if (x
!= this.sentryPos
.x
&& y
!= this.sentryPos
.y
) return [];
183 return this.getPotentialMovesFrom_aux([x
,y
]);
187 let moves
= super.getAllValidMoves().filter(m
=> {
188 // Remove jailer captures
189 return m
.vanish
[0].p
!= V
.JAILER
|| m
.vanish
.length
== 1;
191 const L
= this.sentryPush
.length
;
192 if (!!this.sentryPush
[L
-1] && this.subTurn
== 1) {
193 // Delete moves walking back on sentry push path
194 moves
= moves
.filter(m
=> {
196 m
.vanish
[0].p
!= V
.PAWN
&&
197 this.sentryPush
[L
-1].some(sq
=> sq
.x
== m
.end
.x
&& sq
.y
== m
.end
.y
)
208 // Disable check tests when subTurn == 2, because the move isn't finished
209 if (this.subTurn
== 2) return moves
;
210 return super.filterValid(moves
);
213 getPotentialLancerMoves([x
, y
]) {
214 // TODO: add all lancer possible orientations same as pawn promotions,
215 // except if just after a push: allow all movements from init square then
219 getPotentialSentryMoves([x
, y
]) {
220 // The sentry moves a priori like a bishop:
221 let moves
= super.getPotentialBishopMoves([x
, y
]);
222 // ...but captures are replaced by special move
223 // "appear = [], vanish = init square" to let the pushed piece move then.
227 getPotentialJailerMoves([x
, y
]) {
228 // Captures are removed afterward:
229 return super.getPotentialRookMoves([x
, y
]);
232 getPotentialKingMoves([x
, y
]) {
233 let moves
= super.getPotentialKingMoves([x
, y
]);
234 // Augment with pass move is the king is immobilized:
235 const jsq
= this.isImmobilized([x
, y
]);
241 start: { x: x
, y: y
},
242 end: { x: jsq
[0], y: jsq
[1] }
249 // Adapted: castle with jailer possible
250 getCastleMoves([x
, y
]) {
251 const c
= this.getColor(x
, y
);
252 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
253 if (x
!= firstRank
|| y
!= this.INIT_COL_KING
[c
])
256 const oppCol
= V
.GetOppCol(c
);
259 // King, then rook or jailer:
260 const finalSquares
= [
262 [V
.size
.y
- 2, V
.size
.y
- 3]
269 if (!this.castleFlags
[c
][castleSide
]) continue;
270 // Rook (or jailer) and king are on initial position
272 const finDist
= finalSquares
[castleSide
][0] - y
;
273 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
277 this.isAttacked([x
, i
], [oppCol
]) ||
278 (this.board
[x
][i
] != V
.EMPTY
&&
279 (this.getColor(x
, i
) != c
||
280 ![V
.KING
, V
.ROOK
].includes(this.getPiece(x
, i
))))
282 continue castlingCheck
;
285 } while (i
!= finalSquares
[castleSide
][0]);
287 step
= castleSide
== 0 ? -1 : 1;
288 const rookOrJailerPos
=
290 ? Math
.min(this.INIT_COL_ROOK
[c
], this.INIT_COL_JAILER
[c
])
291 : Math
.max(this.INIT_COL_ROOK
[c
], this.INIT_COL_JAILER
[c
]);
292 for (i
= y
+ step
; i
!= rookOrJailerPos
; i
+= step
)
293 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
295 // Nothing on final squares, except maybe king and castling rook or jailer?
296 for (i
= 0; i
< 2; i
++) {
298 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
299 this.getPiece(x
, finalSquares
[castleSide
][i
]) != V
.KING
&&
300 finalSquares
[castleSide
][i
] != rookOrJailerPos
302 continue castlingCheck
;
306 // If this code is reached, castle is valid
307 const castlingPiece
= this.getPiece(firstRank
, rookOrJailerPos
);
311 new PiPo({ x: x
, y: finalSquares
[castleSide
][0], p: V
.KING
, c: c
}),
312 new PiPo({ x: x
, y: finalSquares
[castleSide
][1], p: castlingPiece
, c: c
})
315 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
316 new PiPo({ x: x
, y: rookOrJailerPos
, p: castlingPiece
, c: c
})
319 Math
.abs(y
- rookOrJailerPos
) <= 2
320 ? { x: x
, y: rookOrJailerPos
}
321 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
329 updateVariables(move) {
330 super.updateVariables(move);
331 if (this.subTurn
== 2) {
332 // A piece is pushed:
333 // TODO: push array of squares between start and end of move, included
334 // (except if it's a pawn)
335 this.sentryPush
.push([]); //TODO
338 if (move.appear
.length
== 0 && move.vanish
.length
== 1) {
339 // Special sentry move: subTurn <- 2, and then move pushed piece
342 // Else: normal move.
347 move.flags
= JSON
.stringify(this.aggregateFlags());
348 this.epSquares
.push(this.getEpSquare(move));
349 V
.PlayOnBoard(this.board
, move);
350 // TODO: turn changes only if not a sentry push or subTurn == 2
351 //this.turn = V.GetOppCol(this.turn);
353 this.updateVariables(move);
357 this.epSquares
.pop();
358 this.disaggregateFlags(JSON
.parse(move.flags
));
359 V
.UndoOnBoard(this.board
, move);
360 // TODO: here too, take care of turn. If undoing when subTurn == 2,
361 // do not change turn (this shouldn't happen anyway).
362 // ==> normal undo() should be ok.
363 //this.turn = V.GetOppCol(this.turn);
365 this.unupdateVariables(move);
368 static get VALUES() {
369 return Object
.assign(
370 { l: 4.8, s: 2.8, j: 3.8 }, //Jeff K. estimations
376 // Special case "king takes jailer" is a pass move
377 if (move.appear
.length
== 0 && move.vanish
.length
== 0) return "pass";
378 return super.getNotation(move);