|
| 1 | +from typing import Dict, List, Mapping, NamedTuple, Optional |
| 2 | + |
| 3 | +from sr.comp.comp import SRComp |
| 4 | +from sr.comp.match_period import Match, MatchSlot |
| 5 | +from sr.comp.scores import BaseScores, LeaguePosition |
| 6 | +from sr.comp.types import ArenaName, GamePoints, MatchNumber, TLA |
| 7 | + |
| 8 | +__description__ = "Show the game and league points achieved for each match" |
| 9 | +DISPLAYED_ZONES = 2 |
| 10 | + |
| 11 | + |
| 12 | +class MatchCorner(NamedTuple): |
| 13 | + tla: Optional[TLA] |
| 14 | + ranking: Optional[LeaguePosition] |
| 15 | + game: Optional[GamePoints] |
| 16 | + league: Optional[int] |
| 17 | + |
| 18 | + |
| 19 | +class MatchResult(NamedTuple): |
| 20 | + num: MatchNumber |
| 21 | + arena: ArenaName |
| 22 | + display_name: str |
| 23 | + corners: Dict[int, MatchCorner] |
| 24 | + |
| 25 | + |
| 26 | +def collect_match_info(comp: SRComp, match: Match) -> MatchResult: |
| 27 | + from sr.comp.match_period import MatchType |
| 28 | + from sr.comp.scores import degroup |
| 29 | + |
| 30 | + score_data: BaseScores |
| 31 | + league_points: Mapping[TLA, Optional[int]] |
| 32 | + game_points: Mapping[TLA, Optional[GamePoints]] |
| 33 | + ranking: Mapping[TLA, Optional[LeaguePosition]] |
| 34 | + |
| 35 | + if match.type == MatchType.knockout: |
| 36 | + score_data = comp.scores.knockout |
| 37 | + elif match.type == MatchType.tiebreaker: |
| 38 | + score_data = comp.scores.tiebreaker |
| 39 | + elif match.type == MatchType.league: |
| 40 | + score_data = comp.scores.league |
| 41 | + |
| 42 | + match_id = (match.arena, match.num) |
| 43 | + |
| 44 | + if match_id in score_data.game_points: |
| 45 | + league_points = score_data.ranked_points[match_id] |
| 46 | + game_points = score_data.game_points[match_id] |
| 47 | + ranking = degroup(score_data.game_positions[match_id]) |
| 48 | + else: |
| 49 | + league_points = {} |
| 50 | + game_points = {} |
| 51 | + ranking = {} |
| 52 | + for team in match.teams: |
| 53 | + if team is not None: |
| 54 | + league_points[team] = None |
| 55 | + game_points[team] = None |
| 56 | + ranking[team] = None |
| 57 | + |
| 58 | + corner_data: Dict[int, MatchCorner] = {} |
| 59 | + |
| 60 | + for corner, team in enumerate(match.teams): |
| 61 | + match_id = (match.arena, match.num) |
| 62 | + |
| 63 | + if team: # corner occupied |
| 64 | + corner_data[corner] = MatchCorner( |
| 65 | + tla=team, |
| 66 | + ranking=ranking[team], |
| 67 | + game=game_points[team], |
| 68 | + league=league_points[team], |
| 69 | + ) |
| 70 | + else: |
| 71 | + corner_data[corner] = MatchCorner( |
| 72 | + tla=None, |
| 73 | + ranking=None, |
| 74 | + game=None, |
| 75 | + league=None, |
| 76 | + ) |
| 77 | + |
| 78 | + return MatchResult( |
| 79 | + num=match.num, |
| 80 | + arena=match.arena, |
| 81 | + display_name=match.display_name, |
| 82 | + corners=corner_data, |
| 83 | + ) |
| 84 | + |
| 85 | + |
| 86 | +def match_index(matches: List[MatchSlot], match_num: int) -> int: |
| 87 | + "Returns the index of the first slot that contains the given match number" |
| 88 | + for idx, slots in enumerate(matches): |
| 89 | + for match in slots.values(): |
| 90 | + if match.num == match_num: |
| 91 | + return idx |
| 92 | + |
| 93 | + # if no match is found use the last one |
| 94 | + return idx |
| 95 | + |
| 96 | + |
| 97 | +def generate_displayed_headings(num_corners: int) -> List[str]: |
| 98 | + displayed_heading = ["Match"] |
| 99 | + |
| 100 | + for _ in range(num_corners): |
| 101 | + displayed_heading.append("Zone") |
| 102 | + displayed_heading.append("TLA") |
| 103 | + displayed_heading.append("Rank") |
| 104 | + displayed_heading.append("Game") |
| 105 | + displayed_heading.append("League") |
| 106 | + |
| 107 | + return displayed_heading |
| 108 | + |
| 109 | + |
| 110 | +def generate_displayed_match(match: MatchResult, num_corners: int) -> List[List[str]]: |
| 111 | + displayed_match = [] |
| 112 | + displayed_corners = [] |
| 113 | + |
| 114 | + for zone, (tla, ranking, game, league) in match.corners.items(): |
| 115 | + displayed_corner: List[str] = [] |
| 116 | + |
| 117 | + displayed_corner.append(str(zone)) |
| 118 | + if tla is not None: |
| 119 | + displayed_corner.append(tla) |
| 120 | + displayed_corner.append("??" if ranking is None else str(ranking)) |
| 121 | + displayed_corner.append("??" if game is None else str(game)) |
| 122 | + displayed_corner.append("??" if league is None else str(league)) |
| 123 | + else: |
| 124 | + displayed_corner.extend(['', '', '', '']) |
| 125 | + |
| 126 | + displayed_corners.append(displayed_corner) |
| 127 | + |
| 128 | + # wrap the number of zones to the DISPLAYED_ZONES constant |
| 129 | + for corner in range(0, num_corners, DISPLAYED_ZONES): |
| 130 | + # first row displays the match and arena information, |
| 131 | + # any extra rows leave this field blank |
| 132 | + if corner == 0: |
| 133 | + match_row = [f"{match.display_name} in {match.arena}"] |
| 134 | + else: |
| 135 | + match_row = [""] |
| 136 | + |
| 137 | + for idx in range(DISPLAYED_ZONES): |
| 138 | + try: |
| 139 | + match_row.extend(displayed_corners[corner + idx]) |
| 140 | + except IndexError: |
| 141 | + # pad the number of corners out to a multiple of DISPLAYED_ZONES |
| 142 | + match_row.extend(['', '', '', '']) |
| 143 | + |
| 144 | + displayed_match.append(match_row) |
| 145 | + |
| 146 | + return displayed_match |
| 147 | + |
| 148 | + |
| 149 | +def command(settings): |
| 150 | + import os.path |
| 151 | + |
| 152 | + from tabulate import tabulate |
| 153 | + |
| 154 | + comp = SRComp(os.path.realpath(settings.compstate)) |
| 155 | + |
| 156 | + match_results: List[MatchResult] = [] |
| 157 | + |
| 158 | + filter_tla = settings.tla |
| 159 | + skip_filter = True |
| 160 | + |
| 161 | + if filter_tla is not None: |
| 162 | + skip_filter = False |
| 163 | + # validate TLA exists |
| 164 | + if filter_tla not in comp.teams.keys(): |
| 165 | + exit(f"TLA {filter_tla!r} not recognised") |
| 166 | + |
| 167 | + if not settings.all and skip_filter: |
| 168 | + # get the index of the last scored match |
| 169 | + end_match = match_index( |
| 170 | + comp.schedule.matches, |
| 171 | + comp.scores.last_scored_match, |
| 172 | + ) + 1 # include last scored match in results |
| 173 | + scan_matches = comp.schedule.matches[ |
| 174 | + max(0, end_match - int(settings.limit)):end_match |
| 175 | + ] |
| 176 | + else: |
| 177 | + scan_matches = comp.schedule.matches |
| 178 | + |
| 179 | + for slots in scan_matches: |
| 180 | + match_results.extend( |
| 181 | + collect_match_info(comp, match) |
| 182 | + for match in slots.values() |
| 183 | + if filter_tla in match.teams or skip_filter |
| 184 | + ) |
| 185 | + |
| 186 | + if len(match_results) == 0: |
| 187 | + exit("No matches found for current filters") |
| 188 | + |
| 189 | + num_teams_per_arena = comp.num_teams_per_arena |
| 190 | + |
| 191 | + displayed_matches: List[List[str]] = [] |
| 192 | + |
| 193 | + for match in match_results: |
| 194 | + displayed_matches.extend(generate_displayed_match(match, num_teams_per_arena)) |
| 195 | + |
| 196 | + print(tabulate( |
| 197 | + displayed_matches, |
| 198 | + headers=generate_displayed_headings(DISPLAYED_ZONES), |
| 199 | + tablefmt='pretty', |
| 200 | + colalign=( |
| 201 | + ('center',) + ('right', 'center', 'right', 'right', 'right') * DISPLAYED_ZONES |
| 202 | + ), |
| 203 | + )) |
| 204 | + |
| 205 | + |
| 206 | +def add_subparser(subparsers): |
| 207 | + parser = subparsers.add_parser( |
| 208 | + 'show-match-scores', |
| 209 | + help=__description__, |
| 210 | + description=__description__, |
| 211 | + ) |
| 212 | + parser.add_argument( |
| 213 | + 'compstate', |
| 214 | + help="competition state repo", |
| 215 | + ) |
| 216 | + parser.add_argument( |
| 217 | + 'tla', |
| 218 | + nargs='?', |
| 219 | + help="filter to matches containing this TLA (ignores --limit)", |
| 220 | + ) |
| 221 | + group = parser.add_mutually_exclusive_group() |
| 222 | + group.add_argument( |
| 223 | + '--all', |
| 224 | + action='store_true', |
| 225 | + help="show all matches (overrides --limit)", |
| 226 | + ) |
| 227 | + group.add_argument( |
| 228 | + '--limit', |
| 229 | + default=15, |
| 230 | + help="how many recently scored matches to show (default: %(default)s)", |
| 231 | + ) |
| 232 | + parser.set_defaults(func=command) |
0 commit comments