Commit | Line | Data |
---|---|---|
32cfcea4 BA |
1 | class UltimaRules extends ChessRules |
2 | { | |
2eef6db6 BA |
3 | static getPpath(b) |
4 | { | |
5 | if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1) | |
6 | return "Ultima/" + b; | |
7 | return b; //usual piece | |
8 | } | |
32cfcea4 | 9 | |
2eef6db6 BA |
10 | initVariables(fen) |
11 | { | |
12 | this.kingPos = {'w':[-1,-1], 'b':[-1,-1]}; | |
13 | const fenParts = fen.split(" "); | |
14 | const position = fenParts[0].split("/"); | |
15 | for (let i=0; i<position.length; i++) | |
16 | { | |
17 | let k = 0; | |
18 | for (let j=0; j<position[i].length; j++) | |
19 | { | |
20 | switch (position[i].charAt(j)) | |
21 | { | |
22 | case 'k': | |
23 | this.kingPos['b'] = [i,k]; | |
24 | break; | |
25 | case 'K': | |
26 | this.kingPos['w'] = [i,k]; | |
27 | break; | |
28 | default: | |
29 | let num = parseInt(position[i].charAt(j)); | |
30 | if (!isNaN(num)) | |
31 | k += (num-1); | |
32 | } | |
33 | k++; | |
34 | } | |
35 | } | |
36 | this.epSquares = []; //no en-passant here | |
37 | } | |
38 | ||
39 | setFlags(fen) | |
40 | { | |
41 | // TODO: for compatibility? | |
42 | this.castleFlags = {"w":[false,false], "b":[false,false]}; | |
43 | } | |
44 | ||
45 | static get IMMOBILIZER() { return 'm'; } | |
46 | // Although other pieces keep their names here for coding simplicity, | |
47 | // keep in mind that: | |
48 | // - a "rook" is a coordinator, capturing by coordinating with the king | |
49 | // - a "knight" is a long-leaper, capturing as in draughts | |
50 | // - a "bishop" is a chameleon, capturing as its prey | |
51 | // - a "queen" is a withdrawer, capturing by moving away from pieces | |
52 | ||
53 | getPotentialMovesFrom([x,y]) | |
54 | { | |
7688bf77 BA |
55 | // Pre-check: is thing on this square immobilized? |
56 | // In this case add potential suicide as a move "taking the immobilizer" | |
57 | const piece = this.getPiece(x,y); | |
58 | const color = this.getColor(x,y); | |
59 | const oppCol = this.getOppCol(color); | |
60 | const V = VariantRules; | |
61 | const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); | |
62 | const [sizeX,sizeY] = V.size; | |
63 | for (let step of adjacentSteps) | |
64 | { | |
65 | const [i,j] = [x+step[0],y+step[1]]; | |
66 | if (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j] != V.EMPTY | |
67 | && this.getColor(i,j) == oppCol) | |
68 | { | |
69 | const oppPiece = this.getPiece(i,j); | |
70 | if (oppPiece == V.IMMOBILIZER | |
71 | || (oppPiece == V.BISHOP && piece == V.IMMOBILIZER)) | |
72 | { | |
73 | return [ new Move({ | |
74 | appear: [], | |
75 | vanish: [{x:x,y:y,p:piece,c:color}], | |
76 | start: {x:x,y:y}, | |
77 | end: {x:i,y:j} | |
78 | }) ]; | |
79 | } | |
80 | } | |
81 | } | |
2eef6db6 BA |
82 | switch (this.getPiece(x,y)) |
83 | { | |
84 | case VariantRules.IMMOBILIZER: | |
85 | return this.getPotentialImmobilizerMoves([x,y]); | |
86 | default: | |
87 | return super.getPotentialMovesFrom([x,y]); | |
88 | } | |
2eef6db6 BA |
89 | } |
90 | ||
91 | getSlideNJumpMoves([x,y], steps, oneStep) | |
92 | { | |
93 | const color = this.getColor(x,y); | |
94 | const piece = this.getPiece(x,y); | |
95 | let moves = []; | |
96 | const [sizeX,sizeY] = VariantRules.size; | |
97 | outerLoop: | |
98 | for (let step of steps) | |
99 | { | |
100 | let i = x + step[0]; | |
101 | let j = y + step[1]; | |
102 | while (i>=0 && i<sizeX && j>=0 && j<sizeY | |
103 | && this.board[i][j] == VariantRules.EMPTY) | |
104 | { | |
105 | moves.push(this.getBasicMove([x,y], [i,j])); | |
106 | if (oneStep !== undefined) | |
107 | continue outerLoop; | |
108 | i += step[0]; | |
109 | j += step[1]; | |
110 | } | |
111 | // Only king can take on occupied square: | |
112 | if (piece==VariantRules.KING && i>=0 && i<sizeX && j>=0 | |
113 | && j<sizeY && this.canTake([x,y], [i,j])) | |
114 | { | |
115 | moves.push(this.getBasicMove([x,y], [i,j])); | |
116 | } | |
117 | } | |
118 | return moves; | |
119 | } | |
120 | ||
7688bf77 | 121 | // "Pincher" |
2eef6db6 BA |
122 | getPotentialPawnMoves([x,y]) |
123 | { | |
7688bf77 BA |
124 | let moves = super.getPotentialRookMoves([x,y]); |
125 | // Add captures | |
126 | moves.forEach(m => { | |
127 | if (m | |
128 | }); | |
2eef6db6 BA |
129 | } |
130 | ||
7688bf77 | 131 | // Coordinator |
2eef6db6 BA |
132 | getPotentialRookMoves(sq) |
133 | { | |
7688bf77 BA |
134 | const color = this.getColor(sq); |
135 | const oppCol = this.getOppCol(color); | |
136 | const kp = this.kingPos[color]; | |
137 | let moves = super.getPotentialQueenMoves(sq); | |
138 | moves.forEach(m => { | |
139 | // Check piece-king rectangle (if any) corners for enemy pieces | |
140 | if (m.end.x == kp[0] || m.end.y == kp[1]) | |
141 | return; //"flat rectangle" | |
142 | const corner1 = [Math.max(m.end.x,kp[0]), Math.min(m.end.y,kp[1])]; | |
143 | const corner2 = [Math.min(m.end.x,kp[0]), Math.max(m.end.y,kp[1])]; | |
144 | for (let [i,j] of [corner1,corner2]) | |
145 | { | |
146 | if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) == oppCol) | |
147 | { | |
148 | m.vanish.push( new PiPo({ | |
149 | x:i, | |
150 | y:j, | |
151 | p:this.getPiece(i,j), | |
152 | c:oppCol | |
153 | }) ); | |
154 | } | |
155 | } | |
156 | }); | |
157 | return moves; | |
2eef6db6 BA |
158 | } |
159 | ||
7688bf77 BA |
160 | // Long-leaper |
161 | getPotentialKnightMoves([x,y]) | |
2eef6db6 | 162 | { |
7688bf77 BA |
163 | let moves = super.getPotentialQueenMoves(sq); |
164 | // Look in every direction for captures | |
165 | const V = VariantRules; | |
166 | const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); | |
167 | const [sizeX,sizeY] = V.size; | |
168 | const color = this.getColor(x,y); | |
169 | for (let step of steps) | |
170 | { | |
171 | let [i,j] = [x+step[0], y+step[1]]; | |
172 | while (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j]==V.EMPTY) | |
173 | { | |
174 | i += step[0]; | |
175 | j += step[1]; | |
176 | } | |
177 | if (i<0 && i>=sizeX || j<0 || j>=sizeY || this.getColor(i,j)==color) | |
178 | continue; | |
179 | // Found an enemy piece: potential capture (if empty space behind) | |
180 | // So, while we find enemy pieces + space in this direction, add captures! | |
181 | i += step[0]; | |
182 | j += step[1]; | |
183 | while ( ) //TODO: finish........ | |
184 | } | |
185 | return moves; | |
2eef6db6 BA |
186 | } |
187 | ||
188 | getPotentialBishopMoves(sq) | |
189 | { | |
190 | return super.getPotentialQueenMoves(sq); | |
7688bf77 | 191 | // TODO: add captures of coordinators,pinchers,withdrawers... by re-using code |
2eef6db6 BA |
192 | } |
193 | ||
7688bf77 | 194 | getPotentialQueenMoves([x,y]) |
2eef6db6 | 195 | { |
7688bf77 BA |
196 | let moves = super.getPotentialQueenMoves(sq); |
197 | const V = VariantRules; | |
198 | const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); | |
199 | let capturingDirections = []; | |
200 | const color = this.getColor(x,y); | |
201 | const oppCol = this.getOppCol(color); | |
202 | adjacentSteps.forEach(step => { | |
203 | const [i,j] = [x+step[0],y+step[1]]; | |
204 | if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol) | |
205 | capturingDirections.push(step); | |
206 | }); | |
207 | moves.forEach(m => { | |
208 | const step = [ | |
209 | m.end.x!=x ? (m.end.x-x)/Math.abs(m.end.x-x) : 0, | |
210 | m.end.y!=y ? (m.end.y-y)/Math.abs(m.end.y-y) : 0 | |
211 | ]; | |
212 | // NOTE: includes() function does not work on complex array elements | |
213 | // TODO: this test should be done only once per direction | |
214 | if (capturingDirection.some(dir => _.isEqual(dir, step))) | |
215 | { | |
216 | const [i,j] = [x-step[0],y-step[1]]; | |
217 | m.vanish.push(new PiPo({ | |
218 | x:i, | |
219 | y:j, | |
220 | p:this.getPiece(i,j), | |
221 | c:oppCol | |
222 | })); | |
223 | } | |
224 | }); | |
2eef6db6 BA |
225 | } |
226 | ||
45338cdd BA |
227 | getPotentialImmobilizerMoves(sq) |
228 | { | |
229 | return super.getPotentialQueenMoves(sq); | |
230 | } | |
231 | ||
2eef6db6 BA |
232 | getPotentialKingMoves(sq) |
233 | { | |
234 | const V = VariantRules; | |
235 | return this.getSlideNJumpMoves(sq, | |
236 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); | |
237 | } | |
238 | ||
239 | // isAttacked() is OK because the immobilizer doesn't take | |
240 | ||
241 | isAttackedByPawn([x,y], colors) | |
242 | { | |
243 | // Square (x,y) must be surrounded by two enemy pieces, | |
244 | // and one of them at least should be a pawn | |
245 | return false; | |
246 | } | |
247 | ||
248 | isAttackedByRook(sq, colors) | |
249 | { | |
250 | // Enemy king must be on same file and a rook on same row (or reverse) | |
251 | } | |
252 | ||
253 | isAttackedByKnight(sq, colors) | |
254 | { | |
255 | // Square (x,y) must be on same line as a knight, | |
256 | // and there must be empty square(s) behind. | |
257 | } | |
258 | ||
259 | isAttackedByBishop(sq, colors) | |
260 | { | |
261 | // switch on piece nature on square sq: a chameleon attack as this piece | |
262 | // ==> call the appropriate isAttackedBy... (exception of immobilizers) | |
263 | // Other exception: a chameleon cannot attack a chameleon (seemingly...) | |
264 | } | |
265 | ||
266 | isAttackedByQueen(sq, colors) | |
267 | { | |
268 | // Square (x,y) must be adjacent to a queen, and the queen must have | |
269 | // some free space in the opposite direction from (x,y) | |
270 | } | |
271 | ||
272 | updateVariables(move) | |
273 | { | |
274 | // Just update king position | |
275 | const piece = this.getPiece(move.start.x,move.start.y); | |
276 | const c = this.getColor(move.start.x,move.start.y); | |
277 | if (piece == VariantRules.KING && move.appear.length > 0) | |
278 | { | |
279 | this.kingPos[c][0] = move.appear[0].x; | |
280 | this.kingPos[c][1] = move.appear[0].y; | |
281 | } | |
282 | } | |
283 | ||
284 | static get VALUES() { //TODO: totally experimental! | |
285 | return { | |
286 | 'p': 1, | |
287 | 'r': 2, | |
288 | 'n': 5, | |
289 | 'b': 3, | |
290 | 'q': 3, | |
291 | 'm': 5, | |
292 | 'k': 1000 | |
293 | }; | |
294 | } | |
295 | ||
296 | static get SEARCH_DEPTH() { return 2; } //TODO? | |
297 | ||
298 | static GenRandInitFen() | |
299 | { | |
300 | let pieces = { "w": new Array(8), "b": new Array(8) }; | |
301 | // Shuffle pieces on first and last rank | |
302 | for (let c of ["w","b"]) | |
303 | { | |
304 | let positions = _.range(8); | |
305 | // Get random squares for every piece, totally freely | |
306 | ||
307 | let randIndex = _.random(7); | |
308 | const bishop1Pos = positions[randIndex]; | |
309 | positions.splice(randIndex, 1); | |
310 | ||
311 | randIndex = _.random(6); | |
312 | const bishop2Pos = positions[randIndex]; | |
313 | positions.splice(randIndex, 1); | |
314 | ||
315 | randIndex = _.random(5); | |
316 | const knight1Pos = positions[randIndex]; | |
317 | positions.splice(randIndex, 1); | |
318 | ||
319 | randIndex = _.random(4); | |
320 | const knight2Pos = positions[randIndex]; | |
321 | positions.splice(randIndex, 1); | |
322 | ||
323 | randIndex = _.random(3); | |
324 | const queenPos = positions[randIndex]; | |
325 | positions.splice(randIndex, 1); | |
326 | ||
327 | randIndex = _.random(2); | |
328 | const kingPos = positions[randIndex]; | |
329 | positions.splice(randIndex, 1); | |
330 | ||
331 | randIndex = _.random(1); | |
332 | const rookPos = positions[randIndex]; | |
333 | positions.splice(randIndex, 1); | |
45338cdd | 334 | const immobilizerPos = positions[0]; |
2eef6db6 BA |
335 | |
336 | pieces[c][bishop1Pos] = 'b'; | |
337 | pieces[c][bishop2Pos] = 'b'; | |
338 | pieces[c][knight1Pos] = 'n'; | |
339 | pieces[c][knight2Pos] = 'n'; | |
340 | pieces[c][queenPos] = 'q'; | |
341 | pieces[c][kingPos] = 'k'; | |
342 | pieces[c][rookPos] = 'r'; | |
343 | pieces[c][immobilizerPos] = 'm'; | |
344 | } | |
345 | return pieces["b"].join("") + | |
346 | "/pppppppp/8/8/8/8/PPPPPPPP/" + | |
347 | pieces["w"].join("").toUpperCase() + | |
348 | " 0000"; //TODO: flags?! | |
349 | } | |
350 | ||
351 | getFlagsFen() | |
352 | { | |
353 | return "0000"; //TODO: or "-" ? | |
354 | } | |
32cfcea4 | 355 | } |