-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsplendorgame.py
More file actions
112 lines (96 loc) · 4.37 KB
/
splendorgame.py
File metadata and controls
112 lines (96 loc) · 4.37 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
"""Main game logic for Splendor board game"""
from itertools import combinations, cycle
from random import shuffle
from card import Card
from gemdict import GemDict, GOLD_GEM
from noble import Noble
from player import HumanPlayer, AIPlayer
from rules import Rules
class SplendorGame:
def __init__(self, human, ai, win_score):
self.rules = Rules(human + ai, win_score)
self.nobles = Noble.get_nobles(self.rules.NOBLE_AMOUNT)
self.decks, self.cards = Card.get_cards(self.rules.OPEN_CARDS_PER_LEVEL)
self.gems = GemDict.from_rules(self.rules.MAX_GEMS_IN_STACK, self.rules.MAX_GOLD)
self.players = [
HumanPlayer(f"Player{number + 1}") for number in range(human)] + [
AIPlayer(f"AI{number + 1}") for number in range(ai)
]
shuffle(self.players)
self.turn = 1
self._playerIterator = cycle(self.players)
self.currentPlayer = next(self._playerIterator)
def __str__(self):
width = 60
st = "\n"
st += f"TURN {self.turn}".center(width, "=") + "\n\n"
st += "# color value price\n"
for level, levelCards in reversed(list(enumerate(self.cards))):
st += f"Level {level+1} Cards ({len(self.decks[level])} left)".center(width, "-") + "\n"
st += '\n'.join(f"{idx}: {card}" for idx, card in enumerate(levelCards, 1))
st += '\n'
st += '\n'
st += f"Target: {self.rules.WIN_SCORE}\n"
st += f"Nobles: {' '.join(str(noble) for noble in self.nobles)}\n"
st += f"Board gems: {self.gems}\n\n"
for player in self.players:
if player == self.currentPlayer:
st += f"--> {player}\n"
else:
st += f"{player}\n"
st += "".center(width, "=")
st += "\n"
return st
def new_table_card(self, level, pos):
"""Put new card on table if player reserved/purchased card"""
self.cards[level][pos] = self.decks[level].pop() if self.decks[level] else Card()
def move_to_next(self):
"""Move to next player. If every player played their turn, increase the turn count"""
if self.currentPlayer == self.players[-1]:
self.turn += 1
self.currentPlayer = next(self._playerIterator)
def check_win(self):
"""Check the win condition after each turn"""
return (
self.currentPlayer == self.players[-1] and
any(player.score >= self.rules.WIN_SCORE for player in self.players)
)
def players_sorted_by_score(self):
"""Return the list of players sorted by score. If equal, who has fewer cards and hand cards is ahead"""
return sorted(
self.players,
key=lambda p: (p.score, -p.purchasedCards.total(), -len(p.handCards)),
reverse=True
)
def update_elo(self, score=1, k=40, m=400):
"""Update Elo score for each player"""
standings = self.players_sorted_by_score()
# Update k factor according to the player count
k = k / (len(standings) - 1)
for (player1, player2) in combinations(standings, 2):
expected = 1 / (1 + 10 ** ((player2.account.elo - player1.account.elo) / m))
gain = round(k * (score - expected))
player1.gain += gain
player2.gain -= gain
# Update player account files
for pos, player in enumerate(standings, 1):
player.account.elo += player.gain
player.account.totalGames += 1
if pos == 1:
player.account.win += 1
else:
player.account.lose += 1
player.account.save()
player.account.update_standings()
def print_results(self):
print("RESULTS:")
for pos, player in enumerate(self.players_sorted_by_score(), 1):
print(f"{pos}) {player.name}: {player.score} points, Current Elo: {player.account.elo} ({player.gain:+})")
def available_combinations(self):
"""Available combinations of gems which can be taken"""
available_gems = sorted(+self.gems)
available_gems.remove(GOLD_GEM)
available_sets = len(available_gems)
comb_of_3 = set(map("".join, combinations(available_gems, min(3, available_sets))))
comb_of_double = {gem * 2 for gem, count in self.gems.items() if (gem != GOLD_GEM) and (count >= 4)}
return comb_of_3 | comb_of_double