# Make a Checkers Blockchain
Make sure you have all you need before proceeding with the exercise:
- You understand the concepts of accounts, Protobuf, and multistore.
- Have Go installed.
- The bare blockchain scaffold codebase with a single module named
checkers
. You can get there by following the previous steps or checking out the relevant version (opens new window).
In the Starport introduction section you learned how to start a brand-new blockchain. Now it is time to dive deeper and explore how you can create a blockchain to play a decentralized game of checkers.
A good start to developing a checkers blockchain is to define the ruleset of the game. There are many versions of the rules. Choose a very simple set of basic rules (opens new window) to not get lost in the rules of checkers or the proper implementation of the board state.
Use a ready-made implementation (opens new window) with the additional rule that the board is 8x8 and played on black cells. This code will not need adjustments. Copy this rules file into a rules
folder inside your module. Change its package from checkers
to rules
. You can do this by command-line:
Do not focus on the GUI for the moment. You will only lay the foundation for an interface.
Now it is time to create the first object.
# The stored game object
Begin with the minimum game information needed to be kept in the storage:
- Red player. A string, the serialized address.
- Black player. A string, the serialized address.
- Game proper. A string, the game as it is serialized by the rules file.
- Player to play next. A string.
# How to store
Knowing what to store, you now have to decide how to store a game. This is important if you want your blockchain application to accommodate multiple simultaneous games. The game is identified by a unique ID.
How should you generate the ID? You cannot let players choose it themselves as this could lead to transactions failing because of an ID clash. It is better to have a counter incrementing on each new game. This is possible because the code execution happens in a single thread. You cannot rely on a large random number like a universally unique identifier (UUID) because transactions have to be verifiable in the future.
You need to keep such a counter in storage between transactions. You can keep a unique object at a singular location instead of a single counter. You can easily add relevant elements to the object as needed in the future. Designate idValue
to the counter.
You can rely on Starport's assistance:
For the counter or rather the object that contains it, call
NextGame
and instruct Starport withscaffold single
:You need to add
--no-message
. If you omit it, Starport creates ansdk.Msg
and an associated service, whose purpose is to overwrite yourNextGame
object. YourNextGame.IdValue
has to be controlled/incremented by the application and not by a player sending a value of their own choosing. Starport still creates convenient getters.You need a map because you're storing games by ID. Instruct Starport with
scaffold map
using theStoredGame
name:The
--no-message
again? You do not want the game objects to be created or overwritten with a simplesdk.Msg
. The application instead creates and updates the objects when receiving properly crafted messages like create game or play a move.
The Starport scaffold
command creates several files as you can see here (opens new window) and here (opens new window).
The command added new constants:
These constants will be used as prefixes for the keys that can access objects' storage.
# Protobuf objects
Starport creates the Protobuf objects in the proto
directory before compiling them. The NextGame
object looks like this:
And the StoredGame
object looks like this:
Both objects compile to:
And:
These are not the only created Protobuf objects. The genesis state is also defined in Protobuf:
Which is compiled to:
You can find query objects as part of the boilerplate objects created by Starport. NextGame
might look out of place, but keep in mind Starport creates the objects according to a model. This does not prevent you from making changes later if you decide these queries are not needed:
The query objects for StoredGame
have more use to your checkers game and look like this:
# Starport's modus operandi
Starport puts the different Protobuf messages into different files depending on their use:
query.proto
. For the objects related to reading the state. Starport modifies this file as you add queries. This includes the objects to query your stored elements (opens new window).tx.proto
. For the objects that relate to updating the state. As you have only defined storage elements with--no-message
, it is empty for now. The file will be modified as you add transaction-related elements like the message to create a game.genesis.proto
. For the genesis. Starport modifies this file according to how your new storage elements evolve.next_game.proto
andstored_game.proto
. Separate files created once that remain untouched by Starport after their creation. You are free to modify them but be careful with the numbering (opens new window).
Files updated by Starport include comments like:
Starport adds code right below the comments, which explains the odd numbering with the oldest members appearing lower than recent ones. But make sure to keep these comments where they are so that Starport knows where to inject code in the future. You could add your code above or below the comments. You will be fine if you keep these comments where they are.
Some files created by Starport can be updated, but you should not modify the Protobuf-compiled files *.pb.go
(opens new window) and *.pb.gw.go
(opens new window) as they are recreated on every re-run of starport generate proto-go
or equivalent.
# Files to adjust
Starport creates files that you can and should update. For example, when it comes to the default genesis values:
You can choose to start with no games or insert a number of games to start with. You will need to choose the first ID of the first game in any case, which here is set at 0
.
# Protobuf service interfaces
Beyond the created objects Starport also creates services that declare and define how to access the newly-created storage objects. Starport introduces empty service interfaces that can be filled as you add objects and messages when scaffolding a brand new module.
In your case, Starport added to service Query
how to query for your objects:
Starport separates concerns into different files in the compilation of a service. Some of which you should edit and some should be left untouched. The following was already taken care of by Starport for your checkers game:
- The query parameters (opens new window), as well as how to serialize (opens new window) and make them conform to the right Protobuf
RequestQuery
(opens new window) interface. - The primary implementation of the gRPC service.
- The implementation of all the storage setters and getters (opens new window) as extra functions in the keeper.
- The implementation of the storage getters in the keeper as they come from the gRPC server (opens new window).
# Helper functions
Your stored game stores are only strings. But you know that they represent sdk.AccAddress
or even a game from the rules
file. You are going to do operations on them. So how about adding helper functions to StoredGame
?
Get the game
Creator
:Plus the same for the red (opens new window) and black (opens new window) players.
Parse the game so that it can be played with. Notice how the
Turn
has to be set by hand:This is a good place to introduce your own errors:
# Next up
Want to continue developing your checkers blockchain? In the next section you will learn all about introducing an sdk.Msg
to create a game.