-
Notifications
You must be signed in to change notification settings - Fork 0
/
AlphaBetaAgent.py
407 lines (338 loc) · 14.1 KB
/
AlphaBetaAgent.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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
from game import *
import itertools
#CSPAgent focuses on 'observe_suggestion()' function.
#- It observes the exchange as first opponent is the suggester
class AlphaBetaAgent(Agent):
def __init__(self, name):
super().__init__(name)
#Make opponent hands
self.first_opponent_hand = Hand()
self.second_opponent_hand = Hand()
#print("START")
#print("cards: {}".format(self.first_opponent_hand.get_cards()))
#print("cards: {}".format(self.second_opponent_hand.get_cards()))
#Sets to keep track of potential domains for each hand
self.first_opponent_sets = []
self.second_opponent_sets = []
#Keep track of prev suggestion
self.past_suggestion = [None]*3
self.combinations = []
def make_move(self):
'''
decide when to make a suggestion or accusation
suggestion is of type Suggestion
accusation is a dict where key is the type, and the value is the card
Update player
Update casefile
Make guess based on values not in sets
'''
weapon_dom = self.caseFileWeapon.cur_domain()
room_dom = self.caseFileRoom.cur_domain()
suspect_dom = self.caseFileSuspect.cur_domain()
self.first_opponent_sets = [x for x in self.first_opponent_sets if x]
self.second_opponent_sets = [x for x in self.second_opponent_sets if x]
#For the first tern - randomly make a suggestion
if None in self.past_suggestion:
#Prune agents cards from opponents' hands
self.first_opponent_hand.pruneHand(self.hand)
self.second_opponent_hand.pruneHand(self.hand)
self._init_combinations()
#Update the player's hands and sets
self._update_player(self.first_opponent_hand, self.first_opponent_sets)
self._update_player(self.second_opponent_hand, self.second_opponent_sets)
#Prune assigned values in one hand from the other
self._update_each_other()
#Prune assigned values from the hands from the cur dom of the casefile
self._update_case(self.first_opponent_hand)
self._update_case(self.second_opponent_hand)
#Update level of tree
self._update_tree()
#print("Player {}'s cards: {}".format(self.firstOppName, self.first_opponent_hand.get_cards()))
#print("Player {}'s cards: {}".format(self.secondOppName, self.second_opponent_hand.get_cards()))
#print("Player {}'s set: {}".format(self.firstOppName, self.first_opponent_sets))
#print("Player {}'s set: {}".format(self.secondOppName, self.second_opponent_sets))
#print("CF: W{}: R:{} S:{}".format(self.caseFileWeapon.cur_domain(), self.caseFileRoom.cur_domain(), self.caseFileSuspect.cur_domain()))
#print(self.combinations)
#Make accusation
if (self.caseFileWeapon.cur_domain_size() == 1 and self.caseFileRoom.cur_domain_size() == 1 and self.caseFileSuspect.cur_domain_size() == 1):
room_value = self.caseFileRoom.cur_domain()[0]
weapon_value = self.caseFileWeapon.cur_domain()[0]
suspect_value = self.caseFileSuspect.cur_domain()[0]
self.caseFileRoom.assign(room_value)
self.caseFileWeapon.assign(weapon_value)
self.caseFileSuspect.assign(suspect_value)
accusation = {}
accusation['Room'] = self.caseFileRoom
accusation['Weapon'] = self.caseFileWeapon
accusation['Suspect'] = self.caseFileSuspect
return accusation
#Make suggestion - from positive terms in game tree
else:
np.random.shuffle(self.combinations)
guess = self.combinations[0]
print(guess)
self.past_suggestion[0] = guess[0]
self.past_suggestion[1] = guess[1]
self.past_suggestion[2] = guess[2]
suggestion = Suggestion(self.name, self.firstOppName, guess[0], guess[1], guess[2])
return suggestion
def respond_to_suggestion(self, suggestion):
'''
update knowledge base with whatever from game
Update CSPs
give a response of a card if you have a card in suggestion, or None otherwise
CSPAgent: Return room last (becomes more rooms - harder to figure out)
'''
for card in self.hand.get_cards():
if card.assignedValue == suggestion.weapon:
return card
elif card.assignedValue == suggestion.suspect:
return card
elif card.assignedValue == suggestion.room:
return card
return None
def observe_suggestion(self, suggestion, did_respond):
'''
used for observation
observe a suggestion, and see the response
suggestion - of type SUGGESTION
did_respond - boolean to see if the responder sent a card back or not
CSPAgent: Add constraints to set
'''
#Add constraints to one card
if did_respond:
if suggestion.responder == self.firstOppName:
self._add_domain(suggestion, self.first_opponent_hand, self.first_opponent_sets)
elif suggestion.responder == self.secondOppName:
self._add_domain(suggestion, self.second_opponent_hand, self.second_opponent_sets)
#If opponent did not respond, they do not have the suggested
#Weapon, room or suspect. Prune these from their cards.
#Also, prune values from domains in sets
#NOTE: Self is second responder
else:
if suggestion.responder == self.firstOppName:
self._prune_sug(suggestion, self.first_opponent_hand, self.first_opponent_sets)
def update_from_response(self, suggestion, response):
'''
after making a suggestion, this method will be called to respond to a response
- Doesnt have
- prune from domain of sets
- prune from paul's hand
- Does have
- prune domain from sets
- prune from casefile
- add response to non assigned value
'''
if response is not None:
#Update casefile - can't have the response
if response.typ == 'Weapon':
self.caseFileWeapon.prune_value(response.assignedValue)
elif response.typ == 'Room':
self.caseFileRoom.prune_value(response.assignedValue)
else:
self.caseFileSuspect.prune_value(response.assignedValue)
if suggestion.responder == self.firstOppName:
#Add value to responder's hand
assigned = self.first_opponent_hand.get_assigned_card_values()
if response.assignedValue in assigned:
return
else:
if None in assigned:
card = Card(response.typ, response.assignedValue, response.cur_domain())
self.first_opponent_hand.add_card(card)
else:
#Add value to responder's hand
assigned = self.second_opponent_hand.get_assigned_card_values()
if response.assignedValue in assigned:
return
else:
if None in assigned:
card = Card(response.typ, response.assignedValue, response.cur_domain())
self.second_opponent_hand.add_card(card)
#If did not respond
else:
#If first responder says no - prune suggestion from cards and domain
self._prune_sug(suggestion, self.first_opponent_hand, self.first_opponent_sets)
#If second responder says no - response must be in casefile!
if suggestion.responder == self.secondOppName:
if self.caseFileWeapon.cur_domain_size() != 1:
if self.caseFileWeapon.in_cur_domain(suggestion.weapon):
#print('**************FOUND WEAPON BC LUCKY GUESS****************')
self._prune_all(self.caseFileWeapon, suggestion.weapon)
if self.caseFileSuspect.cur_domain_size() != 1:
if self.caseFileSuspect.in_cur_domain(suggestion.suspect):
self._prune_all(self.caseFileSuspect, suggestion.suspect)
#print('*************FOUND SUSPECT BC LUCKY GUESS****************')
if self.caseFileRoom.cur_domain_size() != 1:
if self.caseFileRoom.in_cur_domain(suggestion.room):
self._prune_all(self.caseFileRoom, suggestion.room)
#print('*************FOUND ROOM BC LUCKY GUESS*******************')
#print('Case file W:{} R:{} S:{}'.format(self.caseFileWeapon.cur_domain(), self.caseFileRoom.cur_domain(), self.caseFileSuspect.cur_domain()))
def observe_accusation(self, was_accuser, was_correct):
'''
made to respond to an accusation
accuser_name is name of accuser
was_correct is true if the accusation was correct (and the game ends)
'''
return
def reset(self):
'''
to reset for a new game!
'''
super().reset()
self.first_opponent_hand = Hand()
self.second_opponent_hand = Hand()
self.first_opponent_sets = []
self.second_opponent_sets = []
self.past_suggestion = [None]*3
self.combinations = []
def _init_combinations(self):
#Make all combinations
ALL = []
ALL.append(WEAPONS)
ALL.append(ROOMS)
ALL.append(SUSPECTS)
self.combinations = list(itertools.product(*ALL))
def _update_tree(self):
'''
Use casefiles to remove combinations that
were eliminated by pruning.
'''
#Remove all values not in the current domain
prune = []
for weapon in WEAPONS:
if not self.caseFileWeapon.in_cur_domain(weapon):
prune.append(weapon)
for suspect in SUSPECTS:
if not self.caseFileSuspect.in_cur_domain(suspect):
prune.append(suspect)
for room in ROOMS:
if not self.caseFileRoom.in_cur_domain(room):
prune.append(room)
copy = self.combinations
self.combinations = [x for x in copy if set(x).isdisjoint(prune)]
def _update_case(self, hand):
'''
Prune assigned values of cards in hand from casefile
'''
for card in hand.get_cards():
if card.is_assigned():
if card.typ == 'Weapon' and self.caseFileWeapon.in_cur_domain(card.assignedValue):
self.caseFileWeapon.prune_value(card.assignedValue)
elif card.typ == 'Room' and self.caseFileRoom.in_cur_domain(card.assignedValue):
self.caseFileRoom.prune_value(card.assignedValue)
elif card.typ == 'Suspect' and self.caseFileSuspect.in_cur_domain(card.assignedValue):
self.caseFileSuspect.prune_value(card.assignedValue)
else:
continue
def _update_player(self, hand, sets):
'''
Update player's set and hand by pruning cards with domain of length 1
and the entire domain if a value in it is already assigned to a card
If casefile value is known, remove value from domain
'''
assigned = set(hand.get_assigned_card_values())
#print("Assigned: {}".format(hand.get_assigned_card_values()))
for i, domain in enumerate(sets):
#Remove domains of size 1
if len(domain) == 1 and domain[0] not in hand.get_assigned_card_values() and None in hand.get_assigned_card_values():
#print('********************** ASSIGNED BECAUSE SIZE 1: UPDATE PLAYERS {} *************************'.format(domain[0]))
card = Card(typ=None, name=domain[0], domain=domain)
card.update_type()
hand.add_card(card)
#print("ADDED {} so now {}".format(domain, hand.get_cards()))
if self.caseFileWeapon.cur_domain_size() == 1:
if self.caseFileWeapon.cur_domain()[0] in domain:
new_dom = [x for x in domain if (x != self.caseFileWeapon.cur_domain()[0])]
sets[i] = new_dom
#print('********************** CHANGED BC CF WEAPON KNOWN *************************')
if self.caseFileSuspect.cur_domain_size() == 1:
if self.caseFileSuspect.cur_domain()[0] in domain:
new_dom = [x for x in domain if (x != self.caseFileSuspect.cur_domain()[0])]
sets[i] = new_dom
#print('********************** CHANGED BC CF SUSPECT KNOWN *************************')
if self.caseFileRoom.cur_domain_size() == 1:
if self.caseFileRoom.cur_domain()[0] in domain:
new_dom = [x for x in domain if (x != self.caseFileRoom.cur_domain()[0])]
sets[i] = new_dom
#print('********************** CHANGED BC CF ROOM KNOWN *************************')
copy = sets
for domain in copy:
#Prune if already assigned
overlap = assigned.intersection(domain)
if len(overlap) > 0:
sets.remove(domain)
#remove empty domains and already considered domains
elif len(domain) == 1:
sets.remove(domain)
def _update_each_other(self):
'''
Prune assigned values of cards in one hand from the other
Vice versa
'''
assigned_1 = set(self.first_opponent_hand.get_assigned_card_values())
assigned_2 = set(self.second_opponent_hand.get_assigned_card_values())
for value in assigned_1:
if value != None:
for card in self.second_opponent_hand.get_cards():
if card.in_cur_domain(value):
card.prune_value(value)
for value in assigned_2:
if value != None:
for card in self.first_opponent_hand.get_cards():
if card.in_cur_domain(value):
card.prune_value(value)
def _prune_all(self, card, name):
'''
Prune all values from card except name
'''
for c in card.cur_domain():
if c != name:
card.prune_value(c)
def _prune_sug(self, suggestion, hand, sets):
'''
Prune all elements of suggestion from all cards in hand
'''
#Remove from curdom
for card in hand.get_cards():
if card.assignedValue == None:
card.prune_value(suggestion.weapon)
card.prune_value(suggestion.room)
card.prune_value(suggestion.suspect)
copy = sets
#Remove from sets
if len(sets) != 0:
for i, domain in enumerate(sets):
new_dom = [x for x in domain if (x != suggestion.suspect or x!= suggestion.room or x!=suggestion.weapon)]
#if new_dom != domain:
#print("XXXXXXXXXXXXXXXXXXXXXXXXXXXXX CHANGE MADE TO SETS XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
sets[i] = new_dom
def _add_domain(self, suggestion, hand, sets):
'''
Helper fn for observe_suggestion:
- add constraints to represent potential values for a card
'''
domain = []
my_cards = self.hand.get_assigned_card_values()
#Populate domain
for card in hand.get_cards():
#Do not add constraint if already know what card (or a card that) was shown
if (card.get_assigned_value() == suggestion.weapon or card.get_assigned_value() == suggestion.room
or card.get_assigned_value() == suggestion.suspect):
return
if card.in_cur_domain(suggestion.weapon) and suggestion.weapon not in domain and suggestion.weapon not in my_cards:
domain.append(suggestion.weapon)
if card.in_cur_domain(suggestion.room) and suggestion.room not in domain and suggestion.weapon not in my_cards:
domain.append(suggestion.room)
if card.in_cur_domain(suggestion.suspect) and suggestion.suspect not in domain and suggestion.weapon not in my_cards:
domain.append(suggestion.suspect)
#If only one value - assign value to an unassigned card
if len(domain) == 1 and domain[0] not in hand.get_assigned_card_values() and None in hand.get_assigned_card_values():
#print("Domain: {}".format(domain))
#print("************************ ASSIGNED BC SIZE 1: ADD DOMAIN {} **************************".format(domain[0]))
card = Card(typ=None, name=domain[0], domain=domain)
card.update_type()
hand.add_card(card)
#print("ADDED {} so now {}".format(domain, hand.get_cards()))
else:
sets.append(domain)