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
) {
81 m
.appear
[0].c
+ m
.appear
[0].p
,
89 static GenRandInitFen(randomness
) {
92 return "efbqkbnm/pppppppp/8/8/8/8/PPPPPPPP/EDBQKBNM w 0 ahah -";
94 const baseFen
= ChessRules
.GenRandInitFen(randomness
);
95 // Replace black rooks by lancers oriented south,
96 // and white rooks by lancers oriented north:
97 return baseFen
.replace(/r
/g
, 'g').replace(/R
/g
, 'C');
100 // Because of the lancers, getPiece() could be wrong:
101 // use board[x][y][1] instead (always valid).
102 // TODO: base implementation now uses this too (no?)
103 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
104 const initColor
= this.getColor(sx
, sy
);
105 const initPiece
= this.board
[sx
][sy
].charAt(1);
111 c: tr
? tr
.c : initColor
,
112 p: tr
? tr
.p : initPiece
125 // The opponent piece disappears if we take it
126 if (this.board
[ex
][ey
] != V
.EMPTY
) {
131 c: this.getColor(ex
, ey
),
132 p: this.board
[ex
][ey
].charAt(1)
140 getPotentialMovesFrom([x
, y
]) {
141 if (this.getPiece(x
, y
) == V
.LANCER
)
142 return this.getPotentialLancerMoves([x
, y
]);
143 return super.getPotentialMovesFrom([x
, y
]);
146 // Obtain all lancer moves in "step" direction
147 getPotentialLancerMoves_aux([x
, y
], step
, tr
) {
149 // Add all moves to vacant squares until opponent is met:
150 const color
= this.getColor(x
, y
);
151 const oppCol
= V
.GetOppCol(color
)
152 let sq
= [x
+ step
[0], y
+ step
[1]];
153 while (V
.OnBoard(sq
[0], sq
[1]) && this.getColor(sq
[0], sq
[1]) != oppCol
) {
154 if (this.board
[sq
[0]][sq
[1]] == V
.EMPTY
)
155 moves
.push(this.getBasicMove([x
, y
], sq
, tr
));
159 if (V
.OnBoard(sq
[0], sq
[1]))
160 // Add capturing move
161 moves
.push(this.getBasicMove([x
, y
], sq
, tr
));
165 getPotentialLancerMoves([x
, y
]) {
167 // Add all lancer possible orientations, similar to pawn promotions.
168 const color
= this.getColor(x
, y
);
169 const dirCode
= this.board
[x
][y
][1];
170 const curDir
= V
.LANCER_DIRS
[dirCode
];
172 this.getPotentialLancerMoves_aux([x
, y
], V
.LANCER_DIRS
[dirCode
]);
173 monodirMoves
.forEach(m
=> {
174 Object
.keys(V
.LANCER_DIRS
).forEach(k
=> {
175 const newDir
= V
.LANCER_DIRS
[k
];
176 // Prevent orientations toward outer board:
177 if (V
.OnBoard(m
.end
.x
+ newDir
[0], m
.end
.y
+ newDir
[1])) {
178 let mk
= JSON
.parse(JSON
.stringify(m
));
187 isAttacked(sq
, color
) {
189 super.isAttacked(sq
, color
) ||
190 this.isAttackedByLancer(sq
, color
)
194 isAttackedByLancer([x
, y
], color
) {
195 for (let step
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
196 // If in this direction there are only enemy pieces and empty squares,
197 // and we meet a lancer: can he reach us?
198 // NOTE: do not stop at first lancer, there might be several!
199 let coord
= { x: x
+ step
[0], y: y
+ step
[1] };
202 V
.OnBoard(coord
.x
, coord
.y
) &&
204 this.board
[coord
.x
][coord
.y
] == V
.EMPTY
||
205 this.getColor(coord
.x
, coord
.y
) == color
209 this.getPiece(coord
.x
, coord
.y
) == V
.LANCER
&&
210 !this.isImmobilized([coord
.x
, coord
.y
])
212 lancerPos
.push({x: coord
.x
, y: coord
.y
});
217 for (let xy
of lancerPos
) {
218 const dir
= V
.LANCER_DIRS
[this.board
[xy
.x
][xy
.y
].charAt(1)];
219 if (dir
[0] == -step
[0] && dir
[1] == -step
[1]) return true;
225 static get VALUES() {
226 return Object
.assign(
227 { l: 4.8 }, //Jeff K. estimation (for Eightpieces)
232 // For moves notation:
233 static get LANCER_DIRNAMES() {
247 // At move 1, forbid captures (in case of...):
248 if (this.movesCount
>= 2) return moves
;
249 return moves
.filter(m
=> m
.vanish
.length
== 1);
253 let notation
= super.getNotation(move);
254 if (Object
.keys(V
.LANCER_DIRNAMES
).includes(move.vanish
[0].p
))
255 // Lancer: add direction info
256 notation
+= "=" + V
.LANCER_DIRNAMES
[move.appear
[0].p
];
258 move.vanish
[0].p
== V
.PAWN
&&
259 Object
.keys(V
.LANCER_DIRNAMES
).includes(move.appear
[0].p
)
261 // Fix promotions in lancer:
262 notation
= notation
.slice(0, -1) +
263 "L:" + V
.LANCER_DIRNAMES
[move.appear
[0].p
];