Skip to content

Commit

Permalink
Begin rendering live games
Browse files Browse the repository at this point in the history
  • Loading branch information
ty-porter committed Mar 17, 2024
1 parent cb5d82f commit 42085ee
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 15 deletions.
28 changes: 18 additions & 10 deletions rewrite/data/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from data.update_status import UpdateStatus
from data import status as GameState
from data.team import TeamType
from data.pitches import Pitches

from utils import logger as ScoreboardLogger
from utils import value_at_keypath
from utils import format_id, value_at_keypath


class Game:
Expand Down Expand Up @@ -146,12 +147,12 @@ def decision_pitcher_id(self, decision):
return value_at_keypath(self.data, f"liveData.decisions.{decision}").get("id", None)

def full_name(self, player):
ID = Game.format_id(player)
ID = format_id(player)

return value_at_keypath(self.data, f"gameData.players.{ID}").get("fullName", "")

def pitcher_stat(self, player, stat, team=None):
ID = Game.format_id(player)
ID = format_id(player)

keypath = lambda team, ID: value_at_keypath(
self.data, f"liveData.boxscore.teams.{team}.players.{ID}.seasonStats"
Expand All @@ -163,6 +164,20 @@ def pitcher_stat(self, player, stat, team=None):
stats = keypath(team, ID).get("pitching", None) or keypath(team, ID).get("pitching", {})

return stats[stat]

def man_on(self, base_number):
base = { 1: "first", 2: "second", 3: "third" }.get(base_number, None)

if not base:
return None

return value_at_keypath(self.data, f"liveData.linescore.offense.{base}").get("id", None)

def pitches(self):
return Pitches(self)

def outs(self):
return value_at_keypath(self.data, "liveData.linescore").get("outs", 0)

def pregame_weather(self):
return value_at_keypath(self.data, "gameData.weather")
Expand All @@ -174,13 +189,6 @@ def series_status(self):
# TODO: Reimplement series status
return "0-0"

@staticmethod
def format_id(ID):
if "ID" in str(ID):
return ID

return "ID" + str(ID)

"""
Home / Away data accessors.
Expand Down
134 changes: 134 additions & 0 deletions rewrite/data/pitches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from utils import format_id, value_at_keypath

class Pitches:

# A list of mlb pitch types appearing in statcast
# from statsapi.meta("pitchTypes")
# Dont change the index, but feel free to change
# the descriptions

PITCH_LONG = {
"AB": "Auto Ball", # MLB default is "Automatic Ball"
"AS": "Auto Strike", # MLB default is "Automatic Strike"
"CH": "Change-up",
"CU": "Curveball",
"CS": "Slow Curve",
"EP": "Eephus",
"FC": "Cutter",
"FA": "Fastball",
"FF": "Fastball", # MLB default is "Four-Seam Fastball"
"FO": "Forkball",
"FS": "Splitter",
"FT": "2 Seamer", # MLB default is "Two-Seam Fastball"
"GY": "Gyroball",
"IN": "Int Ball", # MLB default is "Intentional Ball"
"KC": "Knuckle Curve",
"KN": "Knuckleball",
"NP": "No Pitch",
"PO": "Pitchout",
"SC": "Screwball",
"SI": "Sinker",
"SL": "Slider",
"ST": "Sweeper",
"SV": "Slurve",
"UN": "Unknown",
}

PITCH_SHORT = {
"AB": "AB",
"AS": "AS",
"CH": "CH",
"CU": "CU",
"CS": "CS",
"EP": "EP",
"FC": "FC",
"FA": "FA",
"FF": "FF",
"FO": "FO",
"FS": "FS",
"FT": "FT",
"GY": "GY",
"IN": "IN",
"KC": "KC",
"KN": "KN",
"NP": "NP",
"PO": "PO",
"SC": "SC",
"SI": "SI",
"SL": "SL",
"SV": "SV",
"ST": "SW", # MLB default is "ST"
"UN": "UN",
}

def __init__(self, game):
self.game = game

self.balls = self.balls()
self.strikes = self.strikes()
self.pitch_count = self.current_pitcher_pitch_count()

last_pitch = self.last_pitch()
self.last_pitch_speed = "0"
self.last_pitch_type = Pitches.PITCH_SHORT["UN"]
self.last_pitch_type_long = Pitches.PITCH_LONG["UN"]

if last_pitch:
self.last_pitch_speed = f"{round(last_pitch[0])}"
self.last_pitch_type = Pitches.fetch_short(last_pitch[1])
self.last_pitch_type_long = Pitches.fetch_long(last_pitch[1])

def balls(self):
return self.__fetch_count_part("balls")

def strikes(self):
return self.__fetch_count_part("strikes")

def last_pitch(self):
# TODO: Clean this up.
try:
play = self.game.data["liveData"]["plays"].get("currentPlay", {}).get("playEvents", [{}])[-1]
if play.get("isPitch", False):
return (
play["pitchData"].get("startSpeed", 0),
play["details"]["type"]["code"],
play["details"]["type"]["description"],
)
except:
return None

def current_pitcher_pitch_count(self):
# TODO: Clean this up
try:
pitcher_id = self.game.data["liveData"]["linescore"]["defense"]["pitcher"]["id"]

# TODO: ID formatting probably doesn't belong on Game object if it's being used here
ID = format_id(pitcher_id)
try:
return self.game.data["liveData"]["boxscore"]["teams"]["away"]["players"][ID]["stats"]["pitching"][
"numberOfPitches"
]
except:
return self.game.data["liveData"]["boxscore"]["teams"]["home"]["players"][ID]["stats"]["pitching"][
"numberOfPitches"
]
except:
return 0

def __fetch_count_part(self, part):
return value_at_keypath(self.game.data, "liveData.linescore").get(part, 0)

@staticmethod
def fetch_long(value):
return Pitches.PITCH_LONG.get(value, Pitches.PITCH_LONG["UN"])

@staticmethod
def fetch_short(value):
return Pitches.PITCH_SHORT.get(value, Pitches.PITCH_SHORT["UN"])

def __str__(self) -> str:
return (
f"Count: {self.balls} - {self.strikes}. "
+ f"Last pitch: {self.last_pitch_speed}mph {self.last_pitch_type} {self.last_pitch_type_long} "
+ f" Total pitches: {self.pitch_count}"
)
7 changes: 6 additions & 1 deletion rewrite/data/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ def update(self):

return UpdateStatus.FAIL

self.game = self.games[self.__next_game_index()]
# TODO: remove debug
# self.game = self.games[self.__next_game_index()]
for game in self.games:
if game.is_live():
self.game = game
break

self.__request_transition_to_game(self.game)

Expand Down
7 changes: 7 additions & 0 deletions rewrite/presenters/live_game.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class LiveGamePresenter:
def __init__(self, game, config):
self.game = game
self.config = config

def batter_count_text(self):
return "{}-{}".format(self.game.pitches().balls, self.game.pitches().strikes)
53 changes: 53 additions & 0 deletions rewrite/screens/components/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from driver import graphics

class Base:
def __init__(self, number, screen):
self.number = number
self.screen = screen

def render(self):
color = self.colors.graphics_color(f"bases.{self.number}B")
coords = self.layout.coords(f"bases.{self.number}B")

self.__render_outline(coords, color)

if self.game.man_on(self.number):
self.__render_runner(coords, color)

def __render_outline(self, coords, color):
x, y = coords.x, coords.y
size = coords.size
half = abs(size // 2)

graphics.DrawLine(self.canvas, x + half, y, x, y + half, color)
graphics.DrawLine(self.canvas, x + half, y, x + size, y + half, color)
graphics.DrawLine(self.canvas, x + half, y + size, x, y + half, color)
graphics.DrawLine(self.canvas, x + half, y + size, x + size, y + half, color)

def __render_runner(self, coords, color):
x, y = coords.x, coords.y
size = coords.size
half = abs(size // 2)
for offset in range(1, half + 1):
graphics.DrawLine(self.canvas, x + half - offset, y + size - offset, x + half + offset, y + size - offset, color)
graphics.DrawLine(self.canvas, x + half - offset, y + offset, x + half + offset, y + offset, color)

@property
def game(self):
return self.screen.game

@property
def canvas(self):
return self.screen.canvas

@property
def config(self):
return self.screen.config

@property
def colors(self):
return self.screen.colors

@property
def layout(self):
return self.screen.layout
48 changes: 48 additions & 0 deletions rewrite/screens/components/out.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from driver import graphics

from utils.graphics import DrawRect

class Out:
def __init__(self, number, screen):
self.number = number
self.screen = screen

def render(self):
color = self.colors.graphics_color(f"outs.{self.number}")
coords = self.layout.coords(f"outs.{self.number}")

if self.game.outs() >= self.number:
self.__render_out(coords, color)
else:
self.__render_outline(coords, color)


def __render_outline(self, coords, color):
x, y, size = coords.x, coords.y, coords.size

DrawRect(self.canvas, x, y, size, size, color, filled=False)

def __render_out(self, coords, color):
x, y, size = coords.x, coords.y, coords.size

DrawRect(self.canvas, x, y, size, size, color, filled=True)

@property
def game(self):
return self.screen.game

@property
def canvas(self):
return self.screen.canvas

@property
def config(self):
return self.screen.config

@property
def colors(self):
return self.screen.colors

@property
def layout(self):
return self.screen.layout
44 changes: 41 additions & 3 deletions rewrite/screens/games/live_game.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,52 @@
from driver import graphics

from screens.games.base import GameScreen
from screens.components.base import Base
from screens.components.out import Out
from presenters.live_game import LiveGamePresenter


class LiveGameScreen(GameScreen):
MAX_DURATION_SECONDS = 5

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.bases = [
Base(1, self),
Base(2, self),
Base(3, self)
]

self.outs = [
Out(1, self),
Out(2, self),
Out(3, self),
]

def render(self):
game_text = "It's a game!"
presenter = self.create_cached_object("live_game_presenter", LiveGamePresenter, self.game, self.config)

self.__render_bases()
self.__render_outs()
self.__render_count(presenter)

# Overlay banners
self.away_team_banner.render()
self.home_team_banner.render()

def __render_bases(self):
for base in self.bases:
base.render()

font, font_size = self.config.layout.font("4x6")
def __render_outs(self):
for out in self.outs:
out.render()

graphics.DrawText(self.canvas, font, 0, 10, (255, 255, 255), game_text)
def __render_count(self, presenter):
text = presenter.batter_count_text()
font, font_size = self.layout.font_for("batter_count")
coords = self.layout.coords("batter_count")
color = self.colors.graphics_color("batter_count")

graphics.DrawText(self.canvas, font, coords.x, coords.y, color, text)
6 changes: 6 additions & 0 deletions rewrite/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,9 @@ def value_at_keypath(current, keypath):
current = current.get(key, {})

return current

def format_id(ID):
if "ID" in str(ID):
return ID

return "ID" + str(ID)
Loading

0 comments on commit 42085ee

Please sign in to comment.