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