Към съдържанието
wannacode

Стек от екрани

Накратко: Стекът от екрани пази отворените екрани един върху друг: входът отива към горния, push отваря нов, pop го затваря, replace го сменя, а прозрачните се рисуват върху тези под тях.

Проблем

Една променлива за всички екрани

Играта има начален екран, село, карта, бой, инвентар, който се отваря върху картата, и меню, което се отваря върху боя. Всеки екран разбира командите по своему: на картата a е крачка наляво, в боя е удар, в инвентара е нищо.

Първият опит, който почти всеки прави: една променлива с името на екрана и if по нея. Веднъж в обработката на командата и още веднъж в рисуването:

import sys
screen = "title"
wolf = 2
for line in sys.stdin:
command = line.strip()
if screen == "title":
if command == "n":
screen = "map"
elif screen == "map":
if command == "i":
screen = "inventory"
if command == "f":
screen = "battle"
elif screen == "battle":
if command == "i":
screen = "inventory"
if command == "a":
wolf -= 1
if wolf == 0:
screen = "map"
elif screen == "inventory":
if command == "x":
screen = "map"
print(command + " → ", end="")
if screen == "title":
print("Хрониките на Арканум")
elif screen == "map":
print(f"Карта, вълк: {wolf}")
elif screen == "battle":
print(f"Бой, вълк: {wolf}")
elif screen == "inventory":
print("Инвентар: Отвара x2")
Изход
n → Карта, вълк: 2
i → Инвентар: Отвара x2
x → Карта, вълк: 2
f → Бой, вълк: 2
i → Инвентар: Отвара x2
x → Карта, вълк: 2
a → Карта, вълк: 2
a → Карта, вълк: 2

Командите идват по една на ред: n нова игра, i инвентар, x затваря, f бой с вълка, a удар.

Вход
n
i
x
f
i
x
a
a

Виж шестия ред. Инвентарът е отворен от боя, а x го затваря към картата. Боят е изчезнал, вълкът е жив с 2 живот, а двата удара след това падат на картата, където a не значи нищо. Променливата помни къде си, но не и откъде дойде.

Второто се вижда, когато играта порасне. Всеки нов екран е нов клон във всеки if: в обработката, в рисуването и във всеки екран, от който се отваря. Меню върху боя и върху картата е още два клона на две места. Забравиш ли един, получаваш екран, който приема команди, но не се рисува.

Идеята

Екранът решава сам за себе си

Обръщаме въпроса. Вместо цикълът да пита „на кой екран съм“ и да знае всеки екран, всеки екран е обект с едни и същи четири метода:

  • handle: какво значи командата тук;
  • update: какво става всеки ход, докато екранът е отгоре (в боя вълкът хапе);
  • draw: какво рисува екранът;
  • is_transparent: дали екранът под него се вижда.
class Screen(ABC):
def __init__(self, game, name):
self.game = game
self.name = name
def is_transparent(self):
return False
@abstractmethod
def handle(self, command):
pass
def update(self):
pass
@abstractmethod
def draw(self, buffer):
pass
ABC и @abstractmethod казват, че всеки екран е длъжен да има свой handle и свой draw: екран без тях не може да се създаде (TypeError). update и is_transparent имат поведение по подразбиране.

Ето боя и инвентара. Боят знае само за себе си: i отваря инвентара отгоре, a удря, а когато вълкът падне, боят маха себе си. Инвентарът не знае откъде е отворен. Той само казва „махни ме“:

class BattleScreen(Screen):
def __init__(self, game):
super().__init__(game, "Бой")
def handle(self, command):
if command == "i":
self.game.stack.push(InventoryScreen(self.game))
if command == "a":
self.game.wolf -= 1
if self.game.wolf == 0:
self.game.stack.pop()
# Всеки ход на боя вълкът хапе.
def update(self):
if self.game.wolf > 0:
self.game.life -= 1
def draw(self, buffer):
buffer.box(0, 0, TextBuffer.W, TextBuffer.H)
buffer.write(2, 1, f"Вълк: {self.game.wolf}")
buffer.write(2, 2, f"Живот: {self.game.life}")
buffer.write(2, 3, "a: удар i: инвентар")
class InventoryScreen(Screen):
def __init__(self, game):
super().__init__(game, "Инвентар")
def handle(self, command):
if command == "x":
self.game.stack.pop()
def draw(self, buffer):
buffer.box(12, 1, 11, 3)
buffer.write(13, 2, "Отвара x2")

Екраните рисуват в TextBuffer: пет реда по 24 знака. write пише текст от дадено място, box рисува рамка. Целият е в програмата по-долу, а как се рисува в истинския терминал, е в урока за рисуването.

Нов екран е нов клас и един ред в екрана, който го отваря. Никой if по имена на екрани не расте.

По-точно, за любопитните

Така е в играта. Регионът screen от канона:

# Всеки екран е обект с еднакъв интерфейс. Играта не пита „на кой екран съм“, а праща командата към
# екрана на върха на стека.
class Screen(ABC):
def __init__(self, game):
self.game = game
# Прозрачният екран (меню, инвентар, диалог) оставя екрана под себе си да се вижда.
def is_transparent(self):
return False
@abstractmethod
def handle(self, command):
pass
def update(self):
pass
@abstractmethod
def draw(self, buffer):
pass
# Екранът под попнатия получава неговия отговор (избрана цел, „да“ или „не“, избран слот).
def on_result(self, result):
pass

Методът on_result е за екрани, които връщат отговор (избраната цел, „да“ или „не“). За него е следващият урок.

Идеята

Стекът: откъдето дойде

Екраните са един върху друг: инвентар върху боя, бой върху картата. Когато затвориш горния, искаш да си точно там, където беше. Това е стек: последният сложен се маха пръв. Стекът от екрани има три операции:

  • push: отвори нов екран отгоре (картата отваря боя, боят отваря инвентара);
  • pop: затвори горния и се върни към този под него;
  • replace: смени горния с нов, без връщане (началният екран става карта, създаването на героя става село).
class ScreenStack:
def __init__(self):
self.screens = []
def push(self, screen):
self.screens.append(screen)
def pop(self):
self.screens.pop()
def replace(self, screen):
self.pop()
self.push(screen)
def top(self):
return self.screens[-1]
def names(self):
return "[" + ", ".join(s.name for s in self.screens) + "]"
def draw(self, buffer):
self.top().draw(buffer)

Цикълът на играта дава командата само на върха. Не на картата, не на боя под инвентара. Само върхът знае какво значи командата сега. Същите команди, сега със стека. show печата стека и кадъра след всяка команда:

def show(game, buffer):
print(game.stack.names())
buffer.clear()
game.stack.draw(buffer)
buffer.print()
game = Game()
buffer = TextBuffer()
game.stack.push(TitleScreen(game))
show(game, buffer)
for line in sys.stdin:
command = line.strip()
print(f"> {command}")
top = game.stack.top()
top.handle(command)
top.update()
show(game, buffer)
Изход
[Начало]
Хрониките на Арканум
n: нова игра
> n
[Карта]
########################
#..@......~~~..........#
#.......w.~~~..........#
########################
Живот: 10
> i
[Карта, Инвентар]
+---------+
|Отвара x2|
+---------+
> x
[Карта]
########################
#..@......~~~..........#
#.......w.~~~..........#
########################
Живот: 10
> f
[Карта, Бой]
+----------------------+
| Вълк: 2 |
| Живот: 10 |
| a: удар i: инвентар |
+----------------------+
> i
[Карта, Бой, Инвентар]
+---------+
|Отвара x2|
+---------+
> x
[Карта, Бой]
+----------------------+
| Вълк: 2 |
| Живот: 9 |
| a: удар i: инвентар |
+----------------------+
> a
[Карта, Бой]
+----------------------+
| Вълк: 1 |
| Живот: 8 |
| a: удар i: инвентар |
+----------------------+
> a
[Карта]
########################
#..@......~~~..........#
#.........~~~..........#
########################
Живот: 8

Инвентарът, отворен от боя, сега се затваря към боя: [Карта, Бой]. Вълкът пада от втория удар, боят маха себе си и отдолу е картата, вече без w. Никой екран не помни откъде е дошъл. Помни го стекът.

Само че виж кадрите с инвентара. Картата е изчезнала, а рамката виси в празно. draw на стека рисува само върха. За това е след малко.

По-точно, за любопитните

Само върхът се обновява. Цикълът вика update на върха. Докато инвентарът е отворен върху боя, боят чака и вълкът не хапе: играта е на пауза под инвентара. Това е избор, не закон. В игра в реално време светът под менюто може да продължи, и тогава стекът обновява и екрани под върха, които кажат, че не спират.

pop на празен стек. Ако последният екран махне себе си, стекът е празен и следващото махане няма какво да махне. В Python pop() на празен списък хвърля IndexError. В играта празният стек значи край: цикълът върти, докато стекът не опустее, а „Изход“ от началния екран изчиства стека.

Пробвай

Предвиди изхода

Стекът е празен. Какво е в него накрая?

game = Game()
stack = game.stack
stack.push(TitleScreen(game))
stack.replace(MapScreen(game))
stack.push(BattleScreen(game))
stack.push(InventoryScreen(game))
stack.pop()
stack.pop()
stack.push(InventoryScreen(game))
print(stack.names())
Отговор

Идеята

Прозрачните екрани

Инвентарът е малка рамка върху картата. За да се види картата около нея, картата трябва да се нарисува първа, а инвентарът след нея, върху нея. Като художник, който рисува първо фона, после фигурите отпред.

Но не от най-долния екран. Под боя е картата, а боят я закрива целия: да я рисуваш е напразно. Затова рисуването започва от най-горния непрозрачен екран. Тръгваш от върха надолу, докато екраните прозират. Първият, който не прозира, е началото. Оттам рисуваш нагоре:

def draw(self, buffer):
first = len(self.screens) - 1
while first > 0 and self.screens[first].is_transparent():
first -= 1
for i in range(first, len(self.screens)):
self.screens[i].draw(buffer)

Инвентарът казва, че прозира:

class InventoryScreen(Screen):
def __init__(self, game):
super().__init__(game, "Инвентар")
def is_transparent(self):
return True
def handle(self, command):
if command == "x":
self.game.stack.pop()
def draw(self, buffer):
buffer.box(12, 1, 11, 3)
buffer.write(13, 2, "Отвара x2")

Само тези две промени. Същият цикъл, същите команди:

Изход
[Начало]
Хрониките на Арканум
n: нова игра
> n
[Карта]
########################
#..@......~~~..........#
#.......w.~~~..........#
########################
Живот: 10
> i
[Карта, Инвентар]
########################
#..@......~~+---------+#
#.......w.~~|Отвара x2|#
############+---------+#
Живот: 10
> x
[Карта]
########################
#..@......~~~..........#
#.......w.~~~..........#
########################
Живот: 10
> f
[Карта, Бой]
+----------------------+
| Вълк: 2 |
| Живот: 10 |
| a: удар i: инвентар |
+----------------------+
> i
[Карта, Бой, Инвентар]
+----------------------+
| Вълк: 2 +---------+|
| Живот: 9 |Отвара x2||
| a: удар i+---------+|
+----------------------+
> x
[Карта, Бой]
+----------------------+
| Вълк: 2 |
| Живот: 9 |
| a: удар i: инвентар |
+----------------------+
> a
[Карта, Бой]
+----------------------+
| Вълк: 1 |
| Живот: 8 |
| a: удар i: инвентар |
+----------------------+
> a
[Карта]
########################
#..@......~~~..........#
#.........~~~..........#
########################
Живот: 8

[Карта, Инвентар]: картата, а върху нея рамката, а вълкът се вижда отдолу. [Карта, Бой, Инвентар]: рисуването започва от боя, защото той не прозира. Картата под него не се рисува изобщо.

first > 0 спира на най-долния екран: той се рисува винаги, дори да прозира, защото под него няма нищо.

По-точно, за любопитните

Методът draw от региона screen-stack на канона:

# Рисуването започва от най-горния непрозрачен екран и върви нагоре.
def draw(self, buffer):
first = len(self.screens) - 1
while first > 0 and self.screens[first].is_transparent():
first -= 1
for i in range(first, len(self.screens)):
if i >= 0:
self.screens[i].draw(buffer)

Проверката i >= 0 е за празен стек: тогава first е −1 и няма какво да се рисува.

Защо не просто от нулата? Ако всеки непрозрачен екран закрива всичко, резултатът е същият, но всеки кадър рисува всички екрани отдолу: карта, бой, избор на цел, диалог. Има и по-лошо: непрозрачен екран, който не пише във всяка клетка, ще остави да личат парчета от картата. „Не прозирам“ е обещание „покривам всичко“, и стекът разчита на него.

Виж го

Ход по ход

Същата програма с прозрачния инвентар. Отляво е стекът: всяка плоча е един екран с това, което той рисува сам. Командата отива към плочата на върха. Бледите плочи са под най-горната непрозрачна и не се рисуват. Под стека е кадърът, който програмата печата.

class ScreenStack:
def __init__(self):
self.screens = []
def push(self, screen):
self.screens.append(screen)
def pop(self):
self.screens.pop()
def replace(self, screen):
self.pop()
self.push(screen)
def top(self):
return self.screens[-1]
def names(self):
return "[" + ", ".join(s.name for s in self.screens) + "]"
def draw(self, buffer):
first = len(self.screens) - 1
while first > 0 and self.screens[first].is_transparent():
first -= 1
for i in range(first, len(self.screens)):
self.screens[i].draw(buffer)
for line in sys.stdin:
command = line.strip()
print(f"> {command}")
top = game.stack.top()
top.handle(command)
top.update()
show(game, buffer)
Стъпка по стъпка, с думи
  1. Играта започва с един екран в стека: началния. Той е на върха и получава входа.
  2. n отива към началния екран. Той слага картата на своето място (replace): началният екран няма да се върне.
  3. i отива към картата и тя слага инвентара отгоре (push). Инвентарът прозира: първо се рисува картата, после инвентарът върху нея.
  4. x отива към инвентара: той се маха (pop). Отдолу е картата, точно каквато я остави.
  5. f: картата слага боя отгоре (push). Боят не прозира, затова картата под него изобщо не се рисува.
  6. Сега i отива към боя, не към картата. Боят отваря инвентара над себе си. Рисуването започва от боя, най-горния непрозрачен екран. Вълкът ухапа: живот 9.
  7. x затваря инвентара и отдолу е боят: връщаш се там, откъдето дойде. Единствената променлива от началото на урока тук те върна на картата.
  8. a отива към боя: вълкът е ранен, а стекът не се променя. Минава update на боя: вълкът хапе, живот 8.
  9. Вълкът пада. Боят се маха (pop) и картата пак е на върха, вече без вълка.
  10. Входът отива към върха. Рисуването върви нагоре от най-горния непрозрачен екран. Екраните, които връщат отговор, и промените, които чакат края на хода, са в следващия урок.

Пробвай

Попълни празнината

Рисуването на стека. Докато екранът на first прозира, слизаш надолу:

def draw(self, buffer):
    first = len(self.screens) - 1
    while first > 0 and :
        first -= 1
    for i in range(first, len(self.screens)):
        self.screens[i].draw(buffer)

Пробвай

Намери грешката

Колега написа стека наново. Програмата с командите n, i, x печата:

Изход
[Начало]
Хрониките на Арканум
n: нова игра
> n
[Карта]
########################
#..@......~~~..........#
#.......w.~~~..........#
########################
Живот: 10
> i
[Карта, Инвентар]
########################
#..@......~~+---------+#
#.......w.~~|Отвара x2|#
############+---------+#
Живот: 10
> x
[Карта, Инвентар]
########################
#..@......~~+---------+#
#.......w.~~|Отвара x2|#
############+---------+#
Живот: 10

Инвентарът не се затваря: след x стекът пак е [Карта, Инвентар]. На кой ред е грешката?

Избери реда с грешката.

Идеята

Стекът в истинската игра

Регионът screen-stack от канона. Методите са същите, но има разлики:

# Стекът не се променя по време на обработката: push, pop и replace само записват промяната, а
# apply() ги прилага след хода. Иначе екран, който се маха от стека, би продължил да работи.
class ScreenStack:
def __init__(self):
self.screens = []
self.changes = []
def push(self, screen):
self.changes.append(SimpleNamespace(kind='push', screen=screen, result=None))
def pop(self, result):
self.changes.append(SimpleNamespace(kind='pop', screen=None, result=result))
def replace(self, screen):
self.changes.append(SimpleNamespace(kind='replace', screen=screen, result=None))
# Изчиства целия стек (зареждане, край на играта); с None стекът остава празен.
def reset(self, screen):
self.changes.append(SimpleNamespace(kind='reset', screen=screen, result=None))
def top(self):
return None if len(self.screens) == 0 else self.screens[-1]
def is_empty(self):
return len(self.screens) == 0 and len(self.changes) == 0
def contains(self, kind_name):
for screen in self.screens:
if getattr(screen, 'kind_name', None) == kind_name:
return True
return False
def apply(self):
index = 0
while index < len(self.changes):
change = self.changes[index]
index += 1
if change.kind == 'push':
self.screens.append(change.screen)
elif change.kind == 'pop':
# pop() на празен списък в Python е грешка; в JavaScript връща undefined.
if len(self.screens) > 0:
self.screens.pop()
below = self.top()
if below is not None:
below.on_result(change.result)
elif change.kind == 'replace':
if len(self.screens) > 0:
self.screens.pop()
self.screens.append(change.screen)
else:
self.screens = []
if change.screen is not None:
self.screens.append(change.screen)
self.changes = []
# Рисуването започва от най-горния непрозрачен екран и върви нагоре.
def draw(self, buffer):
first = len(self.screens) - 1
while first > 0 and self.screens[first].is_transparent():
first -= 1
for i in range(first, len(self.screens)):
if i >= 0:
self.screens[i].draw(buffer)
  • push, pop и replace не пипат списъка веднага. Записват промяната в changes, а apply() ги изпълнява след хода. В нашата програма боят маха себе си в handle, а веднага след това цикълът вика update на същия бой, който вече не е в стека. Тук минава, защото мъртъв вълк не хапе. В по-голяма игра това е бъг, който се търси дълго. Защо и как се отлагат промените, е следващият урок.
  • pop носи отговор: екранът отдолу го получава в on_result. Така екранът за избор на цел връща целта на боя. И това е в следващия урок.
  • reset изчиства целия стек (зареждане на игра, край на играта), а contains пита дали екран от даден вид вече е вътре (например да не се отвори второ качване на ниво).

По-точно

Същата идея другаде

Навигацията в мобилните приложения е стек от екрани. В Android и iOS отваряш екран отгоре (push), а бутонът „Назад“ го маха (pop). Влизането след логин обикновено е replace: назад не трябва да води пак към формата за вход.

Модалният диалог в браузъра е екран върху страницата. Докато е отворен, само той получава кликове и клавиши, а страницата под него се вижда, но не реагира. Същото като инвентара върху картата.

Стекът на извикванията е същата идея в самия език. Метод вика друг метод (push), той връща (pop), и извикващият продължава точно откъдето е спрял. Боят продължава след инвентара по същия начин.

Кога стекът не стига. Стекът казва, че отгоре е точно един екран и той получава всичко. Понякога това не е вярно:

  • два екрана един до друг, които работят заедно: карта и мини-карта, бой и дневник на боя, игра за двама на разделен екран. Тогава екранът е дърво от панели, а входът отива към панела на фокуса;
  • неща, които са винаги отгоре, независимо от екрана: лента с живота, известие „Играта е записана“. Те са отделен слой, който се рисува след стека;
  • прозорци, които се затварят в произволен ред (стратегия с пет отворени прозореца). Това вече не е стек, а списък с фокус.

Упражнение

Меню върху всичко

Реши задачата в редактора си. Цялата програма с прозрачните екрани е под задачата.

Меню. Добави MenuScreen с име „Меню“. m го отваря от картата, от боя и от инвентара. Менюто прозира, а x го затваря. Рисува рамка box(14, 2, 10, 3) и в нея x: назад от място (15, 3).

Вход
n
f
i
m
x
x
a
a
Изход
[Начало]
Хрониките на Арканум
n: нова игра
> n
[Карта]
########################
#..@......~~~..........#
#.......w.~~~..........#
########################
Живот: 10
> f
[Карта, Бой]
+----------------------+
| Вълк: 2 |
| Живот: 10 |
| a: удар i: инвентар |
+----------------------+
> i
[Карта, Бой, Инвентар]
+----------------------+
| Вълк: 2 +---------+|
| Живот: 9 |Отвара x2||
| a: удар i+---------+|
+----------------------+
> m
[Карта, Бой, Инвентар, Меню]
+----------------------+
| Вълк: 2 +---------+|
| Живот: 9 |О+--------+
| a: удар i+-|x: назад|
+-------------+--------+
> x
[Карта, Бой, Инвентар]
+----------------------+
| Вълк: 2 +---------+|
| Живот: 9 |Отвара x2||
| a: удар i+---------+|
+----------------------+
> x
[Карта, Бой]
+----------------------+
| Вълк: 2 |
| Живот: 9 |
| a: удар i: инвентар |
+----------------------+
> a
[Карта, Бой]
+----------------------+
| Вълк: 1 |
| Живот: 8 |
| a: удар i: инвентар |
+----------------------+
> a
[Карта]
########################
#..@......~~~..........#
#.........~~~..........#
########################
Живот: 8

Виж кадъра с менюто: рисуват се боят, инвентарът и менюто, в този ред. Менюто покрива ъгъла на инвентара, защото е по-горе в стека.

По-точно, за любопитните

Цялата програма с прозрачните екрани:

import sys
from abc import ABC, abstractmethod
class TextBuffer:
W = 24
H = 5
def __init__(self):
self.rows = []
def clear(self):
self.rows = [" " * TextBuffer.W for _ in range(TextBuffer.H)]
def write(self, x, y, text):
row = self.rows[y]
self.rows[y] = row[:x] + text + row[x + len(text):]
def box(self, x, y, w, h):
edge = "+" + "-" * (w - 2) + "+"
self.write(x, y, edge)
for i in range(1, h - 1):
self.write(x, y + i, "|" + " " * (w - 2) + "|")
self.write(x, y + h - 1, edge)
def print(self):
for row in self.rows:
print(row.rstrip())
class Game:
def __init__(self):
self.stack = ScreenStack()
self.life = 10
self.wolf = 2
class Screen(ABC):
def __init__(self, game, name):
self.game = game
self.name = name
def is_transparent(self):
return False
@abstractmethod
def handle(self, command):
pass
def update(self):
pass
@abstractmethod
def draw(self, buffer):
pass
class ScreenStack:
def __init__(self):
self.screens = []
def push(self, screen):
self.screens.append(screen)
def pop(self):
self.screens.pop()
def replace(self, screen):
self.pop()
self.push(screen)
def top(self):
return self.screens[-1]
def names(self):
return "[" + ", ".join(s.name for s in self.screens) + "]"
def draw(self, buffer):
first = len(self.screens) - 1
while first > 0 and self.screens[first].is_transparent():
first -= 1
for i in range(first, len(self.screens)):
self.screens[i].draw(buffer)
class TitleScreen(Screen):
def __init__(self, game):
super().__init__(game, "Начало")
def handle(self, command):
if command == "n":
self.game.stack.replace(MapScreen(self.game))
def draw(self, buffer):
buffer.write(1, 1, "Хрониките на Арканум")
buffer.write(1, 3, "n: нова игра")
class MapScreen(Screen):
ROWS = [
"########################",
"#..@......~~~..........#",
"#.........~~~..........#",
"########################",
]
def __init__(self, game):
super().__init__(game, "Карта")
def handle(self, command):
if command == "i":
self.game.stack.push(InventoryScreen(self.game))
if command == "f" and self.game.wolf > 0:
self.game.stack.push(BattleScreen(self.game))
def draw(self, buffer):
for y, row in enumerate(MapScreen.ROWS):
buffer.write(0, y, row)
if self.game.wolf > 0:
buffer.write(8, 2, "w")
buffer.write(1, 4, f"Живот: {self.game.life}")
class BattleScreen(Screen):
def __init__(self, game):
super().__init__(game, "Бой")
def handle(self, command):
if command == "i":
self.game.stack.push(InventoryScreen(self.game))
if command == "a":
self.game.wolf -= 1
if self.game.wolf == 0:
self.game.stack.pop()
# Всеки ход на боя вълкът хапе.
def update(self):
if self.game.wolf > 0:
self.game.life -= 1
def draw(self, buffer):
buffer.box(0, 0, TextBuffer.W, TextBuffer.H)
buffer.write(2, 1, f"Вълк: {self.game.wolf}")
buffer.write(2, 2, f"Живот: {self.game.life}")
buffer.write(2, 3, "a: удар i: инвентар")
class InventoryScreen(Screen):
def __init__(self, game):
super().__init__(game, "Инвентар")
def is_transparent(self):
return True
def handle(self, command):
if command == "x":
self.game.stack.pop()
def draw(self, buffer):
buffer.box(12, 1, 11, 3)
buffer.write(13, 2, "Отвара x2")
def show(game, buffer):
print(game.stack.names())
buffer.clear()
game.stack.draw(buffer)
buffer.print()
game = Game()
buffer = TextBuffer()
game.stack.push(TitleScreen(game))
show(game, buffer)
for line in sys.stdin:
command = line.strip()
print(f"> {command}")
top = game.stack.top()
top.handle(command)
top.update()
show(game, buffer)

Още задачи

  • Създаване на героя. Между началния екран и картата сложи екран, който пита за име. И двата прехода са с replace: от героя назад няма.
  • Пауза. p отваря непрозрачен екран „Пауза“ от всеки екран. Провери, че вълкът не хапе, докато играта е на пауза.
  • Пазач на стека. Направи pop да отказва, когато в стека е останал само един екран, и да печата защо.

Тест

Провери се

1. Стекът е [Карта, Бой, Инвентар]. Играчът пише a (удар). Какво става с вълка?
2. Стекът е [Карта, Бой, Инвентар, Меню]. Инвентарът и менюто прозират, картата и боят не. Кои екрани се рисуват и в какъв ред?
3. Защо началният екран отваря картата с replace, а не с push?
4. Колега пази screen и още една променлива previous за „откъде дойдох“. Кога това не стига?
5. Добавяш екран „Герой“, който се отваря с c от картата. Какво се променя в програмата със стека?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·