diff --git a/README.md b/README.md index 67322297..2a3ca395 100755 --- a/README.md +++ b/README.md @@ -177,6 +177,8 @@ 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 + ### 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/colors/scoreboard.json.example b/colors/scoreboard.json.example index b7eeb96b..b4d340b5 100644 --- a/colors/scoreboard.json.example +++ b/colors/scoreboard.json.example @@ -114,6 +114,11 @@ "g": 235, "b": 59 }, + "pitch": { + "r": 255, + "g": 255, + "b": 255 + }, "strikeout": { "r": 255, "g": 235, diff --git a/coordinates/README.md b/coordinates/README.md index 6672ead3..c500b32f 100644 --- a/coordinates/README.md +++ b/coordinates/README.md @@ -18,6 +18,11 @@ The layout can have a couple of different states where things are rendered diffe * `compress_digits` will reduce the space between digits when the number of runs or hits is > 9. * `spacing` is the number of pixels between the runs/hits and hits/errors. +## 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. + ## 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 81bbdf4c..d0aefb87 100644 --- a/coordinates/w128h32.json.example +++ b/coordinates/w128h32.json.example @@ -167,6 +167,15 @@ "y": 30, "width": 40 }, + "pitch": { + "font_name": "4x6", + "x": 1, + "y": 50, + "width": 41, + "enabled": false, + "mph": false, + "desc_length": "Short" + }, "loop": 64, "strikeout": { "x": 84, diff --git a/coordinates/w128h64.json.example b/coordinates/w128h64.json.example index b823ebec..b21d82ee 100644 --- a/coordinates/w128h64.json.example +++ b/coordinates/w128h64.json.example @@ -1,22 +1,22 @@ { "defaults": { - "font_name": "9x18B" + "font_name": "7x13" }, "bases": { "1B": { "x": 112, "y": 42, - "size": 14 + "size": 10 }, "2B": { "x": 103, "y": 33, - "size": 14 + "size": 10 }, "3B": { "x": 94, "y": 42, - "size": 14 + "size": 10 } }, "final": { @@ -46,7 +46,7 @@ }, "due_up": { "due": { - "font_name": "7x13B", + "font_name": "5x7", "x": 38, "y": 44 }, @@ -55,13 +55,13 @@ "y": 56 }, "divider": { - "draw": true, + "draw": false, "x": 36, "y_start": 32, "y_end": 64 }, "leadoff": { - "font_name": "7x13B", + "font_name": "7x13", "x": 64, "y": 42 }, @@ -76,8 +76,8 @@ } }, "number": { - "x": 93, - "y": 45, + "x": 87, + "y": 42, "nohit": { "x": 93, "y": 42 @@ -88,46 +88,55 @@ } }, "arrow": { - "size": 7, + "size": 6, "up": { - "x_offset": -12, - "y_offset": -10 + "x_offset": -11, + "y_offset": -8 }, "down": { - "x_offset": -12, - "y_offset": -2 + "x_offset": -11, + "y_offset": -4 } } }, "outs": { "1": { - "x": 102, - "y": 58, - "size": 4 + "x": 95, + "y": 56, + "size": 5 }, "2": { - "x": 108, - "y": 58, - "size": 4 + "x": 106, + "y": 56, + "size": 5 }, "3": { - "x": 114, - "y": 58, - "size": 4 + "x": 118, + "y": 56, + "size": 5 } }, "atbat": { "batter": { - "font_name": "7x13B", + "font_name": "5x7", "x": 1, "y": 60, "width": 41 }, "pitcher": { - "font_name": "7x13B", + "font_name": "5x7", "x": 1, - "y": 44, - "width": 48 + "y": 40, + "width": 41 + }, + "pitch": { + "font_name": "4x6", + "x": 1, + "y": 50, + "width": 41, + "enabled": true, + "mph": true, + "desc_length": "Long" }, "loop": 68, "strikeout": { @@ -137,7 +146,7 @@ }, "batter_count": { "x": 66, - "y": 60, + "y": 62, "nohit": { "x": 66, "y": 64 @@ -246,27 +255,31 @@ }, "name": { "away": { + "font_name": "9x18B", "x": 4, - "y": 12 + "y": 13 }, "home": { + "font_name": "9x18B", "x": 4, - "y": 28 + "y": 29 } }, "runs": { "runs_hits_errors": { - "show": false, - "compress_digits": false, - "spacing": 3 - }, + "show": true, + "compress_digits": true, + "spacing": 5 + }, "away": { - "x": 120, - "y": 12 + "font_name": "9x18B", + "x": 126, + "y": 13 }, "home": { - "x": 120, - "y": 28 + "font_name": "9x18B", + "x": 126, + "y": 29 } } }, @@ -321,4 +334,4 @@ "y": 31 } } -} \ No newline at end of file +} diff --git a/coordinates/w32h32.json.example b/coordinates/w32h32.json.example index 86401cc7..e4e88529 100644 --- a/coordinates/w32h32.json.example +++ b/coordinates/w32h32.json.example @@ -147,6 +147,15 @@ "y": 33, "width": 12 }, + "pitch": { + "font_name": "4x6", + "x": 1, + "y": 50, + "width": 41, + "enabled": false, + "mph": false, + "desc_length": "Short" + }, "loop": 16, "strikeout": { "x": 33, diff --git a/coordinates/w64h32.json.example b/coordinates/w64h32.json.example index 72574d0b..108819ab 100644 --- a/coordinates/w64h32.json.example +++ b/coordinates/w64h32.json.example @@ -125,6 +125,15 @@ "y": 21, "width": 24 }, + "pitch": { + "font_name": "4x6", + "x": 1, + "y": 50, + "width": 41, + "enabled": false, + "mph": false, + "desc_length": "Short" + }, "loop": 36, "strikeout": { "x": 15, diff --git a/coordinates/w64h64.json.example b/coordinates/w64h64.json.example index f5340894..bb752760 100644 --- a/coordinates/w64h64.json.example +++ b/coordinates/w64h64.json.example @@ -123,6 +123,15 @@ "y": 28, "width": 50 }, + "pitch": { + "font_name": "4x6", + "x": 1, + "y": 50, + "width": 41, + "enabled": false, + "mph": false, + "desc_length": "Short" + }, "loop": 64, "strikeout": { "x": 31, diff --git a/data/game.py b/data/game.py index 69bb2bd1..200a078c 100644 --- a/data/game.py +++ b/data/game.py @@ -9,9 +9,10 @@ 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," - + "currentPlay,result,eventType,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,losses,saves,era,hits,errors,weather,condition,temp,wind" + + "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," + + "losses,saves,era,hits,errors,weather,condition,temp,wind" ) SCHEDULE_API_FIELDS = "dates,date,games,status,detailedState,abstractGameState,reason" @@ -225,6 +226,13 @@ def strikes(self): def outs(self): return self._data["liveData"]["linescore"].get("outs", 0) + def last_pitch(self): + try: + play = self._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 note(self): try: return self._data["liveData"]["linescore"]["note"] diff --git a/data/pitches.py b/data/pitches.py new file mode 100644 index 00000000..a66606ce --- /dev/null +++ b/data/pitches.py @@ -0,0 +1,47 @@ +#A list of mlb pitch types appearing in statcast +#https://www.daktronics.com/en-us/support/kb/DD3312647 +#Dont change the index, but feel free to change +#the descriptions + +PITCH_LONG = { + "AB": "Automatic Ball", + "AS": "Automatic Strike", + "CH": "Change-up", + "CU": "Curveball", + "EP": "Eephus", + "FC": "Cutter", + "FF": "4 Seam Fastball", #MLB default is "Four-Seam Fastball" + "FO": "Forkball", + "FS": "Splitter", + "FT": "2 Seam Fastball", #MLB default is "Two-Seam Fastball" + "GY": "Gyroball", + "IN": "Intentional Ball", + "KC": "Knuckle Curve", + "KN": "Knuckleball", + "NP": "No Pitch", + "SC": "Screwball", + "SI": "Sinker", + "SL": "Slider", + "UN": "Unknown" +} +PITCH_SHORT = { + "AB": "AB", + "AS": "AS", + "CH": "CH", + "CU": "CU", + "EP": "EP", + "FC": "FC", + "FF": "FF", + "FO": "FO", + "FS": "FS", + "FT": "FT", + "GY": "GY", + "IN": "IN", + "KC": "KC", + "KN": "KN", + "NP": "NP", + "SC": "SC", + "SI": "SI", + "SL": "SL", + "UN": "UN" +} \ No newline at end of file diff --git a/data/scoreboard/pitches.py b/data/scoreboard/pitches.py index cbbe34d9..917f0598 100644 --- a/data/scoreboard/pitches.py +++ b/data/scoreboard/pitches.py @@ -1,7 +1,19 @@ from data.game import Game - +import data.pitches class Pitches: def __init__(self, game: Game): self.balls = game.balls() self.strikes = game.strikes() + last_pitch = game.last_pitch() + if last_pitch is None: + self.last_pitch_speed = "0" + self.last_pitch_type = "UK" + self.last_pitch_type_long = "Unknown" + else: + self.last_pitch_speed = f"{round(last_pitch[0])}" + self.last_pitch_type = data.pitches.PITCH_SHORT[last_pitch[1]] + self.last_pitch_type_long = data.pitches.PITCH_LONG[last_pitch[1]] + + def __str__(self) -> str: + return f"Count: {self.balls} - {self.strikes}. Last pitch: {self.last_pitch_speed}mph {self.last_pitch_type} {self.last_pitch_long}" diff --git a/renderers/games/game.py b/renderers/games/game.py index 810656a4..411453ef 100644 --- a/renderers/games/game.py +++ b/renderers/games/game.py @@ -28,6 +28,7 @@ def render_live_game(canvas, layout: Layout, colors: Color, scoreboard: Scoreboa scoreboard.strikeout(), scoreboard.strikeout_looking(), (animation_time // 6) % 2, + scoreboard.pitches ) # Check if we're deep enough into a game and it's a no hitter or perfect game @@ -49,8 +50,9 @@ 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): +def _render_at_bat(canvas, layout, colors, atbat: AtBat, text_pos, strikeout, looking, animation, pitches: Pitches): plength = __render_pitcher_text(canvas, layout, colors, atbat.pitcher, text_pos) + __render_pitch_text(canvas, layout, colors, pitches) if strikeout: if animation: __render_strikeout(canvas, layout, colors, looking) @@ -108,7 +110,24 @@ def __render_pitcher_text(canvas, layout, colors, pitcher, text_pos): graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], color, "P:") return pos - +def __render_pitch_text(canvas, layout, colors, pitches: Pitches): +#def __render_pitch_text(canvas, layout, colors): + coords = layout.coords("atbat.pitch") + color = colors.graphics_color("atbat.pitch") + font = layout.font("atbat.pitch") + bgcolor = colors.graphics_color("default.background") + if(int(pitches.last_pitch_speed) > 0 and layout.coords("atbat.pitch")["enabled"]): + mph= " " + if(layout.coords("atbat.pitch")["mph"]): + mph="mph " + if(layout.coords("atbat.pitch")["desc_length"]=="Long"): + pitch_text = str(pitches.last_pitch_speed) + mph + pitches.last_pitch_type_long + elif(layout.coords("atbat.pitch")["desc_length"]=="Short"): + pitch_text = str(pitches.last_pitch_speed) + mph + pitches.last_pitch_type + else: + pitch_text = None + graphics.DrawText(canvas, font["font"], coords["x"], coords["y"], color, pitch_text) + # --------------- bases --------------- def _render_bases(canvas, layout, colors, bases: Bases, home_run, animation): base_runners = bases.runners diff --git a/renderers/main.py b/renderers/main.py index de2d5056..45ec63f7 100644 --- a/renderers/main.py +++ b/renderers/main.py @@ -146,7 +146,6 @@ def __draw_game(self): game = self.data.current_game bgcolor = self.data.config.scoreboard_colors.color("default.background") self.canvas.Fill(bgcolor["r"], bgcolor["g"], bgcolor["b"]) - scoreboard = Scoreboard(game) layout = self.data.config.layout colors = self.data.config.scoreboard_colors