6. The namedtuple TextImageSeq

(defined in ctgamestypes.py)

This section explains the namedtuple TextImageSeq, which is the type returned from functions format_problem_instance(), format_correct_answer(), format_question(), format_rules_text(), and format_correct_answer(), and is the data type of the name TUTORIAL_TEXT_IM_SEQ.

The namedtuple is used to store separate lists of text strings, images, and DOMNodes to be displayed interlaced (as a sequence, or as a table) in the DOM.

For example, suppose that you want the following sequence to appear in the DOM: text “Hello”, image “path/a.svg”, image “path/b.svg”, and finally text “there!”. Then, you should use the parameters text_seq and im_seq as follows:

return TextImageSeq(
    text_seq=['Hello', None, 'there!'],
    im_seq=['path/a.svg', 'path/b.svg', None],
    )

The respective values in text_seq are placed to the left of their counterparts in im_seq, unless you pass an appropriate code to interlacing. Parameter dom_element_seq is not mentioned in this example, but it would be used in cases where you have a name referring to a DOMNode object to display (e.g. a SVG object created as described in section “The function create_svg_instance()”).

As another example, the parameters text_table (for text strings), im_table (for image filenames), and dom_elem_table (for DOMNode objects such as SVG instances) are used together to specify a single HTML table. These three parameters each specifies a table as a sequence of rows (where each table row is itself specified as a sequence). The values in each of these three sequences of sequences are interlaced to determine the contents of the cells in the table. So, a table specified with

return TextImageSeq(
    text_table=[['hi', None, 'hi']],
    im_table=[[None, 'path/ball.svg', 'path/ball.svg']],
    )

would be a one-row table with ‘hi’ in the first cell, a ball image in the second cell, and both ‘hi’ and a ball image in the third cell.

The game developer builds up sequences of text, image filenames, and DOMNodes, formats them as an instance of TextImageSeq, returns it from a function such as format_problem_instance(), and the CTGames framework displays it in the appropriate position in the web app (as the problem instance in the case of function format_problem_instance(), as the correct answer if the player gets the wrong answer in the case of function format_correct_answer(), and so on).

6.1. Fields in the namedtuple

The default value of each field is None. Only those fields that you want to use need be assigned values in your code.

text_seq

A sequence or None: a sequence of text strings to be displayed in the DOM, interlaced with the sequences im_seq and dom_element_seq.

im_seq

A sequence or None: a sequence of images (each in the form of an image path accessible by HTTP) to be displayed in the DOM, interlaced with the sequences text_seq and dom_element_seq.

dom_element_seq

A sequence or None: a sequence of DOMNode objects to be added to the DOM tree, interlaced with the text and image filename sequences. For an explanation for when dom_element_seq should be used, see section “Which to use, im_seq or dom_element_seq?”.

text_table

A sequence or None: a sequence of sequences of text strings to be displayed in the DOM as a table, each cell being combined with the appropriate element of im_table and dom_elem_table.

im_table

A sequence or None: a sequence of sequences of images (each in the form of an image path accessible by HTTP) to be displayed in the DOM as a table, each cell being combined with the appropriate element of text_table and dom_elem_table.

dom_elem_table

A sequence or None: a sequence of sequences of DOMNode objects to be displayed in the DOM as a table, each cell combined with the appropriate element of text_table and im_table.

im_height

An int, a sequence of int, or None: the requested height of the images in the DOM. If a sequence is passed, a different height for each image in the sequence/table can be specified. If the sequence is too short, the last element is repeated as necessary.

im_width

An int, a sequence of int, or None: the requested width of the images in the DOM. If a sequence is passed, a different width for each image in the sequence/table can be specified. If the sequence is too short, the last element is repeated as necessary.

cell_border

An int or None: the thickness of the line border (in px units) around each cell of the table (ignored if its truthiness is False).

cell_padding

An int or None: the amount of padding (in px units) to apply to each cell in a table.

caption

A str or None: the caption for the table (ignored if evaluates to False).

table_style

A dict or None: CSS properties to modify the style of the table.

cell_style

A dict or None: CSS properties to modify the style of each cell of the table.

seq_first

A bool or None: Whether the sequence should appear before the table or not.

interlacing

A str or None: A str denoting the order that the elements will be interlaced. For example, ‘TID’ (the default value) denotes that in each element of an interlaced seq/table, the text (‘T’) will appear first, then the image (‘I’), and then the DOMNode element (‘D’).

allow_multi_column_table

A bool or None: Whether the framework is allowed to automatically wrap a table into multiple columns (e.g. half its height and double its width) if it gets too tall.

Note

Some attributes of images can be specified through the fields of TextImageSeq (such as height and width) but not all. If there are other image attributes that you wish to specify (such as id or opacity) you should use dom_element_seq for the images, which allows you to specify any DOM attribute of the images on an individual basis.

6.2. Which to use, im_seq or dom_element_seq?

Both parameters im_seq and dom_element_seq can be used to display images. There are technical differences between the two approaches.

With im_seq, we put the elements into the DOM as separate images and the web browser takes care of putting them side-by-side and figuring out if they overflow onto the next line, etc. We specify values for im_height and im_width for each image and the web browser figures their horizontal and vertical position coordinates. We decide not to have control over issues such as horizontal spacing, vertical adjustments, and overlapping of images. This makes our code shorter and simpler.

With dom_element_seq, we must specify for each image separate horizontal and vertical position coordinates, as well as height and width. We must go to the effort of supplying the coordinates, but this allows us the ability to position the images arbitrarily (one image partially or fully overlapping another, for example).

A list of example use-cases is

  • I just want to get images on the screen in a specified order and I’m happy to let the browser make all sensible decisions about the layout – use im_seq,

  • I wish the browser to automatically wrap my images onto the next line when the width of the browser window is reached – use im_seq,

  • I wish to animate the images (change one of its attributes) now or in the future – use dom_element_seq,

  • I wish to know the id of an image, or give a specific id to an image, or have a direct reference to an image in the DOM so that I can later query or change its attributes – use dom_element_seq, and

  • I wish to have multiple images in one of the elements in the sequence – use dom_element_seq.

The comparison above also holds for im_table and dom_elem_table. If one is interested in such a technical detail: in the DOM, images displayed using im_seq (and im_table) become <img> DOMNode objects while images displayed using dom_element_seq (and dom_elem_table) become <image> SVG elements inside a parent <svg> object.

6.3. Examples

In file justaddition/webapp/__init__.py, the function format_problem_instance() returns a sequence of images preceded by some text:

../_images/textimageseq_justaddition.png

Fig. 6.1 Example of a TextImageSeq object that uses parameters text_seq and im_seq

# Replace each symbol with an appropriate image filename
fnames = [IM_PATH.format(symbol) for symbol in symbols]

text_im_seq = TextImageSeq(
    text_seq=[text + ': '],
    im_seq=fnames,
    im_height=IM_HEIGHT,
    im_width=IM_WIDTH,
    )
return text_im_seq

In file crosscountry/webapp/__init__.py, the function format_rules_text() returns a table containing a mixture of text and images as shown in the image below (some of the code has been simplified to illustrate the main concepts). Also, this example shows the use of parameter table_style to modify CSS properties to style the table (ensuring the images vertically align with one another):

../_images/textimageseq_crosscountry.png

Fig. 6.2 Example of a TextImageSeq object that uses parameters text_table and im_table

items = [['uphill', 'Mrs. Green'], ['downhill', 'Mr. Blue']]

text_table = []
im_table = []
for (terrain, name) in items:
    text_table.append([None, name])
    im_table.append([IM_PATH.format(terrain), IM_PATH.format(name)])

text_image_seq = TextImageSeq(
    text_table=text_table,
    im_table=im_table,
    im_height=40,
    cell_border=1,
    cell_padding=5,
    caption='Overtakers',
    table_style={'text-align': 'right'},
    )
return text_image_seq

In file dungeonescape/webapp/__init__.py, the function format_problem_instance() returns a TextImageSeq object that contains a single DOMNode object (note, it still needs to be formatted as a list even there is only one object):

global_svg_instance = create_svg_instance(
    # Parameters hidden for this example
    )
return TextImageSeq(dom_element_seq=[global_svg_instance])

In file balloons/webapp/__init__.py, the function format_rules_text() returns a table containing a mixture of text and images as shown in the image below. In contrast to Fig. 6.2, in this table we wish to have multiple images in some cells, so we need to combine the multiple images into a single DOMNode object and use a dom_elem_table rather than the im_table used in Fig. 6.2.

../_images/textimageseq_balloons.png

Fig. 6.3 Example of a TextImageSeq object that uses parameters text_table and dom_elem_table

The pseudocode for the the table above is

IM_SIDE = 60
"""The required side length for each image."""

# The first row
svg1 = create_svg_instance(...)
graphic_a = create_image_from_file(x=0, IM_PATH.format('red'))
svg1 <= graphic_a
graphic_b = create_image_from_file(x=IM_SIDE, IM_PATH.format('red'))
svg1 <= graphic_b

# The second row
svg2 = create_svg_instance(...)
graphic_c = create_image_from_file(x=0, IM_PATH.format('red'))
svg2 <= graphic_c
graphic_d = create_image_from_file(x=IM_SIDE, IM_PATH.format('blue'))
svg2 <= graphic_d

# The text will be in the first column of the table only
text_table = [['river', None], ['rock', None]]

# A sequence of images will be in each row of the second column
dom_elem_table = [[None, svg1], [None, svg2]]

text_image_seq = TextImageSeq(
    text_table=text_table,
    dom_elem_table=dom_elem_table,
    )
return text_image_seq

The actual code (in a slightly simplified form) is

IM_SIDE = 60
"""The required side length for each image."""

def _code_svg(code: str) -> DOMNode:
    """Return a SVG encoding of the str `code`."""
    # Create a SVG container for the balloons
    svg = create_svg_instance(
        id=f'svg_{code}', height=IM_SIDE, width=len(code)
        )
    # Add the balloon SVG images one at a time to the SVG container
    for count, symbol in enumerate(code):

        colour = 'blue' if symbol == '0' else 'red'
        graphic = create_image_from_file(
            id=f'svg_{code}_{count}',
            x=IM_SIDE * count,  # Separate them horizontally
            y=0,
            width=IM_SIDE,
            height=IM_SIDE,
            href=IM_PATH.format(colour),  # the files are 'blue.svg' and 'red.svg'
            )
        svg <= graphic
    return svg

codebook = {'river': '00', 'rock': '01'}

# The text will be in the first column of the table only
text_table = [[word, None] for word in codebook.keys()]

# Specify a table with a seq of SVG images in each row of the second column
dom_elem_table = [[None, _code_svg(code)] for code in codebook.values()]

text_image_seq = TextImageSeq(
    text_table=text_table,
    dom_elem_table=dom_elem_table,
    cell_border=1,
    cell_padding=5,
    caption='Codebook',
    table_style={'text-align': 'left'},
    allow_multi_column_table=True,
    )
return text_image_seq