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