diff --git a/modes/fantasy.py b/modes/fantasy.py new file mode 100644 index 0000000..e4e89be --- /dev/null +++ b/modes/fantasy.py @@ -0,0 +1,178 @@ +import requests +from PIL import Image +from time import time +from rgbmatrix import graphics +from vars import PANEL_HEIGHT, PANEL_WIDTH, GAME_WIDTH, DIVIDER_COLOR, Colors, font, font_small + +players = { + "team1": [], + "team2": [] +} +last_fetch = 0 + +scroll_x = 0 +scroll_speed = 10 +frames_per_tick = 2 +tick = 0 +times_scrolled = 0 +virtual_canvas = None +virtual_dirty = True + +def rbg(color_tuple): + r, g, b = color_tuple + return graphics.Color(r, b, g) + +def fetch_data(): + try: + resp = requests.get("https://api.alexav.gg/v4/sports/fantasy", timeout=5) + resp.raise_for_status() + + for player in resp["team1"]["players"]: + players['team1'].append({ + "first_name": player['first_name'], + "last_name": player['last_name'], + "abbr_name": f"{player['first_name'][0]}. {player['last_name']}", + "position": player['position'], + "team": player['team'], + "injury_status": player['injury_status'], + "injury_body_part": player['injury_body_part'] + }) + for player in resp["team2"]["players"]: + players['team1'].append({ + "first_name": player['first_name'], + "last_name": player['last_name'], + "abbr_name": f"{player['first_name'][0]}. {player['last_name']}", + "position": player['position'], + "team": player['team'], + "injury_status": player['injury_status'], + "injury_body_part": player['injury_body_part'] + }) + + return players + except Exception as e: + print(f"[fantasy] fetch error: {e}") + return None + +def draw_text_overlay(canvas, players, scroll_x): + total_width = GAME_WIDTH * len(players) + + for i, player in enumerate(players): + base_x = (i * GAME_WIDTH) - scroll_x + + # draw twice to handle the wrap-around copy + for wrap in [0, total_width]: + x = base_x + wrap + + # cull slots fully off screen + if x + GAME_WIDTH < 0 or x >= PANEL_WIDTH: + continue + + graphics.DrawText(canvas, font_small, x + 18, 11, + rbg(Colors.WHITE.value), player['abbr_name']) + graphics.DrawText(canvas, font_small, x + 18, 27, + rbg(Colors.WHITE.value), player['team']) + # graphics.DrawText(canvas, font, x + 40, 13, + # rbg(Colors.WHITE.value), str(game["away_score"])) + # graphics.DrawText(canvas, font, x + 40, 29, + # rbg(Colors.WHITE.value), str(game["home_score"])) + + # # if the time is shown it should be split between lines + # # if not, just display the status + # if "AM" in game["status"] or "PM" in game["status"]: + # game_status_split = game["status"].split("-") + # date = game_status_split[0].strip() + # time = game_status_split[1].strip() + + # graphics.DrawText(canvas, font_small, x + 60, 10, + # rbg(Colors.YELLOW.value), date) + # graphics.DrawText(canvas, font_small, x + 60, 20, + # rbg(Colors.YELLOW.value), time) + # graphics.DrawText(canvas, font_small, x + 60, 30, + # rbg(Colors.YELLOW.value), game["venue"]) + # else: + # graphics.DrawText(canvas, font_small, x + 65, 20, + # rbg(Colors.YELLOW.value), game["status"]) + +def render_player(matrix, img, player, x_offset): + league = "nfl" + + away_logo = matrix.load_logo(league, player['team']) + home_logo = matrix.load_logo(league, player['team']) + if away_logo: + img.paste(away_logo.resize((14, 14)), (x_offset, 0)) + if home_logo: + img.paste(home_logo.resize((14, 14)), (x_offset, 16)) + + # divider on right edge (except last slot handled by wrapping) + for row in range(PANEL_HEIGHT): + img.putpixel((x_offset + GAME_WIDTH - 1, row), DIVIDER_COLOR) + +def build_canvas(matrix, team): + total_players = len(players[team]) + total_width = GAME_WIDTH * (total_players + 4) + img = Image.new("RGB", (total_width, PANEL_HEIGHT), (0,0,0)) + + for i, player in enumerate(total_players * 2): + if i >= total_players + 4: + break + render_player(matrix, img, player, i * GAME_WIDTH) + + return img, total_players + +def blit_slice(canvas, pil_img, x_offset): + total_width = pil_img.width + for x in range(PANEL_WIDTH): + src_x = (x_offset + x) % total_width + for y in range(PANEL_HEIGHT): + r, g, b = pil_img.getpixel((src_x, y)) + canvas.SetPixel(x, y, r, b, g) + +def draw_frame(matrix): + global players, last_fetch, virtual_canvas, virtual_dirty, scroll_x, tick + global total_players + current_team = "team1" + now = time() + + if now - last_fetch > 30 or not players['team1'] or not players['team2']: + players = fetch_data() + last_fetch = now + virtual_dirty = True + + if virtual_dirty or virtual_canvas is None: + result = build_canvas(matrix, current_team) + if result: + virtual_canvas, total_players = result + virtual_dirty = False + scroll_x = 0 + + matrix.canvas.Clear() + + if not len(players[current_team] > 0): + graphics.DrawText(matrix.canvas, font, 10, 22, + rbg(Colors.RED.value), + "No players found") + scroll_x = tick = 0 + return matrix.canvas + + total_scroll_width = GAME_WIDTH * len(players[current_team]) + if virtual_canvas: + blit_slice(matrix.canvas, virtual_canvas, scroll_x) + draw_text_overlay(matrix.canvas, players[current_team], scroll_x) + + tick += 1 + if tick >= frames_per_tick: + tick = 0 + next_x = scroll_x + scroll_speed + if next_x >= total_scroll_width: + scroll_x = 0 + tick = 0 + + if current_team == "team1": + current_team = "team2" + return matrix.canvas + else: + current_team = "team1" + return None + scroll_x = next_x + + return matrix.canvas \ No newline at end of file