České vysoké učení technické v Praze Fakulta elektrotechnická
ˇ VUT FEL katedra pocˇı´tacˇu˚ C
Bakalářská práce
Mariáš Miroslav Kubík
Vedoucí práce: Ing. Miroslav Balík, Ph.D.
Studijní program: Elektrotechnika a informatika strukturovaný bakalářský Obor: Informatika a výpočetní technika březen 2006
ii
Poděkování Na tomto místě bych chtěl poděkovat ing. Miroslavu Balíkovi, Ph.D. za cenné připomínky k aplikaci, pravidlům mariáše a tomuto dokumentu, dále Vojtěchovi Outulnému za rady při programování GUI v Javě a v neposlední řadě rodičům za jejich podporu. iii
iv
Prohlášení Prohlašuji, že jsem svou diplomovou práci vypracoval samostatně a použil jsem pouze podklady uvedené v přiloženém seznamu. Nemám závažný důvod proti užití tohoto školního díla ve smyslu §60 Zákona č. 121/2000 Sb., o právu autorském, o právech souvisejících s právem autorským a o změně některých zákonů (autorský zákon).
V Praze dne 12.6. 2006
.............................................................
v
vi
Abstract This work describes analysis, design and implementation of so-called three-player auction marriage as a single-player computer game, where two remaining players are computer controlled. The auction marriage is one of the most complex card games which can be played with classic bohemian cards. The application was implemented in Java programming language. The entire documentation, the executable application and its source code are contained on the appended CD.
Abstrakt Tato práce popisuje analýzu, návrh a implementaci tzv. licitovaného mariáše pro tři hráče jako počítačové hry pro jednoho hráče, kdy jsou zbylí dva hráči nahrazeni počítačem. Licitovaný mariáš je jednou z nejsložitějších her, které se hrají s klasickými českými kartami. Aplikace byla implementována v programovacím jazyce Java. Veškerá dokumentace, samotná spustitelná aplikace a její zdrojový kód jsou obsaženy na přiloženém CD.
vii
viii
Obsah Seznam obrázků
xi
1 Úvod
1
2 Popis problému a specifikace cíle 2.1 Pravidla licitovaného mariáše . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Specifikace cíle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 2 7
3 Analýza a návrh řešení 3.1 Model hry . . . . . . . . . . . . 3.2 Spolupráce automatů . . . . . . 3.3 Umělá inteligence . . . . . . . . 3.4 GUI a ovládání . . . . . . . . . 3.5 Volba implementačního jazyka 4 Implementace 4.1 Herní model . . . . . . . . . . 4.2 Umělá inteligence . . . . . . . 4.3 Grafické uživatelské rozhraní 4.4 Webová forma aplikace . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
10 10 12 15 18 19
. . . .
21 21 30 33 35
5 Testování 6 Uživatelská příručka 6.1 Spuštění aplikace . . . 6.2 Herní obrazovka . . . 6.3 Informační panel . . . 6.4 Roletové menu . . . . 6.5 Herní plocha . . . . . 6.6 Ovládání . . . . . . . . 6.7 Mód simulace . . . . . 6.8 Chybové hlášky . . . . 6.9 Minimální požadavky
37
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
38 38 38 38 39 41 42 43 43 44
7 Závěr
45
A Obsah přiloženého CD
47
B Literatura
48
ix
x
Seznam obrázků 2.1 2.2
Rozsazení hráčů kolem stolu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Možná složení balíčku, vlevo prokládaně, vpravo neprokládaně . . . . . . . . . .
3.1 3.2 3.3 3.4 3.5 3.6 3.7
Průběh hry . . . . . . . . . . . . . . Stavový automat licitace . . . . . . . Volba typu hry . . . . . . . . . . . . Flekování . . . . . . . . . . . . . . . Vlastní hra . . . . . . . . . . . . . . Fungování klient-server architektury Základní podoba herní obrazovky . .
. . . . . . .
10 11 12 13 14 14 18
4.1
Základní kompozice herního modelu . . . . . . . . . . . . . . . . . . . . . . . .
21
6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9 6.10
Herní obrazovka . . . . . . . . . Dialogový panel při flekování . . Volby v sadě Hra . . . . . . . . . Volby v sadě Nastavení . . . . . Volba Hráči a účty . . . . . . . . Volba Vzhled . . . . . . . . . . . Volba Obtížnost . . . . . . . . . . Volba Napověz tah . . . . . . . . Simulace – speciální výběr karet Chybové hlášení . . . . . . . . .
38 39 39 40 40 41 42 42 43 43
. . . . . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
xi
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . . . . .
3 7
xii
KAPITOLA 1. ÚVOD
1
1 Úvod Licitovaný mariáš je jednou z nejsložitějších variant mariáše, karetní hry pro tři hráče. Některými hráči bývá komplexita mariáše přirovnávána k šachu či bridži. Ke složitosti přispívá hned několik faktů. Pravidla licitovaného mariáše popisují několik úrovní (typů) hry, které se liší cílem (podmínkou pro splnění závazku), finančním ohodnocením, případně i hodnotou karet a dalšími aspekty. Některé cíle mají navíc podobu dvou nezávislých závazků. Hráč musí být schopen na samém počátku hry na základě informací o distribuci karet odhadnout rizika (pravděpodobnost uhrání) jednotlivých úrovní hry a na základě toho zvolit odpovídající postup. Při vlastní hře kooperují dva hráči za účelem dosažení společného cíle – zabránit zbylému hráči splnit cíle vybrané úrovně hry. Netřeba jistě zmiňovat, že se jedná o hru s neúplnou informací. Úkolem této práce bylo navrhnout a ve zvoleném jazyce implementovat aplikaci, která umožní hrát licitovaný mariáš se dvěma počítačovými spoluhráči. To v prvé řadě znamenalo seznámit se s pravidly licitovaného mariáše, poté provést rešerši existujících řešení a zpřesnit požadavky na aplikaci. Následná analýza a návrh, popisující základní funkčnost, principy a strukturu herního modelu, umělé inteligence a grafického uživatelského rozhraní (GUI), posloužila jako podklad při implementaci. Vytvoření takovéto aplikace zahrnuje samotný herní model a následně umělou inteligenci a GUI nad zmiňovaým modelem. Zejména v herním modelu a umělé inteligenci se odrazí výše zmíněná komplexita pravidel licitovaného mariáše. Závěrem bylo provedeno testování funkčnosti aplikace. Nyní k obsahu tohoto textu. V druhé kapitole je uvedena varianta pravidel licitovaného mariáše, dle kterých je vystavěn herní model, a zpřesnění požadavků na aplikaci na základě úvahy a rešerše existujících řešení. Třetí kapitola nastiňuje vyšší úroveň modelu hry, související stavové automaty a jejich komunikaci, principy fungování umělé inteligence a základní podobu GUI. V jejím závěru je argumentována volba implementačního jazyka. Čtvrtá kapitola je věnována popisu implementace herního modelu, umělé inteligence a v krátkosti i GUI a webové formy aplikace. Pátá kapitola zachycuje testování aplikace. Šestá kapitola je uživatelskou příručkou popisující vzhled a ovládání aplikace. Sedmá kapitola tvoří závěr – zhodnocuje aplikaci a nastiňuje možnosti jejího dalšího vylepšování.
2
KAPITOLA 2. POPIS PROBLÉMU A SPECIFIKACE CÍLE
2 Popis problému a specifikace cíle Stěžejní částí popisu problému jsou vlastní pravidla licitovaného mariáše, která již sama o sobě podstatně určují požadavky na program. Jsou zde rozvedeny i další požadavky týkající se možností uživatele (hra pro jednoho hráče, úprava grafického prostředí,. . .), uživatelského rozhraní (např. grafika, způsob ovládání,. . .) a umělé inteligence.
2.1
Pravidla licitovaného mariáše
K dispozici jsou oficiální pravidla mariáše vydaná Českým svazem mariáše, přesto mariáš nemá jednotnou podobu – místně se mohou ve způsobu hry vyskytovat odlišnosti. Zde uvedená pravidla vychází zejména z [3]. Ujasnění pravidel, která jsou výchozím bodem pro analýzu, bylo jedním z důvodů zahrnutí této kapitoly do textu a během jejího sestavování byla věnována pozornost přesnosti a jednoznačnosti vyjádření. V průběhu výkladu pravidel bude rovněž ozřejmen význam nejpodstatnějších mariášnických termínů, které budou i dále používány. Licitovaný mariáš je jednou z více existujících variant mariáše, tradiční české karetní hry. Jedná se o lehce hazardní hru – hraje se o menší obnosy peněz. Hraje se typicky ve třech hráčích (jako pauzírovaný ho lze hrát i ve čtyřech hráčích, ale tato možnost není v práci dále uvažována, protože není příliš obvyklá a do samotné herní mechaniky nevnáší výraznou změnu). Obvykle se hraje s klasickými jednohlavými kartami, ale v podstatě stačí ke hře jakýkoli ekvivalent – balíček dvaatřiceti karet tvořený čtyřmi skupinami po osmi odlišných kartách. Karty ve skupině se označují 7 (sedma), 8 (osma), 9 (devítka), X (desítka), J (spodek), Q (svršek), K (král), A (eso). Desítka a eso jsou tzv. ostré. Jednotlivé skupiny karet, nazývané „barvyÿ, se jmenují srdce (červené), kule, žaludy a listy (zelené). 2.1.1
Příprava na hru
Před začátkem hry je třeba určit, který z hráčů karty rozdá. To se řeší zpravidla náhodným způsobem – například tím, kdo sejme nejvyšší kartu. Takto určený hráč zamíchá balíček. Důležité je, že od této doby se již karty nemíchají. 2.1.2
Snímání
Rozdávající dá sejmout hráči po své pravici. Musí být sejmuty alespoň dvě karty a po sejmutí musí zůstat v balíku nejméně dvě karty, jinými slovy musí být sejmuto dvě až třicet karet. Je zakázáno karty odpočítávat. 2.1.3
Rozdávání
Karty se rozdávají hráčům po směru hodinových ručiček a to tímto způsobem: Rozdávající dá nejprve pět karet hráči po své levici, poté pět karet dalšímu hráči a nakonec pět karet sobě. Poté odloží dvě karty lícem dolu na stůl (tzv. talón) a opět rozdá po pěti kartách každému. Hráč, který dostal karty jako první, se nazývá forhont. Povinnost rozdávat se posunuje s každou hrou o jednu pozici po směru hodinových ručiček, tedy další hru bude rozdávat současný forhont. Rozsazení hráčů kolem stolu, jejich označení a směr hry ukazuje obrázek 2.1. 2.1.4
Licitace
Následuje licitace o talón. Až na výjimky, které budou popsány níže, vyplývá z licitace následující závěr: Talón získává ten hráč, který nabídne nejvyšší typ1 hry (v licitovaném mariáši 1
Někdy označováno jako „úroveňÿ.
KAPITOLA 2. POPIS PROBLÉMU A SPECIFIKACE CÍLE
3
Obrázek 2.1: Rozsazení hráčů kolem stolu
existuje dvanáct typů hry a jednotlivé typy se liší finančním ohodnocením a někdy i cílem). Tento hráč, který bude později volit typ hry, se nazývá aktér a bude hrát proti zbývajícím dvěma hráčům. Následují jednotlivé typy v licitačním pořadí a s odvozením finančního ohodnocení: • sedma – 2 · základ + 1 · základ = 30 h, • lepší sedma – 2 · sedma = 60 h, • sto – 4 · základ = 40 h, • sto a sedma – sto + sedma = 60 h, • lepší sto – 2 · sto = 80 h, • lepší sto a sedma – 2 · sto sedm = 1,20,- Kč, • betl – 15 · základ = 1,50,- Kč, • durch – 30 · základ = 3,- Kč, • dvě sedmy – 40 · základ = 4,- Kč, • sto a dvě sedmy – sto + dvě sedmy = 4,40,- Kč, • lepší dvě sedmy – 2 · dvě sedmy = 8,- Kč, • lepší sto a dvě sedmy – 2 · sto a dvě sedmy = 8,80,- Kč. Finanční ohodnocení zde uvedené odpovídá desetníkovému mariáši, kdy je základem všech částek, jak již název napovídá, 10 haléřů. Například při dvacetníkovém mariáši by tak lepší sedma měla hodnotu 80 h. Základ je zároveň hodnotou tzv. prosté hry. Nyní k průběhu licitace. Obvykle se typy hry nabízejí postupně od sedmy, bez přeskakování typů, i když přeskakování není proti pravidlům. Pro zjednodušení si hráče nyní očíslujeme od forhonta po směru hodinových ručiček. Forhonta tedy bude hráč jedna, následuje hráč dva a rozdávající bude hráč tři.
4
KAPITOLA 2. POPIS PROBLÉMU A SPECIFIKACE CÍLE
Licitovat začíná hráč tři směrem k hráči jedna nabídkou hry („Máš sedmu?ÿ, „Sedma?ÿ). Hráč jedna odpovídá, zda má či nemá. Pokud ano, hráč tři pokračuje nabídkou dalšího typu hry v pořadí a tak to pokračuje tak dlouho, dokud hráč jedna má, případně hráč tři už vyšší typ hry nabídnout nemůže. Následně hráč dva zahájí licitaci s tím hráčem, u kterého „hraÿ zůstala. Pokud z úvodní licitace vyšel vítězně hráč jedna, potom se k němu vyjádří hráč dva, že nemá, nebo mu nabídne vyšší hru a opakuje se stejný proces jako na začátku licitace. Pokud v úvodní licitaci zůstala hra u hráče tři, oznámí mu hráč dvě, zda má či nemá vylicitovanou hru. Pokud má, pokračuje s ním hráč tři v licitaci stejně jako předtím s hráčem jedna. Zjednodušeně řečeno se stává aktérem ten hráč, který od zbylých dvou hráčů uslyší negativní odpověď, tedy že nemohou nabídnout vyšší hru nebo že nemají karty na nabídnutou hru. Při rovnosti nabídnutých her má prioritu hráč jedna, pak hráč dvě a nakonec hráč tři. V případě, že hráč tři ani hráč dvě nenabídnou hráči jedna žádný typ hry a ten nemá zájem hrát, karty se složí a hráč jedna inkasuje 10 h od obou zbývajících hráčů za vyhranou prostou hru. V případě, že jakýkoli hráč vylicituje sedmu a nechce ji hrát, ohlásí tzv. omyl. Karty se složí a hráč, který hlásil omyl, vyplatí oběma zbývajícím hráčům 60 h za flekovanou hru a flekovanou sedmu. V obou těchto případech se po vyúčtování a složení karet může hrát znovu, počínaje sejmutím balíčku. Pokud tyto výjimky nenastanou, licitací určený aktér si bere do ruky talón a následuje volba typu hry. 2.1.5
Základní princip
Pro porozumění dalšího výkladu pravidel je nutné učinit malou odbočku a předem osvětlit základní princip a typy hry, které lze v licitovaném mariáši hrát. Vlastní hra se skládá z tzv. zdvihů. Zdvih představuje jedno kolo hry, ve kterém hráči postupně, po směru hodinových ručiček vynesou na stůl po jedné kartě. Při vynášení karet musejí být respektována dvě jednoduchá pravidla – ctít barvu a přebíjet. To znamená, že hráč musí (může-li) hrát stejnou barvou, jakou má první karta ve zdvihu, a zároveň musí (může-li) přebíjet, tedy vynést kartu vyšší, než jaká byla doposud hraná. Nemůže-li vynést kartu vyšší hodnoty, vynáší kartu nižší hodnoty a stejné barvy. Nemá-li kartu stejné barvy, musí (může-li) vynést trumf a zároveň musí (může-li) vynést trumf vyšší hodnoty, než jakou má nejvyšší trumf ve zdvihu. Nemá-li trumf vyšší hodnoty, musí vynést trumf nižší hodnoty. Hraje-li po něm další hráč, který má na ruce původní vynesenou barvu, musí ji přiznat, avšak nemusí již cokoli přebíjet, protože trumf nelze žádnou netrumfovou kartou přebít. Pokud hráč nemá na ruce kartu stejné barvy, jaká byla vynesena, ani žádný trumf, může hrát libovolnou kartu. V předchozím odstavci byly zmíněny hodnoty karet. Při betlu a durchu jsou hodnoty ve vzestupném pořadí následující: 7, 8, 9, X, J, Q, K, A. U ostatních typů her se posouvá desítka mezi krále a eso, tedy pořadí hodnot vypadá takto: 7, 8, 9, J, Q, K, X, A. Zdvih náleží hráči, který vynesl nejvyšší trumf, případně který vynesl nejvyšší netrumfovou kartu stejné barvy, jakou měla první karta ve zdvihu. Tento hráč zdvih vezme a položí ho lícem dolu na hromádku svých zdvihů. Pokud aktuální zdvih není ještě dohrán, je přípustné se podívat na karty posledního odehraného zdvihu. V jiných případech je nahlížení do odehraných zdvihů zakázáno. První zdvih začíná vynášet forhont (betl a durch tvoří výjimku – první zdvih zahajuje aktér), následující zdvihy zahajuje vždy ten hráč, který sebral předchozí zdvih. Pro některé typy her je důležité bodování. Každá ostrá karta ve vlastních zdvizích má cenu 10 bodů. Sebrání posledního zdvihu představuje 10 bodů. Další body je možno získat za tzv. hlášky, mariáše. K tomu, aby hráč mohl zahrát hlášku, musí mít na ruce svrška a krále stejné barvy (dva hráči hrající proti aktérovi si hlášky nemohou vzájemně „skládatÿ). Netrumfová hláška se počítá za 20 bodů a trumfová za 40 bodů. Zisk bodů za hlášku je podmíněn pouze tím, že hráč vynese svrška z hlášky dříve než krále. To, že vynáší svrška
KAPITOLA 2. POPIS PROBLÉMU A SPECIFIKACE CÍLE
5
z hlášky, naznačí ostatním hráčům tak, že ho nevynese na místo, kam se obvykle vynáší karty aktuálního zdvihu, ale vynese ho na hromádku svých zdvihů. 2.1.6
Typy her
Nyní zběžně k jednotlivým typům hry a jejich cílům. Typy můžeme rozdělit do dvou skupin – bez trumfů, kam patří betl a durch, a s trumfy, kam se řadí zbytek. „Lepšíÿ varianty typů her s trumfem se liší pouze dvojnásobným finančním ohodnocením a tím, že trumfy v nich jsou červené. • Sedma Při hře sedmy se aktér snaží splnit dva navzájem nezávislé závazky – sebrat poslední zdvih trumfovou sedmou a uhrát prostou hru, to znamená uhrát více bodů než oba spoluhráči dohromady. Hodnota prvního závazku je 20 h, hodnota prosté hry je 10 h. S prvním závazkem se pojí pravidlo, že trumfovou sedmu nemůže aktér hrát v případě, kdy má hráč možnost hrát jinou kartu. • Sto Při stu se aktér zavazuje získat nejméně 100 bodů pomocí maximálně jedné hlášky. V případě, že má trumfovou i netrumfovou hlášku, počítá se do oněch 100 bodů ta silnější – trumfová. • Sto a sedma Sto a sedma je určitou kombinací sta a sedmy. Opět se jedná o dva nezávislé závazky aktéra – získat nejméně 100 bodů pomocí jedné hlášky a sebrat poslední zdvih trumfovou sedmou. S druhým závazkem se pojí stejné pravidlo jako při pouhé sedmě. • Betl (malý) Při betlu se aktér zavazuje, že nesebere ani jeden zdvih. V případě, že zdvih sebere, hra okamžitě končí jeho prohrou. • Durch (velký) Durch je protipól betlu. Aktér se snaží sebrat všechny zdvihy. V momentě, kdy nějaký zdvih sebere jeden z hráčů hrajících proti němu, hra končí aktérovou prohrou. • Dvě sedmy Při dvou sedmách se aktér zavazuje sebrat poslední zdvih pomocí trumfové sedmy a předposlední zdvih pomocí sedmy jiné barvy, tzv. strkací. Jde o jediný závazek. Trumfovou sedmu opět nemůže aktér hrát v případě, kdy má možnost hrát jinou kartu, stejně tak strkací sedmu nemá dovoleno hrát dříve než v předposledním zdvihu, pokud k tomu není donucen. Pokud má jeden z protihráčů čtyři a více karet ve strkací barvě, po flekování na to musí upozornit aktéra a tyto karty ukázat – pro aktéra to znamená prohru a skládají se karty. • Sto a dvě sedmy Nejtěžší typ hry. Aktér se snaží splnit dva nezávislé závazky. Prvním závazkem je uhrát poslední zdvih trumfovou sedmou a předposlední zdvih pomocí strkací sedmy. Druhým závazkem je získat nejméně 100 bodů pomocí jedné hlášky. S prvním závazkem se pojí totožné omezení jako u pouhých dvou sedem. Opět, pokud má jeden z protihráčů čtyři a více karet ve strkací barvě, po flekování na to musí upozornit aktéra, karty ale neukazuje a hra se hraje.
6 2.1.7
KAPITOLA 2. POPIS PROBLÉMU A SPECIFIKACE CÍLE Volba typu hry
Aktér může volit pouze ten typ hry, který vylicitoval, případně jakýkoli vyšší. Pokud vylicitoval sedmu (a nenahlásil omyl) či lepší sedmu a nemá ji na ruce, musí hrát vyšší hru. Oznámí ostatním hráčům typ hry, který se bude hrát, pokud jde o hru s trumfy, oznámí jim samozřejmě také barvu trumfů, pokud jde o typ hry se dvěma sedmami, oznámí jim i barvu strkací sedmy. 2.1.8
Odložení karet do talónu
Po volbě typu hry musí aktér odložit dvě karty lícem dolů na stůl, do talónu. Pokud se bude hrát betl nebo durch, může odložit libovolné dvě karty, v ostatních případech nesmí odkládat ostré, aby nemohlo dojít k nerozhodnému počtu bodů. Výjimkou jsou plonkové desítky, tedy desítky, kdy hráč nemá na ruce jinou kartu stejné barvy. Tyto desítky jsou neuhratelné. Lze je odložit do talónu lícem nahoru. Body v tom případě připadnou protistraně, avšak ta už na desítce nemůže namazat. Toto odložení lícem nahoru se nazývá „točeníÿ. 2.1.9
Flekování
Poté, co aktér odloží karty do talónu, následuje fáze tzv. flekování. Flekování závazku znamená zdvojnásobení jeho současného finančního ohodnocení a hráč jím vyjadřuje důvěru ve své karty a úsudek. U typů her se dvěma závazky lze flekovat závazky nezávisle. Základní závazek se typicky označuje slovem „hraÿ, u sedmy, sto sedmy, sto a dvou sedem a jejich lepších variant se druhý závazek označuje jako „sedmaÿ, respektive „sedmyÿ. K flekování se používá speciálních termínů: flek, re, tutti, boty, košile, kalhoty, kajzr, majzl, pajzl, hajzl, rekajzr, tuttikajzr, botykajzr a výš. Flek odpovídá dvojnásobku základního ohodnocení, re čtyřnásobku, tutti osminásobku, atd. Pokud hráč nechce flekovat, oznámí: „Dobrý.ÿ Při flekování se hráči vyjadřují postupně, po směru hodinových ručiček, počínaje hráčem vlevo od aktéra. Hráči hrající proti aktérovi vystupují v této fázi společně (jakožto jedna strana ve hře) – pokud například při sedmě hráč následující po aktérovi flekuje hru i sedmu, jeho spoluhráč je přeskočen a na řadu se dostává samotný aktér, protože strana hrající proti němu se již plně vyjádřila. S výjimkou úplného počátku flekování mohou být znovu flekovány pouze ty závazky, které byly bezprostředně předtím flekovány druhou stranou. Flekování tedy končí ve chvíli, kdy jedna ze stran neflekuje žádný závazek. Pokud je vybrána jako typ hry sedma a není flekován závazek sedmy, karty se složí a aktér inkasuje od každého z protihráčů 20 h za prohranou sedmu. Je-li flek pouze na hru, nehraje se a finanční výsledek je nulový (je to tzv. zadarmo). Nenastanou-li tyto výjimky, pak se přikročí k vlastní hře. 2.1.10
Vlastní hra
Po flekování následuje vlastní hra sestávající se z jednotlivých zdvihů. Základní princip hry byl již nastíněn na předchozích stránkách. Aktér se snaží splnit závazek či závazky zvoleného typu hry a zbývající dva hráči mu v tom brání. Přestože tito dva hráči tvoří jednu stranu hry (čili je záhodno, aby spolupracovali), není dovoleno, aby se navzájem radili či aby si ukazovali karty. Hra obvykle končí desátým zdvihem, v případě betlu a durchu může končit i dříve aktérovou prohrou. 2.1.11
Vyúčtování
Po skončení hry následuje vyúčtování mezi hráči. Za uhraný závazek inkasuje aktér od každého ze zbývajících hráčů hodnotu tohoto závazku danou základem a případným flekováním, obdobně
KAPITOLA 2. POPIS PROBLÉMU A SPECIFIKACE CÍLE
7
za prohraný závazek platí každému ze zbývajících hráčů hodnotu danou základem a případným flekováním. Se stovkou se pojí specifické počítání jejího výsledného finančního ohodnocení. Pro účely jeho výpočtu se aktérovi započítají body i za případné další hlášky. Základní hodnota stovky činí 40 h. S každými deseti body, které dělí celkové body aktéra od 100 bodů, se zvyšuje její hodnota o dalších 40 h (tzv. sčítaná stovka). 2.1.12
Složení karet
Po vyúčtování sebere dosavadní forhont karty. Obvykle se nejdříve sbírají karty z talónu, poté po směru hodinových ručiček karty zbylé v rukou jednotlivých hráčů a zdvihy jednotlivých hráčů. Případně se lze setkat i se způsobem, kdy se po sebrání talónu seberou karty zbylé v rukou a až poté zdvihy. Sbírání karet začíná nejčastěji u forhonta. Svršci z hlášek zůstávají na místech, kam byli zahráni, pouze se otočí lícem dolu. Možná složení balíčku po sebrání zachycuje obrázek 2.2. U karet je připsáno, kterým hráčům patřily. Hráči, označeni jako „aÿ, „bÿ a „cÿ, sedí okolo stolu po směru hodinových ručiček. Hráčem „aÿ je nejčastěji forhont.
Obrázek 2.2: Možná složení balíčku, vlevo prokládaně, vpravo neprokládaně
2.2
Specifikace cíle
Existuje jistá nepsaná sada implicitních požadavků, které by měl splňovat každý program. Mezi ně patří například minimální chybovost, přiměřená hardwarová náročnost, dobrá udržovatelnost a korektnost, tedy splnění explicitních požadavků, které jsou na program kladeny. Implicitní požadavky zde nebudou dále rozebírány. Základním explicitním požadavkem na cílovou aplikaci je možnost hrát licitovaný mariáš se dvěma spoluhráči nahrazenými počítačem. Obecným požadavkem pak je modulární design, který usnadní její případné rozšiřování v budoucnosti. Hru pro jednoho hráče můžeme považovat za jeden mód hry. Mělo by být snadné do aplikace zabudovat mód, kdy budou proti sobě hrát pouze umělé inteligence, nebo třeba podporu hry po síti. Případně implementovat jiné heuristiky v umělé inteligenci. Modulární design základního modelu hry spolu s flexibilním rozhraním nad tímto modelem by toto vše měl umožnit za relativně nízkou „cenuÿ. 2.2.1
Rešerše existujících řešení
Výše zmíněný základní požadavek je relativně obsáhlý, avšak nepříliš konkrétní. V následujících odstavcích z uživatelského hlediska stručně charakterizuji již existující programy pro hraní mariáše a pokusím se vyvodit další, konkrétnější požadavky, kterým by měla aplikace vyhovět.
8
KAPITOLA 2. POPIS PROBLÉMU A SPECIFIKACE CÍLE • Becherovka Mariáš od Falasoftu Nejedná se o licitovaný mariáš, ale o jednodušší variantu hry – mariáš volený. Grafické rozhraní není designově dotažené do detailu, nicméně vyhovující. Lze volit obrázek na rubu karet. Hru lze ovládat pouze myší, vybraná karta se znázorní povysunutím, nelze zahrát renonc. Hra je ozvučena, ale ozvučení nelze vypnout. Z možností, které hra nabízí, bych jmenoval nastavení jmen hráčů, statistiky výher a proher, které se ukládají, nestandardní nastavení míchání, rozdávání a vyhodnocení her, např. hlášené stovky (neodpovídající pravidlům uvedeným v této práci), řazení karet a nastavení finančního ohodnocení her. • Volně přístupná verze Mariáše z www.marias.ik.cz Zřejmě jediný zdarma dostupný program umožňující hrát licitovaný mariáš. Jeho grafické rozhraní je strohé, přizpůsobené hraní přes internet. Nenabízí standardní menu lištu v horní části okna, vybrané karty jsou označeny textem, lze hrát renonc. Lze hrát jak offline proti počítači, tak po připojení na server s lidskými spoluhráči, což je nejspíše největší pozitivum programu. Hráči spolu mohou chatovat. Z dalších možností uvedu automatické řazení karet, nastavení barvy pozadí, možnost nehrát svrška do hlášky, i když je to možné. • Mariáš 2.7 od Machrsoftu Shareware, který lze používat bez registrace 30 dní. Opět se jedná o volený mariáš pro jednoho hráče. Vyniká nad ostatní grafickým zpracováním – karty mají vysoké rozlišení, navíc je jejich pohyb při hře animovaný. Je možné si vybrat jednohlavé či dvouhlavé karty, rovněž i barvu pozadí. Vybrané karty se povysunou, nelze hrát renonc. Hra poskytuje nápovědu.
Společné všem třem aplikacím bylo, že počítačoví spoluhráči hráli své tahy poměrně svižně (v řádu sekund). Úroveň umělé inteligence nebyla v rámci rešerše podrobně analyzována, nicméně počítačoví protivníci nečinili elementární chyby, například hraní takové karty v prvním zdvihu betlu či durchu, které vede k okamžité prohře, nebo přelicitování v případě licitovaného mariáše. Rovněž se nikdy nedopouštěli renonců. 2.2.2
Možnosti uživatele
Na základě rešerše a úvahy byl vypracován podrobnější seznam základních možností, které by aplikace měla uživateli nabízet: • možnost hrát licitovaný mariáš se dvěma spoluhráči nahrazenými počítačem, • řazení karet, • úprava grafického prostředí, • možnost nápovědy. K prvnímu bodu je třeba poznamenat, že hrát se bude dle pravidel, jež jsou součástí této práce. Hra nebude umožňovat uživateli pravidla měnit a také neumožní hrát renonc. Řazení karet v hráčově ruce dle jejich hodnoty patří k základním pomocným funkcím mariášových programů. Úpravou grafického prostředí hry je míněna například změna pozadí herní plochy či importování vlastních karet.
KAPITOLA 2. POPIS PROBLÉMU A SPECIFIKACE CÍLE 2.2.3
9
Požadavky na grafické a ovládací rozhraní
Rešerše také napomohla při stanovení základních požadavků na uživatelské rozhraní: • přehledné grafické znázornění herní situace a doplňujících informací, • možnost pohodlně ovládat aplikaci kombinací myši a klávesnicí, • minimum dialogových oken. Herní situací je míněno rozložení karet v rukou hráčů a na stole, mezi doplňující informace patří například typ hry, barva trumfů, jméno hráče, který je na řadě, karty v posledním zdvihu, apod. Pro tento typ karetní hry není třeba, aby byly přesuny karet plně animované. Ovládání by mělo být koncipováno tak, aby k vykonání zamýšlené akce bylo třeba minimum úkonů, to znamená též minimum dialogových oken, která se pouze potvrzují, implementování klávesových zkratek pro přístup do menu, apod. 2.2.4
Požadavky na umělou inteligenci
Základní požadavky na umělou inteligenci ve hře jsou následující: • hra v souladu s pravidly, • vyvarování se elementárních chyb při rozhodování, • přiměřená doba reakce, • schopnost nápovědy. Hrou v souladu s pravidly je u umělé inteligence myšleno to, že činí a vykonává rozhodnutí, jež jsou v souladu s pravidly. Za přiměřenou dobu reakce lze považovat několik sekund, případně by délku tohoto intervalu mohl uživatel nastavit. Nápověda by měla být přítomna ve všech fázích hry, nejen při volbě karet do zdvihů.
10
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
3 Analýza a návrh řešení Výchozí bod pro analýzu problému představují pravidla licitovaného mariáše. Cílem analýzy je především formalizace těchto pravidel, vytvoření modelu, který reflektuje průběh a stavy hry z pohledu jednoho jejího účastníka1 . Tím je míněno navržení odpovídajícího stavového automatu, včetně jeho podautomatů – pro licitaci, flekování a vlastní hru. K nejzákladnějším funkcím těchto automatů patří určení, který účastník je právě na řadě. Protože hra samotná představuje systém spolupracujících automatů, bylo nutné do analýzy zahrnout rovněž schéma této spolupráce. Část analýzy je věnována umělé inteligenci řídící počítačové hráče v jednotlivých fázích hry, kombinaci rozhodování dle heuristik postavených na expertních pravidlech a prohledávání stavového prostoru, opět s využitím heuristických hodnotících funkcí. Na závěr této kapitoly je diskutována volba implementačního jazyka – Javy, nejpodstatnější důvody pro tuto volbu jsou rozvedeny.
3.1
Model hry
Průběh hry licitovaného mariáše, tedy kostru modelu, zachycuje ve zjednodušené formě obrázek 3.1. Byla použita standardní notace UML pro stavový diagram, kde každý oválek představuje určitou akci či stav v modelu hry. Poměrně snadno si lze představit i odpovídající klasický diagram stavového automatu, kde by jednotlivé stavy představovaly čekání na provedení určité akce, jako je třeba sejmutí balíčku či flekování, a přechody mezi stavy by byly těmito akcemi označeny.
Obrázek 3.1: Průběh hry
Opět použijeme číselné označení hráčů. Forhonta bude hráč jedna, následuje hráč dva a rozdávající bude hráč tři. Diagram kopíruje možný vývoj hry, tak jak byl popsán v pravidlech. Rozhodování, které následuje po licitaci, zohledňuje dvě výjimečné situace, které mohou nastat při licitaci – hráč tři ani hráč dvě nenabídl hráči jedna žádný typ hry a hráč jedna sám nemá o hru zájem, případně 1 Účastníkem je myšlen jak hráč, disponující neúplnou, subjektivní informací o stavu hry, tak tzv. server, disponující úplnou, objektivní informací.
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
11
byla vylicitována sedma a vítěz licitace nechce z jakéhokoli důvodu hrát. Rozhodování, jež následuje flekování, odráží výjimku, kdy byla vybrána sedma a závazek sedmy nebyl flekován, případně byl flekován pouze závazek hry. Důležitý detail, který je možno z diagramu vyčíst, je fakt, že karty se míchají pouze na samém počátku hraní, ne mezi jednotlivými hrami. Mariáš totiž není hra, kde má velký vliv na výsledek hry náhoda. Zkušení hráči dokáží pozorným sledováním průběhu hry, zapamatováním jednotlivých zdvihů a způsobu skládání karet odhadnout rozložení karet v balíčku. Mohou poté snímat tak, aby byly karty rozdány v jejich prospěch. A samozřejmě mohou tímto vědět, jaké karty mají na rukou spoluhráči. Tento problém je hlouběji rozebrán v části analýzy věnované umělé inteligenci. Pro znázornění akcí licitace, volby typu hry, flekování a vlastní hry byl použit symbol kompozitního stavu, tj. tyto akce mají složitější charakter. • Licitace Licitaci zachycuje obrázek 3.2. Jde o diagram stavového automatu, který v tomto případě vystihuje podstatu licitace lépe než stavový diagram v UML.
Obrázek 3.2: Stavový automat licitace
Hráči jsou na diagramu označováni jako hráč tři, hráč jedna a hráč dvě – v tomto pořadí také sedí okolo stolu po směru hodinových ručiček. Na diagramu jsou velmi zřetelné tři cykly, kdy spolu licitují vždy dva hráči. V automatu existují čtyři typy akcí. Akce nabízí znamená, že daný hráč nabízí vyšší typ hry, než jaký byl dosud vylicitován (jakýkoli typ hry je považován za vyšší než žádný typ). Akce nenabízí znamená, že daný hráč již nechce nebo nemůže nabídnout vyšší typ hry,
12
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ než jaký byl dosud vylicitován. Akce má a nemá jsou odpověďmi na nabídnutí určitého typu hry. Počátečním stavem automatu je stav 0, kdy hráč tři může nabídnout směrem k hráči jedna. Podstatné je, že v každém stavu je aktivní právě jeden hráč, tj. pouze akce tohoto hráče způsobí přechod automatu do jiného stavu. Ve stavech 0 a 3 je aktivní hráč tři, ve stavech 1 a 5 hráč jedna a ve stavech 2 a 4 hráč dva. Automat má tři cílové stavy, označení hráče, který vyšel vítězně z licitace je uvedeno pod stavem v závorkách. • Volba typu hry Volbu typu hry zachycuje stavový diagram na obrázku 3.3. Objasňuje zejména to, jakým způsobem je přeskakována volba trumfů a strkacích. Pokud se jedná o lepší variantu hry, volba trumfů se přeskakuje, protože trumfy jsou pak automaticky červené, neznamená to, že by trumfy nebyly vybrány.
Obrázek 3.3: Volba typu hry
• Flekování Stavový diagram 3.4 zobrazuje průběh flekování, který by z pouhého slovního popisu nemusel být úplně zřejmý. • Vlastní hra Průběh vlastní hry, skládající se ze zdvihů, byl detailně vyložen v pravidlech, přesto byl pro úplnost vytvořen diagram 3.5. Rozhodování po vyhodnocení zdvihu odráží i možnost skončit kolo hry jiným než desátým zdvihem, což může nastat při betlu či durchu.
3.2
Spolupráce automatů
Základní úroveň modelu hry a s ní související automat a jeho podautomaty byli popsány v předchozí kapitole. Při vytváření vyšší úrovně modelu lze na licitovaný mariáš v principu nahlížet dvěma způsoby – jako na jediný tento automat, či jako na systém spolupracujících automatů.
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
13
Obrázek 3.4: Flekování
V obou případech je základní vlastností automatu (automatů) určení hráče, který je právě na řadě, tedy na jehož akci se právě čeká. Od zvoleného modelu se poté budou odvíjet datové struktury, funkce a vztahy mezi nimi. Budeme-li na hru nahlížet jako na jediný automat, znamenalo by to, že hráči budou v průběhu hry střídavě „přebírat kontroluÿ nad tímto automatem jakožto jedním objektem. Z toho by mimo jiné vyplývalo, že není možné, aby hráči měli současně odlišný stav hry. Toto by se na první pohled nemuselo zdát jako problém, avšak je třeba si uvědomit, že stav hry není určen pouze tím, který z hráčů je právě na řadě, ale i dalšími informacemi, kterými hráči disponují, zejména rozložení karet v rukou hráčů. Tyto informace se u jednotlivých hráčů mohou lišit (a přesto představovat navzájem konzistentní subjektivní popisy aktuálního stavu hry). Rovněž si lze představit herní módy, které dokonce přímo vyžadují, aby hra mohla být z pohledu hráčů v různých stavech. Například hra více lidských hráčů u jednoho počítače, kteří se u ovládání hry střídají (tzv. hot seat mód). Hráči, který usedne ke hře, se v tomto módu musí teprve zobrazit akce, které učinili hráči před ním. Modelování hry jediným automatem by také mohlo přinést komplikace při případné budoucí implemetaci podpory hry po síti, kdy by bylo nutné po síti posílat celý objekt reprezentující stav hry. Systém spolupracujících automatů, které si mezi sebou předávají zprávy, nabízí podstatně větší flexibilitu než výše uvedený model, navíc umožňuje přirozeným způsobem oddělit subjektivní a objektivní popis hry a usnadní implementaci hry po síti. V podstatě se jedná o klientserver architekturu. Centrálním automatem v systému je automat serveru. Centrální automat v sobě zachycuje objektivní popis stavu hry, tj. „znáÿ přesné rozložení všech karet ve hře. Automat klientský, zastoupený v systému logicky třemi instancemi, představuje pouze subjektivní popis stavu hry, tj. informace dostupné jednomu jedinému hráči. Klientský automat tak obsahuje totožnou informaci o kartách v ruce svého „vlastníkaÿ jako automat serveru, avšak údaje o kartách v rukou zbylých dvou hráčů se od údajů centrálního automatu mohou lišit a také často liší. Zjednodušeně řečeno, tam, kde má server jednoznačné informace (jedna konkrétní karta), může vidět klient pouze možnosti (množina karet).
14
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
Obrázek 3.5: Vlastní hra
Tato klient-server architektura funguje v případě licitovaného mariáše následujícím způsobem: Klient, který je právě na řadě, učiní na základě jemu dostupných informací volbu akce. Tuto akci aplikuje na svůj klientský automat a předá zprávu o této akci serveru. Ten aplikuje na základě zprávy akci na centrální automat a předá zprávu ostatním dvěma klientům, kteří následně aplikují akci na své automaty. Toto zjednodušeně zachycuje obrázek 3.6. Jednotlivé děje jsou pro přehlednost očíslovány.
Obrázek 3.6: Fungování klient-server architektury
Aplikace akce se u automatů serveru a klientů většinou neliší. Výjimkou jsou ty případy, kdy si klientský automat na základě hraných karet navíc upravuje informace o rozložení karet v rukou spoluhráčů. Rozdílně probíhají též akce sbírání a rozdávání karet, které budou mít u klientského automatu množinový charakter. Server vystupuje v modelu jako fiktivní čtvrtý hráč. Nejvíce je to zřejmé v případě rozdávání
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
15
karet. Hráč, který by měl provádět rozdávání, často nemá absolutně přesné informace o rozložení karet v balíčku a proto nemůže předat zbylým dvěma hráčům zprávu o tom, že obdrželi ty a ty karty. Tuto situaci může řešit jedině server, který objektivní informace k dispozici má, a rozešle tedy jednotlivým hráčům relevantní zprávy. Server má v některých případech i roli „cenzoraÿ. Odkládá-li například jeden z hráčů karty do talónu, posílá ve své zprávě serveru, o které karty se jedná. Server na základě této zprávy aplikuje akci na centrální automat. Zbylí dva hráči by ale neměli vědět, které konkrétní karty byly odloženy (pokud se nejedná o točené desítky). Proto server tuto informaci ze zprávy vymaže před tím, než těmto hráčům zprávu přepošle. Obdobně „zaujatěÿ se chová server při sbírání talónu – zprávu obsahující konkrétní karty zašle pouze tomu hráči, který talón sebral, ostatním dojde pouze zpráva o tom, že byl daným hráčem talón sebrán. Další úlohu server plní při snímání. Hráč pošle serveru počet karet, které zamýšlí sejmout, a server tento počet modifikuje o náhodnou hodnotu z určitého rozsahu a sejme – karty je totiž zakázáno odpočítávat, toto simuluje sejmutí přibližného počtu karet. Hráč poté samozřejmě neví, kolik karet bylo přesně sejmuto.
3.3 3.3.1
Umělá inteligence Odstranění neúplnosti informace
Na úvod k problematice umělé inteligence v mariáši je třeba poznamenat, že se jedná o hru s neúplnou informací, tj. počítačový spoluhráč nemá (neměl by mít) k dispozici všechny informace určující stav hry. Toto často významně omezuje schopnost umělé inteligence činit správná rozhodnutí. Ve valné většině her tohoto typu je tento problém řešen jednoduše – umělá inteligence podvádí a na rozdíl od lidského hráče má k dispozici úplnou informaci, tedy vidí všem do karet. V této práci si možnost podvádění umělé inteligence ponechám pro případ nejtěžší obtížnosti hry. Problém tohoto přístupu by mohl nastat, chceme-li například proti sobě přímo porovnat schopnosti počítačových protivníků dvou her. Jednu hru spustíme jako „serverÿ se dvěma počítačovými spoluhráči a jedním námi ovládaným, u druhé hry bude nutno simulovat akce počítačových hráčů z jmenovaného serveru a zároveň nám druhá hra musí poskytnout rady od umělé inteligence, jakým způsobem by bylo nejlépe právě hrát. Je zjevné, že pokud počítačoví hráči serveru mají k dispozici úplnou informaci, je umělá inteligence druhé hry znevýhodněna. Stejná situace by nastala, pokud bychom od hry chtěli možnost kvalitní nápovědy při případné hře po síti – klient nemá k dispozici objektivní popis stavu hry. Proto byl v rámci této práce navržen takový datový model, aby byl i ve výše zmíněných situacích dopad neúplné informace při rozhodování umělé inteligence co nejmenší. Principy licitovaného mariáše toto umožňují. Již při druhé hře může mít počítačový hráč za jistých okolností úplnou informaci o distribuci karet bez toho, aniž by podváděl. V následujících odstavcích popíši, jak je toto možné. Na samém počátku hry je informace počítačového protivníka o rozložení karet mezi hráči velmi slabá – vidí pouze své karty a v rukou obou spoluhráčů a talónu tedy mohou být jakékoli karty, které on nemá (lze si představit jakési množiny možností). V průběhu vlastní hry (během zdvihů) počítačový hráč tyto množiny redukuje následujícím způsobem: • Je-li vyložena karta jedním hráčem, vyjme se navíc z množiny talónu a ruky druhého hráče. • Vyloží-li protihráč kartu stejné barvy, jakou má současná „vedoucíÿ karta, a je nižší hodnoty, vyjmou se i všechny karty vyšší hodnoty a stejné barvy jako vedoucí karta. • Je-li hrána karta jiné barvy, než jaké je první vyložená, vyjmou se z ruky daného hráče
16
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ i všechny karty barvy shodné s první vyloženou. • Pokud je karta jiné barvy než první vyložená i trumf, vymažou se z ruky hráče také všechny trumfy.
Toto zužování množin možností by mělo pomoci k informovanějšímu rozhodování již během první hry. Zároveň by si počítačový hráč měl všímat, jaké karty jdou do zdvihů. Na konci kola hry sesbírá karty dle zvoleného způsobu (viz. pravidla) s ohledem na množinový charakter informací do jednoho zvláštního balíčku. Zvláštní je v tom smyslu, že v něm tvoří jednu pozici nikoli jedna konkrétní karta, ale množina karet, které připadají pro tuto pozici v úvahu. S tímto balíčkem, akumulujícím informaci přenášenou do následující hry, bude počítačový hráč pracovat dále během snímání a rozdávání. Během snímání může umělá inteligence sejmout onen přibližný počet karet, který byl sejmut. Není to ovšem nutné – jak už bylo naznačeno, žádný z hráčů, ani snímající sám, neví přesně, kolik karet bylo sejmuto. Klíčový moment přijde až během rozdávání, kdy se umělá inteligence pokusí na základě karet, jež obdržela, o určení přesného počtu karet, jenž byl sejmut. Lze jednoduše odvodit, které pozice v balíčku odpovídají kartám forhonta i ostatních hráčů. Např. pro forhonta je to 1. až 5. a 18. až 22. karta odshora. Počítačový hráč tedy zkouší snímat různé počty karet ze zmíněného zvláštního balíčku a porovnává, zda pro dané sejmutí nalezne na „svýchÿ pozicích všechny karty, které má na ruce. Pokud tímto způsobem vyhovuje pouze jedno sejmutí, nalezl počet karet, který byl skutečně sejmut. V tom případě může počítač již v druhé hře simulovaným rozdáváním ze zvláštního balíčku určit možnou distribuci karet mezi spoluhráči mnohem lépe než na počátku prvního kola. Pokud vyhovuje více sejmutí, nelze přesný počet s jistotou určit – v takovém případě je zřejmě lepší opět uvažovat, že spoluhráči mohou mít na rukou jakékoli karty krom vlastních, a žádná informace se z minulé hry do nové nepřenese. Výhodou tohoto přístupu je, že funguje (dovede omezit množiny možných karet v rukou spoluhráčů) mnohdy i v případě, že hra skončila před desátým zdvihem. Vzhledem k tomu, že lidskému hráči má být k dispozici nápověda, tak by bylo vhodné, aby i u jeho automatu byly prováděny tyto základní operace pro odstranění neúplnosti informace – redukce množin možností, zaznamenávání zdvihů, speciální sbírání, rozpoznání počtu sejmutých karet a odvození jejich distribuce mezi hráče při rozdávání. Tyto operace budou prováděny v pozadí, bez vědomí hráče, a díky nim bude moci umělá inteligence nápovědy radit na základě optimálních informací.
3.3.2
Fáze hry
V následujících odstavcích se pokusím o analýzu fází hry, kde musí počítačový hráč činit rozhodnutí, a požadavků, které na umělou inteligenci kladou. Rovněž připojím i možný netriviální způsob řešení. Podstatnou součástí navrženého řešení je heuristická funkce, která zhodnotí vhodnost dané distribuce karet k uhrání jednotlivých typů hry (míněno vlastní typ, případně v kombinaci s barvou trumfů, případně i barvou strkacích) daným hráčem. Pokud hráč jistě ví, které dvě karty jsou v talónu, mohl by ještě před ohodnocováním simulovat sebrání talónu a odhození vybraných dvou karet. Tato hodnotící funkce, která bude muset dobře pracovat i nad subjektivní informací o hře v podobě množin možností, se využije při snímání, dále v licitaci a flekování, při odkládání karet do talónu i flekování. Funkce bude použita v součinnosti s nastavenou přijatelnou mírou vhodnosti (odpovídá tomu, jak silně bude umělá inteligence riskovat). Pokud bude vhodnost distribuce karet pro daný typ hry větší než přijatelná úroveň vhodnosti, počítačový hráč bude tento typ hry považovat za uhratelný.
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
17
Snímání Možnosti umělé inteligence ovlivnit budoucí průběh hry (distribuci karet) vhodným sejmutím poněkud omezuje nemožnost sejmutí přesného počtu karet, ale i to jde určitým způsobem obejít. V této fázi by mohla být hodnotící funkce využita tak, že se pro všechny způsoby sejmutí určí nejvyšší typ hry, který je uhratelný, a označí se číslem odpovídajícím úrovni typu hry. Následně se z důvodu rozptylu počtu skutečně sejmutých karet udělají pro každý počet sejmutí průměry zahrnující navíc i několik okolních hodnot. Jako vhodný počet karet, které se má počítač pokusit sejmout, se vybere ten, ke kterému vyšel nejvyšší průměr. Licitace Licitace a vlastní hra jsou klíčovými fázemi pro umělou inteligenci. Jádrem licitace by mohla být opět výše zmíněná hodnotící funkce, která spolu s nastavenou přijatelnou úrovní rizika určí typ hry, do kterého bude počítač licitovat a přes který nepůjde. Touto hranicí je nejvyšší (nejdražší) úroveň hry, která je zároveň uhratelná. Byla-li zmiňovaná hranice již dosažena či dokonce překročena, umělá inteligence nebude nabízet vyšší typy hry. Volba typu hry Typ hry, včetně barvy trumfů a strkacích, který si počítačem řízený hráč vybere, bude určen již v licitaci jako nejvyšší typ hry s vhodností nad přijatelnou úrovní vhodnosti. Došlo-li k přelicitování, bude zvolen nejnižší možný typ hry a případná barva trumfů a strkacích se zvolí tak, aby byla pravděpodobnost uhrání daného typu hry co možná nejvyšší. Odložení karet do talónu Při odkládání karet do talónu by bylo možné využít omezené podoby hodnotící funkce. Pro všechny možnosti odložení dvojic karet do talónu by se spočetlo ohodnocení již zvoleného typu hry. Možností může být maximálně 132 – vybírají se dvojice karet z tuctu (12·11), přičemž při hrách s trumfem nelze odložit ostré (s výjimkou točení plonkové desítky). Poté by se přirozeně vybrala ta možnost odložení karet, která vedla na nejvyšší ohodnocení. Pokud bylo při předchozím počítání ohodnocení jednotlivých typů her (při licitaci) uvažováno se sebráním talónu a odložením dvou vybraných karet, měly by být v tomto momentě odloženy tytéž karty, jaké byly uvažovány při ohodnocování. Flekování Opět lze využít hodnotící funkce. Umělá inteligence v této fázi spočítá, jaká je vhodnost distribuce karet k odehrání zvolené hry pro aktéra – v předchozích případech se vždy počítala vhodnost distribuce pro počítačového hráče samotného, zde to může být i jiný hráč. Dle „vzdálenostiÿ této hodnoty od přijatelné úrovně vhodnosti se pak určí přiměřený počet flekování. Vlastní hra Pro vlastní hru se nabízí zřejmé řešení – prohledávání stavového prostoru založené na algoritmu minimax s případným alfa-beta prořezáváním. V obdobě minimaxu by se mělo odrazit to, že dva hráči spolupracují proti aktérovi. Heuristická funkce může vycházet z finančního výsledku pro aktéra. MAX úrovně v prohledávání budou odpovídat akcím aktéra, který chce maximalizovat svůj zisk, a MIN úrovně akcím protihráčů, kteří chtějí minimalizovat zisk aktéra. Problematické je, že spolupracující hráči navzájem nevidí své karty, vidí pouze možnosti karet. Když v tomto případě bude jeden z protihráčů uvažovat akci svého spoluhráče jako úroveň MIN, předpokládá tím nejen ideální součinnost
18
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
a spolupráci druhého hráče, ale zároveň i ten fakt, že danou kartu spoluhráč skutečně na ruce má. Stejnou představu o tom, co je a co není vhodný tah, tedy spolupráci, zajistí použití stejných heuristik u spoluhráčů (problematičtější ovšem pak bude spolupráce s lidským hráčem). Vzhledem k tomu, že při typickém průběhu hraní bude mít umělá inteligence k dispozici celkové rozložení karet na konci první hry, můžeme počáteční nepřesnosti v uvažování pro jednoduchost zanedbat. Heuristická funkce by měla být rovněž navržena tak, aby reflektovala finanční ohodnocení jednotlivých závazků. Tímto způsobem se docílí toho, že pokud dojde k situaci, kdy nelze zároveň splnit oba závazky ve hře se dvěma závazky, bude „obětovánÿ levnější závazek (je-li to aktér, který právě uvažuje nad správným tahem). Složení karet Způsob složení karet (zda se skládá prokládaně, od kterého hráče se začíná) se může víceméně ponechat na náhodě, protože složení balíčku a výslednou distribuci karet mezi hráče značně ovlivní následné sejmutí. Karty totiž skládá forhont ukončené hry a snímá je hráč po forhontově pravé ruce. Jakýkoli pokus o vhodný způsob složení by tedy byl pravděpodobně zmařen snímáním. Pro jednoduchost nebude umožněno přerovnávání karet na ruce před skládáním, které by šlo využít pro trhání hlášek.
3.4
GUI a ovládání
V rámci analýzy GUI a ovládání karetní aplikace byla navržena základní podoba aplikace zobrazená na obrázku 3.7.
Obrázek 3.7: Základní podoba herní obrazovky
V horní části obrazovky se nachází roletové hlavní menu. Na pravé straně je panel s informacemi pro hráče. Obsahuje mimo jiné herní žurnál, kam se zaznamenávají jednotlivá rozhodnutí hráčů během hry. Panel označený jako Rozhodování nahrazuje klasická okna – dialogy pro volby, které není možno učinit klikáním do herní plochy. Tento panel bude obsahovat dle potřeby hry vždy patřičnou komponentu.
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
19
Herní plocha zabírá většinu obrazovky. Karty otočené lícem nahoru jsou vybarveny bíle, karty lícem dolu jsou šedé. V dolní části herní plochy jsou karty lidského hráče. Na levé straně jsou zobrazeny karty hráče po levici a v horní části plochy karty hráče po pravici lidského hráče. Karty se budou označovat klikáním myší, označená karta se povysune. Potvrzení označení se provede opětovným kliknutím na označenou kartu. Předpokládané rozlišení, které bude aplikace vyžadovat, je 1024x768.
3.5
Volba implementačního jazyka
Před implementací byl volen programovací jazyk a vybrána byla Java. Jedním z faktorů, který tuto volbu významně ovlivnil je charakter výsledné aplikace. Ta je počítačovou hrou, tedy aplikací, která nevyžaduje speciální (přímý) přístup k hardwaru. Zároveň v sobě spojuje několik oblastí – zahrnuje interaktivní grafické uživatelské rozhraní, umělou inteligenci a potenciálně i komunikaci po síti, to vše založené na relativně komplikovaných datových strukturách a funkcích. Přítomnost grafického uživatelského rozhraní, případně komunikace po síti ve většině případů rovněž implikuje návrh s více paralelně pracujícími vlákny. Proto bylo vybíráno pouze z vysokoúrovňových, objektově orientovaných jazyků univerzálního charakteru, které jsou pro takovéto úkoly vhodné. Konkrétně byly vzaty v úvahu jazyky C++ a Java, které patří v současnosti k nejpoužívanějším. Následně bylo provedeno srovnání výhod a nevýhod, které použití těchto jazyků přináší. Předpokládá se, že výsledná aplikace nebude po většinu běhu výpočetně náročná. Výjimku budou zřejmě tvořit operace spojené s umělou inteligencí a prohledáváním stavového prostoru. Java, jakožto interpretovaný jazyk, často bývá nařčena z „pomalostiÿ a odsouvána do aplikační oblasti jednoduchých webových appletů. Toto nařčení, jak je uvedeno v [5], platilo v době, kdy Java vznikala, v polovině 90. let. Nyní je úroveň javovských překladačů taková, že ekvivalentní program v Javě se může provádět i pouhý 1,1 násobek doby, kdy je prováděn program napsaný v C++. Tedy obvykle uváděná komparativní výhoda jazyka C++ není tolik významná, rychlost výsledného kódu ovlivní nejvýraznějším způsobem um programátora. Dobrým důkazem srovnatelných výkonnostních možností obou jazyků je počítačová hra Jake2, port 3D hry Quake II firmy ID Soft do Javy – počet snímků za sekundu dosahovaných v benchmarcích na totožné počítačové sestavě je u obou aplikací přibližně stejný, jak je zachyceno [1]. Proti Javě mluví nutnost mít nainstalováno javovské runtime prostředí a větší paměťová náročnost spojená s během virtuálního stroje. Při současné velikosti disků a pamětí v běžných počítačových sestavách a rychlosti internetových přípojek je však tato komplikace malou cenu za relativně bezproblémovou platformní nezávislost, kterou Java na rozdíl od C++ nabízí. Ve srovnání s klasickým C++ jsou dynamicky vytvářené objekty předmětem garbage kolekce. Toto usnadňuje práci programátorovi, který se nemusí starat o dealokaci paměti, a navíc se tím zamezuje chybám, kdy zapomenutá dealokace může vést k neustálé spotřebě systémových prostředků a následnému pádu aplikace či celého systému. Menší nevýhodou automatické garbage kolekce je její jistá nepředvídatelnost, což však hraje podstatnou roli pouze u real-time systémů a aplikací. Další faktory již mluví ve prospěch Javy. Oproti C++ je v Javě snazší napsat bezpečnou aplikaci, nevyskytuje se v ní například problém s formátovacími řetězci. Bezpečnost zvyšuje i fakt, že se jedná o interpretovaný jazyk, virtuální stroj tak může provádět kontrolu indexace polí (je pravda, že to s sebou přináší jistou režii). Toto rovněž usnadňuje odlaďování kódu v Javě. Obecně je produktivita programování v Javě vyšší než v C++. Další výhodou Javy je dobrá a úplná dokumentace standardních balíků. Dokumentace, přístupná na stránkách [2], zahrnuje rovněž množství užitečných příkladů a návodů. V rámci této práce byla nejvíce využita dokumentace pokrývající třídy potřebné k tvorbě grafického uživatelského rozhraní.
20
KAPITOLA 3. ANALÝZA A NÁVRH ŘEŠENÍ
V neposlední řadě volbu ovlivnila hloubka autorových znalostí obou jazyků. Jako vývojové prostředí byly použity JBuilder X, JBuilder 2005 a NetBeans IDE 5.0 (práce byla vyvíjena na více strojích). Všechna prostředí nabízejí přibližně stejné možnosti, z nichž nejvíce využívanou je zřejmě kontextová nápověda, a je mezi nimi možné snadno přenášet projekty. Prostředí NetBeans velmi ulehčilo vývoj webové formy aplikace.
KAPITOLA 4. IMPLEMENTACE
21
4 Implementace Aplikace je implementována dle návrhového vzoru Model-View-Controller. View část je vystavěna na GUI frameworku Swing, jde o vyděděné třídy i přímo užité komponenty jako například JColorChooser či jednoduché dialogy. Funkcionalitu části Controller zajišťuje řada vnitřních tříd posluchačů, se jménem typicky zakončeným AL (např. CancelButtonAL). Část Model byla vytvořena prakticky od počátku, pouze s použitím základních tříd z balíků jako java.util či java.io. Větší pozornost bude při popisu věnována právě modelu herních dat, neboť část View a Controller je řešena standardně. Výsledný program se skládá z více než 40 tříd. Tato část práce je věnována popisu jednotlivých tříd, jejich struktuře, funkčnosti a vzájemným vztahům. Hierarchie tříd bude popisována zdola nahoru, nejdříve budou tedy popsány pomocné třídy a třídy popisující nejjednodušší datové a funkční prvky a až posléze komplexnější třídy využívající podřazených tříd.
4.1
Herní model
Obrázek 4.1 zachycuje základní kompozici herního modelu. Jedná se pouze o orientační schéma uvádějící vztah mezi instancemi základních tříd modelu s výčty obsahujícími vybrané důležitější metody a proměnné.
Obrázek 4.1: Základní kompozice herního modelu
Většina tříd herního modelu implementuje rozhraní Cloneable, čehož je využíváno při prohledávání stavového prostoru. Rozhraní Serializable bylo implementováno pro umožnění přenosu objektů po síti a jejich ukládání do souborů.
22 4.1.1
KAPITOLA 4. IMPLEMENTACE Třída Rank
Třída Rank, implementující rozhraní Serializable a Comparable, modeluje jednu ze dvou základních vlastností každé hrací karty, její označení v rámci jedné barvy, počínaje sedmou a konče esem. Podstatný je fakt, že označení karty neurčuje přímo její umístění v pořadí karet jedné barvy. Jak bylo uvedeno v pravidlech, pořadí při betlu a durchu je 7, 8, 9, X, J, Q, K, A zatímco při ostatních typech her je 7, 8, 9, J, Q, K, X, A. Umístění karty v rámci jedné barvy nazvěme hodnotou karty. Tyto hodnoty jsou definovány v poli value. Třída Rank se vždy nachází v jednom ze dvou stavů a každému ze stavů odpovídají jiné hodnoty v poli value. Stav třídy zachycuje třídní proměnná boolean classicOrder a stav se přepíná pomocí funkce setOrder(). Instance třídy Rank obsahuje pouze int ord, jehož hodnota je přímo spjata s označením karty, respektive odpovídá jedné z třídních konstant int SEVEN, EIGHT, NINE, TEN, UNDER, OVER, KING a ACE. Tuto hodnotu využívají instanční funkce jako getVal(), toString() či compareTo() jako index při přístupu k třídním polím. Pro pohodlné vyčítání instancí třídy Rank v aktuálním pořadí slouží třídní funkce elements(), která vrací instanci třídy Enumeration z java.util. Funkce toString() vrací textovou reprezentaci instance, řetězec „7ÿ, „8ÿ, „9ÿ, „Xÿ, atp. 4.1.2
Třída Suit
Třída Suit, implementující rozhraní Serializable, Comparable a Cloneable, modeluje barvu, jednu ze základních vlastností karty. Je obdobou třídy Rank, pouze s tím rozdílem, že je jednodušší, protože nebylo třeba zavádět nějaké rozlišitelné vnitřní stavy. Instance třídy Suit zastupují červenou, kule, zelenou a žaludy a tomu odpovídají třídní konstanty int RED, BELLS, GREEN a ACORNS. Textovou reprezentaci objektu (řetězec „1ÿ, „2ÿ, „3ÿ nebo „4ÿ) vrací standardní instanční funkce toString(). 4.1.3
Třída Double
Instance třídy Double (neplést s java.lang.Double), implementující rozhraní Serializable, slouží k uchovávání stupně flekování. Jednotlivé stupně flekování reflektují třídní konstanty NIC, FLEK, RE, atp. Nejpodstatnější instanční funkcí jsou getMultiplier(), která vrátí pro daný stupeň příslušnou mocninu dvou, a next(), jež vrací instanci představující následující stupeň flekování. 4.1.4
Třída Card
Instance třídy Card, implementující rozhraní Serializable, Comparable a Cloneable, jsou vnitřní reprezentací reálných objektů, karet. Každá instance této třídy zahrnuje instanci tříd Rank rank a instanci Suit suit, které slouží ke kompletní identifikaci karty. Třída Card v sobě zahrnuje i funkčnost související s grafickou reprezentací karet. Třídní metoda loadCustomImages() slouží k nahrání uživatelských obrázků karet ze souborů v adresáři images (při nahrávání je využit MediaTracker z java.awt) a uložení odpovídajících instancí třídy Image do hashovací mapy. Metoda loadDefaultImages() je obdobou předchozí funkce, slouží k nahrávání základních obrázků obsažených v balíku images, případně v adresáři s aplikací. Ze zmiňované hashovací mapy jsou obrázky vyzvedávány instanční funkcí getImage(). Klíčem do mapy je řetězec sestávající se ze dvou znaků – zřetězené textové reprezentace instancí třídy Suit a Rank. Např. klíčem pro srdcového krále je řetězec „1Kÿ. Stejný řetězec musí mít v názvu obrázky karet. Obrázek rubu karty je do mapy uložen pod klíčem „reverseÿ a vrací ho třídní
KAPITOLA 4. IMPLEMENTACE
23
funkce getReverseImage(). Třídní funkce getWidth() a getHeight() vrací šířku a výšku obrázků karet v pixelech. Instance třídy Card se porovnávají (a následně i řadí) na základě hodnoty, která je odvozená z rank a suit a je pro každou dvojici rozdílná. Tuto hodnotu vrací funkce getSortValue(). 4.1.5
Třída CardSet
Třída CardSet, implementující rozhraní Cloneable a Serializable, je modelem sady karet, kde lze rozlišit vzájemné pozice karet. Základem, nad kterým instance třídy CardSet fungují, je ArrayList cards obsahující instance třídy Card. U instancí třídy CardSet se rozlišuje vizuální počet karet, uchovávaný ve vnitřní proměnné int visualSize a přístupný přes funkci getVisualSize(), a skutečný počet instancí třídy Card v cards přístupný přes funkci size(). Vizuální počet karet, tedy počet, který bude zobrazen v podobě rubů karet, se od skutečného může lišit. Na instance třídy Card lze totiž nahlížet nejen jako na existující karty, ale i jako na pouhé možnosti. Vidím-li ve spoluhráčově ruce ruby karet, znám vizuální počet, avšak množina možných karet, kterou si lze za těmi ruby představit, může být mnohem větší. S výše zmíněným faktem souvisí vnitřní boolean isSubjective. Ten nemá vliv na funkčnost, slouží pouze k vnějšímu rozlišení, zda se jedná o pouhé možnosti či známou sadu karet. Třída CardSet poskytuje řadu instančních metod, které umožňují pohodlnou manipulaci se sadou. Jde většinou o krátké metody, jejichž činnost je víceméně zřejmá z názvů, vstupů a výstupů, například cut(), insertToBottom(), shuffle(), swapCards(), sort(), generateFullSet(), drawCard() či empty(). Rozdíl mezi funkcemi typu removeX() a deleteX() spočívá v tom, že u typu deleteX() se neupravuje vizuální velikost. Funkce deleteCard() tedy slouží k odstranění možnosti na základě úvah a funkce removeCard() k odebrání karty, například při jejím zahrání. Řada instančních funkcí, jako je getAceCount(), countOf() či getMarriageOvers(), pouze podává informace o sadě karet. Tyto funkce využívá především umělá inteligence. Funkce getWinningCard() určuje tzv. vítěznou kartu, používá se pro vyhodnocení zdvihu. Třída CardSet obsahuje konstanty int SHIFT, SMALL SHIFT a SELECTION SHIFT, které definují vhodné vzdálenosti při vykreslování jednotlivých karet v sadě. 4.1.6
Třída SuperCardSet
Třída SuperCardSet je datovým modelem sady karet, kde po každé pozici pro kartu můžeme požadovat více možných karet, tedy instanci třídy CardSet. Základem, nad kterým je SuperCardSet vystavěn, je pole instancí CardSet multiCard a index do tohoto pole int index. Pokud vkládáme do instance třídy SuperCardSet sadu karet, lze to provést v principu dvěma způsoby dle toho, zda ona sada představuje množinu možností či zda je skutečnou uspořádanou sadou. V prvním případě musíme na pozice v poli multiCard nakopírovat onu sadu tolikrát za sebou, kolik je její vizuální počet. V druhém případě dáváme na pozice v poli multiCard postupně po jedné kartě ze oné sady. Takto funguje instanční funkce insert(), příslušný způsob je zvolen na základě hodnoty vstupního argumentu boolean ordered. Vkládáme-li při sbírání karet po skončení hry talón, vkládáme ho jako množinu možností, protože většinou přesně nevíme, jaké dvě karty a v jakém pořadí obsahuje. Vkládáme-li karty z rukou při předčasně ukončené hře, opět je vkládáme jako neuspořádané množiny možností. Pouze uhrané zdvihy si můžeme dovolit ukládat jako uspořádané sady. Zřejmě nejpodstatnější funkcí a hlavním důvodem, proč byla tato struktura navržena, je instanční funkce getCutCount() pro podporu umělé inteligence. Je-li zavolána po korektním sebrání všech karet jak bylo popsáno v předchozím odstavci, pak tato funkce na základě karet, které hráč obdrží při rozdávání, a případně i talónu určí, kolik přesně karet bylo sejmuto před rozdáváním (pokud je to z dostupných dat možné přesně určit). Jak již bylo zmíněno v ana-
24
KAPITOLA 4. IMPLEMENTACE
lýze, na základě tohoto počtu a sledování průběhu hry lze simulovaným rozdáním ze struktury SuperCardSet významně omezit neúplnost informace a potenciálně tak zkvalitnit rozhodování umělé inteligence. Funkce getCutCount() funguje tak, že pomocí instanční funkce compareToHand(), respektive compareToHandAndTalon() hledá karty v ruce, respektive v ruce a v talónu na příslušných pozicích v poli multiCard pro všechny možné počty sejmutých karet. Ony příslušné pozice lze pro jednotlivé hráče snadno odvodit ze způsobu rozdávání. Vyhovuje takové sejmutí, kdy jsou všechny současné karty v ruce, respektive v ruce a v talónu nalezeny na pozicích v poli multiCard. Pokud je nalezen jediný vyhovující počet, pak byl sejmut přesně tento počet. Je-li nalezeno více řešení, nelze přesný počet určit. Funkce compareToHand() a compareToHandAndTalon() jsou založeny na funkci subsetContains(), která ověřuje, zda alespoň jedna z vymezených pozic pole multiCard obsahuje danou kartu. Třída SuperCardSet poskytuje i řadu dalších instančních metod jako empty(), cut(), drawMultiCard(), shuffle() či getVisualSize(). Jejich činnost je dostatečně patrná již z názvu. 4.1.7
Třída Modulo3
Pomocná třída poskytující jednoduché třídní funkce jako next(), nextNext(), previous() či plus() pro operace nad tělesem modulo 3. Tyto funkce slouží k jednoduchému přepočítávání indexů při indexaci polí odrážejících fakt, že se hry účastní tři hráči. Třída byla zavedena z toho důvodu, že je často třeba vybrat následujícího či předchozího hráče. 4.1.8
Třída ObjectQueue
Instance třídy ObjectQueue, jak již název napovídá, je frontou objektů. Fronta je implementována spojově, pomocí vnitřní třídy QueueElement. Třída nabízí synchronizované metody sInsert(), sExtract(), sIsEmpty() a další pro bezpečný přístup v případě sdílení instance mezi více vlákny. Tyto fronty jsou používány u klientů a udržují v sobě instance třídy GameAction, tedy zprávy, které si mezi sebou klienti prostřednictvím serveru posílají. 4.1.9
Třída Contract
Instance třídy Contract, implementující rozhraní Serializable, Comparable a Cloneable, představují jednotlivé typy her (sedma, lepší sedma, sto, atd.). Jsou součástí instancí třídy PlayData a GameAction. Třída je navržena podobně jako třídy Rank či Suit, tj. instanci náleží jediná instanční proměnná int ord. Hodnota této proměnné odpovídá jedné z třídních konstant jako SEVEN, BETTER SEVEN, HUNDRED, atp. a zároveň plní funkci indexu při přístupu do třídních polí. Do třídních polí se nepřistupuje přímo, ale přes instanční metody. Na základě int ord se provádí porovnání ve funkcích equals() a compareTo(). Instanční funkce slouží především k získání informací o daném typu hry, např. funkce isDoubleObligation() slouží ke zjištění, zda jde o typ hry se dvěma nezávislými závazky, funkce isPointContract() vrací true, pokud jde o typ hry, kde jsou podstatné body. Obdobné jsou funkce isTrumpContract(), isTwoSevenContract() či isBetter(). Funkce getContractMultiplier(), respektive getSevenMultiplier() vrací násobitel základu pro počítání hodnoty hry (hlavního závazku), respektive sedmy (vedlejšího závazku). Funkce acString() vrací akusativ označení typu hry. Instanční funkce getType() vrací hodnotu, jež je totožná vždy pro hru a její lepší variantu. S touto hodnotou pracují i instanční funkce equalType() a třídní funkce getByType().
KAPITOLA 4. IMPLEMENTACE
25
Pro snadné vygenerování instancí představující všechny existující typy her byla vytvořena třídní funkce elements(), jejíž návratovou hodnotou je Enumeration. 4.1.10
Třída GameState
Pomocná třída GameState definuje hodnoty int konstant označujících možné stavy hry: SERVER, CUTTING, DEALING, AUCTION, GAME SKIP, GETTING TALON a podobně. SERVER reprezentuje stav na počátku hry, kdy se čeká na server (na připojení zbylých hráčů). GAME SKIP označuje stav, kdy se čeká na rozhodnutí aktéra, zda se bude hra hrát, pokud mu nebyla v licitaci nabídnuta žádná hra, případně pokud vylicitoval pouhou sedmu. Význam ostatních je zřejmý z názvu. Těchto konstant využívá zejména třída GameData a GameAction. 4.1.11
Třída GameAction
Instance třídy GameAction, implementující rozhraní Serializable, představují akce hráčů během hry. Jsou to objekty uchovávající informace, dle kterých se mění vnitřní stav instancí třídy GameData, na které jsou aplikovány prostřednictvím procedury perform(). Jedná se o návrhový vzor Command. Instance třídy GameAction jsou v aplikaci použity na více místech. Jedná se o návratový typ funkce think(), kterou definuje rozhraní AI implementované třídami LameGoblin a TehPwnerer. Zároveň se jedná o zprávy, které si mezi sebou zasílají klienti se serverem. Instance třídy GameAction obsahují řadu instančních proměnných. Základními proměnnými jsou int playerID – číslo hráče, který je původcem akce (−2 v případě serveru), a int gameState – stav hry, pro který je akce určena. Proměnná gameState obsahuje jednu z hodnot definovaných třídou GameState a slouží ke kontrole synchronizace. Neshodují-li se gameState akce a gameState herních dat, došlo ke ztrátě synchronizace. Dalšími instančními proměnnými jsou int number, int actionID, CardSet cardSet, Card card, Contract contract, Suit suit a boolean bool. To, které z nich jsou relevantní, závisí na hodnotě proměnné gameState. Způsob implementace se tak blíží konstruktu union z jazyka C. V některých případech hraje roli i to, zda jde o zprávu od klienta či od serveru. Formát akcí zachycuje tabulka 4.1. Instanční funkce getSpeechText(), getDescription(), getMyDescription() a getHintText() vracejí různé textové reprezentace instance. Protože tyto textové reprezentace někdy zahrnují jména hráčů, které instance GameAction neobsahují, byla do třídy GameAction zavedena funkčnost překladu čísel hráčů na jména. Toto zajišťují třídní metody setNames() a getName(). 4.1.12
Třída DoubleData
Instance třídy DoubleData, implementující rozhraní Cloneable, představuje podautomat hry zajišťující průběh flekování a je důležitou součástí instance třídy GameData. Přestože se jedná o konečný automat, nebyl implementován klasickým způsobem, pomocí přechodové tabulky. Vnitřní stav automatu je určen několika instančními proměnnými. Jedná se především o čtveřici proměnných typu boolean gameDoubledByActor, gameDoubledByOpponent, sevenDoubledByActor a sevenDoubledByOpponent, která v sobě nese informaci o tom, zda byla bezprostředně před současným stavem flekována hra či sedma (sedmy) protistranou, a proměnnou int currentPlayer, jejíž hodnota určuje aktuálního hráče a v kombinaci s proměnnou int actorPlayer definuje i jeho protistranu. Na základě těchto hodnot určují instanční funkce gameAndSevenCanBeDoubled(), gameCanBeDoubled() a sevenCanBeDoubled(), jaké závazky může aktuální hráč flekovat. Tyto funkce tak v podstatě definují existující přechody automatu z aktuálního stavu.
26
KAPITOLA 4. IMPLEMENTACE Hodnota gameState SERVER DEALING AUCTION GAME SKIP GETTING TALON CONTRACT CHOICE TRUMP SUIT CHOICE PUSH SUIT CHOICE TALON CHOICE DOUBLING PLAYING GATHERING
Platné proměnné a jejich význam cardSet (rozdané karty) number (číslo hráče, pro kterého jsou karty určeny) actionID (licitační rozhodnutí, viz. AuctionData) contract (nabídnutý typ hry) number (0 – hraje se, 1 – nezájem o hru, 2 – omyl) number (číslo aktéra) cardSet (karty talónu) contract (zvolený typ hry) suit (zvolené trumfy) suit (zvolené strkací) cardSet (karty odložené do talónu) actionID (flekovací rozhodnutí, viz. DoubleData) card (vynesená karta) bool (indikace vynesení svrška do hlášky) bool (indikace prokládaného sbírání) number (číslo hráče, od kterého se začíná skládat)
Tabulka 4.1: Formát instancí třídy GameAction Vlastní přechody se uskutečňují prostřednictvím instančních funkcí doubleGameAndSeven(), doubleGame(), doubleSeven() a good(). Během přechodů se odpovídajícím způsobem upravují hodnoty výše zmíněných proměnných a dále proměnných currentPlayer a Double sevenDouble a gameDouble, uchovávajících aktuální stupeň flekování. Funkce good() může rovněž nastavit hodnotu proměnné int doubleFinished, která indikuje konec flekování. K základním funkcím automatu patří i getCurrentPlayer(), která vrací hodnotu již zmíněné vnitřní proměnné. Pro fungování automatu hraje roli i to, jaká typ hry byl zvolen, konkrétně zda jde o typ hry se dvěma závazky, či pouze s jedním. Toto reflektuje nastavení proměnné sevenDoubledByActor() v konstruktoru. V rámci třídy jsou definovány konstanty odpovídající flekovacím rozhodnutím (int GOOD, DOUBLE GAME, DOUBLE SEVEN a DOUBLE BOTH ), které jsou využívány v třídách GameAction a GameData. 4.1.13
Třída AuctionData
Instance třídy AuctionData, implementující rozhraní Cloneable, je podautomatem zajišťujícím licitaci v rámci automatu představovaným instancí třídy GameData. Na rozdíl od automatu pro flekování byl pro jeho implementaci vhodnější klasický přístup, pomocí tabulky přechodů, kterou představuje dvojrozměrné třídní pole nextState. Automat odpovídá obrázku 3.2 z analytické části až na jeden fakt – akce, které nejsou v daném stavu přípustné, například „rozdávající máÿ ve stavu 0 či jakákoli akce v koncových stavech, způsobují přechod do toho samého stavu (stav automatu se tedy nepřípustnou akcí nemění). Přechody automatu jsou vyvolávány instančními funkcemi yes(), no() a bid(). První z funkcí odpovídá přechodům označeným „máÿ, druhá přechodům „nemáÿ či „nenabízíÿ a poslední „nabízíÿ. Nejvyšší dosud nabídnutá hra je uchovávána v instanční proměnné highestBid, přístupné přes funkci getHighestBid(). Mezi další důležité instanční funkce, jež podávají informace o aktuálním stavu automatu, patří getCurrentPlayer(), auctionFinished(), yesNoState() a bid-
KAPITOLA 4. IMPLEMENTACE
27
NoState(). Poslední dvě jmenované funkce indikují možné přechody z aktuálního stavu. Třída AuctionData podporuje dvě výjimky, které mohou na konci licitace nastat – nahlášení omylu a nezájem o hru, nebyla-li žádná nabídnuta. Skutečnost, že může nastat jedna z těchto výjimek, indikuje instanční funkce skipPossible(). Pokud k výjimce došlo, jsou modifikovány instanční proměnné boolean error a notInterested, ke kterým se přistupuje přes odpovídající set() a get() metodu. K hodnotě těchto flagů se přihlíží při vyúčtování před skládáním karet. Obdobně jako u třídy DoubleData jsou zde definovány třídní konstanty int NO, YES a BID, které odpovídají rozhodnutím při licitaci. Tyto konstanty jsou využívány i v třídách GameAction a GameData.
4.1.14
Třída PlayerData
Třída PlayerData, implementující rozhraní Cloneable, má pomocnou funkci. Instance této třídy zapouzdřuje dvě instance třídy CardSet, představující karty, které má hráč na ruce, a uhrané zdvihy. Pole marriageOversIndex zachycuje pozice svršků z hlášek v uhraných zdvizích, což hraje roli při vykreslování (tito svršci se vykreslují lícem nahoru). Instance této třídy jsou součástí instance třídy PlayData.
4.1.15
Rozhraní Client
Rozhraní Client specifikuje metody, které musí mít klient bez ohledu na to, zda se jedná o lokálního počítačového či lidského hráče, případně hráče vzdáleného. Metody definované rozhraním lze rozdělit na dvě skupiny. První skupinu tvoří funkce, které podávají informaci o klientovi jako isHuman(), isAI(), isRemote(), getPlayerNumber(), getName() a getMessages(). Poslední jmenovaná funkce souvisí s funkčností, která se předpokládá u každého klienta, a to sice udržováním seznamu zpráv, které postihují průběh hry z jeho pohledu. Druhou skupinu tvoří metody související s hrou. Funkce decide() by měla vracet instanci třídy GameAction odpovídající rozhodnutí klienta. Metoda inputAction() bude sloužit k předání instance třídy GameAction od serveru klientovi. U klienta budou tyto instance řazeny do fronty. Metoda processActions() způsobí zpracování všech instancí třídy GameAction ve frontě a přidání odpovídajících zpráv do seznamu zpráv. Metoda play() způsobí zpracování předané instance třídy GameAction, je zamýšlena pro hraní vlastního rozhodnutí klienta. Na základě vstupních parametrů metody initGameData() bude inicializována klientova instance třídy GameData. Rozhraní Client implementují třídy Computer a Human.
4.1.16
Třída PlayerInfo
Instance třídy PlayerInfo, implementující rozhraní Serializable a Cloneable, v sobě drží informaci o jménech hráčů a stavu jejich účtů. S hodnotou proměnné double value představující stav účtu lze manipulovat pomocí funkcí add(), subtract() a set(). Pole těchto instancí je součástí instance třídy GameData.
4.1.17
Třída ImageLoader
Třída ImageLoader je jedinou třídou v balíku images. Jediná funkce, kterou tato třída obsahuje, je loadImage(), která slouží pro přístup k obrázkům karet, které balík images obsahuje.
28 4.1.18
KAPITOLA 4. IMPLEMENTACE Třída PlayData
Instance třídy PlayData, implementující rozhraní Cloneable, tvoří jádro každé instance třídy GameData, je automatem zajišťujícím vlastní hru. Instance této třídy má relativně mnoho proměnných. Ty lze rozdělit na dvě skupiny – samotná herní data a příznakové či informační proměnné. Mezi klíčová herní data patří CardSety trick a talon a pole typu PlayerData playerData, určující spolu s poli marriageOvers a talonTensIndex rozložení a způsob zobrazení karet na stole, integery trickNumber, currentPlayer či actorPlayer a proměnné jako Contract contract, Suit trumpSuit a pushSuit, které úplně popisují typ hry, jež se hraje. Příznaky typu boolean sevenWon, plainWon, hundredWon, apod. zachycují, zda aktér uhrál daný závazek. Důležitým příznakem je boolean isFinished, který indikuje konec vlastní hry. K informačním proměnným patří integery actorPoints, actorMarriagePoints a opponentPoints, které zachycují počet bodů obou stran hry. K velkému množství těchto proměnných se přistupuje prostřednictvím odpovídajících get() a set() funkcí. Základ funkčnost třídy PlayData zajišťují instanční metody playableCards(), cardIntoTrick() a evaluateTrick(). Funkce playableCards() vrací instanci třídy CardSet obsahující všechny karty, jež může aktuální hráč právě hrát. Funkce pracuje ve shodě s pravidly mariáše, tj. pokud nebyla vynesena žádná karta, může být až na výjimky představované trumfovou a strkací sedmou zahrána jakákoli karta, kterou má aktuální hráč na ruce. Byla-li již nějaká karta zahrána, musí se ctít barva a přebíjet. Nejprve se tedy prohledají karty aktuálního hráče, zda obsahují kartu vynesené barvy a vyšší hodnoty. Pokud takto vzniklý CardSet obsahuje nenulový počet karet, je vrácen z funkce. Je-li CardSet prázdný, prohledají se karty na ruce, zda obsahují kartu vynesené barvy. Je-li takto vzniklý CardSet neprázdný, je vrácen z funkce, atd. Funkce playableCards() využívá funkci removeSevens(), která odstraňuje z výsledného CardSetu trumfové a strkací sedmy v situacích, kdy je nelze hrát. Funkce cardIntoTrick() nezajišťuje pouhé vložení hrané karty do aktuálního zdvihu (případně do zahraných zdvihů), ale rovněž zařizuje úpravy subjektivních informací o rozložení karet, které ze zahrání karty vyplývají. Hraje-li hráč svrška do hlášky, vymaže se král stejné barvy z rukou ostatních hráčů. Je-li zahrána karta stejné barvy, jako má dosavadní vedoucí karta, ale nižší hodnoty, u daného hráče se vymažou všechny vyšší karty dané barvy, atd. Při zahrání svrška do hlášky se také odpovídajícím způsobem přičtou body straně, která ho zahrála. Metoda evaluateTrick() slouží k určení dalšího hráče, který je na řadě, poté, co byla zahrána karta. V případě, že byla zahrána třetí karta, se navíc karty zdvihu vloží vítězi do uhraných zdvihů, přičtou se body za uhrané ostré a často se také provede nastavení příznaků po uhrání či neuhrání jednotlivých závazků. Instance této třídy tvoří uzly při prohledávání stavového prostoru během vlastní hry ve třídě Searcher. 4.1.19
Třída GameData
Instance třídy GameData, implementující rozhraní Cloneable, představuje nejvyšší úroveň herních dat, hlavní automat reprezentující hru mariáš. Stav tohoto automatu indikuje proměnná int gameState, která nabývá jedné z hodnot definovaných v rámci třídy GameState. K identifikaci instance slouží int ownerID, tedy číslo hráče, jehož pohled na hru tato instance představuje (−1 v případě serveru), a Client owner, přímý odkaz na tohoto hráče (null v případě serveru). Ke klíčovým instančním proměnným patří podautomaty AuctionData auctionData, DoubleData doubleData a PlayData playData. Proměnná SuperCardSet completeSuperSet slouží k uchování rozložení karet v balíku v množinové podobě po jejich složení, na jejím základě simulují klienti rozdávání. Pole typu PlayerInfo
KAPITOLA 4. IMPLEMENTACE
29
playerInfo slouží k uchovávání informací o hráčích – jejich jmen a stavu účtů. Proměnné jako String bubbleText a int bubbleTextAuthor obsahují informace o posledním prohlášení učiněném během hry. Instance poskytuje řadu funkcí pro zjišťování informací o aktuálním stavu automatu, jako je například getCurrentPlayer(), či getCurrentActionPrompt(), která vrací řetězec s výzvou pro aktuálního hráče, případně getCurrentActionDescription(), která vrací řetězec s popisem aktuálně prováděné akce. Zároveň dává k dispozici metodu evaluationDialog(), představující textové vyhodnocení hry po jejím skončení. Rovněž byla implementována řada get() a set() funkcí pro přístup k proměnným. Nejvýznamnější metodou, kterou instance této třídy disponují, je perform(). Tato metoda na základě svého vstupního parametru typu GameAction provádí přechod automatu z jednoho stavu do jiného. Ke své činnosti využívá soukromé metody jako dealSet(), takeTalon(), chooseContract() či gather(). Zajímavé na některých z těchto metod je, že vykonávají rozdílný kód v závislosti na tom, kdo je vlastníkem instance třídy GameData, případně zda je vlastník aktérem. Například takeTalon() – u serveru se provede tato funkce tak, že talón se přesune z pozice na stole na ruku aktuálního hráče, u hráče, který je aktérem, se kromě přesunu dvou karet talónu do vlastní ruky provede vymazání možností těchto karet v rukou protihráčů, u ostatních hráčů se vloží všechny možnosti karet talónu do rukou aktéra (může jich být více než dvě), talón se vyprázdní a zvětší se vizuální počet aktérových karet o dvě. 4.1.20
Třída Options
Instance třídy Options, implementující rozhraní Serializable, slouží k uchovávání nastavení aplikace – jedná se o nastavení vzhledu, ladicích výpisů, parametrů umělé inteligence, počáteční hodnoty účtů a podobně. Aktuální instance třídy Options je přístupná přes třídní metodu getOptions(). Prostřednictvím třídních metod loadOptions() a saveOptions() lze nahrávat a ukládat serializovanou formu aktuální instance třídy Options – v obou případech se přistupuje k souboru options.mar. K automatickému nahrání instance třídy Options dochází při startu aplikace, k uložení při korektním ukončení aplikace. Toto neplatí pro applet, kde dochází k při inicializaci k nastavení defaultních hodnot. 4.1.21
Třída Computer
Instance třídy Computer, implementující rozhraní Client, představují počítačem řízené klienty běžící na lokálním stroji. Mezi nejdůležitější instanční proměnné patří AI ai, reference na instanci třídy TehPwnerer, GameData gameData, aktuální herní data příslušející počítačovému hráči, List messageList, udržující obsah herního žurnálu, a ObjectQueue actionQueue, kam jsou instancí třídy Server vkládány instance třídy GameAction, tedy herní rozhodnutí. Klíčová je funkce decide(), která vrací instanci třídy GameAction – výsledek volání funkce think() nad aktuálními herními daty. 4.1.22
Třída Human
Instance třídy Human, implementující rozhraní Client, představuje lidského hráče při lokální hře. Implementace je velmi obdobná jako u třídy Computer. Liší se zejména metodou decide(), která v tomto případě vrací vždy null. Vytváření instancí třídy GameAction, která odpovídají rozhodnutím lidského hráče, se děje v tělech posluchačů GUI.
30
KAPITOLA 4. IMPLEMENTACE
4.1.23
Třída Server
Instance třídy Server slouží k uchovávání objektivních herních dat v podobě instance třídy GameData a předávání instancí třídy GameAction mezi třemi klienty. Instanční metoda establishGame() slouží k inicializaci datových struktur potřebných pro hru jednoho lidského hráče proti dvěma počítačem nahrazeným. Klíčovou metodou je inputAction(), prostřednictvím které klienti předávají instanci třídy Server zprávy. Tato metoda způsobí jednak změnu objektivních herních dat metodou perform() a zároveň předání odpovídajících instancí zbylým dvěma klientům metodou service().
4.2 4.2.1
Umělá inteligence Rozhraní AI
Rozhraní AI (zkráceno z artificial intelligence) specifikuje povinné funkce modulů umělé inteligence. Nejpodstatnější funkcí je think() s instancí třídy GameData jako vstupním parametrem a s instancí GameAction jako výstupem. Základním požadavkem na modul umělé inteligence tedy je, aby na základě herních dat vygeneroval optimální akci, rozhodnutí. Rozhraní AI implementují třídy LameGoblin a TehPwnerer. 4.2.2
Třída Composition
Instance třídy Composition poskytuje informace o složení dané instance třídy CardSet (typicky představující karty na ruce). Složením je míněn zejména počet a síla karet jednotlivých barev. V konstruktoru jsou na základě vstupního parametru naplněna tři instanční pole – count (počet karet jednotlivých barev), strength (síla barev pro hru s trumfy) a noTrumpStrength (síla barev pro hru bez trumfů). Jako základ síly se bere výsledek funkce getVal() volané nad instancí Rank třídy Card. Pro případ hry s trumfy je hodnota ostrých zvýšena. Tuto třídu využívají podpůrné třídy pro umělou inteligenci jako Searcher a InitChanceSet. 4.2.3
Třída InitChanceSet
Instance třídy InitChanceSet, volně přeloženo jako „sada počátečních šancíÿ, v sobě zahrnuje výsledky výpočtů k určení pravděpodobností uhrání všech jednotlivých typů her pro daného hráče. U každého typu hry s trumfy se uvažují všechny možnosti volby trumfů, případně i strkacích. Základem pro pozdější výpočet pravděpodobností uhrání jednotlivých typů her je sada heuristických funkcí initPlainChance(), initSevenChance(), initHundredChance(), apod., které odhadují pravděpodobnost uhrání jednotlivých závazků. Pravděpodobnost uhrání hry je v případě hry se dvěma závazky definována jako pravděpodobnost současného uhrání obou závazků (je tedy dána součinem pravděpodobností uhrání závazků). Heuristické funkce využívají další pomocné funkce jako holeCount(), lowestCard() a highestCard(), maxPlainPoints(), apod. Od sady heuristických funkcí použitých v třídě Searcher se liší jen minimálně – funkce přítomné v třídě InitChanceSet umožňují počítat pravděpodobnost uhrání závazku pro libovolného hráče, nikoli pouze pro hráče, který je právě na řadě, rovněž nezohledňují zda má hráč kontrolu nad hrou, poněvadž toto má smysl až při vlastní hře, ne v jejích raných fázích. Nejpodstatnější část výpočtů probíhá již v rámci konstruktoru instance, kdy jsou výsledky heuristických funkcí naplňovány soukromé datové struktury jako například položky durchChance a betlChance, jednorozměrná pole sevenChance, plainChance, či dvojrozměrné pole twoSevenChance. Tuto funkčnost zajišťuje metoda computeObligationChances(). Následně je volána jedna z metod setBestContract(), která provede iteraci přes všechny relevantní typy her a do vnitřních proměnných Contract bestContract, Suit bestTrumpSuit, Suit bestPushSuit
KAPITOLA 4. IMPLEMENTACE
31
a double bestChance zaznamená parametry hry, jejíž finanční ohodnocení je nejvyšší a zároveň je pravděpodobnost jejího uhrání větší než vstupní parametr double acceptableChance. Není-li žádná z pravděpodobností dostatečně vysoká, je vybrána nejnižší možná hra (nejmenší finanční ohodnocení) a případné trumfy či strkací jsou vybrány tak, aby byla pravděpodobnost co nejvyšší. Parametry zvolené hry jsou přístupné přes odpovídající get() funkce. Zajímavý je způsob, jakým jsou do výpočtů zahrnuty karty talónu. Pokud je hodnota vstupní proměnné konstruktoru boolean considerTalon true a obě karty talónu jsou známé, simulují se všechna odložení dvojic karet z onoho tuctu v hráčově ruce. Až z takto získaných rozloh karet se provádějí ohodnocení heuristickými funkcemi a výběr nejvyšší přijatelné hry. Karty, jejichž odložení do talónu vedlo k výběru nejvyšší hry, jsou poté zaznamenány v CardSetu talonCards, který je přístupný pomocí funkce getTalonCards(). Dalšími podstatnými funkcemi, kterou instance třídy InitCardSet mají, jsou getChanceFor(), getSevenChanceFor() a getContractChanceFor(). Jsou to dotazovací funkce, které na základě vnitřních proměnných vracejí pravděpodobnost uhrání jakéhokoli typu hry, respektive pravděpodobnost uhrání závazků daného typu hry. Na základě třídy InitChanceSet a třídy Searcher funguje třída pro umělou inteligenci TehPwnerer. Třídu InitChanceSet využívá pro rozhodování hned v několika fázích hry – při snímání, licitaci, při výběru typu hry, trumfů a strkacích, při výběru karet odkládaných do talónu a dokonce i při flekování. Toto bude rozvedeno přímo v popisu třídy TehPwnerer. 4.2.4
Třída Searcher
Instance třídy Searcher poskytuje funkčnost prohledávání stavového prostoru během vlastní hry a je jednou z klíčových součástí instancí třídy TehPwnerer. Algoritmem prohledávání je mírně upravený minimax s alfa-beta prořezáváním, jako podklad pro implementaci posloužil text [4]. Při minimaxu se rekurzívně prohledává stromová struktura vytvořená z instancí třídy PlayData, které obsahují všechna potřebná data k vlastní hře a k určení finančního dopadu na konta hráčů po skončení hry. MAX úrovně zachycují rozhodnutí aktéra, MIN úrovně rozhodnutí obou protihráčů. Pomocí metod setTimeLimit(), setNodeLimit() a setDepthLimit() lze definovat omezení pro prohledávání. Prohledávání se spouští instanční funkcí search(), která využívá soukromou rekurzívní funkci minimax(). Pro správné fungování algoritmu je důležitá instanční funkce heuristic(). Tato funkce vrací odhad částky, kterou obdrží aktér od každého z protihráčů při fázi vyučtování, tedy určitou nezápornou hodnotu. Pokud je zavolána na instanci třídy PlayData poté, co vlastní hra skončila, vrací pochopitelně nikoli odhad, ale přesnou částku. Funkce byla navržena tak, aby byla volána vždy po dohrání zdvihu, nikoli v jeho průběhu (poté nevrací korektní hodnoty). Funkce heuristic() využívá sadu pomocných heuristických funkcí jako plainChance(), sevenChance(), twoSevenChance(), apod., které vracejí přibližnou pravděpodobnost toho, že se aktérovi podaří uhrát jeden konkrétní závazek aktuálního typu hry. Určování těchto pravděpodobností vychází z expertních pravidel, např. pravděpodobnost uhrání prosté hry ovlivňuje ve funkci plainChance() následující: • aktér nemá kontrolu nad hrou (nevynáší první kartu), • vážený součet ostrých v rukou protihráčů a zdvizích protihráčů je větší než vážený součet ostrých v rukou aktéra a zdvizích aktéra (větší váhu mají ostré ve zdvizích), • protihráči mají v rukou více trumfů než aktér. Takto získaná pravděpodobnost se ve funkci heuristic() vynásobí hodnotou závazku. Jde-li o hru se dvěma závazky, počítají se dvě pravděpodobnosti, násobí se relevantními hodnotami závazků a poté se sečtou ve finální odhad.
32
KAPITOLA 4. IMPLEMENTACE
Funkce betlChance() a durchChance() navíc používají funkci usableHoleCount(), jež vrací počet zneužitelných děr v jedné barvě aktérových karet. Za zneužitelné jsou považovány takové, kdy jeden z protihráčů má onu chybějící kartu na ruce. Dalšími pomocnými funkcemi jsou containsLower(), resp. containsHigher(), indikující zda daná sada karet obsahuje nižší, resp. vyšší kartu dané barvy, a lowestCard(), resp. highestCard(), které vrací nejnižší, resp. nejvyšší kartu dané barvy ze sady. 4.2.5
Třída LameGoblin
Instance třídy LameGoblin, implementující rozhraní AI, je nejjednodušším modulem umělé inteligence. Rozhodnutí, která instance této třídy vrací funkcí think(), jsou pouze v souladu s pravidly. Není za nimi žádné prohledávání stavového prostoru ani heuristika, ale náhodný generátor. Tato třída byla navžena jako pouhý vzor pro složitější moduly, jako je například třída TehPwnerer. 4.2.6
Třída TehPwnerer
Instance třídy TehPwnerer, implementující rozhraní AI, je nejpokročilejší obsažený modul umělé inteligence. Nejpodstatnější funkcí je samotná funkce think(), která ve formě instance třídy GameAction vrací optimální rozhodnutí pro hráče, který je na řadě. Následuje podrobnější popis rozhodování pro jednotlivé fáze hry: • Snímání Počet karet, o který se má hráč pokusit při sejmutí, vrací instanční funkce idealCut(). Funkce idealCut() funguje tak, že nejprve simuluje všechna sejmutí balíčku, pro každé sejmutí následně provádí rozdání, pomocí instance třídy InitChanceSet zjistí nejvyšší přijatelnou hru pro aktuálního hráče a uloží číslo indikující její výšku do pole maxChance. Protože hráč nemůže sejmout přesný počet karet, využije se tohoto pole pro výpočet pole maxAverage tím způsobem, že obsah položky pole maxAverage bude určen jako průměr až pěti sousedních položek pole maxChance. Index položky pole maxAverage obsahující nejvyšší hodnotu odpovídá počtu karet, o který by se měl hráč při snímání pokusit, neboť vede na průměrně (v určitém intervalu počtu sejmutých karet) nejvyšší uhratelný typ hry. • Licitace Pomocí nově vytvořené instance třídy InitChanceSet je zjištěna nejvyšší hra, do které by měl aktuální hráč licitovat. Je-li tedy ve stavu, kdy má nabízet hru, nabídne onu vybranou hru pouze tehdy, je-li vyšší než poslední nabídnutá hra. Pokud je ve stavu, kdy se od něj čeká odpověď, zda má či nemá, porovná jemu nabídnutou hru s onou vybranou hrou. Je-li vybraná hra nižší než nabídnutá, odpoví, že nemá, v ostatních případech, že má. • Volba typu hry Opět se pomocí instance třídy InitChanceSet zjistí typ nejvyšší přijatelné hry. Pokud je zjištěný typ nižší než vylicitovaný, zvolí se vylicitovaný, jinak se dá přednost typu zjištěnému pomocí InitChanceSet. • Volba trumfů Barva trumfů je vybrána na základě dotazu na instanci třídy InitChanceSet, která vypočetla pravděpodobnosti uhrání již zvoleného typu hry pro všechny možné trumfy a popřípadě i strkací.
KAPITOLA 4. IMPLEMENTACE
33
• Volba strkacích Volba barvy strkacích se řeší obdobně jako volba barvy trumfů, tedy dotazem na instanci třídy InitChanceSet, která propočítala pravděpodobnosti uhrání zvoleného typu hry s danými trumfy pro všechny možné strkací. • Výběr karet do talónu Výběr karet do talónu se řeší pomocí instance třídy InitChanceSet. Instance v rámci konstruktoru nalezne ty karty, jejichž odložení vede k největší pravděpodobnosti uhrání již plně zvolené hry. Karty se poté vyzvedávají pomocí funkce getTalonCards(). • Flekování Flekování je řešeno opět pomocí instance třídy InitChanceSet. Rozdílem oproti předchozímu použití je ta skutečnost, že nyní se sada počátečních šancí počítá pro aktéra, nikoli pro aktuálního hráče. Následně je vyzvednuta pravděpodobnost uhrání závazku (závazků). Velikost rozdílu této pravděpodobnosti od zadané hodnoty double acceptableChance určuje, kam až je rozumné při flekování zajít, případně má-li aktuální hráč vůbec flekovat. Je-li aktuální hráč aktér, podmínkou pro flekování je, aby byla zjištěná pravděpodobnost větší než hodnota acceptableChance. Je-li na řadě jeden z protihráčů, podmínka pro flekování je opačná. Pokud je splněna podmínka pro flekování, stupeň flekování se určí vydělením zmíněného rozdílu hodnotou double doubleStep. Pokud je dosavadní stupeň flekování závazku nižší a je možnost závazek flekovat, bude se flekovat. • Vlastní hra Rozhodnutí, kterou kartu je právě vhodné hrát, jsou činěna pomocí instance třídy Searcher. Funkce search vrací vhodnou kartu po provedení prohledání stavového prostoru. Pokud je zvolená karta svršek a je možné ho hrát do hlášky, je tak hrán. • Volba způsobu sbírání karet Volba způsobu sbírání karet je ponechána na náhodném generátoru. Tuto třídu využívají počítačem nahrazení klienti (třída Computer ) a rovněž je pomocí ní řešena nápověda.
4.3
Grafické uživatelské rozhraní
Většina komponent GUI si uchovává referenci na aktuální instanci třídy GameData. K předání reference komponentě slouží metoda setScreenData(), která zpravidla předává referenci i do relevantních podkomponent. Obdobně si většina komponent uchovává referenci na instanci třídy MainPanel, předávanou metodou setMainPanel(). Metoda refresh() slouží k aktualizaci vnitřních proměnných komponent dle hodnot v instanci třídy GameData. Hierarchie tříd je v tomto případě popisována shora dolů. 4.3.1
Třída MainApplication
Třída MainApplication je společně s MainServer a MainApplet nejvyšší třídou v hierarchii. V metodě main() zajišťuje vytvoření instance třídy JFrame, do které následně vloží vzájemně provázané instance třídy MainPanel a MainMenu. 4.3.2
Třída MainMenu
Instance třídy MainMenu, vyděděné z třídy JMenuBar, tvoří roletové menu aplikace. Instanční metoda setItemEnabled() slouží k selektivnímu vypínání a zapínání položek v menu (těm odpovídají definované třídní konstanty). V rámci samotné instance ani příslušných posluchačů
34
KAPITOLA 4. IMPLEMENTACE
není přímo prováděn výkonný kód, který by manipuloval s daty – je volána příslušná metoda instance třídy MainPanel. 4.3.3
Třída MainPanel
Instance třídy MainPanel, vyděděné z třídy JPanel, tvoří hlavní kontejner pro komponenty grafického rozhraní aplikace. Lze ho vložit jak do instance JApplet, tak do instance JFrame. Mezi nejdůležitější instanční metody patří metody pro obsluhu uživatelských voleb z hlavního menu, např. menuGameNew(), menuGameJoin(), menuOptionsRestore() či menuHelpHint(). Tato třída obsahuje vnitřní třídy ClientThread, RMIClientThread a SimulatorThread, které zajišťují chod jednotlivých módů hry. Vlákna byla zavedena do aplikace z toho důvodu, že framework Swing je jednovláknový a příliš dlouhá obslužná metoda (kterou může být například rozhodování počítačového hráče) způsobuje zamrznutí GUI. Podstatnou instanční metodou je stopAllThreads(), která nastaví nastaví příznak u běžícího vlákna obsluhujícího hru a to poté skončí. Rovněž nastaví pomocí metody setScreenData() na null referenci na herní data v GUI komponentách. Touto metodou se zastavuje běžící hra před spuštěním nové. Metody initNetworkClient(), joinNetworkGame() a startGameOverNet() slouží pro inicializaci hry po síti. 4.3.4
Třída SidePanel
Instance třídy SidePanel, vyděděné z třídy JPanel, představuje panel na pravé straně od hrací plochy (instance třídy GameScreen). Je pouhým kontejnerem pro instance tříd ScrollLog, GameInfoPanel, ActualInfoPanel a DialogPanel. 4.3.5
Třídy ScrollLog, GameInfoPanel, ActualInfoPanel, DialogPanel
Instance těchto tříd jsou komponenty vkládané do instance třídy SidePanel. Instance třídy ScrollLog, vyděděné z třídy JScrollPane, představuje herní žurnál. Instance třídy GameInfoPanel a ActualInfoPanel jsou panely podávající informace o hře. Instance třídy DialogPanel je panelem, který nahrazuje dialogová okna – jeho obsah a nadpis se dynamicky mění dle aktuálního stavu hry, tuto obměnu zajišťuje instanční metoda refresh(). Má-li hráč učinit nějaké rozhodnutí, instance třídy DialogPanel obsahuje instanci jedné z tříd vyděděných z třídy DialogPanelCore, např. ContractChoicePanel, GatheringPanel, CutPanel, DoublePanel a podobně. Společnou vlastností těchto instancí (respektive posluchačů komponent těchto instancí) je, že na základě uživatelovi akce vygenerují jedinou korektní instanci třídy GameAction, předají ji ke zpracování instanci třídy MainPanel a zároveň se deaktivují prostřednictvím metody setEnabled() (což odpovídá zmizení dialogového okna). 4.3.6
Třída ColorBox
Instance třídy ColorBox, vyděděné z třídy JPanel, jsou jednoduché komponenty pro zobrazení barvy. Použito v třídě GraphicsDialog. 4.3.7
Třída GameScreen
Instance třídy GameScreen, vyděděné z třídy JPanel, představuje double buffered herní plochu, kam jsou vykreslovány karty. Reference na herní data se předává instanci prostřednictvím metody setScreenData(). Nejdůležitější instanční metodou je zřejmě renderScreen(), která je nejvyšší v hierarchii kreslicích metod a zajišťuje volání drawGame(), drawSimulation() (pro vykreslování obrazovek specifických pro simulaci) či vykreslení prázdné obrazovky s nápisem
KAPITOLA 4. IMPLEMENTACE
35
„Mariášÿ dle stavu aktuálních herních dat. Nižší kreslicí metody jsou například drawSet(), drawCard(), drawReverse() či drawTextBubble(). Instance třídy GameScreen obsahuje mimo jiné instanci třídy CardSet obsahující vybrané karty. Tyto karty jsou vykreslovány posunuté nahoru. Pro určování, na kterou kartu bylo kliknuto, jsou klíčové funkce getCardAt() a getSimulationCardAt(). Ty postupně testují všechny karty, zda nebylo kliknuto do oblasti, kterou zabírají, a vrací první vyhovující kartu. Funkce select() zařazuje kartu do označených, nebylo-li jich označeno dost, a vrací true, pokud je označených karet specifikovaný počet a byla podruhé označena některá z vybraných karet. Na těchto funkcích je vystavěna metoda mouseDecide(), která na základě klikání do herní plochy označuje, odznačuje karty a generuje instance třídy GameAction. Funkce mouseDecide() je využita v třídě GameScreenMA, vyděděné z třídy MouseAdapter. 4.3.8
Třídy GraphicsDialog, AIDialog, PlayersDialog
Každá z těchto tříd, vyděděných z třídy JDialog, představuje jeden z dialogů, které jsou vyvolávány přes hlavní menu aplikace v sadě Nastavení. Je-li aplikace spuštěna jako applet, jsou omezeny možnosti u třídy GraphicsDialog – není možné nastavit uživatelskou sadu karet (není zobrazena instance třídy JPanel cardPanel ). V konstruktoru je dialogům předávána reference na instanci třídy MainPanel, pomocí které poté vyvolávají překreslení, případně přistupují k aktuálním datům (instanci třídy GameData), jako jsou jména například hráčů a hodnota účtů. Většinu dat ovšem dialogy získávají z aktuální instance třídy Options. Změny se do instance třídy Options zanášejí pouze v případě, je-li vyvolána akce nad instancí třídy Jbutton confirmButton, čemuž odpovídá třída ConfirmButtonAL.
4.4
Webová forma aplikace
Webovou formu aplikace představuje servlet a applet pro klientskou stranu a samostatně spustitelný server. Implementace hry po síti je založena na technologii RMI. Toto rozšíření aplikace zajišťují zejména třídy v balíku webmarriage. 4.4.1
Rozhraní ServerArrayRemote, ServerRemote, ClientRemote
Jedná se o rozhraní vyděděná z rozhraní Remote. ServerArrayRemote je implementováno třídou ServerArray, ServerRemote třídou RMIServer a ClientRemote třídou RMIHuman. 4.4.2
Třída RMIHuman
Třída RMIHuman je obdobou třídy Human určenou pro hru po síti. Nejdůležitější metody, které nabízí směrem k serveru, jsou initGameData() používaná při zahajování hry, inputAction() pro zasílání instancí třídy GameAction a informPlayerDisconnected() pro informování o odpojení jiného klienta během hry. 4.4.3
Třída RemoteHumanAdapter
Tato třída slouží k přizpůsobení instance třídy RMIHuman na rozhraní Client. 4.4.4
Třída ServerInfo
Instance třídy ServerInfo, implementující rozhraní Serializable, obsahují informace o instanci RMIServer. Informace zahrnují počet připojených hráčů, jejich jména, číslo stavu hry a řetězec
36
KAPITOLA 4. IMPLEMENTACE
obsahující adresu a jméno, pod kterým je instance třídy RMIServer registrována ve jmenné službě. 4.4.5
Třída RMIServer
Třída RMIServer je obdobou třídy Server určenou pro hru po síti. Nejdůležitější metody, které nabízejí směrem ke klientům, jsou join() pro připojení se a inputAction() pro zasílání instancí třídy GameAction. 4.4.6
Třída ServerArray
Instance třídy ServerArray, vyděděné z třídy UnicastRemoteObject a implementující rozhraní ServerArrayRemote, sdružuje několik instancí třídy RMIServer a poskytuje o nich informace prostřednictvím metody getServerInfos(). Tuto metodu využívá instance třídy MarriageServlet. Metoda init() přihlašuje instanci třídy ServerArray a jednotlivé instance třídy RMIServer do jmenné služby a tím de facto startuje serverovou část aplikace. Vnitřní třída ServerTask, vyděděná z třídy TimerTask, slouží k časovanému spouštění zahájení hry metodou establishGame() a kontrol dostupnosti jednotlivých klientů metodou aliveTest(). Odpojil-li se některý z hráčů během hry, spouští se metoda disconnectClients() – klienti jsou o odpojení informováni a následně je hra ukončena. 4.4.7
Třída MainServer
Třída MainServer slouží ke spuštění instance třídy ServerArray z příkazové řádky, na základě zadaných parametrů. Před spuštěním webové formy aplikace je obvyklé spustit serverovou část aplikace tímto způsobem. 4.4.8
Třída MarriageServlet
Instance třídy MarriageServlet, vyděděné z třídy HttpServlet, zajišťuje webovou formu aplikace – generování webových stránek pro připojení do síťové hry. Jedná se zejména o stránku s přehledovou tabulku, vytvářenou na základě dat získaných metodou getServerInfos(). Na této stránce hráč nalezne odkazy pro vyvolání stránky s appletovou formou aplikace, která se dle nastavených parametrů automaticky připojí k dané instanci třídy RMIServer. 4.4.9
Třída MainApplet
Třída MainApplet, vyděděná z třídy JApplet, je obdobou třídy MainApplication. Slouží ke spuštění aplikace ve formě appletu. Pro applet je specifické, že nenačítá instanci třídy Options ze souboru, ale vždy použije základní nastavení. Rovněž některé možnosti v menu jsou omezeny. V instanční metodě init() je načítána řada parametrů, které slouží zejména pro automatické připojení k síťové hře v závěru inicializace appletu.
KAPITOLA 5. TESTOVÁNÍ
37
5 Testování V průběhu implementace bylo prováděno testování za účelem odstranění chyb v programu. K testování sloužily mimo jiné ladicí výpisy na systémovou konzoli, které se povolují v třídě Options – nastavením pole booleovských hodnot debug. Větší důraz na testování byl kladen u složitějších funkcí a tříd, zejména na korektní fungování automatů tvořících herní model. U herního modelu bylo také ověřováno, zda správně fungují metody pro odstraňování neúplnosti informace. Bylo otestováno grafické uživatelské rozhraní. Bylo vyzkoušeno, zda všechny komponenty (tlačítka, menu, dialogy, herní plocha) fungují tak, jak bylo zamýšleno. Několika hrami byly vyzkoušeny schopnosti umělé inteligence. Bylo potvrzeno, že se počítačem nahrazení hráči drží pravidel a jejich reakční doba odpovídá nastavenému limitu. Testování přineslo zjištění, že při současném nastavení heuristik se počítačoví hráči drží při licitaci poměrně nízko a často preferují durch. Je zřejmé, že heuristiky by potřebovaly vyladit.
38
KAPITOLA 6. UŽIVATELSKÁ PŘÍRUČKA
6 Uživatelská příručka Tato kapitola je věnována především popisu základní formy aplikace z hlediska uživatele. Nepokrývá plně webovou formu aplikace a hru po síti, která se ovšem s výjimkou připojení se do hry neliší od základní formy aplikace.
6.1
Spuštění aplikace
AppMarriageS.jar je spustitelný digitálně podepsaný archiv. Krom JRE 1.5 není třeba nic instalovat či nastavovat. Chcete-li spustit server, je možné tak učinit později ze samotné aplikace či spuštěním MarriageServer.jar.
6.2
Herní obrazovka
Herní obrazovka je rozdělena na tři části, jak si lze všimnout na obrázku 6.1. V horní části se nachází roletové menu, které zpřístupňuje uživateli základní volby a nastavení. Pravou pětinu obrazovky zabírá svislý informační panel, který zobrazuje informace o stavu a průběhu hry. Zbylá část obrazovky představuje samotnou hrací plochu, abstrakci stolu nahlíženého shora.
Obrázek 6.1: Herní obrazovka
6.3
Informační panel
Informační panel na pravé straně obrazovky poskytuje uživateli informaci o aktuálně hrané hře. Panel obsahuje tři části. V horní části panelu je Info o hře. Zde se zobrazuje typ hry i s případnými trumfy a strkacími, dále hodnota fleků na jednotlivé závazky, nejvyšší nabídnutá úroveň hry při licitaci a také, který hráč je aktérem aktuální hry, kdo hru rozdával, kdo je forhontem a zejména kdo je právě na řadě. Druhou komponentou je herní žurnál, kam se zaznamenávají kromě jiného rozhodnutí hráčů během hry.
KAPITOLA 6. UŽIVATELSKÁ PŘÍRUČKA
39
Třetí komponentou je tzv. dialogový panel, pomocí kterého uživatel vykonává rozhodnutí, které nelze učinit klikáním do herní plochy, například rozhodnutí při licitaci, snímání, flekování a podobně. Podobu dialogového panelu při flekování zachycuje obrázek 6.2.
Obrázek 6.2: Dialogový panel při flekování
V dolní části panelu je zobrazena informace o aktuálním stavu hry, případně výzva směrem k uživateli, pokud je právě na řadě.
6.4
Roletové menu
Prostřednictvím roletového menu uživatel přistupuje ke třem sadám voleb pojmenovaným Hra, Nastavení a Nápověda. Volby v sadě Hra slouží ke spouštění her v různých módech a k ukončení aplikace. Volby zachycuje obrázek 6.3.
Obrázek 6.3: Volby v sadě Hra
Nová hra spouští klasickou hru jednoho hráče proti dvěma počítačem nahrazeným protivníkům. Volba Založit hru slouží k založení hry po síti. Uživatel je pouze dotázán na číslo portu, který má server obsadit, následně se automaticky spustí server pro obsluhu jediné hry a uživatel je k němu připojen. Poté musí počkat, až se k založené hře připojí zbylí dva hráči. Ti tak mohou učinit prostřednictvím volby Připojit se, kdy jsou dotázáni na IP adresu (formát x.x.x.x) či jméno počítače, kde byl spuštěn server, a číslo portu, na kterém byl server běží. Volba „Spustit serverÿ slouží k samostatnému spuštění serveru, který může v tomto případě obsluhovat více stolů najednou. Uživatel je dotázán na číslo portu, na kterém má server běžet, a počet stolů, které má server zajišťovat. Upozornění – spuštěný server obsadí port, který udá uživatel, plus tolik následujících portů, kolik her má server zajišťovat. Volba Simulace spouští speciální herní mód, který slouží zejména k porovnání úrovně umělé inteligence v této aplikaci s jinými aplikacemi pro hraní mariáše. Běžný uživatel ho může použít pro získávání rad při hraní mariáše, ať už jakožto jiné aplikace či naživo. Důležitou podmínkou pro možnost korektního použití tohoto módu jsou totožná pravidla hry. Zvláštnosti tohoto módu, kterými se liší od klasické hry jednoho hráče, jsou popsány v samostatném oddílu.
40
KAPITOLA 6. UŽIVATELSKÁ PŘÍRUČKA
Volba Konec slouží k ukončení aplikace. Pod slovem Nastavení se skrývají volby Hráči a účty, Vzhled, Obtížnost a Nastavit výchozí. Volby zachycuje obrázek 6.4. První tři slouží k nastavování různých aspektů aplikace. Každá z voleb vyvolá odpovídající dialogové okno. Změny v nastaveních hry nastanou pouze tehdy, pokud je uživatel potvrdí kliknutím na tlačítko Potvrdit. Volba Nastavit výchozí slouží k obnovení základního nastavení aplikace.
Obrázek 6.4: Volby v sadě Nastavení
Volba Hráči a účty zobrazí informace o jednotlivých hráčích, tj. jejich jména, typ a stav účtů. Typem se rozumí „člověkÿ (uživatel), „počítačÿ (počítačem nahrazený hráč) či „vzdálenýÿ (hráč připojený přes síť). Při lokální hře hře lze měnit jména hráčů, ať už lidských či počítačových. Rovněž lze u těchto hráčů volit počáteční stav účtu na začátku hry a tlačítkem Resetovat účty resetovat aktuální stav na uvedenou počáteční hodnotu. Dialog je zachycen na obrázku 6.5.
Obrázek 6.5: Volba Hráči a účty
Volba Vzhled zobrazí dialogové okno, kde může uživatel měnit grafická nastavení hry a přizpůsobit si tak do určité míry vzhled aplikace. Dialog je zachycen na obrázku 6.6. V panelu Barvy lze nastavit barvu pozadí herní plochy a barvu textů na herní ploše. Panel Obrázky karet dovoluje použít ve hře uživatelem definované obrázky karet. Obrázky musejí splňovat určité požadavky co do pojmenování, umístění, formátu a rozlišení. Podrobnější informace se zobrazí po stisku tlačítka Nápověda. To, zda obrázky vyhovují požadavkům, lze ověřit tlačítkem Ověřit obrázky. Volba Obtížnost vyvolá dialogové okno, kde může uživatel upravovat obtížnost hry a úroveň nápovědy. Dialog je zachycen na obrázku 6.7. Při lehké obtížnosti má počítačový hráč na přemýšlení přibližně 2 sekundy, při střední 10 sekund a při těžké 30 sekund. Ta samá úměra platí i u nápovědy. Pokročilí uživatelé si mohou zadat parametry prohledávání sami, stačí zatrhnout nastavit parametry ručně. Mezi nastavitelné parametry patří maximální počet expandovaných
KAPITOLA 6. UŽIVATELSKÁ PŘÍRUČKA
41
Obrázek 6.6: Volba Vzhled
uzlů, maximální doba reakce v sekundách a přibližná maximální hloubka prohledávání. Parametr je brán v úvahu pouze tehdy, je-li zatržen. Pro ukončení větve prohledávání stačí, aby byl překročen jeden z takto definovaných limitů. Alespoň jeden z parametrů musí být zatržen. Upozornění – nevhodné nastavení může vést k tomu, že bude reakce počítačového hráče trvat velmi dlouhou dobu. Proto je vždy vhodné určit a zatrhnout onu maximální dobu reakce. Pro zefektivnění prohledávání a heuristik je možné zatrhnout volbu použít objektivní data, kdy umělá inteligence vidí všem do karet. Ruční nastavení ovlivňuje jak počítačové hráče, tak úroveň nápovědy. Nejde-li o appletovou formu aplikace, uživatelovo nastavení se při startu automaticky načítá ze souboru options.mar. Do tohoto souboru se rovněž automaticky ukládá při opuštění aplikace přes nabídku Konec. Appletová forma aplikace je vždy inicializována se základním nastavením. Poslední sada voleb, Nápověda, obsahuje pouze dvě volby, Napověz tah a O programu. První volba slouží k zobrazení nápovědy, popisu akce, která je v dané herní situaci optimální, ukázka je na obrázku 6.8. Tuto volbu je možné využívat v jakékoli fázi hry, tedy i v licitaci, flekování, atd. Druhá volba vyvolá dialogové okno se základními informacemi o aplikaci.
6.5
Herní plocha
Herní plocha zobrazuje rozložení karet v aktuální hře z pohledu jednoho hráče (tj. tak, že nevidí do karet zbylým dvěma hráčům). Dolní část plochy zachycuje karty na ruce hráče a jeho uhrané zdvihy. V levé části plochy jsou zobrazeny karty a zdvihy hráče po jeho levici a v pravé horní části karty a zdvihy hráče po jeho pravici. Rozložení hráčů kolem stolu rovněž reflektují jména zobrazená na ploše u karet. V prostřední části plochy se zobrazují karty aktuálního zdvihu a karty talónu. Na odpovídající části herní plochy se rovněž zobrazují oznámení hráčů při flekování, licitaci a podobně. Výše byla popsána standardní podoba herní plochy. V módu simulace se ovšem podoba liší. Rozložení karet je až na výjimky stejné, avšak karty jsou zobrazeny vždy jako ruby. Zobrazují se totiž možnosti, jež vyplývají z dostupných informací a předchozího průběhu hry, nikoli karty
42
KAPITOLA 6. UŽIVATELSKÁ PŘÍRUČKA
Obrázek 6.7: Volba Obtížnost
Obrázek 6.8: Volba Napověz tah
jako takové. Je tudíž možné, že hráči budou mít v módu simulace zobrazeno na ruce víc jak dvanáct karet, případně že talón bude vypadat, jako by měl více karet než dvě.
6.6
Ovládání
Hra je ovládána pomocí kombinace myši a klávesnice. Karta se tzv. označuje kliknutím levým tlačítkem myši na zamýšlenou kartu. Označená karta se povysune nahoru. Druhým kliknutím levým tlačítkem na označenou kartu se potvrdí výběr (karta je zahrána). Pravým tlačítkem se karta odznačuje. Je-li třeba vybrat více karet (např. do talónu), je třeba jich daný počet označit kliknutím levým tlačítkem. Potvrzení výběru více karet se provede kliknutím levým tlačítkem na kteroukoli z označených karet. Není možné označit a tedy ani hrát kartu, která by byla v dané situaci renoncem. Potřebuje-li uživatel učinit rozhodnutí, které nezahrnuje volbu karet, lze tak učinit pomocí panelu na pravé straně obrazovky. Jedná se zejména o volbu počtu karet, které se uživatel pokouší sejmout, volby při licitaci a flekování, volbu způsobu skládání karet, atd.
KAPITOLA 6. UŽIVATELSKÁ PŘÍRUČKA
6.7
43
Mód simulace
Mód simulace slouží ke hráčem zprostředkovanému porovnání úrovně umělé inteligence v této aplikaci s umělou inteligencí v jiné aplikaci pro hraní licitovaného mariáše. Podstatou jeho použití je to, že uživatel předává rozhodnutí zbylých dvou hráčů v jiné aplikaci (simuluje tedy tyto dva hráče, hraje za ně) a sám hraje v obou aplikacích dle rad poskytovaných funkcí Napověz tah. Dle konečného finančního výsledku hry může poté vyvodit hrubé srovnání úrovně umělé inteligence. Na počátku simulace uživatel zadá, který z hráčů je rozdávající a následně si vybere karty, které mu byly rozdány v jiné aplikaci.
Obrázek 6.9: Simulace – speciální výběr karet
Mód simulace zahrnuje speciální výběr karet, kdy je podoba herní plochy odlišná, jak to zachycuje obrázek 6.9. Zobrazí se až čtyři řady karet, kde v každé řadě jsou možné karty od jedné barvy. Takto rozložené karty lze standardním způsobem označovat a následně potvrdit výběr. Tento speciální výběr se uplatňuje nejen na začátku, kdy si uživatel vybere karty, které mu byly rozdány, ale i při hraní karet za zbylé dva hráče a při výběru karet, které byly v talónu, pokud uživatel vyšel vítězně z licitace a vzal si talón.
6.8
Chybové hlášky
Uživatelské vstupy jsou ošetřeny tak, že v případě nevyhovujícího vstupu uživatele o této skutečnosti informují a zažádají znovu o vstup či korektně ukončí dialog. Příklad chybové hlášky, která je pro pokročilé uživatele doprovozena bližší informací o výjimce, je na obrázku 6.10.
Obrázek 6.10: Chybové hlášení
44
KAPITOLA 6. UŽIVATELSKÁ PŘÍRUČKA
6.9
Minimální požadavky
Minimální požadavky na aplikaci jsou následující: • rozlišení 1024x768, • nainstalované JRE, verze 1.5.
KAPITOLA 7. ZÁVĚR
45
7 Závěr Na základě pravidel licitovaného mariáše byla navržena a implementována aplikace v Javě, která umožňuje hrát licitovaný mariáš jako hru pro jednoho hráče, kdy za zbylé dva hráče hraje počítač. Aplikace splňuje základní požadavky, které vyplynuly ze zadání a rešerše existujících řešení, ať už jde o možnosti nabízené uživateli (možnost hrát licitovaný mariáš, nápověda, atd.), požadavky na grafické a ovládací rozhraní (ovládání přes myš a klávesnici, přehledné znázornění herní situace, atd.) či požadavky na umělou inteligenci (hra dle pravidel, vyvarování se elementárních chyb, přiměřená doba reakce, atd.). Jako netriviální úkol se ukázalo samotné vytvoření datového modelu hry, který by reflektoval pravidla v celé jejich šíři a zároveň poskytl vhodný základ pro implementaci různých herních módů a umělé inteligence pro počítačem nahrazené hráče. Grafická stránka hraje u karetních her víceméně druhotnou roli a implementovanou formu je proto možné považovat za dostačující. Unikátním herním módem, který aplikace nabízí nad rámec vytyčených požadavků, je simulace, jež umožňuje hrubé srovnání úrovně umělé inteligence v této a v jiné aplikaci. Překážkou v korektním a plném využití tohoto módu je podmínka totožných pravidel v obou aplikacích. Hra by mohla být rozšířena o některé další možnosti a uživatelské volby jako například zaznamenávání a ukládání průběhu her do souborů, jejich statistické vyhodnocování či zahrnutí bodování ze sportovně-soutěžního mariáše. Největší možnosti pro pokračování na práci však spatřuji v umělé inteligenci – schopnostech počítačem nahrazených protivníků. To, jakých výsledků může umělá inteligence ve hře dosáhnout, je do značné míry determinováno úvodním odhadem vhodnosti rozložení karet pro uhrání jednotlivých her společně s volbou přijatelného rizika. Heuristické funkce, které toto zajišťují, by bylo možné vzájemně lépe vybalancovat, aby umělá inteligence nepreferovala jistý typ hry a zároveň riskovala přiměřeně. Nejprve by bylo vhodné extenzivně otestovat současné chování heuristik mnoha hrami pouze počítačových hráčů a vyhodnotit, jak si umělá inteligence vede jako aktér či naopak protihráč, zda je úspěšná ve hrách, které vylicituje, a podobně, to vše ve vztahu k jednotlivým typům hry. Nastavení optimální váhy parametrů heuristik by zřejmě šlo provést prostřednictvím neuronových sítí, které by se učily při hře s lidskými či počítačovými protivníky, případně genetických algoritmů, kdy by umělé inteligence v jednotlivých generacích hrály proti sobě, i když oba postupy by nejspíše byly značně časově náročné. Alternativou je zpřesnění heuristik na základě rad experta přes tuto hru.
46
KAPITOLA 7. ZÁVĚR
PŘÍLOHA A. OBSAH PŘILOŽENÉHO CD
47
A Obsah přiloženého CD Na přiloženém CD najdete: • JBuilder a NetBeans projekty pro aplikaci, webovou aplikaci a samostatný server v adresáři projects, • spustitelné archivy v adresáři application, • dokumentaci ke zdrojovým kódům a text tohoto dokumentu v adresáři documents. Podrobnější informace o obsahu CD naleznete v souboru index.html.
48
PŘÍLOHA B. LITERATURA
B Literatura [1] Bytonic software. http://www.bytonic.de/html/benchmarks.html. [2] J2se api docs. http://java.sun.com/j2se/1.5.0/docs/api/index.html. [3] Koenigovy stránky o mariáši. http://mujweb.cz/www/kingus/pravidla.html. [4] Minimax with alpha-beta cutoff. http://www.cis.temple.edu/~ingargio/cis587/readings/. [5] A. Davison. Killer Game Programming in Java. O’Reilly, 2005.