Prof. Dr. Manfred Schmidt-Schauß Künstliche Intelligenz/Softwaretechnologie Fachbereich Informatik und Mathematik/ Institut für Informatik Goethe-Universität Frankfurt am Main Grundlagen der Programmierung 2 Sommersemester 2017 Aufgabenblatt Nr. 3 Abgabe: Mittwoch 10. Mai 2017 vor! der Vorlesung Aufgabe 1 (25 Punkte) Verwenden Sie im Wesentlichen elem, head, tail, map, reverse, filter, concat, sum und (++), um die folgenden Funktionen zur Listenverarbeitung in Haskell zu programmieren: a) Eine Funktion, die einen String1 erhält und zunächst jedes Zeichen, das einer Ziffer entspricht, durch die Zahl 1 ersetzt und alle anderen Zeichen durch die Zahl 2, und danach die Summe dieser Zahlen berechnet. Zum Beispiel soll für "123abc" der Wert 9 berechnet werden. (7 Punkte) b) Eine Funktion, die eine Liste von Strings erhält und jedem String, der ein Fragezeichen enthält, vorne den String F: anfügt. Zum Beispiel soll für ["1+1?","2.","ab?","c."] das Ergebnis ["F:1+1?","2.","F:ab?","c."] berechnet werden. (7 Punkte) c) Eine Funktion, die eine Liste von Listen von Listen von Zahlen erhält und zunächst die innersten Listen verschmilzt und die verschmolzenen Listen durch die Summen ihrer Elemente ersetzt. Anschließend sollen aus der eben erzeugten Liste alle Zahlen ≤ 20 entfernt werden und die Reihenfolge der Elemente soll umgedreht werden. Zum Beispiel soll für [[[1,2],[3,4]],[[5,6],[7,8]],[[10,11],[12,13]]] das Ergebnis [46,26] berechnet werden. (11 Punkte) Hinweis: Die Bibliothek Data.Char beinhaltet die Funktion isNumber, die Sie in Ihrer Lösung verwenden dürfen. Sie können die Bibliothek durch import Data.Char am Anfang des Quelltextes in Ihr Programm einbinden. Die Dokumentation von Bibliotheken finden Sie im Internet unter http://www.haskell.org/ghc/docs/latest/html/libraries/. Aufgabe 2 (30 Punkte) Implementieren Sie in Haskell die folgenden Funktionen auf Listen im Wesentlichen unter Verwendung von Pattern-Matching und Rekursion. a) Eine Funktion, die eine Liste von Listen entgegennimmt und eine Liste mit jeweils dem ersten Element der Listen berechnet. Zum Beispiel soll für die Eingabe [[1,2,3],[4,5,6],[7,8,9]] das Ergebnis [1,4,7] berechnet werden. (7 Punkte) 1 Strings sind in Haskell nichts anderes als Listen von Zeichen, d.h. "Haskell" ist nur eine andere Darstellung für die Liste [’H’,’a’,’s’,’k’,’e’,’l’,’l’]. 1 b) Eine Funktion, die einen String erwartet und die Summe aller im String vorkommenden Ziffern berechnet. Zum Beispiel soll für die Eingabe "123a45" das Ergebnis 15 berechnet werden. (10 Punkte) c) Eine Funktion, die eine Liste von Paaren erwartet, wobei das zweite Element eines jeden Paares ein Tripel (i, j, k) ist, wobei die Werte der Tripel Zahlen sind. Zu berechnen ist eine Liste, welche die Summe der jeweiligen Tripel aller geraden Listenelemente enthält. Zum Beispiel soll für die Liste [(’A’,(9,6,3)), (’B’,(9,2,2)), (’C’,(19,1,2))] das Ergebnis [13] berechnet werden. (13 Punkte) Aufgabe 3 (45 Punkte) Diese Aufgabe beschäftigt sich mit Darts. Das Dart-Board ist in 20 Zahlensegmente unterteilt. Die Felder zählen jeweils soviel, wie außen am entsprechenden Segment notiert ist. Ausnahmen sind die schmalen Kreise. Im äußeren Kreis zählen die Felder das Doppelte (Double-Felder), im mittleren Kreis das Dreifache (Triple-Felder) der entsprechenden Zahl. Der Mittelpunkt des Boards, das Bullseye, zählt 50 Punkte (Double 25), der Ring darum herum (Bull) 25 Punkte. Gespielt wird die Variante 501 mit Double-Out: Jeder Spieler beginnt bei 501 Punkten. Eine Aufnahme besteht aus (maximal) drei nacheinander geworfenen Darts. Die erzielten Punkte werden jedesmal von der verbleibenden Punktzahl abgezogen, wobei nach jeder Aufnahme der jeweilige Gegenspieler dran ist. Es dürfen nur diejenigen Darts gezählt werden, die im Board steckenbleiben. Gewonnen hat derjenige Spieler, der zuerst genau 0 Punkte erreicht hat, wobei der letzte Dart ein Doppel-Feld oder das Bullseye treffen muss. Erzielt der Spieler zu viele Punkte oder so viele, dass er nicht mehr mit einem Doppel abschließen kann (der Spieler überwirft sich), so werden alle Darts dieser Aufnahme nicht gezählt – der Spieler bleibt auf demselben Rest wie vor der Aufnahme. In der letzte Aufnahme, in der die Punktzahl genau auf 0 reduziert wird, können auch weniger als 3 Darts geworfen werden. Falls man sich überwirft kann eine Aufnahme ebenso aus weniger als 3 Darts bestehen. 5 20 einfach 1 12 18 9 4 14 13 11 6 8 10 dreifach Bull 16 Bullseye 15 7 2 19 3 17 doppelt Ein einzelnes Spiel dieser Art nennt man Leg. Wer zuerst eine festgelegte Anzahl an Legs gewonnen hat, gewinnt das gesamte Spiel. Dabei beginnen die Spieler die Legs jeweils abwechselnd. Für regelfeste Spieler: Die häufig eingesetzte Two-Clear-Legs-Regel wird aus Gründen der Einfachheit in dieser Aufgabe nicht angewendet. Ein Darts-Spiel im obigen Modus sei in Haskell wie folgt dargestellt: • Ein Paar (m, p) stellt die Punktzahl eines einzelnen geworfenen Darts dar, wobei für m eine 1, 2 oder 3 entsprechend Single, Double oder Triple darstellt und p die Punktzahl des Zahlensegments ist. Das heißt die Triple 20 entspricht der Darstellung (3, 20), Double 20 (2, 20) und Single 20 (1, 20). Da das Bullseye als Doppelfeld gezählt wird, wird es als (2, 25) dargestellt. Außerdem entspricht (0, 0) einem Fehlwurf. • Eine Aufnahme ist ein 3-Tupel solcher Paare, z.B. entspricht ((3, 20), (3, 19), (2, 25)) einem Treffer in die Triple 20, gefolgt von einem Treffer in die Triple 19, gefolgt von einem Treffer in das Bullseye. 2 • Ein Leg wird durch eine Liste solcher 3-Tupel dargestellt, in der jede Aufnahme mit ungerader Listenposition dem Spieler zugeordnet ist, der das Leg begonnen hat und alle geraden dem Gegenspieler. Falls ein Spieler seine Aufnahme schon mit weniger als 3 Darts beendet hat, sich also überworfen oder das Leg gewonnen hat, so wird das 3-Tupel mit (0, 0)-Würfen aufgefüllt, z.B. ((2, 20), (0, 0), (0, 0)) wenn das Leg über die Double 20 gecheckt wird. • Ein gesamtes Spiel ist ein Tupel bestehend aus 4 Komponenten: 1. Name des Spielers, der das erste Leg beginnt. 2. Name des Gegenspielers. 3. Die Anzahl der zum Sieg zu erreichenden Legs. 4. Liste von Legs. Hier zwei Beispiel-Spiele zweier extrem starker Spieler: leg1 = [((3,20),(3,20),(3,20)), ((3,20),(3,19),(2,25)), ((3,19),(3,19),(3,19)), ((3,20),(3,19),(2,25)), ((2,25),(2,25),(2,25))] leg2 = [((3,20),(3,20),(3,20)), ((3,20),(3,19),(2,25)), ((3,19),(3,19),(3,19)), ((3,20),(3,19),(2,25)), ((2,25),(2,25),(1,25)), ((3,20),(3,19),(2,25))] spiel1 = ("Tri-Bull", "Mr. Perfect", 6, [leg1,leg2,leg1,leg1,leg1,leg2,leg1]) spiel2 = ("Tri-Bull", "Mr. Perfect", 6, [leg1,leg2]) a) Implementieren Sie in Haskell eine Funktion legSieger, die ein Leg in der obigen Darstellung als Eingabe erwartet und True zurückliefert, falls der Spieler gewinnt, der das Leg begonnen hat, andernfalls soll False zurückgegeben werden. Berücksichtigen Sie dazu die oben dargestellten Regeln. Die Funktion soll für fehlerhafte Legs eine entsprechende Fehlermeldung mithilfe der vordefinierten Funktion error ausgeben. Zum Beispiel falls kein Spieler die 0 Punkte erreicht hat, die angegebenen Felder gar nicht existieren (zum Beispiel Triple-Bull oder Single-21) oder falls ein Spieler bereits gewonnen hat und der Gegner danach trotzdem noch wirft. Für die obigen Beispiel-Legs soll legSieger leg1 als Ergebnis True liefern und legSieger leg2 soll False berechnen. Tipp: Verwenden Sie Hilfsfunktionen um die Regeln systematisch abzudecken. Überlegen Sie sich, wie man das Abwechseln der Spieler während des Legs einfach implementieren kann. (35 Punkte) b) Implementieren Sie in Haskell eine Funktion spielErgebnis, die ein gesamtes Spiel in obiger Darstellung entgegennimmt und einen String der Form "Name1 x:y Name2" ausgibt, wobei x die Anzahl der gewonnen Legs des Spielers mit dem Namen Name1 und y die Anzahl der gewonnen Legs des Spielers mit dem Namen Name2 ist. Die Funktion soll für unvollständige Spiele eine entsprechende Fehlermeldung ausgeben. Zum Beispiel soll spielErgebnis spiel1 das Ergebnis "Tri-Bull 6:1 Mr. Perfect" zurückgeben, während spielErgebnis spiel2 eine Fehlermeldung liefern soll. (10 Punkte) 3