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

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

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

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

Zadanie

Naprogramujte hru Stena podľa nasledujúceho opisu.

  • Hra bude vytvorená s použitím týchto piatich hlavných prvkov (pojmov): tehly, plošina, lopta, nože a denáre.
  • Tehly budú rozmiestnené v hornej časti hracej plochy a budú tvoriť stenu.
  • Plošina bude prvok ovládaný hráčom, bude umiestnená v úplne spodnej časti hracej plochy, bude sa môcť pohybovať doprava a doľava a bude mať tieto dve hlavné funkcie:
  • odrážanie loptičky,
  • strieľanie nožov.
  • Lopta (alebo loptička) sa riadi nasledujúcimi pravidlami:
  • odráža sa od okrajov hracej plochy, okrem spodného, cez ktorý prepadáva, čím ju hráč stráca (zabrániť tomu môže práve prostredníctvom plošiny);
  • odráža sa od tehál, pričom náraz loptičky do tehly zničí tehlu a zachová loptičku,
  • dokedy je loptička prítomná na ploche (to jest, kým neprepadne cez spodný okraj), môže hráč z plošiny vystreľovať nože (podrobnosti sú nižšie).
  • Nože sú dvojakého druhu. Na zjednodušenie ich môžeme nazvať „dobrými“ a „zlými.“ Pre nože platí toto:
  • „Dobré nože“ môže strieľať hráč z plošiny, keď je na ploche prítomná loptička. Smerujú od plošiny nahor.
  • Keď zasiahne „dobrý nôž“ tehlu, tak je zničená tehla aj nôž.
  • „Zlé nože“ vzniknú pri každom zničení tehly (vrátane zničenia tehly „dobrým nožom“) a smerujú z miesta zničenej tehly nadol.
  • Zanikajú buď keď zasiahnu plošinu, alebo keď prekročia spodný okraj hracej plochy.
  • Pri zásahu plošiny nožom sú deaktivované všetky „zlé nože“ na hracej ploche, ale za cenu „pokuty“ (pozri nižšie).
  • Zásah plošiny „zlým nožnom“ má dva negatívne dôsledky:
  • hráč stráca loptičku,
  • hráč dostáva „pokutu“ za každý nôž, ktorý bol aktívny v čase zásahu, vrátane toho, ktorý plošinu zasiahol.
  • Denáre budú platidlom, pre ktoré budú platiť nasledujúce pravidlá:
  • hráč dostane pri štarte hry určitý nevysoký „vstupný kapitál“ denárov,
  • za sumu, ktorú určíte si bude môcť „kúpiť“ loptičku,
  • pri platobnej neschopnosti a strate loptičky sa hra okamžite končí (neúspechom),
  • zničenie tehly nie je odmenené priamo, hráč získa denár až za „zlý nôž,“ ktorý ho netrafí.
  • Po zničení všetkých tehál sa hra končí úspechom.

Riešenie

Táto implementácia rieši pravidlá a upravuje správanie hry takto:

  • Cena loptičky je 15 denárov.
  • „Pokuta“ za každý „zlý nôž“ aktívny v čase zásahu plošiny (pozri pravidlá vyššie) je dva denáre.
  • Zobrazenie počtu denárov (ktorých história siaha až do čias Rímskej ríše) je v rímskych čísliciach.
  • Stena nie je nijako esteticky riešená. Tehly sú rozmiestnené úplne náhodne v hornej časti hracej plochy. (Mnohé sa prekrývajú.)
  • Tvar plošiny sa mení z vyplneného na dutý podľa stavu hry. Počas aktívneho hrania je plošina vyplnená a po úspešnom alebo neúspešnom ukončení je dutá.
  • Hra sa dá pozastaviť klávesom H.

 

 

Táto trieda zabezpečuje kreslenie loptičky a niektoré aktivity súvisiace s jej fungovaním: reset, zobrazovanie a skrývanie podľa stavu aktivity, spresňuje spôsob overovania kolízií s ostatnými prvkami v hre a odráža alebo deaktivuje loptičku podľa toho, do ktorej hranice hracej plochy narazí (pozri pravidlá vyššie).

~

import knižnica.*;

public class Lopta extends GRobot
{
	public Lopta()
	{
		ohranič(ODRAZ);
		hrúbkaČiary(2);
		farba(zelená);
		zdvihniPero();
		náhodnýSmer();
		maximálnaRýchlosť(14);
		zrýchlenie(1, false);
		vypĺňajTvary();
		skočNa(0, -30);
		skry();
	}

	public void reset()
	{
		rýchlosť(0);
		náhodnýSmer();
		if (smer() >= 170 && smer() <= 190) vľavo(45);
		else if (smer() <= 10 || smer() >= 350) vpravo(45);
		skočNa(0, -30);
		aktivuj(false);
	}

	@Override public void kresliTvar()
	{
		krúžok();
	}

	@Override public void aktivácia()
	{
		zobraz();
	}

	@Override public void deaktivácia()
	{
		skry();
	}

	@Override public boolean koliduje(GRobot objekt)
	{
		if (objekt instanceof Plošina)
		{
			// Keby sme potrebovali pracovať priamo s plošinou (jej
			// metódami a pod.), tak by sme použili nasledujúce
			// pretypovanie:
			//
			// Plošina plošina = (Plošina)objekt;

			return objekt.bodVElipse(this,
				objekt.veľkosť() * 0.2 + veľkosť(),
				objekt.veľkosť() + veľkosť());
		}
		else if (objekt instanceof Tehla)
		{
			// Rovnako, keby sme potrebovali pracovať s tehlou:
			//
			// Tehla tehla = (Tehla)objekt;

			return objekt.bodVObdĺžniku(this,
				objekt.veľkosť() * 2.0 + veľkosť(),
				objekt.veľkosť() + veľkosť());
		}

		return super.koliduje(objekt);
	}

	@Override public boolean mimoHraníc(Bod[] poleBodov, double uhol)
	{
		// (Odrážanie od okrajov plochy plátna.)

		if (polohaY() <= Svet.najmenšieY()) deaktivuj();
		else if (poleBodov[2].getY() == poleBodov[3].getY()) smer(360 - smer());
		else smer(180 - smer());

		if (smer() >= 175 && smer() <= 185) vľavo(25);
		else if (smer() <= 5 || smer() >= 355) vpravo(25);

		return true;
	}


	// Vypnutý „podvod“ (angl. cheat – slov. aj čít) na ladenie…
	/* * /
	@Override public void klik()
	{
		skočNaMyš();
	}
	/* */


	// Nie všetky programátorské prostredia podporujú tvorbu projektov.
	// Nasledujúci riadok dovolí našu hru spustiť aj prostredníctvom tejto
	// triedy. (Je to užitočné vo fáze vývoja.)
	//
	// public static void main(String[] args) { Stena.main(args); }
}
  Nôž »

Táto trieda zabezpečuje kreslenie tvaru noža a niektoré aktivity súvisiace s jeho fungovaním: zobrazovanie a skrývanie podľa stavu aktivity a deaktivácia po prekročení hraníc hracej plochy.

~

import knižnica.*;

public class Nôž extends GRobot
{
	public Nôž()
	{
		ohranič();
		farba(modrá);
		zdvihniPero();
		rýchlosť(12.5, false);
		smer(JUH);
		skry();
	}

	@Override public void kresliTvar()
	{
		double hrúbka = veľkosť() / 3;
		double dĺžka = veľkosť() / 2;
		odskoč();
		for (int i = 0; i < 8; ++i)
		{
			if (hrúbka > 0) hrúbkaČiary(hrúbka);
			else hrúbkaČiary(0.5);
			hrúbka -= 0.5;
			vpred(dĺžka);
		}
	}

	@Override public void mimoHraníc()
	{
		deaktivuj();
	}

	@Override public void aktivácia()
	{
		zobraz();
	}

	@Override public void deaktivácia()
	{
		skry();
	}


	// Nie všetky programátorské prostredia podporujú tvorbu projektov.
	// Nasledujúci riadok dovolí našu hru spustiť aj prostredníctvom tejto
	// triedy. (Je to užitočné vo fáze vývoja.)
	//
	// public static void main(String[] args) { Stena.main(args); }
}
« Lopta Plošina »

Táto trieda zabezpečuje iba reset a kreslenie tvaru plošiny. Ostatné činnosti sú centralizované v triede Stena.

~

import knižnica.*;

public class Plošina extends GRobot
{
	public Plošina()
	{
		ohranič(Svet.najväčšieX() - 50, Svet.najväčšieY(), PLOT);
		hrúbkaČiary(2);
		farba(tmavomodrá);
		zdvihniPero();
		odskoč(Svet.najväčšieY() - 10);
		vpravo(90);
		maximálnaRýchlosť(16);
		zrýchlenie(1.5, false);
		veľkosť(50);
		pomer(0.2);
		vypĺňajTvary();
	}

	public void reset()
	{
		rýchlosť(0);
		polohaX(0);
		vypĺňajTvary();
	}

	@Override public void kresliTvar()
	{
		elipsa();
	}

	// Nie všetky programátorské prostredia podporujú tvorbu projektov.
	// Nasledujúci riadok dovolí našu hru spustiť aj prostredníctvom tejto
	// triedy. (Je to užitočné vo fáze vývoja.)
	//
	// public static void main(String[] args) { Stena.main(args); }
}
« Nôž Stena »

Táto trieda riadi hru, kontroluje všetky pravidlá, ovláda plošinu atď.

~

import knižnica.*;

public class Stena extends GRobot
{
	// Inštancie plošiny, loptičky a tehál (uložených v zozname):
	private final Plošina plošina;
	private final Lopta lopta;
	private final Zoznam<Tehla> tehly = new Zoznam<>();

	// Zoznamy nožov sú dva. „Zlé nože“ sú také, ktoré smerujú zhora nadol
	// a ktoré ohrozujú plošinu. „Dobré nože“ sú tie, ktoré vystrelil hráč
	// s cieľom zbúrať časť steny.
	private final Zoznam<Nôž> zléNože   = new Zoznam<>();
	private final Zoznam<Nôž> dobréNože = new Zoznam<>();

	// Príznak konca hry.
	private boolean koniecHry = false;

	// Denár je platidlom v tejto hre a zároveň abstraktnou mierou zdravia
	// hráča, keďže denármi sa platí aj za loptičky a neschopnosť
	// vystrelenia novej loptičky znamená koniec hry.
	private static int denáre = 60;

	// (Konštruktor.)
	private Stena()
	{
		Svet.farbaPozadia(papierová);
		if (Svet.prvéSpustenie()) Svet.zbaľ();

		// Úprava polohy hlavného robota – hlavný robot je použitý na výpis
		// informácie o priebehu hry (aktuálne skóre alebo palec hore 🖒
		// po úspešnom ukončení a palec dole 🖓 po neúspešnom ukončení hry):
		skočNa(Svet.najmenšieX() + 3 * veľkosť(),
			Svet.najmenšieY() + veľkosť());

		plošina = new Plošina();
		lopta = new Lopta();
		for (int i = 0; i < 200; ++i) tehly.pridaj(new Tehla());
		Svet.pridajPoložkuPonuky("Nová hra", Kláves.VK_N, Kláves.VK_N);

		Svet.spustiČasovač();
	}

	private void nováHra()
	{
		Svet.nekresli();
		for (Tehla tehla : tehly) tehla.reset();
		for (Nôž nôž : zléNože) nôž.deaktivuj();
		for (Nôž nôž : dobréNože) nôž.deaktivuj();
		plošina.reset(); lopta.reset(); lopta.deaktivuj();
		koniecHry = false;
		denáre = 60;
		Svet.kresli();
	}

	private void prehra()
	{
		koniecHry = true;
		plošina.zastav();
		plošina.nevypĺňajTvary();
	}

	private boolean nováLopta()
		// Návratová hodnota tejto metódy udeľuje alebo zamieta povolenie
		// na vystrelenie nového noža. Metóda toto povolenie udelí len ak
		// je lopta aktívna (čiže ak netreba vystreliť novú).
	{
		if (lopta.neaktívny())
		{
			if (denáre >= 0)
			{
				// Loptička stojí 15 denárov, ale na vystrelenie novej
				// loptičky stačí byť v pluse, čiže hráč môže ísť aj do
				// malého dočasného debetu.
				denáre -= 15;
				lopta.reset();
			}
			// Úplná platobná neschopnosť pri vystrelení novej loptičky
			// znamená prehru.
			else prehra();
			return false;
		}
		return true;
	}

	// Vracia „zlý nôž“ – strelu cieliacu do priestoru plošiny.
	private Nôž dajZlýNôž()
	{
		// (Buď nájde neaktívny, alebo vyrobí nový.)
		for (Nôž nôž : zléNože)
			if (nôž.neaktívny()) return nôž;
		Nôž nôž = new Nôž()
		{
			// „Zlý nôž“ má upravenú jedinú vlastnosť – jeho deaktiváciou
			// sa hráčovi pridá denár. (Čiže keď hráča netrafí, hráč tým
			// získa. Pri zásahu je to naopak – hráč totiž dostáva
			// „pokutu.“)
			@Override public void deaktivácia()
			{
				++denáre;
				skry();
			}
		};
		zléNože.pridaj(nôž);
		return nôž;
	}

	// Vracia „dobrý nôž“ – hráčovu strelu.
	private Nôž dajDobrýNôž()
	{
		// (Buď nájde neaktívny, alebo vyrobí nový.)
		for (Nôž nôž : dobréNože)
			if (nôž.neaktívny()) return nôž;
		Nôž nôž = new Nôž();
		nôž.farba(červená);
		nôž.smer(SEVER);
		dobréNože.pridaj(nôž);
		return nôž;
	}

	// Vystrelí „zlý nôž.“
	private void vystreľZlýNôž(Poloha p)
	{
		Nôž nôž = dajZlýNôž();
		nôž.skočNa(p);
		nôž.aktivuj(false);
	}

	// Vystrelí „dobrý nôž.“
	private boolean vystreľDobrýNôž()
	{
		if (denáre > 1)
			// (Na vystrelenie noža treba mať dostatok prostriedkov. Hráč
			// nesmie zostať po vystrelení v úplnej platobnej neschopnosti,
			// aby mal šancu ešte na posledné vystrelenie loptičky.)
		{
			Nôž nôž = dajDobrýNôž();
			nôž.skočNa(plošina);
			if (0 == denáre % 2)
				nôž.preskočVpravo(plošina.veľkosť());
			else
				nôž.preskočVľavo(plošina.veľkosť());
			nôž.aktivuj(false);
			--denáre; // (Vystrelenie „dobrého noža“ stojí jeden denár.)
			return false;
		}
		return true;
	}

	@Override public void kresliTvar()
	{
		// (Tvarom hlavného robota je informácia o stave hry.)
		if (koniecHry)
		{
			if (denáre >= 0)
				text("🖒");
			else
				text("🖓");
		}
		else
			text(Svet.celéNaRímske(denáre));
	}

	@Override public void voľbaPoložkyPonuky()
	{
		// Keďže jestvuje jediná položka ponuky (okrem predvolenej),
		// nekontrolujeme, ktorá položka bola zvolená a priamo vykonávame
		// akciu:
		if (ÁNO == Svet.otázka("Želáte si začať novú hru?")) nováHra();
	}

	@Override public void tik()
	{
		if (koniecHry) return;

		if (lopta.aktívny())
		{
			// (Odraz loptičky od plošiny.)
			if (lopta.koliduje(plošina))
			{
				double Δx = (lopta.polohaX() - plošina.polohaX() +
					Svet.náhodnéReálneČíslo(-1, 1)) / 2;
				lopta.smer(360 - lopta.smer());
				lopta.skoč(lopta.rýchlosť());
				lopta.vpravo(Δx);
			}
		}
		else if (denáre < 0) prehra();

		// (Spracovanie súvisiace s tehlami – žiadna prítomná tehla znamená
		// úspešný koniec; kolízia s loptičkou znamená zbúranie tehly,
		// vystrelenie „zlého noža“ a odrazenie sa loptičky; kolízia tehly
		// s „dobrým nožom“ má podobné dôsledky – vystrelenie „zlého noža,“
		// pričom „dobrý“ je nárazom zničený.)
		boolean jeKoniec = true;
		for (Tehla tehla : tehly)
		{
			if (tehla.viditeľný())
			{
				jeKoniec = false;
				if (lopta.koliduje(tehla))
				{
					double Δx = Math.abs(lopta.polohaX() - tehla.polohaX());
					tehla.skry();
					vystreľZlýNôž(tehla);

					if (Δx < 22)
					{
						// Vypnutý výpis na účely ladenia:
						// Svet.farbaTextu(tmavomodrá); Svet.vypíšRiadok(Δx);
						lopta.smer(360 - lopta.smer());
						lopta.skoč(lopta.rýchlosť());
					}
					else
					{
						// Vypnutý výpis na účely ladenia:
						// Svet.farbaTextu(tmavočervená); Svet.vypíšRiadok(Δx);
						lopta.smer(180 - lopta.smer());
						lopta.skoč(lopta.rýchlosť());
					}

					lopta.vľavo(Svet.náhodnéReálneČíslo(-1, 1));
					break;
				}
				else
				{
					for (Nôž nôž : dobréNože)
					{
						if (nôž.viditeľný() && tehla.koliduje(nôž))
						{
							tehla.skry();
							vystreľZlýNôž(tehla);
							nôž.deaktivuj();
						}
					}
				}
			}
		}

		// (Kontrola kolízia plošiny so „zlými nožmi.“)
		for (Nôž nôž : zléNože)
		{
			if (nôž.viditeľný() && plošina.koliduje(nôž))
			{
				// Pri zásahu plošiny nožom:
				//
				// • Plošina sa resetne (presunie sa do stredu) a hráč
				//   stráca loptičku.
				plošina.reset();
				lopta.deaktivuj();

				// • Tvrdším trestom je „pokuta“ za každý aktívny „zlý nôž“
				//   v čase zásahu s výškou dva denáre za nôž.
				for (Nôž nôž2 : zléNože)
					if (nôž2.aktívny())
					{
						// (Tu sa síce odoberú tri denáre, ale deaktivácia
						// noža (ľubovoľným spôsobom) hráčovi jeden denár
						// pridá.)
						denáre -= 3;
						nôž2.deaktivuj();
					}
			}
		}

		if (jeKoniec)
		{
			if (lopta.aktívny())
			{
				lopta.deaktivuj();

				// (Odpustenie dlhu v prípade výhry.)
				if (denáre < 0) denáre = 0;

				// (Deaktivácia všetkých prípadne aktívnych nožov.)
				for (Nôž nôž : zléNože)
					if (nôž.aktívny()) nôž.deaktivuj();
			}

			plošina.zastav();
			plošina.nevypĺňajTvary();
			koniecHry = true;
		}
	}

	@Override public void stlačenieKlávesu()
	{
		if (koniecHry) return;

		switch (ÚdajeUdalostí.kláves())
		{
		case Kláves.VPRAVO:
			plošina.rozbehniSa();
			break;

		case Kláves.VĽAVO:
			plošina.začniCúvať();
			break;

		case Kláves.MEDZERA:
			if (nováLopta())
				vystreľDobrýNôž();
			break;


		// Vypnutý „podvod“ (angl. cheat – slov. aj čít) na ladenie…
		/* * /
		case Kláves.HORE:
			if (lopta.aktívny())
			{
				if (lopta.smer() >= SEVER &&
					lopta.smer() <= JUH)
					lopta.vpravo(15);
				else
					lopta.vľavo(15);
			}
			break;

		case Kláves.DOLE:
			if (lopta.aktívny())
			{
				if (lopta.smer() >= SEVER &&
					lopta.smer() <= JUH)
					lopta.vľavo(15);
				else
					lopta.vpravo(15);
			}
			break;
		/* */


		}
	}

	@Override public void uvoľnenieKlávesu()
	{
		if (koniecHry) return;

		switch (ÚdajeUdalostí.kláves())
		{
		case Kláves.VPRAVO:
		case Kláves.VĽAVO:
			plošina.zastav();
			break;

		case Kláves.VK_H:
			if (Svet.časovačAktívny())
				Svet.zastavČasovač();
			else
				Svet.spustiČasovač();
		}
	}


	// Vypnutý „podvod“ (angl. cheat – slov. aj čít) na ladenie…
	/* * /
	@Override public void klik()
	{
		if (ÚdajeUdalostí.tlačidloMyši(ĽAVÉ))
		{
			Nôž nôž = dajZlýNôž();
			nôž.skočNaMyš();
			nôž.aktivuj(false);
		}
		else
		{
			Nôž nôž = dajDobrýNôž();
			nôž.skočNaMyš();
			nôž.aktivuj(false);
		}
	}
	/* */


	// (Hlavná metóda.)
	public static void main(String[] args)
	{
		Svet.nekresli();
		try
		{
			Svet.použiKonfiguráciu("Stena.cfg");
			new Stena();
		}
		catch (Throwable t)
		{
			t.printStackTrace();
		}
		Svet.kresli();
	}
}
« Plošina Tehla »

Táto trieda zabezpečuje najmä kreslenie tvaru tehly. Okrem toho upravuje generovanie náhodnej polohy tak, aby sa všetky tehly nachádzali iba v hornej časti obrazovky: po vygenerovaní náhodnej polohy skontroluje y­‑novú súradnicu, ktorej zmení znamnieno v prípade, že je jej hodnota záporná.

~

import knižnica.*;

public class Tehla extends GRobot
{
	public Tehla()
	{
		hrúbkaČiary(2);
		farba(tmavočervená);
		vypĺňajTvary();
		pomer(2);
		reset();
	}

	public void reset()
	{
		náhodnáPoloha();
		zobraz();
	}

	@Override public void kresliTvar()
	{
		obdĺžnik();
	}

	@Override public void náhodnáPoloha()
	{
		super.náhodnáPoloha();
		if (polohaY() < 0) polohaY(-polohaY());
	}

	// Nie všetky programátorské prostredia podporujú tvorbu projektov.
	// Nasledujúci riadok dovolí našu hru spustiť aj prostredníctvom tejto
	// triedy. (Je to užitočné vo fáze vývoja.)
	//
	// public static void main(String[] args) { Stena.main(args); }
}
« Stena  

 

Výsledok

obrázok 
Ukážka rozhoranej hry. 
 

obrázok 
Ukážka hry ukončenej neúspechom. 
 

obrázok 
Ukážka úspešne ukončenej hry. 
 

 

Úlohy a návrhy na zlepšenia

Porozmýšľajte nad nasledujúcimi návrhmi a skúste ich zrealizovať.

  • Zabezpečenie zmysluplnejšieho rozmiestnenia tehál. (Aby sa neprekrývali a tvorili akoby časti murovaných stien.)
  • Zlepšenie grafickej stránky:
  • úprava pozadia,
  • úprava grafiky lopty, tehál, nožov, plošiny,
  • úprava spôsobu znázornenia stavu hry,
  • prípadne ďalšie zlepšenia.