Стек от екрани
Накратко: Стекът от екрани пази отворените екрани един върху друг: входът отива към горния, push отваря нов, pop го затваря, replace го сменя, а прозрачните се рисуват върху тези под тях.
Проблем
Една променлива за всички екрани
Играта има начален екран, село, карта, бой, инвентар, който се отваря върху картата, и меню, което се отваря върху боя. Всеки екран разбира командите по своему: на картата a е крачка наляво, в боя е удар, в инвентара е нищо.
Първият опит, който почти всеки прави: една променлива с името на екрана и if по нея. Веднъж в обработката на командата и още веднъж в рисуването:
import { createInterface } from "node:readline";
const rl = createInterface({ input: process.stdin });let screen = "title";let wolf = 2;for await (const line of rl) { const command = line.trim(); if (screen === "title") { if (command === "n") screen = "map"; } else if (screen === "map") { if (command === "i") screen = "inventory"; if (command === "f") screen = "battle"; } else if (screen === "battle") { if (command === "i") screen = "inventory"; if (command === "a") { wolf--; if (wolf === 0) screen = "map"; } } else if (screen === "inventory") { if (command === "x") screen = "map"; } process.stdout.write(command + " → "); if (screen === "title") console.log("Хрониките на Арканум"); else if (screen === "map") console.log(`Карта, вълк: ${wolf}`); else if (screen === "battle") console.log(`Бой, вълк: ${wolf}`); else if (screen === "inventory") console.log("Инвентар: Отвара x2");}n → Карта, вълк: 2i → Инвентар: Отвара x2x → Карта, вълк: 2f → Бой, вълк: 2i → Инвентар: Отвара x2x → Карта, вълк: 2a → Карта, вълк: 2a → Карта, вълк: 2Командите идват по една на ред: n нова игра, i инвентар, x затваря, f бой с вълка, a удар.
nixfixaaВиж шестия ред. Инвентарът е отворен от боя, а x го затваря към картата. Боят е изчезнал, вълкът е жив с 2 живот, а двата удара след това падат на картата, където a не значи нищо. Променливата помни къде си, но не и откъде дойде.
Второто се вижда, когато играта порасне. Всеки нов екран е нов клон във всеки if: в обработката, в рисуването и във всеки екран, от който се отваря. Меню върху боя и върху картата е още два клона на две места. Забравиш ли един, получаваш екран, който приема команди, но не се рисува.
Идеята
Екранът решава сам за себе си
Обръщаме въпроса. Вместо цикълът да пита „на кой екран съм“ и да знае всеки екран, всеки екран е обект с едни и същи четири метода:
handle: какво значи командата тук;update: какво става всеки ход, докато екранът е отгоре (в боя вълкът хапе);draw: какво рисува екранът;isTransparent: дали екранът под него се вижда.
// JavaScript няма абстрактни класове: тук handle и draw не правят нищо,// а всеки екран ги пише наново.class Screen { constructor(game, name) { this.game = game; this.name = name; }
isTransparent() { return false; }
handle(command) {}
update() {}
draw(buffer) {}}handle и draw. Всеки екран ги пише наново. Ако забравиш, екранът просто не прави нищо, без грешка.Ето боя и инвентара. Боят знае само за себе си: i отваря инвентара отгоре, a удря, а когато вълкът падне, боят маха себе си. Инвентарът не знае откъде е отворен. Той само казва „махни ме“:
class BattleScreen extends Screen { constructor(game) { super(game, "Бой"); }
handle(command) { if (command === "i") this.game.stack.push(new InventoryScreen(this.game)); if (command === "a") { this.game.wolf--; if (this.game.wolf === 0) this.game.stack.pop(); } }
// Всеки ход на боя вълкът хапе. update() { if (this.game.wolf > 0) this.game.life--; }
draw(buffer) { buffer.box(0, 0, TextBuffer.W, TextBuffer.H); buffer.write(2, 1, `Вълк: ${this.game.wolf}`); buffer.write(2, 2, `Живот: ${this.game.life}`); buffer.write(2, 3, "a: удар i: инвентар"); }}class InventoryScreen extends Screen { constructor(game) { super(game, "Инвентар"); }
handle(command) { if (command === "x") this.game.stack.pop(); }
draw(buffer) { buffer.box(12, 1, 11, 3); buffer.write(13, 2, "Отвара x2"); }}Екраните рисуват в TextBuffer: пет реда по 24 знака. write пише текст от дадено място, box рисува рамка. Целият е в програмата по-долу, а как се рисува в истинския терминал, е в урока за рисуването.
Нов екран е нов клас и един ред в екрана, който го отваря. Никой if по имена на екрани не расте.
По-точно, за любопитните
Така е в играта. Регионът screen от канона:
// Всеки екран е обект с еднакъв интерфейс. Играта не пита „на кой екран съм“, а праща командата към// екрана на върха на стека.class Screen { constructor(game) { this.game = game; }
// Прозрачният екран (меню, инвентар, диалог) оставя екрана под себе си да се вижда. isTransparent() { return false; }
handle(command) {}
update() {}
draw(buffer) {}
// Екранът под попнатия получава неговия отговор (избрана цел, „да“ или „не“, избран слот). onResult(result) {}}Методът onResult е за екрани, които връщат отговор (избраната цел, „да“ или „не“). За него е следващият урок.
Идеята
Стекът: откъдето дойде
Екраните са един върху друг: инвентар върху боя, бой върху картата. Когато затвориш горния, искаш да си точно там, където беше. Това е стек: последният сложен се маха пръв. Стекът от екрани има три операции:
- push: отвори нов екран отгоре (картата отваря боя, боят отваря инвентара);
- pop: затвори горния и се върни към този под него;
- replace: смени горния с нов, без връщане (началният екран става карта, създаването на героя става село).
class ScreenStack { constructor() { this.screens = []; }
push(screen) { this.screens.push(screen); }
pop() { this.screens.pop(); }
replace(screen) { this.pop(); this.push(screen); }
top() { return this.screens[this.screens.length - 1]; }
names() { return `[${this.screens.map((s) => s.name).join(", ")}]`; }
draw(buffer) { this.top().draw(buffer); }}Цикълът на играта дава командата само на върха. Не на картата, не на боя под инвентара. Само върхът знае какво значи командата сега. Същите команди, сега със стека. show печата стека и кадъра след всяка команда:
function show(game, buffer) { console.log(game.stack.names()); buffer.clear(); game.stack.draw(buffer); buffer.print();}
const game = new Game();const buffer = new TextBuffer();game.stack.push(new TitleScreen(game));show(game, buffer);const rl = createInterface({ input: process.stdin });for await (const line of rl) { const command = line.trim(); console.log(`> ${command}`); const 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 на празен стек. Ако последният екран махне себе си, стекът е празен и следващото махане няма какво да махне. В JavaScript pop() на празен масив връща undefined без грешка, а програмата пада по-късно, на top().handle(...), далеч от истинската причина. В играта празният стек значи край: цикълът върти, докато стекът не опустее, а „Изход“ от началния екран изчиства стека.
Пробвай
Предвиди изхода
Стекът е празен. Какво е в него накрая?
const game = new Game();const stack = game.stack;stack.push(new TitleScreen(game));stack.replace(new MapScreen(game));stack.push(new BattleScreen(game));stack.push(new InventoryScreen(game));stack.pop();stack.pop();stack.push(new InventoryScreen(game));console.log(stack.names());Идеята
Прозрачните екрани
Инвентарът е малка рамка върху картата. За да се види картата около нея, картата трябва да се нарисува първа, а инвентарът след нея, върху нея. Като художник, който рисува първо фона, после фигурите отпред.
Но не от най-долния екран. Под боя е картата, а боят я закрива целия: да я рисуваш е напразно. Затова рисуването започва от най-горния непрозрачен екран. Тръгваш от върха надолу, докато екраните прозират. Първият, който не прозира, е началото. Оттам рисуваш нагоре:
draw(buffer) { let first = this.screens.length - 1; while (first > 0 && this.screens[first].isTransparent()) first--; for (let i = first; i < this.screens.length; i++) { this.screens[i].draw(buffer); }}Инвентарът казва, че прозира:
class InventoryScreen extends Screen { constructor(game) { super(game, "Инвентар"); }
isTransparent() { return true; }
handle(command) { if (command === "x") this.game.stack.pop(); }
draw(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 на канона:
// Рисуването започва от най-горния непрозрачен екран и върви нагоре.draw(buffer) { let first = this.screens.length - 1; while (first > 0 && this.screens[first].isTransparent()) first--; for (let i = first; i < this.screens.length; i++) { if (i >= 0) this.screens[i].draw(buffer); }}Проверката i >= 0 е за празен стек: тогава first е −1 и няма какво да се рисува.
Защо не просто от нулата? Ако всеки непрозрачен екран закрива всичко, резултатът е същият, но всеки кадър рисува всички екрани отдолу: карта, бой, избор на цел, диалог. Има и по-лошо: непрозрачен екран, който не пише във всяка клетка, ще остави да личат парчета от картата. „Не прозирам“ е обещание „покривам всичко“, и стекът разчита на него.
Виж го
Ход по ход
Същата програма с прозрачния инвентар. Отляво е стекът: всяка плоча е един екран с това, което той рисува сам. Командата отива към плочата на върха. Бледите плочи са под най-горната непрозрачна и не се рисуват. Под стека е кадърът, който програмата печата.
class ScreenStack { constructor() { this.screens = []; }
push(screen) { this.screens.push(screen); }
pop() { this.screens.pop(); }
replace(screen) { this.pop(); this.push(screen); }
top() { return this.screens[this.screens.length - 1]; }
names() { return `[${this.screens.map((s) => s.name).join(", ")}]`; }
draw(buffer) { let first = this.screens.length - 1; while (first > 0 && this.screens[first].isTransparent()) first--; for (let i = first; i < this.screens.length; i++) { this.screens[i].draw(buffer); } }}
for await (const line of rl) { const command = line.trim(); console.log(`> ${command}`); const top = game.stack.top(); top.handle(command); top.update(); show(game, buffer);}Стъпка по стъпка, с думи
- Играта започва с един екран в стека: началния. Той е на върха и получава входа.
- n отива към началния екран. Той слага картата на своето място (replace): началният екран няма да се върне.
- i отива към картата и тя слага инвентара отгоре (push). Инвентарът прозира: първо се рисува картата, после инвентарът върху нея.
- x отива към инвентара: той се маха (pop). Отдолу е картата, точно каквато я остави.
- f: картата слага боя отгоре (push). Боят не прозира, затова картата под него изобщо не се рисува.
- Сега i отива към боя, не към картата. Боят отваря инвентара над себе си. Рисуването започва от боя, най-горния непрозрачен екран. Вълкът ухапа: живот 9.
- x затваря инвентара и отдолу е боят: връщаш се там, откъдето дойде. Единствената променлива от началото на урока тук те върна на картата.
- a отива към боя: вълкът е ранен, а стекът не се променя. Минава update на боя: вълкът хапе, живот 8.
- Вълкът пада. Боят се маха (pop) и картата пак е на върха, вече без вълка.
- Входът отива към върха. Рисуването върви нагоре от най-горния непрозрачен екран. Екраните, които връщат отговор, и промените, които чакат края на хода, са в следващия урок.
Пробвай
Попълни празнината
Рисуването на стека. Докато екранът на first прозира, слизаш надолу:
draw(buffer) {
let first = this.screens.length - 1;
while (first > 0 && ) first--;
for (let i = first; i < this.screens.length; i++) {
this.screens[i].draw(buffer);
}
}Въпросът е дали екранът на място first прозира: this.screens[first].isTransparent(). Ако прозира, и този под него трябва да се нарисува.
Пробвай
Намери грешката
Колега написа стека наново. Програмата с командите n, i, x печата:
[Начало]
Хрониките на Арканум
n: нова игра
> n[Карта]#########################..@......~~~..........##.......w.~~~..........######################### Живот: 10> i[Карта, Инвентар]#########################..@......~~+---------+##.......w.~~|Отвара x2|#############+---------+# Живот: 10> x[Карта, Инвентар]#########################..@......~~+---------+##.......w.~~|Отвара x2|#############+---------+# Живот: 10Инвентарът не се затваря: след x стекът пак е [Карта, Инвентар]. На кой ред е грешката?
Избери реда с грешката.
Върхът на стека е последният елемент, не първият. С this.screens[0] командата отива към картата, която е най-отдолу. Картата не знае какво е x, а инвентарът изобщо не я чува. Списъкът расте отзад, затова и push, и pop, и top работят с края му.
Идеята
Стекът в истинската игра
Регионът screen-stack от канона. Методите са същите, но има разлики:
// Стекът не се променя по време на обработката: push, pop и replace само записват промяната, а// apply() ги прилага след хода. Иначе екран, който се маха от стека, би продължил да работи.class ScreenStack { constructor() { this.screens = []; this.changes = []; }
push(screen) { this.changes.push({ kind: 'push', screen: screen, result: null }); }
pop(result) { this.changes.push({ kind: 'pop', screen: null, result: result }); }
replace(screen) { this.changes.push({ kind: 'replace', screen: screen, result: null }); }
// Изчиства целия стек (зареждане, край на играта); с null стекът остава празен. reset(screen) { this.changes.push({ kind: 'reset', screen: screen, result: null }); }
top() { return this.screens.length === 0 ? null : this.screens[this.screens.length - 1]; }
isEmpty() { return this.screens.length === 0 && this.changes.length === 0; }
contains(kindName) { for (const screen of this.screens) { if (screen.kindName === kindName) return true; } return false; }
apply() { let index = 0; while (index < this.changes.length) { const change = this.changes[index]; index++; if (change.kind === 'push') { this.screens.push(change.screen); } else if (change.kind === 'pop') { this.screens.pop(); const below = this.top(); if (below !== null) below.onResult(change.result); } else if (change.kind === 'replace') { this.screens.pop(); this.screens.push(change.screen); } else { this.screens = []; if (change.screen !== null) this.screens.push(change.screen); } } this.changes = []; }
// Рисуването започва от най-горния непрозрачен екран и върви нагоре. draw(buffer) { let first = this.screens.length - 1; while (first > 0 && this.screens[first].isTransparent()) first--; for (let i = first; i < this.screens.length; i++) { if (i >= 0) this.screens[i].draw(buffer); } }}- push, pop и replace не пипат списъка веднага. Записват промяната в
changes, аapply()ги изпълнява след хода. В нашата програма боят маха себе си вhandle, а веднага след това цикълът викаupdateна същия бой, който вече не е в стека. Тук минава, защото мъртъв вълк не хапе. В по-голяма игра това е бъг, който се търси дълго. Защо и как се отлагат промените, е следващият урок. - pop носи отговор: екранът отдолу го получава в
onResult. Така екранът за избор на цел връща целта на боя. И това е в следващия урок. - reset изчиства целия стек (зареждане на игра, край на играта), а contains пита дали екран от даден вид вече е вътре (например да не се отвори второ качване на ниво).
По-точно
Същата идея другаде
Навигацията в мобилните приложения е стек от екрани. В Android и iOS отваряш екран отгоре (push), а бутонът „Назад“ го маха (pop). Влизането след логин обикновено е replace: назад не трябва да води пак към формата за вход.
Модалният диалог в браузъра е екран върху страницата. Докато е отворен, само той получава кликове и клавиши, а страницата под него се вижда, но не реагира. Същото като инвентара върху картата.
Стекът на извикванията е същата идея в самия език. Метод вика друг метод (push), той връща (pop), и извикващият продължава точно откъдето е спрял. Боят продължава след инвентара по същия начин.
Кога стекът не стига. Стекът казва, че отгоре е точно един екран и той получава всичко. Понякога това не е вярно:
- два екрана един до друг, които работят заедно: карта и мини-карта, бой и дневник на боя, игра за двама на разделен екран. Тогава екранът е дърво от панели, а входът отива към панела на фокуса;
- неща, които са винаги отгоре, независимо от екрана: лента с живота, известие „Играта е записана“. Те са отделен слой, който се рисува след стека;
- прозорци, които се затварят в произволен ред (стратегия с пет отворени прозореца). Това вече не е стек, а списък с фокус.
Упражнение
Меню върху всичко
Реши задачата в редактора си. Цялата програма с прозрачните екрани е под задачата.
Меню. Добави MenuScreen с име „Меню“. m го отваря от картата, от боя и от инвентара. Менюто прозира, а x го затваря. Рисува рамка box(14, 2, 10, 3) и в нея x: назад от място (15, 3).
nfimxxaa[Начало]
Хрониките на Арканум
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 { createInterface } from "node:readline";
class TextBuffer { static W = 24; static H = 5;
constructor() { this.rows = []; }
clear() { this.rows = []; for (let y = 0; y < TextBuffer.H; y++) this.rows.push(" ".repeat(TextBuffer.W)); }
write(x, y, text) { const row = this.rows[y]; this.rows[y] = row.substring(0, x) + text + row.substring(x + text.length); }
box(x, y, w, h) { const edge = `+${"-".repeat(w - 2)}+`; this.write(x, y, edge); for (let i = 1; i < h - 1; i++) this.write(x, y + i, `|${" ".repeat(w - 2)}|`); this.write(x, y + h - 1, edge); }
print() { for (const row of this.rows) console.log(row.trimEnd()); }}
class Game { constructor() { this.stack = new ScreenStack(); this.life = 10; this.wolf = 2; }}
// JavaScript няма абстрактни класове: тук handle и draw не правят нищо,// а всеки екран ги пише наново.class Screen { constructor(game, name) { this.game = game; this.name = name; }
isTransparent() { return false; }
handle(command) {}
update() {}
draw(buffer) {}}
class ScreenStack { constructor() { this.screens = []; }
push(screen) { this.screens.push(screen); }
pop() { this.screens.pop(); }
replace(screen) { this.pop(); this.push(screen); }
top() { return this.screens[this.screens.length - 1]; }
names() { return `[${this.screens.map((s) => s.name).join(", ")}]`; }
draw(buffer) { let first = this.screens.length - 1; while (first > 0 && this.screens[first].isTransparent()) first--; for (let i = first; i < this.screens.length; i++) { this.screens[i].draw(buffer); } }}
class TitleScreen extends Screen { constructor(game) { super(game, "Начало"); }
handle(command) { if (command === "n") this.game.stack.replace(new MapScreen(this.game)); }
draw(buffer) { buffer.write(1, 1, "Хрониките на Арканум"); buffer.write(1, 3, "n: нова игра"); }}
class MapScreen extends Screen { static ROWS = [ "########################", "#..@......~~~..........#", "#.........~~~..........#", "########################", ];
constructor(game) { super(game, "Карта"); }
handle(command) { if (command === "i") this.game.stack.push(new InventoryScreen(this.game)); if (command === "f" && this.game.wolf > 0) this.game.stack.push(new BattleScreen(this.game)); }
draw(buffer) { MapScreen.ROWS.forEach((row, y) => buffer.write(0, y, row)); if (this.game.wolf > 0) buffer.write(8, 2, "w"); buffer.write(1, 4, `Живот: ${this.game.life}`); }}
class BattleScreen extends Screen { constructor(game) { super(game, "Бой"); }
handle(command) { if (command === "i") this.game.stack.push(new InventoryScreen(this.game)); if (command === "a") { this.game.wolf--; if (this.game.wolf === 0) this.game.stack.pop(); } }
// Всеки ход на боя вълкът хапе. update() { if (this.game.wolf > 0) this.game.life--; }
draw(buffer) { buffer.box(0, 0, TextBuffer.W, TextBuffer.H); buffer.write(2, 1, `Вълк: ${this.game.wolf}`); buffer.write(2, 2, `Живот: ${this.game.life}`); buffer.write(2, 3, "a: удар i: инвентар"); }}
class InventoryScreen extends Screen { constructor(game) { super(game, "Инвентар"); }
isTransparent() { return true; }
handle(command) { if (command === "x") this.game.stack.pop(); }
draw(buffer) { buffer.box(12, 1, 11, 3); buffer.write(13, 2, "Отвара x2"); }}
function show(game, buffer) { console.log(game.stack.names()); buffer.clear(); game.stack.draw(buffer); buffer.print();}
const game = new Game();const buffer = new TextBuffer();game.stack.push(new TitleScreen(game));show(game, buffer);const rl = createInterface({ input: process.stdin });for await (const line of rl) { const command = line.trim(); console.log(`> ${command}`); const top = game.stack.top(); top.handle(command); top.update(); show(game, buffer);}Още задачи
- Създаване на героя. Между началния екран и картата сложи екран, който пита за име. И двата прехода са с replace: от героя назад няма.
- Пауза.
pотваря непрозрачен екран „Пауза“ от всеки екран. Провери, че вълкът не хапе, докато играта е на пауза. - Пазач на стека. Направи
popда отказва, когато в стека е останал само един екран, и да печата защо.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Полиморфизъм · Масиви: push, splice и sort · Стек, опашка и свързан списък · Стрелкови функции
- Задачи по темата: Хрониките на Арканум