8721227f7ef266480fdbfeddc489a2e87a328329
[xogo.git] / variants / Hex / class.js
1 import ChessRules from "/base_rules.js";
2 import PiPo from "/utils/PiPo.js";
3 import Move from "/utils/Move.js";
4
5 export default class HexRules extends ChessRules {
6
7 static get Options() {
8 return {
9 input: [
10 {
11 label: "Board size",
12 variable: "bsize",
13 type: "number",
14 defaut: 11
15 },
16 {
17 label: "Swap",
18 variable: "swap",
19 type: "checkbox",
20 defaut: true
21 }
22 ]
23 };
24 }
25
26 get hasFlags() {
27 return false;
28 }
29 get hasEnpassant() {
30 return false;
31 }
32 get hasReserve() {
33 return false;
34 }
35 get noAnimate() {
36 return true;
37 }
38
39 doClick(coords) {
40 if (
41 this.playerColor != this.turn ||
42 (
43 this.board[coords.x][coords.y] != "" &&
44 (!this.options["swap"] || this.movesCount >= 2)
45 )
46 ) {
47 return null;
48 }
49 let res = new Move({
50 start: {x: coords.x, y: coords.y},
51 appear: [
52 new PiPo({
53 x: coords.x,
54 y: coords.y,
55 c: this.turn,
56 p: 'p'
57 })
58 ],
59 vanish: []
60 });
61 if (this.board[coords.x][coords.y] != "") {
62 res.vanish.push(
63 new PiPo({
64 x: coords.x,
65 y: coords.y,
66 c: C.GetOppCol(this.turn),
67 p: 'p'
68 })
69 );
70 }
71 return res;
72 }
73
74 genRandInitFen() {
75 // NOTE: size.x == size.y (square boards)
76 const emptyCount = C.FenEmptySquares(this.size.x);
77 return (emptyCount + "/").repeat(this.size.x).slice(0, -1) + " w 0";
78 }
79
80 getSvgChessboard() {
81 // NOTE: with small margin seems nicer
82 let width = 173.2 * this.size.y + 173.2 * (this.size.y-1) / 2 + 30,
83 height = 50 + Math.floor(150 * this.size.x) + 30,
84 min_x = -86.6 - 15,
85 min_y = -100 - 15;
86 if (this.size.ratio < 1) {
87 // Rotate by 30 degrees to display vertically
88 [width, height] = [height, width];
89 [min_x, min_y] = [min_y, min_x];
90 }
91 let board = `
92 <svg
93 viewBox="${min_x} ${min_y} ${width} ${height}"
94 class="chessboard_SVG">
95 <defs>
96 <g id="hexa">
97 <polygon
98 style="stroke:#000000;stroke-width:1"
99 points="0,-100.0 86.6,-50.0 86.6,50.0 0,100.0 -86.6,50.0 -86.6,-50.0"
100 />
101 </g>
102 </defs>`;
103 board += "<g";
104 if (this.size.ratio < 1)
105 board += ` transform="rotate(30)"`
106 board += ">";
107 for (let i=0; i < this.size.x; i++) {
108 for (let j=0; j < this.size.y; j++) {
109 board += `
110 <use
111 href="#hexa"
112 class="neutral-square"
113 id="${this.coordsToId({x: i, y: j})}"
114 x="${173.2*j + 86.6*i}"
115 y="${150*i}"
116 />`;
117 }
118 }
119 board += `</g><g style="fill:none;stroke-width:10"`;
120 if (this.size.ratio < 1)
121 board += ` transform="rotate(30)"`
122 // Goals: up/down/left/right
123 board += `><polyline style="stroke:red" points="`
124 for (let i=0; i<=2*this.size.y; i++)
125 board += ((i-1)*86.6) + ',' + (i % 2 == 0 ? -50 : -100) + ' ';
126 board += `"/><polyline style="stroke:red" points="`;
127 for (let i=1; i<=2*this.size.y; i++) {
128 const jShift = 200 * Math.floor((this.size.y+1)/2) +
129 100 * (Math.floor(this.size.y/2) - 1) +
130 (i % 2 == 0 ? -50 : 0) +
131 (this.size.y % 2 == 0 ? 50 : 0);
132 board += ((i+this.size.y-2)*86.6) + ',' + jShift + ' ';
133 }
134 board += `"/><polyline style="stroke:blue" points="`;
135 let sumY = -100;
136 for (let i=0; i<=2*this.size.x; i++) {
137 board += ((Math.floor(i/2)-1) * 86.6) + ',' +
138 (sumY += (i % 2 == 0 ? 50 : 100)) + ' ';
139 }
140 board += `"/><polyline style="stroke:blue" points="`;
141 sumY = -100;
142 for (let i=0; i<2*this.size.x; i++) {
143 board += (173.2*this.size.x + (Math.floor(i/2)-1) * 86.6) + ',' +
144 (sumY += (i % 2 == 0 ? 50 : 100)) + ' ';
145 }
146 board += `"/></g></svg>`;
147 return board;
148 }
149
150 setupPieces() {
151 for (let i=0; i<this.size.x; i++) {
152 for (let j=0; j<this.size.y; j++) {
153 if (this.board[i][j] != "") {
154 const sqColor = (this.getColor(i, j) == 'w' ? "white" : "black");
155 const elt = document.getElementById(this.coordsToId({x: i, y: j}));
156 elt.classList.remove("neutral-square");
157 elt.classList.add("bg-" + sqColor);
158 }
159 }
160 }
161 }
162
163 get size() {
164 const baseRatio = 1.6191907514450865; //2801.2 / 1730, "widescreen"
165 const rotate = window.innerWidth < window.innerHeight; //"vertical screen"
166 return {
167 x: this.options["bsize"],
168 y: this.options["bsize"],
169 ratio: (rotate ? 1 / baseRatio : baseRatio)
170 };
171 }
172
173 play(move) {
174 this.playOnBoard(move);
175 this.movesCount++;
176 this.turn = C.GetOppCol(this.turn);
177 }
178
179 getCurrentScore(move) {
180 const oppCol = C.GetOppCol(this.turn);
181 // Search for connecting path of opp color:
182 let explored = {}, component;
183 let min, max;
184 const getIndex = (x, y) => x + "." + y;
185 // Explore one connected component:
186 const neighborsSearch = ([x, y], index) => {
187 // Let's say "white" connects on x and "black" on y
188 const z = (oppCol == 'w' ? x : y);
189 if (z < min)
190 min = z;
191 if (z > max)
192 max = z;
193 explored[index] = true;
194 component[index] = true;
195 for (let [dx, dy] of super.pieces()['k'].moves[0].steps) {
196 const [nx, ny] = [x + dx, y + dy];
197 const nidx = getIndex(nx, ny);
198 if (
199 this.onBoard(nx, ny) &&
200 this.getColor(nx, ny) == oppCol &&
201 !component[nidx]
202 ) {
203 neighborsSearch([nx, ny], nidx);
204 }
205 }
206 };
207 // Explore all components:
208 for (let i=0; i<this.size.x; i++) {
209 for (let j=0; j<this.size.y; j++) {
210 const index = getIndex(i, j);
211 if (this.getColor(i, j) == oppCol && !explored[index]) {
212 component = {};
213 [min, max] = [this.size.x, 0];
214 neighborsSearch([i, j], index);
215 if (max - min == this.size.x - 1)
216 return (oppCol == "w" ? "1-0" : "0-1");
217 }
218 }
219 }
220 return "*";
221 }
222
223 playVisual(move) {
224 move.vanish.forEach(v => {
225 let elt = document.getElementById(this.coordsToId({x: v.x, y: v.y}));
226 elt.classList.remove("bg-" + (v.c == 'w' ? "white" : "black"));
227 });
228 move.appear.forEach(a => {
229 let elt = document.getElementById(this.coordsToId({x: a.x, y: a.y}));
230 elt.classList.add("bg-" + (a.c == 'w' ? "white" : "black"));
231 });
232 }
233
234 };