fb8c27cda7e5efee0ffb401d8dad06915b28a9e3
[vchess.git] / client / src / variants / Grasshopper.js
1 import { ChessRules } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt } from "@/utils/alea";
4
5 export class GrasshopperRules extends ChessRules {
6 static get HasEnpassant() {
7 return false;
8 }
9
10 static get PawnSpecs() {
11 return Object.assign(
12 {},
13 ChessRules.PawnSpecs,
14 {
15 twoSquares: false,
16 promotions: ChessRules.PawnSpecs.promotions.concat([V.GRASSHOPPER])
17 }
18 );
19 }
20
21 static get GRASSHOPPER() {
22 return "g";
23 }
24
25 static get PIECES() {
26 return ChessRules.PIECES.concat([V.GRASSHOPPER]);
27 }
28
29 getPpath(b) {
30 return (b[1] == V.GRASSHOPPER ? "Grasshopper/" : "") + b;
31 }
32
33 getPotentialMovesFrom([x, y]) {
34 switch (this.getPiece(x, y)) {
35 case V.GRASSHOPPER:
36 return this.getPotentialGrasshopperMoves([x, y]);
37 default:
38 return super.getPotentialMovesFrom([x, y]);
39 }
40 }
41
42 getPotentialPawnMoves([x, y]) {
43 const color = this.turn;
44 let moves = [];
45 const [sizeX, sizeY] = [V.size.x, V.size.y];
46 const shiftX = color == "w" ? -1 : 1;
47 const lastRank = color == "w" ? 0 : sizeX - 1;
48
49 const finalPieces =
50 x + shiftX == lastRank
51 ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.GRASSHOPPER]
52 : [V.PAWN];
53 if (this.board[x + shiftX][y] == V.EMPTY) {
54 // One square forward
55 for (let piece of finalPieces) {
56 moves.push(
57 this.getBasicMove([x, y], [x + shiftX, y], {
58 c: color,
59 p: piece
60 })
61 );
62 }
63 // No 2-squares jump
64 }
65 // Captures
66 for (let shiftY of [-1, 1]) {
67 if (
68 y + shiftY >= 0 &&
69 y + shiftY < sizeY &&
70 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
71 this.canTake([x, y], [x + shiftX, y + shiftY])
72 ) {
73 for (let piece of finalPieces) {
74 moves.push(
75 this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
76 c: color,
77 p: piece
78 })
79 );
80 }
81 }
82 }
83
84 return moves;
85 }
86
87 getPotentialGrasshopperMoves([x, y]) {
88 let moves = [];
89 // Look in every direction until an obstacle (to jump) is met
90 for (const step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
91 let i = x + step[0];
92 let j = y + step[1];
93 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
94 i += step[0];
95 j += step[1];
96 }
97 // Move is valid if the next square is empty or occupied by enemy
98 const nextSq = [i+step[0], j+step[1]];
99 if (V.OnBoard(nextSq[0], nextSq[1]) && this.canTake([x, y], nextSq))
100 moves.push(this.getBasicMove([x, y], nextSq));
101 }
102 return moves;
103 }
104
105 isAttacked(sq, color) {
106 return (
107 super.isAttacked(sq, color) ||
108 this.isAttackedByGrasshopper(sq, color)
109 );
110 }
111
112 isAttackedByGrasshopper([x, y], color) {
113 // Reversed process: is there an adjacent obstacle,
114 // and a grasshopper next in the same line?
115 for (const step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
116 const nextSq = [x+step[0], y+step[1]];
117 if (
118 V.OnBoard(nextSq[0], nextSq[1]) &&
119 this.board[nextSq[0]][nextSq[1]] != V.EMPTY
120 ) {
121 let i = nextSq[0] + step[0];
122 let j = nextSq[1] + step[1];
123 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
124 i += step[0];
125 j += step[1];
126 }
127 if (
128 V.OnBoard(i, j) &&
129 this.getPiece(i, j) == V.GRASSHOPPER &&
130 this.getColor(i, j) == color
131 ) {
132 return true;
133 }
134 }
135 }
136 return false;
137 }
138
139 static get VALUES() {
140 return Object.assign(
141 // TODO: grasshoppers power decline with less pieces on board...
142 { g: 2 },
143 ChessRules.VALUES
144 );
145 }
146
147 static get SEARCH_DEPTH() {
148 return 2;
149 }
150
151 static GenRandInitFen(randomness) {
152 return ChessRules.GenRandInitFen(randomness)
153 .slice(0, -2)
154 .replace(
155 "/pppppppp/8/8/8/8/PPPPPPPP/",
156 "/gggggggg/pppppppp/8/8/PPPPPPPP/GGGGGGGG/"
157 );
158 }
159 };