Progress on Go game
[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.genFenOnly && !o.diagram) {
93
94 this.passListener = () => this.play({pass: true}); //TODO: wrong, need to use buildMoveStack (warning empty move...)
95
96 // Show pass btn
97 let passBtn = document.createElement("button");
98 C.AddClass_es(passBtn, "pass-btn");
99 passBtn.innerHTML = "pass";
100 passBtn.addEventListener("click", this.passListener);
101 let container = document.getElementById(this.containerId);
102 container.appendChild(passBtn);
103 }
104 }
105
106 removeListeners() {
107 super.removeListeners();
108 let passBtn = document.getElementsByClassName("pass-btn")[0];
109 passBtn.removeEventListener("click", this.passListener);
110 }
111
112 pieces(color, x, y) {
113 let classe_s = ["stone"];
114 if (this.options["onecolor"] && color == 'w')
115 classe_s.push("one-color");
116 return {
117 's': {
118 "class": classe_s,
119 moves: []
120 }
121 };
122 }
123
124 doClick(coords) {
125 const [x, y] = [coords.x, coords.y];
126 if (this.board[x][y] != "")
127 return null;
128 const color = this.turn;
129 const oppCol = C.GetOppCol(color);
130 let move = new Move({
131 appear: [ new PiPo({ x: x, y: y, c: color, p: 's' }) ],
132 vanish: [],
133 start: {x: x, y: y}
134 });
135 this.playOnBoard(move); //put the stone
136 let noSuicide = false;
137 let captures = [];
138 for (let s of [[0, 1], [1, 0], [0, -1], [-1, 0]]) {
139 const [i, j] = [x + s[0], y + s[1]];
140 if (this.onBoard(i, j)) {
141 if (this.board[i][j] == "")
142 noSuicide = true; //clearly
143 else if (this.getColor(i, j) == color) {
144 // Free space for us = not a suicide
145 if (!noSuicide) {
146 let explored = ArrayFun.init(this.size.x, this.size.y, false);
147 noSuicide = this.searchForEmptySpace([i, j], color, explored);
148 }
149 }
150 else {
151 // Free space for opponent = not a capture
152 let explored = ArrayFun.init(this.size.x, this.size.y, false);
153 const captureSomething =
154 !this.searchForEmptySpace([i, j], oppCol, explored);
155 if (captureSomething) {
156 for (let ii = 0; ii < this.size.x; ii++) {
157 for (let jj = 0; jj < this.size.y; jj++) {
158 if (explored[ii][jj])
159 captures.push(new PiPo({ x: ii, y: jj, c: oppCol, p: 's' }));
160 }
161 }
162 }
163 }
164 }
165 }
166 this.undoOnBoard(move); //remove the stone
167 if (!noSuicide && captures.length == 0)
168 return null;
169 Array.prototype.push.apply(move.vanish, captures);
170 return move;
171 }
172
173 searchForEmptySpace([x, y], color, explored) {
174 if (explored[x][y])
175 return false; //didn't find empty space
176 explored[x][y] = true;
177 let res = false;
178 for (let s of [[1, 0], [0, 1], [-1, 0], [0, -1]]) {
179 const [i, j] = [x + s[0], y + s[1]];
180 if (this.onBoard(i, j)) {
181 if (this.board[i][j] == "")
182 res = true;
183 else if (this.getColor(i, j) == color)
184 res = this.searchForEmptySpace([i, j], color, explored) || res;
185 }
186 }
187 return res;
188 }
189
190 play(move) {
191 if (move.pass) {
192 if (this.turn != this.playerColor)
193 super.displayMessage(null, "pass", "pass-text", 2000);
194 else
195 this.turn = C.GetOppCol(this.turn);
196 }
197 else
198 super.play(move);
199 }
200
201 filterValid(moves) {
202 // Suicide check not here, because side-computation of captures
203 return moves;
204 }
205
206 getCurrentScore() {
207 return "*"; //Go game is a little special...
208 }
209
210 };