-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrules.py
449 lines (413 loc) · 16.5 KB
/
rules.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
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
import random
###
# Define all possible actions and
# select an action according to a strategy
###
def print_state(data):
print("Current player: " + data.currentPlayer)
print("Player hands: ")
for p in data.players:
print(p.toClientString())
print("Table cards: ")
for pos in data.tableCards:
print(pos + ": [ ")
for c in data.tableCards[pos]:
print(c.toClientString() + " ")
print("]")
print("Discard pile: ")
for c in data.discardPile:
print("\t" + c.toClientString())
print("Note tokens used: " + str(data.usedNoteTokens) + "/8")
print("Storm tokens used: " + str(data.usedStormTokens) + "/3")
###
# Select action according to a strategy
###
def select_action(self, strategy, state):
action = None
cardOrder = None
t = None
val = None
dest = None
for rule in strategy:
if rule == 0:
action, cardOrder = play_if_certain(self, state)
elif rule == 1:
action, cardOrder = play_probably_safe_card(self, state, 0.8)
elif rule == 2:
action, cardOrder = play_probably_safe_card(self, state, 0.6)
elif rule == 3:
action, cardOrder = play_probably_safe_card(self, state, 0.4)
elif rule == 4:
action, cardOrder = play_probably_safe_card_with_lives(self, state, 0.8)
elif rule == 5:
action, cardOrder = play_probably_safe_card_with_lives(self, state, 0.6)
elif rule == 6:
action, cardOrder = play_probably_safe_card_with_lives(self, state, 0.4)
elif rule == 7:
action, cardOrder = play_recently_hinted(self, state, 0.01, 0)
elif rule == 8:
action, cardOrder = play_recently_hinted(self, state, 0.2, 0)
elif rule == 9:
action, cardOrder = play_recently_hinted(self, state, 0.01, 1)
elif rule == 10:
action, cardOrder = play_recently_hinted(self, state, 0.2, 1)
elif rule == 11:
action, cardOrder = play_most_probable_if_deck_empty(self, state)
elif rule == 12:
action, t, val, dest = complete_tell_playable_card(self, state)
elif rule == 13:
action, t, val, dest = tell_about_ones(self, state)
elif rule == 14:
action, t, val, dest = tell_about_fives(self, state)
elif rule == 15:
action, t, val, dest = tell_playable_card(self, state)
elif rule == 16:
action, t, val, dest = tell_useless_card(self, state)
elif rule == 17:
action, t, val, dest = tell_most_information(self, state)
elif rule == 18:
action, t, val, dest = tell_random_hint(self, state)
elif rule == 19:
action, t, val, dest = tell_unknown_card(self, state)
elif rule == 20:
action, t, val, dest = tell_unambiguous(self, state)
elif rule == 21:
action, cardOrder = discard_random_card(self, state)
elif rule == 22:
action, cardOrder = discard_useless_card(self, state)
elif rule == 23:
action, cardOrder = discard_highest_known(self, state)
elif rule == 24:
action, cardOrder = discard_unidentified_card(self, state)
elif rule == 25:
action, cardOrder = discard_probably_useless(self, state, 0.8)
elif rule == 26:
action, cardOrder = discard_probably_useless(self, state, 0.6)
elif rule == 27:
action, cardOrder = discard_probably_useless(self, state, 0.4)
elif rule == 28:
action = random_hint_discard(self, state)
if action[0] == "hint":
t = action[1]
val = action[2]
dest = action[3]
action = action[0]
else:
cardOrder = action[1]
action = action[0]
if action is not None:
break
if action is None: # Should not happen
print("+++ ACTION IS NONE +++")
print("Strategy: ", strategy)
print_state(state)
return action, cardOrder, t, val, dest
###
# Rules
###
def is_playable(color, value, state):
return len(state.tableCards[color]) == value - 1
def is_useless(color, value, state):
if len(state.tableCards[color]) > value - 1: # card already played
return True
# count how many cards with same color have been discarded
discarded = {}
for v in range(1, value):
discarded[v] = 0
for c in state.discardPile:
if c.color == color and c.value < value:
discarded[c.value] += 1
for v in range(1, value):
if v == 1 and discarded[v] == 3:
return True
elif discarded[v] == 2:
return True
return False
def play_if_certain(self, state):
playable_cards = [i for i in range(self.n_cards) if self.cards[i].is_playable(state.tableCards)]
if playable_cards:
return "play", playable_cards[0]
else:
return None, None
# play card if probability of being playable is > prob
def play_probably_safe_card(self, state, prob):
prob_playable_cards = [c.score_playable(state.tableCards) for c in self.cards]
if max(prob_playable_cards) > prob:
return "play", prob_playable_cards.index(max(prob_playable_cards))
else:
return None, None
# play card if probability of being playable is > prob and lives > 1
def play_probably_safe_card_with_lives(self, state, prob):
if state.usedStormTokens >= 2:
return None, None
prob_playable_cards = [c.score_playable(state.tableCards) for c in self.cards]
if max(prob_playable_cards) > prob:
return "play", prob_playable_cards.index(max(prob_playable_cards))
else:
return None, None
def play_recently_hinted(self, state, prob, lives):
if state.usedStormTokens >= 3 - lives or len(self.recent_hints) == 0:
return None, None
highest_prob = 0.0
best_card = None
for h in self.recent_hints:
score = self.cards[h].score_playable(state.tableCards)
if score > highest_prob:
highest_prob = score
best_card = h
if highest_prob >= prob:
return "play", best_card
return None, None
def play_most_probable_if_deck_empty(self, state):
if state.usedStormTokens >= 2:
return None, None
if self.compute_played_cards(state) == 50:
prob_playable_cards = [c.score_playable(state.tableCards) for c in self.cards]
return "play", prob_playable_cards.index(max(prob_playable_cards))
return None, None
# hint about a partially known playable card
def complete_tell_playable_card(self, state):
if state.usedNoteTokens >= 8:
return None, None, None, None
for p in state.players:
if p.name != self.id:
# find player in local list
for loc_p in self.other_players:
if str(loc_p.id) == p.name:
break
# try color hint
available_colors = set([c.color for c in p.hand])
for c in available_colors:
score = loc_p.score_hint("color", c, [i for i in range(len(p.hand)) if p.hand[i].color == c], state)
if score == 100: # hint made card playable!
return "hint", "color", c, p.name
# try value hint
available_values = set([c.value for c in p.hand])
for v in available_values:
score = loc_p.score_hint("value", v, [i for i in range(len(p.hand)) if p.hand[i].value == v], state)
if score == 100:
return "hint", "value", v, p.name
return None, None, None, None
# hint about ones
def tell_about_ones(self, state):
if state.usedNoteTokens >= 8:
return None, None, None, None
max_ones = 0
p_max_ones = ""
for p in state.players:
if p.name != self.id:
# find player in local list
for loc_p in self.other_players:
if str(loc_p.id) == p.name:
break
available_values = [c.value for c in p.hand]
ones = available_values.count(1)
if ones > max_ones:
max_ones = ones
p_max_ones = p.name
if max_ones != 0:
return "hint", "value", 1, p_max_ones
else:
return None, None, None, None
# hint about fives
def tell_about_fives(self, state):
if state.usedNoteTokens >= 8:
return None, None, None, None
max_fives = 0
p_max_fives = ""
for p in state.players:
if p.name != self.id:
# find player in local list
for loc_p in self.other_players:
if str(loc_p.id) == p.name:
break
available_values = [c.value for c in p.hand]
fives = available_values.count(5)
if fives > max_fives:
max_fives = fives
p_max_fives = p.name
if max_fives != 0:
return "hint", "value", 5, p_max_fives
else:
return None, None, None, None
# tell a player about a playable card
def tell_playable_card(self, state):
if state.usedNoteTokens >= 8:
return None, None, None, None
for p in state.players:
if p.name != self.id:
# find player in local list
for loc_p in self.other_players:
if str(loc_p.id) == p.name:
break
for i, c in enumerate(p.hand):
if is_playable(c.color, c.value, state) and not loc_p.cards[i].is_playable(state.tableCards):
if len(loc_p.cards[i].value) != 1:
return "hint", "value", c.value, p.name
if len(loc_p.cards[i].color) != 1:
return "hint", "color", c.color, p.name
return None, None, None, None
# tell a player about a useless card
def tell_useless_card(self, state):
if state.usedNoteTokens >= 8:
return None, None, None, None
for p in state.players:
if p.name != self.id:
# find player in local list
for loc_p in self.other_players:
if str(loc_p.id) == p.name:
break
for i, c in enumerate(p.hand):
if is_useless(c.color, c.value, state):
if len(loc_p.cards[i].value) != 1:
return "hint", "value", c.value, p.name
if len(loc_p.cards[i].color) != 1:
return "hint", "color", c.color, p.name
return None, None, None, None
# tell hint that gives most information
def tell_most_information(self, state):
if state.usedNoteTokens >= 8:
return None, None, None, None
best_score = 0
best_hint = None
for p in state.players:
if p.name != self.id:
# find player in local list
for loc_p in self.other_players:
if str(loc_p.id) == p.name:
break
# try color hint
available_colors = set([c.color for c in p.hand])
for c in available_colors:
score = loc_p.score_hint("color", c, [i for i in range(len(p.hand)) if p.hand[i].color == c], state)
if score > best_score:
best_score = score
best_hint = { "t": "color", "val": c, "dest": p.name }
# try value hint
available_values = set([c.value for c in p.hand])
for v in available_values:
score = loc_p.score_hint("value", v, [i for i in range(len(p.hand)) if p.hand[i].value == v], state)
if score > best_score:
best_score = score
best_hint = { "t": "value", "val": v, "dest": p.name }
if best_hint is not None:
return "hint", best_hint["t"], best_hint["val"], best_hint["dest"]
return None, None, None, None
# give random hint
def tell_random_hint(self, state):
if state.usedNoteTokens >= 8:
return None, None, None, None
p = random.choice(state.players)
while p.name == self.id: # if I choose myself, then choose again
p = random.choice(state.players)
t = random.choice(["color", "value"])
if t == "color":
available_colors = list(set([c.color for c in p.hand]))
v = random.choice(available_colors)
else:
available_values = list(set([c.value for c in p.hand]))
v = random.choice(available_values)
return "hint", t, v, p.name
# tell hint about unknown card
def tell_unknown_card(self, state):
if state.usedNoteTokens >= 8:
return None, None, None, None
p = random.choice(state.players)
while p.name == self.id: # if I choose myself, empty player
p = random.choice(state.players)
# find player in local list
for loc_p in self.other_players:
if str(loc_p.id) == p.name:
break
for i, c in enumerate(loc_p.cards):
if len(c.value) != 1:
return "hint", "value", p.hand[i].value, p.name
if len(c.color) != 1:
return "hint", "color", p.hand[i].color, p.name
return None, None, None, None
# tell card that maximizes probability of playable cards for a player
def tell_unambiguous(self, state):
if state.usedNoteTokens >= 8:
return None, None, None, None
best_score = 0.0
best_hint = None
for p in state.players:
if p.name != self.id:
for loc_p in self.other_players:
if str(loc_p.id) == p.name:
break
# try color hint
available_colors = set([c.color for c in p.hand])
for c in available_colors:
score = loc_p.score_unambiguous_hint("color", c, [i for i in range(len(p.hand)) if p.hand[i].color == c],
state, [i for i in range(len(p.hand)) if is_playable(p.hand[i].color, p.hand[i].value, state)])
if score > best_score:
best_score = score
best_hint = { "t": "color", "val": c, "dest": p.name }
# try value hint
available_values = set([c.value for c in p.hand])
for v in available_values:
score = loc_p.score_unambiguous_hint("value", v, [i for i in range(len(p.hand)) if p.hand[i].value == v],
state, [i for i in range(len(p.hand)) if is_playable(p.hand[i].color, p.hand[i].value, state)])
if score > best_score:
best_score = score
best_hint = { "t": "value", "val": v, "dest": p.name }
if best_hint is not None:
return "hint", best_hint["t"], best_hint["val"], best_hint["dest"]
return None, None, None, None
# discard random card
def discard_random_card(self, state):
if state.usedNoteTokens == 0:
return None, None
return "discard", random.randint(0, len(self.cards) - 1)
# discard useless card (already played or not playable anymore)
def discard_useless_card(self, state):
if state.usedNoteTokens == 0:
return None, None
for i, c in enumerate(self.cards):
if c.is_useless(state):
return "discard", i
return None, None
# discard card with highest known value
def discard_highest_known(self, state):
if state.usedNoteTokens == 0:
return None, None
highest = 0
highest_i = None
for i, c in enumerate(self.cards):
if len(c.value) == 1:
if c.value[0] > highest:
highest = c.value[0]
highest_i = i
if highest_i is None:
return None, None
return "discard", highest_i
# discard unidentified card
def discard_unidentified_card(self, state):
if state.usedNoteTokens == 0:
return None, None
for i, c in enumerate(self.cards):
if len(c.value) > 1 and len(c.color) > 1:
return "discard", i
return None, None
# discard card that is useless with probability > prob
def discard_probably_useless(self, state, prob):
if state.usedNoteTokens == 0:
return None, None
for i, c in enumerate(self.cards):
if c.score_useless(state) > prob:
return "discard", i
return None, None
# random hint or discard -> always returns something!!
def random_hint_discard(self, state):
action = None
if state.usedNoteTokens == 0: # forced to hint
action = "hint"
if state.usedNoteTokens == 8: # forced to discard
action = "discard"
if action is None:
action = random.choice(["hint", "discard"])
if action == "hint":
return tell_random_hint(self, state)
else:
return discard_random_card(self, state)