Screen

class pygamelib.engine.Screen(width: int = None, height: int = None)

Bases: PglBaseObject

The screen object is pretty straightforward: it is an object that allow manipulation of the screen.

Warning

Starting with version 1.3.0 the terminal parameter has been removed. The Screen object now takes advantage of base.Console.instance() to get a reference to a blessed.Terminal object.

Version 1.3.0 introduced a new way of managing the screen. It rely on an internally managed display buffer that allows for easier positioning and more regular rendering. This comes at a cost though as the performances takes a hit. The screen should still be able to be refreshed between 50 and 60+ times per seconds (and still around 30 times per second within a virtual machine). These numbers obviously depends on the terminal used, the screen size and the content to display.

This change introduce two ways of displaying things on the screen:

  • The Improved Screen Management stack (referred to as ISM later in the doc).

  • The Legacy Direct Display stack.

It is safer to consider them mutually incompatible. In reality the Improved Screen Management will always use the whole display but you can use the methods from the Direct Display stack to write over the buffer. It is really NOT advised.

We introduced the Improved Screen Management stack because the direct display is messy and does not allow us to do what we want in term of positioning, UI, etc.

A typical usage consist of:

  • Placing elements on the screen with place()

  • Update the screen with update()

That’s it! The screen maintain its own state and knows when to re-render the display buffer. You don’t need to manually call render(). This helps with performances as the frame buffer is only rendered when needed.

Example:

screen = Screen()
# The next 3 lines do the same thing: display a message centered on the screen.
# Screen Buffer style
screen.place('This is centered', screen.vcenter, screen.hcenter)
screen.update()
# Direct Display style
screen.display_at('This is centered', screen.vcenter, screen.hcenter)
# The rest of this example uses the Screen Buffer (because placing a Board
# anywhere on the Screen is not supported by the Direct Display stack).
# delete the previous message and place a Board at the center of the screen
screen.delete(screen.vcenter, screen.hcenter)
screen.place(
    my_awesome_board,
    screen.vcenter - int(my_awesome_board.height/2),
    screen.hcenter - int(my_awesome_board.width/2)
)
screen.update()

Precisions about the Improved Screen Management stack:

You don’t need to know how the frame buffer works to use it. However, if you are interested in more details, here they are.

The Improved Screen Management stacks uses a double numpy buffer to represent the screen. One buffer is used to place elements as objects (that’s the buffer managed by place() or delete()). It is never directly printed to the screen. It is here to simplify screen maintenance. This buffer is called the display buffer. It is practical to use to place, move and delete elements on the screen space. But as said before it cannot be directly printed to the screen. It needs to be rendered first.

For example, if you want to use a sprite on a title screen and want to move it around (or animate the screen). Normally (i.e with Direct Display) you would display the sprite at a specific position and then would either call clear() or overwrite all the sprite with spaces to erase and replace and/or move it. And that’s very slow.

With the Improved Screen Management you place() the sprite and then just delete() it. And since it is only one object reference it is a very fast operation (we only place or delete one cell of the buffer).

When update() is called, it first look at the state of the buffers and call render() if needed (i.e: if something has change in the display buffer). The buffers are only rendered when needed.

When render() is called it goes through the display buffer and render each elements transforming it into a printable sequence that is stored in the frame buffer. The rendering is done from the bottom right corner of the screen to the top left corner. This allows for cleaning junk characters at no additional cost.

TL;DR: The display buffer hold the objects placed on the screen while the frame buffer hold the rendered representation of the display buffer.

The Screen object also inherits from the PglBaseObject and if the object that is place()-ed is an instance of PglBaseObject, the screen will automatically attach itself to the object. When notified of a change it will trigger a render cycle before the next update.

In terms of performances, depending on your terminal emulator and CPU you will most certainly achieve over 30 FPS. Here are a couple of benchmark results:

  • On an Intel Core i7 @ 4.20 GHz: 50 to 70 FPS.

  • On an AMD Ryzen 9 5950X @ 4.80 GHz: 60 to 100 FPS.

The new Improved Screen Management is faster than the legacy stack in most of the cases. The only case when the legacy Direct Display stack might be faster is in the case of a game or application with only simple ASCII characters and not a lot of things to display.

Here are some compiled benchmark results of both of systems over 150 runs:

Benchmark

Improved Screen Management

Legacy Direct Display

Sprite (place, render and update screen), Sprite size: 155x29

10.0 msec. or 71 FPS

380.0 msec. or 3 FPS

Sprite 200 updates

620.0 msec. or 76 FPS

9830.0 msec. or 20 FPS

Phase 1 - 500 frames. Single board avg load

11.02 msec. per frame or 91 FPS

12.65 msec. per frame or 79 FPS

Phase 2 - 500 frames. Dual board high load

18.18 msec. per frame or 55 FPS

28.34 msec. per frame or 35 FPS

Overall - 1000 frames.

14.60 msec. per frame or 68 FPS

20.49 msec. per frame or 49 FPS

You can use the 2 benchmark scripts to compare on your system:

  • benchmark-screen-buffer.py

  • benchmark-screen-direct-display.py

The frame buffer system has been tested on the following terminals:

  • xterm-256color

  • Konsole

  • Kitty

  • Alacritty

  • GNOME Terminal

Performances are consistent across the different terminals. The only exception is the GNOME Terminal, which is slower than the others (about 20~30 % slower).

__init__(width: int = None, height: int = None)

The constructor takes the following (optional) parameters.

Parameters:
  • width (int) – The width of the screen.

  • height (int) – The height of the screen.

Setting any of these parameters fixes the screen size regardless of the actual console/terminal resolution. Leaving any of these parameters unset will let the constructor use the actual console/terminal resolution instead.

Please have a look at the examples for more on this topic.

Example:

# Let's assume a terminal resolution of 170(width)x75(height).
screen = Screen()
# Next line display: "Screen width=170 height=75"
print(f"Screen width={screen.width} height={screen.height}")
screen = Screen(50)
# Next line display: "Screen width=50 height=75"
print(f"Screen width={screen.width} height={screen.height}")
screen = Screen(height=50)
# Next line display: "Screen width=170 height=50"
print(f"Screen width={screen.width} height={screen.height}")
screen = Screen(50, 50)
# Next line display: "Screen width=50 height=50"
print(f"Screen width={screen.width} height={screen.height}")

Methods

__init__([width, height])

The constructor takes the following (optional) parameters.

attach(observer)

Attach an observer to this instance.

clear()

This methods clear the screen.

clear_buffers()

This methods clear the Screen's buffers (both display and frame buffer).

clear_frame_buffer()

This methods clear the frame buffer (but not the display buffer).

delete([row, column])

Delete a element on screen.

detach(observer)

Detach an observer from this instance.

display_at(text[, row, column, clear_eol, ...])

Displays text at a given position.

display_line(*text[, end, file, flush])

A wrapper to Python's print() builtin function except it will always add an ANSI sequence to clear the end of the line.

display_sprite(sprite[, filler, file, flush])

Displays a sprite at the current cursor position.

display_sprite_at(sprite[, row, column, ...])

Displays a sprite at a given position.

force_render()

Force the immediate rendering of the display buffer.

force_update()

Same as force_render() but also force the immediate screen update.

get(row, column)

Get an element from the display buffer at the specified screen coordinates.

handle_notification(subject[, attribute, value])

When a Screen object is notified, it set the display buffer to be rendered before the next update.

notify([modifier, attribute, value])

Notify all the observers that a change occurred.

place([element, row, column, rendering_pass])

Place an element on the screen.

render()

Render the display buffer into the frame buffer.

store_screen_position(row, column)

Store the screen position of the object.

trigger_rendering()

Trigger the frame buffer for rendering at the next update.

update()

Update the screen.

Attributes

buffer

The buffer property return a numpy.array as a writable frame buffer.

hcenter

Return the horizontal center of the screen as an int.

height

This property returns the height of the terminal window in number of characters.

need_rendering

This property return True if the display buffer has been updated since the last rendering cycle and the screen needs to re-render the frame buffer.

screen_column

A property to get/set the screen column.

screen_row

A property to get/set the screen row.

vcenter

Return the vertical center of the screen as an int.

width

This property returns the width of the terminal window in number of characters.

attach(observer)

Attach an observer to this instance. It means that until it is detached, it will be notified every time that a notification is issued (usually on changes).

An object cannot add itself to the list of observers (to avoid infinite recursions).

Parameters:

observer (PglBaseObject) – An observer to attach to this object.

Returns:

True or False depending on the success of the operation.

Return type:

bool

Example:

myboard = Board()
screen = Game.instance().screen
# screen will be notified of all changes in myboard
myboard.attach(screen)
property buffer

The buffer property return a numpy.array as a writable frame buffer.

The buffer is a 2D plane (like a screen) and anything can render in it. However, it is recommended to place objects through Screen.place() and update the screen with Screen.update() (update calls render() if needed and do the actual display).

Warning

Everything that is stored in the buffer must be printable. Each cell of the frame buffer represent a single character on screen, so you need to take care of that when you write into that buffer or you will corrupt the display. If need_rendering returns True, you need to manually call render() before writing anything into the frame buffer. Or else it will be squashed in the next rendering cycle.

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

clear()

This methods clear the screen.

clear_buffers()

This methods clear the Screen’s buffers (both display and frame buffer).

Make sure that you really want to clear the buffers before doing so, because this is a slow operation.

Once the buffer is cleared nothing is left in it, you have to reposition (place) everything.

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

clear_frame_buffer()

This methods clear the frame buffer (but not the display buffer). This means that the next time update() is called, rendering will be triggered.

Make sure that you really want to clear the buffers before doing so, because this is a slow operation. It might however be faster than manually update screen cells.

Once the buffer is cleared nothing is left in it, it sets the Screen for a rendering update.

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

delete(row=None, column=None)

Delete a element on screen.

It is important to note that if you placed an element that occupies more than 1 cell, you only have to erase that specific position not the entire area.

Parameters:
  • row (int) – The row coordinate of the element to delete.

  • column (int) – The column coordinate of the element to delete.

Example:

board = Board(size=[20,20])
screen.place(board, 2, 2)
# With this we have placed a board at screen coordinates 2,2 and the board
# will display on screen coordinates from 2,2 to 22,22.
# However, to delete the board we don't need to clean all these cells.
# Just the one where we placed the board:
screen.delete(2, 2)

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

detach(observer)

Detach an observer from this instance. If observer is not in the list this returns False.

Parameters:

observer (PglBaseObject) – An observer to detach from this object.

Returns:

True or False depending on the success of the operation.

Return type:

bool

Example:

# screen will no longer be notified of the changes in myboard.
myboard.detach(screen)
display_at(text, row=0, column=0, clear_eol=False, end='\n', file=<colorama.ansitowin32.StreamWrapper object>, flush=False)

Displays text at a given position. If clear_eol is True, also clear the end of line. Additionally you can specify all the parameters of a regular print() if you need to.

Parameters:
  • text (str) – The text to display. Please note that in that case text is a single string.

  • row (int) – The row position in the terminal window.

  • column (int) – The column position in the terminal window.

  • clear_eol (bool) – If True this clears the end of the line (everything after the last character displayed by that method).

  • end (str) – end sub string added to the printed text. Usually a carriage return.

  • file (stream) –

  • flush (bool) –

Important

The cursor is only moved for printing the text. It is returned to its previous position after.

Note

The position respect the row/column convention accross the library. It is reversed compared to the blessed module.

Example:

screen.display_at('This is centered',
                  int(screen.height/2),
                  int(screen.width/2),
                  clear_eol=True,
                  end=''
                )
https://img.shields.io/badge/rendering%20stack-Direct%20Display-blue

Note

This method is part of the Legacy Direct Display rendering stack and is incompatible with the methods identified as being part of the Improved Screen Management stack.

display_line(*text, end='\n', file=<colorama.ansitowin32.StreamWrapper object>, flush=False)

A wrapper to Python’s print() builtin function except it will always add an ANSI sequence to clear the end of the line. Making it more suitable to use in a user_update callback.

The reason is that with line with variating length, if you use run() but not clear(), some characters will remain on screen because run(), for performances concerns does not clear the entire screen. It just bring the cursor back to the top left corner of the screen. So if you want to benefit from the increase performances you should use display_line().

Parameters:
  • *text (str|objects) – objects that can serialize to str. The ANSI sequence to clear the end of the line is always appended to the the text.

  • end (str) – end sub string added to the printed text. Usually a carriage return.

  • file (stream) –

  • flush (bool) –

Example:

screen.display_line(f'This line will display correctly: {elapsed_time}')
# That line will have trailing characters that are not cleared after redraw
# if you don't use clear().
print(f'That one won't: {elapsed_time}')

New in version 1.2.0.

https://img.shields.io/badge/rendering%20stack-Direct%20Display-blue

Note

This method is part of the Legacy Direct Display rendering stack and is incompatible with the methods identified as being part of the Improved Screen Management stack.

display_sprite(sprite, filler= , file=<colorama.ansitowin32.StreamWrapper object>, flush=False)

Displays a sprite at the current cursor position. If a Sprixel is empty, then it’s going to be replaced by filler.

Parameters:
  • sprite (Sprite) – The sprite object to display.

  • filler (Sprixel) – A sprixel object to replace all empty sprixels in sprite.

  • file (stream) –

  • flush – print() parameter to flush the stream after printing

Examples:

screen.display_sprite(panda_sprite)

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-Direct%20Display-blue

Note

This method is part of the Legacy Direct Display rendering stack and is incompatible with the methods identified as being part of the Improved Screen Management stack.

display_sprite_at(sprite, row=0, column=0, filler= , file=<colorama.ansitowin32.StreamWrapper object>, flush=False)

Displays a sprite at a given position. If a Sprixel is empty, then it’s going to be replaced by filler.

Parameters:
  • sprite (Sprite) – The sprite object to display.

  • row (int) – The row position in the terminal window.

  • column (int) – The column position in the terminal window.

  • filler (Sprixel) – A sprixel object to replace all empty sprixels in sprite.

  • file (stream) –

  • flush (bool) – print() parameter to flush the stream after printing

Example:

screen.display_sprite_at(panda_sprite,
                         int(screen.height/2),
                         int(screen.width/2)
                         )

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-Direct%20Display-blue

Note

This method is part of the Legacy Direct Display rendering stack and is incompatible with the methods identified as being part of the Improved Screen Management stack.

force_render()

Force the immediate rendering of the display buffer.

If you just want to mark the frame buffer for rendering before the next update use trigger_rendering() instead.

Example:

screen.force_render()

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

force_update()

Same as force_render() but also force the immediate screen update.

Example:

screen.force_update()

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

get(row: int, column: int)

Get an element from the display buffer at the specified screen coordinates.

The element is returned from the display buffer (pre-rendering).

Parameters:
  • row (int) – The row of the element to get.

  • column (int) – The column of the element to get.

Example:

board = Board(size=[20,20])
screen.place(board, 2, 2)
my_board = screen.get(2,2)

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

handle_notification(subject, attribute=None, value=None)

When a Screen object is notified, it set the display buffer to be rendered before the next update.

property hcenter

Return the horizontal center of the screen as an int.

Example:

screen.place('horizontally centered', 0, screen.hcenter)
property height

This property returns the height of the terminal window in number of characters.

property need_rendering

This property return True if the display buffer has been updated since the last rendering cycle and the screen needs to re-render the frame buffer.

It returns False otherwise.

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

notify(modifier=None, attribute: str = None, value: Any = None) None

Notify all the observers that a change occurred.

Parameters:
  • modifier (PglBaseObject) – An optional parameter that identify the modifier object to exclude it from the notified objects.

  • attribute (str) – An optional parameter that identify the attribute that has changed.

  • value (Any) – An optional parameter that identify the new value of the attribute.

Example:

# This example is silly, you would usually notify other objects from inside
# an object that changes a value that's important for the observers.
color = Color(255,200,125)
color.attach(some_text_object)
color.notify()
place(element=None, row=None, column=None, rendering_pass=1)

Place an element on the screen.

This method places an element in the screen display buffer. The element is then going to be rendered in the frame buffer before being printed on screen.

The following elements can be placed on screen:

  • All BoardItem derivatives.

  • All BoardComplexItem derivatives.

  • Board object.

  • Text objects.

  • Sprite objects.

  • Sprixel objects.

  • Regular Python str.

  • Any object that expose a render_to_buffer() method.

Here is the required signature for render_to_buffer:

render_to_buffer(self, buffer, row, column, buffer_height, buffer_width)

The buffer parameter will always be a numpy array, row and column are the position to render to. Finally buffer_height and buffer_width are the dimension of the buffer.

The buffer is rendered in 2 passes. By default all elements are rendered in pass 1. But if for some reason something needs to be drawn over other elements (like if a dialog/popup is needed for example), the element can be set to be rendered only during the second pass.

Parameters:
  • element (various) – The element to place.

  • row (int) – The row to render to.

  • column (int) – The column to render to.

  • rendering_pass (int) – When to render the element. You can have any number of rendering passes but you have to be careful of performances. Higher passses render on top of lower passes. You can see the render passes as plane to write on. The default pass is 1.

Warning

to be rendered on the second+ pass an element needs to implement render_to_buffer(…). This excludes all standard types (but not Text). Regular Python strings and object that can be print() can still be used in the first pass.

Example:

screen.place(my_sprite, 0, 0)

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

render()

Render the display buffer into the frame buffer.

Example:

screen.render()
screen.update()

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

property screen_column: int

A property to get/set the screen column.

Parameters:

value (int) – the screen column

Return type:

int

property screen_row: int

A property to get/set the screen row.

Parameters:

value (int) – the screen row

Return type:

int

store_screen_position(row: int, column: int) bool

Store the screen position of the object.

This method is automatically called by Screen.place().

Parameters:
  • row (int) – The row (or y) coordinate.

  • column (int) – The column (or x) coordinate.

Example:

an_object.store_screen_coordinate(3,8)
trigger_rendering()

Trigger the frame buffer for rendering at the next update.

Example:

screen.trigger_rendering()

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Direct Display stack.

update()

Update the screen. Update means write the frame buffer on screen.

Example:

mygame = Game()
sc = core.SpriteCollection.load_json_file('title_screens.spr')
mygame.screen.place(sc['welcome_screen'], 0, 0)
mygame.screen.update()

New in version 1.3.0.

https://img.shields.io/badge/rendering%20stack-ISM-green

Note

This method is part of the Improved Screen Management rendering stack and is incompatible with the methods identified as being part of the Legacy Direct Display stack.

property vcenter

Return the vertical center of the screen as an int.

Example:

screen.place('vertically centered', screen.vcenter, 0)
property width

This property returns the width of the terminal window in number of characters.