Add Fanorona
[vchess.git] / client / src / variants / Konane.js
CommitLineData
d2af3400 1import { ChessRules, Move, PiPo } from "@/base_rules";
c11afcdf 2import { randInt } from "@/utils/alea";
d2af3400 3
d2af3400
BA
4export class KonaneRules extends ChessRules {
5
6 static get HasFlags() {
7 return false;
8 }
9
10 static get HasEnpassant() {
11 return false;
12 }
13
cbe95378
BA
14 static get ReverseColors() {
15 return true;
16 }
17
cbe95378
BA
18 getPiece() {
19 return V.PAWN;
20 }
21
d2af3400
BA
22 getPpath(b) {
23 return "Konane/" + b;
24 }
25
26 static IsGoodPosition(position) {
27 if (position.length == 0) return false;
28 const rows = position.split("/");
29 if (rows.length != V.size.x) return false;
30 for (let row of rows) {
31 let sumElts = 0;
32 for (let i = 0; i < row.length; i++) {
cdab5663 33 if (row[i].toLowerCase() == V.PAWN) sumElts++;
d2af3400
BA
34 else {
35 const num = parseInt(row[i], 10);
36 if (isNaN(num) || num <= 0) return false;
37 sumElts += num;
38 }
39 }
40 if (sumElts != V.size.y) return false;
41 }
42 return true;
43 }
44
45 static GenRandInitFen() {
46 return (
47 "PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/" +
48 "PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP w 0"
49 );
50 }
51
52 setOtherVariables(fen) {
53 this.captures = []; //reinit for each move
54 }
55
56 hoverHighlight(x, y) {
57 if (this.movesCount >= 2) return false;
58 const c = this.turn;
59 if (c == 'w') return (x == y && [0, 3, 4, 7].includes(x));
60 // "Black": search for empty square and allow nearby
61 for (let i of [0, 3, 4, 7]) {
62 if (this.board[i][i] == V.EMPTY)
63 return (Math.abs(x - i) + Math.abs(y - i) == 1)
64 }
65 }
66
67 onlyClick([x, y]) {
68 return (
69 this.movesCount <= 1 ||
70 // TODO: next line theoretically shouldn't be required...
71 (this.movesCount == 2 && this.getColor(x, y) != this.turn)
72 );
73 }
74
75 doClick([x, y]) {
76 if (this.movesCount >= 2) return null;
77 const color = this.turn;
78 if (color == 'w') {
79 if (x != y || ![0, 3, 4, 7].includes(x)) return null;
80 return new Move({
81 appear: [],
82 vanish: [ new PiPo({ x: x, y: y, c: color, p: V.PAWN }) ],
83 end: { x: x, y: y }
84 });
85 }
86 // "Black": search for empty square and allow nearby
87 for (let i of [0, 3, 4, 7]) {
88 if (this.board[i][i] == V.EMPTY) {
89 if (Math.abs(x - i) + Math.abs(y - i) != 1) return null;
90 return new Move({
91 appear: [],
92 vanish: [ new PiPo({ x: x, y: y, c: color, p: V.PAWN }) ],
93 end: { x: x, y: y }
94 });
95 }
96 }
97 }
98
99 getPotentialMovesFrom([x, y]) {
100 if (this.movesCount <= 1) {
101 const mv = this.doClick([x, y]);
102 return (!!mv ? [mv] : []);
103 }
104 const L = this.captures.length;
105 const c = (L > 0 ? this.captures[L-1] : null);
106 const color = this.turn;
107 const oppCol = V.GetOppCol(color);
108 let step = null;
109 let moves = [];
110 if (!!c) {
111 if (x != c.end.x || y != c.end.y) return [];
112 step = [(c.end.x - c.start.x) / 2, (c.end.y - c.start.y) / 2];
113 // Add move to adjacent empty square to mark "end of capture"
114 moves.push(
115 new Move({
116 appear: [],
117 vanish: [],
118 start: { x: x, y: y },
119 end: { x: x - step[0], y: y - step[1] }
120 })
121 );
122 }
123 // Examine captures from here
124 for (let s of (!!step ? [step] : V.steps[V.ROOK])) {
125 let [i, j] = [x + 2*s[0], y + 2*s[1]];
126 if (
127 !!c || //avoid redundant checks if continuation
128 (
129 V.OnBoard(i, j) &&
130 this.board[i][j] == V.EMPTY &&
131 this.board[i - s[0]][j - s[1]] != V.EMPTY &&
132 this.getColor(i - s[0], j - s[1]) == oppCol
133 )
134 ) {
135 let mv = new Move({
136 appear: [
137 new PiPo({ x: i, y: j, c: color, p: V.PAWN })
138 ],
139 vanish: [
140 new PiPo({ x: x, y: y, c: color, p: V.PAWN }),
141 new PiPo({ x: i - s[0], y: j - s[1], c: oppCol, p: V.PAWN })
142 ]
143 });
144 // Is there another capture possible then?
145 [i, j] = [i + 2*s[0], j + 2*s[1]];
146 if (
147 V.OnBoard(i, j) &&
148 this.board[i][j] == V.EMPTY &&
149 this.board[i - s[0]][j - s[1]] != V.EMPTY &&
150 this.getColor(i - s[0], j - s[1]) == oppCol
151 ) {
152 mv.end.moreCapture = true;
153 }
154 moves.push(mv);
155 }
156 }
157 return moves;
158 }
159
160 filterValid(moves) {
161 return moves;
162 }
163
164 getCheckSquares() {
165 return [];
166 }
167
168 getCurrentScore() {
169 if (this.atLeastOneMove()) return "*";
170 return (this.turn == "w" ? "0-1" : "1-0");
171 }
172
173 play(move) {
174 V.PlayOnBoard(this.board, move);
175 if (!move.end.moreCapture) {
176 this.turn = V.GetOppCol(this.turn);
177 this.movesCount++;
178 this.captures = [];
179 }
180 else {
181 this.captures.push(
182 {
183 start: move.start,
184 end: { x: move.end.x, y: move.end.y }
185 }
186 );
187 }
188 }
189
190 undo(move) {
191 V.UndoOnBoard(this.board, move);
192 if (!move.end.moreCapture) {
193 this.turn = V.GetOppCol(this.turn);
194 this.movesCount--;
195 }
196 else this.captures.pop();
197 }
198
c11afcdf
BA
199 getComputerMove() {
200 const color = this.turn;
201 let mvArray = [];
202 let mv = null;
203 const undoAll = () => {
204 for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
205 };
206 // Just play random moves (for now at least. TODO?)
207 while (this.turn == color) {
208 let moves = super.getAllValidMoves();
209 if (moves.length == 0) {
210 // Shouldn't happen, but...
211 undoAll();
212 return null;
213 }
214 mv = moves[randInt(moves.length)];
215 mvArray.push(mv);
216 this.play(mv);
217 }
218 undoAll();
219 return (mvArray.length > 1 ? mvArray : mvArray[0]);
d2af3400
BA
220 }
221
222 getNotation(move) {
223 if (this.movesCount <= 1) return V.CoordsToSquare(move.start) + "X";
224 if (move.vanish.length == 0) return "end";
225 return V.CoordsToSquare(move.start) + "x" + V.CoordsToSquare(move.end);
226 }
227
228};