1 import { ChessRules
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class SwapRules
extends ChessRules
{
5 setOtherVariables(fen
) {
6 super.setOtherVariables(fen
);
7 // Local stack of swaps
9 const smove
= V
.ParseFen(fen
).smove
;
10 if (smove
== "-") this.swaps
.push(null);
13 start: ChessRules
.SquareToCoords(smove
.substr(0, 2)),
14 end: ChessRules
.SquareToCoords(smove
.substr(2))
20 static ParseFen(fen
) {
22 ChessRules
.ParseFen(fen
),
23 { smove: fen
.split(" ")[5] }
27 static IsGoodFen(fen
) {
28 if (!ChessRules
.IsGoodFen(fen
)) return false;
29 const fenParts
= fen
.split(" ");
30 if (fenParts
.length
!= 6) return false;
31 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
37 if (m
.vanish
.length
== 1) return super.getPPpath(m
);
39 return m
.appear
[1].c
+ m
.appear
[1].p
;
42 getSwapMoves([x1
, y1
], [x2
, y2
]) {
43 let move = super.getBasicMove([x1
, y1
], [x2
, y2
]);
48 c: this.getColor(x2
, y2
),
49 p: this.getPiece(x2
, y2
)
52 const lastRank
= (move.appear
[1].c
== 'w' ? 0 : 7);
53 if (move.appear
[1].p
== V
.PAWN
&& move.appear
[1].x
== lastRank
) {
55 move.appear
[1].p
= V
.ROOK
;
57 [V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
].forEach(p
=> {
58 let m
= JSON
.parse(JSON
.stringify(move));
67 getPotentialMovesFrom([x
, y
]) {
68 if (this.subTurn
== 1) return super.getPotentialMovesFrom([x
, y
]);
69 // SubTurn == 2: only swaps
71 const color
= this.turn
;
72 const piece
= this.getPiece(x
, y
);
73 const addSmoves
= (i
, j
) => {
74 if (this.getPiece(i
, j
) != piece
|| this.getColor(i
, j
) != color
)
75 Array
.prototype.push
.apply(moves
, this.getSwapMoves([x
, y
], [i
, j
]));
79 const forward
= (color
== 'w' ? -1 : 1);
80 const startRank
= (color
== 'w' ? [6, 7] : [0, 1]);
83 this.board
[x
+ forward
][y
] == V
.EMPTY
&&
84 this.board
[x
+ 2 * forward
][y
] != V
.EMPTY
86 // Swap using 2 squares move
87 addSmoves(x
+ 2 * forward
, y
);
89 for (let shift
of [-1, 0, 1]) {
90 const [i
, j
] = [x
+ forward
, y
+ shift
];
91 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) addSmoves(i
, j
);
96 V
.steps
[V
.KNIGHT
].forEach(s
=> {
97 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
98 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) addSmoves(i
, j
);
102 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(s
=> {
103 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
104 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) addSmoves(i
, j
);
113 : V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
115 let [i
, j
] = [x
+ s
[0], y
+ s
[1]];
116 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
120 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) addSmoves(i
, j
);
128 // Does m2 un-do m1 ? (to disallow undoing swaps)
129 oppositeSwaps(m1
, m2
) {
132 m1
.start
.x
== m2
.start
.x
&&
133 m1
.end
.x
== m2
.end
.x
&&
134 m1
.start
.y
== m2
.start
.y
&&
140 const fmoves
= super.filterValid(moves
);
141 if (this.subTurn
== 1) return fmoves
;
142 return fmoves
.filter(m
=> {
143 const L
= this.swaps
.length
; //at least 1: init from FEN
144 return !this.oppositeSwaps(this.swaps
[L
- 1], m
);
148 static GenRandInitFen(randomness
) {
150 return ChessRules
.GenRandInitFen(randomness
) + " -";
154 const L
= this.swaps
.length
;
158 : ChessRules
.CoordsToSquare(this.swaps
[L
- 1].start
) +
159 ChessRules
.CoordsToSquare(this.swaps
[L
- 1].end
)
164 return super.getFen() + " " + this.getSmoveFen();
168 return super.getFenForRepeat() + "_" + this.getSmoveFen();
172 const L
= this.swaps
.length
;
173 if (this.movesCount
>= 2 && !this.swaps
[L
-1])
174 // Opponent had no swap moves: I win
175 return (this.turn
== "w" ? "1-0" : "0-1");
176 if (this.atLeastOneMove()) return "*";
177 // No valid move: I lose
178 return (this.turn
== "w" ? "0-1" : "1-0");
183 const res
= !this.atLeastOneMove();
189 move.flags
= JSON
.stringify(this.aggregateFlags());
190 move.turn
= [this.turn
, this.subTurn
];
191 V
.PlayOnBoard(this.board
, move);
192 let epSq
= undefined;
193 if (this.subTurn
== 1) epSq
= this.getEpSquare(move);
194 if (this.movesCount
== 0) {
195 // First move in game
197 this.epSquares
.push(epSq
);
200 // Any swap available after move? If no, skip subturn 2
201 else if (this.subTurn
== 1 && this.noSwapAfter(move)) {
202 this.turn
= V
.GetOppCol(this.turn
);
203 this.epSquares
.push(epSq
);
208 if (this.subTurn
== 2) {
209 this.turn
= V
.GetOppCol(this.turn
);
210 this.swaps
.push({ start: move.start
, end: move.end
});
213 this.epSquares
.push(epSq
);
216 this.subTurn
= 3 - this.subTurn
;
222 const firstRank
= { 7: 'w', 0: 'b' };
223 // Did some king move?
224 move.appear
.forEach(a
=> {
226 this.kingPos
[a
.c
] = [a
.x
, a
.y
];
227 this.castleFlags
[a
.c
] = [V
.size
.y
, V
.size
.y
];
230 for (let coords
of [move.start
, move.end
]) {
232 Object
.keys(firstRank
).includes(coords
.x
) &&
233 this.castleFlags
[firstRank
[coords
.x
]].includes(coords
.y
)
235 const c
= firstRank
[coords
.x
];
236 const flagIdx
= (coords
.y
== this.castleFlags
[c
][0] ? 0 : 1);
237 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
243 this.disaggregateFlags(JSON
.parse(move.flags
));
244 V
.UndoOnBoard(this.board
, move);
245 if (move.turn
[1] == 1) {
246 // The move may not be full, but is fully undone:
247 this.epSquares
.pop();
248 // Moves counter was just incremented:
252 // Undo the second half of a move
254 this.turn
= move.turn
[0];
255 this.subTurn
= move.turn
[1];
260 // Did some king move?
261 move.vanish
.forEach(v
=> {
262 if (v
.p
== V
.KING
) this.kingPos
[v
.c
] = [v
.x
, v
.y
];
266 // No alpha-beta here, just adapted min-max at depth 2(+1)
268 const maxeval
= V
.INFINITY
;
269 const color
= this.turn
;
270 const oppCol
= V
.GetOppCol(this.turn
);
272 // Search best (half) move for opponent turn (TODO: a bit too slow)
273 // const getBestMoveEval = () => {
274 // let score = this.getCurrentScore();
275 // if (score != "*") return maxeval * (score == "1-0" ? 1 : -1);
276 // let moves = this.getAllValidMoves();
277 // let res = (oppCol == "w" ? -maxeval : maxeval);
278 // for (let m of moves) {
280 // score = this.getCurrentScore();
281 // // Now turn is oppCol,2 if m allow a swap and movesCount >= 2
282 // // Otherwise it's color,1. In both cases the next test makes sense
283 // if (score != "*") {
286 // return maxeval * (score == "1-0" ? 1 : -1);
288 // const evalPos = this.evalPosition();
289 // res = oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos);
295 const moves11
= this.getAllValidMoves();
296 if (this.movesCount
== 0)
297 // Just play first move at random:
298 return moves11
[randInt(moves11
.length
)];
299 let bestMove
= undefined;
300 // Rank moves using a min-max at depth 2
301 for (let i
= 0; i
< moves11
.length
; i
++) {
302 this.play(moves11
[i
]);
303 if (!!moves11
[i
].noSwap
) {
305 if (!bestMove
) bestMove
= {
307 eval: (color
== 'w' ? -maxeval : maxeval
)
311 let moves12
= this.getAllValidMoves();
312 for (let j
= 0; j
< moves12
.length
; j
++) {
313 this.play(moves12
[j
]);
314 // const evalMove = getBestMoveEval() + 0.05 - Math.random() / 10;
315 const evalMove
= this.evalPosition() + 0.05 - Math
.random() / 10;
318 (color
== 'w' && evalMove
> bestMove
.eval
) ||
319 (color
== 'b' && evalMove
< bestMove
.eval
)
322 moves: [moves11
[i
], moves12
[j
]],
326 this.undo(moves12
[j
]);
329 this.undo(moves11
[i
]);
331 return bestMove
.moves
;
335 if (move.appear
.length
== 1)
337 return super.getNotation(move);
338 if (move.appear
[0].p
== V
.KING
&& move.appear
[1].p
== V
.ROOK
)
340 return (move.end
.y
< move.start
.y
? "0-0-0" : "0-0");
342 return "S" + V
.CoordsToSquare(move.start
) + V
.CoordsToSquare(move.end
);