Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 142 additions & 39 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ class _HomePageState extends State<HomePage> {
Position position = Chess.initial;
Side orientation = Side.white;
String fen = kInitialBoardFEN;
NormalMove? lastMove;
Move? lastMove;
NormalMove? promotionMove;
NormalMove? premove;
Move? premove;
ValidMoves validMoves = IMap(const {});
Side sideToMove = Side.white;
PieceSet pieceSet = PieceSet.gioco;
Expand All @@ -87,19 +87,31 @@ class _HomePageState extends State<HomePage> {
bool drawMode = true;
bool pieceAnimation = true;
bool dragMagnify = true;
bool testDropMoves = false;
Mode playMode = Mode.botPlay;
Position? lastPos;
ISet<Shape> shapes = ISet();
bool showBorder = false;

Position get initialPosition => testDropMoves
? Crazyhouse.initial.copyWith(
// Fill initial pockets for easier testing
pockets: Pockets.empty
.increment(Side.white, Role.pawn)
.increment(Side.white, Role.knight)
.increment(Side.white, Role.bishop)
.increment(Side.white, Role.rook)
.increment(Side.white, Role.queen))
: Chess.initial;

@override
Widget build(BuildContext context) {
Widget _buildNewRoundButton() => FilledButton.icon(
icon: const Icon(Icons.refresh_rounded),
label: const Text('New Round'),
onPressed: () {
setState(() {
position = Chess.initial;
position = initialPosition;
fen = position.fen;
validMoves = makeLegalMoves(position);
lastMove = null;
Expand Down Expand Up @@ -280,6 +292,15 @@ class _HomePageState extends State<HomePage> {
},
),
),
_buildSettingsButton(
label: 'Test Drop Moves',
value: testDropMoves ? 'ON' : 'OFF',
onPressed: () {
setState(() {
testDropMoves = !testDropMoves;
position = initialPosition;
});
}),
],
),
],
Expand All @@ -299,41 +320,42 @@ class _HomePageState extends State<HomePage> {
},
);

final settings = ChessboardSettings(
pieceAssets: pieceSet.assets,
colorScheme: boardTheme.colors,
border: showBorder
? BoardBorder(
width: 16.0,
color: _darken(boardTheme.colors.darkSquare, 0.2),
)
: null,
enableCoordinates: true,
animationDuration:
pieceAnimation ? const Duration(milliseconds: 200) : Duration.zero,
dragFeedbackScale: dragMagnify ? 2.0 : 1.0,
dragTargetKind: dragTargetKind,
drawShape: DrawShapeOptions(
enable: drawMode,
onCompleteShape: _onCompleteShape,
onClearShapes: () {
setState(() {
shapes = ISet();
});
},
),
pieceShiftMethod: pieceShiftMethod,
autoQueenPromotionOnPremove: false,
pieceOrientationBehavior: playMode == Mode.freePlay
? PieceOrientationBehavior.opponentUpsideDown
: PieceOrientationBehavior.facingUser,
enableDropMoves: testDropMoves,
);
Widget _buildChessBoardWidget() => Center(
child: LayoutBuilder(
builder: (context, constraints) {
return Chessboard(
final chessboard = Chessboard(
size: min(constraints.maxWidth, constraints.maxHeight),
settings: ChessboardSettings(
pieceAssets: pieceSet.assets,
colorScheme: boardTheme.colors,
border: showBorder
? BoardBorder(
width: 16.0,
color: _darken(boardTheme.colors.darkSquare, 0.2),
)
: null,
enableCoordinates: true,
animationDuration: pieceAnimation
? const Duration(milliseconds: 200)
: Duration.zero,
dragFeedbackScale: dragMagnify ? 2.0 : 1.0,
dragTargetKind: dragTargetKind,
drawShape: DrawShapeOptions(
enable: drawMode,
onCompleteShape: _onCompleteShape,
onClearShapes: () {
setState(() {
shapes = ISet();
});
},
),
pieceShiftMethod: pieceShiftMethod,
autoQueenPromotionOnPremove: false,
pieceOrientationBehavior: playMode == Mode.freePlay
? PieceOrientationBehavior.opponentUpsideDown
: PieceOrientationBehavior.facingUser,
),
settings: settings,
orientation: orientation,
fen: fen,
lastMove: lastMove,
Expand All @@ -360,6 +382,29 @@ class _HomePageState extends State<HomePage> {
),
shapes: shapes.isNotEmpty ? shapes : null,
);

return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (testDropMoves)
CrazyhouseMenu(
position: position,
side: Side.black,
pieceSet: pieceSet,
squareSize: chessboard.squareSize,
settings: settings,
),
chessboard,
if (testDropMoves)
CrazyhouseMenu(
position: position,
side: Side.white,
pieceSet: pieceSet,
squareSize: chessboard.squareSize,
settings: settings,
),
],
);
},
),
);
Expand Down Expand Up @@ -564,7 +609,7 @@ class _HomePageState extends State<HomePage> {
super.initState();
}

void _onSetPremove(NormalMove? move) {
void _onSetPremove(Move? move) {
setState(() {
premove = move;
});
Expand All @@ -588,9 +633,9 @@ class _HomePageState extends State<HomePage> {
});
}

void _playMove(NormalMove move, {bool? isDrop, bool? isPremove}) {
void _playMove(Move move, {bool? isDrop, bool? isPremove}) {
lastPos = position;
if (isPromotionPawnMove(move)) {
if (move is NormalMove && isPromotionPawnMove(move)) {
setState(() {
promotionMove = move;
});
Expand All @@ -608,9 +653,9 @@ class _HomePageState extends State<HomePage> {
}
}

void _onUserMoveAgainstBot(NormalMove move, {isDrop}) async {
void _onUserMoveAgainstBot(Move move, {isDrop}) async {
lastPos = position;
if (isPromotionPawnMove(move)) {
if (move is NormalMove && isPromotionPawnMove(move)) {
setState(() {
promotionMove = move;
});
Expand Down Expand Up @@ -673,3 +718,61 @@ Color _darken(Color c, [double amount = .1]) {
assert(amount >= 0 && amount <= 1);
return Color.lerp(c, const Color(0xFF000000), amount) ?? c;
}

class CrazyhouseMenu extends StatelessWidget {
const CrazyhouseMenu({
super.key,
required this.side,
required this.pieceSet,
required this.squareSize,
required this.settings,
required this.position,
});

final Side side;
final PieceSet pieceSet;
final double squareSize;
final Position position;

final ChessboardSettings settings;

@override
Widget build(BuildContext context) {
return Container(
color: Colors.grey,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: Role.values.where((role) => role != Role.king).map(
(role) {
final piece = Piece(role: role, color: side);

final hasPieceInPocket =
(position.pockets?.of(side, role) ?? 0) > 0;

return IgnorePointer(
ignoring: !hasPieceInPocket,
child: Draggable(
data: piece,
feedback: PieceDragFeedback(
scale: settings.dragFeedbackScale,
squareSize: squareSize,
piece: piece,
pieceAssets: pieceSet.assets,
),
child: PieceWidget(
piece: piece,
size: squareSize,
pieceAssets: pieceSet.assets,
opacity: hasPieceInPocket
? null
: const AlwaysStoppedAnimation(0.3),
),
),
);
},
).toList(),
),
);
}
}
10 changes: 9 additions & 1 deletion lib/src/board_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class ChessboardSettings {
this.autoQueenPromotion = false,
this.autoQueenPromotionOnPremove = true,
this.pieceShiftMethod = PieceShiftMethod.either,
this.enableDropMoves = false,
});

/// Theme of the board
Expand Down Expand Up @@ -175,6 +176,9 @@ class ChessboardSettings {
/// Controls how moves are made.
final PieceShiftMethod pieceShiftMethod;

/// Whether dragging a [Draggable<Piece>] onto the board triggers a [DropMove].
final bool enableDropMoves;

/// Shape drawing options object containing data about how new shapes can be drawn.
final DrawShapeOptions drawShape;

Expand Down Expand Up @@ -205,7 +209,8 @@ class ChessboardSettings {
other.autoQueenPromotion == autoQueenPromotion &&
other.autoQueenPromotionOnPremove == autoQueenPromotionOnPremove &&
other.pieceShiftMethod == pieceShiftMethod &&
other.drawShape == drawShape;
other.drawShape == drawShape &&
other.enableDropMoves == enableDropMoves;
}

@override
Expand All @@ -229,6 +234,7 @@ class ChessboardSettings {
autoQueenPromotionOnPremove,
pieceShiftMethod,
drawShape,
enableDropMoves,
);

ChessboardSettings copyWith({
Expand All @@ -252,6 +258,7 @@ class ChessboardSettings {
bool? autoQueenPromotionOnPremove,
PieceShiftMethod? pieceShiftMethod,
DrawShapeOptions? drawShape,
bool? enableDropMoves,
}) {
return ChessboardSettings(
colorScheme: colorScheme ?? this.colorScheme,
Expand All @@ -278,6 +285,7 @@ class ChessboardSettings {
autoQueenPromotion: autoQueenPromotion ?? this.autoQueenPromotion,
pieceShiftMethod: pieceShiftMethod ?? this.pieceShiftMethod,
drawShape: drawShape ?? this.drawShape,
enableDropMoves: enableDropMoves ?? this.enableDropMoves,
);
}
}
6 changes: 3 additions & 3 deletions lib/src/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class GameData {
/// Callback called after a move has been made.
///
/// If the move has been made with drag and drop, `isDrop` will be true.
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation comment states "If the move has been made with drag and drop, isDrop will be true." However, this is now ambiguous. The isDrop parameter could mean either:

  1. A drag-and-drop interaction (vs tap-to-select)
  2. A DropMove (vs NormalMove)

Consider updating the documentation to clarify this distinction, especially since the code at line 420 in board.dart sets isDrop: true specifically for DropMoves (pieces dragged from outside the board), not for all drag-and-drop moves.

Suggested change
/// If the move has been made with drag and drop, `isDrop` will be true.
/// The optional [isDrop] flag is `true` when the move is a drop move,
/// i.e. a piece is placed onto the board from outside (as opposed to a
/// normal move of a piece already on the board). It does not indicate
/// whether the user interaction was performed via drag-and-drop or
/// tap-to-select.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@veloce I also thought about this, do we want to rename to viaDragAndDrop maybe?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not even used by the app... But we should leave it in case other folks use it. In any case it is BC so major version.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC I added that because the old app was using this metadata to send to server (don't remember why).

final void Function(NormalMove, {bool? isDrop}) onMove;
final void Function(Move, {bool? isDrop}) onMove;

/// Callback called after a piece has been selected for promotion.
///
Expand All @@ -77,12 +77,12 @@ typedef Premovable =
/// Will be shown on the board as a preview move.
///
/// Chessground will not play the premove automatically, it is up to the library user to play it.
NormalMove? premove,
Move? premove,

/// Callback called after a premove has been set/unset.
///
/// If `null`, the premove will be unset.
void Function(NormalMove?) onSetPremove,
void Function(Move?) onSetPremove,
});

/// Describes a set of piece assets.
Expand Down
4 changes: 2 additions & 2 deletions lib/src/widgets/animation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ typedef FadingPieces = Map<Square, Piece>;
(TranslatingPieces, FadingPieces) preparePieceAnimations(
Pieces oldPosition,
Pieces newPosition, {
NormalMove? lastDrop,
Move? lastDrop,
}) {
final Map<Square, ({Piece piece, Square from})> translatingPieces = {};
final Map<Square, Piece> fadingPieces = {};
final List<(Piece, Square)> newOnSquare = [];
final List<(Piece, Square)> missingOnSquare = [];
final Set<Square> animatedOrigins = {};
for (final s in Square.values) {
if (s == lastDrop?.from || s == lastDrop?.to) {
if ((lastDrop is NormalMove && s == lastDrop.from) || s == lastDrop?.to) {
continue;
}
final oldP = oldPosition[s];
Expand Down
Loading