Pojďme vytvořit karetní hru Klondike Solitaire, kterou možná znáš v nějaké počítačové verzi.
Naše hra bude ze začátku jednodušší – nebudeme se zabývat grafikou, ale logikou hry. „Grafiku“ zatím zajistí textová konzole. Obrázek výše se dá ukázat jako:
U V W X Y Z
[???] [ ] [ ] [ ] [ ] [ ]
A B C D E F G
[3♣ ] [???] [???] [???] [???] [???] [???]
[5 ♥] [???] [???] [???] [???] [???]
[6♣ ] [???] [???] [???] [???]
[5♠ ] [???] [???] [???]
[Q ♥] [???] [???]
[4♠ ] [???]
[3 ♦]
Klondike Solitaire je pasiáns – karetní hra pro jednoho hráče. Tyto hry obecně fungují takto:
Než ale počítač naučíš hrát hru, je potřeba ho naučit pár základních věcí, aby pak instrukce pro samotnou hru dávaly smysl. Základní věci, které je potřeba počítač „naučit“, jsou:
Odpovědí na tyhle otázky bude spousta vysvětlování a taky několik pythonních funkcí, které použiješ i ve zbytku hry.
Tady bych rád podotknul, že tyhle materiály ukazují předem vyzkoušený způsob, jak napsat karetní hru. Reálné projekty takhle nefungují: zahrnují spoustu plánování, slepých uliček, oprav špatně navrženého kódu a jiných frustrací. Neděláme tu reálný softwarový projekt – zatím stále zkoušíme základy, jen z nich pak vyleze něco hezkého.
Co je to karta a balíček? Jak tyhle koncepty reprezentovat v Pythonu – tedy pomocí čísel, řetězců, seznamů, n-tic a jiných datových typů – abys s nimi mohla dál pracovat?
Možností jak to udělat je více. Dobrý návrh datového modelu je základ úspěšného projektu: odpověď na otázku výše je základ k tomu, aby se pak program hezky psal. Až budeš potřebovat dobrý návrh datového modelu pro nějaký svůj projekt, doporučuju se ze začátku poradit se zkušenějším programátorem.
Pro Solitaire je tento úkol za tebe vyřešený: hrou Klondike si procvičíš seznamy a n-tice (a později slovníky).
O kartě potřebuješ znát tři kousky informace: hodnotu, barvu a to, jestli je otočená rubem nebo lícem nahoru.
Hodnoty karet jsou čísla 2-10 a navíc J
, Q
, K
, A
.
Hodnoty „obrázkových“ karet je dobré převést na čísla: J=11, Q=12, K=14, A=1.
Hodnoty se tak budou dát jednoduše porovnávat, nebo zjišťovat následující kartu
(např. po desítce je jedenáct – J
).
V programu budeme tedy pro hodnoty používat jen čísla, a teprve když bude
potřeba kartu „ukázat“ člověku, převedeme ji na písmenko.
Pro barvu jsou čtyři možnosti: ♥, ♦, ♣ nebo ♠.
Dají se reprezentovat v podstatě jakýmikoli čtyřmi různými hodnotami.
Různí programátoři by mohli použít čísla 0-3, symboly jako ♥
, nebo třeba jako
čtyři různé funkce.
My použijeme krátké řetězce bez diakritiky, aby se to dobře psalo:
'Sr'
(srdce), 'Pi'
(piky), 'Ka'
(káry), 'Kr'
(kříže).
Použij prosím stejné řetězce (včetně velkých písmen), abys pak mohla
kopírovat ukázkový kód.
Jako u hodnoty platí že tyhle řetězce budeme používat ve většině programu,
jen když bude potřeba kartu „ukázat“ člověku, převedeme je na hezčí symbol.
Pro otočení karty jsou dvě možné hodnoty: buď lícem nebo rubem nahoru.
Když dvě hodnoty, je dobré použít True
a False
.
Jen je pak potřeba vybrat (a dodržovat) která je která.
Řekněme že True
znamená lícem nahoru; False
rubem.
Ideální je podle toho důsledně pojmenovávat proměnné: v programu vždy
používej je_licem_nahoru=True
, ne otoceni=True
.
Samotná karta pak bude trojice těchto hodnot: (hodnota, barva, je_licem_nahoru). Například:
(12, 'Sr', True)
je 🂽 – srdcová královna otočená lícem nahoru(7, 'Pi', False)
je 🂠 – piková sedma otočená rubem nahoruA balíček? Balíček bude seznam karet, tedy seznam trojic. Jakákoli sekvence karet ve hře bude bude seznam trojic: dobírací a odkládací balíčky, „sloupečky“ karet na herní ploše i „hromádky“ sežazených karet.
Například jeden ze sloupečků z obrázku výše obsahuje 4 karty rubem nahoru a na konci srdcovou královnu. Jako seznam by to mohlo být:
[(7, 'Pi', False), (5, 'Kr', False), (1, 'Ka', False), (3, 'Pi', False), (12, 'Sr', True)]
Na balíčcích a kartách je vidět rozdíl v použití seznamů a n-tic:
Ne ve všech programech to bude takhle jednoznačné. Karta a balíček jsou skoro ideální příklady na seznamy a n-tice :)
V Pythonu z použitých typů vyplývá, co se s nimi dá dělat.
N-tice nejdou měnit: abys změnila např. otočení karty, bude
potřeba udělat úplně novou trojici (podobně jako např u tahu
z --------------------
na -------------o------
v 1D piškvorkách).
Seznamy ale měnit jdou. Seznamové operace dokonce často dávají smysl:
Pozor ale na to, že se seznamem trojic toho jde dělat víc než s fyzickým balíčkem karet. Pro počítač není problém udělat kopii karty.
Označovat dsrdcovou dámu jako (12, 'Sr', True)
je skvělé pro počítač,
ale pro lidi je to nepřehledné.
Bude tedy vhodné napsat funkci, která kartu „ukáže“ trochu srozumitelněji.
Taková funkce by měla vyřeši i to, že kartu, která je rubem nahoru
– jako (5, 'Kr', False)
, je potřeba před hráčem skrýt.
Napsat tuhle funkci je docela otrava, a později bude potřeba aby se chovala přesně podle mých očekávání (včetně např. velkých písmen a mezer). Proto ti ji dám k dispozici. Hlavičku má takovouhle:
def popis_kartu(karta):
"""Vrátí popis karty, např. [Q ♥] nebo [6♣ ] nebo [???]
Trojice čísla (2-13), krátkého řetězce ('Sr', 'Ka', 'Kr' nebo 'Pi')
a logické hodnoty (True - lícem nahoru; False - rubem) se jednoduše
zpracovává v Pythonu, ale pro "uživatele" není nic moc.
Proto je tu tahle funkce, která kartu hezky "popíše".
Aby měly všechny hodnoty jen jeden znak, desítka se vypisuje jako
římská číslice "X".
Aby se dalo rychle odlišit červené (♥♦) karty od černých (♣♠),
mají červené mezeru před symbolem a černé za ním.
"""
Druhá užitečná funkce umí otočit karu buď rubem nebo lícem nahoru.
Podobně jako tah
z piškvorek vezme „starou“ hodnotu, rozloží ji
části a výsledek slepí z kombinace „starých“ a „nových“ hodnot.
Projdi si ji řádek po řádku, abys věděla jak funguje:
def otoc_kartu(karta, pozadovane_otoceni):
"""Vrátí kartu otočenou lícem nahoru (True) nebo rubem nahoru (False)
Nemění původní trojici; vytvoří a vrátí novou.
(Ani by to jinak nešlo – n-tice se, podobně jako řetězce čísla, měnit
nedají.)
"""
# Rozbalení n-tice
hodnota, barva, stare_otoceni = karta
# Vytvoření nové n-tice (kombinací staré hodnoty/barvy a nového otočení)
nova_karta = hodnota, barva, pozadovane_otoceni
# Vrácení nové n-tice
return nova_karta
Funkce najdeš v souboru karty.py
. Projdi si je; rozumíš jim?
Testy k nim jsou v test_karty.py
– ty procházet nemusíš.
Oba soubory si ulož.
Další pomocné funkce už napíšeš sama. Aby sis ověřila že fungují, mám pro tebe předpřipravené testy.
Stáhni si soubor s testy, test_klondike.py, a dej ho do adresáře,
kde budeš tvořit hru a kde máš karty.py
.
Na ulehčení testování si nainstaluj modul pytest-level
.
Ten umožňuje pouštět jen určité testy – podle toho, jak jsi daleko.
python -m pip install pytest pytest-level
Zkus pustit všechny testy. Asi ti neprojdou:
python -m pytest -v
Pak zkus pustit testy pro úroveň 0:
python -m pytest -v --level 0
Teď se nepustí žádné testy – všechny nové testy se přeskočí;
projdou jen testy z test_karty.py
.
Uvidíš něco jako:
===== 20 passed, 99 deselected in 0.06s ====
Zadáš-li v posledním příkazu --level 1
, aktivuje se první z testů. Pravděpodobně neprojde – v dalším úkolu ho spravíš!
Do modulu klondike
(tedy do nového souboru klondike.py
) napiš
následující funkci:
def vytvor_balicek():
"""Vrátí balíček 52 karet – od esa (1) po krále (13) ve čtyřech barvách
Všechny karty jsou otočené rubem nahoru.
"""
Zkus si funkci pustit a podívej se, co vrací.
Aby sis ověřila, že se chová správně, pusť na ni testy:
Když výsledek funkce vytvor_balicek
vypíšeš, je docela nepřehledný.
Aby se ti s balíčky lépe pracovalo, vytvoř následující funkci:
def popis_balicek(karty):
"""Vrátí popis všech karet v balíčku. Jednotlivé karty odděluje mezerami.
"""
Funkce by měla vracet řetězec složený z popisů jednotlivých karet. Například:
>>> karty = [
(13, 'Pi', True),
(12, 'Sr', True),
(11, 'Ka', True),
(10, 'Kr', False),
]
>>> popis_balicek(karty)
[K♠ ] [Q ♥] [J ♦] [???]
Jak na to? Vytváření celého řetězce najednou by bylo složité, ale lze si to rozdělit na kroky, které už znáš:
popis_kartu
z modulu karty
!)Funkci opět můžeš otestovat:
Občas je z balíčku vidět jen vrchní karta. Napiš následující funkci, která popíše takový balíček:
def popis_vrchni_kartu(balicek):
"""Vrátí popis daného balíčku karet -- tedy vrchní karty, která je vidět."""
Funkci nezapomeň otestovat:
popis_kartu
z modulu karty
.)[ ]
(3 mezery v hranatých závorkách).Pokračování příště...