1 import { ChessRules
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
} from "@/utils/alea";
5 export class DarkRules
extends ChessRules
{
7 // Analyse in Dark mode makes no sense
8 static get CanAnalyze() {
12 // Moves are revealed only when game ends
13 static get ShowMoves() {
17 static get SomeHiddenMoves() {
21 setOtherVariables(fen
) {
22 super.setOtherVariables(fen
);
23 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
25 w: ArrayFun
.init(sizeX
, sizeY
),
26 b: ArrayFun
.init(sizeX
, sizeY
)
28 // Setup enlightened: squares reachable by each side
29 // (TODO: one side would be enough ?)
30 this.updateEnlightened();
34 for (let i
= 0; i
< V
.size
.x
; i
++) {
35 for (let j
= 0; j
< V
.size
.y
; j
++) {
36 this.enlightened
["w"][i
][j
] = false;
37 this.enlightened
["b"][i
][j
] = false;
40 const pawnShift
= { w: -1, b: 1 };
41 // Initialize with pieces positions (which are seen)
42 for (let i
= 0; i
< V
.size
.x
; i
++) {
43 for (let j
= 0; j
< V
.size
.y
; j
++) {
44 if (this.board
[i
][j
] != V
.EMPTY
) {
45 const c
= this.getColor(i
, j
);
46 this.enlightened
[c
][i
][j
] = true;
47 // Add potential squares visible by "impossible pawn capture"
48 if (this.getPiece(i
, j
) == V
.PAWN
) {
49 for (let shiftY
of [-1, 1]) {
51 V
.OnBoard(i
+ pawnShift
[c
], j
+ shiftY
) &&
52 this.board
[i
+ pawnShift
[c
]][j
+ shiftY
] == V
.EMPTY
54 this.enlightened
[c
][i
+ pawnShift
[c
]][j
+ shiftY
] = true;
61 const currentTurn
= this.turn
;
63 const movesWhite
= this.getAllValidMoves();
65 const movesBlack
= this.getAllValidMoves();
66 this.turn
= currentTurn
;
67 for (let move of movesWhite
)
68 this.enlightened
["w"][move.end
.x
][move.end
.y
] = true;
69 for (let move of movesBlack
)
70 this.enlightened
["b"][move.end
.x
][move.end
.y
] = true;
71 // Include en-passant capturing square if any:
72 let moves
= currentTurn
== "w" ? movesWhite : movesBlack
;
73 for (let m
of moves
) {
75 m
.appear
[0].p
== V
.PAWN
&&
76 m
.vanish
.length
== 2 &&
77 m
.vanish
[1].x
!= m
.end
.x
79 const psq
= m
.vanish
[1];
80 this.enlightened
[currentTurn
][psq
.x
][psq
.y
] = true;
86 // To always allow castling:
92 // Used in the interface
96 // Has to be redefined to avoid an infinite loop
98 const color
= this.turn
;
99 let potentialMoves
= [];
100 for (let i
= 0; i
< V
.size
.x
; i
++) {
101 for (let j
= 0; j
< V
.size
.y
; j
++) {
102 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
)
103 Array
.prototype.push
.apply(
105 this.getPotentialMovesFrom([i
, j
])
109 return potentialMoves
; //because there are no checks
117 super.postPlay(move);
118 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
)
119 // We took opponent king (because if castle vanish[1] is a rook)
120 this.kingPos
[this.turn
] = [-1, -1];
122 // Update lights for both colors:
123 this.updateEnlightened();
127 super.postUndo(move);
128 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
)
129 // Last move took opponent's king:
130 this.kingPos
[move.vanish
[1].c
] = [move.vanish
[1].x
, move.vanish
[1].y
];
132 // Update lights for both colors:
133 this.updateEnlightened();
137 const color
= this.turn
;
138 const kp
= this.kingPos
[color
];
141 return color
== "w" ? "0-1" : "1-0";
142 // Assume that stalemate is impossible (I think so. Would need proof...)
146 static get THRESHOLD_MATE() {
147 return 500; //checkmates evals may be slightly below 1000
150 // In this special situation, we just look 1 half move ahead
152 const maxeval
= V
.INFINITY
;
153 const color
= this.turn
;
154 const oppCol
= V
.GetOppCol(color
);
155 const pawnShift
= color
== "w" ? -1 : 1;
157 // Do not cheat: the current enlightment is all we can see
158 const myLight
= JSON
.parse(JSON
.stringify(this.enlightened
[color
]));
160 // Can a slider on (i,j) apparently take my king?
161 // NOTE: inaccurate because assume yes if some squares are shadowed
162 const sliderTake
= ([i
, j
], piece
) => {
163 const kp
= this.kingPos
[color
];
164 let step
= undefined;
165 if (piece
== V
.BISHOP
) {
166 if (Math
.abs(kp
[0] - i
) == Math
.abs(kp
[1] - j
)) {
168 (i
- kp
[0]) / Math
.abs(i
- kp
[0]),
169 (j
- kp
[1]) / Math
.abs(j
- kp
[1])
172 } else if (piece
== V
.ROOK
) {
173 if (kp
[0] == i
) step
= [0, (j
- kp
[1]) / Math
.abs(j
- kp
[1])];
174 else if (kp
[1] == j
) step
= [(i
- kp
[0]) / Math
.abs(i
- kp
[0]), 0];
176 if (!step
) return false;
177 // Check for obstacles
178 let obstacle
= false;
180 let x
= kp
[0] + step
[0], y
= kp
[1] + step
[1];
182 x
+= step
[0], y
+= step
[1]
184 if (myLight
[x
][y
] && this.board
[x
][y
] != V
.EMPTY
) {
189 if (!obstacle
) return true;
193 // Do I see something which can take my king ?
194 const kingThreats
= () => {
195 const kp
= this.kingPos
[color
];
196 for (let i
= 0; i
< V
.size
.x
; i
++) {
197 for (let j
= 0; j
< V
.size
.y
; j
++) {
200 this.board
[i
][j
] != V
.EMPTY
&&
201 this.getColor(i
, j
) != color
203 switch (this.getPiece(i
, j
)) {
205 if (kp
[0] + pawnShift
== i
&& Math
.abs(kp
[1] - j
) == 1)
210 (Math
.abs(kp
[0] - i
) == 2 && Math
.abs(kp
[1] - j
) == 1) ||
211 (Math
.abs(kp
[0] - i
) == 1 && Math
.abs(kp
[1] - j
) == 2)
217 if (Math
.abs(kp
[0] - i
) == 1 && Math
.abs(kp
[1] - j
) == 1)
221 if (sliderTake([i
, j
], V
.BISHOP
)) return true;
224 if (sliderTake([i
, j
], V
.ROOK
)) return true;
227 if (sliderTake([i
, j
], V
.BISHOP
) || sliderTake([i
, j
], V
.ROOK
))
237 let moves
= this.getAllValidMoves();
238 for (let move of moves
) {
240 if (this.kingPos
[oppCol
][0] >= 0 && kingThreats()) {
241 // We didn't take opponent king, and our king will be captured: bad
242 move.eval
= -maxeval
;
246 if (move.eval
) continue;
248 move.eval
= 0; //a priori...
250 // Can I take something ? If yes, do it if it seems good...
251 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= color
) {
252 // OK this isn't a castling move
253 const myPieceVal
= V
.VALUES
[move.appear
[0].p
];
254 const hisPieceVal
= V
.VALUES
[move.vanish
[1].p
];
256 if (myPieceVal
<= hisPieceVal
)
257 move.eval
= hisPieceVal
- myPieceVal
+ 1;
259 // Taking a pawn with minor piece,
260 // or minor piece or pawn with a rook,
261 // or anything but a queen with a queen,
262 // or anything with a king.
263 move.eval
= hisPieceVal
- myPieceVal
;
264 //Math.random() < 0.5 ? 1 : -1;
269 // TODO: also need to implement the case when an opponent piece (in light)
270 // is threatening something - maybe not the king, but e.g. pawn takes rook.
272 moves
.sort((a
, b
) => b
.eval
- a
.eval
);
273 let candidates
= [0];
274 for (let j
= 1; j
< moves
.length
&& moves
[j
].eval
== moves
[0].eval
; j
++)
276 return moves
[candidates
[randInt(candidates
.length
)]];