# Message Handler - Create and Save a Game Properly

Make sure you have everything you need before proceeding:


In this section, you will:

  • Add application rules- the rules of checkers.
  • Add a Message Handler to create game and return its ID.

In the previous section you added the message to create a game along with its serialization and dedicated gRPC function with the help of Ignite CLI.

Now you must add code that:

  • Creates a brand new game.
  • Saves it in storage.
  • Returns the ID of the new game.

Ignite CLI isolated this concern into a separate file, x/checkers/keeper/msg_server_create_game.go, for you to edit:

Copy func (k msgServer) CreateGame(goCtx context.Context, msg *types.MsgCreateGame) (*types.MsgCreateGameResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // TODO: Handling the message _ = ctx return &types.MsgCreateGameResponse{}, nil } x checkers keeper msg_server_create_game.go View source

Ignite CLI has conveniently created all the message processing code for you. You are only required to code the key features.

Given that you have already done a lot of preparatory work, what coding is involved? How do you replace // TODO: Handling the message?

  • First, rules represents the ready-made file with the imported rules of the game:

    Copy import ( rules "github.com/alice/checkers/x/checkers/rules" ) x checkers keeper msg_server_create_game.go View source
  1. Get the new game's ID with the Keeper.GetNextGame (opens new window) function created by the ignite scaffold single nextGame... command:

    Copy nextGame, found := k.Keeper.GetNextGame(ctx) if !found { panic("NextGame not found") } newIndex := strconv.FormatUint(nextGame.IdValue, 10) x checkers keeper msg_server_create_game.go View source
  2. Create the object to be stored:

    Copy newGame := rules.New() storedGame := types.StoredGame{ Creator: msg.Creator, Index: newIndex, Game: newGame.String(), Turn: rules.PieceStrings[newGame.Turn], Red: msg.Red, Black: msg.Black, } x checkers keeper msg_server_create_game.go View source

    Note the use of:

    • The rules.New() (opens new window) command, which is part of the Checkers rules file you imported earlier.
    • The string content of the msg *types.MsgCreateGame namely .Creator, .Red, and .Black.
  3. Confirm that the values in the object are correct by checking the validity of the players' addresses:

    Copy err := storedGame.Validate() if err != nil { return nil, err } x checkers keeper msg_server_create_game.go View source

    .Creator, .Red, and .Black need to be checked because they were copied as strings. The check on .Creator is redundant because at this stage the message's signatures have been verified, and the creator is the signer.

  4. Save the StoredGame object using the Keeper.SetStoredGame (opens new window) function created by the ignite scaffold map storedGame... command:

    Copy k.Keeper.SetStoredGame(ctx, storedGame) x checkers keeper msg_server_create_game.go View source
  5. Prepare the ground for the next game using the Keeper.SetNextGame (opens new window) function created by Ignite CLI:

    Copy nextGame.IdValue++ k.Keeper.SetNextGame(ctx, nextGame) x checkers keeper msg_server_create_game.go View source
  6. Return the newly created ID for reference:

    Copy return &types.MsgCreateGameResponse{ IdValue: newIndex, }, nil x checkers keeper msg_server_create_game.go View source

# Unit tests

Try the unit test you prepared in the previous section again:

Copy $ go test github.com/alice/checkers/x/checkers/keeper

This should fail with:

Copy panic: NextGame not found [recovered] panic: NextGame not found ...

Your keeper was initialized with an empty genesis. You must fix that one way or another.

You can fix this by initializing the keeper with the default genesis. Initializing the MsgServer with the default genesis is opinionated, so it is better to keep this opinion closest to the tests. Copy the setupMsgServer from msg_server_test.go (opens new window) into your msg_server_create_game_test.go. Modify it to also return the keeper:

Copy func setupMsgServerCreateGame(t testing.TB) (types.MsgServer, keeper.Keeper, context.Context) { k, ctx := setupKeeper(t) checkers.InitGenesis(ctx, *k, *types.DefaultGenesis()) return keeper.NewMsgServerImpl(*k), *k, sdk.WrapSDKContext(ctx) } x checkers keeper msg_server_create_game_test.go View source

Note the new import:

Copy import ( "github.com/xavierlepretre/checkers/x/checkers" ) x checkers keeper msg_server_create_game_test.go View source

Unfortunately, this created an import cycle. To fix that, use the better practice of suffixing _test to the package of your test files:

Copy package keeper_test x checkers keeper msg_server_create_game_test.go View source

You should fix the package in all the (opens new window) other (opens new window) test (opens new window) files (opens new window) in your keeper folder. Afterward, run the tests again with the same command as before:

Copy $ go test github.com/alice/checkers/x/checkers/keeper

The error has changed, and you need to adjust the expected value as per the default genesis:

Copy require.EqualValues(t, types.MsgCreateGameResponse{ IdValue: "1", }, *createResponse) x checkers keeper msg_server_create_game_test.go View source

One unit test is good, but you can add more, in particular testing whether the values in storage are as expected when you create a single game:

Copy func TestCreate1GameHasSaved(t *testing.T) { msgSrvr, keeper, context := setupMsgServerCreateGame(t) msgSrvr.CreateGame(context, &types.MsgCreateGame{ Creator: alice, Red: bob, Black: carol, }) nextGame, found := keeper.GetNextGame(sdk.UnwrapSDKContext(context)) require.True(t, found) require.EqualValues(t, types.NextGame{ Creator: "", IdValue: 2, }, nextGame) game1, found1 := keeper.GetStoredGame(sdk.UnwrapSDKContext(context), "1") require.True(t, found1) require.EqualValues(t, types.StoredGame{ Creator: alice, Index: "1", Game: "*b*b*b*b|b*b*b*b*|*b*b*b*b|********|********|r*r*r*r*|*r*r*r*r|r*r*r*r*", Turn: "b", Red: bob, Black: carol, }, game1) } x checkers keeper msg_server_create_game_test.go View source

Or when you create 3 (opens new window) games. Other tests could include whether the get all functionality works as expected after you have created 1 game (opens new window), or 3 (opens new window), or if you create a game in a hypothetical far future (opens new window). Also add games with badly formatted (opens new window) or missing input (opens new window).

# Interact via the CLI

Now you must confirm that the transaction creates a game. Start with:

Copy $ ignite chain serve

Send your transaction as you did in the previous section:

Copy $ checkersd tx checkers create-game $alice $bob --from $alice --gas auto

A good sign is that the output gas_used is slightly higher than it was before (gas_used: "50671"). Confirm the current state:

Now your game is in the blockchain's storage. Notice how bob was given the black pieces and it is already his turn to play. As a note for the next sections, this is how to understand the board:

Copy *b*b*b*b|b*b*b*b*|*b*b*b*b|********|********|r*r*r*r*|*r*r*r*r|r*r*r*r* ^X:1,Y:2 ^X:3,Y:6

Or if placed in a square:

Copy X 01234567 *b*b*b*b 0 b*b*b*b* 1 *b*b*b*b 2 ******** 3 ******** 4 r*r*r*r* 5 *r*r*r*r 6 r*r*r*r* 7 Y

You can also get this in a one-liner:

# Next up

You will modify this handling in the next sections by:

Now that a game is created, it is time to play it. That is the subject of the next section.