7f5fe422fb32c862a3aa70f27c86060d3cf1f42a
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]{10,10}$/))
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
[4] }
49 return super.getFen() + " " + this.getCapturedFen();
54 let counts
= _
.map(_
.range(10), 0);
55 for (let i
=0; i
<V
.PIECES
.length
-1; i
++) //-1: no king captured
57 counts
[i
] = this.captured
["w"][V
.PIECES
[i
]];
58 counts
[5+i
] = this.captured
["b"][V
.PIECES
[i
]];
60 return counts
.join("");
63 setOtherVariables(fen
)
65 super.setOtherVariables(fen
);
66 const fenParsed
= V
.ParseFen(fen
);
67 // Initialize captured pieces' counts from FEN
72 [V
.PAWN
]: parseInt(fenParsed
.captured
[0]),
73 [V
.ROOK
]: parseInt(fenParsed
.captured
[1]),
74 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[2]),
75 [V
.BISHOP
]: parseInt(fenParsed
.captured
[3]),
76 [V
.QUEEN
]: parseInt(fenParsed
.captured
[4]),
80 [V
.PAWN
]: parseInt(fenParsed
.captured
[5]),
81 [V
.ROOK
]: parseInt(fenParsed
.captured
[6]),
82 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[7]),
83 [V
.BISHOP
]: parseInt(fenParsed
.captured
[8]),
84 [V
.QUEEN
]: parseInt(fenParsed
.captured
[9]),
89 static get size() { return {x:10,y:10}; }
91 static get MARSHALL() { return 'm'; } //rook+knight
92 static get CARDINAL() { return 'c'; } //bishop+knight
96 return ChessRules
.PIECES
.concat([V
.MARSHALL
,V
.CARDINAL
]);
99 // There may be 2 enPassant squares (if pawn jump 3 squares)
102 const L
= this.epSquares
.length
;
103 if (!this.epSquares
[L
-1])
104 return "-"; //no en-passant
106 this.epSquares
[L
-1].forEach(sq
=> {
107 res
+= V
.CoordsToSquare(sq
) + ",";
109 return res
.slice(0,-1); //remove last comma
112 // En-passant after 2-sq or 3-sq jumps
113 getEpSquare(moveOrSquare
)
117 if (typeof moveOrSquare
=== "string")
119 const square
= moveOrSquare
;
123 square
.split(",").forEach(sq
=> {
124 res
.push(V
.SquareToCoords(sq
));
128 // Argument is a move:
129 const move = moveOrSquare
;
130 const [sx
,sy
,ex
] = [move.start
.x
,move.start
.y
,move.end
.x
];
131 if (this.getPiece(sx
,sy
) == V
.PAWN
&& Math
.abs(sx
- ex
) >= 2)
133 const step
= (ex
-sx
) / Math
.abs(ex
-sx
);
138 if (sx
+ 2*step
!= ex
) //3-squares move
147 return undefined; //default
150 getPotentialMovesFrom([x
,y
])
152 switch (this.getPiece(x
,y
))
155 return this.getPotentialMarshallMoves([x
,y
]);
157 return this.getPotentialCardinalMoves([x
,y
]);
159 return super.getPotentialMovesFrom([x
,y
])
163 // Special pawn rules: promotions to captured friendly pieces,
164 // optional on ranks 8-9 and mandatory on rank 10.
165 getPotentialPawnMoves([x
,y
])
167 const color
= this.turn
;
169 const [sizeX
,sizeY
] = [V
.size
.x
,V
.size
.y
];
170 const shift
= (color
== "w" ? -1 : 1);
171 const startRanks
= (color
== "w" ? [sizeX
-2,sizeX
-3] : [1,2]);
172 const lastRanks
= (color
== "w" ? [0,1,2] : [sizeX
-1,sizeX
-2,sizeX
-3]);
174 if (x
+shift
>= 0 && x
+shift
< sizeX
&& x
+shift
!= lastRanks
[0])
177 if (this.board
[x
+shift
][y
] == V
.EMPTY
)
179 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
]));
180 if (startRanks
.includes(x
) && this.board
[x
+2*shift
][y
] == V
.EMPTY
)
183 moves
.push(this.getBasicMove([x
,y
], [x
+2*shift
,y
]));
184 if (x
== startRanks
[0] && this.board
[x
+3*shift
][y
] == V
.EMPTY
)
187 moves
.push(this.getBasicMove([x
,y
], [x
+3*shift
,y
]));
192 if (y
>0 && this.canTake([x
,y
], [x
+shift
,y
-1])
193 && this.board
[x
+shift
][y
-1] != V
.EMPTY
)
195 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
-1]));
197 if (y
<sizeY
-1 && this.canTake([x
,y
], [x
+shift
,y
+1])
198 && this.board
[x
+shift
][y
+1] != V
.EMPTY
)
200 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
+1]));
204 if (lastRanks
.includes(x
+shift
))
207 let promotionPieces
= [V
.ROOK
,V
.KNIGHT
,V
.BISHOP
,V
.QUEEN
,V
.MARSHALL
,V
.CARDINAL
];
208 promotionPieces
.forEach(p
=> {
209 if (this.captured
[color
][p
]==0)
212 if (this.board
[x
+shift
][y
] == V
.EMPTY
)
213 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
], {c:color
,p:p
}));
215 if (y
>0 && this.canTake([x
,y
], [x
+shift
,y
-1])
216 && this.board
[x
+shift
][y
-1] != V
.EMPTY
)
218 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
-1], {c:color
,p:p
}));
220 if (y
<sizeY
-1 && this.canTake([x
,y
], [x
+shift
,y
+1])
221 && this.board
[x
+shift
][y
+1] != V
.EMPTY
)
223 moves
.push(this.getBasicMove([x
,y
], [x
+shift
,y
+1], {c:color
,p:p
}));
229 const Lep
= this.epSquares
.length
;
230 const epSquare
= Lep
>0 ? this.epSquares
[Lep
-1] : undefined;
233 for (let epsq
of epSquare
)
235 // TODO: some redundant checks
236 if (epsq
.x
== x
+shift
&& Math
.abs(epsq
.y
- y
) == 1)
238 var enpassantMove
= this.getBasicMove([x
,y
], [x
+shift
,epsq
.y
]);
239 enpassantMove
.vanish
.push({
243 c: this.getColor(x
,epsq
.y
)
245 moves
.push(enpassantMove
);
253 // TODO: different castle?
255 getPotentialMarshallMoves(sq
)
257 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
258 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"));
261 getPotentialCardinalMoves(sq
)
263 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
264 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"));
267 isAttacked(sq
, colors
)
269 return super.isAttacked(sq
, colors
)
270 || this.isAttackedByMarshall(sq
, colors
)
271 || this.isAttackedByCardinal(sq
, colors
);
274 isAttackedByMarshall(sq
, colors
)
276 return this.isAttackedBySlideNJump(sq
, colors
, V
.MARSHALL
, V
.steps
[V
.ROOK
])
277 || this.isAttackedBySlideNJump(
278 sq
, colors
, V
.MARSHALL
, V
.steps
[V
.KNIGHT
], "oneStep");
281 isAttackedByCardinal(sq
, colors
)
283 return this.isAttackedBySlideNJump(sq
, colors
, V
.CARDINAL
, V
.steps
[V
.BISHOP
])
284 || this.isAttackedBySlideNJump(
285 sq
, colors
, V
.CARDINAL
, V
.steps
[V
.KNIGHT
], "oneStep");
288 updateVariables(move)
290 super.updateVariables(move);
291 if (move.vanish
.length
==2 && move.appear
.length
==1 && move.vanish
[1].p
!= V
.PAWN
)
293 // Capture: update this.captured
294 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]++;
298 unupdateVariables(move)
300 super.unupdateVariables(move);
301 if (move.vanish
.length
==2 && move.appear
.length
==1 && move.vanish
[1].p
!= V
.PAWN
)
302 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]--;
307 return Object
.assign(
309 {'c': 5, 'm': 7} //experimental
313 static get SEARCH_DEPTH() { return 2; }
315 // TODO: this function could be generalized and shared better (how ?!...)
316 static GenRandInitFen()
318 let pieces
= { "w": new Array(10), "b": new Array(10) };
319 // Shuffle pieces on first and last rank
320 for (let c
of ["w","b"])
322 let positions
= _
.range(10);
324 // Get random squares for bishops
325 let randIndex
= 2 * _
.random(4);
326 let bishop1Pos
= positions
[randIndex
];
327 // The second bishop must be on a square of different color
328 let randIndex_tmp
= 2 * _
.random(4) + 1;
329 let bishop2Pos
= positions
[randIndex_tmp
];
330 // Remove chosen squares
331 positions
.splice(Math
.max(randIndex
,randIndex_tmp
), 1);
332 positions
.splice(Math
.min(randIndex
,randIndex_tmp
), 1);
334 // Get random squares for knights
335 randIndex
= _
.random(7);
336 let knight1Pos
= positions
[randIndex
];
337 positions
.splice(randIndex
, 1);
338 randIndex
= _
.random(6);
339 let knight2Pos
= positions
[randIndex
];
340 positions
.splice(randIndex
, 1);
342 // Get random square for queen
343 randIndex
= _
.random(5);
344 let queenPos
= positions
[randIndex
];
345 positions
.splice(randIndex
, 1);
347 // ...random square for marshall
348 randIndex
= _
.random(4);
349 let marshallPos
= positions
[randIndex
];
350 positions
.splice(randIndex
, 1);
352 // ...random square for cardinal
353 randIndex
= _
.random(3);
354 let cardinalPos
= positions
[randIndex
];
355 positions
.splice(randIndex
, 1);
357 // Rooks and king positions are now fixed, because of the ordering rook-king-rook
358 let rook1Pos
= positions
[0];
359 let kingPos
= positions
[1];
360 let rook2Pos
= positions
[2];
362 // Finally put the shuffled pieces in the board array
363 pieces
[c
][rook1Pos
] = 'r';
364 pieces
[c
][knight1Pos
] = 'n';
365 pieces
[c
][bishop1Pos
] = 'b';
366 pieces
[c
][queenPos
] = 'q';
367 pieces
[c
][marshallPos
] = 'm';
368 pieces
[c
][cardinalPos
] = 'c';
369 pieces
[c
][kingPos
] = 'k';
370 pieces
[c
][bishop2Pos
] = 'b';
371 pieces
[c
][knight2Pos
] = 'n';
372 pieces
[c
][rook2Pos
] = 'r';
374 return pieces
["b"].join("") +
375 "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
376 pieces
["w"].join("").toUpperCase() +
377 " w 1111 - 0000000000";
381 const VariantRules
= GrandRules
;