# A Game Winner

synopsis

Make sure you have all you need before proceeding:

To be able to forcibly terminate games and avoid terminating one game twice, you need to identify games that have been terminated. A good field to add is one for the winner. It needs to contain:

  • The rightful winner of a game that reaches completion.
  • Or, the winner by forfeit, when a game is expired.
  • Or a neutral value when the game is active.

In this exercise a draw is not handled and it would require yet another value to save in winner.

# New information

In the StoredGame Protobuf definition file:

Copy message StoredGame { ... string winner = 11; } proto checkers stored_game.proto View source

To have Starport and Protobuf recompile this file use:

Copy $ starport generate proto-go

Add a helper function to get the winner's address, if it exists. A good place for it is in full_game.go:

Copy func (storedGame *StoredGame) GetPlayerAddress(color string) (address sdk.AccAddress, found bool, err error) { red, err := storedGame.GetRedAddress() if err != nil { return nil, false, err } black, err := storedGame.GetBlackAddress() if err != nil { return nil, false, err } address, found = map[string]sdk.AccAddress{ rules.RED_PLAYER.Color: red, rules.BLACK_PLAYER.Color: black, }[color] return address, found, nil } func (storedGame *StoredGame) GetWinnerAddress() (address sdk.AccAddress, found bool, err error) { address, found, err = storedGame.GetPlayerAddress(storedGame.Winner) return address, found, err } x checkers types full_game.go View source

# Update and check for the winner

This is a two-part update. You set the winner where relevant but you also introduce new checks, so that a game with a winner cannot be acted upon.

Start with a new error that you define as a constant:

Copy ErrGameFinished = sdkerrors.Register(ModuleName, 1111, "game is already finished") x checkers types errors.go View source

Then at creation, in the create game message handler, you start with a neutral value:

Copy ... storedGame := types.StoredGame{ ... Winner: rules.NO_PLAYER.Color, } x checkers keeper msg_server_create_game.go View source

With further checks when handling a play in the handler:

  1. Check that the game has not finished yet:

    Copy if storedGame.Winner != rules.NO_PLAYER.Color { return nil, types.ErrGameFinished } x checkers keeper msg_server_play_move.go View source
  2. Update the winner field, which remains neutral (opens new window) if there is no winner yet:

    Copy storedGame.Winner = game.Winner().Color x checkers keeper msg_server_play_move.go View source
  3. Handle the FIFO differently depending on whether the game is finished or not:

    Copy if storedGame.Winner == rules.NO_PLAYER.Color { k.Keeper.SendToFifoTail(ctx, &storedGame, &nextGame) } else { k.Keeper.RemoveFromFifo(ctx, &storedGame, &nextGame) } x checkers keeper msg_server_play_move.go View source

Just in case, when rejecting a game, in its handler:

Copy if storedGame.Winner != rules.NO_PLAYER.Color { return nil, types.ErrGameFinished } x checkers keeper msg_server_reject_game.go View source

Confirm it compiles and you are ready to handle the expiration of games.

# Next up

You have introduced a game FIFO, a game deadline, and a game winner. Time to turn your attention to the next section.