-> Sieger Papier-> Sieger Stein-> Sieger SchereIm Folgenden soll ein Programm entwickelt werden, welches dieses Spiel nachbildet und den Gesamtsieger anhand der Anzahl der gewonnenen Spielrunden ermittelt. Zunächst werden die benötigten Datentypen eingeführt:
data Move = Paper | Rock | Scissors type Round = (Move,Move)
score definiert:
score :: Round -> (Int,Int)
score (x,y) | x beats y = (1,0)
| y beats x = (0,1)
| otherwise = (0,0)
beats kann wie folgt definiert werden:
( beats ) :: Move -> Move -> Bool
x beats y = (m + 1 == n) or (m == n + 2)
where m = code x; n = code y
code :: Move -> Int code Paper = 0 code Rock = 1 code Scissors = 2
Strategy schon existiert und
somit innerhalb von Funktionsdefinitionen benutzt werden darf.rounds wird zur Berechnung einer unendlichen Liste von Runden genutzt:
rounds :: (Strategy,Strategy) -> [Round]
rounds läßt sich die
Funktion match definieren, welche das
Ergebnis einer bestimmten Anzahl von Spielrunden zurückliefert.
match :: Int -> (Strategy,Strategy) -> (Int,Int) match n = total · map score · take n · rounds
total :: [(Int,Int)] -> (Int,Int) total = pair (sum · map fst, sum · map snd)
type Strategy = [Move] -> Move
recip :: Strategy recip ms = if null ms then Rock else last ms
smart :: Strategy smart ms = if null ms then Rock else choose (count ms)
count :: [Move] -> (Int,Int,Int) count = foldl (⊕) (0,0,0)
(⊕) :: (Int,Int,Int) -> Move -> (Int,Int,Int) (p,r,s) ⊕ Paper = (p + 1,r,s) (p,r,s) ⊕ Rock = (p,r + 1,s) (p,r,s) ⊕ Scissors = (p,r,s + 1)
choose wählt eine Figur anhand
einer Analyse der drei Zahlenwerte p,q und r, die jeweils die Anzahl der einzelnen bereits vom Gegner gewählten
Figuren darstellen.
choose :: (Int,Int,Int) -> Move
choose (p,r,s) | m < p = Scissor
| m < p + r = Paper
| otherwise = Rock
where m = random (p + r + s)
choose wählt eine Figur in Abhängigkeit davon aus, in welches der
folgenden drei Intervalle die Zufallszahl m fällt:
(0 ≤ m < p) or (p ≤ m < p + r) or (p + r ≤ m < p + r + s)
rounds
zu definieren.
rounds :: (Strategy,Strategy) -> [Round] rounds (f,g) = (map last ⋅ tail ⋅ iterate (extend (f,g)))[]
extend :: (Strategy,Strategy) -> [Round] -> [Round] extend (f,g) rs = rs ++ [(f (map snd rs),g (map fst rs))]
extend hängt ein neues Paar von Figuren an die
Liste der bisher gespielten Runden und rounds generiert die unendliche Liste von Spielrunden, indem sie die Funktion extend
laufend auf die initialisierende leere Liste anwendet.rounds ist im Hinblick auf Effizienz sehr
ungeschickt implementiert. Die Ursache hierfür liegt darin, dass die Zeit zur Berechnung eines Resultats für die Strategie proportional lange
zur Länge der Liste dauert. Daraus folgt, dass die Funktion extend Ω(n) Schritte zum
Hinzufügen einer Runde zu einem Spiel mit n Runden benötigt. Um ein komplettes Spiel mit n Runden
zu berechnen, werden so Ω(n²) Schritte benötigt. type strategy = [Move] -> [Move]
strategy eine Funktion, die die unendliche Liste von Moves des Gegners als Argument erhält und daraufhin
eine unendliche Liste von geeigneten Antworten generiert.recip
type recip ms = Rock : ms
recip jeweils
eine konstant lange Rechenzeit.smart
smart xs = Rock : map choose (counts xs)
counts = tail · scanl (⊕) (0,0,0)
counts berechnet die laufende Summe der möglichen Moves. Diese
neu Implementierung der smart- Strategy ist ebenfalls sehr effizient, da jedes Ergebnis
in einem konstant grossen Zeitintervall berechnet wird. Auf Grundlage dieser neuen Strategieimplementierungen
kann nun die Funktion rounds neu definiert werden:
rounds (f,g) = zip xs ys
where xs = f ys; ys = g xs
XS entspricht hier der Liste der bisher gewählten Figuren des Gegners nach Anwendung der jeweils
gewählten Strategie. Dieser Sachverhalt gilt analog für ys. Rounds
berechnet die ersten n Figuren des Spiels in O(n) Schritten, da f und g jeden
neuen Move in konstanter Zeit berechnen. Diese Art und Weise Strategien zu implementieren ist also
effizienter als die zu Beginn eingefürte Variante.
cheat:
cheat xs = map trumps xs
trumps :: Move -> Move trumps Paper = Scissors trumps Rock = Paper trumps Scissors = Rock
Cheat wählt also in jeder Runde jeweils die zum Gewinnen benötigte Figur aus.
Das folgende Beispiel zeigt, dass in diesem Fall nichts gegen das Untergraben der Spielregeln getan werden kann.
xs = cheat ys ys = recip xs
xs und ys handelt es sich um die Grenzwerte der Approximationsketten xs0, xs1, ... und ys0, ys1, ...
wobei folgendes gilt:
xs0 = ys0 = ⊥ und xsn+1 = cheat ysn ysn+1 = recip xsn
xs1 = cheat ⊥ = ⊥ ys1 = recip ⊥ = ⊥
xs2 = cheat (Rock : ⊥) = Paper : ⊥ ys2 = recip ⊥ = Rock : ⊥
xs3 = cheat (Rock : ⊥) = Paper : ⊥ ys3 = recip (Paper : ⊥) = Rock : Paper : ⊥
xs4 = cheat (Rock : Paper : ⊥) = Paper : Scissors : ⊥ ys4 = recip (Paper : ⊥) = Rock : Paper : ⊥
usw. ...
cheat zudem jede Runde für sich.a) cunning xs = trumps (head xs) : cunning (tail xs)
Cunning arbeitet genauso wie die Funktion cheat, allerdings mit der
Ausnahme, dass cheat ⊥ das Ergebnis ⊥ liefert und cunning ⊥ = ⊥ : ⊥ : ⊥ : ... gilt.b) oneshot xs = trumps (head xs) : recip (tail xs)
Oneshot schummelt nur in der ersten Runde und delegiert alle weiteren Berechnungen an die Funktion recip.c) devious = take2 (recip xs) ++ cheat (drop 2 xs)
Devious hält sich die ersten beiden Runden an die Spielregeln und beginnt dann zu schummeln.open definiert, um zu zeigen, dass die ersten n Elemente der Liste xs korrekt sind.open (n,xs) => open (n + 1, f xs) für alle n und alle Listen xs gilt.open läßt sich nun beweisen, dass beispielsweise recip
eine "ehrliche" Funktion ist.open (0,⊥) => open (1,recip ⊥) ≡ (1, Rock)
cheat ergibt sich hingegen folgendes:open (0,⊥) => open (1,cheat ⊥) ≡ open (1,⊥)
Cheat erfüllt diese Zusicherung open nicht. Wie im ersten Fall
ist open(0,⊥) gültig, open (1,⊥)jedoch nicht.police definieren, die folgende Eigenschaften besitzt:police f xs = f xspolice gewährleistet, dass die Strategie
f vor der Kenntnis, der vom Gegner gewählten Figur ein Ergebnis liefert. Police wird folgendermaßen
definiert:
police f xs = ys where ys = f (synch xs ys)
mit: synch :: [Move] -> [Move] -> [Move] synch (x : xs) (y : ys) = if defined y then x : synch xs ys else ⊥
mit: defined :: Move -> Bool defined | ⊥ = False | otherwise = True
rounds würde nun folgendermaßen aussehen:
rounds (f,g) = zip xs ys
where xs = police f ys; ys = police g xs