# Released under the MIT License. See LICENSE for details.#"""Functionality related to game results."""from__future__importannotationsimportcopyimportweakreffromdataclassesimportdataclassfromtypingimportTYPE_CHECKINGfromefro.utilimportasserttypeimportbabasefrombascenev1._teamimportTeam,SessionTeamifTYPE_CHECKING:fromtypingimportSequenceimportbascenev1@dataclassclassWinnerGroup:"""Entry for a winning team or teams calculated by game-results."""score:int|Noneteams:Sequence[bascenev1.SessionTeam]classGameResults:""" Results for a completed game. Category: **Gameplay Classes** Upon completion, a game should fill one of these out and pass it to its bascenev1.Activity.end call. """def__init__(self)->None:self._game_set=Falseself._scores:dict[int,tuple[weakref.ref[bascenev1.SessionTeam],int|None]]={}self._sessionteams:list[weakref.ref[bascenev1.SessionTeam]]|None=(None)self._playerinfos:list[bascenev1.PlayerInfo]|None=Noneself._lower_is_better:bool|None=Noneself._score_label:str|None=Noneself._none_is_winner:bool|None=Noneself._scoretype:bascenev1.ScoreType|None=None
[docs]defset_game(self,game:bascenev1.GameActivity)->None:"""Set the game instance these results are applying to."""ifself._game_set:raiseRuntimeError('Game set twice for GameResults.')self._game_set=Trueself._sessionteams=[weakref.ref(team.sessionteam)forteamingame.teams]scoreconfig=game.getscoreconfig()self._playerinfos=copy.deepcopy(game.initialplayerinfos)self._lower_is_better=scoreconfig.lower_is_betterself._score_label=scoreconfig.labelself._none_is_winner=scoreconfig.none_is_winnerself._scoretype=scoreconfig.scoretype
[docs]defset_team_score(self,team:bascenev1.Team,score:int|None)->None:"""Set the score for a given team. This can be a number or None. (see the none_is_winner arg in the constructor) """assertisinstance(team,Team)sessionteam=team.sessionteamself._scores[sessionteam.id]=(weakref.ref(sessionteam),score)
[docs]defget_sessionteam_score(self,sessionteam:bascenev1.SessionTeam)->int|None:"""Return the score for a given bascenev1.SessionTeam."""assertisinstance(sessionteam,SessionTeam)forscoreinlist(self._scores.values()):ifscore[0]()issessionteam:returnscore[1]# If we have no score value, assume None.returnNone
@propertydefsessionteams(self)->list[bascenev1.SessionTeam]:"""Return all bascenev1.SessionTeams in the results."""ifnotself._game_set:raiseRuntimeError("Can't get teams until game is set.")teams=[]assertself._sessionteamsisnotNoneforteam_refinself._sessionteams:team=team_ref()ifteamisnotNone:teams.append(team)returnteams
[docs]defhas_score_for_sessionteam(self,sessionteam:bascenev1.SessionTeam)->bool:"""Return whether there is a score for a given session-team."""returnany(s[0]()issessionteamforsinself._scores.values())
[docs]defget_sessionteam_score_str(self,sessionteam:bascenev1.SessionTeam)->babase.Lstr:"""Return the score for the given session-team as an Lstr. (properly formatted for the score type.) """frombascenev1._scoreimportScoreTypeifnotself._game_set:raiseRuntimeError("Can't get team-score-str until game is set.")forscoreinlist(self._scores.values()):ifscore[0]()issessionteam:ifscore[1]isNone:returnbabase.Lstr(value='-')ifself._scoretypeisScoreType.SECONDS:returnbabase.timestring(score[1],centi=False)ifself._scoretypeisScoreType.MILLISECONDS:returnbabase.timestring(score[1]/1000.0,centi=True)returnbabase.Lstr(value=str(score[1]))returnbabase.Lstr(value='-')
@propertydefplayerinfos(self)->list[bascenev1.PlayerInfo]:"""Get info about the players represented by the results."""ifnotself._game_set:raiseRuntimeError("Can't get player-info until game is set.")assertself._playerinfosisnotNonereturnself._playerinfos@propertydefscoretype(self)->bascenev1.ScoreType:"""The type of score."""ifnotself._game_set:raiseRuntimeError("Can't get score-type until game is set.")assertself._scoretypeisnotNonereturnself._scoretype@propertydefscore_label(self)->str:"""The label associated with scores ('points', etc)."""ifnotself._game_set:raiseRuntimeError("Can't get score-label until game is set.")assertself._score_labelisnotNonereturnself._score_label@propertydeflower_is_better(self)->bool:"""Whether lower scores are better."""ifnotself._game_set:raiseRuntimeError("Can't get lower-is-better until game is set.")assertself._lower_is_betterisnotNonereturnself._lower_is_better@propertydefwinning_sessionteam(self)->bascenev1.SessionTeam|None:"""The winning SessionTeam if there is exactly one, or else None."""ifnotself._game_set:raiseRuntimeError("Can't get winners until game is set.")winners=self.winnergroupsifwinnersandlen(winners[0].teams)==1:returnwinners[0].teams[0]returnNone@propertydefwinnergroups(self)->list[WinnerGroup]:"""Get an ordered list of winner groups."""ifnotself._game_set:raiseRuntimeError("Can't get winners until game is set.")# Group by best scoring teams.winners:dict[int,list[bascenev1.SessionTeam]]={}scores=[scoreforscoreinself._scores.values()ifscore[0]()isnotNoneandscore[1]isnotNone]forscoreinscores:assertscore[1]isnotNonesval=winners.setdefault(score[1],[])team=score[0]()assertteamisnotNonesval.append(team)results:list[tuple[int|None,list[bascenev1.SessionTeam]]]=list(winners.items())results.sort(reverse=notself._lower_is_better,key=lambdax:asserttype(x[0],int),)# Also group the 'None' scores.none_sessionteams:list[bascenev1.SessionTeam]=[]forscoreinself._scores.values():scoreteam=score[0]()ifscoreteamisnotNoneandscore[1]isNone:none_sessionteams.append(scoreteam)# Add the Nones to the list (either as winners or losers# depending on the rules).ifnone_sessionteams:nones:list[tuple[int|None,list[bascenev1.SessionTeam]]]=[(None,none_sessionteams)]ifself._none_is_winner:results=nones+resultselse:results=results+nonesreturn[WinnerGroup(score,team)forscore,teaminresults]