Skip to content

Commit

Permalink
Team banner rendering with full scoreline and options
Browse files Browse the repository at this point in the history
  • Loading branch information
ty-porter committed Mar 14, 2024
1 parent eaa22d5 commit e0a1e0b
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 31 deletions.
40 changes: 25 additions & 15 deletions rewrite/data/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from data.update_status import UpdateStatus
from data import status as GameState
from data.team import TeamType

from utils import logger as ScoreboardLogger
from utils import value_at_keypath
Expand Down Expand Up @@ -122,14 +123,14 @@ def datetime(self):
def winning_team(self):
if self.status == GameState.FINAL:
if self.home_score() > self.away_score():
return "home"
return TeamType.HOME
if self.home_score() < self.away_score():
return "away"
return TeamType.AWAY

return None

def losing_team(self):
opposite = {"home": "away", "away": "home"}
opposite = {TeamType.HOME: TeamType.AWAY, TeamType.AWAY: TeamType.HOME}

return opposite.get(self.winning_team(), None)

Expand Down Expand Up @@ -172,56 +173,65 @@ def format_id(ID):
TODO: Make this dynamic somehow?
"""

def home_runs(self):
return self.__runs(TeamType.HOME)

def away_runs(self):
return self.__runs(TeamType.AWAY)

def __runs(self, variant):
return value_at_keypath(self.data, f"liveData.linescore.teams.{variant}").get("runs", 0)

def home_hits(self):
return self.__hits("home")
return self.__hits(TeamType.HOME)

def away_hits(self):
return self.__hits("away")
return self.__hits(TeamType.AWAY)

def __hits(self, variant):
return value_at_keypath(self.data, f"liveData.linescore.teams.{variant}").get("hits", 0)

def home_errors(self):
return self.__errors("home")
return self.__errors(TeamType.HOME)

def away_errors(self):
return self.__errors("away")
return self.__errors(TeamType.AWAY)

def __errors(self, variant):
return value_at_keypath(self.data, f"liveData.linescore.teams.{variant}").get("errors", 0)

def home_score(self):
return self.__score("home")
return self.__score(TeamType.HOME)

def away_score(self):
return self.__score("away")
return self.__score(TeamType.AWAY)

def __score(self, variant):
return value_at_keypath(self.data, f"liveData.linescore.teams.{variant}").get("runs", 0)

def home_name(self):
return self.__name("home")
return self.__name(TeamType.HOME)

def away_name(self):
return self.__name("away")
return self.__name(TeamType.AWAY)

def __name(self, variant):
return value_at_keypath(self.data, f"gameData.teams.{variant}").get("teamName", "")

def home_abbreviation(self):
return self.__abbreviation("home")
return self.__abbreviation(TeamType.HOME)

def away_abbreviation(self):
return self.__abbreviation("away")
return self.__abbreviation(TeamType.AWAY)

def __abbreviation(self, variant):
return value_at_keypath(self.data, f"gameData.teams.{variant}").get("abbreviation", "")

def home_record(self):
return self.__record("home")
return self.__record(TeamType.HOME)

def away_record(self):
return self.__record("away")
return self.__record(TeamType.AWAY)

def __record(self, variant):
return value_at_keypath(self.data, f"gameData.teams.{variant}.record")
4 changes: 2 additions & 2 deletions rewrite/data/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ def update(self):

return UpdateStatus.FAIL

# TODO: Filter to target game
self.game = self.games[0]
self.game = self.games[self.__next_game_index()]

self.__request_transition_to_game(self.game)

Expand Down Expand Up @@ -71,6 +70,7 @@ def __request_transition_to_game(self, game):
self.data.request_next_screen(next_screen, game=game)

def __next_game_index(self):
# TODO: Some sort of intelligent decisioning here beyond i + 1.
if len(self.games) > 0 and self.game in self.games:
i = self.games.index(self.game)

Expand Down
6 changes: 6 additions & 0 deletions rewrite/data/team.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from enum import StrEnum


class TeamType(StrEnum):
AWAY = "away"
HOME = "home"
127 changes: 115 additions & 12 deletions rewrite/screens/components/team.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from driver import graphics

from data.team import TeamType

from utils.graphics import DrawRect

from collections import namedtuple


class TeamBanner:
def __init__(self, kind, screen):
Expand All @@ -10,17 +14,21 @@ def __init__(self, kind, screen):
# Can reach into the screen to access config for that screen
self.screen = screen

# breakpoint()
self.team_name = self.game.home_name() if kind == "home" else self.game.away_name()
self.team_abbreviation = self.game.home_abbreviation() if kind == "home" else self.game.away_abbreviation()
self.team_name = self.game.home_name() if kind == TeamType.HOME else self.game.away_name()
self.team_abbreviation = (
self.game.home_abbreviation() if kind == TeamType.HOME else self.game.away_abbreviation()
)

self.__load_colors()

# Constructor to build scoreline objects for both teams
self._scoreline = namedtuple("Scoreline", ("runs", "hits", "errors"))

def render(self):
self.__render_background()
self.__render_accents()
self.__render_team_name()
self.__render_score()
self.__render_scoreline()

def __render_background(self):
coords = self.layout.coords(f"teams.background.{self.kind}")
Expand All @@ -32,22 +40,76 @@ def __render_accents(self):

DrawRect(self.canvas, coords.x, coords.y, coords.width, coords.height, self.accent_color)

def __render_score(self):
pass

def __render_team_name(self):
keypath = f"teams.name.{self.kind}"

coords = self.layout.coords(keypath)
font, font_size = self.layout.font_for(keypath)
text = self.__team_name()

graphics.DrawText(self.canvas, font, coords.x, coords.y, self.text_color, text)

def __render_scoreline(self):
coords = self.layout.coords(f"teams.runs.{self.kind}")
font, font_size = self.layout.font_for(f"teams.runs.{self.kind}")

for c, pos in self.__calculate_scoreline_positions(coords.x, font_size):
graphics.DrawText(self.canvas, font, pos, coords.y, self.text_color, c)

# TODO: Trunc on long RHE
if self.config.full_team_names:
text = "{:13s}".format(self.team_name)
def __calculate_scoreline_positions(self, start, font_size):
"""
Returns an array of tuples containing a character and position (c, p) for values in the scoreline.
Character tuples will be arranged in format runs-hits-errors (RHE).
Each value is placed into a column with its value right-justified if required.
This class is responsible for rendering a single team's banner, but guarantees the column will align with the opposite team's columns.
Order of the characters in the returned array is not guaranteed. They should be sorted on position if required.
"""
scoreline_options = self.layout.coords("teams.runs.runs_hits_errors")

if scoreline_options.show:
# Assumes scoreline constructor will have fields in R, H, E order
home = [str(value) for value in self.scoreline[TeamType.HOME]]
away = [str(value) for value in self.scoreline[TeamType.AWAY]]
else:
text = "{:3s}".format(self.team_abbreviation.upper())
home = [str(self.scoreline[TeamType.HOME].runs)]
away = [str(self.scoreline[TeamType.AWAY].runs)]

graphics.DrawText(self.canvas, font, coords.x, coords.y, self.text_color, text)
pairs = list(zip(home, away))
positions = []

# Need to manage the position pointer and iterator on our own.
x = start
i = 0

# Characters are drawn right to left
for pair in pairs[::-1]:
hv, av = pair

# Ensure the algorithm operates on strings of equal length, otherwise add padding left.
hv = hv.rjust(max(len(hv), len(av)), " ")
av = av.rjust(max(len(hv), len(av)), " ")

target = hv if self.kind == TeamType.HOME else av

# Continue to draw right to left
for c in target[::-1]:
# If compression is set, shift pointer back to the left
if i > 0 and scoreline_options.compress_digits:
x += 1

# Append a pair (character, position) if not blank
if c != " ":
positions.append((c, x - font_size[0] * (i + 1)))

i += 1

# Shift position pointer to the right by the spacing setting.
# BUG: Why is this a setting but is possibly off-by-one?
x -= scoreline_options.spacing - 1

return positions

def __load_colors(self):
team_key = self.team_abbreviation.lower()
Expand All @@ -64,6 +126,47 @@ def __color(self, keypath, default_keypath):

return self.colors.team_graphics_color(default_keypath)

def __team_name(self):
"""
Returns either the team's actual name or the abbreviated name based on several criteria.
Long names are returned if:
1. `full_team_names` config setting is enabled
2. Matrix width is greater than 32px
3. `short_team_names_for_runs_hits` config setting is
a. Disabled
-OR-
b. Enabled, but any team's runs and hits is less than 9 (two digits)
Otherwise, short names are returned.
"""
short_name = "{:3s}".format(self.team_abbreviation.upper())
long_name = "{:13s}".format(self.team_name)

enabled = self.config.full_team_names
matrix_size_supported = self.canvas.width > 32
overflow_prevention_enabled = self.config.short_team_names_for_runs_hits

if not enabled or not matrix_size_supported:
return short_name

if not overflow_prevention_enabled:
return long_name

# Either team can possibly overflow
for kind in [TeamType.HOME, TeamType.AWAY]:
if self.scoreline[kind].hits > 9 or self.scoreline[kind].runs > 9:
return short_name

return long_name

@property
def scoreline(self):
return {
TeamType.HOME: self._scoreline(self.game.home_runs(), self.game.home_hits(), self.game.home_errors()),
TeamType.AWAY: self._scoreline(self.game.away_runs(), self.game.away_hits(), self.game.away_errors()),
}

@property
def game(self):
return self.screen.game
Expand Down
6 changes: 4 additions & 2 deletions rewrite/screens/games/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from data.team import TeamType

from screens.base import ScreenBase
from screens.components.team import TeamBanner

Expand All @@ -14,5 +16,5 @@ def __init__(self, *args, game=None, **kwargs):
if self.game is None:
raise GameScreen.MissingGame("Game screens cannot be instantiated without a game object!")

self.away_team_banner = TeamBanner("away", self)
self.home_team_banner = TeamBanner("home", self)
self.away_team_banner = TeamBanner(TeamType.AWAY, self)
self.home_team_banner = TeamBanner(TeamType.HOME, self)

0 comments on commit e0a1e0b

Please sign in to comment.