1 import { ChessRules
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class SwapRules
extends ChessRules
{
6 setOtherVariables(fen
) {
7 super.setOtherVariables(fen
);
8 // Local stack of swaps
10 const smove
= V
.ParseFen(fen
).smove
;
11 if (smove
== "-") this.swaps
.push(null);
14 start: ChessRules
.SquareToCoords(smove
.substr(0, 2)),
15 end: ChessRules
.SquareToCoords(smove
.substr(2))
21 static ParseFen(fen
) {
23 ChessRules
.ParseFen(fen
),
24 { smove: fen
.split(" ")[5] }
28 static IsGoodFen(fen
) {
29 if (!ChessRules
.IsGoodFen(fen
)) return false;
30 const fenParts
= fen
.split(" ");
31 if (fenParts
.length
!= 6) return false;
32 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
38 if (m
.appear
.length
== 1) return super.getPPpath(m
);
40 return m
.appear
[1].c
+ m
.appear
[1].p
;
43 getSwapMoves([x1
, y1
], [x2
, y2
]) {
44 let move = super.getBasicMove([x1
, y1
], [x2
, y2
]);
49 c: this.getColor(x2
, y2
),
50 p: this.getPiece(x2
, y2
)
53 const lastRank
= (move.appear
[1].c
== 'w' ? 0 : 7);
54 if (move.appear
[1].p
== V
.PAWN
&& move.appear
[1].x
== lastRank
) {
56 move.appear
[1].p
= V
.ROOK
;
58 [V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
].forEach(p
=> {
59 let m
= JSON
.parse(JSON
.stringify(move));
68 getPotentialMovesFrom([x
, y
]) {
69 if (this.subTurn
== 1) return super.getPotentialMovesFrom([x
, y
]);
70 // SubTurn == 2: only swaps
72 const color
= this.turn
;
73 const piece
= this.getPiece(x
, y
);
74 const addSmoves
= (i
, j
) => {
75 if (this.getPiece(i
, j
) != piece
|| this.getColor(i
, j
) != color
)
76 Array
.prototype.push
.apply(moves
, this.getSwapMoves([x
, y
], [i
, j
]));
80 const forward
= (color
== 'w' ? -1 : 1);
81 const startRank
= (color
== 'w' ? [6, 7] : [0, 1]);
84 this.board
[x
+ forward
][y
] == V
.EMPTY
&&
85 this.board
[x
+ 2 * forward
][y
] != V
.EMPTY
87 // Swap using 2 squares move
88 addSmoves(x
+ 2 * forward
, y
);
90 for (let shift
of [-1, 0, 1]) {
91 const [i
, j
] = [x
+ forward
, y
+ shift
];
92 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) addSmoves(i
, j
);
97 V
.steps
[V
.KNIGHT
].forEach(s
=> {
98 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
99 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) addSmoves(i
, j
);
103 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(s
=> {
104 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
105 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) addSmoves(i
, j
);
114 : V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
116 let [i
, j
] = [x
+ s
[0], y
+ s
[1]];
117 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
121 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) addSmoves(i
, j
);
129 // Does m2 un-do m1 ? (to disallow undoing swaps)
130 oppositeSwaps(m1
, m2
) {
133 m1
.start
.x
== m2
.start
.x
&&
134 m1
.end
.x
== m2
.end
.x
&&
135 m1
.start
.y
== m2
.start
.y
&&
141 const fmoves
= super.filterValid(moves
);
142 if (this.subTurn
== 1) return fmoves
;
143 return fmoves
.filter(m
=> {
144 const L
= this.swaps
.length
; //at least 1: init from FEN
145 return !this.oppositeSwaps(this.swaps
[L
- 1], m
);
149 static GenRandInitFen(randomness
) {
151 return ChessRules
.GenRandInitFen(randomness
) + " -";
155 const L
= this.swaps
.length
;
159 : ChessRules
.CoordsToSquare(this.swaps
[L
- 1].start
) +
160 ChessRules
.CoordsToSquare(this.swaps
[L
- 1].end
)
165 return super.getFen() + " " + this.getSmoveFen();
169 return super.getFenForRepeat() + "_" + this.getSmoveFen();
173 const L
= this.swaps
.length
;
174 if (this.movesCount
>= 2 && !this.swaps
[L
-1])
175 // Opponent had no swap moves: I win
176 return (this.turn
== "w" ? "1-0" : "0-1");
177 if (this.atLeastOneMove()) return "*";
178 // No valid move: I lose
179 return (this.turn
== "w" ? "0-1" : "1-0");
184 const res
= !this.atLeastOneMove();
190 move.flags
= JSON
.stringify(this.aggregateFlags());
191 move.turn
= [this.turn
, this.subTurn
];
192 V
.PlayOnBoard(this.board
, move);
193 let epSq
= undefined;
194 if (this.subTurn
== 1) epSq
= this.getEpSquare(move);
195 if (this.movesCount
== 0) {
196 // First move in game
198 this.epSquares
.push(epSq
);
201 // Any swap available after move? If no, skip subturn 2
202 else if (this.subTurn
== 1 && this.noSwapAfter(move)) {
203 this.turn
= V
.GetOppCol(this.turn
);
204 this.epSquares
.push(epSq
);
209 if (this.subTurn
== 2) {
210 this.turn
= V
.GetOppCol(this.turn
);
211 this.swaps
.push({ start: move.start
, end: move.end
});
214 this.epSquares
.push(epSq
);
217 this.subTurn
= 3 - this.subTurn
;
223 const firstRank
= { 7: 'w', 0: 'b' };
224 // Did some king move?
225 move.appear
.forEach(a
=> {
227 this.kingPos
[a
.c
] = [a
.x
, a
.y
];
228 this.castleFlags
[a
.c
] = [V
.size
.y
, V
.size
.y
];
231 for (let coords
of [move.start
, move.end
]) {
233 Object
.keys(firstRank
).includes(coords
.x
) &&
234 this.castleFlags
[firstRank
[coords
.x
]].includes(coords
.y
)
236 const c
= firstRank
[coords
.x
];
237 const flagIdx
= (coords
.y
== this.castleFlags
[c
][0] ? 0 : 1);
238 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
244 this.disaggregateFlags(JSON
.parse(move.flags
));
245 V
.UndoOnBoard(this.board
, move);
246 if (move.turn
[1] == 1) {
247 // The move may not be full, but is fully undone:
248 this.epSquares
.pop();
249 // Moves counter was just incremented:
253 // Undo the second half of a move
255 this.turn
= move.turn
[0];
256 this.subTurn
= move.turn
[1];
261 // Did some king move?
262 move.vanish
.forEach(v
=> {
263 if (v
.p
== V
.KING
) this.kingPos
[v
.c
] = [v
.x
, v
.y
];
267 // No alpha-beta here, just adapted min-max at depth 2(+1)
269 const maxeval
= V
.INFINITY
;
270 const color
= this.turn
;
271 const oppCol
= V
.GetOppCol(this.turn
);
273 // Search best (half) move for opponent turn (TODO: a bit too slow)
274 // const getBestMoveEval = () => {
275 // let score = this.getCurrentScore();
276 // if (score != "*") return maxeval * (score == "1-0" ? 1 : -1);
277 // let moves = this.getAllValidMoves();
278 // let res = (oppCol == "w" ? -maxeval : maxeval);
279 // for (let m of moves) {
281 // score = this.getCurrentScore();
282 // // Now turn is oppCol,2 if m allow a swap and movesCount >= 2
283 // // Otherwise it's color,1. In both cases the next test makes sense
284 // if (score != "*") {
287 // return maxeval * (score == "1-0" ? 1 : -1);
289 // const evalPos = this.evalPosition();
290 // res = oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos);
296 const moves11
= this.getAllValidMoves();
297 if (this.movesCount
== 0)
298 // Just play first move at random:
299 return moves11
[randInt(moves11
.length
)];
300 let bestMove
= undefined;
301 // Rank moves using a min-max at depth 2
302 for (let i
= 0; i
< moves11
.length
; i
++) {
303 this.play(moves11
[i
]);
304 if (!!moves11
[i
].noSwap
) {
306 if (!bestMove
) bestMove
= {
308 eval: (color
== 'w' ? -maxeval : maxeval
)
312 let moves12
= this.getAllValidMoves();
313 for (let j
= 0; j
< moves12
.length
; j
++) {
314 this.play(moves12
[j
]);
315 // const evalMove = getBestMoveEval() + 0.05 - Math.random() / 10;
316 const evalMove
= this.evalPosition() + 0.05 - Math
.random() / 10;
319 (color
== 'w' && evalMove
> bestMove
.eval
) ||
320 (color
== 'b' && evalMove
< bestMove
.eval
)
323 moves: [moves11
[i
], moves12
[j
]],
327 this.undo(moves12
[j
]);
330 this.undo(moves11
[i
]);
332 return bestMove
.moves
;
336 if (move.appear
.length
== 1)
338 return super.getNotation(move);
339 if (move.appear
[0].p
== V
.KING
&& move.appear
[1].p
== V
.ROOK
)
341 return (move.end
.y
< move.start
.y
? "0-0-0" : "0-0");
343 return "S" + V
.CoordsToSquare(move.start
) + V
.CoordsToSquare(move.end
);