Sakk játék - gyűjtsd össze a huszárral a többi bábut

Használható segédanyagok: Haskell könyvtárainak dokumentációja, (lokális!) Hoogle, a tárgy honlapja és a BE-AD rendszerbe feltöltött beadandók. Ha bármilyen kérdés, észrevétel felmerül, azt a felügyelőknek kell jelezni, nem a diáktársaknak!

A feladatok egymásra épülnek ezért érdemes ezeket a megadásuk sorrendjében megoldani, de legalább megérteni az aktuális feladatot megelőző feladatokat! A függvények definíciójában lehet, sőt javasolt is alkalmazni a korábban definiált függvényeket (függetlenül attól, hogy sikerült-e azokat megadni).

Tekintve, hogy a tesztesetek, bár odafigyelés mellett íródnak, nem fedik le minden esetben a függvény teljes működését, határozottan javasolt még külön próbálgatni a megoldásokat beadás előtt, vagy megkérdezni a felügyelőket!

Részpontszámokat csak az elégséges szint elérése után lehet kapni!

A feladat összefoglaló leírása

A gyerekek sakkoktatásában egy fontos elem a különböző bábuk lehetséges mozgásának begyakoroltatása. Ennek egyik eszköze egy olyan játék, ahol egy meghatározott sakkbábunak kell sorban leütnie (összegyűjtenie) a táblán lévő többi bábut. A feladványoknak mindig pontosan egy megoldása létezik, ám előfordulhat, hogy egy lépésben több továbbhaladási irány létezik, amelyeket közül egy vezet majd a jó megoldás felé, a többi pedig “zsákutcába”.

A feladatunkban adott egy hagyományos, 8*8-as sakktábla, ahol a sorokat és az oszlopokat az 1-8 egész számokkal azonosítjuk (először sorszám, majd oszlopszám). Egy adott pozícióról induló huszárral fogunk mozogni, célunk a megadott pozíciókon lévő bábuk mindegyikének ütése. A sakkban a huszár “lóugrással” haladhat: vízszintesen jobbra vagy balra két mezőt, majd függőlegesen fel vagy le egyet; vagy fordítva - függőlegesen fel vagy le kettőt és vízszintesen jobbra vagy balra egyet. A huszár “ugrik”, így az esetében nem beszélünk “útjában álló” báburól.

A feladat során használt típusszinonímák:

A bábuk pozícióját a mező sor-oszlop azonosítójából képzett párral adjuk meg. Minden feladatnál feltételezhetjük, hogy 1-8 közötti egész számok szerepelnek a párban (nem kell ellenőrizni).

A játék állapotát egy rendezett a Board típussal adjuk meg, amelynek első komponense tartalmazza a huszár aktuális pozícióját, a második pedig a többi (leütésre váró) bábu pozícióit egy listában.

Fontos: Amikor átmásoljátok ezeket a típusdefiníciókat a saját modulotokba, akkor a Label-höz tartozó deriving clause-ból töröljétek ki a Data-t. Ez csupán a teszteléshez kell, nektek nincs szügségetek rá.

Példa feladványok

Érvényes lépés (1 pont)

Adjuk meg egy függvényt, amely eldönti, hogy az első paraméterben megadott pozíción lévő huszár léphet-e a második paraméterben megadott pozíciójú mezőre.

Segítség: a sor illetve oszlop azonosítók különbségének abszolútértékei közül az egyik 1, a másik 2.


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

Az üthető bábuk kiválogatása (1 pont)

Egy listából válogassuk ki azokat a bábukat, amelyeket üthet az első paraméterben megadott pozíción álló huszár.


Test>
[(3, 2), (2, 5)] :: [Position]
Test>
[(6, 3), (7, 6)] :: [Position]
Test>
[(2, 3), (3, 6)] :: [Position]

Egy bábu leütése (1 pont)

Adjuk meg azt a függvényt, amely egy táblán a huszárral leüt egy megadott pozíciün lévú bábut! Az eredmény egy tábla legyen, ahol a huszár a leütött bábu pozícióján áll, valamint a leütendő bábuk között már nem szerepel a leütött bábu.


Test>
Board (2, 3) [] :: Board
Test>
Board (2, 3) [(4, 4)] :: Board
Test>
Board (3, 2) [(2, 3)] :: Board

Összes lehetséges lépés (2 pont)

Adott egy tábla állapot (Board), amelyben a huszár és a többi bábu pozíciója szerepel. Keressük ki azokat a bábukat, amelyeket üt a huszár, és mindegyikhez egyesével állítsuk elő azt a táblaállapotot, amely a bábu leütésével keletkezik: a huszár a leütött bábu helyére lép, a leütött bábu pedig kikerül a bábukat tartalmazó listából.

Segítség: Használjuk a takePiece és candidates függvényeket.

Az első két lépés bemutatva:


Test>
[Board (3, 2) [(4, 4), (2, 5), (3, 7)], Board (2, 5) [(3, 2), (4, 4), (3, 7)]] :: [Board]
Test>
[Board (6, 3) [(5, 5), (7, 6), (8, 2)], Board (7, 6) [(5, 5), (6, 3), (8, 2)]] :: [Board]
Test>
[Board (2, 3) [(3, 5), (3, 6), (5, 4), (1, 5)], Board (3, 6) [(2, 3), (3, 5), (5, 4), (1, 5)]] :: [Board]

A huszár által bejárt út

A feladványokról feltételezhetjük, hogy mindig pontosan egy jó megoldásuk van, ám a megoldáskeresés során előfordulhat, hogy több irányba is tovább tudunk haladni (a huszár az aktuális pozícióból több bábut is tud ütni). Ezért számon kell tartanunk az összes lehetséges utat.

Az utakat táblaállapot listával fogjuk reprezentálni. A listában az állapotokat fordított sorrendben fogjuk tárolni, tehát a kezdőállapot fog a lista végén szerepelni, a végállapot pedig a lista elején.

Kezdőállapot (1 pont)

Definiáljunk egy függvényt, amely egy tábla állapot alapján előállítja azt az egy hosszú útlistát, amelyben a paraméterül kapott állapotot tartalmazó egy hosszú út van.


Test>
[[Board (1, 3) [(3, 2), (4, 4), (2, 5), (3, 7)]]] :: [Path]
Test>
[[Board (8, 4) [(5, 5), (6, 3), (7, 6), (8, 2)]]] :: [Path]
Test>
[[Board (4, 4) []]] :: [Path]

Megoldáskeresés (3 pont)

A keresés során célszerű csupán azokat az utakat számon tartani, amelyek még nem végződtek zsákutcában (a huszár aktuális pozíciójából még van legalább egy üthető bábu). A keresés addig tart, amíg valamelyik utunk elején egy olyan táblaállapot van, amelyben már a futón kívül nem maradt bábu a táblán.

A megoldó függvényünk utak listájából fog egyetlen utat meghatározni. Az első paramétere a jelenleg lehetséges összes út, visszatérési értéke pedig az a végső út, amely során az összes bábut összegyűjtöttük.

Mélységi keresést alkalmazunk, amely a következő lépésekből áll:

Megjegyzés: Nem teljesen az eredeti útlista elé kell befűzni a kapott kiterjesztéseket, hanem a kiterjesztendő út helyére kell őket berakni.


Test>
[Board (3, 7) [], Board (2, 5) [(3, 7)], Board (4, 4) [(2, 5), (3, 7)], Board (3, 2) [(4, 4), (2, 5), (3, 7)], Board (1, 3) [(3, 2), (4, 4), (2, 5), (3, 7)]] :: Path
Test>
[Board (8, 2) [], Board (6, 3) [(8, 2)], Board (5, 5) [(6, 3), (8, 2)], Board (7, 6) [(5, 5), (6, 3), (8, 2)], Board (8, 4) [(5, 5), (6, 3), (7, 6), (8, 2)]] :: Path
Test>
[Board (5, 4) [], Board (3, 5) [(5, 4)], Board (2, 3) [(3, 5), (5, 4)], Board (1, 5) [(2, 3), (3, 5), (5, 4)], Board (3, 6) [(2, 3), (3, 5), (5, 4), (1, 5)], Board (4, 4) [(2, 3), (3, 5), (3, 6), (5, 4), (1, 5)]] :: Path

A megoldás (2 pont)

A végső megoldás során egy sakk tábla állapotból (Board) indulunk ki, előállítjuk belőle a solve függvény által várt kezdőállapotot (initSolution) és lefuttatjuk rajta a keresést. Az eredményként kapott útban szereplő tábla állapotokból csak azokra a mezőkre vagyunk kíváncsiak, amiket a huszár érintett, és abban a sorrendben, ahogy az a bejárás során történt (a solve eredményében fordított sorrendben szerepelnek).


Test>
[(1, 3), (3, 2), (4, 4), (2, 5), (3, 7)] :: [Position]
Test>
[(8, 4), (7, 6), (5, 5), (6, 3), (8, 2)] :: [Position]
Test>
[(4, 4), (3, 6), (1, 5), (2, 3), (3, 5), (5, 4)] :: [Position]

Egy sakktábla mező megjelenítése (1 pont)

Jelenítsük meg a sakktábla egy adott pozícióján levő mező állapotát a következőképpen:


Test>
'O' :: Char
Test>
'X' :: Char
Test>
'_' :: Char

Egy sakktábla sor megjelenítése (1 pont)

Jelenítsük meg a sakktábla egy adott sorát Stringként.


Test>
"____XX__" :: String
Test>
"___O____" :: String
Test>
"___X____" :: String

A sakktábla megjelenítése (2 pont)

Jelenítsük meg a sakktáblát Stringként úgy, hogy a sorok közé sortörés jelet (\n) szúrunk be.

Segítség: a sortörés jel beszúrásához érdemes az Data.List modulbeli intercalate függvényt használni.

Megjegyzés: a ghci-ben a sortörés “valódi” sortörés lesz (tehát nem a \n látszik a stringen belül), ha az eredményként kapott stringre meghívjuk a putStrLn függvényt.


Test>
"__O_____\n____X___\n_X____X_\n___X____\n________\n________\n________\n________" :: String
Test>
"____X___\n__X_____\n____XX__\n___O____\n___X____\n________\n________\n________" :: String

Pontozás (elmélet + gyakorlat)