A Board Game with Flutter — Part I

Michael Kühne
9 min readApr 15, 2020

--

A couple of months ago I wrote a simple solitaire board game with Google’s new Flutter technology, in order to learn about and as a proof of concept for myself. These days I finally found the time to share my experience with you.

The game app is simple enough to get it done without deep knowledge of Flutter, and it is complex enough to show the potential of working with this exiting new approach. The code described here might serve as a blueprint for more sophisticated board games.

You will not find an introduction into Flutter itself. You should bring a basic understanding of the underlying concepts. You can find a lot of posts which lead through the first steps with Flutter, also on this platform.

This post describes a basic version of the game. In an follow up, I will extend the game with animated peg moves.

The complete code is in my github repo:

We will discuss only the crucial parts of the project. For a complete understanding, you might want to clone the code and set it up in your IDE:

git clone https://github.com/michaelkue/peg_solitaire.git
cd peg_solitaire
git checkout tags/v1.0 -b v1.0-branch

This post is split into the following sections:

  • The Game: Peg Solitaire
  • How the App will look
  • How the Board is built
  • Game Configuration
  • The Game State
  • The Reducer
  • The Widgets for Hole, Board and Pegs

The Game: Peg Solitaire

Peg solitaire is a board game for one player. The board has a number of holes. At start, all holes except of one are occupied by pegs. The pegs can be moved onto an empty hole by jumping over exactly one other peg. The jumped peg is then removed. Aim of the game is to remove as many pegs as possible. If all is done right, usually only one peg is left on the board. Wikipedia gives a good explanation of background, history and variants of the game:

My code sample describes the triangular board variant, however with little extra effort, any other variant can be configured.

How the App will look

Here you see a sequence of the app. The movable pegs are highlighted. As soon as the user selects one of them, all reachable holes are highlighted. Then the user selects one of the holes, the move is done and the cycle restarts.

How the Board is built

The game’s home widget class is called BoardSetting. It is assigned to the app’s main widget.

App widget

BoardSetting is a StatefulWidget. After each state change, it rebuilds the following items:

  1. the board background and the holes, on their configured position; (Depending on the state, some holes might be marked as possible peg targets.)
  2. the remaining pegs;
    (Depending on the state, one of the pegs might be marked as selected.)
  3. and a Text area alongside the board, which describes possible user action, dependent on state.

The following gist shows the part, where holes and pegs are placed onto the board area. The Board widget is a stateless widget that draws the list of holes, the Pegs widget, also stateless, draws the remaining pegs. Both get configuration and and some state information, in order to place and mark the items. We will discuss these widgets later in detail.

BoardSetting widget: build board and pegs

The user can tap holes and pegs. Actions are immediately passed to the reducer. We will look at it later.

BoardSetting widget: User action handling

So this is a short intro in how the game is painted to the screen, in accordance to its configuration and current state. The reducer will derive the initial state of the game from the configuration. For this purpose it will get passed a configuration object.

BoardSetting widget: initialization of reducer and state

The configuration object is a List of HoleConfig objects. Each of them describes a single hole, its position on the board and the possible moves from here.

Let’s have a closer look.

Game Configuration

The HoleConfig class contains:

  • point: the board coordinates of the hole,
  • holesToGo: a list of holes that can be reached from here, see below, and
  • hasInitialPeg: a flag that tells whether the hole is initially occupied by a peg.
HoleConfig class

The HoleToGo class describes a hole that can be reached, and the hole that has to be jumped on the way.
Please mind that every HoleToGo entity is unique:

HoleToGo class

Now, the placement of every hole and the possible peg moves are described completely by a list of HoleConfig. Part of the list is shown here:

List of HoleConfig

This configuration is initially passed to the reducer, which derives the possible moves and the game state.

In the next section we will see how this is done.

The Game State

The following parameters suffice to describe the state of our peg solitaire game at any point in time:

  • the hole index of the selected peg (if there is one selected),
  • the list of hole indexes that can be reached by the selected peg,
  • and the list of remaining pegs; They are described by their hole index, together with a flag for whether they can be moved. This information is packed into a list of PegState classes.

Please note that the pegs themselves have no unique attributes. A peg is described by the index of the hole that it occupies.

So the game state class has the following members:

GameState class members

where the List of PegState keeps the remaining pegs and a flag for whether they are movable under the current board situation:

PegState class members

Please note that there is no need to keep the selected hole (the target of a move) in the game state. As soon as a hole is selected, the move is done and a new state is derived.

This work is done by the reducer.

The Reducer

There are three events that trigger a state change. That means, there are three occasions on which the reducer derives a new game state:

  1. game initialization,
  2. a valid peg selection,
  3. and a valid hole selection (if a peg already is selected).

Game initialization

Remember that the game state is described by:

  1. the currently selected peg,
  2. the holes that currently can be reached from there,
  3. whether the game is over,
  4. and the pegs that currently are on the board, and by whether they can be moved.

The values for attributes 1–3 are obvious at the very beginning of a game. No peg is selected yet, and therefore no holes to reach, and the game is not over. It comes handy that these are the default parameter values for the GameState class, and we need to pass only the PegStates to our first derived GameState.

GameState: initGameState

What we need to set up in the beginning is a list of the pegs on the board. From this list we derive the PegState list for our game state. In the code snippet above, the lines 6–9 extract the indexes of the initially set pegs from the holesConfig list.

As we already learned, the PegState contains the hole index of an exisiting peg, and a flag that tells whether this peg can be moved under the current board setting. The method _initPegStates puts the peg indexes and the movability flag into a PegState list:

GameReducer class: initPegStates

A peg can be moved if it has at least one hole to go to:

bool _canMovePeg(List<int> _pegs, int idx) {
return _getHolesToGo(_pegs, idx).length > 0;
}

How do we find the list of holes, that one peg can jump to? Again we stick to our holesConfig list. For each hole there is a list of HoleToGo items, that contains the holes, that theoretically can be reached from here, and the holes, that are jumped in this move. We check this list against the current list of pegs on the board. The hole to go needs to be empty, while the jumped hole needs to be occupied. The _getHolesToGo method goes as follows:

GameReducer class: getHolesToGo

We discuss the methods above in detail, because they are heavily reused later, when our game evolves.

So now we have our initial game state. Our BoardSetting widget is a StatefulWidget, which means, that immediately after each state change, the board, including holes and pegs, is redrawn. The movable pegs are highlighted. After all, we have all this in our game state, so it is easily applied to the BoardSetting build.

Later in the post we will have a short look on how the pegs and holes widgets are build and applied. These widgets are stateless, so it’s simple matter.

Event Handling

After game setup, the user can select one of the highlighted pegs. The selection can be revised. After selection, the reachable holes are highlighted.
The user selects a hole, the move is done, and a new game state is derived.

In order to be selectable, the pegs and holes have tap handlers. These tap handlers pass the event to the appropriate reducer methods, together with the index of the selected item. These reducer methods are onTapPeg and onTapHole.

Peg Selection

On peg selection, the onTapPeg method first checks whether the peg is movable at all. If it is, the game state changes in two attributes:

  • selectedPeg: is set to the index of the tapped peg, and
  • holesToGo: we get the list of reachable holes from our _getHolesToGo method; We discussed this method above, it is the one we already used on game initialization.
GameReducer class: onTapPeg

Now that we have a new game state, it will be used in the BoardSetting widget in order to redraw the game. This time, the selected peg is marked, together with the holes that this peg can reach.

Hole Selection

The user can tap one of the marked holes as the target of the move. Then the new game state needs to be derived, and the reducer’s onTapHole method does it by solving the following tasks:

  1. check whether a valid target was tapped,
  2. remove the selected peg from peg list (line 8),
  3. add a peg to the selected hole (lines 8–11),
  4. remove the jumped peg from the list (lines 12–13),
  5. get the new PegState list
    (the _initPegStates method does it for us (line 14)),
  6. and check if there are moves left after the change (if not, the game is over (line 17)).

The new GameState is put together from our results. Of course there is no peg selected right after the move, and therefore no hole to go.

GameReducer class: onTapHole

We have a new game state, and the BoardSetting widget will bravely draw the new board situation and mark the movable pegs. If the game is over, it will bring up a modal dialog, from which the game can be played again.

So this is it for the game structure and logic. As promised before, I will quickly document the Peg and Hole widgets, which are used to draw the board situation on the screen, and to pass user interaction.

Hole and Board Widget

A single hole on the board is a StatelessWidget, that is parametrized by an index (used for passing the user action), size, a marked flag, and a tap handler. It mainly wraps a box shape Container into a GestureDetector, which allows tap handling. A tap event is passed to the callback function that is passed to the widget. If the Hole widget is parametrized to be marked, it will use some border attribute to highlight the hole.

Hole widget

The Board widget draws the Hole widgets onto the board, according to the HolesConfig configuration list. An Align widget wraps the holes and places them at their configured position.

Board widget: draw the configured holes

Peg Widget

A Peg is parametrized by its size (which is the same for all pegs), by an assignable event handler, and by movable and selected indicators. It is, similar to a Hole widget, a Container wrapped by a GestureDetector.

Peg widget

The individual pegs are placed on the board within the Pegs widget. This one takes the holes configuration list and the game state, and places all remaining pegs on their respective holes. The alignment is similar to that in the Board widget. Please note that the index of a peg always is the same as the index of the hole that it occupies.

Pegs widget

Conclusion

I enjoyed developing this game with Flutter. This exciting approach brings advanced structure and reasonable conventions to UI development.

In a followup post we will extend our game, so that the peg moves are animated.

--

--

Michael Kühne

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