# The Play Game Elements

synopsis

Make sure you have all you need before proceeding:

To play a game a player only needs to specify:

  • The ID of the game the player wants to join. Call the field idValue.
  • The initial positions of the pawn. Call the fields fromX and fromY and make them uint.
  • The final position of the pawn after a player's move. Call the fields toX and toY to be uint too.

The player does not need to be explicitly added as a field in the message because the player is implicitly the signer of the message. Name the object PlayMove.

Unlike when creating the game, you want to return:

  • The game ID again. Call this field idValue.
  • The captured piece, if any. Call the fields capturedX and capturedY.
  • The winner in the field winner.

# With Starport

Now Starport only creates a response object with a single field. You can update the object after Starport has run:

Copy $ starport scaffold message playMove idValue fromX:uint fromY:uint toX:uint toY:uint --module checkers --response idValue

Starport once more creates all the necessary Protobuf files and the boilerplate for you. All you have left to do is:

  • Add the missing fields to the response in proto/checkers/tx.proto:

    Copy message MsgPlayMoveResponse { string idValue = 1; int64 capturedX = 2; int64 capturedY = 3; string winner = 4; } proto checkers tx.proto View source

    Use int64 here so that you can enter -1 when no pawns have been captured.

  • Fill in the needed part in x/checkers/keeper/msg_server_play_move.go:

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

    Where the TODO is replaced as per below.

# The move handling

rules represent the ready-made file containing the rules of the game you imported earlier. Declare them in x/checkers/types/errors.go given your code has to handle new error situations:

Copy ErrGameNotFound = sdkerrors.Register(ModuleName, 1104, "game by id not found: %s") ErrCreatorNotPlayer = sdkerrors.Register(ModuleName, 1105, "message creator is not a player: %s") ErrNotPlayerTurn = sdkerrors.Register(ModuleName, 1106, "player tried to play out of turn: %s") ErrWrongMove = sdkerrors.Register(ModuleName, 1107, "wrong move") x checkers types errors.go View source

Take the following steps to replace the TODO:

  1. Fetch the stored game information:

    Copy storedGame, found := k.Keeper.GetStoredGame(ctx, msg.IdValue) if !found { return nil, sdkerrors.Wrapf(types.ErrGameNotFound, "game not found %s", msg.IdValue) } x checkers keeper msg_server_play_move.go View source

    Using the Keeper.GetStoredGame (opens new window) function created by Starport.

  2. Is the player legitimate? Check with:

    Copy var player rules.Player if strings.Compare(storedGame.Red, msg.Creator) == 0 { player = rules.RED_PLAYER } else if strings.Compare(storedGame.Black, msg.Creator) == 0 { player = rules.BLACK_PLAYER } else { return nil, types.ErrCreatorNotPlayer } x checkers keeper msg_server_play_move.go View source

    Using the certainty that the MsgPlayMove.Creator has been verified by its signature (opens new window).

  3. Instantiate the board to implement the rules:

    Copy game, err := storedGame.ParseGame() if err != nil { panic(err.Error()) } x checkers keeper msg_server_play_move.go View source

    Good thing you previously created this helper (opens new window).

  4. Is it the player's turn? Check with:

    Copy if !game.TurnIs(player) { return nil, types.ErrNotPlayerTurn } x checkers keeper msg_server_play_move.go View source

    Using the rules file's own TurnIs (opens new window) function.

  5. Properly conduct the move:

    Copy captured, moveErr := game.Move( rules.Pos{ X: int(msg.FromX), Y: int(msg.FromY), }, rules.Pos{ X: int(msg.ToX), Y: int(msg.ToY), }, ) if moveErr != nil { return nil, sdkerrors.Wrapf(moveErr, types.ErrWrongMove.Error()) } x checkers keeper msg_server_play_move.go View source

    Again using the rules proper Move (opens new window) function.

  6. Prepare the updated board to be stored and store the information:

    Copy storedGame.Game = game.String() storedGame.Turn = game.Turn.Color k.Keeper.SetStoredGame(ctx, storedGame) x checkers keeper msg_server_play_move.go View source

    Updating the fields that were modified using the Keeper.SetStoredGame (opens new window) function just as when you created and saved the game.

  7. Return relevant information regarding the move's result:

    Copy return &types.MsgPlayMoveResponse{ IdValue: msg.IdValue, CapturedX: int64(captured.X), CapturedY: int64(captured.Y), Winner: game.Winner().Color, }, nil x checkers keeper msg_server_play_move.go View source

    The Captured and Winner information would be lost if you do not. More accurately, one would have to replay the transaction to find out the values. Better be a good citizen and make this information easily accessible.

That is all there is to it: good preparation and the use of Starport.

# Next up

Before you add a third Message to let a player reject a game, add events to the existing message handlers for relevant information. That is the object of the next section.

If you want to skip ahead and see how you can assist a player in not submitting a transaction that would result in a failed move, you can create a query to test a move.