1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
3 export class AtomicRules
extends ChessRules
{
14 select: ChessRules
.Options
.select
18 static AbbreviateOptions(opts
) {
19 return opts
["balanced"] ? 'B' : '';
22 static GenRandInitFen(options
) {
23 return ChessRules
.GenRandInitFen(options
) + (options
.balanced
? " B" : "");
26 setOtherVariables(fen
) {
27 super.setOtherVariables(fen
);
28 this.balanced
= !!V
.ParseFen(fen
).balanced
;
31 static ParseFen(fen
) {
33 { balanced: fen
.split(" ")[5] },
34 ChessRules
.ParseFen(fen
)
38 static IsGoodFen(fen
) {
39 if (!ChessRules
.IsGoodFen(fen
)) return false;
40 const balanced
= V
.ParseFen(fen
).balanced
;
41 return (!balanced
|| balanced
== 'B');
45 return super.getFen() + (this.balanced
? " B" : "");
48 hoverHighlight([x
, y
]) {
49 return this.balanced
&& this.movesCount
== 0 && [1, 6].includes(x
);
52 canIplay(side
, [x
, y
]) {
53 if (this.balanced
&& this.movesCount
== 0)
54 return (this.turn
== side
&& this.getPiece(x
, y
) == V
.PAWN
);
55 return super.canIplay(side
, [x
, y
]);
59 if (!this.balanced
|| this.movesCount
>= 1) return null;
60 const [x
, y
] = [square
[0], square
[1]];
61 if (![1, 6].includes(x
)) return null;
68 c: this.getColor(x
, y
),
72 start: { x: x
, y: y
},
77 getPotentialMovesFrom([x
, y
]) {
78 if (this.balanced
&& this.movesCount
== 0) {
79 if ([1, 6].includes(x
)) {
80 const c
= this.getColor(x
, y
);
85 new PiPo({ x: x
, y: y
, p: V
.PAWN
, c: c
})
87 start: { x: x
, y: y
},
95 let moves
= super.getPotentialMovesFrom([x
, y
]);
96 if (this.getPiece(x
, y
) == V
.PAWN
) {
97 // Promotions by captures can be reduced to only one deterministic
98 // move (because of the explosion).
99 moves
= moves
.filter(m
=> {
101 m
.vanish
.length
== 1 ||
102 [V
.PAWN
, V
.QUEEN
].includes(m
.appear
[0].p
)
108 // NOTE: if vanish.length==2 and appear.length==2, this is castle
109 if (m
.vanish
.length
> 1 && m
.appear
.length
<= 1) {
110 // Explosion! (TODO?: drop moves which explode our king here)
121 for (let step
of steps
) {
122 let x
= m
.end
.x
+ step
[0];
123 let y
= m
.end
.y
+ step
[1];
126 this.board
[x
][y
] != V
.EMPTY
&&
127 this.getPiece(x
, y
) != V
.PAWN
131 p: this.getPiece(x
, y
),
132 c: this.getColor(x
, y
),
139 m
.end
= { x: m
.appear
[0].x
, y: m
.appear
[0].y
};
140 m
.appear
.pop(); //Nothin appears in this case
146 getPotentialKingMoves([x
, y
]) {
147 // King cannot capture:
149 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
150 for (let step
of steps
) {
151 const i
= x
+ step
[0];
152 const j
= y
+ step
[1];
153 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
154 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
156 return moves
.concat(this.getCastleMoves([x
, y
]));
159 isAttacked(sq
, color
) {
161 this.getPiece(sq
[0], sq
[1]) == V
.KING
&&
162 this.isAttackedByKing(sq
, color
)
164 // A king next to the enemy king is immune to attacks
168 this.isAttackedByPawn(sq
, color
) ||
169 this.isAttackedByRook(sq
, color
) ||
170 this.isAttackedByKnight(sq
, color
) ||
171 this.isAttackedByBishop(sq
, color
) ||
172 this.isAttackedByQueen(sq
, color
)
173 // No "attackedByKing": it cannot take
178 super.postPlay(move);
179 // NOTE: (harmless) condition on movesCount for Atomic2
180 if (move.appear
.length
== 0 && this.movesCount
>= 2) {
182 const firstRank
= { w: 7, b: 0 };
183 for (let c
of ["w", "b"]) {
184 // Did we explode king of color c ? (TODO: remove move earlier)
186 Math
.abs(this.kingPos
[c
][0] - move.end
.x
) <= 1 &&
187 Math
.abs(this.kingPos
[c
][1] - move.end
.y
) <= 1
189 this.kingPos
[c
] = [-1, -1];
190 this.castleFlags
[c
] = [8, 8];
193 // Now check if init rook(s) exploded
194 if (Math
.abs(move.end
.x
- firstRank
[c
]) <= 1) {
195 if (Math
.abs(move.end
.y
- this.castleFlags
[c
][0]) <= 1)
196 this.castleFlags
[c
][0] = 8;
197 if (Math
.abs(move.end
.y
- this.castleFlags
[c
][1]) <= 1)
198 this.castleFlags
[c
][1] = 8;
206 super.postUndo(move);
208 const oppCol
= V
.GetOppCol(c
);
209 // NOTE: condition on movesCount for balanced setting
211 this.movesCount
>= 1 &&
212 [this.kingPos
[c
][0], this.kingPos
[oppCol
][0]].some(e
=> e
< 0)
214 // There is a chance that last move blowed some king away..
215 for (let psq
of move.vanish
) {
217 this.kingPos
[psq
.c
== c
? c : oppCol
] = [psq
.x
, psq
.y
];
223 const oppCol
= V
.GetOppCol(color
);
225 // If our king disappeared, move is not valid
226 if (this.kingPos
[color
][0] < 0) res
= true;
227 // If opponent king disappeared, move is valid
228 else if (this.kingPos
[oppCol
][0] < 0) res
= false;
229 // Otherwise, if we remain under check, move is not valid
230 else res
= this.isAttacked(this.kingPos
[color
], oppCol
);
235 const color
= this.turn
;
238 this.kingPos
[color
][0] >= 0 && //king might have exploded
239 this.isAttacked(this.kingPos
[color
], V
.GetOppCol(color
))
241 res
= [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))];
247 const color
= this.turn
;
248 const kp
= this.kingPos
[color
];
251 return color
== "w" ? "0-1" : "1-0";
252 if (this.atLeastOneMove()) return "*";
253 if (!this.isAttacked(kp
, V
.GetOppCol(color
))) return "1/2";
254 return color
== "w" ? "0-1" : "1-0"; //checkmate
258 if (move.appear
.length
== 0 && move.vanish
.length
== 1)
259 // First move in game (balanced == true)
260 return V
.CoordsToSquare(move.start
) + "X";
261 return super.getNotation(move);