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

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

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

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.

  
obrázok 
 

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):

  
obrázok 
 

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