Úkoly na léto
Pexeso
Jsme tu s úkolem na léto. Cílem je dokončit pexeso, které jsme začali na posledních dvou lekcích Javy. Pexeso je trošku složitější aplikace, budeme ji tedy vyvíjet v několika krocích.
- Oživení toho, co jsme už udělali
- Naprogramovali jsme rozdání 8x8 kartiček na hlavní okno aplikace.
- Naprogramovali jsme zamíchání kartiček.
- Vytvořili jsme třídu
Karticka
, která sdružuje všechny údaje o jedné kartičce.
- Další postup
Nejprve uděláme menší úklid v kódu (odborně se mluví o refactoringu).
- Funkcionalitu rozdání kartiček, kterou pravděpodobně máte jako obsluhu události
priOtevreniOkna(...)
, přesuneme do samostatné metody a z obsluhy ji zavoláme:Původně:
private void priOtevreniOkna(WindowEvent e) { // prikazy }
Změníme na:
public void rozdejKarticky() { // prikazy } private void priOtevreniOkna(WindowEvent e) { rozdejKarticky(); }
- Podobně funcionalitu zamíchání kartiček máte v programu pravděpodobně jako obsluhu události
priStiskuBtnZamichat(...)
. I tu separujeme do samostatné metody a tuto metodu zavoláme z obsluhy události.Původně:
private void priStiskuBtnZamichat(ActionEvent e) { // prikazy }
Změníme na:
public void zamichejKarticky() { // puvodni prikazy } private void priStiskuBtnZamichat(ActionEvent e) { zamichejKarticky(); }
- Podíváme se na třídu Karticka. Trošku ji zjednodušíme.
Původně:
public class Karticka { Integer cisloKarty; // 0..63 Integer cisloObrazku; // 0..31 Integer poziceX; // 0..7 Integer poziceY; // 0..7 Boolean jeLicemNahoru; ImageIcon obrazekLice; ImageIcon obrazekRubu; JButton btnKarticka; }
Změníme na:
public class Karticka { Integer cisloObrazku; // 0..31 Boolean jeLicemNahoru; ImageIcon obrazekLice; ImageIcon obrazekRubu; JButton vizualniKomponenta; }
Základní pravidlo úklidu v kódu (refactoringu) je, že po změnách funguje program pořád stejně. Nepřidáváme ani neodebíráme žádnou funkcionalitu. Zkuste tedy aplikaci spustit a všechno musí pořád fungovat. Každá z vás má trochu jiný program, proto se mohou objevit drobné odchylky a z nich pramenící chyby. Je nutné je opravit, než budete pokračovat dál.
Pokud by to byl i přesto nepřekonatelný problém (a nešlo by to vyřešit naší radou například přes Facebook), můžete vyjít z mého projektu
30-Trida_Karticka
.
Najdete ho zde: Ukol_na_leto-Pexeso-zadani-heslo_czechitas.7z - Funkcionalitu rozdání kartiček, kterou pravděpodobně máte jako obsluhu události
- Přidáme funkcionalitu při kliknutí na kartičku
Budeme chtít, aby se kartička otočila, když se na ni klikne. Jednou rubem nahoru, podruhé lícem nahoru. Pro začátek není důležité, že to není podle pravidel pexesa.
- Připravme si tedy následující metody (a naprogramujme jejich těla):
public void otocKartickuLicemNahoru(Karticka karta) { // prikazy } public void otocKartickuRubemNahoru(Karticka karta) { // prikazy }
- Předchozí metody jsou ale zatím mrtvé. Nikdo je nevolá.
Potřebujeme, aby se tyto metody volaly při kliknutí na kartičku. Součástí objektu typuKarticka
je vizuální komponenta. Je typuJButton
. [Najděte si její deklaraci ve tříde Kartička] Tato vizuální komponenta má událost "při kliknutí". My této události zaregistrujeme obsluhu (addActionListener(...)
). Provedeme to na stejném místě, kde nastavujeme ostatní vlastnosti vizuální komponenty v metoděrozdejKarticky()
:public void rozdejKarticky() { // ... prikazy ... for (Integer y = 0; y < 8; y++) { for (Integer x = 0; x < 8; x++) { Karticka novaKarta = new Karticka(); // ... prikazy ... novaKarta.vizualniKomponenta.addActionListener( evt -> priKliknutiNaKarticku(novaKarta)); // ... prikazy ... } } } private void priKliknutiNaKarticku(Karticka karta) { if (karta.jeLicemNahoru) { otocKartickuRubemNahoru(karta); } else { otocKartickuLicemNahoru(karta); } }
Nyní by se měla každá kartička při kliknutí otáčet rubem nebo lícem nahoru.
- Připravme si tedy následující metody (a naprogramujme jejich těla):
- Úplné zmizení kartičky
Bude se nám hodit i metoda na úplné zmizení kartičky z okna aplikace.
public void nechejZmizetKarticku(Karticka karta) { karta.vizualniKomponenta.setVisible(false); contentPane.remove(karta.vizualniKomponenta); }
Je tam použita technika
contentPane.remove(...)
, což jsme neprobírali. Proto jsem zde uvedl celý výpis metody včetně těla. Metodu samozřejmě vyzkoušejte. Například tak, že změnítepriKliknutiNaKarticku(...)
tak, aby místo otáčení karty lícem nahoru, se karta úplně odstranila. - Hromadné metody
Dále budeme potřebovat hromadnou metodu, která otáčí všechny kartičky, a druhou metodu, která nechá všechny kartičky zmizet.
public void otocVsechnyKartickyRubemNahoru() { // prikazy } public void nechejZmizetVsechnyKarticky() { // prikazy }
Po naprogramování je opět vyzkoušejte. Například zavoláním z metody
priStiskuBtnZamichat(...)
. - Test na zbývající kartičky
Budeme potřebovat metodu, která vyhodnotí, zda je ještě vidět alespoň jedna kartička.
public Boolean zbyvajiNejakeKarticky() { // prikazy }
Metoda vrací
true
, pokud je alespoň jedna kartička vidět. Jinak řečeno,vizualniKomponenta
z alespoň jedné kartičky má vlastnostvisible
nastavenu natrue
. Pokud žádná kartička není vidět (všechny byly nechány zmizet), vracífalse
. - Vyhodnocení dvou kartiček
Poslední přípravná metoda, kterou budeme potřebovat, je vyhodnocení, zda dvě kartičky patří k sobě (zda jde o dvojici se stejným obrázkem).
Pokud ne, obě kartičky se pouze otočí rubem nahoru.
Pokud ano, obě kartičky zmizí, a pokud už nezbývá žádná kartička, pogratulujeme hráči k vítězství.public void vyhodnotUhodnotiDvojice(Karticka karta1, Karticka karta2) { // prikazy }
Gratulaci lze napsat například vyvoláním dialogového okna:
JOptionPane.showMessageDialog( this, "Gratulujeme! Vyhráli jste!", "Výhra", JOptionPane.INFORMATION_MESSAGE);
- Dokončení pexesa
Poslední krok je opravdové dokončení pexesa. Budeme potřebovat zabudovat pravidla ohledně postupného otáčení kartiček a vyhodnocování dvojic.
-
Protože bude nutno nejprve kliknout na
jednu kartičku a teprve pak na druhou,
budeme si potřebovat uchovávat v proměnných obě zvolené kartičky.
public class HlavniOkno extends JFrame { // ostatni promenne Karticka zvolenaKarta1; Karticka zvolenaKarta2; // metody }
- Nyní už jen změníme chování obsluhy události
priKliknutiNaKarticku(...)
Metoda musí:
-
Pokud je stisknutá kartička rubem nahoru,
otočit kartičku lícem nahoru.
Dále uložit otočenou kartičku buď do proměnné
zvolenaKarta1
nebozvolenaKarta2
, podle toho, která z těchto proměnných je ještěnull
. Na začátku jsou obě proměnnénull
. Po prvním stisknutím budezvolenaKarta1
nastavena azvolenaKarta2
zůstanenull
. Po druhém stisknutí bude nastavena izvolenaKarta2
. -
Pokud jsou již obě kartičky otočeny, je třeba vyhodnotit,
zda jde o dvojici a buď je nechat zmizet nebo jen otočit rubem nahoru
(tuto funkcionalitu už máme hotovou z minula v metodě
vyhodnotUhodnotiDvojice(...)
).private void priKliknutiNaKarticku(Karticka karta) { // prikazy }
-
Pokud je stisknutá kartička rubem nahoru,
otočit kartičku lícem nahoru.
Dále uložit otočenou kartičku buď do proměnné
-
Protože bude nutno nejprve kliknout na
jednu kartičku a teprve pak na druhou,
budeme si potřebovat uchovávat v proměnných obě zvolené kartičky.
- Vylepšení
Asi jste si všimly, že po vybrání druhé kartičky se obě karty buď ihned zase otočí rubem nahoru nebo zmizí. Je to způsobeno tím, že se druhá kartička sice otočí, ale ihned se vyhodnotí výsledek a rovnou se otočí zpět.
- Můžeme zkusit proces zpomalit čekáním:
-
Já ale navrhuji, abychom místo pevného čekání vyhodnotili
kartičky až při třetím kliknutí na libovolnou kartičku.
Zařídíme to snadno: Prohodíme podúkol i) a ii) z minulého bodu. Tím se dosáhne toho, že se budou vyhodnocovat kartičky až při kliknutí na třetí kartičku. Mně to přijde přirozenější. Můžete zkusit obě varianty a rozhodnout se samy.
private void priKliknutiNaKarticku(Karticka karta) { // ... prikazy ... ((JComponent)contentPane).paintImmediately( 0, 0, contentPane.getWidth(), contentPane.getHeight()); ThreadUtils.sleep(1000); // Vyhodnot karticky }
Celé řešení je po jednotlivých krocích k dispozici zde:
Ukol_na_leto-Pexeso-heslo_czechitas.7z
Snažte se ho ale nepoužít.