Skip to content

Commit

Permalink
Add non visual game board to game screen
Browse files Browse the repository at this point in the history
  • Loading branch information
370417 committed Jan 18, 2023
1 parent decdb94 commit 46a2128
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 107 deletions.
168 changes: 93 additions & 75 deletions lib/src/features/game/ui/board/playable_game_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:lichess_mobile/src/common/lichess_icons.dart';
import 'package:lichess_mobile/src/utils/chessground_compat.dart';
import 'package:lichess_mobile/src/utils/async_value.dart';
import 'package:lichess_mobile/src/widgets/game_board_layout.dart';
import 'package:lichess_mobile/src/widgets/non_visual_game_board.dart';
import 'package:lichess_mobile/src/widgets/platform.dart';
import 'package:lichess_mobile/src/widgets/feedback.dart';
import 'package:lichess_mobile/src/widgets/player.dart';
Expand Down Expand Up @@ -169,88 +170,105 @@ class _BoardBody extends ConsumerWidget {
final isBoardTurned = ref.watch(isBoardTurnedProvider);
final isReplaying =
gameState != null && positionCursor < gameState.positionIndex;
final mediaQuery = MediaQuery.of(context);

return gameClockStream.when(
data: (clock) {
final black = Player(
key: const ValueKey('black-player'),
name: game.black.name,
rating: game.black.rating,
title: game.black.title,
active: gameState != null &&
gameState.status == GameStatus.started &&
gameState.position.fullmoves > 1 &&
gameState.position.turn == Side.black,
clock: clock.blackTime,
);
final white = Player(
key: const ValueKey('white-player'),
name: game.white.name,
rating: game.white.rating,
title: game.white.title,
active: gameState != null &&
gameState.status == GameStatus.started &&
gameState.position.fullmoves > 1 &&
gameState.position.turn == Side.white,
clock: clock.whiteTime,
);
final topPlayer = game.orientation == Side.white ? black : white;
final bottomPlayer = game.orientation == Side.white ? white : black;
if (mediaQuery.accessibleNavigation) {
return NonVisualGameBoard(
gameState: gameState,
onMove: (Move move) =>
ref.read(gameStateProvider.notifier).onUserMove(game.id, move),
);
} else {
final black = Player(
key: const ValueKey('black-player'),
name: game.black.name,
rating: game.black.rating,
title: game.black.title,
active: gameState != null &&
gameState.status == GameStatus.started &&
gameState.position.fullmoves > 1 &&
gameState.position.turn == Side.black,
clock: clock.blackTime,
);
final white = Player(
key: const ValueKey('white-player'),
name: game.white.name,
rating: game.white.rating,
title: game.white.title,
active: gameState != null &&
gameState.status == GameStatus.started &&
gameState.position.fullmoves > 1 &&
gameState.position.turn == Side.white,
clock: clock.whiteTime,
);
final topPlayer = game.orientation == Side.white ? black : white;
final bottomPlayer = game.orientation == Side.white ? white : black;

return GameBoardLayout(
boardData: cg.BoardData(
interactableSide:
gameState == null || !gameState.playing || isReplaying
? cg.InteractableSide.none
: game.orientation == Side.white
? cg.InteractableSide.white
: cg.InteractableSide.black,
orientation:
(isBoardTurned ? game.orientation.opposite : game.orientation)
.cg,
fen: gameState?.positions[positionCursor].fen ?? game.initialFen,
validMoves: gameState?.validMoves,
lastMove: gameState != null && gameState.gameOver
? positionCursor > 0
? gameState.moveAtPly(positionCursor - 1)?.cg
: null
: gameState?.lastMove?.cg,
sideToMove: gameState?.position.turn.cg ?? game.orientation.cg,
onMove: (cg.Move move, {bool? isPremove}) => ref
.read(gameStateProvider.notifier)
.onUserMove(game.id, Move.fromUci(move.uci)!),
),
topPlayer: topPlayer,
bottomPlayer: bottomPlayer,
moves: gameState?.sanMoves,
currentMoveIndex: positionCursor,
);
return GameBoardLayout(
boardData: cg.BoardData(
interactableSide:
gameState == null || !gameState.playing || isReplaying
? cg.InteractableSide.none
: game.orientation == Side.white
? cg.InteractableSide.white
: cg.InteractableSide.black,
orientation:
(isBoardTurned ? game.orientation.opposite : game.orientation)
.cg,
fen: gameState?.positions[positionCursor].fen ?? game.initialFen,
validMoves: gameState?.validMoves,
lastMove: gameState != null && gameState.gameOver
? positionCursor > 0
? gameState.moveAtPly(positionCursor - 1)?.cg
: null
: gameState?.lastMove?.cg,
sideToMove: gameState?.position.turn.cg ?? game.orientation.cg,
onMove: (cg.Move move, {bool? isPremove}) => ref
.read(gameStateProvider.notifier)
.onUserMove(game.id, Move.fromUci(move.uci)!),
),
topPlayer: topPlayer,
bottomPlayer: bottomPlayer,
moves: gameState?.sanMoves,
currentMoveIndex: positionCursor,
);
}
},
loading: () {
final player = Player(
name: game.player.name,
rating: game.player.rating,
title: game.player.title,
active: false,
clock: Duration.zero,
);
final opponent = Player(
name: game.opponent.name,
rating: game.opponent.rating,
title: game.opponent.title,
active: false,
clock: Duration.zero,
);
if (mediaQuery.accessibleNavigation) {
return NonVisualGameBoard(
gameState: null,
onMove: (move) {},
isLoading: true,
);
} else {
final player = Player(
name: game.player.name,
rating: game.player.rating,
title: game.player.title,
active: false,
clock: Duration.zero,
);
final opponent = Player(
name: game.opponent.name,
rating: game.opponent.rating,
title: game.opponent.title,
active: false,
clock: Duration.zero,
);

return GameBoardLayout(
topPlayer: opponent,
bottomPlayer: player,
boardData: cg.BoardData(
interactableSide: cg.InteractableSide.none,
orientation: game.orientation.cg,
fen: game.initialFen,
),
);
return GameBoardLayout(
topPlayer: opponent,
bottomPlayer: player,
boardData: cg.BoardData(
interactableSide: cg.InteractableSide.none,
orientation: game.orientation.cg,
fen: game.initialFen,
),
);
}
},
error: (err, stackTrace) {
debugPrint(
Expand Down
68 changes: 45 additions & 23 deletions lib/src/widgets/non_visual_board.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ class NonVisualBoard extends StatefulWidget {
required this.position,
required this.lastSanMove,
required this.handleCommand,
this.isLoading = false,
this.loadCompleteMsg = 'Loading complete',
super.key,
});

final Position position;

final String lastSanMove;
final String? lastSanMove;

final bool isLoading;

final String loadCompleteMsg;

/// Callback called when a command is submitted.
///
Expand Down Expand Up @@ -42,29 +48,45 @@ class _NonVisualBoardState extends State<NonVisualBoard> {
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}

return ListView(
children: <Widget>[
const Text('Current position'),
Semantics(
liveRegion: true,
// TODO: decide on a set of parameters
child: Text(renderCurrentPos(widget.lastSanMove, widget.position)),
),
TextField(
decoration: const InputDecoration(
labelText: 'move input',
if (widget.isLoading) {
return ListView(
children: <Widget>[
Semantics(
key: const ValueKey('loading-status'),
liveRegion: true,
child: const Text('Loading'),
),
],
);
} else {
return ListView(
children: <Widget>[
Semantics(
key: const ValueKey('loading-status'),
liveRegion: true,
child: Text(widget.loadCompleteMsg),
),
controller: textController,
onSubmitted: (input) {
final message = widget.handleCommand(input);
if (message != null) {
announce(message);
}
textController.clear();
},
),
],
);
const Text('Current position'),
Semantics(
liveRegion: true,
child: Text(renderCurrentPos(widget.lastSanMove, widget.position)),
),
TextField(
decoration: const InputDecoration(
labelText: 'move input',
),
controller: textController,
onSubmitted: (input) {
final message = widget.handleCommand(input);
if (message != null) {
announce(message);
}
textController.clear();
},
),
],
);
}
}
}

Expand Down
31 changes: 22 additions & 9 deletions lib/src/widgets/non_visual_game_board.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,51 @@ class NonVisualGameBoard extends StatelessWidget {
const NonVisualGameBoard({
required this.gameState,
required this.onMove,
this.isLoading = false,
super.key,
});

final GameState gameState;
final GameState? gameState;

final void Function(Move) onMove;

final bool isLoading;

@override
Widget build(BuildContext context) {
final board = gameState.position.board;
final lastSanMove =
gameState.sanMoves.isEmpty ? null : gameState.sanMoves.last;
final position = gameState?.position ?? Chess.initial;
final lastSanMove = gameState?.sanMoves.isNotEmpty == true
? gameState!.sanMoves.last
: null;
return NonVisualBoard(
position: gameState.position,
lastSanMove: gameState.sanMoves.last,
position: position,
lastSanMove: lastSanMove,
isLoading: isLoading,
loadCompleteMsg: 'Game loaded',
handleCommand: (command) {
final lowered = command.toLowerCase();
if (lowered == 'c' || lowered == 'clock') {
return 'todo: read clocks';
} else if (lowered == 'l' || lowered == 'last') {
return renderCurrentPos(lastSanMove, gameState.position);
return renderCurrentPos(lastSanMove, position);
} else if (lowered == 'o' || lowered == 'opponent') {
return 'todo: read player';
}
final pieceResult = handlePieceCommand(command, board);
final pieceResult = handlePieceCommand(command, position.board);
if (pieceResult != null) {
return pieceResult;
}
final scanResult = handleScanCommand(lowered, board);
final scanResult = handleScanCommand(lowered, position.board);
if (scanResult != null) {
return scanResult;
}
// TODO: uncomment when parseSan is available
final move =
Move.fromUci(command) /*?? gameState.position.parseSan(command)*/;
if (move != null) {
onMove(move);
return null;
}
return 'Invalid command: $command';
},
);
Expand Down

0 comments on commit 46a2128

Please sign in to comment.