a1a98488b23bb989950327137631f7312499a570
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));
138 this.reserve
= { w: null, b: null };
139 if (reserve
[0] > 0) this.reserve
['w'] = { [V
.PAWN
]: reserve
[0] };
140 if (reserve
[1] > 0) this.reserve
['b'] = { [V
.PAWN
]: reserve
[1] };
141 // Local stack of captures during a turn (squares + directions)
142 this.captures
= [ [] ];
145 atLeastOneCaptureFrom([x
, y
], color
, forbiddenStep
) {
146 for (let s
of V
.steps
[V
.BISHOP
]) {
149 (s
[0] != -forbiddenStep
[0] || s
[1] != -forbiddenStep
[1])
151 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
153 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
154 this.board
[i
][j
] != V
.EMPTY
&&
155 this.getColor(i
, j
) != color
&&
156 this.board
[i
+ s
[0]][j
+ s
[1]] == V
.EMPTY
165 atLeastOneCapture(color
) {
166 const L0
= this.captures
.length
;
167 const captures
= this.captures
[L0
- 1];
168 const L
= captures
.length
;
171 this.atLeastOneCaptureFrom(
172 captures
[L
-1].square
, color
, captures
[L
-1].step
)
175 for (let i
= 0; i
< V
.size
.x
; i
++) {
176 for (let j
=0; j
< V
.size
.y
; j
++) {
178 this.board
[i
][j
] != V
.EMPTY
&&
179 this.getColor(i
, j
) == color
&&
180 this.atLeastOneCaptureFrom([i
, j
], color
)
189 maxLengthIndices(caps
) {
192 for (let i
= 0; i
< caps
.length
; i
++) {
193 if (caps
[i
].length
> maxLength
) {
195 maxLength
= caps
[i
].length
;
197 else if (caps
[i
].length
== maxLength
) res
.push(i
);
202 getLongestCaptures_aux([x
, y
], color
, locSteps
) {
204 const L
= locSteps
.length
;
205 const lastStep
= (L
> 0 ? locSteps
[L
-1] : null);
206 for (let s
of V
.steps
[V
.BISHOP
]) {
207 if (!!lastStep
&& s
[0] == -lastStep
[0] && s
[1] == -lastStep
[1]) continue;
208 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
210 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
211 this.board
[i
+ s
[0]][j
+ s
[1]] == V
.EMPTY
&&
212 this.board
[i
][j
] != V
.EMPTY
&&
213 this.getColor(i
, j
) != color
215 const move = this.getBasicMove([x
, y
], [i
+ s
[0], j
+ s
[1]], [i
, j
]);
217 V
.PlayOnBoard(this.board
, move);
219 this.getLongestCaptures_aux([i
+ s
[0], j
+ s
[1]], color
, locSteps
);
220 res
.push(1 + nextRes
);
222 V
.UndoOnBoard(this.board
, move);
225 if (res
.length
== 0) return 0;
226 return Math
.max(...res
);
229 getLongestCapturesFrom([x
, y
], color
, locSteps
) {
231 const L
= locSteps
.length
;
232 const lastStep
= (L
> 0 ? locSteps
[L
-1] : null);
233 for (let s
of V
.steps
[V
.BISHOP
]) {
234 if (!!lastStep
&& s
[0] == -lastStep
[0] && s
[1] == -lastStep
[1]) continue;
235 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
237 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
238 this.board
[i
+ s
[0]][j
+ s
[1]] == V
.EMPTY
&&
239 this.board
[i
][j
] != V
.EMPTY
&&
240 this.getColor(i
, j
) != color
242 const move = this.getBasicMove([x
, y
], [i
+ s
[0], j
+ s
[1]], [i
, j
]);
244 V
.PlayOnBoard(this.board
, move);
246 this.getLongestCaptures_aux([i
+ s
[0], j
+ s
[1]], color
, locSteps
);
247 res
.push({ step: s
, length: 1 + stepRes
});
249 V
.UndoOnBoard(this.board
, move);
252 return this.maxLengthIndices(res
).map(i
=> res
[i
]);;
255 getAllLongestCaptures(color
) {
256 const L0
= this.captures
.length
;
257 const captures
= this.captures
[L0
- 1];
258 const L
= captures
.length
;
261 let locSteps
= [ captures
[L
-1].step
];
263 this.getLongestCapturesFrom(captures
[L
-1].square
, color
, locSteps
);
264 Array
.prototype.push
.apply(
266 res
.map(r
=> Object
.assign({ square: captures
[L
-1].square
}, r
))
270 for (let i
= 0; i
< V
.size
.x
; i
++) {
271 for (let j
=0; j
< V
.size
.y
; j
++) {
273 this.board
[i
][j
] != V
.EMPTY
&&
274 this.getColor(i
, j
) == color
277 let res
= this.getLongestCapturesFrom([i
, j
], color
, locSteps
);
278 Array
.prototype.push
.apply(
280 res
.map(r
=> Object
.assign({ square: [i
, j
] }, r
))
286 return this.maxLengthIndices(caps
).map(i
=> caps
[i
]);
289 getBasicMove([x1
, y1
], [x2
, y2
], capt
) {
290 const cp1
= this.board
[x1
][y1
];
293 appear: [ new PiPo({ x: x2
, y: y2
, c: cp1
[0], p: cp1
[1] }) ],
294 vanish: [ new PiPo({ x: x1
, y: y1
, c: cp1
[0], p: cp1
[1] }) ]
297 // Compute resulting types based on jumped + jumping pieces
298 const color
= this.getColor(x1
, y1
);
299 const firstCodes
= (color
== 'w' ? [65, 97] : [97, 65]);
300 const cpCapt
= this.board
[capt
[0]][capt
[1]];
301 let count1
= [cp1
.charCodeAt(0) - firstCodes
[0], -1];
302 if (cp1
[1] != '@') count1
[1] = cp1
.charCodeAt(1) - firstCodes
[0];
303 let countC
= [cpCapt
.charCodeAt(0) - firstCodes
[1], -1];
304 if (cpCapt
[1] != '@') countC
[1] = cpCapt
.charCodeAt(1) - firstCodes
[1];
307 let colorChange
= false,
310 if (countC
[1] >= 0) {
312 countC
= [countC
[1], -1];
314 else captVanish
= true;
316 const incPrisoners
= String
.fromCharCode(firstCodes
[0] + count1
[1]);
327 new PiPo({ x: x1
, y: y1
, c: cp1
[0], p: cp1
[1] }),
328 new PiPo({ x: capt
[0], y: capt
[1], c: cpCapt
[0], p: cpCapt
[1] })
336 c: String
.fromCharCode(
337 firstCodes
[(colorChange
? 0 : 1)] + countC
[0]),
338 p: (colorChange
? '@' : cpCapt
[1]),
346 const color
= this.turn
;
347 if (!this.reserve
[color
] || this.atLeastOneCapture(color
)) return [];
350 this.reserve
[V
.GetOppCol(color
)] == null
351 ? this.reserve
[color
][V
.PAWN
] - 1
353 const appearColor
= String
.fromCharCode(
354 (color
== 'w' ? 'A' : 'a').charCodeAt(0) + shadowPiece
);
355 const addMove
= ([i
, j
]) => {
358 appear: [ new PiPo({ x: i
, y: j
, c: appearColor
, p: '@' }) ],
360 start: { x: V
.size
.x
+ (color
== 'w' ? 0 : 1), y: 0 }
364 const oppCol
= V
.GetOppCol(color
);
365 const opponentCanCapture
= this.atLeastOneCapture(oppCol
);
366 for (let i
= 0; i
< V
.size
.x
; i
++) {
367 for (let j
= i
% 2; j
< V
.size
.y
; j
+= 2) {
369 this.board
[i
][j
] == V
.EMPTY
&&
370 // prevent playing on central square at move 1:
371 (this.movesCount
>= 1 || i
!= 4 || j
!= 4)
373 if (opponentCanCapture
) addMove([i
, j
]);
375 let canAddMove
= true;
376 for (let s
of V
.steps
[V
.BISHOP
]) {
378 V
.OnBoard(i
+ s
[0], j
+ s
[1]) &&
379 V
.OnBoard(i
- s
[0], j
- s
[1]) &&
380 this.board
[i
+ s
[0]][j
+ s
[1]] != V
.EMPTY
&&
381 this.board
[i
- s
[0]][j
- s
[1]] == V
.EMPTY
&&
382 this.getColor(i
+ s
[0], j
+ s
[1]) == oppCol
388 if (canAddMove
) addMove([i
, j
]);
396 getPotentialMovesFrom([x
, y
], longestCaptures
) {
398 if (longestCaptures
.length
== 0) return this.getReserveMoves(x
);
401 const color
= this.turn
;
402 if (!!this.reserve
[color
] && !this.atLeastOneCapture(color
)) return [];
403 const L0
= this.captures
.length
;
404 const captures
= this.captures
[L0
- 1];
405 const L
= captures
.length
;
407 if (longestCaptures
.length
> 0) {
410 (x
!= captures
[L
-1].square
[0] || y
!= captures
[L
-1].square
[1])
414 longestCaptures
.forEach(lc
=> {
415 if (lc
.square
[0] == x
&& lc
.square
[1] == y
) {
417 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
418 moves
.push(this.getBasicMove([x
, y
], [i
+ s
[0], j
+ s
[1]], [i
, j
]));
423 // Just search simple moves:
424 for (let s
of V
.steps
[V
.BISHOP
]) {
425 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
426 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
427 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
433 const color
= this.turn
;
434 const longestCaptures
= this.getAllLongestCaptures(color
);
435 let potentialMoves
= [];
436 for (let i
= 0; i
< V
.size
.x
; i
++) {
437 for (let j
= 0; j
< V
.size
.y
; j
++) {
438 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
439 Array
.prototype.push
.apply(
441 this.getPotentialMovesFrom([i
, j
], longestCaptures
)
447 potentialMoves
= potentialMoves
.concat(
448 this.getReserveMoves(V
.size
.x
+ (color
== "w" ? 0 : 1))
450 return potentialMoves
;
453 getPossibleMovesFrom([x
, y
]) {
454 const longestCaptures
= this.getAllLongestCaptures(this.getColor(x
, y
));
455 return this.getPotentialMovesFrom([x
, y
], longestCaptures
);
467 const color
= this.turn
;
468 move.turn
= color
; //for undo
469 V
.PlayOnBoard(this.board
, move);
470 if (move.vanish
.length
== 2) {
471 const L0
= this.captures
.length
;
472 let captures
= this.captures
[L0
- 1];
474 square: [move.end
.x
, move.end
.y
],
475 step: [(move.end
.x
- move.start
.x
)/2, (move.end
.y
- move.start
.y
)/2]
477 if (this.atLeastOneCapture(color
))
478 // There could be other captures (mandatory)
479 move.notTheEnd
= true;
481 else if (move.vanish
== 0) {
482 const firstCode
= (color
== 'w' ? 65 : 97);
483 // Generally, reserveCount == 1 (except for shadow piece)
484 const reserveCount
= move.appear
[0].c
.charCodeAt() - firstCode
+ 1;
485 this.reserve
[color
][V
.PAWN
] -= reserveCount
;
486 if (this.reserve
[color
][V
.PAWN
] == 0) this.reserve
[color
] = null;
488 if (!move.notTheEnd
) {
489 this.turn
= V
.GetOppCol(color
);
491 this.captures
.push([]);
496 V
.UndoOnBoard(this.board
, move);
497 if (!move.notTheEnd
) {
498 this.turn
= move.turn
;
502 if (move.vanish
.length
== 0) {
503 const color
= (move.appear
[0].c
== 'A' ? 'w' : 'b');
504 const firstCode
= (color
== 'w' ? 65 : 97);
505 const reserveCount
= move.appear
[0].c
.charCodeAt() - firstCode
+ 1;
506 if (!this.reserve
[color
]) this.reserve
[color
] = { [V
.PAWN
]: 0 };
507 this.reserve
[color
][V
.PAWN
] += reserveCount
;
509 else if (move.vanish
.length
== 2) {
510 const L0
= this.captures
.length
;
511 let captures
= this.captures
[L0
- 1];
517 const color
= this.turn
;
518 if (this.atLeastOneCapture(color
)) return true;
519 for (let i
= 0; i
< V
.size
.x
; i
++) {
520 for (let j
= 0; j
< V
.size
.y
; j
++) {
521 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
522 const moves
= this.getPotentialMovesFrom([i
, j
], []);
523 if (moves
.length
> 0) return true;
528 this.getReserveMoves(V
.size
.x
+ (this.turn
== "w" ? 0 : 1));
529 return (reserveMoves
.length
> 0);
533 const color
= this.turn
;
534 // If no pieces on board + reserve, I lose
535 if (!!this.reserve
[color
]) return "*";
536 let atLeastOnePiece
= false;
537 outerLoop: for (let i
=0; i
< V
.size
.x
; i
++) {
538 for (let j
=0; j
< V
.size
.y
; j
++) {
539 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
540 atLeastOnePiece
= true;
545 if (!atLeastOnePiece
) return (color
== 'w' ? "0-1" : "1-0");
546 if (!this.atLeastOneMove()) return "1/2";
551 // Random mover for now (TODO)
552 const color
= this.turn
;
555 while (this.turn
== color
) {
556 const moves
= this.getAllValidMoves();
557 mv
= moves
[randInt(moves
.length
)];
561 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
562 return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
566 if (move.vanish
.length
== 0) return "@" + V
.CoordsToSquare(move.end
);
567 const L0
= this.captures
.length
;
568 if (this.captures
[L0
- 1].length
> 0) return V
.CoordsToSquare(move.end
);
569 return V
.CoordsToSquare(move.start
) + V
.CoordsToSquare(move.end
);