1 // NOTE: initial setup differs from the original; see
2 // https://www.chessvariants.com/large.dir/freeling.html
3 class GrandRules
extends ChessRules
7 return ([V
.MARSHALL
,V
.CARDINAL
].includes(b
[1]) ? "Grand/" : "") + b
;
12 if (!ChessRules
.IsGoodFen(fen
))
14 const fenParsed
= V
.ParseFen(fen
);
16 if (!fenParsed
.captured
|| !fenParsed
.captured
.match(/^[0-9]{14,14}$/))
21 static IsGoodEnpassant(enpassant
)
25 const squares
= enpassant
.split(",");
26 if (squares
.length
> 2)
28 for (let sq
of squares
)
30 const ep
= V
.SquareToCoords(sq
);
31 if (isNaN(ep
.x
) || !V
.OnBoard(ep
))
40 const fenParts
= fen
.split(" ");
42 ChessRules
.ParseFen(fen
),
43 { captured: fenParts
[5] }
49 return super.getFen() + " " + this.getCapturedFen();
54 let counts
= [...Array(14).fill(0)];
56 for (let j
=0; j
<V
.PIECES
.length
; j
++)
58 if (V
.PIECES
[j
] == V
.KING
) //no king captured
60 counts
[i
] = this.captured
["w"][V
.PIECES
[i
]];
61 counts
[7+i
] = this.captured
["b"][V
.PIECES
[i
]];
64 return counts
.join("");
67 setOtherVariables(fen
)
69 super.setOtherVariables(fen
);
70 const fenParsed
= V
.ParseFen(fen
);
71 // Initialize captured pieces' counts from FEN
76 [V
.PAWN
]: parseInt(fenParsed
.captured
[0]),
77 [V
.ROOK
]: parseInt(fenParsed
.captured
[1]),
78 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[2]),
79 [V
.BISHOP
]: parseInt(fenParsed
.captured
[3]),
80 [V
.QUEEN
]: parseInt(fenParsed
.captured
[4]),
81 [V
.MARSHALL
]: parseInt(fenParsed
.captured
[5]),
82 [V
.CARDINAL
]: parseInt(fenParsed
.captured
[6]),
86 [V
.PAWN
]: parseInt(fenParsed
.captured
[7]),
87 [V
.ROOK
]: parseInt(fenParsed
.captured
[8]),
88 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[9]),
89 [V
.BISHOP
]: parseInt(fenParsed
.captured
[10]),
90 [V
.QUEEN
]: parseInt(fenParsed
.captured
[11]),
91 [V
.MARSHALL
]: parseInt(fenParsed
.captured
[12]),
92 [V
.CARDINAL
]: parseInt(fenParsed
.captured
[13]),
97 static get size() { return {x:10,y:10}; }
99 static get MARSHALL() { return 'm'; } //rook+knight
100 static get CARDINAL() { return 'c'; } //bishop+knight
104 return ChessRules
.PIECES
.concat([V
.MARSHALL
,V
.CARDINAL
]);
107 // There may be 2 enPassant squares (if pawn jump 3 squares)
110 const L
= this.epSquares
.length
;
111 if (!this.epSquares
[L
-1])
112 return "-"; //no en-passant
114 this.epSquares
[L
-1].forEach(sq
=> {
115 res
+= V
.CoordsToSquare(sq
) + ",";
117 return res
.slice(0,-1); //remove last comma
120 // En-passant after 2-sq or 3-sq jumps
121 getEpSquare(moveOrSquare
)
125 if (typeof moveOrSquare
=== "string")
127 const square
= moveOrSquare
;
131 square
.split(",").forEach(sq
=> {
132 res
.push(V
.SquareToCoords(sq
));
136 // Argument is a move:
137 const move = moveOrSquare
;
138 const [sx
,sy
,ex
] = [move.start
.x
,move.start
.y
,move.end
.x
];
139 if (this.getPiece(sx
,sy
) == V
.PAWN
&& Math
.abs(sx
- ex
) >= 2)
141 const step
= (ex
-sx
) / Math
.abs(ex
-sx
);
146 if (sx
+ 2*step
!= ex
) //3-squares move
155 return undefined; //default
158 getPotentialMovesFrom([x
,y
])
160 switch (this.getPiece(x
,y
))
163 return this.getPotentialMarshallMoves([x
,y
]);
165 return this.getPotentialCardinalMoves([x
,y
]);
167 return super.getPotentialMovesFrom([x
,y
])
171 // Special pawn rules: promotions to captured friendly pieces,
172 // optional on ranks 8-9 and mandatory on rank 10.
173 getPotentialPawnMoves([x
,y
])
175 const color
= this.turn
;
177 const [sizeX
,sizeY
] = [V
.size
.x
,V
.size
.y
];
178 const shiftX
= (color
== "w" ? -1 : 1);
179 const startRanks
= (color
== "w" ? [sizeX
-2,sizeX
-3] : [1,2]);
180 const lastRanks
= (color
== "w" ? [0,1,2] : [sizeX
-1,sizeX
-2,sizeX
-3]);
181 const promotionPieces
=
182 [V
.ROOK
,V
.KNIGHT
,V
.BISHOP
,V
.QUEEN
,V
.MARSHALL
,V
.CARDINAL
];
184 // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
185 let finalPieces
= undefined;
186 if (lastRanks
.includes(x
+ shiftX
))
188 finalPieces
= promotionPieces
.filter(p
=> this.captured
[color
][p
] > 0);
189 if (x
+ shiftX
!= lastRanks
[0])
190 finalPieces
.push(V
.PAWN
);
193 finalPieces
= [V
.PAWN
];
194 if (this.board
[x
+shiftX
][y
] == V
.EMPTY
)
196 // One square forward
197 for (let piece
of finalPieces
)
198 moves
.push(this.getBasicMove([x
,y
], [x
+shiftX
,y
], {c:color
,p:piece
}));
199 if (startRanks
.includes(x
))
201 if (this.board
[x
+2*shiftX
][y
] == V
.EMPTY
)
204 moves
.push(this.getBasicMove([x
,y
], [x
+2*shiftX
,y
]));
205 if (x
==startRanks
[0] && this.board
[x
+3*shiftX
][y
] == V
.EMPTY
)
207 // Three squares jump
208 moves
.push(this.getBasicMove([x
,y
], [x
+3*shiftX
,y
]));
214 for (let shiftY
of [-1,1])
216 if (y
+ shiftY
>= 0 && y
+ shiftY
< sizeY
217 && this.board
[x
+shiftX
][y
+shiftY
] != V
.EMPTY
218 && this.canTake([x
,y
], [x
+shiftX
,y
+shiftY
]))
220 for (let piece
of finalPieces
)
222 moves
.push(this.getBasicMove([x
,y
], [x
+shiftX
,y
+shiftY
],
229 const Lep
= this.epSquares
.length
;
230 const epSquare
= this.epSquares
[Lep
-1];
233 for (let epsq
of epSquare
)
235 // TODO: some redundant checks
236 if (epsq
.x
== x
+shiftX
&& Math
.abs(epsq
.y
- y
) == 1)
238 var enpassantMove
= this.getBasicMove([x
,y
], [epsq
.x
,epsq
.y
]);
239 // WARNING: the captured pawn may be diagonally behind us,
240 // if it's a 3-squares jump and we take on 1st passing square
241 const px
= (this.board
[x
][epsq
.y
] != V
.EMPTY
? x : x
- shiftX
);
242 enpassantMove
.vanish
.push({
246 c: this.getColor(px
,epsq
.y
)
248 moves
.push(enpassantMove
);
256 // TODO: different castle?
258 getPotentialMarshallMoves(sq
)
260 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
261 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"));
264 getPotentialCardinalMoves(sq
)
266 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
267 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"));
270 isAttacked(sq
, colors
)
272 return super.isAttacked(sq
, colors
)
273 || this.isAttackedByMarshall(sq
, colors
)
274 || this.isAttackedByCardinal(sq
, colors
);
277 isAttackedByMarshall(sq
, colors
)
279 return this.isAttackedBySlideNJump(sq
, colors
, V
.MARSHALL
, V
.steps
[V
.ROOK
])
280 || this.isAttackedBySlideNJump(
281 sq
, colors
, V
.MARSHALL
, V
.steps
[V
.KNIGHT
], "oneStep");
284 isAttackedByCardinal(sq
, colors
)
286 return this.isAttackedBySlideNJump(sq
, colors
, V
.CARDINAL
, V
.steps
[V
.BISHOP
])
287 || this.isAttackedBySlideNJump(
288 sq
, colors
, V
.CARDINAL
, V
.steps
[V
.KNIGHT
], "oneStep");
291 updateVariables(move)
293 super.updateVariables(move);
294 if (move.vanish
.length
== 2 && move.appear
.length
== 1)
296 // Capture: update this.captured
297 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]++;
299 if (move.vanish
[0].p
!= move.appear
[0].p
)
301 // Promotion: update this.captured
302 this.captured
[move.vanish
[0].c
][move.appear
[0].p
]--;
306 unupdateVariables(move)
308 super.unupdateVariables(move);
309 if (move.vanish
.length
== 2 && move.appear
.length
== 1)
310 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]--;
311 if (move.vanish
[0].p
!= move.appear
[0].p
)
312 this.captured
[move.vanish
[0].c
][move.appear
[0].p
]++;
317 return Object
.assign(
319 {'c': 5, 'm': 7} //experimental
323 static get SEARCH_DEPTH() { return 2; }
325 // TODO: this function could be generalized and shared better (how ?!...)
326 static GenRandInitFen()
328 let pieces
= { "w": new Array(10), "b": new Array(10) };
329 // Shuffle pieces on first and last rank
330 for (let c
of ["w","b"])
332 let positions
= range(10);
334 // Get random squares for bishops
335 let randIndex
= 2 * random(5);
336 let bishop1Pos
= positions
[randIndex
];
337 // The second bishop must be on a square of different color
338 let randIndex_tmp
= 2 * random(5) + 1;
339 let bishop2Pos
= positions
[randIndex_tmp
];
340 // Remove chosen squares
341 positions
.splice(Math
.max(randIndex
,randIndex_tmp
), 1);
342 positions
.splice(Math
.min(randIndex
,randIndex_tmp
), 1);
344 // Get random squares for knights
345 randIndex
= random(8);
346 let knight1Pos
= positions
[randIndex
];
347 positions
.splice(randIndex
, 1);
348 randIndex
= random(7);
349 let knight2Pos
= positions
[randIndex
];
350 positions
.splice(randIndex
, 1);
352 // Get random square for queen
353 randIndex
= random(6);
354 let queenPos
= positions
[randIndex
];
355 positions
.splice(randIndex
, 1);
357 // ...random square for marshall
358 randIndex
= random(5);
359 let marshallPos
= positions
[randIndex
];
360 positions
.splice(randIndex
, 1);
362 // ...random square for cardinal
363 randIndex
= random(4);
364 let cardinalPos
= positions
[randIndex
];
365 positions
.splice(randIndex
, 1);
367 // Rooks and king positions are now fixed, because of the ordering rook-king-rook
368 let rook1Pos
= positions
[0];
369 let kingPos
= positions
[1];
370 let rook2Pos
= positions
[2];
372 // Finally put the shuffled pieces in the board array
373 pieces
[c
][rook1Pos
] = 'r';
374 pieces
[c
][knight1Pos
] = 'n';
375 pieces
[c
][bishop1Pos
] = 'b';
376 pieces
[c
][queenPos
] = 'q';
377 pieces
[c
][marshallPos
] = 'm';
378 pieces
[c
][cardinalPos
] = 'c';
379 pieces
[c
][kingPos
] = 'k';
380 pieces
[c
][bishop2Pos
] = 'b';
381 pieces
[c
][knight2Pos
] = 'n';
382 pieces
[c
][rook2Pos
] = 'r';
384 return pieces
["b"].join("") +
385 "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
386 pieces
["w"].join("").toUpperCase() +
387 " w 1111 - 00000000000000";