Early draft of TitanChess
[vchess.git] / client / src / variants / Monster.js
1 import { ChessRules } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class MonsterRules extends ChessRules {
5 static IsGoodFlags(flags) {
6 // Only black can castle
7 return !!flags.match(/^[a-z]{2,2}$/);
8 }
9
10 static GenRandInitFen(randomness) {
11 if (randomness == 2) randomness--;
12 const fen = ChessRules.GenRandInitFen(randomness);
13 return (
14 // 26 first chars are 6 rows + 6 slashes
15 fen.substr(0, 26)
16 // En passant available, and "half-castle"
17 .concat("1PPPPPP1/4K3 w 0 ")
18 .concat(fen.substr(-6, 2))
19 .concat(" -")
20 );
21 }
22
23 getFlagsFen() {
24 return this.castleFlags['b'].map(V.CoordToColumn).join("");
25 }
26
27 setFlags(fenflags) {
28 this.castleFlags = { 'b': [-1, -1] };
29 for (let i = 0; i < 2; i++)
30 this.castleFlags['b'][i] = V.ColumnToCoord(fenflags.charAt(i));
31 }
32
33 setOtherVariables(fen) {
34 super.setOtherVariables(fen);
35 this.subTurn = 1;
36 }
37
38 getPotentialKingMoves([x, y]) {
39 if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]);
40 // White doesn't castle:
41 return this.getSlideNJumpMoves(
42 [x, y],
43 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
44 "oneStep"
45 );
46 }
47
48 isAttacked() {
49 // Goal is king capture => no checks
50 return false;
51 }
52
53 filterValid(moves) {
54 return moves;
55 }
56
57 getCheckSquares() {
58 return [];
59 }
60
61 getCurrentScore() {
62 const color = this.turn;
63 if (this.kingPos[color][0] < 0) return (color == 'w' ? "0-1" : "1-0");
64 return "*";
65 }
66
67 play(move) {
68 move.flags = JSON.stringify(this.aggregateFlags());
69 if (this.turn == 'b' || this.subTurn == 2)
70 this.epSquares.push(this.getEpSquare(move));
71 else this.epSquares.push(null);
72 V.PlayOnBoard(this.board, move);
73 if (this.turn == 'w') {
74 if (this.subTurn == 1) this.movesCount++;
75 if (
76 this.subTurn == 2 ||
77 // King captured
78 (move.vanish.length == 2 && move.vanish[1].p == V.KING)
79 ) {
80 this.turn = 'b';
81 this.subTurn = 1;
82 }
83 else this.subTurn = 2;
84 }
85 else {
86 this.turn = 'w';
87 this.movesCount++;
88 }
89 this.postPlay(move);
90 }
91
92 updateCastleFlags(move, piece) {
93 // Only black can castle:
94 const firstRank = 0;
95 if (piece == V.KING && move.appear[0].c == 'b')
96 this.castleFlags['b'] = [8, 8];
97 else if (
98 move.start.x == firstRank &&
99 this.castleFlags['b'].includes(move.start.y)
100 ) {
101 const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1);
102 this.castleFlags['b'][flagIdx] = 8;
103 }
104 else if (
105 move.end.x == firstRank &&
106 this.castleFlags['b'].includes(move.end.y)
107 ) {
108 const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1);
109 this.castleFlags['b'][flagIdx] = 8;
110 }
111 }
112
113 postPlay(move) {
114 // Definition of 'c' in base class doesn't work:
115 const c = move.vanish[0].c;
116 const piece = move.vanish[0].p;
117 if (piece == V.KING)
118 this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
119 if (move.vanish.length == 2 && move.vanish[1].p == V.KING) {
120 // Opponent's king is captured, game over
121 this.kingPos[move.vanish[1].c] = [-1, -1];
122 move.captureKing = true; //for undo
123 }
124 this.updateCastleFlags(move, piece);
125 }
126
127 undo(move) {
128 this.epSquares.pop();
129 this.disaggregateFlags(JSON.parse(move.flags));
130 V.UndoOnBoard(this.board, move);
131 if (this.turn == 'w') {
132 if (this.subTurn == 2) this.subTurn = 1;
133 else this.turn = 'b';
134 this.movesCount--;
135 }
136 else {
137 this.turn = 'w';
138 this.subTurn = (!move.captureKing ? 2 : 1);
139 }
140 this.postUndo(move);
141 }
142
143 postUndo(move) {
144 if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
145 // Opponent's king was captured
146 this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y];
147 super.postUndo(move);
148 }
149
150 // Custom search at depth 1(+1)
151 getComputerMove() {
152 const getBestWhiteMove = (terminal) => {
153 // Generate all sequences of 2-moves
154 let moves1 = this.getAllValidMoves();
155 moves1.forEach(m1 => {
156 m1.eval = -V.INFINITY;
157 m1.move2 = null;
158 this.play(m1);
159 if (!!terminal) m1.eval = this.evalPosition();
160 else {
161 const moves2 = this.getAllValidMoves();
162 moves2.forEach(m2 => {
163 this.play(m2);
164 const eval2 = this.evalPosition() + 0.05 - Math.random() / 10;
165 this.undo(m2);
166 if (eval2 > m1.eval) {
167 m1.eval = eval2;
168 m1.move2 = m2;
169 }
170 });
171 }
172 this.undo(m1);
173 });
174 moves1.sort((a, b) => b.eval - a.eval);
175 if (!!terminal)
176 // The move itself doesn't matter, only its eval:
177 return moves1[0];
178 let candidates = [0];
179 for (
180 let i = 1;
181 i < moves1.length && moves1[i].eval == moves1[0].eval;
182 i++
183 ) {
184 candidates.push(i);
185 }
186 const idx = candidates[randInt(candidates.length)];
187 const move2 = moves1[idx].move2;
188 delete moves1[idx]["move2"];
189 return [moves1[idx], move2];
190 };
191
192 const getBestBlackMove = () => {
193 let moves = this.getAllValidMoves();
194 moves.forEach(m => {
195 m.eval = V.INFINITY;
196 this.play(m);
197 const evalM = getBestWhiteMove("terminal").eval
198 this.undo(m);
199 if (evalM < m.eval) m.eval = evalM;
200 });
201 moves.sort((a, b) => a.eval - b.eval);
202 let candidates = [0];
203 for (
204 let i = 1;
205 i < moves.length && moves[i].eval == moves[0].eval;
206 i++
207 ) {
208 candidates.push(i);
209 }
210 const idx = candidates[randInt(candidates.length)];
211 return moves[idx];
212 };
213
214 const color = this.turn;
215 return (color == 'w' ? getBestWhiteMove() : getBestBlackMove());
216 }
217 };