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
) + 'e' +
104 baseFen
.substring(firstBlackRook
+ 1, lastBlackRook
) + 'm' +
105 baseFen
.substring(lastBlackRook
+ 1, firstWhiteRook
) + 'E' +
106 baseFen
.substring(firstWhiteRook
+ 1, lastWhiteRook
) + 'M' +
107 baseFen
.substring(lastWhiteRook
+ 1)
111 // Because of the lancers, getPiece() could be wrong:
112 // use board[x][y][1] instead (always valid).
113 // TODO: base implementation now uses this too (no?)
114 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
115 const initColor
= this.getColor(sx
, sy
);
116 const initPiece
= this.board
[sx
][sy
].charAt(1);
122 c: tr
? tr
.c : initColor
,
123 p: tr
? tr
.p : initPiece
136 // The opponent piece disappears if we take it
137 if (this.board
[ex
][ey
] != V
.EMPTY
) {
142 c: this.getColor(ex
, ey
),
143 p: this.board
[ex
][ey
].charAt(1)
151 getPotentialMovesFrom([x
, y
]) {
152 if (this.getPiece(x
, y
) == V
.LANCER
)
153 return this.getPotentialLancerMoves([x
, y
]);
154 return super.getPotentialMovesFrom([x
, y
]);
157 getPotentialPawnMoves([x
, y
]) {
158 const color
= this.getColor(x
, y
);
160 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
161 let shiftX
= (color
== "w" ? -1 : 1);
162 const startRank
= color
== "w" ? sizeX
- 2 : 1;
163 const lastRank
= color
== "w" ? 0 : sizeX
- 1;
165 let finalPieces
= [V
.PAWN
];
166 if (x
+ shiftX
== lastRank
) {
167 // Only allow direction facing inside board:
168 const allowedLancerDirs
=
170 ? ['e', 'f', 'g', 'h', 'm']
171 : ['c', 'd', 'e', 'm', 'o'];
172 finalPieces
= allowedLancerDirs
.concat([V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]);
174 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
) {
175 // One square forward
176 for (let piece
of finalPieces
) {
178 this.getBasicMove([x
, y
], [x
+ shiftX
, y
], {
184 if (x
== startRank
&& this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
)
186 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
189 for (let shiftY
of [-1, 1]) {
192 y
+ shiftY
< sizeY
&&
193 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
194 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
196 for (let piece
of finalPieces
) {
198 this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
], {
207 // Add en-passant captures
208 Array
.prototype.push
.apply(
210 this.getEnpassantCaptures([x
, y
], shiftX
)
216 // Obtain all lancer moves in "step" direction
217 getPotentialLancerMoves_aux([x
, y
], step
, tr
) {
219 // Add all moves to vacant squares until opponent is met:
220 const color
= this.getColor(x
, y
);
221 const oppCol
= V
.GetOppCol(color
)
222 let sq
= [x
+ step
[0], y
+ step
[1]];
223 while (V
.OnBoard(sq
[0], sq
[1]) && this.getColor(sq
[0], sq
[1]) != oppCol
) {
224 if (this.board
[sq
[0]][sq
[1]] == V
.EMPTY
)
225 moves
.push(this.getBasicMove([x
, y
], sq
, tr
));
229 if (V
.OnBoard(sq
[0], sq
[1]))
230 // Add capturing move
231 moves
.push(this.getBasicMove([x
, y
], sq
, tr
));
235 getPotentialLancerMoves([x
, y
]) {
237 // Add all lancer possible orientations, similar to pawn promotions.
238 const color
= this.getColor(x
, y
);
239 const dirCode
= this.board
[x
][y
][1];
240 const curDir
= V
.LANCER_DIRS
[dirCode
];
242 this.getPotentialLancerMoves_aux([x
, y
], V
.LANCER_DIRS
[dirCode
]);
243 monodirMoves
.forEach(m
=> {
244 Object
.keys(V
.LANCER_DIRS
).forEach(k
=> {
245 const newDir
= V
.LANCER_DIRS
[k
];
246 // Prevent orientations toward outer board:
247 if (V
.OnBoard(m
.end
.x
+ newDir
[0], m
.end
.y
+ newDir
[1])) {
248 let mk
= JSON
.parse(JSON
.stringify(m
));
257 getCastleMoves([x
, y
]) {
258 const c
= this.getColor(x
, y
);
261 const oppCol
= V
.GetOppCol(c
);
264 // King, then lancer:
265 const finalSquares
= [ [2, 3], [V
.size
.y
- 2, V
.size
.y
- 3] ];
269 castleSide
++ //large, then small
271 if (this.castleFlags
[c
][castleSide
] >= V
.size
.y
) continue;
272 // If this code is reached, lancer and king are on initial position
274 const lancerPos
= this.castleFlags
[c
][castleSide
];
275 const castlingPiece
= this.board
[x
][lancerPos
].charAt(1);
277 // Nothing on the path of the king ? (and no checks)
278 const finDist
= finalSquares
[castleSide
][0] - y
;
279 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
283 (this.isAttacked([x
, i
], oppCol
)) ||
285 this.board
[x
][i
] != V
.EMPTY
&&
286 // NOTE: next check is enough, because of chessboard constraints
287 (this.getColor(x
, i
) != c
|| ![y
, lancerPos
].includes(i
))
290 continue castlingCheck
;
293 } while (i
!= finalSquares
[castleSide
][0]);
295 // Nothing on final squares, except maybe king and castling lancer?
296 for (i
= 0; i
< 2; i
++) {
298 finalSquares
[castleSide
][i
] != lancerPos
&&
299 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
301 finalSquares
[castleSide
][i
] != y
||
302 this.getColor(x
, finalSquares
[castleSide
][i
]) != c
305 continue castlingCheck
;
309 // If this code is reached, castle is valid
310 let allowedLancerDirs
= [castlingPiece
];
311 if (finalSquares
[castleSide
][1] != lancerPos
) {
312 // It moved: allow reorientation
315 ? ['e', 'f', 'g', 'h', 'm']
316 : ['c', 'd', 'e', 'm', 'o'];
318 allowedLancerDirs
.forEach(dir
=> {
324 y: finalSquares
[castleSide
][0],
330 y: finalSquares
[castleSide
][1],
336 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
337 new PiPo({ x: x
, y: lancerPos
, p: castlingPiece
, c: c
})
340 Math
.abs(y
- lancerPos
) <= 2
341 ? { x: x
, y: lancerPos
}
342 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
351 isAttacked(sq
, color
) {
353 super.isAttacked(sq
, color
) ||
354 this.isAttackedByLancer(sq
, color
)
358 isAttackedByLancer([x
, y
], color
) {
359 for (let step
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
360 // If in this direction there are only enemy pieces and empty squares,
361 // and we meet a lancer: can he reach us?
362 // NOTE: do not stop at first lancer, there might be several!
363 let coord
= { x: x
+ step
[0], y: y
+ step
[1] };
366 V
.OnBoard(coord
.x
, coord
.y
) &&
368 this.board
[coord
.x
][coord
.y
] == V
.EMPTY
||
369 this.getColor(coord
.x
, coord
.y
) == color
373 this.getPiece(coord
.x
, coord
.y
) == V
.LANCER
&&
374 !this.isImmobilized([coord
.x
, coord
.y
])
376 lancerPos
.push({x: coord
.x
, y: coord
.y
});
381 for (let xy
of lancerPos
) {
382 const dir
= V
.LANCER_DIRS
[this.board
[xy
.x
][xy
.y
].charAt(1)];
383 if (dir
[0] == -step
[0] && dir
[1] == -step
[1]) return true;
389 static get VALUES() {
390 return Object
.assign(
391 { l: 4.8 }, //Jeff K. estimation (for Eightpieces)
396 // For moves notation:
397 static get LANCER_DIRNAMES() {
411 // At move 1, forbid captures (in case of...):
412 if (this.movesCount
>= 2) return moves
;
413 return moves
.filter(m
=> m
.vanish
.length
== 1);
417 let notation
= super.getNotation(move);
418 if (Object
.keys(V
.LANCER_DIRNAMES
).includes(move.vanish
[0].p
))
419 // Lancer: add direction info
420 notation
+= "=" + V
.LANCER_DIRNAMES
[move.appear
[0].p
];
421 else if (move.appear
.length
== 2 && move.vanish
[1].p
!= move.appear
[1].p
)
422 // Same after castle:
423 notation
+= "+L:" + V
.LANCER_DIRNAMES
[move.appear
[1].p
];
425 move.vanish
[0].p
== V
.PAWN
&&
426 Object
.keys(V
.LANCER_DIRNAMES
).includes(move.appear
[0].p
)
428 // Fix promotions in lancer:
429 notation
= notation
.slice(0, -1) +
430 "L:" + V
.LANCER_DIRNAMES
[move.appear
[0].p
];