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