1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class PocketknightRules
extends ChessRules
{
6 hoverHighlight([x
, y
]) {
7 // Testing move validity results in an infinite update loop.
8 // TODO: find a way to test validity anyway.
9 return (this.subTurn
== 2 && this.board
[x
][y
] == V
.EMPTY
);
12 static IsGoodFlags(flags
) {
13 // 4 for castle + 2 for knights
14 return !!flags
.match(/^[a-z]{4,4}[01]{2,2}$/);
18 super.setFlags(fenflags
); //castleFlags
19 this.knightFlags
= fenflags
.substr(4).split("").map(e
=> e
== "1");
23 return [this.castleFlags
, this.knightFlags
];
26 disaggregateFlags(flags
) {
27 this.castleFlags
= flags
[0];
28 this.knightFlags
= flags
[1];
31 setOtherVariables(fen
) {
32 super.setOtherVariables(fen
);
36 static GenRandInitFen(randomness
) {
38 return ChessRules
.GenRandInitFen(randomness
)
39 .slice(0, -2) + "11 -";
44 super.getFlagsFen() + this.knightFlags
.map(e
=> e
? "1" : "0").join("")
48 canIplay(side
, [x
, y
]) {
49 if (this.subTurn
== 1) return super.canIplay(side
, [x
, y
]);
50 // subturn == 2, drop the knight:
51 return side
== this.turn
&& this.board
[x
][y
] == V
.EMPTY
;
54 getPotentialMovesFrom([x
, y
]) {
55 if (this.subTurn
== 1) {
56 let moves
= super.getPotentialMovesFrom([x
, y
]);
57 // If flag allow it, add "king capture"
59 this.knightFlags
[this.turn
== 'w' ? 0 : 1] &&
60 this.getPiece(x
, y
) == V
.KING
62 const kp
= this.kingPos
[V
.GetOppCol(this.turn
)];
67 start: { x: x
, y: y
},
68 end: { x: kp
[0], y: kp
[1] }
74 // subTurn == 2: a move is a click, not handled here
79 if (this.subTurn
== 2) return super.filterValid(moves
);
80 const color
= this.turn
;
81 return moves
.filter(m
=> {
84 if (m
.appear
.length
> 0)
86 res
= !this.underCheck(color
);
88 // "Capture king": find landing square not resulting in check
89 outerLoop: for (let i
=0; i
<8; i
++) {
90 for (let j
=0; j
<8; j
++) {
91 if (this.board
[i
][j
] == V
.EMPTY
) {
92 const tMove
= new Move({
102 start: { x: -1, y: -1 }
105 const moveOk
= !this.underCheck(color
);
121 if (this.subTurn
== 1) return super.getAllValidMoves();
122 // Subturn == 2: only knight landings
124 const color
= this.turn
;
125 for (let i
=0; i
<8; i
++) {
126 for (let j
=0; j
<8; j
++) {
127 if (this.board
[i
][j
] == V
.EMPTY
) {
128 const tMove
= new Move({
138 start: { x: -1, y: -1 }
141 const moveOk
= !this.underCheck(color
);
143 if (moveOk
) moves
.push(tMove
);
151 if (isNaN(square
[0])) return null;
152 // If subTurn == 2 && square is empty && !underCheck, then drop
153 if (this.subTurn
== 2 && this.board
[square
[0]][square
[1]] == V
.EMPTY
) {
154 const color
= this.turn
;
155 const tMove
= new Move({
165 start: { x: -1, y: -1 }
168 const moveOk
= !this.underCheck(color
);
170 if (moveOk
) return tMove
;
176 move.flags
= JSON
.stringify(this.aggregateFlags());
177 if (move.appear
.length
> 0) {
178 // Usual case or knight landing
179 if (move.vanish
.length
> 0) this.epSquares
.push(this.getEpSquare(move));
180 else this.subTurn
= 1;
181 this.turn
= V
.GetOppCol(this.turn
);
183 V
.PlayOnBoard(this.board
, move);
184 if (move.vanish
.length
> 0) this.postPlay(move);
189 this.knightFlags
[this.turn
== 'w' ? 0 : 1] = false;
194 this.disaggregateFlags(JSON
.parse(move.flags
));
195 if (move.appear
.length
> 0) {
196 if (move.vanish
.length
> 0) this.epSquares
.pop();
197 else this.subTurn
= 2;
198 this.turn
= V
.GetOppCol(this.turn
);
200 V
.UndoOnBoard(this.board
, move);
201 if (move.vanish
.length
> 0) this.postUndo(move);
203 else this.subTurn
= 1;
207 let moves
= this.getAllValidMoves();
208 if (moves
.length
== 0) return null;
209 const maxeval
= V
.INFINITY
;
210 const color
= this.turn
;
211 const oppCol
= V
.GetOppCol(color
);
212 const getOppEval
= () => {
213 let evalOpp
= this.evalPosition();
214 this.getAllValidMoves().forEach(m
=> {
215 // Do not consider knight landings here
216 if (m
.appear
.length
> 0) {
218 const score
= this.getCurrentScore();
220 if (["1-0", "0-1"].includes(score
))
221 mvEval
= (score
== "1-0" ? 1 : -1) * maxeval
;
222 else if (score
== "*") mvEval
= this.evalPosition();
224 (oppCol
== 'w' && mvEval
> evalOpp
) ||
225 (oppCol
== 'b' && mvEval
< evalOpp
)
234 // Custom "search" at depth 2
237 m
.eval
= (color
== "w" ? -1 : 1) * maxeval
;
238 if (m
.appear
.length
== 0) {
239 const moves2
= this.getAllValidMoves();
241 moves2
.forEach(m2
=> {
243 const score
= this.getCurrentScore();
245 if (["1-0", "0-1"].includes(score
))
246 mvEval
= (score
== "1-0" ? 1 : -1) * maxeval
;
247 else if (score
== "*")
248 // Add small fluctuations to avoid dropping pieces always on the
249 // first available square.
250 mvEval
= getOppEval() + 0.05 - Math
.random() / 10;
252 (color
== 'w' && mvEval
> m
.eval
) ||
253 (color
== 'b' && mvEval
< m
.eval
)
262 const score
= this.getCurrentScore();
263 if (score
!= "1/2") {
264 if (score
!= "*") m
.eval
= (score
== "1-0" ? 1 : -1) * maxeval
;
265 else m
.eval
= getOppEval();
270 moves
.sort((a
, b
) => {
271 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
273 let candidates
= [0];
274 for (let i
= 1; i
< moves
.length
&& moves
[i
].eval
== moves
[0].eval
; i
++)
276 const mIdx
= candidates
[randInt(candidates
.length
)];
277 if (!moves
[mIdx
].next
) return moves
[mIdx
];
278 const move2
= moves
[mIdx
].next
;
279 delete moves
[mIdx
]["next"];
280 return [moves
[mIdx
], move2
];
284 if (move.vanish
.length
> 0)
285 return super.getNotation(move);
286 if (move.appear
.length
== 0)
290 return "N@" + V
.CoordsToSquare(move.end
);