Add Tencubed and Omega variants + some fixes (updateCastleFlags()) + cleaner FEN...
[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("2PPPP2/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 const singleMoveAttack = super.isAttacked(sq, color);
50 if (singleMoveAttack) return true;
51 if (color == 'b' || !!castling) return singleMoveAttack;
52 // Attacks by white: double-move allowed
53 const curTurn = this.turn;
54 this.turn = 'w';
55 const w1Moves = super.getAllPotentialMoves();
56 this.turn = curTurn;
57 for (let move of w1Moves) {
58 this.play(move);
59 const res = super.isAttacked(sq, 'w');
60 this.undo(move);
61 if (res) return res;
62 }
63 return false;
64 }
65
66 play(move) {
67 move.flags = JSON.stringify(this.aggregateFlags());
68 if (this.turn == 'b' || this.subTurn == 2)
69 this.epSquares.push(this.getEpSquare(move));
70 else this.epSquares.push(null);
71 V.PlayOnBoard(this.board, move);
72 if (this.turn == 'w') {
73 if (this.subTurn == 1) this.movesCount++;
74 else this.turn = 'b';
75 this.subTurn = 3 - this.subTurn;
76 } else {
77 this.turn = 'w';
78 this.movesCount++;
79 }
80 this.postPlay(move);
81 }
82
83 updateCastleFlags(move, piece) {
84 // Only black can castle:
85 const firstRank = 0;
86 if (piece == V.KING && move.appear[0].c == 'b')
87 this.castleFlags['b'] = [8, 8];
88 else if (
89 move.start.x == firstRank &&
90 this.castleFlags['b'].includes(move.start.y)
91 ) {
92 const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1);
93 this.castleFlags['b'][flagIdx] = 8;
94 }
95 else if (
96 move.end.x == firstRank &&
97 this.castleFlags['b'].includes(move.end.y)
98 ) {
99 const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1);
100 this.castleFlags['b'][flagIdx] = 8;
101 }
102 }
103
104 postPlay(move) {
105 // Definition of 'c' in base class doesn't work:
106 const c = move.vanish[0].c;
107 const piece = move.vanish[0].p;
108 if (piece == V.KING) {
109 this.kingPos[c][0] = move.appear[0].x;
110 this.kingPos[c][1] = move.appear[0].y;
111 }
112 this.updateCastleFlags(move, piece);
113 }
114
115 undo(move) {
116 this.epSquares.pop();
117 this.disaggregateFlags(JSON.parse(move.flags));
118 V.UndoOnBoard(this.board, move);
119 if (this.turn == 'w') {
120 if (this.subTurn == 2) this.subTurn = 1;
121 else this.turn = 'b';
122 this.movesCount--;
123 } else {
124 this.turn = 'w';
125 this.subTurn = 2;
126 }
127 this.postUndo(move);
128 }
129
130 filterValid(moves) {
131 if (this.turn == 'w' && this.subTurn == 1) {
132 return moves.filter(m1 => {
133 this.play(m1);
134 // NOTE: no recursion because next call will see subTurn == 2
135 const res = super.atLeastOneMove();
136 this.undo(m1);
137 return res;
138 });
139 }
140 return super.filterValid(moves);
141 }
142
143 static get SEARCH_DEPTH() {
144 return 1;
145 }
146
147 getComputerMove() {
148 const color = this.turn;
149 if (color == 'w') {
150 // Generate all sequences of 2-moves
151 const moves1 = this.getAllValidMoves();
152 moves1.forEach(m1 => {
153 m1.eval = -V.INFINITY;
154 m1.move2 = null;
155 this.play(m1);
156 const moves2 = this.getAllValidMoves();
157 moves2.forEach(m2 => {
158 this.play(m2);
159 const eval2 = this.evalPosition();
160 this.undo(m2);
161 if (eval2 > m1.eval) {
162 m1.eval = eval2;
163 m1.move2 = m2;
164 }
165 });
166 this.undo(m1);
167 });
168 moves1.sort((a, b) => b.eval - a.eval);
169 let candidates = [0];
170 for (
171 let i = 1;
172 i < moves1.length && moves1[i].eval == moves1[0].eval;
173 i++
174 ) {
175 candidates.push(i);
176 }
177 const idx = candidates[randInt(candidates.length)];
178 const move2 = moves1[idx].move2;
179 delete moves1[idx]["move2"];
180 return [moves1[idx], move2];
181 }
182 // For black at depth 1, super method is fine:
183 return super.getComputerMove();
184 }
185 };