From 895f47f4356d42ae2f365e983e9309e57d6f8ce4 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Sat, 29 Apr 2023 13:02:11 -0400 Subject: [PATCH 01/47] Clean up schedule advancement logic --- data/schedule.py | 55 ++++++++++++++++++++++++++---------------------- data/status.py | 7 ++---- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/data/schedule.py b/data/schedule.py index a8037b5b..5c605ff2 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -57,6 +57,7 @@ def update(self, force=False) -> UpdateStatus: if live_games: games = live_games + self.current_idx %= len(games) self._games = games return UpdateStatus.SUCCESS @@ -104,24 +105,25 @@ def next_game(self): and not self.preferred_over ): game_index = self._game_index_for_preferred_team() - scheduled_game = self._games[game_index] - preferred_game = Game.from_scheduled(scheduled_game, self.config.delay_in_10s_of_seconds) - if preferred_game is not None: - debug.log( - "Preferred Team's Game Status: %s, %s %d", - preferred_game.status(), - preferred_game.inning_state(), - preferred_game.inning_number(), - ) - - if status.is_live(preferred_game.status()) and not status.is_inning_break( - preferred_game.inning_state() - ): - self.current_idx = game_index - debug.log("Moving to preferred game, index: %d", self.current_idx) - return preferred_game - if status.is_complete(preferred_game.status()): - self.preferred_over = True + if game_index >= 0: # we return -1 if no live games for preferred team + scheduled_game = self._games[game_index] + preferred_game = Game.from_scheduled(scheduled_game, self.config.delay_in_10s_of_seconds) + if preferred_game is not None: + debug.log( + "Preferred Team's Game Status: %s, %s %d", + preferred_game.status(), + preferred_game.inning_state(), + preferred_game.inning_number(), + ) + + if status.is_live(preferred_game.status()) and not status.is_inning_break( + preferred_game.inning_state() + ): + self.current_idx = game_index + debug.log("Moving to preferred game, index: %d", self.current_idx) + return preferred_game + if status.is_complete(preferred_game.status()): + self.preferred_over = True self.current_idx = self.__next_game_index() return self.__current_game() @@ -129,13 +131,16 @@ def next_game(self): def _game_index_for_preferred_team(self): if self.config.preferred_teams: team_name = data.teams.TEAM_FULL[self.config.preferred_teams[0]] - team_index = self.current_idx - team_idxs = [i for i, game in enumerate(self._games) if team_name in [game["away_name"], game["home_name"]]] - if len(team_idxs) > 0: - team_index = next((i for i in team_idxs if status.is_live(self._games[i]["status"])), team_idxs[0],) - return team_index - else: - return self.current_idx + return next( + ( + i + for i, game in enumerate(self._games) + if team_name in [game["away_name"], game["home_name"]] and status.is_live(game["status"]) + ), + -1, # no live games for preferred team + ) + + return -1 # no preferred team def __next_game_index(self): counter = self.current_idx + 1 diff --git a/data/status.py b/data/status.py index cabcefde..e68e19cb 100644 --- a/data/status.py +++ b/data/status.py @@ -162,15 +162,12 @@ WRITING = "Writing" # Other REVIEW = "Review" # Not in json -GAME_STATE_INNING_BREAK = [Inning.TOP, Inning.BOTTOM] +GAME_STATE_INNING_LIVE = [Inning.TOP, Inning.BOTTOM] GAME_STATE_LIVE = [ IN_PROGRESS, WARMUP, INSTANT_REPLAY, - GAME_OVER, - GAME_OVER_TIE_DECISION_BY_TIEBREAKER, - GAME_OVER_TIED, MANAGER_CHALLENGE, MANAGER_CHALLENGE_CATCHDROP_IN_OUTFIELD, MANAGER_CHALLENGE_CLOSE_PLAY_AT_1ST, @@ -409,4 +406,4 @@ def is_fresh(status): def is_inning_break(inning_state): """Returns whether a game is in an inning break (mid/end). Pass in the inning state.""" - return inning_state not in GAME_STATE_INNING_BREAK + return inning_state not in GAME_STATE_INNING_LIVE From 10197fe7f42c0657fcb0d6b52b67e9c69488cbdb Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Sat, 29 Apr 2023 13:14:34 -0400 Subject: [PATCH 02/47] Remove unnecessary preferred-over check --- data/schedule.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/data/schedule.py b/data/schedule.py index 5c605ff2..fa93aa3f 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -18,7 +18,6 @@ def __init__(self, config): self.date = self.__parse_today() self.starttime = time.time() self.current_idx = 0 - self.preferred_over = False # all games for the day self.__all_games = [] # the actual (filtered) schedule @@ -102,7 +101,6 @@ def next_game(self): not self.config.rotation_preferred_team_live_enabled and self.config.rotation_preferred_team_live_mid_inning and not self.is_offday_for_preferred_team() - and not self.preferred_over ): game_index = self._game_index_for_preferred_team() if game_index >= 0: # we return -1 if no live games for preferred team @@ -122,8 +120,7 @@ def next_game(self): self.current_idx = game_index debug.log("Moving to preferred game, index: %d", self.current_idx) return preferred_game - if status.is_complete(preferred_game.status()): - self.preferred_over = True + self.current_idx = self.__next_game_index() return self.__current_game() From 6a996d1354d01b81e79450c2f127613abc990c1c Mon Sep 17 00:00:00 2001 From: Tyler Porter Date: Sat, 29 Apr 2023 15:07:02 -0400 Subject: [PATCH 03/47] Update version.py --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 2ef739ad..34cf07f4 100644 --- a/version.py +++ b/version.py @@ -1,5 +1,5 @@ SCRIPT_NAME = "MLB LED Scoreboard" -SCRIPT_VERSION = "6.3.1" +SCRIPT_VERSION = "6.3.2" if __name__ == "__main__": From 912d0b6751b51eeabb5031890ccc7dd3cac75c48 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Sun, 30 Apr 2023 22:33:40 -0400 Subject: [PATCH 04/47] Fix re-using prior refresh of games in certain settings --- data/schedule.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/schedule.py b/data/schedule.py index fa93aa3f..f923838e 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -52,8 +52,10 @@ def update(self, force=False) -> UpdateStatus: if self.config.rotation_only_preferred: games = Schedule.__filter_list_of_games(self.__all_games, self.config.preferred_teams) if self.config.rotation_only_live: - live_games = [g for g in self._games if status.is_live(g["status"]) or status.is_fresh(g["status"])] + live_games = [g for g in games if status.is_live(g["status"]) or status.is_fresh(g["status"])] if live_games: + # we never have games drop down to [], since we may still be indexing into it + # but this is fine, since self.games_live() is will work even if we don't do this update games = live_games self.current_idx %= len(games) From f17159ee4ff4e51720c4103f27556a0c9c1be7c4 Mon Sep 17 00:00:00 2001 From: pawptart Date: Thu, 18 May 2023 12:01:37 -0400 Subject: [PATCH 05/47] Handle missing headline keys, fix Windows emulated clock format --- data/config/__init__.py | 7 ++++++- data/headlines.py | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/data/config/__init__.py b/data/config/__init__.py index d109fd7c..25f74622 100644 --- a/data/config/__init__.py +++ b/data/config/__init__.py @@ -1,5 +1,6 @@ import json import os +import platform import sys import debug @@ -138,7 +139,11 @@ def check_time_format(self): if self.time_format.lower() == "24h": self.time_format = "%H" else: - self.time_format = "%-I" + # Windows strftime syntax is different than other platforms + if platform.system() == "Windows": + self.time_format = "%#I" + else: + self.time_format = "%-I" def check_rotate_rates(self): if not isinstance(self.rotation_rates, dict): diff --git a/data/headlines.py b/data/headlines.py index 97eacc5c..3ba4c04d 100644 --- a/data/headlines.py +++ b/data/headlines.py @@ -39,7 +39,7 @@ "Cardinals": "cardinals", "Cubs": "cubs", "Diamondbacks": "dbacks", - "D-Backs": "dbacks", + "D-backs": "dbacks", "Dodgers": "dodgers", "Giants": "giants", "Marlins": "marlins", @@ -75,7 +75,7 @@ "Cardinals": "st-louis-cardinals", "Cubs": "chicago-cubs", "Diamondbacks": "arizona-diamondbacks", - "D-Backs": "arizona-diamondbacks", + "D-backs": "arizona-diamondbacks", "Dodgers": "los-angeles-dodgers", "Giants": "san-francisco-giants", "Marlins": "florida-marlins", @@ -188,10 +188,22 @@ def __compile_feed_list(self): self.feed_urls.append(self.__traderumors_url_for_team(team)) def __mlb_url_for_team(self, team_name): - return "{}/{}/{}".format(MLB_BASE, MLB_FEEDS[team_name], MLB_PATH) + feed_name = MLB_FEEDS.get(team_name, None) + + if feed_name is None: + debug.error(f"Failed to fetch MLB feed name for key '{team_name}', falling back to default feed.") + feed_name = MLB_FEEDS["MLB"] + + return "{}/{}/{}".format(MLB_BASE, feed_name, MLB_PATH) def __traderumors_url_for_team(self, team_name): - return "{}/{}/{}".format(TRADE_BASE, TRADE_FEEDS[team_name], TRADE_PATH) + feed_name = TRADE_FEEDS.get(team_name, None) + + if feed_name is None: + debug.error(f"Failed to fetch MLB Trade Rumors feed name for key '{team_name}', falling back to default feed.") + feed_name = "" + + return "{}/{}/{}".format(TRADE_BASE, feed_name, TRADE_PATH) def __should_update(self): endtime = time.time() From e8edce68804fecd5d922b5d13043b6714fd4930f Mon Sep 17 00:00:00 2001 From: pawptart Date: Thu, 18 May 2023 13:36:45 -0400 Subject: [PATCH 06/47] Make 24h/12h time formats a constant and refer to them consistently --- data/config/__init__.py | 17 ++++++++++++++--- data/scoreboard/pregame.py | 3 ++- renderers/offday.py | 3 ++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/data/config/__init__.py b/data/config/__init__.py index 25f74622..2ca820e4 100644 --- a/data/config/__init__.py +++ b/data/config/__init__.py @@ -17,6 +17,17 @@ DEFAULT_PREFERRED_TEAMS = ["Cubs"] DEFAULT_PREFERRED_DIVISIONS = ["NL Central"] +TIME_FORMAT_24H_ALL_PLATFORMS = "%H" +TIME_FORMAT_12H_NON_WINDOWS = "%-I" +TIME_FORMAT_12H_WINDOWS = "%#I" + +TIME_FORMATS_24H = [ + TIME_FORMAT_24H_ALL_PLATFORMS +] +TIME_FORMATS_12H = [ + TIME_FORMAT_12H_NON_WINDOWS, + TIME_FORMAT_12H_WINDOWS +] class Config: def __init__(self, filename_base, width, height): @@ -137,13 +148,13 @@ def check_preferred_divisions(self): def check_time_format(self): if self.time_format.lower() == "24h": - self.time_format = "%H" + self.time_format = TIME_FORMAT_24H_ALL_PLATFORMS else: # Windows strftime syntax is different than other platforms if platform.system() == "Windows": - self.time_format = "%#I" + self.time_format = TIME_FORMAT_12H_WINDOWS else: - self.time_format = "%-I" + self.time_format = TIME_FORMAT_12H_NON_WINDOWS def check_rotate_rates(self): if not isinstance(self.rotation_rates, dict): diff --git a/data/scoreboard/pregame.py b/data/scoreboard/pregame.py index b5a0dbe9..00f0475e 100644 --- a/data/scoreboard/pregame.py +++ b/data/scoreboard/pregame.py @@ -1,6 +1,7 @@ import tzlocal from data.game import Game +from data.config import TIME_FORMATS_12H PITCHER_TBD = "TBD" @@ -45,7 +46,7 @@ def __init__(self, game: Game, time_format): def __convert_time(self, game_time_utc): """Converts MLB's pregame times (UTC) into the local time zone""" time_str = "{}:%M".format(self.time_format) - if self.time_format == "%-I": + if self.time_format in TIME_FORMATS_12H: time_str += "%p" return game_time_utc.astimezone(tzlocal.get_localzone()).strftime(time_str) diff --git a/renderers/offday.py b/renderers/offday.py index ee99cb48..db4e7973 100644 --- a/renderers/offday.py +++ b/renderers/offday.py @@ -4,6 +4,7 @@ import PIL.Image +from data.config import TIME_FORMATS_12H from data.config.color import Color from data.config.layout import Layout from data.headlines import Headlines @@ -25,7 +26,7 @@ def render_offday_screen( def __render_clock(canvas, layout, colors, time_format): time_format_str = "{}:%M".format(time_format) - if time_format == "%-I": + if time_format in TIME_FORMATS_12H: time_format_str += "%p" time_text = time.strftime(time_format_str) coords = layout.coords("offday.time") From 5572c614f6fc23c5dcea7b5ae5a58f9888c502b3 Mon Sep 17 00:00:00 2001 From: pawptart Date: Thu, 18 May 2023 14:00:26 -0400 Subject: [PATCH 07/47] Fix circular import, PR feedback --- data/config/__init__.py | 21 +++------------------ data/scoreboard/pregame.py | 4 ++-- data/time.py | 4 ++++ renderers/offday.py | 4 ++-- 4 files changed, 11 insertions(+), 22 deletions(-) create mode 100644 data/time.py diff --git a/data/config/__init__.py b/data/config/__init__.py index 2ca820e4..c01266d0 100644 --- a/data/config/__init__.py +++ b/data/config/__init__.py @@ -1,12 +1,12 @@ import json import os -import platform import sys import debug from data import status from data.config.color import Color from data.config.layout import Layout +from data.time import TIME_FORMAT_12H, TIME_FORMAT_24H from utils import deep_update SCROLLING_SPEEDS = [0.3, 0.2, 0.1, 0.075, 0.05, 0.025, 0.01] @@ -17,17 +17,6 @@ DEFAULT_PREFERRED_TEAMS = ["Cubs"] DEFAULT_PREFERRED_DIVISIONS = ["NL Central"] -TIME_FORMAT_24H_ALL_PLATFORMS = "%H" -TIME_FORMAT_12H_NON_WINDOWS = "%-I" -TIME_FORMAT_12H_WINDOWS = "%#I" - -TIME_FORMATS_24H = [ - TIME_FORMAT_24H_ALL_PLATFORMS -] -TIME_FORMATS_12H = [ - TIME_FORMAT_12H_NON_WINDOWS, - TIME_FORMAT_12H_WINDOWS -] class Config: def __init__(self, filename_base, width, height): @@ -148,13 +137,9 @@ def check_preferred_divisions(self): def check_time_format(self): if self.time_format.lower() == "24h": - self.time_format = TIME_FORMAT_24H_ALL_PLATFORMS + self.time_format = TIME_FORMAT_24H else: - # Windows strftime syntax is different than other platforms - if platform.system() == "Windows": - self.time_format = TIME_FORMAT_12H_WINDOWS - else: - self.time_format = TIME_FORMAT_12H_NON_WINDOWS + self.time_format = TIME_FORMAT_12H def check_rotate_rates(self): if not isinstance(self.rotation_rates, dict): diff --git a/data/scoreboard/pregame.py b/data/scoreboard/pregame.py index 00f0475e..6d9b558b 100644 --- a/data/scoreboard/pregame.py +++ b/data/scoreboard/pregame.py @@ -1,7 +1,7 @@ import tzlocal from data.game import Game -from data.config import TIME_FORMATS_12H +from data.time import TIME_FORMAT_12H PITCHER_TBD = "TBD" @@ -46,7 +46,7 @@ def __init__(self, game: Game, time_format): def __convert_time(self, game_time_utc): """Converts MLB's pregame times (UTC) into the local time zone""" time_str = "{}:%M".format(self.time_format) - if self.time_format in TIME_FORMATS_12H: + if self.time_format == TIME_FORMAT_12H: time_str += "%p" return game_time_utc.astimezone(tzlocal.get_localzone()).strftime(time_str) diff --git a/data/time.py b/data/time.py new file mode 100644 index 00000000..0fc4ede9 --- /dev/null +++ b/data/time.py @@ -0,0 +1,4 @@ +import platform + +TIME_FORMAT_24H = "%H" +TIME_FORMAT_12H = "%#I" if platform.system() == "Windows" else "%-I" diff --git a/renderers/offday.py b/renderers/offday.py index db4e7973..81528fb4 100644 --- a/renderers/offday.py +++ b/renderers/offday.py @@ -4,7 +4,7 @@ import PIL.Image -from data.config import TIME_FORMATS_12H +from data.time import TIME_FORMAT_12H from data.config.color import Color from data.config.layout import Layout from data.headlines import Headlines @@ -26,7 +26,7 @@ def render_offday_screen( def __render_clock(canvas, layout, colors, time_format): time_format_str = "{}:%M".format(time_format) - if time_format in TIME_FORMATS_12H: + if time_format == TIME_FORMAT_12H: time_format_str += "%p" time_text = time.strftime(time_format_str) coords = layout.coords("offday.time") From 3298fa9010fdff217cf568d2cd82831915754228 Mon Sep 17 00:00:00 2001 From: pawptart Date: Thu, 18 May 2023 21:29:26 -0400 Subject: [PATCH 08/47] Hotfix time import collision --- data/config/__init__.py | 2 +- data/scoreboard/pregame.py | 2 +- data/{time.py => time_formats.py} | 0 renderers/offday.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename data/{time.py => time_formats.py} (100%) diff --git a/data/config/__init__.py b/data/config/__init__.py index c01266d0..30e56932 100644 --- a/data/config/__init__.py +++ b/data/config/__init__.py @@ -6,7 +6,7 @@ from data import status from data.config.color import Color from data.config.layout import Layout -from data.time import TIME_FORMAT_12H, TIME_FORMAT_24H +from data.time_formats import TIME_FORMAT_12H, TIME_FORMAT_24H from utils import deep_update SCROLLING_SPEEDS = [0.3, 0.2, 0.1, 0.075, 0.05, 0.025, 0.01] diff --git a/data/scoreboard/pregame.py b/data/scoreboard/pregame.py index 6d9b558b..bbd008ba 100644 --- a/data/scoreboard/pregame.py +++ b/data/scoreboard/pregame.py @@ -1,7 +1,7 @@ import tzlocal from data.game import Game -from data.time import TIME_FORMAT_12H +from data.time_formats import TIME_FORMAT_12H PITCHER_TBD = "TBD" diff --git a/data/time.py b/data/time_formats.py similarity index 100% rename from data/time.py rename to data/time_formats.py diff --git a/renderers/offday.py b/renderers/offday.py index 81528fb4..fd400c38 100644 --- a/renderers/offday.py +++ b/renderers/offday.py @@ -4,7 +4,7 @@ import PIL.Image -from data.time import TIME_FORMAT_12H +from data.time_formats import TIME_FORMAT_12H from data.config.color import Color from data.config.layout import Layout from data.headlines import Headlines From 8cd7f71defcf0b8985d699de2e7518aa93448f0e Mon Sep 17 00:00:00 2001 From: Tyler Porter Date: Thu, 18 May 2023 21:50:55 -0400 Subject: [PATCH 09/47] Update version.py --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 34cf07f4..85cbe8b3 100644 --- a/version.py +++ b/version.py @@ -1,5 +1,5 @@ SCRIPT_NAME = "MLB LED Scoreboard" -SCRIPT_VERSION = "6.3.2" +SCRIPT_VERSION = "6.3.4" if __name__ == "__main__": From 6c62504fc8864af2c171397347373bb314a6135d Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Fri, 19 May 2023 10:21:12 -0400 Subject: [PATCH 10/47] Check that version.py is updated on merges to master --- .github/workflows/require_release_branch.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/require_release_branch.yml b/.github/workflows/require_release_branch.yml index 13c387aa..216164f8 100644 --- a/.github/workflows/require_release_branch.yml +++ b/.github/workflows/require_release_branch.yml @@ -12,3 +12,13 @@ jobs: run: | echo "ERROR: You can only merge to master from dev. Contributors should point their PRs to the dev branch." exit 1 + + - uses: actions/checkout@v3 + if: github.base_ref == 'master' + with: + fetch-depth: 0 + + - name: Check version.py + if: github.base_ref == 'master' + run: | + git diff --name-only origin/master | grep -q version.py || (echo "Need to change version for pushes to master!" && exit 1) From a7e8e7b6a99468dc1cf977e86771952da8afd53b Mon Sep 17 00:00:00 2001 From: pawptart Date: Sun, 4 Jun 2023 11:56:25 -0400 Subject: [PATCH 11/47] Guard against ZeroDivisionError in game index calculation --- data/schedule.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/schedule.py b/data/schedule.py index f923838e..194e0f45 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -58,7 +58,9 @@ def update(self, force=False) -> UpdateStatus: # but this is fine, since self.games_live() is will work even if we don't do this update games = live_games - self.current_idx %= len(games) + if len(games) > 0: + self.current_idx %= len(games) + self._games = games return UpdateStatus.SUCCESS From 00ee7bc660f6a08d404f36792734564cca91116d Mon Sep 17 00:00:00 2001 From: pawptart Date: Sun, 4 Jun 2023 12:04:54 -0400 Subject: [PATCH 12/47] bump version to 6.3.5 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 85cbe8b3..b26f5cb4 100644 --- a/version.py +++ b/version.py @@ -1,5 +1,5 @@ SCRIPT_NAME = "MLB LED Scoreboard" -SCRIPT_VERSION = "6.3.4" +SCRIPT_VERSION = "6.3.5" if __name__ == "__main__": From 195206b846e0761fc99de17ab93a61efec31b6de Mon Sep 17 00:00:00 2001 From: Nicholas Saraniti Date: Mon, 5 Jun 2023 09:20:20 -0400 Subject: [PATCH 13/47] Fix wiki link in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f544ef6a..27bb4952 100755 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ If you'd like to see support for another set of board dimensions, or have design * [Custom Colors](#custom-colors) * [Sources](#sources) * [Accuracy Disclaimer](#accuracy-disclaimer) -* [Wiki](#wiki) +* [Wiki](https://github.com/MLB-LED-Scoreboard/mlb-led-scoreboard/wiki) * [Help and Contributing](#help-and-contributing) * [Latest Features](#latest-features) * [Licensing](#licensing) From e4866248bbc3fa55e65189446e3bf35119e5c3db Mon Sep 17 00:00:00 2001 From: pawptart Date: Sat, 8 Jul 2023 11:40:04 -0400 Subject: [PATCH 14/47] Add option to display team record on game screen --- coordinates/w128h32.json.example | 11 +++++++++++ coordinates/w128h64.json.example | 11 +++++++++++ coordinates/w192h64.json.example | 11 +++++++++++ coordinates/w32h32.json.example | 11 +++++++++++ coordinates/w64h32.json.example | 11 +++++++++++ coordinates/w64h64.json.example | 12 ++++++++++++ data/game.py | 8 +++++++- data/scoreboard/__init__.py | 4 ++-- data/scoreboard/team.py | 3 ++- renderers/games/teams.py | 14 ++++++++++++++ 10 files changed, 92 insertions(+), 4 deletions(-) diff --git a/coordinates/w128h32.json.example b/coordinates/w128h32.json.example index d3c59047..ffe92541 100644 --- a/coordinates/w128h32.json.example +++ b/coordinates/w128h32.json.example @@ -291,6 +291,17 @@ "y": 10 } }, + "record": { + "_comment": "Offscreen by default.", + "away": { + "x": -10, + "y": -10 + }, + "home": { + "x": -10, + "y": -10 + } + }, "runs": { "runs_hits_errors": { "show": false, diff --git a/coordinates/w128h64.json.example b/coordinates/w128h64.json.example index 52fe6f11..f246729f 100644 --- a/coordinates/w128h64.json.example +++ b/coordinates/w128h64.json.example @@ -271,6 +271,17 @@ "y": 29 } }, + "record": { + "_comment": "Offscreen by default.", + "away": { + "x": -10, + "y": -10 + }, + "home": { + "x": -10, + "y": -10 + } + }, "runs": { "runs_hits_errors": { "show": true, diff --git a/coordinates/w192h64.json.example b/coordinates/w192h64.json.example index 9a10db27..4d6d61ec 100644 --- a/coordinates/w192h64.json.example +++ b/coordinates/w192h64.json.example @@ -271,6 +271,17 @@ "y": 29 } }, + "record": { + "_comment": "Offscreen by default.", + "away": { + "x": -10, + "y": -10 + }, + "home": { + "x": -10, + "y": -10 + } + }, "runs": { "runs_hits_errors": { "show": true, diff --git a/coordinates/w32h32.json.example b/coordinates/w32h32.json.example index f872886a..0da7dba4 100644 --- a/coordinates/w32h32.json.example +++ b/coordinates/w32h32.json.example @@ -278,6 +278,17 @@ "y": 13 } }, + "record": { + "_comment": "Offscreen by default.", + "away": { + "x": -10, + "y": -10 + }, + "home": { + "x": -10, + "y": -10 + } + }, "runs": { "runs_hits_errors": { "show": false, diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index d36bd9e6..552f9bee 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -263,6 +263,17 @@ "y": 7 } }, + "record": { + "_comment": "Offscreen by default.", + "away": { + "x": -10, + "y": -10 + }, + "home": { + "x": -10, + "y": -10 + } + }, "runs": { "runs_hits_errors": { "show": true, diff --git a/coordinates/w64h64.json.example b/coordinates/w64h64.json.example index bde2213a..c18b4cda 100644 --- a/coordinates/w64h64.json.example +++ b/coordinates/w64h64.json.example @@ -247,6 +247,18 @@ "y": 10 } }, + , + "record": { + "_comment": "Offscreen by default.", + "away": { + "x": -10, + "y": -10 + }, + "home": { + "x": -10, + "y": -10 + } + }, "runs": { "runs_hits_errors": { "show": false, diff --git a/data/game.py b/data/game.py index ef7da6c8..9996bec4 100644 --- a/data/game.py +++ b/data/game.py @@ -10,7 +10,7 @@ API_FIELDS = ( "gameData,game,id,datetime,dateTime,officialDate,flags,noHitter,perfectGame,status,detailedState,abstractGameState," - + "reason,probablePitchers,teams,home,away,abbreviation,teamName,players,id,boxscoreName,fullName,liveData,plays," + + "reason,probablePitchers,teams,home,away,abbreviation,teamName,record,wins,losses,players,id,boxscoreName,fullName,liveData,plays," + "currentPlay,result,eventType,playEvents,isPitch,pitchData,startSpeed,details,type,code,description,decisions," + "winner,loser,save,id,linescore,outs,balls,strikes,note,inningState,currentInning,currentInningOrdinal,offense," + "batter,inHole,onDeck,first,second,third,defense,pitcher,boxscore,teams,runs,players,seasonStats,pitching,wins," @@ -88,6 +88,12 @@ def home_name(self): def home_abbreviation(self): return self._current_data["gameData"]["teams"]["home"]["abbreviation"] + + def home_record(self): + return self._current_data["gameData"]["teams"]["home"]["record"] or {} + + def away_record(self): + return self._current_data["gameData"]["teams"]["away"]["record"] or {} def pregame_weather(self): try: diff --git a/data/scoreboard/__init__.py b/data/scoreboard/__init__.py index 6ac59f6d..93ff5eb6 100644 --- a/data/scoreboard/__init__.py +++ b/data/scoreboard/__init__.py @@ -15,10 +15,10 @@ class Scoreboard: def __init__(self, game: Game): self.away_team = Team( - game.away_abbreviation(), game.away_score(), game.away_name(), game.away_hits(), game.away_errors() + game.away_abbreviation(), game.away_score(), game.away_name(), game.away_hits(), game.away_errors(), game.away_record() ) self.home_team = Team( - game.home_abbreviation(), game.home_score(), game.home_name(), game.home_hits(), game.home_errors() + game.home_abbreviation(), game.home_score(), game.home_name(), game.home_hits(), game.home_errors(), game.home_record() ) self.inning = Inning(game) self.bases = Bases(game) diff --git a/data/scoreboard/team.py b/data/scoreboard/team.py index 08510776..535808ce 100644 --- a/data/scoreboard/team.py +++ b/data/scoreboard/team.py @@ -1,7 +1,8 @@ class Team: - def __init__(self, abbrev, runs, name, hits, errors): + def __init__(self, abbrev, runs, name, hits, errors, record): self.abbrev = abbrev self.runs = runs self.name = name self.hits = hits self.errors = errors + self.record = record diff --git a/renderers/games/teams.py b/renderers/games/teams.py index fc3d6ea5..29e39bc9 100644 --- a/renderers/games/teams.py +++ b/renderers/games/teams.py @@ -58,6 +58,9 @@ def render_team_banner( __render_team_text(canvas, layout, away_colors, away_team, "away", use_full_team_names, default_colors) __render_team_text(canvas, layout, home_colors, home_team, "home", use_full_team_names, default_colors) + __render_record_text(canvas, layout, away_colors, away_team, "away", default_colors) + __render_record_text(canvas, layout, home_colors, home_team, "home", default_colors) + if show_score: # Number of characters in each score. score_spacing = { @@ -110,6 +113,17 @@ def __render_team_text(canvas, layout, colors, team, homeaway, full_team_names, team_text = "{:13s}".format(team.name) graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], text_color_graphic, team_text) +def __render_record_text(canvas, layout, colors, team, homeaway, default_colors): + if "losses" not in team.record or "wins" not in team.record: + return + + text_color = colors.get("text", default_colors["text"]) + text_color_graphic = graphics.Color(text_color["r"], text_color["g"], text_color["b"]) + coords = layout.coords("teams.record.{}".format(homeaway)) + font = layout.font("teams.record.{}".format(homeaway)) + record_text = "({}-{})".format(team.record["wins"], team.record["losses"]) + graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], text_color_graphic, record_text) + def __render_score_component(canvas, layout, colors, homeaway, default_colors, coords, component_val, width_chars): # The coords passed in are the rightmost pixel. From 8216dd1d3dca50d2026c28c173fd1b9a05bd98b3 Mon Sep 17 00:00:00 2001 From: pawptart Date: Sat, 8 Jul 2023 15:06:54 -0400 Subject: [PATCH 15/47] Provide good example values, add a toggle --- coordinates/w128h32.json.example | 12 +++++++----- coordinates/w128h64.json.example | 10 +++++----- coordinates/w192h64.json.example | 10 +++++----- coordinates/w32h32.json.example | 10 +++++----- coordinates/w64h32.json.example | 10 +++++----- coordinates/w64h64.json.example | 13 +++++++------ renderers/games/teams.py | 2 ++ 7 files changed, 36 insertions(+), 31 deletions(-) diff --git a/coordinates/w128h32.json.example b/coordinates/w128h32.json.example index ffe92541..75e49010 100644 --- a/coordinates/w128h32.json.example +++ b/coordinates/w128h32.json.example @@ -292,14 +292,16 @@ } }, "record": { - "_comment": "Offscreen by default.", + "enabled": false, "away": { - "x": -10, - "y": -10 + "font_name": "4x6", + "x": 19, + "y": 7 }, "home": { - "x": -10, - "y": -10 + "font_name": "4x6", + "x": 19, + "y": 17 } }, "runs": { diff --git a/coordinates/w128h64.json.example b/coordinates/w128h64.json.example index f246729f..92c0feb2 100644 --- a/coordinates/w128h64.json.example +++ b/coordinates/w128h64.json.example @@ -272,14 +272,14 @@ } }, "record": { - "_comment": "Offscreen by default.", + "enabled": false, "away": { - "x": -10, - "y": -10 + "x": 30, + "y": 13 }, "home": { - "x": -10, - "y": -10 + "x": 30, + "y": 29 } }, "runs": { diff --git a/coordinates/w192h64.json.example b/coordinates/w192h64.json.example index 4d6d61ec..87cdd9dc 100644 --- a/coordinates/w192h64.json.example +++ b/coordinates/w192h64.json.example @@ -272,14 +272,14 @@ } }, "record": { - "_comment": "Offscreen by default.", + "enabled": false, "away": { - "x": -10, - "y": -10 + "x": 30, + "y": 13 }, "home": { - "x": -10, - "y": -10 + "x": 30, + "y": 29 } }, "runs": { diff --git a/coordinates/w32h32.json.example b/coordinates/w32h32.json.example index 0da7dba4..7a71fad6 100644 --- a/coordinates/w32h32.json.example +++ b/coordinates/w32h32.json.example @@ -279,14 +279,14 @@ } }, "record": { - "_comment": "Offscreen by default.", + "enabled": false, "away": { - "x": -10, - "y": -10 + "x": 4, + "y": 6 }, "home": { - "x": -10, - "y": -10 + "x": 4, + "y": 13 } }, "runs": { diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index 552f9bee..6c98341a 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -264,14 +264,14 @@ } }, "record": { - "_comment": "Offscreen by default.", + "enabled": false, "away": { - "x": -10, - "y": -10 + "x": 15, + "y": 6 }, "home": { - "x": -10, - "y": -10 + "x": 15, + "y": 13 } }, "runs": { diff --git a/coordinates/w64h64.json.example b/coordinates/w64h64.json.example index c18b4cda..36c5ef64 100644 --- a/coordinates/w64h64.json.example +++ b/coordinates/w64h64.json.example @@ -247,16 +247,17 @@ "y": 10 } }, - , "record": { - "_comment": "Offscreen by default.", + "enabled": false, "away": { - "x": -10, - "y": -10 + "font_name": "4x6", + "x": 18, + "y": 7 }, "home": { - "x": -10, - "y": -10 + "font_name": "4x6", + "x": 18, + "y": 17 } }, "runs": { diff --git a/renderers/games/teams.py b/renderers/games/teams.py index 29e39bc9..5bc59b76 100644 --- a/renderers/games/teams.py +++ b/renderers/games/teams.py @@ -116,6 +116,8 @@ def __render_team_text(canvas, layout, colors, team, homeaway, full_team_names, def __render_record_text(canvas, layout, colors, team, homeaway, default_colors): if "losses" not in team.record or "wins" not in team.record: return + if not layout.coords("teams.record.enabled"): + return text_color = colors.get("text", default_colors["text"]) text_color_graphic = graphics.Color(text_color["r"], text_color["g"], text_color["b"]) From 14199b74c5b31777a14e815258d249d700da28e9 Mon Sep 17 00:00:00 2001 From: c2mfj <119268254+c2mfj@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:13:42 -0400 Subject: [PATCH 16/47] added boot logo for w128h32 --- assets/mlb-w128h32.png | Bin 0 -> 2489 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/mlb-w128h32.png diff --git a/assets/mlb-w128h32.png b/assets/mlb-w128h32.png new file mode 100644 index 0000000000000000000000000000000000000000..5a8ebe4af2e64afe7a357f8104d7d0f830aa2158 GIT binary patch literal 2489 zcmV;q2}bsbP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2~J5wK~#8N?VAfw zRM#2D|GRs4m-j>7Pc5u~5Ur!MQ439COu)oYTOWy0(K_)#4brA+$Jc0xWcV`@ecwX{`H3bv^gO>2B5g9K3l*_Fov`?`0x-?@9K+9)iI%U+rOm~YuV=kDcj z&;Nes`_4I60l-1#X>4R>_9h@8K(S`c8cj}44i^*@1VIqsaLA#FgGZrIK&4WlqoV_d z4jtl)ii)g8qp|Ow@h0P(Z#{=x1RxR@7l+!~TExf4qs`EXhNfnylnU`e4i$F0gD!{z zDT(p$_w$iT0inkXW8EdF{He^<_Kq}Gc&REo!??p(O$q}1=MP1D~Af^ZCdr1|LS~{?c698I;neRIa-b#fv?vnI?OJW z_5+>DZnHt5)ga;Zq{cLTjVpj6ToAtTY9|H~-^MMYs!dYbb)gkN$3Zoc<1 z1l~%TRKr1SbI>^-Xe1_uLJ8hx#LZ&|5tuX`LH8s*%J&SAvuzvk|=yW=4 zE-b;73k^`mMng&Cr&7&59MhV15FRu+V&8_uDJ7qD>gI_Nu%(n)~C%_|V78xM=IRlK`hfejZb_`uP_ zVqJ>-IFHAE|3Nka=tJ#}i$g&h-`s{#d!YPg8mIfgRYs`wgI@3kVIu zwTl;#yZnxmRQhN!dfTfIC~2`M3}#(Kb^>q<#va6u zOGZw1hEy;xzgYP){F4$uy$paQo8d127S+>u zoe~xVEd_$E5*a=MPfi{WPzS36{b+1bA$Q3JG~6VwK=@Bc!N}FQu%J~YiS+-*uoZxU zf<8N?VSGa)^;QIy7vxGcu9KELiiQreIGMx6M4G9u z1%#$S288lqSPDS&$kh2`9vh8CbFy&hi$;``R^T6>T^8$IrT!~Q2cdO-pp&Xtz!x5i zop0?%`A0{^;>Z`X5&29ug$g$Z7ZA#XVJQF>l5qikXl%ZP>9Y#(o%A_axp)EoaP*Y( z02eq>Q-{)B`v5;*@I5A{b@w{e#c8l4?{%F2l7<&z^qU3X;0H^m;gJ)899rw15Q-wQ6$WK=m>B#iy|qsB@q)rj!)C2uZ99tv3s#HA`lqKr zw`nDK7$NkMNFJehU|4zp5t5aXG5{5IAj~E#nfE+`1O27Sff=KV$JM|74(Dpi@vq~* z5$*G;_i_1TDJnkMh3`JU2)8Lbu-cqse?sAMO#J;GSZsPp1dm2)CaR&P12Zx<0^?J4NJ|S74(~$7m z3YhpNIBdcrA%fxS0q%nH^+nV9OZfZYckyZ6d8zc9p;mJk5fmVOxRwMSgx!_G* zlt4V>^$a&K>;<5p@tSWl(73He-o_o6nf-)R<0j~?*KNk4wdx{P|MJ(7`r?ByP(?()ORh zBCz#?`+EET4*~rl=-V-N$^;xKFUI%M(=adRDU|Ig1T-27vcyfq{bOv$OF2^!aM$L} zp(gGp^AM1j3NuU#Ubs$W8z@wkh;3xX3V^sX2o= zGnQiQijC-`w2li7yjLmr!)8M?d4R!e0&y4h8%ih!xFPOJmK(AWKxZeVcSJBn&#W{mDS?!*>ke#)dU;m{=C@&zSk_8!)%4!bm#Lx1|M&nK63#v z>9e7VpNjYP9Tv~;E*iI&CA5foafbMwJsl+BaRIRK zS{4t6T)cb@oH`sUm;MM|VNs~6yTF#(oRdCmUmx=bgE6}M1quX>*P7vIZ^G%%8nEcc zKZLK=J#M(I@+6vSK7gW230qeOtQ`h%7Le@|&;*2HOwk5JFUdhwRTZ{u*#e_c39uGGd433J53ck3w*lIGFq+0ROaQ29}2Lb*KquP>EzGsNg00000NkvXXu0mjf DM_i9e literal 0 HcmV?d00001 From 95f450b27915933188e103935f66a0d84b5d06d9 Mon Sep 17 00:00:00 2001 From: pawptart Date: Tue, 11 Jul 2023 22:24:36 -0400 Subject: [PATCH 17/47] Version bump to 6.4.0 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index b26f5cb4..82b432be 100644 --- a/version.py +++ b/version.py @@ -1,5 +1,5 @@ SCRIPT_NAME = "MLB LED Scoreboard" -SCRIPT_VERSION = "6.3.5" +SCRIPT_VERSION = "6.4.0" if __name__ == "__main__": From 6200091d8b4540de9e4d80ea1279be387c41ebc0 Mon Sep 17 00:00:00 2001 From: pawptart Date: Fri, 14 Jul 2023 20:38:05 -0400 Subject: [PATCH 18/47] Fix for games in warmup status --- data/config/layout.py | 14 +++++++++----- renderers/games/teams.py | 2 +- version.py | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/data/config/layout.py b/data/config/layout.py index 89a8134f..99cc75ad 100644 --- a/data/config/layout.py +++ b/data/config/layout.py @@ -1,6 +1,7 @@ from driver import graphics import os.path +import debug FONTNAME_DEFAULT = "4x6" FONTNAME_KEY = "font_name" @@ -36,14 +37,17 @@ def font(self, keypath): def coords(self, keypath): try: - d = self.__find_at_keypath(keypath) + coord_dict = self.__find_at_keypath(keypath) except KeyError as e: raise e - if self.state in AVAILABLE_OPTIONAL_KEYS: - if self.state in d: - return d[self.state] - return d + if not isinstance(coord_dict, dict) or not self.state in AVAILABLE_OPTIONAL_KEYS: + return coord_dict + + if self.state in coord_dict: + return coord_dict[self.state] + + return coord_dict def set_state(self, new_state=None): if new_state in AVAILABLE_OPTIONAL_KEYS: diff --git a/renderers/games/teams.py b/renderers/games/teams.py index 5bc59b76..5d7bb78f 100644 --- a/renderers/games/teams.py +++ b/renderers/games/teams.py @@ -116,7 +116,7 @@ def __render_team_text(canvas, layout, colors, team, homeaway, full_team_names, def __render_record_text(canvas, layout, colors, team, homeaway, default_colors): if "losses" not in team.record or "wins" not in team.record: return - if not layout.coords("teams.record.enabled"): + if not layout.coords("teams.record").get("enabled", False): return text_color = colors.get("text", default_colors["text"]) diff --git a/version.py b/version.py index 82b432be..6bb344b1 100644 --- a/version.py +++ b/version.py @@ -1,5 +1,5 @@ SCRIPT_NAME = "MLB LED Scoreboard" -SCRIPT_VERSION = "6.4.0" +SCRIPT_VERSION = "6.4.1" if __name__ == "__main__": From 7ef8414ca642b65bf1468a98f9c514b982fd02c5 Mon Sep 17 00:00:00 2001 From: pawptart Date: Fri, 14 Jul 2023 20:39:16 -0400 Subject: [PATCH 19/47] remove unnecessary import --- data/config/layout.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data/config/layout.py b/data/config/layout.py index 99cc75ad..88fdef50 100644 --- a/data/config/layout.py +++ b/data/config/layout.py @@ -1,7 +1,6 @@ from driver import graphics import os.path -import debug FONTNAME_DEFAULT = "4x6" FONTNAME_KEY = "font_name" From db2b95b1daf2352594e968e53499d287892b8589 Mon Sep 17 00:00:00 2001 From: jimcreel Date: Fri, 14 Jul 2023 18:00:46 -0700 Subject: [PATCH 20/47] renders play result for hits and walks --- colors/scoreboard.json.example | 10 ++++++++ coordinates/w64h32.json.example | 10 ++++++++ data/scoreboard/__init__.py | 12 +++++++++ renderers/games/game.py | 43 ++++++++++++++++++++++++--------- renderers/main.py | 2 +- 5 files changed, 64 insertions(+), 13 deletions(-) diff --git a/colors/scoreboard.json.example b/colors/scoreboard.json.example index ddff96ab..6dc30961 100644 --- a/colors/scoreboard.json.example +++ b/colors/scoreboard.json.example @@ -128,6 +128,16 @@ "r": 255, "g": 235, "b": 59 + }, + "hit": { + "r": 255, + "g": 235, + "b": 59 + }, + "walk": { + "r": 255, + "g": 235, + "b": 59 } }, "batter_count": { diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index d36bd9e6..93a01aff 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -143,6 +143,16 @@ "x": 15, "y": 29, "font_name": "5x7" + }, + "hit": { + "x": 15, + "y": 29, + "font_name": "5x7" + }, + "walk": { + "x": 15, + "y": 29, + "font_name": "5x7" } }, "batter_count": { diff --git a/data/scoreboard/__init__.py b/data/scoreboard/__init__.py index 6ac59f6d..88b2c4cf 100644 --- a/data/scoreboard/__init__.py +++ b/data/scoreboard/__init__.py @@ -41,6 +41,18 @@ def strikeout(self): def strikeout_looking(self): return self.play_result == "strikeout_looking" + + def single(self): + return self.play_result == "single" + + def double(self): + return self.play_result == "double" + + def triple(self): + return self.play_result == "triple" + + def walk(self): + return self.play_result == "walk" def get_text_for_reason(self): if self.note: diff --git a/renderers/games/game.py b/renderers/games/game.py index cac05611..7953cdcd 100644 --- a/renderers/games/game.py +++ b/renderers/games/game.py @@ -9,13 +9,12 @@ from renderers import scrollingtext from renderers.games import nohitter - +PLAY_RESULT_UPDATES = ["single", "double", "triple", "walk", "home_run", "strikeout", "strikeout_looking"] def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboard, text_pos, animation_time): pos = 0 if scoreboard.inning.state == Inning.TOP or scoreboard.inning.state == Inning.BOTTOM: - - pos = _render_at_bat( + pos = _render_at_bat( canvas, layout, colors, @@ -23,21 +22,24 @@ def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboa text_pos, scoreboard.strikeout(), scoreboard.strikeout_looking(), + scoreboard.play_result, (animation_time // 6) % 2, - scoreboard.pitches, + scoreboard.pitches + ) # Check if we're deep enough into a game and it's a no hitter or perfect game - should_display_nohitter = layout.coords("nohitter")["innings_until_display"] - if scoreboard.inning.number > should_display_nohitter: - if layout.state_is_nohitter(): - nohitter.render_nohit_text(canvas, layout, colors) + should_display_nohitter = layout.coords("nohitter")["innings_until_display"] + if scoreboard.inning.number > should_display_nohitter: + if layout.state_is_nohitter(): + nohitter.render_nohit_text(canvas, layout, colors) _render_count(canvas, layout, colors, scoreboard.pitches) _render_outs(canvas, layout, colors, scoreboard.outs) _render_bases(canvas, layout, colors, scoreboard.bases, scoreboard.homerun(), (animation_time % 16) // 5) _render_inning_display(canvas, layout, colors, scoreboard.inning) + else: _render_inning_break(canvas, layout, colors, scoreboard.inning) _render_due_up(canvas, layout, colors, scoreboard.atbat) @@ -46,18 +48,36 @@ def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboa # --------------- at-bat --------------- -def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, strikeout, looking, animation, pitches: Pitches): +def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, strikeout, looking, play_result, animation, pitches: Pitches): plength = __render_pitcher_text(canvas, layout, colors, atbat.pitcher, pitches, text_pos) __render_pitch_text(canvas, layout, colors, pitches) __render_pitch_count(canvas, layout, colors, pitches) - if strikeout: - if animation: + if play_result and play_result in PLAY_RESULT_UPDATES: + if animation and strikeout: __render_strikeout(canvas, layout, colors, looking) + else: + __render_play_result(canvas, layout, colors, play_result) return plength else: blength = __render_batter_text(canvas, layout, colors, atbat.batter, text_pos) return max(plength, blength) +def __render_play_result(canvas, layout, colors, play_result): + text = "" + if play_result == "single": + text = "1B" + elif play_result == "double": + text = "2B" + elif play_result == "triple": + text = "3B" + elif play_result == "walk": + text = "BB" + elif play_result == "home_run": + text = "HR" + coords = layout.coords("atbat.strikeout") + color = colors.graphics_color("atbat.strikeout") + font = layout.font("atbat.strikeout") + graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], color, text) def __render_strikeout(canvas, layout, colors, looking): coords = layout.coords("atbat.strikeout") @@ -66,7 +86,6 @@ def __render_strikeout(canvas, layout, colors, looking): text = "ꓘ" if looking else "K" graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], color, text) - def __render_batter_text(canvas, layout, colors, batter, text_pos): coords = layout.coords("atbat.batter") color = colors.graphics_color("atbat.batter") diff --git a/renderers/main.py b/renderers/main.py index c98ef020..dd4dd5e0 100644 --- a/renderers/main.py +++ b/renderers/main.py @@ -153,7 +153,7 @@ def __draw_game(self): self.data.scrolling_finished = True else: # draw a live game - if scoreboard.homerun() or scoreboard.strikeout(): + if scoreboard.homerun() or scoreboard.strikeout() or scoreboard.single() or scoreboard.double() or scoreboard.triple() or scoreboard.walk(): self.animation_time += 1 else: self.animation_time = 0 From 31ecbc9db16beb9d8835d9671ce7dc6aee68383a Mon Sep 17 00:00:00 2001 From: jimcreel Date: Fri, 14 Jul 2023 22:03:12 -0700 Subject: [PATCH 21/47] replaced __render_strikeout with __render_play_result --- renderers/games/game.py | 108 ++++++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 42 deletions(-) diff --git a/renderers/games/game.py b/renderers/games/game.py index 7953cdcd..b2c94b78 100644 --- a/renderers/games/game.py +++ b/renderers/games/game.py @@ -9,30 +9,66 @@ from renderers import scrollingtext from renderers.games import nohitter -PLAY_RESULT_UPDATES = ["single", "double", "triple", "walk", "home_run", "strikeout", "strikeout_looking"] +PLAY_RESULT_UPDATES = ["single", "double", "triple", "walk", "intent_walk", "home_run", "strikeout", "strikeout_looking"] + +PLAY_RESULTS = { + "single": { + "short": "1B", + "long": "Single" + }, + "double": { + "short": "2B", + "long": "Double" + }, + "triple": { + "short": "3B", + "long": "Triple" + }, + "home_run": { + "short": "HR", + "long": "Home Run" + }, + "walk": { + "short": "BB", + "long": "Walk" + }, + "intent_walk": { + "short": "IBB", + "long": "Int. Walk" + }, + "strikeout": { + "short": "K", + "long": "K" + }, + "strikeout_looking": { + "short": "ꓘ", + "long" : "ꓘ" + } + +} + def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboard, text_pos, animation_time): pos = 0 - if scoreboard.inning.state == Inning.TOP or scoreboard.inning.state == Inning.BOTTOM: - pos = _render_at_bat( - canvas, - layout, - colors, - scoreboard.atbat, - text_pos, - scoreboard.strikeout(), - scoreboard.strikeout_looking(), - scoreboard.play_result, - (animation_time // 6) % 2, - scoreboard.pitches - - ) + pos = _render_at_bat( + canvas, + layout, + colors, + scoreboard.atbat, + text_pos, + scoreboard.strikeout(), + scoreboard.strikeout_looking(), + scoreboard.play_result, + (animation_time // 6) % 2, + scoreboard.pitches + + ) # Check if we're deep enough into a game and it's a no hitter or perfect game - should_display_nohitter = layout.coords("nohitter")["innings_until_display"] - if scoreboard.inning.number > should_display_nohitter: - if layout.state_is_nohitter(): - nohitter.render_nohit_text(canvas, layout, colors) + should_display_nohitter = layout.coords("nohitter")["innings_until_display"] + if scoreboard.inning.number > should_display_nohitter: + if layout.state_is_nohitter(): + nohitter.render_nohit_text(canvas, layout, colors) _render_count(canvas, layout, colors, scoreboard.pitches) _render_outs(canvas, layout, colors, scoreboard.outs) @@ -48,14 +84,13 @@ def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboa # --------------- at-bat --------------- -def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, strikeout, looking, play_result, animation, pitches: Pitches): +def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, strikeout, looking, play_result, animation, pitches: Pitches): plength = __render_pitcher_text(canvas, layout, colors, atbat.pitcher, pitches, text_pos) __render_pitch_text(canvas, layout, colors, pitches) __render_pitch_count(canvas, layout, colors, pitches) - if play_result and play_result in PLAY_RESULT_UPDATES: - if animation and strikeout: - __render_strikeout(canvas, layout, colors, looking) - else: + if play_result in PLAY_RESULT_UPDATES: + if animation: + if looking: play_result += "_looking" __render_play_result(canvas, layout, colors, play_result) return plength else: @@ -64,26 +99,14 @@ def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, strikeout, lo def __render_play_result(canvas, layout, colors, play_result): text = "" - if play_result == "single": - text = "1B" - elif play_result == "double": - text = "2B" - elif play_result == "triple": - text = "3B" - elif play_result == "walk": - text = "BB" - elif play_result == "home_run": - text = "HR" - coords = layout.coords("atbat.strikeout") - color = colors.graphics_color("atbat.strikeout") - font = layout.font("atbat.strikeout") - graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], color, text) - -def __render_strikeout(canvas, layout, colors, looking): - coords = layout.coords("atbat.strikeout") + if layout.width > 64: + text = PLAY_RESULTS[play_result]["long"] + else: + text = PLAY_RESULTS[play_result]["short"] + print(text) + coords = layout.coords("atbat.batter") if layout.width > 64 else layout.coords("atbat.strikeout") color = colors.graphics_color("atbat.strikeout") font = layout.font("atbat.strikeout") - text = "ꓘ" if looking else "K" graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], color, text) def __render_batter_text(canvas, layout, colors, batter, text_pos): @@ -261,6 +284,7 @@ def __fill_out_circle(canvas, out, color): # --------------- inning information --------------- def _render_inning_break(canvas, layout, colors, inning: Inning): + text_font = layout.font("inning.break.text") num_font = layout.font("inning.break.number") text_coords = layout.coords("inning.break.text") From 88f81bbbcd80fdfc8f08ab29a07d5960db04a287 Mon Sep 17 00:00:00 2001 From: jimcreel Date: Fri, 14 Jul 2023 22:04:05 -0700 Subject: [PATCH 22/47] condensed scoreboard methods to catch all hits in one --- data/scoreboard/__init__.py | 12 +++--------- renderers/main.py | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/data/scoreboard/__init__.py b/data/scoreboard/__init__.py index 88b2c4cf..7e50cd17 100644 --- a/data/scoreboard/__init__.py +++ b/data/scoreboard/__init__.py @@ -42,17 +42,11 @@ def strikeout(self): def strikeout_looking(self): return self.play_result == "strikeout_looking" - def single(self): - return self.play_result == "single" - - def double(self): - return self.play_result == "double" - - def triple(self): - return self.play_result == "triple" + def hit(self): + return self.play_result == "single" or self.play_result == "double" or self.play_result == "triple" def walk(self): - return self.play_result == "walk" + return self.play_result == "walk" or self.play_result == "intent_walk" def get_text_for_reason(self): if self.note: diff --git a/renderers/main.py b/renderers/main.py index dd4dd5e0..bf7eb5ac 100644 --- a/renderers/main.py +++ b/renderers/main.py @@ -153,7 +153,7 @@ def __draw_game(self): self.data.scrolling_finished = True else: # draw a live game - if scoreboard.homerun() or scoreboard.strikeout() or scoreboard.single() or scoreboard.double() or scoreboard.triple() or scoreboard.walk(): + if scoreboard.homerun() or scoreboard.strikeout() or scoreboard.hit() or scoreboard.walk(): self.animation_time += 1 else: self.animation_time = 0 From 943b0a336e0eb6658b13233383369c7fe6a7875b Mon Sep 17 00:00:00 2001 From: jimcreel Date: Sat, 15 Jul 2023 09:30:01 -0700 Subject: [PATCH 23/47] enabled short and long play descriptions in coords files. render all play results through the play_result method --- coordinates/w128h32.json.example | 5 +- coordinates/w128h64.json.example | 5 +- coordinates/w192h64.json.example | 5 +- coordinates/w32h32.json.example | 5 +- coordinates/w64h32.json.example | 5 +- coordinates/w64h64.json.example | 5 +- renderers/games/game.py | 90 +++++++++++++++----------------- 7 files changed, 61 insertions(+), 59 deletions(-) diff --git a/coordinates/w128h32.json.example b/coordinates/w128h32.json.example index d3c59047..61fb3716 100644 --- a/coordinates/w128h32.json.example +++ b/coordinates/w128h32.json.example @@ -183,9 +183,10 @@ "append_pitcher_name": false }, "loop": 64, - "strikeout": { + "play_result": { "x": 84, - "y": 30 + "y": 30, + "desc_length": "Short" } }, "pregame": { diff --git a/coordinates/w128h64.json.example b/coordinates/w128h64.json.example index 52fe6f11..ed8535c6 100644 --- a/coordinates/w128h64.json.example +++ b/coordinates/w128h64.json.example @@ -145,9 +145,10 @@ "append_pitcher_name": false }, "loop": 68, - "strikeout": { + "play_result": { "x": 32, - "y": 60 + "y": 60, + "desc_length": "Long" } }, "batter_count": { diff --git a/coordinates/w192h64.json.example b/coordinates/w192h64.json.example index 9a10db27..3e9fe786 100644 --- a/coordinates/w192h64.json.example +++ b/coordinates/w192h64.json.example @@ -145,9 +145,10 @@ "append_pitcher_name": false }, "loop": 68, - "strikeout": { + "play_result": { "x": 32, - "y": 60 + "y": 60, + "desc_length": "Long" } }, "batter_count": { diff --git a/coordinates/w32h32.json.example b/coordinates/w32h32.json.example index f872886a..da1604d0 100644 --- a/coordinates/w32h32.json.example +++ b/coordinates/w32h32.json.example @@ -163,9 +163,10 @@ "append_pitcher_name": false }, "loop": 16, - "strikeout": { + "play_result": { "x": 33, - "y": 33 + "y": 33, + "desc_length": "Short" } }, "batter_count": { diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index 93a01aff..fe8f6a2a 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -139,10 +139,11 @@ "append_pitcher_name": false }, "loop": 36, - "strikeout": { + "play_result": { "x": 15, "y": 29, - "font_name": "5x7" + "font_name": "5x7", + "desc_length": "Short" }, "hit": { "x": 15, diff --git a/coordinates/w64h64.json.example b/coordinates/w64h64.json.example index bde2213a..93fe3d10 100644 --- a/coordinates/w64h64.json.example +++ b/coordinates/w64h64.json.example @@ -139,9 +139,10 @@ "append_pitcher_name": false }, "loop": 64, - "strikeout": { + "play_result": { "x": 31, - "y": 36 + "y": 36, + "desc_length": "Short" } }, "pregame": { diff --git a/renderers/games/game.py b/renderers/games/game.py index b2c94b78..a3411fc5 100644 --- a/renderers/games/game.py +++ b/renderers/games/game.py @@ -6,46 +6,49 @@ from data.scoreboard.bases import Bases from data.scoreboard.inning import Inning from data.scoreboard.pitches import Pitches + from renderers import scrollingtext from renderers.games import nohitter PLAY_RESULT_UPDATES = ["single", "double", "triple", "walk", "intent_walk", "home_run", "strikeout", "strikeout_looking"] -PLAY_RESULTS = { - "single": { - "short": "1B", - "long": "Single" - }, - "double": { - "short": "2B", - "long": "Double" - }, - "triple": { - "short": "3B", - "long": "Triple" - }, - "home_run": { - "short": "HR", - "long": "Home Run" - }, - "walk": { - "short": "BB", - "long": "Walk" - }, - "intent_walk": { - "short": "IBB", - "long": "Int. Walk" - }, - "strikeout": { - "short": "K", - "long": "K" - }, - "strikeout_looking": { - "short": "ꓘ", - "long" : "ꓘ" - } +PLAY_RESULTS ={ + "single": { + "short": "1B", + "long": "Single" + }, + "double": { + "short": "2B", + "long": "Double" + }, + "triple": { + "short": "3B", + "long": "Triple" + }, + "home_run": { + "short": "HR", + "long": "Home Run" + }, + "walk": { + "short": "BB", + "long": "Walk" + }, + "intent_walk": { + "short": "IBB", + "long": "Int. Walk" + }, + "strikeout": { + "short": "K", + "long": "K" + }, + "strikeout_looking": { + "short": "ꓘ", + "long" : "ꓘ" + } + } + -} + def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboard, text_pos, animation_time): pos = 0 @@ -56,8 +59,6 @@ def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboa colors, scoreboard.atbat, text_pos, - scoreboard.strikeout(), - scoreboard.strikeout_looking(), scoreboard.play_result, (animation_time // 6) % 2, scoreboard.pitches @@ -84,13 +85,12 @@ def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboa # --------------- at-bat --------------- -def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, strikeout, looking, play_result, animation, pitches: Pitches): +def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, play_result, animation, pitches: Pitches): plength = __render_pitcher_text(canvas, layout, colors, atbat.pitcher, pitches, text_pos) __render_pitch_text(canvas, layout, colors, pitches) __render_pitch_count(canvas, layout, colors, pitches) if play_result in PLAY_RESULT_UPDATES: if animation: - if looking: play_result += "_looking" __render_play_result(canvas, layout, colors, play_result) return plength else: @@ -98,15 +98,11 @@ def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, strikeout, lo return max(plength, blength) def __render_play_result(canvas, layout, colors, play_result): - text = "" - if layout.width > 64: - text = PLAY_RESULTS[play_result]["long"] - else: - text = PLAY_RESULTS[play_result]["short"] - print(text) - coords = layout.coords("atbat.batter") if layout.width > 64 else layout.coords("atbat.strikeout") - color = colors.graphics_color("atbat.strikeout") - font = layout.font("atbat.strikeout") + + coords = layout.coords("atbat.play_result") + color = colors.graphics_color("atbat.play_result") + font = layout.font("atbat.play_result") + text = PLAY_RESULTS[play_result][coords["desc_length"]] graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], color, text) def __render_batter_text(canvas, layout, colors, batter, text_pos): From 51a17b2ecf0e98b5a603d582d1c9efc7c62d4141 Mon Sep 17 00:00:00 2001 From: jimcreel Date: Sat, 15 Jul 2023 11:37:24 -0700 Subject: [PATCH 24/47] moved play results to data/plays.py, adjusted play_result positioning --- colors/scoreboard.json.example | 12 +--------- coordinates/w128h32.json.example | 2 +- coordinates/w128h64.json.example | 2 +- coordinates/w192h64.json.example | 2 +- coordinates/w32h32.json.example | 2 +- coordinates/w64h32.json.example | 2 +- coordinates/w64h64.json.example | 2 +- data/plays.py | 36 ++++++++++++++++++++++++++++ renderers/games/game.py | 41 ++------------------------------ 9 files changed, 45 insertions(+), 56 deletions(-) create mode 100644 data/plays.py diff --git a/colors/scoreboard.json.example b/colors/scoreboard.json.example index 6dc30961..b2275f36 100644 --- a/colors/scoreboard.json.example +++ b/colors/scoreboard.json.example @@ -124,17 +124,7 @@ "g": 255, "b": 255 }, - "strikeout": { - "r": 255, - "g": 235, - "b": 59 - }, - "hit": { - "r": 255, - "g": 235, - "b": 59 - }, - "walk": { + "play_result": { "r": 255, "g": 235, "b": 59 diff --git a/coordinates/w128h32.json.example b/coordinates/w128h32.json.example index 61fb3716..4324094b 100644 --- a/coordinates/w128h32.json.example +++ b/coordinates/w128h32.json.example @@ -184,7 +184,7 @@ }, "loop": 64, "play_result": { - "x": 84, + "x": 60, "y": 30, "desc_length": "Short" } diff --git a/coordinates/w128h64.json.example b/coordinates/w128h64.json.example index ed8535c6..eb29fb8a 100644 --- a/coordinates/w128h64.json.example +++ b/coordinates/w128h64.json.example @@ -146,7 +146,7 @@ }, "loop": 68, "play_result": { - "x": 32, + "x": 16, "y": 60, "desc_length": "Long" } diff --git a/coordinates/w192h64.json.example b/coordinates/w192h64.json.example index 3e9fe786..eaf05fc7 100644 --- a/coordinates/w192h64.json.example +++ b/coordinates/w192h64.json.example @@ -146,7 +146,7 @@ }, "loop": 68, "play_result": { - "x": 32, + "x": 16, "y": 60, "desc_length": "Long" } diff --git a/coordinates/w32h32.json.example b/coordinates/w32h32.json.example index da1604d0..9cce89ea 100644 --- a/coordinates/w32h32.json.example +++ b/coordinates/w32h32.json.example @@ -165,7 +165,7 @@ "loop": 16, "play_result": { "x": 33, - "y": 33, + "y": 15, "desc_length": "Short" } }, diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index fe8f6a2a..766e5dbc 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -141,7 +141,7 @@ "loop": 36, "play_result": { "x": 15, - "y": 29, + "y": 15, "font_name": "5x7", "desc_length": "Short" }, diff --git a/coordinates/w64h64.json.example b/coordinates/w64h64.json.example index 93fe3d10..b016e99b 100644 --- a/coordinates/w64h64.json.example +++ b/coordinates/w64h64.json.example @@ -141,7 +141,7 @@ "loop": 64, "play_result": { "x": 31, - "y": 36, + "y": 18, "desc_length": "Short" } }, diff --git a/data/plays.py b/data/plays.py new file mode 100644 index 00000000..027f1057 --- /dev/null +++ b/data/plays.py @@ -0,0 +1,36 @@ +PLAY_RESULT_UPDATES = ["single", "double", "triple", "walk", "intent_walk", "home_run", "strikeout", "strikeout_looking"] + +PLAY_RESULTS ={ + "single": { + "Short": "1B", + "Long": "Single" + }, + "double": { + "Short": "2B", + "Long": "Double" + }, + "triple": { + "Short": "3B", + "Long": "Triple" + }, + "home_run": { + "Short": "HR", + "Long": "Home Run" + }, + "walk": { + "Short": "BB", + "Long": "Walk" + }, + "intent_walk": { + "Short": "IBB", + "Long": "Int. Walk" + }, + "strikeout": { + "Short": "K", + "Long": "K" + }, + "strikeout_looking": { + "Short": "ꓘ", + "Long" : "ꓘ" + } + } diff --git a/renderers/games/game.py b/renderers/games/game.py index a3411fc5..0473e049 100644 --- a/renderers/games/game.py +++ b/renderers/games/game.py @@ -6,49 +6,11 @@ from data.scoreboard.bases import Bases from data.scoreboard.inning import Inning from data.scoreboard.pitches import Pitches +from data.plays import PLAY_RESULT_UPDATES, PLAY_RESULTS from renderers import scrollingtext from renderers.games import nohitter -PLAY_RESULT_UPDATES = ["single", "double", "triple", "walk", "intent_walk", "home_run", "strikeout", "strikeout_looking"] - -PLAY_RESULTS ={ - "single": { - "short": "1B", - "long": "Single" - }, - "double": { - "short": "2B", - "long": "Double" - }, - "triple": { - "short": "3B", - "long": "Triple" - }, - "home_run": { - "short": "HR", - "long": "Home Run" - }, - "walk": { - "short": "BB", - "long": "Walk" - }, - "intent_walk": { - "short": "IBB", - "long": "Int. Walk" - }, - "strikeout": { - "short": "K", - "long": "K" - }, - "strikeout_looking": { - "short": "ꓘ", - "long" : "ꓘ" - } - } - - - def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboard, text_pos, animation_time): pos = 0 @@ -95,6 +57,7 @@ def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, play_result, return plength else: blength = __render_batter_text(canvas, layout, colors, atbat.batter, text_pos) + print(blength) return max(plength, blength) def __render_play_result(canvas, layout, colors, play_result): From 3f94d7126fe36e6dd62760351c111d4fcc29d6e2 Mon Sep 17 00:00:00 2001 From: jimcreel Date: Sat, 15 Jul 2023 11:39:01 -0700 Subject: [PATCH 25/47] removed debug printing --- renderers/games/game.py | 1 - 1 file changed, 1 deletion(-) diff --git a/renderers/games/game.py b/renderers/games/game.py index 0473e049..f785bc57 100644 --- a/renderers/games/game.py +++ b/renderers/games/game.py @@ -57,7 +57,6 @@ def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, play_result, return plength else: blength = __render_batter_text(canvas, layout, colors, atbat.batter, text_pos) - print(blength) return max(plength, blength) def __render_play_result(canvas, layout, colors, play_result): From 585a996ad066816530fa4396a3a86d133adda982 Mon Sep 17 00:00:00 2001 From: jimcreel Date: Sat, 15 Jul 2023 12:01:48 -0700 Subject: [PATCH 26/47] fixing short positioning for small screens --- coordinates/w64h32.json.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index 766e5dbc..fe8f6a2a 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -141,7 +141,7 @@ "loop": 36, "play_result": { "x": 15, - "y": 15, + "y": 29, "font_name": "5x7", "desc_length": "Short" }, From d74e92962cc9570328e180b6e29845eadd9cd9fb Mon Sep 17 00:00:00 2001 From: jimcreel Date: Sat, 15 Jul 2023 12:02:35 -0700 Subject: [PATCH 27/47] fixing positioning for small screens --- coordinates/w32h32.json.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coordinates/w32h32.json.example b/coordinates/w32h32.json.example index 9cce89ea..da1604d0 100644 --- a/coordinates/w32h32.json.example +++ b/coordinates/w32h32.json.example @@ -165,7 +165,7 @@ "loop": 16, "play_result": { "x": 33, - "y": 15, + "y": 33, "desc_length": "Short" } }, From a3ae6f86ed2ca46d5655d875faf4c8ec6e6d2c00 Mon Sep 17 00:00:00 2001 From: jimcreel Date: Sat, 15 Jul 2023 12:03:04 -0700 Subject: [PATCH 28/47] fixing positioning for small screens --- coordinates/w64h64.json.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coordinates/w64h64.json.example b/coordinates/w64h64.json.example index b016e99b..93fe3d10 100644 --- a/coordinates/w64h64.json.example +++ b/coordinates/w64h64.json.example @@ -141,7 +141,7 @@ "loop": 64, "play_result": { "x": 31, - "y": 18, + "y": 36, "desc_length": "Short" } }, From 6d3bdc4720271cc7b040dc5842d6e2249d675ec8 Mon Sep 17 00:00:00 2001 From: Jim Creel Date: Sat, 15 Jul 2023 21:46:09 -0700 Subject: [PATCH 29/47] constantized play results, removed play_result trigger list --- coordinates/w128h64.json.example | 2 +- data/plays.py | 95 ++++++++++++++++++++------------ data/scoreboard/__init__.py | 6 +- renderers/games/game.py | 10 +++- 4 files changed, 72 insertions(+), 41 deletions(-) diff --git a/coordinates/w128h64.json.example b/coordinates/w128h64.json.example index eb29fb8a..6aa5f343 100644 --- a/coordinates/w128h64.json.example +++ b/coordinates/w128h64.json.example @@ -148,7 +148,7 @@ "play_result": { "x": 16, "y": 60, - "desc_length": "Long" + "desc_length": "long" } }, "batter_count": { diff --git a/data/plays.py b/data/plays.py index 027f1057..205a113d 100644 --- a/data/plays.py +++ b/data/plays.py @@ -1,36 +1,61 @@ -PLAY_RESULT_UPDATES = ["single", "double", "triple", "walk", "intent_walk", "home_run", "strikeout", "strikeout_looking"] +SINGLE = "single" +DOUBLE = "double" +TRIPLE = "triple" +HOME_RUN = "home_run" -PLAY_RESULTS ={ - "single": { - "Short": "1B", - "Long": "Single" - }, - "double": { - "Short": "2B", - "Long": "Double" - }, - "triple": { - "Short": "3B", - "Long": "Triple" - }, - "home_run": { - "Short": "HR", - "Long": "Home Run" - }, - "walk": { - "Short": "BB", - "Long": "Walk" - }, - "intent_walk": { - "Short": "IBB", - "Long": "Int. Walk" - }, - "strikeout": { - "Short": "K", - "Long": "K" - }, - "strikeout_looking": { - "Short": "ꓘ", - "Long" : "ꓘ" - } - } +WALK = "walk" +INTENTIONAL_WALK = "intent_walk" + +STRIKEOUT = "strikeout" +STRIKEOUT_LOOKING = "strikeout_looking" + +HITS = [ + SINGLE, + DOUBLE, + TRIPLE +] + +WALKS = [ + WALK, + INTENTIONAL_WALK +] + +STRIKEOUTS = [ + STRIKEOUT, + STRIKEOUT_LOOKING +] + +PLAY_RESULTS = { + SINGLE: { + "short": "1B", + "long": "Single" + }, + DOUBLE: { + "short": "2B", + "long": "Double" + }, + TRIPLE: { + "short": "3B", + "long": "Triple" + }, + HOME_RUN: { + "short": "HR", + "long": "Home Run" + }, + WALK: { + "short": "BB", + "long": "Walk" + }, + INTENTIONAL_WALK: { + "short": "IBB", + "long": "Int. Walk" + }, + STRIKEOUT: { + "short": "K", + "long": "K" + }, + STRIKEOUT_LOOKING: { + "short": "ꓘ", + "long": "ꓘ" + } +} \ No newline at end of file diff --git a/data/scoreboard/__init__.py b/data/scoreboard/__init__.py index 7e50cd17..78f71918 100644 --- a/data/scoreboard/__init__.py +++ b/data/scoreboard/__init__.py @@ -5,6 +5,8 @@ from data.scoreboard.outs import Outs from data.scoreboard.pitches import Pitches from data.scoreboard.team import Team +from data import plays + class Scoreboard: @@ -43,10 +45,10 @@ def strikeout_looking(self): return self.play_result == "strikeout_looking" def hit(self): - return self.play_result == "single" or self.play_result == "double" or self.play_result == "triple" + return self.play_result in plays.HITS def walk(self): - return self.play_result == "walk" or self.play_result == "intent_walk" + return self.play_result in plays.WALKS def get_text_for_reason(self): if self.note: diff --git a/renderers/games/game.py b/renderers/games/game.py index f785bc57..95e01139 100644 --- a/renderers/games/game.py +++ b/renderers/games/game.py @@ -6,7 +6,7 @@ from data.scoreboard.bases import Bases from data.scoreboard.inning import Inning from data.scoreboard.pitches import Pitches -from data.plays import PLAY_RESULT_UPDATES, PLAY_RESULTS +from data.plays import PLAY_RESULTS from renderers import scrollingtext from renderers.games import nohitter @@ -51,7 +51,8 @@ def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, play_result, plength = __render_pitcher_text(canvas, layout, colors, atbat.pitcher, pitches, text_pos) __render_pitch_text(canvas, layout, colors, pitches) __render_pitch_count(canvas, layout, colors, pitches) - if play_result in PLAY_RESULT_UPDATES: + results = list(PLAY_RESULTS.keys()) + if play_result in results: if animation: __render_play_result(canvas, layout, colors, play_result) return plength @@ -64,7 +65,10 @@ def __render_play_result(canvas, layout, colors, play_result): coords = layout.coords("atbat.play_result") color = colors.graphics_color("atbat.play_result") font = layout.font("atbat.play_result") - text = PLAY_RESULTS[play_result][coords["desc_length"]] + try: + text = PLAY_RESULTS[play_result][coords["desc_length"]] + except KeyError: + return # There's no text or coordinates to render graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], color, text) def __render_batter_text(canvas, layout, colors, batter, text_pos): From b6c230c3d4ccc70e6aecfbcc2417bd1817790e77 Mon Sep 17 00:00:00 2001 From: jimcreel Date: Sat, 15 Jul 2023 21:55:10 -0700 Subject: [PATCH 30/47] updated w64h32 to remove hit and walk coords --- coordinates/w64h32.json.example | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index fe8f6a2a..4a25e93d 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -144,17 +144,8 @@ "y": 29, "font_name": "5x7", "desc_length": "Short" - }, - "hit": { - "x": 15, - "y": 29, - "font_name": "5x7" - }, - "walk": { - "x": 15, - "y": 29, - "font_name": "5x7" } + }, "batter_count": { "x": 34, From 03e2b63063c5b73d8050a04dbbc06a8096ef92fb Mon Sep 17 00:00:00 2001 From: jimcreel Date: Sun, 16 Jul 2023 10:12:30 -0700 Subject: [PATCH 31/47] added separate color key for strikeout, updated keys for play_result desc_length in small displays --- colors/scoreboard.json.example | 5 +++++ coordinates/w128h32.json.example | 2 +- coordinates/w32h32.json.example | 2 +- coordinates/w64h32.json.example | 2 +- coordinates/w64h64.json.example | 2 +- renderers/games/game.py | 5 ++++- 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/colors/scoreboard.json.example b/colors/scoreboard.json.example index b2275f36..b016193b 100644 --- a/colors/scoreboard.json.example +++ b/colors/scoreboard.json.example @@ -128,6 +128,11 @@ "r": 255, "g": 235, "b": 59 + }, + "strikeout": { + "r": 255, + "g": 0, + "b": 0 } }, "batter_count": { diff --git a/coordinates/w128h32.json.example b/coordinates/w128h32.json.example index 4324094b..6c224155 100644 --- a/coordinates/w128h32.json.example +++ b/coordinates/w128h32.json.example @@ -186,7 +186,7 @@ "play_result": { "x": 60, "y": 30, - "desc_length": "Short" + "desc_length": "short" } }, "pregame": { diff --git a/coordinates/w32h32.json.example b/coordinates/w32h32.json.example index da1604d0..5d8b0ed6 100644 --- a/coordinates/w32h32.json.example +++ b/coordinates/w32h32.json.example @@ -166,7 +166,7 @@ "play_result": { "x": 33, "y": 33, - "desc_length": "Short" + "desc_length": "short" } }, "batter_count": { diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index 4a25e93d..0a268819 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -143,7 +143,7 @@ "x": 15, "y": 29, "font_name": "5x7", - "desc_length": "Short" + "desc_length": "short" } }, diff --git a/coordinates/w64h64.json.example b/coordinates/w64h64.json.example index 93fe3d10..611f98f0 100644 --- a/coordinates/w64h64.json.example +++ b/coordinates/w64h64.json.example @@ -142,7 +142,7 @@ "play_result": { "x": 31, "y": 36, - "desc_length": "Short" + "desc_length": "short" } }, "pregame": { diff --git a/renderers/games/game.py b/renderers/games/game.py index 95e01139..4773c4dc 100644 --- a/renderers/games/game.py +++ b/renderers/games/game.py @@ -63,7 +63,10 @@ def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, play_result, def __render_play_result(canvas, layout, colors, play_result): coords = layout.coords("atbat.play_result") - color = colors.graphics_color("atbat.play_result") + if "strikeout" in play_result: + color = colors.graphics_color("atbat.strikeout") + else: + color = colors.graphics_color("atbat.play_result") font = layout.font("atbat.play_result") try: text = PLAY_RESULTS[play_result][coords["desc_length"]] From 6557fd9e0a837065030f9e48286f66bb1a178e85 Mon Sep 17 00:00:00 2001 From: pawptart Date: Sun, 16 Jul 2023 19:37:55 -0400 Subject: [PATCH 32/47] Bump version to v6.5.0 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 6bb344b1..96825d08 100644 --- a/version.py +++ b/version.py @@ -1,5 +1,5 @@ SCRIPT_NAME = "MLB LED Scoreboard" -SCRIPT_VERSION = "6.4.1" +SCRIPT_VERSION = "6.5.0" if __name__ == "__main__": From 5c28bdc3e01a6ce83dbded2679a873355286c98a Mon Sep 17 00:00:00 2001 From: pawptart Date: Sun, 16 Jul 2023 19:50:41 -0400 Subject: [PATCH 33/47] Standardize desc_length field on lowercase values --- README.md | 2 +- coordinates/README.md | 2 +- coordinates/w128h32.json.example | 2 +- coordinates/w128h64.json.example | 2 +- coordinates/w32h32.json.example | 2 +- coordinates/w64h32.json.example | 2 +- coordinates/w64h64.json.example | 2 +- renderers/games/game.py | 7 +++---- 8 files changed, 10 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e2fb9182..4d77072d 100755 --- a/README.md +++ b/README.md @@ -259,7 +259,7 @@ A default `config.json.example` file is included for reference. Copy this file t ### Additional Features * Runs/Hits/Errors - Runs are always shown on the games screen, but you can enable or adjust spacing of a "runs, hits, errors" display. Take a look at the [coordinates readme file](/coordinates/README.md) for details. -* Pitch Data - Pitch data can be shown on the game screen, See the [coordinates readme file](/coordinates/README.md) for details. In addition, the Short and Long pitch description can be changed in data/pitches.py +* Pitch Data - Pitch data can be shown on the game screen, See the [coordinates readme file](/coordinates/README.md) for details. In addition, the `short` and `long` pitch description can be changed in data/pitches.py ### Flags diff --git a/coordinates/README.md b/coordinates/README.md index a86d8872..301bd590 100644 --- a/coordinates/README.md +++ b/coordinates/README.md @@ -21,7 +21,7 @@ The layout can have a couple of different states where things are rendered diffe ## Pitch Data * `enabled` (true/false) turn feature on/off * `mph` (true/false) When rendering pitch speed add mph after (99 mph) -* `desc_length` (Short/Long) The short or long pitch type description, you can change both the short and long description to your liking in data/pitches as long as you do not change the index value. +* `desc_length` (short/long) The short or long pitch type description, you can change both the short and long description to your liking in data/pitches as long as you do not change the index value. ## Updates The software develops and releases features with full support for the default layouts, so custom layouts may look unsatisfactory if you update to later versions of the scoreboard. If you as a user decide to create a custom layout file, you are responsible for tweaking the coordinates to your liking with each update. diff --git a/coordinates/w128h32.json.example b/coordinates/w128h32.json.example index bc916972..e8b4269c 100644 --- a/coordinates/w128h32.json.example +++ b/coordinates/w128h32.json.example @@ -173,7 +173,7 @@ "y": 50, "enabled": false, "mph": false, - "desc_length": "Short" + "desc_length": "short" }, "pitch_count": { "font_name": "4x6", diff --git a/coordinates/w128h64.json.example b/coordinates/w128h64.json.example index babb6d54..0004716b 100644 --- a/coordinates/w128h64.json.example +++ b/coordinates/w128h64.json.example @@ -135,7 +135,7 @@ "y": 50, "enabled": true, "mph": true, - "desc_length": "Long" + "desc_length": "long" }, "pitch_count": { "font_name": "4x6", diff --git a/coordinates/w32h32.json.example b/coordinates/w32h32.json.example index 44598f71..f70569e3 100644 --- a/coordinates/w32h32.json.example +++ b/coordinates/w32h32.json.example @@ -153,7 +153,7 @@ "y": 50, "enabled": false, "mph": false, - "desc_length": "Short" + "desc_length": "short" }, "pitch_count": { "font_name": "4x6", diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index b8345e24..0dbe4721 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -130,7 +130,7 @@ "y": 50, "enabled": false, "mph": false, - "desc_length": "Short" + "desc_length": "short" }, "pitch_count": { "x": 1, diff --git a/coordinates/w64h64.json.example b/coordinates/w64h64.json.example index f97c1b27..106bda6e 100644 --- a/coordinates/w64h64.json.example +++ b/coordinates/w64h64.json.example @@ -129,7 +129,7 @@ "y": 50, "enabled": false, "mph": false, - "desc_length": "Short" + "desc_length": "short" }, "pitch_count": { "font_name": "4x6", diff --git a/renderers/games/game.py b/renderers/games/game.py index 4773c4dc..d309ade7 100644 --- a/renderers/games/game.py +++ b/renderers/games/game.py @@ -61,7 +61,6 @@ def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, play_result, return max(plength, blength) def __render_play_result(canvas, layout, colors, play_result): - coords = layout.coords("atbat.play_result") if "strikeout" in play_result: color = colors.graphics_color("atbat.strikeout") @@ -69,7 +68,7 @@ def __render_play_result(canvas, layout, colors, play_result): color = colors.graphics_color("atbat.play_result") font = layout.font("atbat.play_result") try: - text = PLAY_RESULTS[play_result][coords["desc_length"]] + text = PLAY_RESULTS[play_result][coords["desc_length"].lower()] except KeyError: return # There's no text or coordinates to render graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], color, text) @@ -130,9 +129,9 @@ def __render_pitch_text(canvas, layout, colors, pitches: Pitches): mph = " " if coords["mph"]: mph = "mph " - if coords["desc_length"] == "Long": + if coords["desc_length"].lower() == "long": pitch_text = str(pitches.last_pitch_speed) + mph + pitches.last_pitch_type_long - elif coords["desc_length"] == "Short": + elif coords["desc_length"].lower() == "short": pitch_text = str(pitches.last_pitch_speed) + mph + pitches.last_pitch_type else: pitch_text = "" From 2c2db6537079e808f975ed988c506f408265fb52 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Sun, 16 Jul 2023 20:32:03 -0400 Subject: [PATCH 34/47] Allow more config of play results --- coordinates/w128h32.json.example | 9 +++- coordinates/w128h64.json.example | 9 +++- coordinates/w192h64.json.example | 11 ++++- coordinates/w32h32.json.example | 9 +++- coordinates/w64h32.json.example | 12 ++++- coordinates/w64h64.json.example | 9 +++- data/plays.py | 78 +++++++++++--------------------- renderers/games/game.py | 33 +++++++++----- 8 files changed, 100 insertions(+), 70 deletions(-) diff --git a/coordinates/w128h32.json.example b/coordinates/w128h32.json.example index e8b4269c..c181acdf 100644 --- a/coordinates/w128h32.json.example +++ b/coordinates/w128h32.json.example @@ -183,10 +183,17 @@ "append_pitcher_name": false }, "loop": 64, + "strikeout": { + "x": 60, + "y": 30, + "desc_length": "short", + "enabled": true + }, "play_result": { "x": 60, "y": 30, - "desc_length": "short" + "desc_length": "short", + "enabled": true } }, "pregame": { diff --git a/coordinates/w128h64.json.example b/coordinates/w128h64.json.example index 0004716b..184a7e0c 100644 --- a/coordinates/w128h64.json.example +++ b/coordinates/w128h64.json.example @@ -145,10 +145,17 @@ "append_pitcher_name": false }, "loop": 68, + "strikeout": { + "x": 16, + "y": 60, + "desc_length": "long", + "enabled": true + }, "play_result": { "x": 16, "y": 60, - "desc_length": "long" + "desc_length": "long", + "enabled": true } }, "batter_count": { diff --git a/coordinates/w192h64.json.example b/coordinates/w192h64.json.example index 5334dc6a..6f010530 100644 --- a/coordinates/w192h64.json.example +++ b/coordinates/w192h64.json.example @@ -135,7 +135,7 @@ "y": 50, "enabled": true, "mph": true, - "desc_length": "Long" + "desc_length": "long" }, "pitch_count": { "font_name": "4x6", @@ -145,10 +145,17 @@ "append_pitcher_name": false }, "loop": 68, + "strikeout": { + "x": 16, + "y": 60, + "desc_length": "long", + "enabled": true + }, "play_result": { "x": 16, "y": 60, - "desc_length": "Long" + "desc_length": "long", + "enabled": true } }, "batter_count": { diff --git a/coordinates/w32h32.json.example b/coordinates/w32h32.json.example index f70569e3..31a8dd0a 100644 --- a/coordinates/w32h32.json.example +++ b/coordinates/w32h32.json.example @@ -163,10 +163,17 @@ "append_pitcher_name": false }, "loop": 16, + "strikeout": { + "x": 33, + "y": 33, + "desc_length": "short", + "enabled": false + }, "play_result": { "x": 33, "y": 33, - "desc_length": "short" + "desc_length": "short", + "enabled": false } }, "batter_count": { diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index 0dbe4721..a9ee194b 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -139,13 +139,21 @@ "append_pitcher_name": false }, "loop": 36, + "strikeout": { + "x": 15, + "y": 29, + "font_name": "5x7", + "desc_length": "short", + "enabled": true + }, "play_result": { "x": 15, "y": 29, "font_name": "5x7", - "desc_length": "short" + "desc_length": "short", + "enabled": true } - + }, "batter_count": { "x": 34, diff --git a/coordinates/w64h64.json.example b/coordinates/w64h64.json.example index 106bda6e..5ec273bb 100644 --- a/coordinates/w64h64.json.example +++ b/coordinates/w64h64.json.example @@ -139,10 +139,17 @@ "append_pitcher_name": false }, "loop": 64, + "strikeout": { + "x": 31, + "y": 36, + "desc_length": "short", + "enabled": true + }, "play_result": { "x": 31, "y": 36, - "desc_length": "short" + "desc_length": "short", + "enabled": true } }, "pregame": { diff --git a/data/plays.py b/data/plays.py index 205a113d..c1701093 100644 --- a/data/plays.py +++ b/data/plays.py @@ -1,61 +1,37 @@ -SINGLE = "single" -DOUBLE = "double" -TRIPLE = "triple" +SINGLE = "single" +DOUBLE = "double" +TRIPLE = "triple" HOME_RUN = "home_run" -WALK = "walk" +WALK = "walk" INTENTIONAL_WALK = "intent_walk" +HIT_BY_PITCH = "hit_by_pitch" -STRIKEOUT = "strikeout" +STRIKEOUT = "strikeout" +STRIKEOUT_ALT = "strike_out" STRIKEOUT_LOOKING = "strikeout_looking" -HITS = [ - SINGLE, - DOUBLE, - TRIPLE -] +ERROR = "error" +FIELDERS_CHOICE = "fielders_choice" -WALKS = [ - WALK, - INTENTIONAL_WALK -] +HITS = [SINGLE, DOUBLE, TRIPLE] -STRIKEOUTS = [ - STRIKEOUT, - STRIKEOUT_LOOKING -] +WALKS = [WALK, INTENTIONAL_WALK, HIT_BY_PITCH] + +OTHERS = [ERROR, FIELDERS_CHOICE] + +STRIKEOUTS = [STRIKEOUT, STRIKEOUT_ALT, STRIKEOUT_LOOKING] PLAY_RESULTS = { - SINGLE: { - "short": "1B", - "long": "Single" - }, - DOUBLE: { - "short": "2B", - "long": "Double" - }, - TRIPLE: { - "short": "3B", - "long": "Triple" - }, - HOME_RUN: { - "short": "HR", - "long": "Home Run" - }, - WALK: { - "short": "BB", - "long": "Walk" - }, - INTENTIONAL_WALK: { - "short": "IBB", - "long": "Int. Walk" - }, - STRIKEOUT: { - "short": "K", - "long": "K" - }, - STRIKEOUT_LOOKING: { - "short": "ꓘ", - "long": "ꓘ" - } -} \ No newline at end of file + SINGLE: {"short": "1B", "long": "Single"}, + DOUBLE: {"short": "2B", "long": "Double"}, + TRIPLE: {"short": "3B", "long": "Triple"}, + WALK: {"short": "BB", "long": "Walk"}, + INTENTIONAL_WALK: {"short": "IBB", "long": "Int. Walk"}, + STRIKEOUT: {"short": "K", "long": "K"}, + STRIKEOUT_ALT: {"short": "K", "long": "K"}, + STRIKEOUT_LOOKING: {"short": "ꓘ", "long": "ꓘ"}, + HIT_BY_PITCH: {"short": "HBP", "long": "Hit Bttr"}, + ERROR: {"short": "E", "long": "Error"}, + FIELDERS_CHOICE: {"short": "FC", "long": "Fielder's Chc"}, +} diff --git a/renderers/games/game.py b/renderers/games/game.py index d309ade7..5c2e1960 100644 --- a/renderers/games/game.py +++ b/renderers/games/game.py @@ -24,8 +24,7 @@ def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboa scoreboard.play_result, (animation_time // 6) % 2, scoreboard.pitches - - ) + ) # Check if we're deep enough into a game and it's a no hitter or perfect game should_display_nohitter = layout.coords("nohitter")["innings_until_display"] @@ -38,7 +37,7 @@ def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboa _render_bases(canvas, layout, colors, scoreboard.bases, scoreboard.homerun(), (animation_time % 16) // 5) _render_inning_display(canvas, layout, colors, scoreboard.inning) - + else: _render_inning_break(canvas, layout, colors, scoreboard.inning) _render_due_up(canvas, layout, colors, scoreboard.atbat) @@ -47,12 +46,12 @@ def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboa # --------------- at-bat --------------- -def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, play_result, animation, pitches: Pitches): +def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, play_result, animation, pitches: Pitches): plength = __render_pitcher_text(canvas, layout, colors, atbat.pitcher, pitches, text_pos) __render_pitch_text(canvas, layout, colors, pitches) __render_pitch_count(canvas, layout, colors, pitches) results = list(PLAY_RESULTS.keys()) - if play_result in results: + if play_result in results and __should_render_play_result(play_result, layout): if animation: __render_play_result(canvas, layout, colors, play_result) return plength @@ -60,19 +59,31 @@ def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, play_result, blength = __render_batter_text(canvas, layout, colors, atbat.batter, text_pos) return max(plength, blength) + +def __should_render_play_result(play_result, layout): + if "strikeout" in play_result: + coords = layout.coords("atbat.strikeout") + else: + coords = layout.coords("atbat.play_result") + return coords["enabled"] + + def __render_play_result(canvas, layout, colors, play_result): - coords = layout.coords("atbat.play_result") if "strikeout" in play_result: color = colors.graphics_color("atbat.strikeout") - else: + coords = layout.coords("atbat.strikeout") + font = layout.font("atbat.strikeout") + else: color = colors.graphics_color("atbat.play_result") - font = layout.font("atbat.play_result") + coords = layout.coords("atbat.play_result") + font = layout.font("atbat.play_result") try: - text = PLAY_RESULTS[play_result][coords["desc_length"].lower()] + text = PLAY_RESULTS[play_result][coords["desc_length"].lower()] except KeyError: - return # There's no text or coordinates to render + return graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], color, text) + def __render_batter_text(canvas, layout, colors, batter, text_pos): coords = layout.coords("atbat.batter") color = colors.graphics_color("atbat.batter") @@ -248,7 +259,7 @@ def __fill_out_circle(canvas, out, color): # --------------- inning information --------------- def _render_inning_break(canvas, layout, colors, inning: Inning): - + text_font = layout.font("inning.break.text") num_font = layout.font("inning.break.number") text_coords = layout.coords("inning.break.text") From 9ab5327b7d445e70be4bbbdc4b714e4f8c302995 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Sun, 16 Jul 2023 20:37:52 -0400 Subject: [PATCH 35/47] Add test --- tests/test_data_up_to_date.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_data_up_to_date.py b/tests/test_data_up_to_date.py index ba7062e5..64e8b4c8 100644 --- a/tests/test_data_up_to_date.py +++ b/tests/test_data_up_to_date.py @@ -17,18 +17,22 @@ def test_status_complete(self): self.assertSetEqual(official_statuses, our_statuses) def test_teams_complete(self): - teams = statsapi.get('teams', {'sportIds':1})['teams'] + teams = statsapi.get("teams", {"sportIds": 1})["teams"] - abbr_to_full = {t['teamName']:t['name'] for t in teams} + abbr_to_full = {t["teamName"]: t["name"] for t in teams} self.assertEqual(abbr_to_full, data.teams.TEAM_FULL) - full_to_abbr = {t['name']:t['abbreviation'] for t in teams} + full_to_abbr = {t["name"]: t["abbreviation"] for t in teams} self.assertEqual(full_to_abbr, data.teams.TEAM_ABBR_LN) def test_pitches_complete(self): - pitches = set(p["code"] for p in statsapi.meta("pitchTypes")) + pitches = set(p["code"] for p in statsapi.meta("pitchTypes")) self.assertSetEqual(pitches, set(data.pitches.PITCH_SHORT.keys())) self.assertSetEqual(pitches, set(data.pitches.PITCH_LONG.keys())) + def test_results_exist(self): + events = set(e["code"] for e in statsapi.meta("eventTypes")) + events.add("strikeout_looking") # our custom event + self.assertTrue(events.issuperset(data.plays.PLAY_RESULTS.keys())) if __name__ == "__main__": From 58b8093f271f5eba79a36f9c8254979c75207177 Mon Sep 17 00:00:00 2001 From: jimcreel Date: Thu, 20 Jul 2023 13:27:18 -0700 Subject: [PATCH 36/47] Added play result documentation to readme and config/readme --- README.md | 3 +++ coordinates/README.md | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index 4d77072d..d98638d2 100755 --- a/README.md +++ b/README.md @@ -261,6 +261,9 @@ A default `config.json.example` file is included for reference. Copy this file t * Pitch Data - Pitch data can be shown on the game screen, See the [coordinates readme file](/coordinates/README.md) for details. In addition, the `short` and `long` pitch description can be changed in data/pitches.py +* Previous Play Data - Data for the previous play can be shown on the game screen. See the [coordinates readme file](/coordinates/README.md) for details. Long and short play descriptions can be changed in data/plays.py **NOTE** Because play result data is ephemeral, not every play result will be displayed. Situations like a mound visit, injury, or other timeout immediately following a play often cause the play result to be immediately replaced on the MLB API. + + ### Flags You can configure your LED matrix with the same flags used in the [rpi-rgb-led-matrix](https://github.com/hzeller/rpi-rgb-led-matrix) library. More information on these arguments can be found in the library documentation. diff --git a/coordinates/README.md b/coordinates/README.md index 301bd590..0aa630e8 100644 --- a/coordinates/README.md +++ b/coordinates/README.md @@ -23,6 +23,10 @@ The layout can have a couple of different states where things are rendered diffe * `mph` (true/false) When rendering pitch speed add mph after (99 mph) * `desc_length` (short/long) The short or long pitch type description, you can change both the short and long description to your liking in data/pitches as long as you do not change the index value. +## Play Result +* `enabled` (true/false) turn feature on/off +* `desc_length` (short/long) The short or long play result description. You can change both the short and long description to your liking in data/plays. + ## Updates The software develops and releases features with full support for the default layouts, so custom layouts may look unsatisfactory if you update to later versions of the scoreboard. If you as a user decide to create a custom layout file, you are responsible for tweaking the coordinates to your liking with each update. From 1b99d526a47076f874293b0694ddc6f54ca3090b Mon Sep 17 00:00:00 2001 From: Tyler Porter Date: Thu, 20 Jul 2023 17:19:22 -0400 Subject: [PATCH 37/47] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d98638d2..198bc75e 100755 --- a/README.md +++ b/README.md @@ -261,8 +261,8 @@ A default `config.json.example` file is included for reference. Copy this file t * Pitch Data - Pitch data can be shown on the game screen, See the [coordinates readme file](/coordinates/README.md) for details. In addition, the `short` and `long` pitch description can be changed in data/pitches.py -* Previous Play Data - Data for the previous play can be shown on the game screen. See the [coordinates readme file](/coordinates/README.md) for details. Long and short play descriptions can be changed in data/plays.py **NOTE** Because play result data is ephemeral, not every play result will be displayed. Situations like a mound visit, injury, or other timeout immediately following a play often cause the play result to be immediately replaced on the MLB API. - +* Previous Play Data - Data for the previous play can be shown on the game screen. See the [coordinates readme file](/coordinates/README.md) for details. Long and short play descriptions can be changed in data/plays.py + * **NOTE:** Because play result data is ephemeral, not every play result will be displayed. Situations like a mound visit, injury, or other timeout immediately following a play often cause the play result to be immediately replaced on the MLB API. ### Flags From 32106698a3020a8044e80681179133a59fdb11f0 Mon Sep 17 00:00:00 2001 From: Tyler Porter Date: Tue, 15 Aug 2023 19:35:03 -0400 Subject: [PATCH 38/47] Run tests against Python 3.11 in CI --- .github/workflows/run_unittest_on_pr_open.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_unittest_on_pr_open.yml b/.github/workflows/run_unittest_on_pr_open.yml index 1697b03c..c50fde17 100644 --- a/.github/workflows/run_unittest_on_pr_open.yml +++ b/.github/workflows/run_unittest_on_pr_open.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} @@ -25,4 +25,4 @@ jobs: pip install -r ./requirements.txt - name: Test with unittest run: | - python -m unittest discover -s ./tests -p 'test_*.py' \ No newline at end of file + python -m unittest discover -s ./tests -p 'test_*.py' From 5af31858bafed814f1588eb4b8c37c8930bfbcaa Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Sun, 1 Oct 2023 20:56:16 -0400 Subject: [PATCH 39/47] Fix is_postseason logic --- renderers/main.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/renderers/main.py b/renderers/main.py index bf7eb5ac..54fc0e64 100644 --- a/renderers/main.py +++ b/renderers/main.py @@ -232,11 +232,12 @@ def __draw_standings(self, cond: Callable[[], bool]): self.canvas = self.matrix.SwapOnVSync(self.canvas) - if self.data.standings.is_postseason() and update % 20 == 0: - if self.standings_league == "NL": - self.standings_league = "AL" - else: - self.standings_league = "NL" + if self.data.standings.is_postseason(): + if update % 20 == 0: + if self.standings_league == "NL": + self.standings_league = "AL" + else: + self.standings_league = "NL" elif self.canvas.width == 32 and update % 5 == 0: if self.standings_stat == "w": self.standings_stat = "l" From 4a87a1d6ec7960d84a2fa70b455837b220471c57 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Sun, 1 Oct 2023 21:08:51 -0400 Subject: [PATCH 40/47] Update data --- data/status.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data/status.py b/data/status.py index e68e19cb..aadc9d34 100644 --- a/data/status.py +++ b/data/status.py @@ -84,6 +84,7 @@ INSTANT_REPLAY = "Instant Replay" # Live MANAGER_CHALLENGE = "Manager challenge" # Live MANAGER_CHALLENGE_CATCHDROP_IN_OUTFIELD = "Manager challenge: Catch/drop in outfield" # Live +MANAGER_CHALLENGE_CATCHERS_INTERFERENCE = "Manager challenge: Catchers Interference" # Live MANAGER_CHALLENGE_CLOSE_PLAY_AT_1ST = "Manager challenge: Close play at 1st" # Live MANAGER_CHALLENGE_FAIRFOUL_IN_OUTFIELD = "Manager challenge: Fair/foul in outfield" # Live MANAGER_CHALLENGE_FAN_INTERFERENCE = "Manager challenge: Fan interference" # Live @@ -157,6 +158,8 @@ UMPIRE_REVIEW_TOUCHING_A_BASE = "Umpire review: Touching a base" # Live UMPIRE_REVIEW_TRAP_PLAY_IN_OUTFIELD = "Umpire review: Trap play in outfield" # Live UMPIRE_REVIEW_SHIFT_VIOLATION = "Umpire review: Def Shift Violation" # Live +UMPIRE_CHALLENGE_PITCH_RESULT = "Umpire Challenge: Pitch Result" # Live +PLAYER_CHALLENGE_PITCH_RESULT = "Player challenge: Pitch Result" # Live UNKNOWN = "Unknown" # Other WARMUP = "Warmup" # Live WRITING = "Writing" # Other @@ -170,6 +173,7 @@ INSTANT_REPLAY, MANAGER_CHALLENGE, MANAGER_CHALLENGE_CATCHDROP_IN_OUTFIELD, + MANAGER_CHALLENGE_CATCHERS_INTERFERENCE, MANAGER_CHALLENGE_CLOSE_PLAY_AT_1ST, MANAGER_CHALLENGE_FAIRFOUL_IN_OUTFIELD, MANAGER_CHALLENGE_FAN_INTERFERENCE, @@ -211,6 +215,8 @@ UMPIRE_REVIEW_TOUCHING_A_BASE, UMPIRE_REVIEW_TRAP_PLAY_IN_OUTFIELD, UMPIRE_REVIEW_SHIFT_VIOLATION, + UMPIRE_CHALLENGE_PITCH_RESULT, + PLAYER_CHALLENGE_PITCH_RESULT, ] GAME_STATE_PREGAME = [SCHEDULED, PREGAME, WARMUP] @@ -300,6 +306,7 @@ FORFEIT_WILLFUL_RULE_VIOLATION, MANAGER_CHALLENGE, MANAGER_CHALLENGE_CATCHDROP_IN_OUTFIELD, + MANAGER_CHALLENGE_CATCHERS_INTERFERENCE, MANAGER_CHALLENGE_CLOSE_PLAY_AT_1ST, MANAGER_CHALLENGE_FAIRFOUL_IN_OUTFIELD, MANAGER_CHALLENGE_FAN_INTERFERENCE, @@ -371,6 +378,8 @@ UMPIRE_REVIEW_TOUCHING_A_BASE, UMPIRE_REVIEW_TRAP_PLAY_IN_OUTFIELD, UMPIRE_REVIEW_SHIFT_VIOLATION, + UMPIRE_CHALLENGE_PITCH_RESULT, + PLAYER_CHALLENGE_PITCH_RESULT, WRITING, UNKNOWN, ] From 0a86733322b5d0a387565ecda496948ba29c84a9 Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Sun, 1 Oct 2023 21:32:08 -0400 Subject: [PATCH 41/47] Formatting --- renderers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renderers/main.py b/renderers/main.py index 54fc0e64..4bb72b53 100644 --- a/renderers/main.py +++ b/renderers/main.py @@ -233,7 +233,7 @@ def __draw_standings(self, cond: Callable[[], bool]): self.canvas = self.matrix.SwapOnVSync(self.canvas) if self.data.standings.is_postseason(): - if update % 20 == 0: + if update % 20 == 0: if self.standings_league == "NL": self.standings_league = "AL" else: From ece1a72a8a016aebf0621297aba0738b6cd718a5 Mon Sep 17 00:00:00 2001 From: Tyler Porter Date: Mon, 2 Oct 2023 09:46:33 -0400 Subject: [PATCH 42/47] Update version.py --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 96825d08..31937c36 100644 --- a/version.py +++ b/version.py @@ -1,5 +1,5 @@ SCRIPT_NAME = "MLB LED Scoreboard" -SCRIPT_VERSION = "6.5.0" +SCRIPT_VERSION = "6.5.1" if __name__ == "__main__": From 381a072f6333945f64226c3df8be2cad5bd91b2e Mon Sep 17 00:00:00 2001 From: pawptart Date: Wed, 4 Oct 2023 00:31:59 -0400 Subject: [PATCH 43/47] Disable Pillow until upgrade path can be found --- main.py | 26 ++++++++++++++++++++------ renderers/offday.py | 15 +++++++++++---- requirements.txt | 13 ++++++++++++- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/main.py b/main.py index 05aece94..d0e9b99e 100755 --- a/main.py +++ b/main.py @@ -1,33 +1,47 @@ import sys from data.screens import ScreenType +import debug if sys.version_info <= (3, 5): - print("Error: Please run with python3") + debug.error("Please run with python3") sys.exit(1) import statsapi statsapi_version = tuple(map(int, statsapi.__version__.split("."))) if statsapi_version < (1, 5, 1): - print("Error: We require MLB-StatsAPI 1.5.1 or higher. You may need to re-run install.sh") + debug.error("We require MLB-StatsAPI 1.5.1 or higher. You may need to re-run install.sh") sys.exit(1) elif statsapi_version < (1, 6, 1): - print("Warning: We recommend MLB-StatsAPI 1.6.1 or higher. You may want to re-run install.sh") + debug.warning("We recommend MLB-StatsAPI 1.6.1 or higher. You may want to re-run install.sh") import logging import os import threading import time -from PIL import Image +# TODO: This code addresses CVE-2023-4863 in Pillow < 10.0.1, which requires Python 3.8+ +# See requirements.txt for rationale. +try: + from PIL import Image + + pil_version = tuple(map(int, Image.__version__.split("."))) + if pil_version < (10, 0, 1): + debug.warning(f"Attempted to load an insecure PIL version ({Image.__version__}). We require PIL 10.0.1 or higher.") + + raise ModuleNotFoundError + + PIL_LOADED = True +except: + debug.warning("PIL failed to load -- images will not be displayed.") + PIL_LOADED = False # Important! Import the driver first to initialize it, then import submodules as needed. import driver from driver import RGBMatrix, __version__ from utils import args, led_matrix_options -import debug from data import Data from data.config import Config from renderers.main import MainRenderer @@ -60,7 +74,7 @@ def main(matrix, config_base): # MLB image disabled when using renderer, for now. # see: https://github.com/ty-porter/RGBMatrixEmulator/issues/9#issuecomment-922869679 - if os.path.exists(logo) and driver.is_hardware(): + if os.path.exists(logo) and driver.is_hardware() and PIL_LOADED: logo = Image.open(logo) matrix.SetImage(logo.convert("RGB")) logo.close() diff --git a/renderers/offday.py b/renderers/offday.py index fd400c38..2bafab9a 100644 --- a/renderers/offday.py +++ b/renderers/offday.py @@ -2,7 +2,13 @@ import time -import PIL.Image +try: + from PIL import Image + + PIL_LOADED = True +except: + + PIL_LOADED = False from data.time_formats import TIME_FORMAT_12H from data.config.color import Color @@ -38,9 +44,10 @@ def __render_clock(canvas, layout, colors, time_format): def __render_weather(canvas, layout, colors, weather): if weather.available(): - image_file = weather.icon_filename() - weather_icon = PIL.Image.open(image_file) - __render_weather_icon(canvas, layout, colors, weather_icon) + if PIL_LOADED: + image_file = weather.icon_filename() + weather_icon = Image.open(image_file) + __render_weather_icon(canvas, layout, colors, weather_icon) __render_weather_text(canvas, layout, colors, weather.conditions, "conditions") __render_weather_text(canvas, layout, colors, weather.temperature_string(), "temperature") __render_weather_text(canvas, layout, colors, weather.wind_speed_string(), "wind_speed") diff --git a/requirements.txt b/requirements.txt index 7d58f280..50f84928 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,17 @@ feedparser==6.0.10 MLB_StatsAPI>=1.6.1 -Pillow==9.3.0 +# PIL is affected by CVE-2023-4863 +# https://nvd.nist.gov/vuln/detail/CVE-2023-4863 +# +# The vulnerability is patched in Pillow >= 10.0.1. This version does not support Python 3.7 due to this version being end-of-life. +# Python 3.7.3 is the default Python version for Raspbian / Raspberry Pi OS, and upgrading Python versions is difficult for non-technical users. +# +# Therefore, addressing the CVE at this time would be a breaking change for most users without an easy upgrade path to Python 3.8+. +# +# Dependabot PR: +# https://github.com/MLB-LED-Scoreboard/mlb-led-scoreboard/pull/502 +# +# Pillow==9.3.1 pyowm==3.3.0 RGBMatrixEmulator>=0.8.4 tzlocal==4.2 From 4ff82c0af45cb8af713f9b7327eb39e6c7b7cef4 Mon Sep 17 00:00:00 2001 From: Tyler Porter Date: Thu, 5 Oct 2023 21:44:06 -0400 Subject: [PATCH 44/47] Update version.py --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 31937c36..0fbb7e62 100644 --- a/version.py +++ b/version.py @@ -1,5 +1,5 @@ SCRIPT_NAME = "MLB LED Scoreboard" -SCRIPT_VERSION = "6.5.1" +SCRIPT_VERSION = "7.0.0" if __name__ == "__main__": From 91514a0a72d9511d88064862008a21bd22bd5d15 Mon Sep 17 00:00:00 2001 From: Tyler Porter Date: Tue, 20 Feb 2024 23:15:54 -0500 Subject: [PATCH 45/47] Remove Slack join message --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 198bc75e..492ba999 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # mlb-led-scoreboard ![Current Version](https://img.shields.io/github/v/release/MLB-LED-Scoreboard/MLB-LED-Scoreboard) [![Join Discord](https://img.shields.io/badge/discord-join-blue.svg)](https://discord.gg/FdD6ec9fdt) - [![Join Slack](https://img.shields.io/badge/slack-join%20(deprecated)-blue.svg)](https://join.slack.com/t/mlb-led-scoreboard/shared_invite/zt-1f6n4axo4-r32OH7dlSAjEjstFV4RDNQ) + Project header From 42124fddc6fca70eece9e34556b54181d9b866c3 Mon Sep 17 00:00:00 2001 From: Tyler Porter Date: Wed, 21 Feb 2024 13:30:03 -0500 Subject: [PATCH 46/47] Use fully-qualified asset path for logo --- main.py | 6 +++--- version.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index d0e9b99e..aed14378 100755 --- a/main.py +++ b/main.py @@ -70,12 +70,12 @@ def main(matrix, config_base): debug.log("Using rgbmatrix version %s", __version__) # Draw startup screen - logo = "assets/mlb-w" + str(matrix.width) + "h" + str(matrix.height) + ".png" + logo_path = os.path.abspath("./assets/mlb-w" + str(matrix.width) + "h" + str(matrix.height) + ".png") # MLB image disabled when using renderer, for now. # see: https://github.com/ty-porter/RGBMatrixEmulator/issues/9#issuecomment-922869679 - if os.path.exists(logo) and driver.is_hardware() and PIL_LOADED: - logo = Image.open(logo) + if os.path.exists(logo_path) and driver.is_hardware() and PIL_LOADED: + logo = Image.open(logo_path) matrix.SetImage(logo.convert("RGB")) logo.close() diff --git a/version.py b/version.py index 0fbb7e62..16717fca 100644 --- a/version.py +++ b/version.py @@ -1,5 +1,5 @@ SCRIPT_NAME = "MLB LED Scoreboard" -SCRIPT_VERSION = "7.0.0" +SCRIPT_VERSION = "7.0.1" if __name__ == "__main__": From 9b39f8e5c94a67893179e05bb6db83f1450223d8 Mon Sep 17 00:00:00 2001 From: Tyler Porter Date: Wed, 21 Feb 2024 16:11:06 -0500 Subject: [PATCH 47/47] Use imported Image instead of referencing PIL --- data/weather.py | 4 ++-- renderers/offday.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/weather.py b/data/weather.py index 50e228f1..e6dd6476 100644 --- a/data/weather.py +++ b/data/weather.py @@ -1,4 +1,4 @@ -import time +import os, time import pyowm @@ -102,7 +102,7 @@ def wind_string(self): return "{} {}".format(self.wind_speed_string(), self.wind_dir_string()) def icon_filename(self): - return "assets/weather/{}.png".format(self.icon_name) + return os.path.abspath("./assets/weather/{}.png".format(self.icon_name)) def __should_update(self): endtime = time.time() diff --git a/renderers/offday.py b/renderers/offday.py index 2bafab9a..94a4a042 100644 --- a/renderers/offday.py +++ b/renderers/offday.py @@ -70,7 +70,7 @@ def __render_weather_icon(canvas, layout, colors, weather_icon): if resize: weather_icon = weather_icon.resize( - (weather_icon.width * resize, weather_icon.height * resize), PIL.Image.NEAREST + (weather_icon.width * resize, weather_icon.height * resize), Image.NEAREST ) for x in range(weather_icon.width): for y in range(weather_icon.height):