Stránka sa načítava, prosím čakajte…
© 2005 – 2024 Roman Horváth, všetky práva vyhradené. Dnes je 26. 4. 2024.
Dátum: 9. 2. 2015, pred niekoľkými rokmi, aktualizované: 6. 2. 2021, pred tromi rokmi
Táto kapitola obsahuje implementáciu pretrvávajúcej klasiky – Tetrisu. Keďže na názov hry sa stále vzťahujú autorské práva, je tento projekt pomenovaný s miernou obmenou názvu – Tetrisovina. Hra je úplne funkčná, ale projekt je otvorený možnostiam úprav a rozširovania.
Projekt bol vytvorený na vzdelávacie účely.
Ak sa zaujímate o novšie implementácie tejto hernej klasiky, pozrite si nasledujúce príspevky:
- Wumbotize. TETRIS EFFECT Grand Master Level 100 Expert Journey. YouTube, 2018. Dostupné na: ⟨https://youtu.be/geGPkZD_zY4⟩. Citované: 6. 9. 2020. Dĺžka: 1 h 48 min.
- syltefar. Tetris Effect (PS4) Journey Mode Full Playthrough [No commentary, First try, 1080p 60FPS]. YouTube, 2018. Dostupné na: ⟨https://youtu.be/ZublieZKaWM⟩. Citované: 6. 9. 2020. Dĺžka: 2 h 50 min.
Ukážky vzhľadu hry.
Balíček na prevzatie obsahuje všetky potrebné súbory projektu – zdrojové (*.java
), zvukové (*.wav
), hudobné (*.mid
) a ďalšie sprievodné (Pomocník.txt
):
tetrisovina.7z
96,83 kB (94,56 KiB), 6. 9. 2020
Návrhy na zlepšenia
- Pridanie konfigurovateľnej (zapínateľnej) funkcie zrkadlenia tetromín (ktorá by hráčovi umožnila zrkadlové prevracanie tetromín, pričom by zároveň z procesu generovania nových tetromín vylúčila zrkadlové duplikáty: Z – červené a J – modré).
- Zdokonalenie kreslenia spojených mín (pozri metódu
Tetromíno.kresliMíno(
…)
). - Celkové zdokonalenie grafickej stránky. Dopracovanie ikon k miestam zobrazenia nasledujúceho a podržaného tetromína.
- Vyhľadanie úplných pravidiel a dopracovanie systému bodovania (napríklad prideľovanie bodov za rôzne kombinované situácie).
- Overenie správneho fungovania odrazu tetromín pri rotácii (pozri správanie sa tetromín v príspevkoch citovaných vyššie) a prípadné doladenie mechanizmu odrazu.
- Zdokonalenie pravidiel: zväčšenie zásobníka prichádzajúcich tetromín, miešanie, zabezpečenie toho, aby hra nevygenerovala sekvenciu tetromín S (zelených) a Z (červených) dlhšiu ako štyri kusy (oficiálne pravidlo) a pod.
- Naprogramovanie rozhrania na konfiguráciu herných funkcií.
- Spracovanie rôznych jazykových verzií (s pomocou triedy
Texty
). - Tabuľka víťazov.
~
import knižnica.Farba; import knižnica.GRobot; import knižnica.Oblasť; import knižnica.Písmo; import knižnica.Zoznam; /** * <p>Táto trieda slúži na zobrazovanie rôznych informačných správ počas hry. * Ide napríklad o zobrazenie hodnoty skóre, ktoré bolo práve získané, * informovanie o prechode na ďalšiu úroveň, prípadne uznanie vykonania * určitej špeciálnej akcie v hre a podobne. Možnosti sú otvorené, no v tejto * implementácii hry je hlásenie použité len v niekoľkých prípadoch na * ukážku.</p> * * @author Roman Horváth * @version 5. 9. 2020 */ public class Hlásenie extends GRobot { // Písmo použité všetkými hláseniami. private final static Písmo písmo = new Písmo("Helvetica", Písmo.BOLD, 26); // Farba použitá všetkými hláseniami. private final static Farba farba = svetlomodrá; // Zoznam hlásení na recykláciu. private final static Zoznam<Hlásenie> hlásenia = new Zoznam<Hlásenie>(); /** * <p>Statická metóda slúžiaca na automatické vyrobenie alebo recykláciu * hlásenia a jeho aktivovanie. Zadaný text je textom hlásenia a zdržanie * slúži na oddialenie zobrazenia hlásenia, aby bolo možné v jednom čase * určiť vznik viacerých hlásení, ktoré budú v hre zobrazované * postupne.</p> * * @param text znenie tohto hlásenia * @param zdržanie čas oddialenia zobrazenia tohto hlásenia * (v sekundách) */ public static void hlásenie(String text, int zdržanie) { Hlásenie hlásenie1 = null; for (Hlásenie hlásenie2 : hlásenia) if (hlásenie2.neaktívny()) { hlásenie1 = hlásenie2; break; } if (null == hlásenie1) { hlásenie1 = new Hlásenie(text, zdržanie); hlásenia.pridaj(hlásenie1); } else { hlásenie1.aktivuj(text, zdržanie); } } // Nasledujúce štyri súkromné atribúty slúžia na uchovanie textu, oblasti, // zdržania a trvania tohto hlásenia, pričom oblasť je vyrobená z textu // v metóde aktivuj(String, int). Oblasť slúži na zobrazovanie (kreslenie) // textu. V skutočnosti je atribút text v tejto implementácii nepotrebný. // Bol definovaný pre prípad inej implementácie, ktorá mala brať do úvahy // text neaktívnej inštancie počas recyklácie. Význam atribútu trvanie by // mal byť zrejmý – určuje trvanie zobrazovania tohto hlásenia a význam // zdržania je diskutovaný v komentároch pri metóde aktivuj(String, int). private String text; private Oblasť oblasť; private int zdržanie; private int trvanie; /** * <p>Konštruktor hlásenia. Zadaný text je textom tohto hlásenia * a zdržanie slúži na oddialenie zobrazenia tohto hlásenia. Dôvod * oddialenia je diskutovaný v komentároch statickej metódy * hlásenie(String, int).</p> * * @param text znenie tohto hlásenia * @param zdržanie čas oddialenia zobrazenia hlásenia (v sekundách) */ public Hlásenie(String text, int zdržanie) { skry(); písmo(písmo); kresliNaStrop(); zdvihniPero(); nekresliTvary(); hrúbkaČiary(0.75); aktivuj(text, zdržanie); } // Súkromná metóda slúžiaca na aktivovanie tohto hlásenia. To znamená, že // hlásenia majú právo týmto spôsobom aktivovať len metódy tejto triedy. // // V tomto projekte ide o dve metódy – konštruktor (poznámka: konštruktory // sú špeciálnymi prípadmi metód) a statická metóda hlásenie(String, int), // ktorá môže recyklovať jestvujúce pasívne inštancie hlásení. // // V tejto metóde je vypočítaný aj čas trvania tohto hlásenia, ktorý je // stanovený ako desaťnásobok dĺžky textu hlásenia a je meraný v tikoch. // // Želanú dĺžku trvanie tiku môže určiť aplikácia. Predvolená hodnota je // 40 ms – 25-násobok je jedna sekunda. Skutočná dĺžka tiku sa od želanej // môže líšiť, pretože časovač je ovplyvňovaný zaťažením systému. // // Poznámka: Táto metóda inicializuje aj oblasť, ktorej účel je // diskutovaný vyššie v tomto zdrojovom kóde – v komentári // patriacemu ku skupine súkromných atribútov. private void aktivuj(String text, int zdržanie) { domov(); this.text = text; this.zdržanie = 25 * zdržanie; trvanie = 10 * text.length(); oblasť = new Oblasť(text(text)); aktivuj(); } /** * <p>Prekrytá metóda aktivita slúži na zariadenie fungovania nasledujúceho * mechanizmu:</p> * * <ol> * <li><!--1.-->odďaľuje zobrazenie čakajúcich hlásení,</li> * <li><!--2.-->zobrazuje aktívne hlásenia, pričom čas aktivity je * vypočítaný v metóde aktivuj(String, int),</li> * <li><!--3.-->deaktivuje hlásenia, ktorých čas aktivácie prekročil * stanovenú dĺžku trvania.</li> * </ol> */ @Override public void aktivita() { if (zdržanie > 0) { --zdržanie; } else if (trvanie > 0) { --trvanie; farba(farba); vyplňOblasť(oblasť); farba(biela); obkresliOblasť(oblasť); skoč(1); } else { deaktivuj(); } } }
Hudba » |
~
import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; import javax.sound.midi.MidiSystem; import javax.sound.midi.Sequencer; import javax.sound.midi.MidiUnavailableException; import javax.sound.midi.InvalidMidiDataException; /** * <p>Účelom tejto triedy je poskytnúť jednoduché rozhranie na prehrávanie * rôznych melódií vo formáte MIDI. Statické časti triedy slúžia na uchovanie * série skladieb (angl. playlistu) a riadenie prehrávania skladieb. Inštancie * triedy reprezentujú jednotlivé skladby.</p> * * <p> </p> * * <p><b>Pri tvorbe triedy bol použitý nasledujúci zdroj informácií:</b></p> * * <p><a * href="https://examples.javacodegeeks.com/desktop-java/sound/play-midi-audio/" * target="_blank"><small>Ilias Tsagklis</small>: <em>Play Midi audio.</em> * Examples Java Code Geeks, 2012.</a></p> * * @author Roman Horváth * @version 5. 9. 2020 */ public class Hudba { /** <p>Stavový atribút umožňujúci umlčanie hudby.</p> **/ public static boolean zapnutá = true; // Tento súkromný statický atribút slúži na uchovanie poradového čísla // práve prehrávanej skladby. Zoznam skladieb je uložený v poli skladby. private static int skladba = -1; // Súkromné statické pole inštancií slúžiace na uchovanie zoznamu // prehrávaných skladieb (nazývaného aj playlist). private static Hudba skladby[] = { new Hudba("tetris-music-a"), new Hudba("tetris-music-b"), }; // Súkromná inštancia rezervovaná pre skladbu prehrávanú pri konci hry. private static Hudba koniecHry = new Hudba("game-over"); /** * <p>Zistí, či je práve prehrávaná niektorá zo skladieb v zozname (poli) * skladieb.</p> * * @return true, ak nie je prehrávaná žiadna skladba */ public static boolean jeTicho() { for (Hudba skladba : skladby) if (skladba.hrá()) return false; return true; } /** * <p>Spustí prehrávanie nasledujúcej skladby v zozname (poli) * skladieb.</p> */ public static void ďalšiaSkladba() { ++skladba; if (skladba >= skladby.length) skladba = 0; skladby[skladba].spusti(); } /** * <p>Zastaví všetky ostatné skladby a spustí skladbu určenú na * signalizáciu konca hry.</p> */ public static void koniecHry() { for (Hudba skladba : skladby) skladba.zastav(); koniecHry.spusti(); } /** * <p>Zastaví všetky skladby.</p> */ public static void zastavVšetko() { for (Hudba skladba : skladby) skladba.zastav(); koniecHry.zastav(); } // Sekvencér je potrebný pri prehrávaní skladby čítanej prostredníctvom // prúdu údajov zo súboru. private Sequencer sekvencér; // Názov súboru je využitý pri opakovanom prehrávaní skladby, pretože pri // každom prehratí skladby je (opakovane) vytvorený údajový prúd zo súboru. // (Z pohľadu „lenivého programátora“ ide o najjednoduchšie riešenie. // Nie je efektívne, ale na potreby tohto ukážkového projektu postačuje. // Lepšie by bolo pokúsiť sa otvorený prúd údajov resetovať a recyklovať.) private String názovSúboru; /** * <p>Konštruktor inštancií triedy Hudba prijíma len jadro názvu súboru * vo formáte MIDI. K jadru názvu súboru je bez overenia jej prípadnej * prítomnosti pridaná prípona .mid. Súbory sú čítané z priečinka * Hudba.</p> * * @param názovSúboru názov súboru so skladbou bez prípony a názvu * podpriečinka */ public Hudba(String názovSúboru) { this.názovSúboru = "Hudba/" + názovSúboru + ".mid"; try { // Získane predvoleného sekvencéra. sekvencér = MidiSystem.getSequencer(); } catch (MidiUnavailableException e) { sekvencér = null; } } /** * <p>Spustí prehrávanie tejto skladby. Pri každom prehrávaní je vytvorený * údajový prúd zo súboru zadaného v konštruktore.</p> */ public void spusti() { zastav(); if (null != sekvencér) { try { // Vytvorenie prúdu údajov zo súboru. InputStream prúdZoSúboru = new BufferedInputStream( new FileInputStream(new File(názovSúboru))); // Otvorenie zariadenia. sekvencér.open(); // Nastavenie aktuálnej sekvencie na vstup zo súboru. sekvencér.setSequence(prúdZoSúboru); } catch (MidiUnavailableException | InvalidMidiDataException | IOException e) { e.printStackTrace(); } // Začatie prehrávania z načítanej sekvencie. sekvencér.start(); } } /** * <p>Zastaví prehrávanie skladby. Spolu s tým je korektne zavretý * sekvencér MIDI. Keby nebol sekvencér pred ukončením aplikácie korektne * zavretý, aplikácia by sa neukončila, pretože aktívny sekvencér vytvára * samostatné vlákno a žiadna aplikácia nesmie byť ukončená ak sú ešte * aktívne nejaké vlákna.</p> */ public void zastav() { if (null != sekvencér && sekvencér.isOpen()) { // Zastavenie prehrávania. sekvencér.stop(); // Zatvorenie zariadenia. sekvencér.close(); } } /** * <p>Zistí, či je táto skladba prehrávaná. (V skutočnosti metóda zisťuje, * či pracuje súkromná inštancia sekvencéra. Keď sa pozrieme na to, ako * bola táto inštancia získaná, tak zistíme, že by malo ísť o predvolený * sekvencér systému MIDI. Je možné, dokonca pravdepodobné, že sa všetky * skladby delia o ten istý sekvencér, ktorému je len zakaždým priradený * iný prúd údajov. V súvislosti s touto informáciou je pravdepodobné, že * táto metóda má návratovú hodnotu true ak hrá akákoľvek skladba. Na * potreby tohto ukážkového projektu je však aj toto riešenie vysoko * postačujúce a preto tieto skutočnosti neboli overované.)</p> * * @return true, ak táto skladba práve hrá */ public boolean hrá() { return sekvencér.isRunning(); } }
« Hlásenie | Kontrola » |
~
/** * <p>Tento vymenovací typ slúži na riadenie gravitácie a označovanie * a mazanie plných riadkov počas hry. Názov každého prvku je sformulovaný * tak, aby odpovedal na otázku: „Aká kontrola má byť vykonaná v tejto * pracovnej iterácii?“ Každý prvok vymenovacieho typu, okrem posledného, * považuje za svojho nasledovníka prvok s najbližšou vyššou ordinálnou * hodnotou. Posledný prvok (s názvom ŽIADNA) nemá nasledovníka. Jeho metóda * ďalšia má návratovú hodnotu null. Jej volanie by však nikdy nemalo nastať, * pretože typ kontroly ŽIADNA je považovaný za neutrálny stav, v ktorom nie * je potrebné hľadať nasledovníka.</p> * * @author Roman Horváth * @version 5. 9. 2020 */ public enum Kontrola { /** * <p>Tento stav kontroly určuje, že v tejto pracovnej iterácii majú byť * vyhľadané a označené všetky plné riadky.</p> */ NÁJDI_A_OZNAČ_RIADKY, /** * <p>Tento stav kontroly určuje, že v tejto pracovnej iterácii majú byť * vymazané všetky riadky, ktoré boli označené počas predchádzajúcej * pracovnej iterácie.</p> */ VYMAŽ_OZNAČENÉ_RIADKY, /** * <p>Tento stav určuje, že počas tejto pracovnej iterácie sa má vykonať * činnosť nazývaná gravitácia.</p> * * <p>Gravitácia je aktivita posúvajúca bloky rovnakej farby v súlade so * zákonom gravitácie. Je to konfigurovateľná vlastnosť hry (dá sa zapnúť * alebo vypnúť v konfiguračnom súbore). Ak je gravitácia aktívna, tak * tento stav kontroly pretrváva dokedy už nie je možné pohnúť žiadnym * blokom.</p> * * <!-- Pozri poznámku v komentároch metódy Tetrisovina.gravitáciaAktívna() * alebo podrobnejšie vysvetlenie v priebežných komentároch v tele triedy * Tetromíno. --> */ VYKONAJ_GRAVITÁCIU, /** * <p>Toto je pasívny stav kontroly, ktorý nemá žiadneho nasledovníka.</p> */ ŽIADNA { @Override public Kontrola ďalšia() { return null; } }; /** * <p>Toto je metóda, ktorá vráti nasledovníka pre každú hodnotu * vymenovacieho typu. S jej pomocou je implementované zreťazenie * stavov určujúcich typ kontroly alebo akcie súvisiacej s mazaním * plných riadkov a posúvania blokov v súlade s činnosťou nazývanou * gravitácia.</p> * * @return nasledovník tohto prvku */ public Kontrola ďalšia() { return values()[ordinal() + 1]; } }
« Hudba | Tetrisovina » |
~
import knižnica.GRobot; import knižnica.Kláves; import knižnica.ObsluhaUdalostí; import knižnica.Písmo; import knižnica.Súbor; import knižnica.Svet; import knižnica.ÚdajeUdalostí; import knižnica.Zoznam; /** * <p>Táto trieda je hlavnou triedou hry. Obsahuje mnoho rôznych súkromných * atribútov, medzi ktorými je pole hracej plochy, zoznam tetromín, * atribúty ukazujúce na aktuálne, podržané, ďalšie a tieňové tetromíno * v zozname tetromín, rôzne atribúty uchovávajúce konfiguráciu, skóre, * rôzne stavy a podobne. Často používaným termínom v tomto projekte je * míno. Míno je (nielen) v terminológii Tetrisu jeden štvorček tetromína.</p> * * @author Roman Horváth * @version 5. 9. 2020 */ public class Tetrisovina extends GRobot { /** * <p>Táto konštanta slúži na uchovanie indexu ľavého okraja viditeľnej * časti hracej plochy. Vznikla spolu s konštantami * PRAVÝ_OKRAJ_HRACEJ_PLOCHY a DOLNÝ_OKRAJ_HRACEJ_PLOCHY, pretože * v priebehu implementácie vznikla potreba definovať a priebežne podľa * potrieb upravovať skutočný rozmer poľa reprezentujúceho hraciu plochu. * Neviditeľná časť tohto poľa (neviditeľná pre hráča) uchováva betónové * steny a dno slúžiace ako barikády pre padajúce a rotujúce tetromína. * Hrúbka steny sa musela meniť napríklad počas implementácie odrazenia * sa tetromína od jestvujúcej stavby. Zavedením trojice konštánt je * ďalšia zmena rozmerov plochy (a s tým súvisiaca úprava umiestnenia * viditeľnej časti hracej plochy v rámci poľa) jednoduchšia.</p> */ public final static byte ĽAVÝ_OKRAJ_HRACEJ_PLOCHY = 4; /** * <p>Táto konštanta slúži na uchovanie indexu pravého okraja viditeľnej * časti hracej plochy. Podrobnosti o jej účele sú bližšie diskutované * pri konštante ĽAVÝ_OKRAJ_HRACEJ_PLOCHY.</p> */ public final static byte PRAVÝ_OKRAJ_HRACEJ_PLOCHY = 13; /** * <p>Táto konštanta slúži na uchovanie indexu dolného okraja viditeľnej * časti hracej plochy. Podrobnosti o jej účele sú bližšie diskutované * pri konštante ĽAVÝ_OKRAJ_HRACEJ_PLOCHY.</p> */ public final static byte DOLNÝ_OKRAJ_HRACEJ_PLOCHY = 21; // Pole hracej plochy. Je v skutočnosti o niečo väčšie oproti kreslenej // hracej ploche, pretože do neho zarátavame aj neviditeľné hrubé steny // a dno a prázdny priestor nad „jadrom“ hracej plochy. Steny a dno slúžia // ako prirodzená bariéra pre tetromína padajúce, pohybujúce sa a rotujúce // ponad hracou plochou (teda v čase, keď ešte nie sú uložené). Sú // dvojnásobne hrubé, čo by malo byť dostatočnou prirodzenú prekážkou aj // pre tetromíno typu I, ktoré hráč otočí tesne pri stene alebo dne. // Veľkosť priestoru nad hracou plochou je ekvivalentný jednému prázdnemu // riadku, čo dáva priestor na zobrazenie väčšiny nových tetromín čiastočne // nad hracou plochou. private final static byte hraciaPlocha[][] = new byte[18][25]; // Hodnota tohto atribútu je načítaná z konfiguračného súboru. Ak je true, // tak počas hrania hry sa vo vhodnej chvíli (najčastejšie po vymazaní // úplne zaplnených riadkov) aktivuje režim gravitácie, počas ktorého // môže nastať pohnutie časti jestvujúcej stavby v súlade so zákonmi // gravitácie. Toto správanie však nie je v tejto implementácii hry úplne // v súlade s klasickou gravitáciou ako ju poznáme. Uvoľnené bloky stavby // sa v režime gravitácie posúvajú veľmi rýchlo – tak, aby čo najskôr // dosiahli stabilné umiestnenie a aby mohol byť čo najskôr obnovený // klasický režim hrania hry. Menej sústredený hráč si ani nemusí stihnúť // uvedomiť, čo sa vlastne v režime gravitácie stalo (zmenilo). private static boolean gravitáciaAktívna = false; // Tento atribút slúži na signalizáciu toho, či počas vykonávania činnosti // nazývanej gravitácia bolo pohnuté nejakou časťou stavby na hracej // ploche. To znamená, že sa našiel súvislý blok mín (míno je jeden // štvorček tetromína) jednej farby, ktorý nebol zospodu ničím blokovaný // a gravitácia ním mohla pohnúť. Tento príznak má využitie aj pri // určovaní toho, či má byť režim (resp. „stav kontroly“) gravitácie // v hre vypnutý a teda či hra môže prejsť do stavu kontroly ŽIADNA, // ktorý hráčovi umožňuje normálne hrať hru. private static boolean gravitáciaNiečímPohla = false; // Hodnota tohto atribútu je čítaná z konfiguračného súboru. Ak je true, // znamená to, že je aktívna funkcia podržania, ktorá dáva hráčovi // k dispozícii jeden zásobník na tetromína navyše. private static boolean podržanieAktívne = false; // Tento statický blok počas inicializácie triedy Tetrisovina inicializuje // hraciu plochu a pošle ju do statickej časti triedy Tetromíno. static { vybudujStenyADno(); vymažHraciuPlochu(); Tetromíno.nastavHraciuPlochu(hraciaPlocha); } // Zoznam tetromín na recykláciu. Po rozbehnutí sa hry už nebude potrebné // vytvárať nové tetromína. Ušetríme tým prácu virtuálnemu stroju Javy, // pretože nebude potrebné vykonávať stále nové konštruovanie objektov // a nebude zahadzované zbytočne veľké množstvo recyklovateľných objektov // (o ktorých uvoľnenie sa stará zberač odpadkov Javy). private final Zoznam<Tetromíno> tetromína = new Zoznam<Tetromíno>(); // Tento zoznam obsahuje riadky textu pomocníka, ktoré sú načítané zo // súboru v čase zobrazenia pomocníka. private Zoznam<String> textPomocníka = new Zoznam<String>(); // Nasledujúci atribút slúži na riadenie kontroly plných riadkov a ich // mazania. private Kontrola kontrola = Kontrola.ŽIADNA; // Nasledujúci atribút slúži na zvýšenie skóre pri rotovaných spôsoboch // uloženia. private byte rotačnýBonus = 0; // Počítanie za sebou idúcich sérií. private byte séria = -1; // Atribúty, ktoré ukazujú na aktuálne, podržané, ďalšie a tieňové // tetromíno v zozname tetromín. Podržané a tieňové tetromíno sú použité // v závislosti od konfigurácie hry. private Tetromíno aktuálneTetromíno, ďalšieTetromíno, podržanéTetromíno, tieňovéTetromíno; // Aktuálna úroveň hry. Od nej sa odvíja rýchlosť hrania a počet riadkov, // ktorý je potrebné zaplniť na postup na ďalšiu úroveň. private byte úroveň = 1; // Počet riadkov, ktoré sa podarilo zaplniť v rámci tejto úrovne. // Dosiahnutie určitej hodnoty garantuje postup na ďalšiu úroveň. Pri // zaplnení viacerých riadkov naraz (alebo v určitých bonusových // situáciách) je počet pripočítaných riadkov vyšší než skutočný počet // vyplnených riadkov. private byte početRiadkov = 0; // Počítadlo tikov a zdržanie slúžia na spomalenie hry. Keď hodnota // počítadla prekročí hodnotu zdržania, hra sa posunie o jeden hrací // cyklus. Čím je hodnota zdržania nižšia, tým je rýchlosť hrania vyššia. // Hodnota zdržania je prepočítaná po každej zmene hodnoty úrovne (vrátane // štartu novej hry). private byte počítadlo = 0; private byte zdržanie = 48; // Nasledujúca dvojica atribútov slúži na uchovanie hodnoty skóre // a animáciu jeho zvyšovania, prípadne znižovania. Atribút skutočnéSkóre // uchováva skutočnú hodnotu skóre a podľa nej sa priebežne upravuje // (zvyšuje/znižuje) hodnota atribútu zobrazovanéSkóre, kým nie sú rovnaké. // Tým vzniká animácia zobrazovaného skóre. private int skutočnéSkóre = 0; private int zobrazovanéSkóre = 0; // Príznak zobrazenia stránky s pomocníkom. Ak je hodnota tohto atribútu // true, tak je hra pozastavená a na obrazovke sú zobrazené inštrukcie pre // hráča. private boolean pomocník = false; // Príznak pozastavenia hry – pauzy. Ak je hodnota tohto atribútu true, // tak je hra pozastavená a na obrazovke je zobrazená informácia o tom, // že hra je pozastavená. private boolean pauza = false; // Príznak zrýchlenia padania aktuálneho tetromína – mäkkého pokladania. // Keď je hodnota tohto atribútu rovná true, tak je vertikálny pohyb // aktuálneho tetromína (jeho padanie) zrýchlené na maximálnu možnú // hranicu. Hráč získava za zrýchlenie padania body, ale tvrdým položením // získa dvojnásobok. private boolean mäkkéPoloženie = false; // Konštruktory // ------------ // Táto trieda má len jeden súkromný konštruktor, ktorý neprijíma žiadny // parameter a zabezpečuje celkovú inicializáciu aplikácie. private Tetrisovina() { super(400, 400); skry(); písmo(new Písmo("Arial", Písmo.TUČNÉ, 24)); Svet.nekresli(); nakresliPozadie(); Svet.spustiČasovač(0.025); nováHra(); Svet.zastavČasovač(); obsluhaUdalostí(); if (podržanieAktívne) { // Ak je podržanie aktívne, treba prekresliť pozadie. podlaha.vymažGrafiku(); domov(); nakresliPozadie(); } Svet.spustiČasovač(0.025); } // Súkromné metódy // --------------- // Táto súkromná metóda obsahuje úplnú obsluhu udalostí aplikácie. Ide // o udalosti klávesnice (na ovládanie hry), čítania a zápisu konfigurácie // a ukončenie aplikácie. private void obsluhaUdalostí() { new ObsluhaUdalostí() { @Override public void stlačenieKlávesu() { if (pomocník) { if (ÚdajeUdalostí.kláves(Kláves.ESCAPE) || ÚdajeUdalostí.kláves(Kláves.VK_F1)) { Zvuky.pauza.prehraj(); pomocník = false; zobrazPlochu(); Svet.prekresli(); } } else if (aktívny()) { if (pauza) { if (ÚdajeUdalostí.kláves(Kláves.ESCAPE) || ÚdajeUdalostí.kláves(Kláves.VK_F1)) { Zvuky.pauza.prehraj(); pomocník = true; čítajPomocníka(); zobrazPlochu(); Svet.prekresli(); } else if (//ÚdajeUdalostí.kláves(Kláves.VK_F10) || ÚdajeUdalostí.kláves(Kláves.VK_P)) { Zvuky.pauza.prehraj(); pauza = false; zobrazPlochu(); Svet.prekresli(); } } else { switch (ÚdajeUdalostí.kláves()) { //case Kláves.VK_F10: case Kláves.VK_P: Zvuky.pauza.prehraj(); pauza = true; zobrazPlochu(); Svet.prekresli(); break; case Kláves.VK_A: case Kláves.VĽAVO: if (aktuálneTetromíno.vľavo()) { Zvuky.pohni.prehraj(); zobrazPlochu(); Svet.prekresli(); } else Zvuky.nepohni.prehraj(); break; case Kláves.VK_S: case Kláves.VPRAVO: if (aktuálneTetromíno.vpravo()) { Zvuky.pohni.prehraj(); zobrazPlochu(); Svet.prekresli(); } else Zvuky.nepohni.prehraj(); break; case Kláves.VK_C: case Kláves.VK_H: case Kláves.VK_M: vymeňTetromíno(); break; case Kláves.VK_V: case Kláves.VK_B: case Kláves.VK_N: podržTetromíno(); break; case Kláves.VK_D: case Kláves.DOLE: mäkkéPoloženie = true; break; case Kláves.VK_Z: case Kláves.VK_Y: case Kláves.VK_COMMA: // (čiarka) if (aktuálneTetromíno.otočVľavo()) { Zvuky.rotuj.prehraj(); zobrazPlochu(); Svet.prekresli(); } else Zvuky.nerotuj.prehraj(); break; case Kláves.HORE: case Kláves.VK_X: case Kláves.VK_PERIOD: // (bodka) if (aktuálneTetromíno.otočVpravo()) { Zvuky.rotuj.prehraj(); zobrazPlochu(); Svet.prekresli(); } else Zvuky.nerotuj.prehraj(); break; case Kláves.MEDZERA: // Tvrdé položenie. if (aktuálneTetromíno.naVrcholStavby()) { skutočnéSkóre += aktuálneTetromíno. poslednáVýškaPádu() * 2; ďalšieTetromíno(); } else { deaktivuj(); } zobrazPlochu(); Svet.prekresli(); break; case Kláves.ESCAPE: case Kláves.VK_F1: Zvuky.pauza.prehraj(); pomocník = true; čítajPomocníka(); zobrazPlochu(); Svet.prekresli(); break; case Kláves.VK_F5: Zvuky.neprepni.prehraj(); } } } else { switch (ÚdajeUdalostí.kláves()) { //case Kláves.VK_F10: case Kláves.VK_P: case Kláves.VK_A: case Kláves.VK_S: case Kláves.VĽAVO: case Kláves.VPRAVO: case Kláves.VK_C: case Kláves.VK_H: case Kláves.VK_M: case Kláves.VK_V: case Kláves.VK_B: case Kláves.VK_N: case Kláves.VK_D: case Kláves.DOLE: case Kláves.VK_Z: case Kláves.VK_Y: case Kláves.VK_COMMA: // (čiarka) case Kláves.HORE: case Kláves.VK_X: case Kláves.VK_PERIOD: // (bodka) case Kláves.MEDZERA: Zvuky.neprepni.prehraj(); break; case Kláves.ESCAPE: case Kláves.VK_F1: Zvuky.pauza.prehraj(); pomocník = true; čítajPomocníka(); zobrazPlochu(); Svet.prekresli(); break; case Kláves.VK_F5: nováHra(); } } } @Override public void uvoľnenieKlávesu() { switch (ÚdajeUdalostí.kláves()) { case Kláves.DOLE: mäkkéPoloženie = false; break; case Kláves.VK_F2: Zvuky.zapnuté = !Zvuky.zapnuté; break; case Kláves.VK_F3: Hudba.zapnutá = !Hudba.zapnutá; break; } } @Override public boolean konfiguráciaZmenená() { return true; } @Override public void čítajKonfiguráciu(Súbor súbor) throws java.io.IOException { skutočnéSkóre = súbor.čítajVlastnosť( "skóre", (long)skutočnéSkóre).intValue(); séria = súbor.čítajVlastnosť( "séria", (long)séria).byteValue(); String reťazec = súbor.čítajVlastnosť( "plocha", (String)null); if (null != reťazec) reťazecDoPoľa(reťazec, hraciaPlocha); reťazec = súbor.čítajVlastnosť( "podržanéTetromíno", (String)null); if (null != reťazec) { podržanéTetromíno = new Tetromíno(reťazec, súbor); tetromína.pridaj(podržanéTetromíno); } reťazec = súbor.čítajVlastnosť( "ďalšieTetromíno", (String)null); if (null != reťazec) { ďalšieTetromíno = new Tetromíno(reťazec, súbor); tetromína.pridaj(ďalšieTetromíno); } reťazec = súbor.čítajVlastnosť( "aktuálneTetromíno", (String)null); if (null != reťazec) { aktuálneTetromíno = new Tetromíno(reťazec, súbor); tieňovéTetromíno = new Tetromíno(aktuálneTetromíno); tetromína.pridaj(aktuálneTetromíno); } pauza = súbor.čítajVlastnosť("pauza", pauza); úroveň = súbor.čítajVlastnosť("úroveň", (long)1).byteValue(); početRiadkov = súbor.čítajVlastnosť("početRiadkov", (long)početRiadkov).byteValue(); gravitáciaAktívna = súbor.čítajVlastnosť( "gravitáciaAktívna", gravitáciaAktívna); podržanieAktívne = súbor.čítajVlastnosť( "podržanieAktívne", podržanieAktívne); Zvuky.zapnuté = súbor.čítajVlastnosť("zvukyZapnuté", Zvuky.zapnuté); Hudba.zapnutá = súbor.čítajVlastnosť("hudbaZapnutá", Hudba.zapnutá); konfigurujÚroveň(); začniKontrolu(); } @Override public void zapíšKonfiguráciu(Súbor súbor) throws java.io.IOException { súbor.zapíšVlastnosť("skóre", skutočnéSkóre); súbor.zapíšVlastnosť("séria", séria); súbor.zapíšVlastnosť("plocha", poleNaReťazec(hraciaPlocha)); if (null == podržanéTetromíno) súbor.zapíšVlastnosť("podržanéTetromíno", (String)null); else podržanéTetromíno.ulož("podržanéTetromíno", súbor); ďalšieTetromíno.ulož("ďalšieTetromíno", súbor); aktuálneTetromíno.ulož("aktuálneTetromíno", súbor); súbor.zapíšVlastnosť("pauza", pauza); súbor.zapíšVlastnosť("úroveň", úroveň); súbor.zapíšVlastnosť("početRiadkov", početRiadkov); súbor.zapíšVlastnosť("gravitáciaAktívna", gravitáciaAktívna); súbor.zapíšVlastnosť("podržanieAktívne", podržanieAktívne); súbor.zapíšVlastnosť("zvukyZapnuté", Zvuky.zapnuté); súbor.zapíšVlastnosť("hudbaZapnutá", Hudba.zapnutá); } @Override public void ukončenie() { Hudba.zastavVšetko(); } }; } // Načíta texty pomocníka. private void čítajPomocníka() { try { textPomocníka.vymaž(); súbor.otvorNaČítanie("Pomocník.txt"); String riadok; while (null != (riadok = súbor.čítajRiadok())) { // Reťazec #hold# je v texte pomocníka rezervovaný. if (-1 != riadok.indexOf("#hold#")) { // Riadok, ktorý tento reťazec obsahuje je ponechaný len // vtedy, ak je zapnutá funkcia podržania, pričom všetky // výskyty reťazca #hold# sú z tohto riadka vymazané. if (podržanieAktívne) textPomocníka.pridaj(riadok.replace("#hold#", "")); } else textPomocníka.pridaj(riadok); } súbor.zavri(); } catch (Exception e) { // Vypíše chyby. System.err.println(e.getMessage()); } } // Začne ďalšiu sériu kontroly plných riadkov. Túto kontrolu je nevyhnutné // vykonať napríklad po položení (zamknutí) tetromína alebo nejakej zmene // na hracej ploche. Kontrola je pre istotu spustená aj pri začatí novej // hry. V podstate ide len o vloženie tej správnej hodnoty do atribútu // kontrola. To spôsobí zmenu správania programu na inom mieste. Konkrétne // v metóde aktivita tejto triedy. private void začniKontrolu() { kontrola = Kontrola.NÁJDI_A_OZNAČ_RIADKY; } // Vykoná nevyhnutné kroky sprevádzajúce zmenu úrovne hry. Tým je myslené // nielen zvýšenie aktuálnej úrovne, ale aj začatie novej hry. Ide // o vynulovanie počítadiel, ktoré slúžia na signalizáciu toho, kedy má // byť zvýšená úroveň. Popri tom je upravená hodnota zdržania určujúceho // rýchlosť hry. Zdržanie je tým menšie, čím je úroveň hry vyššia. private void konfigurujÚroveň() { početRiadkov = počítadlo = 0; zdržanie = (úroveň >= 15) ? 3 : (byte)(48 - úroveň * 3); } // Táto metóda zabezpečuje jednu z fáz kontroly plných riadkov. Metóda // hľadá riadky, ktoré sú úplne zaplnené mínami – štvorčekmi tetromín. private void nájdiAOznačPlnéRiadky() { /*#* *#* UPOZORNENIE – musíme obrátiť cykus i a j, aby sme postupovali *#* po riadkoch, a nie po stĺpcoch ako pri kreslení. *#*/ int početRiadkov = 0, početOznačených = 0, navýšenieSkóre = 0; for (byte j = 0; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) { boolean jePlný = true; for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) { if (0 == hraciaPlocha[i][j] || 8 <= hraciaPlocha[i][j]) { jePlný = false; break; } } if (jePlný) { ++početOznačených; for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) hraciaPlocha[i][j] += 7; } } // Nasledujúci systém bodovania vychádza v matematickom význame // zo štyroch premenných: // // - počet označených riadkov (koľko ich zmizne); // - rotačný bonus – ten pochádza z metódy ďalšieTetromíno // a jeho hodnota kolíše medzi nulou až štvorkou; bonus je // určený podľa viacerých faktorov: či tetromíno tesne pred // dopadom rotovalo, či je „vo zveráku“ (okolie mu bráni // v horizontálnom posunutí), prikryté (keby to bolo možné, // nemohlo by sa posunúť nahor) a či sa do polohy dostalo // s pomocou odrazu; // - číslo série (koľký raz za sebou nastalo vymazanie riadkov); // - aktuálna úroveň. if (0 < početOznačených) { if (0 < rotačnýBonus) { switch (početOznačených) { case 1: početRiadkov = rotačnýBonus * 2; break; case 2: početRiadkov = rotačnýBonus * 6; break; case 3: početRiadkov = rotačnýBonus * 8; break; case 4: početRiadkov = rotačnýBonus * 10; break; } početRiadkov /= 2; } else { switch (početOznačených) { case 1: početRiadkov = 1; break; case 2: početRiadkov = 3; break; case 3: početRiadkov = 5; break; case 4: početRiadkov = 8; break; } } ++séria; } else { if (0 < séria) { navýšenieSkóre = séria * 50 * úroveň; skutočnéSkóre += navýšenieSkóre; hlásenie(Texty.koniecSérie, 1); hlásenie("" + navýšenieSkóre, 2); séria = -1; } } if (0 != početRiadkov) { navýšenieSkóre = početRiadkov * 100 * úroveň; skutočnéSkóre += navýšenieSkóre; hlásenie("" + navýšenieSkóre); this.početRiadkov += početRiadkov; if (this.početRiadkov >= 5 * úroveň) { ++úroveň; konfigurujÚroveň(); hlásenie(Texty.ďalšiaÚroveň, 4); hlásenie(Texty.úroveňČíslo + úroveň, 5); } } // V tomto komentári je predstavený úplne iný (jednoduchší) systém // bodovania: // - základné skóre je mocninou 10 ^ (početRiadkov - 1), // - bonusové skóre je rovné počtu prázdnych miest na ploche. // Počet prázdnych miest zisťuje metóda, ktorá je v komentároch pod // touto metódou. // // skutočnéSkóre += (int)Math.pow(10, (početRiadkov - 1)); // if (0 != početRiadkov) skutočnéSkóre += početPrázdnychMiest(); } // Táto metóda by bola potrebná pri alternatívnom počítaní skóre, ktoré je // predstavené v komentároch na konci metódy nájdiAOznačPlnéRiadky. // private int početPrázdnychMiest() // { // int miera = 0; // // for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; // i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) // for (byte j = 0; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) // if (0 == hraciaPlocha[i][j]) ++miera; // // return miera; // } // Táto metóda zabezpečuje jednu z fáz kontroly plných riadkov. Tá časť // metódy, ktorá nie je v komentároch funguje tak, že vymaže všetko čo // bolo označené metódou nájdiAOznačPlnéRiadky počas predchádzajúcej fázy // kontroly plných riadkov. V komentároch sú naznačené iné možné // implementácie, ktoré nie sú pre hru Tetris štandardné. private boolean vymažOznačenéRiadky() { boolean bezZmeny = true; for (byte j = 0; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) { for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) { if (8 <= hraciaPlocha[i][j]) { for (byte k = j; k > 0; --k) hraciaPlocha[i][k] = hraciaPlocha[i][k - 1]; hraciaPlocha[i][0] = 0; bezZmeny = false; // Aktivovaním nasledujúceho riadka kódu by // mazanie bolo vykonávané po štvorčekoch: //**/ return false; } } // Aktivovaním nasledujúceho riadka kódu by // mazanie bolo vykonávané po riadkoch: //**/ if (!bezZmeny) return false; } // Klasicky bude vymazané všetko čo bolo označené naraz. return bezZmeny; } // Nájde a aktivuje alebo vyrobí nové tetromíno slúžiace ako tieň – kam by // bolo uložené aktuálne tetromíno po zvolení tvrdého uloženia. private void hľadajVhodnéTieňové() { if (null != tieňovéTetromíno) tieňovéTetromíno.uvoľni(); for (Tetromíno tetromíno : tetromína) if (tetromíno.použiAkoTieňové(aktuálneTetromíno)) { tieňovéTetromíno = tetromíno; return; } tieňovéTetromíno = new Tetromíno(aktuálneTetromíno); tetromína.pridaj(tieňovéTetromíno); } // Vygeneruje ďalšie náhodné tetromíno. private void náhodnéTetromíno() { byte čísloVzoru = Tetromíno.náhodnéČísloVzoru(); for (Tetromíno tetromíno : tetromína) if (tetromíno.použi(čísloVzoru)) { ďalšieTetromíno = tetromíno; return; } ďalšieTetromíno = new Tetromíno(čísloVzoru); tetromína.pridaj(ďalšieTetromíno); } // Vymení aktuálne a nasledujúce tetromíno. private void prehoďTetromína() { Tetromíno prehoď = aktuálneTetromíno; aktuálneTetromíno = ďalšieTetromíno; ďalšieTetromíno = prehoď; hľadajVhodnéTieňové(); } // Vymení aktuálne a podržané tetromíno. private void prehoďPodržanéTetromíno() { if (podržanieAktívne) { if (null == podržanéTetromíno) { podržanéTetromíno = aktuálneTetromíno; novéTetromíno(); } else { Tetromíno prehoď = aktuálneTetromíno; aktuálneTetromíno = podržanéTetromíno; podržanéTetromíno = prehoď; } hľadajVhodnéTieňové(); } } // Zabezpečí všetky akcie bezprostredne súvisiace vytvorením nového // tetromína. (Nejde len o náhodné vygenerovanie ďalšieho tetromína, to // zabezpečuje metóda náhodnéTetromíno.) private void novéTetromíno() { mäkkéPoloženie = false; prehoďTetromína(); náhodnéTetromíno(); aktuálneTetromíno.presuňNaZačiatok(); } // Vykoná všetky akcie súvisiace s vytvorením nového tetromína po uložení // (zamknutí) aktuálneho tetromína. Metóda zároveň overí, či je nové // tetromíno blokované. Ak je následkom ukončenie hry. private void ďalšieTetromíno() { if (aktuálneTetromíno.koliduje()) { // Koniec hry. deaktivuj(); } else { // Zistí hodnotu rotačného bonusu, ktorý je použitý v metóde // nájdiAOznačPlnéRiadky na výpočet bodovania. if (aktuálneTetromíno.rotovalo()) { if (aktuálneTetromíno.voZveráku()) { if (aktuálneTetromíno.prikryté()) rotačnýBonus = 4; else rotačnýBonus = 2; if (aktuálneTetromíno.odraziloSa()) { Zvuky.kopni.prehraj(); rotačnýBonus /= 2; } } else rotačnýBonus = 0; } else rotačnýBonus = 0; // Uloží aktuálne tetromíno, vytvorí nové // a začne kontrolu plných riadkov. Zvuky.zamkni.prehraj(); aktuálneTetromíno.polož(); začniKontrolu(); novéTetromíno(); if (aktuálneTetromíno.koliduje()) { // Koniec hry. deaktivuj(); } } } // Táto metóda slúži na nakreslenie grafického pozadia v hre. Telo metódy // obsahuje komentáre oddeľujúce kód zabezpečujúci kreslenie jednotlivých // grafických prvkov. private void nakresliPozadie() { // Začiatok. kresliNaPodlahu(); hrúbkaČiary(3); // Vyplnenie podlahy. podlaha.vyplň(svetlošedá); // Odlišné vyplnenie oblasti hracej plochy. skočNa(-100, 0); farba(biela); vyplňObdĺžnik(100, 200); // Náhodné čiarky – tvoria „mramor.“ for (int i = 0; i < 4000; ++i) { if (0 == i % 2) farba(šedá); else farba(tmavošedá); náhodnáPoloha(); náhodnýSmer(); dopredu(20); // Priebežné rozmazanie po každých 500 čiarkach. if (0 == i % 500) podlaha.rozmaž(); } domov(); // Záverečné rozmazanie kresby z „mramoru.“ podlaha.rozmaž(6); // „Vyštvorčekovanie“ oblasti hracej plochy. skočNa(-190, 190); farba(svetlošedá); for (int i = 0; i < 10; ++i) { for (int j = 0; j < 20; ++j) { štvorec(7); kružnica(3); skoč(0, -20); } skoč(20, 400); } // Oddeľovací pás. domov(); posuňVpravo(6); farba(Tetromíno.čiernyTieň); vyplňObdĺžnik(3, 200); posuňVľavo(2); farba(Tetromíno.bielyTieň); vyplňObdĺžnik(3, 200); // Miesto na zobrazenie nasledujúceho tetromína. skočNa(110, -120); farba(Tetromíno.čiernyTieň); vyplňObdĺžnik(50, 50); skoč(2, -2); obdĺžnik(50, 50); skoč(-4, 4); farba(Tetromíno.bielyTieň); obdĺžnik(50, 50); if (podržanieAktívne) { // Miesto na zobrazenie podržaného tetromína. // (Nakreslí sa len v prípade, že je funkcia podržania zapnutá.) skočNa(110, 20); farba(Tetromíno.čiernyTieň); vyplňObdĺžnik(50, 50); skoč(2, -2); obdĺžnik(50, 50); skoč(-4, 4); farba(Tetromíno.bielyTieň); obdĺžnik(50, 50); } // Miesto na zobrazenie skóre. skočNa(85, 150); farba(Tetromíno.čiernyTieň); vyplňObdĺžnik(60, 20); skoč(2, -2); obdĺžnik(60, 20); skoč(-4, 4); farba(Tetromíno.bielyTieň); obdĺžnik(60, 20); // Miesto na zobrazenie úrovne. skočNa(165, 150); farba(Tetromíno.čiernyTieň); vyplňObdĺžnik(20, 20); skoč(2, -2); obdĺžnik(20, 20); skoč(-4, 4); farba(Tetromíno.bielyTieň); obdĺžnik(20, 20); // Záverečné rozmazanie (takmer) dokončenej kresby. podlaha.rozmaž(3); // Koniec. kresliNaStrop(); hrúbkaČiary(0.5); } // Táto súkromná (statická) metóda vybuduje steny a dno hracej plochy, // čím sa vytvorí tvar akejsi virtuálnej nádoby, do ktorej budú tetromína // padať. private static void vybudujStenyADno() { // Výroba dna. for (byte i = 0; i < hraciaPlocha.length; ++i) { for (byte j = DOLNÝ_OKRAJ_HRACEJ_PLOCHY; j < hraciaPlocha[i].length; ++j) { hraciaPlocha[i][j] = -1; } } // Výroba stien. for (byte j = 0; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) { for (byte i = 0; i < ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) hraciaPlocha[i][j] = -1; for (byte i = PRAVÝ_OKRAJ_HRACEJ_PLOCHY + 1; i < hraciaPlocha.length; ++i) hraciaPlocha[i][j] = -1; } } // Verejné metódy // -------------- /** * <p>Deaktivácia hry znamená jej ukončenie. V súčasnosti je následkom * vykonanie jedinej akcie – prehratie melódie konca hry (ak je hudba * zapnutá).</p> */ @Override public void deaktivácia() { Hudba.koniecHry(); } /** * <p>V tejto prekrytej metóde sú koordinované všetky aktivity súvisiace * s hraním hry. Vo zvyšku programu často stačí len nastaviť určitú * hodnotu niektorého z atribútov a táto metóda na základe toho zabezpečí * vykonanie (resp. zabránenie vykonania) určitej akcie. Napríklad * zapnutie/vypnutie hudby, pauza, zmena stavu kontroly a podobne.</p> */ @SuppressWarnings("fallthrough") @Override public void aktivita() { if (Hudba.jeTicho()) { if (Hudba.zapnutá) Hudba.ďalšiaSkladba(); } else if (!Hudba.zapnutá) Hudba.zastavVšetko(); if (!pauza && !pomocník) { switch (kontrola) { case NÁJDI_A_OZNAČ_RIADKY: nájdiAOznačPlnéRiadky(); kontrola = kontrola.ďalšia(); zobrazPlochu(); Svet.prekresli(); break; case VYMAŽ_OZNAČENÉ_RIADKY: if (vymažOznačenéRiadky()) { if (gravitáciaAktívna) { gravitáciaNiečímPohla = false; kontrola = kontrola.ďalšia(); } else kontrola = Kontrola.ŽIADNA; } else Zvuky.riadok.prehraj(); zobrazPlochu(); Svet.prekresli(); break; case VYKONAJ_GRAVITÁCIU: if (Tetromíno.vykonajGravitáciu()) { zobrazPlochu(); Svet.prekresli(); gravitáciaNiečímPohla = true; } else { if (gravitáciaNiečímPohla) kontrola = Kontrola.NÁJDI_A_OZNAČ_RIADKY; else kontrola = kontrola.ďalšia(); } // Uvedenie príkazu break na tomto mieste by spôsobilo // chybné izolovanie režimu gravitácie od režimu // klasického hrania hry. // (Je to jedna z mála výnimiek zo zásady používania // breakov v riadiacej štruktúre switch.) // —— break; —— default: if (mäkkéPoloženie) { ++skutočnéSkóre; if (!aktuálneTetromíno.dole()) ďalšieTetromíno(); zobrazPlochu(); Svet.prekresli(); } else if (počítadlo > 0) --počítadlo; else { počítadlo = zdržanie; if (!aktuálneTetromíno.dole()) ďalšieTetromíno(); zobrazPlochu(); Svet.prekresli(); } } } if (skutočnéSkóre != zobrazovanéSkóre) { if (skutočnéSkóre > zobrazovanéSkóre) zobrazovanéSkóre += 1 + ((skutočnéSkóre - zobrazovanéSkóre) / 23); else if (skutočnéSkóre < zobrazovanéSkóre) zobrazovanéSkóre += -1 + ((skutočnéSkóre - zobrazovanéSkóre) / 2); zobrazPlochu(); Svet.prekresli(); } } /** * <p>Táto metóda vykoná všetky aktivity potrebné na začatie novej hry, * vrátane preventívneho vyčistenia priestoru po prípadnej starej hre.</p> */ public void nováHra() { kontrola = Kontrola.ŽIADNA; pauza = false; pomocník = false; skutočnéSkóre = 0; séria = -1; úroveň = 1; konfigurujÚroveň(); vymažHraciuPlochu(); náhodnéTetromíno(); podržanéTetromíno = null; novéTetromíno(); zobrazPlochu(); Svet.prekresli(); aktivuj(); } /** * <p>Táto metóda slúži na vymenenie aktuálneho a nasledujúceho tetromína * počas hry.</p> */ public void vymeňTetromíno() { ďalšieTetromíno.presuňNa(aktuálneTetromíno); prehoďTetromína(); if (aktuálneTetromíno.koliduje() && !aktuálneTetromíno.odraz()) { Zvuky.neprepni.prehraj(); prehoďTetromína(); } else { Zvuky.prepni.prehraj(); zobrazPlochu(); Svet.prekresli(); } } /** * <p>Táto metóda slúži na podržanie aktuálneho tetromína, ak je táto * funkcia zapnutá v konfigurácii. Podržanie dáva hráčovi k dispozícii * jeden zásobník na tetromína navyše.</p> * * <p>Niektoré implementácie Tetrisu podržanie neumožňujú a tie, ktoré * túto funkciu majú, nemusia hráčovi umožňovať vymieňať tetromína * s vrcholom zásobníka (t. j. s najbližším nasledujúcim tetromínom). * V tejto implementácii je povolené používať obidve funkcie naraz.</p> */ public void podržTetromíno() { if (podržanieAktívne) { if (null != podržanéTetromíno) podržanéTetromíno.presuňNa(aktuálneTetromíno); prehoďPodržanéTetromíno(); if (aktuálneTetromíno.koliduje() && !aktuálneTetromíno.odraz()) { Zvuky.neprepni.prehraj(); prehoďPodržanéTetromíno(); } else { Zvuky.prepni.prehraj(); zobrazPlochu(); Svet.prekresli(); } } } /** * <p>Toto je metóda, ktorá zabezpečuje prekreslenie hracej plochy.</p> */ public void zobrazPlochu() { strop.vymažGrafiku(); /*#* *#* Kreslenie hracej plochy je obrátené – zhora nadol, takže os y *#* je obrátená. To je výhodné napríklad preto, lebo pole vzory *#* v triede Tetromíno je prirodzene definované tak, že prvý prvok *#* je umiestnený v ľavom hornom rohu (vyplýva to zo spôsobu písania *#* zdrojového kódu v Jave). *#*/ skočNa(-190, 190); if (gravitáciaAktívna) { for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) { byte spojenia; for (byte j = 1; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) { spojenia = 0; if (j > 0 && hraciaPlocha[i][j - 1] == hraciaPlocha[i][j]) spojenia |= 1; if (i < PRAVÝ_OKRAJ_HRACEJ_PLOCHY && hraciaPlocha[i + 1][j] == hraciaPlocha[i][j]) spojenia |= 2; if (j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY - 1 && hraciaPlocha[i][j + 1] == hraciaPlocha[i][j]) spojenia |= 4; if (i > ĽAVÝ_OKRAJ_HRACEJ_PLOCHY && hraciaPlocha[i - 1][j] == hraciaPlocha[i][j]) spojenia |= 8; Tetromíno.kresliMíno(this, hraciaPlocha[i][j], spojenia); skoč(0, -20); } skoč(20, 400); } } else { for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) { for (byte j = 1; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) { Tetromíno.kresliMíno(this, hraciaPlocha[i][j], (byte)0); skoč(0, -20); } skoč(20, 400); } } tieňovéTetromíno.kresliAkoTieňové(this, aktuálneTetromíno); aktuálneTetromíno.kresli(this); ďalšieTetromíno.kresliDoBoxu(this); if (podržanieAktívne && null != podržanéTetromíno) podržanéTetromíno.kresliDoZásobníka(this); skočNa(85, 150); farba(čierna); if (pomocník) { if (aktívny()) Texty.olemovanýText(this, "" + zobrazovanéSkóre, tmavotyrkysová); else Texty.olemovanýText(this, "" + skutočnéSkóre, tmavotyrkysová); skočNa(165, 150); farba(čierna); Texty.olemovanýText(this, "" + úroveň, tmavotyrkysová); domov(); farba(Tetromíno.bielyTieň); vyplňObdĺžnik(200, 200); skoč(0, 15 * textPomocníka.veľkosť()); farba(ružová); Texty.olemovanýText(this, Texty.pomocník, tmavomodrá); skoč(0, -40); for (String text : textPomocníka) { farba(svetlozelená); Texty.olemovanýText(this, text, svetlomodrá); skoč(0, -30); } strop.rozmaž(); } else if (!aktívny()) { Texty.olemovanýText(this, "" + skutočnéSkóre, tmavotyrkysová); skočNa(165, 150); farba(čierna); Texty.olemovanýText(this, "" + úroveň, tmavotyrkysová); domov(); farba(biela); Texty.olemovanýText(this, Texty.nováHra, modrá); strop.rozmaž(); } else { Texty.olemovanýText(this, "" + zobrazovanéSkóre, tmavotyrkysová); skočNa(165, 150); farba(čierna); Texty.olemovanýText(this, "" + úroveň, tmavotyrkysová); if (pauza) { domov(); farba(biela); Texty.olemovanýText(this, Texty.pauza, modrá); strop.rozmaž(); } } } /** * <p>Táto metóda zakrýva statickú metódu Hlásenie.hlásenie(String, int) * a otvára možnosti implementácie ďalších sprievodných javov hlásenia, * napríklad spustenie určitého zvuku a podobne. Text je textom hlásenia * a zdržanie slúži na oddialenie zobrazenia tohto hlásenia. Všetko je * podrobnejšie diskutované v komentároch zdrojového kódu triedy * Hlásenie.</p> * * @param text znenie hlásenia * @param zdržanie čas oddialenia zobrazenia tohto hlásenia (v sekundách) */ public void hlásenie(String text, int zdržanie) { Hlásenie.hlásenie(text, zdržanie); } /** * <p>Táto metóda preťažuje názov hlásenie tak, aby bolo možné vynechať * argument zdržanie. Vynechané zdržanie je v tejto implementácii * chápané ako nulové zdržanie (čiže žiadne zdržanie).</p> * * @param text znenie hlásenia */ public void hlásenie(String text) { hlásenie(text, 0); } // Statické metódy // --------------- /** * <p>Táto statická metóda vymaže viditeľnú časť hracej plochy. Rozdiel * medzi viditeľnou a neviditeľnou časťou hracej plochy je diskutovaný * napríklad pri konštante ĽAVÝ_OKRAJ_HRACEJ_PLOCHY.</p> */ public static void vymažHraciuPlochu() { for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) for (byte j = 0; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) hraciaPlocha[i][j] = 0; } /** * <p>Toto je pomocná metóda na transformáciu poľa na reťazec. Je * definovaná staticky, aby mohla byť použitá univerzálne. Metóda * transformuje údaje z dvojrozmerného poľa typu byte do reťazca, * pričom číselná hodnota nula je prevedená na znakovú hodnotu nula * a tak ďalej. To znamená, že hodnoty väčšie od čísla deväť sú * reprezentované prislúchajúcimi znakmi nad ASCII hodnotou znaku * deväť. Doplnkovou metódou je metóda reťazecDoPoľa.</p> * * @param pole pole s tvarom tetromína * @return tvar tetromína zakódovaný do reťazca */ public static String poleNaReťazec(byte[][] pole) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < pole.length; ++i) for (int j = 0; j < pole[i].length; ++j) sb.append((char)('0' + pole[i][j])); return sb.toString(); } /** * <p>Toto je pomocná metóda slúžiaca na transformáciu reťazca na pole. * Je definovaná staticky, aby mohla byť použitá univerzálne. Metóda * trasformuje údaje reťazca získaného (zakódovaného) metódou * poleNaReťazec späť na dvojrozmerné pole, pričom z dôvodu bezpečnosti * transformácie dodržuje dodatočné pravidlá.</p> * * <p>Záporné hodnoty sú v poli zachované v nezmenenom stave. Ak v reťazci * nie je dostatočný počet hodnôt, ostatné hodnoty poľa sú vynulované. * Ak sa na vstupe vyskytnú záporné hodnoty, tak sú tiež ignorované. * Maximálna dovolená hodnota na vstupe je štrnásť. (Súvisí to s tým, že * vnútorne v hre sú hodnoty v rozmedzí osem až štrnásť používané na * označenie mín v plných riadkoch.)</p> * * @param reťazec zakódovaný reťazec tvaru tetromína * @param pole pole, ktoré bude naplnené (prepísané) dekódovanými * údajmi (okrem záporných hodnôt v poli) */ public static void reťazecDoPoľa(String reťazec, byte[][] pole) { int pozícia = 0; byte hodnota = 0; for (int i = 0; i < pole.length; ++i) for (int j = 0; j < pole[i].length; ++j, ++pozícia) { if (pole[i][j] >= 0) { if (pozícia < reťazec.length()) hodnota = (byte)(reťazec.charAt(pozícia) - '0'); else hodnota = 0; if (hodnota >= 0) { if (hodnota > 14) hodnota = 14; pole[i][j] = hodnota; } } } } /** * <p>Vráti hodnotu príznaku gravitáciaAktívna, ktorý súvisí s aktivitou * v hre nazvanou gravitácia. Táto aktivita je diskutovaná na viacerých * miestach dokumentácie. Napríklad: v triede Kontrola, pri metóde * Tetromíno.odober() a na viacerých miestach v komentároch zdrojových * kódov projektu.</p> * * @return true, ak je gravitácia aktívna * * <!-- Poznámka: Táto gravitácia (ktorá je v komentároch tohto projektu * spomínaná na viacerých miestach) nesúvisí s bežným * padaním tetromín, ale s posúvaním blokov rovnakých * farieb počas vymazávania prázdnych riadkov tak, aby * žiadny blok rovnakej farby nezostal „visieť vo * vzduchu“ – čiže ak blok môže spadnúť, lebo je pod ním * voľné miesto, tak spadne (čo môže vyvolať kaskádu * mazania riadkov). --> */ public static boolean gravitáciaAktívna() { return gravitáciaAktívna; } /** * <p>Toto je hlavná metóda – vstupný bod programu.</p> * * @param args parametre prijaté z príkazového riadka (konzoly, ktorá * spúšťala proces hry; iného procesu…) * * <!-- Poznámka: Názov parametra args sa odkazuje na argumenty * (príkazového riadka) odovzdávané tomuto procesu * konzolou alebo iným procesom. Pre túto metódu je * to však parameter. * * Medzi termínmi parameter a argument je rozdiel. * * Parameter je premenná používaná v definícii metódy * (deklarovaná v hlavičke a používaná v tele metódy) * a argument je výraz spracúvaný v čase (a mieste) * volania (spúšťania) metódy, čiže výraz, ktorého * výsledok je odosielaný do metódy (do hodnoty jej * parametra). * * Pozri aj: * * • User:rohancragg. What’s the difference between an argument and * a parameter? Stack Overflow, 2008–2016. Dostupné na: ⟨https:// * stackoverflow.com/questions/156767/whats-the-difference-between-an- * argument-and-a-parameter?page=1&tab=active#tab-top⟩. Citované: * 5. 9. 2020. * • Moskala, Marcin. Programmer dictionary: Parameter vs Argument, * Type parameter vs Type argument. Kt. Academy, 2017. Dostupné na: * ⟨https://blog.kotlin-academy.com/programmer-dictionary-parameter- * vs-argument-type-parameter-vs-type-argument-b965d2cc6929⟩. Citované: * 5. 9. 2020. * --> */ public static void main(String[] args) { Svet.použiKonfiguráciu("Tetrisovina.cfg"); new Tetrisovina(); } }
« Kontrola | Tetromíno » |
~
import knižnica.Častica; import knižnica.Farba; import knižnica.GRobot; import knižnica.ObsluhaUdalostí; import knižnica.Písmo; import knižnica.Súbor; import knižnica.Svet; import knižnica.Zoznam; import static knižnica.Farebnosť.*; /** * <p>Originálny názov tetromína je tetrimíno, ale tento názov by sa mohol * stať predmetom sporov súvisiacich s obchodnou značkou Tetrisu, preto * používame názov tetromíno, ktorý je všeobecný (pozri napr. domino, * pentomíno a tak podobne). Projekt sa usiluje používať takú terminológiu * Tetrisu, ktorá nie je sporná. Ďalším veľmi často používaným slovom je * míno. Míno je v terminológii Tetrisu jeden štvorček tetromína.</p> * * <p>V tejto triede sa dá (opäť) odpozorovať to, že niektoré záležitosti * sú pri programovaní vecou „internej dohody,“ lebo sú interpretačne * voliteľné. Táto „dohoda“ však musí byť vopred stanovená, jasná * a dostatočne dobre zdokumentovaná, aby bol program (trieda, knižnica…) * čitateľný a použiteľný aj inými programátormi, nie iba autorom programu * (a aj to často iba v čase písania programu). Napríklad, stanovenie toho, * ktorý index poľa bude horizontálna súradnica (x) a ktorý vertikálna (y). * Logické (z pohľadu geometrie) je, aby skorší index v poradí určoval * horizontálnu a neskorší vertikálnu súradnicu. Z pohľadu * definície/inicializácie polí to však je logické presne naopak. A podľa * toho sa k tomu pristupuje aj v tejto triede.</p> * * @author Roman Horváth * @version 5. 9. 2020 */ public class Tetromíno extends Častica { /** * <p>Pole, ktoré obsahuje zoznam farieb jednotlivých tetromín. Prvý * prvok poľa má hodnotu null, pretože index nula reprezentuje prázdne * miesto na hracej ploche, ktoré nie je reprezentované žiadnou * farbou. (Mohli by sme použiť aj farbu: žiadna.)</p> */ public final static Farba farby[] = { null, svetlotyrkysová, modrá, oranžová, žltá, svetlozelená, tmavopurpurová, svetločervená }; /** * <p>Pole, ktoré obsahuje zoznam farieb na kreslenie tieňov jednotlivých * tetromín. (Prvý prvok poľa má hodnotu null, podobne ako pri zozname * farby definovanom vyššie.)</p> */ public final static Farba tieňovéFarby[] = { null, svetlotyrkysová.priehľadnejšia(0.20), modrá.priehľadnejšia(0.20), oranžová.priehľadnejšia(0.20), žltá.priehľadnejšia(0.20), svetlozelená.priehľadnejšia(0.20), tmavopurpurová.priehľadnejšia(0.20), svetločervená.priehľadnejšia(0.20) }; /** * <p>Konštantná farba používaná na viacerých miestach tohto projektu * v úlohe tmavého zatienenia.</p> */ public final static Farba čiernyTieň = čierna.priehľadnejšia(0.5); /** * <p>Konštantná farba používaná na viacerých miestach tohto projektu * v úlohe svetlého „zatienenia,“ ktoré sa odlišuje od svetlého * zvýraznenia úrovňou priehľadnosti.</p> */ public final static Farba bielyTieň = biela.priehľadnejšia(0.5); /** * <p>Konštantná farba používaná na viacerých miestach tohto projektu * v úlohe tmavého „zvýraznenia,“ ktoré sa odlišuje od tmavého * zatienenia úrovňou priehľadnosti.</p> */ public final static Farba čierneZvýraznenie = čierna.priehľadnejšia(0.80); /** * <p>Konštantná farba používaná na viacerých miestach tohto projektu * v úlohe svetlého zvýraznenia.</p> */ public final static Farba bieleZvýraznenie = biela.priehľadnejšia(0.80); // Statická kópia hracej plochy na rýchlejšiu interakciu tetromín s ňou. private static byte[][] hraciaPlocha = null; // Výška a šírka tohto tetromína. private final byte výška, šírka; // Aktuálny tvar tohto tetromína a záloha tvaru slúžiaca na rýchle // vrátenie poslednej zmeny. private byte[][] tvar, záloha; // Poloha tohto tetromína na hracej ploche. private byte polohaX, polohaY; // Atribút signalizujúci, že toto tetromíno už nie je aktívne // a môže byť recyklované. private boolean voľné; // Táto dvojica atribútov pomáha pri riadení činnosti nazvanej gravitácia. // Skratka gt označuje tzv. „gravitačné tetromíno,“ čo je v rámci tohto // projektu také tetromíno, ktoré bolo účelovo vytvorené na zabezpečenie // fungovania gravitácie v rámci jestvujúcej stavby. private boolean gtPohloSa = false; private boolean gtPoužité = false; // Táto trojica atribútov slúži na uchovanie rôznych stavov, ktoré // ovplyvňujú bodovanie akcií počas hry. private boolean odraziloSa = false; private boolean rotovalo = false; private byte poslednáVýškaPádu = 0; // V nasledujúcom poli sú definované vzory pre všetky typy tetromín // Tetrisoviny. Vzory a podľa nich aj všetky klasické tetromína majú // štvorcový rozmer, inak by v tejto implementácii nemohli rotovať. // Z dôvodu jednoduchosti sú vzory uložené v transponovanom tvare – prvá // súradnica (druhý index poľa) je vertikálna, druhá (tretí index poľa) // horizontálna. Neprekáža to, lebo vzory sú použité len počas // inicializácie tetromín, kedy sú transponované tak, aby prvá súradnica // polí tvar a záloha určovala horizontálnu súradnicu (x) a druhá // vertikálnu (y). private final static byte vzory[][][] = { { // štvorec (O) – žltá {4, 4}, {4, 4}, }, { // štvorka (S) – zelená {0, 5, 5}, {5, 5, 0}, {0, 0, 0}, }, { // zet (Z) – červená {7, 7, 0}, {0, 7, 7}, {0, 0, 0}, }, { // té (T) – purpurová {0, 6, 0}, {6, 6, 6}, {0, 0, 0}, }, { // el (L) – oranžová {0, 0, 3}, {3, 3, 3}, {0, 0, 0}, }, { // jé (J) – modrá {2, 0, 0}, {2, 2, 2}, {0, 0, 0}, }, { // í (I) – tyrkysová {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, }, }; // Konštruktory // ------------ /** * <p>Toto je základný konštruktor, ktorý vytvorí nové tetromíno podľa * zadaného čísla vzoru. Vzory sú uložené v rovnomennom trojrozmernom * poli – vzory.</p> * * @param čísloVzoru nulou začínajúce poradové číslo vzoru (index), * podľa ktorého má byť vytvorené toto tetromíno */ public Tetromíno(byte čísloVzoru) { // Nastaví veľkosť tohto tetromína (podľa veľkosti vzoru). výška = šírka = (byte)vzory[čísloVzoru].length; // Inicializuje vnútorné polia (podľa veľkosti tohto tetromína). tvar = new byte[šírka][výška]; záloha = new byte[šírka][výška]; // Nastaví tvar tetromína podľa prijatého vzoru. obnov(čísloVzoru); } /** * <p>Tento konštruktor vytvorí tetromíno dekódovaním zadaného reťazca * a načítaním polohy zo zadaného konfiguračného súboru. Na dekódovanie * reťazca je použitá statická metóda Tetrisovina.reťazecDoPoľa(String, * byte[][]). „Opakom“ (resp. náprotivkom, doplnkom…) tohto konštruktora * je metóda ulož(String, Súbor).</p> * * <p>Tento konštruktor je používaný pri automatickom čítaní * konfigurácie – v reakcii na udalosť čítajKonfiguráciu(Súbor). Prvý * parameter je obsah prečítaný z vlastnosti ďalšieTetromíno, * podržanéTetromíno alebo aktuálneTetromíno a druhý parameter je * konfiguračný súbor, z ktorého sú prečítané ďalšie údaje (poloha * tetromína).</p> * * @param reťazec reťazec, v ktorom je zakódovaný tvar tetromína * @param súbor súbor, z ktorého sú prečítané ďalšie vlastnosti * tetromína (v podstate iba poloha tetromína uložená v dvoch * atribútoch) * * @throws IOException chyba, ktorá mohla vzniknúť pri čítaní zo súboru * * @see Tetrisovina#reťazecDoPoľa(String, byte[][]) */ public Tetromíno(String reťazec, Súbor súbor) throws java.io.IOException { // Nastaví veľkosť tohto tetromína (podľa rozmeru reťazca). výška = šírka = (byte)Math.sqrt(reťazec.length()); // Inicializuje vnútorné polia (podľa veľkosti tohto tetromína). tvar = new byte[šírka][výška]; záloha = new byte[šírka][výška]; // Nastaví tvar tetromína podľa vzoru zakódovaného v prijatom reťazci. Tetrisovina.reťazecDoPoľa(reťazec, tvar); Tetrisovina.reťazecDoPoľa(reťazec, záloha); // Načíta polohu zo súboru. polohaX = súbor.čítajVlastnosť("polohaX", (long)0).byteValue(); polohaY = súbor.čítajVlastnosť("polohaY", (long)0).byteValue(); // Inicializuje atribút voľné. voľné = false; } /** * <p>Toto je kopírovací konštruktor. Toto tetromíno bude vytvorené ako * kópia zadaného tetromína. To sa dá s výhodou využiť na vytváranie * tieňových tetromín.</p> * * @param inéTetromíno tetromíno, ktorého vlastnosti budú skopírované */ public Tetromíno(Tetromíno inéTetromíno) { // Nastaví vlastnosti tohto tetromína podľa zadaného tetromína. výška = inéTetromíno.výška; šírka = inéTetromíno.šírka; // Inicializuje vnútorné polia (podľa veľkosti tohto tetromína). tvar = new byte[šírka][výška]; záloha = new byte[šírka][výška]; // Nastaví tvar a polohu tetromína podľa zadaného tetromína. kopíruj(inéTetromíno); } // Tento konštruktor je určený na vyrobenie zvláštneho „gravitačného“ // tetromína s rozmerom 10 × 21 políčok. Toto tetromíno slúži na výrobu // padajúcich blokov vyrábaných počas realizácie činnosti nazývanej // gravitácia. // // (Pozri poznámku v komentároch metódy Tetrisovina.gravitáciaAktívna() // alebo podrobnejšie vysvetlenie v priebežných komentároch v tele tejto // triedy.) private Tetromíno() { výška = 21; šírka = 10; polohaX = polohaY = 0; záloha = tvar = new byte[šírka][výška]; for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) tvar[i][j] = 0; voľné = false; } // Súkromné metódy // --------------- // Vráti voľné tetromíno späť do „služby,“ pri čom mu nastaví nový tvar // podľa zadaného čísla vzoru. private void obnov(byte čísloVzoru) { for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) try { tvar[i][j] = vzory[čísloVzoru][j][i]; záloha[i][j] = vzory[čísloVzoru][j][i]; } catch (Exception e) { tvar[i][j] = 0; záloha[i][j] = 0; } voľné = false; } // Ak sa veľkosť zadaného tetromína zhoduje s týmto tetromínom, // tak sa z neho skopírujú tvar a veľkosť do tohto tetromína. private void kopíruj(Tetromíno inéTetromíno) { if (šírka == inéTetromíno.šírka && výška == inéTetromíno.výška) { polohaX = inéTetromíno.polohaX; polohaY = inéTetromíno.polohaY; for (byte i = 0; i < šírka; ++i) { for (byte j = 0; j < výška; ++j) { tvar[i][j] = inéTetromíno.tvar[i][j]; záloha[i][j] = inéTetromíno.záloha[i][j]; } } } voľné = false; } // Použije zadaný robot na nakreslenie tetromína bez ohľadu na aktuálnu // pozíciu robota. Táto metóda je použitá vo verejných metódach kresli // a kresliDoBoxu. private void nakresli(GRobot r) { if (Tetrisovina.gravitáciaAktívna()) { byte spojenia; for (byte i = 0; i < šírka; ++i) { for (byte j = 0; j < výška; ++j) { spojenia = 0; if (j > 0 && tvar[i][j - 1] > 0) spojenia |= 1; if (i < šírka - 1 && tvar[i + 1][j] > 0) spojenia |= 2; if (j < výška - 1 && tvar[i][j + 1] > 0) spojenia |= 4; if (i > 0 && tvar[i - 1][j] > 0) spojenia |= 8; kresliMíno(r, tvar[i][j], spojenia); r.skoč(0, -20); } r.skoč(20, 20 * výška); } } else { for (byte i = 0; i < šírka; ++i) { for (byte j = 0; j < výška; ++j) { kresliMíno(r, tvar[i][j], (byte)0); r.skoč(0, -20); } r.skoč(20, 20 * výška); } } } // Použije zadaný robot na nakreslenie tieňového tetromína. Správna // poloha robota musí byť nastavená vopred. Táto metóda je použitá vo // verejnej metóde kresliAkoTieňové. private void nakresliTieňové(GRobot r) { for (byte i = 0; i < šírka; ++i) { for (byte j = 0; j < výška; ++j) { kresliTieňovéMíno(r, tvar[i][j]); r.skoč(0, -20); } r.skoč(20, 20 * výška); } } // Prepne tvar tetromína medzi zálohovanou a aktuálnou verziou. Zálohu // používajú len (verejné) metódy slúžiace na rotáciu tetromína. private void prehoďZálohu() { byte[][] prehoď = tvar; tvar = záloha; záloha = prehoď; } // Verejné metódy // -------------- /** * <p>Uvoľní toto tetromíno, aby mohlo byť pri najbližšej príležitosti * recyklované.</p> */ public void uvoľni() { voľné = true; } /** * <p>Vráti výšku posledného pádu po tvrdom položení tetromína. Táto * informácia je použiteľná pri adekvátnom navýšení skóre.</p> * * @return údaj o poslednej výške pádu */ public byte poslednáVýškaPádu() { return poslednáVýškaPádu; } /** * <p>Zistí, či pri poslednom pokuse o rotáciu tetromína nastalo * odrazenie, alebo nie. Informácia je aktualizovaná pri každom pokuse * o pohnutie tetromínom (či už posunutím, alebo otočením) a je * využiteľná pri počítaní skóre.</p> * * @return true, ak sa tetromíno pri poslednom pohybe (v dôsledku akcie * hráča) odrazilo */ public boolean odraziloSa() { return odraziloSa; } /** * <p>Zistí, či posledným pohybom tetromína bola rotácia.</p> * * @return true, ak posledným pohybom tetromína (v dôsledku akcie hráča) * bola rotácia */ public boolean rotovalo() { return rotovalo; } /** * <p>Zistí, či je tetromíno vo svojej aktuálnej pozícii „vo zveráku,“ * to znamená, že je zablokované v horizontálnom smere. Tento test sa * používa tesne pred položením tetromína na plochu a slúži na rýchlu * detekciu zvláštnych bonusových situácií.</p> * * @return true, ak je tetromíno „vo zveráku“ */ public boolean voZveráku() { if (nekolidovaloBy(-1, 0) || nekolidovaloBy(1, 0)) return false; return true; } /** * <p>Zistí, či je tetromíno vo svojej aktuálnej pozícii zablokované * zhora. Tento test sa používa tesne pred položením tetromína na plochu * a slúži na rýchlu detekciu zvláštnych bonusových situácií.</p> * * @return true, ak je tetromíno „prikryté,“ čiže aspoň čiastočne * zablokované zhora */ public boolean prikryté() { if (polohaY > 0) return !nekolidovaloBy(0, -1); return false; } /** * <p>Táto metóda pomáha pri recyklovaní tetromín. Skúsi recyklovať toto * tetromíno, čo je možné len v takom prípade, ak je toto tetromíno voľné * a len ak sú jeho rozmery zhodné s rozmermi vzoru určeného zadaným * číslom vzoru. Ak nie sú obidve podmienky splnené, tak táto metóda * zlyhá a vráti false. Inak vráti true a volajúca metóda bude vedieť, * že môže svoju činnosť ukončiť.</p> * * @param čísloVzoru index vzoru, s ktorým bude porovnané toto * tetromíno (ak je voľné), na základe čoho sa rozhodne, či môže * byť použité (recyklované) * @return true, ak bolo toto tetromíno úspešne recyklované */ public boolean použi(byte čísloVzoru) { if (voľné && šírka == (byte)vzory[čísloVzoru][0].length && výška == (byte)vzory[čísloVzoru].length) { obnov(čísloVzoru); return true; } return false; } /** * <p>Táto metóda slúži na podobný účel ako metóda použi, ale pokrýva * potreby recyklácie tetromín určených na účely zobrazenia tieňového * tetromína. Tieňové tetromíno je zobrazené na tom mieste, kam by * dopadlo aktuálne tetromíno po zvolení akcie tvrdého položenia. * Vstupom tejto metódy je inštancia tetromína slúžiaceho ako predloha * (čo by malo byť aktuálne tetromíno) a výsledkom prípadnej úspešnej * recyklácie je jeho kópia.</p> * * @param inéTetromíno vzorové tetromíno * @return true, ak sa podarilo vytvoriť zo zadaného tetromína kópiu */ public boolean použiAkoTieňové(Tetromíno inéTetromíno) { if (voľné && šírka == inéTetromíno.šírka && výška == inéTetromíno.šírka) { kopíruj(inéTetromíno); return true; } return false; } /** * <p>Táto metóda slúži na presunutie tohto tetromína na začiatok. * Začiatkom je v tomto projekte chápaný stred vrchu hracej plochy.</p> * * <p>Táto akcia je súčasťou inicializácie ďalšieho tetromína * nastupujúceho do hry (vstupujúceho na hraciu plochu po uložení * aktuálneho tetromína) a je nevyhnutná preto, lebo ďalšie tetromíno * (t. j. nasledujúce tetromíno, ktoré je v hre zobrazené v zásobníku * vpravo) mohlo byť počas predchádzajúceho ťahu vymenené s aktuálnym * tetromínom, takže jeho aktuálna poloha nemusí zodpovedať začiatku.</p> */ public void presuňNaZačiatok() { odraziloSa = false; rotovalo = false; polohaX = (byte)(5 - šírka / 2); polohaY = (byte)0; } /** * <p>Presunie toto tetromíno na pozíciu zadaného tetromína. To sa dá * použiť pri vymieňaní aktuálneho (padajúceho) tetromína za nasledujúce * tetromíno a pri funkcii podržania tetromína.</p> * * @param pár iné tetromíno, na ktoré má byť presunuté toto tetromíno * (napríklad pri vymieňaní alebo podržaní) */ public void presuňNa(Tetromíno pár) { odraziloSa = false; rotovalo = false; polohaX = pár.polohaX; polohaY = pár.polohaY; } /** * <p>Overí, či tetromíno na svojej aktuálnej pozícii koliduje.</p> * * @return true, ak tetromíno koliduje so stavbou na hracej ploche */ public boolean koliduje() { for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) if (tvar[i][j] != 0 && hraciaPlocha[polohaX + Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][polohaY + j] != 0) { return true; } return false; } /** * <p>Overí, či by tetromíno nekolidovalo, keby bolo od svojej aktuálnej * pozície odchýlené o zadaný rozdiel súradníc.</p> * * @param Δx posunutie v horizontálnom smere * @param Δy posunutie vo vertikálnom smere * @return true, ak by tetromíno po zadanom posunutí nekolidovalo */ public boolean nekolidovaloBy(int Δx, int Δy) { Δx += polohaX; Δy += polohaY; for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) if (tvar[i][j] != 0 && hraciaPlocha[Δx + Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][Δy + j] != 0) { return false; } return true; } /** * <p>Položí tetromíno na hraciu plochu a uvoľní ho na ďalšie použitie. * Všetky (použité aj aktívne) tetromína sú totiž uložené v zozname * tetromín v triede Tetrisovina, v ktorom tie neaktívne čakajú na svoju * recykláciu.</p> */ public void polož() { for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) if (tvar[i][j] != 0 && hraciaPlocha[polohaX + Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][polohaY + j] == 0) { hraciaPlocha[polohaX + Tetrisovina. ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i] [polohaY + j] = tvar[i][j]; } voľné = true; } /** * <p>Odoberie kompletný tvar tohto „tetromína“ z hracej plochy a zablokuje * inštanciu tohto „tetromína“ voči ďalšiemu používaniu. (Ironizácia * významu tetromíno vyplynie z ďalšieho vysvetľovania.) Táto metóda má * význam len pri oživení padajúceho bloku ťahaného gravitáciou, čiže * sa použije len v prípade, že je gravitácia aktívna.</p> * * <p>Keď sa v režime gravitácie uvoľní pod súvislým blokom mín rovnakej * farby prázdny priestor, spätne sa z tohto bloku vytvorí padajúce * „tetromíno,“ ktoré už nemôže byť riadené hráčom a ktoré môže mať rôzne * absurdné tvary – vôbec nemusí byť tvorené štyrmi mínami, preto nie je * celkom na mieste nazývať ho tetromínom. (Aj keď inak funguje ako * klasické tetromíno.) Vďaka tomuto princípu nemôže nastať prípad, kedy * by časť stavby rovnakej farby „visela“ vo vzduchu.</p> * * <p>Z histórie hry Tetris: Vlastnosť gravitácie bola prvý raz * implementovaná až vo vybraných novších verziách Terisu. V historicky * prvých verziách Tetrisu implementovaná nebola.</p> * * <p>Tento projekt umožňuje zapnutie alebo vypnutie gravitácie * v textovom konfiguračnom súbore.</p> */ public void odober() { for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) if (tvar[i][j] != 0 && tvar[i][j] == hraciaPlocha[polohaX + Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][polohaY + j]) { hraciaPlocha[polohaX + Tetrisovina. ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i] [polohaY + j] = 0; } voľné = false; } /** * <p>Táto metóda sa pokúša odraziť toto kolidujúce tetromíno do takej * polohy, aby nekolidovalo. Používa sa to po zistení kolízie po rotácii * alebo po zámene tetromín v súlade s pravidlami hry.</p> * * @return true, ak sa odrazenie podarilo */ public boolean odraz() { // Tetromíno typu I môže vyžadovať posun až od dve políčka. // Preto sa kontrola vykonáva pre dve úrovne. for (byte n = 1; n <= 2; ++n) { // Nasledujúca séria podmienok otestuje priestor v piatich // kľúčových smeroch v okolí tohto tetromína: // - vľavo, if (nekolidovaloBy(-1 * n, 0)) { polohaX -= 1 * n; return true; } // - vpravo, if (nekolidovaloBy(1 * n, 0)) { polohaX += 1 * n; return true; } // - dole, if (nekolidovaloBy(0, 1 * n)) { polohaY += 1 * n; return true; } // - vľavo dole, if (nekolidovaloBy(-1 * n, 1 * n)) { polohaX -= 1 * n; polohaY += 1 * n; return true; } // - a vpravo dole. if (nekolidovaloBy(1 * n, 1 * n)) { polohaX += 1 * n; polohaY += 1 * n; return true; } // Tetromíno je presunuté na prvú nájdenú polohu, // v ktorej nekoliduje. } // Ak nie je nájdená žiadna nekolízna poloha, tak tetromíno // zostáva tam kde je a metóda ohlási neúspech. return false; } /** * <p>Ak tomu nič nebráni, tak táto metóda pohne tetromínom hore, * v opačnom prípade vráti false.</p> * * @return true, ak sa pohyb podaril */ public boolean hore() // (táto metóda je pravdepodobne úplne zbytočná) { if (nekolidovaloBy(0, -1)) { --polohaY; odraziloSa = false; rotovalo = false; return true; } return false; } /** * <p>Ak tomu nič nebráni, tak táto metóda pohne tetromínom dole, * v opačnom prípade vráti false.</p> * * @return true, ak sa pohyb podaril */ public boolean dole() { if (nekolidovaloBy(0, 1)) { ++polohaY; odraziloSa = false; rotovalo = false; return true; } return false; } /** * <p>Ak tomu nič nebráni, tak táto metóda pohne tetromínom doprava, * v opačnom prípade vráti false.</p> * * @return true, ak sa pohyb podaril */ public boolean vpravo() { if (nekolidovaloBy(1, 0)) { ++polohaX; odraziloSa = false; rotovalo = false; return true; } return false; } /** * <p>Ak tomu nič nebráni, tak táto metóda pohne tetromínom doľava, * v opačnom prípade vráti false.</p> * * @return true, ak sa pohyb podaril */ public boolean vľavo() { if (nekolidovaloBy(-1, 0)) { --polohaX; odraziloSa = false; rotovalo = false; return true; } return false; } /** * <p>Ak tomu nič nebráni, tak táto metóda otočí tetromíno doprava, * v opačnom prípade vráti false.</p> * * @return true, ak sa otočenie podarilo */ public boolean otočVpravo() { odraziloSa = false; rotovalo = false; if (výška != šírka) return false; for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) záloha[šírka - 1 - j][i] = tvar[i][j]; prehoďZálohu(); if (koliduje()) { if (odraz()) odraziloSa = true; else { prehoďZálohu(); return false; } } rotovalo = true; return true; } /** * <p>Ak tomu nič nebráni, tak táto metóda otočí tetromíno doľava, * v opačnom prípade vráti false.</p> * * @return true, ak sa otočenie podarilo */ public boolean otočVľavo() { odraziloSa = false; rotovalo = false; if (výška != šírka) return false; for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) záloha[j][i] = tvar[šírka - 1 - i][j]; prehoďZálohu(); if (koliduje()) { if (odraz()) odraziloSa = true; else { prehoďZálohu(); return false; } } rotovalo = true; return true; } /** * <p>Táto metóda zabezpečuje akciu tvrdého položenia a zároveň slúži na * umiestnenie tieňového tetromína na „vrchol stavby“ na hracej ploche. * Vrcholom stavby môže byť aj prázdne dno nádoby.</p> * * <p>Návratová hodnota false je braná do úvahy len pri akcii tvrdého * položenia a znamená, že tetromíno uviazlo a hra sa musí ukončiť. Ak by * bolo uviaznutie ignorované, hráč by mohol donekonečna posielať ďalšie * tetromína na vrchol stavby (dávno po uviaznutí prvého z nich).</p> * * @return true, ak tetromíno uviazlo */ public boolean naVrcholStavby() { poslednáVýškaPádu = -1; while (!koliduje()) { ++polohaY; ++poslednáVýškaPádu; } if (polohaY > 0) { if (poslednáVýškaPádu > 0) { odraziloSa = false; rotovalo = false; } --polohaY; return true; } return false; } /** * <p>Nakreslí toto tetromíno na jeho aktuálnej pozícii s pomocou zadaného * kresliaceho robota.</p> * * @param r kresliaci robot */ public void kresli(GRobot r) { r.skočNa(-190 + polohaX * 20, 210 - polohaY * 20); nakresli(r); } /** * <p>Nakreslí toto tetromíno do boxu určeného na zobrazenie nasledujúceho * tetromína s pomocou zadaného kresliaceho robota.</p> * * @param r kresliaci robot */ public void kresliDoBoxu(GRobot r) { r.skočNa(20 - (10 * (šírka % 2)) + (5 - šírka / 2) * 20, -30 + (10 * (výška % 2)) - (5 - výška / 2) * 20); nakresli(r); } /** * <p>Nakreslí toto tetromíno do boxu zásobníka určeného na zobrazenie * podržaného tetromína s pomocou zadaného kresliaceho robota.</p> * * @param r kresliaci robot */ public void kresliDoZásobníka(GRobot r) { r.skočNa(20 - (10 * (šírka % 2)) + (5 - šírka / 2) * 20, 110 + (10 * (výška % 2)) - (5 - výška / 2) * 20); nakresli(r); } /** * <p>Nakreslí podľa zadaného tetromína toto tetromíno ako tieňové * s použitím zadaného kresliaceho robota. Tieňové tetromína sú * priehľadné priemety tetromín umiestnené na vrchole stavieb na hracej * ploche a sú určené na orientáciu hráča, ktorý sa vďaka nim môže * rýchlejšie rozhodnúť, či môže vykonať akciu tvrdého položenia.</p> * * @param r kresliaci robot * @param inéTetromíno zdrojové tetromíno, podľa ktorého vlastností je * pripravené (upravené) toto tieňové tetromíno */ public void kresliAkoTieňové(GRobot r, Tetromíno inéTetromíno) { kopíruj(inéTetromíno); naVrcholStavby(); r.skočNa(-190 + polohaX * 20, 210 - polohaY * 20); nakresliTieňové(r); } /** * <p>Uloží toto tetromíno do súboru. Opakom tejto metódy je konštruktor * rekonštruujúci tetromíno podľa zadaného reťazca – Tetromíno(String, * Súbor).</p> * * @param vlastnosť názov konfiguračnej vlastnosti, do ktorej má byť * zakódovaný tvar tohto tetromína * @param súbor konfiguračný súbor, do ktorého má byť uložené toto * tetromíno * * @see Tetrisovina#poleNaReťazec(byte[][]) */ public void ulož(String vlastnosť, Súbor súbor) throws java.io.IOException { súbor.zapíšVlastnosť(vlastnosť, Tetrisovina.poleNaReťazec(tvar)); súbor.zapíšVlastnosť("polohaX", polohaX); súbor.zapíšVlastnosť("polohaY", polohaY); } // Statické metódy // --------------- /** * <p>Táto metóda slúži na uchovanie kópie poľa hracej plochy v statickom * atribúte tejto triedy.</p> * * <p>Tetromína vo veľkej miere využívajú hraciu plochu, ktorej statická * inštancia je fyzicky definovaná v triede Tetrisovina. Aj trieda * Tetrisovina plochu často používa, preto je v tomto projekte zariadené, * aby jej kópiu obsahovali obidve triedy. Volanie tejto metódy je * umiestnené v statickom inicializačnom bloku v triede Tetrisovina.</p> * * <p>Iná implementácia by mohla definovať samostatnú triedu Plocha, do * ktorej by boli presunuté aj niektoré statické metódy, napríklad všetky * metódy slúžiace na kreslenie mín, ale táto implementácia to rieši * takto.</p> * * @param hraciaPlocha pole hracej plochy */ public static void nastavHraciuPlochu(byte[][] hraciaPlocha) { Tetromíno.hraciaPlocha = hraciaPlocha; } /** * <p>Metóda vráti náhodné číslo vzoru podľa počtu vzorov uložených v poli * vzory. Metóda je s výhodou použitá pri generovaní nových tetromín * počas hry.</p> * * @return náhodné (nulou začínajúce) poradové číslo vzoru (index) */ public static byte náhodnéČísloVzoru() { return (byte)Svet.náhodnéCeléČíslo(vzory.length - 1); } /** * <p>Táto metóda nakreslí jedno míno (čo je jeden štvorček tetromína) * s pomocou zadaného kresliaceho robota. Je pri tom použitá aktuálna * pozícia robota. Farba je určená indexom mína.</p> * * <p>Parameter s názvom spojenia má v sebe zakódované najviac štyri * smery, ktorými majú viesť spojenia so susediacimi mínami. V tejto * implementácii sú spojenia využité veľmi jednoducho – metóda nakreslí * čiaru smerujúcu k susednému mínu. Plný potenciál by bol využitý * prispôsobením kreslenia blokov podľa spojení tak, aby boli susediace * mína skutočne spojené.</p> * * @param r kresliaci robot * @param indexMína index farby mína (zodpovedajúci zároveň číslu vzoru * tetromína, ku ktorému toto míno patrí) * @param spojenia bitová mapa spojení so susednými mínami */ public static void kresliMíno(GRobot r, byte indexMína, byte spojenia) { if (indexMína != 0) { if (indexMína >= 1 && indexMína <= 7) { r.farba(Tetromíno.farby[indexMína]); r.vyplňŠtvorec(10); r.farba(bielyTieň); r.štvorec(7.0); r.skoč(-1.0, 1.0); r.farba(čiernyTieň); r.štvorec(7.0); if (0 != spojenia) { for (int i = 0; i < 4; ++i) { if (0 != (spojenia & 1)) { r.skoč(10); r.vzad(10); } spojenia /= 2; r.vpravo(90); } } r.skoč(1.0, -1.0); } else if (indexMína >= 8 && indexMína <= 14) { r.farba(Tetromíno.farby[indexMína - 7]); r.vyplňŠtvorec(10); r.farba(čiernyTieň); r.štvorec(7.5); r.farba(bieleZvýraznenie); r.vyplňŠtvorec(10); } else { r.farba(svetločervená); r.vpravo(45); for (int i = 0; i < 4; ++i) { r.dopredu(10); r.vzad(10); r.vpravo(90); } r.vľavo(45); } } } /** * <p>Táto metóda kreslí tieňové mína. Je veľmi zjednodušenou verziou * metódy kresliMíno. Nekreslí spojenia so susednými mínami ani označené * verzie mín. Očakáva priamočiary index mína, ktoré nakreslí priehľadnou * (tieňovou) farbou.</p> * * @param r kresliaci robot * @param indexMína index farby tieňového mína (zodpovedajúci zároveň * číslu vzoru tetromína, ku ktorému toto tieňové míno patrí) */ public static void kresliTieňovéMíno(GRobot r, byte indexMína) { if (indexMína >= 1 && indexMína <= 7) { r.farba(Tetromíno.tieňovéFarby[indexMína]); r.vyplňŠtvorec(10); r.farba(čiernyTieň); r.štvorec(7.5); } } // ···································································· // // Od tohto miesta nižšie je umiestnená implementácia gravitácie. // // Je celá implementovaná staticky, pretože ide o akúsi „vonkajšiu // // vrstvu“ kódu pracujúceho s inštanciami tetromín. // // ···································································· // // Najprv je vhodné vysvetliť niekoľko všeobecných pravidiel alebo faktov, // ktorými sa vykonávanie gravitácie riadi. Pri procese gravitácie v tejto // hre ide o to nájsť súvislé bloky jednej farby, ktoré nie sú zospodu // ničím blokované a mali by teda spadnúť, lebo „visia“ vo vzduchu. // // Princíp gravitácie v tejto hre považuje celé teleso za celok, ktorým // nie je možné otáčať. Keď sa teleso zospodu dotýka stavby len v jedinom // rohu, nemôže spadnúť ani sa inak zrútiť. Akoby bolo ku zvyšku stavby // pevne prilepené. To značne zjednodušuje implementáciu. // // Telesá sú vyhľadávané od najspodnejšieho riadka. Nájdené teleso sa // umiestňuje do pomocného poľa, ktorého názov v slovenčine znie rovnako // ako termín z fyziky – gravitačné pole (v angličtine by názov gravity // array nekorešpondoval s fyzikálnym termínom gravity field). S tým // súvisí termín gravitíno, ktorý v tomto projekte označuje prvok poľa // gravitačnéPole. Tiež ide o náhodnú zhodu s jedným zatiaľ hypotetickým // fyzikálnym termínom. V tomto projekte ide o slovo, ktoré vzniklo // spojením slov gravitácia a míno. // // Do tohto „gravitačného poľa“ sa postupne premietnu všetky objekty na // hracej ploche. Každý nájdený objekt sa najprv premení na „tetromíno,“ // ktoré však môže mať rôzny absurdný tvar (vôbec nemusí pozostávať zo // štyroch mín), týmto „tetromínom“ sa pokúsi hra pohnúť smerom nadol // a podľa toho, či sa to podarí alebo nie ho umiestni na pozíciu pod ním, // alebo na rovnaké miesto. Tento proces sa opakuje pre všetky vytvorené // objekty, kým sa ešte darí pohnúť niektorým nepohnutým telesom. // To znamená, že každým telesom je v jednom cykle dovolené pohnúť // len raz. Tým sa vytvorí rýchla animácia pádu všetkých uvoľnených // blokov. Dôsledkom procesu gravitácie môže byť vytvorenie malej // reťazovej reakcie, pretože spadnuté bloky môžu opäť vytvoriť zaplnené // riadky, ktoré v súlade s pravidlami hry zmiznú a tým uvoľnia ďalšie // miesto na hracej ploche. // // Na nájdenie telies pozostávajúcich z mín rovnakej farby je použitý // algoritmus semienkového vypĺňania (resp. v tomto prípade by bolo // priliehavejšie pomenovanie semienkové vyhľadávanie). Tento algoritmus // hľadá nových susedov rovnakej farby dovtedy, kým nejakí jestvujú. Jeho // implementácia s pomocou rekurzie je veľmi jednoduchá. (V tomto projekte // ho reprezentuje metóda označGravitína. Sama o sebe by však nefungovala, // potrebuje spoluprácu ostatných súčastí, najmä správnu inicializáciu.) // Atribút gravitačnáHodnota je používaný pri označovaní blokov stavby // na uchovanie aktuálnej farby, ktorú má mať blok. Je inicializovaný // absurdnou hodnotou -15, ktorá by sa v hre na hracej ploche nemala // nikdy vyskytnúť. Pomenovanie vzniklo z dvoch kritérií. Názov mal // obsahovať slovo gravitácia, aby bolo jasné, že atribút patrí // k statickému bloku slúžiacemu na spracovanie gravitácie a nemal byť // pritom príliš dlhý. private static byte gravitačnáHodnota = -15; // Význam tohto poľa je podrobnejšie diskutovaný v komentároch vyššie. private final static byte[][] gravitačnéPole = new byte[10][21]; // Zoznam tetromín, ktorý poslúži na recyklovanie tetromín. private final static Zoznam<Tetromíno> gravitačnéTetromína = new Zoznam<Tetromíno>(); // Táto metóda inicializuje/vymaže gravitačné pole na začiatku // každého gravitačného cyklu. private static void vymažGravitačnéPole() { for (byte i = 0; i < 10; ++i) for (byte j = 0; j < 21; ++j) gravitačnéPole[i][j] = 0; } // Táto metóda slúži na zamknutie blokov, ktoré boli premietnuté do // gravitačného poľa a už boli spracované. (Zamknuté gravitína majú // záporné hodnoty.) private static void pozamykajGravitačnéPole() { for (byte i = 0; i < 10; ++i) for (byte j = 0; j < 21; ++j) if (gravitačnéPole[i][j] > 0) gravitačnéPole[i][j] = (byte)-gravitačnéPole[i][j]; } // Táto metóda slúži na bezpečný zápis gravitín do gravitačného poľa. // (Vykoná kontrolu rozsahov parametrov i a j a potom zapíše zadanú // hodnotu do poľa. Metóda je definovaná, ale nakoniec nebola ani raz // použitá.) private static void zapíšGravitíno(int i, int j, byte hodnota) { if (i >= 0 && i < 10 && j >= 0 && j < 21) gravitačnéPole[i][j] = hodnota; } // Táto metóda slúži na bezpečné čítanie gravitín z gravitačného poľa. // (Vykoná kontrolu rozsahov parametrov i a j a potom prečíta želanú // hodnotu z poľa. Metóda je definovaná, ale nakoniec nebola ani raz // použitá.) private static byte čítajGravitíno(int i, int j) { if (i >= 0 && i < 10 && j >= 0 && j < 21) return gravitačnéPole[i][j]; return -15; } // Táto metóda slúži na bezpečné čítanie mín z poľa hraciaPlocha. // (V podstate malo ísť o ekvivalent metódy čítajGravitíno a nakoniec sa // ukázalo, že táto metóda nájde širšie využitie, než pôvodná metóda, // z ktorej bola vytvorená.) private static byte čítajMíno(int i, int j) { if (i >= 0 && i < 10 && j >= 0 && j < 21) return hraciaPlocha[Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][j]; return -15; } // Toto je metóda, ktorá semienkovým vyhľadávaním označí tie gravitína, // ktoré patria do jedného zatiaľ nespracovaného bloku stavby. // // To znamená, že musí ísť o také gravitína, ktoré sú zatiaľ prázdne // a ktorých hodnota je totožná s gravitačnou hodnotou, pretože tá určuje // aktuálnu farbu bloku, s ktorou sa práve pracuje. // // Za označené sú považované tie gravitína, ktoré majú kladnú hodnotu. // Záporné hodnoty sú považované za zamknuté gravitína a nulové za // prázdne (neprítomné gravitína). private static void označGravitína(int i, int j) { if (i >= 0 && i < 10 && j >= 0 && j < 21 && 0 == gravitačnéPole[i][j] && gravitačnáHodnota == hraciaPlocha[ Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][j]) { gravitačnéPole[i][j] = gravitačnáHodnota; označGravitína(i - 1, j); označGravitína(i + 1, j); označGravitína(i, j - 1); označGravitína(i, j + 1); } } // Táto metóda slúži na inicializáciu zadaného gravitačného tetromína // (gt). Metóda pracuje s gravitačnou hodnotou, ktorá signalizuje, ktorá // farba mín je práve spracúvaná. Táto metóda skopíruje do gravitačného // tetromína všetky také gravitína z gravitačného poľa, ktoré majú // hodnotu rovnú gravitačnej hodnote. Ide o gravitína, ktoré považujeme // za označené. Ostatné mína budú v gravitačnom tetromíne nastavené // na nulu. Tak sa v gravitačnom tetromíne vytvorí presný obraz súvislého // bloku jednej farby, ktorý môže byť posunutý v prípade, že nie je ničím // zospodu blokovaný. private static void inicializujGravitačnéTetromíno(Tetromíno gt) { gt.polohaX = gt.polohaY = 0; for (byte i = 0; i < 10; ++i) for (byte j = 0; j < 21; ++j) { if (gravitačnáHodnota == gravitačnéPole[i][j]) gt.tvar[i][j] = gravitačnáHodnota; else gt.tvar[i][j] = 0; } gt.gtPohloSa = false; gt.gtPoužité = true; } /** * <p>Táto metóda vykoná jeden cyklus procesu gravitácie.</p> * * <!-- Podrobnosti sú v komentároch v tele metódy. --> * * @return true, ak nastala ľubovoľná zmena stavby na ploche v dôsledku * procesu gravitácie */ public static boolean vykonajGravitáciu() { // Upratanie pred akciou. vymažGravitačnéPole(); for (Tetromíno gt : gravitačnéTetromína) gt.gtPoužité = false; // Najprv sa zamknú všetky bloky dotýkajúce sa dna, […] { byte j = 20; for (byte i = 0; i < 10; ++i) { gravitačnáHodnota = čítajMíno(i, j); if (0 == gravitačnéPole[i][j] && 0 != gravitačnáHodnota) { označGravitína(i, j); pozamykajGravitačnéPole(); } } } // […] potom sa z ostatných blokov povytvárajú gravitačné // tetromína […] for (byte j = 19; j >= 0; --j) for (byte i = 0; i < 10; ++i) { gravitačnáHodnota = čítajMíno(i, j); if (0 == gravitačnéPole[i][j] && 0 != gravitačnáHodnota) { označGravitína(i, j); Tetromíno gt1 = null; for (Tetromíno gt2 : gravitačnéTetromína) { if (!gt2.gtPoužité) { gt1 = gt2; break; } } if (null == gt1) { gt1 = new Tetromíno(); gravitačnéTetromína.pridaj(gt1); } inicializujGravitačnéTetromíno(gt1); pozamykajGravitačnéPole(); } } // (Nasledujúci kód v komentári ukazuje jeden z možných spôsobov // ladenia aplikácie – ide o ladenie výpismi. Úlohou tohto výpisu // bolo zobrazenie obsahu gravitačného poľa v textovej podobe – // na konzole, ktorá spustila proces hry.) /** / System.out.print("DEBUG"); for (byte j = 10; j <= 20; ++j) { for (byte i = 0; i < 10; ++i) { System.out.print((gravitačnéPole[i][j] < 0 ? "." : " ") + (char)(' ' + Math.abs(gravitačnéPole[i][j]))); } System.out.println(); } /**/ // […] a cyklicky sa opakujú pokusy o pohnutie každým tetromínom // (každým najviac raz) dokedy sa to darí. Hneď ako prejde celý // cyklus bez jediného úspechu, považuje sa tento gravitačný krok // za uzavretý. boolean pohloSaNiečímTeraz, pohloSaNiečímCelkovo = false; do { pohloSaNiečímTeraz = false; for (Tetromíno gt : gravitačnéTetromína) { if (gt.gtPoužité && !gt.gtPohloSa) { gt.odober(); if (gt.dole()) { gt.gtPohloSa = true; pohloSaNiečímTeraz = true; pohloSaNiečímCelkovo = true; } gt.polož(); } } } while (pohloSaNiečímTeraz); return pohloSaNiečímCelkovo; } }
« Tetrisovina | Texty » |
~
import knižnica.Farba; import knižnica.GRobot; /** * <p>Táto trieda zhromažďuje všetky texty použité v hre. Tento prístup uľahčí * prípravu a preklad hry do iného jazyka. Táto trieda by mohla byť rozšírená * tak, aby umožňovala načítanie textov z jazykového súboru, ktorého názov by * mohlo určiť niektoré nastavenie (buď uložené iba v konfiguračnom súbore, * alebo dostupné aj cez používateľské rozhranie).</p> * * @author Roman Horváth * @version 5. 9. 2020 */ public class Texty { /** * <p>Titulok pomocníka. (Zvyšok textu je prečítaný z textového * súboru.)</p> */ public static String pomocník = "Pomocník"; /** * <p>Informácia o spôsobe naštartovania novej hry.</p> */ public static String nováHra = "„F5“ – štart novej hry"; /** * <p>Informácia o spustení alebo zrušení režimu pauzy.</p> */ public static String pauza = "„P“ – pauza"; /** * <p>Hlásenie prechodu do ďalšej úrovne.</p> */ public static String ďalšiaÚroveň = "Ďalšia úroveň!"; /** * <p>Informácia o aktuálnej hodnote úrovne.</p> */ public static String úroveňČíslo = "Úroveň: "; /** * <p>Hlásenie oznamujúce, že bodovaná séria sa skončila.</p> */ public static String koniecSérie = "Koniec série!"; // Toto sú pomocné polia slúžiace ako šablónky // posunov pri vykresľovaní olemovaných textov. private final static int[] Δx = {1, 0, -2, 0, 1}, Δy = {1, -2, 0, 2, -1}; /** * <p>Metóda použije zadaný robot (r) na vykreslenie zadaného textu (t) * zadanou farbou (f).</p> */ public static void olemovanýText(GRobot r, String t, Farba f) { // Táto metóda používa na nakreslenie lemu písma polia Δx a Δy, ktoré // obsahujú také hodnoty posunov, aby sa robot postupne popresúval do // vhodných susedných polôh v jeho okolí. Z vizuálneho pohľadu by išlo // akoby o rohy štvorca nakresleného v blízkom okolí robota (stred // štvorca by bol na pôvodnej polohe robota). Olemovanie bude // nakreslené tou farbou, akú mal robot nastavenú pred volaním tejto // metódy. r.skoč(Δx[0], Δy[0]); for (int i = 1; i <= 4; ++i) { r.text(t); r.skoč(Δx[i], Δy[i]); } // Nakoniec sa robot dostane do východiskovej polohy, v ktorej // nakreslí posledný text zadanou farbou (f). r.farba(f); r.text(t); } }
« Tetromíno | Zvuky » |
~
import knižnica.Svet; import knižnica.Zvuk; /** * <p>Táto trieda slúži na uschovanie riadených zvukov, ktoré sú prehrávané * počas hry. Obsahuje vnorenú triedu RiadenýZvuk, ktorá umožňuje ovládať * prehrávanie zvukov podľa hodnoty verejného statického atribútu * Zvuky.zapnuté.</p> * * @author Roman Horváth * @version 5. 9. 2020 */ public class Zvuky { /** <p>Stavový atribút umožňujúci umlčanie všetkých zvukov.</p> **/ public static boolean zapnuté = true; /** * <p>Táto trieda umožňuje podriadiť prehrávanie všetkých zvukov hodnote * atribútu zapnuté.</p> */ public static class RiadenýZvuk { /** <p>Zvukový klip pre tento riadený zvuk.</p> **/ public final Zvuk zvuk; /** * <p>Konštruktor prijímajúci meno zvukového súboru bez prípony.</p> * * @param meno názov súboru (bez prípony) */ public RiadenýZvuk(String meno) { zvuk = Svet.čítajZvuk(meno + ".wav"); } /** <p>Ak nie sú zvuky umlčané, tak prehrá tento zvukový klip.</p> **/ public void prehraj() { if (zapnuté) zvuk.prehraj(); } } // Statický blok obsahujúci inicializáciu priečinka zvukov. static { Svet.priečinokZvukov("Zvuky"); } /** * <p>Zvuk prehraný počas vymazania označeného riadka. (Označený riadok * je taký riadok, ktorý bol úplne zaplnený časťami tetromín a má byť * vymazaný.)</p> */ public static RiadenýZvuk riadok = new RiadenýZvuk("clear-line"); /** * <p>Zvuk, ktorý mal byť prehraný počas vykonávania rôznych nastavení * v hre, to jest počas manipulácie s ovládacími prvkami nastavení.</p> */ public static RiadenýZvuk nastav = new RiadenýZvuk("config"); /** * <p>Zvuk prehraný počas odrazenia sa tetromína od steny alebo * jestvujúcej stavby. Tento fakt je spracúvaný počas výpočtu výsledného * bodovania za určitú akciu.</p> */ public static RiadenýZvuk kopni = new RiadenýZvuk("kick"); /** * <p>Zvuk prehraný počas záverečného umiestnenia tetromína na určitej * pozícii. Po zamknutí tetromína s ním už nie je možné hýbať.</p> */ public static RiadenýZvuk zamkni = new RiadenýZvuk("lock"); /** * <p>Zvuk prehraný počas neúspešného pokusu o pohnutie tetromínom * v horizontálnom smere. Ide o protiklad zvuku „pohni.“</p> */ public static RiadenýZvuk nepohni = new RiadenýZvuk("move-fail"); /** * <p>Zvuk prehraný počas úspešného posunutia tetromína v horizontálnom * smere. Protikladom je zvuk „nepohni.“</p> */ public static RiadenýZvuk pohni = new RiadenýZvuk("move"); /** * <p>Zvuk prehraný počas aktivácie alebo deaktivácie režimu pauzy.</p> */ public static RiadenýZvuk pauza = new RiadenýZvuk("pause"); /** * <p>Zvuk prehraný počas neúspešného pokusu o otočenie tetromína vpravo * alebo vľavo. Protikladom tohto zvuku je zvuk „rotuj.“</p> */ public static RiadenýZvuk nerotuj = new RiadenýZvuk("rotate-fail"); /** * <p>Zvuk prehraný počas úspešného otočenia tetromína vpravo alebo * vľavo. Protikladom je zvuk „nerotuj.“</p> */ public static RiadenýZvuk rotuj = new RiadenýZvuk("rotate"); /** * <p>Zvuk prehraný počas neúspešného pokusu o výmenu medzi aktuálnym * (padajúcim) tetromínom a tetromínom v zásobníku (v najjednoduchšom * prípade ide priamo o nasledujúce tetromíno). Neúspech je spôsobený * nedostatkom miesta na hracej ploche v okolí aktuálneho tetromína.</p> */ public static RiadenýZvuk neprepni = new RiadenýZvuk("swap-fail"); /** * <p>Zvuk prehraný počas úspešnej výmeny aktuálneho (padajúceho) tetromína * a tetromína umiestneného v zásobníku (v najjednoduchšom prípade ide * priamo o nasledujúce tetromíno).</p> */ public static RiadenýZvuk prepni = new RiadenýZvuk("swap"); }
« Texty |
Táto verzia projektu je naprogramovaná s využitím upravenej skupiny tried grafického robota, ktorá je súčasťou balíčka na prevzatie (nižšie).
;package knižnica; ;#endCode ; ;*Iným nástrojom:* ; ;2. Zdrojový kód skupiny tried grafického robota je potrebné presunúť do priečinka s názvom `knižnica` vytvoreného v rámci projektu. ;3. Na začiatok zdrojového kódu skupiny tried (`GRobot.java`) je potrebné pridať ;nasledujúci riadok kódu: ; ;~ #startJavaCode ;package knižnica; ;#endCode \{center|img:Tetrisovina-classes.png}\|Triedy projektu.|\ Balíček na prevzatie obsahuje všetky potrebné súbory projektu (zdrojové, textové, zvukové (`**.wav`), hudobné (`**.mid`) a konfiguračné): -{`tetrisovina.zip`|down:tetrisovina.zip} Nižšie sú publikované zdrojové kódy určené na prezeranie on--line: \ #Podkarta: `Hudba.java` #Alias podkarty: hudba-old <||{Novšia verzia|script:navigateTab('tab1-2', true)}| ~ #startJavaCode import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; import javax.sound.midi.MidiSystem; import javax.sound.midi.Sequencer; import javax.sound.midi.MidiUnavailableException; import javax.sound.midi.InvalidMidiDataException; /** * Účelom tejto triedy je poskytnúť jednoduché rozhranie na prehrávanie rôznych * melódií vo formáte MIDI. Statické časti triedy slúžia na uchovanie série * skladieb (playlistu) a riadenie prehrávania skladieb. Inštancie triedy * reprezentujú jednotlivé skladby. * <p> * Pri tvorbe triedy bol použitý nasledujúci zdroj informácií: * http://examples.javacodegeeks.com/desktop-java/sound/play-midi-audio/ * * @author Roman Horváth * @version 9. 2. 2015 */ public class Hudba { /** Stavový atribút umožňujúci umlčanie hudby. **/ public static boolean zapnutá = true; // Tento súkromný statický atribút slúži na uchovanie poradového čísla // práve prehrávanej skladby. Zoznam skladieb je uložený v poli skladby. private static int skladba = -1; // Súkromné statické pole inštancií slúžiace na uchovanie zoznamu // prehrávaných skladieb (nazývaného aj playlist). private static Hudba skladby[] = { new Hudba("tetris-music-a"), new Hudba("tetris-music-b"), }; // Súkromná inštancia rezervovaná pre skladbu prehrávanú pri konci hry. private static Hudba koniecHry = new Hudba("game-over"); /** * Zistí, či je práve prehrávaná niektorá zo skladiebo v zozname (poli) * skladieb. */ public static boolean jeTicho() { for (Hudba skladba : skladby) if (skladba.hrá()) return false; return true; } /** * Spustí prehrávanie nasledujúcej skladby v zozname (poli) skladieb. */ public static void ďalšiaSkladba() { ++skladba; if (skladba >= skladby.length) skladba = 0; skladby[skladba].spusti(); } /** * Zastaví všetky ostatné skladby a spustí skladbu určenú na signalizáciu * konca hry. */ public static void koniecHry() { for (Hudba skladba : skladby) skladba.zastav(); koniecHry.spusti(); } /** * Zastaví všetky skladby. */ public static void zastavVšetko() { for (Hudba skladba : skladby) skladba.zastav(); koniecHry.zastav(); } // Sekvencér je potrebný pri prehrávaní skladby čítanej prostredníctvom // prúdu údajov zo súboru. private Sequencer sekvencér; // Názov súboru je využitý pri opakovanom prehrávaní skladby, pretože pri // každom prehratí skladby je (opakovane) vytvorený údajový prúd zo súboru. // (Z pohľadu „lenivého programátora“ ide o najjednoduchšie riešenie, no // nie je najefektívnejšie. Pre potreby tohto ukážkového projektu však // postačuje. Efektívnejšie by bolo pokúsiť sa otvorený prúd údajov // resetovať a recyklovať…) private String názovSúboru; /** * Konštruktor inštancií triedy Hudba prijíma len jadro názvu súboru vo * formáte MIDI. K jadru názvu súboru je bez overenia jej prípadnej * prítomnosti pridaná prípona .mid. Súbory sú čítané z priečinka Music. */ public Hudba(String názovSúboru) { this.názovSúboru = "Music/" + názovSúboru + ".mid"; try { // Získane predvoleného sekvencéra. sekvencér = MidiSystem.getSequencer(); } catch (MidiUnavailableException e) { sekvencér = null; } } /** * Spustí prehrávanie tejto skladby. Pri každom prehrávaní je vytvorený * údajový prúd zo súboru zadaného v konštruktore. */ public void spusti() { zastav(); if (null != sekvencér) { try { // Vytvorenie prúdu údajov zo súboru. InputStream prúdZoSúboru = new BufferedInputStream( new FileInputStream(new File(názovSúboru))); // Otvorenie zariadenia. sekvencér.open(); // Nastavenie aktuálnej sekvencie na vstup zo súboru. sekvencér.setSequence(prúdZoSúboru); } catch (MidiUnavailableException | InvalidMidiDataException | IOException e) { e.printStackTrace(); } // Začatie prehrávania z prečítanej sekvencie. sekvencér.start(); } } /** * Zastaví prehrávanie skladby. Spolu s tým je korektne zavretý sekvencér * MIDI. Keby nebol sekvencér pred ukončením aplikácie korektne zavretý, * aplikácia by sa neukončila, pretože aktívny sekvencér vytvára samostatné * vlákno a žiadna aplikácia nesmie byť ukončená ak sú ešte aktívne nejaké * vlákna. */ public void zastav() { if (null != sekvencér && sekvencér.isOpen()) { // Zastavenie prehrávania. sekvencér.stop(); // Zatvorenie zariadenia. sekvencér.close(); } } /** * Zistí, či je táto skladba prehrávaná. (V skutočnosti metóda zisťuje, či * pracuje súkromná inštancia sekvencéra. Keď sa pozrieme na to, ako bola * táto inštancia získaná, tak zistíme, že by malo ísť o predvolený * sekvencér systému MIDI. Je možné, dokonca pravdepodobné, že sa všetky * skladby delia o ten istý sekvencér, ktorému je len zakaždým priradený * iný prúd údajov. V súvislosti s touto informáciou je pravdepodobné, že * táto metóda má návratovú hodnotu true ak hrá akákoľvek skladba. Pre * potreby tohto ukážkového projektu je však aj toto riešenie vysoko * postačujúce a preto tieto skutočnosti neboli overované…) */ public boolean hrá() { return sekvencér.isRunning(); } }
~
/** * Tento vymenovací typ slúži na riadenie gravitácie, označovania a mazania * plných riadkov počas hry. Názov každého prvku je sformulovaný tak, aby * odpovedal na otázku: „Aká kontrola má byť vykonaná v tejto pracovnej * iterácii?“ Každý prvok vymenovacieho typu, okrem posledného, považuje za * svojho nasledovníka prvok s najbližšou vyššou ordinálnou hodnotou. Posledný * prvok (s názvom ŽIADNA) nemá nasledovníka. Metóda ďalšia má návratovú * hodnotu null. Tento stav by však nikdy nemal byť dosiahnutý, pretože typ * kontroly ŽIADNA je považovaný za neutrálny stav, v ktorom nie je potrebné * hľadať nasledovníka. * * @author Roman Horváth * @version 9. 2. 2015 */ public enum Kontrola { /** * Tento stav kontroly určuje, že v tejto pracovnej iterácii majú byť * vyhľadané a označené všetky plné riadky. */ NÁJDI_A_OZNAČ_RIADKY, /** * Tento stav kontroly určuje, že v tejto pracovnej iterácii majú byť * vymazané všetky riadky, ktoré boli označené počas predchádzajúcej * pracovnej iterácie. */ VYMAŽ_OZNAČENÉ_RIADKY, /** * Tento stav určuje, že počas tejto pracovnej iterácie sa má vykonať * činnosť nazývaná gravitácia. Gravitácia je aktivita posúvajúce bloky * rovnakej farby v súlade so zákonom gravitácie. Je to konfigurovateľná * vlastnosť hry (dá sa zapnúť alebo vypnúť v konfiguračnom súbore). Ak je * gravitácia aktívna, tak tento stav kontroly pretrváva dokedy už nie je * možné pohnúť žiadnym blokom. */ VYKONAJ_GRAVITÁCIU, /** * Toto je pasívny stav kontroly, ktorý nemá žiadneho nasledovníka. */ ŽIADNA { @Override public Kontrola ďalšia() { return null; }; }; /** * Toto je metóda, ktorá vráti nasledovníka pre každú hodnotu * vymenovacieho typu. S jej pomocou je implementované zreťazenie stavov * určujúcich typ kontroly alebo akcie súvisiacej s mazaním plných riadkov * a posúvania blokov v súlade s činnosťou nazývanou gravitácia. */ public Kontrola ďalšia() { return values()[ordinal() + 1]; } };
~
import knižnica.GRobot; /** * Táto trieda je hlavnou triedou hry. Obsahuje mnoho rôznych súkromných * atribútov, medzi ktorými je pole pre hraciu plochu, zoznam tetromín, * atribúty ukazujúce na aktuálne, podržané, ďalšie a tieňové tetromíno * v zozname tetromín, rôzne atribúty uchovávajúce konfiguráciu, skóre, * rôzne stavy a podobne. Často používaným termínom v tomto projektje je * míno. Míno je v terminológii Tetrisu jeden štvorček tetromína. * * @author Roman Horváth * @version 9. 2. 2015 */ public class Tetrisovina extends GRobot { /** * Táto konštanta slúži na uchovanie indexu ľavého okraja viditeľnej časti * hracej plochy. Vznikla spolu s konštantami PRAVÝ_OKRAJ_HRACEJ_PLOCHY * a DOLNÝ_OKRAJ_HRACEJ_PLOCHY, pretože v priebehu implementácie vznikla * potreba definovať a priebežne podľa potrieb upravovať skutočný rozmer * poľa reprezentujúceho hraciu plochu. Neviditeľná časť tohto poľa * (neviditeľná pre hráča) uchováva betónové steny a dno slúžiace ako * barikády pre padajúce a rotujúce tetromína. Hrúbka steny sa musela meniť * napríklad počas implementácie odrazenia sa tetromína od jestvujúcej * stavby. Zavedením trojice konštánt je ďalšia zmena rozmerov plochy * (a s tým súvisiaca úprava umiestnenia viditeľnej časti hracej plochy * v rámci poľa) jednoduchšia. */ public final static byte ĽAVÝ_OKRAJ_HRACEJ_PLOCHY = 4; /** * Táto konštanta slúži na uchovanie indexu pravého okraja viditeľnej časti * hracej plochy. Podrobnosti o jej účele sú bližšie diskutované pri * konštante ĽAVÝ_OKRAJ_HRACEJ_PLOCHY. */ public final static byte PRAVÝ_OKRAJ_HRACEJ_PLOCHY = 13; /** * Táto konštanta slúži na uchovanie indexu dolného okraja viditeľnej časti * hracej plochy. Podrobnosti o jej účele sú bližšie diskutované pri * konštante ĽAVÝ_OKRAJ_HRACEJ_PLOCHY. */ public final static byte DOLNÝ_OKRAJ_HRACEJ_PLOCHY = 21; // Pole pre hraciu plochu. Je v skutočnosti o niečo väčšie oproti kreslenej // hracej ploche, pretože do neho zarátavame aj neviditeľné hrubé steny // a dno a prázdny priestor nad „jadrom“ hracej plochy. Steny a dno slúžia // ako prirodzená bariéra pre tetromína padajúce, pohybujúce sa a rotujúce // ponad hracou plochou (teda v čase, keď ešte nie sú uložené). Sú // dvojnásobne hrubé, čo by malo byť dostatočnou prirodzenú prekážkou aj // pre terimíno typu „I“, ktoré hráč otočí tesne pri stene alebo dne. // Veľkosť priestoru nad hracou plochou je ekvivalentný jednému prázdnemu // riadku, čo dáva priestor na zobrazenie väčšiny nových tetromín čiastočne // nad hracou plochou. private final static byte hraciaPlocha[][] = new byte[18][25]; // Hodnota tohto atribútu je prečítaná z konfiguračného súboru. Ak je true, // tak počas hrania hry sa vo vhodnej chvíli (najčastejšie po vymazaní // úplne zaplnených riadkov) aktivuje režim gravitácie, počas ktorého // môže dôsť k pohnutiu časti jestvujúcej stavby v súlade so zákonmi // gravitácie. Toto správanie však nie je v tejto implementácii hry úplne // v súlade s klasickou gravitáciou ako ju poznáme. Uvoľnené bloky stavby // sa v režime gravitácie posúvajú veľmi rýchlo – tak, aby čo najskôr // dosiahli stabilné umiestnenie a aby mohol byť čo najskôr obnovený // klasický režim hrania hry. Menej pozorný hráč si ani nemusí stihnúť // uvedomiť, čo sa vlastne v režime gravitácie stalo (zmenilo). private static boolean gravitáciaAktívna = false; // Tento atribút slúži na signalizáciu toho, či počas vykonávania činnosti // nazývanej gravitácia bolo pohnuté nejakou časťou stavby na hracej // ploche. To znamená, že sa našiel súvislý blok mín (míno je jeden // štvorček tetromína) jednej farby, ktorý nebol zospodu ničím blokovaný // a gravitácia ním mohla pohnúť. Tento príznak má využitie aj pri // určovaní toho, či má byť režim (resp. „stav kontroly“) gravitácie // v hre vypnutý a teda či hra môže prejsť do stavu kontroly ŽIADNA, // ktorý hráčovi umožňuje normálne hrať hru. private static boolean gravitáciaNiečímPohla = false; // Tento statický blok počas inicializácie triedy Tetrisovina inicializuje // hraciu plochu a pošle ju do statickej časti triedy Tetromíno. static { vybudujStenyADno(); vymažHraciuPlochu(); Tetromíno.nastavHraciuPlochu(hraciaPlocha); } // Zoznam tetromín na recykláciu. Po rozbehnutí sa hry už nebude potrebné // vytvárať nové tetromína. Ušetríme tým prácu virtuálnemu stroju Javy, // pretože nebude potrebné vykonávať stále nové konštruovanie objektov // a nebude zahadzované zbytočne veľké množstvo recyklovateľných objektov // (o ktorých uvoľnenie sa stará zberač odpadkov Javy). private final Zoznam<Tetromíno> tetromína = new Zoznam<Tetromíno>(); // Tento zoznam obsahuje riadky textu pomocníka, ktoré sú prečítané zo // súboru v čase zobrazenia pomocníka. private Zoznam<String> textPomocníka = new Zoznam<String>(); // Nasledujúci atribút slúži na riadenie kontroly plných riadkov a ich // mazania. private Kontrola kontrola = Kontrola.ŽIADNA; // Nasledujúci atribút slúži na zvýšenie skóre pri rotovaných spôsoboch // uloženia. private byte rotačnýBonus = 0; // Počítanie za sebou idúcich sérií. private byte séria = -1; // Atribúty, ktoré ukazujú na aktuálne, podržané, ďalšie a tieňové tetromíno // v zozname tetromín. Podržané a tieňové tetromíno sú použité v závislosti // od konfigurácie hry. (Podržanie nakoniec nebolo v tomto projekte // implementované.) private Tetromíno aktuálneTetromíno, podržanéTetromíno, ďalšieTetromíno, tieňovéTetromíno; // Aktuálna úroveň hry. Od nej sa odvíja rýchlosť hrania a počet riadkov, // ktorý je potrebné zaplniť na postup na ďalšiu úroveň. private byte úroveň = 1; // Počet riadkov, ktoré sa podarilo zaplniť v rámci tejto úrovne. // Dosiahnutie určitej hodnoty garantuje postup na ďalšiu úroveň. Pri // zaplnení viacerých riadkov naraz (alebo v určitých bonusových situácách) // je počet pripočítaných riadkov vyšší než skutočný počet vyplnených // riadkov. private byte početRiadkov = 0; // Počítadlo tikov a zdržanie slúžia na spomalenie hry. Keď hodnota // počítadla prekročí hodnotu zdržania, hra sa posunie o jeden hrací // cyklus. Čím je hodnota zdržania nižšia, tým je rýchloť hrania vyššia. // Hodnota zdržania je prepočítaná po každej zmene hodnoty úrovne (vrátane // štartu novej hry). private byte počítadlo = 0; private byte zdržanie = 48; // Nasledujúca dvojica atribútov slúži na uchovanie hodnoty skóre // a animáciu jeho zvyšovania, prípadne znižovania. Atribút skutočnéSkóre // uchováva skutočnú hodnotu skóre a podľa nej sa priebežne upravuje // (zvyšuje/znižuje) hodnota atribútu zobrazovanéSkóre, kým nie sú rovnaké. // Tým vzniká animácia zobrazovaného skóre. private int skutočnéSkóre = 0; private int zobrazovanéSkóre = 0; // Príznak zobrazenia stránky s pomocníkom. Ak je hodnota tohto atribútu // true, tak je hra pozastavená a na obrazovke sú zobrazené inštrukcie pre // hráča. private boolean pomocník = false; // Príznak pozastavenia hry – pauzy. Ak je hodnota tohto atribútu true, tak // je hra pozastavená a na obrazovke je zobrazená informácia o tom, že hra // je pozastavená. private boolean pauza = false; // Príznak zrýchlenia padania aktuálneho tetromína – mäkkého pokladania. // Keď je hodnota tohto atribútu rovná true, tak je vertikálny pohyb // aktuálneho tetromína (jeho padanie) zrýchlené na maximálnu možnú // hranicu. Hráč získava za zrýchlenie padania body, ale tvrdým položením // získa dvojnásobok. private boolean mäkkéPoloženie = false; // Konštruktory // ------------ // Táto trieda má len jeden súkromný konštruktor, ktorý neprijíma žiadny // parameter a zabezpečuje celkovú inicializáciu aplikácie. private Tetrisovina() { super(400, 400); skry(); písmo(new Písmo("Arial", Písmo.TUČNÉ, 24)); Svet.nekresli(); nakresliPozadie(); Svet.spustiČasovač(0.025); nováHra(); Svet.zastavČasovač(); obsluhaUdalostí(); } // Súkromné metódy // --------------- // Táto súkromná metóda obsahuje úplnú obsluhu udalostí aplikácie. Ide // o udalosti klávesnice (na ovládanie hry), čítania a zápisu konfigurácie // a ukončenie aplikácie. private void obsluhaUdalostí() { new ObsluhaUdalostí() { @Override public void stlačenieKlávesu() { if (pomocník) { if (ÚdajeUdalostí.kláves(Kláves.ESCAPE) || ÚdajeUdalostí.kláves(Kláves.VK_F1)) { Zvuky.pauza.prehraj(); pomocník = false; zobrazPlochu(); Svet.prekresli(); } } else if (aktívny()) { if (pauza) { if (ÚdajeUdalostí.kláves(Kláves.ESCAPE) || ÚdajeUdalostí.kláves(Kláves.VK_F1)) { Zvuky.pauza.prehraj(); pomocník = true; čítajPomocníka(); zobrazPlochu(); Svet.prekresli(); } else if (ÚdajeUdalostí.kláves(Kláves.VK_F10) || ÚdajeUdalostí.kláves(Kláves.VK_P)) { Zvuky.pauza.prehraj(); pauza = false; zobrazPlochu(); Svet.prekresli(); } } else { switch (ÚdajeUdalostí.kláves()) { case Kláves.VK_F10: case Kláves.VK_P: Zvuky.pauza.prehraj(); pauza = true; zobrazPlochu(); Svet.prekresli(); break; case Kláves.VK_A: case Kláves.VĽAVO: if (aktuálneTetromíno.vľavo()) { Zvuky.pohni.prehraj(); zobrazPlochu(); Svet.prekresli(); } else Zvuky.nepohni.prehraj(); break; case Kláves.VK_S: case Kláves.VPRAVO: if (aktuálneTetromíno.vpravo()) { Zvuky.pohni.prehraj(); zobrazPlochu(); Svet.prekresli(); } else Zvuky.nepohni.prehraj(); break; case Kláves.VK_C: case Kláves.VK_H: case Kláves.VK_M: vymeňTetromíno(); break; case Kláves.VK_D: case Kláves.DOLE: mäkkéPoloženie = true; break; case Kláves.VK_Z: case Kláves.VK_Y: case Kláves.VK_COMMA: // čiarka if (aktuálneTetromíno.otočVľavo()) { Zvuky.rotuj.prehraj(); zobrazPlochu(); Svet.prekresli(); } else Zvuky.nerotuj.prehraj(); break; case Kláves.HORE: case Kláves.VK_X: case Kláves.VK_PERIOD: // bodka if (aktuálneTetromíno.otočVpravo()) { Zvuky.rotuj.prehraj(); zobrazPlochu(); Svet.prekresli(); } else Zvuky.nerotuj.prehraj(); break; case Kláves.MEDZERA: // tvrdé položenie if (aktuálneTetromíno.naDno()) { skutočnéSkóre += aktuálneTetromíno. poslednáVýškaPádu() * 2; ďalšieTetromíno(); } else { deaktivuj(); } zobrazPlochu(); Svet.prekresli(); break; case Kláves.ESCAPE: case Kláves.VK_F1: Zvuky.pauza.prehraj(); pomocník = true; čítajPomocníka(); zobrazPlochu(); Svet.prekresli(); break; case Kláves.VK_F5: Zvuky.neprepni.prehraj(); } } } else { switch (ÚdajeUdalostí.kláves()) { case Kláves.VK_F10: case Kláves.VK_P: case Kláves.VK_A: case Kláves.VK_S: case Kláves.VĽAVO: case Kláves.VPRAVO: case Kláves.VK_C: case Kláves.VK_H: case Kláves.VK_M: case Kláves.VK_D: case Kláves.DOLE: case Kláves.VK_Z: case Kláves.VK_Y: case Kláves.VK_COMMA: // čiarka case Kláves.HORE: case Kláves.VK_X: case Kláves.VK_PERIOD: // bodka case Kláves.MEDZERA: Zvuky.neprepni.prehraj(); break; case Kláves.ESCAPE: case Kláves.VK_F1: Zvuky.pauza.prehraj(); pomocník = true; čítajPomocníka(); zobrazPlochu(); Svet.prekresli(); break; case Kláves.VK_F5: nováHra(); } } } @Override public void uvoľnenieKlávesu() { switch (ÚdajeUdalostí.kláves()) { case Kláves.DOLE: mäkkéPoloženie = false; break; case Kláves.VK_F2: Zvuky.zapnuté = !Zvuky.zapnuté; break; case Kláves.VK_F3: Hudba.zapnutá = !Hudba.zapnutá; break; } } @Override public boolean konfiguráciaZmenená() { return true; } @Override public void čítajKonfiguráciu(Súbor súbor) throws java.io.IOException { try { skutočnéSkóre = súbor.čítajVlastnosť( "skóre", (long)skutočnéSkóre).intValue(); séria = súbor.čítajVlastnosť( "séria", (long)séria).byteValue(); String reťazec = súbor.čítajVlastnosť( "plocha", (String)null); if (null != reťazec) reťazecDoPoľa(reťazec, hraciaPlocha); reťazec = súbor.čítajVlastnosť( "ďalšieTetromíno", (String)null); if (null != reťazec) { ďalšieTetromíno = new Tetromíno(reťazec, súbor); tetromína.pridaj(ďalšieTetromíno); } reťazec = súbor.čítajVlastnosť( "aktuálneTetromíno", (String)null); if (null != reťazec) { aktuálneTetromíno = new Tetromíno(reťazec, súbor); tieňovéTetromíno = new Tetromíno(aktuálneTetromíno); tetromína.pridaj(aktuálneTetromíno); } pauza = súbor.čítajVlastnosť("pauza", pauza); úroveň = súbor.čítajVlastnosť("úroveň", (long)1).byteValue(); početRiadkov = súbor.čítajVlastnosť("početRiadkov", (long)početRiadkov).byteValue(); gravitáciaAktívna = súbor.čítajVlastnosť( "gravitáciaAktívna", gravitáciaAktívna); Zvuky.zapnuté = súbor.čítajVlastnosť("zvukyZapnuté", Zvuky.zapnuté); Hudba.zapnutá = súbor.čítajVlastnosť("hudbaZapnutá", Hudba.zapnutá); konfigurujÚroveň(); začniKontrolu(); } finally { Svet.spustiČasovač(0.025); } } @Override public void zapíšKonfiguráciu(Súbor súbor) throws java.io.IOException { súbor.zapíšVlastnosť("skóre", skutočnéSkóre); súbor.zapíšVlastnosť("séria", séria); súbor.zapíšVlastnosť("plocha", poleNaReťazec(hraciaPlocha)); ďalšieTetromíno.ulož("ďalšieTetromíno", súbor); aktuálneTetromíno.ulož("aktuálneTetromíno", súbor); súbor.zapíšVlastnosť("pauza", pauza); súbor.zapíšVlastnosť("úroveň", úroveň); súbor.zapíšVlastnosť("početRiadkov", početRiadkov); súbor.zapíšVlastnosť("gravitáciaAktívna", gravitáciaAktívna); súbor.zapíšVlastnosť("zvukyZapnuté", Zvuky.zapnuté); súbor.zapíšVlastnosť("hudbaZapnutá", Hudba.zapnutá); } @Override public void ukončenie() { Hudba.zastavVšetko(); } }; } // Prečíta texty pomocníka. private void čítajPomocníka() { try { textPomocníka.vymaž(); súbor.otvorNaČítanie("pomocnik.txt"); String riadok; while (null != (riadok = súbor.čítajRiadok())) textPomocníka.pridaj(riadok); súbor.zavri(); } catch (Exception e) { // Vypíše chyby… System.err.println(e.getMessage()); } } // Začne ďalšiu sériu kontroly plných riadkov. Túto kontrolu je nevyhnutné // vykonať napríklad po položení (zamknutí) tetromína alebo nejakej zmene // na hracej ploche. Kontrola je pre istotu spustená aj pri začatí novej // hry. V podstate ide len o vloženie tej správnej hodnoty do atribútu // kontrola. To spôsobí zmenu správania programu na inom mieste. Konkrétne // v metóde aktivita tejto triedy. private void začniKontrolu() { kontrola = Kontrola.NÁJDI_A_OZNAČ_RIADKY; } // Vykoná nevyhnutné kroky sprevádzajúce zmenu úrovne hry. Tým je myslené // nielen zvýšenie aktuálnej úrovne, ale aj začatie novej hry. Ide // o vynulovanie počítadiel, ktoré slúžia na signalizáciu toho, kedy má // byť zvýšená úroveň. Popri tom je upravená hodnota zdržania určujúceho // rýchlosť hry. Zdržanie je tým menšie, čím je úroveň hry vyššia. private void konfigurujÚroveň() { početRiadkov = počítadlo = 0; zdržanie = (úroveň >= 15) ? 3 : (byte)(48 - úroveň * 3); } // Táto metóda zabezpečuje jednu z fáz kontroly plných riadkov. Metóda // hľadá riadky, ktoré sú úplne zaplnené mínami – štvorčekmi tetromín. private void nájdiAOznačPlnéRiadky() { /*#* *#* UPOZORNENIE – musíme obrátiť cykus i a j, aby sme postupovali *#* po riadkoch, a nie po stĺpcoch ako pri kreslení. *#*/ int početRiadkov = 0, početOznačených = 0, navýšenieSkóre = 0; for (byte j = 0; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) { boolean jePlný = true; for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) { if (0 == hraciaPlocha[i][j] || 8 <= hraciaPlocha[i][j]) { jePlný = false; break; } } if (jePlný) { ++početOznačených; for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) hraciaPlocha[i][j] += 7; } } // Nasledujúci systém bodovania vychádza v matematickom význame // zo štyroch premenných: // // - počet označených riadkov (koľko ich zmizne); // - rotačný bonus – ten pochádza z metódy ďalšieTetromíno // a jeho hodnota kolíše medzi nulou až štvorkou; bonus je // určený podľa viacerých faktorov: či tetromíno tesne pred // dopadom rotovalo, či je „vo zveráku“ (okolie mu bráni // v horizontálnom posunutí), prikryté (keby to bolo možné, // nemohlo by sa posunúť nahor) a či sa do polohy dostalo // s pomocou odrazu; // - číslo série (koľký raz za sebou dochádza k vymazaniu riadkov); // - aktuálna úroveň. if (0 < početOznačených) { if (0 < rotačnýBonus) { switch (početOznačených) { case 1: početRiadkov = rotačnýBonus * 2; break; case 2: početRiadkov = rotačnýBonus * 6; break; case 3: početRiadkov = rotačnýBonus * 8; break; case 4: početRiadkov = rotačnýBonus * 10; break; } početRiadkov /= 2; } else { switch (početOznačených) { case 1: početRiadkov = 1; break; case 2: početRiadkov = 3; break; case 3: početRiadkov = 5; break; case 4: početRiadkov = 8; break; } } ++séria; } else { if (0 < séria) { navýšenieSkóre = séria * 50 * úroveň; skutočnéSkóre += navýšenieSkóre; výkrik(Texty.koniecSérie, 1); výkrik("" + navýšenieSkóre, 2); séria = -1; } } if (0 != početRiadkov) { navýšenieSkóre = početRiadkov * 100 * úroveň; skutočnéSkóre += navýšenieSkóre; výkrik("" + navýšenieSkóre); this.početRiadkov += početRiadkov; if (this.početRiadkov >= 5 * úroveň) { ++úroveň; konfigurujÚroveň(); výkrik(Texty.ďalšiaÚroveň, 4); výkrik(Texty.úroveňČíslo + úroveň, 5); } } // V tomto komentári je predstavený úplne iný (jednoduchší) systém // bodovania: // - základné skóre je mocninou 10 ^ (početRiadkov - 1), // - bonusové skóre je rovné počtu prázdnych miest na ploche. // Počet prázdnych miest zisťuje metóda, ktorá je v komentároch pod // touto metódou. // // skutočnéSkóre += (int)Math.pow(10, (početRiadkov - 1)); // if (0 != početRiadkov) skutočnéSkóre += početPrázdnychMiest(); } // Táto metóda by bola potrebná pri alternatívnom počítaní skóre, ktoré je // predstavené v komentároch na konci metódy nájdiAOznačPlnéRiadky. // private int početPrázdnychMiest() // { // int miera = 0; // // for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; // i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) // for (byte j = 0; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) // if (0 == hraciaPlocha[i][j]) ++miera; // // return miera; // } // Táto metóda zabezpečuje jednu z fáz kontroly plných riadkov. Tá časť // metódy, ktorá nie je v komentároch funguje tak, že vymaže všetko čo // bolo označené metódou nájdiAOznačPlnéRiadky počas predchádzajúcej fázy // kontroly plných riadkov. V komentároch sú naznačené iné možné // implementácie, ktoré nie sú pre hru Tetris štandardné. private boolean vymažOznačenéRiadky() { boolean bezZmeny = true; for (byte j = 0; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) { for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) { if (8 <= hraciaPlocha[i][j]) { for (byte k = j; k > 0; --k) hraciaPlocha[i][k] = hraciaPlocha[i][k - 1]; hraciaPlocha[i][0] = 0; bezZmeny = false; // Aktivovaním nasledujúceho riadka kódu by // mazanie bolo vykonávané po štvorčekoch: //**/ return false; } } // Aktivovaním nasledujúceho riadka kódu by // mazanie bolo vykonávané po riadkoch: //**/ if (!bezZmeny) return false; } // Klasicky bude vymazané všetko čo bolo označené naraz. return bezZmeny; } // Nájde a aktivuje alebo vyrobí nové tetromíno slúžiace ako tieň – kam by // bolo uložené aktuálne tetromíno po zvolení tvrdého uloženia. private void hľadajVhodnéTieňové() { if (null != tieňovéTetromíno) tieňovéTetromíno.uvoľni(); for (Tetromíno tetromíno : tetromína) if (tetromíno.použiAkoTieňové(aktuálneTetromíno)) { tieňovéTetromíno = tetromíno; return; } tieňovéTetromíno = new Tetromíno(aktuálneTetromíno); tetromína.pridaj(tieňovéTetromíno); } // Vygeneruje ďalšie náhodné tetromíno. private void náhodnéTetromíno() { byte čísloVzoru = Tetromíno.náhodnéČísloVzoru(); for (Tetromíno tetromíno : tetromína) if (tetromíno.použi(čísloVzoru)) { ďalšieTetromíno = tetromíno; return; } ďalšieTetromíno = new Tetromíno(čísloVzoru); tetromína.pridaj(ďalšieTetromíno); } // Vymení aktuálne a nasledujúce tetromíno. private void prehoďTetromína() { Tetromíno prehoď = aktuálneTetromíno; aktuálneTetromíno = ďalšieTetromíno; ďalšieTetromíno = prehoď; hľadajVhodnéTieňové(); } // Zabezpečí všetky akcie bezprostredne súvisiace vytvorením nového // tetromína. (Nejde len o náhodné vygenerovanie ďalšieho tetromína, to // zabezpečuje metóda náhodnéTetromíno.) private void novéTetromíno() { mäkkéPoloženie = false; prehoďTetromína(); náhodnéTetromíno(); aktuálneTetromíno.presuňNaZačiatok(); } // Vykoná všetky akcie súvisiace s vytvorením nového tetromína po uložení // (zamknutí) aktuálneho tetromína. Metóda zároveň overí, či je nové // tetromíno blokované. Ak je, má to za následok ukončenie hry. private void ďalšieTetromíno() { if (aktuálneTetromíno.koliduje()) { // Koniec hry. deaktivuj(); } else { // Zistí hodnotu rotačného bonusu, ktorý je použitý v metóde // nájdiAOznačPlnéRiadky na výpočet bodovania. if (aktuálneTetromíno.rotovalo()) { if (aktuálneTetromíno.voZveráku()) { if (aktuálneTetromíno.prikryté()) rotačnýBonus = 4; else rotačnýBonus = 2; if (aktuálneTetromíno.odraziloSa()) { Zvuky.kopni.prehraj(); rotačnýBonus /= 2; } } else rotačnýBonus = 0; } else rotačnýBonus = 0; // Uloží aktuálne tetromíno, vytvorí nové // a začne kontrolu plných riadkov. Zvuky.zamkni.prehraj(); aktuálneTetromíno.polož(); začniKontrolu(); novéTetromíno(); if (aktuálneTetromíno.koliduje()) { // Koniec hry. deaktivuj(); } } } // Táto metóda slúži na nakreslenie grafického pozadia v hre. Telo metódy // obsahuje komentáre oddeľujúce kód zabezpečujúci kreslenie jednotlivých // grafických prvkov. private void nakresliPozadie() { // Začiatok. kresliNaPodlahu(); hrúbkaČiary(3); // Vyplnenie podlahy. podlaha.vyplň(svetlošedá); // Odlišné vyplnenie oblasti hracej plochy. skočNa(-100, 0); farba(biela); vyplňObdĺžnik(100, 200); // Náhodné čiarky – tvoria „mramor“. for (int i = 0; i < 4000; ++i) { if (0 == i % 2) farba(šedá); else farba(tmavošedá); náhodnáPoloha(); náhodnýSmer(); dopredu(20); // Priebežné rozmazanie po každých 500 čiarkach. if (0 == i % 500) podlaha.rozmaž(); } domov(); // Záverečné rozmazanie kresby z „mramoru“. podlaha.rozmaž(6); // Vyštvorčekovanie obhlasti hracej plochy. skočNa(-190, 190); farba(svetlošedá); for (int i = 0; i < 10; ++i) { for (int j = 0; j < 20; ++j) { štvorec(7); kružnica(3); skoč(0, -20); } skoč(20, 400); } // Oddeľovací pás. domov(); posuňVpravo(6); farba(Tetromíno.čiernyTieň); vyplňObdĺžnik(3, 200); posuňVľavo(2); farba(Tetromíno.bielyTieň); vyplňObdĺžnik(3, 200); // Miesto na zobrazenie nasledujúceho tetromína. skočNa(110, -120); farba(Tetromíno.čiernyTieň); vyplňObdĺžnik(50, 50); skoč(2, -2); obdĺžnik(50, 50); skoč(-4, 4); farba(Tetromíno.bielyTieň); obdĺžnik(50, 50); // Miesto na zobrazenie skóre. skočNa(85, 150); farba(Tetromíno.čiernyTieň); vyplňObdĺžnik(60, 20); skoč(2, -2); obdĺžnik(60, 20); skoč(-4, 4); farba(Tetromíno.bielyTieň); obdĺžnik(60, 20); // Miesto na zobrazenie úrovne. skočNa(165, 150); farba(Tetromíno.čiernyTieň); vyplňObdĺžnik(20, 20); skoč(2, -2); obdĺžnik(20, 20); skoč(-4, 4); farba(Tetromíno.bielyTieň); obdĺžnik(20, 20); // Záverečné rozmazanie (takmer) dokončenej kresby. podlaha.rozmaž(3); // Koniec. kresliNaStrop(); hrúbkaČiary(0.5); } // Táto súkromná (statická) metóda vybuduje steny a dno hracej plochy, čím // sa vytvorí tvar akejsi virtuálnej nádoby, do ktorej budú tetromína padať. private static void vybudujStenyADno() { // Výroba dna. for (byte i = 0; i < hraciaPlocha.length; ++i) { for (byte j = DOLNÝ_OKRAJ_HRACEJ_PLOCHY; j < hraciaPlocha[i].length; ++j) { hraciaPlocha[i][j] = -1; } } // Výroba stien. for (byte j = 0; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) { for (byte i = 0; i < ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) hraciaPlocha[i][j] = -1; for (byte i = PRAVÝ_OKRAJ_HRACEJ_PLOCHY + 1; i < hraciaPlocha.length; ++i) hraciaPlocha[i][j] = -1; } } // Verejné metódy // -------------- /** * Deaktivácia hry znamená jej ukončenie. V súčasnosti to má za následok * vykonanie jedinej akcie – prehratie melódie konca hry (ak je hudba * zapnutá). */ @Override public void deaktivácia() { Hudba.koniecHry(); } /** * V tejto prekrytej metóde sú koordinované všetky aktivity súvisiace * s hraním hry. Vo zvyšku programu často stačí len nastaviť určitú hodnotu * niektorého z atribútov a táto metóda na základe toho zabezpečí vykonanie * (resp. zabránenie vykonania) určitej akcie. Napríklad zapnutie/vypnutie * hudby, pauza, zmena stavu kontroly a podobne. */ @Override public void aktivita() { if (Hudba.jeTicho()) { if (Hudba.zapnutá) Hudba.ďalšiaSkladba(); } else if (!Hudba.zapnutá) Hudba.zastavVšetko(); if (!pauza && !pomocník) { switch (kontrola) { case NÁJDI_A_OZNAČ_RIADKY: nájdiAOznačPlnéRiadky(); kontrola = kontrola.ďalšia(); zobrazPlochu(); Svet.prekresli(); break; case VYMAŽ_OZNAČENÉ_RIADKY: if (vymažOznačenéRiadky()) { if (gravitáciaAktívna) { gravitáciaNiečímPohla = false; kontrola = kontrola.ďalšia(); } else kontrola = Kontrola.ŽIADNA; } else Zvuky.riadok.prehraj(); zobrazPlochu(); Svet.prekresli(); break; case VYKONAJ_GRAVITÁCIU: if (Tetromíno.vykonajGravitáciu()) { zobrazPlochu(); Svet.prekresli(); gravitáciaNiečímPohla = true; } else { if (gravitáciaNiečímPohla) kontrola = Kontrola.NÁJDI_A_OZNAČ_RIADKY; else kontrola = kontrola.ďalšia(); } // Nasledujúci riadok kódu spôsobí izolovanie režimu // gravitácie od režimu klasického hrania hry. // break; default: if (mäkkéPoloženie) { ++skutočnéSkóre; if (!aktuálneTetromíno.dole()) ďalšieTetromíno(); zobrazPlochu(); Svet.prekresli(); } else if (počítadlo > 0) --počítadlo; else { počítadlo = zdržanie; if (!aktuálneTetromíno.dole()) ďalšieTetromíno(); zobrazPlochu(); Svet.prekresli(); } } } if (skutočnéSkóre != zobrazovanéSkóre) { if (skutočnéSkóre > zobrazovanéSkóre) zobrazovanéSkóre += 1 + ((skutočnéSkóre - zobrazovanéSkóre) / 23); else if (skutočnéSkóre < zobrazovanéSkóre) zobrazovanéSkóre += -1 + ((skutočnéSkóre - zobrazovanéSkóre) / 2); zobrazPlochu(); Svet.prekresli(); } } /** * Táto metóda vykoná všetky aktivity potrebné na začatie novej hry vrátane * preventívneho vyčistenia priestoru po prípadnej starej hre. */ public void nováHra() { kontrola = Kontrola.ŽIADNA; pauza = false; pomocník = false; skutočnéSkóre = 0; séria = -1; úroveň = 1; konfigurujÚroveň(); vymažHraciuPlochu(); náhodnéTetromíno(); novéTetromíno(); zobrazPlochu(); Svet.prekresli(); aktivuj(); } /** * Táto metóda slúži na vymenenie aktuálneho a nasledujúceho tetromína * počas hry. */ public void vymeňTetromíno() { ďalšieTetromíno.presuňNa(aktuálneTetromíno); prehoďTetromína(); if (aktuálneTetromíno.koliduje() && !aktuálneTetromíno.odraz()) { Zvuky.neprepni.prehraj(); prehoďTetromína(); } else { Zvuky.prepni.prehraj(); zobrazPlochu(); Svet.prekresli(); } } /** * Toto je metóda, ktorá zabezpečuje prekreslenie hracej plochy. */ public void zobrazPlochu() { strop.vymažGrafiku(); /*#* *#* Kreslenie hracej plochy je obrátené – zhora nadol, takže os y *#* je obrátená. To je výhodné napríklad preto, lebo pole vzory *#* v triede Tetromíno je prirodzene definované tak, že prvý prvok *#* je umiestnený v ľavom hornom rohu (vyplýva to zo spôsobu písania *#* zdrojového kódu v Jave). *#*/ skočNa(-190, 190); if (gravitáciaAktívna) { for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) { byte spojenia; for (byte j = 1; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) { spojenia = 0; if (j > 0 && hraciaPlocha[i][j - 1] == hraciaPlocha[i][j]) spojenia |= 1; if (i < PRAVÝ_OKRAJ_HRACEJ_PLOCHY && hraciaPlocha[i + 1][j] == hraciaPlocha[i][j]) spojenia |= 2; if (j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY - 1 && hraciaPlocha[i][j + 1] == hraciaPlocha[i][j]) spojenia |= 4; if (i > ĽAVÝ_OKRAJ_HRACEJ_PLOCHY && hraciaPlocha[i - 1][j] == hraciaPlocha[i][j]) spojenia |= 8; Tetromíno.kresliMíno(this, hraciaPlocha[i][j], spojenia); skoč(0, -20); } skoč(20, 400); } } else { for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) { for (byte j = 1; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) { Tetromíno.kresliMíno(this, hraciaPlocha[i][j], (byte)0); skoč(0, -20); } skoč(20, 400); } } tieňovéTetromíno.kresliAkoTieňové(this, aktuálneTetromíno); aktuálneTetromíno.kresli(this); ďalšieTetromíno.kresliDoBoxu(this); skočNa(85, 150); farba(čierna); if (pomocník) { if (aktívny()) Texty.olemovanýText(this, "" + zobrazovanéSkóre, tmavotyrkysová); else Texty.olemovanýText(this, "" + skutočnéSkóre, tmavotyrkysová); skočNa(165, 150); farba(čierna); Texty.olemovanýText(this, "" + úroveň, tmavotyrkysová); domov(); skoč(0, 15 * textPomocníka.veľkosť()); farba(ružová); Texty.olemovanýText(this, Texty.pomocník, tmavomodrá); skoč(0, -40); for (String text : textPomocníka) { farba(svetlozelená); Texty.olemovanýText(this, text, svetlomodrá); skoč(0, -30); } strop.rozmaž(); } else if (!aktívny()) { Texty.olemovanýText(this, "" + skutočnéSkóre, tmavotyrkysová); skočNa(165, 150); farba(čierna); Texty.olemovanýText(this, "" + úroveň, tmavotyrkysová); domov(); farba(biela); Texty.olemovanýText(this, Texty.nováHra, modrá); strop.rozmaž(); } else { Texty.olemovanýText(this, "" + zobrazovanéSkóre, tmavotyrkysová); skočNa(165, 150); farba(čierna); Texty.olemovanýText(this, "" + úroveň, tmavotyrkysová); if (pauza) { domov(); farba(biela); Texty.olemovanýText(this, Texty.pauza, modrá); strop.rozmaž(); } } } /** * Táto metóda zakrýva statickú metódu Výkrik.výkrik(String, int) a otvára * možnosti implementácie ďalších sprievodných javov výkriku, napríklad * spustenie určitého zvuku a podobne. Text je textom výkriku a zdržanie * slúži na oddialenie zobrazenia tohto výkriku. Všetko je podrobnejšie * diskutované v komentároch zdrojového kódu triedy Výkrik. */ public void výkrik(String text, int zdržanie) { Výkrik.výkrik(text, zdržanie); } /** * Táto metóda preťažuje názov výkrik tak, aby bolo možné vynechať argument * zdržanie. Vynechané zdržanie je v tejto implementácii chápané ako nulové * zdržanie (čiže žiadne zdržanie). */ public void výkrik(String text) { výkrik(text, 0); } // Statické metódy // --------------- /** * Táto statická metóda vymaže viditeľnú časť hracej plochy. Rozdiel medzi * viditeľnou a neviditeľnou časťou hracej plochy je diskutovaný napríklad * pri konštante ĽAVÝ_OKRAJ_HRACEJ_PLOCHY. */ public static void vymažHraciuPlochu() { for (byte i = ĽAVÝ_OKRAJ_HRACEJ_PLOCHY; i <= PRAVÝ_OKRAJ_HRACEJ_PLOCHY; ++i) for (byte j = 0; j < DOLNÝ_OKRAJ_HRACEJ_PLOCHY; ++j) hraciaPlocha[i][j] = 0; } /** * Toto je pomocná metóda na transformáciu poľa na reťazec. Aby bola * univerzálna, tak je definovaná staticky. Metóda transformuje údaje * z dvojrozmerného poľa typu byte do reťazca, pričom číselná hodnota * nula je prevedená na znakovú hodnotu nula a tak ďalej. To znamená, * že hodnoty väčšie od čísla deväť sú reprezentované prislúchajúcimi * znakmi nad ASCII hodnotou znaku deväť. Doplnkovou metódou je metóda * reťazecDoPoľa. */ public static String poleNaReťazec(byte[][] pole) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < pole.length; ++i) for (int j = 0; j < pole[i].length; ++j) sb.append((char)('0' + pole[i][j])); return sb.toString(); } /** * Toto je pomocná metóda na transformáciu reťazca na pole. Aby bola * univerzálna, tak je definovaná staticky. Metóda trasformuje údaje * reťazca získaného metódou poleNaReťazec späť na dvojrozmerné pole, * pričom z dôvodu bezpečnosti transformácie dodržuje dodatočné pravidlá. * Záporné hodnoty sú v poli zachované v nezmenenom stave. Ak v reťazci * nie je dostatočný počet hodnôt, ostatné hodnoty poľa sú vynulované. * Ak sa na vstupe vyskytnú záporné hodnoty, tak sú tiež ignorované. * Maximálna dovolená hodnota na vstupe je štrnásť. (Súvisí to s tým, že * vnútorne v hre sú hodnoty v rozmedzí osem až štrnásť používané na * označenie mín v plných riadkoch.) */ public static void reťazecDoPoľa(String reťazec, byte[][] pole) { int pozícia = 0; byte hodnota = 0; for (int i = 0; i < pole.length; ++i) for (int j = 0; j < pole[i].length; ++j, ++pozícia) { if (pole[i][j] >= 0) { if (pozícia < reťazec.length()) hodnota = (byte)(reťazec.charAt(pozícia) - '0'); else hodnota = 0; if (hodnota >= 0) { if (hodnota > 14) hodnota = 14; pole[i][j] = hodnota; } } } } /** * Vráti hodnotu príznaku gravitáciaAktívna, ktorý súvisí s aktivitou * v hre nazvanou gravitácia. Táto aktivita je diskutovaná na viacerých * miestach dokumentácie. Napríklad: v triede Kontrola, pri metóde * Tetromíno.odober() a na viacerých miestach v komentároch zdrojových * kódov projektu. */ public static boolean gravitáciaAktívna() { return gravitáciaAktívna; } /** * Toto je hlavná metóda – vstupný bod programu. */ public static void main(String[] args) { Svet.použiKonfiguráciu("tetrisovina.cfg"); new Tetrisovina(); } }
~
import knižnica.GRobot; import static knižnica.GRobot.*; /** * Originálny názov tetromína je tetrimíno, ale tento názov by sa mohol stať * predmetom sporov súvisiacich s obchodnou značkou Tetrisu, preto používame * názov tetromíno, ktorý je všeobecný (pozri napr. domino, pentomíno a tak * podobne). Projekt sa usiluje používať takú terminológiu Tetrisu, ktorá * nie je sporná. Ďalším veľmi často používaným slovom je míno. Míno je * v terminológii Tetrisu jeden štvorček tetromína. * <p> * V tejto triede sa dá (opäť) odpozorovať to, že niektoré záležitosti sú pri * programovaní vecou dohody, lebo sú interpretačne voliteľné. Táto dohoda však * musí byť vopred stanovená, jasná a dostatočne dobre zdokumentovaná, aby bol * program (trieda, knižnica…) čitateľný a použiteľný aj inými programátormi, * nie iba autorom (aj to iba v čase písania). Napríklad stanovenie toho, ktorý * index poľa bude horizontálna súradnica (x) a ktorý vertikálna (y). Logické * z pohľadu geometrie je, aby skorší index v poradí určoval horizontálnu * a neskorší vertikálnu súradnicu. Z pohľadu definície/inicializácie polí to * však je logické presne naopak. A podľa toho sa k tomu pristupuje aj v tejto * triede. * * @author Roman Horváth * @version 9. 2. 2015 */ public class Tetromíno extends Častica { /** * Pole, ktoré obsahuje zoznam farieb jednotlivých * tetromín. Prvý prvok poľa je null, pretože index * nula reprezentuje prázne miesto na hracej ploche, * ktoré nie je reprezentované žiadnou farbou. */ public final static Farba farby[] = { null, svetlotyrkysová, modrá, oranžová, žltá, svetlozelená, tmavopurpurová, svetločervená }; /** * Pole, ktoré obsahuje zoznam farieb na kreslenie tieňov * jednotlivých tetromín. (Prvý prvok poľa je null, podobne * ako pri zozname farby definovanom vyššie.) */ public final static Farba tieňovéFarby[] = { null, svetlotyrkysová.priehľadnejšia(0.20), modrá.priehľadnejšia(0.20), oranžová.priehľadnejšia(0.20), žltá.priehľadnejšia(0.20), svetlozelená.priehľadnejšia(0.20), tmavopurpurová.priehľadnejšia(0.20), svetločervená.priehľadnejšia(0.20) }; /** * Konštantná farba používaná na viacerých miestach tohto projektu * v úlohe tmavého zatienenia. */ public final static Farba čiernyTieň = čierna.priehľadnejšia(0.5); /** * Konštantná farba používaná na viacerých miestach tohto projektu * v úlohe svetlého „zatienenia“, ktoré sa odlišuje od svetlého * zvýraznenia úrovňou priehľadnosti. */ public final static Farba bielyTieň = biela.priehľadnejšia(0.5); /** * Konštantná farba používaná na viacerých miestach tohto projektu * v úlohe tmavého „zvýraznenia“, ktoré sa odlišuje od tmavého * zatienenia úrovňou priehľadnosti. */ public final static Farba čierneZvýraznenie = čierna.priehľadnejšia(0.80); /** * Konštantná farba používaná na viacerých miestach tohto projektu * v úlohe svetlého zvýraznenia. */ public final static Farba bieleZvýraznenie = biela.priehľadnejšia(0.80); // Statická kópia hracej plochy na rýchlejšiu interakciu tetromín s ňou. private static byte[][] hraciaPlocha = null; // Výška a šírka tohto tetromína. private final byte výška, šírka; // Aktuálny tvar tohto tetromína a záloha tvaru slúžiaca na rýchle // vrátenie poslednej zmeny. private byte[][] tvar, záloha; // Poloha tohto tetromína na hracej ploche. private byte polohaX, polohaY; // Atribút signalizujúci, že toto tetromíno už nie je aktívne // a môže byť recyklované. private boolean voľné; // Táto dvojica atribútov pomáha pri riadení činnosti nazvanej gravitácia. // Skratka gt označuje tzv. „gravitačné tetromíno“, čo je v rámci tohto // projektu také tetromíno, ktoré bolo účelovo vytvorené na zabezpečenie // fungovania gravitácie v rámci jestvujúcej stavby. private boolean gtPohloSa = false; private boolean gtPoužité = false; // Táto trojica atribútov slúži na uchovanie rôznych stavov, ktoré // ovplyvňujú bodovanie akcií počas hry. private boolean odraziloSa = false; private boolean rotovalo = false; private byte poslednáVýškaPádu = 0; // V nasledujúcom poli sú definované vzory pre všetky typy tetromín // Tetrisoviny. Vzory a podľa nich aj všetky klasické tetromína majú // štvorcový rozmer, inak by v tejto implementácii nemohli rotovať. // Z dôvodu jednoduchosti sú vzory uložené v transponovanom tvare – prvá // súradnica (druhý index poľa) je vertikálna, druhá (tretí index poľa) // horizontálna. Neprekáža to, lebo vzory sú použité len počas // inicializácie tetromín, kedy sú transponované tak, aby prvá súradnica // polí tvar a záloha určovala horizontálnu súradnicu (x) a druhá // vertikálnu (y). private final static byte vzory[][][] = { { // štvorec (O) – žltá {4, 4}, {4, 4}, }, { // štvorka (S) – zelená {0, 5, 5}, {5, 5, 0}, {0, 0, 0}, }, { // zet (Z) – červená {7, 7, 0}, {0, 7, 7}, {0, 0, 0}, }, { // té (T) – purpurová {0, 6, 0}, {6, 6, 6}, {0, 0, 0}, }, { // el (L) – oranžová {0, 0, 3}, {3, 3, 3}, {0, 0, 0}, }, { // jé (J) – modrá {2, 0, 0}, {2, 2, 2}, {0, 0, 0}, }, { // í (I) – tyrkysová {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, }, }; // Konštruktory // ------------ /** * Toto je základný konštruktor, ktorý vytvorí nové tetromíno podľa zadaného * čísla vzoru. Vzory sú uložené v rovnomennom trojrozmernom poli – vzory. */ public Tetromíno(byte čísloVzoru) { // Nastaví veľkosť tohto tetromína (podľa veľkosti vzoru). výška = šírka = (byte)vzory[čísloVzoru].length; // Inicializuje vnútorné polia (podľa veľkosti tohto tetromína). tvar = new byte[šírka][výška]; záloha = new byte[šírka][výška]; // Nastaví tvar tetromína podľa prijatého vzoru. obnov(čísloVzoru); } /** * Tento konštruktor vytvorí tetromíno dekódovaním zadaného reťazca * a prečítaním polohy zo zadaného konfiguračného súboru. Na dekódovanie * reťazca je použitá statická metóda Tetrisovina.reťazecDoPoľa(String, * byte[][]). Opakom tohto konštruktora je metóda ulož(String, Súbor). */ public Tetromíno(String reťazec, Súbor súbor) throws java.io.IOException { // Nastaví veľkosť tohto tetromína (podľa rozmeru reťazca). výška = šírka = (byte)Math.sqrt(reťazec.length()); // Inicializuje vnútorné polia (podľa veľkosti tohto tetromína). tvar = new byte[šírka][výška]; záloha = new byte[šírka][výška]; // Nastaví tvar tetromína podľa vzoru zakódovaného v prijatom reťazci. Tetrisovina.reťazecDoPoľa(reťazec, tvar); Tetrisovina.reťazecDoPoľa(reťazec, záloha); // Prečíta polohu zo súboru. polohaX = súbor.čítajVlastnosť("polohaX", (long)0).byteValue(); polohaY = súbor.čítajVlastnosť("polohaY", (long)0).byteValue(); // Inicializuje atribút voľné. voľné = false; } /** * Toto je kopírovací konštruktor. Toto tetromíno bude vytvorené ako kópia * zadaného tetromína. To sa dá s výhodou využiť na vytváranie tieňových * tetromín. */ public Tetromíno(Tetromíno inéTetromíno) { // Nastaví vlastnosti tohto tetromína podľa zadaného tetromína. výška = inéTetromíno.výška; šírka = inéTetromíno.šírka; // Inicializuje vnútorné polia (podľa veľkosti tohto tetromína). tvar = new byte[šírka][výška]; záloha = new byte[šírka][výška]; // Nastaví tvar a polohu tetromína podľa zadaného tetromína. kopíruj(inéTetromíno); } // Tento konštruktor je určený na vyrobenie zvláštneho „gravitačného“ // tetromína s rozmerom 10 × 21 políčok. Toto tetromíno slúži na výrobu // padajúcich blokov vyrábaných pre potreby činnosti nazývanej gravitácia. private Tetromíno() { výška = 21; šírka = 10; polohaX = polohaY = 0; záloha = tvar = new byte[šírka][výška]; for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) tvar[i][j] = 0; voľné = false; } // Súkromné metódy // --------------- // Vráti voľné tetromíno späť do „služby“, pri čom mu nastaví nový tvar // podľa zadaného čísla vzoru. private void obnov(byte čísloVzoru) { for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) try { tvar[i][j] = vzory[čísloVzoru][j][i]; záloha[i][j] = vzory[čísloVzoru][j][i]; } catch (Exception e) { tvar[i][j] = 0; záloha[i][j] = 0; } voľné = false; } // Ak sa veľkosť zadaného tetromína zhoduje s týmto tetromínom, // tak sa z neho skopírujú tvar a veľkosť do tohto tetromína. private void kopíruj(Tetromíno inéTetromíno) { if (šírka == inéTetromíno.šírka && výška == inéTetromíno.výška) { polohaX = inéTetromíno.polohaX; polohaY = inéTetromíno.polohaY; for (byte i = 0; i < šírka; ++i) { for (byte j = 0; j < výška; ++j) { tvar[i][j] = inéTetromíno.tvar[i][j]; záloha[i][j] = inéTetromíno.záloha[i][j]; } } } voľné = false; } // Použije zadaný robot na nakreslenie tetromína bez ohľadu na jeho // aktuálnu pozíciu. Táto metóda je použitá vo verejných metódach kresli // a kresliDoBoxu. private void nakresli(GRobot r) { if (Tetrisovina.gravitáciaAktívna()) { byte spojenia; for (byte i = 0; i < šírka; ++i) { for (byte j = 0; j < výška; ++j) { spojenia = 0; if (j > 0 && tvar[i][j - 1] > 0) spojenia |= 1; if (i < šírka - 1 && tvar[i + 1][j] > 0) spojenia |= 2; if (j < výška - 1 && tvar[i][j + 1] > 0) spojenia |= 4; if (i > 0 && tvar[i - 1][j] > 0) spojenia |= 8; kresliMíno(r, tvar[i][j], spojenia); r.skoč(0, -20); } r.skoč(20, 20 * výška); } } else { for (byte i = 0; i < šírka; ++i) { for (byte j = 0; j < výška; ++j) { kresliMíno(r, tvar[i][j], (byte)0); r.skoč(0, -20); } r.skoč(20, 20 * výška); } } } // Použije zadaný robot na nakreslenie tieňového tetromína. Správna // poloha robota musí byť nastavená vopred. Táto metóda je použitá vo // verejnej metóde kresliAkoTieňové. private void nakresliTieňové(GRobot r) { for (byte i = 0; i < šírka; ++i) { for (byte j = 0; j < výška; ++j) { kresliTieňovéMíno(r, tvar[i][j]); r.skoč(0, -20); } r.skoč(20, 20 * výška); } } // Prepne tvar tetromína medzi zálohovanou a aktuálnou verziou. Zálohu // používajú len (verejné) metódy slúžiace na rotáciu tetromína. private void prehoďZálohu() { byte[][] prehoď = tvar; tvar = záloha; záloha = prehoď; } // Verejné metódy // -------------- /** * Uvoľní toto tetromíno, aby mohlo byť pri najbližšej príležitosti * recyklované. */ public void uvoľni() { voľné = true; } /** * Vráti výšku posledného pádu po tvrdom položení tetromína. * Táto informácia je použiteľná pri adekvátnom navýšení skóre. */ public byte poslednáVýškaPádu() { return poslednáVýškaPádu; } /** * Zistí, či pri poslednom pokuse o rotáciu tetromína došlo k odrazeniu * alebo nie. Informácia je aktualizovaná pri každom pokuse o pohnutie * tetromínom (či už posunutím alebo otočením) a je využiteľná pri počítaní * skóre. */ public boolean odraziloSa() { return odraziloSa; } /** * Zistí, či posledným pohybom tetromína bola rotácia. */ public boolean rotovalo() { return rotovalo; } /** * Zistí, či je tetromíno vo svojej aktuálnej pozícii vo zveráku, to * znamená, že je zablokované v horizontálnom smere. Tento test sa používa * tesne pred položením tetromína na plochu a slúži na rýchlu detekciu * zvláštnych bonusových situácií. */ public boolean voZveráku() { if (nekolidovaloBy(-1, 0) || nekolidovaloBy(1, 0)) return false; return true; } /** * Zistí, či je tetromíno vo svojej aktuálnej pozícii zablokované zhora. * Tento test sa používa tesne pred položením tetromína na plochu a slúži * na rýchlu detekciu zvláštnych bonusových situácií. */ public boolean prikryté() { if (polohaY > 0) return !nekolidovaloBy(0, -1); return false; } /** * Táto metóda pomáha pri recyklovaní tetromín. Skúsi recyklovať toto * tetromíno, čo je možné len v takom prípade, ak je toto tetromíno voľné * a len ak sú jeho rozmery zhodné s rozmermi vzoru určeného zadaným číslom * vzoru. Ak nie sú obidve podmienky splnené, tak táto metóda zlyhá a vráti * false. Inak vráti true a volajúca metóda bude vedieť, že môže svoju * činnosť ukončiť. */ public boolean použi(byte čísloVzoru) { if (voľné && šírka == (byte)vzory[čísloVzoru][0].length && výška == (byte)vzory[čísloVzoru].length) { obnov(čísloVzoru); return true; } return false; } /** * Táto metóda slúži na podobný účel ako metóda použi, ale pokrýva potreby * recyklácie tetromín určených na účely zobrazenia tieňového tetromína. * Tieňové tetromíno je zobrazené na tom mieste, kam by dopadlo aktuálne * tetromíno po zvolení akcie tvrdého položenia. Vstupom tejto metódy je * inštancia tetromína slúžiaceho ako predloha (čo by malo byť aktuálne * tetromíno) a výsledkom prípadnej úspešnej recyklácie je jeho kópia. */ public boolean použiAkoTieňové(Tetromíno inéTetromíno) { if (voľné && šírka == inéTetromíno.šírka && výška == inéTetromíno.šírka) { kopíruj(inéTetromíno); return true; } return false; } /** * Táto metóda slúži na presunutie tohto tetromína na začiatok. Začiatkom * je v tomto projekte chápaný stred vrchu hracej plochy. Táto akcia je * súčasťou inicializácie ďalšieho tetromína nastupujúceho do hry * (vstupujúceho na hraciu plochu po uložení aktuálneho tetromína) a je * nevyhnutná preto, lebo toto ďalšie tetromíno (t. j. tetromíno zobrazené * v zásobníku vpravo) mohlo byť počas predchádzajúceho ťahu vymenené * s aktuálnym tetromínom, takže jeho aktuálna poloha nemusí zodpovedať * začiatku. */ public void presuňNaZačiatok() { odraziloSa = false; rotovalo = false; polohaX = (byte)(5 - šírka / 2); polohaY = (byte)0; } /** * Presunie toto tetromíno na pozíciu zadaného tetromína. To sa dá použiť * pri vymieňaní aktuálneho (padajúceho) tetromína za nasledujúce tetromíno, * resp. pri tzv. podržaní tetromína. (Podľa implementácie.) Podržanie * nakoniec nebolo v tomto projekte implementované. */ public void presuňNa(Tetromíno s) { odraziloSa = false; rotovalo = false; polohaX = s.polohaX; polohaY = s.polohaY; } /** * Overí, či tetromíno na svojej aktuálnej pozícii koliduje. */ public boolean koliduje() { for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) if (tvar[i][j] != 0 && hraciaPlocha[polohaX + Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][polohaY + j] != 0) { return true; } return false; } /** * Overí, či by tetromíno nekolidovalo, keby bolo od svojej * aktuálnej pozície odchýlené o zadaný rozdiel súradníc. */ public boolean nekolidovaloBy(int Δx, int Δy) { Δx += polohaX; Δy += polohaY; for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) if (tvar[i][j] != 0 && hraciaPlocha[Δx + Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][Δy + j] != 0) { return false; } return true; } /** * Položí tetromíno na hraciu plochu a uvoľní ho na ďalšie použitie. Všetky * (použité aj aktívne) tetromína sú totiž uložené v zozname tetromín * v triede Tetrisovina, v ktorom tie neaktívne čakajú na svoju recykláciu. */ public void polož() { for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) if (tvar[i][j] != 0 && hraciaPlocha[polohaX + Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][polohaY + j] == 0) { hraciaPlocha[polohaX + Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i] [polohaY + j] = tvar[i][j]; } voľné = true; } /** * Odoberie kompletný tvar tohto „tetromína“ z hracej plochy a zablokuje * inštanciu tohto „tetromína“ voči ďalšiemu používaniu. (Ironizácia * významu tetromíno vyplynie z ďalšieho vysvetľovania.) Táto metóda má * význam len pri oživení padajúceho bloku ťahaného gravitáciou, čiže * k jej použitiu dôjde len v prípade, že je gravitácia aktívna. Keď sa * v režime gravitácie uvoľní pod súvislým blokom mín rovnakej farby * prázdny priestor, spätne sa z tohto bloku vytvorí padajúce „tetromíno“, * ktoré už nemôže byť riadené hráčom a ktoré môže mať rôzne absurdné * tvary – vôbec nemusí byť tvorené štyrmi mínami, preto nie je celkom na * mieste nazývať ho tetromínom. (Aj keď inak funguje ako klasické * tetromíno.) * <p> * Gravitáciou je v tomto projekte nazývaná špeciálna činnosť * zabezpečujúca v súlade so zákonmi gravitácie vykonávanie padania * súvislých blokov, ktoré nie sú zospodu ničím blokované. Nemôže tak * nastať prípad, kedy by časť stavby rovnakej farby „visela“ vo vzduchu. * Táto vlastnosť hry bola implementovaná až v niektorých novších verziách * Terisu. V historicky prvých verziách Tetrisu implementovaná nebola. * Tento projekt umožňuje zapnutie alebo vypnutie gravitácie v textovom * konfiguračnom súbore. */ public void odober() { for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) if (tvar[i][j] != 0 && tvar[i][j] == hraciaPlocha[polohaX + Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][polohaY + j]) { hraciaPlocha[polohaX + Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i] [polohaY + j] = 0; } voľné = false; } /** * Táto metóda sa pokúša odraziť toto kolidujúce tetromíno do takej polohy, * aby nekolidovalo. Používa sa to po zistení kolízie po rotácii alebo po * zámene tetromín v súlade s pravidlami hry. */ public boolean odraz() { // Tetromíno typu „I“ môže vyžadovať posun až od dve políčka. // Preto sa kontrola vykonáva pre dve úrovne. for (byte n = 1; n <= 2; ++n) { // Nasledujúca séria podmienok otestuje priestor v piatich kľúčových // smeroch v okolí tohto tetromína: // - vľavo, if (nekolidovaloBy(-1 * n, 0)) { polohaX -= 1 * n; return true; } // - vpravo, if (nekolidovaloBy(1 * n, 0)) { polohaX += 1 * n; return true; } // - dole, if (nekolidovaloBy(0, 1 * n)) { polohaY += 1 * n; return true; } // - vľavo dole, if (nekolidovaloBy(-1 * n, 1 * n)) { polohaX -= 1 * n; polohaY += 1 * n; return true; } // - a vpravo dole. if (nekolidovaloBy(1 * n, 1 * n)) { polohaX += 1 * n; polohaY += 1 * n; return true; } // Tetromíno je presunuté na prvú nájdenú polohu, // v ktorej nekoliduje. } // Ak nie je nájdená žiadna nekolízna poloha, tak tetromíno // zostáva tam kde je a metóda ohlási neúspech. return false; } /** * Ak tomu nič nebráni, tak táto metóda pohne tetromínom hore, * v opačnom prípade vráti false. */ public boolean hore() // (táto metóda je pravdepodobne úplne zbytočná) { if (nekolidovaloBy(0, -1)) { --polohaY; odraziloSa = false; rotovalo = false; return true; } return false; } /** * Ak tomu nič nebráni, tak táto metóda pohne tetromínom dole, * v opačnom prípade vráti false. */ public boolean dole() { if (nekolidovaloBy(0, 1)) { ++polohaY; odraziloSa = false; rotovalo = false; return true; } return false; } /** * Ak tomu nič nebráni, tak táto metóda pohne tetromínom doprava, * v opačnom prípade vráti false. */ public boolean vpravo() { if (nekolidovaloBy(1, 0)) { ++polohaX; odraziloSa = false; rotovalo = false; return true; } return false; } /** * Ak tomu nič nebráni, tak táto metóda pohne tetromínom doľava, * v opačnom prípade vráti false. */ public boolean vľavo() { if (nekolidovaloBy(-1, 0)) { --polohaX; odraziloSa = false; rotovalo = false; return true; } return false; } /** * Ak tomu nič nebráni, tak táto metóda otočí tetromíno doprava, * v opačnom prípade vráti false. */ public boolean otočVpravo() { odraziloSa = false; rotovalo = false; if (výška != šírka) return false; for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) záloha[šírka - 1 - j][i] = tvar[i][j]; prehoďZálohu(); if (koliduje()) { if (odraz()) odraziloSa = true; else { prehoďZálohu(); return false; } } rotovalo = true; return true; } /** * Ak tomu nič nebráni, tak táto metóda otočí tetromíno doľava, * v opačnom prípade vráti false. */ public boolean otočVľavo() { odraziloSa = false; rotovalo = false; if (výška != šírka) return false; for (byte i = 0; i < šírka; ++i) for (byte j = 0; j < výška; ++j) záloha[j][i] = tvar[šírka - 1 - i][j]; prehoďZálohu(); if (koliduje()) { if (odraz()) odraziloSa = true; else { prehoďZálohu(); return false; } } rotovalo = true; return true; } /** * Táto metóda zabezpečuje akciu tvrdého položenia a zároveň slúži na * umiestnenie tieňového tetromína na dno, čím je v tomto prípade chápané * buď prázdne dno nádoby, alebo vrch jestvujúcej stavby. Návratová hodnota * false je braná do úvahy len pri akcii tvrdého položenia a znamená, že * tetromíno uviazlo a hra sa musí ukončiť. Ak by sa tak nestalo, hráč by * mohol donekonečna posielať ďalšie tetromína na dno – dávno po uviaznutí * prvého z nich. */ public boolean naDno() { poslednáVýškaPádu = -1; while (!koliduje()) { ++polohaY; ++poslednáVýškaPádu; } if (polohaY > 0) { if (poslednáVýškaPádu > 0) { odraziloSa = false; rotovalo = false; } --polohaY; return true; } return false; } /** * Nakreslí toto tetromíno na jeho aktuálnej pozícii s pomocou zadaného * kresliaceho robota. */ public void kresli(GRobot r) { r.skočNa(-190 + polohaX * 20, 210 - polohaY * 20); nakresli(r); } /** * Nakreslí toto tetromíno do boxu určeného na zobrazenie podržaného * tetromína s pomocou zadaného kresliaceho robota. (Podržanie nakoniec * nebolo v tomto projekte implementované.) */ public void kresliDoZásobníka(GRobot r) { r.skočNa(20 - (10 * (šírka % 2)) + (5 - šírka / 2) * 20, -0 + (10 * (výška % 2)) - (5 - výška / 2) * 20); nakresli(r); } /** * Nakreslí toto tetromíno do boxu určeného na zobrazenie nasledujúceho * tetromína s pomocou zadaného kresliaceho robota. */ public void kresliDoBoxu(GRobot r) { r.skočNa(20 - (10 * (šírka % 2)) + (5 - šírka / 2) * 20, -30 + (10 * (výška % 2)) - (5 - výška / 2) * 20); nakresli(r); } /** * S pomocou zadaného kresliaceho robota nakreslí podľa zadaného tetromína * toto tetromíno ako tieňové, ktoré je určené na orientáciu hráča. */ public void kresliAkoTieňové(GRobot r, Tetromíno inéTetromíno) { kopíruj(inéTetromíno); naDno(); r.skočNa(-190 + polohaX * 20, 210 - polohaY * 20); nakresliTieňové(r); } /** * Uloží toto tetromíno do súboru. Opakom tejto metódy je konštruktor * rekonštruujúci tetromíno podľa zadaného reťazca – Tetromíno(String, * Súbor). */ public void ulož(String vlastnosť, Súbor súbor) throws java.io.IOException { súbor.zapíšVlastnosť(vlastnosť, Tetrisovina.poleNaReťazec(tvar)); súbor.zapíšVlastnosť("polohaX", polohaX); súbor.zapíšVlastnosť("polohaY", polohaY); } // Statické metódy // --------------- /** * Táto metóda slúži na uchovanie kópie poľa hracej plochy v statickom * atribúte tejto triedy. Tetromína vo veľkej miere využívajú hraciu * plochu, ktorej statická inštancia je fyzicky definovaná v triede * Tetrisovina. Aj trieda Tetrisovina plochu používa, preto je v tomto * projekte zariadené, aby jej kópiu obsahovali obe triedy. Volanie tejto * metódy je umiestnené do statického inicializačného bloku v triede * Tetrisovina. * <p> * Iná implementácia by mohla definovať samostatnú triedu Plocha, do ktorej * by boli presunuté aj niektoré statické metódy, napríklad všetky metódy * slúžiace na kreslenie mín, ale táto implementácia to rieši takto. */ public static void nastavHraciuPlochu(byte[][] hraciaPlocha) { Tetromíno.hraciaPlocha = hraciaPlocha; } /** * Metóda vráti náhodné číslo vzoru podľa počtu vzorov uložených v poli * vzory. Metóda je s výhodou použitá pri generovaní nových tetromín počas * hry. */ public static byte náhodnéČísloVzoru() { return (byte)Svet.náhodnéCeléČíslo(vzory.length - 1); } /** * Táto metóda nakreslí jedno míno, čo je jeden štvorček tetromína, * s pomocou zadaného kresliaceho robota. Je pri tom použitá aktuálna * pozícia robota. Farba je určená indexom mína. Parameter s názvom * spojenia má v sebe zakódované najviac štyri smery, ktorými majú viesť * spojenia so susediacimi mínami. */ public static void kresliMíno(GRobot r, byte indexMína, byte spojenia) { if (indexMína != 0) { if (indexMína >= 1 && indexMína <= 7) { r.farba(Tetromíno.farby[indexMína]); r.vyplňŠtvorec(10); r.farba(bielyTieň); r.štvorec(7.0); r.skoč(-1.0, 1.0); r.farba(čiernyTieň); r.štvorec(7.0); if (0 != spojenia) { for (int i = 0; i < 4; ++i) { if (0 != (spojenia & 1)) { r.zdvihniPero(); r.dopredu(10); r.položPero(); r.dozadu(10); } spojenia /= 2; r.doprava(90); } } r.skoč(1.0, -1.0); } else if (indexMína >= 8 && indexMína <= 14) { r.farba(Tetromíno.farby[indexMína - 7]); r.vyplňŠtvorec(10); r.farba(čiernyTieň); r.štvorec(7.5); r.farba(bieleZvýraznenie); r.vyplňŠtvorec(10); } else { r.farba(svetločervená); r.doprava(45); for (int i = 0; i < 4; ++i) { r.zdvihniPero(); r.dopredu(10); r.položPero(); r.dozadu(10); r.doprava(90); } r.doľava(45); } } } /** * Táto metóda kreslí tieňové mína. Je veľmi zjednodušenou verziou metódy * kresliMíno. Nekreslí spojenia so susednými mínami ani označené verzie * mín. Očakáva priamočiary index mína, ktoré nakreslí priehľadnou * (tieňovou) farbou. */ public static void kresliTieňovéMíno(GRobot r, byte indexMína) { if (indexMína >= 1 && indexMína <= 7) { r.farba(Tetromíno.tieňovéFarby[indexMína]); r.vyplňŠtvorec(10); r.farba(čiernyTieň); r.štvorec(7.5); } } // ···································································· // // Od tohto miesta nižšie je umiestnená implementácia gravitácie. // // Je celá implementovaná staticky, pretože ide o akúsi „vonkajšiu // // vrstvu“ kódu pracujúceho s inštanciami tetromín. // // ···································································· // // Najprv je vhodné vysvetliť niekoľko všeobecných pravidiel alebo faktov, // ktorými sa vykonávanie gravitácie riadi. Pri procese gravitácie v tejto // hre ide o to nájsť súvislé bloky jednej farby, ktoré nie sú zospodu // ničím blokované a mali by teda spadnúť, lebo „visia“ vo vzduchu. // // Princíp gravitácie v tejto hre považuje celé teleso za celok, ktorým // nie je možné otáčať. Keď sa teleso zospodu dotýka stavby len v jedinom // rohu, nemôže spadnúť ani sa inak zrútiť. Akoby bolo ku zvyšku stavby // pevne prilepené. To značne zjednodušuje implementáciu. // // Telesá sú vyhľadávané od najspodnejšieho riadka. Nájdené teleso sa // umiestňuje do pomocného poľa, ktorého názov v slovenčine znie rovnako // ako termín z fyziky – gravitačné pole (v angličtine by názov gravity // array nekorešpondoval s fyzikálnym termínom gravity field). S tým // súvisí termín gravitíno, ktorý v tomto projekte označuje prvok poľa // gravitačnéPole. Tiež ide o náhodnú zhodu s jedným zatiaľ hypotetickým // fyzikálnym termínom. V tomto projekte ide o slovo, ktoré vzniklo // spojením slov gravitácia a míno. // // Do tohto „gravitačného poľa“ sa postupne premietnu všetky objekty na // hracej ploche. Každý nájdený objekt sa najprv premení na „tetromíno“, // ktoré však môže mať rôzny absurdný tvar (vôbec nemusí pozostávať zo // štyroch mín), týmto „tetromínom“ sa pokúsi hra pohnúť smerom nadol // a podľa toho, či sa to podarí alebo nie ho umiestni na pozíciu pod ním // alebo na rovneké miesto. Tento proces sa opakuje pre všetky vytvorené // objekty, kým sa ešte darí pohnúť niektorým nepohnutým telesom. // To znamená, že každým telesom je v jednom cykle dovolené pohnúť // len raz. Tým sa vytvorí rýchla animácia pádu všetkých uvoľnených // blokov. Proces gravitácie môže mať za následok vytvorenie malej // reťazovej reakcie, pretože spadnuté bloky môžu opäť vytvoriť zaplnené // riadky, ktoré v súlade s pravidlami hry zmiznú a tým uvoľnia ďalšie // miesto. // // Na nájdenie telies pozostávajúcich z mín rovnakej farby je použitý // algoritmus semienkového vypĺňania (resp. v tomto prípade by bolo // priliehavejšie pomenovanie semienkové vyhľadávanie). Tento algoritmus // hľadá nových susedov rovnakej farby dovtedy, kým nejakí jestvujú. Jeho // implementácia s pomocou rekurzie je veľmi jednoduchá. (V tomto projekte // ho reprezentuje metóda označGravitína. Sama o sebe by však nefungovala, // potrebuje spoluprácu ostatných súčastí, najmä správnu inicializáciu.) // Atribút gravitačnáHodnota je používaný pri označovaní blokov stavby // na uchovanie aktuálnej farby, ktorú má mať blok. Je inicializovaný // absurdnou hodnotou -15, ktorá by sa v hre na hracej ploche nemala // nikdy vyskytnúť. Pomenovanie vzniklo z dvoch kritérií. Názov mal // obsahovať slovo gravitácia, aby bolo jasné, že atribút patrí k statickému // bloku slúžiacemu na spracovanie gravitácie a nemal byť pritom príliš // dlhý. private static byte gravitačnáHodnota = -15; // Význam tohto poľa je podrobnejšie diskutovaný v komentároch vyššie. private final static byte[][] gravitačnéPole = new byte[10][21]; // Zoznam tetromín, ktorý poslúži na recyklovanie tetromín. private final static Zoznam<Tetromíno> gravitačnéTetromína = new Zoznam<Tetromíno>(); // Táto metóda inicializuje/vymaže gravitačné pole na začiatku // každého gravitačného cyklu. private static void vymažGravitačnéPole() { for (byte i = 0; i < 10; ++i) for (byte j = 0; j < 21; ++j) gravitačnéPole[i][j] = 0; } // Táto metóda slúži na zamknutie blokov, ktoré boli premietnuté do // gravitačného poľa a už boli spracované. (Zamknuté gravitína majú // záporné hodnoty.) private static void pozamykajGravitačnéPole() { for (byte i = 0; i < 10; ++i) for (byte j = 0; j < 21; ++j) if (gravitačnéPole[i][j] > 0) gravitačnéPole[i][j] = (byte)-gravitačnéPole[i][j]; } // Táto metóda slúži na bezpečný zápis gravitín do gravitačného poľa. // (Vykoná kontrolu rozsahov parametrov i a j a potom zapíše zadanú // hodnotu do poľa. Metóda je definovaná, ale nakoniec nebola ani raz // použitá.) private static void zapíšGravitíno(int i, int j, byte hodnota) { if (i >= 0 && i < 10 && j >= 0 && j < 21) gravitačnéPole[i][j] = hodnota; } // Táto metóda slúži na bezpečné čítanie gravitín z gravitačného poľa. // (Vykoná kontrolu rozsahov parametrov i a j a potom prečíta želanú // hodnotu z poľa. Metóda je definovaná, ale nakoniec nebola ani raz // použitá.) private static byte čítajGravitíno(int i, int j) { if (i >= 0 && i < 10 && j >= 0 && j < 21) return gravitačnéPole[i][j]; return -15; } // Táto metóda slúži na bezpečné čítanie mín z poľa hraciaPlocha. // (V podstate malo ísť o ekvivalent metódy čítajGravitíno a nakoniec sa // ukázalo, že táto metóda nájde širšie využitie než pôvodná metóda, // z ktorej bola vytvorená.) private static byte čítajMíno(int i, int j) { if (i >= 0 && i < 10 && j >= 0 && j < 21) return hraciaPlocha[Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][j]; return -15; } // Toto je metóda, ktorá semienkovým vyhľadávaním označí tie gravitína, // ktoré patria do jedného zatiaľ nespracovaného bloku stavby. // // To znamená, že musí ísť o také gravitína, ktoré sú zatiaľ prázdne // a ktorých hodnota je totožná s gravitačnou hodnotou, pretože tá určuje // aktuálnu farbu bloku, s ktorou sa práve pracuje. // // Za označené sú považované tie gravitína, ktoré majú kladnú hodnotu. // Záporné hodnoty sú považované za zamknuté gravitína a nulové za // prázdne (neprítomné gravitína). private static void označGravitína(int i, int j) { if (i >= 0 && i < 10 && j >= 0 && j < 21 && 0 == gravitačnéPole[i][j] && gravitačnáHodnota == hraciaPlocha[ Tetrisovina.ĽAVÝ_OKRAJ_HRACEJ_PLOCHY + i][j]) { gravitačnéPole[i][j] = gravitačnáHodnota; označGravitína(i - 1, j); označGravitína(i + 1, j); označGravitína(i, j - 1); označGravitína(i, j + 1); } } // Táto metóda slúži na inicializáciu zadaného gravitačného tetromína (gt). // Metóda pracuje s gravitačnou hodnotou, ktorá signalizuje, ktorá farba // mín je práve spracúvaná. Táto metóda skopíruje do gravitačného // tetromína všetky také gravitína z gravitačného poľa, ktoré majú // hodnotu rovnú gravitačnej hodnote. Ide o gravitína, ktoré považujeme // za označené. Ostatné mína budú v gravitačnom tetromíne nastavené // na nulu. Tak sa v gravitačnom tetromíne vytvorí presný obraz súvislého // bloku jednej farby, ktorý môže byť posunutý v prípade, že nie je ničím // zospodu blokovaný. private static void inicializujGravitačnéTetromíno(Tetromíno gt) { gt.polohaX = gt.polohaY = 0; for (byte i = 0; i < 10; ++i) for (byte j = 0; j < 21; ++j) { if (gravitačnáHodnota == gravitačnéPole[i][j]) gt.tvar[i][j] = gravitačnáHodnota; else gt.tvar[i][j] = 0; } gt.gtPohloSa = false; gt.gtPoužité = true; } /** * Táto metóda vykoná jeden cyklus gravitácie. * <!-- Podrobnosti sú v komentároch v tele metódy. --> */ public static boolean vykonajGravitáciu() { // Upratanie pred akciou. vymažGravitačnéPole(); for (Tetromíno gt : gravitačnéTetromína) gt.gtPoužité = false; // Najprv sa zamknú všetky bloky dotýkajúce sa dna, { byte j = 20; for (byte i = 0; i < 10; ++i) { gravitačnáHodnota = čítajMíno(i, j); if (0 == gravitačnéPole[i][j] && 0 != gravitačnáHodnota) { označGravitína(i, j); pozamykajGravitačnéPole(); } } } // potom sa z ostatných blokov povytvárajú gravitačné tetromína… for (byte j = 19; j >= 0; --j) for (byte i = 0; i < 10; ++i) { gravitačnáHodnota = čítajMíno(i, j); if (0 == gravitačnéPole[i][j] && 0 != gravitačnáHodnota) { označGravitína(i, j); Tetromíno gt1 = null; for (Tetromíno gt2 : gravitačnéTetromína) { if (!gt2.gtPoužité) { gt1 = gt2; break; } } if (null == gt1) { gt1 = new Tetromíno(); gravitačnéTetromína.pridaj(gt1); } inicializujGravitačnéTetromíno(gt1); pozamykajGravitačnéPole(); } } // (Nasledujúci kód v komentári ukazuje jeden z možných spôsobov // ladenia aplikácie – ide o ladenie výpismi. Tento výpis mal // za úlohu zobraziť obsah gravitačného poľa v textovej podobe // na konzole.) /** / System.out.print("DEBUG"); for (byte j = 10; j <= 20; ++j) { for (byte i = 0; i < 10; ++i) { System.out.print((gravitačnéPole[i][j] < 0 ? "." : " ") + (char)(' ' + Math.abs(gravitačnéPole[i][j]))); } System.out.println(); } /**/ // …a cyklicky sa opakujú pokusy o pohnutie každým tetromínom (každým // najviac raz) dokedy sa to darí. Ako nále prejde celý cyklus bez // jediného úspechu, považuje sa tento gravitačný krok za uzavretý. boolean pohloSaNiečímTeraz, pohloSaNiečímCelkovo = false; do { pohloSaNiečímTeraz = false; for (Tetromíno gt : gravitačnéTetromína) { if (gt.gtPoužité && !gt.gtPohloSa) { gt.odober(); if (gt.dole()) { gt.gtPohloSa = true; pohloSaNiečímTeraz = true; pohloSaNiečímCelkovo = true; } gt.polož(); } } } while (pohloSaNiečímTeraz); return pohloSaNiečímCelkovo; } }
~
import knižnica.GRobot; import static knižnica.GRobot.*; /** * Táto trieda zhromažďuje všetky texty použité v hre. Tento prístup uľahčí * prípravu a preklad hry do iného jazyka. Táto trieda by mohla byť rozšírená * tak, aby umožňovala čítanie textov z jazykového súboru, ktorého názov by * mohlo určiť niektoré nastavenie. * * @author Roman Horváth * @version 9. 2. 2015 */ public class Texty { /** * Titulok pomocníka. (Zvyšok textu je prečítaný z textového súboru.) */ public static String pomocník = "Pomocník"; /** * Informácia o spôsobe naštartovania novej hry. */ public static String nováHra = "„F5“ – štart novej hry"; /** * Informácia o spustení alebo zrušení režimu pauzy. */ public static String pauza = "„F10“ – pauza"; /** * Výkrik prechodu do ďalšej úrovne. */ public static String ďalšiaÚroveň = "Ďalšia úroveň!"; /** * Informácia o aktuálnej hodnote úrovne. */ public static String úroveňČíslo = "Úroveň: "; /** * Výkrik oznamujúci, že bodovaná séria sa skončila. */ public static String koniecSérie = "Koniec série!"; // Toto sú pomocné polia slúžiace ako šablónky // posunov pri vykresľovaní olemovaných textov. private final static int[] Δx = {1, 0, -2, 0, 1}, Δy = {1, -2, 0, 2, -1}; /** * Metóda použije zadaný robot (r) na vykreslenie zadaného textu (t) * zadanou farbou (f). */ public static void olemovanýText(GRobot r, String t, Farba f) { // Táto metóda používa na nakreslenie lemu písma polia Δx a Δy, ktoré // obsahujú také hodnoty posunov, aby sa robot postupne popresúval // vhodných susedných polôh v jeho okolí. Z vizuálneho pohľadu by išlo // akoby o rohy štvorca nakresleného v robotovom blízkom okolí (stred // štvorca by bol na robotovej pôvodnej polohe). Olemovanie bude // nakreslené tou farbou, akú mal robot nastavenú pred volaním tejto // metódy. r.skoč(Δx[0], Δy[0]); for (int i = 1; i <= 4; ++i) { r.text(t); r.skoč(Δx[i], Δy[i]); } // Nakoniec sa robot dostane do východiskovej polohy, v ktorej nakreslí // posledný text zadanou farbou (f). r.farba(f); r.text(t); } }
~
import knižnica.GRobot; import static knižnica.GRobot.*; /** * Trieda výkrik slúži na zobrazovanie rôznych informačných správ počas hry, * ktoré sú chápané ako „výkriky“ hry. Ide napríklad o zobrazenie hodnoty * skóre, ktoré bolo práve získané, informovanie o prechode na ďalšiu úroveň, * prípadne uznanie vykonania určitej špeciálnej akcie v hre a podobne. * Možnosti sú otvorené, no v tejto implementácii hry je výkrik použitý len * v niekoľkých prípadoch na ukážku… * * @author Roman Horváth * @version 9. 2. 2015 */ public class Výkrik extends GRobot { // Písmo použité všetkými výkrikmi private final static Písmo písmo = new Písmo("Helvetica", Písmo.BOLD, 26); // Farba použitá všetkými výkrikmi private final static Farba farba = svetlomodrá; // Zoznam výkrikov na recykláciu private final static Zoznam<Výkrik> výkriky = new Zoznam<Výkrik>(); /** * Statická metóda slúžiaca na automatické vyrobenie alebo recykláciu * výkriku a jeho aktivovanie. Zadaný text je textom výkriku a zdržanie * slúži na oddialenie zobrazenia výkriku, aby bolo možné v jednom čase * určiť vznik viacerých výkrikov, ktoré budú v hre zobrazované postupne. */ public static void výkrik(String text, int zdržanie) { Výkrik výkrik1 = null; for (Výkrik výkrik2 : výkriky) if (výkrik2.neaktívny()) { výkrik1 = výkrik2; break; } if (null == výkrik1) { výkrik1 = new Výkrik(text, zdržanie); výkriky.pridaj(výkrik1); } else { výkrik1.aktivuj(text, zdržanie); } } // Nasledujúce štyri súkromné atribúty slúžia na uchovanie textu, oblasti, // zdržania a trvania tohto výkriku, pričom oblasť je vyrobená z textu // v metóde aktivuj(String, int). Oblasť slúži na zobrazovanie (kreslenie) // textu. V skutočnosti je atribút text v tejto implementácii nepotrebný. // Bol definovaný pre prípad inej implementácie, ktorá mala brať do úvahy // text neaktívnej inštancie počas recyklácie. Význam atribútu trvanie by // mal byť zrejmý – určuje trvanie zobrazovania tohto výkriku a význam // zdržania je diskutovaný v komentároch pri metóde aktivuj(String, int). private String text; private Oblasť oblasť; private int zdržanie; private int trvanie; /** * Konštruktor výkriku. Zadaný text je textom tohto výkriku a zdržanie * slúži na oddialenie zobrazenia tohto výkriku. Dôvod oddialenia je * diskutovaný v komentároch statickej metódy výkrik(String, int). */ public Výkrik(String text, int zdržanie) { skry(); písmo(písmo); kresliNaStrop(); zdvihniPero(); nekresliTvary(); hrúbkaČiary(0.75); aktivuj(text, zdržanie); } // Súkromná metóda slúžiaca na aktivovanie tohto výkriku. To znamená, že // výkrik majú právo aktivovať len metódy tejto triedy. V realite ide // o dve metódy – konštruktor (keďže konštruktory sú špeciálnymi prípadmi // metód) a statická metóda výkrik(String, int), ktorá môže recyklovať // jestvujúce pasívne inštancie výkrikov. V tejto metóde je vypočítaný aj // čas trvania tohto výkriku, ktorý je stanovený ako desaťnásobok dĺžky // textu výkriku a je meraný v tikoch, ktorých želanú dĺžku určuje hra // a skutočnú dĺžku ovplyvňuje zaťaženie systému. Účel oblasti je // diskutovaný vyššie v tomto zdrojovom kóde – pred skupinou súkromných // atribútov. private void aktivuj(String text, int zdržanie) { domov(); this.text = text; this.zdržanie = 25 * zdržanie; trvanie = 10 * text.length(); oblasť = new Oblasť(text(text)); aktivuj(); } /** * Prekrytá metóda aktivita slúži na zariadenie fungovania nasledujúceho * mechanizmu: * 1. odďaľuje zobrazenie čakajúcich výkrikov, * 2. zobrazuje aktívne výkriky, pričom doba aktivity je vypočítaná * v metóde aktivuj(String, int), * 3. deaktivuje výkriky, ktorých doba aktivácie prekročila stanovenú * dĺžku trvania. */ @Override public void aktivita() { if (zdržanie > 0) { --zdržanie; } else if (trvanie > 0) { --trvanie; farba(farba); vyplňOblasť(oblasť); farba(biela); obkresliOblasť(oblasť); dopredu(1); } else { deaktivuj(); } } }
~
import static knižnica.GRobot.*; /** * Táto trieda slúži na uschovanie riadených zvukov, ktoré sú prehrávané počas * hry. Obsahuje vnorenú triedu RiadenýZvuk, ktorá umožňuje ovládať prehrávanie * zvukov podľa hodnoty verejného statického atribútu Zvuky.zapnuté. * * @author Roman Horváth * @version 9. 2. 2015 */ public class Zvuky { /** Stavový atribút umožňujúci umlčanie všetkých zvukov. **/ public static boolean zapnuté = true; /** * Táto trieda umožňuje podriadiť prehrávanie všetkých zvukov hodnote * atribútu zapnuté. */ public static class RiadenýZvuk { /** Zvukový klip pre tento riadený zvuk. **/ public final Zvuk zvuk; /** Konštruktor prijímajúci meno zvukového súboru bez prípony. **/ public RiadenýZvuk(String meno) { zvuk = Svet.načítajZvuk(meno + ".wav"); } /** Ak nie sú zvuky umlčané, tak prehrá tento zvukový klip. **/ public void prehraj() { if (zapnuté) zvuk.prehraj(); } } // Statický blok obsahujúci inicializáciu priečinka zvukov. static { Svet.priečinokZvukov("Sounds"); } /** * Zvuk prehraný počas vymazania označeného riadka. (Označený riadok je taký * riadok, ktorý bol úplne zaplnený časťami tetromín a má byť vymazaný.) */ public static RiadenýZvuk riadok = new RiadenýZvuk("clear-line"); /** * Zvuk, ktorý mal byť prehraný počas vykonávania rôznych nastavení v hre, * to jest počas manipulácie s ovládacími prvkami nastavení. */ public static RiadenýZvuk nastav = new RiadenýZvuk("config"); /** * Zvuk prehraný počas odrazenia sa tetromína od steny alebo jestvujúcej * stavby. Tento fakt je spracúvaný počas výpočtu výsledného bodovania za * určitú akciu. */ public static RiadenýZvuk kopni = new RiadenýZvuk("kick"); /** * Zvuk prehraný počas záverečného umiestnenia tetromína na určitej pozícii. * Po zamknutí tetromína s ním už nie je možné hýbať. */ public static RiadenýZvuk zamkni = new RiadenýZvuk("lock"); /** * Zvuk prehraný počas neúspešného pokusu o pohnutie tetromínom * v horizontálnom smere. Ide o protiklad zvuku „pohni“. */ public static RiadenýZvuk nepohni = new RiadenýZvuk("move-fail"); /** * Zvuk prehraný počas úspešného posunutia tetromína v horizontálnom smere. * Protikladom je zvuk „nepohni“. */ public static RiadenýZvuk pohni = new RiadenýZvuk("move"); /** * Zvuk prehraný počas aktivácie alebo deaktivácie režimu pauzy. */ public static RiadenýZvuk pauza = new RiadenýZvuk("pause"); /** * Zvuk prehraný počas neúspešného pokusu o otočenie tetromína vpravo * alebo vľavo. Protikladom tohto zvuku je zvuk „rotuj“. */ public static RiadenýZvuk nerotuj = new RiadenýZvuk("rotate-fail"); /** * Zvuk prehraný počas úspešného otočenia tetromína vpravo alebo vľavo. * Protikladom je zvuk „nerotuj“. */ public static RiadenýZvuk rotuj = new RiadenýZvuk("rotate"); /** * Zvuk prehraný počas neúspešného pokusu o výmenu medzi aktuálnym * (padajúcim) tetromínom a tetromínom v zásobníku (v najjednoduchšom * prípade ide priamo o nasledujúcie tetromíno). Neúspech je spôsobený * nedostatkom miesta na hracej ploche v okolí aktuálneho tetromína. */ public static RiadenýZvuk neprepni = new RiadenýZvuk("swap-fail"); /** * Zvuk prehraný počas úspešnej výmeny aktuálneho (padajúceho) tetromína * a tetromína umiestneného v zásobníku (v najjednoduchšom prípade ide * priamo o nasledujúce tetromíno). */ public static RiadenýZvuk prepni = new RiadenýZvuk("swap"); }