6863026e7708916e05ec972a7ed790864456def9
1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class YoteRules
extends ChessRules
{
10 static get HasFlags() {
14 static get HasEnpassant() {
18 static get Monochrome() {
22 static get Notoodark() {
26 static get ReverseColors() {
30 static IsGoodPosition(position
) {
31 if (position
.length
== 0) return false;
32 const rows
= position
.split("/");
33 if (rows
.length
!= V
.size
.x
) return false;
34 for (let row
of rows
) {
36 for (let i
= 0; i
< row
.length
; i
++) {
37 if (row
[i
].toLowerCase() == V
.PAWN
) sumElts
++;
39 const num
= parseInt(row
[i
], 10);
40 if (isNaN(num
) || num
<= 0) return false;
44 if (sumElts
!= V
.size
.y
) return false;
49 static IsGoodFen(fen
) {
50 if (!ChessRules
.IsGoodFen(fen
)) return false;
51 const fenParsed
= V
.ParseFen(fen
);
55 !fenParsed
.reserve
.match(/^([0-9]{1,2},?){2,2}$/)
60 if (!fenParsed
.lastMove
) return false;
61 const lmParts
= fenParsed
.lastMove
.split(",");
63 if (lp
!= "-" && !lp
.match(/^([a-f][1-5]){2,2}$/)) return false;
68 static ParseFen(fen
) {
69 const fenParts
= fen
.split(" ");
71 ChessRules
.ParseFen(fen
),
79 static GenRandInitFen() {
80 return "6/6/6/6/6 w 0 12,12 -,-";
85 super.getFen() + " " +
86 this.getReserveFen() + " " +
92 return super.getFenForRepeat() + "_" + this.getReserveFen();
97 (!this.reserve
["w"] ? 0 : this.reserve
["w"][V
.PAWN
]) + "," +
98 (!this.reserve
["b"] ? 0 : this.reserve
["b"][V
.PAWN
])
103 const L
= this.lastMove
.length
;
104 const lm
= this.lastMove
[L
-1];
109 : V
.CoordsToSquare(lm
['w'].start
) + V
.CoordsToSquare(lm
['w'].end
)
115 : V
.CoordsToSquare(lm
['b'].start
) + V
.CoordsToSquare(lm
['b'].end
)
120 setOtherVariables(fen
) {
121 const fenParsed
= V
.ParseFen(fen
);
122 const reserve
= fenParsed
.reserve
.split(",").map(x
=> parseInt(x
, 10));
124 w: { [V
.PAWN
]: reserve
[0] },
125 b: { [V
.PAWN
]: reserve
[1] }
127 // And last moves (to avoid undoing your last move)
128 const lmParts
= fenParsed
.lastMove
.split(",");
129 this.lastMove
= [{ w: null, b: null }];
130 ['w', 'b'].forEach((c
, i
) => {
131 if (lmParts
[i
] != '-') {
132 this.lastMove
[0][c
] = {
133 start: V
.SquareToCoords(lmParts
[i
].substr(0, 2)),
134 end: V
.SquareToCoords(lmParts
[i
].substr(2))
138 // Local stack to know if (current) last move captured something
139 this.captures
= [false];
143 return { x: 5, y: 6 };
147 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
148 return this.board
[i
][j
].charAt(0);
159 getReservePpath(index
, color
) {
160 return "Yote/" + color
+ V
.PAWN
;
163 static get RESERVE_PIECES() {
167 canIplay(side
, [x
, y
]) {
168 if (this.turn
!= side
) return false;
169 const L
= this.captures
.length
;
170 if (!this.captures
[L
-1]) return this.getColor(x
, y
) == side
;
171 return (x
< V
.size
.x
&& this.getColor(x
, y
) != side
);
174 hoverHighlight([x
, y
], side
) {
175 if (!!side
&& side
!= this.turn
) return false;
176 const L
= this.captures
.length
;
177 if (!this.captures
[L
-1]) return false;
178 const oppCol
= V
.GetOppCol(this.turn
);
179 return (this.board
[x
][y
] != V
.EMPTY
&& this.getColor(x
, y
) == oppCol
);
182 // TODO: onlyClick() doesn't fulfill exactly its role.
183 // Seems that there is some lag... TOFIX
185 const L
= this.captures
.length
;
186 return (this.captures
[L
-1] && this.getColor(x
, y
) != this.turn
);
189 // PATCH related to above TO-DO:
190 getPossibleMovesFrom([x
, y
]) {
191 if (x
< V
.size
.x
&& this.board
[x
][y
] == V
.EMPTY
) return [];
192 return super.getPossibleMovesFrom([x
, y
]);
196 const L
= this.captures
.length
;
197 if (!this.captures
[L
-1]) return null;
198 const oppCol
= V
.GetOppCol(this.turn
);
199 if (this.board
[x
][y
] == V
.EMPTY
|| this.getColor(x
, y
) != oppCol
)
203 vanish: [ new PiPo({ x: x
, y: y
, c: oppCol
, p: V
.PAWN
}) ],
209 const color
= this.turn
;
210 const L
= this.captures
.length
;
212 this.captures
[L
-1] ||
213 !this.reserve
[color
] ||
214 this.reserve
[color
][V
.PAWN
] == 0
219 for (let i
= 0; i
< V
.size
.x
; i
++) {
220 for (let j
= 0; j
< V
.size
.y
; j
++) {
221 if (this.board
[i
][j
] == V
.EMPTY
) {
232 start: { x: x
, y: 0 }, //a bit artificial...
242 getPotentialMovesFrom([x
, y
]) {
243 const L
= this.captures
.length
;
244 if (this.captures
[L
-1]) {
245 if (x
>= V
.size
.x
) return [];
246 const mv
= this.doClick([x
, y
]);
247 return (!!mv
? [mv
] : []);
249 if (x
>= V
.size
.x
) return this.getReserveMoves(x
);
250 return this.getPotentialPawnMoves([x
, y
]);
253 getPotentialPawnMoves([x
, y
]) {
255 const color
= this.turn
;
256 const L
= this.lastMove
.length
;
257 const lm
= this.lastMove
[L
-1];
258 let forbiddenStep
= null;
259 if (!!lm
[color
] && x
== lm
[color
].end
.x
&& y
== lm
[color
].end
.y
) {
261 lm
[color
].start
.x
- lm
[color
].end
.x
,
262 lm
[color
].start
.y
- lm
[color
].end
.y
265 const oppCol
= V
.GetOppCol(color
);
266 for (let s
of V
.steps
[V
.ROOK
]) {
267 const [i1
, j1
] = [x
+ s
[0], y
+ s
[1]];
268 if (V
.OnBoard(i1
, j1
)) {
269 if (this.board
[i1
][j1
] == V
.EMPTY
) {
272 s
[0] != forbiddenStep
[0] ||
273 s
[1] != forbiddenStep
[1]
275 moves
.push(super.getBasicMove([x
, y
], [i1
, j1
]));
278 else if (this.getColor(i1
, j1
) == oppCol
) {
279 const [i2
, j2
] = [i1
+ s
[0], j1
+ s
[1]];
280 if (V
.OnBoard(i2
, j2
) && this.board
[i2
][j2
] == V
.EMPTY
) {
283 new PiPo({ x: i2
, y: j2
, c: color
, p: V
.PAWN
})
286 new PiPo({ x: x
, y: y
, c: color
, p: V
.PAWN
}),
287 new PiPo({ x: i1
, y: j1
, c: oppCol
, p: V
.PAWN
})
298 getAllPotentialMoves() {
299 const L
= this.captures
.length
;
300 const color
= (this.captures
[L
-1] ? V
.GetOppCol(this.turn
) : this.turn
);
301 let potentialMoves
= [];
302 for (let i
= 0; i
< V
.size
.x
; i
++) {
303 for (let j
= 0; j
< V
.size
.y
; j
++) {
304 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
305 Array
.prototype.push
.apply(
307 this.getPotentialMovesFrom([i
, j
])
312 potentialMoves
= potentialMoves
.concat(
313 this.getReserveMoves(V
.size
.x
+ (color
== "w" ? 0 : 1)));
314 return potentialMoves
;
326 if (!super.atLeastOneMove()) {
327 // Search one reserve move
329 this.getReserveMoves(V
.size
.x
+ (this.turn
== "w" ? 0 : 1));
330 if (moves
.length
> 0) return true;
337 const color
= this.turn
;
338 move.turn
= color
; //for undo
339 const L
= this.lastMove
.length
;
341 this.lastMove
.push({ w: null, b: this.lastMove
[L
-1]['b'] });
342 if (move.appear
.length
== move.vanish
.length
) { //== 1
343 // Normal move (non-capturing, non-dropping, non-removal)
344 let lm
= this.lastMove
[L
- (color
== 'w' ? 0 : 1)];
345 if (!lm
[color
]) lm
[color
] = {};
346 lm
[color
].start
= move.start
;
347 lm
[color
].end
= move.end
;
349 const oppCol
= V
.GetOppCol(color
);
350 V
.PlayOnBoard(this.board
, move);
351 const captureNotEnding
= (
352 move.vanish
.length
== 2 &&
353 this.board
.some(b
=> b
.some(cell
=> cell
!= "" && cell
[0] == oppCol
))
355 this.captures
.push(captureNotEnding
);
356 // Change turn unless I just captured something,
357 // and an opponent stone can be removed from board.
358 if (!captureNotEnding
) {
366 V
.UndoOnBoard(this.board
, move);
367 if (this.turn
== 'b') this.lastMove
.pop();
368 else this.lastMove
['b'] = null;
370 if (move.turn
!= this.turn
) {
371 this.turn
= move.turn
;
378 if (move.vanish
.length
== 0) {
379 const color
= move.appear
[0].c
;
380 this.reserve
[color
][V
.PAWN
]--;
381 if (this.reserve
[color
][V
.PAWN
] == 0) delete this.reserve
[color
];
386 if (move.vanish
.length
== 0) {
387 const color
= move.appear
[0].c
;
388 if (!this.reserve
[color
]) this.reserve
[color
] = { [V
.PAWN
]: 0 };
389 this.reserve
[color
][V
.PAWN
]++;
394 if (this.movesCount
<= 2) return "*";
395 const color
= this.turn
;
396 // If no stones on board, or no move available, I lose
398 this.board
.every(b
=> {
399 return b
.every(cell
=> {
400 return (cell
== "" || cell
[0] != color
);
404 !this.atLeastOneMove()
406 return (color
== 'w' ? "0-1" : "1-0");
412 const moves
= super.getAllValidMoves();
413 if (moves
.length
== 0) return null;
414 const color
= this.turn
;
415 const oppCol
= V
.GetOppCol(color
);
416 // Capture available? If yes, play it
417 const captures
= moves
.filter(m
=> m
.vanish
.length
== 2);
418 if (captures
.length
>= 1) {
419 const m1
= captures
[randInt(captures
.length
)];
421 const moves2
= super.getAllValidMoves();
422 // Remove a stone which was about to capture one of ours, if possible
424 for (let m2
of moves2
) {
425 const [x
, y
] = [m2
.start
.x
, m2
.start
.y
];
426 for (let s
of V
.steps
[V
.ROOK
]) {
427 const [i
, j
] = [x
+ 2*s
[0], y
+ 2*s
[1]];
430 this.board
[i
][j
] == V
.EMPTY
&&
431 this.board
[i
- s
[0], j
- s
[1]] != V
.EMPTY
&&
432 this.getColor(i
- s
[0], j
- s
[1]) == color
440 if (candidates
.length
>= 1)
441 return [m1
, candidates
[randInt(candidates
.length
)]];
442 return [m1
, moves2
[randInt(moves2
.length
)]];
444 // Just play a random move, which if possible do not let a capture
446 for (let m
of moves
) {
448 const moves2
= super.getAllValidMoves();
449 if (moves2
.every(m2
=> m2
.vanish
.length
<= 1))
453 if (candidates
.length
>= 1) return candidates
[randInt(candidates
.length
)];
454 return moves
[randInt(moves
.length
)];
458 if (move.vanish
.length
== 0)
460 return "@" + V
.CoordsToSquare(move.end
);
461 if (move.appear
.length
== 0)
462 // Removal after capture:
463 return V
.CoordsToSquare(move.start
) + "X";
465 V
.CoordsToSquare(move.start
) +
466 (move.vanish
.length
== 2 ? "x" : "") +
467 V
.CoordsToSquare(move.end
)