Simplify Monster + Doublemove2. Smoother example games
[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(sq, color, castling) {
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 else this.turn = 'b';
76 this.subTurn = 3 - this.subTurn;
77 } else {
78 this.turn = 'w';
79 this.movesCount++;
80 }
81 this.postPlay(move);
82 }
83
84 updateCastleFlags(move, piece) {
85 // Only black can castle:
86 const firstRank = 0;
87 if (piece == V.KING && move.appear[0].c == 'b')
88 this.castleFlags['b'] = [8, 8];
89 else if (
90 move.start.x == firstRank &&
91 this.castleFlags['b'].includes(move.start.y)
92 ) {
93 const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1);
94 this.castleFlags['b'][flagIdx] = 8;
95 }
96 else if (
97 move.end.x == firstRank &&
98 this.castleFlags['b'].includes(move.end.y)
99 ) {
100 const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1);
101 this.castleFlags['b'][flagIdx] = 8;
102 }
103 }
104
105 postPlay(move) {
106 // Definition of 'c' in base class doesn't work:
107 const c = move.vanish[0].c;
108 const piece = move.vanish[0].p;
109 if (piece == V.KING)
110 this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
111 if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
112 // Opponent's king is captured, game over
113 this.kingPos[move.vanish[1].c] = [-1, -1];
114 this.updateCastleFlags(move, piece);
115 }
116
117 undo(move) {
118 this.epSquares.pop();
119 this.disaggregateFlags(JSON.parse(move.flags));
120 V.UndoOnBoard(this.board, move);
121 if (this.turn == 'w') {
122 if (this.subTurn == 2) this.subTurn = 1;
123 else this.turn = 'b';
124 this.movesCount--;
125 }
126 else {
127 this.turn = 'w';
128 this.subTurn = 2;
129 }
130 this.postUndo(move);
131 }
132
133 postUndo(move) {
134 if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
135 // Opponent's king was captured
136 this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y];
137 super.postUndo(move);
138 }
139
140 // Custom search at depth 1(+1)
141 getComputerMove() {
142 const getBestWhiteMove = (terminal) => {
143 // Generate all sequences of 2-moves
144 let moves1 = this.getAllValidMoves();
145 moves1.forEach(m1 => {
146 m1.eval = -V.INFINITY;
147 m1.move2 = null;
148 this.play(m1);
149 if (!!terminal) m1.eval = this.evalPosition();
150 else {
151 const moves2 = this.getAllValidMoves();
152 moves2.forEach(m2 => {
153 this.play(m2);
154 const eval2 = this.evalPosition() + 0.05 - Math.random() / 10;
155 this.undo(m2);
156 if (eval2 > m1.eval) {
157 m1.eval = eval2;
158 m1.move2 = m2;
159 }
160 });
161 }
162 this.undo(m1);
163 });
164 moves1.sort((a, b) => b.eval - a.eval);
165 if (!!terminal)
166 // The move itself doesn't matter, only its eval:
167 return moves1[0];
168 let candidates = [0];
169 for (
170 let i = 1;
171 i < moves1.length && moves1[i].eval == moves1[0].eval;
172 i++
173 ) {
174 candidates.push(i);
175 }
176 const idx = candidates[randInt(candidates.length)];
177 const move2 = moves1[idx].move2;
178 delete moves1[idx]["move2"];
179 return [moves1[idx], move2];
180 };
181
182 const getBestBlackMove = () => {
183 let moves = this.getAllValidMoves();
184 moves.forEach(m => {
185 m.eval = V.INFINITY;
186 this.play(m);
187 const evalM = getBestWhiteMove("terminal").eval
188 this.undo(m);
189 if (evalM < m.eval) m.eval = evalM;
190 });
191 moves.sort((a, b) => a.eval - b.eval);
192 let candidates = [0];
193 for (
194 let i = 1;
195 i < moves.length && moves[i].eval == moves[0].eval;
196 i++
197 ) {
198 candidates.push(i);
199 }
200 const idx = candidates[randInt(candidates.length)];
201 return moves[idx];
202 };
203
204 const color = this.turn;
205 return (color == 'w' ? getBestWhiteMove() : getBestBlackMove());
206 }
207 };