Stránka sa načítava, prosím čakajte…
© 2005 – 2024 Roman Horváth, všetky práva vyhradené. Dnes je 24. 4. 2024.
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.
- Balíček so zdrojovými súbormi tried na prevzatie. 4,52 kB (4,41 KiB), 7. 8. 2020
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
Ukážka rozhoranej hry.
Ukážka hry ukončenej neúspechom.
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.