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(options
) {
38 return ChessRules
.GenRandInitFen(options
).slice(0, -2) + "11 -";
43 super.getFlagsFen() + this.knightFlags
.map(e
=> e
? "1" : "0").join("")
47 canIplay(side
, [x
, y
]) {
48 if (this.subTurn
== 1) return super.canIplay(side
, [x
, y
]);
49 // subturn == 2, drop the knight:
50 return side
== this.turn
&& this.board
[x
][y
] == V
.EMPTY
;
53 getPotentialMovesFrom([x
, y
]) {
54 if (this.subTurn
== 1) {
55 let moves
= super.getPotentialMovesFrom([x
, y
]);
56 // If flag allow it, add "king capture"
58 this.knightFlags
[this.turn
== 'w' ? 0 : 1] &&
59 this.getPiece(x
, y
) == V
.KING
61 const kp
= this.kingPos
[V
.GetOppCol(this.turn
)];
66 start: { x: x
, y: y
},
67 end: { x: kp
[0], y: kp
[1] }
73 // subTurn == 2: a move is a click, not handled here
78 if (this.subTurn
== 2) return super.filterValid(moves
);
79 const color
= this.turn
;
80 return moves
.filter(m
=> {
83 if (m
.appear
.length
> 0)
85 res
= !this.underCheck(color
);
87 // "Capture king": find landing square not resulting in check
88 outerLoop: for (let i
=0; i
<8; i
++) {
89 for (let j
=0; j
<8; j
++) {
90 if (this.board
[i
][j
] == V
.EMPTY
) {
91 const tMove
= new Move({
101 start: { x: -1, y: -1 }
104 const moveOk
= !this.underCheck(color
);
120 if (this.subTurn
== 1) return super.getAllValidMoves();
121 // Subturn == 2: only knight landings
123 const color
= this.turn
;
124 for (let i
=0; i
<8; i
++) {
125 for (let j
=0; j
<8; j
++) {
126 if (this.board
[i
][j
] == V
.EMPTY
) {
127 const tMove
= new Move({
137 start: { x: -1, y: -1 }
140 const moveOk
= !this.underCheck(color
);
142 if (moveOk
) moves
.push(tMove
);
150 if (isNaN(square
[0])) return null;
151 // If subTurn == 2 && square is empty && !underCheck, then drop
152 if (this.subTurn
== 2 && this.board
[square
[0]][square
[1]] == V
.EMPTY
) {
153 const color
= this.turn
;
154 const tMove
= new Move({
164 start: { x: -1, y: -1 }
167 const moveOk
= !this.underCheck(color
);
169 if (moveOk
) return tMove
;
175 move.flags
= JSON
.stringify(this.aggregateFlags());
176 if (move.appear
.length
> 0) {
177 // Usual case or knight landing
178 if (move.vanish
.length
> 0) this.epSquares
.push(this.getEpSquare(move));
179 else this.subTurn
= 1;
180 this.turn
= V
.GetOppCol(this.turn
);
182 V
.PlayOnBoard(this.board
, move);
183 if (move.vanish
.length
> 0) this.postPlay(move);
188 this.knightFlags
[this.turn
== 'w' ? 0 : 1] = false;
193 this.disaggregateFlags(JSON
.parse(move.flags
));
194 if (move.appear
.length
> 0) {
195 if (move.vanish
.length
> 0) this.epSquares
.pop();
196 else this.subTurn
= 2;
197 this.turn
= V
.GetOppCol(this.turn
);
199 V
.UndoOnBoard(this.board
, move);
200 if (move.vanish
.length
> 0) this.postUndo(move);
202 else this.subTurn
= 1;
206 let moves
= this.getAllValidMoves();
207 if (moves
.length
== 0) return null;
208 const maxeval
= V
.INFINITY
;
209 const color
= this.turn
;
210 const oppCol
= V
.GetOppCol(color
);
211 const getOppEval
= () => {
212 let evalOpp
= this.evalPosition();
213 this.getAllValidMoves().forEach(m
=> {
214 // Do not consider knight landings here
215 if (m
.appear
.length
> 0) {
217 const score
= this.getCurrentScore();
219 if (["1-0", "0-1"].includes(score
))
220 mvEval
= (score
== "1-0" ? 1 : -1) * maxeval
;
221 else if (score
== "*") mvEval
= this.evalPosition();
223 (oppCol
== 'w' && mvEval
> evalOpp
) ||
224 (oppCol
== 'b' && mvEval
< evalOpp
)
233 // Custom "search" at depth 2
236 m
.eval
= (color
== "w" ? -1 : 1) * maxeval
;
237 if (m
.appear
.length
== 0) {
238 const moves2
= this.getAllValidMoves();
240 moves2
.forEach(m2
=> {
242 const score
= this.getCurrentScore();
244 if (["1-0", "0-1"].includes(score
))
245 mvEval
= (score
== "1-0" ? 1 : -1) * maxeval
;
246 else if (score
== "*")
247 // Add small fluctuations to avoid dropping pieces always on the
248 // first available square.
249 mvEval
= getOppEval() + 0.05 - Math
.random() / 10;
251 (color
== 'w' && mvEval
> m
.eval
) ||
252 (color
== 'b' && mvEval
< m
.eval
)
261 const score
= this.getCurrentScore();
262 if (score
!= "1/2") {
263 if (score
!= "*") m
.eval
= (score
== "1-0" ? 1 : -1) * maxeval
;
264 else m
.eval
= getOppEval();
269 moves
.sort((a
, b
) => {
270 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
272 let candidates
= [0];
273 for (let i
= 1; i
< moves
.length
&& moves
[i
].eval
== moves
[0].eval
; i
++)
275 const mIdx
= candidates
[randInt(candidates
.length
)];
276 if (!moves
[mIdx
].next
) return moves
[mIdx
];
277 const move2
= moves
[mIdx
].next
;
278 delete moves
[mIdx
]["next"];
279 return [moves
[mIdx
], move2
];
283 if (move.vanish
.length
> 0)
284 return super.getNotation(move);
285 if (move.appear
.length
== 0)
289 return "N@" + V
.CoordsToSquare(move.end
);