Labirintus

Ebben a feladatban egyszerű labirintusokat fogunk ábrázolni, valamint függvényeket készítünk egy ilyen labirintus (determinisztikus) előállítására.

A labirintus ábrázolása

Az ábrázolást úgy oldjuk meg, hogy egy listában felsoroljuk azokat a pozíciókat, ahol nincs fal a labirintusban. Azért választjuk ezt, mert a fal nélküli részek száma várhatóan kevesebb lesz a falat tartalmazó részekénél, tehát kevesebb adattal tudjuk így leírni (ritkás ábrázolás). Továbbá feltételezzük, hogy a labirintusok 16x16-os méretűek.

A feladatok megfogalmazásához két típusnevet vezetünk be.

type Position = (Int,Int)
type Maze     = [Position]

A Position elnevezés lényegében egy egész számokból álló párt jelent, amelyek a labirintus egy adott pozícióját jelenti. A Maze pedig ezek sorozata, vagyis maga a komplett labirintus.

Elsőként adjuk meg az obstructed nevű függvényt, amely megmondja, hogy az adott koordinátában fal van, vagy sem!

obstructed :: Maze -> Position -> Bool

Test>
False :: Bool
Test>
True :: Bool
Test>
True :: Bool

A labirintus generálása

A következő fontos lépés annak megadása, miként tudunk labirintusokat készíteni. Ehhez egy olyan módszert választunk (lásd lentebb), amely viszont feltételezi, hogy (ál)véletlenszámokkal dolgozunk.

Az egyszerűség kedvéért viszont most ezeket a véletlen adatokat egy felhasználó által megadott karakterláncból vesszük. Feltételezzük, hogy a karakterláncban 8 bites ASCII karakterek sorakoznak, és ezekből vesszük majd a labirintus bejáratának pozícióját, valamint a döntésekhez szükséges (ál)véletlen értékeket.

8 bites szám felbontása 4 bites értékekre

Készítsünk egy olyan függvényt, amely egy 8 bites értéket felbont két 4 bites (0 és 15 közti) értékre! Ezzel így tulajdonképpen a labirintus valamelyik pozíciójára tudunk hivatkozni. A felbontás elve egyszerű: a pozíció x koordinátája a 8 bites bemenet első 4 bitje, a y koordinátája pedig a második 4 bitje legyen.


Test>
(0, 0) :: Position
Test>
(2, 15) :: Position
Test>
(15, 15) :: Position

Például:

 47 = 00101111 --> 0010 1111 --> (2,15)

8 bites szám felbontása 2 bites értékekre

Készítsünk egy olyan függvényt, amely 2 bites (0 és 3 közti) számokat hoz létre 8 bitesekből! Ezeket a 2 bites értékeket Direction néven fogjuk a továbbiakban hivatkozni, mivel ezek reprezentáljuk a labirintus generálása során az irányt, amerre továbblépünk.

A felbontás elve ebben az esetben is egyszerű: a 8 bitet egymás után következő 2 bites (Direction) értékek sorozatára osztjuk fel.


Test>
[3, 3, 3, 3] :: [Direction]
Test>
[0, 0, 0, 0] :: [Direction]
Test>
[1, 0, 2, 0] :: [Direction]
Test>
[2, 1, 2, 3] :: [Direction]

Például:

 72 = 01001000 --> 01 00 10 00 --> [1,0,2,0]
155 = 10011011 --> 10 01 10 11 --> [2,1,2,3]

Karakterlánc leképezése pozícióra és irányokra

Készítsünk egy függvényt, amely egy tetszőleges karaktersorozatot átalakít egy kezdőpozícióra és irányok sorozatára! Feltételezzük, hogy legalább egy karaktert tartalmaz a bemenő paraméter.

A leképezés alapja, hogy az a karakterlánc első karaktere egy kezdőpozíciót, vagyis egy Position típusú értéket, ad meg, a fennmaradó része pedig irányok, vagyis Direction típusú értékek, sorozatát határozza meg.


Test>
((5, 8), []) :: (Position, [Direction])
Test>
((4, 8), [1, 2, 1, 1, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 3, 0, 2, 0, 0, 1, 3, 1, 3, 1, 2, 3, 3, 1, 3, 0, 2, 1, 2, 3, 0, 1, 2, 1, 0, 0, 2, 0, 1]) :: (Position, [Direction])
Test>
((4, 5), [1, 2, 3, 0, 1, 2, 2, 3, 1, 2, 0, 1, 1, 3, 0, 0, 1, 2, 3, 3, 1, 3, 0, 3, 1, 3, 2, 2, 1, 3, 1, 0, 1, 2, 0, 1, 1, 3, 0, 3, 1, 2, 2, 1, 1, 3, 1, 0, 1, 2, 2, 0, 1, 2, 0, 1, 1, 3, 1, 0, 1, 2, 0, 1, 1, 3, 1, 0, 1, 2, 3, 0, 1, 2, 0, 1, 1, 2, 3, 2, 1, 3, 0, 3, 1, 2, 0, 1, 1, 2, 1, 3, 1, 2, 3, 3, 1, 3, 1, 0, 1, 2, 3, 3, 1, 2, 2, 3, 1, 2, 0, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 3, 0, 2, 1, 3, 1, 0]) :: (Position, [Direction])

Segítség: A karakterek számokká alakítására alkalmazzuk a Data.Char.ord függvényt.

Négyszomszédok

Adjuk meg egy pozíció négyszomszédságát! Ebbe azok a pozíciók tartoznak, amelyek a kiindulási ponttól két lépésre találhatóak (mivel a lépések során majd szeretnénk kihagyni helyet a falaknak, ezért kettesével lépkedünk) északi, keleti, déli, majd nyugati irányban, ahol koordinátáik 1 és 14 közti értéket vesznek fel. (Ez utóbbi pedig majd azért kell, hogy a labirintusnak meglegyenek a külső falai is.)

Négyszomszédok 


Test>
[(8, 6), (6, 8), (8, 10), (10, 8)] :: [Position]
Test>
[] :: [Position]
Test>
[(14, 5), (12, 7), (14, 9)] :: [Position]

A következő pozíció megválasztása

A generálás során a korábban kiszámolt irányok szerint fogunk majd lépkedni, miközben folyamatosan feljegyezzük, hogy merre jártunk már. Most határozzuk meg, hogy az adott pontból kiindulva, a korábban bejárt pontok kiszűrésével, milyen irányokba tudunk egyáltalán menni!


Test>
[(6, 5), (6, 9), (8, 7)] :: [Position]
Test>
[(6, 5), (6, 9), (8, 7)] :: [Position]
Test>
[] :: [Position]

Két pont között

Készítsünk egy olyan függvényt, amely megadja két pont között (a közéjük húzott szakasz felénél) található pont koordinátáját!


Test>
(3, 5) :: Position
Test>
(4, 4) :: Position
Test>
(10, 6) :: Position

A generálás

Az előbbiek felhasználásával most már elkészíthetünk egy, labirintusok karakterlánc alapján történő generálását megvalósító függvényt. Ez a függvény a Maze típus korábban javasolt definíciója alapján tehát olyan koordináták sorozatát adja meg, ahol nincs fal. (Tehát a labirintusban futó utakat adja meg.)

Az algoritmus a következő:


Test>
[(6, 1), (6, 2), (6, 3), (7, 3), (8, 3), (8, 2), (8, 1), (9, 1), (10, 1)] :: Maze
Test>
[(6, 1), (6, 2), (6, 3), (7, 3), (8, 3), (8, 2), (8, 1), (9, 1), (10, 1), (11, 1), (12, 1), (13, 1), (14, 1), (14, 2), (14, 3), (13, 3), (12, 3), (12, 4), (12, 5), (13, 5), (14, 5), (14, 6), (14, 7), (14, 8), (14, 9), (14, 10), (14, 11), (13, 11), (12, 11), (12, 12), (12, 13), (13, 13), (14, 13)] :: Maze
Test>
[(6, 2), (6, 3), (6, 4), (7, 4), (8, 4), (8, 3), (8, 2), (9, 2), (10, 2), (11, 2), (12, 2), (12, 3), (12, 4), (11, 4), (10, 4), (10, 5), (10, 6), (10, 7), (10, 8), (11, 8), (12, 8), (12, 7), (12, 6), (13, 6), (14, 6), (14, 7), (14, 8), (14, 9), (14, 10), (14, 11), (14, 12), (13, 12), (12, 12), (11, 12), (10, 12), (10, 13), (10, 14), (9, 14), (8, 14), (7, 14), (6, 14), (5, 14), (4, 14), (4, 13), (4, 12), (4, 11), (4, 10), (5, 10), (6, 10), (6, 11), (6, 12), (7, 12), (8, 12), (8, 11), (8, 10), (8, 9), (8, 8), (7, 8), (6, 8), (6, 7), (6, 6), (5, 6), (4, 6), (3, 6), (…, …), …, ……] :: Maze

A labirintus bejárata

Határozzuk meg a labirintus bejáratát! Ez a generált labirintus elsőként szereplő szabad (falmentes) pozíciója.

entry :: Maze -> Position

Test>
(6, 1) :: Position
Test>
(6, 2) :: Position

A labirintus kijárata

Határozzuk meg a labirintus kijáratát! Ez a generált labirintus utolsóként szereplő szabad (falmentes) pozíciója.

exit :: Maze -> Position

Test>
(6, 7) :: Position
Test>
(14, 3) :: Position