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 | class Game:
def __init__(self, *,
notesheet: list[Sequence[Note]] = None,
end_conditions: list[EndCondition] = None,
miscommunication_func = miscommunication_rule,
interception_func = interception_rule,
tiebreaker_func = interception_miscommunication_diff_tiebreaker
):
"""Initialize the game.
Args:
notesheet (list[Sequence[Note]], optional): The list of notes for each round. Defaults to None and is initilized to an empty sequence.
end_conditions (list[EndCondition], optional): The list of end conditions for the game. Defaults to None and is initialized to the official end conditions.
miscommunication_func (function, optional): The function to calculate miscommunications. Defaults to miscommunication_rule.
interception_func (function, optional): The function to calculate interceptions. Defaults to interception_rule.
tiebreaker_func (function, optional): The tiebreaker function to decide the winner. Defaults to interception_miscommunication_diff_tiebreaker.
"""
self.notesheet = []
self.end_conditions = end_conditions if end_conditions is not None else OfficialEndConditions()
self.miscommunication_func = miscommunication_func
self.interception_func = interception_func
self.tiebreaker_func = tiebreaker_func
self._data = GameData()
# initialize game data based on round notes in notesheet
if notesheet is None:
return
for round_notes in notesheet:
if self.game_over():
break
self.process_round_notes(round_notes)
@property
def data(self) -> GameData:
"""Get a copy of the game data.
Returns:
GameData: A copy of the current game data.
"""
# data shouldn't be altered for simulating plies or viewing round results
# so accessing yields a copy to minimize unintended side effects
return self._data.copy()
def process_round_notes(self, round_notes: list[Note]):
"""Process the notes for a round. The GameData is updated according to the rules and round results, and the round_notes are then added to the notesheet.
Args:
round_notes (list[Note]): The list of notes for the current round.
"""
for team_name, note in enumerate(round_notes):
opponent = not team_name
self._data.miscommunications[team_name] += self.miscommunication_func(note, self._data)
self._data.interceptions[opponent] += self.interception_func(note, self._data)
self._data.rounds_played += 1
self.notesheet.append(round_notes)
def game_over(self, game_data: GameData = None) -> bool:
"""Check if the game is over based on the provided game data.
Args:
game_data (GameData, optional): The game data to check. If not provided, internal game data will be used.
Returns:
bool: True if the game is over, False otherwise.
"""
# if called without an argument, use internal data
game_data = game_data if game_data is not None else self._data
return any(end_condition.game_over(game_data) for end_condition in self.end_conditions)
def winner(self, game_data: GameData = None) -> Optional[TeamName]:
"""Determine the winner of the game based on the provided game data.
Args:
game_data (GameData, optional): The game data to check. If not provided, internal game data will be used.
Returns:
Optional[int]: The team name of the winner or None if there is no winner (tie or the game is not over).
"""
# if called without an argument, use internal data
game_data = game_data if game_data is not None else self._data
# if the game is not over, there is no winner
if not self.game_over(game_data):
return None
candidate_winners = [end_condition.winner(game_data) for end_condition in self.end_conditions]
unique_winners = {candidate for candidate in candidate_winners if candidate is not None}
losers = [end_condition.loser(game_data) for end_condition in self.end_conditions]
corresponding_winners = {not loser for loser in losers if loser is not None}
unique_winners.update(corresponding_winners)
# if the game the game ending conditions decide exactly one winner, they are the winner
if len(unique_winners) == 1:
return TeamName(unique_winners.pop())
# otherwise, decide by tiebreaker (can return None to indicate tie anyway)
return self.tiebreaker_func(game_data)
|