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
{
6 // Standard rules, in the shadow
7 setOtherVariables(fen
) {
8 super.setOtherVariables(fen
);
9 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
11 w: ArrayFun
.init(sizeX
, sizeY
),
12 b: ArrayFun
.init(sizeX
, sizeY
)
14 // Setup enlightened: squares reachable by each side
15 // (TODO: one side would be enough ?)
16 this.updateEnlightened();
20 for (let i
= 0; i
< V
.size
.x
; i
++) {
21 for (let j
= 0; j
< V
.size
.y
; j
++) {
22 this.enlightened
["w"][i
][j
] = false;
23 this.enlightened
["b"][i
][j
] = false;
26 const pawnShift
= { w: -1, b: 1 };
27 // Initialize with pieces positions (which are seen)
28 for (let i
= 0; i
< V
.size
.x
; i
++) {
29 for (let j
= 0; j
< V
.size
.y
; j
++) {
30 if (this.board
[i
][j
] != V
.EMPTY
) {
31 const color
= this.getColor(i
, j
);
32 this.enlightened
[color
][i
][j
] = true;
33 // Add potential squares visible by "impossible pawn capture"
34 if (this.getPiece(i
, j
) == V
.PAWN
) {
35 for (let shiftY
of [-1, 1]) {
37 V
.OnBoard(i
+ pawnShift
[color
], j
+ shiftY
) &&
38 this.board
[i
+ pawnShift
[color
]][j
+ shiftY
] == V
.EMPTY
40 this.enlightened
[color
][i
+ pawnShift
[color
]][
49 const currentTurn
= this.turn
;
51 const movesWhite
= this.getAllValidMoves();
53 const movesBlack
= this.getAllValidMoves();
54 this.turn
= currentTurn
;
55 for (let move of movesWhite
)
56 this.enlightened
["w"][move.end
.x
][move.end
.y
] = true;
57 for (let move of movesBlack
)
58 this.enlightened
["b"][move.end
.x
][move.end
.y
] = true;
61 // Has to be redefined to avoid an infinite loop
63 const color
= this.turn
;
64 let potentialMoves
= [];
65 for (let i
= 0; i
< V
.size
.x
; i
++) {
66 for (let j
= 0; j
< V
.size
.y
; j
++) {
67 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
)
68 Array
.prototype.push
.apply(
70 this.getPotentialMovesFrom([i
, j
])
74 return potentialMoves
; //because there are no checks
78 if (this.kingPos
[this.turn
][0] < 0) return false;
79 return true; //TODO: is it right?
83 return false; //there is no check
90 updateVariables(move) {
91 super.updateVariables(move);
92 if (move.vanish
.length
>= 2 && move.vanish
[1].p
== V
.KING
) {
93 // We took opponent king ! (because if castle vanish[1] is a rook)
94 this.kingPos
[this.turn
] = [-1, -1];
97 // Update lights for both colors:
98 this.updateEnlightened();
101 unupdateVariables(move) {
102 super.unupdateVariables(move);
103 const c
= move.vanish
[0].c
;
104 const oppCol
= V
.GetOppCol(c
);
105 if (this.kingPos
[oppCol
][0] < 0) {
106 // Last move took opponent's king
107 for (let psq
of move.vanish
) {
109 this.kingPos
[oppCol
] = [psq
.x
, psq
.y
];
115 // Update lights for both colors:
116 this.updateEnlightened();
120 const color
= this.turn
;
121 const kp
= this.kingPos
[color
];
124 return color
== "w" ? "0-1" : "1-0";
125 if (this.atLeastOneMove())
128 return "1/2"; //no moves but kings still there (seems impossible)
131 static get THRESHOLD_MATE() {
132 return 500; //checkmates evals may be slightly below 1000
135 // In this special situation, we just look 1 half move ahead
137 const maxeval
= V
.INFINITY
;
138 const color
= this.turn
;
139 const oppCol
= V
.GetOppCol(color
);
140 const pawnShift
= color
== "w" ? -1 : 1;
142 // Do not cheat: the current enlightment is all we can see
143 const myLight
= JSON
.parse(JSON
.stringify(this.enlightened
[color
]));
145 // Can a slider on (i,j) apparently take my king?
146 // NOTE: inaccurate because assume yes if some squares are shadowed
147 const sliderTake
= ([i
, j
], piece
) => {
148 const kp
= this.kingPos
[color
];
149 let step
= undefined;
150 if (piece
== V
.BISHOP
) {
151 if (Math
.abs(kp
[0] - i
) == Math
.abs(kp
[1] - j
)) {
153 (i
- kp
[0]) / Math
.abs(i
- kp
[0]),
154 (j
- kp
[1]) / Math
.abs(j
- kp
[1])
157 } else if (piece
== V
.ROOK
) {
158 if (kp
[0] == i
) step
= [0, (j
- kp
[1]) / Math
.abs(j
- kp
[1])];
159 else if (kp
[1] == j
) step
= [(i
- kp
[0]) / Math
.abs(i
- kp
[0]), 0];
161 if (!step
) return false;
162 // Check for obstacles
163 let obstacle
= false;
165 let x
= kp
[0] + step
[0], y
= kp
[1] + step
[1];
167 x
+= step
[0], y
+= step
[1]
169 if (myLight
[x
][y
] && this.board
[x
][y
] != V
.EMPTY
) {
174 if (!obstacle
) return true;
178 // Do I see something which can take my king ?
179 const kingThreats
= () => {
180 const kp
= this.kingPos
[color
];
181 for (let i
= 0; i
< V
.size
.x
; i
++) {
182 for (let j
= 0; j
< V
.size
.y
; j
++) {
185 this.board
[i
][j
] != V
.EMPTY
&&
186 this.getColor(i
, j
) != color
188 switch (this.getPiece(i
, j
)) {
190 if (kp
[0] + pawnShift
== i
&& Math
.abs(kp
[1] - j
) == 1)
195 (Math
.abs(kp
[0] - i
) == 2 && Math
.abs(kp
[1] - j
) == 1) ||
196 (Math
.abs(kp
[0] - i
) == 1 && Math
.abs(kp
[1] - j
) == 2)
202 if (Math
.abs(kp
[0] - i
) == 1 && Math
.abs(kp
[1] - j
) == 1)
206 if (sliderTake([i
, j
], V
.BISHOP
)) return true;
209 if (sliderTake([i
, j
], V
.ROOK
)) return true;
212 if (sliderTake([i
, j
], V
.BISHOP
) || sliderTake([i
, j
], V
.ROOK
))
222 let moves
= this.getAllValidMoves();
223 for (let move of moves
) {
225 if (this.kingPos
[oppCol
][0] >= 0 && kingThreats()) {
226 // We didn't take opponent king, and our king will be captured: bad
227 move.eval
= -maxeval
;
231 if (move.eval
) continue;
233 move.eval
= 0; //a priori...
235 // Can I take something ? If yes, do it if it seems good...
236 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= color
) {
238 const myPieceVal
= V
.VALUES
[move.appear
[0].p
];
239 const hisPieceVal
= V
.VALUES
[move.vanish
[1].p
];
240 if (myPieceVal
<= hisPieceVal
) move.eval
= hisPieceVal
- myPieceVal
+ 2;
243 // Taking a pawn with minor piece,
244 // or minor piece or pawn with a rook,
245 // or anything but a queen with a queen,
246 // or anything with a king.
247 // ==> Do it at random, although
248 // this is clearly inferior to what a human can deduce...
249 move.eval
= Math
.random() < 0.5 ? 1 : -1;
254 // TODO: also need to implement the case when an opponent piece (in light)
255 // is threatening something - maybe not the king, but e.g. pawn takes rook.
257 moves
.sort((a
, b
) => b
.eval
- a
.eval
);
258 let candidates
= [0];
259 for (let j
= 1; j
< moves
.length
&& moves
[j
].eval
== moves
[0].eval
; j
++)
261 return moves
[candidates
[randInt(candidates
.length
)]];