Einführung in die funktionale Programmierung Aufgabenblatt Nr. 4

Werbung
PD Dr. David Sabel
Institut für Informatik
Fachbereich Informatik und Mathematik
Johann Wolfgang Goethe-Universität Frankfurt am Main
Einführung in die funktionale Programmierung
Wintersemester 2015/2016
Aufgabenblatt Nr. 4
Abgabe: Montag 9. November 2015 vor der Vorlesung
Senden Sie bitte Ihren Quellcode auch per Email an
[email protected]
Ziel dieses Aufgabenblattes ist es, eine Variante von Mine Sweeper“ in Haskell zu implementieren1 .
”
Dabei sind Teile des Codes schon vorgegeben. Die meisten zu implementierenden Funktionen sind
Funktionen auf Listen in Haskell.
Das Spiel: Das Spielfeld ist eine quadratische Matrix, wobei jeder Eintrag eine Zelle darstellt, die
entweder leer oder mit einer Mine versehen ist.
Jedoch sind alle Zellen am Anfang des Spiels verdeckt, d.h. es ist unbekannt, wo die Minen liegen.
Ziel des Spiels ist es, alle leeren Zellen sichtbar zu machen ohne jemals eine Mine aufzudecken2 :
• Wird eine Zelle mit einer Mine aufgedeckt, so ist das Spiel verloren.
• Wird eine leere Zelle aufgedeckt, so wird der Inhalt der Zelle und gleichzeitig der Inhalt aller
ihrer Nachbarzellen sichtbar. Hierbei wird für sichtbare Nicht-Minen-Zellen eine Zahl angezeigt:
Die Anzahl der Minen in der Nachbarschaft.
Desweiteren besteht die Möglichkeit nicht-sichtbare Zellen zu markieren (mit einem Fähnchen) und
diese Markierung auch wieder zu entfernen. Ein Eintrag mit Index (i, j) in der Matrix bezeichnet die
Zelle in Spalte i und Zeile j, wobei von 0 angefangen wird zu zählen. Ein Beispiel zur Indizierung zeigt
Abbildung 1 (c).
Abbildung 1 (a) zeigt ein mögliches Spielfeld, wobei alle Zellen sichtbar gemacht sind, Abbildung 1 (b)
zeigt eine typische Spielsituation: Die Zelle mit Index (3,0) wurde aufgedeckt, dadurch wurden die
Zellen mit Index (2,0), (3,0), (2,1) und (3,1) sichtbar, außerdem hat der Spieler die Zellen mit Index
(0,0) und (3,2) markiert.
Repräsentation in Haskell: Zur Repräsentation des Spiels in Haskell verwenden wir die folgenden
Datentypen: Eine Matrix ist eine Liste von Listen von Entry-Objekten
1
Der hier verwendete Quellcode ist auch auf der Webseite der Veranstaltung zu finden.
Beachten Sie, dass wir hier zwischen Sichtbar-machen und Aufdecken unterscheiden: Aufdecken bedeutet, dass der
Spieler die Zelle öffnet (und verliert, wenn sich in dieser Zelle eine Mine verbirgt), während Sichtbar-machen nur bedeutet,
dass der Inhalt der Zelle angezeigt wird).
2
(0,0) (1,0) (2,0) (3,0)
(0,1) (1,1) (2,1) (3,1)
(0,2) (1,2) (2,2) (3,2)
(0,3) (1,3) (2,3) (3,3)
(a)
(b)
(c)
Abbildung 1: Das MineSweeper-Spielfeld: (a) komplett sichtbares 4 × 4 Spielfeld, (b) rin teilweise
aufgedecktes Spielfeld mit Markierungen, (c) Indizierung der 4 × 4 Matrix
1
type Matrix = [[Entry]]
data Entry = Marked | Unmarked | Open Int | Mine
deriving(Show)
Hierbei repräsentiert das Entry-Objekt die aktuelle Anzeige:
• Marked: Die Zelle ist verdeckt und mit einer Markierung versehen.
• Unmarked: Die Zelle ist verdeckt und nicht markiert.
• Open Int: Die Zelle ist sichtbar, enthält keine Mine, aber die Anzahl an Minen in der Nachbarschaft.
• Mine: Die Zelle ist sichtbar und enthält eine Mine.
Die Matrix aus Abbildung 1 (b) wird dementsprechend als
exampleMatrix = [[Marked, Unmarked,Open 2, Open 1]
,[Unmarked,Unmarked,Mine,
Open 1]
,[Unmarked,Unmarked,Unmarked,Marked]
,[Unmarked,Unmarked,Unmarked,Unmarked]]
als Objekt vom Typ Matrix repräsentiert.
Ein Spielzustand besteht aus der aktuell angezeigten Matrix, einer Liste derjenigen Koordinaten, die
eine Mine enthalten und dem Gewinnzustand. Dies wird durch den Datentyp GameState repräsentiert,
der die Record-Syntax verwendet. Für den Gewinnzustand wird dabei der Aufzählungstyp State
verwendet, wobei Won bedeutet, dass der Spieler gewonnen hat, Lost, dass der Spieler verloren hat
und Undecided, dass das Spiel noch im Gange ist. Für Koordinaten verwenden wir das Typsynonym
Coordinates:
data GameState = GameState { matrix :: Matrix,
mines :: [Coordinates],
state :: State }
type Coordinates = (Int,Int)
data State = Won | Lost | Undecided
Die Spielsituation in Abbildung 1 (b), wobei die Minen entsprechend Abbildung 1 (a) platziert sind,
wird daher als
exampleState
= GameState {matrix = exampleMatrix,
mines = [(0,0),(1,0),(2,1),(1,3),(3,3)],
state = Undecided }
repräsentiert.
Schließlich gibt es drei mögliche Aktionen durch den Spieler: Er kann eine Zelle aufdecken, oder er
kann eine Markierung setzen oder entfernen. Es reicht jedoch aus das Setzen/Entfernen der Markierung
durch eine Aktion zu repräsentieren, die wir mit Toggle bezeichnen. Wir führen für Spieleraktionen
daher den Datentyp Action ein:
data Action = Toggle Coordinates | OpenEntry Coordinates
2
Aufgabe 1 (50 Punkte)
Implementieren Sie im Modul MineSweeper
a) eine Funktion (!!!) :: Matrix -> Coordinates -> Entry, die eine Matrix und Koordinaten
erhält und die Belegung der Zelle mit den Koordinaten berechnet.
(5 Punkte)
Beispiele:
*Main> exampleMatrix !!! (2,0)
Open 2
*Main> exampleMatrix !!! (1,3)
Unmarked
b) eine Funktion updateMatrix :: Matrix -> Coordinates -> Entry -> Matrix, die eine Matrix m, Koordinaten (x, y) und einen Eintrag e erhält und den Inhalt der Zelle mit den Koordinaten (x, y) in der Matrix m auf den Wert e abändert.
(5 Punkte)
Beispiele:
*Main> updateMatrix exampleMatrix (2,0) Unmarked
[ [Marked,Unmarked,Unmarked,Open 1]
,[Unmarked,Unmarked,Mine,Open 1]
,[Unmarked,Unmarked,Unmarked,Marked]
,[Unmarked,Unmarked,Unmarked,Unmarked]]
*Main> updateMatrix exampleMatrix (0,0) (Open 10)
[ [Open 10,Unmarked,Open 2,Open 1]
,[Unmarked,Unmarked,Mine,Open 1]
,[Unmarked,Unmarked,Unmarked,Marked]
,[Unmarked,Unmarked,Unmarked,Unmarked]]
c) eine Funktion neighbours :: GameState -> Coordinates -> [Coordinates], die für einen
Spielzustand und Koordinaten, die Koordinaten aller benachbarten Zellen berechnet. Hierbei
bietet es sich an, eine List Comprehension zu verwenden.
(5 Punkte)
Beispiele:
*Main> neighbours exampleState (1,1)
[(0,0),(0,1),(0,2),(1,0),(1,2),(2,0),(2,1),(2,2)]
*Main> neighbours exampleState (0,3)
[(0,2),(1,2),(1,3)]
d) eine Funktion minesAround :: Coordinates -> GameState -> Int, die Koordinaten und
einen Spielzustand erhält und berechnet, wieviele Minen sich in der Nachbarschaft der Zelle
mit den gegebenen Koordinaten befinden.
(5 Punkte)
Beispiele:
*Main> minesAround (3,0) exampleState
1
*Main> minesAround (0,2) exampleState
1
*Main> minesAround (3,2) exampleState
2
e) eine Funktion updateSingleCell :: GameState -> Coordinates -> GameState die einen
Spielzustand und Koordinaten erhält und die Zelle mit den gegebenen Koordinaten sichtbar
macht, d.h. wenn die Zelle mit einer Mine belegt ist, wird der Eintrag auf Mine geändert, und
ansonsten wird der Eintrag auf Open i geändert, wobei i die Anzahl der Minen in der Nachbarschaft ist.
(5 Punkte)
Beispiele:
*Main> updateSingleCell exampleState (0,0)
GameState {matrix = [[Mine,Unmarked,Open 2,Open 1]
,[Unmarked,Unmarked,Mine,Open 1]
,[Unmarked,Unmarked,Unmarked,Marked]
,[Unmarked,Unmarked,Unmarked,Unmarked]]
,mines = [(0,0),(1,0),(2,1),(1,3),(3,3)]
,state = Undecided}
*Main> updateSingleCell exampleState (1,3)
GameState {matrix = [[Marked,Unmarked,Open 2,Open 1]
,[Unmarked,Unmarked,Mine,Open 1]
,[Unmarked,Unmarked,Unmarked,Marked]
,[Unmarked,Mine,Unmarked,Unmarked]]
,mines = [(0,0),(1,0),(2,1),(1,3),(3,3)]
,state = Undecided}
3
f) eine Funktion updateCells :: GameState -> Coordinates -> GameState, die einen Spielzustand und Koordinaten erhält und die Zelle mit den gegebenen Koordinaten sowie alle ihre
Nachbarn sichtbar macht. Versuchen Sie die Funktion im wesentlichen durch updateSingleCell
und foldl zu programmieren.
(5 Punkte)
Beispiele:
*Main> updateCells exampleState (0,0)
GameState {matrix = [[Mine,Mine,Open 2,Open 1]
,[Open 2,Open 3,Mine,Open 1]
,[Unmarked,Unmarked,Unmarked,Marked]
,[Unmarked,Unmarked,Unmarked,Unmarked]]
,mines = [(0,0),(1,0),(2,1),(1,3),(3,3)]
,state = Undecided}
*Main> updateCells exampleState (1,3)
GameState {matrix = [[Marked,Unmarked,Open 2,Open 1]
,[Unmarked,Unmarked,Mine,Open 1]
,[Open 1,Open 2,Open 3,Marked]
,[Open 1,Mine,Open 2,Unmarked]]
,mines = [(0,0),(1,0),(2,1),(1,3),(3,3)]
,state = Undecided}
g) eine Funktion playStep :: Action -> GameState -> GameState, die eine Aktion und einen
Spielzustand erhält und den nachfolgenden Spielzustand nach den oben beschrieben Regeln
berechnet. Wenn der Gewinnzustand schon auf Verloren (Lost) oder Gewonnen (Won) steht, so
geben Sie den Spielzustand unverändert zurück.
(15 Punkte)
Beispiele:
*Main> playStep (Toggle (0,0)) exampleState
GameState {matrix = [[Unmarked,Unmarked,Open 2,Open 1]
,[Unmarked,Unmarked,Mine,Open 1]
,[Unmarked,Unmarked,Unmarked,Marked]
,[Unmarked,Unmarked,Unmarked,Unmarked]]
,mines = [(0,0),(1,0),(2,1),(1,3),(3,3)]
,state = Undecided}
*Main> playStep (Toggle (0,3)) exampleState
GameState {matrix = [[Marked,Unmarked,Open 2,Open 1]
,[Unmarked,Unmarked,Mine,Open 1]
,[Unmarked,Unmarked,Unmarked,Marked]
,[Marked,Unmarked,Unmarked,Unmarked]]
,mines = [(0,0),(1,0),(2,1),(1,3),(3,3)]
,state = Undecided}
*Main> playStep (OpenEntry (3,3)) exampleState
GameState {matrix = [[Marked,Unmarked,Open 2,Open 1]
,[Unmarked,Unmarked,Mine,Open 1]
,[Unmarked,Unmarked,Unmarked,Marked]
,[Unmarked,Unmarked,Unmarked,Mine]]
,mines = [(0,0),(1,0),(2,1),(1,3),(3,3)]
,state = Lost}
*Main> playStep (OpenEntry (0,3)) exampleState
GameState {matrix = [[Marked,Unmarked,Open 2,Open 1]
,[Unmarked,Unmarked,Mine,Open 1]
,[Open 1,Open 2,Unmarked,Marked]
,[Open 1,Mine,Unmarked,Unmarked]]
, mines = [(0,0),(1,0),(2,1),(1,3),(3,3)]
, state = Undecided}
h) Auf der Webseite zur Veranstaltung finden Sie eine Haskell-Datei Main.hs. Diese verwendet die
gloss-Bibliothek zum Anzeigen und zur Interaktion des Spieles. Die Datei importiert das Modul MineSweeper. Installieren Sie die gloss-Bibliothek3 und kompilieren Sie das Main-Modul4 .
Führen Sie das Programm aus. Die möglichen Interaktionen sind:
– linke Maustaste: Zelle aufdecken
– rechte Maustaste: Markierung setzen/entfernen
– Taste q: Spiel verlassen.
3
4
üblicherweise genügt es in einer Shell cabal install gloss einzugeben.
Hinweise hierzu sind im Kopf der Datei Main.hs
4
(5 Punkte)
Herunterladen