# Make Sure a Player Can Reject a Game
Before proceeding, make sure you have all you need:
- You understand the concepts of transactions, messages, and Protobuf.
- You know how to create a message with Ignite CLI, and code its handling. This section does not aim to repeat what can be learned in earlier sections.
- Go is installed.
- You have the checkers blockchain codebase with the previous messages and their events. If not, follow the previous steps or check out the relevant version (opens new window).
In this section, you will:
- Add a new protocol rule.
- Define custom errors.
- Add a message handler.
- Extend unit tests.
Your blockchain can now create and play games, and inform the outside world about the process. It would be good to add a way for players to back out of games they do not want to play. What do you need to make this possible?
# Some initial thoughts
- What goes into the message?
- How do you sanitize the inputs?
- How do you unequivocally identify games?
- What conditions have to be satisfied to reject a game?
- How do you report back errors?
- What event should you emit?
- How do you use your files that implement the checkers rules?
- What do you do with a rejected game?
# Code needs
When you think about the code you might need, try to first answer the following questions:
- What Ignite CLI commands will create your message?
- How do you adjust what Ignite CLI created for you?
- How would you unit-test these new elements?
- How would you use Ignite CLI to locally run a one-node blockchain and interact with it via the CLI to see what you get?
As before, do not bother yet with niceties like gas metering.
If anyone can create a game for any two other players, it is important to allow a player to reject a game. But a player should not be allowed to reject a game once they have made their first move.
To reject a game, a player needs to provide the ID of the game that the player wants to reject. Call the field
gameIndex. This should be sufficient, as the signer of the message is implicitly the player.
# Working with Ignite CLI
Name the message object
RejectGame. Invoke Ignite CLI:
This creates all the boilerplate for you and leaves a single place for the code you want to include:
# Additional information
A new rule of the game should be that a player cannot reject a game once they begin to play. When loading a
StoredGame from storage you have no way of knowing whether a player already played or not. To access this information add a new field to the
Run Protobuf to recompile the relevant Go files:
MoveCount should start at
0 and increment by
1 on each move.
Adjust it first in the handler when creating the game:
Before saving to the storage, adjust it in the handler when playing a move:
MoveCount counting properly, you are now ready to handle a rejection request.
# The reject handling
To follow the Cosmos SDK conventions, declare the following new errors:
This time you will add an event for rejection. Begin by preparing the new keys:
In the message handler, the reject steps are:
Fetch the relevant information:
Is the player expected? Did the player already play? Check with:
Remember that the player with the color black plays first.
Remove the game using the
Keeper.RemoveStoredGame(opens new window) function created long ago by the
ignite scaffold map storedGame...command:
Emit the relevant event:
Leave the returned object as it is, as you have nothing new to tell the caller.
Finally, confirm that your project at least compiles with (opens new window):
# Unit tests
When you are done with the existing tests, the tests for reject here are similar to those you created for create and play, except that now you test a game rejection by the game creator (opens new window), the black player (opens new window), or the red player (opens new window) which is made before anyone has played (opens new window), or after one (opens new window) or two moves (opens new window) have been made. Check also that the game is removed (opens new window), and that events are emitted (opens new window).
Try these tests:
# Interact with the CLI
Time to see if it is possible to reject a game from the command line. If you did not do it already, start your chain with Ignite.
First, is it possible to reject the current game from the command line?
reject-game is the command. What is its syntax?
Have Bob, who played poorly in game
1, try to reject it:
Against expectations, the system carried out Bob's request to reject the game. Confirm that the game has indeed been removed from storage:
How is it possible that Bob could reject a game he had already played in, despite the code preventing that? Because game
1 was created in an earlier version of your code. This earlier version created a game without any
.MoveCount, or more precisely with
MoveCount == 0. When you later added the code for rejection, Ignite CLI kept the current state of your blockchain. In effect, your blockchain was in a broken state, where the code and the state were out of sync.
To see how to properly handle code changes that would otherwise result in a broken state, see the section on migrations.
You have to create other games and test the rejection on them. Notice the incrementing game ID.
Above, Alice creates a game and rejects it immediately. This returns:
Correct result, because nobody played a move.
Above, Alice creates a game and Bob rejects it immediately. This returns:
Correct again, because nobody played a move.
Black plays and rejects:
Above, Alice creates a game, makes a move, and then rejects the game. This returns:
Correct: the request fails, because Alice has already played a move.
Alice plays and Bob rejects:
Above, Alice creates a game, makes a move, and Bob rejects the game. This returns:
Correct: Bob has not played a move yet, so he can still reject the game.
Alice & Bob play, Bob rejects:
Above, Alice creates a game and makes a move, then Bob makes a poor move and rejects the game. This returns:
Correct: this time Bob could not reject the game because the state recorded his move in
To belabor the point made in the earlier box: if you change your code, think about what it means for the current state of the chain and whether you end up in a broken state.
In this case, you could first introduce the
MoveCount and its handling. Then when all games have been correctly counted, you introduce the rejection mechanism.
To summarize, this section has explored:
- How to use messages and handlers to build on the gameplay functionalities of your application by adding the capacity for players to reject participating in a game.
- How to create a new
RejectGamemessage object including ID of the game to be rejected.
- How to add a new rule with the necessary additional information to prevent players from backing out of games in which they have already played moves, and how to declare new errors that respond to attempts to break this new rule.
- How to add a unit test to check that games can be rejected by the game creator, the black player, and the red player under the approved circumstances, and to check that rejected games are removed and that events are emitted.
- How to interact via the CLI to confirm the new "game rejection" function is performing as required, and to be aware that preexisting games will permit incorrect game rejection due to your blockchain being in a broken state due to your subsequent changes.