Commit | Line | Data |
---|---|---|
2a8a94c9 BA |
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 | ||
28b32b4f BA |
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 | ||
2a8a94c9 | 40 | getPpath(b) { |
90e814b6 BA |
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); | |
2a8a94c9 BA |
68 | } |
69 | ||
28b32b4f BA |
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 | |
90e814b6 BA |
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 | } | |
2a8a94c9 BA |
84 | } |
85 | ||
28b32b4f BA |
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]); | |
2a8a94c9 BA |
91 | } |
92 | ||
93 | static GenRandInitFen(randomness) { | |
94 | // TODO: special conditions | |
90e814b6 | 95 | return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 1111 - -"; |
2a8a94c9 BA |
96 | } |
97 | ||
90e814b6 | 98 | // Scan kings, rooks and jailers |
28b32b4f | 99 | scanKingsRooks(fen) { |
90e814b6 BA |
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 }; | |
28b32b4f BA |
103 | this.kingPos = { w: [-1, -1], b: [-1, -1] }; |
104 | const fenRows = V.ParseFen(fen).position.split("/"); | |
90e814b6 | 105 | const startRow = { 'w': V.size.x - 1, 'b': 0 }; |
28b32b4f | 106 | for (let i = 0; i < fenRows.length; i++) { |
90e814b6 | 107 | let k = 0; |
28b32b4f BA |
108 | for (let j = 0; j < fenRows[i].length; j++) { |
109 | switch (fenRows[i].charAt(j)) { | |
110 | case "k": | |
28b32b4f | 111 | this.kingPos["b"] = [i, k]; |
90e814b6 | 112 | this.INIT_COL_KING["b"] = k; |
28b32b4f BA |
113 | break; |
114 | case "K": | |
28b32b4f | 115 | this.kingPos["w"] = [i, k]; |
90e814b6 BA |
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; | |
28b32b4f BA |
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 | } | |
2a8a94c9 BA |
142 | } |
143 | ||
28b32b4f | 144 | getPotentialMovesFrom([x,y]) { |
90e814b6 BA |
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 | |
2a8a94c9 BA |
156 | } |
157 | ||
90e814b6 BA |
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; | |
2a8a94c9 BA |
236 | } |
237 | ||
28b32b4f | 238 | updateVariables(move) { |
90e814b6 BA |
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 | } | |
2a8a94c9 BA |
253 | } |
254 | ||
90e814b6 BA |
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 | } | |
2a8a94c9 | 264 | |
90e814b6 BA |
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 | } | |
2a8a94c9 BA |
276 | |
277 | static get VALUES() { | |
278 | return Object.assign( | |
28b32b4f | 279 | { l: 4.8, s: 2.8, j: 3.8 }, //Jeff K. estimations |
2a8a94c9 BA |
280 | ChessRules.VALUES |
281 | ); | |
282 | } | |
283 | }; |