Tvorba aplikací a her s textovým rozhraním s knihovnou Blessed (dokončení)

6. 5. 2021
Doba čtení: 26 minut

Sdílet

 Autor: Voltron project
Ve druhém článku o knihovně Blessed si nejprve ukážeme implementaci prohlížeče obrázků v terminálu, popíšeme si tři režimy práce terminálu, které souvisí se čtením kláves a nezapomeneme ani na funkce pro pohyb kurzoru.

Obsah

1. Tvorba aplikací a her s textovým uživatelským rozhraním s využitím knihovny Blessed (dokončení)

2. Prohlížeč obrázků pro terminál?

3. Využití „plného“ znaku ve funkci pixelu

4. Využití mezery s modifikovanou barvou pozadí

5. Použití znaků Unicode na terminálu

6. Režim terminálu

7. Čtení kláves v režimu cbreak

8. Rozpoznání jména a kódu stisknutých kláves

9. Sekvence znaků poslaná po stisku klávesy

10. Neblokující čtení klávesy

11. „Raw“ režim terminálu

12. Pohyb kurzoru po ploše terminálu

13. Ukázka skriptu přesunujícího kurzor

14. Výpočet umístění textu s řídicími znaky, obnovení původní pozice kurzoru

15. Přeformátování textu

16. Chybějící funkcionalita

17. Dodatek 1: klávesy rozeznávané knihovnou Blessed

18. Dodatek 2: příklad nastavení xtermu

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Tvorba aplikací a her s textovým uživatelským rozhraním s využitím knihovny Blessed (dokončení)

V úvodním článku o knihovně Blessed jsme si popsali pouze základní funkce této potenciálně užitečné knihovny, které je možné použít pro změnu (vylepšení) zobrazení údajů na terminálu. To je však ve skutečnosti pouze relativně malá (ovšem pochopitelně důležitá) část celkové funkcionality. Aby bylo možné vytvářet plnohodnotné celoobrazovkové aplikace, je nutné umět přesunout textový kurzor na libovolnou pozici na terminálu (sloupec, řádek), reagovat na stisk klávesy (a nikoli čekat na potvrzení Enterem, což je implicitní režim práce s terminálem), popř. reagovat na změnu velikosti okna terminálu. Tyto funkce budou popsány v dnešní a současně i poslední části mikroseriálku o knihovně Blessed.

Obrázek 1: Rogue, aneb prapředek klasických roguelike her – průzkum prvního patra dungeonu. Tato hra využívá některých možností poskytovaných textovými terminály.

Tyto funkce poskytované knihovnou Blessed jsou dostatečné pro vývoj většiny aplikací pracujících v terminálu. Mezi takové aplikace patří především textové editory, dále například celoobrazovkové debuggery (příkladem může být PuDB, což je debugger pro Python), nástroje typu Midnight Commander a dokonce i některé hry. U her se na chvíli zastavme: vytvářet lze hry typu Rogue/Nethack, pochopitelně textovky, strategické hry i některé hry akční. Ovšem zde poměrně záhy narazíme na problematickou práci s klávesnicí, neboť nelze (alespoň ne jednoduše) reagovat na stisk i puštění klávesy, u kláves se posílá informace o automatickém opakování atd. Možnosti knihovny Blessed jsou v tomto ohledu poněkud limitovány – nejedná se tedy v žádném případě o „SDL pro konzoli“.

Obrázek 2: Pohled na textové uživatelské rozhraní debuggeru PuDB s pěti regiony.

2. Prohlížeč obrázků pro terminál?

Minule jsme si kromě dalších informací řekli, že terminály většinou podporují osm, šestnáct, 256, popř. 224 barev (xterm pak 4096 barev). Knihovna Blessed dokáže pracovat s celým barvovým spektrem, tedy s oněmi více než šestnácti miliony barvových odstínů (záleží pak na konkrétním terminálu, zda dokáže všechny tyto barvy zobrazit nebo například provede převod na nejbližší barvu z palety 4096 odstínů). Pro převod vlastní barvy (reprezentované většinou v barvovém prostoru RGB) na příslušný řídicí kód (escape sekvenci) slouží funkce nazvaná color_rgb.

Obrázek 3: Kingdom of Kroz II – úvodní obrazovka s ANSI artem (starší hra pro IBM PC).

Pokusme se nyní tuto vlastnost moderních terminálů využít a vytvořit primitivní prohlížeč obrázků. Namísto bitmapy s jednotlivými pixely použijeme textový terminál, přičemž funkci pixelu (obrazového elementu) převezme celý znak – buď vykreslíme určitou barvou znak „box“ z Unicode (viz též tento přehledový článek), nebo vykreslíme mezeru, ovšem se změněnou barvou pozadí. Nejdříve získáme testovací obrázek, který po stažení zmenšíme na rozměry 128×128 pixelů. O obě tyto operace se postará následující jednoduchý skript:

"""Retrieve test image, resize it, and store under new filename."""
 
from PIL import Image
import urllib.request
 
url = "https://homepages.cae.wisc.edu/~ece533/images/fruits.png"
 
original_filename = "fruits_.png"
resized_filename = "fruits.png"
 
urllib.request.urlretrieve(url, original_filename)
 
img = Image.open(original_filename)
resized = img.resize((128, 128), Image.BILINEAR)
resized.save(resized_filename)

Obrázek 4: Originální obrázek s rozlišením 512×512 pixelů.

Obrázek 5: Obrázek po zmenšení na 128×128 pixelů.

3. Využití „plného“ znaku ve funkci pixelu

První verze prohlížeče obrázků využívá „plný“ znak, tj. znak, který by měl mít ve všech znakových sadách tvar vyplněného obdélníku. Jedná se o následující znak Unicode. Program pracuje jednoduše:

  1. Vnutí terminálu režim true color (to je nutné například pro xterm)
  2. Načte rastrový obrázek s využitím funkce Image.open z knihovny PIL/Pillow
  3. Postupně projde všemi sudými řádky obrázku (toto je trik, protože znaky mají většinou poměr výška:šířka roven 2:1)
  4. Projde všemi pixely na řádku
  5. Přečte barvové složky pixelů, převede je s využitím metody Terminal.color_rgb na sekvenci řídicích znaků a ty následně vypíše
  6. Nový řádek je pochopitelně zajištěn zavoláním print()

Úplný zdrojový kód vypadá následovně:

from PIL import Image
import blessed
 
terminal = blessed.Terminal()
 
terminal.number_of_colors = 1 << 24
 
filename = "fruits.png"
img = Image.open(filename)
 
for j in range(0, img.height, 2):
    for i in range(img.width):
        red, green, blue = img.getpixel((i, j))
        print(f"{terminal.color_rgb(red, green, blue)}█", end="")
    print()
 
print()
print(f"{terminal.normal}DONE")

Pokud má váš terminál minimálně 129 znaků na řádku, měli byste uvidět náhled na obrázek:

Obrázek 6: První část obrázku zobrazená na terminálu.

Obrázek 7: Druhá část obrázku zobrazená na terminálu.

4. Využití mezery s modifikovanou barvou pozadí

Může se stát, že font nastavený pro použití v terminálu výše zmíněný znak „full block“ nezobrazí korektně. V takovém případě je možné prohlížeč přepsat, a to takovým způsobem, že se bude vykreslovat pouze mezera, ovšem s modifikovaným pozadím. Změnu pozadí na základě barvových složek red, green, blue zajišťuje metoda Terminal.on_color_rgb. Skript je upraven takto:

from PIL import Image
import blessed
 
terminal = blessed.Terminal()
 
terminal.number_of_colors = 1 << 24
 
filename = "fruits.png"
img = Image.open(filename)
 
for j in range(0, img.height, 2):
    for i in range(img.width):
        red, green, blue = img.getpixel((i, j))
        print(f"{terminal.on_color_rgb(red, green, blue)}.", end="")
    print()
 
print()
print(f"{terminal.normal}DONE")
Poznámka: aby bylo patrné, že se provádí odlišný způsob vykreslování, je namísto mezery použit znak tečky – „.“. Tečka je tedy zobrazena i na následující dvojici screenshotů. Přepis zpět na mezeru ve skriptu je triviální.

Obrázek 8: První část obrázku zobrazená na terminálu.

Obrázek 9: Druhá část obrázku zobrazená na terminálu.

5. Použití znaků Unicode na terminálu

Již v předchozích dvou kapitolách jsme se krátce zmínili o Unicode. Pokud je nainstalován a především nastaven korektní font, je Unicode podporován většinou moderních emulátorů terminálu (i když například v případě rxvt je nutné použít jeho variantu urxvt). Pro otestování korektnosti vykreslování Unicode znaků doporučuji použít dokument uložený na adrese https://www.cl.cam.ac.uk/~mgk25/uc­s/examples/UTF-8-demo.txt, jenž obsahuje různé části Unicode, včetně různých abeced, znaků pro pseudografiku atd. Na základě tohoto souboru je možné si vytvořit testovací program, který se spustí v terminálu a zjistí se tak jeho skutečné možnosti:

Zdrojový kód vypadá takto:

# Based on famous UTF-8-demo.txt created by:
# Markus Kuhn [ˈmaʳkʊs kuːn] <http://www.cl.cam.ac.uk/~mgk25/> — 2002-07-25
 
import blessed
 
terminal = blessed.Terminal()
 
print("""
  STARGΛ̊TE SG-1, a = v̇ = r̈, a⃑ ⊥ b⃑
                                                                      ▉
  ╔══╦══╗  ┌──┬──┐  ╭──┬──╮  ╭──┬──╮  ┏━━┳━━┓  ┎┒┏┑   ╷  ╻ ┏┯┓ ┌┰┐    ▊ ╱╲╱╲╳╳╳
  ║┌─╨─┐║  │╔═╧═╗│  │╒═╪═╕│  │╓─╁─╖│  ┃┌─╂─┐┃  ┗╃╄┙  ╶┼╴╺╋╸┠┼┨ ┝╋┥    ▋ ╲╱╲╱╳╳╳
  ║│╲ ╱│║  │║   ║│  ││ │ ││  │║ ┃ ║│  ┃│ ╿ │┃  ┍╅╆┓   ╵  ╹ ┗┷┛ └┸┘    ▌ ╱╲╱╲╳╳╳
  ╠╡ ╳ ╞╣  ├╢   ╟┤  ├┼─┼─┼┤  ├╫─╂─╫┤  ┣┿╾┼╼┿┫  ┕┛┖┚     ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
  ║│╱ ╲│║  │║   ║│  ││ │ ││  │║ ┃ ║│  ┃│ ╽ │┃  ░░▒▒▓▓██ ┊  ┆ ╎ ╏  ┇ ┋ ▎
  ║└─╥─┘║  │╚═╤═╝│  │╘═╪═╛│  │╙─╀─╜│  ┃└─╂─┘┃  ░░▒▒▓▓██ ┊  ┆ ╎ ╏  ┇ ┋ ▏
  ╚══╩══╝  └──┴──┘  ╰──┴──╯  ╰──┴──╯  ┗━━┻━━┛  ▗▄▖▛▀▜   └╌╌┘ ╎ ┗╍╍┛ ┋  ▁▂▃▄▅▆▇█
                                               ▝▀▘▙▄▟
 
 
  ∮ E⋅da = Q,  n → ∞, ∑ f(i) = ∏ g(i),      ⎧⎡⎛┌─────┐⎞⎤⎫
                                            ⎪⎢⎜│a²+b³ ⎟⎥⎪
  ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),    ⎪⎢⎜│───── ⎟⎥⎪
                                            ⎪⎢⎜⎷ c₈   ⎟⎥⎪
  ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ,                   ⎨⎢⎜       ⎟⎥⎬
                                            ⎪⎢⎜ ∞     ⎟⎥⎪
  ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (⟦A⟧ ⇔ ⟪B⟫),      ⎪⎢⎜ ⎲     ⎟⎥⎪
                                            ⎪⎢⎜ ⎳aⁱ-bⁱ⎟⎥⎪
  2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm     ⎩⎣⎝i=1    ⎠⎦⎭
""")

Příklad spuštění na mé variantě terminálu ukazuje, že ne všechny znaky se skutečně zobrazily korektně:

Obrázek 10: Výsledek testu zobrazení Unicode znaků.

Poznámka: je použit font Terminus, viz též dodatek uvedený v osmnácté kapitole.

6. Režim terminálu

Terminál může pracovat v několika režimech. Na příkazové řádce se používá takzvaný cooked mode, v němž jsou významy některých kláves interpretovány a zpracovány ještě předtím, než jsou poslány do programu. Zcela typickým příkladem je zápis „Hix(Backspace)!“, který není v cooked režimu poslán do aplikace v této podobě – aplikace totiž dostane na vstupu pouze řetězec „Hi!“, protože klávesa Backspace je mezitím interpretována a zpracována. Pro běžné vstupy a základní interaktivitu se jedná o užitečný režim, který ovšem přestane být výhodný ve chvíli, kdy je nutné reagovat (a to ihned) na stisky všech kláves. V takovém případě je nutné terminál přepnout do režimu raw nebo do režimu cbreak, jenž je někdy známý pod jménem rare (což je odvozeno od „half cooked“). V knihovně Blessed se toto přepnutí provádí metodou Terminal.cbreak():

Help on function cbreak in module blessed.terminal:
 
cbreak(self)
    Allow each keystroke to be read immediately after it is pressed.
 
    This is a context manager for :func:`tty.setcbreak`.
 
    This context manager activates 'rare' mode, the opposite of 'cooked'
    mode: On entry, :func:`tty.setcbreak` mode is activated disabling
    line-buffering of keyboard input and turning off automatic echo of
    input as output.
    ...

Obrázek 11: Hra DoomRL – hlavní menu (co mi to připomíná…).

Většinou se setkáme s tím, že se režim cbreak zapíná pouze pro určitou část programu. Využívá se zde kontextu a zápis tedy vypadá následovně:

import blessed
 
terminal = blessed.Terminal()
 
with terminal.cbreak():
    ...
    ...
    ...

Obrázek 12: Hra DoomRL – první úroveň.

7. Čtení kláves v režimu cbreak

Ve třídě Terminal nalezneme i funkci nazvanou inkey, kterou je možné použít pro čtení kódu stisknuté klávesy v režimu cbreak:

Help on function inkey in module blessed.terminal:
 
inkey(self, timeout=None, esc_delay=0.35)
    Read and return the next keyboard event within given timeout.
 
    Generally, this should be used inside the :meth:`raw` context manager.

Podívejme se nyní na příklad použití této funkce v kontextu (režimu) cbreak. Celý program, který dokáže jednotlivé klávesy číst a zobrazovat je, může vypadat následovně:

import blessed
 
terminal = blessed.Terminal()
 
with terminal.cbreak():
    while True:
        key = terminal.inkey()
        print(key)

Příklad po spuštění tohoto skriptu (po stisku různých kláves):

$ python3 inkey_1.py
+
2
3
q
w
P
Q
R

Skript lze ukončit klávesovou zkratkou Ctrl+C. Proto také používáme režim cbreak a nikoli raw, protože v režimu cbreak je tato zkratka správně rozpoznána a zpracována:

Traceback (most recent call last):
  File "inkey_1.py", line 7, in
    key = terminal.inkey()
  File "/home/ptisnovs/.local/lib/python3.9/site-packages/blessed/terminal.py", line 1385, in inkey
    while not ks and self.kbhit(timeout=_time_left(stime, timeout)):
  File "/home/ptisnovs/.local/lib/python3.9/site-packages/blessed/terminal.py", line 1203, in kbhit
    ready_r, _, _ = select.select(check_r, [], [], timeout)
KeyboardInterrupt

8. Rozpoznání jména a kódu stisknutých kláves

Hodnota, která je vrácena metodou Terminal.inkey, není typu „znak“, ale typu blessed.keyboard.Keystroke:

class Keystroke(builtins.str)
 |  A unicode-derived class for describing a single keystroke.
 |
 |  A class instance describes a single keystroke received on input,
 |  which may contain multiple characters as a multibyte sequence,
 |  which is indicated by properties :attr:`is_sequence` returning
 |  ``True``.
 |
 |  When the string is a known sequence, :attr:`code` matches terminal
 |  class attributes for comparison, such as ``term.KEY_LEFT``.
 |
 |  The string-name of the sequence, such as ``u'KEY_LEFT'`` is accessed
 |  by property :attr:`name`, and is used by the :meth:`__repr__` method
 |  to display a human-readable form of the Keystroke this class
 |  instance represents. It may otherwise by joined, split, or evaluated
 |  just as as any other unicode string.

Jedná se tedy o plnohodnotné objekty, které obsahují několik užitečných atributů. Především se jedná o jméno stisknuté klávesy a taktéž o její numerický kód. Jména a kódy všech podporovaných kláves jsou uvedeny v prvním dodatku v sedmnácté kapitole.

Samozřejmě si vše vyzkoušíme na následujícím jednoduchém skriptu:

import blessed
 
terminal = blessed.Terminal()
 
with terminal.cbreak():
    while True:
        key = terminal.inkey()
        print(key.name, key.code, key)

Ukázka použití (včetně speciálních kláves):

KEY_F1 265 P
KEY_F12 276
KEY_LEFT 260
KEY_RIGHT 261
KEY_ENTER 343
KEY_BACKSPACE 263
KEY_TAB 512
None None a
None None A
KEY_F24 288

Povšimněte si, že:

  1. U běžných ASCII kláves se nevrací ani jméno klávesy ani její numerický kód. Navíc je možné použít Shift a rozlišit tak mezi klávesou „a“ a „A“ atd.
  2. Funkční klávesy F1 atd. jsou (většinou) zpracovány korektně.
  3. Další klávesy typu Backspace, Enter a kurzorové šipky jsou taktéž zpracovány korektně.
  4. Při stisku Shift+Fx se typicky vrací klávesa Fy, kde y je zvýšeno o 12 (na PC). Například Shift+F12 vrací jméno a kód odpovídající klávese F24.

9. Sekvence znaků poslaná po stisku klávesy

Některé kombinace kláves, například Ctrl+End apod., nemají vlastní jméno ani kód. Namísto toho knihovna Blessed tuto kombinaci zaznamená jako sekvenci řídicích znaků, které aplikace může (pokud je to skutečně zapotřebí) zpracovat a následně použít, ovšem s tím, že se nebude jednat o plně přenositelný kód (což je mimochodem jeden z důvodů, proč má například Midnight Commander dialog pro „učení kláves“ a dokáže tedy zaznamenat i různé méně stabilní kombinace kláves).

Obrázek 13: Dialog Midnight Commanderu, ve kterém je možné zaznamenat sekvenci řídicích znaků po stisku neznámé klávesové kombinace.

Sekvenci řídicích znaků lze ve skriptu získat a zobrazit například takto:

import blessed
 
terminal = blessed.Terminal()
 
with terminal.cbreak():
    while True:
        key = terminal.inkey()
        if key.is_sequence:
            print("got sequence: {0}.".format(key.name, key.code, (str(key))))
        else:
            print(key.name, key.code, key)

Výsledky pro známé klávesy:

None None a
None None A
got sequence: KEY_F1.
got sequence: KEY_F12.
got sequence: KEY_F24.
got sequence: KEY_LEFT.
got sequence: KEY_RIGHT.
got sequence: KEY_UP.
got sequence: KEY_DOWN.
got sequence: KEY_ESCAPE.

Výsledky pro neznámé kombinace:

Ctrl+Up:

got sequence: KEY_ESCAPE.
None None [
None None 1
None None ;
None None 5
None None A

Ctrl+Home:

got sequence: KEY_ESCAPE.
None None [
None None 1
None None ;
None None 5
None None H

10. Neblokující čtení klávesy

Pro některé interaktivní aplikace, které například potřebují zobrazit průběžně se měnící grafy, příchozí zprávy atd. (a pochopitelně i pro hry) je někdy nutné nečekat na stisk klávesy po nekonečně dlouhou dobu. Knihovna Blessed sice programátorům nenabízí přístup do plnohodnotné smyčky událostí (event loop), ale přesto je možné alespoň specifikovat maximální dobu, po kterou se bude na stisk klávesy čekat. Příkladem může být tento skript, který čeká jen přibližně 300 milisekund a pokud klávesa stisknuta není, lze tento stav jednoznačně programově detekovat:

import blessed
 
terminal = blessed.Terminal()
 
with terminal.cbreak():
    while True:
        key = terminal.inkey(timeout=0.3)
 
        if str(key) == "":
            print("Nothing... try again")
        elif key.is_sequence:
            print("got sequence: {0}.".format(key.name, key.code, (str(key))))
        else:
            print(key.name, key.code, key)
Poznámka: nejedná se tedy o skutečně neblokující čtení, ale o přihlížení se k němu.

Příklad výstupu:

$ python3 inkey_4.py 
 
Nothing... try again
Nothing... try again
got sequence: KEY_ENTER.
None None d
None None f
None None d
None None s
Nothing... try again
got sequence: KEY_F1.
Nothing... try again

Obrázek 14: ADOM ve verzi pro DOS – podrobnější měnitelné vlastnosti hrdiny.

11. „Raw“ režim terminálu

V šesté kapitole jsme se zmínili o třech režimech terminálu – „cooked“, „raw“ a „cbreak/rare“. V prvním režimu je vstup zpracován až po stisku klávesy Enter, ve druhém režimu lze okamžitě číst stisky kláves a reagovat na ně, ovšem s tím, že některé klávesové zkratky, například Ctrl+C jsou zachyceny a zpracovány systémem (takže lze aplikaci ukončit právě s využitím Ctrl+C). A konečně v režimu „raw“ může program zachytit stisky všech kláves, a to i například již zmíněné Ctrl+C. To má své přednosti i zápory. Předností je možnost využít tyto klávesové zkratky například v textových editorech, záporem to, že se o ukončení aplikace musíme postarat sami. Jak to provést nám ukazuje tento skript:

import blessed

terminal = blessed.Terminal()

with terminal.raw():
    while True:
        key = terminal.inkey()
        if key == 'q':
            break
        print(key)

Tento skript lze ukončit stiskem klávesy q.

Obrázek 15: Angband pro Linux – podrobnější charakteristiky hrdiny.

12. Pohyb kurzoru po ploše terminálu

Na terminál se nemůžeme dívat jako na obdobu rastrového obrázku v klasickém framebufferu. Největší rozdíl spočívá v tom, že zobrazení znaku na určitém místě terminálu se provádí nepřímo – přesunem textového kurzoru na potřebné místo, které je následováno vytištěním příslušného znaku (typicky bez odřádkování). Samotné vytištění znaku je snadné, protože lze použít standardní funkci print, ovšem pro přesun kurzoru muselo být do knihovny Blessed přidáno několik metod objektu typu Terminal:

# Metoda Stručný popis metody
1 move_xy(x, y) přesun kurzoru na souřadnice [x, y]
2 move_x(x) horizontální přesun kurzoru
3 move_y(y) vertikální přesun kurzoru
4 home přesun kurzoru na souřadnice [0, 0]
     
5 move_up posun kurzoru o řádek nahoru
6 move_up(y) posun kurzoru o y řádků nahoru
7 move_down posun kurzoru o řádek dolů
8 move_down(y) posun kurzoru o y řádků dolů
     
9 move_left posun kurzoru o znak doleva
10 move_left(x) posun kurzoru o x znaků doleva
11 move_right posun kurzoru o znak doprava
12 move_right(x) posun kurzoru o x znaků doprava
Poznámka: taktéž to paradoxně znamená, že překreslení části textového terminálu může být pomalejší, než překreslení framebufferu (který obsahuje řádově více informací).

Obrázek 16: Úvodní animace legendární hry Dwarf Fortress – vstup do herního světa. Tato hra ve skutečnosti terminál pouze emuluje (tedy emuluje emulátor terminálu).

13. Ukázka skriptu přesunujícího kurzor

Následující (do co největší míry zjednodušený) skript vykreslí okolo terminálu rámeček. Kvůli jednoduchosti není terminál vymazán, pro rámeček se nepoužívají znaky Unicode a rohy nejsou vykresleny korektně:

import blessed
 
terminal = blessed.Terminal()
 
with terminal.cbreak():
    for x in range(terminal.width):
        print(f"{terminal.move_xy(x, 0)}-", end="", flush=True)
        print(f"{terminal.move_xy(x, terminal.height-1)}-", end="", flush=True)
 
    for y in range(terminal.height):
        print(f"{terminal.move_xy(0, y)}|", end="", flush=True)
        print(f"{terminal.move_xy(terminal.width-1, y)}|", end="", flush=True)
 
    terminal.inkey()

Obrázek 17: Testovací aréna hry Dwarf Fortress.

14. Výpočet umístění textu s řídicími znaky, obnovení původní pozice kurzoru

V případě, že je zapotřebí zobrazit například vycentrovaný text, který ovšem obsahuje i příkazy (řídicí znaky) pro změnu stylu či barvy, je možné pro tento účel použít jednu ze čtyř funkcí poskytovaných přímo knihovnou Blessed. Tyto funkce zajistí, že se vypočítá korektní umístění řetězce i s přihlédnutím k řídicím znakům (které mají při zobrazení nulovou šířku):

# Metoda Stručný popis
1 center(text, width=None, fillchar=' ') vycentrování textu na zadanou šířku
2 ljust(text, width=None, fillchar=' ') úprava textu takovým způsobem, aby byl zarovnán doprava na zadanou šířku
3 rjust(text, width=None, fillchar=' ') úprava textu takovým způsobem, aby byl zarovnán doprava na zadanou šířku
4 wrap(text, width=None, …) vrací seznam řádků získaných zarovnáním textu na zadanou šířku
Poznámka: jak je z hlavičky metod patrné, je možné zvolit i znaky, které budou použity jako výplň namísto mezer.

Další užitečnou metodou je metoda nazvaná Terminal.location, která vytvoří nový kontext, v němž se zapamatuje původní pozice textového kurzoru:

with terminal.location(nastavení umístění kurzoru):
    ...
    ...
    ...

V dalším demonstračním příkladu je ukázáno jak použití metody location společně s vycentrováním textu na zadanou šířku:

import blessed
 
terminal = blessed.Terminal()
 
with terminal.cbreak():
    for x in range(terminal.width):
        print(f"{terminal.move_xy(x, 0)}-", end="", flush=True)
        print(f"{terminal.move_xy(x, terminal.height-1)}-", end="", flush=True)
 
    for y in range(terminal.height-1):
        print(f"{terminal.move_xy(0, y)}|", end="", flush=True)
        print(f"{terminal.move_xy(terminal.width-1, y)}|", end="", flush=True)
 
    with terminal.location(y=terminal.height // 2):
        print(terminal.center(terminal.bold("Press any key to exit...")))
 
    terminal.inkey()

Obrázek 18: Různé ikony představující trpaslíky ve hře Dwarf Fortress (použito v režimu fortress).

15. Přeformátování textu

Podívejme se ještě na metodu Terminal.wrap zmíněnou v předchozí kapitole. Tuto metodu je možné použít pro zalomení textu do zvolené šířky. Vše si otestujeme interaktivně přímo v REPLu programovacího jazyka Python:

>>> import blessed
 
>>> terminal = blessed.Terminal()
 
>>> t = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. "
 
>>> lines = terminal.wrap(t, width=40)
>>> for line in lines:
...     print(line)
...
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit
in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim
id est laborum.

Totéž, ovšem s textem, který obsahuje řídicí znaky pro změnu barvy a stylu písma:

>>> t = f"{terminal.red}Lorem ipsum {terminal.yellow}dolor{terminal.normal} sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. {terminal.bold}Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat{terminal.normal}. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. "
 
>>> li;
>>> lnes = terminal.wrap(t, width=40)
>>> for line in lines:
...     print(line)
...
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit
in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim
id est laborum.

Obrázek 19: Vytváření historie herního světa v Dward Fortress.

16. Chybějící funkcionalita

V knihovně Blessed některé funkce nenalezneme. Především se jedná o sadu textových ovládacích prvků (widgetů), což by však nemuselo vadit, protože pro tyto účely již existují jiné knihovny (prompt_toolkit atd.). Nenalezneme zde ani funkce či metody, které na ploše terminálů vytváří vlastní (relativně nezávislá) okna, což je opět řešeno dalšími balíčky (a nejedná se o zcela triviální problematiku, zejména při použití překrývajících se oken). A pro tvorbu interaktivnějších her chybí především obdoba funkce delay, kterou je nutné získat z jiných balíčků určených pro jazyk Python.

xyzzy

Obrázek 20: Pro tvorbu rozhraní ve stylu TurboVision je vhodnější použít jiný nástroj, než knihovnu Blessed.

17. Dodatek 1: klávesy rozeznávané knihovnou Blessed

V tomto dodatku jsou vypsána jména a číselné kódy všech kláves rozpoznávaných knihovnou Blessed. Mnoho kláves na vaší klávesnici nenajdete, protože se jedná o dnes již historické pozůstatky po starších terminálech a počítačích (ostatně jestli chce navštívit lokální počítačové muzeum, podívejte se do databáze termcap a terminfo):

Jméno Kód Jméno Kód
KEY_BACKSPACE 263 KEY_FIND 362
KEY_BEGIN 354 KEY_HELP 363
KEY_BTAB 353 KEY_HOME 262
KEY_C1 351 KEY_IL 329
KEY_C3 352 KEY_INSERT 331
KEY_CANCEL 355 KEY_KP0 520
KEY_CATAB 342 KEY_KP1 521
KEY_CENTER 350 KEY_KP2 522
KEY_CLEAR 333 KEY_KP3 523
KEY_CLOSE 356 KEY_KP4 524
KEY_COMMAND 357 KEY_KP5 525
KEY_COPY 358 KEY_KP6 526
KEY_CREATE 359 KEY_KP7 527
KEY_CTAB 341 KEY_KP8 528
KEY_DELETE 330 KEY_KP9 529
KEY_DL 328 KEY_KP_ADD 514
KEY_DOWN 258 KEY_KP_DECIMAL 517
KEY_EIC 332 KEY_KP_DIVIDE 518
KEY_END 360 KEY_KP_EQUAL 519
KEY_ENTER 343 KEY_KP_MULTIPLY 513
KEY_EOL 335 KEY_KP_SEPARATOR 515
KEY_EOS 334 KEY_KP_SUBTRACT 516
KEY_ESCAPE 361 KEY_LEFT 260
KEY_F0 264 KEY_LL 347
KEY_F1 265 KEY_MARK 364
KEY_F2 266 KEY_MAX 511
KEY_F3 267 KEY_MESSAGE 365
KEY_F4 268 KEY_MIN 257
KEY_F5 269 KEY_MOUSE 409
KEY_F6 270 KEY_MOVE 366
KEY_F7 271 KEY_NEXT 367
KEY_F8 272 KEY_OPEN 368
KEY_F9 273 KEY_OPTIONS 369
KEY_F10 274 KEY_PGDOWN 338
KEY_F11 275 KEY_PGUP 339
KEY_F12 276 KEY_PREVIOUS 370
KEY_F13 277 KEY_PRINT 346
KEY_F14 278 KEY_REDO 371
KEY_F15 279 KEY_REFERENCE 372
KEY_F16 280 KEY_REFRESH 373
KEY_F17 281 KEY_REPLACE 374
KEY_F18 282 KEY_RESET 345
KEY_F19 283 KEY_RESIZE 410
KEY_F20 284 KEY_RESTART 375
KEY_F21 285 KEY_RESUME 376
KEY_F22 286 KEY_RIGHT 261
KEY_F23 287 KEY_SAVE 377
KEY_SBEG 378 KEY_SCANCEL 379
KEY_SCOMMAND 380 KEY_SCOPY 381
KEY_SCREATE 382 KEY_SDC 383
KEY_SDL 384 KEY_SDOWN 336
KEY_SELECT 385 KEY_SEND 386
KEY_SEOL 387 KEY_SEXIT 388
KEY_SFIND 389 KEY_SHELP 390
KEY_SHOME 391 KEY_SIC 392
KEY_SLEFT 393 KEY_SMESSAGE 394
KEY_SMOVE 395 KEY_SNEXT 396
KEY_SOPTIONS 397 KEY_SPREVIOUS 398
KEY_SPRINT 399 KEY_SREDO 400
KEY_SREPLACE 401 KEY_SRESET 344
KEY_SRIGHT 402 KEY_SRSUME 403
KEY_SSAVE 404 KEY_SSUSPEND 405
KEY_STAB 340 KEY_SUNDO 406
KEY_SUP 337 KEY_SUSPEND 407
KEY_TAB 512 KEY_UNDO 408
KEY_UP 259 KEY_UP_LEFT 348
KEY_UP_RIGHT 349  

18. Dodatek 2: příklad nastavení xtermu

V dodatku je ukázáno praktické nastavení xtermu (což ani zdaleka není primitivní terminál – jen své vlastnosti před uživateli dobře skrývá) v souboru .Xresources. Povšimněte si přemapování logických barev na barvy fyzické:

xterm*eightBitControl: false
xterm*eightBitInput:   false
xterm*eightBitOutput:  true
xterm*utf8:            1
xterm*background:  gray90
xterm*foreground:  Black
xterm*cursorColor: rgb:ff/00/00
xterm*saveLines: 1000
xterm*color0: black
xterm*color1: red3
xterm*color2: green3
xterm*color3: brown
xterm*color4: blue3
xterm*color5: magenta3
xterm*color6: cyan4
xterm*color7: gray50
xterm*color8: gray40
xterm*color9: red
xterm*color10: green3
xterm*color11: Goldenrod
xterm*color12: DodgerBlue1
xterm*color13: magenta2
xterm*color14: cyan3
xterm*color15: white
xterm*colorUL: yellow
xterm*colorBD: white
xterm*scrollBar: false
xterm*rightScrollBar: true
xterm*font:     -*-terminus-bold-r-*-*-*-240-*-*-*-*-iso10646-1
xterm*boldFont:     -*-terminus-bold-r-*-*-*-240-*-*-*-*-iso10646-1
xterm*geometry: 80x25
XTerm*fullscreen: never
XTerm.omitTranslation: fullscreen
Xterm.sessionMgt: false

Tento soubor se zpracovává nástrojem xrdb, a to následovně:

bitcoin školení listopad 24

$ xrdb ~/.Xresources

Nebo častěji:

$ xrdb -merge ~/.Xresources
Poznámka: pro více info viz xrdb(1).

19. Repositář s demonstračními příklady

Zdrojové kódy všech minule i dnes popsaných demonstračních příkladů určených pro Python 3 byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Jméno souboru Stručný popis souboru Cesta
1 terminal_info.py přečtení základních informací o terminálu https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/ter­minal_info.py
2 clear_screen.py smazání obrazovky terminálu https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/cle­ar_screen.py
       
3 text_styles1.py nastavení stylu vykreslení textu https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/tex­t_styles1.py
4 text_styles2.py použití https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/tex­t_styles2.py
       
5 basic8_colors.py základních osm barev textu podporovaných většinou terminálů https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/ba­sic8_colors.py
6 basic8_colors_bold.py modifikátor „bold“ ovlivňující barvu textu https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/ba­sic8_colors_bold.py
7 basic8_backgrounds.py základních osm barev pozadí podporovaných většinou terminálů https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/ba­sic8_backgrounds.py
8 basic8_bold_backgrounds.py modifikátor „bold“ ovlivňující barvu pozadí https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/ba­sic8_bold_backgrounds.py
9 basic8_combinations.py kombinace barev textu a barev pozadí (64 různých kombinací) https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/ba­sic8_combinations.py
       
10 palette.py plnohodnotná barvová paleta (true color) na popředí https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/palette.py
11 palette_background.py plnohodnotná barvová paleta (true color) na pozadí https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/pa­lette_background.py
12 named_colors.py použití jmen barev https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/na­med_colors.py
13 named_colors_background.py použití jmen barev pro specifikaci barvy pozadí https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/na­med_colors_background.py
       
14 hyperlink.py hypertextkový odkaz v terminálu https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/hyperlink.py
       
15 get_image.py skript pro stažení testovacího obrázku s jeho zmenšením pro další operace https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/get_image.py
16 show_image1.py první varianta prohlížeče obrázků https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/show_i­mage1.py
17 show_image2.py druhá varianta prohlížeče obrázků https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/show_i­mage2.py
       
18 unicode_test.py test vykreslení znaků z Unicode https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/u­nicode_test.py
       
19 inkey1.py čtení stisknutých kláves, nejjednodušší varianta https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/inkey1.py
20 inkey2.py rozpoznání jména a kódu stisknutých kláves https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/inkey2.py
21 inkey3.py sekvence znaků poslaná po stisku klávesy https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/inkey3.py
22 inkey4.py neblokující čtení klávesy https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/inkey4.py
23 inkey5.py „raw“ režim terminálu https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/inkey5.py
       
24 movexy.py použití metody move_xy pro přesun kurzoru https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/movexy.py
25 movexy_restore_location.py použití metody move_xy pro přesun kurzoru, obnovení pozice kurzoru https://github.com/tisnik/most-popular-python-libs/blob/master/blessed/mo­vexy_restore_location.py

20. Odkazy na Internetu

  1. blessed na PyPi
    https://pypi.org/project/blessed/
  2. blessed na GitHubu
    https://github.com/jquast/blessed
  3. Blessed documentation!
    https://blessed.readthedoc­s.io/en/latest/
  4. termbox-go na GitHubu
    https://github.com/nsf/termbox-go
  5. termui na GitHubu
    https://github.com/gizak/termui
  6. blessed na GitHubu
    https://github.com/chjj/blessed
  7. blessed-contrib na GitHubu
    https://github.com/yaronn/blessed-contrib
  8. tui-rs na GitHubu
    https://github.com/fdehau/tui-rs
  9. Operace s framebufferem na Raspberry Pi
    https://www.root.cz/clanky/operace-s-framebufferem-na-raspberry-pi/
  10. Framebuffer na Raspberry Pi: vykreslování složitějších objektů
    https://www.root.cz/clanky/framebuffer-na-raspberry-pi-vykreslovani-slozitejsich-objektu/
  11. 256 COLORS – CHEAT SHEET
    https://jonasjacek.github.io/colors/
  12. Terminfo (Wikipedia)
    https://en.wikipedia.org/wi­ki/Terminfo
  13. Termcap (Wikipedia)
    https://en.wikipedia.org/wiki/Termcap
  14. Python 3's f-Strings: An Improved String Formatting Syntax (Guide)
    https://realpython.com/python-f-strings/
  15. Top 20 Best ASCII Games on Linux System
    https://www.ubuntupit.com/best-ascii-games-on-linux/
  16. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  17. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  18. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  19. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  20. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  21. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  22. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  23. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  24. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  25. Editline Library (libedit)
    http://thrysoee.dk/editline/
  26. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  27. IBM 2741
    https://en.wikipedia.org/wi­ki/IBM_2741
  28. Terminal mode
    https://en.wikipedia.org/wi­ki/Terminal_mode
  29. Box-drawing character
    https://en.wikipedia.org/wiki/Box-drawing_character
  30. Angrenost – brána do světa J.R.R.Tolkiena (nejedná se o popis hry)
    http://www.angrenost.cz/
  31. Angband na rephial.org
    http://rephial.org/
  32. Angband – stránka s možností downloadu hry
    http://angband.oook.cz/download.php
  33. Angband a její klony (varianty)
    http://angband.oook.cz/variants.php
  34. Další seznam klonů hry Angband (podrobnější)
    http://roguebasin.rogueli­kedevelopment.org/index.php?ti­tle=List_of_Angband_varian­ts
  35. Angband (pevnost ve Středozemi)
    http://en.wikipedia.org/wiki/Angband
  36. Angband (hra)
    http://en.wikipedia.org/wi­ki/Angband_(video_game)
  37. Doom, the Roguelike
    http://doomwiki.org/wiki/DoomRL
  38. Roguelike evolution
    http://roguebasin.rogueli­kedevelopment.org/index.php?ti­tle=Tree_of_roguelike_evo­lution
  39. Roguelike (Wikipedia)
    http://en.wikipedia.org/wi­ki/Roguelike
  40. Brogue Home Page
    https://sites.google.com/si­te/broguegame/
  41. Brogue (Roguelike wiki)
    http://roguebasin.rogueli­kedevelopment.org/index.php?ti­tle=Brogue
  42. Zangband.org
    http://www.zangband.org/
  43. Dungeon crawl (Wikipedia)
    http://en.wikipedia.org/wi­ki/Dungeon_crawl
  44. FULL BLOCK (znak Unicode)
    https://www.unicodepedia.com/u­nicode/block-elements/2588/full-block/
  45. Unicode.org
    https://www.unicode.org/main.html
  46. Screenshot ze hry Brogue
    https://drive.google.com/file/d/1-_bjBA2rewDnleV87cu4PiytXzVWkWkT/view

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.