Scarne’s Dice: A fun way to learn Flutter and Bloc

This is the second installment in the Applied CS with Flutter series.

Scarne’s Dice: A fun way to learn Flutter and Bloc
Scarne’s Dice: A fun way to learn Flutter

This is the second installment in the Applied CS with Flutter series.

Chapters:

  1. Learn Data Structures the fun way: Anagram Game
  2. Scarne’s Dice: A fun way to learn Flutter and Bloc
  3. Pagination in Flutter with Generics: Write Once and use Anywhere
  4. More to come …

In the previous workshop, we tackled Lists, HashMaps and HashSets and just ignored the UI part of the application when we were provided with the starter code. In this workshop, we will focus on the UI part and learn State Management with Bloc.

Free Link for Readers

Scarne’s Dice

Scarne’s Dice is a turn-based dice game where players score points by rolling a die and then:

  • if they roll a 1, score no points and lose their turn
  • if they roll a 2 to 6, add the rolled value to their points and choose to either reroll or keep their score and end their turn

The winner is the first player that reaches (or exceeds) 100 points.

Scarne’s Dice Tutorial

For example, if a player starts their turn and rolls a 6, they can choose to either hold and end their turn, in which case they can add the 6 to their score, or to reroll and potentially score more points.

Let’s say they decide to roll again, and they get a 4. They now have the option to end their turn and add 10 points (6 + 4) to their score, or to roll again to get even more points.

They decide to roll again, but get a 1. Getting a 1 makes the player lose all the points from their turn (so their score is the same as before their turn), and finishes their turn, allowing the second player to begin their turn.

This goes on until one of the players reaches 100 points or more.

Milestone 1: Implementing UI

To create the project, I usually use Very Good Starter Template. It is easy and provides you with Industry-grade starter code with environments and basic state setup for you.

On your preferred terminal, run these commands and you will have your project setup for you.

dart pub global activate very_good_cli 
 
very_good create flutter_app scarnes_dice
These are some images for the dice faces. Add them in your project and reference them in your pubspec.yaml file and run, pub get

The UI is composed of :

  • A text widget to display the scores
  • An Asset Image widget that displays the current die (default to the image of your choice)
  • Three buttons to either roll the die, end your turn or start over.
Solution to Milestone 1

Milestone 2: Implementing the game

All the logic for this game will be implemented in the GameBloc class (the file can be named anything, for this article purposes, we will call it Game Bloc).

You will need to create a GameEvents class that will list all the events that need to be fired to update the game, namely, roll, hold and reset.

Create a GameState class that will hold the state of the game. We will create 6 variables to store:

  1. The user’s overall score
  2. The user’s turn score
  3. The computer’s overall score
  4. The computer’s turn score
  5. Dice current value
  6. Who is playing the game

We will also need to create a GameBloc class that will handle all the logic for the game. In this bloc, add a roll click handler that:

  1. Randomly select a dice value
  2. Update the display to reflect the rolled value

Use ```BlocBuilder``` in the UI to build certain parts of the UI that need to be updated on dice rolls.

Solution to Milestone 2

Milestone 3: Game logic

If the roll is not a 1, update the user’s turn score by the value of the roll and update the label to “Your score: 0 computer score: 0 your turn score: X”. If the roll is a 1, reset the turn score to 0 and update the UI accordingly.

Having written the basic “Roll” functionality, you can tackle the other two button handlers:

  • When ResetButton is clicked, reset the 4 global variables to 0 and update the label text
  • When HoldButton is clicked, updating the user's overall score, resets the user's round score and updates the label.
Stop and check
Solution to Milstone 3

Milestone 4: computerTurn

Create a helper method called computerTurn, it will need to:

  • Disable the roll and hold buttons
  • Create a while loop that loops over each of the computer’s turns. During each iteration of the loop:
  • pick a random die value and display it (hopefully using the helper you created earlier)
  • follow the game rules depending on the value of the roll.

Be sure to update the label with the computer’s round score or “Computer holds” or “Computer rolled a one” as appropriate.

Finally, invoke the computerTurn procedure from both the HoldButton handler and the RollButton handler (if the user rolled a 1).

Solution to Milestone 4

Milestone 5: Refactoring

The game should now be functional so try playing a few rounds against the computer. You can look at the console for the debug logging from the bloc or you can use your logging library to debug if any issue occurs.

Although the game (hopefully) works roughly as intended, you may find the computer turn to be quite hard to follow as it happens so quickly that you can hardly see the die rolls and the label updates. Let’s address that by refactoring the computer turn:

  • Get rid of the while loop (but not its contents!) and make the computerTurn method handles a single roll of the computer's turn
  • If the computer can and does decide to roll again, create a timed event that will do so after an appropriate delay (say 500 ms). To accomplish this, you can use Future.delayed, an example of which can be seen on StackOverflow.

Enjoy the wonders of a fully functional game of Scarne’s dice!

Solution to Milestone 5

Extensions

This unit’s extension suggestions are:

  • Two-dice version. In this version, two standard dice are rolled:
    - If neither shows a 1, their sum is added to the turn total
    - If a single 1 is rolled, the player scores nothing and the turn ends
    - If two 1s are rolled, the player’s entire score is lost, and the turn ends
    - If a double is rolled, the point total is added to the turn total as with any roll, but the player is obligated to roll again.
  • Fast mode: Rather than rolling the dice a variable number of times, the user picks several dice to roll then rolls those dice all at once. If a 1 is shown, the user gets nothing. If no 1 is shown, the user gets the sum of the dice. In either case, the turn is over and the other player takes a turn. You can read more about this game in this research paper.
  • Implement a smarter computer player. I wouldn’t recommend attempting the optimal player (which you can read about here ), but take into account the difference between the computer score and user score in deciding when to hold
  • Write another dice game of your choice
  • Add support for playing the game to 100 points, declaring a winner and starting a new game
  • Create an animation representing each die roll. This will avoid confusion when rolling the same value twice in a row.

Through this workshop, we explored how to build Scarne’s Dice using Flutter and Bloc, breaking it down into structured milestones. Beyond just implementing the game, this tutorial introduced key concepts of state management, UI updates with BlocBuilder, and handling game logic efficiently.

The goal of this series is to make learning Flutter fun and engaging while reinforcing core programming concepts. If you enjoyed this project, consider exploring the extensions to challenge yourself further. Stay tuned for the next installment, where we’ll dive into more exciting applications of Flutter!

Would love to hear your thoughts — feel free to share your experiences or improvements in the comments. Happy coding! 🚀