Co mi dala patnáctka

Učenlivost je jednou z klíčových vlastností dobrého programátora. Jak už jsem psal, při programování H15 bylo mým cílem naučit se programování pro Android. Je čas na résumé.

Nové technologie se nejraději učím metodou learning-by-doing, tedy na vhodném příkladě, pokud možno reálném, ale fiktivní příklad také může svůj účel splnit. Vymyslím, co má aplikace dělat, jak má vypadat, a pak se ji snažím s využitím (pro mne) nové technologie naprogramovat. Kromě toho, že mohu snadno posoudit proveditelnost řešení, přináší tento způsob učení ještě jednu výhodu: mohu snadno narazit na zádrhel nebo potenciální problém, který budu muset nějak vyřešit. Což se při slepém následování tutoriálu obvykle nestává. Takhle mám zmapované nejen problémy, ale i jejich řešení.

Kromě již popsaných třech stavů diody jsem poznal životní cyklus Activity, vyřešil jsem ukládání dat aplikace, naučil jsem se práci se styly. A když jsem prohlásil první verzi aplikace za hotovou, zjistil jsem, že na publikování je totálně nepřipravená. Pokud by ale zůstalo jenom u tohoto, byl by tento blog jenom zbytečným plýtváním mým vlastním časem a diskovým prostorem mého poskytovatele hostingu.

Takže se pojďme podívat na ty zádrhele a specifické požadavky.

Hrací plocha - čtverec

Pro implemetaci hrací plochy se mi jevila jako nejvhodnější komponenta SurfaceView, protože se zde člověk při vykreslování nemusí omezovat na metodu onDraw(canvas) a vyvolávat událost "překreslit vše." Prvním úkolem k řešení bylo tohle:

  1. zjistit rozměry displeje
  2. podle kratšího rozměru stanovit velikost čtverce hrací plochy
  3. a podle velikosti hrací plochy pak určit velikost hrací kostky

Inspirací pro řešení byla pro mne hra TicTacToe coby ukázka programování jednoduché hry pro Android. Ukázka sice pracuje s komponentou View, princip je ale stejný: v metodě onMeasure(int widthMeasureSpec, int heightMeasureSpec) implementovat výpočet rozměrů hrací plochy a v metodě onSizeChanged(int w, int h, int oldw, int oldh) na základě rozměrů hrací plochy určit velikost hrací kostky.

Výměna fragmentů

Určitě jste zaznamenali, že ovládací panel pod hrací plochou vypadá jinak před hrou a v průběhu hry - viz screenshoty. Což vyplývá z logiky hry. Spuštění hry - stisk tlačítka "start" -  se skládá z následujících kroků:

  1. vyměnit ovládací panel
  2. zamíchat hrací kostky
  3. spustit časomíru

Kroky samy o sobě jsou jednoduché, nicméně už v tom prvním je skrytý potenciální problém. Výměna Fragmentů totiž probíhá jako transakce - tedy proces na pozadí. Časomíra je součástí panelu zobrazeného v průběhu hry, a pokud ji chci spustit, musím zajistit, aby panel již byl zobrazen. Tedy krok 3 nemohu provést dříve, než je krok 1 úplně dokončen.

Řešení je popsáno třeba tady a spočívá v explicitním vynucení a kontrole spuštění transakcí. Více dokumentace zde.

Zkrocení časomíry

Komponenta Chronometer je na první pohled užitečná, ale už si nejsem jistý, zda byla dobře navržena. Implementací jednoduchých stopek pomocí ní lze nalézt na Internetu více, jsou to však variace téhož. Rozhodl jsem se proto zveřejnit vlastní snippet.

	private long clockTime;
	private Chronometer clock;

	private void startClock() {
		clock.setBase(SystemClock.elapsedRealtime());
		clock.start();
	}
	
	private void stopClock() {
		clockTime = SystemClock.elapsedRealtime() - clock.getBase();
		clock.stop();
	}	
	
	private void resumeClock() {
		clock.setBase(SystemClock.elapsedRealtime() - clockTime);
		clock.start();
	}
	
	private void resetClock() {
		clock.setBase(SystemClock.elapsedRealtime());
	}

Inspirováno nalezenými řešeními. Výhodou je, že proměnná clockTime obsahuje naměřený čas, který mohu bez dalších úprav použít např. k určení nového high score.

Pro účel hry je tato implementace dostačující. Až budu potřebovat např. změnit formát zobrazení času nebo měřit čas v desetinách sekundy, bude to vyžadovat další programování.

Blikání hrací plochy

Posledním problémem, který jsem řešil, bylo poblikávání hrací plochy při míchání a při jednotlivých tazích. Příčina byla pro mne překvapující. To, že SurfaceView využívá dvojitý buffering, je vcelku pochopitelné, překvapením však pro mne bylo, jakým způsobem je dvojitý buffering implementován. Stručně: buffer A je zobrazen, zatímco do bufferu B kreslím. Když skončím kreslení, systém buffery A a B prohodí. Pokud chci pokračovat, kreslím do bufferu A, který je ovšem prázdný, obsah bufferu B do něj zkopírován nebyl.

Řešení spočívá v implementaci vlastní Bitmapy velikosti hrací plochy, do které kreslím a která kumuluje veškeré moje změny. Bitmapu pak překresluji do komponenty SurfaceView. Jakkoli se jedná o další krok navíc, zpomalení aplikace není patrné. Více informací a snippet je k nalezení zde.

Závěr

Co říct závěrem? H15 je možná jednoduchá hra. Vzhledem k výše napsanému ale musím říct, že to byla dobrá škola praxí.

Při té příležitosti jsem si vzpomněl na jednu prezentaci, které jsem se zúčastnil. Týkala se jistého vývojového prostředí (název si už nevybavím). Prezentující na jednom předpřipraveném příkladu ukazoval, jak snadno lze aplikaci pomalu jenom naklikat s naprostým minimem nutného programování. Všechno fungovalo nádherně. A pak jsem přišel já s nevinným dotazem: "já jsem onehdy programoval jistou aplikaci, vypadalo to takhle a takhle, umělo to to či ono, jak by se to dalo ve vašem prostředí snadno vytvořit?" Prezentující měl co dělat, aby z debaty odešel "bez ztráty kytičky."

Tagy: H15, Android, Java, Programování

Tento web bude tebe a tvůj počítač krmit piškotkami, jelikož a protože je to slušný web a jako takový ví, že je potřeba návštěvu řádně pohostit, aby se u nás cítila dobře. Užíváním tohoto webu potvrzuješ, že netrpíš mentální anorexií, nedržíš žádnou obskurní dietu a že můžeš piškotki do sebe cpát kdykoli a v jakémkoli množství. Více informací...