Add Knightmate2: two kings, as in Spartan Chess
[vchess.git] / client / src / variants / Knightmate2.js
1 import { ChessRules } from "@/base_rules";
2
3 export class Knightmate2Rules extends ChessRules {
4
5 static get HasFlags() {
6 return false;
7 }
8
9 static get COMMONER() {
10 return "c";
11 }
12
13 static get PIECES() {
14 return ChessRules.PIECES.concat([V.COMMONER]);
15 }
16
17 getPpath(b) {
18 return ([V.KING, V.COMMONER].includes(b[1]) ? "Knightmate/" : "") + b;
19 }
20
21 static IsGoodPosition(position) {
22 if (position.length == 0) return false;
23 const rows = position.split("/");
24 if (rows.length != V.size.x) return false;
25 let kings = { "k": 0, "K": 0 };
26 for (let row of rows) {
27 let sumElts = 0;
28 for (let i = 0; i < row.length; i++) {
29 if (['K','k'].includes(row[i])) kings[row[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 // 1 or 2 kings should be on board.
40 if (Object.values(kings).some(k => ![1, 2].includes(k))) return false;
41 return true;
42 }
43
44 scanKings() {}
45
46 static GenRandInitFen(randomness) {
47 return (
48 ChessRules.GenRandInitFen(randomness)
49 .replace(/k/g, 'c').replace(/K/g, 'C')
50 .replace(/n/g, 'k').replace(/N/g, 'K')
51 );
52 }
53
54 getPotentialMovesFrom([x, y]) {
55 switch (this.getPiece(x, y)) {
56 case V.COMMONER:
57 return this.getPotentialCommonerMoves([x, y]);
58 default:
59 return super.getPotentialMovesFrom([x, y]);
60 }
61 }
62
63 getPotentialCommonerMoves(sq) {
64 return this.getSlideNJumpMoves(
65 sq,
66 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
67 "oneStep"
68 );
69 }
70
71 getPotentialKingMoves(sq) {
72 return super.getPotentialKnightMoves(sq);
73 }
74
75 isAttacked(sq, color) {
76 return (
77 this.isAttackedByCommoner(sq, color) ||
78 this.isAttackedByPawn(sq, color) ||
79 this.isAttackedByRook(sq, color) ||
80 this.isAttackedByBishop(sq, color) ||
81 this.isAttackedByQueen(sq, color) ||
82 this.isAttackedByKing(sq, color)
83 );
84 }
85
86 isAttackedByKing(sq, color) {
87 return this.isAttackedBySlideNJump(
88 sq,
89 color,
90 V.KING,
91 V.steps[V.KNIGHT],
92 "oneStep"
93 );
94 }
95
96 isAttackedByCommoner(sq, color) {
97 return this.isAttackedBySlideNJump(
98 sq,
99 color,
100 V.COMMONER,
101 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
102 "oneStep"
103 );
104 }
105
106 postPlay() {}
107 postUndo() {}
108
109 // NOTE: 4 next functions (almost) copy-paste from Spartan Chess
110 getKingsPos(color) {
111 let kings = [];
112 for (let i=0; i<8; i++) {
113 for (let j=0; j<8; j++) {
114 if (
115 this.board[i][j] != V.EMPTY &&
116 this.getColor(i, j) == color &&
117 this.getPiece(i, j) == V.KING
118 ) {
119 kings.push({ x: i, y: j });
120 }
121 }
122 }
123 return kings;
124 }
125
126 getCheckSquares() {
127 const color = this.turn;
128 const oppCol = V.GetOppCol(color);
129 const kings = this.getKingsPos(color);
130 let res = [];
131 for (let i of [0, 1]) {
132 if (
133 kings.length >= i+1 &&
134 super.isAttacked([kings[i].x, kings[i].y], oppCol)
135 ) {
136 res.push([kings[i].x, kings[i].y]);
137 }
138 }
139 return res;
140 }
141
142 filterValid(moves) {
143 if (moves.length == 0) return [];
144 const color = moves[0].vanish[0].c;
145 const oppCol = V.GetOppCol(color);
146 // Check if both kings under attack.
147 // If yes, moves must remove at least one attack.
148 const kings = this.getKingsPos(color);
149 return moves.filter(m => {
150 this.play(m);
151 let attacks = 0;
152 for (let k of kings) {
153 const curKingPos =
154 this.board[k.x][k.y] == V.EMPTY
155 ? [m.appear[0].x, m.appear[0].y] //king moved
156 : [k.x, k.y]
157 if (super.isAttacked(curKingPos, oppCol)) attacks++;
158 else break; //no need to check further
159 }
160 this.undo(m);
161 return (
162 (kings.length == 2 && attacks <= 1) ||
163 (kings.length == 1 && attacks == 0)
164 );
165 });
166 }
167
168 getCurrentScore() {
169 if (super.atLeastOneMove()) return "*";
170 // Count kings on board
171 const color = this.turn;
172 const oppCol = V.GetOppCol(color);
173 const kings = this.getKingsPos(color);
174 if (
175 super.isAttacked([kings[0].x, kings[0].y], oppCol) ||
176 (kings.length == 2 && super.isAttacked([kings[1].x, kings[1].y], oppCol))
177 ) {
178 return (color == 'w' ? "0-1" : "1-0");
179 }
180 return "1/2"; //stalemate
181 }
182
183 static get VALUES() {
184 return {
185 p: 1,
186 r: 5,
187 c: 5, //the commoner is valuable
188 b: 3,
189 q: 9,
190 k: 1000
191 };
192 }
193
194 };