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 HasFlags() {
15 static get HasEnpassant() {
19 static get DarkBottomRight() {
23 // board element == file name:
31 static IsGoodPosition(position
) {
32 if (position
.length
== 0) return false;
33 const rows
= position
.split("/");
34 if (rows
.length
!= V
.size
.x
) return false;
35 for (let row
of rows
) {
37 for (let i
= 0; i
< row
.length
; i
++) {
38 // Add only 0.5 per symbol because 2 per piece
39 if (row
[i
].toLowerCase().match(/^[a-lA-L@]$/)) sumElts
+= 0.5;
41 const num
= parseInt(row
[i
], 10);
42 if (isNaN(num
) || num
<= 0) return false;
46 if (sumElts
!= V
.size
.y
) return false;
51 static GetBoard(position
) {
52 const rows
= position
.split("/");
53 let board
= ArrayFun
.init(V
.size
.x
, V
.size
.y
, "");
54 for (let i
= 0; i
< rows
.length
; i
++) {
56 for (let indexInRow
= 0; indexInRow
< rows
[i
].length
; indexInRow
++) {
57 const character
= rows
[i
][indexInRow
];
58 const num
= parseInt(character
, 10);
59 // If num is a number, just shift j:
60 if (!isNaN(num
)) j
+= num
;
62 // Something at position i,j
63 board
[i
][j
++] = V
.fen2board(character
+ rows
[i
][++indexInRow
]);
74 if (x
>= V
.size
.x
) return x
== V
.size
.x
? "w" : "b";
75 if (this.board
[x
][y
].charCodeAt(0) < 97) return 'w';
80 return V
.PAWN
; //unused
83 static IsGoodFen(fen
) {
84 if (!ChessRules
.IsGoodFen(fen
)) return false;
85 const fenParsed
= V
.ParseFen(fen
);
89 !fenParsed
.reserve
.match(/^([0-9]{1,2},?){2,2}$/)
96 static ParseFen(fen
) {
97 const fenParts
= fen
.split(" ");
99 ChessRules
.ParseFen(fen
),
100 { reserve: fenParts
[3] }
105 return { x: 9, y: 9 };
108 static GenRandInitFen(randomness
) {
109 return "9/9/9/9/9/9/9/9/9 w 0 12,12";
113 return super.getFen() + " " + this.getReserveFen();
117 return super.getFenForRepeat() + "_" + this.getReserveFen();
122 (!this.reserve
["w"] ? 0 : this.reserve
["w"][V
.PAWN
]) + "," +
123 (!this.reserve
["b"] ? 0 : this.reserve
["b"][V
.PAWN
])
127 getReservePpath(index
, color
) {
128 return "Emergo/" + (color
== 'w' ? 'A' : 'a') + '@';
131 static get RESERVE_PIECES() {
132 return [V
.PAWN
]; //only array length matters
135 setOtherVariables(fen
) {
137 V
.ParseFen(fen
).reserve
.split(",").map(x
=> parseInt(x
, 10));
139 w: { [V
.PAWN
]: reserve
[0] },
140 b: { [V
.PAWN
]: reserve
[1] }
142 // Local stack of captures during a turn (squares + directions)
143 this.captures
= [ [] ];
146 atLeastOneCaptureFrom([x
, y
], color
) {
147 for (let s
of V
.steps
[V
.BISHOP
]) {
148 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
150 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
151 this.board
[i
][j
] != V
.EMPTY
&&
152 this.getColor(i
, j
) != color
&&
153 this.board
[i
+ s
[0]][j
+ s
[1]] == V
.EMPTY
161 atLeastOneCapture(color
) {
162 const L0
= this.captures
.length
;
163 const captures
= this.captures
[L0
- 1];
164 const L
= captures
.length
;
165 if (L
> 0) return this.atLeastOneCaptureFrom(captures
[L
-1].square
, color
);
166 for (let i
= 0; i
< V
.size
.x
; i
++) {
167 for (let j
=0; j
< V
.size
.y
; j
++) {
169 this.board
[i
][j
] != V
.EMPTY
&&
170 this.getColor(i
, j
) == color
&&
171 this.atLeastOneCaptureFrom([i
, j
], color
)
180 maxLengthIndices(caps
) {
183 for (let i
= 0; i
< caps
.length
; i
++) {
184 if (caps
[i
].length
> maxLength
) {
186 maxLength
= caps
[i
].length
;
188 else if (caps
[i
].length
== maxLength
) res
.push(i
);
193 getLongestCapturesFrom([x
, y
], color
, locSteps
) {
195 // TODO: debug here, from
196 // 9/9/2a@1a@4/5A@3/9/3aa1A@3/9/9/8A@ w 10 8,9
197 // White to move, double capture.
200 const L
= locSteps
.length
;
201 const lastStep
= (L
> 0 ? locSteps
[L
-1] : null);
202 for (let s
of V
.steps
[V
.BISHOP
]) {
203 if (!!lastStep
&& s
[0] == -lastStep
[0] && s
[1] == -lastStep
[1]) continue;
204 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
206 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
207 this.board
[i
+ s
[0]][j
+ s
[1]] == V
.EMPTY
&&
208 this.board
[i
][j
] != V
.EMPTY
&&
209 this.getColor(i
, j
) != color
211 const move = this.getBasicMove([x
, y
], [i
+ s
[0], j
+ s
[1]], [i
, j
]);
213 V
.PlayOnBoard(this.board
, move);
214 const sRes
= this.getLongestCapturesFrom(
215 [i
+ s
[0], j
+ s
[1]], color
, locSteps
);
218 length: 1 + (sRes
.length
== 0 ? 0 : sRes
[0].length
)
221 V
.UndoOnBoard(this.board
, move);
224 return this.maxLengthIndices(res
).map(i
=> res
[i
]);
227 getAllLongestCaptures(color
) {
228 const L0
= this.captures
.length
;
229 const captures
= this.captures
[L0
- 1];
230 const L
= captures
.length
;
233 const caps
= Object
.assign(
234 { square: captures
[L
-1].square
},
235 this.getLongestCapturesFrom(captures
[L
-1].square
, color
, locSteps
)
237 return this.maxLengthIndices(caps
).map(i
=> caps
[i
]);
240 for (let i
= 0; i
< V
.size
.x
; i
++) {
241 for (let j
=0; j
< V
.size
.y
; j
++) {
243 this.board
[i
][j
] != V
.EMPTY
&&
244 this.getColor(i
, j
) == color
247 let res
= this.getLongestCapturesFrom([i
, j
], color
, locSteps
);
248 Array
.prototype.push
.apply(
250 res
.map(r
=> Object
.assign({ square: [i
, j
] }, r
))
258 return this.maxLengthIndices(caps
).map(i
=> caps
[i
]);
261 getBasicMove([x1
, y1
], [x2
, y2
], capt
) {
262 const cp1
= this.board
[x1
][y1
];
265 appear: [ new PiPo({ x: x2
, y: y2
, c: cp1
[0], p: cp1
[1] }) ],
266 vanish: [ new PiPo({ x: x1
, y: y1
, c: cp1
[0], p: cp1
[1] }) ]
269 // Compute resulting types based on jumped + jumping pieces
270 const cpCapt
= this.board
[capt
[0]][capt
[1]];
271 const newAtCapt
= cpCapt
.charCodeAt(0) - 1;
274 ? (cp1
.charCodeAt(0) < 97 ? 65 : 97)
275 : (cp1
.charCodeAt(1) + 1);
276 const color
= this.turn
;
283 p: String
.fromCharCode(newAtDest
)
287 new PiPo({ x: x1
, y: y1
, c: cp1
[0], p: cp1
[1] }),
288 new PiPo({ x: capt
[0], y: capt
[1], c: cpCapt
[0], p: cpCapt
[1] })
291 if ([64, 96].includes(newAtCapt
)) {
292 // Enemy units vanish from capturing square
293 if (cpCapt
.charAt(1) != '@') {
310 c: String
.fromCharCode(newAtCapt
),
319 const color
= this.turn
;
320 if (!this.reserve
[color
] || this.atLeastOneCapture(color
)) return [];
323 this.reserve
[V
.GetOppCol(color
)] == null
324 ? this.reserve
[color
][V
.PAWN
] - 1
326 const appearColor
= String
.fromCharCode(
327 (color
== 'w' ? 'A' : 'a').charCodeAt(0) + shadowPiece
);
328 const addMove
= ([i
, j
]) => {
331 appear: [ new PiPo({ x: i
, y: j
, c: appearColor
, p: '@' }) ],
333 start: { x: V
.size
.x
+ (color
== 'w' ? 0 : 1), y: 0 }
337 const oppCol
= V
.GetOppCol(color
);
338 const opponentCanCapture
= this.atLeastOneCapture(oppCol
);
339 for (let i
= 0; i
< V
.size
.x
; i
++) {
340 for (let j
= i
% 2; j
< V
.size
.y
; j
+= 2) {
342 this.board
[i
][j
] == V
.EMPTY
&&
343 // prevent playing on central square at move 1:
344 (this.movesCount
>= 1 || i
!= 4 || j
!= 4)
346 if (opponentCanCapture
) addMove([i
, j
]);
348 let canAddMove
= true;
349 for (let s
of V
.steps
[V
.BISHOP
]) {
351 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
352 V
.OnBoard(i
- s
[0], j
- s
[1]) &&
353 this.board
[i
+ s
[0]][j
+ s
[1]] != V
.EMPTY
&&
354 this.board
[i
- s
[0]][j
- s
[1]] == V
.EMPTY
&&
355 this.getColor(i
+ s
[0], j
+ s
[1]) == oppCol
361 if (canAddMove
) addMove([i
, j
]);
369 getPotentialMovesFrom([x
, y
], longestCaptures
) {
371 if (longestCaptures
.length
== 0) return this.getReserveMoves(x
);
374 const color
= this.turn
;
375 const L0
= this.captures
.length
;
376 const captures
= this.captures
[L0
- 1];
377 const L
= captures
.length
;
379 if (longestCaptures
.length
> 0) {
382 (x
!= captures
[L
-1].square
[0] || y
!= captures
[L
-1].square
[1])
386 longestCaptures
.forEach(lc
=> {
387 if (lc
.square
[0] == x
&& lc
.square
[1] == y
) {
389 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
390 moves
.push(this.getBasicMove([x
, y
], [i
+ s
[0], j
+ s
[1]], [i
, j
]));
395 // Just search simple moves:
396 for (let s
of V
.steps
[V
.BISHOP
]) {
397 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
398 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
399 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
405 const color
= this.turn
;
406 const longestCaptures
= this.getAllLongestCaptures(color
);
407 let potentialMoves
= [];
408 for (let i
= 0; i
< V
.size
.x
; i
++) {
409 for (let j
= 0; j
< V
.size
.y
; j
++) {
410 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
411 Array
.prototype.push
.apply(
413 this.getPotentialMovesFrom([i
, j
], longestCaptures
)
419 potentialMoves
= potentialMoves
.concat(
420 this.getReserveMoves(V
.size
.x
+ (color
== "w" ? 0 : 1))
422 return potentialMoves
;
425 getPossibleMovesFrom([x
, y
]) {
426 const longestCaptures
= this.getAllLongestCaptures(this.getColor(x
, y
));
427 return this.getPotentialMovesFrom([x
, y
], longestCaptures
);
439 const color
= this.turn
;
440 move.turn
= color
; //for undo
441 V
.PlayOnBoard(this.board
, move);
442 if (move.vanish
.length
== 2) {
443 const L0
= this.captures
.length
;
444 let captures
= this.captures
[L0
- 1];
446 square: [move.start
.x
, move.start
.y
],
447 step: [move.end
.x
- move.start
.x
, move.end
.y
- move.start
.y
]
449 if (this.atLeastOneCapture())
450 // There could be other captures (optional)
451 move.notTheEnd
= true;
453 else if (move.vanish
== 0) {
454 if (--this.reserve
[color
][V
.PAWN
] == 0) this.reserve
[color
] = null;
456 if (!move.notTheEnd
) {
457 this.turn
= V
.GetOppCol(color
);
459 this.captures
.push([]);
464 V
.UndoOnBoard(this.board
, move);
465 if (!move.notTheEnd
) {
466 this.turn
= move.turn
;
470 if (move.vanish
.length
== 0) {
471 const color
= (move.appear
[0].c
== 'A' ? 'w' : 'b');
472 if (!this.reserve
[color
]) this.reserve
[color
] = { [V
.PAWN
]: 1 };
473 else this.reserve
[color
][V
.PAWN
]++;
475 else if (move.vanish
.length
== 2) {
476 const L0
= this.captures
.length
;
477 let captures
= this.captures
[L0
- 1];
483 if (this.atLeastOneCapture()) return true;
484 const color
= this.turn
;
485 for (let i
= 0; i
< V
.size
.x
; i
++) {
486 for (let j
= 0; j
< V
.size
.y
; j
++) {
487 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
488 const moves
= this.getPotentialMovesFrom([i
, j
], []);
489 if (moves
.length
> 0) return true;
494 this.getReserveMoves(V
.size
.x
+ (this.turn
== "w" ? 0 : 1));
495 return (reserveMoves
.length
> 0);
499 const color
= this.turn
;
500 // If no pieces on board + reserve, I lose
502 !this.reserve
[color
] &&
503 this.board
.every(b
=> {
504 return b
.every(cell
=> {
505 return (cell
== "" || cell
[0] != color
);
509 return (color
== 'w' ? "0-1" : "1-0");
511 if (!this.atLeastOneMove()) return "1/2";
516 // Random mover for now (TODO)
517 const color
= this.turn
;
520 while (this.turn
== color
) {
521 const moves
= this.getAllValidMoves();
522 mv
= moves
[randInt(moves
.length
)];
526 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
527 return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
531 if (move.vanish
.length
== 0) return "@" + V
.CoordsToSquare(move.end
);
532 return V
.CoordsToSquare(move.start
) + V
.CoordsToSquare(move.end
);