1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
3 import { ArrayFun
} from "@/utils/array";
5 export class EmergoRules
extends ChessRules
{
7 // Simple encoding: A to L = 1 to 12, from left to right, if white controls.
8 // Lowercase if black controls.
9 // Single piece (no prisoners): A@ to L@ (+ lowercase)
11 static get Options() {
15 static get HasFlags() {
19 static get HasEnpassant() {
23 static get DarkBottomRight() {
27 // board element == file name:
35 static IsGoodPosition(position
) {
36 if (position
.length
== 0) return false;
37 const rows
= position
.split("/");
38 if (rows
.length
!= V
.size
.x
) return false;
39 for (let row
of rows
) {
41 for (let i
= 0; i
< row
.length
; i
++) {
42 // Add only 0.5 per symbol because 2 per piece
43 if (row
[i
].toLowerCase().match(/^[a-lA-L@]$/)) sumElts
+= 0.5;
45 const num
= parseInt(row
[i
], 10);
46 if (isNaN(num
) || num
<= 0) return false;
50 if (sumElts
!= V
.size
.y
) return false;
55 static GetBoard(position
) {
56 const rows
= position
.split("/");
57 let board
= ArrayFun
.init(V
.size
.x
, V
.size
.y
, "");
58 for (let i
= 0; i
< rows
.length
; i
++) {
60 for (let indexInRow
= 0; indexInRow
< rows
[i
].length
; indexInRow
++) {
61 const character
= rows
[i
][indexInRow
];
62 const num
= parseInt(character
, 10);
63 // If num is a number, just shift j:
64 if (!isNaN(num
)) j
+= num
;
66 // Something at position i,j
67 board
[i
][j
++] = V
.fen2board(character
+ rows
[i
][++indexInRow
]);
78 if (x
>= V
.size
.x
) return x
== V
.size
.x
? "w" : "b";
79 if (this.board
[x
][y
].charCodeAt(0) < 97) return 'w';
84 return V
.PAWN
; //unused
87 static IsGoodFen(fen
) {
88 if (!ChessRules
.IsGoodFen(fen
)) return false;
89 const fenParsed
= V
.ParseFen(fen
);
93 !fenParsed
.reserve
.match(/^([0-9]{1,2},?){2,2}$/)
100 static ParseFen(fen
) {
101 const fenParts
= fen
.split(" ");
102 return Object
.assign(
103 ChessRules
.ParseFen(fen
),
104 { reserve: fenParts
[3] }
109 return { x: 9, y: 9 };
112 static GenRandInitFen() {
113 return "9/9/9/9/9/9/9/9/9 w 0 12,12";
117 return super.getFen() + " " + this.getReserveFen();
121 return super.getFenForRepeat() + "_" + this.getReserveFen();
126 (!this.reserve
["w"] ? 0 : this.reserve
["w"][V
.PAWN
]) + "," +
127 (!this.reserve
["b"] ? 0 : this.reserve
["b"][V
.PAWN
])
131 getReservePpath(index
, color
) {
132 return "Emergo/" + (color
== 'w' ? 'A' : 'a') + '@';
135 static get RESERVE_PIECES() {
136 return [V
.PAWN
]; //only array length matters
139 setOtherVariables(fen
) {
141 V
.ParseFen(fen
).reserve
.split(",").map(x
=> parseInt(x
, 10));
142 this.reserve
= { w: null, b: null };
143 if (reserve
[0] > 0) this.reserve
['w'] = { [V
.PAWN
]: reserve
[0] };
144 if (reserve
[1] > 0) this.reserve
['b'] = { [V
.PAWN
]: reserve
[1] };
145 // Local stack of captures during a turn (squares + directions)
146 this.captures
= [ [] ];
149 atLeastOneCaptureFrom([x
, y
], color
, forbiddenStep
) {
150 for (let s
of V
.steps
[V
.BISHOP
]) {
153 (s
[0] != -forbiddenStep
[0] || s
[1] != -forbiddenStep
[1])
155 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
157 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
158 this.board
[i
][j
] != V
.EMPTY
&&
159 this.getColor(i
, j
) != color
&&
160 this.board
[i
+ s
[0]][j
+ s
[1]] == V
.EMPTY
169 atLeastOneCapture(color
) {
170 const L0
= this.captures
.length
;
171 const captures
= this.captures
[L0
- 1];
172 const L
= captures
.length
;
175 this.atLeastOneCaptureFrom(
176 captures
[L
-1].square
, color
, captures
[L
-1].step
)
179 for (let i
= 0; i
< V
.size
.x
; i
++) {
180 for (let j
=0; j
< V
.size
.y
; j
++) {
182 this.board
[i
][j
] != V
.EMPTY
&&
183 this.getColor(i
, j
) == color
&&
184 this.atLeastOneCaptureFrom([i
, j
], color
)
193 maxLengthIndices(caps
) {
196 for (let i
= 0; i
< caps
.length
; i
++) {
197 if (caps
[i
].length
> maxLength
) {
199 maxLength
= caps
[i
].length
;
201 else if (caps
[i
].length
== maxLength
) res
.push(i
);
206 getLongestCaptures_aux([x
, y
], color
, locSteps
) {
208 const L
= locSteps
.length
;
209 const lastStep
= (L
> 0 ? locSteps
[L
-1] : null);
210 for (let s
of V
.steps
[V
.BISHOP
]) {
211 if (!!lastStep
&& s
[0] == -lastStep
[0] && s
[1] == -lastStep
[1]) continue;
212 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
214 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
215 this.board
[i
+ s
[0]][j
+ s
[1]] == V
.EMPTY
&&
216 this.board
[i
][j
] != V
.EMPTY
&&
217 this.getColor(i
, j
) != color
219 const move = this.getBasicMove([x
, y
], [i
+ s
[0], j
+ s
[1]], [i
, j
]);
221 V
.PlayOnBoard(this.board
, move);
223 this.getLongestCaptures_aux([i
+ s
[0], j
+ s
[1]], color
, locSteps
);
224 res
.push(1 + nextRes
);
226 V
.UndoOnBoard(this.board
, move);
229 if (res
.length
== 0) return 0;
230 return Math
.max(...res
);
233 getLongestCapturesFrom([x
, y
], color
, locSteps
) {
235 const L
= locSteps
.length
;
236 const lastStep
= (L
> 0 ? locSteps
[L
-1] : null);
237 for (let s
of V
.steps
[V
.BISHOP
]) {
238 if (!!lastStep
&& s
[0] == -lastStep
[0] && s
[1] == -lastStep
[1]) continue;
239 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
241 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
242 this.board
[i
+ s
[0]][j
+ s
[1]] == V
.EMPTY
&&
243 this.board
[i
][j
] != V
.EMPTY
&&
244 this.getColor(i
, j
) != color
246 const move = this.getBasicMove([x
, y
], [i
+ s
[0], j
+ s
[1]], [i
, j
]);
248 V
.PlayOnBoard(this.board
, move);
250 this.getLongestCaptures_aux([i
+ s
[0], j
+ s
[1]], color
, locSteps
);
251 res
.push({ step: s
, length: 1 + stepRes
});
253 V
.UndoOnBoard(this.board
, move);
256 return this.maxLengthIndices(res
).map(i
=> res
[i
]);;
259 getAllLongestCaptures(color
) {
260 const L0
= this.captures
.length
;
261 const captures
= this.captures
[L0
- 1];
262 const L
= captures
.length
;
265 let locSteps
= [ captures
[L
-1].step
];
267 this.getLongestCapturesFrom(captures
[L
-1].square
, color
, locSteps
);
268 Array
.prototype.push
.apply(
270 res
.map(r
=> Object
.assign({ square: captures
[L
-1].square
}, r
))
274 for (let i
= 0; i
< V
.size
.x
; i
++) {
275 for (let j
=0; j
< V
.size
.y
; j
++) {
277 this.board
[i
][j
] != V
.EMPTY
&&
278 this.getColor(i
, j
) == color
281 let res
= this.getLongestCapturesFrom([i
, j
], color
, locSteps
);
282 Array
.prototype.push
.apply(
284 res
.map(r
=> Object
.assign({ square: [i
, j
] }, r
))
290 return this.maxLengthIndices(caps
).map(i
=> caps
[i
]);
293 getBasicMove([x1
, y1
], [x2
, y2
], capt
) {
294 const cp1
= this.board
[x1
][y1
];
297 appear: [ new PiPo({ x: x2
, y: y2
, c: cp1
[0], p: cp1
[1] }) ],
298 vanish: [ new PiPo({ x: x1
, y: y1
, c: cp1
[0], p: cp1
[1] }) ]
301 // Compute resulting types based on jumped + jumping pieces
302 const color
= this.getColor(x1
, y1
);
303 const firstCodes
= (color
== 'w' ? [65, 97] : [97, 65]);
304 const cpCapt
= this.board
[capt
[0]][capt
[1]];
305 let count1
= [cp1
.charCodeAt(0) - firstCodes
[0], -1];
306 if (cp1
[1] != '@') count1
[1] = cp1
.charCodeAt(1) - firstCodes
[0];
307 let countC
= [cpCapt
.charCodeAt(0) - firstCodes
[1], -1];
308 if (cpCapt
[1] != '@') countC
[1] = cpCapt
.charCodeAt(1) - firstCodes
[1];
311 let colorChange
= false,
314 if (countC
[1] >= 0) {
316 countC
= [countC
[1], -1];
318 else captVanish
= true;
320 const incPrisoners
= String
.fromCharCode(firstCodes
[0] + count1
[1]);
331 new PiPo({ x: x1
, y: y1
, c: cp1
[0], p: cp1
[1] }),
332 new PiPo({ x: capt
[0], y: capt
[1], c: cpCapt
[0], p: cpCapt
[1] })
340 c: String
.fromCharCode(
341 firstCodes
[(colorChange
? 0 : 1)] + countC
[0]),
342 p: (colorChange
? '@' : cpCapt
[1]),
350 const color
= this.turn
;
351 if (!this.reserve
[color
] || this.atLeastOneCapture(color
)) return [];
354 this.reserve
[V
.GetOppCol(color
)] == null
355 ? this.reserve
[color
][V
.PAWN
] - 1
357 const appearColor
= String
.fromCharCode(
358 (color
== 'w' ? 'A' : 'a').charCodeAt(0) + shadowPiece
);
359 const addMove
= ([i
, j
]) => {
362 appear: [ new PiPo({ x: i
, y: j
, c: appearColor
, p: '@' }) ],
364 start: { x: V
.size
.x
+ (color
== 'w' ? 0 : 1), y: 0 }
368 const oppCol
= V
.GetOppCol(color
);
369 const opponentCanCapture
= this.atLeastOneCapture(oppCol
);
370 for (let i
= 0; i
< V
.size
.x
; i
++) {
371 for (let j
= i
% 2; j
< V
.size
.y
; j
+= 2) {
373 this.board
[i
][j
] == V
.EMPTY
&&
374 // prevent playing on central square at move 1:
375 (this.movesCount
>= 1 || i
!= 4 || j
!= 4)
377 if (opponentCanCapture
) addMove([i
, j
]);
379 let canAddMove
= true;
380 for (let s
of V
.steps
[V
.BISHOP
]) {
382 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
383 V
.OnBoard(i
- s
[0], j
- s
[1]) &&
384 this.board
[i
+ s
[0]][j
+ s
[1]] != V
.EMPTY
&&
385 this.board
[i
- s
[0]][j
- s
[1]] == V
.EMPTY
&&
386 this.getColor(i
+ s
[0], j
+ s
[1]) == oppCol
392 if (canAddMove
) addMove([i
, j
]);
400 getPotentialMovesFrom([x
, y
], longestCaptures
) {
402 if (longestCaptures
.length
== 0) return this.getReserveMoves(x
);
405 const color
= this.turn
;
406 if (!!this.reserve
[color
] && !this.atLeastOneCapture(color
)) return [];
407 const L0
= this.captures
.length
;
408 const captures
= this.captures
[L0
- 1];
409 const L
= captures
.length
;
411 if (longestCaptures
.length
> 0) {
414 (x
!= captures
[L
-1].square
[0] || y
!= captures
[L
-1].square
[1])
418 longestCaptures
.forEach(lc
=> {
419 if (lc
.square
[0] == x
&& lc
.square
[1] == y
) {
421 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
422 moves
.push(this.getBasicMove([x
, y
], [i
+ s
[0], j
+ s
[1]], [i
, j
]));
427 // Just search simple moves:
428 for (let s
of V
.steps
[V
.BISHOP
]) {
429 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
430 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
431 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
437 const color
= this.turn
;
438 const longestCaptures
= this.getAllLongestCaptures(color
);
439 let potentialMoves
= [];
440 for (let i
= 0; i
< V
.size
.x
; i
++) {
441 for (let j
= 0; j
< V
.size
.y
; j
++) {
442 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
443 Array
.prototype.push
.apply(
445 this.getPotentialMovesFrom([i
, j
], longestCaptures
)
451 potentialMoves
= potentialMoves
.concat(
452 this.getReserveMoves(V
.size
.x
+ (color
== "w" ? 0 : 1))
454 return potentialMoves
;
457 getPossibleMovesFrom([x
, y
]) {
458 const longestCaptures
= this.getAllLongestCaptures(this.getColor(x
, y
));
459 return this.getPotentialMovesFrom([x
, y
], longestCaptures
);
471 const color
= this.turn
;
472 move.turn
= color
; //for undo
473 V
.PlayOnBoard(this.board
, move);
474 if (move.vanish
.length
== 2) {
475 const L0
= this.captures
.length
;
476 let captures
= this.captures
[L0
- 1];
478 square: [move.end
.x
, move.end
.y
],
479 step: [(move.end
.x
- move.start
.x
)/2, (move.end
.y
- move.start
.y
)/2]
481 if (this.atLeastOneCapture(color
))
482 // There could be other captures (mandatory)
483 move.notTheEnd
= true;
485 else if (move.vanish
== 0) {
486 const firstCode
= (color
== 'w' ? 65 : 97);
487 // Generally, reserveCount == 1 (except for shadow piece)
488 const reserveCount
= move.appear
[0].c
.charCodeAt() - firstCode
+ 1;
489 this.reserve
[color
][V
.PAWN
] -= reserveCount
;
490 if (this.reserve
[color
][V
.PAWN
] == 0) this.reserve
[color
] = null;
492 if (!move.notTheEnd
) {
493 this.turn
= V
.GetOppCol(color
);
495 this.captures
.push([]);
500 V
.UndoOnBoard(this.board
, move);
501 if (!move.notTheEnd
) {
502 this.turn
= move.turn
;
506 if (move.vanish
.length
== 0) {
507 const color
= (move.appear
[0].c
== 'A' ? 'w' : 'b');
508 const firstCode
= (color
== 'w' ? 65 : 97);
509 const reserveCount
= move.appear
[0].c
.charCodeAt() - firstCode
+ 1;
510 if (!this.reserve
[color
]) this.reserve
[color
] = { [V
.PAWN
]: 0 };
511 this.reserve
[color
][V
.PAWN
] += reserveCount
;
513 else if (move.vanish
.length
== 2) {
514 const L0
= this.captures
.length
;
515 let captures
= this.captures
[L0
- 1];
521 const color
= this.turn
;
522 if (this.atLeastOneCapture(color
)) return true;
523 for (let i
= 0; i
< V
.size
.x
; i
++) {
524 for (let j
= 0; j
< V
.size
.y
; j
++) {
525 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
526 const moves
= this.getPotentialMovesFrom([i
, j
], []);
527 if (moves
.length
> 0) return true;
532 this.getReserveMoves(V
.size
.x
+ (this.turn
== "w" ? 0 : 1));
533 return (reserveMoves
.length
> 0);
537 const color
= this.turn
;
538 // If no pieces on board + reserve, I lose
539 if (!!this.reserve
[color
]) return "*";
540 let atLeastOnePiece
= false;
541 outerLoop: for (let i
=0; i
< V
.size
.x
; i
++) {
542 for (let j
=0; j
< V
.size
.y
; j
++) {
543 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
544 atLeastOnePiece
= true;
549 if (!atLeastOnePiece
) return (color
== 'w' ? "0-1" : "1-0");
550 if (!this.atLeastOneMove()) return "1/2";
555 // Random mover for now (TODO)
556 const color
= this.turn
;
559 while (this.turn
== color
) {
560 const moves
= this.getAllValidMoves();
561 mv
= moves
[randInt(moves
.length
)];
565 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
566 return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
570 if (move.vanish
.length
== 0) return "@" + V
.CoordsToSquare(move.end
);
571 const L0
= this.captures
.length
;
572 if (this.captures
[L0
- 1].length
> 0) return V
.CoordsToSquare(move.end
);
573 return V
.CoordsToSquare(move.start
) + V
.CoordsToSquare(move.end
);