Screen

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

Bases: pygamelib.base.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 Screen Buffer stack.
  • The Direct Display stack.

It is safer to consider them mutually incompatible. In reality the Screen Buffer 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 Screen Buffer 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 screen buffer is only rendered when needed.

Example:

screen = Screen()
# The next 2 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.
# 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 Screen Buffer stack:

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

The Screen Buffer 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.

For example, if you want to use a sprite on a title screen and wants 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 Screen Buffer 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).

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

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

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: 45 to 65 FPS.
  • On an AMD Ryzen 9 5900X @ 4.80 GHz: 60 to 80 FPS.

If these are not good enough for you, the Direct Display stack is for you. You just need to deal with more stuff with less helper methods.

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

  • xterm-256color
  • Konsole
  • Kitty
  • Alacritty
  • GNOME Terminal

Performances are consistants 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.
clear() This methods clear the screen.
clear_buffers()

New in version 1.3.0.

clear_screen_buffer()

New in version 1.3.0.

delete([row, column])

New in version 1.3.0.

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

Note

This method is part of the Direct Display rendering stack and is

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

New in version 1.2.0.

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

New in version 1.3.0.

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

New in version 1.3.0.

force_render()

New in version 1.3.0.

force_update()

New in version 1.3.0.

get(row, column)

New in version 1.3.0.

place([element, row, column, rendering_pass]) Place an element on the screen.
render()

New in version 1.3.0.

trigger_rendering()

New in version 1.3.0.

update()

New in version 1.3.0.

Attributes

buffer

New in version 1.3.0.

hcenter Return the horizontal center of the screen as an int.
height This method wraps Terminal.height and return the height of the terminal window in number of characters.
need_rendering

New in version 1.3.0.

vcenter Return the vertical center of the screen as an int.
width This method wraps Terminal.width and return 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 everytime 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)
be_notified(subject, attribute=None, value=None)

A virtual method that needs to be implemented by the observer. By default it does nothing but each observer needs to implement it if something needs to be done when notified.

This method always receive the notifying object as first parameter. The 2 other paramters are optional and can be None.

You can use the attribute and value as you see fit. You are free to consider attribute as an event and value as the event’s value.

Parameters:
  • subject (PglBaseObject) – The object that has changed.
  • attribute (str) – The attribute that has changed. This can be None.
  • value (Any) – The new value of the attribute. This can be None.
buffer

New in version 1.3.0.

Note

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

The buffer property return a numpy.array as a writable screen 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 screen 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 screen buffer. Or else it will be squashed in the next rendering cycle.

clear()

This methods clear the screen.

clear_buffers()

New in version 1.3.0.

Note

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

This methods clear the Screen’s buffers (both rendering and screen 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.

clear_screen_buffer()

New in version 1.3.0.

Note

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

This methods clear the screen 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.

delete(row=None, column=None)

New in version 1.3.0.

Note

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

Delete a element on screen. Pay attention that if you placed a multi cells element on screen, you only need to erase that specific spot.

Parameters:
  • row (int) – The row to render to.
  • column (int) – The column to render to.

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

Note

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

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=''
                )
display_line(*text, end='\n', file=<colorama.ansitowin32.StreamWrapper object>, flush=False)

New in version 1.2.0.

Note

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

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:

game.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}')
display_sprite(sprite, filler= , file=<colorama.ansitowin32.StreamWrapper object>, flush=False)

New in version 1.3.0.

Note

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

Displays 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)
display_sprite_at(sprite, row=0, column=0, filler= , file=<colorama.ansitowin32.StreamWrapper object>, flush=False)

New in version 1.3.0.

Note

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

Displays 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)
                         )
force_render()

New in version 1.3.0.

Note

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

Force the immediate rendering of the display buffer.

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

Example:

screen.force_render()
force_update()

New in version 1.3.0.

Note

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

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

Example:

screen.force_update()
get(row: int = None, column: int = None)

New in version 1.3.0.

Note

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

Get an element at the specified screen coordinates.

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

Parameters:
  • row (int) – The row to render to.
  • column (int) – The column to render to.

Example:

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

Return the horizontal center of the screen as an int.

Example:

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

This method wraps Terminal.height and return the height of the terminal window in number of characters.

need_rendering

New in version 1.3.0.

Note

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

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

It returns False otherwise.

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

Notify all the observers that a change occurred. Two important points:

  1. The “change” that occurred is not specified (but the notifying object is passed as parameter)
  2. No parameters are passed to the be_notified() method except the notifying object.
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.

New in version 1.3.0.

Note

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

This method places an element in the screen display buffer. The element is then going to be rendered in the screen 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 (first or second pass).

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)
render()

New in version 1.3.0.

Note

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

Parameters:name (str) – some param

Example:

method()
trigger_rendering()

New in version 1.3.0.

Note

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

Trigger the screen buffer for rendering at the next update.

Example:

screen.trigger_rendering()
update()

New in version 1.3.0.

Note

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

Update the screen. Update means write the display 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()
vcenter

Return the vertical center of the screen as an int.

Example:

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

This method wraps Terminal.width and return the width of the terminal window in number of characters.