Build the Single-player Game Model Service


You will need to have these installed:

Have the Games API service is running on http://localhost:8100/.


In a separate terminal, create a new virtualenv called ‘calc-model’:

$ mkvirtualenv calc-model

Install Django

$ pip install Django~=1.11

Create a Django project folder and rename it to serve as a git repository

$ django-admin startproject calc_model
$ mv calc_model calc-model

Change to the project folder:

$ cd calc-model
$ add2virtualenv .

Create a requirements.txt file that installs the simpl-modelservice and unit testing apps:


# tests

Install these requirements along with their dependencies:

$ pip install -r requirements.txt

Please note, if DJANGO_SETTINGS_MODULE is leftover from a previous session, you may need to unset it:


Create a django app that will contain your game logic:

$ ./ startapp game

Add the following to your INSTALLED_APPS in calc_model/


CALLBACK_URL = os.environ.get('CALLBACK_URL', 'http://{hostname}:{port}/callback')

SIMPL_GAMES_URL = os.environ.get('SIMPL_GAMES_URL', 'http://localhost:8100/apis')

SIMPL_GAMES_AUTH = ('[email protected]', 'simpl')

ROOT_TOPIC = 'world.simpl.sims.calc'

It’s highly recommended that you set a 'users' cache. Since the modelservice will run single-threaded, you can take advantage of the locmem backend:

    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    'users': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'users',


For simplicity, we’re going to create a single-player Game in which each player has a Scenario that can advance multiple periods.

In your game app module, define our model in

class Model(object):
    The model adds an operand to the previous total and returns the result.

    def step(self, operand, prev_total=0.0):
            operand - current period's decision
            prev_total - the calculated total from the previous period
        Returns new total
        return operand + prev_total

In your game app module, add a unit test directory tests and a model unit test tests/

import pytest
from test_plus.test import TestCase

from game.models import Model

class ModelTestCase(TestCase):
    def setUp(self):
        self.m = Model()

    def test_create(self):
        m = Model()
        self.assertNotEqual(m, None)

    def test_first_step(self):
        m = Model()
        total = m.step(5)
        self.assertEquals(total, 5)

    def test_increase_step(self):
        m = Model()
        total = m.step(5, 3)
        self.assertEquals(total, 8)

    def test_decrease_step(self):
        m = Model()
        total = m.step(5, -2.5)
        self.assertEquals(total, 2.5)

Run your unit test:

$ export DJANGO_SETTINGS_MODULE=calc_model.settings
$ py.test

Create a management command that will create your game and initialize it with one run, a leader and 2 players.

Create a management folder in the game folder and add an empty file.

Create a commands folder in the game/management folder and add an empty file.

Finally, create a script in the game/management/commands folder containing this code:

import djclick as click

from modelservice.simpl import games_client
from modelservice.utils.asyncio import coro

def echo(text, value):
    click.echo(, fg='green') + '{0}'.format(value)

async def delete_default_run(api_session):
    """ Delete default Run """
    echo('Resetting the Calc game default run...', ' done')
    runs = await api_session.runs.filter(game_slug='calc')
    for run in runs:
        if == 'default':
            await api_session.runs.delete(

@click.option('--reset', default=False, is_flag=True,
              help="Delete default game run and recreate it from scratch")
async def command(reset):
    Create and initialize Calc game.
    Create a "default" Calc run.
    Set the run phase to "Play".
    Add 1 leader ("leader") to the run
    Add 2 players ("s1", "s2") to the run.
    Add a scenario and period 1 for each player.

    async with games_client as api_session:

        # Handle resetting the game
        if reset:
            if click.confirm(
                    'Are you sure you want to delete the default game run and recreate from scratch?'):
                await delete_default_run(api_session)

        # Create a Game
        game = await
        echo('getting or creating game: ',

        # Create game Phases ("Play")
        play_phase = await api_session.phases.get_or_create(
        echo('getting or creating phase: ',

        # Add run with 2 players ready to play
        run = await add_run(game, 'default', 2, play_phase, api_session)

        echo('Completed setting up run: id=',

async def add_run(game, run_name, user_count, phase, api_session):
    # Create or get the Run
    run = await api_session.runs.get_or_create(,
    echo('getting or creating run: ',

    # Set run to phase
    run.phase =
    echo('setting run to phase: ',

    fac_user = await api_session.users.get_or_create(
        email='[email protected]',
    echo('getting or creating user: ',

    fac_runuser = await api_session.runusers.get_or_create(,,
    echo('getting or creating leader runuser for user: ',

    for n in range(0, user_count):
        user_number = n + 1
        # Add player to run
        await add_player(user_number, run, api_session)

    return run

async def add_player(user_number, run, api_session):
    """Add player with name based on user_number to run with role"""

    username = 's{0}'.format(user_number)
    first_name = 'Student{0}'.format(user_number)
    email = '{0}'.format(username)

    user = await api_session.users.get_or_create(
    echo('getting or creating user: ',

    runuser = await api_session.runusers.get_or_create(,,
        defaults={"role": None}
    echo('getting or creating runuser for user: ',

    await add_runuser_scenario(runuser, api_session)

async def add_runuser_scenario(runuser, api_session):
    """Add a scenario named 'Scenario 1' to the runuser"""

    scenario = await api_session.scenarios.get_or_create(,
        name='Scenario 1',
    click.echo('getting or creating runuser {} scenario: {}'.format(,

    period = await api_session.periods.get_or_create(,
    click.echo('getting or creating runuser {} period 1 for scenario: {}'.format(,

Run your command:

$ export DJANGO_SETTINGS_MODULE=calc_model.settings
$ ./ create_default_env

Every player’s submission will be a Decision saved on the current Period. The model will then produce a Result for the current Period, and the player’s Scenario will step to the next Period.

In your game app module, create a file called Next, add save_decision and step_scenario functions to perform these steps:

from modelservice.simpl import games_client
from .models import Model

async def save_decision(period_id, decision):
    # add decision to period
    async with games_client as api_session:
        decision = await api_session.decisions.get_or_create(
            data={"operand": decision},
            defaults={"role": None}
        return decision

async def step_scenario(scenario_id):
    Step the scenario's current period
    async with games_client as api_session:
        periods = await api_session.periods.filter(scenario=scenario_id,
        period_count = len(periods)
        period = periods[period_count - 1]

        operand = 0.0
        period_decisions = await api_session.decisions.filter(
        if len(period_decisions) > 0:
            operand = float(period_decisions[0].data["operand"])

        prev_total = 0.0
        if period_count > 1:
            prev_period = periods[period_count - 2]
            prev_period_results = \
                await api_session.results.filter(
            if len(prev_period_results) > 0:
                prev_total = float(prev_period_results[0].data["total"])

        # step model
        model = Model()
        total = model.step(operand, prev_total)
        data = {"total": total}

        result = await api_session.results.get_or_create(
            defaults={"role": None}

        # prepare for next step by adding a new period
        next_period_order = period.order + 1
        next_period = await api_session.periods.get_or_create(


In your game app module, create a file called with the following content:

from import Period, Game
from import subscribe, register

from .runmodel import step_scenario, save_decision

class CalcPeriod(Period):
    async def submit_decision(self, operand, **kwargs):
        Receives the operand played and stores as a ``Decision`` then
        steps the model saving the ``Result``. A new ``Period`` is added to
        scenario in preparation for the next decision.
        # Call will prefix the ROOT_TOPIC
        # "world.simpl.sims.calc.model.period.1.submit_decision"

        for k in kwargs:
  "submit_decision: Key: {}".format(k))

        await save_decision(, operand)"submit_decision: saved decision")

        await step_scenario("submit_decision: stepped scenario")

Game.register('calc', [

NOTE: if you want to use a filename other than you must ensure the file is imported somewhere, usually in a somewhere for the @game decorator to find and register your game into the system.

You can start your model service by running:

$ export DJANGO_SETTINGS_MODULE=calc_model.settings
$ ./ run_modelservice

By default the service will bind to

This concludes the tutorial on building a single-player game Model Service. A completed example implementation is available at that uses the game slug simpl-calc.

You can now head over to the Single-player Game Frontend tutorial.