# Can Play Query

synopsis

Make sure you have all you need before proceeding:

A player sends a MsgPlayMove when making a move. This message can succeed or fail for several reasons. One error situation is when the message represents an invalid move.

Players should be able to make sure that a move is valid before burning gas. To add this functionality, you need to create a way for the player to call the Move (opens new window) function without changing the game's state. Use a query because they are evaluated in memory and do not commit anything permanently to storage.

# New information

To run a query to check the validity of a move you need to pass:

  • The game ID, call the field IdValue.
  • player as queries do not have a signer.
  • The board position to start from: fromX and fromY.
  • The board position to land on: toX and toY.

The information to be returned is:

  • A boolean whether the move is valid, called Possible.
  • A text reason as to why the move is not valid, called Reason.

As with other data structures, you can create the query message object with Starport:

Copy $ starport scaffold query canPlayMove idValue player fromX:uint fromY:uint toX:uint toY:uint --module checkers --response possible:bool,reason

Among other files, you should now have this:

Copy message QueryCanPlayMoveRequest { string idValue = 1; string player = 2; uint64 fromX = 3; uint64 fromY = 4; uint64 toX = 5; uint64 toY = 6; } message QueryCanPlayMoveResponse { bool possible = 1; string reason = 2; } proto checkers query.proto View source

Starport has created the following boilerplate for you:

# Query handling

Now you need to implement the answer to the player's query in grpc_query_can_play_move.go. Differentiate between two types of errors:

  • Errors relating to the move, returning a reason.
  • Errors if a move test is impossible, returning an error.
  1. The game needs to be fetched. If it does not exist at all, you can return an error message because you did not test the move:

    Copy storedGame, found := k.GetStoredGame(ctx, req.IdValue) if !found { return nil, sdkerrors.Wrapf(types.ErrGameNotFound, types.ErrGameNotFound.Error(), req.IdValue) } x checkers keeper grpc_query_can_play_move.go View source
  2. Has the game already been won?

    Copy if storedGame.Winner != rules.NO_PLAYER.Color { return &types.QueryCanPlayMoveResponse{ Possible: false, Reason: types.ErrGameFinished.Error(), }, nil } x checkers keeper grpc_query_can_play_move.go View source
  3. Is the player given a valid player?

    Copy var player rules.Player if strings.Compare(rules.RED_PLAYER.Color, req.Player) == 0 { player = rules.RED_PLAYER } else if strings.Compare(rules.BLACK_PLAYER.Color, req.Player) == 0 { player = rules.BLACK_PLAYER } else { return &types.QueryCanPlayMoveResponse{ Possible: false, Reason: types.ErrCreatorNotPlayer.Error(), }, nil } x checkers keeper grpc_query_can_play_move.go View source
  4. Is it the player's turn?

    Copy game, err := storedGame.ParseGame() if err != nil { return nil, err } if !game.TurnIs(player) { return &types.QueryCanPlayMoveResponse{ Possible: false, Reason: types.ErrNotPlayerTurn.Error(), }, nil } x checkers keeper grpc_query_can_play_move.go View source
  5. Attempt the move and report back:

    Copy _, moveErr := game.Move( rules.Pos{ X: int(req.FromX), Y: int(req.FromY), }, rules.Pos{ X: int(req.ToX), Y: int(req.ToY), }, ) if moveErr != nil { return &types.QueryCanPlayMoveResponse{ Possible: false, Reason: fmt.Sprintf(types.ErrWrongMove.Error(), moveErr.Error()), }, nil } x checkers keeper grpc_query_can_play_move.go View source
  6. If all went fine:

    Copy return &types.QueryCanPlayMoveResponse{ Possible: true, Reason: "ok", }, nil x checkers keeper grpc_query_can_play_move.go View source

# Next up

Do you want to give players more flexibility on which tokens they can use for the checkers blockchain's games? Let players wager any fungible token in the next section.