->
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 xs
police
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