-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathai.py
More file actions
222 lines (192 loc) · 8.75 KB
/
ai.py
File metadata and controls
222 lines (192 loc) · 8.75 KB
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
# -*- coding: utf-8 -*-
import random
import card
import operator
class AI:
def __init__(self, complexity):
self.complexity = complexity
def suggest_move(self, round, trick, player):
''' A dispatcher function that will play the AI at the appropriate level '''
if self.complexity == 0:
return self._suggest_move_0(round, trick, player)
elif self.complexity == 1:
return self._suggest_move_1(round, trick, player)
else:
print "Invalid complexity provided"
def suggest_pass(self, player):
if self.complexity == 0:
return self._suggest_pass_0(player)
elif self.complexity == 1:
return self._suggest_pass_1(player)
else:
print "Invalid complexity provided"
def _suggest_pass_0(self, player):
return random.choice(player.hand)
def _suggest_pass_1(self, player):
passes_left = len(player.hand) - 10
suitOrder = {"♣": 1, "♦": 7, "♥": 11, "♠": 14}
holds_queen = player.hand.has_queen()
# Increase the passing priority of a suit if it can be drained easily
for k, v in suitOrder.iteritems():
count = player.hand.suitCount(k)
if count <= 2:
suitOrder[k] += (4 * passes_left * (3 - count))
choices = {}
for p_card in player.hand:
value = suitOrder.get(p_card.suit) + p_card.getWeight()
#we should probably hold onto the queen
if holds_queen:
if p_card.suit == "♠" and p_card.getWeight() <= 12:
count = self.cards_below_qspades(player)
value -= 5 * count
choices[p_card] = value
return max(choices, key=choices.get)
def threat(self, round, trick, player, card_choice):
# Probability that a point card will be played
other_cards = float(len(round.cards_in_play) - len(player.hand))
if other_cards == 0:
return 0
queen_remaining = self.queen_remaining(round, player)
hearts_remaining = self.suit_remaining(round, player, '♥')
p_queen = queen_remaining / other_cards
p_heart = hearts_remaining / other_cards
moves_after = max(0.01, trick.moves_left() - 1)
win_chance = max(0.01, self.chance_of_winning(round, trick, player, card_choice))
d_threat = self.drained_threat(round, trick, player, card_choice)
win_by = self.win_by(trick, card_choice)
threat = (((1 * p_heart) + (13 * p_queen) + d_threat + win_by) * moves_after) * win_chance
return threat
def win_by(self, trick, card_choice):
result = 0
current_winning = trick.current_winning_card()
if current_winning == None:
result = card_choice.getWeight()
elif current_winning.suit == card_choice.suit:
if card_choice.getWeight() < current_winning.getWeight():
return 0
else:
result = card_choice.getWeight() - current_winning.getWeight()
else:
return 0
return result/13.0
def drained_threat(self, round, trick, player, card_choice):
drained_threat = 0
current_position = trick.players.index(player) + 1
next_players = trick.players[current_position:]
for opponent in next_players:
if opponent in player.drained_players:
for drained_suit in player.drained_players[opponent]:
if drained_suit == card_choice.suit:
if player.debug:
print "{0} is drained of {1} so playing {2} of {3} might not be good" \
.format(opponent.name, drained_suit, card_choice.value, card_choice.suit)
drained_threat += 1.0/len(next_players)
return drained_threat
def queen_remaining(self, round, player):
''' Returns true if the queen of spades is in play and the player does not have it '''
queen_spades = card.Card('♠', 'Q')
if queen_spades in player.hand:
return False
else:
return (queen_spades in round.cards_in_play)
def cards_below_qspades(self, player):
count = 0
for card in player.hand:
if card.suit == '♠':
count += 1
return count
def suit_remaining(self, round, player, suit):
''' Retrieves the number of point cards that could be played by other players '''
count = 0
for card in round.cards_in_play:
count += bool(card.suit == suit)
for card in player.hand:
count -= bool(card.suit == suit)
return count
def _suggest_move_0(self, round, trick, player):
return player.hand.getRandomCard()
def _suggest_move_1(self, round, trick, player):
choices = {}
for choice in player.hand:
val = self._heuristic(round, trick, player, choice)
# Don't consider illegal moves
if val == None:
continue
choices[choice] = val
max_choice = self.max_heuristic(choices)
min_choice = self.min_heuristic(choices)
max_win_chance = self.chance_of_winning(
round, trick, player, max_choice)
min_win_chance = self.chance_of_winning(
round, trick, player, min_choice)
if min_win_chance >= max_win_chance or round.firstTrick:
if player.debug:
print "PLAYING {0} of {1} : maximizing heuristic" \
.format(max_choice.value, max_choice.suit)
return max_choice
else:
if player.debug:
print "PLAYING {0} of {1} : minimizing heuristic" \
.format(min_choice.value, min_choice.suit)
return min_choice
def max_heuristic(self, choices):
temp = max(choices.iteritems(), key=operator.itemgetter(1))
choices = [k for k, v in choices.items() if abs(v - temp[1]) <= 0.03
and k.getWeight() >= temp[0].getWeight()]
best = max(choices, key=lambda p: p.getWeight())
return best
def min_heuristic(self, choices):
temp = min(choices.iteritems(), key=operator.itemgetter(1))
choices = [k for k, v in choices.items() if v == temp[1]
and k.getWeight() >= temp[0].getWeight()]
best = max(choices, key=lambda p: p.getWeight())
return best
def _heuristic(self, round, trick, player, card_choice):
''' Used by bots to decide which card to play (card with min(heuristic) ) '''
if not trick.isLegalMove(player, card_choice):
return None
current_trick_value = (trick.value() / 26.0)
current_card_value = (card_choice._getPoints() / 13.0)
threat = self.threat(round, trick, player, card_choice)
score = (current_card_value + current_trick_value + threat)
if player.debug:
print "{0} of {1} : heuristic {2}" \
.format(card_choice.value, card_choice.suit, score)
return score
def cards_that_can_beat(self, round, player, card_choice):
''' Returns the number of cards that are still in play that can beat card_choice in a trick '''
count = 0
for card in round.cards_in_play:
if card.suit == card_choice.suit:
if card.getWeight() > card_choice.getWeight():
count += 1
for card in player.hand:
if card.suit == card_choice.suit:
if card.getWeight() > card_choice.getWeight():
count -= 1
return count
def chance_of_winning(self, round, trick, player, card_choice):
''' Returns the probability of winning the trick after playing card_choice '''
winning_cards = 0
current_winning_card = trick.current_winning_card()
if current_winning_card == None:
# Find number of cards that can beat this card
winning_cards = self.cards_that_can_beat(
round, player, card_choice)
elif current_winning_card.suit == card_choice.suit:
# If their card is higher than mine, I can't win the trick.
if card_choice.getWeight() < current_winning_card.getWeight():
return 0.0
else:
winning_cards = self.cards_that_can_beat(
round, player, card_choice)
else:
# My card is not the same suit as the winning card
return 0
moves_after = trick.moves_left() - 1
num_suit_left = self.suit_remaining(round, player, card_choice.suit)
if num_suit_left == 0:
return 1.0
lose_chance = float(winning_cards) / num_suit_left
result = (1 - lose_chance) ** moves_after
return result