6aa94aa5e1f971ffcb96a32c7a149e785cbc014f
[vchess.git] / client / src / variants / Pocketknight.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class PocketknightRules extends ChessRules {
5 hoverHighlight(x, y) {
6 // Testing move validity results in an infinite update loop.
7 // TODO: find a way to test validity anyway.
8 return (this.subTurn == 2 && this.board[x][y] == V.EMPTY);
9 }
10
11 static IsGoodFlags(flags) {
12 // 4 for castle + 2 for knights
13 return !!flags.match(/^[a-z]{4,4}[01]{2,2}$/);
14 }
15
16 setFlags(fenflags) {
17 super.setFlags(fenflags); //castleFlags
18 this.knightFlags = fenflags.substr(4).split("").map(e => e == "1");
19 }
20
21 aggregateFlags() {
22 return [this.castleFlags, this.knightFlags];
23 }
24
25 disaggregateFlags(flags) {
26 this.castleFlags = flags[0];
27 this.knightFlags = flags[1];
28 }
29
30 setOtherVariables(fen) {
31 super.setOtherVariables(fen);
32 this.subTurn = 1;
33 }
34
35 static GenRandInitFen(randomness) {
36 // Add 2 knight flags
37 return ChessRules.GenRandInitFen(randomness)
38 .slice(0, -2) + "11 -";
39 }
40
41 getFlagsFen() {
42 return (
43 super.getFlagsFen() + this.knightFlags.map(e => e ? "1" : "0").join("")
44 );
45 }
46
47 getPotentialMovesFrom([x, y]) {
48 if (this.subTurn == 1) {
49 let moves = super.getPotentialMovesFrom([x, y]);
50 // If flag allow it, add "king capture"
51 if (
52 this.knightFlags[this.turn == 'w' ? 0 : 1] &&
53 this.getPiece(x, y) == V.KING
54 ) {
55 const kp = this.kingPos[V.GetOppCol(this.turn)];
56 moves.push(
57 new Move({
58 appear: [],
59 vanish: [],
60 start: { x: x, y: y },
61 end: { x: kp[0], y: kp[1] }
62 })
63 );
64 }
65 return moves;
66 }
67 // subTurn == 2: a move is a click, not handled here
68 return [];
69 }
70
71 filterValid(moves) {
72 if (this.subTurn == 2) return super.filterValid(moves);
73 const color = this.turn;
74 return moves.filter(m => {
75 this.play(m);
76 let res = false;
77 if (m.appear.length > 0)
78 // Standard check:
79 res = !this.underCheck(color);
80 else {
81 // "Capture king": find landing square not resulting in check
82 outerLoop: for (let i=0; i<8; i++) {
83 for (let j=0; j<8; j++) {
84 if (this.board[i][j] == V.EMPTY) {
85 const tMove = new Move({
86 appear: [
87 new PiPo({
88 x: i,
89 y: j,
90 c: color,
91 p: V.KNIGHT
92 })
93 ],
94 vanish: [],
95 start: { x: -1, y: -1 }
96 });
97 this.play(tMove);
98 const moveOk = !this.underCheck(color);
99 this.undo(tMove);
100 if (moveOk) {
101 res = true;
102 break outerLoop;
103 }
104 }
105 }
106 }
107 }
108 this.undo(m);
109 return res;
110 });
111 }
112
113 getAllValidMoves() {
114 if (this.subTurn == 1) return super.getAllValidMoves();
115 // Subturn == 2: only knight landings
116 let moves = [];
117 const color = this.turn;
118 for (let i=0; i<8; i++) {
119 for (let j=0; j<8; j++) {
120 if (this.board[i][j] == V.EMPTY) {
121 const tMove = new Move({
122 appear: [
123 new PiPo({
124 x: i,
125 y: j,
126 c: color,
127 p: V.KNIGHT
128 })
129 ],
130 vanish: [],
131 start: { x: -1, y: -1 }
132 });
133 this.play(tMove);
134 const moveOk = !this.underCheck(color);
135 this.undo(tMove);
136 if (moveOk) moves.push(tMove);
137 }
138 }
139 }
140 return moves;
141 }
142
143 doClick(square) {
144 if (isNaN(square[0])) return null;
145 // If subTurn == 2 && square is empty && !underCheck, then drop
146 if (this.subTurn == 2 && this.board[square[0]][square[1]] == V.EMPTY) {
147 const color = this.turn;
148 const tMove = new Move({
149 appear: [
150 new PiPo({
151 x: square[0],
152 y: square[1],
153 c: color,
154 p: V.KNIGHT
155 })
156 ],
157 vanish: [],
158 start: { x: -1, y: -1 }
159 });
160 this.play(tMove);
161 const moveOk = !this.underCheck(color);
162 this.undo(tMove);
163 if (moveOk) return tMove;
164 }
165 return null;
166 }
167
168 play(move) {
169 move.flags = JSON.stringify(this.aggregateFlags());
170 if (move.appear.length > 0) {
171 // Usual case or knight landing
172 if (move.vanish.length > 0) this.epSquares.push(this.getEpSquare(move));
173 else this.subTurn = 1;
174 this.turn = V.GetOppCol(this.turn);
175 this.movesCount++;
176 V.PlayOnBoard(this.board, move);
177 if (move.vanish.length > 0) this.postPlay(move);
178 }
179 else {
180 // "king capture"
181 this.subTurn = 2;
182 this.knightFlags[this.turn == 'w' ? 0 : 1] = false;
183 }
184 }
185
186 undo(move) {
187 this.disaggregateFlags(JSON.parse(move.flags));
188 if (move.appear.length > 0) {
189 if (move.vanish.length > 0) this.epSquares.pop();
190 else this.subTurn = 2;
191 this.turn = V.GetOppCol(this.turn);
192 this.movesCount--;
193 V.UndoOnBoard(this.board, move);
194 if (move.vanish.length > 0) this.postUndo(move);
195 }
196 else this.subTurn = 1;
197 }
198
199 getComputerMove() {
200 let moves = this.getAllValidMoves();
201 if (moves.length == 0) return null;
202 // Custom "search" at depth 1 (for now. TODO?)
203 const maxeval = V.INFINITY;
204 const color = this.turn;
205 const initEval = this.evalPosition();
206 moves.forEach(m => {
207 this.play(m);
208 m.eval = (color == "w" ? -1 : 1) * maxeval;
209 if (m.appear.length == 0) {
210 const moves2 = this.getAllValidMoves();
211 m.next = moves2[0];
212 moves2.forEach(m2 => {
213 this.play(m2);
214 const score = this.getCurrentScore();
215 let mvEval = 0;
216 if (["1-0", "0-1"].includes(score))
217 mvEval = (score == "1-0" ? 1 : -1) * maxeval;
218 else if (score == "*")
219 // Add small fluctuations to avoid dropping pieces always on the
220 // first square available.
221 mvEval = initEval + 0.05 - Math.random() / 10;
222 if (
223 (color == 'w' && mvEval > m.eval) ||
224 (color == 'b' && mvEval < m.eval)
225 ) {
226 m.eval = mvEval;
227 m.next = m2;
228 }
229 this.undo(m2);
230 });
231 }
232 else {
233 const score = this.getCurrentScore();
234 if (score != "1/2") {
235 if (score != "*") m.eval = (score == "1-0" ? 1 : -1) * maxeval;
236 else m.eval = this.evalPosition();
237 }
238 }
239 this.undo(m);
240 });
241 moves.sort((a, b) => {
242 return (color == "w" ? 1 : -1) * (b.eval - a.eval);
243 });
244 let candidates = [0];
245 for (let i = 1; i < moves.length && moves[i].eval == moves[0].eval; i++)
246 candidates.push(i);
247 const mIdx = candidates[randInt(candidates.length)];
248 if (!moves[mIdx].next) return moves[mIdx];
249 const move2 = moves[mIdx].next;
250 delete moves[mIdx]["next"];
251 return [moves[mIdx], move2];
252 }
253
254 getNotation(move) {
255 if (move.vanish.length > 0)
256 return super.getNotation(move);
257 if (move.appear.length == 0)
258 // "king capture"
259 return "-";
260 // Knight landing:
261 return "N@" + V.CoordsToSquare(move.end);
262 }
263 };