Univerzita Karlova v Praze Matematicko-fyzikální fakulta
BAKALÁŘSKÁ PRÁCE
Michal Staruch Simultánní tahová hra pro mobilní zařízení Katedra softwaru a výuky informatiky
Vedoucí bakalářské práce: Mgr. Jakub Gemrot Studijní program: Informatika Studijní obor: Programování a softwarové systémy
Praha 2016
Prohlašuji, že jsem tuto bakalářskou práci vypracoval(a) samostatně a výhradně s použitím citovaných pramenů, literatury a dalších odborných zdrojů. Beru na vědomí, že se na moji práci vztahují práva a povinnosti vyplývající ze zákona č. 121/2000 Sb., autorského zákona v platném znění, zejména skutečnost, že Univerzita Karlova v Praze má právo na uzavření licenční smlouvy o užití této práce jako školního díla podle §60 odst. 1 autorského zákona.
V . . . . . . . . dne . . . . . . . . . . . .
Podpis autora
i
Název práce: Simultánní tahová hra pro mobilní zařízení Autor: Michal Staruch Katedra: Katedra softwaru a výuky informatiky Vedoucí bakalářské práce: Mgr. Jakub Gemrot, Katedra softwaru a výuky informatiky Abstrakt: Práce se zabývá návrhem a realizací tahové strategické hry se simultánním hraním pro systém Android. Součástí této práce je analýza nejhranějších her pro systém Android, omezení mobilních zařízení a potřebných knihoven. V práci je následně popsán princip fungování prototypu herního serveru a zátěžový test implementace tohoto prototypu. Na základě výsledků uvedených testů je u serveru implementováno vícevláknové zpracování a provedena analýza nové implementace. V práci jsou rovněž popsány techniky použité u síťové komunikace. Výsledkem této práce je herní server v jazyce Java a mobilní aplikace pro systém Android, pomocí které uživatelé hrají vytvořenou hru. Klíčová slova: simultánní hra, tahová strategie, mobilní platforma
Title: Simulatenous move turn-based game for mobile devices Author: Michal Staruch Department: Department of Software and Computer Science Education Supervisor: Mgr. Jakub Gemrot, Department of Software and Computer Science Education Abstract: This thesis deals with a conception and implementation of a simultaneous move turn-based game for devices running Android. The thesis contains an analysis of the most played games on Android, limitations of mobile devices and required libraries. The work further focuses on the principle of operation of a server prototype and a stress test of an implementation of the described prototype. Based on the test results, the thesis describes an implementation of multithreaded processing and an further analysis of this implementation. The work also portrays the techniques used for network communication. The result of this work is a game server written in Java and a mobile application for Android, which is used by the users to play the created game. Keywords: simulatenous move, turn-based strategy, mobile platform
ii
Touto cestou bych rád poděkoval mému vedoucímu Mgr. Jakubovi Gemrotovi za jeho rady a čas věnovaný této práci. Dále bych chtěl poděkovat své rodině za jejich podporu a Mgr. Pavlíně Šustkové za pomoc s korekturou textu práce.
iii
Obsah Úvod Cíle práce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Struktura práce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3 4 4
1 Volba Platformy 1.1 Zadání práce . . . . . . . . 1.2 Výběr platformy . . . . . . 1.3 Hry na Androidu . . . . . . 1.4 Omezení mobilních zařízení
. . . .
5 5 5 6 7
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
2 Design Hry 2.1 Inspirace . . . . . . . . . . . . . 2.1.1 The Battle for Wesnoth 2.1.2 Advance Wars . . . . . . 2.1.3 UniWar . . . . . . . . . 2.2 Vlastnosti hry . . . . . . . . . . 2.2.1 Tahová strategie . . . . 2.2.2 Délka hry . . . . . . . . 2.2.3 Náhodné prvky . . . . . 2.2.4 Herní objekty . . . . . . 2.3 Herní návrh . . . . . . . . . . . 2.3.1 Reprezentace mapy . . . 2.3.2 Vlastnosti polí . . . . . . 2.3.3 Časová omezení . . . . . 2.3.4 Suroviny . . . . . . . . . 2.3.5 Tah a akce . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
8 8 8 9 10 10 11 11 12 13 13 13 15 16 17 18
3 Analýza a implementace 3.1 Založení zápasu . . . . . . . . 3.1.1 Herní místnost . . . . 3.1.2 Automatický zápas . . 3.2 Komunikace mezi hráči . . . . 3.2.1 Model komunikace . . 3.2.2 Komunikační protokol 3.3 Persistence . . . . . . . . . . . 3.4 Vykreslování . . . . . . . . . . 3.5 Záznamy dat . . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
20 20 20 20 22 22 23 24 26 27
4 Použité knihovny 4.1 Android a podpůrné knihovny 4.2 Commons Math . . . . . . . . 4.3 Logování . . . . . . . . . . . . 4.4 Databáze . . . . . . . . . . . 4.5 Vykreslování mapy . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
29 29 29 29 30 32
1
5 Prototyp serveru 5.1 Funkce serveru . . . . . . . . . . . 5.2 Rozhraní Task . . . . . . . . . . . . 5.3 Hlavní smyčka serveru . . . . . . . 5.4 Výkon serveru . . . . . . . . . . . . 5.4.1 Specifika použitých zařízení 5.4.2 Princip testu . . . . . . . . 5.4.3 Výsledky testování . . . . . 5.4.4 Závěr . . . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
33 33 33 35 35 36 36 37 37
6 Vícevláknový server 6.1 Rozdělení práce serveru . . . . . . . . 6.2 Zámky . . . . . . . . . . . . . . . . . 6.3 Nové rozhraní Task . . . . . . . . . . 6.4 Fronty úloh . . . . . . . . . . . . . . 6.4.1 Nové funkce třídy Connection 6.4.2 Přeplánování úlohy . . . . . . 6.5 Výkon vícevláknového serveru . . . . 6.5.1 Test na virtuálním serveru . . 6.5.2 Test na notebooku . . . . . . 6.5.3 Závěr . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
39 39 39 40 41 43 43 44 44 45 46
7 Síťová komunikace 7.1 Komunikační protokol . 7.1.1 Existující řešení . 7.1.2 Náš protokol . . . 7.2 Sockety v Javě . . . . . 7.3 Připojení klienta . . . . 7.4 Detekce výpadku spojení 7.5 Ping . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
48 48 48 49 50 51 52 53
. . . .
55 55 55 55 55
8 Další rozvoj 8.1 Představení hry hráči . . 8.2 Další platformy . . . . . 8.3 Více serverů . . . . . . . 8.4 Grafické rozhraní a zvuk
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
Závěr
57
Seznam použité literatury
58
Seznam obrázků
60
Seznam odkazovaných her
61
Seznam tabulek
62
Seznam souborů v příloze
63
2
Úvod Tahové strategické hry jsou jednou z oblíbených kategorií počítačových her. Tyto hry jsou nejčastěji simulací bitvy a cílem hry je vojensky přemoci protivníka použitím vhodně volené strategie. Na rozdíl od real-time her, kde hráči hrají současně a každá jejich akce má okamžitý vliv na vývoj hry, v tahových strategiích je průběh hry rozdělen na tahy. Hráči poté odehrávají jednotlivé tahy a v odehrávání tahů se nejčastěji střídají. V této práci se budeme věnovat návrhu a implementaci tahové strategie pro více hráčů se simultánním hraním, ve které hráče omezíme časovým limitem. Pro účely hry vytvoříme herní server, který bude zprostředkovávat komunikaci mezi jednotlivými hráči. Dalším úkolem herního serveru bude tvorba zápasů a automatické párování hráčů na základě jejich dovedností a zkušeností. Herní server se také bude starat o zajištění konzistence jednotlivých zápasů a udržování statistik jednotlivých hráčů. Dále vytvoříme herního klienta pro mobilní zařízení, který budou používat jednotliví hráči. Pomocí tohoto klienta budou hráči zakládat jednotlivé zápasy a odehrávat své tahy. Klient bude během svého běhu připojený na server a komunikovat s ním. Cílovou platformou pro hru bude platforma Android, jelikož mobilní zařízení s operačním systémem Android tvořila nadpoloviční většinu[1] prodaných zařízení v roce 2015. Obě části programu implementujeme v jazyce Java, jelikož Java je primárním jazykem pro psaní aplikací na platformě Android a použitím stejného jazyka v obou částech programu můžeme využít sdílený kód. Výsledkem práce bude tahová strategie pro mobilní platformy se simultánním hraním. Na mobilních platformách nejsou tahové strategie příliš rozšířené, naší hru tedy zkusíme přizpůsobit herním zvykům hráčů. Pro tyto účely analyzujeme nejoblíbenější hry na Androidu a vlastnosti mobilních zařízeních. Na základě této analýzy poté navrhneme naši hru. Na základě návrhu dále rozhodneme, jaké prostředky použijeme k implementaci této hry. Součástí hry bude také simultánní odehrávání tahů. Tento princip není příliš obvyklý, lze na něj však narazit například ve hře Age of Wonders III [H1] nebo v multiplatformní hře Frozen Synapse[H2]. V těchto hrách hráči odehrávají své tahy současně a jejich tahy se poté společně vyhodnotí. Simultánní odehrávání budeme dále kombinovat s omezením času na herní tah. Čas zavedeme jako surovinu a dovolíme hráčům krást“ čas svým soupeřům. ” Při návrhu hry se budeme inspirovat oblíbenými tahovými strategiemi. Jednou inspirací pro nás bude open-source hra The Battle for Wesnoth[H3], která je k dispozici na mnoha operačních systémech. Dále se inspirujeme oblíbenou sérií her Advance Wars, konkrétně se podíváme na Advance Wars 2 [H4], která byla vydána v roce 2003 pro konzole Game Boy Advance. Počet prodaných kusů této hry přesahuje půl milionů kopií.[2] Mechaniky těchto her adaptujeme právě na vlastnosti mobilních zařízení a na zvyky hráčů na těchto zařízeních. Výsledná aplikace bude také implementovat mechanismy pro zjištění výpadků spojení a obnovení hry při takových výpadcích. Herní server bude rovněž kontrolovat stav spojení s jednotlivými klienty. Aplikace bude také minimalizovat počet přenesených dat a méně tak zatěžovat datové limity mobilních připojení.
3
Cíle práce • výběr mobilní platformy a analýza her této platformy • návrh vlastní tahové strategie se simultánním hraním • výběr prostředků pro implementaci této strategie • implementace klienta pro Android • implementace serveru v jazyce Java • implementace mechanismů pro detekci a řešení výpadků spojení
Struktura práce Práce se skládá z osmi kapitol. V první kapitole srovnáme mobilní platformy, vybereme jednu z nich a podíváme se na omezení mobilních platforem. V druhé kapitole provedeme analýzu oblíbených her vybrané platformy a na základě této analýzy navrhneme princip hry a herní mechaniky. Ve třetí kapitole vysvětlíme, jak budou hráči zakládat zápasy a co budeme potřebovat k implementaci naší hry. Ve čtvrté kapitole srovnáme a popíšeme knihovny, které použijeme při implementaci. V páté kapitole vytvoříme prototyp herního serveru, ten implementujeme a otestujeme jeho výkon. V šesté kapitole popíšeme, jak implementujeme u serveru vícevláknové zpracování a nový server znovu otestujeme. V sedmé kapitole vysvětlíme, jak aplikace implementuje síťovou komunikaci a jaké mechanismy využívá. Na závěr v osmé kapitole navrhneme možná vylepšení naší aplikace.
4
1. Volba Platformy V této kapitole si podrobněji rozebereme zadání práce, zvolíme cílovou platformu pro naši hru a podíváme se, jak vypadají nejčastěji hrané hry na zvolené platformě. Na závěr kapitoly rozebereme omezení mobilních platforem. Analýzu nejčastěji hraných her a omezení platformy později využijeme v dalších kapitolách při návrhu naší hry.
1.1
Zadání práce
Cílem naší práce je návrh a implementace tahové strategické hry pro více hráčů, ve které budou hráči své tahy odehrávat simultánně. Cílovou platformou pro hru je nějaká z mobilních platforem, které jsou v době psaní práce rozšířené. Pro zvolenou platformu tedy vytvoříme mobilní aplikaci, pomocí které budou hráči hrát naší hru. Pro zajištění fungování hry bude také potřeba vytvořit herní server, se kterým budou aplikace na naší zvolené mobilní platformě komunikovat. Tento server bude zprostředkovávat komunikaci mezi hráči, spravovat běžící hry, simulovat tahy hráčů, vytvářet pro hráče nové hry a v neposlední řadě udržovat historii hráčů. Důvody, proč takový server potřebujeme mít, si ukážeme v kapitole 3. Nejprve se podíváme na dostupné mobilní platformy a zvolíme cílovou platformu pro naši aplikaci.
1.2
Výběr platformy
Při výběru cílové platformy (operačního systému) se zaměříme na to, jak je tento systém rozšířený a jak dostupná jsou pro nás zařízení s tímto systémem pro účely testování. Na mobilních zařízeních dominují 3 operační systémy: Android, iOS a Windows Phone. Podle statistik z roku 2015[1] je nejrožšířenější operační systém Android, s více než 80% podílem na trhu. Druhým nejrozšířenějším systémem je poté systém iOS, třetím systém Windows Phone. Pokud bychom si zvolili za cílový systém právě Android, cílili bychom potenciálně na nějvětší uživatelskou základnu. Pro naší hru budeme zároveň potřebovat vytvořit uživatelské rozhraní, pomocí kterého hráči budou hledat soupeře a zakládat nové hry. Naším kritériem pro výběr operačního systému tedy bude rovněž programovací jazyk, pro který systém nabízí API a který můžeme využít pro vytvoření uživatelského rozhraní. Primárním jazykem pro systém Android je jazyk Java, pro iOS je to jazyk Swift a pro Windows Phone jazyk C#. Kvůli své rozšířenosti by pro nás však jazyky Java a C# byly výhodnější. Velký význam pro nás má dostupnost zařízení s cílovým operačním systémem. Vytváříme hru pro více hráčů, pro testování budeme tedy potřebovat minimálně dvě zařízení. Z tohoto důvodu odpadá systém iOS, jelikož cena zařízení s tímto systémem je vysoká. Zařízení se systémy Android a Windows Phone jsou cenově dostupnější a tedy je bude možné snáze získat pro účely testování. 5
Naše pozorování shrneme v tabulce 1.1. Operační systém
Zastoupení
Android iOS Windows Phone
82,8% 13,9% 2,6%
Primární jazyk
Cenová dostupnost
Java Swift C#
lepší horší lepší
Tabulka 1.1: Porovnání mobilních operačních systémů
Kvůli výše uvedeným důvodům jsme se rozhodovali mezi systémy Android a Windows Phone. Nakonec jsme se rozhodli pro systém Android kvůli velké rozšířenosti a snadnému přístupu k zařízením s tímto systémem.
1.3
Hry na Androidu
Nejprve se podíváme na nejoblíbenější hry na systému Android a na nejhranější strategické hry na tomto systému. K tomu využijeme žebříček nejlépe hodnocených her na portálu Play Store.2 Poté se podíváme, co mají tyto hry společného, a výsledná pozorování použijeme při návrhu naší hry. Na prvních příčkách žebříčku najdeme převážně akční hry, logické hry a arkády. V květnu roku 2016 první tři přední místa obsadily hry slither.io[H5], Hungry Shark World [H6] a Color Switch[H7]. Tyto tři hry jsou akční hry s jednoduchým ovládáním, které bývá založené na pohybu prstem po obrazovce a občasném kliknutí. Mezi nejlepšími padesáti hrami najdeme sedm her, které jsou zařazené do kategorie strategické hry. Mezi těmito hrami je například válečná strategie Mobile Strike[H8], tower defense hra Toy Defense 2 [H9] nebo budovatelská strategie MegapoObrázek 1.1: Screenshot ze lis[H10]. 1 U všech výše zmíněných her se vyskytuje jeden hry Mobile Strike z následujících dvou principů. Buď průběh této hry neustále pokračuje s tím, že hráč může hru kdykoliv přerušit (například budování ve strategické hře Megapolis), anebo jedna hra zpravidla netrvá příliš dlouho a hráč tedy může hru brzy ukončit (to je případ akční hry Color Switch). Tomu odpovídají průzkumy průměrné doby herního sezení (doba od spuštění aplikace po její ukončení), která činí v průměru necelých 7 minut.[3] 1
Zdroj: https://play.google.com/store/apps/details?id=com.epicwaronline.ms& hl=cs 2 Tento seznam je průběžně aktualizovaný a je k dispozici na adrese https://play.google. com/store/apps/category/GAME/collection/topselling_free.
6
Dalším společným prvkem výše zmíněných strategických her je přítomnost herního tutoriálu nebo návodu. Tato pozorování vezmeme v potaz při návrhu naší strategické hry a navrhneme naší hru tak, aby se přizpůsobila zvykům hráčů.
1.4
Omezení mobilních zařízení
Při návrhu naší hry se budeme také muset přizpůsobit omezením, která vyplývají z vlastností dnešních mobilních zařízení. V této práci budeme termín mobilní zařízení používat jako souhrnné označení pro chytré telefony a tablety. Na rozdíl od stolních počítačů či herních konzolí, mobilní zařízení mají určitá omezení. Mezi ně patří omezená výdrž baterie zařízení, nižší výkon a menší velikost displejů. Tato omezení si popíšeme v následujících odstavcích. Prvním omezením, se kterým se potýkají uživatelé mobilních zařízení, je výdrž baterie. U levnějších zařízení často také najdeme slabší baterii, což nadále může omezovat uživatele při hraní mobilních her. Starší baterie ztrácí maximální kapacitu a výdrž baterie zařízení se tak dále snižuje. Dalším omezením mobilních zařízení je nižší výkon. Nižší výkon se nejvíce projevuje při vykreslování herních prvků. Zařízení se slabším výkonem mohou mít problém s náročnými animacemi, zvláště ve 3D. Slabší zařízení mohou mít problémy s tímto vykreslováním a hra musí běžet buď zpomaleně, nebo kvůli přeskakování zobrazených snímků nemusí být vidět některé efekty, jako například rychlé projektily u akčních her. Nižší výkon nás také může omezit při použití algoritmů s velkou časovou složitostí, jejichž výpočet by mohl probíhat příliš dlouho. Při návrhu her je potřeba vyhnout se časově náročným algoritmům a najít kompromis mezi náročnou grafikou a škálou podporovaných zařízení. Velikost displejů zařízení dále přináší problémy s navržením vhodného ovládání hry. Na rozdíl od počítačových nebo konzolových her, kde hráči mají pevně umístěná tlačítka, kterými hru ovládají, hry na mobilních zařízeních je možné ovládat pouze dotykem a gesty, případně akcelerometrem a gyroskopem (součástky, které umožňují rozpoznat pohyb zařízení nebo jeho náklon). Pro menší velikosti displejů je návrh ovládání náročnější, jelikož všechny prvky na displeji mají menší velikost a ovládací prvky je potřeba mít větší. Velké velikosti displejů také mohou hráčům přinášet výhodu, jelikož mohou mít hráči na displeji více informací o hře (například vidět větší část herní mapy). Tato zjištění musíme vzít v potaz při návrhu naší hry.
7
2. Design Hry V této kapitole si nejprve ukážeme, jakými hrami se budeme inspirovat při návrhu hry a tyto hry popíšeme. Později si ukážeme, jaké vlastnosti by naše hra měla mít. Na závěr kapitoly poté navrhneme vlastní hru a její mechaniky.
2.1
Inspirace
Nejprve si představíme hry, kterými se budeme inspirovat při návrhu naší hry. Jako inspiraci jsem si vybral hry The Battle for Wesnoth a Advance Wars, se kterými mám zkušenosti a můžu je tedy lépe popsat.
2.1.1
The Battle for Wesnoth
The Battle for Wesnoth je oblíbená tahová strategická hra pro stolní počítače. Verze 1.0 této hry vyšla v roce 2005.[4] Tato hra je open-source a je k dispozici pro širokou škálu operačních systémů. Jedná se o válečnou fantasy strategii, kde hráči mezi sebou navzájem soupeří a snaží se vojensky přemoci svého protivníka. V této hře je k dispozici i režim hry pro jednoho hráče.
Obrázek 2.1: Screenshot ze hry The Battle for Wesnoth1 V následujících bodech si zdůrazníme charakteristické prvky této hry: • cílem hry je vojensky přemoci protivníky a zabít jejich vůdce • hrací plocha 1
Zdroj: http://wesnoth.garage.maemo.org/
8
– hrací plocha je rozdělena na šestiúhelníková pole – na hrací ploše se nachází tvrze a vesnice – na hrací ploše jsou vůdci hráčů – v tvrzích hráči verbují jednotky, v každém tahu lze provést jednu akci s každou jednotkou • herní mechaniky – hráči hru odehrávají v tazích – jedinou surovinou je zlato – jednotky bojují s ostatními a zabírají vesnice – jednotky získávají v boji zkušenosti – každá jednotka má své slabé a silné stránky – vlastnosti jednotek jsou ovlivněné denní dobou a terénem – udělené poškození je náhodné
2.1.2
Advance Wars
Další naší inspirací jsou hry ze série Advance Wars. Jelikož v této sérii je her více, popíšeme si podrobněji hru Advance Wars 2. Advance Wars 2 je hra pro kapesní konzole Game Boy Advance. V této hře spolu hráči navzájem bojují pomocí svých jednotek a důstojníků. Hra je zaměřená na hru pro jednoho hráče, nabízí však také režim pro více hráčů, ve kterém hráči hrají na jednom zařízení a v tazích se střídají. Mezi charakteristické prvky této hry patří: • cíl hry je zničit všechny jednotky nebo obsadit velící středisko protivníka • hrací plocha – hrací plocha je rozdělena na čtvercová pole – na hrací ploše se nachází políčka s terénem a budovami – každý hráč vlastní velící středisko a důstojníka – na hrací ploše se nachází jednotky hráčů • herní mechaniky – hra je odehrávána v tazích, v každém tahu lze provést jednu akci s každou jednotkou a budovou – v některých budovách je možné cvičit jednotky – pomocí jednotek hráči obsazují v průběhu hry budovy – jedinou surovinou jsou peníze – hráči získávají při útoku a obraně energii, kterou mohou použít a využít speciální schopnosti jejich důstojníka – udělené poškození kolísá, jednotky mají šanci na kritický zásah 9
Obrázek 2.2: Screenshot ze hry Advance Wars 22 – každá jednotka má omezenou munici a palivo – různé typy jednotek mají rozdílné silné a slabé stránky
2.1.3
UniWar
Velmi podobná je rovněž hra UniWar [H11], která je inspirována hrami série Advance Wars a je dostupná pro mobilní platformy. Zajímavostí této hry je princip hraní více her naráz a jejich ukládání. Každý hráč může mít rozehraných několik her s různými protivníky najednou. V těchto hrách hráč odehraje svůj tah, hra se uloží a na tah se dostane protivník. Než odehraje protivník, může hráč odehrávat tahy v jiných rozehraných hrách, nebo hru ukončit a vrátit se ke hře, až se dostane znovu na tah.
2.2
Vlastnosti hry
V této podkapitole rozebereme, jak se přizpůsobíme velikosti displejů a omezené výdrži baterií, které jsme uvedli v předchozích kapitolách. Také rozebereme hlavní prvky naší hry a rozhodneme, které herní mechaniky převezmeme z her, kterými jsme se inspirovali, a které naopak změníme. Tyto informace použijeme k vytvoření kostry naší hry, kterou doplníme v další kapitole o konkrétní herní mechaniky, jako jsou suroviny, herní akce, apod. 2
Zdroj: http://image.jeuxvideo.com/images-sm/videos/extraits-images/201006/ advance_wars_2___black_hole_rising_gba-00008918-low.jpg
10
Obrázek 2.3: Screenshot ze hry UniWar3
2.2.1
Tahová strategie
Naším cílem je vytvořit tahovou strategii pro více hráčů. Průběh hry bude rozdělen na jednotlivé tahy, které budou omezené. Po každém odehraném tahu vyhodnotíme provedené hráčovy akce a změníme stav hry. V každém tahu dáme hráči možnost provést nejvýše jednu akci s každým vlastním objektem. Tímto zvýšíme počet možností, jak lze každý tah odehrát a tedy i různorodost tahů a herních strategií. Tento nárůst nám pomůže vyrovnat se s použitím menších herních map, které tento počet snižují kvůli menšímu počtu objektů na mapě. Použitím menších map se můžeme lépe přizpůsobit menším displejům, na kterých je možné zobrazit větší část mapy při stejném rozlišení a tím hráči umožnit snáze udržet přehled o jejím stavu.
2.2.2
Délka hry
Abychom se vyhnuli různým významům pojmu hra, budeme v následujícím textu používat pojem zápas. Zápasem budeme rozumět jedno celé utkání mezi hráči, tedy dobu od začátku prvního tahu do ukončení tohoto utkání. Ze zvyků hráčů vyplývá, že bude vhodné mít zápasy v naší hře krátké. Zavedeme tedy do hry mechaniky, které nám umožní urychlit jednotlivé zápasy. V následujících odstavcích popíšeme, jak tohoto urychlení dosáhneme. Prvním způsobem, jak zkrátit dobu trvání zápasů, je zavést simultánní odehrávání tahů. Jednotliví hráči tedy současně odehrají tah a všechny provedené akce se současně vyhodnotí. Zavedení simultánního odehrávání bude mít také dopad na návrh dalších mechanik hry. Například při provedení akcí útok na ves3
Zdroj: http://gamesforbloggers.blogspot.cz/2016/04/uniwar.html
11
nici A jedním hráčem a opravě vesnice A napadeným hráčem budeme muset rozhodnout, která akce se provede první nezávisle na pořadí zadání akcí. Princip simultánního odehrávání je dále znázorněn na obrázku 2.4.
Obrázek 2.4: Ukázka průběhu hry. V horní části je průběh hry, pokud hráči odehrávají tahy postupně, ve spodní části je průběh se simultánním odehráváním. Druhým způsobem je omezení času na jednotlivé tahy. Každému hráči dáme pouze krátký časový interval, ve kterém může svůj tah odehrát. Musíme vzít do úvahy také simultánní odehrávání a dát hráči dostatek času na zjištění, jak odehráli jeho soupeři v předchozím tahu, aby mohl tyto informace využít při odehrávání nového tahu. Dále by bylo vhodné přizpůsobit časové limity současné herní situaci, tedy na malých mapách mít časové limity kratší, než na velkých mapách. Zavedením výše uvedených mechanik ale narazíme na nové problémy. Nejvýraznějším z těchto problémů je složitost ovládání, které potřebujeme zjednodušit natolik, aby bylo možné hru ovládat dostatečně rychle pouze dotykem a gesty. Z tohoto důvodu bude potřeba zjednodušit další mechaniky, které jsou v jiných hrách obvyklé a které by nám zvyšovaly obtížnost ovládání. Na tyto mechaniky se podíváme ve zbytku této kapitoly.
2.2.3
Náhodné prvky
Jak jsme již uvedli v předchozí podkapitole, v obou hrách, kterými jsme se inspirovali, je poškození jednotek náhodné. To může vyvolávat u hráčů nejistotu při odehrávání tahu, kdy se mohou obávat selhání jejich útoku a ve svých tazích méně útočit. Z tohoto důvodu navrhneme boj v naší hře deterministicky. Hráči budou vědět, jak jejich zahrané akce dopadnou a budou moci lépe předvídat události ve hře. Tato jistota může urychlit jejich uvažování a tím i průběh hry.
12
2.2.4
Herní objekty
Jak v The Battle for Wesnoth, tak v Advance Wars existují dva typy herních objektů. Prvním typem jsou budovy, které mají svou pevnou pozici na herní mapě a pouze mění svého vlastníka. Tyto budovy poskytují hráči potřebné zdroje, popřípadě jednotky. Druhým typem jsou samotné jednotky, kterými hráč pohybuje po mapě. Každý hráč může mít i několik desítek jednotek, které lze v každém tahu ovládat, což je další významný faktor, který může zpomalit odehrávání tahů. Abychom urychlili odehrávání, rozhodli jsme se ponechat ve hře jen budovy. Díky tomu máme možnost snáze předvídat odehrávání hráčů a podle toho upravit časové limity. Hráči se také budou moci snáze a rychleji orientovat a zjistit, jak odehráli tah jejich soupeři. Nevýhodou tohoto přístupu je ztráta významné herní mechaniky a opětovné snížení počtu herních strategií. Nicméně, stále máme možnost zvýšit hustotu budov a tak tento počet opět navýšit. Odebráním jednotek jsme také přišli o systém útočení. Ten nahradíme útočením přímo z budov, kde některým budovám dáme možnost vyslat útok na nepřátelské budovy. Takto získáme princip útočení, při kterém nepotřebujeme jednotky, a zároveň získáme možnost navrhnout ovládání hry tak, že hráči budou provádět všechny akce pouze prostřednictvím svých budov.
2.3
Herní návrh
V této podkapitole popíšeme detailněji princip hry, jakým způsobem budou hráči hru ovládat a jaký bude cíl hry. Popsaný princip hry a vysvětlení ovládání hry bude také dostupné pro hráče v herním manuálu, který bude součástí naší aplikace. Nejprve se podíváme na způsoby reprezentace herní mapy a na rozdíly mezi nimi. Poté jeden ze způsobů vybereme a popíšeme, jak budou vypadat objekty na herní mapě. Také se podíváme, jak určíme časová omezení naší hry a jaké budou herní suroviny. Z toho také vyvodíme cíl hry, kterým bude připravit soupeře o všechny velitelská střediska. Na závěr si ukážeme, jaké herní akce budou mít hráči k dispozici. V této kapitole budeme využívat poznatky z předchozích kapitol a přizpůsobovat se omezením platformy. Při návrhu hry se často inspirujeme jinými hrami a tato inspirace je založená na osobních preferencích, a proto budou některé části textu subjektivní.
2.3.1
Reprezentace mapy
Zápasy v naší hře se budou odehrávat na herní mapě. Tuto mapu je ale nejprve potřeba navrhnout, definovat a reprezentovat v kódu. Máme několik možností, jak reprezentovat herní mapu. Tyto možnosti si představíme v následujících odstavcích. Podíváme se, jestli budeme mít dvourozměrný nebo trojrozměrný svět, na rozdíly mezi spojitou a diskrétní reprezentací, jakým způsobem lze mapu diskrétně reprezentovat a jak takovou mapu ukládat v kódu. Nejprve porovnáme, jaký význam pro vykreslování má trojrozměrný a dvourozměrný svět. Trojrozměrný svět má na rozdíl od dvourozměrného světa definovanou výšku jednotlivých objektů a jednotlivé objekty se mohou překrývat. 13
Jelikož máme malý displej, reprezentace rozdílné výšky by zabírala více prostoru a docházelo by k zakrývání jiných objektů. To může být navíc neintuitivní pro hráče, kteří by kvůli zakrytým objektům mohli mít obtížnější orientaci na mapě. Pokud budeme mít pouze dvourozměrný svět, bude na každém místě existovat vždy nejvýše jeden objekt a hráči tak budeme moci zobrazit vždy všechny objekty na zobrazené části mapy. Kvůli malým displejům mobilních zařízení a intuitivnímu zobrazování mapy budeme používat pouze dvourozměrný svět. Mapu tedy stačí reprezentovat jako souvislou, dvourozměrnou plochu. Můžeme mapu chápat buď jako spojitou plochu, nebo ji diskrétně rozdělit. Spojitě herní mapu reprezentujeme tak, že každému objektu na mapě přiřadíme reálné souřadnice x a y. Tento způsob reprezentace odpovídá ploše v reálném světě, kde každý objekt můžeme umístit na libovolné místo této plochy. Každá jednotka pak musí mít přesnou velikost a může se nacházet na libovolném místě. Při oddálení herní mapy bychom museli přesnou pozici a velikost jednotky zaokrouhlovat, kde by potom docházelo k chybám po zaoukrouhlení. Vykreslováním objektů na chybném místě bychom mátli hráče, v případě malých objektů by nebylo na malých displejích možné objekty správně vykreslit. U spojitých map vzniká také problém při výpočtu cesty mezi herními objekty. Takovou cestu je obtížné nejen definovat (jak musí být široká mezera mezi 2 objekty, aby tudy mohla vést cesta), ale i najít. Abychom mohli použít algoritmy pro hledání cest, musíme rozdělit rovinu na diskrétní pole a na ně aplikovat tradiční algoritmy pro hledání cesty.[5] Dále je potřeba při tvorbě jednotlivých map kontrolovat, zda na mapu neumisťujeme objekty, které by se překrývaly. Pokud se rozhodneme pro diskrétní rozdělení mapy, herní mapu rozdělíme na jednotlivá pole, kde každému poli přidělíme celočíselné souřadnice. Každý objekt se bude nacházet na nějakém poli, větší objekty mohou ležet na skupině sousedních polí. To zjednoduší hledání cesty po mapě, protože nám bude stačit hledat cestu pouze mezi jednotlivými poli, které můžeme jednoduše reprezentovat neorientovaným grafem. Překrývání objektů lze také zjistit jednoduše, stačí se podívat na každé pole a zkontrolovat, jestli do něj zasahuje nejvýše jeden objekt. Tuto reprezentaci často používají tahové strategické hry. Otázkou zůstává, jak mapu diskrétně rozdělit. Nejčastější rozdělení jsou buď na čtvercová, nebo na šestiúhelníková pole. Rozdělení na čtvercová pole je jednodušší z hlediska návrhu kódu a jeho implementace. Každému poli můžeme snadno přiřadit dvojici souřadnic (x,y). Další výhodou je hledání sousedů u čtvercových polí. Hlavní nevýhodou čtvercových polí je poté odhad vzdálenosti hráče a právě vnímání sousedů. V této reprezentaci mají dvě pole, které spolu sdílí jeden vrchol, vzdálenost rovnou 2. Pokud tedy hráč chce cestovat po diagonále, musí urazit mnohem větší vzdálenost, než je na první pohled patrné. Toto rozdělení nalezneme například ve hrách ze série Advance Wars (Obrázek 2.5). Problém s vnímáním vzdálenosti není tak patrný, pokud mapu rozdělíme na šestiúhelníky. Použijeme-li pravidelné šestiúhelníky, může mít každý šestiúhelník až šest sousedů, kde s každým sousedem sdílí vždy právě jednu stejně dlouhou hranu. Tím tedy odpadá problém čtvercových polí, kdy některá pole sousedí hranou a jiná pouze vrcholem. Šestiúhelníková pole mohou také pomoci při návrhu map, kde je možné využít toho, že pole může mít až šest sousedů a podle toho na14
Obrázek 2.5: Čtvercové rozdělení ve hře Advance Wars 24 vrhnout mapu s jinými překážkami. Nevýhodou šestiúhelníkových polí je obtížná implementace v kódu, jelikož nelze každému poli snadno přiřadit jednoznačné souřadnice. Další nevýhodou je obtížnější hledání sousedů a cest mezi dvěma poli, což však závisí na definici souřadnic jednotlivých polí. Šestiúhelníková pole nalezneme například ve hrách The Battle for Wesnoth a UniWar. Jelikož chceme, aby pro hráče byl odhad vzdálenosti přirozený, budeme mapu reprezentovat jako šestiúhelníková pole. Každému poli přiřadíme jednoznačnou dvojici souřadnic (x,y) tak, že souřadnice y určí horizontální půlvrstvu“ (do ” jednoho pole zasahují dvě, souřadnice y je ta nižší z nich) a souřadnice x vertikální vrstvu, viz obrázek 2.6. Sousední pole poté poznáme podle souřadnic, jejich definice se ale bude lišit v závislosti na tom, zda bude souřadnice y sudá, nebo lichá. Při takové definici souřadnic není sice na první pohled vidět, jak daleko jsou od sebe dvě různá pole, lze však rychle nalézt sousedy libovolného pole, což je vlastnost, kterou budeme potřebovat při hledání cesty.
2.3.2
Vlastnosti polí
Na herní mapě budou existovat různé druhy polí (dále budeme herní pole nazývat hex ). Při návrhu hexů se zaměříme na jejich význam a využití pro naši hru. Množství druhů hexů ale omezíme kvůli nižšímu výkonu zařízení a omezené paměti pro grafiku těchto hexů. Při návrhu těchto hexů se budeme inspirovat polemi z her The Battle for Wesnoth a Advance Wars. Hexy, které vytvoříme, můžeme rozdělit do dvou skupin. V jedné skupině budou hexy reprezentující přírodu a v druhé hexy reprezentující hráčovy objekty. Hlavní úlohou hexů s přírodou bude vytvoření vzdálenosti mezi hexy hráčů. Kdybychom žádnou přírodu neměli, všechny hexy hráčů by spolu sousedily. To 4
Zdroj: http://199.101.98.242/media/images/44298-Advance_Wars_2_-_Black_Hole_ Rising_(U)(Mode7)-8.png
15
y
1 2 3 4 5 6
x
1
2
Obrázek 2.6: Souřadnice při šestiúhelníkovém rozdělení však nemusí být pro každou mapu vhodné a proto chceme mít možnost vytvořit vzdálenost mezi budovami, kterou poté budou moci hráči využít ve své taktice. Přírodní hexy dále rozdělíme do dvou skupin. V jedné skupině budou hexy, které se budou chovat jako překážky – tyto hexy nebude hráč smět použít k cestování po mapě, nebudou tedy použity při hledání cesty. To nám umožní vytvořit například neprůchodné pohoří. Druhou skupinou pak budou hexy, které naopak hráč bude používat při hledání tras, takovými hexy mohou být například travnatá pole. Hexy s objekty hráče budou jedinými hexy, se kterými bude hráč přímo interagovat. Pro jednoduchost budeme tyto objekty dále v textu nazývat budovami. Každou budovu bude vlastnit nejvýše jeden hráč, pokud ji nebude vlastnit nikdo, bude budova neutrální. Jednotlivé budovy budou v závislosti na akcích hráčů měnit svého vlastníka. Každá budova bude hráči přidávat zdroje nebo dávat možnost útočit na budovy ostatních hráčů. Zbývá vyřešit, jak se budou budovy chovat při hledání cesty. V tomto případě jsme se rozhodli průchodnost povolit nebo zakázat v závislosti na vlastníkovi budovy. Pokud hráč bude budovu vlastnit, bude se pro něj budova počítat jako průchodné pole, v opačném případě jako neprůchodné. Každá budova bude mít také svou úroveň, kterou budou moci hráči zvyšovat a snižovat. Úroveň budovy bude ovlivňovat její vlastnosti, počet zdrojů které poskytne hráči a sílu útoků. Úrovně budov budou poskytovat alternativu k agresivní expanzi po mapě, což nabídne hráčům nové herní strategie.
2.3.3
Časová omezení
Aby jednotlivé zápasy probíhaly dostatečně rychle, bude potřeba zavést do hry časové limity. Pokud bychom žádné limity nezavedli, jeden z hráčů by mohl být dostatečně dlouho neaktivní, dokud se ostatní nevzdají. Dalším důvodem pro zavedení časových limitů je náš cíl, kterým je vytvořit tahovou strategickou hru, kde zápasy nepotrvají příliš dlouho. Zavedení omezení se ale potýká s různými problémy, které v následujících odstavcích rozebereme.
16
První z problémů nastane při zavedení simultánního odehrávání. Pokud oba hráči odehrají tah a ten se vyhodnotí současně, musíme časové limity zvýšit o nějaký interval, během kterého se hráči mohou seznámit s akcemi svých protihráčů. Tyto časové limity je potřeba nemít příliš krátké, jinak by hráč neměl dostatek času na toto seznámení, naopak příliš dlouhé limity by prodlužovaly hru a dávaly hráči nechtěný čas navíc. Abychom zabránili hráčům si prodlužovat takto svůj tah, během tohoto období zakážeme hráčům zadávat akce, hráči tedy budou moci pouze procházet mapu. Dalším problémem jsou vlastnosti přenosu dat po síti. Pokud dojde ke zpoždění přenosu, získá hráč čas navíc, který může využít k dalšímu plánování svého odehrávání. Dále, pokud hráč ukončí odehrávku tahu dříve, než mu vyprší časový limit, získá také čas navíc. Jelikož je vždy potřeba počkat na vyhodnocení tahu, hráči čas navíc ponecháme s tím, že nebude moci v tomto intervalu nijak ovlivnit stav hry. Časové limity je také potřeba správně navrhnout. Limity je možné udělat statické, nebo dynamické. Pokud bychom zavedli statické limity (tedy na každé kolo by hráč měl stejný čas), v některých fázích zápasu by tento limit mohl být příliš dlouhý, v jiných fázích naopak příliš krátký. Pokud zavedeme dynamické limity (čas na tah bude záviset na stavu zápasu), můžeme lépe respektovat současný stav zápasu. Je však potřeba rozhodnout, na čem dynamické limity budou záviset. Častým problémem tahových strategických her jsou také tzv. nekonečné“ ” hry, tedy hry, kde soupeři jsou vyrovnaní a ani jeden z nich nemůže získat převahu. Tento problém jsme se rozhodli vyřešit tak, že po určitém počtu tahů hra přejde do režimu rychlé smrti (anglicky sudden death“). V tomto režimu urych” líme hru tak, že omezíme hráčovy akce, v každém tahu zvýšíme dosah budov a zrušíme kontrolu průchodnosti budov. Jelikož zakážeme hráčovi získávat nové území a bude moci své území pouze ztrácet, budou mít hráči možnost zápas vždy dohrát do konce.
2.3.4
Suroviny
Tak jako ve většině strategických her budeme i v naší hře mít herní suroviny. Tyto suroviny budou hráči získávat z vlastních budov a budou je využívat k odehrávání tahů. Tento princip také přejímáme z her, kterými jsme se inspirovali. První surovinou bude zlato. Zlato bude hráč získávat na začátku každého tahu ze svých budov a bude ho potřebovat ke každé akci. Omezení akcí zlatem umožní hráči strategicky zlato šetřit a poté ho najednou využít, případně ovlivní hráčova rozhodnutí při expanzi po mapě. Druhou a poslední surovinou bude čas. Ten bude hráč získávat pouze ze svých velitelských středisek. Tento čas poté určí časové limity hráče na jeho tah. 5 Tímto vyřešíme problém z předchozí podkapitoly s časem na tah – jelikož má hráč na začátku málo budov, bude mít málo času na tah, až jich získá více, časové limity se mu prodlouží. Zavedením času jako suroviny zavádíme do hry nové prvky. Jelikož hráči čas získávají dynamicky v závislosti na stavu zápasu, mohou ovlivňovat čas na tah sebe i svých protihráčů. Mohou tak vznikat nové strategie, které budou založené 5
Toto je analogie času a generálů ve velitelských střediscích – čím více má hráč generálů, tím více lidí stráví čas nad vymýšlením strategií.
17
na ochromení hráče omezením jeho času, apod. Z tohoto faktu vyplyne i cíl naší hry: ukrást svému protivníkovi všechen jeho čas na tah. Cílem hry tedy bude zničit všechna velitelská střediska svých protivníků.
2.3.5
Tah a akce
Na závěr se podíváme, jak budou vypadat jednotlivé tahy hráčů. Také si ukážeme, jaké herní akce budeme implementovat a jak budou tyto akce fungovat. Herních akcí nechceme mít mnoho, abychom nemuseli vykreslovat příliš mnoho ovládacích prvků na malých displejích a mít tak složité ovládání hry. Jak už jsme uvedli dříve, průběh zápasu bude rozdělen na jednotlivé tahy. Každý tah bude omezen časovým limitem, který bude záviset na počtu velitelských středisek, které hráč vlastní. Dále bude hráče omezovat zlato, které bude vlastnit, jelikož pokud ho nebude mít dostatek, nebude moci provést některé akce. Tak, jako v Advance Wars dovolíme každému hráči provést nejvýše jednu akci s každou jeho vlastní budovou. Cena a účinnost akce bude záviset na úrovni hráčovy budovy. Každý hráč bude moci na každé budově použít jednu z pětí akcí: vylepšení, útok, oprava, zabrání a obnova morálky. Tyto akce nyní popíšeme podrobněji. Prvním typem akce bude vylepšení budovy. Tím se zvýší úroveň budovy a upraví se její vlastnosti. Každá budova bude na počátku na první úrovni a maximální úroveň budovy bude omezená. Další dvojicí akcí budou akce útok a oprava. Akci útok provede hráč na budovu jiného hráče, která bude v dosahu budovy, ze které útok vysílá. Cílovou budovu útok poškodí, pokud bude budova poškozená dostatečně, sníží se její úroveň. Pokud by mělo dojít ke snížení na úroveň 0, hráč o budovu přijde a budova se stane neutrální. Akce oprava naopak vlastní budovu opraví, tedy sníží hodnotu poškození. Posledními dvěma akcemi budou zabrání a obnova morálky. Tento pár akcí bude fungovat podobně, jako akce útok a oprava. Pokud hráč vyšle zabrání na protivníkovu budovu, sníží morálku obyvatel této budovy. Pokud morálka dostatečně klesne, vlastníkem budovy se stane útočící hráč. Morálku bude možné obnovit akcí obnova morálky. Na rozdíl od útoku, který budově sníží úroveň a jeho vyslání bude levné, akce zabrání bude stát více zlata a pokud hráč budovu zabere, budova zůstane na stejné úrovni jako před akcí. Aby byl důsledek každé akce vždy předem znám, je potřeba zavést jednoznačnou prioritu akcí. Aby měly akce jako jsou vylepšení nebo oprava smysl, provedou se vždy před akcemi útok a zabrání. Pokud by to bylo jinak, hráč by mohl přijít o budovu dříve, než by se akce provedla. Dále je potřeba si v kódu vždy pamatovat stav budovy na začátku tahu, jelikož z tohoto stavu je potřeba vy- Obrázek 2.7: Vyhodnocení cházet při útočení. Pokud by hráč napadl budovu B tahu 18
svého protivníka a snížil její úroveň, tak následný útok z budovy B by ve stejném tahu byl slabší. Průběh vyhodnocení akcí je znázorněn na obrázku 2.7. V poslední řadě je potřeba rozhodnout, komu připadne budova, pokud dva hráči vyšlou akci zabrat na stejnou budovu. Jednou z možností je nechat budovu hráči, který vyslal zabrat jako poslední – to by dávalo výhodu hráčům, kteří mají delší čas na tah a dávalo tomuto času další výhodu. Naopak, druhou možností by bylo zavést jakousi imunitu po prvním zabrání, tedy hráč, který úspěšně zabere budovu první, budovu získá. Dalším možným řešení je vyhodnotit útoky společně a budovu ponechat hráči, který provedl silnější útok. Toto řešení je férové, avšak neřeší situaci, kdy oba hráči vyšlou stejně silný útok. Nakonec jsme se pro jednoduchost rozhodli ponechat budovu poslednímu útočícímu hráči.
19
3. Analýza a implementace V předchozích kapitolách jsme popsali, jak bude probíhat naše hra a jaké nabídneme hráčům možnosti. V této kapitole si ukážeme, co budeme potřebovat implementovat pro zajištění slíbených funkcí hry a podíváme se, s jakými problémy se při implementaci potkáme. Budeme se zabývat vytvořením zápasu pro hráče a implementací hry pro více hráčů. Zároveň se podíváme, jak a která data ukládat, jak uživateli zobrazovat stav hraného zápasu a jak budeme využívat síťovou komunikaci. Tato pozorování dále využijeme při rozhodování o struktuře našeho kódu a knihovnách, které využijeme při implementaci.
3.1 3.1.1
Založení zápasu Herní místnost
Jedním ze způsobů, jak budou moci hráči začít hrát zápas, bude vytvoření vlastní herní místnosti (anglicky game lobby). Tuto místnost bude moci vytvořit kterýkoliv hráč. Při tvorbě místnosti hráčovi dovolíme nastavit parametry své herní místnosti. Hráč si tak bude moci svou místnost pojmenovat, vybrat herní mapu, délku časových limitů, apod. Jakmile jeden hráč založí herní místnost, ostatní hráči ji budou moci najít v seznamu herních místností. V tomto seznamu budou hráči mít také možnost místnosti filtrovat podle parametrů. Pokud najdou místnost, která jim vyhovuje, budou se moci do herní místnosti připojit. Až bude místnost plná, vlastník místnosti zahájí zápas. Zakládající hráč bude mít také možnost nastavit místnost jako soukromou. V takovém případě se tato místnost neobjeví v seznamu místností a jediný způsob, jak tuto místnost najít, je znát její přesné jméno. Takto se bude moci skupina hráčů domluvit a zahrát si společně zápas bez toho, aby ostatní hráči vstoupili do jejich místnosti.
3.1.2
Automatický zápas
Druhým způsobem bude automatické vytvoření zápasu. Hráč vstoupí do fronty, ve které poté párovací algoritmus bude hledat dvojice hráčů, pro které založí zápas. Aby nedocházelo k neférovým zápasům, budeme vytvářet pouze dvojice hráčů. Předejdeme tím tomu, aby více hráčů útočilo nejprve jen na jednoho hráče (a ten měl zkaženou hru), nebo v případě týmových zápasů mohl takový hráč kazit hru spoluhráči. Hlavním důvodem, proč tuto možnost pro hráče vytvoříme, je možnost nabídnout hráči vhodného protivníka. Založením vlastní herní místnosti totiž hráč nemůže snadno ovlivnit, jak zkušený protivník s ním bude hrát, případně se také může stát, že se do jeho herní místnosti nikdo nepřipojí. Pro některé hráče může být tedy možnost automatického vytvoření zápasů také rychlejší, než tvorba místnosti a čekání na protihráče.
20
Abychom mohli nalézt vhodnou dvojici hráčů, musíme mít nějaký způsob, jak ohodnotit hráčovu výkonnost. Takové ohodnocení poté budeme moci použít jak při párování hráčů, tak při vytváření statistik a při tvorbě herních místností. Pokud budou hráči ohodnocení, budou moci v herních místnostech odhadnout zkušenost jejich protivníků. Aby však nedocházelo k manipulaci s hodnocením, hodnocení bude ovlivnitelné pouze automaticky založenými zápasy. Při zakládání zápasů můžeme také nastavit parametry zápasu dle hodnocení hráčů. Takto můžeme například zvolit obtížnější herní mapu, nebo snížit časové limity hráčů. Hodnocení hráčů Při návrhu systému hodnocení hráčů je potřeba, aby takový systém dobře reprezentoval dovednosti těchto hráčů. Chceme, aby takový systém byl přesný, tedy hráč s výrazně vyšším hodnocením obvykle vítězil nad hráčem s menším hodnocením. Dalším naším požadavkem je schopnost hodnocení se přizpůsobit změnám. Pokud se hráč naučí lépe hru hrát a začne vyhrávat většinu zápasů, chceme, aby jeho hodnocení vyrostlo co nejdříve a aby ho systém znovu pároval s vhodnými protivníky. Nejrozšířenějším systémem pro hodnocení výkonnosti hráčů je systém Elo. Tento systém je známý svým použitím u šachových turnajů, avšak tento systém používají i některé hry ve svých žebříčcích.[6] Elo je systém, který každému hráči přiděluje jednu konkrétní hodnotu vyjadřující jeho výkonnost. Tato hodnota se poté mění v závislosti na tom, jak dopadly zápasy, které hráč odehrál. Pokud tedy vyhraje hráč s nižším Elo, jeho Elo se zvýší mnohem více, něž když vyhraje jeho soupeř. Hlavní problémem systému Elo je nastavení hodnoty koeficientu rozvoje. Tento koeficient ovlivňuje změnu Elo hráčů a bývá neměnný.[7] Při příliš vysoké hodnotě koeficientu rozvoje se hodnota Elo mění příliš rychle, při příliš nízké naopak příliš pomalu. Tento nedostatek se snaží řešit systémy Glicko[8] a TrueSkill [9]. Oba tyto systémy přiřazují každému hráči dvě hodnoty namísto jedné: dovednost a odchylku. Hodnota dovednosti odhaduje přibližnou hráčovu výkonnost, tak jak k tomu je v systému Elo. Nová hodnota odchylky naopak říká, jak přesný tento odhad je, čím vyšší odchylka, tím více je odhad nepřesný. Tato odchylka se poté využívá při změně hodnocení – čím je odchylka vyšší, tím více se změní hráčovo hodnocení. Velkou výhodou přidání odchylky je její dynamika. Odchylku potom snížíme vždy při změně hodnocení, jelikož víme více o hráčově výkonnosti a tedy se méně lišíme. Odchylku můžeme také zvyšovat, ať už při dlouhé neaktivitě hráče, při neočekávaném výsledku hry (hráč, který měl skoro jistě prohrát, vyhrál) apod. Náš systém hodnocení jsme se rozhodli založit právě na systému TrueSkill, který má popsán i princip hledání soupeře, který budeme také potřebovat. Hodnotu odchylky upravíme vždy na konci zápasu v závislosti na tom, jak se výsledek zápasu lišil od našich očekávání – pokud hráč, který měl s velkou pravděpodobností vyhrát, vyhrál, odchylka se příliš nezmění, v opačném případě bude změna markantnější. Odchylku pak zvýšíme na začátku každého zápasu vždy o nějakou konstantu (abychom ji udrželi nenulovou a vždy docházelo k růstu odchylky) a dále v závislosti na neaktivitě hráče. Díky tomu bude mít hráč, který dlouho
21
žádný zápas nehrál a jehož dovednosti se mohly změnit, vyšší odchylku a tedy se jeho hodnota výkonnosti bude rychleji měnit. Hledání protihráče Pro zavedení automatického párování hráčů nám ještě zbývá navrhnout algoritmus, který ze skupiny hráčů vybere dvojici hráčů, pro které vytvoří zápas. Algoritmus založíme na předpokladu, že hráčů nebude ve frontě nikdy mnoho najednou a tedy můžeme proti sobě porovnávat všechny dvojice hráčů. Při porovnávání dvou hráčů použijeme jejich hodnoty dovedností a odchylky. Z těchto hodnot spočítáme pro oba hráče očekávanou kvalitu[9] zápasu, tedy jak moc si myslíme, že bude zápas vyrovnaný. Tato hodnota závisí primárně na rozdílu dovedností, dále je poté nižší, pokud je odchylka hráčů vysoká (nevěříme našemu odhadu). S touto hodnotou budeme dále pracovat při výběru vhodné dvojice soupeřů. Dvojici hráčů budeme považovat za vhodnou, pokud kvalita jejich společného zápasu bude dostatečně vysoká. Tuto dolní hranici určíme v závislosti na tom, jak dlouho jsou hráči ve frontě. Takto nalezneme i pro hráče, který je dlouho ve frontě, alespoň nějaký zápas. Pokud hráč takový zápas nechce, může po nějakém čase opustit frontu.
3.2 3.2.1
Komunikace mezi hráči Model komunikace
Pro hraní hry pro více hráčů, kde každý hráč používá jiné zařízení, je potřeba zajistit komunikaci mezi jednotlivými zařízeními. Jelikož chceme zavést simultánní odehrávání, nemůžeme hru pro více hráčů implementovat na jednom zařízení. Herní aplikace tedy bude potřebovat komunikovat s instancí aplikace na ostatních zařízeních. V následujících odstavcích si popíšeme vlastnosti dvou modelů komunikace: vzájemné komunikace mezi sebou navzájem (peer-to-peer), a komunikace za pomoci neustále dostupné třetí strany (klient-server). Jedním modelem komunikace mezi klienty je model peer-to-peer. V tomto modelu komunikují zařízení mezi sebou navzájem s tím, že každé posílá data tomu protějšku, který je potřebuje. Požadavkem pro funkčnost tohoto modelu je znalost síťových adres všech zařízení, se kterými bude naše zařízení potřebovat komunikovat. Výhodou peer-to-peer modelu je právě přímé posílání dat mezi klienty, které nemusí jít přes žádného prostředníka, tedy komunikace má nižší latenci. Nevýhodou je právě potřeba znalosti adresy protějšku. Mobilní zařízení obvykle nemají veřejnou a neměnnou síťovou adresu, popřípadě jsou v lokální síti a nemají přesměrované porty. Peer-to-peer komunikaci tedy využít nemůžeme. Dalším modelem komunikace je model klient-server. V tomto modelu existuje jedno vyhrazené zařízení nazývané server, ostatní nazýváme klient. Úkolem serveru je poté zprostředkovávat komunikaci mezi jednotlivými klienty. Každý klient má vlastní komunikační kanál se serverem, server potom data od klienta zpracovává a na základě toho informuje ostatní klienty.
22
Obrázek 3.1: Znázornění obou modelů komunikace, nalevo model peer-to-peer, napravo model klient-server 1 Klient v modelu klient-server tedy nemusí znát síťové adresy ostatních klientů, což je velká výhoda tohoto modelu. Pokud mají navíc klienti překládané adresy, tento model bude také fungovat. Z herního hlediska je další výhodou možnost pamatovat si stav hry na serveru, čímž lze zajistit konzistenci stavu zápasu (stav zápasu bude mít uložený centrální autorita). Nevýhodou modelu klient-server je pak vyšší latence, jelikož všechna komunikace musí být směrována právě přes server. Dalším problémem je potřeba existence samotného serveru, který musí mít dostatečný výkon na zvládání většího množství klientů. Některé hry poté používají různé hybridní modely, jako například hry pro více hráčů v oblíbené hře Counter-Strike[H12].[10] V této hře se při založení zápasu jeden z klientů, jehož zařízení nemá překládané adresy, stane serverem a v průběhu zápasu je právě tento klient hlavní autoritou. Přesunutím simulace hry na takového klienta se vyhneme simulaci zápasu na samotném herním serveru. V takovém případě by náš server nemusel provádět tolik výpočtů. Díky tomu by se mohly snížit požadavky na jeho výkon a výsledná cena jeho provozování. Jelikož mobilní zařízení nemusí mít vždy veřejnou síťovou adresu, musíme využít klient-server model. Tím také využijeme server, který bude hlavní autoritou a bude zajišťovat konzistenci stavu hry.
3.2.2
Komunikační protokol
Komunikace mezi klientem a serverem bude probíhat v síti Internet. Na síťové vrstvě budeme komunikovat pomocí protokolu IP, který je podporován našimi cílovými zařízeními. Na transportní vrstvě můžeme použít buď protokol TCP, nebo UDP. 1 Obrázek vznikl ze dvou obrázků, zdroj levého obrázku: https://upload.wikimedia. org/wikipedia/commons/thumb/3/3f/P2P-network.svg/440px-P2P-network.svg.png; zdroj pravého obrázku: https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/ Server-based-network.svg/220px-Server-based-network.svg.png
23
Transportní protokol UDP lze použít pro jednoduchý, nespojovaný a nespolehlivý provoz. Hlavička datagramů UDP obsahuje pouze zdrojový port, cílový port a délku dat.[11] Hlavička má dále ještě rezervované dva bajty pro kontrolní součet, ten je však pouze volitelný. Pomocí protokolu UDP tedy můžeme přenášet bloky dat, segmentaci dat si musíme zajistit sami. Dále protokol UDP nezajišťuje spolehlivý přenos, datagramy tedy nemusí příjemce obdržet a musí existovat mechanismus opravy dat. Protokol UDP má významné využití u aplikací používajících model peer-to-peer, jelikož jeden klient může přijímat datagramy od různých protějšků a poté je sám skládat. Dále je UDP často využíváno v aplikacích, které streamují zvuk a video. U těchto aplikací má větší význam latence a pokud při přenosu přijdeme o některé datagramy, můžeme při přehrávání tato data přeskočit a průběh přehrávání tak nepřerušit. Protokol TCP je naopak složitější a zajišťuje spojovaný a spolehlivý přenos. V protokolu TCP je nejprve navázáno spojení a pomocí tohoto spojení následně zařízení komunikují. Na rozdíl od UDP, protokol TCP vytváří přenosový kanál – přenáší se tedy proud dat namísto datagramů. V TCP se data dělí na pakety, toto dělení ale zajišťuje samotné TCP. Hlavička paketů v TCP je také složitější, nalezneme v ní například číslo segmentu, offset dat a kontrolní součet.[12] Díky těmto údajům může TCP zajišťovat spolehlivý přenos dat, tedy aplikace se může spolehnout, že pokud odešle data, tak dorazí vždy ve správném pořadí a beze změny. Použití protokolu UDP by se mohlo zdát na první pohled vhodnější, jeho použití by nám ale přineslo nové problémy. Jelikož jsou datagramy protokolu UDP odesílany samostatně, musel by server u každého datagramu zvlášť rozlišovat jeho původ a klienta, který ho odeslal. Použitím protokolu UDP bychom tak museli v každém datagramu posílat jednoznačnou identifikaci klienta, aby server věděl, který klient mu datagram poslal. Dalším problémem UDP je možnost vypršení záznamu v tabulce pro překlad adres. Pokud by toto nastalo, neměl by server žádnou možnost poslat zprávu klientovi, musel by počkat, až s ním klient naváže nové spojení. Z těchto důvodů použijeme pro přenos protokol TCP. Využití TCP nám poskytne také automatické kontroly přenesených dat a vyžádání ztracených dat. Nevýhodou TCP je pro nás větší velikost paketů, jelikož mají hlavičky bloků TCP větší velikost.
3.3
Persistence
V této sekci se podíváme, jaká data budeme ukládat, jaká naopak ukládat nebudeme a rozhodneme, v jakém formátu budeme tato data ukládat. Na závěr poté vybereme konkrétní technologii pro ukládání dat. Víme už, že budeme mít aplikaci rozdělenou na dvě části, a potřebujeme rozhodnout, kde a jak budeme ukládat data. Některá data je vhodnější ukládat perzistentně na disk (tato data poté přežijí restart serveru) a některá je vhodnější mít pouze v paměti programu. Protože o stav hry a o všechny uživatele se stará server, budeme potřebná data ukládat právě na serveru. Při rozhodování, jaká data budeme ukládat na disk, se budeme dívat, jestli ukládaná data budou potřeba někdy později. Uložená data pro nás musí mít využití, jelikož diskový prostor serveru je omezený. 24
Obrázek 3.2: Diagram znázorňující rozdíl mezi komunikací v protokolech TCP a UDP Rozhodli jsme se na disk ukládat informace o hráčích, jako jsou jejich přezdívky a osobní údaje. Tyto údaje budeme potřebovat pro identifikování hráčů při každém novém připojení klienta na server. Dále budeme ukládat souhrn každého proběhlého zápasu. Ukládání souhrnu o zápasech můžeme poté využít k vytváření statistických dat, jako žebříčky hráčů, nebo vytíženost serveru v závislosti na čase. Mezi data, která nebudeme ukládat, bude patřit stav jednotlivých zápasů, herních místností, připojených hráčů, apod. Tato data se budou často měnit a jejich ukládání by vyžadovalo častý přístup k disku. Diskové operace jsou ale řádově pomalejší, než práce s daty v paměti, ukládáním těchto dat bychom přišli o výkon serveru. Ke ztrátě těchto dat by sice mohlo dojít při výpadku serveru, četnost těchto výpadků však bývá nízká.2 Další otázkou je způsob ukládání dat. Existují různé formáty a způsoby pro 2
Například hostingový poskytovatel WEDOS má garanci dostupnosti 99,99%
25
ukládání dat. Jednou z možností je ukládat data jako text. Výhodou takto uložených dat je jejich snadná čitelnost pomocí textových editorů. Nevýhodou je poté velikost těchto dat.3 Další možností je použít nějaký binární formát. Data v binární podobě mohou využívat méně diskového prostoru, musíme však nejprve formát nějak definovat. Problémem obou těchto možností je potřeba mít schopnost data přečíst, zpracovat a umět v nich vyhledávat a třídit. Alternativou je použít databázi, která vhodným ukládáním dat a metadat zajišťuje rychlé vyhledávání a třídění dat. Použití databáze vyžaduje využít jak databázový software, tak použít jazyk, který umožňuje komunikaci s databází, případně i knihovnu pro práci s touto databází. Použití databáze usnadňuje návrh ostatních částí kódu a nabízí rychlé a pohodlné vyhledávání v datech. Další výhodou je možnost využít transakce, které nám zaručí konzistenci dat při složitějších operacích. Z těchto důvodů použijeme pro ukládání našich dat na serveru právě databázi. Zbývá nám vybrat konkrétní databázi. Porovnáme tedy trojici relačních databází, SQLite, MySQL a PostgreSQL a vybereme si pro nás tu nejvhodnější.[13] První relační databází je SQLite. SQLite je pouze knihovna, která implementuje část jazyka SQL a operuje nad soubory. Výhodou této databáze je její jednoduchost a rychlost, právě kvůli přímé komunikaci se souborem. Nevýhodou je nemožnost vytvářet více uživatelů a absence zabudované komunikace s databází po síti. Druhou databází je MySQL. Jedná se o open-source implementaci relační databáze, která je hojně využívána ve webových aplikacích. Díky své popularitě existuje velké množství dalších nástroju pro usnadnění práce s touto databází. Její výhodou je jednoduchost, rychlost a možnost přidat zabezpečení dat. Nevýhodou mohou být chybějící funkce a nižší spolehlivost některých funkcionalist. Třetí databází, kterou si představíme, je PostgreSQL. PostgreSQL je pokročilá open-source databáze, jejíž hlavním cílem je naplnění standardu SQL. PostgreSQL také nabízí kompletní podporu pro spolehlivé transakce. Další výhodou této databáze je také snadná rozšířitelnost. Hlavní nevýhodou PostgreSQL je nižší výkon při náročném čtení a menší oblíbenost. Kvůli menší oblíbenosti poté není PostgreSQL tak rozšířená a někteří poskytovatelé služeb tuto databázi neposkytují. Jedním z možných kroků v budoucnu by mohl být přesun databáze na jiný stroj a sdílet ji mezi různými servery. Z tohoto důvodu se vyhneme použití databáze SQLite. Ze zbývajících dvou jsme se rozhodli pro MySQL kvůli její snazší dostupnosti u poskytovatelů služeb.
3.4
Vykreslování
Významnou částí aplikace bude také vykreslování herní mapy, objektů na mapě a ovládacích prvků uživateli. Toto vykreslování bude probíhat v klientské části aplikace, tedy na zařízeních hráčů. Některé prvky bude stačit vykreslit pouze jednou při startu zápasu, některé však bude potřeba neustále překreslovat. V následujících odstavcích si popíšeme, jak budeme vykreslovat informace o hře, 3
Například pro uložení čísla 2,147,483,647, což je maximální hodnota čtyřbajtového datového typu integer, potřebujeme 10 číslic.
26
herní mapu, ovládací prvky a uživatelské rozhraní. Jednou částí vykreslování bude zobrazení informací o zápasu. Mezi informace o zápasu zařadíme počet zlata hráče, číslo tahu, zbývající čas do konce tahu a zbývající počet hráčů ve hře. Tyto informace se nebudou často měnit a pro zobrazení nám postačí pouze text, tuto část obrazovky tedy můžeme vykreslit pomocí komponent systému Android, jako jsou TextView a ImageView. Příklad vykreslení pomocí těchto komponent je vidět na obrázku 3.3.
Obrázek 3.3: Možný vzhled lišty s informacemi o probíhajícím zápase Dalšími prvky, které budeme potřebovat vykreslit, jsou ovládací prvky. Ovládacími prvky myslíme způsob, jakým hráč bude zadávat akce, které chce v tahu vykonat. Těmito prvky budou převážně jednoduchá tlačítka s obrázky a textem. Pozice a zobrazená tlačítka se sice mohou měnit, k tomu však bude docházet jen při změně označené budovy. Pro vykreslení těchto prvků nám tedy také postačí komponenty Androidu, což budou Button a LinearLayout, popřípadě RelativeLayout. Už tedy umíme zobrazit hráči informace o hře a hráč může zadávat akce, zbývá nám zobrazit mapu a navrhnout, jak hráčům dovolíme opustit hru nebo ukončit tah dříve (aby hra probíhala rychleji) tak, aby hráči při rychlejším ovládání omylem tyto operace nespustili. Vykreslování herní mapy musí probíhat dynamicky za pomocí textur a obrázků, protože hexy, které hráč uvidí na mapě, se budou lišit v závislosti na stavu hry. Mapu bude tedy potřeba skládat z různých částí až za běhu a často ji během tahu překreslovat, abychom hráči mohli například zobrazit vybranou budovu nebo cíl jeho akce. Herní mapu můžeme vykreslovat pomocí překreslování obrázku využitím komponenty ImageView a třídy Canvas systému Android. Druhou možností je použít GPU a vykreslovat pomocí grafického API, ať už OpenGL ES, nebo nějaké grafické knihovny. Konkrétní použitou technologii popíšeme detailněji v příští kapitole. Opuštění hry, ukončení tahu a další operace, které hráči nebudou často potřebovat a nesouvisí přímo s odehráváním tahů, schováme do vysunovacího menu. Toto menu zobrazíme uživateli vysunutím buď při posunu prstem z okraje obrazovky nebo při stisknutí tlačítka zpět. Změnou výchozího chování tohoto tlačítka také zabráníme uživateli omylem ukončit naši herní aplikaci. Ostatní ovládací prvky v aplikaci, jako jsou herní menu, seznam herních místností, herní místnost, apod. jsou neměnné. Pro jejich vykreslení tedy použijeme standardní komponenty systému Android.
3.5
Záznamy dat
Rozdělení funkčnosti programu mezi klienta a server pro nás znamená, že někde bude muset existovat běžící server, na kterém poběží neustále instance naší aplikace. Tuto běžící instanci ale nemůžeme v reálném čase sledovat a při běhu 27
této instance mohou nastávat problémy, které je potřeba nějak sdělit administrátorovi. Mezi tyto problémy patří nejen chyby v našem programu, nýbrž i problémy jako jsou nedostatek paměti v systému nebo místa na disku. Z toho důvodu bude potřeba na serveru ukládat záznamy o jeho běhu a zapisovat veškeré neobvyklé situace a co k nim vedlo. Na serveru tedy zavedeme logování do souborů. Souborů budeme mít více v závislosti na závažnosti problému, který se vyskytl. Tomu je potřeba přizpůsobit i kód serveru a všechny informace logovat do těchto souborů. Logovat je možné i u klienta, to však není tak nutné. Z většiny výjimečných stavů se musíme zotavit u uživatele, protože nemůžeme od klienta snadno získat záznam o chybě a navíc nechceme, aby klient poznal, že nastala chyba a on ji mohl zneužít. V případě kritických chyb, kdy aplikace tzv. spadne“, můžeme ” využít funkce systému Android a dovolit uživateli poslat záznam o chybě přímo nám.4
4
Služba která toto zajišťuje, se nazývá Google Feedback a mimo zasílání chyb také umožňuje uživatelům zasílat ostatní zpětnou vazbu přímo na účet Google, který je spojený s aplikací v obchodě Google Play.
28
4. Použité knihovny V předchozí kapitole jsme si ukázali, jaké prostředky potřebujeme použít při implementaci naší hry. Nyní si představíme použité knihovny v naší aplikaci a popíšeme si jejich použití. Také si ukážeme možné alternativy těchto knihoven a srovnáme jejich vlastnosti. Nejprve se podíváme, k čemu slouží podpůrné knihovny pro Android. Poté se podíváme na knihovnu Commons Math. Dále si vybereme knihovnu pro práci s databází a knihovnu pro logování. Na závěr si popíšeme, jaký používáme přístup při vykreslování herní mapy.
4.1
Android a podpůrné knihovny
Jednou sadou použitých programů jsou nástroje pro Android, které nám dovolují přeložit kód pro systém Android, ladit běžící aplikace a číst logy systému. Tyto nástroje jsou součástí balíčku Android SDK [14], který je volně k dispozici na internetu. Pro snazší používání nástrojů dále používáme pluginy pro integraci Android SDK do IDE Eclipse. Dále používáme podpůrné knihovny pro systém Android.[15] Tyto knihovny implementují některé funkcionality pro starší verze systému, které byly přidány až v nových verzích systému. Takto můžeme používat funkce z nejnovějších verzí Androidu (například vysunovací menu) a stále mít aplikaci kompatibilní se staršími verzemi systému.
4.2
Commons Math
Commons Math[16] je knihovna implementující matematické a statistické funkce chybějící ve standardních knihovnách jazyka Java. Tuto knihovnu používáme na serveru při výpočtu hodnocení hráčů. Pro výpočet pravděpodobnosti výhry totiž používáme chybovou funkci, která ve standardní knihovně Javy chybí.
4.3
Logování
Rozhraní pro logování na našem serveru vybíráme podle toho, jak náročné je tuto rozhraní používat, jestli nám umožňuje pohodlně logovat do více souborů zároveň a jak obtížné je změnit nastavení logování a výstupů u zkompilované binárky. V následujících odstavcích se podíváme na tři možné alternativy. První alternativou je rozhraní dostupné přímo v jazyce Java v balíčku java.util.logging, druhou knihovna slf4j a třetí knihovna log4j. První nabízenou možností je rozhraní poskytované třídami v balíčku java.util.logging[17]. Tyto třídy jsou součástí standardní knihovny jazyka. Výhodou této implementace je jednoduchost použití a právě dostupnost v samotném jazyce. Velkou nevýhodou je pro nás naopak chybějící možnost jednoduše změnit nastavení výstupu logování a chybějící podpora logování do více souborů. To je proto, že nastavení výstupu se definuje pomocí statických proměnných tříd, pro vytvoření dalšího souboru bychom tedy museli podědit třídu tohoto rozhraní, 29
což je poměrně velký zásah do kódu. Takto vypadá konfigurace výstupu logování do souboru v tomto frameworku: java.util.logging.FileHandler.level java.util.logging.FileHandler.filter java.util.logging.FileHandler.formatter java.util.logging.FileHandler.encoding java.util.logging.FileHandler.limit java.util.logging.FileHandler.count java.util.logging.FileHandler.append java.util.logging.FileHandler.pattern
= WARNING = = = = = = false = log.%u.%g.txt
Další alternativou je knihovna slf4j [18]. Tato knihovna je velmi modulární a to proto, že programátorovi poskytuje pouze API pro logování. Knihovna tedy slouží jako fasáda. Samotné logování pak zajišťují další knihovny, které lze přidat až v době spouštění programu. Velkou výhodou této knihovny je právě velká modularita, nevýhodou je potřeba zajišťovat další knihovny, tedy samotná knihovna poskytuje pouze nekompletní řešení. Jednotlivé komponenty potřebné pro logování při použití slf4j ukazuje obrázek 4.1.
Obrázek 4.1: Komponenty potřebné k logování při použití slf4j Další logovací knihovnou je log4j [19]. Tato knihovna pochází z dílny Apache a je volně k dispozici ke stažení. Knihovna obsahuje velké množství funkcí, jako je změna konfigurace logování za běhu (opětovným načtením konfiguračního souboru), podporu lambda funkcí nebo možnost zavedení vlastních logovacích úrovní. Další předností knihovny je přehledná dokumentace. Nevýhodou log4j v porovnání s knihovnou slf4j je právě obtížná náhrada knihovny za jinou, pokud bychom chtěli změnit logovací knihovnu. Logování v této knihovně vypadá následovně: private static final Logger logger = LogManager.getLogger("Server"); logger.debug("Počet právě připojených klientů: {}", clients);
Právě kvůli snadné změně výstupu logování, možnosti zavedení více souborů s různými úrovněmi logování a dobrým tutoriálům jsme se rozhodli použít knihovnu log4j.
4.4
Databáze
Pro ukládání dat jsme se rozhodli použít databázi a potřebujeme tedy nějakou knihovnu pro práci s ní. Podíváme se na API JPA, framework Hibernate a framework MyBatis.[20]
30
Nejprve se podíváme na Java Persistence API [21]. Toto API je součástí Java EE a poskytuje rozhraní pro práci s databází. JPA však neimplementuje samotnou komunikaci s databází, tu musí zajišťovat jiná, konkrétní knihovna. Součástí definic JPA jsou také entity, transakce a Java Persistence Query Language, který abstrahuje dotazovací jazyk od konkrétní databáze. Hlavní nevýhodou JPA je absence definic některých funkctionalit, jako například cachování. Tyto funkcionality však mohou být obsažené v konkrétní implementaci. Jedním z frameworků, který implementuje JPA, je framework Hibernate[22]. Tento framework implementuje kromě funkcí JPA také cache, vlastní dotazovací objektově-orientovaný jazyk nazývaný HQL, a další. Konfigurace tohoto frameworku využívá formát XML. Jelikož Hibernate implementuje JPA, je založený na objektovém přístupu a je vhodný, pokud máme možnost přímo mapovat databáze přímo na objekty.1 Následuje příklad konfiguračního souboru pro práci s objektem Employee a související databázovou tabulkou:
<property name="EmpFirstname" column="emp_firstname" type="string" not-null="true" length="30" /> <property name="EmpLastname" column="emp_lastname" type="string" not-null="true" length="30" />
Na opačné straně poté stojí framework MyBatis[23]. Ten naopak dává programátorům plnou kontrolu nad SQL dotazy a přidává persistenční vrstvu, není tedy potřeba mít veškeré parametry komunikace s databází napevno v kódu. Framework MyBatis je vhodné použít, pokud nemáme kontrolu nad databází a objekty aplikace a nemůžeme tedy provést přesné mapování objektů na tabulky. Díky možnosti psát vlastní SQL dotazy také dovoluje napsat lépe optimalizované dotazy. V konfiguraci frameworku MyBatis poté definujeme konkrétní dotazy, které bude aplikace používat. Příklad konfigurace pro čtení dat z databáze: <select id="selectEmpId" parameterClass="int" resultClass="Employee"> select emp_id as id,emp_firstname as firstName, emp_lastname as lastName from EMPLOYEE where emp_id= #id#
Jelikož máme plný přístup k databázi i k aplikaci, můžeme využít mapování objektů. Rozhodli jsme se tedy použít framework Hibernate, který nám usnadňuje mapování objektů a v případě složitějších dotazů nám nabízí svůj jazyk HQL. 1
Tyto objekty se nazývají POJO (Plain Old Java Object). Hibernate mapování využívá právě objekty, které mají veřejný bezparametrický konstruktor a ke každému atributu, který se ukládá, existuje veřejná metoda get a set.
31
4.5
Vykreslování mapy
Jelikož jsme chtěli mít vykreslování mapy jednoduché, v první implementaci jsme použili třídu Canvas[24], která je dostupná přímo v Androidu. Mapu jsme vykreslovali tak, že jsme vytvořili obrázek s pozadím a následně jsme za pomocí třídy Canvas na obrázek kreslili jednotlivé hexy. Celou mapu jsme tedy měli v paměti uloženou jako jeden obrázek, který se překresloval jen při nějaké změně. Tento obrázek se poté zobrazil hráči a posouváním obrázku se hráč mohl posouvat po mapě. Výhodou tohoto přístupu byla jednoduchost kódu a menší velikost výsledné aplikace, protože nebyla potřeba žádná další externí knihovna. S postupem času se ale začaly objevovat u této implementace problémy. První problém vycházel přímo z návrhu vykreslování. Protože překreslujeme jen při nějaké změně, nebyla možnost vytvořit žádnou jednoduchou animaci či plynulý pohyb. Tento problém byl nejvíce znát při zvětšování a zmenšování mapy. Jediná možnost, jak velikost změnit, bylo překreslit celý obrázek od začátku s jinou konkrétní velikostí. Nejvýznamnějším problémem však byla rychlost překreslování a s tím související latence. Jakmile jsme do hry přidali více různých druhů obrázků, které jsme vykreslovali, doba překreslování se začala zvyšovat. Pokud pak hráč vybral budovu a kolem té jsme chtěli nakreslit rámeček, hráč si mohl všimnout pomalé odezvy a aplikace mu mohla připadat pomalá. Z těchto důvodů jsme se rozhodli, že bude potřeba využít grafický procesor v zařízeních a vykreslovat pomocí OpenGL, které Android podporuje. Nové vykreslovaní jsme mohli implementovat buď pomocí samotného OpenGL nebo využít knihovnu, která nám vytvoří abstrakci. Android má zabudovanou podporu pro OpenGL ES, kterou bychom mohli použít. To by nám dalo možnost využít grafické procesory zařízení. Byli bychom nuceni ale vykreslovat pomocí OpenGL API, což znamená, že na místo původního jednoho volání na vykreslení obrázků na konkrétní souřadnice bychom museli vytvořit mnohoúhelníky, správně aplikovat transformační matice, shadery, apod. Používáním nízkoúrovňového API bychom mohli zvýšit grafický výkon, ten však pro naši aplikaci nepotřebujeme. Proto jsme se rozhodli použít knihovnu, která nám poskytne vysokoúrovňové API pro 2D vykreslování. Dalším kritériem pro výběr byla potřeba knihovnu zakomponovat do existujícího projektu. Proto jsme tedy hledali knihovnu, která by šla zakomponovat do projektu bez nutnosti široce měnit strukturu kódu. Pro toto vykreslování jsme našli dvojici volně dostupných knihoven poskytujících API v jazyce Java. Tyto knihovny se nazývají AndEngine[25] a libGDX [26]. Předností libGDX však byla velmi přehledná dokumentace2 a nabízená metoda initializeForView, díky které jsme mohli snadno vyměnit Canvas za komponentu používající právě grafickou akceleraci. Z tohoto důvodu jsme nakonec při vykreslování začali používat právě knihovnu libGDX a implementovali nové vykreslování použitím této knihovny.
2
Tutoriál je dostupný ve wiki u repozitáře na adrese https://github.com/libgdx/libgdx/wiki
32
5. Prototyp serveru V předchozích kapitolách jsme popsali návrh naší hry a jaké možnosti nabídneme hráčům. Dále jsme si rozebrali, co potřebujeme při implementaci kódu a jaké knihovny použijeme. V této kapitole si popíšeme náš herní server. Nejdříve si popíšeme, co od serveru potřebujeme. Poté navrhneme rozhraní pro zpracovávané úlohy a hlavní smyčku serveru. Na závěr kapitoly tento server otestujeme abychom zjistili, kolik klientů zvládne server obsluhovat zároveň.
5.1
Funkce serveru
Začneme výčtem funkcí serveru, na jejichž základě následně navrhneme strukturu a fungování serveru. Server musí zajišťovat tyto funkce: • navazujovat spojení s klienty • zajišťovat autentizaci asociaci hráčského účtu s konkrétním spojením • přijímat požadavky od klientů a zpracovávat je • rozesílat notifikace konkrétním klientům při výjimečných událostech (např. odpojení hráče ze hry) • spravovat běžící zápasy a herní místnosti a simulovat průběh těchto zápasů • párovat hráče, kteří jsou ve frontě Server tedy bude potřebovat vykonávat různé úlohy. Navrhneme si rozhraní, které budou jednotlivé úlohy implementovat a server bude moci pouze volat metody těchto úloh.
5.2
Rozhraní Task
Rozhraní Task bude základní rozhraní, které budou implementovat všechny třídy, která reprezentují nějakou úlohu serveru. K tomuto rozhraní poskytneme i výchozí implementaci ve formě abstraktní, bude tedy stačit, aby implementující třída přetížila jen potřebné metody. Při návrhu tohoto rozhraní nesmíme zapomenout, že většina úloh bude zpracovávat požadavky, které chodí po síti. Zdržením přenášených dat od klienta by server musel čekat, než dorazí všechna data potřebná pro zpracování úlohy. Chtěli bychom tedy rozhraní navrhnout tak, aby bylo možné zpracování úlohy odložit, pokud nebude mít server dostatek dat od klienta pro její zpracování. Úlohy jednotlivých klientů budeme asociovat s jejich spojením. Příchozí data od klienta tak budeme moci přímo předat úloze, která byla předtím odložená. Rozhraní jednotlivých úloh bude tedy vypadat takto:
33
public interface Task { public void loadNext(); public boolean canPrepare(); public void prepare(); public boolean isPrepared(); public boolean canCall(); public Void call(); }
Metoda loadNext() bude implementovat načtení příchozích dat do interního bufferu, tu zavoláme pokaždé, když přijdou nová data od klienta. Z rozhraní je také patrné, že každá úloha bude zpracovávána ve dvou částech. První část, nazývaná prepare(), bude sloužit k vypočítání, jaké množství dat potřebujeme přijmout. Některé úlohy totiž mohou vyžadovat různé množství dat, například úloha, která bude zpracovávat herní akce zahrané hráčem v jednom tahu. Velikost těchto dat si tedy pošleme na začátku a v metodě prepare() tak zjistíme, jak velká data bude potřebovat úloha k zpracování. Vlastní kód úlohy bude v přetížené metodě call(). V této metodě úloha načtená data deserializuje, zpracuje požadavek a pošle klientovi odpověď. Úlohy, které probíhají pouze na serveru (např. párování hráčů) tedy implementují pouze metodu call(). Kontrolní metody canCall() a canPrepare() budou sloužit pro zjištění, jestli je v bufferu dostatek dat pro zpracování. Průběh zpracování úlohy implementující rozhraní Task znázorňuje diagram 5.1.
Obrázek 5.1: Diagram ukazující volání metod rozhraní Task. Žlutá buňka odložení znázorňuje rozhodnutí serveru odložit zpracování na později.
34
5.3
Hlavní smyčka serveru
Už máme navržené rozhraní pro zpracování úloh, popíšeme tedy hlavní smyčku serveru. Průběh hlavní smyčky serveru je znázorněn na diagramu 5.2.
Obrázek 5.2: Diagram ukazující průběh hlavní smyčky serveru. Některé úlohy na serveru musíme pouštět opakovaně. Takovou úlohou je například algoritmus zajišťující párování hráčů. V první části smyčky se rozhodneme, které lokální úlohy potřebujeme znovu spustit, a tyto úlohy spustíme. Po provedení těchto úloh zavoláme na soketu metodu select(). Tato metoda se volá s parametrem timeout, který určuje maximální dobu čekání na data. Pokud tedy žádná data dostupná nejsou, vlákno serveru se na určitý okamžik uspí, maximálně však do doby, než přijdou nová data. Díky tomu můžeme server na krátkou dobu pozastavit, pokud není žádná práce pro server. Hodnotu parametru timeout nesmíme však zvolit příliš vysokou, jinak by se server mohl uspat na příliš dlouhou dobu a některé z periodických úloh by se nespustily včas. Volání metody select() vrátí seznam kanálů (SocketChannel). Jednotlivé kanály poté procházíme a zpracováváme. Volání select vrací jak nově otevřené kanály, tak i kanály, které obdržely nová data. Pro každý nově otevřený kanál vytvoříme instanci třídy Connection. Tuto třídu použijeme k ukládání metadat o kanálu. Takto si můžeme u každého spojení pamatovat právě zpracovávanou úlohu, nebo přihlášeného hráče. Pro kanál, který jsme otevřeli už dříve, budeme zpracovávat úlohy. O vytvoření úlohy se postará třída Connection voláním metody getNextTask(), která vytvoří buď novou úlohu, nebo vrátí předtím odloženou úlohu. Na této úloze poté voláme metody rozhraní Task, jak jsme znázornili na obrázku 5.1. Po vyprázdnění seznamu kanálů se spustí tato smyčka od začátku.
5.4
Výkon serveru
Pro otestování, jak takto navržený server bude výkonný, jsme se rozhodli udělat zátěžový test. Jeho specifika a výsledky nyní popíšeme. 35
5.4.1
Specifika použitých zařízení
Výkon naší implementace serveru jsme testovali na našem virtuální stroji, hostovaném u společnosti WEDOS. Parametry tohoto stroje jsou: • CPU: Intel, QEMU Virtual CPU version (Intel Xeon E5-2650L), 1 CPU, 1 thread, 1800 MHz, výkon procesoru je sdílený s ostatními uživateli. • RAM: DDR3, 2 GB • HDD: pevný disk s 15000 RPM, 30 GB diskového prostoru • Síť: 100 Mbps up/down • OS: Debian Jessie, verze jádra 3.16.7 Testovací program jsme pouštěli na výkonném výpočetním serveru, abychom měli jistotu, že mají klienti k dispozici dostatečný výkon. Na tomto serveru jsme pouštěli všechny klienty paralelně. Klientský program pouze simuluje komunikaci, neprovádí žádné výpočty navíc. Parametry výpočetního stroje jsou: • CPU: Intel(R) Xeon(R) CPU X5660, 24 CPUs, 48 threads, 2800 MHz • RAM: DDR3, 64 GB • OS: Debian Jessie, verze jádra 3.16.7 Před spuštěním testů měl výpočetní server load average 0.2.1 Pomocí těchto strojů jsme testovali náš server.
5.4.2
Princip testu
Test jsme napsali v jazyce C++ a simulovali jsme komunikaci se serverem. V testu probíhaly následující operace • Připojíme se na server • Přihlásíme se • Vezmeme dvojici klientů, jeden vytvoří herní místnost, druhý do ní vstoupí • Po chvíli klienti odstartují hru • Klienti odehrají 7 herních tahů, v každém tahu zahraje každý klient několik akcí • Na začátku osmého tahu se jeden z klientů vzdá a tím se ukončí hra 1
Běžící programy využívaly v průměru pouze 20% výkonu jednoho vlákna za posledních patnáct minut.
36
Celý test simuluje odehraný zápas dvou hráčů, mezi jednotlivými operacemi je tedy časté čekání. Doba trvání těchto operací činí přibližně minutu a čtvrt. V testu se měří chybovost jednotlivých klientů, pokud nastane libovolná chyba v komunikaci, prohlásí se výsledek testu za chybný. Tento test jsme paralelně spouštěli nejprve s 200 klienty. Po několika testech jsme vždy zvýšili počet klientů o 200 a pokračovali jsme do té doby, než byla chybovost serveru příliš vysoká. Jednotliví klienti se postupně připojovali na server tak, že první klient se připojil na začátku testu, poslední klient po 50 sekundách od začátku testu a časy připojení ostatních klientů byly rovnoměrně rozloženy ve zbývajícím intervalu. Test tedy simuloval rozložení akcí uživatelů, kdy všichni klienti nejsou ve stejném stavu zároveň. Jelikož se poslední klient spustil až po 50 sekundách od začátku běhu, po určitou dobu byli na serveru připojeni všichni klienti zároveň. Test jsme spouštěli vždy pětkrát a ve chvíli, kdy začaly na serveru vznikat chyby, jsme test začali pouštět vždy desetkrát.
5.4.3
Výsledky testování
Prvním pozitivním výsledkem testu byla schopnost nasimulovat běh zápasu a komunikaci se serverem v jiném programovacím jazyce. To ukazuje možnost implementovat hru také pro jiné platformy. Konkrétní chybovost klientů znázorníme v tabulce 5.1. Každý řádek tabulky znázorňuje testování s použitím konkrétního počtu klientů. Tento počet je v prvním sloupečku tabulky. Druhý sloupeček dále říká, kolikrát jsme tento test pustili s tímto počtem klientů. Ve sloupečku min je nejlepší výsledek, kterého jsme v tomto testu dosáhli. Pokud je v tabulce u hodnoty min 0, znamená to, že alespoň v jednom z testů s tímto počtem klientů nedošlo při komunikaci k žádné chybě. Sloupeček max naopak popisuje test, který dopadl nejhůře. Pokud je ve sloupečku max hodnota 1092, znamená to, že v jednom z testů selhalo 1092 klientů. Ve sloupečku avg je poté průměrný počet chyb při komunikaci. To znamená, že jsme z počtů chyb jednotlivých testů vytvořili průměr.
5.4.4
Závěr
Výsledky ukazují, že server běžící na výše popsaném stroji zvládne 2000 klientů současně bez toho, aby nastávaly chyby. Při vyšším počtu klientů došlo k chybám u některých klientů. Výpadek více klientů zároveň je pravděpodobně způsoben hromaděním požadavků u serveru, který poté není schopen včas odpovědět klientům a ti se odpojí. Během běhu všech testů byla nastavena nejvyšší úroveň logování a to proto, abychom případné chyby na serveru, které vyvolalo velké množství současně připojených klientů, mohli odchytit a opravit. Pokud bychom toto logování vypnuli, mohl by být výkon serveru vyšší. Nízký výkon serveru je pravděpodobně způsoben absencí vícevláknového zpracování, jelikož jak všechny úlohy, tak obsluhu klientů zajišťuje pouze jedno vlákno. Současné servery mají často k dispozici více vláken, tyto prostředky však neumí
37
# klientů 200 400 600 800 1000 1200 1400 1600 1800 2000 2200 2400 2600 2800
# testů
min
5 5 5 5 5 5 5 5 5 5 10 10 10 10
0 0 0 0 0 0 0 0 0 0 0 0 0 186
max
avg
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1092 210 1686 332 2264 1192 2320 1167
Tabulka 5.1: Výsledky testování prototypu našeho serveru
naše aplikace využít. Změníme tedy návrh našeho serveru tak, aby podporoval vícevláknové zpracování.
38
6. Vícevláknový server V předchozí kapitole jsme navrhli a implementovali prototyp herního serveru. Na základě testů tohoto serveru jsme se rozhodli implementovat vícevláknové zpracování. Implementace více vláken bude vyžadovat změnu návrhu serveru, aby nenastávaly souběhy (race conditions). V této kapitole navrhneme, jak rozdělit práci serveru do více vláken. Také se podíváme na problém zamykání herních objektů a jaký mají zámky vliv na rychlost zpracování požadavků. Poté upravíme naše rozhraní Task a vysvětlíme funkce soukromé a veřejné fronty úloh. Na závěr kapitoly provedeme znovu testy a porovnáme výsledky.
6.1
Rozdělení práce serveru
Abychom mohli rozdělit práci serveru mezi více vláken, musíme si nejdříve ukázat možná omezení. Nejvýraznějším omezením pro nás je čtení dat ze sítě. Náš server čte data pomocí ze síťových kanálů a při čtení využívá třídu Selector. Ta po zavolání metody select() vrací seznam kanálů, které obdržely nová data. Tato data jsme v našem prototypu ihned zpracovávali a vraceli klientovi odpověď. Aby mohl Selector číst síťové požadavky dostatečně rychle, všechny další operace s daty budou obstarávat další vlákna, která nazveme Worker. Další částí hlavní smyčky serveru bylo plánování periodických úloh. Tuto úlohu přidělíme dalšímu vláknu, které se bude starat o automatické plánování. Hlavní vlákno serveru tedy přečte data ze síťe a vloží je do fronty zpracování. Jednotlivé instance třídy Worker pak budou data odebírat z fronty a zpracovávat je.
6.2
Zámky
Při zpracování požadavku pomocí více vláken je potřeba zajistit, aby více vláken neměnilo jeden objekt zároveň. K tomu nám poslouží zámky, jejich použití však musíme zvážit, jelikož zamykání objektů spotřebovává systémové prostředky a zpomaluje běh aplikace. Při zamykání také hrozí čekání na zpracování požadavku, které nám může zablokovat ostatní vlákna a tím bychom ztratili výhodu vícevláknového zpracování. Základní možností, jak v jazyce Java zamknout nějaký objekt, je použitím klíčového slova synchronized. Tímto slovem můžeme označit metodu nebo blok kódu, do které nesmí vstoupit více vláken zároveň. Některé objekty však často pouze čteme a k zápisu dochází málokdy. Časté zamykání pomocí běžných zámků zpomaluje čtení, jelikož jednotlivá vlákna musí číst data postupně. Z těchto důvodů nabízí standardní knihovna tzv. read-write lock. Při použití tohoto zámku si jednotlivá vlákna zamykají zámek buď pro čtení, nebo pro zápis, a implementace tohoto zámku umožňuje číst objekt více vláknům, dokud se nesnaží jiné vlákno do objektu zapsat. Tyto zámky tedy urychlují práci s objekty, ze kterých je častější čtení, než zápis. 39
Alternativní možností je počet zámků omezit tak, že každé vlákno bude spravovat svou skupinu objektů a ostatní vlákna nebudou moci tuto skupinu ovlivňovat. Objekt tak může měnit jediné vlákno a nemusí se tedy zamykat, otázkou však zůstává, jak a které objekty vláknům rozdělovat a jak toto rozdělení zajistit.
6.3
Nové rozhraní Task
Kvůli zavedení vícevláknového zpracování musíme upravit naše rozhraní Task. Původní rozhraní Task totiž nepočítá s více vlákny ani s předáním úlohy jinému vláknu. Nejprve si ukážeme celé nové rozhraní a popíšeme si změny. public interface Task { public void loadNext(ByteBuffer data); public boolean canPrepare(); public void prepare(); public boolean isPrepared(); public boolean canValidate(); public boolean validate(); public boolean isValidated(); public Void call() throws IOException, Exception; public int tryLock(int workerId); public void unlock(); public boolean isDone(); public void finish() throws IOException; }
Princip fungování metody loadNext() se téměř nezmění, nyní však metoda nebude načítat data přímo ze síťového kanálu. Metodě předáme buffer s daty, které jsme dostali od hlavního vlákna a tato data se uloží do vnitřního bufferu úlohy. Význam metod canPrepare(), prepare() a isPrepared() se nemění. Tyto metody mají stále za cíl zjistit, jaké množství dat potřebuje úloha pro své zpracování. V rozhraní nám přibyla trojice metod canValidate(), validate() a isValidated(). Tyto metody budou nyní mít za úkol první část zpracování úlohy. V této metodě budeme implementovat deserializaci dat a zjištění, komu data patří. Na konci této metody bychom měli vědět, zda data přijatá od klienta dávají smysl a které objekty potřebujeme pro samotné provedení změn. Výsledkem metody validate() bude tedy znalost objektů, které potřebujeme zamknout. K tomu využijeme metodu tryLock(), která se tyto objekty pokusí zamknout. Pokud zamčení objektů selže, můžeme úlohu odložit k pozdnímu zpracování, nezablokovat tedy vlákno čekáním. Samotná metoda call() už bude mít zamčené potřebné objekty a může vykonat potřebné změny. Po dokončení zpracování úlohy se zavolá metoda unlock(), která všechny získané zámky opět odemkne. Do rozhraní jsme také přidali metodu finish(), kterou můžeme využít pro pozdržení odeslání dat po síti. Jelikož posílání dat může nějakou dobu trvat, 40
můžeme před odesláním odemknout zámky metodou unlock() a tak uvolnit objekty ostatním vláknům dříve. Průběh zpracování úlohy znázorňuje diagram na obrázku 6.1.
Obrázek 6.1: Diagram ukazující volání metod nového rozhraní Task. Žlutá buňka odložení znázorňuje rozhodnutí vlákna odložit zpracování na později.
6.4
Fronty úloh
Abychom předešli zamykání některých objektů a odkládání úloh, rozhodli jsme se implementovat na serveru více front úloh. Přincipu fungování těchto front a přenášení úloh věnujeme následující odstavce. Pro pochopení těchto odstavců však nejprve ukážeme naše tři hlavní třídy modelu a kdy je potřeba k těmto objektům přistupovat. Těmito třídami jsou třídy Player, Lobby a Game. První třídou modelu je třída Player. Instance této třídy reprezentují jednotlivé přihlášené hráče. S objektem hráče server pracuje téměř při každém požadavku klienta. Pokud je hráč pouze v menu, tak s jeho objektem pracuje téměř jen on sám. Během doby, kdy je hráč v herní místnosti nebo hraje zápas, můžou s jeho objektem pracovat i úlohy, které byly vytvořeny na základě požadavků ostatních hráčů v místnosti, nebo zápase. Objekt hráče tedy budeme muset zamykat. Druhou třídou modelu je třída Lobby. Tato třída reprezentuje konkrétní herní místnost. Objekty herní místnosti čtou požadavky klientů, kteří místnosti vyhledávají, a hráči v této místnosti. Jelikož informace o místnosti mohou hráči často číst při intenzivním hledání, budeme u této třídy používat read-write zámky.
41
Třetí třídou modelu, kterou si popíšeme, je třída Game. Tato třída reprezentuje probíhající zápas a ukládá jeho stav. Objekty třídy Game vzniknou z objektu herní místnosti při zahájení zápasu a během zápasu s objektem hry interagují pouze hráči tohoto zápasu. Tito hráči také během zápasu neinteragují s jinými objekty. Pokud tedy vezmeme hrající hráče a jejich hry, můžeme vytvořit skupiny zápas, ve které bude objekt zápasu a hráči tohoto zápasu. S objekty v této skupině budou manipulovat pouze jiné objekty stejné skupiny, pokud tedy každou skupinu přidělíme jednomu vláknu, nebudeme muset tyto objekty zamykat. Při zahájení zápasu přidělíme každý zápas právě jednomu vláknu, které se bude o tento zápas starat. Pro tyto účely vytvoříme pro každé vlákno vlastní frontu úloh, do které budeme úlohy související se zápasy plánovat. Tato fronta bude mít přednost před hlavní frontou, zápasy se tedy budou zpracovávat přednostně. Fronty serveru znázorňuje obrázek 6.2.
Obrázek 6.2: Diagram ukazující vkládání úloh do front hlavním vláknem.
42
6.4.1
Nové funkce třídy Connection
Abychom nemuseli při každém novém požadavku číst vyhledávat nejprve objekt hráče a podle nalezeného objektu hráče teprve zjistit, v jakém zápase se hráč nachází, vytvoříme si pomocí třídy Connection zkratku. Instance třídy Connection máme asociované přímo s komunikačním kanálem. Do této třídy tedy přidáme nové datové položky, ve kterých si uložíme ID asociovaného hráče, herní místnosti a zápasu. Takto budeme při obdržení požadavku rovnou vědět, které objekty budeme muset zamknout, případně které objekty budeme vyhledávat. Nevýhodou tohoto přístupu je potřeba vyšší kontroly těchto hodnot, jelikož je musíme při změnách ručně měnit. Každé instanci třídy Connection také přiřadíme ID vlákna, které toto spojení obsluhuje. Nastavením ID spojení konkrétní hodnotu při startu zápasu řekneme hlavnímu vláknu, že od té doby má všechny nové úlohy plánovat právě tomuto vláknu. Dalším efektem přiřazení třídy vlákna je zabránění souběhu při zpracovávání jednoho kanálu. Může se stát, že nám přijdou data, která začneme zpracovávat v úloze a během tohoto zpracování přijdou další data pro stejnou úlohu. Naplánováním této úlohy znovu by mohlo dojít k vyzvednutí této úlohy jiným vláknem, zatímco úlohu už jedno vlákno zpracovává. Dočasným přiřazením vlákna připojení se však úloha naplánuje vláknu, které zpracovává předchozí část a tím se tomuto problému vyhneme. Toto znázorňuje obrázek 6.3.
Obrázek 6.3: Zpracování úlohy jednoho spojení ve dvou částech. Během zpracování přijdou na spojení nová data a úloha se zařadí znovu do fronty přímo vláknu, které už spojení zpracovává.
6.4.2
Přeplánování úlohy
Existence soukromých front nám dává možnost některé úlohy přeplánovat. V některých případech mohou dvě vlákna zároveň začít zpracovávat dvojici úloh,
43
kde obě tyto úlohy vyžadují zamknout instanci jednoho objektu. Při běžném plánování úloh pomocí jedné fronty bychom tak mohli počkat, než se uvolní zámek, nebo úlohu vrátit zpátky do fronty. Jelikož však máme front více, můžeme tuto úlohu předat právě druhému vláknu. Stačí zjistit, které vlákno úlohu zpracovává. To zjistíme pomocí metody tryLock(), která nám vrátí ID vlákna, které vlastní požadovaný zámek. Pokud žádné vlákno tento zámek nevlastní, úlohu zpracujeme. V opačném případě úlohu můžeme vložit do fronty vláknu, které zámek drží. Takto po dokončení úlohy může toto vlákno zpracovat tuto úlohu. Přeplánování úlohy je znázorněno obrázkem 6.4.
Obrázek 6.4: Při blokování zámku jedním vlákem přeplánujeme úlohu tomuto vláknu. Červené buňky znázorňují držení zámku.
6.5
Výkon vícevláknového serveru
Výslednou implementaci serveru jsme se rozhodli otestovat. Otestujeme jeho chování opět na našem virtuálním serveru, abychom zjistili, jestli se výkon serveru díky jiné implementaci nezměnil. Poté otestujeme server také na středně výkonném notebooku, který má k dispozici více vláken. Testovací program i parametry testování ponecháme stejné, aby byly výsledky testování porovnatelné. Pro simulaci klienta budeme opět používat náš program v C++ a tento program budeme opět pouštět na stroji popsaném v kapitole 5.4.1.
6.5.1
Test na virtuálním serveru
Nejprve otestujeme server znovu na virtuálním serveru, popsaném v kapitole 5.4.1. Server pustíme s výchozími parametry, limit klientů navýšíme na 5000. 44
Parametry testu budou stejné, jako u testování prototypu serveru. Pro ušetření času začneme testovat s 2000 připojenými klienty. Provedeme vždy 10 testů a pak zvýšíme počet klientů o 200. Budeme pokračovat, dokud nebude nastávat příliš mnoho chyb. Tabulka 6.1 ukazuje výsledky těchto testů. # klientů
# testů
min
10 10 10 10 10 10 10 10 10 10
0 0 0 0 0 0 0 0 0 120
2000 2200 2400 2600 2800 3000 3200 3400 3600 3800
max
avg
0 0 0 0 12 3 1852 186 625 64 16 3 2159 322 2740 274 3600 743 3800 2812
Tabulka 6.1: Výsledky testování druhé implementace naší nové implementace na virtuálním serveru Oproti předchozímu testu, kde jsme při přihlášení více, než 2000 klientů, měli velkou chybovost, při tomto testu byla chybovost o dost nižší. To přisuzujeme tomu, že náš virtuální server využívá sdílený výkon CPU. Jelikož mohly ostatní virtuální systémy běžící na stejném stroji spotřebovávat málo prostředků, přidělil hypervisor našemu systému více výpočetních prostředků a tak se při výpočtu mohla využít obě vlákna. Tento test ukázal, že naše vícevláknová implementace dokázala zvýšit počet simultánně připojených klientů.
6.5.2
Test na notebooku
Pro další testování jsme se rozhodli využít přenosný počítač. Parametry tohoto počítače popíšeme v seznamu. • CPU: Intel(R) Core(TM) i7-3537U, 2 core, 4 thread, 2000 MHz • RAM: DDR3, 8 GB • SSD: Intel SSD 535, 240 GB • Síť: 100 Mbps up/down • OS: Debian Stretch, verze jádra 3.16.7 Na tomto systému jsme zkusili pustit test také, avšak chybovost našich testů byla vysoká už u 4000 klientů. Začali jsme tedy analyzovat frontu požadavků serveru a hledat příčinu nízkého výkonu. Nejprve jsme rozhodli vyzkoušet spustit test pokaždé se 4000 klienty 45
a přitom měnit intervaly, ve kterých se klienti připojují. Každý test jsme spustili pětkrát, poté jsme interval mezi dvěma klienty zvedli o jednu milisekundu. Cílem testu bylo zjistit, kdy začnou spojení klientů vypadávat. Abychom neměřili náhodný výpadek, po každém testu jsme se podívali, kolikátý klient už byl připojen, když se odpojil desátý klient. Například, pokud se připojil 1963. klient a hned po něm vypadl náš desátý klient, byl výsledek testu 1963. Výsledek testu je zaznamenán v tabulce 6.2. První sloupeček říká, jaký byl interval mezi dvěma klienty, kteří se připojili po sobě. Hodnota druhého sloupečku vypovídá, kolikrát doběhl test bez chyby, tedy v kolika testech úspěšně všichni klienti odehráli své zápasy. Ve sloupečku min je nejhorší výsledek, kterého jsme v tomto testu dosáhli. Pokud je v tabulce u hodnoty min 3204, znamená to, že v nějakém testu bylo při desátém vypadnutém klientovi připojeno jen 3204 klientů. Sloupeček max naopak popisuje test, který dopadl nejlépe. Pokud je ve sloupečku max hodnota 4000, znamená to, že v alespoň v jednom z testů bylo připojeno všech 4000 klientů najednou. Ve sloupečku avg je průměrný výsledek pěti spuštěných testů. interval 9 ms 10 ms 11 ms 12 ms 13 ms
počet
min
max
avg
0 1968 0 2202 0 2374 0 3070 5 4000
2201 2303 2748 3301 4000
2129 2242 2609 3195 4000
Tabulka 6.2: Naměřená závislost intervalu mezi připojenými klienty na ztrátě spojení klientů
Z naměřených hodnot vyplývá, že maximální počet současně připojených klientů závisí převážně na intervalu, v jakém se klienti připojují. Během posledního testu (testu s 13 ms intervalem) jsme také měřili počet požadavků ve veřejné frontě, ten znázorníme grafem na obrázku 6.5. Z grafu lze vidět, že nejvíce je server zatížen během připojování a odpojování klientů. To je pravděpodobně způsobeno databází. V době připojování klientů se server dotazuje databáze na informace o klientech, v době ukončení zápasu a odhlášení naopak informace ukládá.
6.5.3
Závěr
Implementací vícevláknového zpracování jsme získali vyšší výkon serveru, můžeme tedy obsluhovat více klientů zároveň. Herní server je však zpomalován databázovými dotazy. Ty bychom se mohli pokusit redukovat a klienty si přednačítat, respektive odkládat dotazy až na později, kdy nebude už potřeba obsluhovat žádné klienty. Dalším způsobem, jak bychom mohli zrychlit dotazování, by bylo vyhnout se databázovým transakcím.
46
47
Obrázek 6.5: Graf znázorňuje počet úloh ve frontě v závislosti na čase. První svislá čára ukazuje dobu, kdy se přihlásil první klient, druhá čára, kdy se přihlásil poslední klient a třetí čára ukazuje, kdy se první klient odhlásil. Graf v původním rozlišení je součástí příloh.
7. Síťová komunikace V předchozích kapitolách jsme si popsali naši hru a představili jsme si, jak funguje náš server. V této kapitole se podíváme podrobněji, jak v aplikaci zajišťujeme síťovou komunikaci. Podíváme se nejprve, v jakém formátu se přenáší data po síti. Poté si ukážeme implementaci komunikace na serveru a jak funguje třída Selector. Dále se podíváme na klienta a představíme si možné způsoby, jak v systému Android implementovat síťovou komunikaci a co se stane, když klient přijde o internetové spojení. Na závěr kapitoly si ukážeme, jak v naší aplikaci kontrolujeme stav spojení s klienty.
7.1 7.1.1
Komunikační protokol Existující řešení
Nejprve si popíšeme, jakým způsobem spolu mohou dvě zařízení komunikovat po síti při použití jazyka Java. Jazyk Java definuje dvě rozhraní pro zápis a čtení dat: rozhraní InputStream a OutputStream. Tato rozhraní definují metody pro zápis a čtení jednoho, nebo více bajtů. Tato rozhraní jsou poté implementována třídami, které umožňují I/O operace se soubory, sítí, apod. V Javě dále existují třídy, které berou v konstruktoru právě InputStream nebo OutputStream jako parametr a vytváří další abstrakci. Jednou takovou třídou je třída PrintStream, která přidává metody pro zápis primitivních typů a řetězců. Další třídou usnadňující zápis je ObjectOutputStream, která umožňuje serializovat celé objekty. Pokud bychom použili tento způsob při posílání dat, stačilo by vložit data do jednoho objektu a ten pouze nechat serializovat. V naší aplikaci jsme se však rozhodli použití těchto tříd vyhnout. Jedním z problémů je režie, kterou nám tyto třídy přináší. Třída PrintStream data zapisuje jako čistý text, tedy počet přenesených bajtů se pro některé datové typy zvyšuje. Třída ObjectOutputStream naopak přidává k přeneseným datům svou hlavičku. Hlavním důvodem, kvůli kterému se vyhneme těmto třídám, je však přenositelnost. V případě, že bychom chtěli později vytvořit server pro jinou platformu, nebo klienta pro jinou mobilní platformu, bychom museli přepsat celou síťovou komunikaci. Data tedy budeme přenášet ve formátu, který půjde implementovat i na jiných platformách. Jednou z možností je použít nějaký jazyk nebo notaci pro přenos dat, druhou je poté implementovat vlastní binární protokol. Pro přenos dat je možné použít jazyk XML nebo notaci JSON. Tyto způsoby si nyní ve zkratce představíme. Jazyk XML je značkovací jazyk, který používá pro definici dat stromovou strukturu. Dokument jazyka XML se skládá z elementů, kde každý element může mít podelementy, atributy, nebo čistý text. Výhodou jazyka XML je existence velkého množství nástrojů pro kontrolu formátu dat, zpracování a vyhledávání v takových datech. Nevýhodou je velká režie dat, kdy velkou část dokumentu tvoří právě značky elementů. Pro ukázku si ukážeme příklad zprávy v jazyce XML, kterou bychom mohli poslat klientovi a ve které bychom klientovi sdělili 48
hráčovu přezdívku a jeho identifikační číslo. <message sent="1422745786" type="2">
24 John
Jiným jazykem pro přenos dat je poté notace nazývaná JSON. Tato notace se používá pro přenos dat u webových aplikací, nebo jako způsob posílání dat některými API.[27] Data v notaci JSON jsou definována jako dvojice klíč-hodnota. Výhodou notace JSON je mnohem menší režie, než u jazyka XML. Nevýhodou oproti jazyku XML je obtížnější vynucení formátu dat. Následuje ukázka zprávy zapsané v notaci JSON. { "id": 24, "nickname": "John" }
Nevýhodou obou výše uvedených způsobů je potřeba existence způsobu v tomto formátu zapisovat a číst. To mohou vyřešit některé knihovny, popřípadě i standardní API jazyka. Hlavní nevýhodou je pro nás ale režie. Někteří operátoři limitují počet přenesených dat po mobilních sítích. Chtěli bychom tedy po síti přenášet co nejmenší objemy dat. Z tohoto důvodu si vytvoříme vlastní binární protokol a přesně definujeme formát přenášených dat v binární podobě tak, abychom mohli přenášet co nejméně dat.
7.1.2
Náš protokol
Pro snížení počtu přenesených dat jsme se rozhodli implementovat náš vlastní binární protokol. V tomto protokolu navíc nemusíme posílat téměř žádná kontrolní data navíc, jelikož spolehlivost přenosu za nás zajišťuje už transportní protokol TCP. V následujícim seznamu si popíšeme, jak přesně vypadají přenášená data: • logické hodnoty přenášíme jako jediný bajt, kdy nulová hodnota znamená false, nenulová true • celočíselné hodnoty (tedy typy byte, short, int a long) přenášíme binárně, binární formát odpovídá uložení hodnoty ve dvojkovém dopl´ nku • hodnoty typu double (64-bitový datový floating-point typ) budeme přenášet podle standardu IEEE 754 [28] • u řetězce nejprve přeneseme jeho délku, poté jednotlivé znaky; aby navíc hráči nemuseli zadávat speciální znaky ve jménech, dovolujeme pouze znaky ASCII a tedy nám postačí na přenos pouze jeden bajt pro každý znak
49
Při přenosu také dodržujeme jednotnou endianitu, všechna data jsou tudíž přenášena v síťovém pořadí bajtů, což je Big-endian. Při dodržování této konvence nám pomáhá samotný jazyk Java. Všechny proměnné v jazyce Java jsou ukládané s pořadím bajtů Big-endian, při posílání dat po síti tedy nemusíme dělat žádný převod. Při posílání dále využíváme buffery, kde předem připravíme data a poté je najednou pošleme. Stačí tedy, pokud třída, kterou na ukládání do bufferu používáme, umí data zapisovat podle našich konvencí. V jazyce Java se nám o toto stará třída ByteBuffer. Jaká konkrétní data přenášíme jsou závislé na úloze. V každé úloze se snažíme přenést co nejméně nadbytečných dat. Jak vypadají přenášená data pro konkrétní požadavky klienta popíšeme v programátorské dokumentaci.
7.2
Sockety v Javě
Nyní si popíšeme, jak server přijímá nová spojení a jaké třídy pro to používá. Přitom si vysvětlíme rozdíl mezi standardní třídou Socket a třídou z Java NIO nazývanou SocketChannel. Také si vysvětlíme, jak poznáme při obdržení dat, jaký hráč s námi komunikuje a jak naopak poslat libovolnému hráči zprávu. První možností, jak vytvořit síťové spojení v jazyce Java, je použitím tříd ServerSocket a Socket. Tato dvojice tříd je v jazyce přístupná už od verze 1.1. Při vytvoření instance třídy ServerSocket a zavolání metody bind() začne aplikace poslouchat na daném portu. Pro přijetí spojení se poté volá accept(), který vrátí instanci Socket. Na té se dále provádí konkrétní operace čtení a zápisu. Problémem těchto tříd je, že při výchozím chování není možné snadno zjistit, zda přišla nová data. Navíc jsou operace čtení a zápisu blokující, pokud tedy žádná data přístupná nejsou, aplikace se zablokuje čekáním na nová data. V jazyce Java verze 7 přibyly nové třídy, nazývané souhrnně Java NIO. Mezi těmito třídami je také dvojice tříd ServerSocketChannel a SocketChannel. Tyto třídy nabízí blokující i neblokující čtení, zápis dat pomocí třídy ByteBuffer a možnost snadno zjistit, na jaké kanály přišla nová data. Ke správě kanálů můžeme v balíčku nalézt třídu Selector. U instance této třídy zaregistrujeme jednotlivé kanály. Zavoláním metody select() dostaneme seznam instancí SelectionKey, u kterých jsou dostupná data pro čtení. Pomocí Selectoru tak můžeme pracovat pouze se sokety, které mají nová data. Zbývá nám asociovat sokety s hráči. V tom nám pomůže právě třída SelectionKey, která má metody attach() a attachment(). Pomocí těchto metod můžeme s třídou SelectionKey asociovat právě jeden objekt. Pro tuto asociaci jsme vytvořili třídu Connection. Po přihlášení hráče pak asociujeme hráče právě s instancí Connection. Na obrázku 7.1 jsou poté ilustrovány delegace mezi jednotlivými třídami v situaci, kdy od jednoho klienta přijde požadavek na start hry v herní místnosti a server potřebuje o události notifikovat druhého hráče. Nejprve požadavek obdrží kanál a ten je vybrán selektorem. Od něj se následně získá instance Connection a vytvoří se nová úloha. Dále se zjistí, jaký hráč je s tímto spojením asociován a získají se údaje o jeho herní místnosti. Tam už se pak vytvoří notifikace, z objektu hráče se získá asociované spojení a notifikace se vypropaguje na kanál a odešle klientovi.
50
Obrázek 7.1: Jednotlivá volání na serveru při příjmu dat od jednoho hráče a následné notifikaci hráče druhého
7.3
Připojení klienta
Nyní se naopak podíváme, jak řešíme síťové spojení u klienta, jaké máme na Androidu možnosti a jak spojení implementujeme. Nejprve si popíšeme, co od spojení potřebujeme. Prvním naším požadavkem na spojení je možnost mít spojení plně pod kontrolou. V průběhu hry si musíme být jistí, že naše spojení Android nebude z nějakého důvodu přerušeno a neukončí se spojení se serverem. Dalším požadavkem je persistence spojení. Pro komunikaci musíme mít možnost držet stále spojení, abychom mohli kdykoliv přijímat notifikace od serveru. Nejběžnějším paradigmatem přenosu dat po síti u systému Android je použití protokolu HTTP.1 Tento způsob však nesplňuje náš požadavek, protože se na server posílají jednotlivé zprávy a není snadné získat kdykoliv od serveru notifikaci. Další možností je použít komponentu Service. Komponenta Service běží v pozadí aplikace a je nabízena systémem pro vykonávání časově náročných operací, které běží na pozadí. Každý Service se poté buď spouští a ukončuje manuálně, nebo se spustí, pokud nějaká aktivita zavolá metodu bind. Vlastností komponenty Service je schopnost běžet na pozadí, i když samotná aplikace není v popředí. To může být vhodné pro některé aplikace, které provádí náročné operace, pro nás je ale tato funkcionalita zbytečná. V případě, že hráč ukončí aplikaci, tak chceme přerušit spojení se serverem pro úsporu prostředků jak hráčova zařízení, tak serveru. Pokud spustíme Service manuálně pomocí metody start a dojde k neočekávanému pádu aplikace, komponenta Service může zůstat běžet nezávisle v pozadí. 1 Toto je také jediný způsob, který je popsaný přímo v návodu pro vývojáře systému Android, který je dostupný na adrese https://developer.android.com/training/basics/ network-ops/index.html
51
Použití metod bind a unbind přináší problém s nechtěným ukončováním Service. Při zavolání bind při vytvoření aktivity a unbind při ukončení aktivity může nastat problém při přepínání aktivit. Pokud se jedna aktivita odpojí dříve, než se druhá připojí, ztráta poslední reference na Service vyvolá ukončení síťového spojení.2 Pro implementaci síťového připojení jsme se rozhodli vytvořit vlastní třídu. Tato třída se nazývá ConnectionHandler a její návrh vychází z návrhu Service a metody bind. Třída vytvoří síťové spojení pomocí běžného soketu až v době pokusu o zápis do takového spojení. Jednotlivé aktivity se poté připojí voláním bind, při přepínání aktivit můžeme třídě však říct, že spojení ukončovat nemá. Tím jsme vyřešili problém se ztrátou spojení při přepínání aktivit, při ztrátě poslední aktivity však dojde k automatickému ukončení spojení. Životnost spojení třídy ConnectionHandler je znázorněna na diagramu 7.2.
Obrázek 7.2: Ukázka volání metod třídy ConnectionHandler – při nastavení flagu transition se při dalším volání unbind neukončí síťové spojení Další vlastností systému Android je zablokování blokujících I/O operací v hlavním vlákně aplikace. Všechny síťové operace se tedy musí spustit jako asynchronní úloha a jejich výsledek se musí znovu promítnout do hlavního vlákna starajícího se o grafické rozhraní. Nedodržení těchto pravidel vyvolá výjimku a pád aplikace. Z toho důvodu je potřeba také pouštět všechny síťové operace v jiném vlákně a hlídat mezivláknovou komunikaci.
7.4
Detekce výpadku spojení
Zajistit připojení na mobilním zařízení není snadné. U mobilních připojení často můžou nastávat různé problémy, například změna sítě z 4G na 3G, změna sítě z 3G na domácí WiFi síť anebo úplná ztráta fyzického připojení (vjezd do tunelu ve vlaku). 2 Implementaci pomocí Service a bind jsme vyzkoušeli. Problém s ukončováním spojení nastával právě u slabších zařízení, u výkonnějších zařízení k problému nedocházelo. To by mohlo být pravděpodobně proto, že u slabších zařízení muselo nejdříve dojít k ukončení předchozí aktivity, než se spustila aktivita nová, a u výkonnějších zařízení se nová aktivita pravděpodobně spouštěla už během ukončování aktivity předchozí.
52
Při takové změně nutně ztratíme spojení se serverem, ať už kvůli změně IP adresy, nebo úplné ztrátě spojení. Je potřeba tedy implementovat mechanismy, které se s těmito ztrátami vyrovnají a klienta znovu připojí k serveru. Nastává otázka, jak chybu v připojení poznáme. Při ztrátě spojení vyhodí metoda write výjimku. Tuto výjimku odchytí ConnectionHandler a vrátí aplikaci chybu. Tuto chybu odchytí úloha, která posílá požadavek, a uklidí po spojení. Hráče poté přesměrujeme do aktivity pro přihlášení, kde se pro něj vytvoří nové spojení a dojde k nové autentizaci, tedy k opětovnému přihlášení hráče. Nyní si popíšeme, jak měníme náš stav aplikace při ztrátě spojení. Při ztrátě spojení hráče během menu nebo při čekání v herní místnosti nevěnujeme situaci větší pozornost, hráč se totiž může bez problému kdykoliv připojit zpátky. Problém však nastává při ztrátě připojení během probíhajícího zápasu. Pro tento případ jsme implementovali mechanismus pro znovu připojení do rozehraného zápasu. Pokud hráč naváže spojení se serverem do určité doby od výpadku, server pošle hráči stav zápasu a v aplikaci pro hráče zrekonstruujeme stav zápasu. Tuto dobu si mohou hráči specifikovat při vytváření herní místnosti. Abychom však hráče opakovaně neobtěžovali s přihlašováním, při přihlášení si zapamatujeme jeho údaje. Při výpadku spojení se poté pokusíme spojení obnovit a automaticky hráče přihlásit. Při této implementaci můžeme také toto chování zavést při startu aplikace, kdy hráče rovnou automaticky přihlásíme. Pokud chce hráč použít jiný účet, lze automatické přihlášení zrušit manuálním odhlášením hráče z hlavního menu.
7.5
Ping
Už tedy víme, jak detekovat výpadek spojení u klienta a znovu se připojit, neukázali jsme si však, jak toto zajišťujeme na serveru. Nejprve si vysvětlíme, proč tuto detekci vůbec potřebujeme. Detekci výpadků připojení potřebujeme jen v některých případech. Pokud ztratí hráč náhle spojení, ztracené spojení zůstane ještě chvíli pro server aktivní, a po nějaké době dojde k timeoutu. V některých případech nám nevadí, pokud si počkáme na timeout, v průběhu zápasu si však nemůžeme dovolit čekat delší dobu na opětovné připojení hráče. Z tohoto důvodu chceme výpadek nějak detekovat a řešit. Logickým řešením by se mohlo zdát poslání nějaké zprávy klientovi a zjistit, jestli se tato zpráva přenese. To však není správný přístup, jelikož i při výpadku se bude protokol TCP snažit opakovaně doručit, přenos tak neselže a bude se zdát, že k žádné chybě nedošlo. Problém však lze vyřešit opačně, tedy tak, že klient bude serveru periodicky posílat krátkou zprávu. Pokud žádná zpráva od klienta delší dobu nepřijde, server prohlásí takové klienta za odpojeného. Tyto zprávy budeme od klienta vyžadovat během zápasu, pokud je připojený v herní místnosti, nebo pokud čeká ve frontě automatického párování. Tento přístup využijeme i k dalšímu zajištění včasnosti komunikace a předejití chyb. V některých případech chceme od klienta, aby nám obratem poslal odpověď. Taková situace nastává například při nalezení soupeře během párování, chceme, aby klient potvrdil obdržený stav hry a my mohli začít zápas. To zajistíme tak, že od klienta budeme požadovat, aby poslal zprávu co nejdříve. Tímto také přejdeme 53
uváznutí u klienta. Pokud by z nějakého důvodu došlo například k deadlocku v aplikaci, klient včas neodpoví a my mu ukončíme spojení. Detekci těchto problému zajišťuje na serveru třída CheckAliveTask, která projde všechny připojené klienty a zkontroluje jejich stav. Úloha, která toto vykonává, se spouští každé tři sekundy, výpadek klienta jsme tedy schopni detekovat už po několika sekundách. O posílání těchto zpráv se u klienta stará třída ConnectionHandler. V této třídě je definována metoda ping, která pošle serveru zprávu velikosti 1 bajt. Tyto zprávy jsou však posílány jen tehdy, když nedošlo v poslední době k poslání žádné jiné zprávy. Metoda se tedy stará sama o dodržování intervalů.
54
8. Další rozvoj Výsledkem naší práce je implementace naší hry tak, jak jsme ji popsali v předchozích kapitolách. Stále však zůstal prostor pro zlepšení. Některé návrhy pro zlepšení si popíšeme právě v této kapitole.
8.1
Představení hry hráči
Jedním možným zlepšením naší hry by bylo snáze hráče do této hry uvést. V naší implementaci je herní manuál, který popisuje základní ovládání a princip hry. Tento manuál však není interaktivní a je založený převážně na textu. Vytvoření interaktivního tutoriálu by mohlo pomoci hráčům pochopit a naučit se naši hru. Jednou z možných implementací by byl krátký výukový scénář, kde by si hráč mohl vyzkoušet ovládání hry, její mechaniky a hra by mu interaktivně dávala úkoly a popsala jejich význam.
8.2
Další platformy
Dalším logickým krokem pro rozšíření hry by mohlo být vytvoření klientské části aplikace i pro ostatní mobilní platformy. Náš komunikační protokol je nezávislý na platformě, stačilo by tedy vytvořit uživatelské rozhraní, vykreslování a naimplementovat komunikaci se serverem.
8.3
Více serverů
V současné době podporuje náš herní klient připojení pouze na jeden konkrétní server. Možným rozšířením by bylo přidat možnost připojení na další servery. To by šlo zařídit existencí další malého serveru, který by si udržoval stav o funkčních serverech a klientům poskytoval jejich seznam. Jednotlivé servery by se poté u tohoto serveru registrovaly. Otázkou by potom bylo, zda-li by bylo vhodné sdílet některá uživatelská data mezi jednotlivými servery, nebo mít uživatele na serverech oddělené. Pokud bychom data uživatelů uzavřeli na jeden konkrétní server, dali bychom možnost zakládat ostatním soukromé servery a tím tedy správu serverů komunitě. Sdílením dat bychom naopak umožnili uživateli sdílet profil mezi servery, potřebovali bychom však větší kontrolu nad servery, které přistupují k databázi.
8.4
Grafické rozhraní a zvuk
Jednou z částí aplikace, kterou lze dále zdokonalit, je uživatelské rozhraní. Naše aplikace poskytuje základní a přehledné uživatelské rozhraní, toto rozhraní však nemusí být uživatelsky přívětivé. Lepší uživatelské rozhraní jsme nevytvořili, jelikož návrh a vytvoření grafických prvků tohoto rozhraní trvá dlouho a tvorba grafiky není cílem naší práce.
55
Nynější grafické rozhraní aplikace bylo primárně navrženo pro mobilní telefon s fullHD displejem a uhlopříčkou 5”. Pro pohodlnější ovládání uživatelů by bylo vhodné vytvořit další rozhraní pro tablety, které mají větší displej, nebo pro mobilní telefony s menším displejem. Při vykreslování zápasů by také mohly pomoct uživatelům další animace a speciální efekty. Existence více různých obrázků pro vykreslování by také mohla usnadnit orientaci po mapě. Vytvoření takové grafiky však zabere hodně času a jelikož toto nebylo cílem práce, pro naši hru jsme vytvořili pouze jednoduchou grafiku. Dalším chybějícím prvkem souvisejícím s uživatelským rozhraním je absence zvuku. Tím myslíme jak hudbu hrající v pozadí, tak speciální zvukové efekty, jako například tikání hodinek (dochází čas).
56
Závěr V této práci jsme si vybrali mobilní platformu a u této platformy jsme analyzovali nejoblíbenější hry. Na základě této analýzy jsme vytvořili návrh vlastní tahové strategické hry se simultánním odehráváním pro tuto platformu a zdůvodnili naše rozhodnutí. Dále jsme popsali potřebné prostředky pro implementaci této hry a vybrali knihovny, které jsme pak využili při implementaci. Představili jsme prototyp herního serveru, který jsme implementovali a zhodnotili jeho výkon. Na základě výsledků testů jsme se rozhodli server upravit, aby podporoval vícevláknové zpracování, a tento upravený server znovu otestovat. Také jsme popsali, jak řeší aplikace komunikaci po síti. V závěru práce jsme poukázali na možnosti dalšího rozvoje naší aplikace. Důležitým prvkem hry, který jsme nestihli vytvořit, je herní tutoriál. Místo něj jsme hráčům nabídli alespoň herní manuál, ve kterém jsou popsány mechaniky hry a její princip. Výsledkem této práce je mobilní tahová strategická hra, ve které hráči odehrávají tahy souběžně. Během práce byl vytvořen vícevláknový herní server, který zpracovává požadavky hráčů, simuluje zápasy a je schopen vyhledat hráčům soupeře. Kromě toho jsme vytvořili mobilní aplikaci pro systém Android, pomocí které se hráči mohou připojit na server a hrát zápasy. Vytvořená hra je jednoduchou strategií se simultánním odehráváním pro mobilní zařízení, která je zaměřená především na rychlý průběh hry. Hra by tak mohla zaujmout příznivce strategických her, kteří by si chtěli takovou hru na mobilních zařízeních vyzkoušet.
57
Seznam použité literatury [1] IDC. Smartphone OS Market Share, 2015 Q2 [online] [2015] [cit. 2016-0315]. Dostupné z: http://web.archive.org/web/20160315113602/http: //www.idc.com/prodserv/smartphone-os-market-share.jsp. [2] VGChartz. Počet prodaných kopií hry Advance Wars 2 [online] @2006-2016 [cit. 2016-05-15]. Dostupné z: http://www.vgchartz.com/game/12461/ advance-wars-2-black-hole-rising/. [3] Dave Hoch. Time in App Increases by 21% Across All Apps [online] 16. 9. 2014 [cit. 2016-05-02]. Dostupné z: http://info.localytics.com/blog/ time-in-app-increases-by-21-across-all-apps. [4] Repozitář s kódem the battle for wesnoth [online] @2016 [cit. 2016-05-13]. Dostupné z: https://github.com/wesnoth/wesnoth/releases?after=1. 1. [5] Bryan Stout. Smart Moves: Intelligent Pathfinding. Gamasutra, page 9, 12. 2. 1999. Dostupné z: http://www.gamasutra.com/view/feature/131724/ smart_move_intelligent_.php. [6] Enemy Down. CSS Open Ladder Now Live [online] 10. 6. 2009 [cit. 2013-1204]. Dostupné z: https://web.archive.org/web/20131204020228/http: //www.enemydown.eu/news/502. [7] Mezinárodní šachová federace. Pravidla hodnocení FIDE [online] [2014] [cit. 2016-05-06]. Dostupné z: http://www.fide.com/fide/handbook.html?id= 172&view=article. [8] Mark E. Glickman. The Glicko System [online] [cit. 2016-05-06]. Dostupné z: http://www.glicko.net/glicko/glicko.pdf. [9] Microsoft Research. TrueSkillTM Ranking System: Details [online] @2016 [cit. 2016-05-06]. Dostupné z: http://research.microsoft.com/en-us/ projects/trueskill/details.aspx. [10] Morgan McGuire and Odest Chadwicke Jenkins. Creating Games: Mechanics, Content, and Technology. A. K. Peters, October 2008. [11] University of Southern California ISI. RFC 768 [online] 28. 8. 1980 [cit. 2016-05-09]. Dostupné z: https://tools.ietf.org/rfc/rfc768.txt. [12] University of Southern California ISI. RFC 793 [online] září 1981 [cit. 201605-09]. Dostupné z: https://tools.ietf.org/rfc/rfc793.txt. [13] DigitalOcean. SQLite vs MySQL vs PostgreSQL: A Comparison Of Relational Database Management Systems [online] 21. 2. 2014 [cit. 2016-0519]. Dostupné z: https://www.digitalocean.com/community/tutorials/ sqlite-vs-mysql-vs-postgresql-a-comparison-of-relationaldatabase-management-systems. 58
[14] Android SDK Download [online] 11. 5. 2016 [cit. 2016-05-23]. Dostupné z: https://developer.android.com/studio/index.html#downloads. [15] Android Support Library [online] [2016] [cit. 2016-05-23]. Dostupné z: https://developer.android.com/topic/libraries/support-library/ index.html. [16] The apache commons mathematics library [online] 17. 3. 2016 [cit. 2016-0523]. Dostupné z: http://commons.apache.org/proper/commons-math/. [17] Dokumentace balíčku java.util.logging [online] @2016 [cit. 2016-05-23]. Dostupné z: https://docs.oracle.com/javase/7/docs/api/java/util/ logging/package-summary.html. [18] Simple Logging Facade for Java [online] @2004-2016 [cit. 2016-05-23]. Dostupné z: http://www.slf4j.org/. [19] Apache Log4j 2 [online] @1999-2015 [cit. 2016-05-23]. Dostupné z: http: //logging.apache.org/log4j/2.x/. [20] Nitin, Ananya, Mahalakshmi, and Sangeetha. iBATIS, Hibernate, and JPA: Which is right for you? Dostupné z: http: //www.javaworld.com/article/2077875/open-source-tools/ ibatis--hibernate--and-jpa--which-is-right-for-you-.html. [21] Java Persistence API [online] [cit. 2016-05-23]. Dostupné z: http://www.oracle.com/technetwork/java/javaee/tech/ persistence-jsp-140049.html. [22] Hibernate ORM [online] [cit. 2016-05-23]. Dostupné z: http://hibernate. org/orm/. [23] The MyBatis Blog [online] [cit. 2016-05-23]. Dostupné z: http://blog. mybatis.org/. [24] Canvas — Android Developers [online] [2016] [cit. 2016-05-23]. Dostupné z: https://developer.android.com/reference/android/graphics/ Canvas.html. [25] AndEngine - Free Android 2D OpenGL Game Engine [online] [cit. 2016-0523]. Dostupné z: http://www.andengine.org/. [26] LibGDX [online] @2013 [cit. 2016-05-23]. Dostupné z: https://libgdx. badlogicgames.com/. [27] Google Cloud Platform. Google Cloud Storage JSON API [online] 27. 4. 2016 [cit. 2016-05-16]. Dostupné z: https://cloud.google.com/storage/ docs/json_api/. [28] W. Kahan. IEEE Standard 754 for Binary Floating-Point Arithmetic [online] 1. 10. 1997 [cit. 2016-05-17]. Dostupné z: http://www.cs.berkeley.edu/ ~wkahan/ieee754status/IEEE754.PDF.
59
Seznam obrázků 1.1
Screenshot ze hry Mobile Strike . . . . . . . . . . . . . . . . . . .
2.1 2.2 2.3 2.4 2.5 2.6 2.7
Screenshot ze hry The Battle for Wesnoth . . Screenshot ze hry Advance Wars 2 . . . . . . Screenshot ze hry UniWar . . . . . . . . . . . Ukázka průběhu hry při zavedení simultánního Čtvercové rozdělení ve hře Advance Wars 2 . . Souřadnice při šestiúhelníkovém rozdělení . . . Vyhodnocení tahu . . . . . . . . . . . . . . .
. . . . . . .
8 10 11 12 15 16 18
3.1 3.2 3.3
Modely komunikace . . . . . . . . . . . . . . . . . . . . . . . . . . Rozdíl mezi TCP a UDP . . . . . . . . . . . . . . . . . . . . . . . Možný vzhled lišty s informacemi o probíhajícím zápase . . . . . .
23 25 27
4.1
Komponenty potřebné k logování při použití slf4j . . . . . . . . .
30
5.1 5.2
Volání metod rozhraní Task . . . . . . . . . . . . . . . . . . . . . Průběh hlavní smyčky serveru . . . . . . . . . . . . . . . . . . . .
34 35
6.1 6.2 6.3 6.4 6.5
Volání metod nového rozhraní Task Vkládání úloh do front . . . . . . . Zpracování úlohy po částech . . . . Přeplánování úlohy . . . . . . . . . Počet úloh ve frontě během testu .
. . . . .
41 42 43 44 47
7.1 7.2
Volání metod při příjmu dat . . . . . . . . . . . . . . . . . . . . . Volání metod ConnectionHandler . . . . . . . . . . . . . . . . .
51 52
60
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . odehrávání . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . . . .
. . . . .
. . . . . . .
. . . . .
. . . . . . .
. . . . .
6
Seznam odkazovaných her Age of Wonders III: http://aow.triumph.net/about-ageofwonders-iii/ Frozen Synapse: http://www.frozensynapse.com/ The Battle for Wesnoth: https://www.wesnoth.org/ Advance Wars 2: Black Hole Rising: https://en.wikipedia.org/wiki/Advance_Wars_2:_Black_Hole_Rising Slither.io: https://play.google.com/store/apps/details?id=air.com. hypah.io.slither&hl=cs Hungry Shark World: https://play.google.com/store/apps/details?id= com.ubisoft.hungrysharkworld&hl=cs Colorswitch: https://play.google.com/store/apps/details?id=com. fortafygames.colorswitch&hl=cs Mobile Strike: https: //play.google.com/store/apps/details?id=com.epicwaronline.ms&hl=cs Toy Defense 2: https://play.google.com/store/apps/details?id=com. melesta.toydefense2&hl=cs Megapolis: https://play.google.com/store/apps/details?id=com. socialquantum.acityint&hl=cs UniWar: https://play.google.com/store/apps/details?id=android.uniwar&hl=cs Counter-Strike: http://store.steampowered.com/app/10/
61
Seznam tabulek 1.1
Porovnání mobilních operačních systémů . . . . . . . . . . . . . .
6
5.1
Výsledky testování prototypu našeho serveru . . . . . . . . . . . .
38
6.1
Výsledky testování druhé implementace naší nové implementace na virtuálním serveru . . . . . . . . . . . . . . . . . . . . . . . . . Naměřená závislost intervalu mezi připojenými klienty na ztrátě spojení klientů . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2
62
45 46
Seznam souborů v příloze prilohy.zip bin ...................................... zkompilované části programu klient...........................................klientská aplikace server................................server a konfigurační soubory dokumentace programatorska.pdf..................programátorská dokumentace uzivatelska.pdf..........................uživatelská dokumentace src Droid........................................kód klientské aplikace Droid-model................................kód společného modelu Droid-server................................kód serverové aplikace testy test fronta............................grafy k testu vytížení fronty original.pdf znacky.pdf test invervaly..............záznam z měření intervalů mezi klienty test multithread ........ záznam z testování vícevláknového serveru test prototyp ................ záznam z testování prototypu serveru
63