A few fixes (for updateCastleFlags()) + add Madhouse and Pocketknight variants
[vchess.git] / client / src / variants / Takenmake.js
1 import { ChessRules } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class TakenmakeRules extends ChessRules {
5 setOtherVariables(fen) {
6 super.setOtherVariables(fen);
7 // Stack of "last move" only for intermediate captures
8 this.lastMoveEnd = [null];
9 }
10
11 getPotentialMovesFrom([x, y], asA) {
12 const L = this.lastMoveEnd.length;
13 if (!asA && !!this.lastMoveEnd[L-1]) {
14 asA = this.lastMoveEnd[L-1].p;
15 if (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y) {
16 // A capture was played: wrong square
17 return [];
18 }
19 }
20 let moves = [];
21 const piece = this.getPiece(x, y);
22 switch (asA || piece) {
23 case V.PAWN:
24 if (!asA || piece == V.PAWN)
25 moves = this.getPotentialPawnMoves([x, y]);
26 else {
27 // Special case: we don't want promotion, since just moving like
28 // a pawn, but I'm in fact not a pawn :)
29 const shiftX = (this.turn == 'w' ? -1 : 1);
30 if (this.board[x + shiftX][y] == V.EMPTY)
31 moves = [this.getBasicMove([x, y], [x + shiftX, y])];
32 }
33 break;
34 case V.ROOK:
35 moves = this.getPotentialRookMoves([x, y]);
36 break;
37 case V.KNIGHT:
38 moves = this.getPotentialKnightMoves([x, y]);
39 break;
40 case V.BISHOP:
41 moves = this.getPotentialBishopMoves([x, y]);
42 break;
43 case V.KING:
44 moves = this.getPotentialKingMoves([x, y]);
45 break;
46 case V.QUEEN:
47 moves = this.getPotentialQueenMoves([x, y]);
48 break;
49 }
50 // Post-process: if capture,
51 // can a move "as-capturer" be achieved with the same piece?
52 if (!asA) {
53 const color = this.turn;
54 return moves.filter(m => {
55 if (m.vanish.length == 2 && m.appear.length == 1) {
56 this.play(m);
57 let moveOk = true;
58 const makeMoves =
59 this.getPotentialMovesFrom([m.end.x, m.end.y], m.vanish[1].p);
60 if (
61 makeMoves.every(mm => {
62 // Cannot castle after a capturing move
63 // (with the capturing piece):
64 if (mm.vanish.length == 2) return true;
65 this.play(mm);
66 const res = this.underCheck(color);
67 this.undo(mm);
68 return res;
69 })
70 ) {
71 moveOk = false;
72 }
73 this.undo(m);
74 return moveOk;
75 }
76 return true;
77 });
78 }
79 // Moving "as a": filter out captures (no castles here)
80 return moves.filter(m => m.vanish.length == 1);
81 }
82
83 getPossibleMovesFrom(sq) {
84 const L = this.lastMoveEnd.length;
85 let asA = undefined;
86 if (!!this.lastMoveEnd[L-1]) {
87 if (
88 sq[0] != this.lastMoveEnd[L-1].x ||
89 sq[1] != this.lastMoveEnd[L-1].y
90 ) {
91 return [];
92 }
93 asA = this.lastMoveEnd[L-1].p;
94 }
95 return this.filterValid(this.getPotentialMovesFrom(sq, asA));
96 }
97
98 filterValid(moves) {
99 let noCaptureMoves = [];
100 let captureMoves = [];
101 moves.forEach(m => {
102 if (m.vanish.length == 1 || m.appear.length == 2) noCaptureMoves.push(m);
103 else captureMoves.push(m);
104 });
105 // Capturing moves were already checked in getPotentialMovesFrom()
106 return super.filterValid(noCaptureMoves).concat(captureMoves);
107 }
108
109 play(move) {
110 move.flags = JSON.stringify(this.aggregateFlags());
111 this.epSquares.push(this.getEpSquare(move));
112 V.PlayOnBoard(this.board, move);
113 if (move.vanish.length == 1 || move.appear.length == 2) {
114 // Not a capture: change turn
115 this.turn = V.GetOppCol(this.turn);
116 this.movesCount++;
117 this.lastMoveEnd.push(null);
118 }
119 else {
120 this.lastMoveEnd.push(
121 Object.assign({}, move.end, { p: move.vanish[1].p })
122 );
123 }
124 this.postPlay(move);
125 }
126
127 postPlay(move) {
128 const c = move.vanish[0].c;
129 const piece = move.vanish[0].p;
130 if (piece == V.KING && move.appear.length > 0) {
131 this.kingPos[c][0] = move.appear[0].x;
132 this.kingPos[c][1] = move.appear[0].y;
133 }
134 super.updateCastleFlags(move, piece, c);
135 }
136
137 undo(move) {
138 this.disaggregateFlags(JSON.parse(move.flags));
139 this.epSquares.pop();
140 this.lastMoveEnd.pop();
141 V.UndoOnBoard(this.board, move);
142 if (move.vanish.length == 1 || move.appear.length == 2) {
143 this.turn = V.GetOppCol(this.turn);
144 this.movesCount--;
145 }
146 super.postUndo(move);
147 }
148
149 getComputerMove() {
150 let moves = this.getAllValidMoves();
151 if (moves.length == 0) return null;
152 // Custom "search" at depth 1 (for now. TODO?)
153 const maxeval = V.INFINITY;
154 const color = this.turn;
155 moves.forEach(m => {
156 this.play(m);
157 m.eval = (color == "w" ? -1 : 1) * maxeval;
158 if (m.vanish.length == 2 && m.appear.length == 1) {
159 const moves2 = this.getPossibleMovesFrom([m.end.x, m.end.y]);
160 m.next = moves2[0];
161 moves2.forEach(m2 => {
162 this.play(m2);
163 const score = this.getCurrentScore();
164 let mvEval = 0;
165 if (score != "1/2") {
166 if (score != "*") mvEval = (score == "1-0" ? 1 : -1) * maxeval;
167 else mvEval = this.evalPosition();
168 }
169 if (
170 (color == 'w' && mvEval > m.eval) ||
171 (color == 'b' && mvEval < m.eval)
172 ) {
173 m.eval = mvEval;
174 m.next = m2;
175 }
176 this.undo(m2);
177 });
178 }
179 else {
180 const score = this.getCurrentScore();
181 if (score != "1/2") {
182 if (score != "*") m.eval = (score == "1-0" ? 1 : -1) * maxeval;
183 else m.eval = this.evalPosition();
184 }
185 }
186 this.undo(m);
187 });
188 moves.sort((a, b) => {
189 return (color == "w" ? 1 : -1) * (b.eval - a.eval);
190 });
191 let candidates = [0];
192 for (let i = 1; i < moves.length && moves[i].eval == moves[0].eval; i++)
193 candidates.push(i);
194 const mIdx = candidates[randInt(candidates.length)];
195 if (!moves[mIdx].next) return moves[mIdx];
196 const move2 = moves[mIdx].next;
197 delete moves[mIdx]["next"];
198 return [moves[mIdx], move2];
199 }
200 };