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: 5. 8. 2020, pred štyrmi rokmi

Zadanie

Naprogramujte jednoduchú hru hľadanie mín (na mínovom poli), ktorá má nasledujúce pravidlá hrania:

  • Mínové pole je hracia plocha (s dostatočne veľkým počtom polí), s náhodne rozmiestnenými mínami (ktoré má hráč nájsť). Každé pole sa dá odkryť alebo označiť (vlajočkou).
  • Hráč začína hľadať míny na úplne zakrytom kompletne neoznačenom mínovom poli.
  • Hra ponúka hráčovi spätnú väzbu vo forme čísiel, ktoré znamenajú, koľko mín sa nachádza v tesnom okolí tohto poľa (0 – 8), pričom toto číslo je viditeľné až po odkrytí poľa. Nuly sa nezobrazujú, namiesto čísla 0 sa hráčovi zobrazuje prázdne pole.
  • Ak hráč odkryje pole s mínou, prehráva. Vtedy mu hra ukáže umiestnenie všetkých mín.
  • Ak hráč odkryje prázdne pole (s nulou), spustí sa kaskáda automatického odkrývania polí, pričom hranicu tvoria polia s číslami 1 – 8 a hranice mínového poľa.
  • Hráčovým cieľom je vlajočkami označiť všetky polia s mínami (na základe logického uvažovania a informácií, ktoré hráč dostáva vo forme čísiel odokrytých polí)…

Návrhy, tipy a odporúčania:

  • Zakryté polia môžu mať grafickú formu dlaždíc, ktoré pôsobia ako vypuklé (na zjednodušenie môžu byť aj hranaté).
  • Odokryté polia môžu mať grafickú formu opačne zakrivených dlaždíc alebo plochých ohraničených políčok.
  • Čísla označujúce počty mín v okolí môžu mať rôzne farby podľa ich hodnoty.
  • Grafika mín a vlajočiek môže byť ľubovoľná. Hlavné je, aby bolo hráčovi jasné, že predstavujú presne to, čo majú predstavovať (vlajočky a míny).
  • Keď je v okolí políčka dostatočne veľký počet vlajočiek (teda taký, ktorý je rovný hodnote poľa), môže hráč odkryť zvyšné polia jedným kliknutím (napríklad pravým tlačidlom myši). Riziko pri tejto akcii je, že hráč umiestnil niektorú vlajočku nesprávne.
  • Ak hráč prehrá, mali by sa mu vhodným spôsobom ukázať jeho chyby. Napríklad nesprávne umiestnené vlajočky prečiarknutím, kliknutie na mínu (jej odkrytie) červeným podfarbením.

Riešenie

Riešenie je uvedené bez vysvetľujúcich komentárov. Zdrojový kód nie je nijakým spôsobom zašifrovaný, všetky identifikátory (názvy atribútov, metód, konštánt, premenných…) sú volené čo najvýstižnejšie. Cieľom je naučiť čitateľa čítať kód s porozumením a orientovať sa v ňom.

~

import knižnica.*;

public class HľadanieMín extends GRobot
{
	private final static int veľkosťŠtvorčeka = 25;
	private final static int polovicaŠtvorčeka = veľkosťŠtvorčeka / 2;

	private final static Farba[] farbyČísiel = new Farba[]{
		svetlomodrá, svetlozelená, svetločervená,
		tmavomodrá, tmavočervená, tmavozelená, tmavošedá, uhlíková
	};

	private int šírka;
	private int výška;
	private int početMín;
	private int zásobaZástaviek;
	private int xx;
	private int yy;
	private int Δy;
	private int polovicaVýšky;
	private int polovicaŠírky;
	private char[][] pole;
	private boolean koniecHry;
	private boolean výhra;

	private HľadanieMín()
	{
		super("Hľadanie mín…");
		skry();
		hrúbkaČiary(2.5);
		// test();
		nováHra();
		Svet.pridajPoložkuPonuky("Nová hra", Kláves.VK_N, Kláves.VK_N);
	}

	private void nováHra()
	{
		novéPole(32, 23);
		rozmiestniMíny(128);
		// System.out.println("početMín: " + početMín);
		zakryPole();
		Svet.nekresli();
		Svet.vymažGrafiku();
		kresliPole();
	}

	private void novéPole(int šírka, int výška)
	{
		this.šírka = šírka;
		this.výška = výška;
		xx = (0 == šírka % 2) ? polovicaŠtvorčeka : 0;
		yy = (0 == výška % 2) ? polovicaŠtvorčeka : 0;
		Δy = polovicaŠtvorčeka;
		polovicaVýšky = výška / 2;
		polovicaŠírky = šírka / 2;
		pole = new char[výška][šírka];
		vymažPole();
	}

	/*private void test()
	{
		výhra = koniecHry = true;
		for (int i = 0; i < výška; ++i)
			for (int j = 0; j < šírka; ++j)
				pole[i][j] = (char)(((i * výška) + j) % 31);
	}*/

	private void vymažPole()
	{
		výhra = koniecHry = false;
		for (int i = 0; i < výška; ++i)
			for (int j = 0; j < šírka; ++j)
				pole[i][j] = 0;
	}

	private void zvýšHodnotu(int x, int y)
	{
		for (int i = y - 1; i <= y + 1; ++i)
			for (int j = x - 1; j <= x + 1; ++j)
				if (i >= 0 && i < výška && j >= 0 && j < šírka &&
					pole[i][j] != 9) ++pole[i][j];
	}

	private void rozmiestniMíny(int počet)
	{
		if (počet >= (šírka * výška) / 3)
			počet = (šírka * výška) / 3;

		int x, y;
		for (int i = 0; i < počet; ++i)
		{
			do
			{
				x = (int)Svet.náhodnéCeléČíslo(0, šírka - 1);
				y = (int)Svet.náhodnéCeléČíslo(0, výška - 1);
			}
			while (9 == pole[y][x]);
			pole[y][x] = 9;
			zvýšHodnotu(x, y);
		}

		početMín = 0;
		for (int i = 0; i < výška; ++i)
			for (int j = 0; j < šírka; ++j)
				if (jeMína(j, i)) ++početMín;
		zásobaZástaviek = početMín;
	}

	private void zakryPole()
	{
		for (int i = 0; i < výška; ++i)
			for (int j = 0; j < šírka; ++j)
				if (pole[i][j] < 10) pole[i][j] += 10;
	}

	private void kresliZástavku()
	{
		preskočVľavo(polovicaŠtvorčeka / 3);
		farba(červená);
		začniCestu();
		for (int k = 0; k < 3; ++k)
		{
			vpred(polovicaŠtvorčeka - 5);
			vpravo(120);
		}
		vyplňCestu();

		farba(antracitová);
		skoč(polovicaŠtvorčeka - 5);
		vzad(veľkosťŠtvorčeka - 10);
		skoč(polovicaŠtvorčeka - 5);
		preskočVpravo(polovicaŠtvorčeka / 3);
		// text("⚑");
	}

	private void kresliKrížik()
	{
		double hč = hrúbkaČiary();
		hrúbkaČiary(hč + 1);
		farba(snehová);
		for (int i = 0; i < 2; ++i)
		{
			vpravo(45);
			skoč(polovicaŠtvorčeka - 5);
			vzad(veľkosťŠtvorčeka - 10);
			skoč(polovicaŠtvorčeka - 5);
			vľavo(90);
			skoč(polovicaŠtvorčeka - 5);
			vzad(veľkosťŠtvorčeka - 10);
			skoč(polovicaŠtvorčeka - 5);
			vpravo(45);
			farba(tmavočervená);
			hrúbkaČiary(hč);
		}
	}

	private void kresliČíslo(char hodnota)
	{
		String číslo = "" + (char)('0' + hodnota);

		skoč(-1, 1);
		farba(svetlošedá);
		text(číslo);

		skoč(2, -2);
		farba(uhlíková);
		text(číslo);

		skoč(-1, 1);
		farba(farbyČísiel[hodnota - 1]);
		text(číslo);
	}

	private void kresliMínu()
	{
		farba(antracitová);
		kruh(polovicaŠtvorčeka - 5);
		for (int k = 0; k < 8; ++k)
		{
			vpred(polovicaŠtvorčeka - 3);
			odskoč(polovicaŠtvorčeka - 3);
			vpravo(45);
		}
		skoč(-polovicaŠtvorčeka / 5, polovicaŠtvorčeka / 5);
		farba(snehová);
		kruh(polovicaŠtvorčeka / 4);
		skoč(polovicaŠtvorčeka / 5, -polovicaŠtvorčeka / 5);
	}

	private void kresliPole()
	{
		Svet.nekresli();

		if (koniecHry)
		{
			domov();
			if (výhra) farba(svetlozelená); else farba(svetločervená);
			kresliObdĺžnik(
				2 + polovicaŠírky * veľkosťŠtvorčeka,
				2 + polovicaVýšky * veľkosťŠtvorčeka);
		}

		for (int i = 0; i < výška; ++i)
		{
			for (int j = 0; j < šírka; ++j)
			{
				skočNa(
					((j - polovicaŠírky) * veľkosťŠtvorčeka) + xx,
					((i - polovicaVýšky) * veľkosťŠtvorčeka) + yy + Δy);
				char hodnota = pole[i][j];


				if (koniecHry && 9 == hodnota)
					farba(červená); else farba(tyrkysová);
				vyplňŠtvorec(polovicaŠtvorčeka);

				skoč(-polovicaŠtvorčeka + 1, -polovicaŠtvorčeka + 1);
				if (hodnota >= 10)
				{
					farba(svetlotyrkysová);
					for (int k = 0; k < 2; ++k)
					{
						vpred(veľkosťŠtvorčeka - 2);
						vpravo(90);
					}

					farba(tmavotyrkysová);
					for (int k = 0; k < 2; ++k)
					{
						vpred(veľkosťŠtvorčeka - 2);
						vpravo(90);
					}
				}
				else
				{
					farba(tmavotyrkysová);
					for (int k = 0; k < 2; ++k)
					{
						vpred(veľkosťŠtvorčeka - 2);
						vpravo(90);
					}

					farba(svetlotyrkysová);
					for (int k = 0; k < 2; ++k)
					{
						vpred(veľkosťŠtvorčeka - 2);
						vpravo(90);
					}
				}
				skoč(polovicaŠtvorčeka - 1, polovicaŠtvorčeka - 1);

				if (hodnota >= 20)
				{
					kresliZástavku();
					if (koniecHry && hodnota != 29) kresliKrížik();
				}
				else if (hodnota < 10 || koniecHry)
				{
					if (hodnota >= 1 && hodnota <= 8)
					{
						kresliČíslo(hodnota);
					}
					else if (9 == hodnota || 19 == hodnota)
					{
						kresliMínu();
					}
					else if (hodnota > 18)
					{
						farba(snehová);
						text("¿?");
					}
				}

				// text("💣"); text("💠"); text("🔥"); text("⚒");
			}
		}


		double index = veľkosťŠtvorčeka / 2;

		/*System.out.println("zásobaZástaviek * index: " + zásobaZástaviek * index);
		System.out.println("šírka * veľkosťŠtvorčeka: " + (šírka - 1) * veľkosťŠtvorčeka);*/

		for (int i = 3; zásobaZástaviek * index >
			(šírka - 1) * veľkosťŠtvorčeka && i < 9; ++i)
		{
			// System.out.println("i: " + i);
			index = veľkosťŠtvorčeka / i;
			// System.out.println("zásobaZástaviek * index: " + zásobaZástaviek * index);
		}

		for (int i = 0; i < zásobaZástaviek; ++i)
		{
			skočNa(
				(-polovicaŠírky - 0) * veľkosťŠtvorčeka + xx +
					i * index,
				(-polovicaVýšky - 1) * veľkosťŠtvorčeka + yy + Δy);
			kresliZástavku();
		}

		Svet.kresli();
	}

	private boolean jeČisté(int x, int y)
	{
		return y >= 0 && y < výška && x >= 0 && x < šírka && pole[y][x] < 9;
	}

	private boolean jePrázdne(int x, int y)
	{
		return y >= 0 && y < výška && x >= 0 && x < šírka &&
			0 == pole[y][x] % 10;
	}

	private boolean jeMína(int x, int y)
	{
		return y >= 0 && y < výška && x >= 0 && x < šírka &&
			9 == pole[y][x] % 10;
	}

	private boolean jeZástavka(int x, int y)
	{
		return y >= 0 && y < výška && x >= 0 && x < šírka && pole[y][x] >= 20;
	}

	private boolean odhaľAkSaDá(int x, int y)
	{
		if (y >= 0 && y < výška && x >= 0 && x < šírka)
		{
			char hodnota = pole[y][x];
			if (hodnota > 0 && hodnota < 9)
			{
				char početZástaviek = 0;
				for (int i = y - 1; i <= y + 1; ++i)
					for (int j = x - 1; j <= x + 1; ++j)
						if (jeZástavka(j, i)) ++početZástaviek;

				if (hodnota == početZástaviek)
				{
					for (int i = y - 1; i <= y + 1; ++i)
						for (int j = x - 1; j <= x + 1; ++j)
							odhaľPolíčko(j, i);
					return true;
				}
			}
		}
		return false;
	}

	private boolean odhaľPolíčko(int x, int y)
	{
		if (y >= 0 && y < výška && x >= 0 && x < šírka)
		{
			char hodnota = pole[y][x];
			if (20 > hodnota && 10 <= hodnota)
			{
				hodnota -= 10;
				pole[y][x] = hodnota;
				if (9 == hodnota) prehra();
				else if (0 == hodnota)
				{
					for (int i = y - 1; i <= y + 1; ++i)
						for (int j = x - 1; j <= x + 1; ++j)
							if (i != y || j != x) odhaľPolíčko(j, i);
				}
				return true;
			}
		}
		return false;
	}

	private boolean prepniZástavku(int x, int y)
	{
		if (y >= 0 && y < výška && x >= 0 && x < šírka)
		{
			char hodnota = pole[y][x];
			if (10 <= hodnota)
			{
				if (20 <= hodnota)
				{
					hodnota -= 10;
					++zásobaZástaviek;
				}
				else
				{
					hodnota += 10;
					--zásobaZástaviek;
				}
				pole[y][x] = hodnota;
				return true;
			}
		}
		return false;
	}

	private void overVýhru()
	{
		int početZástaviek = 0;

		for (int i = 0; i < výška; ++i)
			for (int j = 0; j < šírka; ++j)
				if (jeZástavka(j, i)) ++početZástaviek;
				else if (!jeČisté(j, i)) return;

		if (početZástaviek == početMín) výhra();
		else zásobaZástaviek = početMín - početZástaviek;
	}

	private void výhra()
	{
		zásobaZástaviek = 0;
		výhra = true;
		koniecHry = true;
		Svet.pípni();
	}

	private void prehra()
	{
		výhra = false;
		koniecHry = true;
		Svet.pípni();
	}

	@Override public void voľbaPoložkyPonuky()
	{
		if (ÁNO == Svet.otázka("Želáte si začať novú hru?")) nováHra();
	}

	/*@Override public void pohybMyši() // TEST
	{
		domov();
		skoč(Δy);

		farba(oranžová);
		kresliObdĺžnik(
			polovicaŠírky * veľkosťŠtvorčeka + polovicaŠtvorčeka - xx,
			polovicaVýšky * veľkosťŠtvorčeka + polovicaŠtvorčeka - yy);

		if (myšVObdĺžniku(
			polovicaŠírky * veľkosťŠtvorčeka + polovicaŠtvorčeka - xx,
			polovicaVýšky * veľkosťŠtvorčeka + polovicaŠtvorčeka - yy))
			farba(biela); else farba(svetložltá);
		vyplňObdĺžnik(30, 10);

		int j = (int)(ÚdajeUdalostí.polohaMyšiX() + polovicaŠtvorčeka - xx +
			polovicaŠírky * veľkosťŠtvorčeka) / veľkosťŠtvorčeka;
		int i = (int)(ÚdajeUdalostí.polohaMyšiY() + polovicaŠtvorčeka - yy -
			Δy + polovicaVýšky * veľkosťŠtvorčeka) / veľkosťŠtvorčeka;

		farba(čierna);
		text(j + " " + i);
	}*/

	@Override public void klik()
	{
		domov();
		skoč(Δy);

		if (!koniecHry && myšVObdĺžniku(
			polovicaŠírky * veľkosťŠtvorčeka + polovicaŠtvorčeka - xx,
			polovicaVýšky * veľkosťŠtvorčeka + polovicaŠtvorčeka - yy))
		{
			int j = (int)(ÚdajeUdalostí.polohaMyšiX() + polovicaŠtvorčeka -
				xx + polovicaŠírky * veľkosťŠtvorčeka) / veľkosťŠtvorčeka;
			int i = (int)(ÚdajeUdalostí.polohaMyšiY() + polovicaŠtvorčeka -
				yy - Δy + polovicaVýšky * veľkosťŠtvorčeka) / veľkosťŠtvorčeka;

			if ((ÚdajeUdalostí.myš().getClickCount() > 1 &&
				odhaľAkSaDá(j, i)) ||
				(ÚdajeUdalostí.tlačidloMyši(ĽAVÉ) && odhaľPolíčko(j, i)) ||
				(ÚdajeUdalostí.tlačidloMyši(PRAVÉ) && prepniZástavku(j, i)))
			{
				overVýhru();
				Svet.nekresli();
				Svet.vymažGrafiku();
				kresliPole();
			}
		}
	}

	public static void main(String[] args)
	{
		Svet.použiKonfiguráciu("HľadanieMín.cfg");
		new HľadanieMín();
	}
}

Výsledok

obrázok 
Neúspešný začiatok hry.

obrázok 
Úspešný začiatok hry.

obrázok 
Rozohratá hra.

 

Poznámka: V balíčku na prevzatie je priložený aj konfiguračný súbor, vďaka ktorému sa okno aplikácie po spustení zobrazí o niečo väčšie, než predvolené. Vedeli by ste zariadiť, aby sa veľosť okna pri prvom spustení (bez prítomnosti konfiguračného súboru) upravila automaticky, aby hráč nemusel okno zväčšovať ručne?