4. Game state in the template game

The two subsections in this section are

section “Modify GameStateCustom”, and section “Modify create_game_round()”.

4.1. Modify GameStateCustom

(found in logic.py)

The GameStateCustom object is a namedtuple that is used to store

  • information about the current game round, and

  • information that is specific to how your game works.

These two properties are explained as follows. A lot of information needs to be tracked during a game round, such as what is the game round question, what are the specific game round rules (if any), what are the multiple choice answers for this round, what is the correct answer for this round, what has the player answered, what is the player’s current score, and so on. All of these values will be different from round to round, but this type of data is common to every game, so a game state namedtuple is created in the CTGames framework with specific a field for each of them.

However, each developer’s game may have extra information that needs to be stored. Typically, this is information that has been randomly generated specifically for that game round, and will change from one game round to another.

Information that is specific to any particular game would not be appropriate for the CTGames game state namedtuple, which only has fields for data that is common to every game, e.g. the correct answer(s) for the current game round. Therefore, a custom namedtuple GameStateCustom is available for the game developer to store all of the game state information that is specific to their game.

Tip

The design of fields in the GameStateCustom namedtuple can go hand-in-hand with the design of the function _decide_on_problem_instance() (see section “Modify _decide_on_problem_instance”). As a rule of thumb, whatever your function _decide_on_problem_instance() creates dynamically when making a new round, i.e. anything that will change with different playthroughs, that is not already stored in GAME_BEHAVIOUR, is likely to be appropriate to be stored in GameStateCustom.

Note

The data in GameStateCustom is available from all parts of the code of a developer’s game. It can be imported into any module, so a developer will have access to it from all places in the code that they may need it, e.g. the file webapp/__init__.py that contains the code for the game’s web app.

For the template, the only thing that changes from one game round to another is the list of numbers. Therefore, a field for the list of numbers is added to GameStateCustom with

GameStateCustom = namedtuple('GameStateCustom', ('custom_field1',))

Tip

In the template, this field is given the unhelpful name custom_field1. Make sure to change it to a name that is meaningful for your game.

You’ll need to ensure that there is a docstring for each field of GameStateCustom. In the template, there is only one field in the namedtuple, and this looks like

GameStateCustom.custom_field1.__doc__ = """list: The list of numbers."""

4.2. Modify create_game_round()

(found in logic.py)

All of the previous steps have concerned data structures. This function is the first function that you have to write.

Function create_game_round() creates a single round of one’s game dynamically. It acts as the “main” method of the game for the game developer. There is no actual logic in this function. Instead, it is where data structures returned from the game logic are used to populate a dict to be returned to the CTGames framework.

Note

The CTGames framework calls this function each time a new game round is to be created. The framework passes a namedtuple of type SublevelBehaviour. It either chooses the appropriate namedtuple from file behaviour.py or, if it is a custom round, from the developer through the command line or from a teacher through the web app). The game developer does not need to understand how the framework chooses which SublevelBehaviour object to pass as round_behaviour, they just need to be able to write this function so that it responds appropriately whatever values are passed.

Tip

The game developer can be confident that the only values that will be passed in parameter round_behaviour will be those allowed by ROUND_BEHAVIOUR_RANGES (see section “Modify ROUND_BEHAVIOUR_RANGES”). Therefore, it is unlikely that there will be a need for the developer to check for invalid values.

Note

The dict returned from this function is used by the CTGames framework to update the game state that it maintains throughout the game round. The dict should contain everything that the CTGames framework needs for a new round of your game.

There are five aspects of this function to update, each explained in detail in the subsequent sections:

  1. Decide on an answer type

  2. Use the game logic to decide on a game instance

  3. (Only for MCQ-type games) Decide on a suitable list of multiple choice answers

  4. Populate a new GameStateCustom object

  5. Construct a dict to update the game state

4.2.1. Decide on an answer type

You must decide what is the style of answer expected from the player for your game, e.g AnswerType.Int if the game expects an integer, AnswerType.MCQ if it is an MCQ game, and so on.

Note

The choice made here affects what the player will be presented with during a game round (e.g. MCQ answers, a text box, an integer spin button, and so on) and how the player’s answer will be pre-processed (all values entered on the command line are strings; if you specify AnswerType.Int for your game, the CTGames framework will ensure those strings can be converted into integers before passing them to your code).

The full list of possibilities for the style of answer is given in subsection “Answer style options (key answer_type)”.

When you decide on the style of answer for your game, you will enter your decision into the dict that updates the game state (explained later in this section).

4.2.2. Use the game logic to decide on a game instance

A call must be made to the function _decide_on_problem_instance() passing a value for round_behaviour to it. This function (which you can treat as a black box just for now – we’ll fill in the code for it later) has all of the game logic for the game. It should return

  • whatever data should be stored in GameStateCustom, which is whatever information is unique to this game round (and should have the same name(s) as the field(s) in GameStateCustom), and

  • the correct answer (or sequence of correct answers, if there are multiple equally correct ways the player can answer).

In Game Template Int, this line looks like

custom_field1, target = _decide_on_problem_instance(round_behaviour)

In Game Template MCQ, this line looks like

custom_field1, target_long = _decide_on_problem_instance(round_behaviour)

Note

There is a slight difference between the two names we use for the correct answer: in MCQ-type games we use the name target_long, and for all other types of game we use target. This is a convention to acknowledge that there are two concepts of correct answer in MCQ-type games.

Firstly, there is the logical answer to the game round (i.e. the correct answer to the logical problem, which might be an int such as 5, a bool such as True, or a str such as '0011'), and

secondly, there is the answer that we want the player to select in a MCQ-type game, which is always one of the multiple-choice question labels ('A', 'B', 'C', and so on).

In MCQ-type games, we always use target_long for the logical answer and target for the correct multiple-choice question label. Further, in MCQ-type games as well as all other games, target will refer to the exact string that the player is supposed to enter.

4.2.3. Decide on a suitable list of multiple choice answers

This is only relevant for MCQ-type games. This function takes as parameters whatever is needed for that particular game to return

  • a randomised-order list of possible answers, one of which must be name target_long (the logical answer to the game round), and

  • the multiple-choice question label (‘A’, ‘B’, ‘C’, and so on) that represents the location of :py:data:``target_long` in the list.

In Game Template MCQ, what is needed as parameters are the logical answer to the game round and the list of values given in the question, and this looks like:

multiple_answers, targets = _decide_on_answers(target_long, custom_field1)

4.2.4. Populate a new GameStateCustom object

The custom information returned from _decide_on_problem_instance() needs to be put in the correct format (a GameStateCustom object). In the template, this looks like:

custom = GameStateCustom(custom_field1)

4.2.5. Construct a dict to update the game state

The following three keys, at least, must be be returned to the CTGames framework from this function

answer_type

Use whatever was decided in the previous step, e.g AnswerType.Int, AnswerType.MCQ, and so on.

targets

This is the list of correct/expected answers. It is always in the form of a list, even if there is only one answer. Each answer is generally the int or string expected (e.g. 3, ‘Apple’, etc.), except in the cases of AnswerType.MCQ and AnswerType.ImgMCQ, where it is the multiple-choice label corresponding to the correct answer, e.g. ‘A’, ‘B’, etc.

custom

This is the name of the GameStateCustom object created a couple of steps before.

For MCQ-type games, the following key must also be returned

multiple_answers

The list of possible answers.

In Game Template Int, this dict looks like

updates = {
    'answer_type': AnswerType.Int,
    'targets': [target],  # `targets` is a list of correct answers
    'custom': custom,
    }

In Game Template MCQ, this dict looks like

updates = {
    'answer_type': AnswerType.MCQ,
    'multiple_answers': multiple_answers,
    'targets': targets,  # `targets` is a list of correct answers
    'custom': custom,
    }