Skip to content
On this page

3. New model โ€‹

INFO

This step is optional. It is only needed if you want to define a new game with new rules. To get started, it may be easier to use one of the pre-defined example models.

A model is a piece of code that determine the rules of a game. They consist of two lambdas: init and apply_.

init takes a set of parameters and returns the initial state of the game. apply_ takes a current state, a move, and a current state metadata. It returns a new state and, if the game is finished, an outcome. The outcome can be mapped to a settlement, using a list defined at game creation.

Push a new model on the platform โ€‹

TIP

The model_wrap function of model_wrap.py returns the structure expected by the new_model entrypoint.

Click me to view the code
python
import smartpy as sp
gp         = sp.io.import_template("state_channel_games/game_platform.py")
Tictactoe  = sp.io.import_template("state_channel_games/models/tictactoe.py").Tictactoe
model_wrap = sp.io.import_template("state_channel_games/model_wrap.py").model_wrap

@sp.add_test(name="New Model")
def test():
    sc = sp.test_scenario()
    player1 = sp.test_account("player1")
    platform_address = sp.address('KT1_ADDRESS_OF_THE_PLATFORM'),
    platform = gp.GamePlatform(admins = sp.set([player1.address]), self_addr = platform_address)
    sc += platform

    # New Model
    tictactoe = Tictactoe()
    model = sc.compute(model_wrap.model_wrap(tictactoe))
    model_id = sc.compute(model_wrap.model_id(model))
    platform.new_model(model).run(sender = player1.address)

Compute the model_id โ€‹

The model_id is a BLAKE2B hash of the parameters model given to new_model.

python
import smartpy as sp
model_wrap   = sp.io.import_template("state_channel_games/model_wrap.py").model_wrap
Tictactoe    = sp.io.import_template("state_channel_games/models/tictactoe.py").Tictactoe

tictactoe = Tictactoe()
model     = sc.compute(model_wrap.model_wrap(tictactoe))
model_id  = sc.compute(model_wrap.model_id(model))

How to code the model? โ€‹

WARNING

You should never use blockchain operations instructions inside your lambda.

The complete list of instructions is available in the Michelson Reference. It includes:
AMOUNT, BALANCE, CONTRACT, CREATE_CONTRACT, IMPLICIT_ACCOUNT, LEVEL, NOW, SELF, SELF_ADDRESS, SENDER, SET_DELEGATE, SOURCE, TOTAL_VOTING_POWER, TRANSFER_TOKENS, VOTING_POWER.

You should never accept a game with a model that contains one of these instructions. More explanations here

Model class โ€‹

A model is a python class that defines the following attributes:

  • name: A string with the name of the model
  • t_init_input: SmartPy type of the parameter of init
  • t_game_state: SmartPy type of the game state
  • t_move_data: SmartPy type of the parameter of a play move
  • t_outcome: list of the model outcomes

And two methods:

  • apply_: How the game acts after a play move`
  • init: How the game is initialized
python
@sp.entrypoint
def admin_new_model(self, metadata, model, permissions, rewards):
    # The metadata of the model.
    sp.set_type(metadata, sp.TMap(sp.TString, sp.TBytes))
    # The model
    sp.set_type(model, sp.TBytes)
    # Model's permissions
    sp.set_type(permissions, sp.TMap(sp.TString, sp.TBytes))
    # A list of transfers from the platform to reward the creators of the model.
    sp.set_type(rewards,
        sp.TList(sp.TRecord(
            amount=sp.TNat,
            to_=sp.TAddress,
            token_id=sp.TNat).layout(('to_', ('token_id', 'amount'))))
    )
python
class MyModel:
    def __init__(self):
        # Name of the model
        # Example: "Tictactoe"
        self.name = ...
        # Type of the unpacked `model_init` params
        # Example: sp.TUnit
        self.t_initial_config = ...
        # Type of the unpacked game state
        # Example: sp.TMap(sp.TInt, sp.TMap(sp.TInt, sp.TInt))
        self.t_game_state = ...
        # Type of the unpacked `mode_data`
        # Example: sp.TRecord(i=sp.TInt, j=sp.TInt)
        self.t_move_data =
        # List of possible outcomes
        # Example: ["draw", "player_1_won", "player_2_won"]
        self.t_outcome = ...

    def model_init(self, params):
        """The lambda that will be called when instantiating a new game."""
        sp.set_type(params, sp.TBytes)  # The game params
        # The initial state computation and checks
        sp.result(sp.pack(...))  # The game's initial state

    def model_apply(self, apply_input):
        """The lambda that will be called every time someone is playing."""
        sp.set_type(apply_input, sp.TRecord(
            move_data=sp.TBytes,  # Move data given to the model
            move_nb=sp.TNat,  # Incremental id of the move. (Managed by the platform.)
            player=sp.TNat,  # Id of the current player. (Managed by the platform)
            state=sp.TBytes,  # Current state of the game. (Last state returned by the model)
        ))
        # A new state computed from inputs.
        result = ...
        sp.result(sp.set_type_expr(result,
            sp.TPair(
                sp.TBytes,  # New state of the game
                # Outcome: If `None`, the game continues.
                #          Otherwise the game ends with the given outcome.
                sp.TOption(sp.TString),
            )
        ))

Model wrap โ€‹

model_wrap from model_wrap.py is a helper that takes an instance of your class and returns a record that you can give to the new_model entrypoint.

model_wrap automatically

packs and unpacks your inputs and outputs. So the game platform only stores bytes.

The gameTester let you test your model without the wrapping process and channels complexity. :::

init lambda โ€‹

The init lambda takes one parameter of the type defined in t_init_input.

It returns (by calling sp.result) a game state of the type defined in t_game_state.

apply_ lambda โ€‹

The apply_ lambda takes 4 parameters:

  • move_data of type t_move defined in t_move_data
  • move_nb of type nat: the id of the current move
  • player of type int: the id of the current player
  • state of type defined in t_game_state

It returns (by calling sp.result) a pair of

  • new_state of type defined in t_game_state
  • outcome (see outcomes)

INFO

The outcome returned by apply_ is surrounded by sp.bounded because model_wrap.py verifies that it is listed in self.t_outcome.

Example โ€‹

Example with the simplest model: transfer.py

python
import smartpy as sp

class Transfer:
    def __init__(self):
        self.name = "Transfer"
        self.t_init_input = sp.TUnit
        self.t_game_state = sp.TUnit
        self.t_move_data  = sp.TUnit
        self.t_outcome    = ["transferred"]

    def apply_(self, move_data, move_nb, player, state):
        sp.result(
            sp.pair(
                sp.unit,
                sp.some(sp.bounded("transferred"))
            )
        )

    def init(self, params):
        sp.result(sp.unit)

The rest is SmartPy code that you can learn and try on SmartPy.io.

We've build a lot of models that can be used as examples. Also, you can always contact us on Telegram

Game tester โ€‹

Game tester is a little platform that let you test your models in a simple environment. Examples of its usage can be found in the tictactoe template

Models permissions โ€‹

Models can have permissions to mint platform tokens. see tokens