Functioneel programmeren Practicumopgave 2: Mastermind Het doel van deze opgave is het implementeren van het spel Mastermind; zie http://nl.wikipedia.org/wiki/mastermind voor een uitleg. Het spel is klein genoeg om in een enkele Haskell-module ingepast te worden. Het spel Mastermind is een spel voor twee spelers: een codemaker en een codebreker. De rol van codemaker wordt in ons geval vervuld door de computer. Aan het begin van het spel stelt de codemaker een code samen van vier getallen tussen 1 en 6 (inclusief). De codebreker (gespeeld door de gebruiker van ons programma) probeert in zo weinig mogelijk beurten de code te raden. Na elke poging worden er twee scores bepaald. De zwarte score geeft aan hoeveel posities in de geprobeerde code exact overeenkomen met de code van de codemaker; de witte score komt overeen met het aantal getallen in de geprobeerde code die weliswaar overeenkomen met een getal in de gezochte code, maar nog in de verkeerde positie staan. Zodra de zwarte score 4 bedraagt, is de code gebroken en het spel over. De volgende tabel toont een mogelijk spelverloop: code poging score 3466 1122 zwart 0, wit 0 3466 3344 zwart 1, wit 1 3466 3536 zwart 2, wit 0 3466 3466 zwart 4, wit 0. Een tweede voorbeeld is code poging score 5114 1234 zwart 1, wit 1 5114 1356 zwart 0, wit 2 5114 5215 zwart 2, wit 0 5114 5241 zwart 1, wit 2 5114 5411 zwart 2, wit 2 5114 5114 zwart 4, wit 0. Als het spel gespeeld wordt met het te schrijven Haskell-programma, zou een en ander er als volgt uitzien: 1
? 1 1 2 2 zwart 0, wit 0? 3 3 4 4 zwart 1, wit 1? 3 5 3 6 zwart 2, wit 0? 3 4 6 6 zwart 4, wit 0 gefeliciteerd! Het programma vraagt de speler met een naar het scherm geschreven vraagteken (?) om een poging te wagen. De speler typt dan een reeks van vier cijfers tussen 1 en 6, gescheiden door spaties. Het spel antwoordt met het weergeven van de score. Als de zwarte score 4 bedraagt, wordt de speler gefeliciteerd en is het spel afgelopen; als de zwarte score lager is wordt de speler om een volgende poging gevraagd enzovoort. We implementeren het spel door een aantal kleine hulpfuncties te schrijven en die vervolgens te verweven tot een heus interactief programma. We zullen codes representeren met lijsten van gehele getallen: type Code = [Int ]. Opgave 1 black :: Code Code Int die voor een gegeven code (het eerste argument) en een gegeven poging (het tweede argument) de zwarte score bepaalt. Bijvoorbeeld: black [3, 4, 6, 6] [3, 5, 3, 6] 2 black [5, 1, 1, 4] [1, 2, 3, 4] 1. Opgave 2 white :: Code Code Int die voor een gegeven code (het eerste argument) en een gegeven poging (het tweede argument) de witte score bepaalt. Bijvoorbeeld: white [3, 4, 6, 6] [3, 5, 3, 6] 0 white [5, 1, 1, 4] [1, 2, 3, 4] 1. Opgave 3 score :: Code Code (Int, Int, Bool) 2
die voor een gegeven code (het eerste argument) en een gegeven poging (het tweede argument) een drietupel oplevert waarvan de eerste component de zwarte score voor de poging bevat, de tweede component de witte score voor de poging en de derde component een Boolse waarde (True of False) die aangeeft of de poging exact overeenkomt met de gezochte code. Bijvoorbeeld: score [3, 4, 6, 6] [3, 5, 3, 6] (2, 0, False) score [5, 1, 1, 4] [1, 2, 3, 4] (1, 1, False) score [5, 1, 1, 4] [5, 1, 1, 4] (4, 0, True ). Opgave 4 report :: (Int, Int, Bool) String die op basis van een door de functie score (zie opgave 3) geproduceerd drietupel een tekst samenstelt die aan de speler gepresenteerd kan worden als feedback op een poging. Bijvoorbeeld: report (score [3, 4, 6, 6] [3, 5, 3, 6]) "zwart 2, wit 0" report (score [5, 1, 1, 4] [1, 2, 3, 4]) "zwart 1, wit 1" report (score [5, 1, 1, 4] [5, 1, 1, 4]) "zwart 4, wit 0\ngefeliciteerd!". Opgave 5 readint :: String Maybe Int die voor een gegeven tekenreeks het door die tekenreeks weergegeven natuurlijke getal oplevert in de Maybe-monad. Als de tekenreeks geen natuurlijk getal weergeeft, dan levert de functie Nothing op. (Een natuurlijk getal is een geheel getal groter dan of gelijk aan 0.) Bijvoorbeeld: readint "3" Just 3 readint "42" Just 42 readint "hallo" Nothing readint "-3" Nothing. Opgave 6 Definineer een actie getcode :: IO Code die de invoer van een speler inleest en de bijbehorende code oplevert. Bijvoorbeeld: 3 5 3 6 [3,5,3,6] 3
Representeer niet-numerieke invoer met de constante 1. Bijvoorbeeld: 3 5 hallo 6 [3,5,-1,6] De actie moet probleemloos overweg kunnen met invoer die niet uit precies vier getallen bestaat en met getallen die kleiner zijn dan 0 of groter dan 6. Bijvoorbeeld: 3 5 7 6 [3,5,7,6] 3 5 3 [3,5,3] 3 5 3 6 6 [3,5,3,6,6] Opgave 7 valid :: Code Bool die bepaalt of een gegeven code geldig is, dat wil zeggen uit precies vier getallen tussen 1 en 6 bestaat. Bijvoorbeeld: valid [3, 5, 3, 6] True valid [3, 5, 1, 6] False valid [3, 5, 7, 6] False valid [3, 5, 3] False valid [3, 5, 3, 6, 6] False. Opgave 8 Definieer een actie input :: IO Code die een vraagteken (?) naar het scherm schrijft en vervolgens invoer van de speler inleest en controleert of deze overeenkomt met een geldige code. Als de invoer inderdaad overeenkomt met een geldige code, dan wordt deze code opgeleverd. Komt de invoer niet overeen met een geldige code dan wordt een foutmelding naar het scherm geschreven en wordt de speler nogmaals om invoer gevraagd enzovoort. Bijvoorbeeld: *Main> do { code <- input ; print code }? 3 5 hello 6 ongeldige invoer? 3 5 3 ongeldige invoer? 3 5 3 6 [3,5,3,6] 4
Opgave 9 loop :: Code IO () die voor een gegeven code een actie produceert die de speler herhaaldelijk om invoer vraagt en telkens feedback geeft, totdat de speler de code gebroken heeft. Bijvoorbeeld: *Main> loop [3, 4, 6, 6]? 1 1 2 2 zwart 0, wit 0? 3 3 4 4 zwart 1, wit 1? 3 5 hallo 6 ongeldige invoer? 3 5 3 6 zwart 2, wit 0? 3 4 6 6 zwart 4, wit 0 gefeliciteerd! Opgave 10 Importeer de module Random door boven in je programma, direct onder de module-header, de declaratie import Random op te nemen. De module Random bevat, onder andere, de actie newstdgen :: IO StdGen die een zogenaamde random-number generator oplevert en een functie randomrs :: (Int, Int) StdGen [Int ] die gegeven een onder- en bovengrens en een random-number generator een oneindige lijst van willekeurig gekozen getallen binnen de onder- en bovengrens oplevert. Schrijf nu (gebruikmakend van newstdgen en randomrs) een actie generatecode :: IO Code die een willekeurig gekozen code oplevert. Het hoofdprogramma Importeer de module System.IO door de declaratie import System.IO. in je programma op te nemen. Als alle hierboven beschreven functies en acties correct geïmplementeerd zijn, dan laat het hoofdprogramma zich schrijven als 5
main :: IO () main = do hsetbuffering stdout NoBuffering code generatecode loop code. Je kunt het programma testen vanuit de GHCi door de actie main aan te roepen: *Main> main Inleveren Rangschik alle geschreven functies en acties (dus zowel de gevraagde functies en acties als eventuale hulpfuncties en -acties én het hoofdprogramma main) in een module Main en plaats deze module in een bestand Mastermind.hs. Begin het bestand met commentaarregels waarin je naam, login en studentnummer vermeld worden. Als je de opdracht met zijn tweeën gemaakt hebt, vermeld dan de gegevens van beide teamleden. Lever dit bestand, met behulp van Submit, vóór dinsdag 25 maart 2008, 9:00 uur in via http://www.cs.uu.nl/docs/submit/. Zorg ervoor dat je code correct functioneert met de GHC (versie 6.4 of hoger). Als je module niet door de vertaler geaccepteerd wordt, komt je uitwerking niet in aanmerking voor een cijfer. Het overnemen van uitwerkingen, bijvoorbeeld van medestudenten of van het internet, is niet toegestaan. Een uitvoerbaar programma Als je je code wilt omzetten naar een uitvoerbaar programma (een zogenaamde executable), dan kan dat door je module vanaf de prompt van het besturingssysteem te compileren met ghc --make Mastermind Deze aanroep resulteert onder Windows in een uitvoerbaar bestand met de naam Mastermind.exe en onder Mac OS X en Linux in een uitvoerbaar bestand met de naam mastermind. 6