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 "enbqkbnm/pppppppp/8/8/8/8/PPPPPPPP/ENBQKBNM 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 getPotentialMovesFrom([x
, y
]) {
118 if (this.getPiece(x
, y
) == V
.LANCER
)
119 return this.getPotentialLancerMoves([x
, y
]);
120 return super.getPotentialMovesFrom([x
, y
]);
123 getPotentialPawnMoves([x
, y
]) {
124 const color
= this.getColor(x
, y
);
125 let shiftX
= (color
== "w" ? -1 : 1);
126 const lastRank
= (color
== "w" ? 0 : 7);
127 let finalPieces
= [V
.PAWN
];
128 if (x
+ shiftX
== lastRank
) {
129 // Only allow direction facing inside board:
130 const allowedLancerDirs
=
132 ? ['e', 'f', 'g', 'h', 'm']
133 : ['c', 'd', 'e', 'm', 'o'];
134 finalPieces
= allowedLancerDirs
.concat([V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]);
136 return super.getPotentialPawnMoves([x
, y
], finalPieces
);
139 // Obtain all lancer moves in "step" direction
140 getPotentialLancerMoves_aux([x
, y
], step
, tr
) {
142 // Add all moves to vacant squares until opponent is met:
143 const color
= this.getColor(x
, y
);
144 const oppCol
= V
.GetOppCol(color
)
145 let sq
= [x
+ step
[0], y
+ step
[1]];
146 while (V
.OnBoard(sq
[0], sq
[1]) && this.getColor(sq
[0], sq
[1]) != oppCol
) {
147 if (this.board
[sq
[0]][sq
[1]] == V
.EMPTY
)
148 moves
.push(this.getBasicMove([x
, y
], sq
, tr
));
152 if (V
.OnBoard(sq
[0], sq
[1]))
153 // Add capturing move
154 moves
.push(this.getBasicMove([x
, y
], sq
, tr
));
158 getPotentialLancerMoves([x
, y
]) {
160 // Add all lancer possible orientations, similar to pawn promotions.
161 const color
= this.getColor(x
, y
);
162 const dirCode
= this.board
[x
][y
][1];
163 const curDir
= V
.LANCER_DIRS
[dirCode
];
165 this.getPotentialLancerMoves_aux([x
, y
], V
.LANCER_DIRS
[dirCode
]);
166 monodirMoves
.forEach(m
=> {
167 Object
.keys(V
.LANCER_DIRS
).forEach(k
=> {
168 const newDir
= V
.LANCER_DIRS
[k
];
169 // Prevent orientations toward outer board:
170 if (V
.OnBoard(m
.end
.x
+ newDir
[0], m
.end
.y
+ newDir
[1])) {
171 let mk
= JSON
.parse(JSON
.stringify(m
));
180 getCastleMoves([x
, y
]) {
181 const c
= this.getColor(x
, y
);
184 const oppCol
= V
.GetOppCol(c
);
187 // King, then lancer:
188 const finalSquares
= [ [2, 3], [V
.size
.y
- 2, V
.size
.y
- 3] ];
192 castleSide
++ //large, then small
194 if (this.castleFlags
[c
][castleSide
] >= V
.size
.y
) continue;
195 // If this code is reached, lancer and king are on initial position
197 const lancerPos
= this.castleFlags
[c
][castleSide
];
198 const castlingPiece
= this.board
[x
][lancerPos
].charAt(1);
200 // Nothing on the path of the king ? (and no checks)
201 const finDist
= finalSquares
[castleSide
][0] - y
;
202 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
206 (this.isAttacked([x
, i
], oppCol
)) ||
208 this.board
[x
][i
] != V
.EMPTY
&&
209 // NOTE: next check is enough, because of chessboard constraints
210 (this.getColor(x
, i
) != c
|| ![y
, lancerPos
].includes(i
))
213 continue castlingCheck
;
216 } while (i
!= finalSquares
[castleSide
][0]);
218 // Nothing on final squares, except maybe king and castling lancer?
219 for (i
= 0; i
< 2; i
++) {
221 finalSquares
[castleSide
][i
] != lancerPos
&&
222 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
224 finalSquares
[castleSide
][i
] != y
||
225 this.getColor(x
, finalSquares
[castleSide
][i
]) != c
228 continue castlingCheck
;
232 // If this code is reached, castle is valid
233 let allowedLancerDirs
= [castlingPiece
];
234 if (finalSquares
[castleSide
][1] != lancerPos
) {
235 // It moved: allow reorientation
238 ? ['e', 'f', 'g', 'h', 'm']
239 : ['c', 'd', 'e', 'm', 'o'];
241 allowedLancerDirs
.forEach(dir
=> {
247 y: finalSquares
[castleSide
][0],
253 y: finalSquares
[castleSide
][1],
259 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
260 new PiPo({ x: x
, y: lancerPos
, p: castlingPiece
, c: c
})
263 Math
.abs(y
- lancerPos
) <= 2
264 ? { x: x
, y: lancerPos
}
265 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
274 isAttacked(sq
, color
) {
276 super.isAttacked(sq
, color
) ||
277 this.isAttackedByLancer(sq
, color
)
281 isAttackedByLancer([x
, y
], color
) {
282 for (let step
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
283 // If in this direction there are only enemy pieces and empty squares,
284 // and we meet a lancer: can he reach us?
285 // NOTE: do not stop at first lancer, there might be several!
286 let coord
= { x: x
+ step
[0], y: y
+ step
[1] };
289 V
.OnBoard(coord
.x
, coord
.y
) &&
291 this.board
[coord
.x
][coord
.y
] == V
.EMPTY
||
292 this.getColor(coord
.x
, coord
.y
) == color
295 if (this.getPiece(coord
.x
, coord
.y
) == V
.LANCER
)
296 lancerPos
.push({x: coord
.x
, y: coord
.y
});
300 for (let xy
of lancerPos
) {
301 const dir
= V
.LANCER_DIRS
[this.board
[xy
.x
][xy
.y
].charAt(1)];
302 if (dir
[0] == -step
[0] && dir
[1] == -step
[1]) return true;
308 static get VALUES() {
309 return Object
.assign(
310 { l: 4.8 }, //Jeff K. estimation (for Eightpieces)
315 // For moves notation:
316 static get LANCER_DIRNAMES() {
330 // At move 1, forbid captures (in case of...):
331 if (this.movesCount
>= 2) return super.filterValid(moves
);
332 return moves
.filter(m
=> m
.vanish
.length
== 1);
335 static get SEARCH_DEPTH() {
340 let notation
= super.getNotation(move);
341 if (Object
.keys(V
.LANCER_DIRNAMES
).includes(move.vanish
[0].p
))
342 // Lancer: add direction info
343 notation
+= "=" + V
.LANCER_DIRNAMES
[move.appear
[0].p
];
344 else if (move.appear
.length
== 2 && move.vanish
[1].p
!= move.appear
[1].p
)
345 // Same after castle:
346 notation
+= "+L:" + V
.LANCER_DIRNAMES
[move.appear
[1].p
];
348 move.vanish
[0].p
== V
.PAWN
&&
349 Object
.keys(V
.LANCER_DIRNAMES
).includes(move.appear
[0].p
)
351 // Fix promotions in lancer:
352 notation
= notation
.slice(0, -1) +
353 "L:" + V
.LANCER_DIRNAMES
[move.appear
[0].p
];