• Nem Talált Eredményt

forráskód. Labirintus kiírása a képernyőre

Hernyák Zoltán)

9.21. forráskód. Labirintus kiírása a képernyőre

string[] ss = r.ReadLine().Split(’ ’);

for(int j=0;j<M;j++)

m[i,j] = int.Parse(ss[j]);

i++;

}

r.Close();

9.14. feladat (Labirintus beolvasása fájlból – szint: 3). Egy text fájlban * és . karakterekből (csillag és pont) álló sorok vannak. A sorok egyforma hosszúak, egyéb karaktert nem tartalmaznak. A * és . karakterek egy labirintust írnak le, ahol a * reprezentálja a falat, a . karakter a folyosót. Ezek együtt egy méretű, karakterekből álló mátrixot írnak le, ahol N a sorok száma a text fájlban, M pedig a soronkénti karakterszám. A fájl első sorában egyetlen szám, a mátrix sorainak száma szerepel, a maradék sorokban a csillag és pont karakterekből álló rajzolat. Olvassuk be a mátrixot, majd jelenítsük meg a képernyőn oly módon, hogy a * karakterek pirossal, a . karakterek zöld színnel jelenjenek meg a képernyőn!

Magyarázat: A feladat olvasása során egy karakterekből álló mátrixot képzelünk el, melyben az egyes csillag és pont karaktereket el tudjuk tárolni. Természetesen a feladat megoldható ezen az alapon is. Képzeljük el azonban ehelyett azt, hogy egy m hosszú s string valójában egy m hosszú karakter típusú vektor! Ekkor ha van n darab ilyen hosszú stringünk, akkor van n m karakterünk is.

Ezért sokat egyszerűsödhet a fájlbeolvasás, ha a beolvasott stringeket nem bontjuk fel karakterekre, hanem meghagyjuk azt eredeti string alakjukban. Ez igazából a későbbi feldolgozási műveleteket nem bonyolítja (a beolvasás a 9.41 kódban látható).

9.20. forráskód. Labirintus beolvasása fájlból

StreamReader r = new StreamReader(fname, Encoding.Default);

// elso sor N erteke

int N = int.Parse( Console.ReadLine() );

string[] m = new string[N];

for(int i=0;i<N;i++) {

m[i] = r.ReadLine();

}

r.Close();

A kijelzés is könnyen megoldható, minden egyes karakter kiírása előtt át kell váltani a megfelelő írási színre (a 9.42 forráskód). Vegyük észre, hogy ha m egy string[] stringek egy vektora, akkor a m[i] az i. stringet jelöli, a m[i][j] pedig az i. string j. karakterét. Ez most nem jelölhető m[i,j] módon!

9.21. forráskód. Labirintus kiírása a képernyőre

for(int i=0;i<N;i++) {

for(int j=0;j<m[i].Length;j++) {

if (m[i][j]==’.’) Console.ForegroundColor = ConsoleColor.Green;

else Console.ForegroundColor = ConsoleColor.Red;

Console.Write(m[i][j]);

}

Console.WriteLine();

}

9.15. feladat (Sziget generálása – szint: 4). Az óceán közepén egy szabálytalan körvonalú sziget terül el, mely befoglalható egy méretű téglalapba. A sziget közepén egy vulkán terül el. A téglalap minden négyzetkilométeréhez hozzárendelünk egy jellemző tengerszint feletti magasságértéket, melyet méréssel és átlagolással határoztunk meg. Generáljunk egy méretű mátrixot, amely lehetne akár ezen sziget magassági értékeit tároló mátrixa is!

A magasságértékek közötti (méterben értett) értékek legyenek! Jelenítsük meg a mátrixot a képernyőn táblázatos alakban, használjunk színeket is a különböző magasságértékek esetén sávosan (pl: legyen sárga, legyen zöld, stb.)!

A 9.3. ábrán látható egy minta, ahol a sötétebb színek a magasabb részeket jelölik, a világosabbak pedig az alacsonyabbakat. Ügyeljünk a következőkre:

• a sziget a közepe fele haladva magasodik, tehát minél beljebb vagyunk, annál valószínűbben szerepeljenek nagyobb számértékek a mátrixban,

• a sziget közepe tájékán elterülő vulkán krátere jóval alacsonyabb, mint a kráter szélei, ez egy cellát jelent a mátrix esetén (magassága [200,300] közé esik).

9.3. ábra. A sziget egy lehetséges kinézete

Magyarázat: A sziget generálását érdemes a közepén kezdeni, a vulkán kráterével. Generáljunk oda egy kisebb értéket, a kráter közepének alacsony szintjét jelölve! A vulkán kráterének pozícióját oly módon határozhatjuk meg, hogy meghatározzuk a szélesség/2, magasság/2 pozíciót (a mátrix kellős közepe), majd ehhez véletlen értéket adunk x és y irányban is. Ily módon a kiinduló pozíciónk ugyan középre esik, de mégsem pontosan középre (tároljuk el ezt a pozíciót x0 és y0 változókba).

A következő lépés, hogy az x0 és y0 pozíciót „hízlaljuk” n vastagsággal. Ezt több módon is meg lehet tenni. Az egyik legegyszerűbb módszer, hogy szkenneljük a mátrixot soronként (és oszloponként), keresünk benne olyan cellát, amely egyelőre kitöltetlen, de a vele szomszédos cella szimpatikus. Esetünkben a szimpatikus cella az x0, y0 (8 ilyen cellát találunk, lásd a 9.4. ábra). Hogy ne kerüljön minden ilyen cella kiválasztásra, minden cella kiválasztásának esélyét csökkentsük le 100%-ról mondjuk 70%-ra! Ekkor bizonyos szomszédos cellák kiválasztásra kerülnek, mások nem (9.5. ábra).

9.4. ábra. A vulkán krátere és a 8 szomszéd

9.5. ábra. A vulkán krátere és a véletlenszerű kiválasztás

A következő fázisban vonjuk be a szimpatikus cellák körébe az előző körben kiválasztott és átszínezett cellákat is! Igazából fogalmazhatunk úgy is, hogy minden cella szimpatikus, amely ki van töltve. Az előző módszert újra

alkalmazva megint válasszuk ki a szimaptikus cellák szomszédjait 70% eséllyel! Ekkor egy vastagabb kitöltést kapunk (9.6. ábra). Jegyezzük meg, hogy a második menet kiválasztási esélyét akár csökkenthetjük is, hogy a

„vastagodás” ne legyen olyan látványos! Valamint jegyezzük meg, hogy a második menetben kiválasztott cellák maximum kettő távolságra lehetnek a kezdeti pozíciótól!

9.6. ábra. A vulkán krátere kétmenetes vastagítás után

A további körökben is ugyanezt fogjuk tenni, minden menetben legfeljebb egytávolságnyival „vastagítva” a kiinduló pontunkat. A nem 100% eséllyel történő kiválasztás során egy nem teljesen szabályos körvonalú befestett területet fogunk kialakítani a mátrixban. Az egyes menetekben más-más „színt” használva a kiválasztott cellák befestéséhez, kialakíthatjuk a réteges vastagítást is. A „szín” esetén itt most használhatunk más-más számintervallumot, amelyből a véletlen számokat generáljuk. A kitöltéshez használt forráskódok az 9.43 ... 9.45. forráskódokban, a futási eredmény egy lehetséges képernyőképe a 9.7. ábrán látható.

9.7. ábra. A véletlen vastagítás eredménye

9.22. forráskód. A vastagítás kódja (1. rész)

const int N = 20;

static int[,] m = new int[N, N];

static Random rnd = new Random();

//

static void vulkanKratere(int a, int f) {

int x = (N / 2) + rnd.Next(-1,+2);

int y = (N / 2) + rnd.Next(-1, +2);

m[x, y] = rnd.Next(a, f + 1);

}

9.23. forráskód. A vastagítás kódja (2. rész)

static bool szomszedSzimpatikus(int x, int y)

{

static void vastagit(int a, int f, int esely) {

9.24. forráskód. A vastagítás kódja (3. rész)

Ha alaposan megfigyeljük a futási eredményt, láthatjuk, hogy már majdnem készen vagyunk. Van azonban egy ijesztően nagy gond. A kifestett területek „belsejében” tengerszintmagasságokat észlelhetünk. Ez nem csak annak egyenes következménye, hogy a vastagítás során mind a 8 irányban ellenőrizzük a szimpatikus cella szomszédságát (9.8. ábra), ez akkor is kialakulhat, ha csak 4 irányban ellenőrizzük ezeket (9.9. ábra). A 8 irány miatt könnyű szomszédot találni, miközben a 2 cella közöttinek is van szomszédja, de az esély elvétése miatt egy cella kimarad.

9.8. ábra. A 8 irányú ellenőrzés miatt kimarad 1 cella

9.9. ábra. A 4 irányú ellenőrzéskor is kimaradhat 1 cella

Sajnos ezen egyszerűen azonban nem tudunk segíteni, mivel ez a sziget szélén normális jelenség, sőt, akkor is, ha a cella nem a szélén van, de esetleg egy szigetről a tengerbe ömlő folyó deltája, s ily módon mélyen bemetsz a sziget belsejébe (9.10. ábra).

9.10. ábra. A szélről induló benyúlás nem hibás

Ebből egy fontos dolog következik. A generálás (vastagítás) közben ezek a szünetek nem szűrhetőek ki, nem korrigálhatóak, mivel akár a végső állapotban is benne maradhatnak. Helyette egy utólagos szűrési és korrigálási fázisra van szükség, hogy a belső kis tócsákat megszüntessük. Persze, ha megengedett a sziget belsejében a tengerszint magasságában lévő területek jelenléte (beszüremlő tengervízi tavak, alacsonyan fekvő lapos földrészek, stb.), akkor ezen korrigálási lépésre nincs is szükség.

Tegyük fel, hogy nem megengedett! Hogyan különítsük el a szigetet körbefonó víz 0 magasságú celláit a hibásnak számító belső 0 értékű celláktól? A kulcs: a belső részek körbe vannak zárva magasabb részekkel. Itt megint lehet vitázni, hogy a bezárást mind a 8 irányból igényeljük-e, vagy 4 irányú bezárást már elégnek tekintünk. De ez csak a detektáló algoritmus apró módosításán múlik. A feladat egyelőre úgy fogalmazható meg: különítsük el a belső, bezárt 0 magasságú cellákat a külső (normális) 0 magasságú celláktól.

A megoldást a festő algoritmus jelentheti. Feltételezhetjük, hogy a 0,0 pozíción tengervíz van (ha ezt nem feltételezhetjük, akkor keresnünk kell egy szélső cellát, ahol 0 magasság van – az biztosan tengervíznek tekinthető). Ha ilyet nem találunk, akkor készen is vagyunk: minden 0 magasságú cella biztosan hibás! Ezt tehát egyelőre zárjuk ki, legyen egy kiinduló , cellánk, ahol biztosan tengervíz van. Ezen cellából kiindulva fessük be a tenger vizét (4 vagy 8 irányban lépkedve, a korábban említettektől függően)! Ezek után amely 0 magasságú cellához nem jutott el a festés, az „belső cella”, vagyis hibásnak tekinthető a benne szereplő 0 érték.

A 9.11. ábrán látható a 4 irányú festés eredménye, a tengervizet sötétkék színű pöttyök szimbolizálják, míg a belső, „hibás” cellák maradtak pont karakterek. A festő algoritmus a 9.46. forráskódban olvasható, indítása a külső jolBefest(); függvény hívásával történik meg. Ez minden szélső cellában talált 0 (tengervíz) cellából kiindulva elvégzi a festést.

9.11. ábra. Hibákat tartalmazó sziget