Stránka sa načítava, prosím čakajte…
© 2005 – 2024 Roman Horváth, všetky práva vyhradené. Dnes je 20. 4. 2024.
Dátum: 9. 8. 2020, pred štyrmi rokmi
Zdrojový kód v tomto článku ukazuje implementáciu (resp. jednu z možných implementácií) hry Piškvorky na hracej ploche 3 × 3 (na tri výherné piškvorky, evidentne) pre dvoch hráčov.
Väčšina kódu je buď samovysvetľujúca, alebo komentovaná. Jedna časť je však lepšie pochopiteľná s pomocou obrázka. Je to spôsob vyhodnocovania zhodnej trojice piškvoriek hráčov…
Metóda nájdiZhodu
spolu s poľom vz
zároveň ukazujú spôsob, ako sa na zjednodušenie určitého algoritmu (v našom prípade algoritmu na hľadanie zhodnej trojice na hracej ploche) dá využiť údajová štruktúra. Bez poľa vz
, ktoré určuje počiatočné súradnice hľadania a smer hľadania, by sme prehľadávací algoritmus museli realizovať viacerými cyklami.
Tento algoritmus transformuje prehľadávané trojice na jedno celé číslo. Ich význam je znázornený na nasledujúcom obrázku (pričom v programe kladné čísla značia trojicu prvého hráča a záporné druhého):
(Porovnajte tento opis so zdrojovým kódom nižšie. Pravdepodobne sa dá nájsť aj kratšie a efektívnejšie riešenie, ale ako ukážka konceptu postačuje aj nami predložené riešenie.)
~
import knižnica.*; public class Piškvorky extends GRobot { // Konštanty na zlepšenie čitateľnosti kódu. Hracia plocha môže obsahovať: public final static int PRÁZDNE = 0; public final static int HRÁČ_JEDEN = 1; public final static int HRÁČ_DVA = -1; // Konštanta na rýchu úpravu rozostupov medzi poľami hracej plochy. public final static double ROZOSTUP = 10; // Hracia plocha. private final static int plocha[][] = new int[3][3]; // Pomocná mriežka bodov na uchovanie aktuálnych polôh polí hracej // plochy. (Pomáha najmä pri kreslení zvýraznenia trojíc hráčov.) private final static Bod[][] polohy = { {new Bod(), new Bod(), new Bod()}, {new Bod(), new Bod(), new Bod()}, {new Bod(), new Bod(), new Bod()} }; // Pomocné súkromné pole k metóde na hľadanie zhôd trojíc na hracom poli. // Index prvého rozmeru poľa zvýšený o jedna sa zhoduje s poradovým // číslom zhody a hodnoty prvkov poľa majú nasledujúci význam // (pričom nasledujúci zoznam určuje indexy prvkov v druhom rozmere): // 0 – počiatočné i; // 1 – počiatočné j; // 2 – prírastok/úbytok i (i-čkový smer); // 3 – prírastok/úbytok j (j-čkový smer). private final static int[][] vz = { {0, 0, 0, 1}, {1, 0, 0, 1}, {2, 0, 0, 1}, {2, 0, -1, 1}, {0, 0, 1, 0}, {0, 1, 1, 0}, {0, 2, 1, 0}, {0, 0, 1, 1}, }; // Indexy dotknutých polí hracej plochy pri poslednom overení polohy bodu // metódou bodV. private int pi = -1; private int pj = -1; // Táto premenná je príznakom výhernej zhody niektorého z hráčov. // (0 – žiadna zhoda; kladné číslo – poradové číslo trojice so zhodou // prvého hráča; záporné číslo – poradové číslo trojice so zhodou // druhého hráča) private int zhoda = 0; // Atribút určujúci, ktorý hráč je práve na ťahu. private int naŤahu = HRÁČ_JEDEN; // Príznak signalizujúci pokračovanie (resp. koniec) aktuálnej hry. // Hodnota false znamená, že hra sa už skončila. private boolean hraPokračuje = true; // Položka ponuky spúšťajúca novú hru. private final PoložkaPonuky nováHra; // Konštruktor. private Piškvorky() { super(330, 330, "Piškvorky"); // ## super(450, 450, "Piškvorky"); skry(); /*# *# Poznámka: Keby sme chceli hru trochu spestriť, môžeme zapnúť *# pomalé otáčanie hracej plochy. Treba „zapnúť“ (odkomentovať) *# riadky označené // ##, pričom pri volaní nadradeného *# konštruktora (super; vyššie) treba „vypnúť“ (zakomentovať) *# pôvodné volanie, pretože toto volanie môže byť iba jedno. */ veľkosť(100); hrúbkaČiary(1.5); zdvihniPero(); // ## rýchlosťOtáčania(-0.5); nováHra = Svet.pridajPoložkuPonuky( "Nová hra…", Kláves.VK_N, Kláves.VK_N); Svet.zbaľ(); zobraz(); } // Súkromná metóda spúšťajúca novú hru. private void nováHra() { // ## uhol(90); for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) plocha[i][j] = PRÁZDNE; zhoda = 0; naŤahu = HRÁČ_JEDEN; hraPokračuje = true; Svet.prekresli(); } // Metóda, ktorá nájde zhodu trojice na hracom poli a vráti ju vo forme // celého čísla, ktoré má význam tzv. poradového čísla zhody (pozri // obrázok na stránke https://pdf.truni.sk/horvath/materialy?piskvorky). // Nula znamená žiadna zhoda. Kladné číslo znamená poradové číslo // zhodnej trojice prvého hráča. Záporné číslo znamená poradové číslo // zhodnej trojice druhého hráča. private int nájdiZhodu() { for (int a = 0; a < 8; ++a) { int i = vz[a][0], j = vz[a][1]; int zhoda = a + 1; for (int b = 0; b < 3; ++b) { if (HRÁČ_JEDEN != plocha[i][j]) { zhoda = 0; break; } i += vz[a][2]; j += vz[a][3]; } if (0 != zhoda) return zhoda; i = vz[a][0]; j = vz[a][1]; zhoda = -a - 1; for (int b = 0; b < 3; ++b) { if (HRÁČ_DVA != plocha[i][j]) { zhoda = 0; break; } i += vz[a][2]; j += vz[a][3]; } if (0 != zhoda) return zhoda; } return 0; } // Pomocná metóda detegujúca pokračovanie, resp. koniec hry. (Návratová // hodnota false znamená, že hra sa skončila.) private boolean hraPokračuje() { zhoda = nájdiZhodu(); if (0 != zhoda) return hraPokračuje = false; hraPokračuje = false; for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) if (PRÁZDNE == plocha[i][j]) return hraPokračuje = true; return hraPokračuje; } // Obsluha položiek ponuky. @Override public void voľbaPoložkyPonuky() { if (nováHra.zvolená()) { if (!hraPokračuje || ÁNO == Svet.otázka( "Chcete spustiť novú hru?")) nováHra(); } } // Kreslenie tvaru = kreslenie obsahu plochy. @Override public void kresliTvar() { /*# *# Poznámka: *# *# Aj pri malých projektoch môže významne pomôcť ladenie. *# *# Ladenie aplikácií vytváraných prostredníctvom programovacích *# rámcov môže byť ľstivé. (Niekedy sa zdá, že priamo *# v programovacom rámci je skrytá neodladená chyba, pretože *# ku kódu rámca nemusíme mať prístup, pritom príčiny môžu byť *# iné, nepredvídateľné.) V progamovacom rámci GRobot sa preto *# dá zapnúť režim ladenia: *# *# Svet.režimLadenia(true); *# *# Využili sme ho aj pri tomto projekte. Hneď na začiatku sme *# zachytili nevysvetliteľné správanie. Mriežka hracej plochy *# sa z neznámeho dôvodu zobrazovala vysunutá o kúsok doľava *# a hore. Zapli sme preto ladenie a po spustení sme zistili, *# že v programovacom rámci vzniká niekoľko výnimiek *# (GRobotException) oznamujúcich chyby (výpisy sú skrátené): *# *# Súbor „Piškvorky.cfg“ nebol nájdený. *# knižnica.GRobotException: Súbor „Piškvorky.cfg“ nebol *# nájdený. *# … *# *# Polomer vpísanej kružnice nesmie byť záporný! *# knižnica.GRobotException: Polomer vpísanej kružnice *# nesmie byť záporný! *# … *# *# Tá prvá nie je podstatná. Iba oznamuje, že konfiguračný *# súbor zatiaľ nebol vytvorený. Môžeme ju ignorovať. *# *# Tá druhá sa opakovala trikrát a je závažnejšia, pretože *# vnútorne prerušuje činnosť programovacieho rámca, čím *# zabráni návratu robota do korektného stavu po skončení *# kreslenia. Keď sme overili polohu pred začiatkom kreslenia *# týmto príkazom: *# *# System.out.println("" + poloha()); *# *# tak sme zistili, že pred vznikom prvej výnimky z trojice *# „Polomer vpísanej kružnice…“ bola poloha korektná [0; 0], *# pred vznikom druhej už bola posunutá do bodu [−10; 10] *# a nakoniec robot skončil v bode[−20; 20]. (Robot sa trikrát *# pokúšal kresliť svoj tvar ešte predtým, než bol zväčšený *# nad rozmer rozostupu polí mriežky, čo spôsobovalo pokus *# o kreslenie štvorca so záporným rozmerom = polomerom *# vpísanej kružnice.) Stačilo pridať nasledujúci riadok za *# výpočet polomeru: *# *# if (polomer < 0) return; *# *# a všetko opäť fungovalo korektne. Ladenie sme mohli vypnúť. *# *# (Podobne o dva riadky nižšie s premennou kamene.) *#*/ double veľkosť = veľkosť(); double hč1 = hrúbkaČiary(); double hč0 = hč1 / 2, hč2 = hč1 * 2, hč3 = hč1 * 4; double polomer = veľkosť / 2 - ROZOSTUP; if (polomer < 0) return; double kamene = polomer - ROZOSTUP; if (kamene < 0) return; if (hraPokračuje) štvorec(veľkosť * 1.5 + ROZOSTUP); skoč(); preskočVľavo(); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { polohy[i][j].poloha(poloha()); štvorec(polomer); if (HRÁČ_JEDEN == plocha[i][j]) { hrúbkaČiary(hč2); krúžok(kamene); hrúbkaČiary(hč1); } else if (HRÁČ_DVA == plocha[i][j]) { hrúbkaČiary(hč2); vpravo(45); for (int k = 0; k < 4; ++k) { vpravo(90); dopredu(kamene); odskoč(kamene); } vľavo(45); hrúbkaČiary(hč1); } else if (hraPokračuje) { if (HRÁČ_JEDEN == naŤahu) { hrúbkaČiary(hč0); krúžok(kamene); hrúbkaČiary(hč1); } else if (HRÁČ_DVA == naŤahu) { hrúbkaČiary(hč0); vpravo(45); for (int k = 0; k < 4; ++k) { vpravo(90); dopredu(kamene); odskoč(kamene); } vľavo(45); hrúbkaČiary(hč1); } } preskočVpravo(); } odskoč(); preskočVľavo(veľkosť * 3); } skoč(); preskočVpravo(); if (0 != zhoda) { hrúbkaČiary(hč3); int a = zhoda; if (a < 0) a = -a; --a; int i = vz[a][0], j = vz[a][1]; skočNa(polohy[i][j]); i += 2 * vz[a][2]; j += 2 * vz[a][3]; choďNa(polohy[i][j]); } } // Overenie polohy bodu voči hracej mriežke. Ak sa bod nachádza // v niektorom z polí hracej plochy, tak v atribútoch pi a pj budú // uložené indexy tohto poľa. @Override public boolean bodV(Poloha bod) { Bod poloha = poloha(); try { pi = -1; pj = -1; double veľkosť = veľkosť(); double polomer = veľkosť / 2 - ROZOSTUP; if (polomer < 0) return false; skoč(); preskočVľavo(); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { if (bodVoŠtvorci(bod, polomer)) { pi = i; pj = j; return true; } preskočVpravo(); } odskoč(); preskočVľavo(veľkosť * 3); } skoč(); preskočVpravo(); return false; } finally { poloha(poloha); } } // Reakcia na kliknutie tlačidlom myši = hranie za hráča. @Override public void klik() { /* (Čít na testovanie.) * / if (ÚdajeUdalostí.myš().isShiftDown() && bodV(ÚdajeUdalostí.polohaMyši())) { if (!ÚdajeUdalostí.myš().isControlDown()) { if (ÚdajeUdalostí.tlačidloMyši(ĽAVÉ)) plocha[pi][pj] = HRÁČ_JEDEN; else if (ÚdajeUdalostí.tlačidloMyši(PRAVÉ)) plocha[pi][pj] = HRÁČ_DVA; } else plocha[pi][pj] = PRÁZDNE; hraPokračuje(); Svet.prekresli(); return; } /* */ if (hraPokračuje && bodV(ÚdajeUdalostí.polohaMyši())) { if (0 == plocha[pi][pj]) { plocha[pi][pj] = naŤahu; if (hraPokračuje()) naŤahu = -naŤahu; Svet.prekresli(); } } } // Hlavná metóda. public static void main(String[] args) { Svet.použiKonfiguráciu("Piškvorky.cfg"); new Piškvorky(); } }