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