Skip to content

Commit f2ece59

Browse files
authored
crazyhouse: fix premoves (#2811)
Also, highlight the premoved piece in the pockets menu.
1 parent b955cf4 commit f2ece59

File tree

3 files changed

+80
-12
lines changed

3 files changed

+80
-12
lines changed

lib/src/widgets/game_layout.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ class _GameLayoutState extends ConsumerState<GameLayout> {
205205
};
206206

207207
final pockets = widget.boardParams.pockets;
208+
final premoveDropRole = switch (gameData?.premovable?.premove) {
209+
DropMove(:final role) => role,
210+
_ => null,
211+
};
208212

209213
Widget topTable({required double boardSize}) => RotatedBox(
210214
quarterTurns: widget.topTableUpsideDown ? 2 : 0,
@@ -222,6 +226,7 @@ class _GameLayoutState extends ConsumerState<GameLayout> {
222226
pockets: pockets,
223227
squareSize: pocketSquareSize(boardSize: boardSize, isTablet: isTablet),
224228
isUpsideDown: widget.topTableUpsideDown,
229+
premoveDropRole: premoveDropRole,
225230
),
226231
widget.topTable,
227232
],
@@ -245,6 +250,7 @@ class _GameLayoutState extends ConsumerState<GameLayout> {
245250
pockets: pockets,
246251
squareSize: pocketSquareSize(boardSize: boardSize, isTablet: isTablet),
247252
isUpsideDown: widget.bottomTableUpsideDown,
253+
premoveDropRole: premoveDropRole,
248254
),
249255
],
250256
),

lib/src/widgets/pockets.dart

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class PocketsMenu extends ConsumerWidget {
1414
required this.playerSide,
1515
required this.squareSize,
1616
this.isUpsideDown = false,
17+
this.premoveDropRole,
1718
this.pieceAssets,
1819
});
1920

@@ -37,6 +38,9 @@ class PocketsMenu extends ConsumerWidget {
3738
/// This is used to also flip the drag feedback widget when dragging a piece onto the board.
3839
final bool isUpsideDown;
3940

41+
/// If non-null and [side] is the opposite of [sideToMove], the pocket with this role will be highlighted.
42+
final Role? premoveDropRole;
43+
4044
/// Optionally overrides pieces assets used to render the pieces in the pockets.
4145
///
4246
/// If null, the piece assets from the current board preferences are used.
@@ -59,18 +63,31 @@ class PocketsMenu extends ConsumerWidget {
5963
children: Role.values
6064
.where((role) => role != Role.king)
6165
.map(
62-
(role) => _Pocket(
63-
count: pockets.of(side, role),
64-
role: role,
65-
interactive:
66-
side == sideToMove &&
67-
(playerSide == PlayerSide.both ||
68-
(playerSide == PlayerSide.white && side == Side.white) ||
69-
(playerSide == PlayerSide.black && side == Side.black)),
70-
side: side,
71-
squareSize: squareSize,
72-
pieceAssets: pieceAssets ?? boardPrefs.pieceSet.assets,
73-
isUpsideDown: isUpsideDown,
66+
(role) => Container(
67+
color: side == sideToMove?.opposite && premoveDropRole == role
68+
? ref.watch(
69+
boardPreferencesProvider.select(
70+
(prefs) => prefs.boardTheme.colors.validPremoves,
71+
),
72+
)
73+
: null,
74+
child: _Pocket(
75+
count: pockets.of(side, role),
76+
role: role,
77+
interactive: switch (playerSide) {
78+
PlayerSide.none => false,
79+
// If these are the pockets of the user, they are always interactive to allow premoves.
80+
PlayerSide.white => side == Side.white,
81+
PlayerSide.black => side == Side.black,
82+
// In OTB games, premoves are not possible, so pockets are only interactive if it's this player's turn.
83+
PlayerSide.both => side == sideToMove,
84+
},
85+
86+
side: side,
87+
squareSize: squareSize,
88+
pieceAssets: pieceAssets ?? boardPrefs.pieceSet.assets,
89+
isUpsideDown: isUpsideDown,
90+
),
7491
),
7592
)
7693
.toList(growable: false),

test/view/game/game_screen_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,51 @@ void main() {
340340
expect(find.byKey(const Key('d4-whitepawn')), findsOneWidget);
341341
});
342342

343+
testWidgets('can premove drop moves in Crazyhouse', (WidgetTester tester) async {
344+
const gameFullId = GameFullId('qVChCOTcHSeW');
345+
final gameSocketUri = GameController.socketUri(gameFullId);
346+
347+
await createTestGame(
348+
tester,
349+
pgn: 'e4 d5 exd5',
350+
variant: Variant.crazyhouse,
351+
clock: const (
352+
running: true,
353+
initial: Duration(minutes: 1),
354+
increment: Duration.zero,
355+
white: Duration(seconds: 58),
356+
black: Duration(seconds: 54),
357+
emerg: Duration(seconds: 10),
358+
),
359+
serverPrefs: const ServerGamePrefs(
360+
showRatings: true,
361+
enablePremove: true,
362+
autoQueen: .always,
363+
confirmResign: true,
364+
submitMove: false,
365+
zenMode: .no,
366+
),
367+
);
368+
369+
await playDropMove(tester, Side.white, Role.pawn, 'a4');
370+
371+
// premove indicator should be visible
372+
expect(find.byKey(const ValueKey('a4-premove')), findsOneWidget);
373+
374+
// opponent plays Qxd5
375+
sendServerSocketMessages(gameSocketUri, [
376+
'{"t": "move", "v": 1, "d": {"ply": 4, "uci": "d8d5", "san": "Qxd5", "clock": {"white": 57, "black": 52}}}',
377+
]);
378+
await tester.pump();
379+
380+
// let the premove microtask run
381+
await tester.pump(const Duration(milliseconds: 1));
382+
383+
// premove should have been played
384+
expect(find.byKey(const ValueKey('a4-premove')), findsNothing);
385+
expect(find.byKey(const Key('a4-whitepawn')), findsOneWidget);
386+
});
387+
343388
testWidgets('takeback', (WidgetTester tester) async {
344389
await createTestGame(
345390
tester,

0 commit comments

Comments
 (0)