diff --git a/addon.xml b/addon.xml index dd839f7..918e69d 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + @@ -22,12 +22,10 @@ Requires an MLB.tv account - - optional zip code setting to detect and label blackout streams (does not circumvent blackouts) - - tweaked skip timings, particularly for non-action pitches and overturned reviews - - option to disable video length padding (in case of proxy conflicts) - - restored 24-hour time format label, applied it to Big Inning listitem - - label regional national games - - added additional action play type in skip monitor + - live game changer based on leverage index + - streamlined blackout detection + - reduced autoplay checks + - improved monitor logging en all diff --git a/main.py b/main.py index f8f9290..3d30bed 100644 --- a/main.py +++ b/main.py @@ -144,11 +144,18 @@ # Logout elif mode == 400: + from resources.lib.account import Account account = Account() account.logout() dialog = xbmcgui.Dialog() dialog.notification(LOCAL_STRING(30260), LOCAL_STRING(30261), ICON, 5000, False) +# Game Changer +elif mode == 500: + from resources.lib.mlbmonitor import MLBMonitor + mlbmonitor = MLBMonitor() + mlbmonitor.change_monitor(blackout.split(',')) + # play all recaps or condensed games for selected date elif mode == 900: playAllHighlights(stream_date) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 352536e..e390f1f 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -327,3 +327,19 @@ msgstr "" msgctxt "#30416" msgid "Disable video length padding (in case of proxy conflict)" msgstr "" + +msgctxt "#30417" +msgid "MLB.TV Game Changer" +msgstr "" + +msgctxt "#30418" +msgid "Automatically switches between the highest leverage active live games." +msgstr "" + +msgctxt "#30419" +msgid "Currently there are no active live games" +msgstr "" + +msgctxt "#30420" +msgid "Game Changer delay (seconds)" +msgstr "" diff --git a/resources/lib/globals.py b/resources/lib/globals.py index 2f877c7..76acb7f 100644 --- a/resources/lib/globals.py +++ b/resources/lib/globals.py @@ -7,6 +7,7 @@ import json from kodi_six import xbmc, xbmcvfs, xbmcplugin, xbmcgui, xbmcaddon import random +from collections import namedtuple, deque if sys.version_info[0] > 2: import http @@ -51,10 +52,12 @@ ASK_TO_SKIP = str(settings.getSetting(id='ask_to_skip')) AUTO_PLAY_FAV = str(settings.getSetting(id='auto_play_fav')) ONLY_FREE_GAMES = str(settings.getSetting(id="only_free_games")) +GAME_CHANGER_DELAY = int(settings.getSetting(id="game_changer_delay")) #Monitor setting MLB_MONITOR_STARTED = settings.getSetting(id='mlb_monitor_started') -if MLB_MONITOR_STARTED != '' and not xbmc.getCondVisibility("Player.HasMedia"): +now = datetime.now() +if MLB_MONITOR_STARTED != '' and not xbmc.getCondVisibility("Player.HasMedia") and (parse(MLB_MONITOR_STARTED) + timedelta(seconds=5)) < now: xbmc.log("MLB Monitor detection resetting due to no stream playing") settings.setSetting(id='mlb_monitor_started', value='') @@ -94,17 +97,6 @@ VERIFY = True -#Skip monitor -#These are the break events to skip -BREAK_TYPES = ['Game Advisory', 'Pitching Substitution', 'Offensive Substitution', 'Defensive Sub', 'Defensive Switch', 'Runner Placed On Base', 'Injury'] -#These are the action events to keep, in addition to the last event of each at-bat, if we're skipping non-decision pitches -ACTION_TYPES = ['Wild Pitch', 'Passed Ball', 'Stolen Base', 'Caught Stealing', 'Pickoff', 'Error', 'Out', 'Balk', 'Defensive Indiff', 'Other Advance'] -#Pad events at both start (-) and end (+) -EVENT_START_PADDING = -3 -PITCH_END_PADDING = 3 -ACTION_END_PADDING = 8 -MINIMUM_BREAK_DURATION = 5 - SECONDS_PER_SEGMENT = 5 def find(source,start_str,end_str): diff --git a/resources/lib/mlb.py b/resources/lib/mlb.py index 0bdb0f5..1d2009e 100644 --- a/resources/lib/mlb.py +++ b/resources/lib/mlb.py @@ -1,7 +1,4 @@ from resources.lib.globals import * -from .account import Account -from .mlbmonitor import MLBMonitor - def categories(): addDir(LOCAL_STRING(30360), 100, ICON, FANART) @@ -44,44 +41,56 @@ def todays_games(game_day, start_inning='False'): r = requests.get(url,headers=headers, verify=VERIFY) json_source = r.json() + games = [] + if 'dates' in json_source and len(json_source['dates']) > 0 and 'games' in json_source['dates'][0]: + games = json_source['dates'][0]['games'] + favorite_games = [] remaining_games = [] fav_team_id = getFavTeamId() - fox_regional_games = False - fox_start_time = None - for game in json_source['dates'][0]['games']: + inprogress_exists = False + blackouts = [] + regional_fox_games_exist = False + if game_day >= today: + regional_fox_games_exist = check_regional_fox_games(games) + + # loop through games to find favorite team + for game in games: if fav_team_id is not None and fav_team_id in [str(game['teams']['home']['team']['id']), str(game['teams']['away']['team']['id'])]: favorite_games.append(game) else: remaining_games.append(game) - # while looping through games, check if any FOX national games have the same start time, which indicates regional broadcasts - game['fox_game'] = False - if game['seriesDescription'] == 'Regular Season' and 'content' in game and 'media' in game['content'] and 'epg' in game['content']['media']: - for epg in game['content']['media']['epg']: - if epg['title'] == 'MLBTV': - for item in epg['items']: - if item['callLetters'] == 'FOX': - game['fox_game'] = True - if fox_start_time is not None and game['gameDate'] == fox_start_time: - fox_regional_games = True - else: - fox_start_time = game['gameDate'] - break + game['scheduled_innings'] = get_scheduled_innings(game) + + # while looping through today or future games, check blackout status + if game_day >= today: + game['blackout_type'], game['blackout_time'] = get_blackout_status(game, regional_fox_games_exist) + if game_day == today: + # while looping through today's games, also count in progress, non-blackout games for Game Changer + if game['blackout_type'] != 'False': + blackouts.append(str(game['gamePk'])) + elif not inprogress_exists and game['status']['detailedState'] == 'In Progress': + inprogress_exists = True try: for game in favorite_games: - create_game_listitem(game, game_day, start_inning, today, fox_regional_games) + create_game_listitem(game, game_day, start_inning, today) except: pass + # Big Inning and game changer only available for non-free accounts if ONLY_FREE_GAMES != 'true': create_big_inning_listitem(game_day) + # if it's today, show the game changer listitem + if today == game_day: + create_game_changer_listitem(blackouts, inprogress_exists) + try: for game in remaining_games: - create_game_listitem(game, game_day, start_inning, today, fox_regional_games) + create_game_listitem(game, game_day, start_inning, today) except: pass @@ -89,7 +98,7 @@ def todays_games(game_day, start_inning='False'): addDir('[B]%s >>[/B]' % LOCAL_STRING(30011), 101, NEXT_ICON, FANART, next_day.strftime("%Y-%m-%d"), start_inning) -def create_game_listitem(game, game_day, start_inning, today, fox_regional_games): +def create_game_listitem(game, game_day, start_inning, today): #icon = ICON icon = 'https://img.mlbstatic.com/mlb-photos/image/upload/ar_167:215,c_crop/fl_relative,l_team:' + str(game['teams']['home']['team']['id']) + ':fill:spot.png,w_1.0,h_1,x_0.5,y_0,fl_no_overflow,e_distort:100p:0:200p:0:200p:100p:0:100p/fl_relative,l_team:' + str(game['teams']['away']['team']['id']) + ':logo:spot:current,w_0.38,x_-0.25,y_-0.16/fl_relative,l_team:' + str(game['teams']['home']['team']['id']) + ':logo:spot:current,w_0.38,x_0.25,y_0.16/w_750/team/' + str(game['teams']['away']['team']['id']) + '/fill/spot.png' # http://mlb.mlb.com/mlb/images/devices/ballpark/1920x1080/2681.jpg @@ -99,8 +108,8 @@ def create_game_listitem(game, game_day, start_inning, today, fox_regional_games # fanart = 'http://www.mlb.com/mlb/images/devices/ballpark/1920x1080/color/' + str(game['venue']['id']) + '.jpg' fanart = 'http://cd-images.mlbstatic.com/stadium-backgrounds/color/light-theme/1920x1080/%s.png' % game['venue']['id'] - - xbmc.log(str(game['gamePk'])) + game_pk = game['gamePk'] + xbmc.log(str(game_pk)) if TEAM_NAMES == "0": away_team = game['teams']['away']['team']['teamName'] @@ -138,14 +147,8 @@ def create_game_listitem(game, game_day, start_inning, today, fox_regional_games game_state = game['status']['detailedState'] - scheduled_innings = 9 - if 'linescore' in game and 'scheduledInnings' in game['linescore']: - scheduled_innings = int(game['linescore']['scheduledInnings']) - if game_state.startswith('Completed Early') and 'currentInning' in game['linescore']: - scheduled_innings = int(game['linescore']['currentInning']) - #if game['status']['abstractGameState'] == 'Preview': - if game['status']['detailedState'].lower() == 'scheduled' or game['status']['detailedState'].lower() == 'pre-game': + if game_state == 'Scheduled' or game_state == 'Pre-Game' or game_state == 'Warmup': if game['status']['startTimeTBD'] is True: game_time = 'TBD' else: @@ -160,7 +163,7 @@ def create_game_listitem(game, game_day, start_inning, today, fox_regional_games if game_state != 'Postponed': # if we've requested to see scores at a particular inning if start_inning != 'False' and 'linescore' in game: - relative_inning = (int(start_inning) - (9 - scheduled_innings)) + relative_inning = (int(start_inning) - (9 - game['scheduled_innings'])) if relative_inning > len(game['linescore']['innings']): relative_inning = len(game['linescore']['innings']) start_inning = relative_inning @@ -178,8 +181,8 @@ def create_game_listitem(game, game_day, start_inning, today, fox_regional_games # top_bottom = u"\u25BC" top_bottom = "B" - if game['linescore']['currentInning'] >= scheduled_innings and spoiler == 'False': - game_time = str(scheduled_innings) + 'th+' + if game['linescore']['currentInning'] >= game['scheduled_innings'] and spoiler == 'False': + game_time = str(game['scheduled_innings']) + 'th+' else: game_time = top_bottom + ' ' + game['linescore']['currentInningOrdinal'] @@ -204,7 +207,7 @@ def create_game_listitem(game, game_day, start_inning, today, fox_regional_games game_time = colorString(game_time, FINAL) elif game['status']['abstractGameState'] == 'Live': - if 'linescore' in game and game['linescore']['currentInning'] >= scheduled_innings: + if 'linescore' in game and game['linescore']['currentInning'] >= game['scheduled_innings']: color = CRITICAL else: color = LIVE @@ -214,8 +217,6 @@ def create_game_listitem(game, game_day, start_inning, today, fox_regional_games else: game_time = colorString(game_time, LIVE) - game_pk = game['gamePk'] - stream_date = str(game_day) desc = '' @@ -285,27 +286,22 @@ def create_game_listitem(game, game_day, start_inning, today, fox_regional_games if 'description' in game and game['description'] != "": desc += '[CR]' + game['description'] - if scheduled_innings != 9: - desc += '[CR]' + str(scheduled_innings) + '-inning game' + if game['scheduled_innings'] != 9: + desc += '[CR]' + str(game['scheduled_innings']) + '-inning game' # Check local/national blackout status blackout = 'False' try: if game_day >= today and game_state != 'Postponed': - if game['fox_game'] == True and fox_regional_games == True: - desc += '[CR]Regional FOX game' - else: - blackout_type, blackout_time = get_blackout_status(game, suspended, scheduled_innings) - - if blackout_type != 'False': - name = blackoutString(name) - desc += '[CR]' + blackout_type + ' video blackout until approx. 90 min. after the game' - if blackout_time is None: - blackout = 'True' - else: - blackout = blackout_time - blackout_display_time = get_display_time(UTCToLocal(blackout_time)) - desc += ' (~' + blackout_display_time + ')' + if 'blackout_type' in game and game['blackout_type'] != 'False': + name = blackoutString(name) + desc += '[CR]' + game['blackout_type'] + ' video blackout until approx. 90 min. after the game' + if 'blackout_time' not in game or game['blackout_time'] is None: + blackout = 'True' + else: + blackout = game['blackout_time'] + blackout_display_time = get_display_time(UTCToLocal(game['blackout_time'])) + desc += ' (~' + blackout_display_time + ')' except: pass @@ -501,7 +497,24 @@ def create_big_inning_listitem(game_day): pass -def stream_select(game_pk, spoiler, suspended, start_inning, blackout, description, name, icon, fanart, from_context_menu=False, autoplay=False): +# display a Game Changer item within a game list +def create_game_changer_listitem(blackouts, inprogress_exists): + name = LOCAL_STRING(30417) + if inprogress_exists: + name = LOCAL_STRING(30367) + name + desc = LOCAL_STRING(30418) + + # create the list item + liz=xbmcgui.ListItem(name) + liz.setInfo( type="Video", infoLabels={ "Title": name, 'plot': desc } ) + liz.setProperty("IsPlayable", "true") + liz.setArt({'icon': ICON, 'thumb': ICON, 'fanart': FANART}) + u=sys.argv[0]+"?mode="+str(500)+"&name="+urllib.quote_plus(name)+"&description="+urllib.quote_plus(desc)+"&blackout="+urllib.quote_plus(','.join(blackouts)) + xbmcplugin.addDirectoryItem(handle=addon_handle,url=u,listitem=liz,isFolder=False) + xbmcplugin.setContent(addon_handle, 'episodes') + + +def stream_select(game_pk, spoiler='True', suspended='False', start_inning='False', blackout='False', description=None, name=None, icon=None, fanart=None, from_context_menu=False, autoplay=False): # fetch the epg content using the game_pk url = API_URL + '/api/v1/game/' + game_pk + '/content' headers = { @@ -692,6 +705,7 @@ def stream_select(game_pk, spoiler, suspended, start_inning, blackout, descripti # only proceed with start/skip dialogs if we have a content_id, either from auto-selection or the stream selection dialog if selected_content_id is not None: # need to log in to get the stream url and headers + from .account import Account account = Account() # get the broadcast start offset and timestamp too, to know where to start playback and calculate skip markers, if necessary stream_url, headers, broadcast_start_offset, broadcast_start_timestamp = account.get_stream(selected_content_id) @@ -788,6 +802,7 @@ def stream_select(game_pk, spoiler, suspended, start_inning, blackout, descripti play_stream(stream_url, headers, description, title=name, icon=icon, fanart=fanart, start=broadcast_start_offset, stream_type=stream_type, music_type_unset=from_context_menu) # start the skip monitor if a skip type or start inning has been requested and we have a broadcast start timestamp if (skip_type > 0 or start_inning > 0) and broadcast_start_timestamp is not None: + from .mlbmonitor import MLBMonitor mlbmonitor = MLBMonitor() mlbmonitor.skip_monitor(skip_type, game_pk, broadcast_start_timestamp, is_live, start_inning, start_inning_half) # otherwise exit @@ -798,6 +813,7 @@ def stream_select(game_pk, spoiler, suspended, start_inning, blackout, descripti # select a stream for a featured video def featured_stream_select(featured_video, name, description): xbmc.log('video select') + from .account import Account account = Account() video_url = None # check if our request video is a URL @@ -1085,19 +1101,29 @@ def get_airings_data(content_id=None, game_pk=None): return json_source -# check blackout status -def get_blackout_status(game, suspended='False', scheduled_innings=9): +# check blackout status for a game +def get_blackout_status(game, regional_fox_games_exist): blackout_type = 'False' blackout_time = None - if re.match('^[0-9]{5}$', ZIP_CODE) and 'content' in game and 'media' in game['content'] and 'epg' in game['content']['media'] and len(game['content']['media']['epg']) > 0 and 'items' in game['content']['media']['epg'][0] and len(game['content']['media']['epg'][0]['items']) > 0 and 'mediaFeedType' in game['content']['media']['epg'][0]['items'][0] and game['content']['media']['epg'][0]['items'][0]['mediaFeedType'] == 'NATIONAL': - blackout_type = 'National' - elif game['seriesDescription'] != 'Spring Training' and (game['teams']['away']['team']['abbreviation'] in BLACKOUT_TEAMS or game['teams']['home']['team']['abbreviation'] in BLACKOUT_TEAMS): - blackout_type = 'Local' + if re.match('^[0-9]{5}$', ZIP_CODE): + if 'content' in game and 'media' in game['content'] and 'epg' in game['content']['media'] and len(game['content']['media']['epg']) > 0 and 'items' in game['content']['media']['epg'][0] and len(game['content']['media']['epg'][0]['items']) > 0 and 'mediaFeedType' in game['content']['media']['epg'][0]['items'][0] and game['content']['media']['epg'][0]['items'][0]['mediaFeedType'] == 'NATIONAL': + if game['content']['media']['epg'][0]['items'][0]['callLetters'] == 'FOX': + if regional_fox_games_exist == False: + blackout_type = 'National' + else: + blackout_type = 'National' + + if blackout_type == 'False' and game['seriesDescription'] != 'Spring Training' and (game['teams']['away']['team']['abbreviation'] in BLACKOUT_TEAMS or game['teams']['home']['team']['abbreviation'] in BLACKOUT_TEAMS): + blackout_type = 'Local' # also calculate a blackout time for non-suspended, non-TBD games - if blackout_type != 'False' and suspended == 'False' and game['status']['startTimeTBD'] is False: + if blackout_type != 'False' and 'resumeGameDate' not in game and 'resumedFromDate' not in game and game['status']['startTimeTBD'] is False: + blackout_wait_minutes = 90 + if 'scheduled_innings' not in game: + game['scheduled_innings'] = get_scheduled_innings(game) + innings = max(game['scheduled_innings'], get_current_inning(game)) # avg 9 inning game was 3:11 in 2021, or 21.22 minutes per inning - gameDurationMinutes = 21.22 * scheduled_innings + gameDurationMinutes = 21.22 * innings # default to assuming the scheduled game time is the first pitch time firstPitch = parse(game['gameDate']) if 'gameInfo' in game: @@ -1110,12 +1136,50 @@ def get_blackout_status(game, suspended='False', scheduled_innings=9): # add any delays if 'delayDurationMinutes' in game['gameInfo']: gameDurationMinutes += game['gameInfo']['delayDurationMinutes'] - gameDurationMinutes += 90 + gameDurationMinutes += blackout_wait_minutes blackout_time = firstPitch + timedelta(minutes=gameDurationMinutes) return blackout_type, blackout_time +# check if regional fox games exist for a date +def check_regional_fox_games(games): + fox_start_time = None + regional_fox_games_exist = False + for game in games: + if game['seriesDescription'] == 'Regular Season' and 'content' in game and 'media' in game['content'] and 'epg' in game['content']['media']: + for epg in game['content']['media']['epg']: + if epg['title'] == 'MLBTV': + for item in epg['items']: + if item['callLetters'] == 'FOX': + if fox_start_time is not None and game['gameDate'] == fox_start_time: + regional_fox_games_exist = True + else: + fox_start_time = game['gameDate'] + break + + return regional_fox_games_exist + + +def get_scheduled_innings(game): + scheduled_innings = 9 + if 'linescore' in game: + if 'scheduledInnings' in game['linescore']: + scheduled_innings = int(game['linescore']['scheduledInnings']) + if 'currentInning' in game['linescore']: + if game['status']['detailedState'].startswith('Completed Early'): + scheduled_innings = int(game['linescore']['currentInning']) + return scheduled_innings + + +def get_current_inning(game): + current_inning = 1 + if 'linescore' in game: + if 'currentInning' in game['linescore']: + current_inning = int(game['linescore']['currentInning']) + return current_inning + + def live_fav_game(): game_day = localToEastern() @@ -1127,49 +1191,62 @@ def live_fav_game(): # don't check if don't have a fav team id or if we've already flagged today's fav games as complete if fav_team_id is not None and auto_play_game_date != game_day: - # don't check more often than 5 minute intervals - auto_play_game_checked = str(settings.getSetting(id='auto_play_game_checked')) now = datetime.now() - if auto_play_game_checked == '' or (parse(auto_play_game_checked) + timedelta(minutes=5)) < now: - settings.setSetting(id='auto_play_game_checked', value=str(now)) - - url = API_URL + '/api/v1/schedule' - url += '?hydrate=game(content(media(epg)))' - url += '&sportId=1,51' - url += '&teamId=' + fav_team_id - url += '&date=' + game_day - - headers = { - 'User-Agent': UA_ANDROID - } - r = requests.get(url,headers=headers, verify=VERIFY) - json_source = r.json() - - upcoming_game = False - - if 'dates' in json_source and len(json_source['dates']) > 0 and 'games' in json_source['dates'][0]: - for game in json_source['dates'][0]['games']: - try: - # only check games that aren't final - if game['status']['abstractGameState'] != 'Final': - # also only check games that aren't blacked out - blackout_type, blackout_time = get_blackout_status(game) - if blackout_type == 'False': - if 'content' in game and 'media' in game['content'] and 'epg' in game['content']['media'] and len(game['content']['media']['epg']) > 0 and 'items' in game['content']['media']['epg'][0]: - for epg in game['content']['media']['epg'][0]['items']: - # if media is off, assume it is still upcoming - if epg['mediaState'] == 'MEDIA_OFF': - upcoming_game = True - # if media is on, that means it is live - elif game_pk is None and epg['mediaState'] == 'MEDIA_ON': - game_pk = str(game['gamePk']) - xbmc.log('Found live fav game ' + game_pk) - except: - pass - - # set the date setting if there are no more upcoming fav games today - if upcoming_game is False: - xbmc.log('No more upcoming fav games today') - settings.setSetting(id='auto_play_game_date', value=game_day) + # don't check if it is before the stored next game time (if available) + auto_play_next_game = str(settings.getSetting(id='auto_play_next_game')) + if auto_play_next_game == '' or UTCToLocal(parse(auto_play_next_game)) <= now: + # don't check more often than 5 minute intervals + auto_play_game_checked = str(settings.getSetting(id='auto_play_game_checked')) + if auto_play_game_checked == '' or (parse(auto_play_game_checked) + timedelta(minutes=5)) < now: + settings.setSetting(id='auto_play_game_checked', value=str(now)) + + url = API_URL + '/api/v1/schedule' + url += '?hydrate=game(content(media(epg))),team' + url += '&sportId=1,51' + url += '&date=' + game_day + + headers = { + 'User-Agent': UA_ANDROID + } + r = requests.get(url,headers=headers, verify=VERIFY) + json_source = r.json() + + upcoming_game = False + + if 'dates' in json_source and len(json_source['dates']) > 0 and 'games' in json_source['dates'][0]: + games = json_source['dates'][0]['games'] + regional_fox_games_exist = check_regional_fox_games(games) + for game in games: + try: + # only check games that include our fav team + if fav_team_id in [str(game['teams']['home']['team']['id']), str(game['teams']['away']['team']['id'])]: + # only check games that aren't final + if game['status']['abstractGameState'] != 'Final': + # only check games that aren't blacked out + if get_blackout_status(game, regional_fox_games_exist)[0] == 'False': + if 'content' in game and 'media' in game['content'] and 'epg' in game['content']['media'] and len(game['content']['media']['epg']) > 0 and 'items' in game['content']['media']['epg'][0]: + for epg in game['content']['media']['epg'][0]['items']: + # if media is off, assume it is still upcoming + if epg['mediaState'] == 'MEDIA_OFF': + if game['status']['startTimeTBD'] is True: + upcoming_game = 'TBD' + else: + upcoming_game = parse(game['gameDate']) - timedelta(minutes=10) + # if media is on, that means it is live + elif game_pk is None and epg['mediaState'] == 'MEDIA_ON': + game_pk = str(game['gamePk']) + xbmc.log('Found live fav game ' + game_pk) + break + except: + pass + + # set the date setting if there are no more upcoming fav games today + if upcoming_game is False: + xbmc.log('No more upcoming fav games today') + settings.setSetting(id='auto_play_game_date', value=game_day) + # otherwise store the time of the next game, and delay further checks until then + elif game_pk is None and upcoming_game != 'TBD': + xbmc.log('Setting next game time') + settings.setSetting(id='auto_play_next_game', value=str(upcoming_game)) return game_pk diff --git a/resources/lib/mlbmonitor.py b/resources/lib/mlbmonitor.py index 06b3549..51c527a 100644 --- a/resources/lib/mlbmonitor.py +++ b/resources/lib/mlbmonitor.py @@ -1,5 +1,11 @@ +# coding=UTF-8 +# SPDX-License-Identifier: GPL-2.0-or-later +# Original plugin.video.mlbbasesloaded © jakecar +# Code integrated here as change_monitor + from resources.lib.utils import Util from resources.lib.globals import * +from resources.lib.mlb import * class MLBMonitor(xbmc.Monitor): stream_started = False @@ -7,6 +13,812 @@ class MLBMonitor(xbmc.Monitor): verify = True broadcast_start_timestamp = None + #Skip monitor + #These are the break events to skip + BREAK_TYPES = ['Game Advisory', 'Pitching Substitution', 'Offensive Substitution', 'Defensive Sub', 'Defensive Switch', 'Runner Placed On Base', 'Injury'] + #These are the action events to keep, in addition to the last event of each at-bat, if we're skipping non-decision pitches + ACTION_TYPES = ['Wild Pitch', 'Passed Ball', 'Stolen Base', 'Caught Stealing', 'Pickoff', 'Error', 'Out', 'Balk', 'Defensive Indiff', 'Other Advance'] + #Pad events at both start (-) and end (+) + EVENT_START_PADDING = -3 + PITCH_END_PADDING = 3 + ACTION_END_PADDING = 8 + MINIMUM_BREAK_DURATION = 5 + + #Change monitor + MAX_LEVERAGE = 11 + + #Structure of game state to store for each game + GameState = namedtuple('GameState', + ['teams', + 'away_score', + 'home_score', + 'inning_half', + 'inning_num', + 'outs', + 'runners_on_base', + 'game_pk', + 'new_batter', + 'leverage_adjust']) + + #Leverage index table, used to comput leverage index from game state + LI_TABLE = { + 1: { + "top": { + "_ _ _": { + 0: [0.4, 0.6, 0.7, 0.8, 0.9, 0.0, 0.0, 0.0, 0.0], + 1: [0.3, 0.4, 0.5, 0.6, 0.6, 0.0, 0.0, 0.0, 0.0], + 2: [0.2, 0.3, 0.3, 0.4, 0.4, 0.0, 0.0, 0.0, 0.0], + }, + "1 _ _": { + 0: [0.7, 0.9, 1.1, 1.3, 1.4, 0.0, 0.0, 0.0, 0.0], + 1: [0.6, 0.7, 0.9, 1.0, 1.1, 0.0, 0.0, 0.0, 0.0], + 2: [0.4, 0.5, 0.6, 0.7, 0.8, 0.0, 0.0, 0.0, 0.0], + }, + "_ 2 _": { + 0: [0.6, 0.7, 0.9, 1.0, 1.2, 0.0, 0.0, 0.0, 0.0], + 1: [0.6, 0.8, 0.9, 1.1, 1.2, 0.0, 0.0, 0.0, 0.0], + 2: [0.6, 0.7, 0.9, 1.0, 1.1, 0.0, 0.0, 0.0, 0.0], + }, + "_ _ 3": { + 0: [0.5, 0.6, 0.8, 0.9, 1.0, 0.0, 0.0, 0.0, 0.0], + 1: [0.7, 0.9, 1.0, 1.2, 1.3, 0.0, 0.0, 0.0, 0.0], + 2: [0.7, 0.9, 1.0, 1.2, 1.3, 0.0, 0.0, 0.0, 0.0], + }, + "1 2 _": { + 0: [0.8, 1.1, 1.3, 1.6, 1.8, 0.0, 0.0, 0.0, 0.0], + 1: [0.9, 1.2, 1.5, 1.7, 1.9, 0.0, 0.0, 0.0, 0.0], + 2: [0.8, 1.0, 1.3, 1.5, 1.6, 0.0, 0.0, 0.0, 0.0], + }, + "1 _ 3": { + 0: [0.6, 0.8, 1.1, 1.3, 1.5, 0.0, 0.0, 0.0, 0.0], + 1: [0.9, 1.1, 1.3, 1.6, 1.7, 0.0, 0.0, 0.0, 0.0], + 2: [0.9, 1.1, 1.4, 1.6, 1.7, 0.0, 0.0, 0.0, 0.0], + }, + "_ 2 3": { + 0: [0.6, 0.8, 1.0, 1.2, 1.3, 0.0, 0.0, 0.0, 0.0], + 1: [0.7, 0.9, 1.1, 1.3, 1.4, 0.0, 0.0, 0.0, 0.0], + 2: [1.0, 1.2, 1.5, 1.7, 1.9, 0.0, 0.0, 0.0, 0.0], + }, + "1 2 3": { + 0: [0.8, 1.1, 1.4, 1.7, 2.0, 0.0, 0.0, 0.0, 0.0], + 1: [1.1, 1.5, 1.8, 2.1, 2.4, 0.0, 0.0, 0.0, 0.0], + 2: [1.4, 1.8, 2.1, 2.5, 2.7, 0.0, 0.0, 0.0, 0.0], + }, + }, + "bot": { + "_ _ _": { + 0: [0.7, 0.8, 0.9, 0.9, 0.9, 0.8, 0.6, 0.5, 0.4], + 1: [0.5, 0.6, 0.6, 0.7, 0.6, 0.6, 0.5, 0.4, 0.3], + 2: [0.3, 0.4, 0.4, 0.4, 0.4, 0.4, 0.3, 0.3, 0.2], + }, + "1 _ _": { + 0: [1.2, 1.4, 1.5, 1.5, 1.4, 1.2, 1.0, 0.8, 0.6], + 1: [1.0, 1.1, 1.2, 1.2, 1.1, 1.0, 0.8, 0.7, 0.5], + 2: [0.6, 0.7, 0.8, 0.8, 0.8, 0.7, 0.6, 0.5, 0.4], + }, + "_ 2 _": { + 0: [1.1, 1.2, 1.3, 1.2, 1.1, 1.0, 0.8, 0.6, 0.5], + 1: [1.0, 1.1, 1.2, 1.2, 1.2, 1.0, 0.9, 0.7, 0.5], + 2: [0.8, 1.0, 1.1, 1.1, 1.1, 1.0, 0.8, 0.7, 0.5], + }, + "_ _ 3": { + 0: [1.0, 1.1, 1.1, 1.1, 1.0, 0.8, 0.7, 0.5, 0.4], + 1: [1.0, 1.1, 1.3, 1.3, 1.3, 1.1, 1.0, 0.8, 0.6], + 2: [1.0, 1.1, 1.3, 1.3, 1.3, 1.2, 1.0, 0.8, 0.6], + }, + "1 2 _": { + 0: [1.7, 1.9, 2.0, 1.9, 1.7, 1.5, 1.2, 0.9, 0.7], + 1: [1.7, 1.9, 2.0, 2.0, 1.8, 1.6, 1.3, 1.0, 0.8], + 2: [1.3, 1.5, 1.6, 1.7, 1.6, 1.4, 1.2, 0.9, 0.7], + }, + "1 _ 3": { + 0: [1.6, 1.7, 1.7, 1.6, 1.4, 1.2, 1.0, 0.7, 0.5], + 1: [1.5, 1.7, 1.8, 1.8, 1.7, 1.5, 1.2, 1.0, 0.8], + 2: [1.4, 1.6, 1.7, 1.8, 1.7, 1.5, 1.3, 1.0, 0.8], + }, + "_ 2 3": { + 0: [1.4, 1.5, 1.5, 1.4, 1.3, 1.1, 0.9, 0.7, 0.5], + 1: [1.4, 1.5, 1.6, 1.5, 1.4, 1.2, 1.0, 0.8, 0.6], + 2: [1.6, 1.8, 2.0, 2.0, 1.9, 1.7, 1.4, 1.1, 0.8], + }, + "1 2 3": { + 0: [2.2, 2.3, 2.3, 2.1, 1.9, 1.6, 1.2, 0.9, 0.7], + 1: [2.4, 2.6, 2.6, 2.6, 2.3, 2.0, 1.6, 1.3, 1.0], + 2: [2.4, 2.7, 2.9, 2.9, 2.7, 2.4, 2.0, 1.5, 1.2], + }, + }, + }, + 2: { + "top": { + "_ _ _": { + 0: [0.4, 0.6, 0.7, 0.8, 0.9, 1.0, 0.9, 0.8, 0.7], + 1: [0.3, 0.4, 0.5, 0.6, 0.7, 0.7, 0.6, 0.6, 0.5], + 2: [0.2, 0.3, 0.3, 0.4, 0.4, 0.4, 0.4, 0.3, 0.3], + }, + "1 _ _": { + 0: [0.7, 0.9, 1.1, 1.3, 1.5, 1.5, 1.5, 1.4, 1.2], + 1: [0.6, 0.7, 0.9, 1.1, 1.2, 1.3, 1.2, 1.1, 0.9], + 2: [0.4, 0.5, 0.7, 0.8, 0.8, 0.9, 0.8, 0.7, 0.6], + }, + "_ 2 _": { + 0: [0.5, 0.7, 0.9, 1.1, 1.2, 1.3, 1.3, 1.2, 1.0], + 1: [0.6, 0.8, 1.0, 1.1, 1.2, 1.3, 1.2, 1.1, 0.9], + 2: [0.6, 0.8, 0.9, 1.1, 1.2, 1.2, 1.1, 0.9, 0.8], + }, + "_ _ 3": { + 0: [0.4, 0.6, 0.8, 0.9, 1.1, 1.1, 1.2, 1.1, 0.9], + 1: [0.7, 0.9, 1.1, 1.2, 1.3, 1.3, 1.3, 1.1, 0.9], + 2: [0.7, 0.9, 1.1, 1.3, 1.4, 1.4, 1.2, 1.1, 0.9], + }, + "1 2 _": { + 0: [0.8, 1.1, 1.4, 1.6, 1.9, 2.0, 2.0, 1.9, 1.7], + 1: [0.9, 1.2, 1.5, 1.8, 2.0, 2.1, 2.0, 1.8, 1.6], + 2: [0.8, 1.0, 1.3, 1.5, 1.7, 1.7, 1.6, 1.4, 1.2], + }, + "1 _ 3": { + 0: [0.6, 0.8, 1.1, 1.3, 1.6, 1.7, 1.8, 1.7, 1.5], + 1: [0.9, 1.1, 1.4, 1.6, 1.8, 1.8, 1.8, 1.6, 1.4], + 2: [0.9, 1.1, 1.4, 1.6, 1.8, 1.8, 1.7, 1.5, 1.3], + }, + "_ 2 3": { + 0: [0.6, 0.8, 1.0, 1.2, 1.4, 1.5, 1.6, 1.5, 1.4], + 1: [0.7, 0.9, 1.1, 1.3, 1.5, 1.6, 1.6, 1.5, 1.3], + 2: [1.0, 1.2, 1.5, 1.8, 2.0, 2.1, 2.0, 1.7, 1.4], + }, + "1 2 3": { + 0: [0.8, 1.1, 1.4, 1.7, 2.0, 2.3, 2.4, 2.3, 2.1], + 1: [1.1, 1.5, 1.8, 2.2, 2.5, 2.7, 2.7, 2.6, 2.3], + 2: [1.3, 1.7, 2.2, 2.6, 2.9, 3.0, 2.9, 2.6, 2.2], + }, + }, + "bot": { + "_ _ _": { + 0: [0.8, 0.9, 1.0, 1.0, 0.9, 0.8, 0.6, 0.5, 0.4], + 1: [0.5, 0.6, 0.7, 0.7, 0.7, 0.6, 0.5, 0.4, 0.3], + 2: [0.3, 0.4, 0.4, 0.5, 0.4, 0.4, 0.3, 0.2, 0.2], + }, + "1 _ _": { + 0: [1.3, 1.5, 1.6, 1.6, 1.5, 1.2, 1.0, 0.8, 0.6], + 1: [1.0, 1.2, 1.3, 1.3, 1.2, 1.0, 0.8, 0.6, 0.5], + 2: [0.6, 0.8, 0.9, 0.9, 0.8, 0.7, 0.6, 0.5, 0.3], + }, + "_ 2 _": { + 0: [1.1, 1.3, 1.3, 1.3, 1.2, 1.0, 0.8, 0.6, 0.4], + 1: [1.0, 1.2, 1.3, 1.3, 1.2, 1.1, 0.9, 0.7, 0.5], + 2: [0.8, 1.0, 1.2, 1.2, 1.2, 1.0, 0.9, 0.7, 0.5], + }, + "_ _ 3": { + 0: [1.0, 1.2, 1.2, 1.2, 1.0, 0.9, 0.7, 0.5, 0.4], + 1: [1.0, 1.2, 1.3, 1.4, 1.4, 1.2, 1.0, 0.8, 0.6], + 2: [1.0, 1.2, 1.3, 1.4, 1.4, 1.2, 1.0, 0.8, 0.6], + }, + "1 2 _": { + 0: [1.8, 2.0, 2.1, 2.0, 1.8, 1.5, 1.2, 0.9, 0.7], + 1: [1.7, 2.0, 2.1, 2.1, 2.0, 1.7, 1.3, 1.0, 0.7], + 2: [1.3, 1.6, 1.8, 1.8, 1.7, 1.5, 1.2, 0.9, 0.7], + }, + "1 _ 3": { + 0: [1.6, 1.8, 1.8, 1.7, 1.5, 1.2, 0.9, 0.7, 0.5], + 1: [1.5, 1.8, 1.9, 1.9, 1.8, 1.6, 1.3, 1.0, 0.7], + 2: [1.4, 1.7, 1.9, 1.9, 1.8, 1.6, 1.3, 1.0, 0.7], + }, + "_ 2 3": { + 0: [1.5, 1.6, 1.6, 1.5, 1.3, 1.1, 0.9, 0.7, 0.5], + 1: [1.4, 1.6, 1.7, 1.6, 1.5, 1.3, 1.0, 0.8, 0.6], + 2: [1.6, 1.9, 2.1, 2.1, 2.0, 1.7, 1.4, 1.1, 0.8], + }, + "1 2 3": { + 0: [2.3, 2.4, 2.4, 2.2, 1.9, 1.6, 1.2, 0.9, 0.6], + 1: [2.5, 2.7, 2.8, 2.7, 2.4, 2.1, 1.7, 1.3, 0.9], + 2: [2.5, 2.8, 3.1, 3.1, 2.9, 2.5, 2.0, 1.5, 1.1], + }, + }, + }, + 3: { + "top": { + "_ _ _": { + 0: [0.4, 0.6, 0.7, 0.9, 1.0, 1.0, 1.0, 0.9, 0.7], + 1: [0.3, 0.4, 0.5, 0.6, 0.7, 0.7, 0.7, 0.6, 0.5], + 2: [0.2, 0.3, 0.4, 0.4, 0.5, 0.5, 0.4, 0.4, 0.3], + }, + "1 _ _": { + 0: [0.6, 0.9, 1.1, 1.4, 1.6, 1.7, 1.6, 1.4, 1.2], + 1: [0.5, 0.7, 1.0, 1.2, 1.3, 1.4, 1.3, 1.1, 0.9], + 2: [0.4, 0.5, 0.7, 0.8, 0.9, 0.9, 0.8, 0.7, 0.6], + }, + "_ 2 _": { + 0: [0.5, 0.7, 0.9, 1.1, 1.3, 1.4, 1.4, 1.2, 1.0], + 1: [0.6, 0.8, 1.0, 1.2, 1.3, 1.4, 1.3, 1.1, 0.9], + 2: [0.6, 0.8, 1.0, 1.1, 1.3, 1.3, 1.1, 1.0, 0.8], + }, + "_ _ 3": { + 0: [0.4, 0.6, 0.8, 1.0, 1.1, 1.2, 1.2, 1.1, 1.0], + 1: [0.6, 0.9, 1.1, 1.3, 1.5, 1.5, 1.3, 1.1, 0.9], + 2: [0.7, 0.9, 1.1, 1.3, 1.5, 1.5, 1.3, 1.1, 0.9], + }, + "1 2 _": { + 0: [0.8, 1.0, 1.4, 1.7, 2.0, 2.2, 2.1, 2.0, 1.7], + 1: [0.9, 1.2, 1.5, 1.8, 2.1, 2.2, 2.1, 1.9, 1.6], + 2: [0.8, 1.0, 1.3, 1.6, 1.8, 1.9, 1.8, 1.5, 1.2], + }, + "1 _ 3": { + 0: [0.6, 0.8, 1.1, 1.4, 1.6, 1.8, 1.9, 1.8, 1.6], + 1: [0.8, 1.1, 1.4, 1.7, 1.9, 2.0, 1.9, 1.7, 1.5], + 2: [0.8, 1.1, 1.4, 1.7, 1.9, 2.0, 1.9, 1.6, 1.3], + }, + "_ 2 3": { + 0: [0.5, 0.8, 1.0, 1.2, 1.5, 1.6, 1.7, 1.6, 1.4], + 1: [0.7, 0.9, 1.2, 1.4, 1.6, 1.7, 1.7, 1.6, 1.3], + 2: [0.9, 1.2, 1.6, 1.9, 2.2, 2.2, 2.1, 1.8, 1.5], + }, + "1 2 3": { + 0: [0.7, 1.0, 1.4, 1.8, 2.1, 2.4, 2.6, 2.5, 2.3], + 1: [1.1, 1.4, 1.9, 2.3, 2.7, 2.9, 2.9, 2.7, 2.4], + 2: [1.3, 1.7, 2.2, 2.7, 3.1, 3.3, 3.1, 2.7, 2.3], + }, + }, + "bot": { + "_ _ _": { + 0: [0.8, 0.9, 1.0, 1.1, 1.0, 0.8, 0.6, 0.5, 0.3], + 1: [0.5, 0.7, 0.7, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3], + 2: [0.3, 0.4, 0.5, 0.5, 0.5, 0.4, 0.3, 0.2, 0.2], + }, + "1 _ _": { + 0: [1.3, 1.6, 1.7, 1.7, 1.5, 1.3, 1.0, 0.7, 0.5], + 1: [1.0, 1.2, 1.4, 1.4, 1.3, 1.1, 0.8, 0.6, 0.4], + 2: [0.6, 0.8, 0.9, 1.0, 0.9, 0.8, 0.6, 0.5, 0.3], + }, + "_ 2 _": { + 0: [1.2, 1.3, 1.5, 1.4, 1.3, 1.1, 0.8, 0.6, 0.4], + 1: [1.0, 1.3, 1.4, 1.4, 1.3, 1.1, 0.9, 0.6, 0.5], + 2: [0.9, 1.1, 1.3, 1.3, 1.3, 1.1, 0.9, 0.6, 0.5], + }, + "_ _ 3": { + 0: [1.1, 1.2, 1.3, 1.2, 1.1, 0.9, 0.7, 0.5, 0.3], + 1: [1.0, 1.3, 1.4, 1.5, 1.5, 1.3, 1.0, 0.7, 0.5], + 2: [1.0, 1.2, 1.4, 1.6, 1.5, 1.3, 1.0, 0.8, 0.5], + }, + "1 2 _": { + 0: [1.9, 2.1, 2.3, 2.2, 1.9, 1.6, 1.2, 0.9, 0.6], + 1: [1.8, 2.1, 2.3, 2.3, 2.1, 1.7, 1.3, 1.0, 0.7], + 2: [1.4, 1.7, 1.9, 2.0, 1.8, 1.5, 1.2, 0.9, 0.6], + }, + "1 _ 3": { + 0: [1.7, 1.9, 2.0, 1.8, 1.5, 1.2, 0.9, 0.7, 0.4], + 1: [1.6, 1.9, 2.0, 2.1, 1.9, 1.6, 1.3, 0.9, 0.7], + 2: [1.4, 1.8, 2.0, 2.1, 1.9, 1.6, 1.3, 1.0, 0.7], + }, + "_ 2 3": { + 0: [1.6, 1.7, 1.8, 1.6, 1.4, 1.1, 0.9, 0.6, 0.4], + 1: [1.5, 1.7, 1.8, 1.7, 1.6, 1.3, 1.0, 0.8, 0.5], + 2: [1.6, 2.0, 2.3, 2.3, 2.2, 1.8, 1.4, 1.0, 0.7], + }, + "1 2 3": { + 0: [2.4, 2.6, 2.6, 2.4, 2.0, 1.6, 1.2, 0.8, 0.6], + 1: [2.6, 2.9, 3.1, 2.9, 2.6, 2.1, 1.6, 1.2, 0.8], + 2: [2.5, 3.0, 3.3, 3.4, 3.1, 2.5, 2.0, 1.5, 1.0], + }, + }, + }, + 4: { + "top": { + "_ _ _": { + 0: [0.4, 0.5, 0.7, 0.9, 1.1, 1.1, 1.1, 0.9, 0.7], + 1: [0.3, 0.4, 0.5, 0.7, 0.8, 0.8, 0.7, 0.6, 0.5], + 2: [0.2, 0.3, 0.4, 0.5, 0.5, 0.5, 0.5, 0.4, 0.3], + }, + "1 _ _": { + 0: [0.6, 0.8, 1.1, 1.4, 1.7, 1.8, 1.7, 1.5, 1.2], + 1: [0.5, 0.7, 1.0, 1.2, 1.4, 1.5, 1.4, 1.2, 0.9], + 2: [0.4, 0.5, 0.7, 0.9, 1.0, 1.0, 0.9, 0.7, 0.6], + }, + "_ 2 _": { + 0: [0.5, 0.7, 0.9, 1.2, 1.4, 1.5, 1.5, 1.3, 1.1], + 1: [0.5, 0.7, 1.0, 1.2, 1.5, 1.5, 1.4, 1.2, 0.9], + 2: [0.5, 0.7, 1.0, 1.2, 1.4, 1.4, 1.2, 1.0, 0.7], + }, + "_ _ 3": { + 0: [0.4, 0.6, 0.8, 1.0, 1.2, 1.3, 1.3, 1.2, 1.0], + 1: [0.6, 0.9, 1.1, 1.4, 1.6, 1.6, 1.4, 1.2, 0.9], + 2: [0.6, 0.9, 1.2, 1.4, 1.6, 1.6, 1.4, 1.1, 0.8], + }, + "1 2 _": { + 0: [0.7, 1.0, 1.4, 1.8, 2.1, 2.3, 2.3, 2.1, 1.8], + 1: [0.8, 1.1, 1.5, 1.9, 2.3, 2.4, 2.3, 2.0, 1.6], + 2: [0.7, 1.0, 1.4, 1.7, 2.0, 2.1, 1.9, 1.6, 1.2], + }, + "1 _ 3": { + 0: [0.5, 0.8, 1.0, 1.4, 1.7, 2.0, 2.1, 1.9, 1.7], + 1: [0.8, 1.1, 1.4, 1.8, 2.1, 2.2, 2.1, 1.8, 1.5], + 2: [0.8, 1.1, 1.5, 1.8, 2.1, 2.2, 2.0, 1.7, 1.3], + }, + "_ 2 3": { + 0: [0.5, 0.7, 1.0, 1.3, 1.6, 1.8, 1.8, 1.7, 1.5], + 1: [0.6, 0.9, 1.2, 1.5, 1.7, 1.9, 1.9, 1.7, 1.4], + 2: [0.9, 1.2, 1.6, 2.0, 2.4, 2.5, 2.3, 1.9, 1.5], + }, + "1 2 3": { + 0: [0.7, 1.0, 1.4, 1.8, 2.2, 2.6, 2.8, 2.7, 2.4], + 1: [1.0, 1.4, 1.9, 2.4, 2.9, 3.1, 3.2, 2.9, 2.4], + 2: [1.2, 1.7, 2.3, 2.9, 3.4, 3.6, 3.4, 2.9, 2.3], + }, + }, + "bot": { + "_ _ _": { + 0: [0.8, 1.0, 1.1, 1.2, 1.1, 0.9, 0.6, 0.4, 0.3], + 1: [0.5, 0.7, 0.8, 0.9, 0.8, 0.6, 0.5, 0.3, 0.2], + 2: [0.3, 0.4, 0.5, 0.6, 0.5, 0.4, 0.3, 0.2, 0.2], + }, + "1 _ _": { + 0: [1.4, 1.7, 1.9, 1.9, 1.7, 1.3, 1.0, 0.7, 0.5], + 1: [1.0, 1.3, 1.5, 1.6, 1.4, 1.1, 0.8, 0.6, 0.4], + 2: [0.7, 0.8, 1.0, 1.1, 1.0, 0.8, 0.6, 0.4, 0.3], + }, + "_ 2 _": { + 0: [1.2, 1.4, 1.6, 1.6, 1.4, 1.1, 0.8, 0.5, 0.4], + 1: [1.1, 1.3, 1.5, 1.6, 1.4, 1.2, 0.9, 0.6, 0.4], + 2: [0.9, 1.1, 1.4, 1.5, 1.4, 1.1, 0.9, 0.6, 0.4], + }, + "_ _ 3": { + 0: [1.1, 1.3, 1.4, 1.4, 1.1, 0.9, 0.6, 0.4, 0.3], + 1: [1.0, 1.3, 1.6, 1.7, 1.6, 1.3, 1.0, 0.7, 0.5], + 2: [1.0, 1.3, 1.6, 1.7, 1.6, 1.3, 1.0, 0.7, 0.5], + }, + "1 2 _": { + 0: [2.0, 2.3, 2.5, 2.4, 2.0, 1.6, 1.1, 0.8, 0.5], + 1: [1.8, 2.2, 2.5, 2.5, 2.2, 1.8, 1.3, 0.9, 0.6], + 2: [1.4, 1.8, 2.1, 2.2, 2.0, 1.6, 1.2, 0.8, 0.6], + }, + "1 _ 3": { + 0: [1.8, 2.1, 2.1, 2.0, 1.6, 1.2, 0.9, 0.6, 0.4], + 1: [1.7, 2.0, 2.2, 2.3, 2.1, 1.7, 1.3, 0.9, 0.6], + 2: [1.5, 1.9, 2.2, 2.3, 2.1, 1.7, 1.3, 0.9, 0.6], + }, + "_ 2 3": { + 0: [1.7, 1.9, 1.9, 1.8, 1.5, 1.1, 0.8, 0.6, 0.4], + 1: [1.6, 1.8, 2.0, 1.9, 1.7, 1.4, 1.0, 0.7, 0.5], + 2: [1.7, 2.1, 2.5, 2.6, 2.3, 1.9, 1.4, 1.0, 0.7], + }, + "1 2 3": { + 0: [2.6, 2.8, 2.8, 2.6, 2.1, 1.6, 1.1, 0.8, 0.5], + 1: [2.7, 3.2, 3.3, 3.2, 2.8, 2.2, 1.6, 1.1, 0.7], + 2: [2.6, 3.2, 3.6, 3.7, 3.3, 2.6, 1.9, 1.4, 0.9], + }, + }, + }, + 5: { + "top": { + "_ _ _": { + 0: [0.4, 0.5, 0.7, 1.0, 1.2, 1.3, 1.1, 0.9, 0.7], + 1: [0.3, 0.4, 0.6, 0.7, 0.9, 0.9, 0.8, 0.6, 0.5], + 2: [0.2, 0.3, 0.4, 0.5, 0.6, 0.6, 0.5, 0.4, 0.3], + }, + "1 _ _": { + 0: [0.5, 0.8, 1.1, 1.5, 1.9, 2.0, 1.9, 1.6, 1.2], + 1: [0.5, 0.7, 1.0, 1.3, 1.6, 1.7, 1.5, 1.2, 0.9], + 2: [0.3, 0.5, 0.7, 0.9, 1.1, 1.1, 1.0, 0.8, 0.6], + }, + "_ 2 _": { + 0: [0.4, 0.6, 0.9, 1.2, 1.5, 1.7, 1.6, 1.4, 1.1], + 1: [0.5, 0.7, 1.0, 1.3, 1.6, 1.7, 1.5, 1.2, 0.9], + 2: [0.5, 0.7, 1.0, 1.3, 1.6, 1.6, 1.3, 1.0, 0.7], + }, + "_ _ 3": { + 0: [0.3, 0.5, 0.7, 1.0, 1.3, 1.5, 1.5, 1.3, 1.1], + 1: [0.6, 0.8, 1.1, 1.5, 1.8, 1.8, 1.5, 1.2, 0.9], + 2: [0.6, 0.8, 1.2, 1.5, 1.8, 1.8, 1.5, 1.1, 0.8], + }, + "1 2 _": { + 0: [0.6, 0.9, 1.3, 1.8, 2.3, 2.6, 2.5, 2.3, 1.8], + 1: [0.7, 1.1, 1.5, 2.0, 2.5, 2.7, 2.5, 2.1, 1.6], + 2: [0.7, 1.0, 1.4, 1.8, 2.2, 2.3, 2.1, 1.6, 1.2], + }, + "1 _ 3": { + 0: [0.5, 0.7, 1.0, 1.4, 1.8, 2.2, 2.3, 2.1, 1.7], + 1: [0.7, 1.0, 1.4, 1.9, 2.3, 2.4, 2.3, 1.9, 1.5], + 2: [0.7, 1.1, 1.5, 1.9, 2.4, 2.5, 2.2, 1.7, 1.3], + }, + "_ 2 3": { + 0: [0.4, 0.7, 1.0, 1.3, 1.7, 1.9, 2.0, 1.9, 1.6], + 1: [0.6, 0.8, 1.2, 1.5, 1.9, 2.1, 2.1, 1.8, 1.4], + 2: [0.8, 1.1, 1.6, 2.1, 2.6, 2.8, 2.5, 2.0, 1.4], + }, + "1 2 3": { + 0: [0.6, 0.9, 1.3, 1.8, 2.4, 2.8, 3.0, 2.9, 2.5], + 1: [0.9, 1.3, 1.9, 2.5, 3.1, 3.5, 3.5, 3.1, 2.5], + 2: [1.1, 1.6, 2.2, 3.0, 3.7, 4.0, 3.7, 3.0, 2.3], + }, + }, + "bot": { + "_ _ _": { + 0: [0.8, 1.1, 1.3, 1.3, 1.2, 0.9, 0.6, 0.4, 0.3], + 1: [0.5, 0.7, 0.9, 1.0, 0.9, 0.7, 0.5, 0.3, 0.2], + 2: [0.3, 0.4, 0.6, 0.6, 0.6, 0.4, 0.3, 0.2, 0.1], + }, + "1 _ _": { + 0: [1.4, 1.8, 2.1, 2.1, 1.8, 1.3, 0.9, 0.6, 0.4], + 1: [1.1, 1.4, 1.7, 1.8, 1.5, 1.2, 0.8, 0.5, 0.3], + 2: [0.6, 0.9, 1.1, 1.2, 1.1, 0.8, 0.6, 0.4, 0.2], + }, + "_ 2 _": { + 0: [1.2, 1.6, 1.8, 1.8, 1.5, 1.1, 0.7, 0.5, 0.3], + 1: [1.1, 1.4, 1.7, 1.8, 1.6, 1.2, 0.8, 0.5, 0.3], + 2: [0.8, 1.2, 1.5, 1.7, 1.6, 1.2, 0.8, 0.6, 0.4], + }, + "_ _ 3": { + 0: [1.2, 1.5, 1.6, 1.5, 1.2, 0.9, 0.6, 0.4, 0.2], + 1: [1.0, 1.4, 1.7, 2.0, 1.8, 1.4, 1.0, 0.6, 0.4], + 2: [0.9, 1.3, 1.7, 2.0, 1.9, 1.4, 1.0, 0.7, 0.4], + }, + "1 2 _": { + 0: [2.1, 2.5, 2.7, 2.6, 2.2, 1.6, 1.1, 0.7, 0.4], + 1: [1.9, 2.4, 2.8, 2.8, 2.4, 1.8, 1.2, 0.8, 0.5], + 2: [1.4, 1.9, 2.3, 2.5, 2.2, 1.6, 1.1, 0.8, 0.5], + }, + "1 _ 3": { + 0: [1.9, 2.3, 2.4, 2.2, 1.7, 1.2, 0.8, 0.5, 0.3], + 1: [1.7, 2.2, 2.5, 2.6, 2.3, 1.7, 1.2, 0.8, 0.5], + 2: [1.5, 2.0, 2.4, 2.6, 2.4, 1.8, 1.2, 0.8, 0.5], + }, + "_ 2 3": { + 0: [1.8, 2.1, 2.1, 2.0, 1.6, 1.1, 0.8, 0.5, 0.3], + 1: [1.6, 2.0, 2.2, 2.1, 1.9, 1.4, 1.0, 0.6, 0.4], + 2: [1.7, 2.3, 2.8, 3.0, 2.6, 1.9, 1.3, 0.9, 0.6], + }, + "1 2 3": { + 0: [2.8, 3.1, 3.1, 2.8, 2.2, 1.5, 1.0, 0.7, 0.4], + 1: [2.9, 3.4, 3.7, 3.5, 3.0, 2.2, 1.5, 1.0, 0.6], + 2: [2.7, 3.4, 4.0, 4.2, 3.6, 2.7, 1.8, 1.2, 0.8], + }, + }, + }, + 6: { + "top": { + "_ _ _": { + 0: [0.3, 0.5, 0.7, 1.0, 1.3, 1.4, 1.3, 1.0, 0.7], + 1: [0.2, 0.4, 0.5, 0.8, 1.0, 1.1, 0.9, 0.7, 0.4], + 2: [0.2, 0.3, 0.4, 0.5, 0.7, 0.7, 0.5, 0.4, 0.2], + }, + "1 _ _": { + 0: [0.5, 0.7, 1.1, 1.6, 2.1, 2.3, 2.1, 1.7, 1.2], + 1: [0.4, 0.6, 0.9, 1.3, 1.8, 1.9, 1.7, 1.3, 0.9], + 2: [0.3, 0.5, 0.7, 1.0, 1.3, 1.3, 1.1, 0.8, 0.5], + }, + "_ 2 _": { + 0: [0.4, 0.6, 0.9, 1.3, 1.7, 1.9, 1.8, 1.5, 1.1], + 1: [0.4, 0.6, 1.0, 1.4, 1.8, 2.0, 1.7, 1.3, 0.9], + 2: [0.4, 0.7, 1.0, 1.4, 1.8, 1.8, 1.4, 1.0, 0.7], + }, + "_ _ 3": { + 0: [0.3, 0.5, 0.7, 1.0, 1.4, 1.7, 1.7, 1.4, 1.1], + 1: [0.5, 0.8, 1.1, 1.6, 2.0, 2.1, 1.6, 1.3, 0.9], + 2: [0.5, 0.8, 1.2, 1.6, 2.1, 2.1, 1.6, 1.1, 0.7], + }, + "1 2 _": { + 0: [0.5, 0.8, 1.3, 1.8, 2.5, 2.9, 2.8, 2.4, 1.9], + 1: [0.6, 1.0, 1.5, 2.1, 2.8, 3.1, 2.8, 2.2, 1.6], + 2: [0.6, 0.9, 1.3, 1.9, 2.5, 2.6, 2.3, 1.7, 1.1], + }, + "1 _ 3": { + 0: [0.4, 0.6, 0.9, 1.4, 1.9, 2.4, 2.6, 2.3, 1.8], + 1: [0.6, 0.9, 1.4, 2.0, 2.6, 2.8, 2.5, 2.1, 1.5], + 2: [0.6, 1.0, 1.4, 2.0, 2.7, 2.8, 2.4, 1.8, 1.2], + }, + "_ 2 3": { + 0: [0.4, 0.6, 0.9, 1.3, 1.8, 2.2, 2.3, 2.1, 1.7], + 1: [0.5, 0.8, 1.1, 1.6, 2.1, 2.3, 2.3, 1.9, 1.5], + 2: [0.7, 1.0, 1.6, 2.2, 2.9, 3.2, 2.7, 2.0, 1.4], + }, + "1 2 3": { + 0: [0.5, 0.8, 1.2, 1.8, 2.5, 3.1, 3.4, 3.2, 2.7], + 1: [0.8, 1.2, 1.8, 2.6, 3.4, 3.9, 3.9, 3.3, 2.6], + 2: [0.9, 1.4, 2.2, 3.1, 4.1, 4.5, 4.1, 3.2, 2.3], + }, + }, + "bot": { + "_ _ _": { + 0: [0.8, 1.1, 1.4, 1.6, 1.3, 0.9, 0.6, 0.3, 0.2], + 1: [0.5, 0.8, 1.0, 1.2, 1.0, 0.7, 0.4, 0.3, 0.2], + 2: [0.3, 0.4, 0.6, 0.8, 0.7, 0.5, 0.3, 0.2, 0.1], + }, + "1 _ _": { + 0: [1.4, 1.9, 2.3, 2.4, 2.0, 1.3, 0.8, 0.5, 0.3], + 1: [1.0, 1.5, 1.9, 2.1, 1.7, 1.1, 0.7, 0.4, 0.3], + 2: [0.6, 0.9, 1.2, 1.5, 1.3, 0.8, 0.5, 0.3, 0.2], + }, + "_ 2 _": { + 0: [1.3, 1.7, 2.0, 2.0, 1.6, 1.1, 0.7, 0.4, 0.2], + 1: [1.1, 1.5, 1.9, 2.1, 1.8, 1.2, 0.8, 0.5, 0.3], + 2: [0.8, 1.2, 1.6, 2.0, 1.8, 1.2, 0.8, 0.5, 0.3], + }, + "_ _ 3": { + 0: [1.2, 1.6, 1.8, 1.7, 1.3, 0.8, 0.5, 0.3, 0.2], + 1: [1.0, 1.4, 1.9, 2.3, 2.1, 1.4, 0.9, 0.5, 0.3], + 2: [0.9, 1.3, 1.9, 2.4, 2.2, 1.4, 0.9, 0.6, 0.3], + }, + "1 2 _": { + 0: [2.2, 2.7, 3.1, 3.0, 2.3, 1.5, 0.9, 0.6, 0.3], + 1: [1.9, 2.6, 3.1, 3.3, 2.7, 1.8, 1.1, 0.7, 0.4], + 2: [1.4, 2.0, 2.6, 2.9, 2.5, 1.6, 1.0, 0.6, 0.4], + }, + "1 _ 3": { + 0: [2.0, 2.5, 2.7, 2.4, 1.7, 1.1, 0.7, 0.4, 0.2], + 1: [1.8, 2.4, 2.8, 3.0, 2.6, 1.7, 1.1, 0.7, 0.4], + 2: [1.4, 2.1, 2.7, 3.1, 2.7, 1.8, 1.1, 0.7, 0.4], + }, + "_ 2 3": { + 0: [1.9, 2.3, 2.4, 2.2, 1.7, 1.1, 0.7, 0.4, 0.2], + 1: [1.7, 2.2, 2.5, 2.5, 2.1, 1.4, 0.9, 0.5, 0.3], + 2: [1.6, 2.4, 3.1, 3.5, 2.9, 1.9, 1.2, 0.7, 0.4], + }, + "1 2 3": { + 0: [3.0, 3.5, 3.6, 3.1, 2.2, 1.4, 0.9, 0.5, 0.3], + 1: [3.0, 3.8, 4.2, 4.0, 3.3, 2.2, 1.4, 0.8, 0.5], + 2: [2.7, 3.7, 4.5, 4.9, 4.0, 2.6, 1.7, 1.0, 0.6], + }, + }, + }, + 7: { + "top": { + "_ _ _": { + 0: [0.2, 0.4, 0.7, 1.0, 1.5, 1.7, 1.4, 1.0, 0.6], + 1: [0.2, 0.3, 0.5, 0.8, 1.2, 1.3, 1.0, 0.6, 0.4], + 2: [0.1, 0.2, 0.4, 0.5, 0.8, 0.8, 0.6, 0.3, 0.2], + }, + "1 _ _": { + 0: [0.4, 0.6, 1.0, 1.6, 2.4, 2.7, 2.3, 1.7, 1.2], + 1: [0.3, 0.5, 0.9, 1.4, 2.0, 2.3, 1.8, 1.3, 0.8], + 2: [0.2, 0.4, 0.6, 1.0, 1.5, 1.6, 1.2, 0.7, 0.4], + }, + "_ 2 _": { + 0: [0.3, 0.5, 0.8, 1.2, 1.9, 2.3, 2.0, 1.5, 1.1], + 1: [0.3, 0.6, 0.9, 1.4, 2.1, 2.3, 1.8, 1.3, 0.8], + 2: [0.3, 0.6, 0.9, 1.4, 2.1, 2.2, 1.5, 0.9, 0.6], + }, + "_ _ 3": { + 0: [0.2, 0.4, 0.6, 1.0, 1.5, 2.0, 1.9, 1.5, 1.1], + 1: [0.4, 0.7, 1.1, 1.6, 2.4, 2.5, 1.8, 1.3, 0.8], + 2: [0.4, 0.7, 1.1, 1.7, 2.5, 2.5, 1.7, 1.1, 0.6], + }, + "1 2 _": { + 0: [0.4, 0.7, 1.1, 1.8, 2.7, 3.4, 3.2, 2.6, 1.9], + 1: [0.5, 0.8, 1.3, 2.1, 3.2, 3.6, 3.1, 2.3, 1.6], + 2: [0.4, 0.8, 1.2, 1.9, 2.9, 3.2, 2.5, 1.7, 1.0], + }, + "1 _ 3": { + 0: [0.3, 0.5, 0.8, 1.3, 2.0, 2.8, 3.0, 2.5, 1.8], + 1: [0.5, 0.8, 1.3, 2.0, 3.1, 3.3, 2.9, 2.2, 1.5], + 2: [0.5, 0.8, 1.4, 2.1, 3.1, 3.4, 2.6, 1.7, 1.1], + }, + "_ 2 3": { + 0: [0.3, 0.5, 0.8, 1.3, 2.0, 2.5, 2.6, 2.3, 1.7], + 1: [0.4, 0.7, 1.1, 1.6, 2.5, 2.8, 2.7, 2.1, 1.4], + 2: [0.5, 0.9, 1.5, 2.3, 3.4, 3.9, 3.1, 2.0, 1.2], + }, + "1 2 3": { + 0: [0.4, 0.6, 1.1, 1.7, 2.6, 3.5, 3.9, 3.6, 2.8], + 1: [0.6, 1.0, 1.6, 2.6, 3.9, 4.5, 4.4, 3.6, 2.6], + 2: [0.7, 1.2, 2.0, 3.1, 4.7, 5.4, 4.5, 3.3, 2.1], + }, + }, + "bot": { + "_ _ _": { + 0: [0.8, 1.2, 1.6, 1.9, 1.5, 0.8, 0.4, 0.2, 0.1], + 1: [0.5, 0.8, 1.1, 1.4, 1.2, 0.6, 0.3, 0.2, 0.1], + 2: [0.2, 0.4, 0.7, 1.0, 0.8, 0.4, 0.2, 0.1, 0.1], + }, + "1 _ _": { + 0: [1.4, 2.0, 2.6, 3.0, 2.3, 1.2, 0.7, 0.4, 0.2], + 1: [1.0, 1.5, 2.1, 2.5, 2.0, 1.1, 0.6, 0.3, 0.2], + 2: [0.5, 0.9, 1.4, 1.8, 1.5, 0.8, 0.4, 0.2, 0.1], + }, + "_ 2 _": { + 0: [1.3, 1.8, 2.3, 2.4, 1.8, 0.9, 0.5, 0.3, 0.1], + 1: [1.0, 1.5, 2.1, 2.6, 2.1, 1.1, 0.6, 0.3, 0.2], + 2: [0.7, 1.1, 1.8, 2.6, 2.2, 1.1, 0.7, 0.4, 0.2], + }, + "_ _ 3": { + 0: [1.3, 1.8, 2.2, 2.0, 1.5, 0.8, 0.4, 0.2, 0.1], + 1: [1.0, 1.5, 2.1, 2.9, 2.5, 1.3, 0.7, 0.4, 0.2], + 2: [0.8, 1.3, 2.0, 3.0, 2.6, 1.4, 0.8, 0.4, 0.2], + }, + "1 2 _": { + 0: [2.2, 3.0, 3.6, 3.5, 2.5, 1.3, 0.8, 0.4, 0.2], + 1: [1.9, 2.7, 3.6, 4.0, 3.0, 1.6, 0.9, 0.5, 0.3], + 2: [1.2, 2.0, 2.9, 3.6, 2.9, 1.5, 0.8, 0.5, 0.2], + }, + "1 _ 3": { + 0: [2.1, 2.8, 3.3, 2.8, 1.7, 0.9, 0.5, 0.3, 0.1], + 1: [1.8, 2.6, 3.2, 3.7, 3.1, 1.6, 0.9, 0.5, 0.3], + 2: [1.3, 2.1, 3.0, 3.8, 3.1, 1.6, 0.9, 0.5, 0.3], + }, + "_ 2 3": { + 0: [2.0, 2.6, 2.9, 2.6, 1.7, 0.9, 0.5, 0.3, 0.1], + 1: [1.7, 2.4, 3.0, 3.0, 2.5, 1.3, 0.7, 0.4, 0.2], + 2: [1.5, 2.4, 3.6, 4.3, 3.3, 1.7, 1.0, 0.5, 0.3], + }, + "1 2 3": { + 0: [3.3, 4.0, 4.1, 3.5, 2.3, 1.2, 0.7, 0.4, 0.2], + 1: [3.1, 4.2, 4.9, 4.8, 3.7, 2.0, 1.1, 0.6, 0.3], + 2: [2.6, 3.9, 5.2, 5.9, 4.5, 2.4, 1.3, 0.7, 0.4], + }, + }, + }, + 8: { + "top": { + "_ _ _": { + 0: [0.2, 0.3, 0.6, 1.0, 1.9, 2.2, 1.5, 0.9, 0.6], + 1: [0.1, 0.2, 0.4, 0.7, 1.4, 1.6, 1.0, 0.6, 0.3], + 2: [0.1, 0.2, 0.3, 0.5, 1.0, 1.1, 0.6, 0.3, 0.1], + }, + "1 _ _": { + 0: [0.2, 0.4, 0.8, 1.5, 2.8, 3.4, 2.6, 1.7, 1.0], + 1: [0.2, 0.4, 0.7, 1.3, 2.4, 2.9, 2.0, 1.2, 0.7], + 2: [0.2, 0.3, 0.5, 1.0, 1.8, 2.1, 1.3, 0.7, 0.3], + }, + "_ 2 _": { + 0: [0.2, 0.4, 0.6, 1.1, 2.2, 2.8, 2.3, 1.6, 1.0], + 1: [0.2, 0.4, 0.8, 1.3, 2.5, 2.9, 2.0, 1.2, 0.7], + 2: [0.2, 0.4, 0.8, 1.4, 2.7, 2.8, 1.5, 0.8, 0.4], + }, + "_ _ 3": { + 0: [0.2, 0.3, 0.5, 0.9, 1.8, 2.4, 2.3, 1.6, 1.0], + 1: [0.3, 0.5, 0.9, 1.6, 3.1, 3.2, 2.0, 1.3, 0.7], + 2: [0.3, 0.5, 0.9, 1.6, 3.2, 3.3, 1.7, 0.9, 0.4], + }, + "1 2 _": { + 0: [0.3, 0.5, 0.9, 1.6, 3.1, 4.1, 3.7, 2.8, 1.8], + 1: [0.3, 0.6, 1.1, 2.0, 3.7, 4.5, 3.5, 2.4, 1.4], + 2: [0.3, 0.6, 1.0, 1.8, 3.5, 4.0, 2.7, 1.6, 0.8], + }, + "1 _ 3": { + 0: [0.2, 0.3, 0.6, 1.1, 2.1, 3.3, 3.6, 2.8, 1.8], + 1: [0.3, 0.6, 1.1, 2.0, 3.8, 4.2, 3.3, 2.3, 1.4], + 2: [0.3, 0.6, 1.1, 2.0, 3.8, 4.3, 2.8, 1.6, 0.8], + }, + "_ 2 3": { + 0: [0.2, 0.3, 0.6, 1.1, 2.1, 3.1, 3.2, 2.6, 1.7], + 1: [0.3, 0.5, 0.9, 1.6, 3.0, 3.4, 3.2, 2.2, 1.3], + 2: [0.3, 0.7, 1.2, 2.1, 4.0, 4.9, 3.5, 1.9, 1.0], + }, + "1 2 3": { + 0: [0.2, 0.4, 0.8, 1.5, 2.8, 4.1, 4.6, 4.0, 3.0], + 1: [0.4, 0.7, 1.4, 2.4, 4.6, 5.6, 5.2, 3.9, 2.6], + 2: [0.5, 0.9, 1.6, 2.9, 5.6, 6.8, 5.1, 3.3, 1.9], + }, + }, + "bot": { + "_ _ _": { + 0: [0.7, 1.1, 1.8, 2.5, 1.8, 0.6, 0.3, 0.1, 0.1], + 1: [0.4, 0.7, 1.2, 1.9, 1.4, 0.5, 0.2, 0.1, 0.0], + 2: [0.2, 0.4, 0.7, 1.3, 1.1, 0.3, 0.2, 0.1, 0.0], + }, + "1 _ _": { + 0: [1.3, 2.1, 3.1, 3.8, 2.6, 0.9, 0.4, 0.2, 0.1], + 1: [0.8, 1.5, 2.4, 3.4, 2.4, 0.8, 0.4, 0.2, 0.1], + 2: [0.4, 0.8, 1.6, 2.5, 1.8, 0.6, 0.3, 0.1, 0.1], + }, + "_ 2 _": { + 0: [1.2, 1.9, 2.7, 3.1, 2.0, 0.7, 0.3, 0.1, 0.1], + 1: [0.8, 1.5, 2.4, 3.4, 2.5, 0.8, 0.4, 0.2, 0.1], + 2: [0.5, 1.0, 1.9, 3.5, 2.8, 0.9, 0.4, 0.2, 0.1], + }, + "_ _ 3": { + 0: [1.2, 1.9, 2.7, 2.5, 1.7, 0.6, 0.3, 0.1, 0.1], + 1: [0.9, 1.5, 2.4, 3.9, 3.2, 1.0, 0.5, 0.2, 0.1], + 2: [0.5, 1.1, 2.1, 4.1, 3.4, 1.0, 0.5, 0.2, 0.1], + }, + "1 2 _": { + 0: [2.2, 3.3, 4.2, 4.4, 2.8, 0.9, 0.4, 0.2, 0.1], + 1: [1.7, 2.9, 4.1, 5.1, 3.5, 1.1, 0.5, 0.3, 0.1], + 2: [1.0, 2.0, 3.3, 4.7, 3.5, 1.1, 0.5, 0.2, 0.1], + }, + "1 _ 3": { + 0: [2.2, 3.2, 4.1, 3.3, 1.8, 0.6, 0.3, 0.1, 0.1], + 1: [1.7, 2.7, 3.9, 4.9, 3.8, 1.2, 0.6, 0.3, 0.1], + 2: [1.0, 2.0, 3.4, 5.1, 3.8, 1.2, 0.6, 0.3, 0.1], + }, + "_ 2 3": { + 0: [2.1, 3.0, 3.6, 3.3, 1.8, 0.6, 0.3, 0.1, 0.1], + 1: [1.6, 2.6, 3.7, 4.0, 3.0, 0.9, 0.4, 0.2, 0.1], + 2: [1.2, 2.3, 4.2, 5.8, 3.9, 1.3, 0.6, 0.3, 0.1], + }, + "1 2 3": { + 0: [3.5, 4.6, 5.0, 4.2, 2.2, 0.8, 0.4, 0.2, 0.1], + 1: [3.1, 4.6, 5.9, 6.2, 4.4, 1.4, 0.7, 0.3, 0.1], + 2: [2.3, 4.0, 6.1, 7.7, 5.3, 1.7, 0.8, 0.4, 0.2], + }, + }, + }, + 9: { + "top": { + "_ _ _": { + 0: [0.1, 0.2, 0.3, 0.7, 2.4, 2.9, 1.6, 0.8, 0.4], + 1: [0.1, 0.1, 0.3, 0.6, 1.9, 2.2, 1.0, 0.5, 0.2], + 2: [0.0, 0.1, 0.2, 0.4, 1.4, 1.5, 0.5, 0.2, 0.1], + }, + "1 _ _": { + 0: [0.1, 0.2, 0.5, 1.1, 3.4, 4.6, 2.9, 1.6, 0.8], + 1: [0.1, 0.2, 0.5, 1.0, 3.1, 3.9, 2.2, 1.0, 0.4], + 2: [0.1, 0.2, 0.3, 0.7, 2.4, 2.9, 1.3, 0.4, 0.1], + }, + "_ 2 _": { + 0: [0.1, 0.2, 0.4, 0.8, 2.6, 3.7, 2.7, 1.5, 0.8], + 1: [0.1, 0.2, 0.5, 1.0, 3.3, 4.0, 2.2, 1.0, 0.5], + 2: [0.1, 0.2, 0.5, 1.1, 3.7, 4.0, 1.4, 0.5, 0.2], + }, + "_ _ 3": { + 0: [0.1, 0.2, 0.3, 0.7, 2.3, 3.1, 2.9, 1.6, 0.8], + 1: [0.1, 0.3, 0.6, 1.2, 4.3, 4.4, 2.3, 1.1, 0.5], + 2: [0.1, 0.3, 0.6, 1.3, 4.4, 4.6, 1.4, 0.5, 0.2], + }, + "1 2 _": { + 0: [0.1, 0.3, 0.6, 1.2, 3.6, 5.3, 4.4, 2.9, 1.6], + 1: [0.1, 0.3, 0.7, 1.4, 4.6, 6.1, 4.0, 2.3, 1.1], + 2: [0.1, 0.3, 0.7, 1.4, 4.5, 5.5, 2.9, 1.3, 0.4], + }, + "1 _ 3": { + 0: [0.1, 0.2, 0.4, 0.8, 2.3, 4.2, 4.6, 3.0, 1.7], + 1: [0.1, 0.3, 0.7, 1.5, 5.0, 5.7, 4.0, 2.3, 1.1], + 2: [0.2, 0.3, 0.7, 1.5, 5.0, 5.9, 2.9, 1.3, 0.4], + }, + "_ 2 3": { + 0: [0.1, 0.2, 0.4, 0.8, 2.4, 4.0, 4.0, 2.9, 1.6], + 1: [0.1, 0.3, 0.6, 1.2, 3.9, 4.6, 3.9, 2.3, 1.1], + 2: [0.2, 0.3, 0.7, 1.6, 5.1, 6.9, 3.9, 1.4, 0.5], + }, + "1 2 3": { + 0: [0.1, 0.2, 0.5, 1.0, 2.9, 5.2, 5.7, 4.6, 3.1], + 1: [0.2, 0.4, 0.8, 1.8, 5.7, 7.3, 6.2, 4.2, 2.4], + 2: [0.2, 0.5, 1.0, 2.1, 6.9, 9.1, 5.7, 3.1, 1.4], + }, + }, + "bot": { + "_ _ _": { + 0: [0.5, 1.0, 2.0, 3.6, 2.3, 0.0, 0.0, 0.0, 0.0], + 1: [0.2, 0.6, 1.3, 2.8, 1.9, 0.0, 0.0, 0.0, 0.0], + 2: [0.1, 0.2, 0.6, 1.9, 1.5, 0.0, 0.0, 0.0, 0.0], + }, + "1 _ _": { + 0: [1.0, 2.0, 3.6, 5.4, 3.1, 0.0, 0.0, 0.0, 0.0], + 1: [0.6, 1.3, 2.8, 4.8, 3.0, 0.0, 0.0, 0.0, 0.0], + 2: [0.2, 0.5, 1.7, 3.7, 2.4, 0.0, 0.0, 0.0, 0.0], + }, + "_ 2 _": { + 0: [1.0, 1.9, 3.3, 4.3, 2.5, 0.0, 0.0, 0.0, 0.0], + 1: [0.6, 1.3, 2.7, 5.0, 3.2, 0.0, 0.0, 0.0, 0.0], + 2: [0.2, 0.6, 1.8, 5.2, 3.9, 0.0, 0.0, 0.0, 0.0], + }, + "_ _ 3": { + 0: [1.1, 2.0, 3.6, 3.5, 2.1, 0.0, 0.0, 0.0, 0.0], + 1: [0.6, 1.4, 2.9, 5.8, 4.5, 0.0, 0.0, 0.0, 0.0], + 2: [0.2, 0.7, 1.8, 6.1, 4.7, 0.0, 0.0, 0.0, 0.0], + }, + "1 2 _": { + 0: [2.0, 3.6, 5.2, 6.0, 3.2, 0.0, 0.0, 0.0, 0.0], + 1: [1.3, 2.9, 4.8, 7.2, 4.3, 0.0, 0.0, 0.0, 0.0], + 2: [0.6, 1.7, 3.7, 6.8, 4.4, 0.0, 0.0, 0.0, 0.0], + }, + "1 _ 3": { + 0: [2.1, 3.7, 5.5, 4.3, 2.1, 0.0, 0.0, 0.0, 0.0], + 1: [1.4, 2.9, 4.9, 7.1, 5.0, 0.0, 0.0, 0.0, 0.0], + 2: [0.6, 1.7, 3.7, 7.4, 5.0, 0.0, 0.0, 0.0, 0.0], + }, + "_ 2 3": { + 0: [2.0, 3.5, 4.7, 4.3, 2.0, 0.0, 0.0, 0.0, 0.0], + 1: [1.4, 2.8, 4.8, 5.7, 4.0, 0.0, 0.0, 0.0, 0.0], + 2: [0.7, 1.8, 5.1, 8.4, 4.7, 0.0, 0.0, 0.0, 0.0], + }, + "1 2 3": { + 0: [3.7, 5.4, 6.4, 5.3, 2.4, 0.0, 0.0, 0.0, 0.0], + 1: [3.0, 5.1, 7.3, 8.6, 5.4, 0.0, 0.0, 0.0, 0.0], + 2: [1.8, 3.9, 7.0, 10.9, 6.4, 0.0, 0.0, 0.0, 0.0], + }, + }, + }, + } + + def __init__(self, *args, **kwargs): xbmc.log("MLB Monitor init") self.monitor = xbmc.Monitor() @@ -25,38 +837,39 @@ def skip_monitor(self, skip_type, game_pk, broadcast_start_timestamp, is_live, s self.mlb_monitor_started = str(datetime.now()) settings.setSetting(id='mlb_monitor_started', value=self.mlb_monitor_started) - xbmc.log("Skip monitor for " + game_pk + " started at " + self.mlb_monitor_started) + monitor_name = 'Skip monitor for ' + game_pk + xbmc.log(monitor_name + ' started at ' + self.mlb_monitor_started) # initialize player to monitor play time player = xbmc.Player() last_time = None # fetch skip markers - skip_markers = self.get_skip_markers(skip_type, game_pk, broadcast_start_timestamp, 0, start_inning, start_inning_half) - xbmc.log('Skip monitor for ' + game_pk + ' skip markers : ' + str(skip_markers)) + skip_markers = self.get_skip_markers(skip_type, game_pk, broadcast_start_timestamp, monitor_name, 0, start_inning, start_inning_half) + xbmc.log(monitor_name + ' skip markers : ' + str(skip_markers)) while not self.monitor.abortRequested(): if self.monitor.waitForAbort(1): - xbmc.log("Skip monitor for " + game_pk + " aborting") + xbmc.log(monitor_name + " aborting") break elif len(skip_markers) == 0: - xbmc.log("Skip monitor for " + game_pk + " closing due to no more skip markers") + xbmc.log(monitor_name + " closing due to no more skip markers") break elif self.stream_started == True and not xbmc.getCondVisibility("Player.HasMedia"): - xbmc.log("Skip monitor for " + game_pk + " closing due to stream stopped") + xbmc.log(monitor_name + " closing due to stream stopped") break elif self.mlb_monitor_started == '': - xbmc.log("Skip monitor for " + game_pk + " closing due to reset") + xbmc.log(monitor_name + " closing due to reset") break elif xbmc.getCondVisibility("Player.HasMedia"): if self.stream_started == False: - xbmc.log("Skip monitor for " + game_pk + " detected stream start") + xbmc.log(monitor_name + ' detected stream start') self.stream_started = True # just for fun, we can log our stream duration, to compare it against skip time detected if start_inning == 0: try: total_stream_time = player.getTotalTime() - xbmc.log('Skip monitor for ' + game_pk + ' total stream time ' + str(timedelta(seconds=total_stream_time))) + xbmc.log(monitor_name + ' total stream time ' + str(timedelta(seconds=total_stream_time))) except: pass current_time = player.getTime() @@ -65,11 +878,11 @@ def skip_monitor(self, skip_type, game_pk, broadcast_start_timestamp, is_live, s last_time = current_time # remove any past skip markers so user can seek backward freely while len(skip_markers) > 0 and current_time > skip_markers[0][1]: - xbmc.log("Skip monitor for " + game_pk + " removed skip marker at " + str(skip_markers[0][1]) + ", before current time " + str(current_time)) + xbmc.log(monitor_name + " removed skip marker at " + str(skip_markers[0][1]) + ", before current time " + str(current_time)) skip_markers.pop(0) # seek to end of break if we fall within skip marker range, then remove marker so user can seek backward freely if len(skip_markers) > 0 and current_time >= skip_markers[0][0] and current_time < skip_markers[0][1]: - xbmc.log("Skip monitor for " + game_pk + " processed skip marker at " + str(skip_markers[0][1])) + xbmc.log(monitor_name + " processed skip marker at " + str(skip_markers[0][1])) player.seekTime(skip_markers[0][1]) skip_markers.pop(0) # since we just processed a skip marker, we can delay further processing a little bit @@ -78,23 +891,23 @@ def skip_monitor(self, skip_type, game_pk, broadcast_start_timestamp, is_live, s if len(skip_markers) == 0 and is_live == True and skip_type > 0: # refresh current time, and look ahead slightly current_time = player.getTime() + 10 - xbmc.log('Skip monitor for ' + game_pk + ' refreshing skip markers from ' + str(current_time)) - skip_markers = self.get_skip_markers(skip_type, game_pk, broadcast_start_timestamp, current_time) - xbmc.log('Skip monitor for ' + game_pk + ' refreshed skip markers : ' + str(skip_markers)) + xbmc.log(monitor_name + ' refreshing skip markers from ' + str(current_time)) + skip_markers = self.get_skip_markers(skip_type, game_pk, broadcast_start_timestamp, monitor_name, current_time) + xbmc.log(monitor_name + ' refreshed skip markers : ' + str(skip_markers)) else: if self.stream_started == False: - xbmc.log("Skip monitor for " + game_pk + " waiting for stream to start") + xbmc.log(monitor_name + " waiting for stream to start") self.stream_start_tries -= 1 if self.stream_start_tries < 1: - xbmc.log("Skip monitor for " + game_pk + " closing due to stream not starting") + xbmc.log(monitor_name + " closing due to stream not starting") break - xbmc.log("Skip monitor for " + game_pk + " closed") + xbmc.log(monitor_name + " closed") # get the gameday data, which contains the event timestamps - def get_gameday_data(self, game_pk): - xbmc.log('Skip monitor for ' + game_pk + ' getting gameday data') + def get_gameday_data(self, game_pk, monitor_name): + xbmc.log(monitor_name + ' getting gameday data') url = API_URL + '/api/v1.1/game/' + game_pk + '/feed/live' headers = { @@ -109,15 +922,15 @@ def get_gameday_data(self, game_pk): # calculate skip markers from gameday events - def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, current_time=0, start_inning=0, start_inning_half='top'): - xbmc.log('Skip monitor for ' + game_pk + ' getting skip markers for skip type ' + str(skip_type)) + def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monitor_name, current_time=0, start_inning=0, start_inning_half='top'): + xbmc.log(monitor_name + ' getting skip markers for skip type ' + str(skip_type)) if current_time > 0: - xbmc.log('Skip monitor for ' + game_pk + ' searching beyond ' + str(current_time)) + xbmc.log(monitor_name + ' searching beyond ' + str(current_time)) # initialize our list of skip times skip_markers = [] - json_source = self.get_gameday_data(game_pk) + json_source = self.get_gameday_data(game_pk, monitor_name) # calculate total skip time (for fun) total_skip_time = 0 @@ -154,9 +967,9 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, curren # loop through events within each play for index, playEvent in enumerate(play['playEvents']): # default to longer action end padding - event_end_padding = ACTION_END_PADDING + event_end_padding = self.ACTION_END_PADDING # always exclude break types - if 'event' in playEvent['details'] and playEvent['details']['event'] in BREAK_TYPES: + if 'event' in playEvent['details'] and playEvent['details']['event'] in self.BREAK_TYPES: # if we're in the process of skipping inning breaks, treat the first break type we find as another inning break if skip_type == 1 and previous_inning > 0: break_start = (parse(playEvent['startTime']) - broadcast_start_timestamp).total_seconds() + event_end_padding @@ -164,15 +977,15 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, curren continue else: # for non-action plays, use shorter pitch end padding instead - if index < (len(play['playEvents'])-1) and ('details' not in playEvent or 'event' not in playEvent['details'] or not any(substring in playEvent['details']['event'] for substring in ACTION_TYPES)): - event_end_padding = PITCH_END_PADDING + if index < (len(play['playEvents'])-1) and ('details' not in playEvent or 'event' not in playEvent['details'] or not any(substring in playEvent['details']['event'] for substring in self.ACTION_TYPES)): + event_end_padding = self.PITCH_END_PADDING action_index = None # skip type 1 (breaks) and 2 (idle time) will look at all plays with an endTime if skip_type <= 2 and 'endTime' in playEvent: action_index = index elif skip_type == 3: # skip type 3 excludes non-action pitches (events that aren't last in the at-bat and don't fall under action types) - if index < (len(play['playEvents'])-1) and ('details' not in playEvent or 'event' not in playEvent['details'] or not any(substring in playEvent['details']['event'] for substring in ACTION_TYPES)): + if index < (len(play['playEvents'])-1) and ('details' not in playEvent or 'event' not in playEvent['details'] or not any(substring in playEvent['details']['event'] for substring in self.ACTION_TYPES)): continue else: # if the action is associated with another play or the event doesn't have an end time, use the previous event instead @@ -183,13 +996,13 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, curren if action_index is None: continue else: - break_end = (parse(play['playEvents'][action_index]['startTime']) - broadcast_start_timestamp).total_seconds() + EVENT_START_PADDING + break_end = (parse(play['playEvents'][action_index]['startTime']) - broadcast_start_timestamp).total_seconds() + self.EVENT_START_PADDING # if the break end should be greater than the current playback time # and the break duration should be greater than than our specified minimum # and if skip type is not 1 (inning breaks) or the inning has changed # then we'll add the skip marker # otherwise we'll ignore it and move on to the next one - if break_end > current_time and (break_end - break_start) >= MINIMUM_BREAK_DURATION and (skip_type != 1 or current_inning != previous_inning or current_inning_half != previous_inning_half): + if break_end > current_time and (break_end - break_start) >= self.MINIMUM_BREAK_DURATION and (skip_type != 1 or current_inning != previous_inning or current_inning_half != previous_inning_half): skip_markers.append([break_start, break_end]) total_skip_time += break_end - break_start previous_inning = current_inning @@ -209,6 +1022,246 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, curren if isOverturned == True: break_start += 40 - xbmc.log('Skip monitor for ' + game_pk + ' found ' + str(timedelta(seconds=total_skip_time)) + ' total skip time') + xbmc.log(monitor_name + ' found ' + str(timedelta(seconds=total_skip_time)) + ' total skip time') return skip_markers + + + def change_monitor(self, blackouts): + xbmc.log("Change monitor starting") + + self.mlb_monitor_started = str(datetime.now()) + settings.setSetting(id='mlb_monitor_started', value=self.mlb_monitor_started) + monitor_name = 'Change monitor from ' + self.mlb_monitor_started + xbmc.log(monitor_name + ' started') + + # initialize player to monitor video title + player = xbmc.Player() + video_title = LOCAL_STRING(30417) + ' ' + self.mlb_monitor_started + + game_refresh_sec = 10 + stream_refresh_sec = 1 + # check games every `refresh_sec` but return data from `delay_sec` ago to account for stream delay/buffering + refresh_sec = game_refresh_sec + delay_sec = GAME_CHANGER_DELAY + games_buffer = deque(maxlen=int((delay_sec / refresh_sec) + 1)) + players_buffer = deque(maxlen=int((delay_sec / refresh_sec) + 1)) + + games = [] + players = dict() + curr_game = None + + u_params = '&name=' + video_title + '&description=' + urllib.quote_plus(LOCAL_STRING(30418)) + '&icon=' + urllib.quote_plus(ICON) + + today = localToEastern() + date_string = today[0:4] + '/month_' + today[5:7] + '/day_' + today[8:10] + + while not self.monitor.abortRequested(): + if refresh_sec != stream_refresh_sec: + new_games, new_players = self.get_best_games(date_string, blackouts, monitor_name, players) + games_buffer.append(new_games) + players_buffer.append(new_players) + games = games_buffer[0] + if delay_sec > 0: + xbmc.log(monitor_name + ' ' + str(delay_sec) + ' second delayed data ' + json.dumps(games)) + players = players_buffer[0] + + if curr_game is not None and len(games) == 0: + xbmc.log(monitor_name + ' not switching from ' + curr_game['state'].teams + ' because there are no active games') + elif curr_game is not None and len(games) > 0 and curr_game['state'].game_pk == games[0]['state'].game_pk: + xbmc.log(monitor_name + ' not switching because ' + curr_game['state'].teams + ' is still the only/best game') + elif curr_game is None and len(games) == 0: + dialog = xbmcgui.Dialog() + dialog.ok(LOCAL_STRING(30417),LOCAL_STRING(30419)) + break + else: + # Update state of curr_game + if curr_game is not None: + new_curr_game = [game for game in games if game['state'].game_pk == curr_game['state'].game_pk] + if not new_curr_game: + curr_game = None + else: + curr_game = new_curr_game[0] + + # Only switch games if: + # curr_game is None (either no curr_game or it's in commercial break) + # The change in leverage is > 1.5 and there's a new batter in curr_game + # game has a better leverage than curr_game and curr_game is below average leverage (1.0) and there's a new batter in curr_game + curr_game_none = curr_game is None + new_batter = curr_game and curr_game['state'].new_batter + curr_game_below_avg = curr_game and curr_game['leverage_index'] < 1.0 + for game in games: + large_leverage_diff = curr_game and (game['leverage_index'] - curr_game['leverage_index'] > 1.5) + game_better = curr_game and game['leverage_index'] > curr_game['leverage_index'] + if curr_game_none or (new_batter and (large_leverage_diff or (curr_game_below_avg and game_better))): + curr_game = game + xbmc.log(monitor_name + ' loading game ' + game['state'].teams) + self.stream_started = False + refresh_sec = stream_refresh_sec + #player.stop() + # next line helps avoid a crash per https://github.com/xbmc/xbmc/issues/14838#issuecomment-750289254 + xbmc.executebuiltin('Dialog.Close(all,true)') + #xbmc.Player().play('plugin://plugin.video.mlbtv/?mode=102&game_pk='+game['state'].game_pk+u_params) + xbmc.executebuiltin('PlayMedia("plugin://plugin.video.mlbtv/?mode=102&game_pk='+game['state'].game_pk+u_params+'")') + xbmcplugin.endOfDirectory(addon_handle) + xbmc.log(monitor_name + ' loaded game ' + game['state'].teams) + break + elif large_leverage_diff: + xbmc.log(monitor_name + ' ' + game['state'].teams + ' is a better game, but ' + curr_game['state'].teams + ' still has a batter at the plate') + elif game_better: + xbmc.log(monitor_name + ' ' + game['state'].teams + ' is better game, but not enough better to switch from ' + curr_game['state'].teams) + + if xbmc.getCondVisibility("Player.HasMedia"): + if self.stream_started == False: + xbmc.log(monitor_name + " detected stream start") + self.stream_started = True + refresh_sec = game_refresh_sec + else: + if self.stream_started == False: + xbmc.log(monitor_name + " waiting for stream to start") + self.stream_start_tries -= 1 + if self.stream_start_tries < 1: + xbmc.log(monitor_name + " closing due to stream not starting") + break + + if self.monitor.waitForAbort(refresh_sec): + xbmc.log(monitor_name + " aborting") + break + elif self.stream_started == True: + if not xbmc.getCondVisibility("Player.HasMedia"): + xbmc.log(monitor_name + " closing due to stream stopped") + break + elif player.getVideoInfoTag().getTitle() != video_title: + xbmc.log(monitor_name + " closing due to stream changed") + break + elif self.mlb_monitor_started == '': + xbmc.log(monitor_name + " closing due to reset") + break + + xbmc.log(monitor_name + " closed") + + + # get active live games ordered by leverage + def get_best_games(self, date_string, blackouts, monitor_name, players): + url = 'http://gd2.mlb.com/components/game/mlb/year_' + date_string + '/master_scoreboard.json' + headers = { + 'User-Agent': UA_PC + } + r = requests.get(url,headers=headers, verify=VERIFY) + json_source = r.json() + #xbmc.log('Change monitor ' + self.mlb_monitor_started + ' json source : ' + json.dumps(json_source)) + + best_games = [] + new_players = dict() + omitted_games = {'blackout': [], 'inactive': [], 'break': [], 'pitching_change': []} + + if 'data' in json_source and 'games' in json_source['data'] and 'game' in json_source['data']['games']: + games = [] + for game in json_source['data']['games']['game']: + teams = game['away_name_abbrev'] + '@' + game['home_name_abbrev'] + # Game is blacked out + game_pk = str(game['game_pk']) + if game_pk in blackouts: + omitted_games['blackout'].append(teams) + continue + + game_status = game['status'] + + # Game is not active (not started, game over, or in replay review) + if game_status['status'] != 'In Progress': + omitted_games['inactive'].append(teams) + continue + + # Game is between innings + inning_half = self.convert_inning_half(game_status['inning_state']) + outs = int(game_status['o']) + if inning_half == 'Middle' or inning_half == 'End' or outs == 3: + omitted_games['break'].append(teams) + continue + + pitcher = game['pitcher']['id'] + new_pitcher = game_pk in players and 'pitcher' in players[game_pk] and players[game_pk]['pitcher'] != pitcher + if new_pitcher: + omitted_games['pitching_change'].append(teams) + continue + + balls = int(game_status['b']) + strikes = int(game_status['s']) + batter = game['batter']['id'] + new_batter = (balls == 0 and strikes == 0) or balls == 4 or strikes == 3 or (game_pk in players and 'batter' in players[game_pk] and players[game_pk]['batter'] != batter) + + inning_num = int(game_status['inning']) + runners_on_base = self.convert_runners_on_base(game['runners_on_base']) + away_score = int(game['linescore']['r']['away']) + home_score = int(game['linescore']['r']['home']) + + # bump perfect games or no hitters in the 9th inning to the top of the leverage list + leverage_adjust = 0 + if inning_num == 9 and (game_status['is_perfect_game'] == 'Y' or game_status['is_no_hitter'] == 'Y'): + away_hits = int(game['linescore']['h']['away']) + if (away_hits == 0 and inning_half == 'top') or (inning_half == 'bot'): + if game_status['is_perfect_game'] == 'Y': + xbmc.log(monitor_name + ' adjusting ' + teams + ' for perfect game') + leverage_adjust = MAX_LEVERAGE * 2 + else: + xbmc.log(monitor_name + ' adjusting ' + teams + ' for no hitter') + leverage_adjust = MAX_LEVERAGE + + state = self.GameState( + teams, + away_score, + home_score, + inning_half, + inning_num, + outs, + runners_on_base, + game_pk, + new_batter, + leverage_adjust) + games.append(state) + + new_players[game_pk] = {'batter': batter, 'pitcher': pitcher} + + xbmc.log(monitor_name + ' omitted games ' + json.dumps(omitted_games)) + + leverage_indices = [{ + "leverage_index": self.get_li(game.inning_num, game.inning_half, game.runners_on_base, game.outs, game.away_score, game.home_score) + game.leverage_adjust, + "state": game + } for game in games] + if len(leverage_indices) > 0: + best_games = sorted(leverage_indices, key=lambda x: x['leverage_index'], reverse=True) + xbmc.log(monitor_name + ' live data ' + json.dumps(best_games)) + + return best_games, new_players + + + def convert_inning_half(self, inning_state): + if inning_state == 'Bottom': + return 'bot' + elif inning_state == 'Top': + return 'top' + else: + return inning_state + + + def convert_runners_on_base(self, runners_on_base): + runners_on_str = '' + runners_on_str += '1 ' if 'runner_on_1b' in runners_on_base else '_ ' + runners_on_str += '2 ' if 'runner_on_2b' in runners_on_base else '_ ' + runners_on_str += '3' if 'runner_on_3b' in runners_on_base else '_' + return runners_on_str + + + def get_run_differential(self, away_score, home_score): + MINIMUM = -4 + MAXIMUM = 4 + differential = home_score - away_score + clamped_differential = max(MINIMUM, min(differential, MAXIMUM)) + return clamped_differential + + + def get_li(self, inning_num, inning_half, runners_on_base, num_outs, away_score, home_score): + run_differential_index = self.get_run_differential(away_score, home_score) + 4 + inning_num_index = min(inning_num, 9) + + return self.LI_TABLE[inning_num_index][inning_half][runners_on_base][num_outs][run_differential_index] diff --git a/resources/settings.xml b/resources/settings.xml index 062be73..f250744 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -34,6 +34,7 @@ + @@ -43,6 +44,7 @@ + diff --git a/service.py b/service.py index 94f8a87..77f07fe 100644 --- a/service.py +++ b/service.py @@ -1,3 +1,8 @@ +# coding=UTF-8 +# SPDX-License-Identifier: GPL-2.0-or-later +# Original proxy.plugin.example © matthuisman +# Modified for MLB.TV compatibility + import threading import xbmc