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

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

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

Dátum: 7. 8. 2020, pred štyrmi rokmi

Názov hry bol zvolený až po jej vytvorení. Na začiatku stála predstava vytvorenia kruhového bludiska zostaveného z rôznych druhov prekážok (statických, rotujúcich, striedavo sa rozťahujúcich a sťahujúcich alebo kombinovaných) okolo cieľa, do ktorého sa má hráč dostať bez kontaktu s ktoroukoľvek prekážkou.

Kľúčové bolo vymyslieť vhodný princíp generovania prekážok. Podrobnosti sú vysvetlené v texte nad výpisom triedy HlavnáTrieda (a tiež v komentároch viacerých tried projektu). Tézeus je bájny grécky hrdina, ktorý vošiel do bludiska, aby zabil Minotaura.

 

obrázok 
Ukážka hry.

ikonaZdrojové súbory projektu na prevzatie. 3,54 kB (3,46 KiB), 7. 8. 2020

 

~

import knižnica.*;
import static knižnica.Svet.*;

/**
 * Z triedy Cieľ bude vytvorená jediná inštancia. To bude pasívny cieľ, do
 * ktorého sa má Tézeus dostať.
 */
public class Cieľ extends GRobot
{
	/**
	 * Konštruktor. Nastavuje zopár základných vlastností a tvar.
	 * Tiež upravuje polohu cieľa – volaním metódy premiestni.
	 */
	public Cieľ()
	{
		veľkosť(15);
		hrúbkaČiary(1);

		// Tvar cieľa je súkružie.
		vlastnýTvar(r ->
		{
			krúžok();
			krúžok(veľkosť() * 0.8);
		});

		premiestni();
	}

	/**
	 * Upravuje polohu cieľa. Ide o náhodnú polohu v oblasti štvorca
	 * vymedzeného bodmi [−20; −20] – [20; 20].
	 */
	public void premiestni()
	{
		skočNa(náhodnéReálneČíslo(-20, 20),
			náhodnéReálneČíslo(-20, 20));
	}
}

Základ generovania prekážok (reč je o ich polohe) je v nájdení náhodnej polohy v širokom medzikruží so stredom v polohe cieľa, do ktorého sa má Tézeus dostať. Takto generované prekážky by však s vysokou pravdepodobnosťou vytvorili bariéru, ktorá by bola úplne nepriechodná, preto bol k tomuto základnému princípu pridaný ešte jeden. Ide o vygenerovanie ôsmych náhodných cestičiek, v rámci ktorých budú polohy prekážok zamietnuté (a opätovne generované).

Na ich uchovanie postačí dvojrozmerné pole bodov. Keby sme ich nechali vykresliť, tak ich tvar by vyzeral napríklad takto:

obrázok 
Tri ukážky tvarov cestičiek.

Potom treba zvoliť vhodnú šírku cestičiek. Príliš úzke cestičky by boli nepriechodné a príliš široké by boli príliš viditeľné a príliš jednoducho prekonateľné. Vhodným kompromisom sa ukázalo generovanie takých polôh, v ktorých prekážky môžu svojou dĺžkou do cestičiek zasahovať, nesmú však na nich priamo ležať. Dlhodobé testovanie ukázalo, že týmto spôsobom sa vygeneruje taká konfigurácia prekážok, medzi ktorými je minimálne jedna z ôsmych cestičiek prekonateľná (i keď nie vždy jednoduchým spôsobom).

Počas testovania sa ukázalo byť užitočné poskytnutie spätnej väzby hráčovi, do ktorej prekážky počas preniknutia do stredu bludiska narazil (pretože nie vždy to bolo na pohľad jasné), preto bola do triedy Prekážka pridaná možnosť zasvietenia.

~

import knižnica.*;
import static knižnica.Svet.*;

/**
 * Hlavná trieda prepája všetky prvky hry.
 */
public class HlavnáTrieda extends GRobot
{
	// Inštancie hlavných herných prvkov. (Inštancie prekážok budú uložené
	// v zozname.)
	private final Cieľ cieľ = new Cieľ();
	private final Tézeus tézeus = new Tézeus();
	private final Zoznam<Prekážka> prekážky = new Zoznam<>();

	// Pole slúžiace na vygenerovanie neviditeľných krivoľakých „cestičiek“
	// vedúcich do stredu bludiska, ktoré majú zaručiť, že bude jestvovať
	// minimálne jedna cesta, ktorou sa bude môcť Tézeus dostať k cieľu.
	private final Bod[][] body = new Bod[8][8];

	// Konštruktor hlavnej triedy. Nastaví okno, vyrobí prekážky a spustí hru.
	private HlavnáTrieda()
	{
		super(600, 600, "Tézeus");
		nekresli();
		zbaľ();

		for (int i = 0; i < 300; ++i)
			prekážky.pridaj(new Prekážka());

		rozmiestniPrekážky();
		skry();
		kresli();
	}

	/**
	 * Metóda generujúca vhodné rozmiestnenie prekážok, ktoré budú
	 * reprezentovať dynamické bludisko pre Tézea.
	 */
	public void rozmiestniPrekážky()
	{
		// Generovanie „cestičiek.“
		for (int i = 0; i < 8; ++i)
		{
			skočNa(cieľ);
			for (int j = 0; j < 8; ++j)
			{
				skoč(33);
				double vysuň = náhodnéReálneČíslo(-44, 44);
				preskočVpravo(vysuň);
				body[i][j] = poloha();
				preskočVľavo(vysuň);
			}
			vpravo();
		}

		// Generovanie prekážok.
		int uhol = 90, režim = 0;
		for (Prekážka prekážka : prekážky)
		{
			boolean opakuj;
			do
			{
				opakuj = false;

				prekážka.náhodnýSmer();
				prekážka.skočNa(cieľ);
				prekážka.skoč(náhodnéReálneČíslo(50, 230));
				prekážka.polohaX((int)(prekážka.polohaX() / 10) * 10);
				prekážka.polohaY((int)(prekážka.polohaY() / 10) * 10);

				// Kontrolakolízií prekážok s cestičkami (s pomocou
				// hľadania vzdialeností polôh prekážok od úsečiek
				// tvoriacich cestičky).
				kontrola:
				for (int i = 0; i < 8; ++i)
				{
					Bod posledný = cieľ.poloha();
					for (int j = 0; j < 8; ++j)
					{
						if (vzdialenosťBoduOdÚsečky(prekážka,
							posledný, body[i][j]) < 10)
						{
							// Kolízia znamená opakovanie
							// generovania prekážky.
							opakuj = true;
							break kontrola;
						}
						posledný = body[i][j];
					}
				}
			}
			while (opakuj);

			// Pridelenie režimu prekážke.
			prekážka.režim(režim);
			if (0 == (režim / 2) % 3)
			{
				prekážka.uhol(uhol);
				uhol += 45;
			}
			++režim;
		}
	}


	/*# Reakcia na časovač, v ktorej sú formou kolízií riešené dve hlavné
	 *# situácie v hre: úspešné dosiahnutie cieľa Tézeom a zrazenie sa Tézea
	 *# s prekážkou. #*/

	@Override public void tik()
	{
		if (cieľ.bodVKruhu(tézeus, 20))
		{
			cieľ.premiestni();
			rozmiestniPrekážky();
			tézeus.nováPoloha();
		}

		boolean kolízia = false;

		for (Prekážka prekážka : prekážky)
		{
			if (prekážka.koliduje(tézeus))
			{
				prekážka.zasvieť();
				kolízia = true;
			}
		}

		if (kolízia)
		{
			pípni();
			tézeus.nováPoloha();
		}
	}

	/**
	 * Hlavná metóda. Vstupný bod programu.
	 *
	 * @param args  zoznam argumentov prijímaný z konzoly
	 */
	public static void main(String[] args)
	{
		použiKonfiguráciu("Tézeus.cfg");
		new HlavnáTrieda();
		spustiČasovač();
	}
}

~

import knižnica.*;
import static knižnica.Svet.*;

/**
 * Z tejto triedy bude vytvorených mnoho inštancií (300), ktoré budú
 * fungovať v rôznych režimoch. Režim určuje, či sa prekážka otáča a ktorým
 * smerom a či mení svoju veľkosť.
 */
public class Prekážka extends GRobot
{
	private int fáza = -1;
	private int režim = 0;
	private int svieti = 0;

	/**
	 * Konštruktor. Nastavuje zopár základných vlastností a tvar.
	 * Tiež prekážku aktivuje bez automatického spustenia časovača.
	 */
	public Prekážka()
	{
		veľkosť(15);
		hrúbkaČiary(8);
		zdvihniPero();

		// Tvar prekážky je hrubšia čiarka. Vďaka hrúbke a zaobleným okrajom
		// pôsobí dojmom malého vyplneného oválu.
		vlastnýTvar(r ->
		{
			odskoč(veľkosť() / 2);
			vpred();
		});

		aktivuj(false);
	}

	/**
	 * Zmení farbu prekážky a spustí časovač svietenia.
	 */
	public void zasvieť()
	{
		svieti = 10;
		farba(červená);
	}

	/**
	 * Zhasne prekážku.
	 */
	public void zhasni()
	{
		svieti = 0;
		farba(čierna);
	}

	/**
	 * Zistí, či prekážka svieti.
	 *
	 * @return true – prekážka svieti
	 */
	public boolean svieti()
	{
		return svieti > 0;
	}

	/**
	 * Zistí režim prekážky.
	 *
	 * @return režim prekážky vo forme celého čísla
	 */
	public int režim()
	{
		return režim;
	}

	/**
	 * Zmení režim tejto prekážky.
	 *
	 * @param režim  nový režim tejto prekážky
	 */
	public void režim(int režim)
	{
		this.režim = režim;
		veľkosť(15);
		switch ((režim / 2) % 3)
		{
		case 0: rýchlosťOtáčania(0); break;
		case 1: rýchlosťOtáčania(-6); break;
		case 2: rýchlosťOtáčania(6); break;
		}
	}


	/*# Prekryté metódy. Aktivita animuje meniace sa prekážky. Koliduje
	 *# upravuje spôsob detekcie kolízií prekážky s inými robotmi. #*/

	@Override public void aktivita()
	{
		if (0 != režim % 2) veľkosť(3 + Math.abs(12 - (++fáza % 24)));
		if (svieti > 0 && --svieti <= 0) zhasni();
	}

	@Override public boolean koliduje(GRobot iný)
	{
		// Kolízia bude detegovaná zistením vzdialenosti polohy objektu „iný“
		// od úsečky vymedzenej bodmi „bod1“ a „bod2,“ ktoré sú umiestnené
		// presne v súlade s kreslením tvaru prekážky (čo je čiarka s určitou
		// hrúbkou).

		Bod bod1 = poloha();
		Bod bod2 = poloha();

		bod1.posuňVSmere(this, -veľkosť() / 2);
		bod2.posuňVSmere(this, veľkosť() / 2);

		return (iný.veľkosť() + (hrúbkaČiary() / 2)) >=
			vzdialenosťBoduOdÚsečky(iný, bod1, bod2);
	}
}

~

import knižnica.*;
import static knižnica.Svet.*;
import static knižnica.Kláves.*;
import static knižnica.ÚdajeUdalostí.*;

/**
 * Z triedy Tézeus bude vytvorená jediná inštancia. To bude postavička
 * riadená hráčom.
 */
public class Tézeus extends GRobot
{
	// Nasledujúca dvojica príznakov je oprava, ktorá zabraňuje tomu,
	// aby sa Tézeus hýbal pri ťahaní myšou po smrti. (Bolo by to
	// nežiaduce. Často to spôsobovalo opakovanú smrť Tézea.)
	private boolean voľný;
	private boolean myšHore = true;

	/**
	 * Konštruktor. Nastavuje rôzne vlastnosti a tvar.
	 */
	public Tézeus()
	{
		rýchlosť(5, false);
		zdvihniPero();
		veľkosť(6);
		ohranič(300, 300);
		hrúbkaČiary(2);

		// Kreslenie tvaru je jednoduché: krúžok so zárezom v smere Tézea.
		vlastnýTvar(r ->
		{
			krúžok();
			vpred();
		});

		nováPoloha();
	}

	/**
	 * Presun Tézea na novú (bezpečnú) polohu.
	 */
	public void nováPoloha()
	{
		zrušCieľ();
		náhodnýSmer();
		skočNa(stred);
		skoč(náhodnéReálneČíslo(260, 290));
		voľný = myšHore;
	}


	/*# Reakcie ovládania klávesnicou aj myšou. #*/

	@Override public void stlačenieKlávesu()
	{
		switch (kláves())
		{
			case VPRAVO:
				smer(VÝCHOD);
				aktivuj();
				break;

			case VĽAVO:
				smer(ZÁPAD);
				aktivuj();
				break;

			case HORE:
				smer(SEVER);
				aktivuj();
				break;

			case DOLE:
				smer(JUH);
				aktivuj();
				break;
		}
	}

	@Override public void uvoľnenieKlávesu()
	{
		switch (kláves())
		{
			case VPRAVO: case VĽAVO:
			case HORE: case DOLE:
				deaktivuj();
				break;
		}
	}

	@Override public void stlačenieTlačidlaMyši()
	{
		myšHore = false;
		ťahanieMyšou();
	}

	@Override public void uvoľnenieTlačidlaMyši()
	{
		voľný = myšHore = true;
	}

	@Override public void ťahanieMyšou()
	{
		if (voľný)
		{
			otočNaMyš();
			if (vzdialenosťOdMyši() > 2 * veľkosť())
				cieľNaMyš();
		}
	}
}