Advance on Checkered. TODO: fix checks detection by checkered pieces
[xogo.git] / variants / Weiqi / class.js
1 import ChessRules from "/base_rules.js";
2 import Move from "/utils/Move.js";
3 import PiPo from "/utils/PiPo.js";
4 import {ArrayFun} from "/utils/array.js";
5
6 export default class WeiqiRules extends ChessRules {
7
8 static get Options() {
9 return {
10 input: [
11 {
12 label: "Board size",
13 variable: "bsize",
14 type: "number",
15 defaut: 9
16 },
17 {
18 label: "One color",
19 variable: "onecolor",
20 type: "checkbox",
21 defaut: false
22 }
23 ]
24 };
25 }
26
27 get hasFlags() {
28 return false;
29 }
30 get hasEnpassant() {
31 return false;
32 }
33 get clickOnly() {
34 return true;
35 }
36
37 getSvgChessboard() {
38 const flipped = (this.playerColor == 'b');
39 let board = `
40 <svg
41 viewBox="0 0 ${10*(this.size.y)} ${10*(this.size.x)}"
42 class="chessboard_SVG">`;
43 for (let i=0; i < this.size.x; i++) {
44 for (let j=0; j < this.size.y; j++) {
45 const ii = (flipped ? this.size.x - 1 - i : i);
46 const jj = (flipped ? this.size.y - 1 - j : j);
47 board += `
48 <rect
49 id="${this.coordsToId({x: ii, y: jj})}"
50 width="10"
51 height="10"
52 x="${10*j}"
53 y="${10*i}"
54 fill="transparent"
55 />`;
56 }
57 }
58 // Add lines to delimitate "squares"
59 for (let i = 0; i < this.size.x; i++) {
60 const y = i * 10 + 5, maxX = this.size.y * 10 - 5;
61 board += `
62 <line x1="5" y1="${y}" x2="${maxX}" y2="${y}"
63 stroke="black" stroke-width="0.2"/>`;
64 }
65 for (let i = 0; i < this.size.x; i++) {
66 const x = i * 10 + 5, maxY = this.size.x * 10 - 5;
67 board += `
68 <line x1="${x}" y1="5" x2="${x}" y2="${maxY}"
69 stroke="black" stroke-width="0.2"/>`;
70 }
71 board += "</svg>";
72 return board;
73 }
74
75 get size() {
76 return {
77 x: this.options["bsize"],
78 y: this.options["bsize"],
79 };
80 }
81
82 genRandInitBaseFen() {
83 const fenLine = C.FenEmptySquares(this.size.y);
84 return {
85 fen: (fenLine + '/').repeat(this.size.x - 1) + fenLine,
86 o: {}
87 };
88 }
89
90 constructor(o) {
91 super(o);
92 if (!o.diagram) {
93 this.passListener = () => {
94 if (this.turn == this.playerColor) {
95 let mv = {
96 // Need artificial start/end for animate (TODO?)
97 start: {x: -1, y: -1},
98 end: {x: -1, y: -1},
99 appear: [],
100 vanish: [],
101 pass: true
102 };
103 this.buildMoveStack(mv);
104 }
105 };
106 // Show pass btn
107 let passBtn = document.createElement("button");
108 C.AddClass_es(passBtn, "pass-btn");
109 passBtn.innerHTML = "pass";
110 passBtn.addEventListener("click", this.passListener);
111 let container = document.getElementById(this.containerId);
112 container.appendChild(passBtn);
113 }
114 }
115
116 removeListeners() {
117 super.removeListeners();
118 let passBtn_arr = document.getElementsByClassName("pass-btn");
119 if (passBtn_arr.length >= 1)
120 passBtn_arr[0].removeEventListener("click", this.passListener);
121 }
122
123 pieces(color, x, y) {
124 let classe_s = ["stone"];
125 if (this.options["onecolor"] && color == 'w')
126 classe_s.push("one-color");
127 return {
128 's': {
129 "class": classe_s,
130 moves: []
131 }
132 };
133 }
134
135 doClick(coords) {
136 const [x, y] = [coords.x, coords.y];
137 if (this.board[x][y] != "" || this.turn != this.playerColor)
138 return null;
139 const color = this.turn;
140 const oppCol = C.GetOppTurn(color);
141 let move = new Move({
142 appear: [new PiPo({x: x, y: y, c: color, p: 's'})],
143 vanish: [],
144 start: {x: x, y: y}
145 });
146 this.playOnBoard(move); //put the stone
147 let captures = [];
148 let inCaptures = {};
149 let explored = ArrayFun.init(this.size.x, this.size.y, false);
150 const suicide = !this.searchForEmptySpace([x, y], color, explored);
151 for (let s of [[0, 1], [1, 0], [0, -1], [-1, 0]]) {
152 const [i, j] = [x + s[0], y + s[1]];
153 if (this.onBoard(i, j) && !inCaptures[i + "." + j]) {
154 if (this.getColor(i, j) == oppCol) {
155 // Free space for opponent => not a capture
156 let oppExplored = ArrayFun.init(this.size.x, this.size.y, false);
157 const captureSomething =
158 !this.searchForEmptySpace([i, j], oppCol, oppExplored);
159 if (captureSomething) {
160 for (let ii = 0; ii < this.size.x; ii++) {
161 for (let jj = 0; jj < this.size.y; jj++) {
162 if (oppExplored[ii][jj]) {
163 captures.push(new PiPo({x: ii, y: jj, c: oppCol, p: 's'}));
164 inCaptures[ii + "." + jj] = true;
165 }
166 }
167 }
168 }
169 }
170 }
171 }
172 this.undoOnBoard(move); //remove the stone
173 if (suicide && captures.length == 0)
174 return null;
175 Array.prototype.push.apply(move.vanish, captures);
176 return move;
177 }
178
179 searchForEmptySpace([x, y], color, explored) {
180 explored[x][y] = true;
181 for (let s of [[1, 0], [0, 1], [-1, 0], [0, -1]]) {
182 const [i, j] = [x + s[0], y + s[1]];
183 if (
184 this.onBoard(i, j) &&
185 !explored[i][j] &&
186 (
187 this.board[i][j] == "" ||
188 (
189 this.getColor(i, j) == color &&
190 this.searchForEmptySpace([i, j], color, explored)
191 )
192 )
193 ) {
194 return true;
195 }
196 }
197 return false;
198 }
199
200 play(move) {
201 if (move.pass) {
202 if (this.turn != this.playerColor)
203 super.displayMessage(null, "pass", "pass-text", 2000);
204 this.turn = C.GetOppTurn(this.turn);
205 }
206 else
207 super.play(move);
208 }
209
210 filterValid(moves) {
211 // Suicide check not here, because side-computation of captures
212 return moves;
213 }
214
215 getCurrentScore() {
216 return "*"; //Go game is a little special...
217 }
218
219 };