TlačiťTlačiť Slovenčina English Hľadať RSS

© 2005 – 2024 Roman Horváth, všetky práva vyhradené. Dnes je 26. 4. 2024.

Stránka sa načítava, prosím čakajte…

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:

 

  
obrázokobrázokobrázok 
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):

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.

 

Staršia verzia

~

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 »

Staršia verzia

~

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 »

Staršia verzia

~

/**
 * <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 »

Staršia verzia

~

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 »

Staršia verzia

~

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 »

Staršia verzia

~

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 »

Staršia verzia

~

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, otvárané v novom okne
(obvykle ide o externý odkaz) 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();
	}
}

Novšia verzia

~

/**
 * 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];
	}
};

Novšia verzia

~

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();
	}
}

Novšia verzia

~

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;
	}
}

Novšia verzia

~

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);
	}
}

Novšia verzia

~

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();
		}
	}
}

Novšia verzia

~

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");
}