Early draft of Fanorona
[vchess.git] / client / src / variants / Fanorona.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class FanoronaRules extends ChessRules {
5
6 static get HasFlags() {
7 return false;
8 }
9
10 static get HasEnpassant() {
11 return false;
12 }
13
14 static get Monochrome() {
15 return true;
16 }
17
18 static get Lines() {
19 let lines = [];
20 // Draw all inter-squares lines, shifted:
21 for (let i = 0; i < V.size.x; i++)
22 lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]);
23 for (let j = 0; j < V.size.y; j++)
24 lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]);
25 const columnDiags = [
26 [[0.5, 0.5], [2.5, 2.5]],
27 [[0.5, 2.5], [2.5, 0.5]],
28 [[2.5, 0.5], [4.5, 2.5]],
29 [[4.5, 0.5], [2.5, 2.5]]
30 ];
31 for (let j of [0, 2, 4, 6]) {
32 lines = lines.concat(
33 columnDiags.map(L => [[L[0][0], L[0][1] + j], [L[1][0], L[1][1] + j]])
34 );
35 }
36 return lines;
37 }
38
39 static get Notoodark() {
40 return true;
41 }
42
43 static GenRandInitFen() {
44 return "ppppppppp/ppppppppp/pPpP1pPpP/PPPPPPPPP/PPPPPPPPP w 0";
45 }
46
47 setOtherVariables(fen) {
48 // Local stack of captures during a turn (squares + directions)
49 this.captures = [];
50 }
51
52 static get size() {
53 return { x: 5, y: 9 };
54 }
55
56 static get PIECES() {
57 return [V.PAWN];
58 }
59
60 getPiece() {
61 return V.PAWN;
62 }
63
64 getPpath(b) {
65 return "Fanorona/" + b;
66 }
67
68 //TODO
69 //getPPpath() {}
70
71 getPotentialMovesFrom([x, y]) {
72 // NOTE: (x + y) % 2 == 0 ==> has diagonals
73 // TODO
74 // Même stratégie que Yote, revenir sur ses pas si stop avant de tout capturer
75 // Mais première capture obligatoire (si this.captures.length == 0).
76 // After a capture: allow only capturing.
77 // Warning: case 3 on Wikipedia page, if both percussion & aspiration,
78 // two different moves, cannot take all ==> adjust getPPpath showing arrows.
79 // nice looking arrows, with something representing a capture at its end...
80 return [];
81 }
82
83 filterValid(moves) {
84 return moves;
85 }
86
87 getCheckSquares() {
88 return [];
89 }
90
91 //TODO: function aux to detect if continuation captures
92 //(not trivial, but not difficult)
93
94 play(move) {
95 const color = this.turn;
96 move.turn = color; //for undo
97 const captureNotEnding = (
98 move.vanish.length >= 2 &&
99 true //TODO: detect if there are continuation captures
100 );
101 this.captures.push(captureNotEnding); //TODO: something more structured
102 //with square + direction of capture
103 if (captureNotEnding) move.notTheEnd = true;
104 else {
105 this.turn = oppCol;
106 this.movesCount++;
107 }
108 this.postPlay(move);
109 }
110
111 undo(move) {
112 V.UndoOnBoard(this.board, move);
113 this.captures.pop();
114 if (move.turn != this.turn) {
115 this.turn = move.turn;
116 this.movesCount--;
117 }
118 this.postUndo(move);
119 }
120
121 getCurrentScore() {
122 const color = this.turn;
123 // If no stones on board, I lose
124 if (
125 this.board.every(b => {
126 return b.every(cell => {
127 return (cell == "" || cell[0] != color);
128 });
129 })
130 ) {
131 return (color == 'w' ? "0-1" : "1-0");
132 }
133 return "*";
134 }
135
136 getComputerMove() {
137 const moves = super.getAllValidMoves();
138 if (moves.length == 0) return null;
139 const color = this.turn;
140 // Capture available? If yes, play it
141 let captures = moves.filter(m => m.vanish.length >= 2);
142 let mvArray = [];
143 while (captures.length >= 1) {
144 // Then just pick random captures (trying to maximize)
145 let candidates = captures.filter(c => !!c.notTheEnd);
146 let mv = null;
147 if (candidates.length >= 1) mv = candidates[randInt(candidates.length)];
148 else mv = captures[randInt(captures.length)];
149 this.play(mv);
150 captures = (this.turn == color ? super.getAllValidMoves() : []);
151 }
152 if (mvArray.length >= 1) {
153 for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
154 return mvArray;
155 }
156 // Just play a random move, which if possible do not let a capture
157 let candidates = [];
158 for (let m of moves) {
159 this.play(m);
160 const moves2 = super.getAllValidMoves();
161 if (moves2.every(m2 => m2.vanish.length <= 1))
162 candidates.push(m);
163 this.undo(m);
164 }
165 if (candidates.length >= 1) return candidates[randInt(candidates.length)];
166 return moves[randInt(moves.length)];
167 }
168
169 getNotation(move) {
170 return (
171 V.CoordsToSquare(move.start) +
172 (move.vanish.length >= 2 ? "x" : "") +
173 V.CoordsToSquare(move.end)
174 );
175 }
176
177 };