98549db49de1229d6ad97bca42da561fb304f916
1 import { ArrayFun
} from "@/utils/array";
2 import { randInt
} from "@/utils/alea";
3 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
5 export class FullcavalryRules
extends ChessRules
{
11 static get IMAGE_EXTENSION() {
12 // Temporarily, for the time SVG pieces are being designed:
16 // Lancer directions *from white perspective*
17 static get LANCER_DIRS() {
31 return ChessRules
.PIECES
.concat(Object
.keys(V
.LANCER_DIRS
));
35 const piece
= this.board
[i
][j
].charAt(1);
36 // Special lancer case: 8 possible orientations
37 if (Object
.keys(V
.LANCER_DIRS
).includes(piece
)) return V
.LANCER
;
41 getPpath(b
, color
, score
, orientation
) {
42 if (Object
.keys(V
.LANCER_DIRS
).includes(b
[1])) {
43 if (orientation
== 'w') return "Eightpieces/tmp_png/" + b
;
44 // Find opposite direction for adequate display:
72 return "Eightpieces/tmp_png/" + b
[0] + oppDir
;
74 // TODO: after we have SVG pieces, remove the folder and next prefix:
75 return "Eightpieces/tmp_png/" + b
;
78 getPPpath(m
, orientation
) {
79 // If castle, show choices on m.appear[1]:
80 const index
= (m
.appear
.length
== 2 ? 1 : 0);
83 m
.appear
[index
].c
+ m
.appear
[index
].p
,
91 static GenRandInitFen(randomness
) {
94 return "efbqkbnm/pppppppp/8/8/8/8/PPPPPPPP/EDBQKBNM w 0 ahah -";
96 const baseFen
= ChessRules
.GenRandInitFen(randomness
);
97 // Replace rooks by lancers with expected orientation:
98 const firstBlackRook
= baseFen
.indexOf('r'),
99 lastBlackRook
= baseFen
.lastIndexOf('r'),
100 firstWhiteRook
= baseFen
.indexOf('R'),
101 lastWhiteRook
= baseFen
.lastIndexOf('R');
103 baseFen
.substring(0, firstBlackRook
) +
104 (firstBlackRook
<= 3 ? 'e' : 'm') +
105 baseFen
.substring(firstBlackRook
+ 1, lastBlackRook
) +
106 (lastBlackRook
>= 5 ? 'm' : 'e') +
107 // Subtract 35 = total number of characters before last FEN row:
108 // 8x3 (full rows) + 4 (empty rows) + 7 (separators)
109 baseFen
.substring(lastBlackRook
+ 1, firstWhiteRook
) +
110 (firstWhiteRook
- 35 <= 3 ? 'E' : 'M') +
111 baseFen
.substring(firstWhiteRook
+ 1, lastWhiteRook
) +
112 (lastWhiteRook
- 35 >= 5 ? 'M' : 'E') +
113 baseFen
.substring(lastWhiteRook
+ 1)
117 // Because of the lancers, getPiece() could be wrong:
118 // use board[x][y][1] instead (always valid).
119 // TODO: base implementation now uses this too (no?)
120 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
121 const initColor
= this.getColor(sx
, sy
);
122 const initPiece
= this.board
[sx
][sy
].charAt(1);
128 c: tr
? tr
.c : initColor
,
129 p: tr
? tr
.p : initPiece
142 // The opponent piece disappears if we take it
143 if (this.board
[ex
][ey
] != V
.EMPTY
) {
148 c: this.getColor(ex
, ey
),
149 p: this.board
[ex
][ey
].charAt(1)
157 getPotentialMovesFrom([x
, y
]) {
158 if (this.getPiece(x
, y
) == V
.LANCER
)
159 return this.getPotentialLancerMoves([x
, y
]);
160 return super.getPotentialMovesFrom([x
, y
]);
163 getPotentialPawnMoves([x
, y
]) {
164 const color
= this.getColor(x
, y
);
166 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
167 let shiftX
= (color
== "w" ? -1 : 1);
168 const startRank
= color
== "w" ? sizeX
- 2 : 1;
169 const lastRank
= color
== "w" ? 0 : sizeX
- 1;
171 let finalPieces
= [V
.PAWN
];
172 if (x
+ shiftX
== lastRank
) {
173 // Only allow direction facing inside board:
174 const allowedLancerDirs
=
176 ? ['e', 'f', 'g', 'h', 'm']
177 : ['c', 'd', 'e', 'm', 'o'];
178 finalPieces
= allowedLancerDirs
.concat([V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]);
180 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
) {
181 // One square forward
182 for (let piece
of finalPieces
) {
184 this.getBasicMove([x
, y
], [x
+ shiftX
, y
], {
190 if (x
== startRank
&& this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
)
192 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
195 for (let shiftY
of [-1, 1]) {
198 y
+ shiftY
< sizeY
&&
199 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
200 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
202 for (let piece
of finalPieces
) {
204 this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
], {
213 // Add en-passant captures
214 Array
.prototype.push
.apply(
216 this.getEnpassantCaptures([x
, y
], shiftX
)
222 // Obtain all lancer moves in "step" direction
223 getPotentialLancerMoves_aux([x
, y
], step
, tr
) {
225 // Add all moves to vacant squares until opponent is met:
226 const color
= this.getColor(x
, y
);
227 const oppCol
= V
.GetOppCol(color
)
228 let sq
= [x
+ step
[0], y
+ step
[1]];
229 while (V
.OnBoard(sq
[0], sq
[1]) && this.getColor(sq
[0], sq
[1]) != oppCol
) {
230 if (this.board
[sq
[0]][sq
[1]] == V
.EMPTY
)
231 moves
.push(this.getBasicMove([x
, y
], sq
, tr
));
235 if (V
.OnBoard(sq
[0], sq
[1]))
236 // Add capturing move
237 moves
.push(this.getBasicMove([x
, y
], sq
, tr
));
241 getPotentialLancerMoves([x
, y
]) {
243 // Add all lancer possible orientations, similar to pawn promotions.
244 const color
= this.getColor(x
, y
);
245 const dirCode
= this.board
[x
][y
][1];
246 const curDir
= V
.LANCER_DIRS
[dirCode
];
248 this.getPotentialLancerMoves_aux([x
, y
], V
.LANCER_DIRS
[dirCode
]);
249 monodirMoves
.forEach(m
=> {
250 Object
.keys(V
.LANCER_DIRS
).forEach(k
=> {
251 const newDir
= V
.LANCER_DIRS
[k
];
252 // Prevent orientations toward outer board:
253 if (V
.OnBoard(m
.end
.x
+ newDir
[0], m
.end
.y
+ newDir
[1])) {
254 let mk
= JSON
.parse(JSON
.stringify(m
));
263 getCastleMoves([x
, y
]) {
264 const c
= this.getColor(x
, y
);
267 const oppCol
= V
.GetOppCol(c
);
270 // King, then lancer:
271 const finalSquares
= [ [2, 3], [V
.size
.y
- 2, V
.size
.y
- 3] ];
275 castleSide
++ //large, then small
277 if (this.castleFlags
[c
][castleSide
] >= V
.size
.y
) continue;
278 // If this code is reached, lancer and king are on initial position
280 const lancerPos
= this.castleFlags
[c
][castleSide
];
281 const castlingPiece
= this.board
[x
][lancerPos
].charAt(1);
283 // Nothing on the path of the king ? (and no checks)
284 const finDist
= finalSquares
[castleSide
][0] - y
;
285 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
289 (this.isAttacked([x
, i
], oppCol
)) ||
291 this.board
[x
][i
] != V
.EMPTY
&&
292 // NOTE: next check is enough, because of chessboard constraints
293 (this.getColor(x
, i
) != c
|| ![y
, lancerPos
].includes(i
))
296 continue castlingCheck
;
299 } while (i
!= finalSquares
[castleSide
][0]);
301 // Nothing on final squares, except maybe king and castling lancer?
302 for (i
= 0; i
< 2; i
++) {
304 finalSquares
[castleSide
][i
] != lancerPos
&&
305 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
307 finalSquares
[castleSide
][i
] != y
||
308 this.getColor(x
, finalSquares
[castleSide
][i
]) != c
311 continue castlingCheck
;
315 // If this code is reached, castle is valid
316 let allowedLancerDirs
= [castlingPiece
];
317 if (finalSquares
[castleSide
][1] != lancerPos
) {
318 // It moved: allow reorientation
321 ? ['e', 'f', 'g', 'h', 'm']
322 : ['c', 'd', 'e', 'm', 'o'];
324 allowedLancerDirs
.forEach(dir
=> {
330 y: finalSquares
[castleSide
][0],
336 y: finalSquares
[castleSide
][1],
342 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
343 new PiPo({ x: x
, y: lancerPos
, p: castlingPiece
, c: c
})
346 Math
.abs(y
- lancerPos
) <= 2
347 ? { x: x
, y: lancerPos
}
348 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
357 isAttacked(sq
, color
) {
359 super.isAttacked(sq
, color
) ||
360 this.isAttackedByLancer(sq
, color
)
364 isAttackedByLancer([x
, y
], color
) {
365 for (let step
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
366 // If in this direction there are only enemy pieces and empty squares,
367 // and we meet a lancer: can he reach us?
368 // NOTE: do not stop at first lancer, there might be several!
369 let coord
= { x: x
+ step
[0], y: y
+ step
[1] };
372 V
.OnBoard(coord
.x
, coord
.y
) &&
374 this.board
[coord
.x
][coord
.y
] == V
.EMPTY
||
375 this.getColor(coord
.x
, coord
.y
) == color
379 this.getPiece(coord
.x
, coord
.y
) == V
.LANCER
&&
380 !this.isImmobilized([coord
.x
, coord
.y
])
382 lancerPos
.push({x: coord
.x
, y: coord
.y
});
387 for (let xy
of lancerPos
) {
388 const dir
= V
.LANCER_DIRS
[this.board
[xy
.x
][xy
.y
].charAt(1)];
389 if (dir
[0] == -step
[0] && dir
[1] == -step
[1]) return true;
395 static get VALUES() {
396 return Object
.assign(
397 { l: 4.8 }, //Jeff K. estimation (for Eightpieces)
402 // For moves notation:
403 static get LANCER_DIRNAMES() {
417 // At move 1, forbid captures (in case of...):
418 if (this.movesCount
>= 2) return moves
;
419 return moves
.filter(m
=> m
.vanish
.length
== 1);
423 let notation
= super.getNotation(move);
424 if (Object
.keys(V
.LANCER_DIRNAMES
).includes(move.vanish
[0].p
))
425 // Lancer: add direction info
426 notation
+= "=" + V
.LANCER_DIRNAMES
[move.appear
[0].p
];
427 else if (move.appear
.length
== 2 && move.vanish
[1].p
!= move.appear
[1].p
)
428 // Same after castle:
429 notation
+= "+L:" + V
.LANCER_DIRNAMES
[move.appear
[1].p
];
431 move.vanish
[0].p
== V
.PAWN
&&
432 Object
.keys(V
.LANCER_DIRNAMES
).includes(move.appear
[0].p
)
434 // Fix promotions in lancer:
435 notation
= notation
.slice(0, -1) +
436 "L:" + V
.LANCER_DIRNAMES
[move.appear
[0].p
];