Work on Eightpieces draft. Fix Grand deterministic initial position
[vchess.git] / client / src / variants / Eightpieces.js
1 import { ArrayFun } from "@/utils/array";
2 import { randInt, shuffle } from "@/utils/alea";
3 import { ChessRules, PiPo, Move } from "@/base_rules";
4
5 export const VariantRules = class EightpiecesRules extends ChessRules {
6 static get JAILER() {
7 return "j";
8 }
9 static get SENTRY() {
10 return "s";
11 }
12 static get LANCER() {
13 return "l";
14 }
15
16 static get PIECES() {
17 return ChessRules.PIECES.concat([V.JAILER, V.SENTRY, V.LANCER]);
18 }
19
20 static get LANCER_DIRS() {
21 return {
22 'c': [-1, 0], //north
23 'd': [-1, 1], //N-E
24 'e': [0, 1], //east
25 'f': [1, 1], //S-E
26 'g': [1, 0], //south
27 'h': [1, -1], //S-W
28 'm': [0, -1], //west
29 'o': [-1, -1] //N-W
30 };
31 }
32
33 getPiece(i, j) {
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;
37 return piece;
38 }
39
40 getPpath(b) {
41 if ([V.JAILER, V.SENTRY].concat(Object.keys(V.LANCER_DIRS)).includes(b[1]))
42 return "Eightpieces/" + b;
43 return b;
44 }
45
46 static ParseFen(fen) {
47 const fenParts = fen.split(" ");
48 return Object.assign(ChessRules.ParseFen(fen), {
49 sentrypath: fenParts[5]
50 });
51 }
52
53 getFen() {
54 return super.getFen() + " " + this.getSentrypathFen();
55 }
56
57 getFenForRepeat() {
58 return super.getFenForRepeat() + "_" + this.getSentrypathFen();
59 }
60
61 getSentrypathFen() {
62 const L = this.sentryPath.length;
63 if (!this.sentryPath[L-1]) return "-";
64 let res = "";
65 this.sentryPath[L-1].forEach(coords =>
66 res += V.CoordsToSquare(coords) + ",");
67 return res.slice(0, -1);
68 }
69
70 setOtherVariables(fen) {
71 super.setOtherVariables(fen);
72 // subTurn == 2 only when a sentry moved, and is about to push something
73 this.subTurn = 1;
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];
77 else {
78 this.sentryPath = [
79 parsedFen.sentrypath.split(",").map(sq => {
80 return V.SquareToCoords(sq);
81 })
82 ];
83 }
84 }
85
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]);
91 }
92
93 static GenRandInitFen(randomness) {
94 // TODO: special conditions
95 return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 1111 - -";
96 }
97
98 // Scan kings, rooks and jailers
99 scanKingsRooks(fen) {
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++) {
107 let k = 0;
108 for (let j = 0; j < fenRows[i].length; j++) {
109 switch (fenRows[i].charAt(j)) {
110 case "k":
111 this.kingPos["b"] = [i, k];
112 this.INIT_COL_KING["b"] = k;
113 break;
114 case "K":
115 this.kingPos["w"] = [i, k];
116 this.INIT_COL_KING["w"] = k;
117 break;
118 case "r":
119 if (i == startRow['b'] && this.INIT_COL_ROOK["b"] < 0)
120 this.INIT_COL_ROOK["b"] = k;
121 break;
122 case "R":
123 if (i == startRow['w'] && this.INIT_COL_ROOK["w"] < 0)
124 this.INIT_COL_ROOK["w"] = k;
125 break;
126 case "j":
127 if (i == startRow['b'] && this.INIT_COL_JAILER["b"] < 0)
128 this.INIT_COL_JAILER["b"] = k;
129 break;
130 case "J":
131 if (i == startRow['w'] && this.INIT_COL_JAILER["w"] < 0)
132 this.INIT_COL_JAILER["w"] = k;
133 break;
134 default: {
135 const num = parseInt(fenRows[i].charAt(j));
136 if (!isNaN(num)) k += num - 1;
137 }
138 }
139 k++;
140 }
141 }
142 }
143
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
148 //
149 // TODO: special pass move: take jailer with king, only if king immobilized
150 // Move(appear:[], vanish:[], start == king and end = jailer (for animation))
151 //
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
156 }
157
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])
163 return [];
164
165 const oppCol = V.GetOppCol(c);
166 let moves = [];
167 let i = 0;
168 // King, then rook or jailer:
169 const finalSquares = [
170 [2, 3],
171 [V.size.y - 2, V.size.y - 3]
172 ];
173 castlingCheck: for (
174 let castleSide = 0;
175 castleSide < 2;
176 castleSide++
177 ) {
178 if (!this.castleFlags[c][castleSide]) continue;
179 // Rook (or jailer) and king are on initial position
180
181 const finDist = finalSquares[castleSide][0] - y;
182 let step = finDist / Math.max(1, Math.abs(finDist));
183 i = y;
184 do {
185 if (
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))))
190 ) {
191 continue castlingCheck;
192 }
193 i += step;
194 } while (i != finalSquares[castleSide][0]);
195
196 step = castleSide == 0 ? -1 : 1;
197 const rookOrJailerPos =
198 castleSide == 0
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;
203
204 // Nothing on final squares, except maybe king and castling rook or jailer?
205 for (i = 0; i < 2; i++) {
206 if (
207 this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
208 this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
209 finalSquares[castleSide][i] != rookOrJailerPos
210 ) {
211 continue castlingCheck;
212 }
213 }
214
215 // If this code is reached, castle is valid
216 const castlingPiece = this.getPiece(firstRank, rookOrJailerPos);
217 moves.push(
218 new Move({
219 appear: [
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 })
222 ],
223 vanish: [
224 new PiPo({ x: x, y: y, p: V.KING, c: c }),
225 new PiPo({ x: x, y: rookOrJailerPos, p: castlingPiece, c: c })
226 ],
227 end:
228 Math.abs(y - rookOrJailerPos) <= 2
229 ? { x: x, y: rookOrJailerPos }
230 : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
231 })
232 );
233 }
234
235 return moves;
236 }
237
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
245 this.subTurn = 1;
246 } else {
247 if (move.appear.length == 0 && move.vanish.length == 0) {
248 // Special sentry move: subTurn <- 2, and then move pushed piece
249 this.subTurn = 2;
250 }
251 // Else: normal move.
252 }
253 }
254
255 play(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);
261 this.movesCount++;
262 this.updateVariables(move);
263 }
264
265 undo(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);
273 this.movesCount--;
274 this.unupdateVariables(move);
275 }
276
277 static get VALUES() {
278 return Object.assign(
279 { l: 4.8, s: 2.8, j: 3.8 }, //Jeff K. estimations
280 ChessRules.VALUES
281 );
282 }
283 };