A Board Game with Flutter — Part II

How to animate peg moves in a board game

Michael Kühne
Flutter Community
5 min readAug 3, 2020

--

Welcome to my Flutter board game sequel. In the first post, we stepped through the implementation of a peg solitaire game. We implemented the game logic, the game configuration, and the widgets that constitute the game items. You can find it here:

By the way, the dull colors and layout were chosen on purpose. We don’t want to be distracted from our technical discussion :-).
In this post, we add animations to the peg moves. The sequence below gives an impression of how this will look like.

The final code can be found on Github:

Extend the Game

How do we need to extend the existing game:

  1. Extend the game state (add states for peg move and for removal of the jumped peg),
  2. extend the reducer logic, so that the animations are instantiated,
  3. add widgets for the moving peg and the removed peg, and
  4. extend the board widget, so that our new widgets are shown, if necessary.

I will not list all changes to the code. The complete diff is here:

Extend the Game State

The GameState gets two additional sub-states: MovingPegState and RemovingPegState. Both contain a flag, indicating whether a peg is on the move or to be removed.

MovingPegState and RemovingPegState

The MovingPegState indicates whether a peg move is active, and it keeps start and end positions of the peg to be moved:

No surprise, the RemovingPegState is similar. However, it needs only one board position for the peg to be removed.

Extend the GameReducer

The reducer’s task is to respond to events (e.g. user action) and to produce a new game state from the current game state and the event data.

If the user taps a valid hole in the unanimated version of our game, the reducer just computes the new peg situation, and the board is redrawn.

For our animation, we add two steps:

  • if the user taps a valid hole, the original peg to be moved is no longer drawn. Instead, the animated MovingPeg wigdet visualizes the peg movement of the selected peg towards the tapped hole.
  • if the movement is finished, the removal of the jumped peg is started. This peg increases in size and gets transparent until it vanishes. Again, instead of the original peg, an animated widget is shown. After that the animation is finished, the board is settled and the user can select a new peg to be moved, or the game is over.

The reducer’s onTapHole function sets up the movingPegState: it will be active, the start position is set to the selected peg, and the end position is set to the selected hole.

With this game state, the move animation runs, and once it finishes, it triggers the function onFinishMove of the reducer. This function, in turn, activates the sub-state removingPegState, with the index of the peg to be removed.

The widget shown in this state is called FadingPeg, due to its graphical effect. When instantiated, it will run the animation. After that, it will call the reducer’s onFinishRemove function. This one finally will settle the peg states and will enable the selection of pegs or will end the game, depending on the state.

The MovingPeg Widget

Here we enter the interesting part. We want the selected peg to move to the target hole. Since Flutter graphics is based on widgets, this will be a widget. And since it moves, it is an animated stateful widget. Flutter gives us great opportunities to animate widgets.

If you are not yet familiar with Flutter animation, especially with the Animation class, you should look at the documentation first,

or find some great intro’s here around.

The MovingPeg widget is a StatefulWidget. It is mainly described by its state object.

The MovingPegState is configured with start and end positions of its move, the peg’s size, and the callback for finalizing the move. During its move, it keeps its current position a Point variable, movingPosition.

During animation, the movingPosition value is evolved with help of Flutter’s animation stack:

  • moveController: AnimationController; defines the progress of the animation by the help of the mixed in TickerProvider and the given Duration of the move. Progress is expressed by a value between zero and one.
  • animation: Animation; the first of our two animation objects will turn the linear progress of the animation into a curved one. The peg starts moving slowly, accelerates, and then slows down.
  • moveAnimation: Animation; the second animation object finally sets the peg’s current position, as an interpolation between the start point and endpoint, following the current progress value of the upper animation object. It will, therefore, use the Tween class (like “in-between”), an interpolation helper for a selection of numeric constructs.

The whole animation is set up during the initialization of the MovingPegState.

The MovingPegState class has to provide a method to build the resulting Widget in the widget tree. This method paints a usual Peg widget, but with an alignment adjusted to the current peg position.

The FadingPeg Widget

Once the user moves a peg, another peg, that one that is jumped, is removed from the board. It pops and disappears. That means, it increases its size to factor 1.5 and turns down its opacity from 1 to 0.

The implementation of this widget is close to the MovingPeg, except for one additional step:

We define a construct for the animated parameters of our widget (size and opacity), and an extended Tween class, that interpolates over these dimensions. The lerpDouble method just gives the linear interpolation between two doubles.

The animation now is done with help of a FadingTween, and it evolves a Fading object, which keeps the current state, similar to the movePosition in the MovingPeg animation.

The widget that is given to the widget tree has altered opacity and size, according to the animation’s current fading state.

Extend the Main Widget

There is one last thing left to do: place our animated widgets on the game’s surface. This is easily done. BoardSettingState is the central widget provider for our board. Its widget list is extended by our animated widgets, but only if they are active.

Conclusion

As a Newby, I was impressed. It seems to me that Flutter’s concepts lead to clear and capable solutions.

I am curious to try more complex tasks, and I will be glad if I can share my experience with you once more.

https://www.twitter.com/FlutterComm

--

--

Michael Kühne
Flutter Community

Software developer / freelancer; I work in full stack Java Angular projects, Flutter, ML; Love to spend time with my family