3. The function create_game_round()

(found in logic.py)

This section tells you all you need to know about the function create_game_round(). You will have already read the first subsection below (subsection “Basics”) from Chapter “Template game explained” that explained how to get started programming a command-line CTGames game (specifically, subsection “Modify create_game_round()”). It is reproduced here for completeness.

The full list of subsections in this section is

  • subsection “Basics” everything you need to get started with this function

  • subsection “Answer style options (key answer_type)” explains the full set of possibilities for the style of answer for one’s game (values for key answer_type returned from create_game_round())

3.1. Basics

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

3.1.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).

3.1.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.

3.1.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)

3.1.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)

3.1.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,
    }

3.2. Answer style options (key answer_type)

The game developer has several possibilities for the style of answer they wish for their game. The choice is made in function create_game_round() in file logic.py.

Note

When scoring a game round, usually the player gets full marks for a correct answer and (for example for MCQ-type games) a player can lose some marks for an incorrect answer. However, there is also the concept of a partial mark in games. In such a case, a player may get some marks for a good attempt even if their answer is incorrect. In the descriptions below, it is mentioned for which game styles the concept of a partial mark is supported.

The full list of possibilities is:

AnswerType.String

The answer to the game round is a string. No marks will be given for a partially correct answer (i.e. there is no partial mark).

AnswerType.StringCaseI

The answer to the game round is a string. No marks will be given for a partially correct answer (i.e. there is no partial mark). The comparison is case insensitive.

AnswerType.Partial

The answer to the game round is a string. The partial mark will be the length of the correct prefix. (Default.)

AnswerType.PartialCaseI

The answer to the game round is a string. The partial mark will be the length of the correct prefix. The comparison is case insensitive.

AnswerType.MCQ

The answer to the game round is a label for one of the multiple answers presented to the player (it was a multiple-choice question). Negative marking is used for the partial mark.

AnswerType.ImgMCQ

The same as MCQ but allows images to be displayed instead of text in the Brython version of the game.

AnswerType.Int

The answer to the game round is an int. No marks will be given for an incorrect answer.

AnswerType.NaturalMin

The answer to the game round is a non-negative int. The player’s answer will be used as the partial mark. An answer greater than or equal to the target is deemed correct. If there is a goal, and that goal is achieved, the partial mark will be doubled.

AnswerType.NaturalMax

The answer to the game round is a non-negative int. An answer less than or equal to the target is deemed correct. If correct, the target minus the player’s answer will be used as the partial mark. If there is a goal, and that goal is achieved, the partial mark will be doubled.

AnswerType.Bag

The answer to the game round is a bag (an unordered list, or, equivalently, a set that can contain multiple elements). The number of elements in the set will be used as the partial mark. No marks will be given for an incorrect answer. It is assumed that the bag contains strings (as that is what comes naturally from the command line) and that each string contains no spaces and commas.

AnswerType.BagCaseI

As with Bag above, except the strings are compared case-insensitively.

3.2.1. Examples

In file countfromzero/logic.py, the function create_game_round() specifies that the answer type should be an integer, with

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

return updates

In file justaddition/logic.py, the function create_game_round() specifies that the answer type should be a multiple-choice question label (e.g. ‘A’, ‘B’, ‘C’, …) with

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

return updates

In file justreverse/logic.py, the function create_game_round() specifies that the answer type should be a case-insensitive string where the player gets a partial mark depending on the position of the first incorrect character in the string (full marks if there is no incorrect character) with

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

return updates

In file justodd/logic.py, the function create_game_round() specifies that the answer type should be a set of strings, with

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

return updates