1 // NOTE: initial setup differs from the original; see
2 // https://www.chessvariants.com/large.dir/freeling.html
3 class GrandRules
extends ChessRules
7 const V
= VariantRules
;
8 return ([V
.MARSHALL
,V
.CARDINAL
].includes(b
[1]) ? "Grand/" : "") + b
;
13 super.initVariables(fen
);
14 this.captures
= { "w": {}, "b": {} }; //for promotions
17 static get size() { return [10,10]; }
19 static get MARSHALL() { return 'm'; } //rook+knight
20 static get CARDINAL() { return 'c'; } //bishop+knight
22 // En-passant after 2-sq or 3-sq jumps
25 const [sx
,sy
,ex
] = [move.start
.x
,move.start
.y
,move.end
.x
];
26 if (this.getPiece(sx
,sy
) == VariantRules
.PAWN
&& Math
.abs(sx
- ex
) >= 2)
28 const step
= (ex
-sx
) / Math
.abs(ex
-sx
);
33 if (sx
+ 2*step
!= ex
) //3-squares move
42 return undefined; //default
45 getPotentialMovesFrom([x
,y
])
47 switch (this.getPiece(x
,y
))
49 case VariantRules
.MARSHALL:
50 return this.getPotentialMarshallMoves([x
,y
]);
51 case VariantRules
.CARDINAL:
52 return this.getPotentialCardinalMoves([x
,y
]);
54 return super.getPotentialMovesFrom([x
,y
])
58 // Special pawn rules: promotions to captured friendly pieces,
59 // optional on ranks 8-9 and mandatory on rank 10.
60 getPotentialPawnMoves([x
,y
])
62 const color
= this.turn
;
64 const V
= VariantRules
;
65 const [sizeX
,sizeY
] = VariantRules
.size
;
66 const shift
= (color
== "w" ? -1 : 1);
67 const startRanks
= (color
== "w" ? [sizeX
-2,sizeX
-3] : [1,2]);
68 const lastRanks
= (color
== "w" ? [0,1,2] : [sizeX
-1,sizeX
-2,sizeX
-3]);
70 if (x
+shift
>= 0 && x
+shift
< sizeX
&& x
+shift
!= lastRanks
[0])
73 if (this.board
[x
+shift
][y
] == V
.EMPTY
)
75 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
]));
76 if (startRanks
.includes(x
) && this.board
[x
+2*shift
][y
] == V
.EMPTY
)
79 moves
.push(this.getBasicMove([x
,y
], [x
+2*shift
,y
]));
80 if (x
== startRanks
[0] && this.board
[x
+3*shift
][y
] == V
.EMPTY
)
83 moves
.push(this.getBasicMove([x
,y
], [x
+3*shift
,y
]));
88 if (y
>0 && this.canTake([x
,y
], [x
+shift
,y
-1])
89 && this.board
[x
+shift
][y
-1] != V
.EMPTY
)
91 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
-1]));
93 if (y
<sizeY
-1 && this.canTake([x
,y
], [x
+shift
,y
+1])
94 && this.board
[x
+shift
][y
+1] != V
.EMPTY
)
96 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
+1]));
100 if (lastRanks
.includes(x
+shift
))
103 let promotionPieces
= [V
.ROOK
,V
.KNIGHT
,V
.BISHOP
,V
.QUEEN
,V
.MARSHALL
,V
.CARDINAL
];
104 promotionPieces
.forEach(p
=> {
105 if (!this.captures
[color
][p
] || this.captures
[color
][p
]==0)
108 if (this.board
[x
+shift
][y
] == V
.EMPTY
)
109 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
], {c:color
,p:p
}));
111 if (y
>0 && this.canTake([x
,y
], [x
+shift
,y
-1])
112 && this.board
[x
+shift
][y
-1] != V
.EMPTY
)
114 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
-1], {c:color
,p:p
}));
116 if (y
<sizeY
-1 && this.canTake([x
,y
], [x
+shift
,y
+1])
117 && this.board
[x
+shift
][y
+1] != V
.EMPTY
)
119 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
+1], {c:color
,p:p
}));
125 const Lep
= this.epSquares
.length
;
126 const epSquare
= Lep
>0 ? this.epSquares
[Lep
-1] : undefined;
129 for (let epsq
of epSquare
)
131 // TODO: some redundant checks
132 if (epsq
.x
== x
+shift
&& Math
.abs(epsq
.y
- y
) == 1)
134 let epStep
= epsq
.y
- y
;
135 var enpassantMove
= this.getBasicMove([x
,y
], [x
+shift
,y
+epStep
]);
136 enpassantMove
.vanish
.push({
140 c: this.getColor(x
,y
+epStep
)
142 moves
.push(enpassantMove
);
150 // TODO: different castle?
152 getPotentialMarshallMoves(sq
)
154 const V
= VariantRules
;
155 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
156 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"));
159 getPotentialCardinalMoves(sq
)
161 const V
= VariantRules
;
162 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
163 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"));
166 isAttacked(sq
, colors
)
168 return super.isAttacked(sq
, colors
)
169 || this.isAttackedByMarshall(sq
, colors
)
170 || this.isAttackedByCardinal(sq
, colors
);
173 isAttackedByMarshall(sq
, colors
)
175 const V
= VariantRules
;
176 return this.isAttackedBySlideNJump(sq
, colors
, V
.MARSHALL
, V
.steps
[V
.ROOK
])
177 || this.isAttackedBySlideNJump(
178 sq
, colors
, V
.MARSHALL
, V
.steps
[V
.KNIGHT
], "oneStep");
181 isAttackedByCardinal(sq
, colors
)
183 const V
= VariantRules
;
184 return this.isAttackedBySlideNJump(sq
, colors
, V
.CARDINAL
, V
.steps
[V
.BISHOP
])
185 || this.isAttackedBySlideNJump(
186 sq
, colors
, V
.CARDINAL
, V
.steps
[V
.KNIGHT
], "oneStep");
189 updateVariables(move)
191 super.updateVariables(move);
192 if (move.vanish
.length
==2 && move.appear
.length
==1
193 && move.vanish
[1].p
!= VariantRules
.PAWN
)
195 // Capture: update this.captures
196 if (!this.captures
[move.vanish
[1].c
][move.vanish
[1].p
])
197 this.captures
[move.vanish
[1].c
][move.vanish
[1].p
] = 1;
199 this.captures
[move.vanish
[1].c
][move.vanish
[1].p
]++;
203 unupdateVariables(move)
205 super.unupdateVariables(move);
206 if (move.vanish
.length
==2 && move.appear
.length
==1
207 && move.vanish
[1].p
!= VariantRules
.PAWN
)
209 this.captures
[move.vanish
[1].c
][move.vanish
[1].p
] =
210 Math
.max(0, this.captures
[move.vanish
[1].c
][move.vanish
[1].p
]-1);
214 static get VALUES() {
215 return Object
.assign(
217 {'c': 5, 'm': 7} //experimental
221 static get SEARCH_DEPTH() { return 2; }
223 // TODO: this function could be generalized and shared better
224 static GenRandInitFen()
226 let pieces
= { "w": new Array(10), "b": new Array(10) };
227 // Shuffle pieces on first and last rank
228 for (let c
of ["w","b"])
230 let positions
= _
.range(10);
232 // Get random squares for bishops
233 let randIndex
= 2 * _
.random(4);
234 let bishop1Pos
= positions
[randIndex
];
235 // The second bishop must be on a square of different color
236 let randIndex_tmp
= 2 * _
.random(4) + 1;
237 let bishop2Pos
= positions
[randIndex_tmp
];
238 // Remove chosen squares
239 positions
.splice(Math
.max(randIndex
,randIndex_tmp
), 1);
240 positions
.splice(Math
.min(randIndex
,randIndex_tmp
), 1);
242 // Get random squares for knights
243 randIndex
= _
.random(7);
244 let knight1Pos
= positions
[randIndex
];
245 positions
.splice(randIndex
, 1);
246 randIndex
= _
.random(6);
247 let knight2Pos
= positions
[randIndex
];
248 positions
.splice(randIndex
, 1);
250 // Get random square for queen
251 randIndex
= _
.random(5);
252 let queenPos
= positions
[randIndex
];
253 positions
.splice(randIndex
, 1);
255 // ...random square for marshall
256 randIndex
= _
.random(4);
257 let marshallPos
= positions
[randIndex
];
258 positions
.splice(randIndex
, 1);
260 // ...random square for cardinal
261 randIndex
= _
.random(3);
262 let cardinalPos
= positions
[randIndex
];
263 positions
.splice(randIndex
, 1);
265 // Rooks and king positions are now fixed, because of the ordering rook-king-rook
266 let rook1Pos
= positions
[0];
267 let kingPos
= positions
[1];
268 let rook2Pos
= positions
[2];
270 // Finally put the shuffled pieces in the board array
271 pieces
[c
][rook1Pos
] = 'r';
272 pieces
[c
][knight1Pos
] = 'n';
273 pieces
[c
][bishop1Pos
] = 'b';
274 pieces
[c
][queenPos
] = 'q';
275 pieces
[c
][marshallPos
] = 'm';
276 pieces
[c
][cardinalPos
] = 'c';
277 pieces
[c
][kingPos
] = 'k';
278 pieces
[c
][bishop2Pos
] = 'b';
279 pieces
[c
][knight2Pos
] = 'n';
280 pieces
[c
][rook2Pos
] = 'r';
282 let fen
= pieces
["b"].join("") +
283 "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
284 pieces
["w"].join("").toUpperCase() +