76d737dd0e1be265056e8c05be2b697a9dc6be69
[vchess.git] / client / src / variants / Refusal.js
1 import { ChessRules } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class RefusalRules extends ChessRules {
5
6 static IsGoodFen(fen) {
7 if (!ChessRules.IsGoodFen(fen)) return false;
8 if (!V.ParseFen(fen).lastMove) return false;
9 return true;
10 }
11
12 static ParseFen(fen) {
13 return Object.assign(
14 { lastMove: fen.split(" ")[5] },
15 ChessRules.ParseFen(fen)
16 );
17 }
18
19 getFen() {
20 const L = this.lastMove.length;
21 const lm = this.lastMove[L-1];
22 return super.getFen() + " " + JSON.stringify(lm);
23 }
24
25 // NOTE: with this variant's special rule,
26 // some extra repetitions could be detected... TODO (...)
27
28 static GenRandInitFen(randomness) {
29 return ChessRules.GenRandInitFen(randomness) + " null";
30 }
31
32 setOtherVariables(fen) {
33 super.setOtherVariables(fen);
34 this.lastMove = [JSON.parse(V.ParseFen(fen).lastMove)]; //may be null
35 }
36
37 canIplay(side, [x, y]) {
38 if (super.canIplay(side, [x, y])) return true;
39 if (this.turn != side) return false;
40 // Check if playing last move, reversed:
41 const L = this.lastMove.length;
42 const lm = this.lastMove[L-1];
43 return (!!lm && !lm.noRef && x == lm.end.x && y == lm.end.y);
44 }
45
46 getPotentialMovesFrom([x, y]) {
47 if (this.getColor(x, y) != this.turn) {
48 const L = this.lastMove.length;
49 const lm = this.lastMove[L-1];
50 const beforeLastRank = (this.turn == 'w' ? 1 : 6);
51 if (
52 !!lm && !lm.noRef && x == lm.end.x && y == lm.end.y &&
53 (this.getPiece(x, y) != V.PAWN || x != beforeLastRank)
54 ) {
55 let revLm = JSON.parse(JSON.stringify(lm));
56 let tmp = revLm.appear;
57 revLm.appear = revLm.vanish;
58 revLm.vanish = tmp;
59 tmp = revLm.start;
60 revLm.start = revLm.end;
61 revLm.end = tmp;
62 return [revLm];
63 }
64 return [];
65 }
66 return super.getPotentialMovesFrom([x, y]);
67 }
68
69 // NOTE: do not take refusal move into account here (two own moves)
70 atLeastTwoMoves() {
71 let movesCounter = 0;
72 const color = this.turn;
73 for (let i = 0; i < V.size.x; i++) {
74 for (let j = 0; j < V.size.y; j++) {
75 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
76 const moves = this.getPotentialMovesFrom([i, j]);
77 for (let m of moves) {
78 if (m.vanish[0].c == color && this.filterValid([m]).length > 0) {
79 movesCounter++;
80 if (movesCounter >= 2) return true;
81 }
82 }
83 }
84 }
85 }
86 return false;
87 }
88
89 filterValid(moves) {
90 if (moves.length == 0) return [];
91 const color = this.turn;
92 const L = this.lastMove.length;
93 const lm = this.lastMove[L-1];
94 return moves.filter(m => {
95 if (
96 !!lm && !!lm.refusal &&
97 m.start.x == lm.end.x && m.start.y == lm.end.y &&
98 m.end.x == lm.start.x && m.end.y == lm.start.y &&
99 (m.vanish[0].p != V.PAWN || m.appear[0].p == lm.vanish[0].p)
100 ) {
101 return false;
102 }
103 // NOTE: not using this.play()/undo() ==> infinite loop
104 V.PlayOnBoard(this.board, m);
105 if (m.appear[0].p == V.KING)
106 this.kingPos[m.appear[0].c] = [m.appear[0].x, m.appear[0].y];
107 const res = !this.underCheck(color);
108 V.UndoOnBoard(this.board, m);
109 if (m.vanish[0].p == V.KING)
110 this.kingPos[m.vanish[0].c] = [m.vanish[0].x, m.vanish[0].y];
111 return res;
112 });
113 }
114
115 prePlay(move) {
116 const L = this.lastMove.length;
117 const lm = this.lastMove[L-1];
118 if (
119 // My previous move was already refused?
120 (!!lm && this.getColor(lm.end.x, lm.end.y) == this.turn) ||
121 // I've only one move available?
122 !this.atLeastTwoMoves()
123 ) {
124 move.noRef = true;
125 }
126 // NOTE: refusal could be recomputed, but, it's easier like this
127 if (move.vanish[0].c != this.turn) move.refusal = true;
128 }
129
130 getEpSquare(move) {
131 if (!move.refusal) return super.getEpSquare(move);
132 return null; //move refusal
133 }
134
135 postPlay(move) {
136 if (!move.refusal) super.postPlay(move);
137 else {
138 const L = this.lastMove.length;
139 const lm = this.lastMove[L-1];
140 this.disaggregateFlags(JSON.parse(lm.flags));
141 }
142 // NOTE: explicitely give fields, because some are assigned in BaseGame
143 let mvInLm = {
144 start: move.start,
145 end: move.end,
146 appear: move.appear,
147 vanish: move.vanish,
148 flags: move.flags
149 };
150 if (!!move.noRef) mvInLm.noRef = true;
151 if (!!move.refusal) mvInLm.refusal = true;
152 this.lastMove.push(mvInLm);
153 }
154
155 postUndo(move) {
156 if (!move.refusal) super.postUndo(move);
157 this.lastMove.pop();
158 }
159
160 getAllPotentialMoves() {
161 const color = this.turn;
162 const L = this.lastMove.length;
163 const lm = this.lastMove[L-1];
164 let potentialMoves = [];
165 for (let i = 0; i < V.size.x; i++) {
166 for (let j = 0; j < V.size.y; j++) {
167 if (
168 this.board[i][j] != V.EMPTY &&
169 (
170 this.getColor(i, j) == color ||
171 // Add move refusal:
172 (!!lm && lm.end.x == i && lm.end.y == j)
173 )
174 ) {
175 Array.prototype.push.apply(
176 potentialMoves,
177 this.getPotentialMovesFrom([i, j])
178 );
179 }
180 }
181 }
182 return potentialMoves;
183 }
184
185 getComputerMove() {
186 // Just play at random for now... (TODO?)
187 // Refuse last move with odds 1/3.
188 const moves = this.getAllValidMoves();
189 const refusal = moves.find(m => m.vanish[0].c != this.turn);
190 if (!!refusal) {
191 if (Math.random() <= 0.33) return refusal;
192 const others = moves.filter(m => m.vanish[0].c == this.turn);
193 return others[randInt(others.length)];
194 }
195 else return moves[randInt(moves.length)];
196 }
197
198 getNotation(move) {
199 if (move.vanish[0].c != this.turn) return "Refuse";
200 return super.getNotation(move);
201 }
202
203 };