Experimental symmetric randomness + deterministic option
[vchess.git] / client / src / variants / Shatranj.js
1 // TODO: bishop OK, but queen should move vertical/horizontal and capture diagonally.
2 // ==> then the pawn promotion is a real promotion (enhancement).
3
4 import { ChessRules } from "@/base_rules";
5
6 export const VariantRules = class ShatranjRules extends ChessRules {
7 static get HasFlags() {
8 return false;
9 }
10
11 static get HasEnpassant() {
12 return false;
13 }
14
15 static get ElephantSteps() {
16 return [
17 [-2, -2],
18 [-2, 2],
19 [2, -2],
20 [2, 2]
21 ];
22 }
23
24 static GenRandInitFen(randomness) {
25 return ChessRules.GenRandInitFen(randomness).replace("w 1111 -", "w");
26 }
27
28 getPotentialPawnMoves([x, y]) {
29 const color = this.turn;
30 let moves = [];
31 const [sizeX, sizeY] = [V.size.x, V.size.y];
32 const shiftX = color == "w" ? -1 : 1;
33 const startRank = color == "w" ? sizeX - 2 : 1;
34 const lastRank = color == "w" ? 0 : sizeX - 1;
35 // Promotion in minister (queen) only:
36 const finalPiece = x + shiftX == lastRank ? V.QUEEN : V.PAWN;
37
38 if (this.board[x + shiftX][y] == V.EMPTY) {
39 // One square forward
40 moves.push(
41 this.getBasicMove([x, y], [x + shiftX, y], {
42 c: color,
43 p: finalPiece
44 })
45 );
46 }
47 // Captures
48 for (let shiftY of [-1, 1]) {
49 if (
50 y + shiftY >= 0 &&
51 y + shiftY < sizeY &&
52 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
53 this.canTake([x, y], [x + shiftX, y + shiftY])
54 ) {
55 moves.push(
56 this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
57 c: color,
58 p: finalPiece
59 })
60 );
61 }
62 }
63
64 return moves;
65 }
66
67 getPotentialBishopMoves(sq) {
68 let moves = this.getSlideNJumpMoves(sq, V.ElephantSteps, "oneStep");
69 // Complete with "repositioning moves": like a queen, without capture
70 let repositioningMoves = this.getSlideNJumpMoves(
71 sq,
72 V.steps[V.BISHOP],
73 "oneStep"
74 ).filter(m => m.vanish.length == 1);
75 return moves.concat(repositioningMoves);
76 }
77
78 getPotentialQueenMoves(sq) {
79 // Diagonal capturing moves
80 let captures = this.getSlideNJumpMoves(
81 sq,
82 V.steps[V.BISHOP],
83 "oneStep"
84 ).filter(m => m.vanish.length == 2);
85 return captures.concat(
86 // Orthogonal non-capturing moves
87 this.getSlideNJumpMoves(
88 sq,
89 V.steps[V.ROOK],
90 "oneStep"
91 ).filter(m => m.vanish.length == 1)
92 );
93 }
94
95 getPotentialKingMoves(sq) {
96 return this.getSlideNJumpMoves(
97 sq,
98 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
99 "oneStep"
100 );
101 }
102
103 isAttackedByBishop(sq, colors) {
104 return this.isAttackedBySlideNJump(
105 sq,
106 colors,
107 V.BISHOP,
108 V.ElephantSteps,
109 "oneStep"
110 );
111 }
112
113 isAttackedByQueen(sq, colors) {
114 return this.isAttackedBySlideNJump(
115 sq,
116 colors,
117 V.QUEEN,
118 V.steps[V.BISHOP],
119 "oneStep"
120 );
121 }
122
123 getCurrentScore() {
124 const color = this.turn;
125 const getScoreLost = () => {
126 // Result if I lose:
127 return color == "w" ? "0-1" : "1-0";
128 };
129 if (!this.atLeastOneMove())
130 // No valid move: I lose (this includes checkmate)
131 return getScoreLost();
132 // Win if the opponent has no pieces left (except king),
133 // and cannot bare king on the next move.
134 let piecesLeft = {
135 // No need to remember all pieces' squares:
136 // variable only used if just one remaining piece.
137 "w": {count: 0, square: null},
138 "b": {count: 0, square: null}
139 };
140 outerLoop: for (let i=0; i<V.size.x; i++) {
141 for (let j=0; j<V.size.y; j++) {
142 if (this.board[i][j] != V.EMPTY && this.getPiece(i,j) != V.KING) {
143 const sqCol = this.getColor(i,j);
144 piecesLeft[sqCol].count++;
145 piecesLeft[sqCol].square = [i,j];
146 }
147 }
148 }
149 if (Object.values(piecesLeft).every(v => v.count > 0))
150 return "*";
151 // No pieces left for some side: if both kings are bare, it's a draw
152 if (Object.values(piecesLeft).every(v => v.count == 0))
153 return "1/2";
154 if (piecesLeft[color].count > 0)
155 // He could have drawn, but didn't take my last piece...
156 return color == "w" ? "1-0" : "0-1";
157 const oppCol = V.GetOppCol(color);
158 if (piecesLeft[oppCol].count >= 2)
159 // 2 enemy units or more: I lose
160 return getScoreLost();
161 // I don't have any piece, my opponent have one: can I take it?
162 if (this.isAttacked(piecesLeft[oppCol].square, [color]))
163 // Yes! But I still need to take it
164 return "*";
165 // No :(
166 return getScoreLost();
167 }
168
169 static get VALUES() {
170 return {
171 p: 1,
172 r: 5,
173 n: 3,
174 b: 3,
175 q: 3,
176 k: 1000
177 };
178 }
179 };