From 9546ed0249b46cb534433667864a0ccc5c9a8f07 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Mon, 15 Jul 2024 18:53:06 +0200 Subject: [PATCH 01/17] feat: add a BoardEditor widget --- CHANGELOG.md | 4 + example/lib/board_editor_page.dart | 159 ++++++++++++++++++++++ example/lib/main.dart | 20 ++- example/pubspec.lock | 2 +- lib/chessground.dart | 3 + lib/src/board_editor_settings.dart | 94 +++++++++++++ lib/src/fen.dart | 33 +++++ lib/src/widgets/board.dart | 20 +-- lib/src/widgets/board_editor.dart | 155 +++++++++++++++++++++ lib/src/widgets/drag.dart | 46 +++++++ pubspec.yaml | 2 +- test/widgets/board_editor_test.dart | 203 ++++++++++++++++++++++++++++ 12 files changed, 722 insertions(+), 19 deletions(-) create mode 100644 example/lib/board_editor_page.dart create mode 100644 lib/src/board_editor_settings.dart create mode 100644 lib/src/widgets/board_editor.dart create mode 100644 lib/src/widgets/drag.dart create mode 100644 test/widgets/board_editor_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index e364dd4a..4b76b089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.3.0 + +- Add a `BoardEditor` widget, intended to be used as the basis for a board editor like lichess.org/editor + ## 3.2.0 - Add `pieceShiftMethod` to `BoardSetttings`, with possible values: `either` (default), `drag`, or `tapTwoSquares`. diff --git a/example/lib/board_editor_page.dart b/example/lib/board_editor_page.dart new file mode 100644 index 00000000..7706d85c --- /dev/null +++ b/example/lib/board_editor_page.dart @@ -0,0 +1,159 @@ +import 'package:board_example/board_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:chessground/chessground.dart'; +import 'package:dartchess/dartchess.dart' as dc; +import 'package:collection/collection.dart'; + +class BoardEditorPage extends StatefulWidget { + const BoardEditorPage({super.key}); + + @override + State createState() => _BoardEditorPageState(); +} + +class _BoardEditorPageState extends State { + Pieces pieces = readFen(dc.kInitialFEN); + + Piece? pieceToAddOnTap; + bool deleteOnTap = false; + + @override + Widget build(BuildContext context) { + final double screenWidth = MediaQuery.of(context).size.width; + + const PieceSet pieceSet = PieceSet.merida; + + final settings = BoardEditorSettings( + pieceAssets: pieceSet.assets, + colorScheme: BoardTheme.blue.colors, + enableCoordinates: true, + ); + final boardEditor = BoardEditor( + size: screenWidth, + orientation: Side.white, + pieces: pieces, + settings: settings, + onTappedSquare: (squareId) => setState(() { + if (deleteOnTap) { + pieces.remove(squareId); + } else if (pieceToAddOnTap != null) { + pieces[squareId] = pieceToAddOnTap!; + } + }), + onDiscardedPiece: (squareId) => setState(() { + pieces.remove(squareId); + }), + onDroppedPiece: (origin, destination, piece) => setState(() { + pieces[destination] = piece; + if (origin != null) { + pieces.remove(origin); + } + }), + ); + + makePieceMenu(side) => PieceMenu( + side: side, + pieceSet: pieceSet, + squareSize: boardEditor.squareSize, + settings: settings, + selectedPiece: pieceToAddOnTap, + pieceTapped: (role) => setState(() { + pieceToAddOnTap = Piece(role: role, color: side); + deleteOnTap = false; + }), + deleteSelected: deleteOnTap, + deleteTapped: () => setState(() { + pieceToAddOnTap = null; + deleteOnTap = !deleteOnTap; + }), + ); + + return Scaffold( + appBar: AppBar( + title: const Text('Board Editor'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + makePieceMenu(Side.white), + boardEditor, + makePieceMenu(Side.black), + Text('FEN: ${writeFen(pieces)}'), + ], + ), + ), + ); + } +} + +class PieceMenu extends StatelessWidget { + const PieceMenu({ + super.key, + required this.side, + required this.pieceSet, + required this.squareSize, + required this.selectedPiece, + required this.deleteSelected, + required this.settings, + required this.pieceTapped, + required this.deleteTapped, + }); + + final Side side; + final PieceSet pieceSet; + final double squareSize; + final Piece? selectedPiece; + final bool deleteSelected; + final BoardEditorSettings settings; + final Function(Role role) pieceTapped; + final Function() deleteTapped; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.grey, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ...Role.values.mapIndexed( + (i, role) { + final piece = Piece(role: role, color: side); + final pieceWidget = PieceWidget( + piece: piece, + size: squareSize, + pieceAssets: pieceSet.assets, + ); + + return Container( + color: + selectedPiece == piece ? Colors.blue : Colors.transparent, + child: GestureDetector( + onTap: () => pieceTapped(role), + child: Draggable( + data: piece, + feedback: PieceDragFeedback( + piece: piece, + pieceAssets: pieceSet.assets, + squareSize: squareSize, + ), + child: pieceWidget), + ), + ); + }, + ).toList(), + Container( + color: deleteSelected ? Colors.red : Colors.transparent, + child: GestureDetector( + onTap: () => deleteTapped(), + child: Icon( + Icons.delete, + size: squareSize, + ), + ), + ), + ], + ), + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index f638116c..1d51ca91 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:math'; +import 'package:board_example/board_editor_page.dart'; import 'package:flutter/material.dart'; import 'package:chessground/chessground.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; @@ -77,10 +78,10 @@ class _HomePageState extends State { return Scaffold( appBar: AppBar( - title: playMode == Mode.botPlay - ? const Text('Random Bot') - : const Text('Free Play'), - ), + title: switch (playMode) { + Mode.botPlay => const Text('Random Bot'), + Mode.freePlay => const Text('Free Play'), + }), drawer: Drawer( child: ListView( children: [ @@ -105,6 +106,17 @@ class _HomePageState extends State { Navigator.pop(context); }, ), + ListTile( + title: const Text('Board Editor'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const BoardEditorPage(), + ), + ); + }, + ), ListTile( title: const Text('Board Thumbnails'), onTap: () { diff --git a/example/pubspec.lock b/example/pubspec.lock index 8d33af3a..6cc4a71f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -31,7 +31,7 @@ packages: path: ".." relative: true source: path - version: "3.2.0" + version: "3.3.0" clock: dependency: transitive description: diff --git a/lib/chessground.dart b/lib/chessground.dart index 49519e3c..38c50d32 100644 --- a/lib/chessground.dart +++ b/lib/chessground.dart @@ -6,12 +6,15 @@ library chessground; export 'src/board_color_scheme.dart'; export 'src/draw_shape_options.dart'; export 'src/board_data.dart'; +export 'src/board_editor_settings.dart'; export 'src/board_settings.dart'; export 'src/fen.dart'; export 'src/models.dart'; export 'src/piece_set.dart'; export 'src/premove.dart'; export 'src/widgets/board.dart'; +export 'src/widgets/board_editor.dart'; +export 'src/widgets/drag.dart'; export 'src/widgets/highlight.dart'; export 'src/widgets/piece.dart'; export 'src/widgets/background.dart'; diff --git a/lib/src/board_editor_settings.dart b/lib/src/board_editor_settings.dart new file mode 100644 index 00000000..517ffe2b --- /dev/null +++ b/lib/src/board_editor_settings.dart @@ -0,0 +1,94 @@ +import 'package:flutter/widgets.dart'; + +import 'board_color_scheme.dart'; +import 'models.dart'; +import 'piece_set.dart'; + +/// Board editor settings that control the theme, behavior and purpose of the board editor. +/// +/// This is meant for fixed settings that don't change while editing the board. Sensible +/// defaults are provided. +@immutable +class BoardEditorSettings { + const BoardEditorSettings({ + // theme + this.colorScheme = BoardColorScheme.brown, + this.pieceAssets = PieceSet.cburnettAssets, + // visual settings + this.borderRadius = BorderRadius.zero, + this.boxShadow = const [], + this.enableCoordinates = true, + this.dragFeedbackSize = 2.0, + this.dragFeedbackOffset = const Offset(0.0, -1.0), + }); + + /// Theme of the board + final BoardColorScheme colorScheme; + + /// Piece set + final PieceAssets pieceAssets; + + /// Border radius of the board + final BorderRadiusGeometry borderRadius; + + /// Box shadow of the board + final List boxShadow; + + /// Whether to show board coordinates + final bool enableCoordinates; + + // Scale up factor for the piece currently under drag + final double dragFeedbackSize; + + // Offset for the piece currently under drag + final Offset dragFeedbackOffset; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is BoardEditorSettings && + other.colorScheme == colorScheme && + other.pieceAssets == pieceAssets && + other.borderRadius == borderRadius && + other.boxShadow == boxShadow && + other.enableCoordinates == enableCoordinates && + other.dragFeedbackSize == dragFeedbackSize && + other.dragFeedbackOffset == dragFeedbackOffset; + } + + @override + int get hashCode => Object.hash( + colorScheme, + pieceAssets, + borderRadius, + boxShadow, + enableCoordinates, + dragFeedbackSize, + dragFeedbackOffset, + ); + + BoardEditorSettings copyWith({ + BoardColorScheme? colorScheme, + PieceAssets? pieceAssets, + BorderRadiusGeometry? borderRadius, + List? boxShadow, + bool? enableCoordinates, + double? dragFeedbackSize, + Offset? dragFeedbackOffset, + }) { + return BoardEditorSettings( + colorScheme: colorScheme ?? this.colorScheme, + pieceAssets: pieceAssets ?? this.pieceAssets, + borderRadius: borderRadius ?? this.borderRadius, + boxShadow: boxShadow ?? this.boxShadow, + enableCoordinates: enableCoordinates ?? this.enableCoordinates, + dragFeedbackSize: dragFeedbackSize ?? this.dragFeedbackSize, + dragFeedbackOffset: dragFeedbackOffset ?? this.dragFeedbackOffset, + ); + } +} diff --git a/lib/src/fen.dart b/lib/src/fen.dart index a57c0403..578e050f 100644 --- a/lib/src/fen.dart +++ b/lib/src/fen.dart @@ -39,6 +39,39 @@ Pieces readFen(String fen) { return pieces; } +/// Convert the pieces to the board part of a FEN string +String writeFen(Pieces pieces) { + final buffer = StringBuffer(); + int empty = 0; + for (int rank = 7; rank >= 0; rank--) { + for (int file = 0; file < 8; file++) { + final piece = pieces[Coord(x: file, y: rank).squareId]; + if (piece == null) { + empty++; + } else { + if (empty > 0) { + buffer.write(empty.toString()); + empty = 0; + } + buffer.write( + piece.color == Side.white + ? piece.role.letter.toUpperCase() + : piece.role.letter.toLowerCase(), + ); + } + + if (file == 7) { + if (empty > 0) { + buffer.write(empty.toString()); + empty = 0; + } + if (rank != 0) buffer.write('/'); + } + } + } + return buffer.toString(); +} + const _roles = { 'p': Role.pawn, 'r': Role.rook, diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 1acca75e..66001a60 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:chessground/src/widgets/drag.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; @@ -672,7 +673,6 @@ class _BoardState extends State { void _onDragStart(PointerEvent origin) { final squareId = widget.localOffset2SquareId(origin.localPosition); final piece = squareId != null ? pieces[squareId] : null; - final feedbackSize = widget.squareSize * widget.settings.dragFeedbackSize; if (squareId != null && piece != null && (_isMovable(piece) || _isPremovable(piece))) { @@ -693,18 +693,12 @@ class _BoardState extends State { shape: BoxShape.circle, ), ), - pieceFeedback: Transform.translate( - offset: Offset( - ((widget.settings.dragFeedbackOffset.dx - 1) * feedbackSize) / 2, - ((widget.settings.dragFeedbackOffset.dy - 1) * feedbackSize) / 2, - ), - child: PieceWidget( - piece: piece, - size: feedbackSize, - pieceAssets: widget.settings.pieceAssets, - blindfoldMode: widget.settings.blindfoldMode, - upsideDown: _isUpsideDown(piece), - ), + pieceFeedback: PieceDragFeedback( + piece: piece, + squareSize: widget.squareSize, + pieceAssets: widget.settings.pieceAssets, + size: widget.settings.dragFeedbackSize, + offset: widget.settings.dragFeedbackOffset - const Offset(0.5, 0.5), ), ); } diff --git a/lib/src/widgets/board_editor.dart b/lib/src/widgets/board_editor.dart new file mode 100644 index 00000000..c95a0c68 --- /dev/null +++ b/lib/src/widgets/board_editor.dart @@ -0,0 +1,155 @@ +import 'package:chessground/chessground.dart'; +import 'package:flutter/widgets.dart'; + +import 'positioned_square.dart'; + +/// A chessboard widget where pieces can be dragged around freely (including dragging piece off and onto the board). +/// +/// This widget can be used as the basis for a fully fledged board editor, similar to https://lichess.org/editor. +class BoardEditor extends StatefulWidget { + const BoardEditor({ + super.key, + required this.size, + required this.orientation, + required this.pieces, + this.settings = const BoardEditorSettings(), + this.onTappedSquare, + this.onDroppedPiece, + this.onDiscardedPiece, + }); + + /// Visual size of the board. + final double size; + + double get squareSize => size / 8; + + /// The pieces to display on the board. + /// + /// This is read-only, it will never be modified by the board editor. + /// See [readFen] and [writeFen] for converting between [Pieces] and FEN strings. + final Pieces pieces; + + /// Settings that control the appearance of the board editor. + final BoardEditorSettings settings; + + /// Side by which the board is oriented. + final Side orientation; + + /// Called when the given [square] was tapped. + final void Function(SquareId square)? onTappedSquare; + + /// Called when a [piece] has been dragged to a new [destination] square. + /// + /// If [origin] is not `null`, the piece was dragged from that square of the board editor. + /// Otherwise, it was dragged from outside the board editor. + /// Each square of the board is a [DragTarget], so to drop your own piece widgets + /// onto the board, put them in a [Draggable] and set the data to the piece you want to drop. + final void Function(SquareId? origin, SquareId destination, Piece piece)? + onDroppedPiece; + + /// Called when a piece that was originally at the given [square] was dragged off the board. + final void Function(SquareId square)? onDiscardedPiece; + + @override + State createState() => _BoardEditorState(); +} + +class _BoardEditorState extends State { + SquareId? draggedPieceOrigin; + + @override + Widget build(BuildContext context) { + final List pieceWidgets = allSquares.map((squareId) { + final piece = widget.pieces[squareId]; + + return PositionedSquare( + key: ValueKey('$squareId-${piece?.kind.name ?? 'empty'}'), + size: widget.squareSize, + orientation: widget.orientation, + squareId: squareId, + child: GestureDetector( + onTap: () => widget.onTappedSquare?.call(squareId), + child: DragTarget( + hitTestBehavior: HitTestBehavior.opaque, + builder: (context, candidateData, rejectedData) { + return Stack( + children: [ + // Show a drop target if a piece is dragged over the square + if (candidateData.isNotEmpty) + Transform.scale( + scale: 2, + child: Container( + decoration: const BoxDecoration( + color: Color(0x33000000), + shape: BoxShape.circle, + ), + ), + ), + if (piece != null) + Draggable( + hitTestBehavior: HitTestBehavior.translucent, + data: piece, + feedback: PieceDragFeedback( + piece: piece, + squareSize: widget.squareSize, + size: widget.settings.dragFeedbackSize, + offset: widget.settings.dragFeedbackOffset, + pieceAssets: widget.settings.pieceAssets, + ), + childWhenDragging: const SizedBox.shrink(), + onDragStarted: () => draggedPieceOrigin = squareId, + onDraggableCanceled: (_, __) { + widget.onDiscardedPiece?.call(squareId); + draggedPieceOrigin = null; + }, + child: PieceWidget( + piece: piece, + size: widget.squareSize, + pieceAssets: widget.settings.pieceAssets, + ), + ), + ], + ); + }, + onAcceptWithDetails: (details) { + widget.onDroppedPiece?.call( + draggedPieceOrigin, + squareId, + details.data, + ); + draggedPieceOrigin = null; + }, + ), + ), + ); + }).toList(); + + final background = widget.settings.enableCoordinates + ? widget.orientation == Side.white + ? widget.settings.colorScheme.whiteCoordBackground + : widget.settings.colorScheme.blackCoordBackground + : widget.settings.colorScheme.background; + + return SizedBox.square( + dimension: widget.size, + child: Stack( + clipBehavior: Clip.none, + children: [ + if (widget.settings.boxShadow.isNotEmpty || + widget.settings.borderRadius != BorderRadius.zero) + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: widget.settings.borderRadius, + boxShadow: widget.settings.boxShadow, + ), + child: background, + ) + else + background, + ...pieceWidgets, + ], + ), + ); + } +} diff --git a/lib/src/widgets/drag.dart b/lib/src/widgets/drag.dart new file mode 100644 index 00000000..739c8b54 --- /dev/null +++ b/lib/src/widgets/drag.dart @@ -0,0 +1,46 @@ +import 'package:flutter/widgets.dart'; + +import 'piece.dart'; +import '../models.dart'; + +/// The [Piece] to show under the pointer when a drag is under way. +/// +/// You can use this to drag pieces onto a [BoardEditor] with the same appearance as when the pieces on the board are dragged. +class PieceDragFeedback extends StatelessWidget { + const PieceDragFeedback({ + super.key, + required this.piece, + required this.squareSize, + required this.pieceAssets, + this.size = 2, + this.offset = const Offset(0.0, -1.0), + }); + + /// The piece that is being dragged. + final Piece piece; + + /// Size of a square on the board. + final double squareSize; + + /// Size of the feedback widget in units of [squareSize]. + final double size; + + /// Offset the feedback widget from the pointer position. + final Offset offset; + + /// Piece set + final PieceAssets pieceAssets; + + @override + Widget build(BuildContext context) { + final feedbackSize = squareSize * size; + return Transform.translate( + offset: (offset - const Offset(0.5, 0.5)) * feedbackSize / 2, + child: PieceWidget( + piece: piece, + size: feedbackSize, + pieceAssets: pieceAssets, + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 4cd9c5ed..b0624ee6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: chessground description: Chess board UI developed for lichess.org. It has no chess logic inside so it can be used for chess variants. -version: 3.2.0 +version: 3.3.0 repository: https://github.com/lichess-org/flutter-chessground funding: - https://lichess.org/patron diff --git a/test/widgets/board_editor_test.dart b/test/widgets/board_editor_test.dart new file mode 100644 index 00000000..0b1d4be3 --- /dev/null +++ b/test/widgets/board_editor_test.dart @@ -0,0 +1,203 @@ +import 'package:flutter/material.dart'; +import 'package:dartchess/dartchess.dart' as dc; +import 'package:flutter_test/flutter_test.dart'; +import 'package:chessground/chessground.dart'; + +const boardSize = 200.0; +const squareSize = boardSize / 8; + +void main() { + group('BoardEditor', () { + testWidgets('empty board has no pieces', (WidgetTester tester) async { + await tester.pumpWidget(buildBoard(pieces: {})); + expect(find.byType(BoardEditor), findsOneWidget); + expect(find.byType(PieceWidget), findsNothing); + + for (final square in allSquares) { + expect(find.byKey(Key('$square-empty')), findsOneWidget); + } + }); + + testWidgets('displays pieces on the correct squares', + (WidgetTester tester) async { + await tester.pumpWidget( + buildBoard( + pieces: { + 'a1': Piece.whiteKing, + 'b2': Piece.whiteQueen, + 'c3': Piece.whiteRook, + 'd4': Piece.whiteBishop, + 'e5': Piece.whiteKnight, + 'f6': Piece.whitePawn, + 'a2': Piece.blackKing, + 'a3': Piece.blackQueen, + 'a4': Piece.blackRook, + 'a5': Piece.blackBishop, + 'a6': Piece.blackKnight, + 'a7': Piece.blackPawn, + }, + ), + ); + expect(find.byKey(const Key('a1-whiteKing')), findsOneWidget); + expect(find.byKey(const Key('b2-whiteQueen')), findsOneWidget); + expect(find.byKey(const Key('c3-whiteRook')), findsOneWidget); + expect(find.byKey(const Key('d4-whiteBishop')), findsOneWidget); + expect(find.byKey(const Key('e5-whiteKnight')), findsOneWidget); + expect(find.byKey(const Key('f6-whitePawn')), findsOneWidget); + + expect(find.byKey(const Key('a2-blackKing')), findsOneWidget); + expect(find.byKey(const Key('a3-blackQueen')), findsOneWidget); + expect(find.byKey(const Key('a4-blackRook')), findsOneWidget); + expect(find.byKey(const Key('a5-blackBishop')), findsOneWidget); + expect(find.byKey(const Key('a6-blackKnight')), findsOneWidget); + expect(find.byKey(const Key('a7-blackPawn')), findsOneWidget); + + expect(find.byType(PieceWidget), findsNWidgets(12)); + }); + + testWidgets('tapping a square triggers the onTappedSquare callback', + (WidgetTester tester) async { + for (final orientation in Side.values) { + SquareId? tappedSquare; + await tester.pumpWidget( + buildBoard( + pieces: {}, + onTappedSquare: (square) => tappedSquare = square, + orientation: orientation, + ), + ); + + await tester.tapAt(squareOffset('a1', orientation: orientation)); + expect(tappedSquare, 'a1'); + + await tester.tapAt(squareOffset('g8', orientation: orientation)); + expect(tappedSquare, 'g8'); + } + }); + + testWidgets('dragging pieces to a new square calls onDroppedPiece', + (WidgetTester tester) async { + (SquareId? origin, SquareId? destination, Piece? piece) callbackParams = + (null, null, null); + + await tester.pumpWidget( + buildBoard( + pieces: readFen(dc.kInitialFEN), + onDroppedPiece: (o, d, p) => callbackParams = (o, d, p), + ), + ); + + // Drag an empty square => nothing happens + await tester.dragFrom( + squareOffset('e4'), + const Offset(0, -(squareSize * 2)), + ); + await tester.pumpAndSettle(); + expect(callbackParams, (null, null, null)); + + // Play e2-e4 (legal move) + await tester.dragFrom( + squareOffset('e2'), + const Offset(0, -(squareSize * 2)), + ); + await tester.pumpAndSettle(); + expect(callbackParams, ('e2', 'e4', Piece.whitePawn)); + + // Capture our own piece (illegal move) + await tester.dragFrom( + squareOffset('a1'), + const Offset(squareSize, 0), + ); + expect(callbackParams, ('a1', 'b1', Piece.whiteRook)); + }); + + testWidgets('dragging a piece onto the board calls onDroppedPiece', + (WidgetTester tester) async { + (SquareId? origin, SquareId? destination, Piece? piece) callbackParams = + (null, null, null); + + await tester.pumpWidget( + MaterialApp( + home: Column( + children: [ + BoardEditor( + size: boardSize, + orientation: Side.white, + pieces: const {}, + onDroppedPiece: (o, d, p) { + callbackParams = (o, d, p); + }, + ), + Draggable( + key: const Key('new piece'), + hitTestBehavior: HitTestBehavior.translucent, + data: Piece.whitePawn, + feedback: const SizedBox.shrink(), + child: PieceWidget( + piece: Piece.whitePawn, + size: squareSize, + pieceAssets: PieceSet.merida.assets, + ), + ), + ], + ), + ), + ); + + final pieceDraggable = find.byKey(const Key('new piece')); + final pieceCenter = tester.getCenter(pieceDraggable); + + final newSquareCenter = + tester.getCenter(find.byKey(const Key('e2-empty'))); + + await tester.drag( + pieceDraggable, + newSquareCenter - pieceCenter, + ); + expect(callbackParams, (null, 'e2', Piece.whitePawn)); + }); + + testWidgets('dragging a piece off the board calls onDiscardedPiece', + (WidgetTester tester) async { + SquareId? discardedSquare; + await tester.pumpWidget( + buildBoard( + pieces: readFen(dc.kInitialFEN), + onDiscardedPiece: (square) => discardedSquare = square, + ), + ); + + await tester.dragFrom( + squareOffset('e1'), + const Offset(0, squareSize), + ); + await tester.pumpAndSettle(); + expect(discardedSquare, 'e1'); + }); + }); +} + +Widget buildBoard({ + required Pieces pieces, + Side orientation = Side.white, + void Function(SquareId square)? onTappedSquare, + void Function(SquareId? origin, SquareId destination, Piece piece)? + onDroppedPiece, + void Function(SquareId square)? onDiscardedPiece, +}) { + return MaterialApp( + home: BoardEditor( + size: boardSize, + orientation: orientation, + pieces: pieces, + onTappedSquare: onTappedSquare, + onDiscardedPiece: onDiscardedPiece, + onDroppedPiece: onDroppedPiece, + ), + ); +} + +Offset squareOffset(SquareId id, {Side orientation = Side.white}) { + final o = Coord.fromSquareId(id).offset(orientation, squareSize); + return Offset(o.dx + squareSize / 2, o.dy + squareSize / 2); +} From ad332521f04b53c28366420c8af688b4291af8c7 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Sun, 21 Jul 2024 16:40:22 +0200 Subject: [PATCH 02/17] Refactor SquareId to an extension type --- example/lib/main.dart | 47 ++++++- lib/src/models.dart | 103 +++++++++++++-- lib/src/premove.dart | 6 +- lib/src/widgets/board.dart | 32 ++--- lib/src/widgets/board_annotation.dart | 8 +- lib/src/widgets/positioned_square.dart | 2 +- lib/src/widgets/promotion.dart | 11 +- lib/src/widgets/shape.dart | 6 +- pubspec.yaml | 2 +- test/board_data_test.dart | 2 +- test/models_test.dart | 102 ++++++++------- test/premove_test.dart | 17 ++- test/widgets/board_editor_test.dart | 42 +++--- test/widgets/board_test.dart | 170 ++++++++++++++++--------- 14 files changed, 364 insertions(+), 186 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 1d51ca91..a49146fe 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -298,8 +298,7 @@ class _HomePageState extends State { ? () => setState(() { position = lastPos!; fen = position.fen; - validMoves = - dc.algebraicLegalMoves(position); + validMoves = algebraicLegalMoves(position); lastPos = null; }) : null, @@ -365,7 +364,7 @@ class _HomePageState extends State { @override void initState() { - validMoves = dc.algebraicLegalMoves(position); + validMoves = algebraicLegalMoves(position); super.initState(); } @@ -382,7 +381,7 @@ class _HomePageState extends State { position = position.playUnchecked(m); lastMove = move; fen = position.fen; - validMoves = dc.algebraicLegalMoves(position); + validMoves = algebraicLegalMoves(position); }); } @@ -414,13 +413,47 @@ class _HomePageState extends State { final mv = (allMoves..shuffle()).first; setState(() { position = position.playUnchecked(mv); - lastMove = - Move(from: dc.toAlgebraic(mv.from), to: dc.toAlgebraic(mv.to)); + lastMove = Move( + from: SquareId(dc.toAlgebraic(mv.from)), + to: SquareId(dc.toAlgebraic(mv.to))); fen = position.fen; - validMoves = dc.algebraicLegalMoves(position); + validMoves = algebraicLegalMoves(position); }); lastPos = position; } } } } + +/// Gets all the legal moves of this position in the algebraic coordinate notation. +/// +/// Includes both possible representations of castling moves (unless `chess960` is true). +IMap> algebraicLegalMoves( + dc.Position pos, { + bool isChess960 = false, +}) { + final Map> result = {}; + for (final entry in pos.legalMoves.entries) { + final dests = entry.value.squares; + if (dests.isNotEmpty) { + final from = entry.key; + final destSet = dests.map((e) => SquareId(dc.toAlgebraic(e))).toSet(); + if (!isChess960 && + from == pos.board.kingOf(pos.turn) && + dc.squareFile(entry.key) == 4) { + if (dests.contains(0)) { + destSet.add(const SquareId('c1')); + } else if (dests.contains(56)) { + destSet.add(const SquareId('c8')); + } + if (dests.contains(7)) { + destSet.add(const SquareId('g1')); + } else if (dests.contains(63)) { + destSet.add(const SquareId('g8')); + } + } + result[SquareId(dc.toAlgebraic(from))] = ISet(destSet); + } + } + return IMap(result); +} diff --git a/lib/src/models.dart b/lib/src/models.dart index b9ef7c44..e9e98dca 100644 --- a/lib/src/models.dart +++ b/lib/src/models.dart @@ -82,7 +82,90 @@ enum PieceKind { typedef PieceAssets = IMap; /// Square identifier using the algebraic coordinate notation such as e2, c3, etc. -typedef SquareId = String; +extension type const SquareId._(String value) { + const SquareId(this.value) + : assert( + value == 'a1' || + value == 'a2' || + value == 'a3' || + value == 'a4' || + value == 'a5' || + value == 'a6' || + value == 'a7' || + value == 'a8' || + value == 'b1' || + value == 'b2' || + value == 'b3' || + value == 'b4' || + value == 'b5' || + value == 'b6' || + value == 'b7' || + value == 'b8' || + value == 'c1' || + value == 'c2' || + value == 'c3' || + value == 'c4' || + value == 'c5' || + value == 'c6' || + value == 'c7' || + value == 'c8' || + value == 'd1' || + value == 'd2' || + value == 'd3' || + value == 'd4' || + value == 'd5' || + value == 'd6' || + value == 'd7' || + value == 'd8' || + value == 'e1' || + value == 'e2' || + value == 'e3' || + value == 'e4' || + value == 'e5' || + value == 'e6' || + value == 'e7' || + value == 'e8' || + value == 'f1' || + value == 'f2' || + value == 'f3' || + value == 'f4' || + value == 'f5' || + value == 'f6' || + value == 'f7' || + value == 'f8' || + value == 'g1' || + value == 'g2' || + value == 'g3' || + value == 'g4' || + value == 'g5' || + value == 'g6' || + value == 'g7' || + value == 'g8' || + value == 'h1' || + value == 'h2' || + value == 'h3' || + value == 'h4' || + value == 'h5' || + value == 'h6' || + value == 'h7' || + value == 'h8', + ); + + /// The file of the square, such as 'a', 'b', 'c', etc. + String get file => value[0]; + + /// The rank of the square, such as '1', '2', '3', etc. + String get rank => value[1]; + + /// The x-coordinate of the square on the board. + int get x => value.codeUnitAt(0) - 97; + + /// The y-coordinate of the square on the board. + int get y => value.codeUnitAt(1) - 49; + + /// The coordinate of the square on the board. + Coord get coord => Coord(x: x, y: y); +} /// Representation of the piece positions on a board. typedef Pieces = Map; @@ -102,10 +185,10 @@ const ranks = ['1', '2', '3', '4', '5', '6', '7', '8']; /// All the squares of the chessboard. /// -/// This is an immutable list of strings from 'a1' to 'h8'. +/// This is an immutable list of [SquareId] from 'a1' to 'h8'. final List allSquares = List.unmodifiable([ for (final f in files) - for (final r in ranks) '$f$r', + for (final r in ranks) SquareId('$f$r'), ]); /// All the coordinates of the chessboard. @@ -113,7 +196,7 @@ final List allSquares = List.unmodifiable([ /// This is an immutable list of [Coord] from (0, 0) to (7, 7). final List allCoords = List.unmodifiable([ for (final f in files) - for (final r in ranks) Coord.fromSquareId('$f$r'), + for (final r in ranks) SquareId('$f$r').coord, ]); /// Square highlight color or image on the chessboard. @@ -143,12 +226,10 @@ class Coord { }) : assert(x >= 0 && x <= 7), assert(y >= 0 && y <= 7); - /// Construct a [Coord] from a square identifier such as 'e2', 'c3', etc. - Coord.fromSquareId(SquareId id) - : x = id.codeUnitAt(0) - 97, - y = id.codeUnitAt(1) - 49; - + /// The x-coordinate of the coordinate. final int x; + + /// The y-coordinate of the coordinate. final int y; /// Gets the square identifier of the coordinate. @@ -278,8 +359,8 @@ class Move { }); Move.fromUci(String uci) - : from = uci.substring(0, 2), - to = uci.substring(2, 4), + : from = SquareId(uci.substring(0, 2)), + to = SquareId(uci.substring(2, 4)), promotion = uci.length > 4 ? _toRole(uci.substring(4)) : null; final SquareId from; diff --git a/lib/src/premove.dart b/lib/src/premove.dart index 7c1b6183..4ff9875d 100644 --- a/lib/src/premove.dart +++ b/lib/src/premove.dart @@ -8,7 +8,7 @@ Set premovesOf( }) { final piece = pieces[square]; if (piece == null) return {}; - final coord = Coord.fromSquareId(square); + final coord = square.coord; final r = piece.role; final mobility = (() { @@ -88,10 +88,10 @@ List _rookFilesOf(Pieces pieces, Side color) { final backrank = color == Side.white ? '1' : '8'; final List files = []; for (final entry in pieces.entries) { - if (entry.key[1] == backrank && + if (entry.key.rank == backrank && entry.value.color == color && entry.value.role == Role.rook) { - files.add(Coord.fromSquareId(entry.key).x); + files.add(entry.key.x); } } return files; diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 66001a60..61396b5d 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -55,7 +55,8 @@ class Board extends StatefulWidget { double get squareSize => size / 8; - Coord? localOffset2Coord(Offset offset) { + /// Converts a board offset to a coordinate if it is within the board bounds. + Coord? _localOffsetCoord(Offset offset) { final x = (offset.dx / squareSize).floor(); final y = (offset.dy / squareSize).floor(); final orientX = data.orientation == Side.black ? 7 - x : x; @@ -67,8 +68,9 @@ class Board extends StatefulWidget { } } - SquareId? localOffset2SquareId(Offset offset) { - final coord = localOffset2Coord(offset); + /// Converts a board offset to a square id if it is within the board bounds. + SquareId? _localOffsetSquareId(Offset offset) { + final coord = _localOffsetCoord(offset); return coord?.squareId; } @@ -79,8 +81,8 @@ class Board extends StatefulWidget { class _BoardState extends State { Pieces pieces = {}; - Map translatingPieces = {}; - Map fadingPieces = {}; + Map translatingPieces = {}; + Map fadingPieces = {}; SquareId? selected; Move? _promotionMove; Move? _lastDrop; @@ -396,14 +398,14 @@ class _BoardState extends State { final newPieces = readFen(widget.data.fen); final List newOnSquare = []; final List missingOnSquare = []; - final Set animatedOrigins = {}; + final Set animatedOrigins = {}; for (final s in allSquares) { if (s == _lastDrop?.from || s == _lastDrop?.to) { continue; } final oldP = pieces[s]; final newP = newPieces[s]; - final squareCoord = Coord.fromSquareId(s); + final squareCoord = s.coord; if (newP != null) { if (oldP != null) { if (newP != oldP) { @@ -454,7 +456,7 @@ class _BoardState extends State { /// Returns the position of the square target during drag as a global offset. Offset? _squareTargetGlobalOffset(Offset localPosition, RenderBox box) { - final coord = widget.localOffset2Coord(localPosition); + final coord = widget._localOffsetCoord(localPosition); if (coord == null) return null; final localOffset = coord.offset(widget.data.orientation, widget.squareSize); @@ -468,7 +470,7 @@ class _BoardState extends State { void _onPointerDown(PointerDownEvent details) { if (details.buttons != kPrimaryButton) return; - final squareId = widget.localOffset2SquareId(details.localPosition); + final squareId = widget._localOffsetSquareId(details.localPosition); if (squareId == null) return; final Piece? piece = pieces[squareId]; @@ -573,7 +575,7 @@ class _BoardState extends State { _drawOrigin!.pointer == details.pointer) { final distance = (details.position - _drawOrigin!.position).distance; if (distance > _kDragDistanceThreshold) { - final squareId = widget.localOffset2SquareId(details.localPosition); + final squareId = widget._localOffsetSquareId(details.localPosition); if (squareId == null) return; setState(() { _shapeAvatar = _shapeAvatar!.newDest(squareId); @@ -620,7 +622,7 @@ class _BoardState extends State { if (_dragAvatar != null && _renderBox != null) { final localPos = _renderBox!.globalToLocal(_dragAvatar!._position); - final squareId = widget.localOffset2SquareId(localPos); + final squareId = widget._localOffsetSquareId(localPos); if (squareId != null && squareId != selected) { _tryMoveOrPremoveTo(squareId, drop: true); } @@ -631,7 +633,7 @@ class _BoardState extends State { _premoveDests = null; }); } else if (selected != null) { - final squareId = widget.localOffset2SquareId(details.localPosition); + final squareId = widget._localOffsetSquareId(details.localPosition); if (squareId == selected && _shouldDeselectOnTapUp) { _shouldDeselectOnTapUp = false; setState(() { @@ -671,7 +673,7 @@ class _BoardState extends State { } void _onDragStart(PointerEvent origin) { - final squareId = widget.localOffset2SquareId(origin.localPosition); + final squareId = widget._localOffsetSquareId(origin.localPosition); final piece = squareId != null ? pieces[squareId] : null; if (squareId != null && piece != null && @@ -786,7 +788,7 @@ class _BoardState extends State { } bool _isPromoMove(Piece piece, SquareId targetSquareId) { - final rank = targetSquareId[1]; + final rank = targetSquareId.rank; return piece.role == Role.pawn && (rank == '1' || rank == '8'); } @@ -926,6 +928,6 @@ class _DragAvatar { } } -const ISet _emptyValidMoves = ISetConst({}); +const ISet _emptyValidMoves = ISetConst({}); const ISet _emptyShapes = ISetConst({}); const IMap _emptyAnnotations = IMapConst({}); diff --git a/lib/src/widgets/board_annotation.dart b/lib/src/widgets/board_annotation.dart index 3ac11d49..82f50509 100644 --- a/lib/src/widgets/board_annotation.dart +++ b/lib/src/widgets/board_annotation.dart @@ -54,12 +54,12 @@ class _BoardAnnotationState extends State { @override Widget build(BuildContext context) { - final squareOffset = Coord.fromSquareId(widget.squareId) - .offset(widget.orientation, widget.squareSize); + final squareOffset = + widget.squareId.coord.offset(widget.orientation, widget.squareSize); final size = widget.squareSize * 0.48; final onRightEdge = widget.orientation == Side.white - ? widget.squareId[0] == 'h' - : widget.squareId[0] == 'a'; + ? widget.squareId.file == 'h' + : widget.squareId.file == 'a'; final offset = squareOffset.translate( onRightEdge ? widget.squareSize - (size * 0.9) diff --git a/lib/src/widgets/positioned_square.dart b/lib/src/widgets/positioned_square.dart index c5f2fa0d..bd27a926 100644 --- a/lib/src/widgets/positioned_square.dart +++ b/lib/src/widgets/positioned_square.dart @@ -23,7 +23,7 @@ class PositionedSquare extends StatelessWidget { @override Widget build(BuildContext context) { - final offset = Coord.fromSquareId(squareId).offset(orientation, size); + final offset = squareId.coord.offset(orientation, size); return Positioned( width: size, height: size, diff --git a/lib/src/widgets/promotion.dart b/lib/src/widgets/promotion.dart index 5cbc18a4..1f8a968f 100644 --- a/lib/src/widgets/promotion.dart +++ b/lib/src/widgets/promotion.dart @@ -34,12 +34,11 @@ class PromotionSelector extends StatelessWidget { @override Widget build(BuildContext context) { - final file = squareId[0]; - final rank = squareId[1]; - final coord = (orientation == Side.white && rank == '8' || - orientation == Side.black && rank == '1') - ? Coord.fromSquareId(squareId) - : Coord.fromSquareId(file + (orientation == Side.white ? '4' : '5')); + final coord = (orientation == Side.white && squareId.rank == '8' || + orientation == Side.black && squareId.rank == '1') + ? squareId.coord + : SquareId(squareId.file + (orientation == Side.white ? '4' : '5')) + .coord; final offset = coord.offset(orientation, squareSize); return GestureDetector( diff --git a/lib/src/widgets/shape.dart b/lib/src/widgets/shape.dart index 9af3094c..b7e7150d 100644 --- a/lib/src/widgets/shape.dart +++ b/lib/src/widgets/shape.dart @@ -43,8 +43,8 @@ class ShapeWidget extends StatelessWidget { painter: _ArrowPainter( color, orientation, - Coord.fromSquareId(orig), - Coord.fromSquareId(dest), + orig.coord, + dest.coord, scale, ), ), @@ -56,7 +56,7 @@ class ShapeWidget extends StatelessWidget { painter: _CirclePainter( color, orientation, - Coord.fromSquareId(orig), + orig.coord, scale, ), ), diff --git a/pubspec.yaml b/pubspec.yaml index b0624ee6..efb5aa5c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ funding: - https://lichess.org/patron environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.3.0 <4.0.0' flutter: ">=3.10.0" dependencies: diff --git a/test/board_data_test.dart b/test/board_data_test.dart index fee4b04e..387b3514 100644 --- a/test/board_data_test.dart +++ b/test/board_data_test.dart @@ -117,7 +117,7 @@ void main() { orientation: Side.white, fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', sideToMove: Side.white, - lastMove: Move(from: 'e2', to: 'e4'), + lastMove: Move(from: SquareId('e2'), to: SquareId('e4')), ); // pass null values to non-nullable fields should not change the field diff --git a/test/models_test.dart b/test/models_test.dart index f8b5953e..9a138691 100644 --- a/test/models_test.dart +++ b/test/models_test.dart @@ -3,6 +3,15 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:chessground/chessground.dart'; void main() { + group('SquareId', () { + test('coord', () { + expect(const SquareId('a1').coord, const Coord(x: 0, y: 0)); + expect(const SquareId('a5').coord, const Coord(x: 0, y: 4)); + expect(const SquareId('e3').coord, const Coord(x: 4, y: 2)); + expect(const SquareId('h8').coord, const Coord(x: 7, y: 7)); + }); + }); + group('Coord', () { test('implements hashCode/==', () { expect(const Coord(x: 0, y: 0), const Coord(x: 0, y: 0)); @@ -17,13 +26,6 @@ void main() { ); }); - test('fromSquareId', () { - expect(Coord.fromSquareId('a1'), const Coord(x: 0, y: 0)); - expect(Coord.fromSquareId('a5'), const Coord(x: 0, y: 4)); - expect(Coord.fromSquareId('e3'), const Coord(x: 4, y: 2)); - expect(Coord.fromSquareId('h8'), const Coord(x: 7, y: 7)); - }); - test('squareId', () { expect(const Coord(x: 0, y: 0).squareId, 'a1'); expect(const Coord(x: 0, y: 4).squareId, 'a5'); @@ -37,38 +39,38 @@ void main() { expect( const Arrow( color: Color(0xFF000000), - orig: 'a1', - dest: 'a2', + orig: SquareId('a1'), + dest: SquareId('a2'), ), const Arrow( color: Color(0xFF000000), - orig: 'a1', - dest: 'a2', + orig: SquareId('a1'), + dest: SquareId('a2'), ), ); expect( const Arrow( color: Color(0xFF000000), - orig: 'a1', - dest: 'a2', + orig: SquareId('a1'), + dest: SquareId('a2'), ).hashCode, const Arrow( color: Color(0xFF000000), - orig: 'a1', - dest: 'a2', + orig: SquareId('a1'), + dest: SquareId('a2'), ).hashCode, ); expect( const Arrow( color: Color(0xFF000000), - orig: 'a1', - dest: 'a2', + orig: SquareId('a1'), + dest: SquareId('a2'), ), isNot( const Arrow( color: Color(0xFF000000), - orig: 'a1', - dest: 'a3', + orig: SquareId('a1'), + dest: SquareId('a3'), scale: 0.9, ), ), @@ -76,42 +78,48 @@ void main() { expect( const Arrow( color: Color(0xFF000000), - orig: 'a1', - dest: 'a2', + orig: SquareId('a1'), + dest: SquareId('a2'), ).hashCode, isNot( const Arrow( color: Color(0xFF000000), - orig: 'a1', - dest: 'a3', + orig: SquareId('a1'), + dest: SquareId('a3'), scale: 0.2, ).hashCode, ), ); expect( - const Circle(color: Color(0xFF000000), orig: 'a1'), - const Circle(color: Color(0xFF000000), orig: 'a1'), + const Circle(color: Color(0xFF000000), orig: SquareId('a1')), + const Circle(color: Color(0xFF000000), orig: SquareId('a1')), ); expect( - const Circle(color: Color(0xFF000000), orig: 'a1').hashCode, - const Circle(color: Color(0xFF000000), orig: 'a1').hashCode, + const Circle(color: Color(0xFF000000), orig: SquareId('a1')).hashCode, + const Circle(color: Color(0xFF000000), orig: SquareId('a1')).hashCode, ); expect( - const Circle(color: Color(0xFF000000), orig: 'a1'), - isNot(const Circle(color: Color(0xFF000000), orig: 'a1', scale: 0.1)), + const Circle(color: Color(0xFF000000), orig: SquareId('a1')), + isNot( + const Circle( + color: Color(0xFF000000), + orig: SquareId('a1'), + scale: 0.1, + ), + ), ); expect( const PieceShape( - orig: 'a1', + orig: SquareId('a1'), role: Role.knight, color: Color(0xFF000000), ), const PieceShape( - orig: 'a1', + orig: SquareId('a1'), role: Role.knight, color: Color(0xFF000000), ), @@ -119,12 +127,12 @@ void main() { expect( const PieceShape( - orig: 'a1', + orig: SquareId('a1'), role: Role.knight, color: Color(0xFF000000), ).hashCode, const PieceShape( - orig: 'a1', + orig: SquareId('a1'), role: Role.knight, color: Color(0xFF000000), ).hashCode, @@ -132,13 +140,13 @@ void main() { expect( const PieceShape( - orig: 'a1', + orig: SquareId('a1'), role: Role.knight, color: Color(0xFF000000), ), isNot( const PieceShape( - orig: 'a1', + orig: SquareId('a1'), role: Role.knight, color: Color(0xFF000000), scale: 0.9, @@ -150,53 +158,53 @@ void main() { test('copyWith', () { const arrow = Arrow( color: Color(0xFF000000), - orig: 'a1', - dest: 'a2', + orig: SquareId('a1'), + dest: SquareId('a2'), ); expect( arrow.copyWith( color: const Color(0xFF000001), - orig: 'a3', - dest: 'a4', + orig: const SquareId('a3'), + dest: const SquareId('a4'), ), const Arrow( color: Color(0xFF000001), - orig: 'a3', - dest: 'a4', + orig: SquareId('a3'), + dest: SquareId('a4'), ), ); const circle = Circle( color: Color(0xFF000000), - orig: 'a1', + orig: SquareId('a1'), ); expect( circle.copyWith( color: const Color(0xFF000001), - orig: 'a2', + orig: const SquareId('a2'), ), const Circle( color: Color(0xFF000001), - orig: 'a2', + orig: SquareId('a2'), ), ); const pieceShape = PieceShape( - orig: 'a1', + orig: SquareId('a1'), role: Role.knight, color: Color(0xFF000000), ); expect( pieceShape.copyWith( - orig: 'a2', + orig: const SquareId('a2'), role: Role.bishop, color: const Color(0xFF000001), ), const PieceShape( - orig: 'a2', + orig: SquareId('a2'), role: Role.bishop, color: Color(0xFF000001), ), diff --git a/test/premove_test.dart b/test/premove_test.dart index e297c812..e19bd9cc 100644 --- a/test/premove_test.dart +++ b/test/premove_test.dart @@ -6,25 +6,28 @@ const initialFen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'; void main() { test('pawn premoves', () { expect( - premovesOf('e2', readFen(initialFen)), + premovesOf(const SquareId('e2'), readFen(initialFen)), equals({'d3', 'e3', 'e4', 'f3'}), ); }); test('knight premoves', () { - expect(premovesOf('b1', readFen(initialFen)), equals({'a3', 'c3', 'd2'})); + expect( + premovesOf(const SquareId('b1'), readFen(initialFen)), + equals({'a3', 'c3', 'd2'}), + ); }); test('bishop premoves', () { expect( - premovesOf('c1', readFen(initialFen)), + premovesOf(const SquareId('c1'), readFen(initialFen)), equals({'a3', 'b2', 'd2', 'e3', 'f4', 'g5', 'h6'}), ); }); test('rook premoves', () { expect( - premovesOf('a1', readFen(initialFen)), + premovesOf(const SquareId('a1'), readFen(initialFen)), equals({ 'a2', 'a3', @@ -46,7 +49,7 @@ void main() { test('queen premoves', () { expect( - premovesOf('d1', readFen(initialFen)), + premovesOf(const SquareId('d1'), readFen(initialFen)), equals({ 'a1', 'b1', @@ -75,12 +78,12 @@ void main() { test('king premoves', () { expect( - premovesOf('e1', readFen(initialFen), canCastle: true), + premovesOf(const SquareId('e1'), readFen(initialFen), canCastle: true), equals({'a1', 'c1', 'd1', 'd2', 'e2', 'f2', 'f1', 'g1', 'h1'}), ); expect( - premovesOf('e1', readFen(initialFen)), + premovesOf(const SquareId('e1'), readFen(initialFen)), equals({'d1', 'd2', 'e2', 'f2', 'f1'}), ); }); diff --git a/test/widgets/board_editor_test.dart b/test/widgets/board_editor_test.dart index 0b1d4be3..c7449b63 100644 --- a/test/widgets/board_editor_test.dart +++ b/test/widgets/board_editor_test.dart @@ -23,18 +23,18 @@ void main() { await tester.pumpWidget( buildBoard( pieces: { - 'a1': Piece.whiteKing, - 'b2': Piece.whiteQueen, - 'c3': Piece.whiteRook, - 'd4': Piece.whiteBishop, - 'e5': Piece.whiteKnight, - 'f6': Piece.whitePawn, - 'a2': Piece.blackKing, - 'a3': Piece.blackQueen, - 'a4': Piece.blackRook, - 'a5': Piece.blackBishop, - 'a6': Piece.blackKnight, - 'a7': Piece.blackPawn, + const SquareId('a1'): Piece.whiteKing, + const SquareId('b2'): Piece.whiteQueen, + const SquareId('c3'): Piece.whiteRook, + const SquareId('d4'): Piece.whiteBishop, + const SquareId('e5'): Piece.whiteKnight, + const SquareId('f6'): Piece.whitePawn, + const SquareId('a2'): Piece.blackKing, + const SquareId('a3'): Piece.blackQueen, + const SquareId('a4'): Piece.blackRook, + const SquareId('a5'): Piece.blackBishop, + const SquareId('a6'): Piece.blackKnight, + const SquareId('a7'): Piece.blackPawn, }, ), ); @@ -67,10 +67,14 @@ void main() { ), ); - await tester.tapAt(squareOffset('a1', orientation: orientation)); + await tester.tapAt( + squareOffset(const SquareId('a1'), orientation: orientation), + ); expect(tappedSquare, 'a1'); - await tester.tapAt(squareOffset('g8', orientation: orientation)); + await tester.tapAt( + squareOffset(const SquareId('g8'), orientation: orientation), + ); expect(tappedSquare, 'g8'); } }); @@ -89,7 +93,7 @@ void main() { // Drag an empty square => nothing happens await tester.dragFrom( - squareOffset('e4'), + squareOffset(const SquareId('e4')), const Offset(0, -(squareSize * 2)), ); await tester.pumpAndSettle(); @@ -97,7 +101,7 @@ void main() { // Play e2-e4 (legal move) await tester.dragFrom( - squareOffset('e2'), + squareOffset(const SquareId('e2')), const Offset(0, -(squareSize * 2)), ); await tester.pumpAndSettle(); @@ -105,7 +109,7 @@ void main() { // Capture our own piece (illegal move) await tester.dragFrom( - squareOffset('a1'), + squareOffset(const SquareId('a1')), const Offset(squareSize, 0), ); expect(callbackParams, ('a1', 'b1', Piece.whiteRook)); @@ -168,7 +172,7 @@ void main() { ); await tester.dragFrom( - squareOffset('e1'), + squareOffset(const SquareId('e1')), const Offset(0, squareSize), ); await tester.pumpAndSettle(); @@ -198,6 +202,6 @@ Widget buildBoard({ } Offset squareOffset(SquareId id, {Side orientation = Side.white}) { - final o = Coord.fromSquareId(id).offset(orientation, squareSize); + final o = id.coord.offset(orientation, squareSize); return Offset(o.dx + squareSize / 2, o.dy + squareSize / 2); } diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index 6422de4a..eb317de9 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -76,7 +76,7 @@ void main() { await tester.tap(find.byKey(const Key('a1-whiteRook'))); await tester.pump(); expect(find.byKey(const Key('a1-selected')), findsOneWidget); - await tester.tapAt(squareOffset('c4')); + await tester.tapAt(squareOffset(const SquareId('c4'))); await tester.pump(); expect(find.byKey(const Key('a1-selected')), findsNothing); @@ -96,7 +96,7 @@ void main() { expect(find.byKey(const Key('e2-selected')), findsOneWidget); expect(find.byType(MoveDest), findsNWidgets(2)); - await tester.tapAt(squareOffset('e4')); + await tester.tapAt(squareOffset(const SquareId('e4'))); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); @@ -123,7 +123,7 @@ void main() { // ... but move by drag should work await tester.dragFrom( - squareOffset('e2'), + squareOffset(const SquareId('e2')), const Offset(0, -(squareSize * 2)), ); await tester.pumpAndSettle(); @@ -159,7 +159,7 @@ void main() { buildBoard(initialInteractableSide: InteractableSide.both), ); - final e2 = squareOffset('e2'); + final e2 = squareOffset(const SquareId('e2')); await tester.dragFrom(e2, const Offset(0, -(squareSize * 4))); await tester.pumpAndSettle(); expect(find.byKey(const Key('e2-whitePawn')), findsOneWidget); @@ -173,8 +173,9 @@ void main() { ); await tester.dragFrom( - squareOffset('e2'), - squareOffset('e2') + const Offset(0, -boardSize + squareSize), + squareOffset(const SquareId('e2')), + squareOffset(const SquareId('e2')) + + const Offset(0, -boardSize + squareSize), ); await tester.pumpAndSettle(); expect(find.byKey(const Key('e2-whitePawn')), findsOneWidget); @@ -187,7 +188,7 @@ void main() { buildBoard(initialInteractableSide: InteractableSide.both), ); await tester.dragFrom( - squareOffset('e2'), + squareOffset(const SquareId('e2')), const Offset(0, -(squareSize * 2)), ); await tester.pumpAndSettle(); @@ -207,7 +208,7 @@ void main() { ), ); await tester.dragFrom( - squareOffset('e2'), + squareOffset(const SquareId('e2')), const Offset(0, -(squareSize * 2)), ); await tester.pumpAndSettle(); @@ -221,7 +222,7 @@ void main() { expect(find.byType(MoveDest), findsNWidgets(2)); // ...so we can still tap to move - await tester.tapAt(squareOffset('e4')); + await tester.tapAt(squareOffset(const SquareId('e4'))); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); @@ -240,13 +241,13 @@ void main() { buildBoard(initialInteractableSide: InteractableSide.both), ); await TestAsyncUtils.guard(() async { - await tester.startGesture(squareOffset('e2')); + await tester.startGesture(squareOffset(const SquareId('e2'))); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsOneWidget); - await tester.startGesture(squareOffset('e4')); + await tester.startGesture(squareOffset(const SquareId('e4'))); await tester.pump(); @@ -267,7 +268,8 @@ void main() { // drag a piece and tap on another own square while dragging await TestAsyncUtils.guard(() async { - final dragGesture = await tester.startGesture(squareOffset('e2')); + final dragGesture = + await tester.startGesture(squareOffset(const SquareId('e2'))); await tester.pump(); // trigger a piece drag by moving the pointer by 4 pixels @@ -281,7 +283,7 @@ void main() { await tester.tap(find.byKey(const Key('d2-whitePawn'))); // finish the move as to release the piece - await dragGesture.moveTo(squareOffset('e4')); + await dragGesture.moveTo(squareOffset(const SquareId('e4'))); await dragGesture.up(); }); @@ -295,7 +297,8 @@ void main() { // drag a piece and tap on an empty square while dragging await TestAsyncUtils.guard(() async { - final dragGesture = await tester.startGesture(squareOffset('d2')); + final dragGesture = + await tester.startGesture(squareOffset(const SquareId('d2'))); await tester.pump(); // trigger a piece drag by moving the pointer by 4 pixels @@ -307,10 +310,10 @@ void main() { expect(find.byKey(const Key('d2-selected')), findsOneWidget); // tap on an empty square - await tester.tapAt(squareOffset('f5')); + await tester.tapAt(squareOffset(const SquareId('f5'))); // finish the move as to release the piece - await dragGesture.moveTo(squareOffset('d4')); + await dragGesture.moveTo(squareOffset(const SquareId('d4'))); await dragGesture.up(); }); @@ -329,7 +332,7 @@ void main() { await tester.pumpWidget( buildBoard(initialInteractableSide: InteractableSide.both), ); - final e2 = squareOffset('e2'); + final e2 = squareOffset(const SquareId('e2')); await tester.tapAt(e2); await tester.pump(); final dragFuture = tester.timedDragFrom( @@ -357,9 +360,9 @@ void main() { await tester.tap(find.byKey(const Key('f7-whitePawn'))); await tester.pump(); - await tester.tapAt(squareOffset('f8')); + await tester.tapAt(squareOffset(const SquareId('f8'))); await tester.pump(); - await tester.tapAt(squareOffset('f7')); + await tester.tapAt(squareOffset(const SquareId('f7'))); await tester.pump(); expect(find.byKey(const Key('f8-whiteKnight')), findsOneWidget); expect(find.byKey(const Key('f7-whitePawn')), findsNothing); @@ -376,7 +379,7 @@ void main() { await tester.tap(find.byKey(const Key('f7-whitePawn'))); await tester.pump(); - await tester.tapAt(squareOffset('f8')); + await tester.tapAt(squareOffset(const SquareId('f8'))); await tester.pump(); expect(find.byKey(const Key('f8-whiteQueen')), findsOneWidget); expect(find.byKey(const Key('f7-whitePawn')), findsNothing); @@ -416,7 +419,7 @@ void main() { ), ); - await tester.tapAt(squareOffset('e2')); + await tester.tapAt(squareOffset(const SquareId('e2'))); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsOneWidget); @@ -441,7 +444,7 @@ void main() { ); await TestAsyncUtils.guard(() async { - await tester.startGesture(squareOffset('e2')); + await tester.startGesture(squareOffset(const SquareId('e2'))); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsOneWidget); }); @@ -457,7 +460,7 @@ void main() { expect(find.byKey(const Key('e2-selected')), findsNothing); // board is not interactable, so the piece should not be selected - await tester.tapAt(squareOffset('e2')); + await tester.tapAt(squareOffset(const SquareId('e2'))); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsNothing); @@ -471,7 +474,7 @@ void main() { // the piece selection should work (which would not be the case if the // pointer event was not cancelled) - await tester.tapAt(squareOffset('e2')); + await tester.tapAt(squareOffset(const SquareId('e2'))); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsOneWidget); }); @@ -488,12 +491,12 @@ void main() { ), ); - await tester.tapAt(squareOffset('f1')); + await tester.tapAt(squareOffset(const SquareId('f1'))); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsOneWidget); expect(find.byType(MoveDest), findsNWidgets(7)); - await tester.tapAt(squareOffset('b4')); + await tester.tapAt(squareOffset(const SquareId('b4'))); await tester.pump(); expect(find.byKey(const Key('e4-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); @@ -509,12 +512,12 @@ void main() { ), ); - await tester.tapAt(squareOffset('f1')); + await tester.tapAt(squareOffset(const SquareId('f1'))); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsOneWidget); expect(find.byType(MoveDest), findsNWidgets(7)); - await tester.tapAt(squareOffset('f8')); + await tester.tapAt(squareOffset(const SquareId('f8'))); await tester.pump(); expect(find.byKey(const Key('e4-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); @@ -530,12 +533,12 @@ void main() { ), ); - await tester.tapAt(squareOffset('f1')); + await tester.tapAt(squareOffset(const SquareId('f1'))); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsOneWidget); expect(find.byType(MoveDest), findsNWidgets(7)); - await tester.tapAt(squareOffset('f1')); + await tester.tapAt(squareOffset(const SquareId('f1'))); await tester.pump(); expect(find.byKey(const Key('e4-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); @@ -556,7 +559,7 @@ void main() { expect(find.byKey(const Key('f5-premove')), findsOneWidget); // unset by tapping empty square - await tester.tapAt(squareOffset('c5')); + await tester.tapAt(squareOffset(const SquareId('c5'))); await tester.pump(); expect(find.byKey(const Key('e4-premove')), findsNothing); expect(find.byKey(const Key('f5-premove')), findsNothing); @@ -565,7 +568,7 @@ void main() { await makeMove(tester, 'd1', 'f3'); expect(find.byKey(const Key('d1-premove')), findsOneWidget); expect(find.byKey(const Key('f3-premove')), findsOneWidget); - await tester.tapAt(squareOffset('g8')); + await tester.tapAt(squareOffset(const SquareId('g8'))); await tester.pump(); expect(find.byKey(const Key('d1-premove')), findsNothing); expect(find.byKey(const Key('f3-premove')), findsNothing); @@ -583,13 +586,13 @@ void main() { await makeMove(tester, 'd1', 'f3'); expect(find.byKey(const Key('d1-premove')), findsOneWidget); expect(find.byKey(const Key('f3-premove')), findsOneWidget); - await tester.tapAt(squareOffset('d2')); + await tester.tapAt(squareOffset(const SquareId('d2'))); await tester.pump(); // premove is still set expect(find.byKey(const Key('d1-premove')), findsOneWidget); expect(find.byKey(const Key('f3-premove')), findsOneWidget); expect(find.byType(MoveDest), findsNWidgets(4)); - await tester.tapAt(squareOffset('d4')); + await tester.tapAt(squareOffset(const SquareId('d4'))); await tester.pump(); // premove is changed expect(find.byKey(const Key('d1-premove')), findsNothing); @@ -608,7 +611,7 @@ void main() { ); await tester.dragFrom( - squareOffset('e4'), + squareOffset(const SquareId('e4')), const Offset(0, -squareSize), ); await tester.pumpAndSettle(); @@ -669,8 +672,9 @@ void main() { await tester.pumpWidget( buildBoard( initialInteractableSide: InteractableSide.both, - initialShapes: - ISet({const Circle(orig: 'e4', color: Color(0xFF0000FF))}), + initialShapes: ISet( + {const Circle(orig: SquareId('e4'), color: Color(0xFF0000FF))}, + ), ), ); @@ -687,8 +691,8 @@ void main() { initialInteractableSide: InteractableSide.both, initialShapes: ISet({ const Arrow( - orig: 'e2', - dest: 'e4', + orig: SquareId('e2'), + dest: SquareId('e4'), color: Color(0xFF0000FF), ), }), @@ -710,7 +714,7 @@ void main() { initialInteractableSide: InteractableSide.both, initialShapes: ISet({ const PieceShape( - orig: 'e4', + orig: SquareId('e4'), role: Role.pawn, color: Color(0xFF0000FF), ), @@ -730,10 +734,12 @@ void main() { await TestAsyncUtils.guard(() async { // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset('a3')); + final pressGesture = + await tester.startGesture(squareOffset(const SquareId('a3'))); // drawing a circle with another tap - final tapGesture = await tester.startGesture(squareOffset('e4')); + final tapGesture = + await tester.startGesture(squareOffset(const SquareId('e4'))); await tapGesture.up(); await pressGesture.up(); @@ -752,10 +758,12 @@ void main() { await TestAsyncUtils.guard(() async { // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset('a3')); + final pressGesture = + await tester.startGesture(squareOffset(const SquareId('a3'))); // drawing a circle with another tap - final tapGesture = await tester.startGesture(squareOffset('e4')); + final tapGesture = + await tester.startGesture(squareOffset(const SquareId('e4'))); await tapGesture.up(); await pressGesture.up(); @@ -779,10 +787,11 @@ void main() { ); // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset('a3')); + final pressGesture = + await tester.startGesture(squareOffset(const SquareId('a3'))); await tester.dragFrom( - squareOffset('e2'), + squareOffset(const SquareId('e2')), const Offset(0, -(squareSize * 2)), ); @@ -809,10 +818,11 @@ void main() { ); // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset('a3')); + final pressGesture = + await tester.startGesture(squareOffset(const SquareId('a3'))); await tester.dragFrom( - squareOffset('e2'), + squareOffset(const SquareId('e2')), const Offset(0, -(squareSize * 2)), ); @@ -839,10 +849,12 @@ void main() { await TestAsyncUtils.guard(() async { // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset('a3')); + final pressGesture = + await tester.startGesture(squareOffset(const SquareId('a3'))); // drawing a circle with another tap - final tapGesture = await tester.startGesture(squareOffset('e4')); + final tapGesture = + await tester.startGesture(squareOffset(const SquareId('e4'))); await tapGesture.up(); await pressGesture.up(); @@ -852,10 +864,11 @@ void main() { await tester.pump(const Duration(milliseconds: 210)); // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset('a3')); + final pressGesture = + await tester.startGesture(squareOffset(const SquareId('a3'))); await tester.dragFrom( - squareOffset('e2'), + squareOffset(const SquareId('e2')), const Offset(0, -(squareSize * 2)), ); @@ -866,8 +879,8 @@ void main() { expect(find.byType(ShapeWidget), findsNWidgets(2)); - await tester.tapAt(squareOffset('a3')); - await tester.tapAt(squareOffset('a3')); + await tester.tapAt(squareOffset(const SquareId('a3'))); + await tester.tapAt(squareOffset(const SquareId('a3'))); await tester.pump(); expect(find.byType(ShapeWidget), findsNothing); @@ -884,10 +897,12 @@ void main() { await TestAsyncUtils.guard(() async { // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset('a3')); + final pressGesture = + await tester.startGesture(squareOffset(const SquareId('a3'))); // drawing a circle with another tap - final tapGesture = await tester.startGesture(squareOffset('e4')); + final tapGesture = + await tester.startGesture(squareOffset(const SquareId('e4'))); await tapGesture.up(); await pressGesture.up(); @@ -898,7 +913,7 @@ void main() { expect(find.byType(ShapeWidget), findsOneWidget); - await tester.tapAt(squareOffset('e2')); + await tester.tapAt(squareOffset(const SquareId('e2'))); await tester.pump(); expect(find.byType(ShapeWidget), findsNothing); @@ -907,9 +922,9 @@ void main() { } Future makeMove(WidgetTester tester, String from, String to) async { - await tester.tapAt(squareOffset(from)); + await tester.tapAt(squareOffset(SquareId(from))); await tester.pump(); - await tester.tapAt(squareOffset(to)); + await tester.tapAt(squareOffset(SquareId(to))); await tester.pump(); } @@ -960,7 +975,7 @@ Widget buildBoard({ isCheck: position.isCheck, sideToMove: position.turn == dc.Side.white ? Side.white : Side.black, - validMoves: dc.algebraicLegalMoves(position), + validMoves: algebraicLegalMoves(position), premove: premove, shapes: shapes, ), @@ -1003,6 +1018,39 @@ Widget buildBoard({ } Offset squareOffset(SquareId id, {Side orientation = Side.white}) { - final o = Coord.fromSquareId(id).offset(orientation, squareSize); + final o = id.coord.offset(orientation, squareSize); return Offset(o.dx + squareSize / 2, o.dy + squareSize / 2); } + +/// Gets all the legal moves of this position in the algebraic coordinate notation. +/// +/// Includes both possible representations of castling moves (unless `chess960` is true). +IMap> algebraicLegalMoves( + dc.Position pos, { + bool isChess960 = false, +}) { + final Map> result = {}; + for (final entry in pos.legalMoves.entries) { + final dests = entry.value.squares; + if (dests.isNotEmpty) { + final from = entry.key; + final destSet = dests.map((e) => SquareId(dc.toAlgebraic(e))).toSet(); + if (!isChess960 && + from == pos.board.kingOf(pos.turn) && + dc.squareFile(entry.key) == 4) { + if (dests.contains(0)) { + destSet.add(const SquareId('c1')); + } else if (dests.contains(56)) { + destSet.add(const SquareId('c8')); + } + if (dests.contains(7)) { + destSet.add(const SquareId('g1')); + } else if (dests.contains(63)) { + destSet.add(const SquareId('g8')); + } + } + result[SquareId(dc.toAlgebraic(from))] = ISet(destSet); + } + } + return IMap(result); +} From 11eb93edbd0cbebb9a5c612fb1fef1b9d3b07499 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 12:39:22 +0200 Subject: [PATCH 03/17] Refactor to use dartchess types; add legalMovesOf helper --- example/lib/board_editor_page.dart | 22 +- example/lib/board_thumbnails.dart | 3 +- example/lib/maestro.dart | 25 +- example/lib/main.dart | 65 +- example/pubspec.lock | 19 +- example/pubspec.yaml | 7 +- lib/chessground.dart | 1 + lib/src/board_color_scheme.dart | 1 + lib/src/board_data.dart | 17 +- lib/src/fen.dart | 5 +- lib/src/models.dart | 149 +---- lib/src/piece_set.dart | 865 +++++++++++++------------ lib/src/premove.dart | 2 + lib/src/utils.dart | 37 ++ lib/src/widgets/animation.dart | 1 + lib/src/widgets/background.dart | 2 +- lib/src/widgets/board.dart | 33 +- lib/src/widgets/board_annotation.dart | 1 + lib/src/widgets/board_editor.dart | 11 +- lib/src/widgets/drag.dart | 1 + lib/src/widgets/piece.dart | 1 + lib/src/widgets/positioned_square.dart | 1 + lib/src/widgets/promotion.dart | 7 +- lib/src/widgets/shape.dart | 1 + pubspec.yaml | 2 +- test/board_data_test.dart | 3 +- test/models_test.dart | 1 + test/widgets/board_editor_test.dart | 33 +- test/widgets/board_test.dart | 140 ++-- 29 files changed, 669 insertions(+), 787 deletions(-) create mode 100644 lib/src/utils.dart diff --git a/example/lib/board_editor_page.dart b/example/lib/board_editor_page.dart index 7706d85c..3a7bb8c6 100644 --- a/example/lib/board_editor_page.dart +++ b/example/lib/board_editor_page.dart @@ -14,7 +14,7 @@ class BoardEditorPage extends StatefulWidget { class _BoardEditorPageState extends State { Pieces pieces = readFen(dc.kInitialFEN); - Piece? pieceToAddOnTap; + dc.Piece? pieceToAddOnTap; bool deleteOnTap = false; @override @@ -28,9 +28,9 @@ class _BoardEditorPageState extends State { colorScheme: BoardTheme.blue.colors, enableCoordinates: true, ); - final boardEditor = BoardEditor( + final boardEditor = ChessBoardEditor( size: screenWidth, - orientation: Side.white, + orientation: dc.Side.white, pieces: pieces, settings: settings, onTappedSquare: (squareId) => setState(() { @@ -58,7 +58,7 @@ class _BoardEditorPageState extends State { settings: settings, selectedPiece: pieceToAddOnTap, pieceTapped: (role) => setState(() { - pieceToAddOnTap = Piece(role: role, color: side); + pieceToAddOnTap = dc.Piece(role: role, color: side); deleteOnTap = false; }), deleteSelected: deleteOnTap, @@ -76,9 +76,9 @@ class _BoardEditorPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - makePieceMenu(Side.white), + makePieceMenu(dc.Side.white), boardEditor, - makePieceMenu(Side.black), + makePieceMenu(dc.Side.black), Text('FEN: ${writeFen(pieces)}'), ], ), @@ -100,13 +100,13 @@ class PieceMenu extends StatelessWidget { required this.deleteTapped, }); - final Side side; + final dc.Side side; final PieceSet pieceSet; final double squareSize; - final Piece? selectedPiece; + final dc.Piece? selectedPiece; final bool deleteSelected; final BoardEditorSettings settings; - final Function(Role role) pieceTapped; + final Function(dc.Role role) pieceTapped; final Function() deleteTapped; @override @@ -116,9 +116,9 @@ class PieceMenu extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - ...Role.values.mapIndexed( + ...dc.Role.values.mapIndexed( (i, role) { - final piece = Piece(role: role, color: side); + final piece = dc.Piece(role: role, color: side); final pieceWidget = PieceWidget( piece: piece, size: squareSize, diff --git a/example/lib/board_thumbnails.dart b/example/lib/board_thumbnails.dart index a37cc7c5..47fe40ab 100644 --- a/example/lib/board_thumbnails.dart +++ b/example/lib/board_thumbnails.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart' show Side; import 'package:flutter/material.dart'; import 'package:chessground/chessground.dart'; @@ -18,7 +19,7 @@ class BoardThumbnailsPage extends StatelessWidget { children: [ for (final fen in positions) LayoutBuilder(builder: (context, constraints) { - return Board( + return ChessBoard( size: constraints.biggest.width, settings: BoardSettings( enableCoordinates: false, diff --git a/example/lib/maestro.dart b/example/lib/maestro.dart index f6580b6c..5ce8ca94 100644 --- a/example/lib/maestro.dart +++ b/example/lib/maestro.dart @@ -1,18 +1,19 @@ +import 'package:dartchess/dartchess.dart'; import 'package:flutter/widgets.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:chessground/chessground.dart' as cg; const cg.PieceAssets maestroPieceSet = IMapConst({ - cg.PieceKind.blackRook: AssetImage('lib/piece_set/maestro/bR.png'), - cg.PieceKind.blackPawn: AssetImage('lib/piece_set/maestro/bP.png'), - cg.PieceKind.blackKnight: AssetImage('lib/piece_set/maestro/bN.png'), - cg.PieceKind.blackBishop: AssetImage('lib/piece_set/maestro/bB.png'), - cg.PieceKind.blackQueen: AssetImage('lib/piece_set/maestro/bQ.png'), - cg.PieceKind.blackKing: AssetImage('lib/piece_set/maestro/bK.png'), - cg.PieceKind.whiteRook: AssetImage('lib/piece_set/maestro/wR.png'), - cg.PieceKind.whiteKnight: AssetImage('lib/piece_set/maestro/wN.png'), - cg.PieceKind.whiteBishop: AssetImage('lib/piece_set/maestro/wB.png'), - cg.PieceKind.whiteQueen: AssetImage('lib/piece_set/maestro/wQ.png'), - cg.PieceKind.whiteKing: AssetImage('lib/piece_set/maestro/wK.png'), - cg.PieceKind.whitePawn: AssetImage('lib/piece_set/maestro/wP.png'), + kBlackRookKind: AssetImage('lib/piece_set/maestro/bR.png'), + kBlackPawnKind: AssetImage('lib/piece_set/maestro/bP.png'), + kBlackKnightKind: AssetImage('lib/piece_set/maestro/bN.png'), + kBlackBishopKind: AssetImage('lib/piece_set/maestro/bB.png'), + kBlackQueenKind: AssetImage('lib/piece_set/maestro/bQ.png'), + kBlackKingKind: AssetImage('lib/piece_set/maestro/bK.png'), + kWhiteRookKind: AssetImage('lib/piece_set/maestro/wR.png'), + kWhiteKnightKind: AssetImage('lib/piece_set/maestro/wN.png'), + kWhiteBishopKind: AssetImage('lib/piece_set/maestro/wB.png'), + kWhiteQueenKind: AssetImage('lib/piece_set/maestro/wQ.png'), + kWhiteKingKind: AssetImage('lib/piece_set/maestro/wK.png'), + kWhitePawnKind: AssetImage('lib/piece_set/maestro/wP.png'), }); diff --git a/example/lib/main.dart b/example/lib/main.dart index a49146fe..71a25953 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -56,12 +56,12 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { dc.Position position = dc.Chess.initial; - Side orientation = Side.white; + dc.Side orientation = dc.Side.white; String fen = dc.kInitialBoardFEN; - Move? lastMove; - Move? premove; + BoardMove? lastMove; + BoardMove? premove; ValidMoves validMoves = IMap(const {}); - Side sideToMove = Side.white; + dc.Side sideToMove = dc.Side.white; PieceSet pieceSet = PieceSet.merida; PieceShiftMethod pieceShiftMethod = PieceShiftMethod.either; BoardTheme boardTheme = BoardTheme.blue; @@ -134,7 +134,7 @@ class _HomePageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Board( + ChessBoard( size: screenWidth, settings: BoardSettings( pieceAssets: pieceSet.assets, @@ -166,8 +166,9 @@ class _HomePageState extends State { opponentsPiecesUpsideDown: playMode == Mode.freePlay, fen: fen, lastMove: lastMove, - sideToMove: - position.turn == dc.Side.white ? Side.white : Side.black, + sideToMove: position.turn == dc.Side.white + ? dc.Side.white + : dc.Side.black, isCheck: position.isCheck, premove: premove, shapes: shapes.isNotEmpty ? shapes : null, @@ -298,7 +299,7 @@ class _HomePageState extends State { ? () => setState(() { position = lastPos!; fen = position.fen; - validMoves = algebraicLegalMoves(position); + validMoves = legalMovesOf(position); lastPos = null; }) : null, @@ -364,28 +365,29 @@ class _HomePageState extends State { @override void initState() { - validMoves = algebraicLegalMoves(position); + validMoves = legalMovesOf(position); super.initState(); } - void _onSetPremove(Move? move) { + void _onSetPremove(BoardMove? move) { setState(() { premove = move; }); } - void _onUserMoveFreePlay(Move move, {bool? isDrop, bool? isPremove}) { + void _onUserMoveFreePlay(BoardMove move, {bool? isDrop, bool? isPremove}) { lastPos = position; final m = dc.Move.fromUci(move.uci)!; setState(() { position = position.playUnchecked(m); lastMove = move; fen = position.fen; - validMoves = algebraicLegalMoves(position); + validMoves = legalMovesOf(position); }); } - void _onUserMoveAgainstBot(Move move, {bool? isDrop, bool? isPremove}) async { + void _onUserMoveAgainstBot(BoardMove move, + {bool? isDrop, bool? isPremove}) async { lastPos = position; final m = dc.Move.fromUci(move.uci)!; setState(() { @@ -413,47 +415,14 @@ class _HomePageState extends State { final mv = (allMoves..shuffle()).first; setState(() { position = position.playUnchecked(mv); - lastMove = Move( + lastMove = BoardMove( from: SquareId(dc.toAlgebraic(mv.from)), to: SquareId(dc.toAlgebraic(mv.to))); fen = position.fen; - validMoves = algebraicLegalMoves(position); + validMoves = legalMovesOf(position); }); lastPos = position; } } } } - -/// Gets all the legal moves of this position in the algebraic coordinate notation. -/// -/// Includes both possible representations of castling moves (unless `chess960` is true). -IMap> algebraicLegalMoves( - dc.Position pos, { - bool isChess960 = false, -}) { - final Map> result = {}; - for (final entry in pos.legalMoves.entries) { - final dests = entry.value.squares; - if (dests.isNotEmpty) { - final from = entry.key; - final destSet = dests.map((e) => SquareId(dc.toAlgebraic(e))).toSet(); - if (!isChess960 && - from == pos.board.kingOf(pos.turn) && - dc.squareFile(entry.key) == 4) { - if (dests.contains(0)) { - destSet.add(const SquareId('c1')); - } else if (dests.contains(56)) { - destSet.add(const SquareId('c8')); - } - if (dests.contains(7)) { - destSet.add(const SquareId('g1')); - } else if (dests.contains(63)) { - destSet.add(const SquareId('g8')); - } - } - result[SquareId(dc.toAlgebraic(from))] = ISet(destSet); - } - } - return IMap(result); -} diff --git a/example/pubspec.lock b/example/pubspec.lock index 6cc4a71f..000e69b7 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -52,19 +52,18 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" dartchess: dependency: "direct main" description: - path: "." - ref: main - resolved-ref: "394b21b842d4bd2e0d5a52b98480a9a6bcfb0c26" - url: "https://github.com/lichess-org/dartchess.git" - source: git - version: "0.6.1" + name: dartchess + sha256: "047ee9973f2546744f3d24eeee8c01d563714d3f10a73b0b0799218dc00c65c1" + url: "https://pub.dev" + source: hosted + version: "0.7.1" fake_async: dependency: transitive description: @@ -77,10 +76,10 @@ packages: dependency: transitive description: name: fast_immutable_collections - sha256: "6df5b5bb29f52644c4c653ef0ae7d26c8463f8d6551b0ac94561103ff6c5ca17" + sha256: c3c73f4f989d3302066e4ec94e6ec73b5dc872592d02194f49f1352d64126b8c url: "https://pub.dev" source: hosted - version: "10.1.1" + version: "10.2.4" flutter: dependency: "direct main" description: flutter diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 34b865f9..0a4b006f 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=3.0.0 <4.0.0" + sdk: ">=3.3.0 <4.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -36,10 +36,7 @@ dependencies: cupertino_icons: ^1.0.2 chessground: path: ../ - dartchess: - git: - url: https://github.com/lichess-org/dartchess.git - ref: main + dartchess: ^0.7.1 dev_dependencies: flutter_test: diff --git a/lib/chessground.dart b/lib/chessground.dart index 38c50d32..ff832d9b 100644 --- a/lib/chessground.dart +++ b/lib/chessground.dart @@ -12,6 +12,7 @@ export 'src/fen.dart'; export 'src/models.dart'; export 'src/piece_set.dart'; export 'src/premove.dart'; +export 'src/utils.dart'; export 'src/widgets/board.dart'; export 'src/widgets/board_editor.dart'; export 'src/widgets/drag.dart'; diff --git a/lib/src/board_color_scheme.dart b/lib/src/board_color_scheme.dart index 9fcdf6c6..68e5a5d8 100644 --- a/lib/src/board_color_scheme.dart +++ b/lib/src/board_color_scheme.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart' show Side; import 'package:flutter/widgets.dart'; import './widgets/background.dart'; import './models.dart'; diff --git a/lib/src/board_data.dart b/lib/src/board_data.dart index ee07a12d..3f9c5cee 100644 --- a/lib/src/board_data.dart +++ b/lib/src/board_data.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart' show Side; import 'package:flutter/widgets.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; @@ -14,8 +15,8 @@ abstract class BoardData { required String fen, bool opponentsPiecesUpsideDown, Side? sideToMove, - Move? premove, - Move? lastMove, + BoardMove? premove, + BoardMove? lastMove, ValidMoves? validMoves, bool? isCheck, ISet? shapes, @@ -58,10 +59,10 @@ abstract class BoardData { final String fen; /// Registered premove. Will be played right after the next opponent move. - final Move? premove; + final BoardMove? premove; /// Last move played, used to highlight corresponding squares. - final Move? lastMove; + final BoardMove? lastMove; /// Set of [Move] allowed to be played by current side to move. final ValidMoves? validMoves; @@ -113,8 +114,8 @@ abstract class BoardData { String? fen, bool? opponentsPiecesUpsideDown, Side? sideToMove, - Move? premove, - Move? lastMove, + BoardMove? premove, + BoardMove? lastMove, ValidMoves? validMoves, bool? isCheck, ISet? shapes, @@ -159,8 +160,8 @@ class _BoardData extends BoardData { fen: fen ?? this.fen, sideToMove: sideToMove == _Undefined ? this.sideToMove : sideToMove as Side?, - premove: premove == _Undefined ? this.premove : premove as Move?, - lastMove: lastMove == _Undefined ? this.lastMove : lastMove as Move?, + premove: premove == _Undefined ? this.premove : premove as BoardMove?, + lastMove: lastMove == _Undefined ? this.lastMove : lastMove as BoardMove?, validMoves: validMoves == _Undefined ? this.validMoves : validMoves as ValidMoves?, diff --git a/lib/src/fen.dart b/lib/src/fen.dart index 578e050f..f571d46f 100644 --- a/lib/src/fen.dart +++ b/lib/src/fen.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart' show Piece, Role, Side; import 'package:flutter/widgets.dart'; import 'models.dart'; @@ -55,8 +56,8 @@ String writeFen(Pieces pieces) { } buffer.write( piece.color == Side.white - ? piece.role.letter.toUpperCase() - : piece.role.letter.toLowerCase(), + ? piece.role.uppercaseLetter + : piece.role.letter, ); } diff --git a/lib/src/models.dart b/lib/src/models.dart index e9e98dca..6da787d3 100644 --- a/lib/src/models.dart +++ b/lib/src/models.dart @@ -1,81 +1,10 @@ +import 'package:dartchess/dartchess.dart' show Piece, PieceKind, Role, Side; import 'package:flutter/widgets.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -/// The chessboard side, white or black. -enum Side { - white, - black; - - Side get opposite => this == Side.white ? Side.black : Side.white; -} - /// The side that can interact with the board. enum InteractableSide { both, none, white, black } -/// Piece role, such as pawn, knight, etc. -enum Role { - king, - queen, - knight, - bishop, - rook, - pawn; - - String get letter => switch (this) { - Role.king => 'K', - Role.queen => 'Q', - Role.knight => 'N', - Role.bishop => 'B', - Role.rook => 'R', - Role.pawn => 'P', - }; -} - -/// Piece kind, such as white pawn, black knight, etc. -enum PieceKind { - whitePawn, - whiteKnight, - whiteBishop, - whiteRook, - whiteQueen, - whiteKing, - blackPawn, - blackKnight, - blackBishop, - blackRook, - blackQueen, - blackKing; - - static PieceKind fromPiece(Piece piece) { - switch (piece.role) { - case Role.pawn: - return piece.color == Side.white - ? PieceKind.whitePawn - : PieceKind.blackPawn; - case Role.knight: - return piece.color == Side.white - ? PieceKind.whiteKnight - : PieceKind.blackKnight; - case Role.bishop: - return piece.color == Side.white - ? PieceKind.whiteBishop - : PieceKind.blackBishop; - case Role.rook: - return piece.color == Side.white - ? PieceKind.whiteRook - : PieceKind.blackRook; - case Role.queen: - return piece.color == Side.white - ? PieceKind.whiteQueen - : PieceKind.blackQueen; - case Role.king: - return piece.color == Side.white - ? PieceKind.whiteKing - : PieceKind.blackKing; - } - } -} - /// Describes a set of piece assets. /// /// The [PieceAssets] must be complete with all the pieces for both sides. @@ -260,68 +189,6 @@ class Coord { int get hashCode => Object.hash(x, y); } -/// Describes a chess piece by its role and color. -/// -/// Can be promoted. -@immutable -class Piece { - const Piece({ - required this.color, - required this.role, - this.promoted = false, - }); - - final Side color; - final Role role; - final bool promoted; - - PieceKind get kind => PieceKind.fromPiece(this); - - Piece copyWith({ - Side? color, - Role? role, - bool? promoted, - }) { - return Piece( - color: color ?? this.color, - role: role ?? this.role, - promoted: promoted ?? this.promoted, - ); - } - - @override - String toString() { - return 'Piece(${kind.name})'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - other is Piece && - other.runtimeType == runtimeType && - other.color == color && - other.role == role && - other.promoted == promoted; - } - - @override - int get hashCode => Object.hash(color, role, promoted); - - static const whitePawn = Piece(color: Side.white, role: Role.pawn); - static const whiteKnight = Piece(color: Side.white, role: Role.knight); - static const whiteBishop = Piece(color: Side.white, role: Role.bishop); - static const whiteRook = Piece(color: Side.white, role: Role.rook); - static const whiteQueen = Piece(color: Side.white, role: Role.queen); - static const whiteKing = Piece(color: Side.white, role: Role.king); - - static const blackPawn = Piece(color: Side.black, role: Role.pawn); - static const blackKnight = Piece(color: Side.black, role: Role.knight); - static const blackBishop = Piece(color: Side.black, role: Role.bishop); - static const blackRook = Piece(color: Side.black, role: Role.rook); - static const blackQueen = Piece(color: Side.black, role: Role.queen); - static const blackKing = Piece(color: Side.black, role: Role.king); -} - /// A piece and its position on the board. @immutable class PositionedPiece { @@ -349,16 +216,16 @@ class PositionedPiece { } } -/// A chess move. +/// A chess move from one [SquareId] to another, with an optional promotion. @immutable -class Move { - const Move({ +class BoardMove { + const BoardMove({ required this.from, required this.to, this.promotion, }); - Move.fromUci(String uci) + BoardMove.fromUci(String uci) : from = SquareId(uci.substring(0, 2)), to = SquareId(uci.substring(2, 4)), promotion = uci.length > 4 ? _toRole(uci.substring(4)) : null; @@ -375,8 +242,8 @@ class Move { return from == squareId || to == squareId; } - Move withPromotion(Role promotion) { - return Move( + BoardMove withPromotion(Role promotion) { + return BoardMove( from: from, to: to, promotion: promotion, @@ -386,7 +253,7 @@ class Move { @override bool operator ==(Object other) { return identical(this, other) || - other is Move && + other is BoardMove && other.runtimeType == runtimeType && other.from == from && other.to == to && diff --git a/lib/src/piece_set.dart b/lib/src/piece_set.dart index 6d644b93..4f25d2fa 100644 --- a/lib/src/piece_set.dart +++ b/lib/src/piece_set.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart'; import 'package:flutter/widgets.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'models.dart'; @@ -49,920 +50,944 @@ enum PieceSet { const PieceSet(this.label, this.assets); static const PieceAssets alphaAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/alpha/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/alpha/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/alpha/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/alpha/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/alpha/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/alpha/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/alpha/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/alpha/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/alpha/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/alpha/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/alpha/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/alpha/wK.png', package: 'chessground'), }); static const PieceAssets calienteAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/caliente/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/caliente/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/caliente/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/caliente/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/caliente/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/caliente/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/caliente/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/caliente/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/caliente/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/caliente/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/caliente/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/caliente/wK.png', package: 'chessground'), }); static const PieceAssets anarcandyAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/anarcandy/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/anarcandy/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/anarcandy/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/anarcandy/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/anarcandy/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/anarcandy/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/anarcandy/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/anarcandy/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/anarcandy/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/anarcandy/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/anarcandy/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/anarcandy/wK.png', package: 'chessground'), }); static const PieceAssets californiaAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/california/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/california/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/california/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/california/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/california/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/california/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/california/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/california/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/california/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/california/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/california/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/california/wK.png', package: 'chessground'), }); static const PieceAssets cardinalAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/cardinal/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/cardinal/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/cardinal/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/cardinal/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/cardinal/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/cardinal/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/cardinal/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/cardinal/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/cardinal/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/cardinal/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/cardinal/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/cardinal/wK.png', package: 'chessground'), }); static const PieceAssets cburnettAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/cburnett/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/cburnett/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/cburnett/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/cburnett/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/cburnett/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/cburnett/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/cburnett/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/cburnett/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/cburnett/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/cburnett/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/cburnett/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/cburnett/wK.png', package: 'chessground'), }); static const PieceAssets celticAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/celtic/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/celtic/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/celtic/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/celtic/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/celtic/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/celtic/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/celtic/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/celtic/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/celtic/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/celtic/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/celtic/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/celtic/wK.png', package: 'chessground'), }); static const PieceAssets chess7Assets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/chess7/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/chess7/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/chess7/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/chess7/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/chess7/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/chess7/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/chess7/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/chess7/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/chess7/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/chess7/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/chess7/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/chess7/wK.png', package: 'chessground'), }); static const PieceAssets chessnutAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/chessnut/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/chessnut/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/chessnut/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/chessnut/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/chessnut/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/chessnut/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/chessnut/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/chessnut/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/chessnut/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/chessnut/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/chessnut/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/chessnut/wK.png', package: 'chessground'), }); static const PieceAssets companionAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/companion/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/companion/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/companion/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/companion/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/companion/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/companion/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/companion/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/companion/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/companion/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/companion/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/companion/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/companion/wK.png', package: 'chessground'), }); static const PieceAssets disguisedAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/disguised/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/disguised/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/disguised/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/disguised/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/disguised/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/disguised/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/disguised/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/disguised/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/disguised/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/disguised/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/disguised/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/disguised/wK.png', package: 'chessground'), }); static const PieceAssets dubrovnyAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/dubrovny/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/dubrovny/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/dubrovny/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/dubrovny/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/dubrovny/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/dubrovny/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/dubrovny/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/dubrovny/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/dubrovny/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/dubrovny/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/dubrovny/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/dubrovny/wK.png', package: 'chessground'), }); static const PieceAssets fantasyAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/fantasy/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/fantasy/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/fantasy/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/fantasy/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/fantasy/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/fantasy/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/fantasy/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/fantasy/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/fantasy/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/fantasy/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/fantasy/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/fantasy/wK.png', package: 'chessground'), }); static const PieceAssets frescaAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/fresca/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/fresca/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/fresca/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/fresca/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/fresca/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/fresca/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/fresca/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/fresca/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/fresca/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/fresca/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/fresca/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/fresca/wK.png', package: 'chessground'), }); static const PieceAssets giocoAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/gioco/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/gioco/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/gioco/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/gioco/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/gioco/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/gioco/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/gioco/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/gioco/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/gioco/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/gioco/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/gioco/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/gioco/wK.png', package: 'chessground'), }); static const PieceAssets governorAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/governor/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/governor/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/governor/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/governor/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/governor/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/governor/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/governor/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/governor/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/governor/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/governor/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/governor/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/governor/wK.png', package: 'chessground'), }); static const PieceAssets horseyAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/horsey/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/horsey/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/horsey/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/horsey/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/horsey/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/horsey/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/horsey/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/horsey/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/horsey/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/horsey/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/horsey/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/horsey/wK.png', package: 'chessground'), }); static const PieceAssets icpiecesAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/icpieces/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/icpieces/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/icpieces/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/icpieces/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/icpieces/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/icpieces/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/icpieces/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/icpieces/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/icpieces/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/icpieces/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/icpieces/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/icpieces/wK.png', package: 'chessground'), }); static const PieceAssets kiwenSuwiAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/kiwen-suwi/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/kiwen-suwi/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/kiwen-suwi/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/kiwen-suwi/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/kiwen-suwi/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/kiwen-suwi/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/kiwen-suwi/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/kiwen-suwi/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/kiwen-suwi/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/kiwen-suwi/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/kiwen-suwi/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/kiwen-suwi/wK.png', package: 'chessground'), }); static const PieceAssets kosalAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/kosal/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/kosal/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/kosal/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/kosal/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/kosal/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/kosal/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/kosal/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/kosal/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/kosal/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/kosal/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/kosal/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/kosal/wK.png', package: 'chessground'), }); static const PieceAssets leipzigAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/leipzig/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/leipzig/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/leipzig/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/leipzig/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/leipzig/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/leipzig/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/leipzig/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/leipzig/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/leipzig/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/leipzig/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/leipzig/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/leipzig/wK.png', package: 'chessground'), }); static const PieceAssets letterAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/letter/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/letter/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/letter/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/letter/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/letter/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/letter/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/letter/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/letter/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/letter/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/letter/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/letter/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/letter/wK.png', package: 'chessground'), }); static const PieceAssets libraAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/libra/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/libra/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/libra/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/libra/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/libra/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/libra/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/libra/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/libra/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/libra/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/libra/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/libra/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/libra/wK.png', package: 'chessground'), }); static const PieceAssets maestroAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/maestro/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/maestro/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/maestro/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/maestro/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/maestro/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/maestro/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/maestro/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/maestro/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/maestro/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/maestro/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/maestro/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/maestro/wK.png', package: 'chessground'), }); static const PieceAssets meridaAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/merida/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/merida/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/merida/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/merida/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/merida/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/merida/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/merida/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/merida/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/merida/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/merida/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/merida/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/merida/wK.png', package: 'chessground'), }); static const PieceAssets pirouettiAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/pirouetti/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/pirouetti/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/pirouetti/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/pirouetti/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/pirouetti/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/pirouetti/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/pirouetti/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/pirouetti/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/pirouetti/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/pirouetti/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/pirouetti/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/pirouetti/wK.png', package: 'chessground'), }); static const PieceAssets mpchessAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/mpchess/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/mpchess/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/mpchess/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/mpchess/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/mpchess/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/mpchess/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/mpchess/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/mpchess/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/mpchess/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/mpchess/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/mpchess/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/mpchess/wK.png', package: 'chessground'), }); static const PieceAssets pixelAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/pixel/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/pixel/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/pixel/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/pixel/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/pixel/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/pixel/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/pixel/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/pixel/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/pixel/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/pixel/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/pixel/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/pixel/wK.png', package: 'chessground'), }); static const PieceAssets reillycraigAssets = IMapConst({ - PieceKind.blackRook: - AssetImage('$_pieceSetsPath/reillycraig/bR.png', package: 'chessground'), - PieceKind.blackPawn: - AssetImage('$_pieceSetsPath/reillycraig/bP.png', package: 'chessground'), - PieceKind.blackKnight: - AssetImage('$_pieceSetsPath/reillycraig/bN.png', package: 'chessground'), - PieceKind.blackBishop: - AssetImage('$_pieceSetsPath/reillycraig/bB.png', package: 'chessground'), - PieceKind.blackQueen: - AssetImage('$_pieceSetsPath/reillycraig/bQ.png', package: 'chessground'), - PieceKind.blackKing: - AssetImage('$_pieceSetsPath/reillycraig/bK.png', package: 'chessground'), - PieceKind.whiteRook: - AssetImage('$_pieceSetsPath/reillycraig/wR.png', package: 'chessground'), - PieceKind.whitePawn: - AssetImage('$_pieceSetsPath/reillycraig/wP.png', package: 'chessground'), - PieceKind.whiteKnight: - AssetImage('$_pieceSetsPath/reillycraig/wN.png', package: 'chessground'), - PieceKind.whiteBishop: - AssetImage('$_pieceSetsPath/reillycraig/wB.png', package: 'chessground'), - PieceKind.whiteQueen: - AssetImage('$_pieceSetsPath/reillycraig/wQ.png', package: 'chessground'), - PieceKind.whiteKing: - AssetImage('$_pieceSetsPath/reillycraig/wK.png', package: 'chessground'), + kBlackRookKind: AssetImage( + '$_pieceSetsPath/reillycraig/bR.png', + package: 'chessground', + ), + kBlackPawnKind: AssetImage( + '$_pieceSetsPath/reillycraig/bP.png', + package: 'chessground', + ), + kBlackKnightKind: AssetImage( + '$_pieceSetsPath/reillycraig/bN.png', + package: 'chessground', + ), + kBlackBishopKind: AssetImage( + '$_pieceSetsPath/reillycraig/bB.png', + package: 'chessground', + ), + kBlackQueenKind: AssetImage( + '$_pieceSetsPath/reillycraig/bQ.png', + package: 'chessground', + ), + kBlackKingKind: AssetImage( + '$_pieceSetsPath/reillycraig/bK.png', + package: 'chessground', + ), + kWhiteRookKind: AssetImage( + '$_pieceSetsPath/reillycraig/wR.png', + package: 'chessground', + ), + kWhitePawnKind: AssetImage( + '$_pieceSetsPath/reillycraig/wP.png', + package: 'chessground', + ), + kWhiteKnightKind: AssetImage( + '$_pieceSetsPath/reillycraig/wN.png', + package: 'chessground', + ), + kWhiteBishopKind: AssetImage( + '$_pieceSetsPath/reillycraig/wB.png', + package: 'chessground', + ), + kWhiteQueenKind: AssetImage( + '$_pieceSetsPath/reillycraig/wQ.png', + package: 'chessground', + ), + kWhiteKingKind: AssetImage( + '$_pieceSetsPath/reillycraig/wK.png', + package: 'chessground', + ), }); static const PieceAssets riohachaAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/riohacha/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/riohacha/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/riohacha/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/riohacha/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/riohacha/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/riohacha/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/riohacha/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/riohacha/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/riohacha/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/riohacha/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/riohacha/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/riohacha/wK.png', package: 'chessground'), }); static const PieceAssets shapesAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/shapes/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/shapes/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/shapes/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/shapes/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/shapes/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/shapes/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/shapes/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/shapes/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/shapes/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/shapes/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/shapes/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/shapes/wK.png', package: 'chessground'), }); static const PieceAssets spatialAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/spatial/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/spatial/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/spatial/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/spatial/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/spatial/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/spatial/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/spatial/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/spatial/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/spatial/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/spatial/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/spatial/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/spatial/wK.png', package: 'chessground'), }); static const PieceAssets stauntyAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/staunty/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/staunty/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/staunty/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/staunty/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/staunty/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/staunty/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/staunty/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/staunty/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/staunty/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/staunty/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/staunty/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/staunty/wK.png', package: 'chessground'), }); static const PieceAssets tatianaAssets = IMapConst({ - PieceKind.blackRook: + kBlackRookKind: AssetImage('$_pieceSetsPath/tatiana/bR.png', package: 'chessground'), - PieceKind.blackPawn: + kBlackPawnKind: AssetImage('$_pieceSetsPath/tatiana/bP.png', package: 'chessground'), - PieceKind.blackKnight: + kBlackKnightKind: AssetImage('$_pieceSetsPath/tatiana/bN.png', package: 'chessground'), - PieceKind.blackBishop: + kBlackBishopKind: AssetImage('$_pieceSetsPath/tatiana/bB.png', package: 'chessground'), - PieceKind.blackQueen: + kBlackQueenKind: AssetImage('$_pieceSetsPath/tatiana/bQ.png', package: 'chessground'), - PieceKind.blackKing: + kBlackKingKind: AssetImage('$_pieceSetsPath/tatiana/bK.png', package: 'chessground'), - PieceKind.whiteRook: + kWhiteRookKind: AssetImage('$_pieceSetsPath/tatiana/wR.png', package: 'chessground'), - PieceKind.whitePawn: + kWhitePawnKind: AssetImage('$_pieceSetsPath/tatiana/wP.png', package: 'chessground'), - PieceKind.whiteKnight: + kWhiteKnightKind: AssetImage('$_pieceSetsPath/tatiana/wN.png', package: 'chessground'), - PieceKind.whiteBishop: + kWhiteBishopKind: AssetImage('$_pieceSetsPath/tatiana/wB.png', package: 'chessground'), - PieceKind.whiteQueen: + kWhiteQueenKind: AssetImage('$_pieceSetsPath/tatiana/wQ.png', package: 'chessground'), - PieceKind.whiteKing: + kWhiteKingKind: AssetImage('$_pieceSetsPath/tatiana/wK.png', package: 'chessground'), }); } diff --git a/lib/src/premove.dart b/lib/src/premove.dart index 4ff9875d..ba8da480 100644 --- a/lib/src/premove.dart +++ b/lib/src/premove.dart @@ -1,3 +1,5 @@ +import 'package:dartchess/dartchess.dart' show Role, Side; + import './models.dart'; /// Returns the set of squares that the piece on [square] can potentially premove to. diff --git a/lib/src/utils.dart b/lib/src/utils.dart new file mode 100644 index 00000000..de26a303 --- /dev/null +++ b/lib/src/utils.dart @@ -0,0 +1,37 @@ +import 'package:dartchess/dartchess.dart'; +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; + +import 'models.dart'; + +/// Gets all the legal moves of the [Position] in the algebraic coordinate notation. +/// +/// Includes both possible representations of castling moves (unless `chess960` is true). +IMap> legalMovesOf( + Position pos, { + bool isChess960 = false, +}) { + final Map> result = {}; + for (final entry in pos.legalMoves.entries) { + final dests = entry.value.squares; + if (dests.isNotEmpty) { + final from = entry.key; + final destSet = dests.map((e) => SquareId(toAlgebraic(e))).toSet(); + if (!isChess960 && + from == pos.board.kingOf(pos.turn) && + squareFile(entry.key) == 4) { + if (dests.contains(0)) { + destSet.add(const SquareId('c1')); + } else if (dests.contains(56)) { + destSet.add(const SquareId('c8')); + } + if (dests.contains(7)) { + destSet.add(const SquareId('g1')); + } else if (dests.contains(63)) { + destSet.add(const SquareId('g8')); + } + } + result[SquareId(toAlgebraic(from))] = ISet(destSet); + } + } + return IMap(result); +} diff --git a/lib/src/widgets/animation.dart b/lib/src/widgets/animation.dart index cdbb4822..5f91357f 100644 --- a/lib/src/widgets/animation.dart +++ b/lib/src/widgets/animation.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart' show Piece, Side; import 'package:flutter/widgets.dart'; import '../models.dart'; import './piece.dart'; diff --git a/lib/src/widgets/background.dart b/lib/src/widgets/background.dart index 274c43d3..fc225c6f 100644 --- a/lib/src/widgets/background.dart +++ b/lib/src/widgets/background.dart @@ -1,5 +1,5 @@ +import 'package:dartchess/dartchess.dart' show Side; import 'package:flutter/widgets.dart'; -import '../models.dart'; /// Base widget for the background of the chessboard. /// diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 61396b5d..8789b565 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:chessground/src/widgets/drag.dart'; +import 'package:dartchess/dartchess.dart' show Piece, Role, Side; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; @@ -26,8 +27,8 @@ const _kCancelShapesDoubleTapDelay = Duration(milliseconds: 200); /// /// This widget can be used to display a static board, a dynamic board that /// shows a live game, or a full user interactable board. -class Board extends StatefulWidget { - const Board({ +class ChessBoard extends StatefulWidget { + const ChessBoard({ super.key, required this.size, required this.data, @@ -46,12 +47,12 @@ class Board extends StatefulWidget { final BoardData data; /// Callback called after a move has been made. - final void Function(Move, {bool? isDrop, bool? isPremove})? onMove; + final void Function(BoardMove, {bool? isDrop, bool? isPremove})? onMove; /// Callback called after a premove has been set/unset. /// /// If the callback is null, the board will not allow premoves. - final void Function(Move?)? onPremove; + final void Function(BoardMove?)? onPremove; double get squareSize => size / 8; @@ -79,13 +80,13 @@ class Board extends StatefulWidget { _BoardState createState() => _BoardState(); } -class _BoardState extends State { +class _BoardState extends State { Pieces pieces = {}; Map translatingPieces = {}; Map fadingPieces = {}; SquareId? selected; - Move? _promotionMove; - Move? _lastDrop; + BoardMove? _promotionMove; + BoardMove? _lastDrop; Set? _premoveDests; bool _shouldDeselectOnTapUp = false; @@ -224,7 +225,7 @@ class _BoardState extends State { final List objects = [ for (final entry in fadingPieces.entries) PositionedSquare( - key: ValueKey('${entry.key}-${entry.value.kind.name}-fading'), + key: ValueKey('${entry.key}-${entry.value}-fading'), size: widget.squareSize, orientation: widget.data.orientation, squareId: entry.key, @@ -244,7 +245,7 @@ class _BoardState extends State { if (!translatingPieces.containsKey(entry.key) && entry.key != _draggedPieceSquareId) PositionedSquare( - key: ValueKey('${entry.key}-${entry.value.kind.name}'), + key: ValueKey('${entry.key}-${entry.value}'), size: widget.squareSize, orientation: widget.data.orientation, squareId: entry.key, @@ -258,7 +259,7 @@ class _BoardState extends State { ), for (final entry in translatingPieces.entries) PositionedSquare( - key: ValueKey('${entry.key}-${entry.value.$1.piece.kind.name}'), + key: ValueKey('${entry.key}-${entry.value.$1.piece}'), size: widget.squareSize, orientation: widget.data.orientation, squareId: entry.key, @@ -361,7 +362,7 @@ class _BoardState extends State { } @override - void didUpdateWidget(Board oldBoard) { + void didUpdateWidget(ChessBoard oldBoard) { super.didUpdateWidget(oldBoard); if (oldBoard.settings.drawShape.enable && !widget.settings.drawShape.enable) { @@ -725,7 +726,7 @@ class _BoardState extends State { _shouldDeselectOnTapUp = false; } - void _onPromotionSelect(Move move, Piece promoted) { + void _onPromotionSelect(BoardMove move, Piece promoted) { setState(() { pieces[move.to] = promoted; _promotionMove = null; @@ -733,14 +734,14 @@ class _BoardState extends State { widget.onMove?.call(move.withPromotion(promoted.role), isDrop: true); } - void _onPromotionCancel(Move move) { + void _onPromotionCancel(BoardMove move) { setState(() { pieces = readFen(widget.data.fen); _promotionMove = null; }); } - void _openPromotionSelector(Move move) { + void _openPromotionSelector(BoardMove move) { setState(() { final pawn = pieces.remove(move.from); pieces[move.to] = pawn!; @@ -798,7 +799,7 @@ class _BoardState extends State { bool _tryMoveOrPremoveTo(SquareId squareId, {bool drop = false}) { final selectedPiece = selected != null ? pieces[selected] : null; if (selectedPiece != null && _canMoveTo(selected!, squareId)) { - final move = Move(from: selected!, to: squareId); + final move = BoardMove(from: selected!, to: squareId); if (drop) { _lastDrop = move; } @@ -814,7 +815,7 @@ class _BoardState extends State { return true; } else if (_isPremovable(selectedPiece) && _canPremoveTo(selected!, squareId)) { - widget.onPremove?.call(Move(from: selected!, to: squareId)); + widget.onPremove?.call(BoardMove(from: selected!, to: squareId)); return true; } return false; diff --git a/lib/src/widgets/board_annotation.dart b/lib/src/widgets/board_annotation.dart index 82f50509..0f9bb5ea 100644 --- a/lib/src/widgets/board_annotation.dart +++ b/lib/src/widgets/board_annotation.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:dartchess/dartchess.dart' show Side; import 'package:flutter/widgets.dart'; import '../models.dart'; diff --git a/lib/src/widgets/board_editor.dart b/lib/src/widgets/board_editor.dart index c95a0c68..96b751f0 100644 --- a/lib/src/widgets/board_editor.dart +++ b/lib/src/widgets/board_editor.dart @@ -1,4 +1,5 @@ import 'package:chessground/chessground.dart'; +import 'package:dartchess/dartchess.dart' show Piece, Side; import 'package:flutter/widgets.dart'; import 'positioned_square.dart'; @@ -6,8 +7,8 @@ import 'positioned_square.dart'; /// A chessboard widget where pieces can be dragged around freely (including dragging piece off and onto the board). /// /// This widget can be used as the basis for a fully fledged board editor, similar to https://lichess.org/editor. -class BoardEditor extends StatefulWidget { - const BoardEditor({ +class ChessBoardEditor extends StatefulWidget { + const ChessBoardEditor({ super.key, required this.size, required this.orientation, @@ -51,10 +52,10 @@ class BoardEditor extends StatefulWidget { final void Function(SquareId square)? onDiscardedPiece; @override - State createState() => _BoardEditorState(); + State createState() => _BoardEditorState(); } -class _BoardEditorState extends State { +class _BoardEditorState extends State { SquareId? draggedPieceOrigin; @override @@ -63,7 +64,7 @@ class _BoardEditorState extends State { final piece = widget.pieces[squareId]; return PositionedSquare( - key: ValueKey('$squareId-${piece?.kind.name ?? 'empty'}'), + key: ValueKey('$squareId-${piece ?? 'empty'}'), size: widget.squareSize, orientation: widget.orientation, squareId: squareId, diff --git a/lib/src/widgets/drag.dart b/lib/src/widgets/drag.dart index 739c8b54..ff90c551 100644 --- a/lib/src/widgets/drag.dart +++ b/lib/src/widgets/drag.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart' show Piece; import 'package:flutter/widgets.dart'; import 'piece.dart'; diff --git a/lib/src/widgets/piece.dart b/lib/src/widgets/piece.dart index cd7882b6..77cf83e7 100644 --- a/lib/src/widgets/piece.dart +++ b/lib/src/widgets/piece.dart @@ -1,4 +1,5 @@ import 'dart:math' as math; +import 'package:dartchess/dartchess.dart' show Piece; import 'package:flutter/widgets.dart'; import '../models.dart'; diff --git a/lib/src/widgets/positioned_square.dart b/lib/src/widgets/positioned_square.dart index bd27a926..d443ccff 100644 --- a/lib/src/widgets/positioned_square.dart +++ b/lib/src/widgets/positioned_square.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart' show Side; import 'package:flutter/widgets.dart'; import '../models.dart'; import './highlight.dart'; diff --git a/lib/src/widgets/promotion.dart b/lib/src/widgets/promotion.dart index 1f8a968f..ce2ae269 100644 --- a/lib/src/widgets/promotion.dart +++ b/lib/src/widgets/promotion.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart' show Piece, Role, Side; import 'package:flutter/widgets.dart'; import '../models.dart'; import 'piece.dart'; @@ -22,13 +23,13 @@ class PromotionSelector extends StatelessWidget { }); final PieceAssets pieceAssets; - final Move move; + final BoardMove move; final Side color; final double squareSize; final Side orientation; final bool piecesUpsideDown; - final void Function(Move, Piece) onSelect; - final void Function(Move) onCancel; + final void Function(BoardMove, Piece) onSelect; + final void Function(BoardMove) onCancel; SquareId get squareId => move.to; diff --git a/lib/src/widgets/shape.dart b/lib/src/widgets/shape.dart index b7e7150d..f8147f41 100644 --- a/lib/src/widgets/shape.dart +++ b/lib/src/widgets/shape.dart @@ -1,4 +1,5 @@ import 'dart:math' as math; +import 'package:dartchess/dartchess.dart' show Side; import 'package:flutter/widgets.dart'; import '../models.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index efb5aa5c..bb87e8e9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,12 +10,12 @@ environment: flutter: ">=3.10.0" dependencies: + dartchess: ^0.7.1 fast_immutable_collections: ^10.0.0 flutter: sdk: flutter dev_dependencies: - dartchess: ^0.6.1 flutter_test: sdk: flutter lint: ^2.0.1 diff --git a/test/board_data_test.dart b/test/board_data_test.dart index 387b3514..3d198613 100644 --- a/test/board_data_test.dart +++ b/test/board_data_test.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart' hide Move, Piece; import 'package:flutter_test/flutter_test.dart'; import 'package:chessground/chessground.dart'; @@ -117,7 +118,7 @@ void main() { orientation: Side.white, fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', sideToMove: Side.white, - lastMove: Move(from: SquareId('e2'), to: SquareId('e4')), + lastMove: BoardMove(from: SquareId('e2'), to: SquareId('e4')), ); // pass null values to non-nullable fields should not change the field diff --git a/test/models_test.dart b/test/models_test.dart index 9a138691..87ae3a30 100644 --- a/test/models_test.dart +++ b/test/models_test.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:chessground/chessground.dart'; diff --git a/test/widgets/board_editor_test.dart b/test/widgets/board_editor_test.dart index c7449b63..b49403ee 100644 --- a/test/widgets/board_editor_test.dart +++ b/test/widgets/board_editor_test.dart @@ -1,3 +1,4 @@ +import 'package:dartchess/dartchess.dart' show Piece, Side; import 'package:flutter/material.dart'; import 'package:dartchess/dartchess.dart' as dc; import 'package:flutter_test/flutter_test.dart'; @@ -10,7 +11,7 @@ void main() { group('BoardEditor', () { testWidgets('empty board has no pieces', (WidgetTester tester) async { await tester.pumpWidget(buildBoard(pieces: {})); - expect(find.byType(BoardEditor), findsOneWidget); + expect(find.byType(ChessBoardEditor), findsOneWidget); expect(find.byType(PieceWidget), findsNothing); for (final square in allSquares) { @@ -38,19 +39,19 @@ void main() { }, ), ); - expect(find.byKey(const Key('a1-whiteKing')), findsOneWidget); - expect(find.byKey(const Key('b2-whiteQueen')), findsOneWidget); - expect(find.byKey(const Key('c3-whiteRook')), findsOneWidget); - expect(find.byKey(const Key('d4-whiteBishop')), findsOneWidget); - expect(find.byKey(const Key('e5-whiteKnight')), findsOneWidget); - expect(find.byKey(const Key('f6-whitePawn')), findsOneWidget); - - expect(find.byKey(const Key('a2-blackKing')), findsOneWidget); - expect(find.byKey(const Key('a3-blackQueen')), findsOneWidget); - expect(find.byKey(const Key('a4-blackRook')), findsOneWidget); - expect(find.byKey(const Key('a5-blackBishop')), findsOneWidget); - expect(find.byKey(const Key('a6-blackKnight')), findsOneWidget); - expect(find.byKey(const Key('a7-blackPawn')), findsOneWidget); + expect(find.byKey(const Key('a1-whiteking')), findsOneWidget); + expect(find.byKey(const Key('b2-whitequeen')), findsOneWidget); + expect(find.byKey(const Key('c3-whiterook')), findsOneWidget); + expect(find.byKey(const Key('d4-whitebishop')), findsOneWidget); + expect(find.byKey(const Key('e5-whiteknight')), findsOneWidget); + expect(find.byKey(const Key('f6-whitepawn')), findsOneWidget); + + expect(find.byKey(const Key('a2-blackking')), findsOneWidget); + expect(find.byKey(const Key('a3-blackqueen')), findsOneWidget); + expect(find.byKey(const Key('a4-blackrook')), findsOneWidget); + expect(find.byKey(const Key('a5-blackbishop')), findsOneWidget); + expect(find.byKey(const Key('a6-blackknight')), findsOneWidget); + expect(find.byKey(const Key('a7-blackpawn')), findsOneWidget); expect(find.byType(PieceWidget), findsNWidgets(12)); }); @@ -124,7 +125,7 @@ void main() { MaterialApp( home: Column( children: [ - BoardEditor( + ChessBoardEditor( size: boardSize, orientation: Side.white, pieces: const {}, @@ -190,7 +191,7 @@ Widget buildBoard({ void Function(SquareId square)? onDiscardedPiece, }) { return MaterialApp( - home: BoardEditor( + home: ChessBoardEditor( size: boardSize, orientation: orientation, pieces: pieces, diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index eb317de9..e8369599 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -3,6 +3,7 @@ import 'package:chessground/src/widgets/shape.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:dartchess/dartchess.dart' show Role, Side; import 'package:dartchess/dartchess.dart' as dc; import 'package:chessground/chessground.dart'; @@ -13,7 +14,7 @@ void main() { group('Non-interactable board', () { const viewOnlyBoard = Directionality( textDirection: TextDirection.ltr, - child: Board( + child: ChessBoard( size: boardSize, data: BoardData( interactableSide: InteractableSide.none, @@ -29,13 +30,13 @@ void main() { testWidgets('initial position display', (WidgetTester tester) async { await tester.pumpWidget(viewOnlyBoard); - expect(find.byType(Board), findsOneWidget); + expect(find.byType(ChessBoard), findsOneWidget); expect(find.byType(PieceWidget), findsNWidgets(32)); }); testWidgets('cannot select piece', (WidgetTester tester) async { await tester.pumpWidget(viewOnlyBoard); - await tester.tap(find.byKey(const Key('e2-whitePawn'))); + await tester.tap(find.byKey(const Key('e2-whitepawn'))); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsNothing); @@ -48,32 +49,32 @@ void main() { await tester.pumpWidget( buildBoard(initialInteractableSide: InteractableSide.both), ); - await tester.tap(find.byKey(const Key('a2-whitePawn'))); + await tester.tap(find.byKey(const Key('a2-whitepawn'))); await tester.pump(); expect(find.byKey(const Key('a2-selected')), findsOneWidget); expect(find.byType(MoveDest), findsNWidgets(2)); // selecting same deselects - await tester.tap(find.byKey(const Key('a2-whitePawn'))); + await tester.tap(find.byKey(const Key('a2-whitepawn'))); await tester.pump(); expect(find.byKey(const Key('a2-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); // selecting another square - await tester.tap(find.byKey(const Key('a1-whiteRook'))); + await tester.tap(find.byKey(const Key('a1-whiterook'))); await tester.pump(); expect(find.byKey(const Key('a1-selected')), findsOneWidget); expect(find.byType(MoveDest), findsNothing); // selecting an opposite piece deselects - await tester.tap(find.byKey(const Key('e7-blackPawn'))); + await tester.tap(find.byKey(const Key('e7-blackpawn'))); await tester.pump(); expect(find.byKey(const Key('a1-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); // selecting an empty square deselects - await tester.tap(find.byKey(const Key('a1-whiteRook'))); + await tester.tap(find.byKey(const Key('a1-whiterook'))); await tester.pump(); expect(find.byKey(const Key('a1-selected')), findsOneWidget); await tester.tapAt(squareOffset(const SquareId('c4'))); @@ -81,7 +82,7 @@ void main() { expect(find.byKey(const Key('a1-selected')), findsNothing); // cannot select a piece whose side is not the turn to move - await tester.tap(find.byKey(const Key('e7-blackPawn'))); + await tester.tap(find.byKey(const Key('e7-blackpawn'))); await tester.pump(); expect(find.byKey(const Key('e7-selected')), findsNothing); }); @@ -90,7 +91,7 @@ void main() { await tester.pumpWidget( buildBoard(initialInteractableSide: InteractableSide.both), ); - await tester.tap(find.byKey(const Key('e2-whitePawn'))); + await tester.tap(find.byKey(const Key('e2-whitepawn'))); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsOneWidget); @@ -100,8 +101,8 @@ void main() { await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); - expect(find.byKey(const Key('e4-whitePawn')), findsOneWidget); - expect(find.byKey(const Key('e2-whitePawn')), findsNothing); + expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); + expect(find.byKey(const Key('e2-whitepawn')), findsNothing); expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); }); @@ -114,7 +115,7 @@ void main() { pieceShiftMethod: PieceShiftMethod.drag, ), ); - await tester.tap(find.byKey(const Key('e2-whitePawn'))); + await tester.tap(find.byKey(const Key('e2-whitepawn'))); await tester.pump(); // Tapping a square should have no effect... @@ -127,8 +128,8 @@ void main() { const Offset(0, -(squareSize * 2)), ); await tester.pumpAndSettle(); - expect(find.byKey(const Key('e4-whitePawn')), findsOneWidget); - expect(find.byKey(const Key('e2-whitePawn')), findsNothing); + expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); + expect(find.byKey(const Key('e2-whitepawn')), findsNothing); expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); }); @@ -142,14 +143,14 @@ void main() { initialInteractableSide: InteractableSide.both, ), ); - await tester.tap(find.byKey(const Key('e1-whiteKing'))); + await tester.tap(find.byKey(const Key('e1-whiteking'))); await tester.pump(); - await tester.tap(find.byKey(const Key('h1-whiteRook'))); + await tester.tap(find.byKey(const Key('h1-whiterook'))); await tester.pump(); - expect(find.byKey(const Key('e1-whiteKing')), findsNothing); - expect(find.byKey(const Key('h1-whiteRook')), findsNothing); - expect(find.byKey(const Key('g1-whiteKing')), findsOneWidget); - expect(find.byKey(const Key('f1-whiteRook')), findsOneWidget); + expect(find.byKey(const Key('e1-whiteking')), findsNothing); + expect(find.byKey(const Key('h1-whiterook')), findsNothing); + expect(find.byKey(const Key('g1-whiteking')), findsOneWidget); + expect(find.byKey(const Key('f1-whiterook')), findsOneWidget); expect(find.byKey(const Key('e1-lastMove')), findsOneWidget); expect(find.byKey(const Key('h1-lastMove')), findsOneWidget); }); @@ -162,7 +163,7 @@ void main() { final e2 = squareOffset(const SquareId('e2')); await tester.dragFrom(e2, const Offset(0, -(squareSize * 4))); await tester.pumpAndSettle(); - expect(find.byKey(const Key('e2-whitePawn')), findsOneWidget); + expect(find.byKey(const Key('e2-whitepawn')), findsOneWidget); expect(find.byKey(const Key('e2-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); }); @@ -178,7 +179,7 @@ void main() { const Offset(0, -boardSize + squareSize), ); await tester.pumpAndSettle(); - expect(find.byKey(const Key('e2-whitePawn')), findsOneWidget); + expect(find.byKey(const Key('e2-whitepawn')), findsOneWidget); expect(find.byKey(const Key('e2-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); }); @@ -192,8 +193,8 @@ void main() { const Offset(0, -(squareSize * 2)), ); await tester.pumpAndSettle(); - expect(find.byKey(const Key('e4-whitePawn')), findsOneWidget); - expect(find.byKey(const Key('e2-whitePawn')), findsNothing); + expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); + expect(find.byKey(const Key('e2-whitepawn')), findsNothing); expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); }); @@ -212,8 +213,8 @@ void main() { const Offset(0, -(squareSize * 2)), ); await tester.pumpAndSettle(); - expect(find.byKey(const Key('e4-whitePawn')), findsNothing); - expect(find.byKey(const Key('e2-whitePawn')), findsOneWidget); + expect(find.byKey(const Key('e4-whitepawn')), findsNothing); + expect(find.byKey(const Key('e2-whitepawn')), findsOneWidget); expect(find.byKey(const Key('e2-lastMove')), findsNothing); expect(find.byKey(const Key('e4-lastMove')), findsNothing); @@ -226,8 +227,8 @@ void main() { await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsNothing); expect(find.byType(MoveDest), findsNothing); - expect(find.byKey(const Key('e4-whitePawn')), findsOneWidget); - expect(find.byKey(const Key('e2-whitePawn')), findsNothing); + expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); + expect(find.byKey(const Key('e2-whitepawn')), findsNothing); expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); }); @@ -252,8 +253,8 @@ void main() { await tester.pump(); // move is cancelled - expect(find.byKey(const Key('e4-whitePawn')), findsNothing); - expect(find.byKey(const Key('e2-whitePawn')), findsOneWidget); + expect(find.byKey(const Key('e4-whitepawn')), findsNothing); + expect(find.byKey(const Key('e2-whitepawn')), findsOneWidget); // selection is cancelled expect(find.byKey(const Key('e2-selected')), findsNothing); }); @@ -280,7 +281,7 @@ void main() { expect(find.byKey(const Key('e2-selected')), findsOneWidget); - await tester.tap(find.byKey(const Key('d2-whitePawn'))); + await tester.tap(find.byKey(const Key('d2-whitepawn'))); // finish the move as to release the piece await dragGesture.moveTo(squareOffset(const SquareId('e4'))); @@ -290,8 +291,8 @@ void main() { await tester.pump(); // the piece should not have moved - expect(find.byKey(const Key('e4-whitePawn')), findsNothing); - expect(find.byKey(const Key('e2-whitePawn')), findsOneWidget); + expect(find.byKey(const Key('e4-whitepawn')), findsNothing); + expect(find.byKey(const Key('e2-whitepawn')), findsOneWidget); // the piece should not be selected expect(find.byKey(const Key('e2-selected')), findsNothing); @@ -320,8 +321,8 @@ void main() { await tester.pump(); // the piece should not have moved - expect(find.byKey(const Key('d4-whitePawn')), findsNothing); - expect(find.byKey(const Key('d2-whitePawn')), findsOneWidget); + expect(find.byKey(const Key('d4-whitepawn')), findsNothing); + expect(find.byKey(const Key('d2-whitepawn')), findsOneWidget); // the piece should not be selected expect(find.byKey(const Key('d2-selected')), findsNothing); }); @@ -341,7 +342,7 @@ void main() { const Duration(milliseconds: 200), ); - expectSync(find.byKey(const Key('e2-whitePawn')), findsOneWidget); + expectSync(find.byKey(const Key('e2-whitepawn')), findsOneWidget); expectSync(find.byKey(const Key('e2-selected')), findsOneWidget); await dragFuture; @@ -358,14 +359,14 @@ void main() { ), ); - await tester.tap(find.byKey(const Key('f7-whitePawn'))); + await tester.tap(find.byKey(const Key('f7-whitepawn'))); await tester.pump(); await tester.tapAt(squareOffset(const SquareId('f8'))); await tester.pump(); await tester.tapAt(squareOffset(const SquareId('f7'))); await tester.pump(); - expect(find.byKey(const Key('f8-whiteKnight')), findsOneWidget); - expect(find.byKey(const Key('f7-whitePawn')), findsNothing); + expect(find.byKey(const Key('f8-whiteknight')), findsOneWidget); + expect(find.byKey(const Key('f7-whitepawn')), findsNothing); }); testWidgets('promotion, auto queen enabled', (WidgetTester tester) async { @@ -377,12 +378,12 @@ void main() { ), ); - await tester.tap(find.byKey(const Key('f7-whitePawn'))); + await tester.tap(find.byKey(const Key('f7-whitepawn'))); await tester.pump(); await tester.tapAt(squareOffset(const SquareId('f8'))); await tester.pump(); - expect(find.byKey(const Key('f8-whiteQueen')), findsOneWidget); - expect(find.byKey(const Key('f7-whitePawn')), findsNothing); + expect(find.byKey(const Key('f8-whitequeen')), findsOneWidget); + expect(find.byKey(const Key('f7-whitepawn')), findsNothing); }); testWidgets('king check square black', (WidgetTester tester) async { @@ -652,7 +653,7 @@ void main() { await tester.pump(const Duration(milliseconds: 200)); // opponent move is played - expect(find.byKey(const Key('a5-blackPawn')), findsOneWidget); + expect(find.byKey(const Key('a5-blackpawn')), findsOneWidget); // wait for the premove to be played await tester.pumpAndSettle(); @@ -661,8 +662,8 @@ void main() { expect(find.byKey(const Key('f3-premove')), findsNothing); // premove has been played - expect(find.byKey(const Key('d1-whiteQueen')), findsNothing); - expect(find.byKey(const Key('f3-whiteQueen')), findsOneWidget); + expect(find.byKey(const Key('d1-whitequeen')), findsNothing); + expect(find.byKey(const Key('f3-whitequeen')), findsOneWidget); }); }); @@ -943,8 +944,8 @@ Widget buildBoard({ InteractableSide interactableSide = initialInteractableSide; dc.Position position = dc.Chess.fromSetup(dc.Setup.parseFen(initialFen)); - Move? lastMove; - Move? premove; + BoardMove? lastMove; + BoardMove? premove; ISet shapes = initialShapes ?? ISet(); return MaterialApp( @@ -964,7 +965,7 @@ Widget buildBoard({ pieceShiftMethod: pieceShiftMethod, ); - return Board( + return ChessBoard( size: boardSize, settings: settings ?? defaultSettings, data: BoardData( @@ -975,11 +976,11 @@ Widget buildBoard({ isCheck: position.isCheck, sideToMove: position.turn == dc.Side.white ? Side.white : Side.black, - validMoves: algebraicLegalMoves(position), + validMoves: legalMovesOf(position), premove: premove, shapes: shapes, ), - onMove: (Move move, {bool? isDrop, bool? isPremove}) { + onMove: (BoardMove move, {bool? isDrop, bool? isPremove}) { setState(() { position = position.playUnchecked(dc.Move.fromUci(move.uci)!); if (position.isGameOver) { @@ -1001,12 +1002,12 @@ Widget buildBoard({ if (position.isGameOver) { interactableSide = InteractableSide.none; } - lastMove = Move.fromUci(opponentMove.uci); + lastMove = BoardMove.fromUci(opponentMove.uci); }); }); } }, - onPremove: (Move? move) { + onPremove: (BoardMove? move) { setState(() { premove = move; }); @@ -1021,36 +1022,3 @@ Offset squareOffset(SquareId id, {Side orientation = Side.white}) { final o = id.coord.offset(orientation, squareSize); return Offset(o.dx + squareSize / 2, o.dy + squareSize / 2); } - -/// Gets all the legal moves of this position in the algebraic coordinate notation. -/// -/// Includes both possible representations of castling moves (unless `chess960` is true). -IMap> algebraicLegalMoves( - dc.Position pos, { - bool isChess960 = false, -}) { - final Map> result = {}; - for (final entry in pos.legalMoves.entries) { - final dests = entry.value.squares; - if (dests.isNotEmpty) { - final from = entry.key; - final destSet = dests.map((e) => SquareId(dc.toAlgebraic(e))).toSet(); - if (!isChess960 && - from == pos.board.kingOf(pos.turn) && - dc.squareFile(entry.key) == 4) { - if (dests.contains(0)) { - destSet.add(const SquareId('c1')); - } else if (dests.contains(56)) { - destSet.add(const SquareId('c8')); - } - if (dests.contains(7)) { - destSet.add(const SquareId('g1')); - } else if (dests.contains(63)) { - destSet.add(const SquareId('g8')); - } - } - result[SquareId(dc.toAlgebraic(from))] = ISet(destSet); - } - } - return IMap(result); -} From 8eb87f74d35ad291814cabf1b03dd9eab2456542 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 13:33:21 +0200 Subject: [PATCH 04/17] Update board editor API to allow edit with pan moves --- example/lib/board_editor_page.dart | 70 ++++++++--- lib/src/widgets/board.dart | 76 +++++++----- lib/src/widgets/board_editor.dart | 186 ++++++++++++++++------------ test/widgets/board_editor_test.dart | 12 +- 4 files changed, 215 insertions(+), 129 deletions(-) diff --git a/example/lib/board_editor_page.dart b/example/lib/board_editor_page.dart index 3a7bb8c6..1af9ed90 100644 --- a/example/lib/board_editor_page.dart +++ b/example/lib/board_editor_page.dart @@ -14,8 +14,9 @@ class BoardEditorPage extends StatefulWidget { class _BoardEditorPageState extends State { Pieces pieces = readFen(dc.kInitialFEN); - dc.Piece? pieceToAddOnTap; - bool deleteOnTap = false; + dc.Piece? pieceToAddOnTouch; + bool deleteOnTouch = false; + PointerToolMode pointerMode = PointerToolMode.drag; @override Widget build(BuildContext context) { @@ -33,11 +34,12 @@ class _BoardEditorPageState extends State { orientation: dc.Side.white, pieces: pieces, settings: settings, - onTappedSquare: (squareId) => setState(() { - if (deleteOnTap) { + pointerToolMode: pointerMode, + onTouchedSquare: (squareId) => setState(() { + if (deleteOnTouch) { pieces.remove(squareId); - } else if (pieceToAddOnTap != null) { - pieces[squareId] = pieceToAddOnTap!; + } else if (pieceToAddOnTouch != null) { + pieces[squareId] = pieceToAddOnTouch!; } }), onDiscardedPiece: (squareId) => setState(() { @@ -56,15 +58,23 @@ class _BoardEditorPageState extends State { pieceSet: pieceSet, squareSize: boardEditor.squareSize, settings: settings, - selectedPiece: pieceToAddOnTap, + selectedPiece: + pointerMode == PointerToolMode.edit ? pieceToAddOnTouch : null, pieceTapped: (role) => setState(() { - pieceToAddOnTap = dc.Piece(role: role, color: side); - deleteOnTap = false; + pieceToAddOnTouch = dc.Piece(role: role, color: side); + deleteOnTouch = false; + pointerMode = PointerToolMode.edit; }), - deleteSelected: deleteOnTap, + deleteOnTouch: deleteOnTouch, + pointerMode: pointerMode, deleteTapped: () => setState(() { - pieceToAddOnTap = null; - deleteOnTap = !deleteOnTap; + pieceToAddOnTouch = null; + deleteOnTouch = !deleteOnTouch; + pointerMode = PointerToolMode.edit; + }), + pointerModeTapped: () => setState(() { + pointerMode = PointerToolMode.drag; + deleteOnTouch = false; }), ); @@ -76,10 +86,14 @@ class _BoardEditorPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - makePieceMenu(dc.Side.white), - boardEditor, makePieceMenu(dc.Side.black), - Text('FEN: ${writeFen(pieces)}'), + boardEditor, + makePieceMenu(dc.Side.white), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: + SizedBox(height: 50, child: Text('FEN: ${writeFen(pieces)}')), + ), ], ), ), @@ -94,20 +108,24 @@ class PieceMenu extends StatelessWidget { required this.pieceSet, required this.squareSize, required this.selectedPiece, - required this.deleteSelected, + required this.deleteOnTouch, + required this.pointerMode, required this.settings, required this.pieceTapped, required this.deleteTapped, + required this.pointerModeTapped, }); final dc.Side side; final PieceSet pieceSet; final double squareSize; final dc.Piece? selectedPiece; - final bool deleteSelected; + final bool deleteOnTouch; + final PointerToolMode pointerMode; final BoardEditorSettings settings; final Function(dc.Role role) pieceTapped; final Function() deleteTapped; + final Function() pointerModeTapped; @override Widget build(BuildContext context) { @@ -116,6 +134,18 @@ class PieceMenu extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ + Container( + color: pointerMode == PointerToolMode.drag + ? Colors.green + : Colors.transparent, + child: GestureDetector( + onTap: () => pointerModeTapped(), + child: Icon( + Icons.pan_tool_alt_outlined, + size: squareSize, + ), + ), + ), ...dc.Role.values.mapIndexed( (i, role) { final piece = dc.Piece(role: role, color: side); @@ -143,11 +173,13 @@ class PieceMenu extends StatelessWidget { }, ).toList(), Container( - color: deleteSelected ? Colors.red : Colors.transparent, + color: pointerMode == PointerToolMode.edit && deleteOnTouch + ? Colors.red + : Colors.transparent, child: GestureDetector( onTap: () => deleteTapped(), child: Icon( - Icons.delete, + Icons.delete_outline, size: squareSize, ), ), diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 8789b565..8cbaf2b2 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -1,10 +1,10 @@ import 'dart:async'; -import 'package:chessground/src/widgets/drag.dart'; import 'package:dartchess/dartchess.dart' show Piece, Role, Side; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'drag.dart'; import 'piece.dart'; import 'highlight.dart'; import 'positioned_square.dart'; @@ -23,11 +23,45 @@ const double _kDragDistanceThreshold = 3.0; const _kCancelShapesDoubleTapDelay = Duration(milliseconds: 200); +mixin BoardGeometry { + /// Visual size of the board. + double get size; + + /// Side by which the board is oriented. + Side get orientation; + + /// Size of a single square on the board. + double get squareSize => size / 8; + + /// Converts a board offset to a coordinate. + /// + /// Returns `null` if the offset is outside the board. + Coord? offsetCoord(Offset offset) { + final x = (offset.dx / squareSize).floor(); + final y = (offset.dy / squareSize).floor(); + final orientX = orientation == Side.black ? 7 - x : x; + final orientY = orientation == Side.black ? y : 7 - y; + if (orientX >= 0 && orientX <= 7 && orientY >= 0 && orientY <= 7) { + return Coord(x: orientX, y: orientY); + } else { + return null; + } + } + + /// Converts a board offset to a square id. + /// + /// Returns `null` if the offset is outside the board. + SquareId? offsetSquareId(Offset offset) { + final coord = offsetCoord(offset); + return coord?.squareId; + } +} + /// A chessboard widget. /// /// This widget can be used to display a static board, a dynamic board that /// shows a live game, or a full user interactable board. -class ChessBoard extends StatefulWidget { +class ChessBoard extends StatefulWidget with BoardGeometry { const ChessBoard({ super.key, required this.size, @@ -37,9 +71,12 @@ class ChessBoard extends StatefulWidget { this.onPremove, }); - /// Visal size of the board. + @override final double size; + @override + Side get orientation => data.orientation; + /// Settings that control the theme, behavior and purpose of the board. final BoardSettings settings; @@ -54,27 +91,6 @@ class ChessBoard extends StatefulWidget { /// If the callback is null, the board will not allow premoves. final void Function(BoardMove?)? onPremove; - double get squareSize => size / 8; - - /// Converts a board offset to a coordinate if it is within the board bounds. - Coord? _localOffsetCoord(Offset offset) { - final x = (offset.dx / squareSize).floor(); - final y = (offset.dy / squareSize).floor(); - final orientX = data.orientation == Side.black ? 7 - x : x; - final orientY = data.orientation == Side.black ? y : 7 - y; - if (orientX >= 0 && orientX <= 7 && orientY >= 0 && orientY <= 7) { - return Coord(x: orientX, y: orientY); - } else { - return null; - } - } - - /// Converts a board offset to a square id if it is within the board bounds. - SquareId? _localOffsetSquareId(Offset offset) { - final coord = _localOffsetCoord(offset); - return coord?.squareId; - } - @override // ignore: library_private_types_in_public_api _BoardState createState() => _BoardState(); @@ -457,7 +473,7 @@ class _BoardState extends State { /// Returns the position of the square target during drag as a global offset. Offset? _squareTargetGlobalOffset(Offset localPosition, RenderBox box) { - final coord = widget._localOffsetCoord(localPosition); + final coord = widget.offsetCoord(localPosition); if (coord == null) return null; final localOffset = coord.offset(widget.data.orientation, widget.squareSize); @@ -471,7 +487,7 @@ class _BoardState extends State { void _onPointerDown(PointerDownEvent details) { if (details.buttons != kPrimaryButton) return; - final squareId = widget._localOffsetSquareId(details.localPosition); + final squareId = widget.offsetSquareId(details.localPosition); if (squareId == null) return; final Piece? piece = pieces[squareId]; @@ -576,7 +592,7 @@ class _BoardState extends State { _drawOrigin!.pointer == details.pointer) { final distance = (details.position - _drawOrigin!.position).distance; if (distance > _kDragDistanceThreshold) { - final squareId = widget._localOffsetSquareId(details.localPosition); + final squareId = widget.offsetSquareId(details.localPosition); if (squareId == null) return; setState(() { _shapeAvatar = _shapeAvatar!.newDest(squareId); @@ -623,7 +639,7 @@ class _BoardState extends State { if (_dragAvatar != null && _renderBox != null) { final localPos = _renderBox!.globalToLocal(_dragAvatar!._position); - final squareId = widget._localOffsetSquareId(localPos); + final squareId = widget.offsetSquareId(localPos); if (squareId != null && squareId != selected) { _tryMoveOrPremoveTo(squareId, drop: true); } @@ -634,7 +650,7 @@ class _BoardState extends State { _premoveDests = null; }); } else if (selected != null) { - final squareId = widget._localOffsetSquareId(details.localPosition); + final squareId = widget.offsetSquareId(details.localPosition); if (squareId == selected && _shouldDeselectOnTapUp) { _shouldDeselectOnTapUp = false; setState(() { @@ -674,7 +690,7 @@ class _BoardState extends State { } void _onDragStart(PointerEvent origin) { - final squareId = widget._localOffsetSquareId(origin.localPosition); + final squareId = widget.offsetSquareId(origin.localPosition); final piece = squareId != null ? pieces[squareId] : null; if (squareId != null && piece != null && diff --git a/lib/src/widgets/board_editor.dart b/lib/src/widgets/board_editor.dart index 96b751f0..43ca13f1 100644 --- a/lib/src/widgets/board_editor.dart +++ b/lib/src/widgets/board_editor.dart @@ -1,28 +1,44 @@ -import 'package:chessground/chessground.dart'; import 'package:dartchess/dartchess.dart' show Piece, Side; import 'package:flutter/widgets.dart'; +import '../board_editor_settings.dart'; +import '../models.dart'; +import 'board.dart'; +import 'drag.dart'; +import 'piece.dart'; import 'positioned_square.dart'; +/// Controls the behavior of pointer events on the board editor. +enum PointerToolMode { + /// The default mode where pieces can be dragged around the board. + drag, + + /// The mode where pieces can be put/removed from the board when the pointer + /// is over a square. + edit, +} + /// A chessboard widget where pieces can be dragged around freely (including dragging piece off and onto the board). /// /// This widget can be used as the basis for a fully fledged board editor, similar to https://lichess.org/editor. -class ChessBoardEditor extends StatefulWidget { +class ChessBoardEditor extends StatefulWidget with BoardGeometry { const ChessBoardEditor({ super.key, required this.size, required this.orientation, required this.pieces, + this.pointerToolMode = PointerToolMode.drag, this.settings = const BoardEditorSettings(), - this.onTappedSquare, + this.onTouchedSquare, this.onDroppedPiece, this.onDiscardedPiece, }); - /// Visual size of the board. + @override final double size; - double get squareSize => size / 8; + @override + final Side orientation; /// The pieces to display on the board. /// @@ -33,11 +49,10 @@ class ChessBoardEditor extends StatefulWidget { /// Settings that control the appearance of the board editor. final BoardEditorSettings settings; - /// Side by which the board is oriented. - final Side orientation; + final PointerToolMode pointerToolMode; /// Called when the given [square] was tapped. - final void Function(SquareId square)? onTappedSquare; + final void Function(SquareId square)? onTouchedSquare; /// Called when a [piece] has been dragged to a new [destination] square. /// @@ -60,7 +75,7 @@ class _BoardEditorState extends State { @override Widget build(BuildContext context) { - final List pieceWidgets = allSquares.map((squareId) { + final List squareWidgets = allSquares.map((squareId) { final piece = widget.pieces[squareId]; return PositionedSquare( @@ -68,59 +83,63 @@ class _BoardEditorState extends State { size: widget.squareSize, orientation: widget.orientation, squareId: squareId, - child: GestureDetector( - onTap: () => widget.onTappedSquare?.call(squareId), - child: DragTarget( - hitTestBehavior: HitTestBehavior.opaque, - builder: (context, candidateData, rejectedData) { - return Stack( - children: [ - // Show a drop target if a piece is dragged over the square - if (candidateData.isNotEmpty) - Transform.scale( - scale: 2, - child: Container( - decoration: const BoxDecoration( - color: Color(0x33000000), - shape: BoxShape.circle, - ), + child: DragTarget( + hitTestBehavior: HitTestBehavior.opaque, + builder: (context, candidateData, rejectedData) { + return Stack( + children: [ + // Show a drop target if a piece is dragged over the square + if (candidateData.isNotEmpty) + Transform.scale( + scale: 2, + child: Container( + decoration: const BoxDecoration( + color: Color(0x33000000), + shape: BoxShape.circle, ), ), - if (piece != null) - Draggable( - hitTestBehavior: HitTestBehavior.translucent, - data: piece, - feedback: PieceDragFeedback( - piece: piece, - squareSize: widget.squareSize, - size: widget.settings.dragFeedbackSize, - offset: widget.settings.dragFeedbackOffset, - pieceAssets: widget.settings.pieceAssets, - ), - childWhenDragging: const SizedBox.shrink(), - onDragStarted: () => draggedPieceOrigin = squareId, - onDraggableCanceled: (_, __) { - widget.onDiscardedPiece?.call(squareId); - draggedPieceOrigin = null; - }, - child: PieceWidget( - piece: piece, - size: widget.squareSize, - pieceAssets: widget.settings.pieceAssets, - ), + ), + if (widget.pointerToolMode == PointerToolMode.drag && + piece != null) + Draggable( + hitTestBehavior: HitTestBehavior.translucent, + data: piece, + feedback: PieceDragFeedback( + piece: piece, + squareSize: widget.squareSize, + size: widget.settings.dragFeedbackSize, + offset: widget.settings.dragFeedbackOffset, + pieceAssets: widget.settings.pieceAssets, + ), + childWhenDragging: const SizedBox.shrink(), + onDragStarted: () => draggedPieceOrigin = squareId, + onDraggableCanceled: (_, __) { + widget.onDiscardedPiece?.call(squareId); + draggedPieceOrigin = null; + }, + child: PieceWidget( + piece: piece, + size: widget.squareSize, + pieceAssets: widget.settings.pieceAssets, ), - ], - ); - }, - onAcceptWithDetails: (details) { - widget.onDroppedPiece?.call( - draggedPieceOrigin, - squareId, - details.data, - ); - draggedPieceOrigin = null; - }, - ), + ) + else if (piece != null) + PieceWidget( + piece: piece, + size: widget.squareSize, + pieceAssets: widget.settings.pieceAssets, + ), + ], + ); + }, + onAcceptWithDetails: (details) { + widget.onDroppedPiece?.call( + draggedPieceOrigin, + squareId, + details.data, + ); + draggedPieceOrigin = null; + }, ), ); }).toList(); @@ -133,24 +152,39 @@ class _BoardEditorState extends State { return SizedBox.square( dimension: widget.size, - child: Stack( - clipBehavior: Clip.none, - children: [ - if (widget.settings.boxShadow.isNotEmpty || - widget.settings.borderRadius != BorderRadius.zero) - Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: widget.settings.borderRadius, - boxShadow: widget.settings.boxShadow, - ), - child: background, - ) - else - background, - ...pieceWidgets, - ], + child: GestureDetector( + onTapDown: (details) => _onTouchedEvent(details.localPosition), + onPanStart: (details) => _onTouchedEvent(details.localPosition), + onPanUpdate: (details) => _onTouchedEvent(details.localPosition), + child: Stack( + clipBehavior: Clip.none, + children: [ + if (widget.settings.boxShadow.isNotEmpty || + widget.settings.borderRadius != BorderRadius.zero) + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: widget.settings.borderRadius, + boxShadow: widget.settings.boxShadow, + ), + child: background, + ) + else + background, + ...squareWidgets, + ], + ), ), ); } + + void _onTouchedEvent(Offset localPosition) { + if (widget.pointerToolMode == PointerToolMode.drag) { + return; + } + final squareId = widget.offsetSquareId(localPosition); + if (squareId != null) { + widget.onTouchedSquare?.call(squareId); + } + } } diff --git a/test/widgets/board_editor_test.dart b/test/widgets/board_editor_test.dart index b49403ee..ec38cf3e 100644 --- a/test/widgets/board_editor_test.dart +++ b/test/widgets/board_editor_test.dart @@ -56,14 +56,16 @@ void main() { expect(find.byType(PieceWidget), findsNWidgets(12)); }); - testWidgets('tapping a square triggers the onTappedSquare callback', + testWidgets( + 'touching a square triggers the onTouchedSquare callback when the board pointer tool mode is `edit`', (WidgetTester tester) async { for (final orientation in Side.values) { SquareId? tappedSquare; await tester.pumpWidget( buildBoard( pieces: {}, - onTappedSquare: (square) => tappedSquare = square, + pointerToolMode: PointerToolMode.edit, + onTouchedSquare: (square) => tappedSquare = square, orientation: orientation, ), ); @@ -185,7 +187,8 @@ void main() { Widget buildBoard({ required Pieces pieces, Side orientation = Side.white, - void Function(SquareId square)? onTappedSquare, + PointerToolMode pointerToolMode = PointerToolMode.drag, + void Function(SquareId square)? onTouchedSquare, void Function(SquareId? origin, SquareId destination, Piece piece)? onDroppedPiece, void Function(SquareId square)? onDiscardedPiece, @@ -194,8 +197,9 @@ Widget buildBoard({ home: ChessBoardEditor( size: boardSize, orientation: orientation, + pointerToolMode: pointerToolMode, pieces: pieces, - onTappedSquare: onTappedSquare, + onTouchedSquare: onTouchedSquare, onDiscardedPiece: onDiscardedPiece, onDroppedPiece: onDroppedPiece, ), From 9fc3afc6a3cb3d50c12438282a069607df6873e7 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 13:48:59 +0200 Subject: [PATCH 05/17] Update version and changelog --- CHANGELOG.md | 13 ++++++++++--- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b76b089..3d18a246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ -## 3.3.0 - -- Add a `BoardEditor` widget, intended to be used as the basis for a board editor like lichess.org/editor +## 4.0.0 + +- Add a `ChessBoardEditor` widget, intended to be used as the basis for a board editor like lichess.org/editor +- Chessground is now dependant on `dartchess`. It is only used to share + common types: `Role`, `Side` and `Piece`. It is not used for any chess logic. +- `SquareId` is now an extension type on String. +- `Board` was renamed to `ChessBoard`. +- Add the `writeFen` helper function. +- Add the `legalMovesOf` helper function to convert a dartchess `Position` to a + set of valid moves compatible with Chessground. ## 3.2.0 diff --git a/example/pubspec.lock b/example/pubspec.lock index 000e69b7..f13da5fc 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -31,7 +31,7 @@ packages: path: ".." relative: true source: path - version: "3.3.0" + version: "4.0.0" clock: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bb87e8e9..4075e99d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: chessground description: Chess board UI developed for lichess.org. It has no chess logic inside so it can be used for chess variants. -version: 3.3.0 +version: 4.0.0 repository: https://github.com/lichess-org/flutter-chessground funding: - https://lichess.org/patron From d9f6a29308ada4c2f3c7b0420e57d5e78e62adee Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 13:54:59 +0200 Subject: [PATCH 06/17] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d18a246..99bf42d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ common types: `Role`, `Side` and `Piece`. It is not used for any chess logic. - `SquareId` is now an extension type on String. - `Board` was renamed to `ChessBoard`. +- `Move` was renamed to `BoardMove`. - Add the `writeFen` helper function. - Add the `legalMovesOf` helper function to convert a dartchess `Position` to a set of valid moves compatible with Chessground. From 612d9ad112e4d083ab73ebc61dbc38e6faeca55d Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 15:25:38 +0200 Subject: [PATCH 07/17] Fix shape tests --- lib/src/widgets/shape.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/widgets/shape.dart b/lib/src/widgets/shape.dart index f8147f41..a395e234 100644 --- a/lib/src/widgets/shape.dart +++ b/lib/src/widgets/shape.dart @@ -73,7 +73,7 @@ class ShapeWidget extends StatelessWidget { orientation: orientation, squareId: orig, child: Image.asset( - 'assets/piece_sets/mono/${role.letter}.png', + 'assets/piece_sets/mono/${role.uppercaseLetter}.png', package: 'chessground', color: color, width: scale * squareSize, From 6b024f76024514b278f2b6c4a994660ec6e6aff7 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 15:56:16 +0200 Subject: [PATCH 08/17] Improve doc comments --- lib/src/board_data.dart | 4 ++- lib/src/board_editor_settings.dart | 16 ++++++------ lib/src/fen.dart | 4 +-- lib/src/models.dart | 22 ++++++++--------- lib/src/piece_set.dart | 39 ++++++++++++++++++++++++++++-- lib/src/utils.dart | 7 ++---- lib/src/widgets/board.dart | 1 + 7 files changed, 65 insertions(+), 28 deletions(-) diff --git a/lib/src/board_data.dart b/lib/src/board_data.dart index 3f9c5cee..e3dac193 100644 --- a/lib/src/board_data.dart +++ b/lib/src/board_data.dart @@ -9,6 +9,7 @@ import 'models.dart'; /// Used to configure the board with state that will/may change during a game. @immutable abstract class BoardData { + /// Creates a new [BoardData] with the provided values. const factory BoardData({ required InteractableSide interactableSide, required Side orientation, @@ -41,7 +42,7 @@ abstract class BoardData { 'sideToMove must be set when isCheck is set, or when the board is interactable.', ); - /// Which color is allowed to move? It can be both, none, white or black + /// Which color is allowed to move? It can be both, none, white or black. /// /// If `none` is chosen the board will be non interactable. final InteractableSide interactableSide; @@ -108,6 +109,7 @@ abstract class BoardData { annotations, ); + /// Creates a copy of this [BoardData] but with the given fields replaced with the new values. BoardData copyWith({ InteractableSide? interactableSide, Side? orientation, diff --git a/lib/src/board_editor_settings.dart b/lib/src/board_editor_settings.dart index 517ffe2b..a15fcbe2 100644 --- a/lib/src/board_editor_settings.dart +++ b/lib/src/board_editor_settings.dart @@ -10,6 +10,7 @@ import 'piece_set.dart'; /// defaults are provided. @immutable class BoardEditorSettings { + /// Creates a new [BoardEditorSettings] with the provided values. const BoardEditorSettings({ // theme this.colorScheme = BoardColorScheme.brown, @@ -22,25 +23,25 @@ class BoardEditorSettings { this.dragFeedbackOffset = const Offset(0.0, -1.0), }); - /// Theme of the board + /// Theme of the board. final BoardColorScheme colorScheme; - /// Piece set + /// Piece set. final PieceAssets pieceAssets; - /// Border radius of the board + /// Border radius of the board. final BorderRadiusGeometry borderRadius; - /// Box shadow of the board + /// Box shadow of the board. final List boxShadow; - /// Whether to show board coordinates + /// Whether to show board coordinates. final bool enableCoordinates; - // Scale up factor for the piece currently under drag + // Scale up factor for the piece currently under drag. final double dragFeedbackSize; - // Offset for the piece currently under drag + // Offset for the piece currently under drag. final Offset dragFeedbackOffset; @override @@ -72,6 +73,7 @@ class BoardEditorSettings { dragFeedbackOffset, ); + /// Creates a copy of this [BoardEditorSettings] but with the given fields replaced with the new values. BoardEditorSettings copyWith({ BoardColorScheme? colorScheme, PieceAssets? pieceAssets, diff --git a/lib/src/fen.dart b/lib/src/fen.dart index f571d46f..eb0c8e48 100644 --- a/lib/src/fen.dart +++ b/lib/src/fen.dart @@ -2,7 +2,7 @@ import 'package:dartchess/dartchess.dart' show Piece, Role, Side; import 'package:flutter/widgets.dart'; import 'models.dart'; -/// Parse the board part of a FEN string. +/// Parses the board part of a FEN string. Pieces readFen(String fen) { final Pieces pieces = {}; int row = 7; @@ -40,7 +40,7 @@ Pieces readFen(String fen) { return pieces; } -/// Convert the pieces to the board part of a FEN string +/// Converts the pieces to the board part of a FEN string. String writeFen(Pieces pieces) { final buffer = StringBuffer(); int empty = 0; diff --git a/lib/src/models.dart b/lib/src/models.dart index 6da787d3..ab6d311c 100644 --- a/lib/src/models.dart +++ b/lib/src/models.dart @@ -10,7 +10,7 @@ enum InteractableSide { both, none, white, black } /// The [PieceAssets] must be complete with all the pieces for both sides. typedef PieceAssets = IMap; -/// Square identifier using the algebraic coordinate notation such as e2, c3, etc. +/// Square identifier using the algebraic coordinate notation, such as e2, c3, etc. extension type const SquareId._(String value) { const SquareId(this.value) : assert( @@ -148,7 +148,7 @@ class HighlightDetails { /// For instance a1 is (0, 0), a2 is (0, 1), etc. @immutable class Coord { - /// Create a new [Coord] with the provided values. + /// Creates a new [Coord] with the provided values. const Coord({ required this.x, required this.y, @@ -291,7 +291,7 @@ class Annotation { /// Annotation background color. final Color color; - /// Specify a duration to create a transient annotation. + /// Optional duration to create a transient annotation. final Duration? duration; @override @@ -330,17 +330,17 @@ sealed class Shape { /// Scale factor for the shape. Must be between 0.0 and 1.0. double get scale => 1.0; - /// Decide what shape to draw based on the current shape and the new destination. + /// Decides what shape to draw based on the current shape and the new destination. Shape newDest(SquareId newDest); /// Returns a new shape with the same properties but a different scale. Shape withScale(double scale); } -/// An circle shape that can be drawn on the board. +/// A circle shape that can be drawn on the board. @immutable class Circle implements Shape { - /// Create a new [Circle] with the provided values. + /// Creates a new [Circle] with the provided values. /// /// The [scale] must be between 0.0 and 1.0. const Circle({ @@ -383,7 +383,7 @@ class Circle implements Shape { @override int get hashCode => Object.hash(color, orig, scale); - /// Create a new [Circle] with the provided values. + /// Creates a copy of this [Circle] with the given fields replaced by the new values. Circle copyWith({ Color? color, SquareId? orig, @@ -410,7 +410,7 @@ class Arrow implements Shape { @override final double scale; - /// Create a new [Arrow] with the provided values. + /// Creates a new [Arrow] with the provided values. /// /// The [orig] and [dest] must be different squares. /// The [scale] must be between 0.0 and 1.0. @@ -445,7 +445,7 @@ class Arrow implements Shape { @override int get hashCode => Object.hash(color, orig, dest, scale); - /// Create a new [Arrow] with the provided values. + /// Creates a copy of this [Arrow] with the given fields replaced by the new values. Arrow copyWith({ Color? color, SquareId? orig, @@ -470,7 +470,7 @@ class PieceShape implements Shape { @override final double scale; - /// Create a new [PieceShape] with the provided values. + /// Creates a new [PieceShape] with the provided values. /// /// The [scale] must be between 0.0 and 1.0. const PieceShape({ @@ -504,7 +504,7 @@ class PieceShape implements Shape { @override int get hashCode => Object.hash(color, role, orig, scale); - /// Create a new [PieceShape] with the provided values. + /// Creates a copy of this [PieceShape] with the given fields replaced by the new values. PieceShape copyWith({ Color? color, Role? role, diff --git a/lib/src/piece_set.dart b/lib/src/piece_set.dart index 4f25d2fa..c8bc60be 100644 --- a/lib/src/piece_set.dart +++ b/lib/src/piece_set.dart @@ -42,13 +42,15 @@ enum PieceSet { letter('Letter', PieceSet.letterAssets), disguised('Disguised', PieceSet.disguisedAssets); + const PieceSet(this.label, this.assets); + + /// The label of this [PieceSet]. final String label; /// The [PieceAssets] for this [PieceSet]. final PieceAssets assets; - const PieceSet(this.label, this.assets); - + /// The [PieceAssets] for the 'Alpha' piece set. static const PieceAssets alphaAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/alpha/bR.png', package: 'chessground'), @@ -76,6 +78,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/alpha/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Caliente' piece set. static const PieceAssets calienteAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/caliente/bR.png', package: 'chessground'), @@ -103,6 +106,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/caliente/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Anarcandy' piece set. static const PieceAssets anarcandyAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/anarcandy/bR.png', package: 'chessground'), @@ -130,6 +134,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/anarcandy/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'California' piece set. static const PieceAssets californiaAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/california/bR.png', package: 'chessground'), @@ -157,6 +162,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/california/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Cardinal' piece set. static const PieceAssets cardinalAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/cardinal/bR.png', package: 'chessground'), @@ -184,6 +190,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/cardinal/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Colin M.L. Burnett' piece set. static const PieceAssets cburnettAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/cburnett/bR.png', package: 'chessground'), @@ -211,6 +218,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/cburnett/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Celtic' piece set. static const PieceAssets celticAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/celtic/bR.png', package: 'chessground'), @@ -238,6 +246,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/celtic/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Chess7' piece set. static const PieceAssets chess7Assets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/chess7/bR.png', package: 'chessground'), @@ -265,6 +274,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/chess7/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Chessnut' piece set. static const PieceAssets chessnutAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/chessnut/bR.png', package: 'chessground'), @@ -292,6 +302,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/chessnut/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Companion' piece set. static const PieceAssets companionAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/companion/bR.png', package: 'chessground'), @@ -319,6 +330,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/companion/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Disguised' piece set. static const PieceAssets disguisedAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/disguised/bR.png', package: 'chessground'), @@ -346,6 +358,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/disguised/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Dubrovny' piece set. static const PieceAssets dubrovnyAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/dubrovny/bR.png', package: 'chessground'), @@ -373,6 +386,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/dubrovny/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Fantasy' piece set. static const PieceAssets fantasyAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/fantasy/bR.png', package: 'chessground'), @@ -400,6 +414,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/fantasy/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Fresca' piece set. static const PieceAssets frescaAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/fresca/bR.png', package: 'chessground'), @@ -427,6 +442,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/fresca/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Gioco' piece set. static const PieceAssets giocoAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/gioco/bR.png', package: 'chessground'), @@ -454,6 +470,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/gioco/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Governor' piece set. static const PieceAssets governorAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/governor/bR.png', package: 'chessground'), @@ -481,6 +498,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/governor/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Horsey' piece set. static const PieceAssets horseyAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/horsey/bR.png', package: 'chessground'), @@ -508,6 +526,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/horsey/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Icpieces' piece set. static const PieceAssets icpiecesAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/icpieces/bR.png', package: 'chessground'), @@ -535,6 +554,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/icpieces/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Kiwen-suwi' piece set. static const PieceAssets kiwenSuwiAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/kiwen-suwi/bR.png', package: 'chessground'), @@ -562,6 +582,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/kiwen-suwi/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Kosal' piece set. static const PieceAssets kosalAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/kosal/bR.png', package: 'chessground'), @@ -589,6 +610,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/kosal/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Leipzig' piece set. static const PieceAssets leipzigAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/leipzig/bR.png', package: 'chessground'), @@ -616,6 +638,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/leipzig/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Letter' piece set. static const PieceAssets letterAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/letter/bR.png', package: 'chessground'), @@ -643,6 +666,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/letter/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Libra' piece set. static const PieceAssets libraAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/libra/bR.png', package: 'chessground'), @@ -670,6 +694,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/libra/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Maestro' piece set. static const PieceAssets maestroAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/maestro/bR.png', package: 'chessground'), @@ -697,6 +722,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/maestro/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Merida' piece set. static const PieceAssets meridaAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/merida/bR.png', package: 'chessground'), @@ -724,6 +750,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/merida/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Pirouetti' piece set. static const PieceAssets pirouettiAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/pirouetti/bR.png', package: 'chessground'), @@ -751,6 +778,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/pirouetti/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Mpchess' piece set. static const PieceAssets mpchessAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/mpchess/bR.png', package: 'chessground'), @@ -778,6 +806,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/mpchess/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Pixel' piece set. static const PieceAssets pixelAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/pixel/bR.png', package: 'chessground'), @@ -805,6 +834,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/pixel/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Reillycraig' piece set. static const PieceAssets reillycraigAssets = IMapConst({ kBlackRookKind: AssetImage( '$_pieceSetsPath/reillycraig/bR.png', @@ -856,6 +886,7 @@ enum PieceSet { ), }); + /// The [PieceAssets] for the 'Riohacha' piece set. static const PieceAssets riohachaAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/riohacha/bR.png', package: 'chessground'), @@ -883,6 +914,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/riohacha/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Shapes' piece set. static const PieceAssets shapesAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/shapes/bR.png', package: 'chessground'), @@ -910,6 +942,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/shapes/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Spatial' piece set. static const PieceAssets spatialAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/spatial/bR.png', package: 'chessground'), @@ -937,6 +970,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/spatial/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Staunty' piece set. static const PieceAssets stauntyAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/staunty/bR.png', package: 'chessground'), @@ -964,6 +998,7 @@ enum PieceSet { AssetImage('$_pieceSetsPath/staunty/wK.png', package: 'chessground'), }); + /// The [PieceAssets] for the 'Tatiana' piece set. static const PieceAssets tatianaAssets = IMapConst({ kBlackRookKind: AssetImage('$_pieceSetsPath/tatiana/bR.png', package: 'chessground'), diff --git a/lib/src/utils.dart b/lib/src/utils.dart index de26a303..54775c4b 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -3,13 +3,10 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'models.dart'; -/// Gets all the legal moves of the [Position] in the algebraic coordinate notation. +/// Gets all the legal moves of the [Position]. /// /// Includes both possible representations of castling moves (unless `chess960` is true). -IMap> legalMovesOf( - Position pos, { - bool isChess960 = false, -}) { +ValidMoves legalMovesOf(Position pos, {bool isChess960 = false}) { final Map> result = {}; for (final entry in pos.legalMoves.entries) { final dests = entry.value.squares; diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 8cbaf2b2..73d65400 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -23,6 +23,7 @@ const double _kDragDistanceThreshold = 3.0; const _kCancelShapesDoubleTapDelay = Duration(milliseconds: 200); +/// A mixin that provides geometry information about the board. mixin BoardGeometry { /// Visual size of the board. double get size; From c57b1168394abf8671a1b6bdccf7c4e31501d0c7 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 16:13:44 +0200 Subject: [PATCH 09/17] Add more board editor tests --- test/widgets/board_editor_test.dart | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/test/widgets/board_editor_test.dart b/test/widgets/board_editor_test.dart index ec38cf3e..44319ce4 100644 --- a/test/widgets/board_editor_test.dart +++ b/test/widgets/board_editor_test.dart @@ -82,6 +82,71 @@ void main() { } }); + testWidgets( + 'touching a square does not trigger the onTouchedSquare callback when the board pointer tool mode is `drag`', + (WidgetTester tester) async { + SquareId? tappedSquare; + await tester.pumpWidget( + buildBoard( + pieces: {}, + onTouchedSquare: (square) => tappedSquare = square, + ), + ); + + await tester.tapAt(squareOffset(const SquareId('a1'))); + expect(tappedSquare, null); + + await tester.tapAt(squareOffset(const SquareId('g8'))); + expect(tappedSquare, null); + }); + + testWidgets('pan movements trigger the onTouchedSquare callback', + (WidgetTester tester) async { + final Set touchedSquares = {}; + await tester.pumpWidget( + buildBoard( + pieces: {}, + pointerToolMode: PointerToolMode.edit, + onTouchedSquare: (square) => touchedSquares.add(square), + ), + ); + + // Pan from a1 to a8 + await tester.timedDragFrom( + squareOffset(const SquareId('a1')), + const Offset(0, -(squareSize * 7)), + const Duration(seconds: 1), + ); + expect(touchedSquares, { + 'a1', + 'a2', + 'a3', + 'a4', + 'a5', + 'a6', + 'a7', + 'a8', + }); + + touchedSquares.clear(); + // Pan from a1 to h1 + await tester.timedDragFrom( + squareOffset(const SquareId('a1')), + const Offset(squareSize * 7, 0), + const Duration(seconds: 1), + ); + expect(touchedSquares, { + 'a1', + 'b1', + 'c1', + 'd1', + 'e1', + 'f1', + 'g1', + 'h1', + }); + }); + testWidgets('dragging pieces to a new square calls onDroppedPiece', (WidgetTester tester) async { (SquareId? origin, SquareId? destination, Piece? piece) callbackParams = From 321079c845f53eaa38600709fbbd808cbff95a91 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 16:29:00 +0200 Subject: [PATCH 10/17] Update board editor example --- example/lib/board_editor_page.dart | 31 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/example/lib/board_editor_page.dart b/example/lib/board_editor_page.dart index 1af9ed90..3f3b1b1a 100644 --- a/example/lib/board_editor_page.dart +++ b/example/lib/board_editor_page.dart @@ -14,8 +14,9 @@ class BoardEditorPage extends StatefulWidget { class _BoardEditorPageState extends State { Pieces pieces = readFen(dc.kInitialFEN); + /// The piece to add when a square is touched. If null, will delete the piece. dc.Piece? pieceToAddOnTouch; - bool deleteOnTouch = false; + PointerToolMode pointerMode = PointerToolMode.drag; @override @@ -36,10 +37,10 @@ class _BoardEditorPageState extends State { settings: settings, pointerToolMode: pointerMode, onTouchedSquare: (squareId) => setState(() { - if (deleteOnTouch) { - pieces.remove(squareId); - } else if (pieceToAddOnTouch != null) { + if (pieceToAddOnTouch != null) { pieces[squareId] = pieceToAddOnTouch!; + } else { + pieces.remove(squareId); } }), onDiscardedPiece: (squareId) => setState(() { @@ -58,23 +59,19 @@ class _BoardEditorPageState extends State { pieceSet: pieceSet, squareSize: boardEditor.squareSize, settings: settings, - selectedPiece: + pieceEdition: pointerMode == PointerToolMode.edit ? pieceToAddOnTouch : null, pieceTapped: (role) => setState(() { pieceToAddOnTouch = dc.Piece(role: role, color: side); - deleteOnTouch = false; pointerMode = PointerToolMode.edit; }), - deleteOnTouch: deleteOnTouch, pointerMode: pointerMode, deleteTapped: () => setState(() { pieceToAddOnTouch = null; - deleteOnTouch = !deleteOnTouch; pointerMode = PointerToolMode.edit; }), pointerModeTapped: () => setState(() { pointerMode = PointerToolMode.drag; - deleteOnTouch = false; }), ); @@ -107,8 +104,7 @@ class PieceMenu extends StatelessWidget { required this.side, required this.pieceSet, required this.squareSize, - required this.selectedPiece, - required this.deleteOnTouch, + required this.pieceEdition, required this.pointerMode, required this.settings, required this.pieceTapped, @@ -119,8 +115,12 @@ class PieceMenu extends StatelessWidget { final dc.Side side; final PieceSet pieceSet; final double squareSize; - final dc.Piece? selectedPiece; - final bool deleteOnTouch; + + /// The piece that is currently being edited. + /// + /// If null while [pointerMode] is [PointerToolMode.edit], the user is in delete mode. + final dc.Piece? pieceEdition; + final PointerToolMode pointerMode; final BoardEditorSettings settings; final Function(dc.Role role) pieceTapped; @@ -156,8 +156,7 @@ class PieceMenu extends StatelessWidget { ); return Container( - color: - selectedPiece == piece ? Colors.blue : Colors.transparent, + color: pieceEdition == piece ? Colors.blue : Colors.transparent, child: GestureDetector( onTap: () => pieceTapped(role), child: Draggable( @@ -173,7 +172,7 @@ class PieceMenu extends StatelessWidget { }, ).toList(), Container( - color: pointerMode == PointerToolMode.edit && deleteOnTouch + color: pointerMode == PointerToolMode.edit && pieceEdition == null ? Colors.red : Colors.transparent, child: GestureDetector( From 61edcdb3f9f24b7fa56fef4a393208c315ca47b0 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 16:29:34 +0200 Subject: [PATCH 11/17] Update board editor documentation --- lib/src/widgets/board_editor.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/board_editor.dart b/lib/src/widgets/board_editor.dart index 43ca13f1..b93880ab 100644 --- a/lib/src/widgets/board_editor.dart +++ b/lib/src/widgets/board_editor.dart @@ -5,6 +5,7 @@ import '../board_editor_settings.dart'; import '../models.dart'; import 'board.dart'; import 'drag.dart'; +import '../fen.dart'; import 'piece.dart'; import 'positioned_square.dart'; @@ -21,6 +22,13 @@ enum PointerToolMode { /// A chessboard widget where pieces can be dragged around freely (including dragging piece off and onto the board). /// /// This widget can be used as the basis for a fully fledged board editor, similar to https://lichess.org/editor. +/// The logic for creating a board editor should be implemented by the consumer of this widget. +/// This widget only provides the visual representation of the board and the pieces on it, and responds to pointer events through the [onTouchedSquare], [onDroppedPiece], and [onDiscardedPiece] callbacks. +/// +/// Use the [pointerToolMode] property to switch between dragging pieces and adding/removing pieces from the board using pan gestures. +/// +/// A [writeFen] method is provided by this package to convert the current state +/// of the board editor to a FEN string. class ChessBoardEditor extends StatefulWidget with BoardGeometry { const ChessBoardEditor({ super.key, @@ -49,9 +57,10 @@ class ChessBoardEditor extends StatefulWidget with BoardGeometry { /// Settings that control the appearance of the board editor. final BoardEditorSettings settings; + /// The current mode of the pointer tool. final PointerToolMode pointerToolMode; - /// Called when the given [square] was tapped. + /// Called when the given [square] was touched or hovered over. final void Function(SquareId square)? onTouchedSquare; /// Called when a [piece] has been dragged to a new [destination] square. From b8c09034fe62556b4486d53578cffc3b4ddace6e Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 16:35:39 +0200 Subject: [PATCH 12/17] Don't remove piece if dropped on the same square --- example/lib/board_editor_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/board_editor_page.dart b/example/lib/board_editor_page.dart index 3f3b1b1a..05cdc067 100644 --- a/example/lib/board_editor_page.dart +++ b/example/lib/board_editor_page.dart @@ -48,7 +48,7 @@ class _BoardEditorPageState extends State { }), onDroppedPiece: (origin, destination, piece) => setState(() { pieces[destination] = piece; - if (origin != null) { + if (origin != null && origin != destination) { pieces.remove(origin); } }), From 0c1927f3d2f72fdc7c8d5e43bd6257df417acadb Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 16:41:06 +0200 Subject: [PATCH 13/17] Fix drag feedback offset --- lib/src/widgets/board.dart | 2 +- lib/src/widgets/drag.dart | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 73d65400..c3948191 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -718,7 +718,7 @@ class _BoardState extends State { squareSize: widget.squareSize, pieceAssets: widget.settings.pieceAssets, size: widget.settings.dragFeedbackSize, - offset: widget.settings.dragFeedbackOffset - const Offset(0.5, 0.5), + offset: widget.settings.dragFeedbackOffset, ), ); } diff --git a/lib/src/widgets/drag.dart b/lib/src/widgets/drag.dart index ff90c551..fee01ca2 100644 --- a/lib/src/widgets/drag.dart +++ b/lib/src/widgets/drag.dart @@ -36,7 +36,10 @@ class PieceDragFeedback extends StatelessWidget { Widget build(BuildContext context) { final feedbackSize = squareSize * size; return Transform.translate( - offset: (offset - const Offset(0.5, 0.5)) * feedbackSize / 2, + offset: Offset( + ((offset.dx - 1) * feedbackSize) / 2, + ((offset.dy - 1) * feedbackSize) / 2, + ), child: PieceWidget( piece: piece, size: feedbackSize, From 13c4baa167a365fd3f973f060fdc0af2d422cbc5 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 22 Jul 2024 17:36:14 +0200 Subject: [PATCH 14/17] Another drag feedback fix --- example/lib/board_editor_page.dart | 10 ++++++--- example/lib/main.dart | 2 +- lib/src/board_editor_settings.dart | 12 +++++------ lib/src/board_settings.dart | 12 +++++------ lib/src/widgets/board.dart | 13 ++++++++---- lib/src/widgets/board_editor.dart | 12 +++++++---- lib/src/widgets/drag.dart | 34 ++++++++++++------------------ 7 files changed, 50 insertions(+), 45 deletions(-) diff --git a/example/lib/board_editor_page.dart b/example/lib/board_editor_page.dart index 05cdc067..e43b300c 100644 --- a/example/lib/board_editor_page.dart +++ b/example/lib/board_editor_page.dart @@ -161,10 +161,14 @@ class PieceMenu extends StatelessWidget { onTap: () => pieceTapped(role), child: Draggable( data: piece, - feedback: PieceDragFeedback( - piece: piece, - pieceAssets: pieceSet.assets, + feedback: BoardDragFeedback( + scale: settings.dragFeedbackScale, squareSize: squareSize, + child: PieceWidget( + piece: piece, + size: squareSize * settings.dragFeedbackScale, + pieceAssets: pieceSet.assets, + ), ), child: pieceWidget), ), diff --git a/example/lib/main.dart b/example/lib/main.dart index 71a25953..a084f198 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -143,7 +143,7 @@ class _HomePageState extends State { animationDuration: pieceAnimation ? const Duration(milliseconds: 200) : Duration.zero, - dragFeedbackSize: dragMagnify ? 2.0 : 1.0, + dragFeedbackScale: dragMagnify ? 2.0 : 1.0, drawShape: DrawShapeOptions( enable: drawMode, onCompleteShape: _onCompleteShape, diff --git a/lib/src/board_editor_settings.dart b/lib/src/board_editor_settings.dart index a15fcbe2..32bb59fb 100644 --- a/lib/src/board_editor_settings.dart +++ b/lib/src/board_editor_settings.dart @@ -19,7 +19,7 @@ class BoardEditorSettings { this.borderRadius = BorderRadius.zero, this.boxShadow = const [], this.enableCoordinates = true, - this.dragFeedbackSize = 2.0, + this.dragFeedbackScale = 2.0, this.dragFeedbackOffset = const Offset(0.0, -1.0), }); @@ -39,7 +39,7 @@ class BoardEditorSettings { final bool enableCoordinates; // Scale up factor for the piece currently under drag. - final double dragFeedbackSize; + final double dragFeedbackScale; // Offset for the piece currently under drag. final Offset dragFeedbackOffset; @@ -58,7 +58,7 @@ class BoardEditorSettings { other.borderRadius == borderRadius && other.boxShadow == boxShadow && other.enableCoordinates == enableCoordinates && - other.dragFeedbackSize == dragFeedbackSize && + other.dragFeedbackScale == dragFeedbackScale && other.dragFeedbackOffset == dragFeedbackOffset; } @@ -69,7 +69,7 @@ class BoardEditorSettings { borderRadius, boxShadow, enableCoordinates, - dragFeedbackSize, + dragFeedbackScale, dragFeedbackOffset, ); @@ -80,7 +80,7 @@ class BoardEditorSettings { BorderRadiusGeometry? borderRadius, List? boxShadow, bool? enableCoordinates, - double? dragFeedbackSize, + double? dragFeedbackScale, Offset? dragFeedbackOffset, }) { return BoardEditorSettings( @@ -89,7 +89,7 @@ class BoardEditorSettings { borderRadius: borderRadius ?? this.borderRadius, boxShadow: boxShadow ?? this.boxShadow, enableCoordinates: enableCoordinates ?? this.enableCoordinates, - dragFeedbackSize: dragFeedbackSize ?? this.dragFeedbackSize, + dragFeedbackScale: dragFeedbackScale ?? this.dragFeedbackScale, dragFeedbackOffset: dragFeedbackOffset ?? this.dragFeedbackOffset, ); } diff --git a/lib/src/board_settings.dart b/lib/src/board_settings.dart index 3737c1f4..ed149c41 100644 --- a/lib/src/board_settings.dart +++ b/lib/src/board_settings.dart @@ -35,7 +35,7 @@ class BoardSettings { this.showLastMove = true, this.showValidMoves = true, this.blindfoldMode = false, - this.dragFeedbackSize = 2.0, + this.dragFeedbackScale = 2.0, this.dragFeedbackOffset = const Offset(0.0, -1.0), // shape drawing @@ -76,7 +76,7 @@ class BoardSettings { final bool blindfoldMode; // Scale up factor for the piece currently under drag - final double dragFeedbackSize; + final double dragFeedbackScale; // Offset for the piece currently under drag final Offset dragFeedbackOffset; @@ -116,7 +116,7 @@ class BoardSettings { other.showLastMove == showLastMove && other.showValidMoves == showValidMoves && other.blindfoldMode == blindfoldMode && - other.dragFeedbackSize == dragFeedbackSize && + other.dragFeedbackScale == dragFeedbackScale && other.dragFeedbackOffset == dragFeedbackOffset && other.enablePremoveCastling == enablePremoveCastling && other.autoQueenPromotion == autoQueenPromotion && @@ -136,7 +136,7 @@ class BoardSettings { showLastMove, showValidMoves, blindfoldMode, - dragFeedbackSize, + dragFeedbackScale, dragFeedbackOffset, enablePremoveCastling, autoQueenPromotion, @@ -155,7 +155,7 @@ class BoardSettings { bool? showLastMove, bool? showValidMoves, bool? blindfoldMode, - double? dragFeedbackSize, + double? dragFeedbackScale, Offset? dragFeedbackOffset, bool? enablePremoveCastling, bool? autoQueenPromotion, @@ -173,7 +173,7 @@ class BoardSettings { showLastMove: showLastMove ?? this.showLastMove, showValidMoves: showValidMoves ?? this.showValidMoves, blindfoldMode: blindfoldMode ?? this.blindfoldMode, - dragFeedbackSize: dragFeedbackSize ?? this.dragFeedbackSize, + dragFeedbackScale: dragFeedbackScale ?? this.dragFeedbackScale, dragFeedbackOffset: dragFeedbackOffset ?? this.dragFeedbackOffset, enablePremoveCastling: enablePremoveCastling ?? this.enablePremoveCastling, diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index c3948191..4ec1b1d3 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -713,12 +713,17 @@ class _BoardState extends State { shape: BoxShape.circle, ), ), - pieceFeedback: PieceDragFeedback( - piece: piece, + pieceFeedback: BoardDragFeedback( squareSize: widget.squareSize, - pieceAssets: widget.settings.pieceAssets, - size: widget.settings.dragFeedbackSize, + scale: widget.settings.dragFeedbackScale, offset: widget.settings.dragFeedbackOffset, + child: PieceWidget( + piece: piece, + size: widget.squareSize * widget.settings.dragFeedbackScale, + pieceAssets: widget.settings.pieceAssets, + blindfoldMode: widget.settings.blindfoldMode, + upsideDown: _isUpsideDown(piece), + ), ), ); } diff --git a/lib/src/widgets/board_editor.dart b/lib/src/widgets/board_editor.dart index b93880ab..fb246bbd 100644 --- a/lib/src/widgets/board_editor.dart +++ b/lib/src/widgets/board_editor.dart @@ -113,12 +113,16 @@ class _BoardEditorState extends State { Draggable( hitTestBehavior: HitTestBehavior.translucent, data: piece, - feedback: PieceDragFeedback( - piece: piece, + feedback: BoardDragFeedback( squareSize: widget.squareSize, - size: widget.settings.dragFeedbackSize, + scale: widget.settings.dragFeedbackScale, offset: widget.settings.dragFeedbackOffset, - pieceAssets: widget.settings.pieceAssets, + child: PieceWidget( + piece: piece, + size: widget.squareSize * + widget.settings.dragFeedbackScale, + pieceAssets: widget.settings.pieceAssets, + ), ), childWhenDragging: const SizedBox.shrink(), onDragStarted: () => draggedPieceOrigin = squareId, diff --git a/lib/src/widgets/drag.dart b/lib/src/widgets/drag.dart index fee01ca2..8e43aef0 100644 --- a/lib/src/widgets/drag.dart +++ b/lib/src/widgets/drag.dart @@ -1,50 +1,42 @@ -import 'package:dartchess/dartchess.dart' show Piece; import 'package:flutter/widgets.dart'; import 'piece.dart'; -import '../models.dart'; -/// The [Piece] to show under the pointer when a drag is under way. +/// Drag feedback to show under the pointer when a drag is under way. /// /// You can use this to drag pieces onto a [BoardEditor] with the same appearance as when the pieces on the board are dragged. -class PieceDragFeedback extends StatelessWidget { - const PieceDragFeedback({ +class BoardDragFeedback extends StatelessWidget { + const BoardDragFeedback({ super.key, - required this.piece, + required this.child, required this.squareSize, - required this.pieceAssets, - this.size = 2, + this.scale = 2.0, this.offset = const Offset(0.0, -1.0), }); - /// The piece that is being dragged. - final Piece piece; + /// The widget to show under the pointer. + /// + /// Typically a [PieceWidget]. + final Widget child; /// Size of a square on the board. final double squareSize; - /// Size of the feedback widget in units of [squareSize]. - final double size; + /// Scale factor for the feedback widget. + final double scale; /// Offset the feedback widget from the pointer position. final Offset offset; - /// Piece set - final PieceAssets pieceAssets; - @override Widget build(BuildContext context) { - final feedbackSize = squareSize * size; + final feedbackSize = squareSize * scale; return Transform.translate( offset: Offset( ((offset.dx - 1) * feedbackSize) / 2, ((offset.dy - 1) * feedbackSize) / 2, ), - child: PieceWidget( - piece: piece, - size: feedbackSize, - pieceAssets: pieceAssets, - ), + child: child, ); } } From 937306ab632a35a08cab9a469c003f1240631782 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 23 Jul 2024 08:35:22 +0200 Subject: [PATCH 15/17] Only use PieceDragFeedback with the editor --- example/lib/board_editor_page.dart | 9 ++--- lib/chessground.dart | 1 - lib/src/widgets/board.dart | 13 +++---- lib/src/widgets/board_editor.dart | 55 +++++++++++++++++++++++++----- lib/src/widgets/drag.dart | 42 ----------------------- 5 files changed, 56 insertions(+), 64 deletions(-) delete mode 100644 lib/src/widgets/drag.dart diff --git a/example/lib/board_editor_page.dart b/example/lib/board_editor_page.dart index e43b300c..7320898e 100644 --- a/example/lib/board_editor_page.dart +++ b/example/lib/board_editor_page.dart @@ -161,14 +161,11 @@ class PieceMenu extends StatelessWidget { onTap: () => pieceTapped(role), child: Draggable( data: piece, - feedback: BoardDragFeedback( + feedback: PieceDragFeedback( scale: settings.dragFeedbackScale, squareSize: squareSize, - child: PieceWidget( - piece: piece, - size: squareSize * settings.dragFeedbackScale, - pieceAssets: pieceSet.assets, - ), + piece: piece, + pieceAssets: pieceSet.assets, ), child: pieceWidget), ), diff --git a/lib/chessground.dart b/lib/chessground.dart index ff832d9b..7c4eaf8f 100644 --- a/lib/chessground.dart +++ b/lib/chessground.dart @@ -15,7 +15,6 @@ export 'src/premove.dart'; export 'src/utils.dart'; export 'src/widgets/board.dart'; export 'src/widgets/board_editor.dart'; -export 'src/widgets/drag.dart'; export 'src/widgets/highlight.dart'; export 'src/widgets/piece.dart'; export 'src/widgets/background.dart'; diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 4ec1b1d3..9c1e3584 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -4,7 +4,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'drag.dart'; import 'piece.dart'; import 'highlight.dart'; import 'positioned_square.dart'; @@ -693,6 +692,7 @@ class _BoardState extends State { void _onDragStart(PointerEvent origin) { final squareId = widget.offsetSquareId(origin.localPosition); final piece = squareId != null ? pieces[squareId] : null; + final feedbackSize = widget.squareSize * widget.settings.dragFeedbackScale; if (squareId != null && piece != null && (_isMovable(piece) || _isPremovable(piece))) { @@ -713,13 +713,14 @@ class _BoardState extends State { shape: BoxShape.circle, ), ), - pieceFeedback: BoardDragFeedback( - squareSize: widget.squareSize, - scale: widget.settings.dragFeedbackScale, - offset: widget.settings.dragFeedbackOffset, + pieceFeedback: Transform.translate( + offset: Offset( + ((widget.settings.dragFeedbackOffset.dx - 1) * feedbackSize) / 2, + ((widget.settings.dragFeedbackOffset.dy - 1) * feedbackSize) / 2, + ), child: PieceWidget( piece: piece, - size: widget.squareSize * widget.settings.dragFeedbackScale, + size: feedbackSize, pieceAssets: widget.settings.pieceAssets, blindfoldMode: widget.settings.blindfoldMode, upsideDown: _isUpsideDown(piece), diff --git a/lib/src/widgets/board_editor.dart b/lib/src/widgets/board_editor.dart index fb246bbd..848bcbb0 100644 --- a/lib/src/widgets/board_editor.dart +++ b/lib/src/widgets/board_editor.dart @@ -3,9 +3,8 @@ import 'package:flutter/widgets.dart'; import '../board_editor_settings.dart'; import '../models.dart'; -import 'board.dart'; -import 'drag.dart'; import '../fen.dart'; +import 'board.dart'; import 'piece.dart'; import 'positioned_square.dart'; @@ -113,16 +112,12 @@ class _BoardEditorState extends State { Draggable( hitTestBehavior: HitTestBehavior.translucent, data: piece, - feedback: BoardDragFeedback( + feedback: PieceDragFeedback( squareSize: widget.squareSize, scale: widget.settings.dragFeedbackScale, offset: widget.settings.dragFeedbackOffset, - child: PieceWidget( - piece: piece, - size: widget.squareSize * - widget.settings.dragFeedbackScale, - pieceAssets: widget.settings.pieceAssets, - ), + piece: piece, + pieceAssets: widget.settings.pieceAssets, ), childWhenDragging: const SizedBox.shrink(), onDragStarted: () => draggedPieceOrigin = squareId, @@ -201,3 +196,45 @@ class _BoardEditorState extends State { } } } + +/// The [Piece] to show under the pointer when a drag is under way. +/// +/// You can use this to drag pieces onto a [ChessBoardEditor] with the same appearance as when the pieces on the board are dragged. +class PieceDragFeedback extends StatelessWidget { + const PieceDragFeedback({ + super.key, + required this.piece, + required this.squareSize, + required this.pieceAssets, + this.scale = 2.0, + this.offset = const Offset(0.0, -1.0), + }); + + /// The piece that is being dragged. + final Piece piece; + + /// Size of a square on the board. + final double squareSize; + + /// Scale factor for the feedback widget. + final double scale; + + /// Offset the feedback widget from the pointer position. + final Offset offset; + + /// Piece set + final PieceAssets pieceAssets; + + @override + Widget build(BuildContext context) { + final feedbackSize = squareSize * scale; + return Transform.translate( + offset: (offset - const Offset(0.5, 0.5)) * feedbackSize / 2, + child: PieceWidget( + piece: piece, + size: feedbackSize, + pieceAssets: pieceAssets, + ), + ); + } +} diff --git a/lib/src/widgets/drag.dart b/lib/src/widgets/drag.dart deleted file mode 100644 index 8e43aef0..00000000 --- a/lib/src/widgets/drag.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import 'piece.dart'; - -/// Drag feedback to show under the pointer when a drag is under way. -/// -/// You can use this to drag pieces onto a [BoardEditor] with the same appearance as when the pieces on the board are dragged. -class BoardDragFeedback extends StatelessWidget { - const BoardDragFeedback({ - super.key, - required this.child, - required this.squareSize, - this.scale = 2.0, - this.offset = const Offset(0.0, -1.0), - }); - - /// The widget to show under the pointer. - /// - /// Typically a [PieceWidget]. - final Widget child; - - /// Size of a square on the board. - final double squareSize; - - /// Scale factor for the feedback widget. - final double scale; - - /// Offset the feedback widget from the pointer position. - final Offset offset; - - @override - Widget build(BuildContext context) { - final feedbackSize = squareSize * scale; - return Transform.translate( - offset: Offset( - ((offset.dx - 1) * feedbackSize) / 2, - ((offset.dy - 1) * feedbackSize) / 2, - ), - child: child, - ); - } -} From dca98b289f117ae443895ffbf328341dc9b751cc Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 23 Jul 2024 09:03:48 +0200 Subject: [PATCH 16/17] Fix documentation --- lib/src/draw_shape_options.dart | 1 + lib/src/widgets/board_editor.dart | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/src/draw_shape_options.dart b/lib/src/draw_shape_options.dart index 4a4a45f5..a5b8e095 100644 --- a/lib/src/draw_shape_options.dart +++ b/lib/src/draw_shape_options.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import 'models.dart'; +/// Settings for drawing shapes on the board. @immutable class DrawShapeOptions { const DrawShapeOptions({ diff --git a/lib/src/widgets/board_editor.dart b/lib/src/widgets/board_editor.dart index 848bcbb0..64885fc2 100644 --- a/lib/src/widgets/board_editor.dart +++ b/lib/src/widgets/board_editor.dart @@ -59,19 +59,19 @@ class ChessBoardEditor extends StatefulWidget with BoardGeometry { /// The current mode of the pointer tool. final PointerToolMode pointerToolMode; - /// Called when the given [square] was touched or hovered over. + /// Called when the given square was touched or hovered over. final void Function(SquareId square)? onTouchedSquare; - /// Called when a [piece] has been dragged to a new [destination] square. + /// Called when a piece has been dragged to a new destination square. /// - /// If [origin] is not `null`, the piece was dragged from that square of the board editor. + /// If `origin` is not `null`, the piece was dragged from that square of the board editor. /// Otherwise, it was dragged from outside the board editor. /// Each square of the board is a [DragTarget], so to drop your own piece widgets /// onto the board, put them in a [Draggable] and set the data to the piece you want to drop. final void Function(SquareId? origin, SquareId destination, Piece piece)? onDroppedPiece; - /// Called when a piece that was originally at the given [square] was dragged off the board. + /// Called when a piece that was originally at the given `square` was dragged off the board. final void Function(SquareId square)? onDiscardedPiece; @override From b3a134aebb8cf7011dfa7d53fab512334842012c Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 23 Jul 2024 09:15:09 +0200 Subject: [PATCH 17/17] Update README --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c81f5537..0496b766 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,11 @@ chess logic so you can use it with different chess variants. - draw shapes on board while playing using 2 fingers - move annotations - opponent's pieces can be displayed upside down +- create positions with a board editor ## Getting started -This package exports a `Board` widget which can be interactable or not. It is +This package exports a `ChessBoard` widget which can be interactable or not. It is entirely configurable with a `BoardSettings` object. ## Usage @@ -53,7 +54,7 @@ class _MyHomePageState extends State { title: const Text('Chessground demo'), ), body: Center( - child: Board( + child: ChessBoard( size: screenWidth, data: BoardData( interactableSide: InteractableSide.none,