1 import { ChessRules
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
} from "@/utils/alea";
5 export const VariantRules
= class DarkRules
extends ChessRules
7 // Standard rules, in the shadow
10 super.setOtherVariables(fen
);
11 const [sizeX
,sizeY
] = [V
.size
.x
,V
.size
.y
];
13 "w": ArrayFun
.init(sizeX
,sizeY
),
14 "b": ArrayFun
.init(sizeX
,sizeY
)
16 // Setup enlightened: squares reachable by each side
17 // (TODO: one side would be enough ?)
18 this.updateEnlightened();
23 for (let i
=0; i
<V
.size
.x
; i
++)
25 for (let j
=0; j
<V
.size
.y
; j
++)
27 this.enlightened
["w"][i
][j
] = false;
28 this.enlightened
["b"][i
][j
] = false;
31 const pawnShift
= {"w":-1, "b":1};
32 // Initialize with pieces positions (which are seen)
33 for (let i
=0; i
<V
.size
.x
; i
++)
35 for (let j
=0; j
<V
.size
.y
; j
++)
37 if (this.board
[i
][j
] != V
.EMPTY
)
39 const color
= this.getColor(i
,j
);
40 this.enlightened
[color
][i
][j
] = true;
41 // Add potential squares visible by "impossible pawn capture"
42 if (this.getPiece(i
,j
) == V
.PAWN
)
44 for (let shiftY
of [-1,1])
46 if (V
.OnBoard(i
+pawnShift
[color
],j
+shiftY
)
47 && this.board
[i
+pawnShift
[color
]][j
+shiftY
] == V
.EMPTY
)
49 this.enlightened
[color
][i
+pawnShift
[color
]][j
+shiftY
] = true;
56 const currentTurn
= this.turn
;
58 const movesWhite
= this.getAllValidMoves();
60 const movesBlack
= this.getAllValidMoves();
61 this.turn
= currentTurn
;
62 for (let move of movesWhite
)
63 this.enlightened
["w"][move.end
.x
][move.end
.y
] = true;
64 for (let move of movesBlack
)
65 this.enlightened
["b"][move.end
.x
][move.end
.y
] = true;
68 // Has to be redefined to avoid an infinite loop
71 const color
= this.turn
;
72 const oppCol
= V
.GetOppCol(color
);
73 let potentialMoves
= [];
74 for (let i
=0; i
<V
.size
.x
; i
++)
76 for (let j
=0; j
<V
.size
.y
; j
++)
78 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
,j
) == color
)
79 Array
.prototype.push
.apply(potentialMoves
, this.getPotentialMovesFrom([i
,j
]));
82 return potentialMoves
; //because there are no checks
87 if (this.kingPos
[this.turn
][0] < 0)
89 return true; //TODO: is it right?
94 return false; //there is no check
97 getCheckSquares(color
)
102 updateVariables(move)
104 super.updateVariables(move);
105 if (move.vanish
.length
>= 2 && move.vanish
[1].p
== V
.KING
)
107 // We took opponent king ! (because if castle vanish[1] is a rook)
108 this.kingPos
[this.turn
] = [-1,-1];
111 // Update lights for both colors:
112 this.updateEnlightened();
115 unupdateVariables(move)
117 super.unupdateVariables(move);
118 const c
= move.vanish
[0].c
;
119 const oppCol
= V
.GetOppCol(c
);
120 if (this.kingPos
[oppCol
][0] < 0)
122 // Last move took opponent's king
123 for (let psq
of move.vanish
)
127 this.kingPos
[oppCol
] = [psq
.x
, psq
.y
];
133 // Update lights for both colors:
134 this.updateEnlightened();
139 const color
= this.turn
;
140 const kp
= this.kingPos
[color
];
141 if (kp
[0] < 0) //king disappeared
142 return (color
== "w" ? "0-1" : "1-0");
143 if (this.atLeastOneMove()) // game not over
145 return "1/2"; //no moves but kings still there (seems impossible)
148 static get THRESHOLD_MATE()
150 return 500; //checkmates evals may be slightly below 1000
153 // In this special situation, we just look 1 half move ahead
156 const maxeval
= V
.INFINITY
;
157 const color
= this.turn
;
158 const oppCol
= V
.GetOppCol(color
);
159 const pawnShift
= (color
== "w" ? -1 : 1);
161 // Do not cheat: the current enlightment is all we can see
162 const myLight
= JSON
.parse(JSON
.stringify(this.enlightened
[color
]));
164 // Can a slider on (i,j) apparently take my king?
165 // NOTE: inaccurate because assume yes if some squares are shadowed
166 const sliderTake
= ([i
,j
], piece
) => {
167 const kp
= this.kingPos
[color
];
168 let step
= undefined;
169 if (piece
== V
.BISHOP
)
171 if (Math
.abs(kp
[0] - i
) == Math
.abs(kp
[1] - j
))
175 (i
-kp
[0]) / Math
.abs(i
-kp
[0]),
176 (j
-kp
[1]) / Math
.abs(j
-kp
[1])
180 else if (piece
== V
.ROOK
)
183 step
= [0, (j
-kp
[1]) / Math
.abs(j
-kp
[1])];
185 step
= [(i
-kp
[0]) / Math
.abs(i
-kp
[0]), 0];
189 // Check for obstacles
190 let obstacle
= false;
192 let x
=kp
[0]+step
[0], y
=kp
[1]+step
[1];
194 x
+= step
[0], y
+= step
[1])
196 if (myLight
[x
][y
] && this.board
[x
][y
] != V
.EMPTY
)
207 // Do I see something which can take my king ?
208 const kingThreats
= () => {
209 const kp
= this.kingPos
[color
];
210 for (let i
=0; i
<V
.size
.x
; i
++)
212 for (let j
=0; j
<V
.size
.y
; j
++)
214 if (myLight
[i
][j
] && this.board
[i
][j
] != V
.EMPTY
215 && this.getColor(i
,j
) != color
)
217 switch (this.getPiece(i
,j
))
220 if (kp
[0] + pawnShift
== i
&& Math
.abs(kp
[1]-j
) == 1)
224 if ((Math
.abs(kp
[0] - i
) == 2 && Math
.abs(kp
[1] - j
) == 1) ||
225 (Math
.abs(kp
[0] - i
) == 1 && Math
.abs(kp
[1] - j
) == 2))
231 if (Math
.abs(kp
[0] - i
) == 1 && Math
.abs(kp
[1] - j
) == 1)
235 if (sliderTake([i
,j
], V
.BISHOP
))
239 if (sliderTake([i
,j
], V
.ROOK
))
243 if (sliderTake([i
,j
], V
.BISHOP
) || sliderTake([i
,j
], V
.ROOK
))
253 let moves
= this.getAllValidMoves();
254 for (let move of moves
)
257 if (this.kingPos
[oppCol
][0] >= 0 && kingThreats())
259 // We didn't take opponent king, and our king will be captured: bad
260 move.eval
= -maxeval
;
267 move.eval
= 0; //a priori...
269 // Can I take something ? If yes, do it if it seems good...
270 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= color
) //avoid castle
272 const myPieceVal
= V
.VALUES
[move.appear
[0].p
];
273 const hisPieceVal
= V
.VALUES
[move.vanish
[1].p
];
274 if (myPieceVal
<= hisPieceVal
)
275 move.eval
= hisPieceVal
- myPieceVal
+ 2; //favor captures
278 // Taking a pawn with minor piece,
279 // or minor piece or pawn with a rook,
280 // or anything but a queen with a queen,
281 // or anything with a king.
282 // ==> Do it at random, although
283 // this is clearly inferior to what a human can deduce...
284 move.eval
= (Math
.random() < 0.5 ? 1 : -1);
289 // TODO: also need to implement the case when an opponent piece (in light)
290 // is threatening something - maybe not the king, but e.g. pawn takes rook.
292 moves
.sort((a
,b
) => b
.eval
- a
.eval
);
293 let candidates
= [0];
294 for (let j
=1; j
<moves
.length
&& moves
[j
].eval
== moves
[0].eval
; j
++)
296 return moves
[candidates
[randInt(candidates
.length
)]];