2. Game behaviour in the template game

In this section…

2.1. Modify SublevelBehaviour

Authors

Thomas J. Naughton (2018-), Scott Fitzgerald (2018)

(found in logic.py)

The SubLevelBehaviour object describes all the parameters needed to randomly generate a new instance of one’s game with a specific difficulty level. In the template, this is specified with the code:

SublevelBehaviour = namedtuple('SublevelBehaviour', ('param1', 'param2'))

where param1 specifies the length of the list of numbers, and param2 specifies how many digits should be in each number. They are given unhelpful names param1 and param2 in the template to remind you to change them to something meaningful. If this was a real game, they might have been called list_length and num_digits, respectively.

You should decide what they are for your game. There can be any number of parameters in your version of SublevelBehaviour.

Tip

The most sophisticated games may need four parameters in their SublevelBehaviour. Probably every game needs at least two.

2.1.1. Need help deciding what parameters to choose?

One way to think about what parameters your game needs is to ask yourself what parameters most affect the difficulty of a game round of your game, and list them in order of how much they affect difficulty. For the template game, it was decided that the length of the list of numbers has the greatest effect on the difficult of an instance of the game, and that next is the length of each decimal number. No third parameter comes to mind, so it seems that just two parameters are sufficient to control the difficulty of the game.

Another way to think about what parameters you need is as follows. We want all children to play the game at the same time on individual screens in a classroom. They should each get a unique instance of the game when they start (i.e. the game instance should be randomly generated) but this starting game round should have the same difficulty level for each child. Given the infinite number of possible different game instances (in the case of the template game think of the infinite number of different possible lists of numbers) what properties of these game instances would you need to fix in order to get a set of game instances with equal difficulty? Whatever answer you come up with (in the case of the template game it would be length of the list and length of the numbers), that set of properties is likely to be exactly the set of parameters you should have in your game’s SublevelBehaviour.

2.1.2. Sufficiency

After choosing parameters for your game you should double check that those parameters are sufficient. To do this ask yourself the following.

Sufficiency: for a particular sequence of values for your parameters, can you say that all possible game rounds generated from those values are equally difficult?

To help you understand this question, here’s an example. For the template game, let’s say the pair of parameters was SublevelBehaviour(4, 3). Can we say that all game rounds consisting of lists of 4 numbers each with 3 digits are equally difficult? Here are four examples randomly generated by the game to help you visualise this:

[106, 705, 477, 770]
[775, 678, 218, 220]
[876, 254, 626, 263]
[689, 468, 127, 563]

It seems we can say that for the template game, yes, each of these four instances of the game is equally difficult. Therefore, the parameters chosen for the template game are sufficient.

If you cannot answer positively to the sufficiency question for you own game’s SublevelBehaviour then you need to add another parameter.

2.1.3. Hints on choosing the parameter type

Where a parameter has only two possible values, a bool works well. For example,

  • Parameters Easy and Hard could be represented with a bool with name easy with possible values True and False, respectively.

  • Parameters Odd and Even could be represented with a bool with name is_even with possible values False and True, respectively.

Where a parameter has three or more values, but still a small number of values, you could get away in the short term by using an integer to represent that small number of choices. However, in the long term, an enumerated type (see section “Creating your own enumerated type”) is appropriate. For example,

  • Parameters Easy, Medium, and Hard could be represented with an enumerated type with name GameMode with possible values GameMode.Easy, GameMode.Medium, and GameMode.Hard.

  • Parameters Plain, Spots, Stars, and Stripes could be represented with an enumerated type with name GameMode with possible values GameMode.Plain, GameMode.Spots, GameMode.Stars, and GameMode.Stripes.

Tip

It is possible to add additional parameters to SublevelBehaviour at a later time as the design of your game evolves.

2.2. Modify SublevelBehaviour docstrings

(found in logic.py)

Each parameter in SublevelBehaviour needs a docstring that describes it. The docstrings are re-used for the command-line help and appear in the web app as information for fellow developers and teachers, so please put effort into them. In the template, they are specified with the code:

SublevelBehaviour.param1.__doc__ = """A positive int that specifies the
    length of the list of numbers."""
SublevelBehaviour.param2.__doc__ = """A positive int that specifies how
    many digits are in each number."""

The names param1 and param2 need to be changed to match the names in your SublevelBehaviour. You should give some thought to the type of each parameter; is it an integer, a string, a bool, or something else? If it is an int, is zero and are negative values allowed?

2.2.1. Examples of SublevelBehaviour docstrings in CTGames

Just Reverse has only one parameter, an integer:

SublevelBehaviour.length.__doc__ = """A positive int that specifies the
        length of the word to reverse."""

Just Addition has a Boolean as one of its three parameters:

SublevelBehaviour.maximum.__doc__ = """A positive int that specifies the
        maximum value for each number."""
SublevelBehaviour.subtraction.__doc__ = """A bool that specifies whether
        or not subtraction is to be used rather than the default
        addition."""
SublevelBehaviour.num_mcq_answers.__doc__ = """A positive int that
        specifies the number of multiple-choice answers in the current
        round."""

Most Frequent has an enumerated type parameter GameMode for which the possible values should be specified for someone playing the command-line version of the game (the function class_attributes returns a list of the names of attributes of a class or named tuple):

SublevelBehaviour.mode.__doc__ = f"""A game mode from the set
        {{{', '.join(class_attributes(GameMode))}}}."""
SublevelBehaviour.listlength.__doc__ = """A positive int that specifies
        the length of the list of fruit."""

2.2.2. Where will these docstrings appear in a game?

A CTGames developer can create a custom round by entering their own chosen values for the SublevelBehaviour parameters when running the command-line version of the game. This is done by entering a command of the form python cli.py followed by the directory of the game, followed by a sequence of values separated by spaces. For example, for a game newly-created from the template, in a directory called catchaball, one would create a custom round by opening a terminal in any directory and entering:

conda activate ctgames
cd ~/gitlab.cs.nuim.ie/ctgames/ctgames/CTGames/bin/
python cli.py catchaball 5 2

Here, SublevelBehaviour is expecting two ints, so that is why we entered two integers after the game directory name. If the type was bool we could have entered True or False. If the type was an enumerated type, we could have entered any of the members in the type, e.g. for Most Frequent we could have entered OnlyTwo or ThreeChooseOne.

In order to explain to other CTGames developers what are the SublevelBehaviour parameters in a custom round, the command-line version of each game has a parameter help message. To see it, assuming your newly-created game is in a directory called catchaball, open a terminal in any directory and enter:

conda activate ctgames
cd ~/gitlab.cs.nuim.ie/ctgames/ctgames/CTGames/bin/
python cli.py catchaball -p

For one of the templates, this parameter help message is:

Usage: cli.py gametemplatemcq PARAM1 PARAM2

  Play a custom fixed-behaviour round of the game Game Template MCQ
    (version 0.0.1, {{ date_last_modified }}).

  PARAM1  a positive int that specifies the length of the list of numbers.

  PARAM2  a positive int that specifies how many digits are in each
    number.

  As an example, consider cli.py gametemplatemcq 5 1

In the web app, the docstrings appear when the player clicks the Custom round button in the Teacher’s Area for a game.

2.3. Modify GAME_BEHAVIOUR

(found in behaviour.py)

How do the names in SublevelBehaviour get their values? There are two places in the framework where the names in SublevelBehaviour get specific values. One is described here.

The GAME_BEHAVIOUR object is used by the CTGames framework to choose the values for the parameters for SublevelBehaviour for the first game round and for each subsequent round. As players answer game rounds correctly they advance from level to level, with the game rounds getting more difficult each time. The levels are numbered from zero. Within a level, it is possible to have sublevels, also numbered from zero, for finer increases in difficulty. Combined, they are represented as a pair (level, sublevel).

Tip

Any large jumps in difficulty or newly introduced game mechanics in a game should be introduced with a new level. Only small increases in difficulty should be introduced with a new sublevel.

Tip

The levels can have different numbers of sublevels each, but probably for the player there should be some predictability to the number of levels, e.g. the same number of sublevels in each level, or proportional increases in the number of sublevels in a level as the level number increases.

Tip

For games for younger children there should be 3 or 4 levels with between 3 or 4 sublevels in each level. For games for older children, with presumably more variation in the games, there should be between 4 and 6 levels with between 4 and 6 sublevels in each level.

In the template, GAME_BEHAVIOUR is specified with the code:

GAME_BEHAVIOUR.update(
    {
        L(0, 0): SB(2, 1),
        L(1, 0): SB(4, 2),
        L(2, 0): SB(6, 3),
        }
    )

This states that in level (0, 0), the length of the list is 2 and the numbers will be randomly chosen from the range [0, 9]. In level (2, 0) the length of the list is 6 and the numbers will be randomly chosen from the range [100, 999]. This is a very simple game, so the number of levels is probably fine. All that remains would be to add two more sublevels to each level, such as like this:

GAME_BEHAVIOUR.update(
    {
        L(0, 0): SB(2, 1),
        L(0, 1): SB(3, 1),
        L(0, 2): SB(3, 2),
        L(1, 0): SB(4, 2),
        L(1, 1): SB(5, 2),
        L(1, 2): SB(5, 3),
        L(2, 0): SB(6, 3),
        L(2, 1): SB(7, 3),
        L(2, 2): SB(7, 4),
        }
    )

You can see in this example there is a small increment in game difficulty from sublevel to sublevel. You have to do the same for your game.

Tip

Sublevel (0, 0) of each game should be extremely simple.

2.3.1. Wildcard values in GAME_BEHAVIOUR

It is possible to include wildcards as parameters in GAME_BEHAVIOUR. A wildcard is a value that is not defined until run-time.

They are implemented with the gamesbehaviour.AmbiguousValue class. AmbiguousValue objects can be used in place of any parameter in GAME_BEHAVIOUR. The options are

  • If you want to specify that a parameter can have any legal value but you don’t care which, you should use AmbiguousValue.Wild.

  • If you want to specify that a parameter should have the same value as was used in the round before, you should use AmbiguousValue.SameAsAbove.

  • If you want to specify that a parameter can have any value from an integer range of values, you should use AmbiguousValue.Range, and give it parameters according to the same syntax as Python’s built-in range.

Examples of wildcard use are shown below.

2.3.2. Specifying in GAME_BEHAVIOUR how to replay an incorrect round

In your game you may wish to be very specific about what happens if a player answers a round incorrectly. The choices are

  • replay the same round

  • randomly generate another instance of the game with the same difficulty level

  • randomly generate another instance of the game with an easier difficulty level (not implemented yet)

2.3.3. GAME_BEHAVIOUR examples from CTGames

Just Addition uses a combination of integer and Boolean parameters:

GAME_BEHAVIOUR.update(
    {
        L(0, 0): SB(4, False, 3),
        L(0, 1): SB(10, False, 4),
        L(1, 0): SB(10, True, 5),
        }
    )

Bowls uses a wildcard range of integer values for its second parameter:

GAME_BEHAVIOUR.update(
    {
        L(0, 0): SB(2, A.Range(3, 5), 5),
        L(0, 1): SB(3, A.Range(4, 6), 6),
        L(0, 2): SB(3, A.Range(5, 7), 7),
        .
        .
        .
        }
    )

Most Frequent uses a game mode enumerated type for its first parameter, and also uses a wildcard for this parameter in its final rounds to give the player a randomly generated selection of all the game modes that appeared in the game:

GAME_BEHAVIOUR.update(
    {
        L(0, 0): SB(G.OnlyTwo, 3),
        L(0, 1): SB(G.OnlyTwo, 4),
        L(0, 2): SB(G.OnlyTwo, 5),
        L(1, 0): SB(G.ThreeChooseOne, 6),
        .
        .
        .
        L(4, 2): SB(A.Wild, A.Range(18, 25)),
        }
    )

Ordered List uses ReplayIncorrectRound:

GAME_BEHAVIOUR.update(
    {
        L(0, 0): SB(3, False, R.ShuffleMCQ, 4, 1),
        .
        .
        .
        L(4, 1): SB(8, True, R.Never, 6, 1),
        }
    )

2.4. Modify the SublevelBehaviour comment

(found in behaviour.py)

Update the comment that appears in the template as:

# Note, SublevelBehaviour is a namedtuple (param1, param2)

to reflect the names in your SublevelBehaviour. This comment will be a helpful reminder during your initial modifications to GAME_BEHAVIOUR, and for anyone modifying your code later.

2.4.1. Examples

Password has the following comment:

# Note, SublevelBehaviour is a namedtuple (game_mode, length)