Write a Program for winning sevens game
Synergistic
I'm not really a Python guy, but wanted to give this a go. This builds the set of playable cards at each turn, and assigns each of them a simple static score. The card with the highest score is played (assuming any playable card exists).
def synergistic(cards_in_hand, played_cards):
def list2dict(lst):
d = {}
for val, suit in lst:
if suit in d:
d[suit].append(val)
else:
d[suit] = [val]
return d
def play_card(card):
cards_in_hand.remove(card)
played_cards.append(card)
hand = list2dict(cards_in_hand)
if not played_cards:
if 7 in hand['hearts']:
play_card([7, 'hearts'])
return (cards_in_hand, played_cards)
table = list2dict(played_cards)
playable_cards = {}
for suit in hand:
if 7 in hand[suit]:
playable_cards[(7, suit)] = -1
if suit not in table:
continue
visible = set(table[suit] + hand[suit])
opp_hand = set(range(1,14)) - visible
highcard = max(table[suit]) + 1
if highcard in hand[suit]:
if highcard+1 in opp_hand:
playable_cards[(highcard, suit)] = 1
else:
playable_cards[(highcard, suit)] = 2
lowcard = min(table[suit]) - 1
if lowcard in hand[suit]:
if lowcard - 1 in opp_hand:
playable_cards[(lowcard, suit)] = 0
else:
playable_cards[(lowcard, suit)] = 1
if not playable_cards:
return (cards_in_hand, played_cards)
best_card = list(max(playable_cards, key=playable_cards.get))
#print(hand, "\n", table, "\n", best_card)
play_card(best_card)
return (cards_in_hand, played_cards)
By the way, the controller seemed to have several issues, including in score calculation and comparison. I made some changes to the controller here, please take a look and update your version if this seems right.
Two things I haven't fixed in the controller:
why is the loop condition
(win2 <= 50) and (win1 <= 100)
? This should probably be symmetrical, it should exit the loop whenever either of the players has 100 consecutive wins.trying some runs of the controller locally, with the same function for both players, Player 2 seems to win most of the time - it can't be inherent to the game since the initial 7H requirement would smooth that out (as @Veskah mentioned in the comments), so, yet undetected controller bugs? Or my player code somehow maintaining state and having a bias this way? Per-game, it's not like Player 2 dominates heavily (from the results output txt), but somehow the overall score per controller run ends up favouring player 2 much more than random (Player 1's total scores are often more than 2x that of Player 2).
Tactical
This ended up different enough that I felt it deserved a separate entry. This one calculates slightly smarter scores, looking not just at the next step but future choices for each player as well, based on the cards they hold. Seems to do a lot better than the "synergistic" version, better enough to beat the mysterious player2
advantage.
def tactical(cards_in_hand, played_cards):
def list2dict(lst):
d = {}
for val, suit in lst:
if suit in d:
d[suit].append(val)
else:
d[suit] = [val]
return d
def play_card(card):
cards_in_hand.remove(card)
played_cards.append(card)
hand = list2dict(cards_in_hand)
if not played_cards:
if 7 in hand['hearts']:
play_card([7, 'hearts'])
return (cards_in_hand, played_cards)
table = list2dict(played_cards)
playable_cards = {}
for suit in hand:
if suit not in table:
if 7 in hand[suit]:
# Do I hold the majority of the cards of this suit?
suit_advantage = (len(hand[suit]) - 6.5)
playable_cards[(7, suit)] = suit_advantage * 20
if 6 in hand[suit] and 8 in hand[suit]:
# opponent can't immediately make use of this
playable_cards[(7, suit)] += 20
continue
visible = set(table[suit] + hand[suit])
opp_hand = set(range(1,14)) - visible
highcard = max(table[suit]) + 1
if highcard in hand[suit]:
advantage = sum(c > highcard for c in hand[suit]) - sum(c > highcard for c in opp_hand)
playable_cards[(highcard, suit)] = advantage * 10
if highcard + 1 in opp_hand:
playable_cards[(highcard, suit)] -= 20
lowcard = min(table[suit]) - 1
if lowcard in hand[suit]:
advantage = sum(c < lowcard for c in hand[suit]) - sum(c < lowcard for c in opp_hand)
playable_cards[(lowcard, suit)] = advantage * 10
if lowcard - 1 in opp_hand:
playable_cards[(lowcard, suit)] -= 20
if not playable_cards:
return (cards_in_hand, played_cards)
best_card = max(playable_cards, key=playable_cards.get)
#print(hand, "\n", table, "\n", best_card, ":", playable_cards[best_card])
play_card(list(best_card))
return (cards_in_hand, played_cards)
SearchBot
import random
suits = ["clubs", "diamonds", "hearts", "spades"]
suit_mul = 14
hearts = suit_mul * suits.index("hearts")
def evaluate(hand):
return sum(min(c % suit_mul, 10) for c in hand)
def rollout(hand0, hand1, runs):
sign = -1
counts = [[0.] * 8 for _ in range(2)]
def counts_index(card):
return 2 * (card // suit_mul) + ((card % suit_mul) > 7)
for card in hand0:
counts[0][counts_index(card)] += 1
for card in hand1:
counts[1][counts_index(card)] += 1
while True:
if not hand1:
return sign * evaluate(hand0)
can_play = []
for i, run in enumerate(runs):
if run[0] == 8 or run[1] == 6:
if run[1] != 6:
run[0] = 7
if run[0] != 8:
run[1] = 7
suit = suit_mul * i
rank = run[0] - 1
next_low = suit + rank
if next_low in hand0:
if next_low - 1 in hand0:
runs[i][0] -= 1
hand0.remove(next_low)
counts[0][counts_index(next_low)] -= 1
can_play = []
break
can_play.append((next_low, 0, -1))
rank = run[1] + 1
next_high = suit + rank
if next_high in hand0:
if next_high + 1 in hand0:
runs[i][1] += 1
hand0.remove(next_high)
counts[0][counts_index(next_high)] -= 1
can_play = []
break
can_play.append((next_high, 1, 1))
if can_play:
weights = [(a - 1) / (a + b - 1) if a + b - 1 > 0 else 0 for a, b in zip(*counts)]
weighted = [(0 if t[0] % suit_mul == 7 else weights[counts_index(t[0])], t) for t in can_play]
weight = sum(t[0] for t in weighted)
total = random.uniform(0, weight)
for (w, (card, index, direction)) in weighted:
total -= w
if total <= 0:
break
hand0.remove(card)
counts[0][counts_index(card)] -= 1
runs[card // suit_mul][index] += direction
hand0, hand1 = hand1, hand0
counts[0], counts[1] = counts[1], counts[0]
sign *= -1
def select_move(hand0, hand1, runs, n=40):
if hearts + 7 in hand0:
return hearts + 7
if hearts + 7 in hand1:
return
can_play = []
for i, run in enumerate(runs):
suit = suit_mul * i
rank = run[0] - 1
next_low = suit + rank
if next_low in hand0:
if next_low - 1 in hand0:
return next_low
can_play.append((next_low, 0, -1))
rank = run[1] + 1
next_high = suit + rank
if next_high in hand0:
if next_high + 1 in hand0:
return next_high
can_play.append((next_high, 1, 1))
if not can_play:
return
if len(can_play) == 1:
return can_play[0][0]
scores = [0 for _ in can_play]
for i, (card, index, sign) in enumerate(can_play):
hand0_copy = set(hand0)
runs_copy = [list(r) for r in runs]
hand0_copy.remove(card)
runs_copy[card // suit_mul][index] += sign
for j in range(n):
scores[i] -= rollout(set(hand1), set(hand0_copy), [list(r) for r in runs_copy])
return can_play[scores.index(max(scores))][0]
def search(cards_in_hand, played_cards):
def play_card(c):
if c is None:
return
suit = suits[c // suit_mul]
rank = c % suit_mul
for i, card in enumerate(cards_in_hand):
if card[0] == rank and card[1] == suit:
del cards_in_hand[i]
played_cards.append([rank, suit])
return
assert(False)
hand = set(suit_mul * suits.index(s) + v for v, s in cards_in_hand)
played = set(suit_mul * suits.index(s) + v for v, s in played_cards)
opponent_hand = (suit_mul * s + v for v in range(1, 14) for s in range(4))
opponent_hand = set(c for c in opponent_hand if c not in hand and c not in played)
runs = [[8, 6] for _ in range(4)]
for i, run in enumerate(runs):
suit = suit_mul * i
while suit + run[0] - 1 in played:
run[0] -= 1
while suit + run[1] + 1 in played:
run[1] += 1
card = select_move(hand, opponent_hand, runs)
play_card(card)
return cards_in_hand, played_cards