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 sentrypath: fenParts
[5]
54 return super.getFen() + " " + this.getSentrypathFen();
58 return super.getFenForRepeat() + "_" + this.getSentrypathFen();
62 const L
= this.sentryPath
.length
;
63 if (!this.sentryPath
[L
-1]) return "-";
65 this.sentryPath
[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
.sentrypath
== "-") this.sentryPath
= [null];
79 parsedFen
.sentrypath
.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
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 getPotentialMovesFrom([x
,y
]) {
145 // if subturn == 1, normal situation, allow moves except walking back on sentryPath,
146 // if last element isn't null in sentryPath array
147 // if subTurn == 2, allow only the end of the path (occupied by a piece) to move
149 // TODO: special pass move: take jailer with king, only if king immobilized
150 // Move(appear:[], vanish:[], start == king and end = jailer (for animation))
152 // TODO: post-processing if sentryPath forbid some moves.
153 // + add all lancer possible orientations
154 // (except if just after a push: allow all movements from init square then)
155 // Test if last sentryPath ends at our position: if yes, OK
158 // Adapted: castle with jailer possible
159 getCastleMoves([x
, y
]) {
160 const c
= this.getColor(x
, y
);
161 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
162 if (x
!= firstRank
|| y
!= this.INIT_COL_KING
[c
])
165 const oppCol
= V
.GetOppCol(c
);
168 // King, then rook or jailer:
169 const finalSquares
= [
171 [V
.size
.y
- 2, V
.size
.y
- 3]
178 if (!this.castleFlags
[c
][castleSide
]) continue;
179 // Rook (or jailer) and king are on initial position
181 const finDist
= finalSquares
[castleSide
][0] - y
;
182 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
186 this.isAttacked([x
, i
], [oppCol
]) ||
187 (this.board
[x
][i
] != V
.EMPTY
&&
188 (this.getColor(x
, i
) != c
||
189 ![V
.KING
, V
.ROOK
].includes(this.getPiece(x
, i
))))
191 continue castlingCheck
;
194 } while (i
!= finalSquares
[castleSide
][0]);
196 step
= castleSide
== 0 ? -1 : 1;
197 const rookOrJailerPos
=
199 ? Math
.min(this.INIT_COL_ROOK
[c
], this.INIT_COL_JAILER
[c
])
200 : Math
.max(this.INIT_COL_ROOK
[c
], this.INIT_COL_JAILER
[c
]);
201 for (i
= y
+ step
; i
!= rookOrJailerPos
; i
+= step
)
202 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
204 // Nothing on final squares, except maybe king and castling rook or jailer?
205 for (i
= 0; i
< 2; i
++) {
207 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
208 this.getPiece(x
, finalSquares
[castleSide
][i
]) != V
.KING
&&
209 finalSquares
[castleSide
][i
] != rookOrJailerPos
211 continue castlingCheck
;
215 // If this code is reached, castle is valid
216 const castlingPiece
= this.getPiece(firstRank
, rookOrJailerPos
);
220 new PiPo({ x: x
, y: finalSquares
[castleSide
][0], p: V
.KING
, c: c
}),
221 new PiPo({ x: x
, y: finalSquares
[castleSide
][1], p: castlingPiece
, c: c
})
224 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
225 new PiPo({ x: x
, y: rookOrJailerPos
, p: castlingPiece
, c: c
})
228 Math
.abs(y
- rookOrJailerPos
) <= 2
229 ? { x: x
, y: rookOrJailerPos
}
230 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
238 updateVariables(move) {
239 super.updateVariables(move);
240 if (this.subTurn
== 2) {
241 // A piece is pushed:
242 // TODO: push array of squares between start and end of move, included
243 // (except if it's a pawn)
244 this.sentryPath
.push([]); //TODO
247 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
248 // Special sentry move: subTurn <- 2, and then move pushed piece
251 // Else: normal move.
256 move.flags
= JSON
.stringify(this.aggregateFlags());
257 this.epSquares
.push(this.getEpSquare(move));
258 V
.PlayOnBoard(this.board
, move);
259 // TODO: turn changes only if not a sentry push or subTurn == 2
260 //this.turn = V.GetOppCol(this.turn);
262 this.updateVariables(move);
266 this.epSquares
.pop();
267 this.disaggregateFlags(JSON
.parse(move.flags
));
268 V
.UndoOnBoard(this.board
, move);
269 // TODO: here too, take care of turn. If undoing when subTurn == 2,
270 // do not change turn (this shouldn't happen anyway).
271 // ==> normal undo() should be ok.
272 //this.turn = V.GetOppCol(this.turn);
274 this.unupdateVariables(move);
277 static get VALUES() {
278 return Object
.assign(
279 { l: 4.8, s: 2.8, j: 3.8 }, //Jeff K. estimations