a6be9f2bd21311d15a34fa043868cdf7dad4c2e4
1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
} from "@/utils/alea";
7 export const VariantRules
= class WormholeRules
extends ChessRules
{
8 static get HasFlags() {
12 static get HasEnpassant() {
16 // Analyse in Hidden mode makes no sense
17 static get CanAnalyze() {
21 // Moves are revealed only when game ends
22 static get ShowMoves() {
26 static get HIDDEN_DECODE() {
36 static get HIDDEN_CODE() {
47 // Turn a hidden piece or revealed piece into revealed piece:
49 if (Object
.keys(V
.HIDDEN_DECODE
).includes(p
))
50 return V
.HIDDEN_DECODE
[p
];
55 return ChessRules
.PIECES
.concat(Object
.values(V
.HIDDEN_CODE
));
58 // Pieces can be hidden :)
60 const piece
= this.board
[i
][j
].charAt(1);
61 if (Object
.keys(V
.HIDDEN_DECODE
).includes(piece
))
62 return V
.HIDDEN_DECODE
[piece
];
66 // Scan board for kings positions (no castling)
68 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
69 const fenRows
= V
.ParseFen(fen
).position
.split("/");
70 for (let i
= 0; i
< fenRows
.length
; i
++) {
71 let k
= 0; //column index on board
72 for (let j
= 0; j
< fenRows
[i
].length
; j
++) {
73 switch (fenRows
[i
].charAt(j
)) {
76 this.kingPos
["b"] = [i
, k
];
80 this.kingPos
["w"] = [i
, k
];
83 const num
= parseInt(fenRows
[i
].charAt(j
));
84 if (!isNaN(num
)) k
+= num
- 1;
92 getPpath(b
, color
, score
) {
93 if (Object
.keys(V
.HIDDEN_DECODE
).includes(b
[1])) {
94 // Supposed to be hidden.
95 if (score
== "*" && (!color
|| color
!= b
[0]))
96 return "Hidden/" + b
[0] + "p";
97 // Else: condition OK to show the piece
98 return b
[0] + V
.HIDDEN_DECODE
[b
[1]];
100 // The piece is already not supposed to be hidden:
104 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
107 Object
.keys(V
.HIDDEN_DECODE
).includes(this.board
[sx
][sy
].charAt(1))
109 // The transformed piece is a priori hidden
110 tr
.p
= V
.HIDDEN_CODE
[tr
.p
];
117 c: tr
? tr
.c : this.getColor(sx
, sy
),
118 p: tr
? tr
.p : this.board
[sx
][sy
].charAt(1)
125 c: this.getColor(sx
, sy
),
126 p: this.board
[sx
][sy
].charAt(1)
131 // The opponent piece disappears if we take it
132 if (this.board
[ex
][ey
] != V
.EMPTY
) {
137 c: this.getColor(ex
, ey
),
138 p: this.board
[ex
][ey
].charAt(1)
141 // Pieces are revealed when they capture
142 mv
.appear
[0].p
= V
.Decode(mv
.appear
[0].p
);
148 // What are the king moves from square x,y ?
149 getPotentialKingMoves(sq
) {
151 return this.getSlideNJumpMoves(
153 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
162 static GenRandInitFen() {
163 let pieces
= { w: new Array(8), b: new Array(8) };
164 // Shuffle pieces + pawns on two first ranks
165 for (let c
of ["w", "b"]) {
166 let positions
= ArrayFun
.range(16);
168 // Get random squares for bishops
169 let randIndex
= 2 * randInt(8);
170 const bishop1Pos
= positions
[randIndex
];
171 // The second bishop must be on a square of different color
172 let randIndex_tmp
= 2 * randInt(8) + 1;
173 const bishop2Pos
= positions
[randIndex_tmp
];
174 // Remove chosen squares
175 positions
.splice(Math
.max(randIndex
, randIndex_tmp
), 1);
176 positions
.splice(Math
.min(randIndex
, randIndex_tmp
), 1);
178 // Get random squares for knights
179 randIndex
= randInt(14);
180 const knight1Pos
= positions
[randIndex
];
181 positions
.splice(randIndex
, 1);
182 randIndex
= randInt(13);
183 const knight2Pos
= positions
[randIndex
];
184 positions
.splice(randIndex
, 1);
186 // Get random squares for rooks
187 randIndex
= randInt(12);
188 const rook1Pos
= positions
[randIndex
];
189 positions
.splice(randIndex
, 1);
190 randIndex
= randInt(11);
191 const rook2Pos
= positions
[randIndex
];
192 positions
.splice(randIndex
, 1);
194 // Get random square for queen
195 randIndex
= randInt(10);
196 const queenPos
= positions
[randIndex
];
197 positions
.splice(randIndex
, 1);
199 // Get random square for king
200 randIndex
= randInt(9);
201 const kingPos
= positions
[randIndex
];
202 positions
.splice(randIndex
, 1);
204 // Pawns position are all remaining slots:
205 for (let p
of positions
)
208 // Finally put the shuffled pieces in the board array
209 pieces
[c
][rook1Pos
] = "u";
210 pieces
[c
][knight1Pos
] = "o";
211 pieces
[c
][bishop1Pos
] = "c";
212 pieces
[c
][queenPos
] = "t";
213 pieces
[c
][kingPos
] = "l";
214 pieces
[c
][bishop2Pos
] = "c";
215 pieces
[c
][knight2Pos
] = "o";
216 pieces
[c
][rook2Pos
] = "u";
218 let upFen
= pieces
["b"].join("");
219 upFen
= upFen
.substr(0,8) + "/" + upFen
.substr(8).split("").reverse().join("");
220 let downFen
= pieces
["b"].join("").toUpperCase();
221 downFen
= downFen
.substr(0,8) + "/" + downFen
.substr(8).split("").reverse().join("");
222 return upFen
+ "/8/8/8/8/" + downFen
+ " w 0";
229 updateVariables(move) {
230 super.updateVariables(move);
232 move.vanish
.length
>= 2 &&
233 [V
.KING
,V
.HIDDEN_CODE
[V
.KING
]].includes(move.vanish
[1].p
)
235 // We took opponent king
236 this.kingPos
[this.turn
] = [-1, -1];
240 unupdateVariables(move) {
241 super.unupdateVariables(move);
242 const c
= move.vanish
[0].c
;
243 const oppCol
= V
.GetOppCol(c
);
244 if (this.kingPos
[oppCol
][0] < 0)
245 // Last move took opponent's king:
246 this.kingPos
[oppCol
] = [move.vanish
[1].x
, move.vanish
[1].y
];
250 const color
= this.turn
;
251 const kp
= this.kingPos
[color
];
254 return color
== "w" ? "0-1" : "1-0";
255 // Assume that stalemate is impossible:
260 const color
= this.turn
;
261 let moves
= this.getAllValidMoves();
262 for (let move of moves
) {
263 move.eval
= 0; //a priori...
265 // Can I take something ? If yes, do it with some probability
266 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= color
) {
267 // OK this isn't a castling move
268 const myPieceVal
= V
.VALUES
[move.appear
[0].p
];
269 const hisPieceVal
= Object
.keys(V
.HIDDEN_DECODE
).includes(move.vanish
[1].p
)
271 : V
.VALUES
[move.vanish
[1].p
];
273 // Opponent's piece is unknown: do not take too much risk
274 move.eval
= -myPieceVal
+ 1.5; //so that pawns always take
277 else if (myPieceVal
<= hisPieceVal
)
278 move.eval
= hisPieceVal
- myPieceVal
+ 1;
280 // Taking a pawn with minor piece,
281 // or minor piece or pawn with a rook,
282 // or anything but a queen with a queen,
283 // or anything with a king.
284 move.eval
= hisPieceVal
- myPieceVal
;
287 // If no capture, favor small step moves,
288 // but sometimes move the knight anyway
289 const penalty
= V
.Decode(move.vanish
[0].p
) != V
.KNIGHT
290 ? Math
.abs(move.end
.x
- move.start
.x
) + Math
.abs(move.end
.y
- move.start
.y
)
291 : (Math
.random() < 0.5 ? 3 : 1);
292 move.eval
-= penalty
/ (V
.size
.x
+ V
.size
.y
- 1);
295 // TODO: also favor movements toward the center?
298 moves
.sort((a
, b
) => b
.eval
- a
.eval
);
299 let candidates
= [0];
300 for (let j
= 1; j
< moves
.length
&& moves
[j
].eval
== moves
[0].eval
; j
++)
302 return moves
[candidates
[randInt(candidates
.length
)]];
306 // Translate final square
307 const finalSquare
= V
.CoordsToSquare(move.end
);
309 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
310 if (piece
== V
.PAWN
) {
313 if (move.vanish
.length
> move.appear
.length
) {
315 const startColumn
= V
.CoordToColumn(move.start
.y
);
316 notation
= startColumn
+ "x" + finalSquare
;
318 else notation
= finalSquare
;
319 if (move.appear
.length
> 0 && !["p","s"].includes(move.appear
[0].p
)) {
321 const appearPiece
= V
.Decode(move.appear
[0].p
);
322 notation
+= "=" + appearPiece
.toUpperCase();
328 piece
.toUpperCase() +
329 (move.vanish
.length
> move.appear
.length
? "x" : "") +