1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class YoteRules
extends ChessRules
{
6 static get HasFlags() {
10 static get HasEnpassant() {
14 static get Monochrome() {
18 static get Notoodark() {
22 static get ReverseColors() {
26 static IsGoodPosition(position
) {
27 if (position
.length
== 0) return false;
28 const rows
= position
.split("/");
29 if (rows
.length
!= V
.size
.x
) return false;
30 for (let row
of rows
) {
32 for (let i
= 0; i
< row
.length
; i
++) {
33 if (row
[i
].toLowerCase() == V
.PAWN
) sumElts
++;
35 const num
= parseInt(row
[i
], 10);
36 if (isNaN(num
) || num
<= 0) return false;
40 if (sumElts
!= V
.size
.y
) return false;
45 static IsGoodFen(fen
) {
46 if (!ChessRules
.IsGoodFen(fen
)) return false;
47 const fenParsed
= V
.ParseFen(fen
);
51 !fenParsed
.reserve
.match(/^([0-9]{1,2},?){2,2}$/)
56 if (!fenParsed
.lastMove
) return false;
57 const lmParts
= fenParsed
.lastMove
.split(",");
59 if (lp
!= "-" && !lp
.match(/^([a-f][1-5]){2,2}$/)) return false;
64 static ParseFen(fen
) {
65 const fenParts
= fen
.split(" ");
67 ChessRules
.ParseFen(fen
),
75 static GenRandInitFen(randomness
) {
76 return "6/6/6/6/6 w 0 12,12 -,-";
81 super.getFen() + " " +
82 this.getReserveFen() + " " +
88 return super.getFenForRepeat() + "_" + this.getReserveFen();
93 (!this.reserve
["w"] ? 0 : this.reserve
["w"][V
.PAWN
]) + "," +
94 (!this.reserve
["b"] ? 0 : this.reserve
["b"][V
.PAWN
])
99 const L
= this.lastMove
.length
;
100 const lm
= this.lastMove
[L
-1];
105 : V
.CoordsToSquare(lm
['w'].start
) + V
.CoordsToSquare(lm
['w'].end
)
111 : V
.CoordsToSquare(lm
['b'].start
) + V
.CoordsToSquare(lm
['b'].end
)
116 setOtherVariables(fen
) {
117 const fenParsed
= V
.ParseFen(fen
);
118 const reserve
= fenParsed
.reserve
.split(",").map(x
=> parseInt(x
, 10));
120 w: { [V
.PAWN
]: reserve
[0] },
121 b: { [V
.PAWN
]: reserve
[1] }
123 // And last moves (to avoid undoing your last move)
124 const lmParts
= fenParsed
.lastMove
.split(",");
125 this.lastMove
= [{ w: null, b: null }];
126 ['w', 'b'].forEach((c
, i
) => {
127 if (lmParts
[i
] != '-') {
128 this.lastMove
[0][c
] = {
129 start: V
.SquareToCoords(lmParts
[i
].substr(0, 2)),
130 end: V
.SquareToCoords(lmParts
[i
].substr(2))
134 // Local stack to know if (current) last move captured something
135 this.captures
= [false];
139 return { x: 5, y: 6 };
143 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
144 return this.board
[i
][j
].charAt(0);
155 getReservePpath(index
, color
) {
156 return "Yote/" + color
+ V
.PAWN
;
159 static get RESERVE_PIECES() {
163 canIplay(side
, [x
, y
]) {
164 if (this.turn
!= side
) return false;
165 const L
= this.captures
.length
;
166 if (!this.captures
[L
-1]) return this.getColor(x
, y
) == side
;
167 return (x
< V
.size
.x
&& this.getColor(x
, y
) != side
);
170 hoverHighlight([x
, y
], side
) {
171 if (!!side
&& side
!= this.turn
) return false;
172 const L
= this.captures
.length
;
173 if (!this.captures
[L
-1]) return false;
174 const oppCol
= V
.GetOppCol(this.turn
);
175 return (this.board
[x
][y
] != V
.EMPTY
&& this.getColor(x
, y
) == oppCol
);
178 // TODO: onlyClick() doesn't fulfill exactly its role.
179 // Seems that there is some lag... TOFIX
181 const L
= this.captures
.length
;
182 return (this.captures
[L
-1] && this.getColor(x
, y
) != this.turn
);
185 // PATCH related to above TO-DO:
186 getPossibleMovesFrom([x
, y
]) {
187 if (x
< V
.size
.x
&& this.board
[x
][y
] == V
.EMPTY
) return [];
188 return super.getPossibleMovesFrom([x
, y
]);
192 const L
= this.captures
.length
;
193 if (!this.captures
[L
-1]) return null;
194 const oppCol
= V
.GetOppCol(this.turn
);
195 if (this.board
[x
][y
] == V
.EMPTY
|| this.getColor(x
, y
) != oppCol
)
199 vanish: [ new PiPo({ x: x
, y: y
, c: oppCol
, p: V
.PAWN
}) ],
205 const color
= this.turn
;
206 const L
= this.captures
.length
;
208 this.captures
[L
-1] ||
209 !this.reserve
[color
] ||
210 this.reserve
[color
][V
.PAWN
] == 0
215 for (let i
= 0; i
< V
.size
.x
; i
++) {
216 for (let j
= 0; j
< V
.size
.y
; j
++) {
217 if (this.board
[i
][j
] == V
.EMPTY
) {
228 start: { x: x
, y: 0 }, //a bit artificial...
238 getPotentialMovesFrom([x
, y
]) {
239 const L
= this.captures
.length
;
240 if (this.captures
[L
-1]) {
241 if (x
>= V
.size
.x
) return [];
242 const mv
= this.doClick([x
, y
]);
243 return (!!mv
? [mv
] : []);
245 if (x
>= V
.size
.x
) return this.getReserveMoves(x
);
246 return this.getPotentialPawnMoves([x
, y
]);
249 getPotentialPawnMoves([x
, y
]) {
251 const color
= this.turn
;
252 const L
= this.lastMove
.length
;
253 const lm
= this.lastMove
[L
-1];
254 let forbiddenStep
= null;
255 if (!!lm
[color
] && x
== lm
[color
].end
.x
&& y
== lm
[color
].end
.y
) {
257 lm
[color
].start
.x
- lm
[color
].end
.x
,
258 lm
[color
].start
.y
- lm
[color
].end
.y
261 const oppCol
= V
.GetOppCol(color
);
262 for (let s
of V
.steps
[V
.ROOK
]) {
263 const [i1
, j1
] = [x
+ s
[0], y
+ s
[1]];
264 if (V
.OnBoard(i1
, j1
)) {
265 if (this.board
[i1
][j1
] == V
.EMPTY
) {
268 s
[0] != forbiddenStep
[0] ||
269 s
[1] != forbiddenStep
[1]
271 moves
.push(super.getBasicMove([x
, y
], [i1
, j1
]));
274 else if (this.getColor(i1
, j1
) == oppCol
) {
275 const [i2
, j2
] = [i1
+ s
[0], j1
+ s
[1]];
276 if (V
.OnBoard(i2
, j2
) && this.board
[i2
][j2
] == V
.EMPTY
) {
279 new PiPo({ x: i2
, y: j2
, c: color
, p: V
.PAWN
})
282 new PiPo({ x: x
, y: y
, c: color
, p: V
.PAWN
}),
283 new PiPo({ x: i1
, y: j1
, c: oppCol
, p: V
.PAWN
})
294 getAllPotentialMoves() {
295 const L
= this.captures
.length
;
296 const color
= (this.captures
[L
-1] ? V
.GetOppCol(this.turn
) : this.turn
);
297 let potentialMoves
= [];
298 for (let i
= 0; i
< V
.size
.x
; i
++) {
299 for (let j
= 0; j
< V
.size
.y
; j
++) {
300 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
301 Array
.prototype.push
.apply(
303 this.getPotentialMovesFrom([i
, j
])
308 potentialMoves
= potentialMoves
.concat(
309 this.getReserveMoves(V
.size
.x
+ (color
== "w" ? 0 : 1)));
310 return potentialMoves
;
322 if (!super.atLeastOneMove()) {
323 // Search one reserve move
325 this.getReserveMoves(V
.size
.x
+ (this.turn
== "w" ? 0 : 1));
326 if (moves
.length
> 0) return true;
333 const color
= this.turn
;
334 move.turn
= color
; //for undo
335 const L
= this.lastMove
.length
;
337 this.lastMove
.push({ w: null, b: this.lastMove
[L
-1]['b'] });
338 if (move.appear
.length
== move.vanish
.length
) { //== 1
339 // Normal move (non-capturing, non-dropping, non-removal)
340 let lm
= this.lastMove
[L
- (color
== 'w' ? 0 : 1)];
341 if (!lm
[color
]) lm
[color
] = {};
342 lm
[color
].start
= move.start
;
343 lm
[color
].end
= move.end
;
345 const oppCol
= V
.GetOppCol(color
);
346 V
.PlayOnBoard(this.board
, move);
347 const captureNotEnding
= (
348 move.vanish
.length
== 2 &&
349 this.board
.some(b
=> b
.some(cell
=> cell
!= "" && cell
[0] == oppCol
))
351 this.captures
.push(captureNotEnding
);
352 // Change turn unless I just captured something,
353 // and an opponent stone can be removed from board.
354 if (!captureNotEnding
) {
362 V
.UndoOnBoard(this.board
, move);
363 if (this.turn
== 'b') this.lastMove
.pop();
364 else this.lastMove
['b'] = null;
366 if (move.turn
!= this.turn
) {
367 this.turn
= move.turn
;
374 if (move.vanish
.length
== 0) {
375 const color
= move.appear
[0].c
;
376 this.reserve
[color
][V
.PAWN
]--;
377 if (this.reserve
[color
][V
.PAWN
] == 0) delete this.reserve
[color
];
382 if (move.vanish
.length
== 0) {
383 const color
= move.appear
[0].c
;
384 if (!this.reserve
[color
]) this.reserve
[color
] = { [V
.PAWN
]: 0 };
385 this.reserve
[color
][V
.PAWN
]++;
390 if (this.movesCount
<= 2) return "*";
391 const color
= this.turn
;
392 // If no stones on board, or no move available, I lose
394 this.board
.every(b
=> {
395 return b
.every(cell
=> {
396 return (cell
== "" || cell
[0] != color
);
400 !this.atLeastOneMove()
402 return (color
== 'w' ? "0-1" : "1-0");
408 const moves
= super.getAllValidMoves();
409 if (moves
.length
== 0) return null;
410 const color
= this.turn
;
411 const oppCol
= V
.GetOppCol(color
);
412 // Capture available? If yes, play it
413 const captures
= moves
.filter(m
=> m
.vanish
.length
== 2);
414 if (captures
.length
>= 1) {
415 const m1
= captures
[randInt(captures
.length
)];
417 const moves2
= super.getAllValidMoves();
418 // Remove a stone which was about to capture one of ours, if possible
420 for (let m2
of moves2
) {
421 const [x
, y
] = [m2
.start
.x
, m2
.start
.y
];
422 for (let s
of V
.steps
[V
.ROOK
]) {
423 const [i
, j
] = [x
+ 2*s
[0], y
+ 2*s
[1]];
426 this.board
[i
][j
] == V
.EMPTY
&&
427 this.board
[i
- s
[0], j
- s
[1]] != V
.EMPTY
&&
428 this.getColor(i
- s
[0], j
- s
[1]) == color
436 if (candidates
.length
>= 1)
437 return [m1
, candidates
[randInt(candidates
.length
)]];
438 return [m1
, moves2
[randInt(moves2
.length
)]];
440 // Just play a random move, which if possible do not let a capture
442 for (let m
of moves
) {
444 const moves2
= super.getAllValidMoves();
445 if (moves2
.every(m2
=> m2
.vanish
.length
<= 1))
449 if (candidates
.length
>= 1) return candidates
[randInt(candidates
.length
)];
450 return moves
[randInt(moves
.length
)];
454 if (move.vanish
.length
== 0)
456 return "@" + V
.CoordsToSquare(move.end
);
457 if (move.appear
.length
== 0)
458 // Removal after capture:
459 return V
.CoordsToSquare(move.start
) + "X";
461 V
.CoordsToSquare(move.start
) +
462 (move.vanish
.length
== 2 ? "x" : "") +
463 V
.CoordsToSquare(move.end
)