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

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

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

Dátum: 12. 8. 2021, pred tromi rokmi

Galtonova doska je nástroj opísaný v roku 1889 lordom Francisom Galtonom. Pozostáva zo šikmej dosky, na ktorej sú ako prekážky v siedmich radoch nastrelené kolíky, pričom v prvom (najhornejšom) rade sa nachádza len jeden kolík a ich počet postupne rastie až po číslo sedem (pozri obrázok). Úlohu zohráva aj ich rozmiestnenie (ktoré sa tiež lepšie chápe z obrázka).

  
obrázok 
 

Z vrchnej časti (zo zásobníka, ktorý nie je na obrázku) padajú do priestoru s prekážkami guľôčky, ktoré sa postupne posúvajú nadol. Narážajúc na prekážky sa počas pádu odkláňajú doprava alebo doľava. Nakoniec každá z guľôčok skončí v jednej z priehradok (na obrázku očíslovaných číslami od 0 po 7). Otázka znie, aké bude ich výsledné rozloženie (prípadne i prečo)?

 

Pri programovaní simulácie Galtonovej dosky si treba uvedomiť, ako je doska priestorovo organizovaná – každý riadok je o polovicu posunutý oproti predchádzajúcemu riadku. To môžeme interpretovať aj tak, že je posunutý iba každý druhý riadok (oproti zvolenému univerzálnemu počiatku).

Po tejto úvahe vieme zjednodušiť implementáciu kreslenia tak, že posúvame každý druhý riadok.

Príklad pseudokódu jednoduchej implementácie:

   x := stĺpec × šírkaStĺpca        // základ výpočtu x­‑ovej súradnice polohy prvku
ak (riadok mod 2) = 0 tak // overenie, či ide o každý druhý riadok
     x += šírkaStĺpca / 2 // pripočítanie polovice šírky k x­‑ovej súradnici
y := riadok × výškaRiadka // výpočet y­‑ovej súradnice polohy prvku

Tento istý predpoklad musíme potom zobrať do úvahy pri implementovaní metód (príp. procedúr) na posúvanie prvkov (guľôčok) smerom doľava alebo doprava.

Príklad pseudokódu jednoduchej implementácie:

   procedúra padniVľavo        // (hlavička procedúry)
     ak (riadok mod 2) = 1 tak // iba pre nepárne riadky
     −−stĺpec // zníž hodnotu stĺpca o 1
++riadok // hodnotu riadka zvýš v každom prípade
 
   procedúra padniVpravo        // (hlavička procedúry)
     ak (riadok mod 2) = 0 tak // iba pre párne riadky
     ++stĺpec // zvýš hodnotu stĺpca o 1
++riadok // hodnotu riadka zvýš v každom prípade

Zvyšok implementácie je relatívne jednoduchý:

  • kreslenie prekážok, priehradok a guľôčok s prípadnou animáciou,
  • vytvorenie zásobníka guľôčok (priehradok) pre tie guľôčky, ktoré prejdú doskou a spadnú do niektorej priehradky, čo je principiálne rovnaké ako histogramy v predchádzajúcich príkladoch
  • a, samozrejme, využitie poznatkov opísaných vyššie.

  
obrázokobrázokobrázokobrázok 
Rôzne fázy simulácie. 
 

Simulácia sa spúšťa kliknutím myšou (každé ďalšie kliknutie simuláciu reštartuje). Simulácia sa zastaví v okamihu naplnenia ľubovoľného zo zásobníkov.

~

import knižnica.*;

public class GaltonovaDoska extends GRobot
{
	// Rôzne vnútorné premenné dosky:

	private int[] histogram = new int[8];
	private int hodnôtMimo = 0;

	private int medzeraStĺpcov = 15;
	private int odstupZarážok = 15;
	private int posunutieScényZľava = 30;
	private int posunutieScényZdola = 30;

	private final Zoznam<Guľôčka> guľôčky = new Zoznam<>();


	// Trieda, ktorá uchováva informácie o fázach animácie a aktuálnej
	// polohe (v rámci dosky) o jednej z padajúcich guľôčok.
	private class Guľôčka
	{
		public int riadok = 0;
		public int stĺpec = 3;
		public int čakaj = 0;
		public int kĺžemSa = 0;
		public int padám = 5;

		public void kresli()
		{
			if (čakaj <= 0)
			{
				if (kĺžemSa != 0 || padám >= 0)
					kresliObjekt(riadok, stĺpec, červená, 0,
						2 * veľkosť() + (veľkosť() / 2) * padám, kĺžemSa);
				else
					kresliObjekt(riadok, stĺpec, červená, 0, 2 * veľkosť());
			}
		}

		// Metóda, ktorá overí, či je guľôčka nečinná (či sa neanimuje),
		// a ktorá zároveň posúva fázy animácie (v prípade, že nie je
		// nečinná).
		public boolean nečinná()
		{
			if (čakaj > 0)
			{
				--čakaj;
				return false;
			}
			else if (kĺžemSa != 0)
			{
				if (kĺžemSa > 0) --kĺžemSa;
				else if (kĺžemSa < 0) ++kĺžemSa;
				return false;
			}
			else if (padám >= 0)
			{
				--padám;
				return false;
			}
			return true;
		}

		// Metóda, ktorá „pošle“ guľôčku doľava a spustí animáciu.
		public void padniVľavo()
		{
			if (1 == riadok % 2) --stĺpec;
			++riadok; kĺžemSa = -5; padám = 5;
		}

		// Metóda, ktorá „pošle“ guľôčku doprava a spustí animáciu.
		public void padniVpravo()
		{
			if (0 == riadok % 2) ++stĺpec;
			++riadok; kĺžemSa = 5; padám = 5;
		}

		public void reset()
		{
			riadok = 0;
			stĺpec = 3;
			čakaj = 0;
			kĺžemSa = 0;
			padám = 5;
		}
	}


	// Konštruktor.
	private GaltonovaDoska()
	{
		super(305, 530, "Galtonova doska (" + versionString + ")");
		Svet.zbaľ();
		farba(modrá);
		skry();
		for (int i = 0; i < 7; ++i)
			guľôčky.pridaj(new Guľôčka());
		resetujScénu();
		kresliScénu();
	}


	// Resetuje scénu: vymaže histogram, resetuje guľôčky a nastaví
	// každej o niečo väčšie oneskorenie. (Oneskorenie je úmyselne
	// reálne číslo. Čím menšia hodnota prírastku bude za operátorom
	// +=, tým hustejšie budú guľôčky padať, až to môže prejsť do
	// štádia, keď budú padať viaceré guľôčky naraz.)
	private void resetujScénu()
	{
		for (int i = 0; i < histogram.length; ++i)
			histogram[i] = 0;

		double oneskorenie = 0;
		for (Guľôčka guľôčka : guľôčky)
		{
			guľôčka.reset();
			guľôčka.čakaj = (int)oneskorenie;
			oneskorenie += 10;
		}
	}


	// Séria metód na uľahčenie kreslenia guľôčok a prekážok
	// na správnych pozíciách.
	private void kresliObjekt(int riadok, int stĺpec,
		Farba farba, double Δx, double Δy, int sklz)
	{
		Farba f;

		if (null != farba)
		{
			f = farba();
			farba(farba);
		}
		else f = null;

		skočNa(
			Svet.najmenšieX() + posunutieScényZľava + Δx +
				stĺpec * (2 * veľkosť() + medzeraStĺpcov) +
				((0 == riadok % 2) ? ((2 * veľkosť() +
					medzeraStĺpcov) / 2) : 0),
			Svet.najmenšieY() + posunutieScényZdola + Δy -
				riadok * (2 * veľkosť() + odstupZarážok) +
				45 * veľkosť());
		if (0 != sklz)
		{
			if (sklz < 0)
				skočPoOblúku(18 * -sklz, true);
			else
				skočPoOblúku(18 * sklz);
			uhol(90);
		}
		kruh();

		if (null != farba) farba(f);
	}

	private void kresliObjekt(int riadok, int stĺpec,
		Farba farba, double Δx, double Δy)
	{ kresliObjekt(riadok, stĺpec, farba, Δx, Δy, 0); }

	private void kresliObjekt(int riadok, int stĺpec, double Δx, double Δy)
	{ kresliObjekt(riadok, stĺpec, null, Δx, Δy, 0); }

	private void kresliObjekt(int riadok, int stĺpec, Farba farba)
	{ kresliObjekt(riadok, stĺpec, farba, 0, 0, 0); }

	private void kresliObjekt(int riadok, int stĺpec)
	{ kresliObjekt(riadok, stĺpec, null, 0, 0, 0); }


	// Metóda, ktorá nakreslí celú scénu.
	private void kresliScénu()
	{
		// Kreslenie stĺpcov dopadnutých guľôčok:
		for (int i = 0; i < histogram.length; ++i)
		{
			skočNa(
				Svet.najmenšieX() + posunutieScényZľava +
					i * (2 * veľkosť() + medzeraStĺpcov),
				Svet.najmenšieY() + posunutieScényZdola);

			for (int j = 0; j < histogram[i]; ++j)
			{ kruh(); skoč(); skoč(); }
		}

		// Toto by nemalo nikdy nastať:
		if (0 != hodnôtMimo)
		{
			skočNa(
				Svet.najmenšieX() + posunutieScényZľava +
					histogram.length * (2 * veľkosť() + medzeraStĺpcov),
				Svet.najmenšieY() + posunutieScényZdola);

			Farba f = farba();
			farba(červená);
			vpred(hodnôtMimo * veľkosť());
			farba(f);
		}

		// Kreslenie prekážok:
		Farba f = farba();
		farba(zelená);
		int ľs = 3, ps = 3;
		for (int i = 0; i < 7; ++i)
		{
			for (int j = ľs; j <= ps; ++j)
				kresliObjekt(i, j);

			if (1 == i % 2) --ľs; else ++ps;
		}
		farba(f);

		// Kreslenie padajúcich guľôčok:
		for (Guľôčka guľôčka : guľôčky)
			guľôčka.kresli();
	}


	@Override public void klik()
	{
		resetujScénu();
		if (ÚdajeUdalostí.tlačidloMyši(ĽAVÉ))
			Svet.spustiČasovač();
		else
		{
			Svet.zastavČasovač();
			Svet.nekresli();
			Svet.vymaž();
			kresliScénu();
			Svet.kresli();
		}
	}


	@Override public void tik()
	{
		Svet.nekresli();
		Svet.vymaž();

		// Padanie guľôčok:
		for (Guľôčka guľôčka : guľôčky)
		{
			if (guľôčka.nečinná())
			{
				if (guľôčka.riadok > 6)
				{
					if (++histogram[guľôčka.stĺpec] > 10)
						Svet.zastavČasovač();
					guľôčka.reset();
				}
				else if (0 == Svet.náhodnéCeléČíslo(0, 1))
					guľôčka.padniVľavo();
				else
					guľôčka.padniVpravo();
			}
		}

		kresliScénu();
		Svet.kresli();
	}


	public static void main(String[] args)
	{
		Svet.použiKonfiguráciu("GaltonovaDoska.cfg");
		new GaltonovaDoska();
	}
}

Zdroje