forked from iSchool-590PR-2019-Spring/Final_Project
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGame.py
237 lines (194 loc) · 7.79 KB
/
Game.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
import random
import Player
import Strategy
class Game:
"""Game class - two Players compete for a set number of rounds. The player with the most points wins.
>>> p1 = Player.Player(strategy=Strategy.AlwaysCooperate)
>>> p2 = Player.Player(strategy=Strategy.AlwaysDefect)
>>> g1 = Game(p1, p2, noiseMax=0)
>>> g1.p1Strategy
'AllC'
>>> g1.p2Strategy
'AllD'
>>> g1.set_mode('I')
True
>>> g1.set_mode('mis')
Traceback (most recent call last):
Exception: Mode must be 'P' for 'misperception', or 'I' for 'misimplementation'.
>>> g1.set_payoffs(1, 1, 1, 1)
Traceback (most recent call last):
AssertionError: Payoffs must follow rule: T > R > P > S
>>> g1.set_payoffs(10, 5, 3, 1)
Traceback (most recent call last):
AssertionError: Payoffs must follow rule: 2*R > T + S
>>> g1.set_noise(0)
0.0
>>> g1.play_game()
>>> g1.gameHistory[0]
('C', 'D')
>>> len(g1.gameHistory)
100
>>> g1.p1Score
0
>>> g1.p2Score
500
"""
gameCount = 0
def __init__(self, p1: Player, p2: Player, rounds=100, noise_growthMax=0.01, noiseMax=0.5, name: str = None):
self.p1 = p1
self.p2 = p2
self.rounds = rounds
self.noise_growthMax = noise_growthMax # must be between 0 and 1
self.noiseMax = noiseMax # must be between 0 and 1
# shortcut for player names & strategies
self.p1Name = self.p1.name
self.p2Name = self.p2.name
self.p1Strategy = self.p1.strategy.id
self.p2Strategy = self.p2.strategy.id
# name the game based on participating players
if name is None:
self.name = "{} ({}) v. {} ({})".format(self.p1Name, self.p1Strategy, self.p2Name, self.p2Strategy)
else:
self.name = name
# generate game ID
Game.gameCount += 1
self.id = Game.gameCount
# keep track of real and perceived moves in the game
self.realHistory = []
self.p1History = []
self.p2History = []
self.gameHistory = []
# keep track of scores
self.p1Score = 0
self.p2Score = 0
# keep track of final noise level
self.noise = self.set_noise()
self.noiseHistory = []
# payoffs
self.payoffs = self.set_payoffs()
self.implementNoise = self.set_mode()
def set_noise(self, max=0.5):
"""This sets the starting noise level - a random value between 0 and the max (default .5)"""
return random.uniform(0, max)
def set_mode(self, mode='I'):
"""Determines whether payoffs are determined by a player's actual moves (misperception) or the moves
possibly changed by noise (misimplementation)"""
if mode in ['misperception', 'P', 'p']:
return False
elif mode in ['misimplementation', 'I', 'i']:
return True
else:
raise Exception("Mode must be 'P' for 'misperception', or 'I' for 'misimplementation'.")
def set_payoffs(self, T: int = 5, R: int = 3, P: int = 1, S: int = 0):
"""This sets the payoffs for the game, determining how many points each player gets per round."""
assert T > R > P > S, "Payoffs must follow rule: T > R > P > S"
assert 2*R > T + S, "Payoffs must follow rule: 2*R > T + S"
return (T, R, P, S)
def flip(self, play):
"""This flips the player's move from 'C' to 'D' or from 'D' to 'C', if called due to noise"""
if play == 'C':
return 'D'
else:
return 'C'
def play_round(self):
"""This plays through one round of the game. To be called once per round."""
# get moves, as determined by each player's strategy
p1move = self.p1.strategy.next_move(self.p1)
p2move = self.p2.strategy.next_move(self.p2)
realMoves = (p1move, p2move)
# add real moves to real history
self.realHistory.append(realMoves)
# account for noise
# add current noise to noise history
self.noiseHistory.append(self.noise)
# generate random value for each player to be compared to the noise
p1chance = random.random() # a value between 0 and 1
p2chance = random.random() # a different value between 0 and 1
# increase noise if defection occurred
if realMoves is ('D', 'D'):
noiseIncrementor = random.uniform(0, self.noise_growthMax * 2) # more noise for more defection
elif 'D' in realMoves:
noiseIncrementor = random.uniform(0, self.noise_growthMax) # if only one player defects
else:
noiseIncrementor = (random.uniform(0, self.noise_growthMax)) * -1 # if both cooperate, reduce noise
# increment noise, make sure noise stays between 0 and max
self.noise += noiseIncrementor
if self.noise > self.noiseMax:
self.noise = self.noiseMax
elif self.noise < 0:
self.noise = 0
else:
self.noise = self.noise
# use noise to possibly flip moves
if p1chance < self.noise:
p1PerMove = self.flip(p1move)
else:
p1PerMove = p1move
if p2chance < self.noise:
p2PerMove = self.flip(p2move)
else:
p2PerMove = p2move
p1PerRound = (p1move, p2PerMove) # P1's perceived history of the round
p2PerRound = (p2move, p1PerMove) # P2's perceived history of the round
self.p1History.append(p1PerRound)
self.p2History.append(p2PerRound)
# moves (potentially altered by noise) that will determine payoffs
noisyMoves = (p1PerMove, p2PerMove)
# get payoff values, either default or those specified when set_payoffs() is called
T, R, P, S = self.payoffs
# use game mode to determine if real moves or noisy moves are used to award payoffs
if self.implementNoise:
moves = noisyMoves
else:
moves = realMoves
# determine payoff values, to be added to the player's real scores
if moves == ('C', 'C'):
p1payoff = R
p2payoff = R
elif moves == ('D', 'D'):
p1payoff = P
p2payoff = P
elif moves == ('C', 'D'):
p1payoff = S
p2payoff = T
elif moves == ('D', 'C'):
p1payoff = T
p2payoff = S
else:
raise Exception("Invalid move(s) made, choose 'C' or 'D'.")
# increment player scores
self.p1Score += p1payoff
self.p2Score += p2payoff
# send moves used to determine payoffs to game history
self.gameHistory.append(moves)
def send_history(self):
"""This sends the perceived moves to each player's own known history of the game.
To be called after each round."""
# get last moves (just played)
forP1 = self.p1History[-1]
forP2 = self.p2History[-1]
# send to players's history
self.p1.history.append(forP1)
self.p2.history.append(forP2)
def play_game(self):
"""This plays through an entire game between two players, for the set number of rounds."""
# reset player histories
self.p1.history = []
self.p2.history = []
# play all rounds
for r in range(self.rounds):
self.play_round()
self.send_history()
# send players their scores
self.p1.points += self.p1Score / self.rounds
self.p2.points += self.p2Score / self.rounds
# send players their win/lose/tie results
if self.p1Score == self.p2Score:
self.p1.ties += 1
self.p2.ties += 1
elif self.p1Score > self.p2Score:
self.p1.wins += 1
self.p2.losses += 1
elif self.p1Score < self.p2Score:
self.p1.losses += 1
self.p2.wins += 1