Генерация игры в НБА по играм за квартал

#python #algorithm #linked-list

#python #алгоритм #связанный список

Вопрос:

Я пытаюсь генерировать игры NBA по играм за квартал.

Как я это делал, я выбирал количество очков, которые одна команда наберет за квартал, а затем генерировал игры, чтобы соответствовать этому количеству очков.

Итак, допустим, я выбираю команду А, чтобы набрать 5 очков за четверть. Я создал алгоритм для генерации этого списка примеров:

[ [‘2 указателя’, ‘ПЕРЕКЛЮЧАТЕЛЬ’], [‘ПРОПУЩЕННЫЕ 2 указателя’, ‘Отскок в атаке’, ‘ОСТАТЬСЯ’], [‘3 указателя’, ‘ПЕРЕКЛЮЧАТЕЛЬ’]]

(Этот список намного проще, чем тот, который создается на самом деле. Я добавил больше функций, таких как передачи и подборы, за исключением перехватов и передач.)

«ПЕРЕКЛЮЧИТЬ» означает поменять владения, в то время как «ОСТАТЬСЯ» означает, что текущая команда сохраняет владение. У каждого «ПЕРЕКЛЮЧЕНИЯ» должно быть хотя бы одно действие, доступное в списке другой команды, в то время как у каждого «ПРЕБЫВАНИЯ» должно быть хотя бы одно действие, доступное в списке текущей команды.

Теперь, когда я создаю два списка для 2 разных команд, их списки могут отличаться по длине, потому что (среди прочего) одна команда может набрать больше очков, чем другая.

Когда я пытаюсь объединить два списка вместе, чтобы создать симуляцию игры за игрой, это может привести к тому, что у одной команды не будет действий, когда в другой команде есть «ПЕРЕКЛЮЧАТЕЛИ».

Мой вопрос в том, как я могу добавить дополнительные «ПЕРЕКЛЮЧАТЕЛИ» или «ПРЕБЫВАНИЕ» в список любых команд, чтобы каждый «ПЕРЕКЛЮЧАТЕЛЬ» выполнял действие в списке другой команды, а каждое «ПРЕБЫВАНИЕ» выполняло действие в списке текущей команды.

Моей первой мыслью было добавить случайные обороты и перехваты (приводящие к «ПЕРЕКЛЮЧЕНИЮ») в списки и надеяться, что это сработает, но я не могу придумать, как это сделать.

Кроме того, порядок списка начальной команды не имеет значения, если список соответствует количеству требуемых очков.

Это то, что у меня есть до сих пор:

def join_steals_tov(teamA_list, teamB_list):

 overall_list = []

possession = 'Team A'

print(len(teamA_list))
print(len(teamB_list))

while teamA_list and teamB_list:
    
    if possession == 'Team A':
        play = teamA_list.pop(0)
        play.insert(0,'Team A')
        overall_list.append(play)
        print(f"Team A: {play}")
        
        if play[-1] == 'SWITCH':
            possession = 'Team B'
    else:
        play = teamB_list.pop(0)
        play.insert(0,'Team B')
        overall_list.append(play)
        print(f"Team B: {play}")
        
        if play[-1] == 'SWITCH':
            possession = 'Team A'
    
return teamA_list, teamB_list, overall_list
 

Комментарии:

1. Имеет ли значение, сколько очков набирает другая команда? Или вы просто пытаетесь генерировать игру за игрой для 1 команды?

2. ДА. Итак, изначально я создаю два списка для двух отдельных команд. Один список добавит столько очков, сколько я установил. Таким образом, например, один список добавит до 20 очков, что означает, что команда, соответствующая этому списку, наберет 20 очков. Затем я пытаюсь объединить эти 2 списка вместе, чтобы сформировать данные о воспроизведении.

3. Я думаю, что могу случайным образом добавлять обороты и перехваты в каждый список, но 1) Я не знаю, как это будет работать 2) выполнение этого может просто усложнить задачу. Может быть, я не могу начать с требуемого количества очков? Я сейчас в тупике

4. Хорошо, я подумаю. Я думаю, есть несколько способов, которыми вы могли бы сделать это, используя вероятности для создания этих случайных событий.

Ответ №1:

Вот один из возможных подходов к созданию списка игр за квартал, начиная с двух списков событий подсчета очков, например

 teamA_scores = ['A2', 'A3']          
teamB_scores = ['B3', 'B3', 'B2']
 

Шаг 1. объедините списки в один список и перетасуйте объединенный список, например

 scores: ['A3', 'B3', 'A2', 'B3', 'B2']
          ^^ --- team A scores 3 points
 

Шаг 2. Когда одна и та же команда забивает дважды подряд, должно быть событие, в котором другая команда не забивает. Поэтому создайте новый список игр, который включает в себя обязательные игры с переключением, например

 switch: ['A3', 'B3', 'A2', 'B3', 'AS', 'B2']
                                  ^^ --- team A gives the ball back to team B without scoring
 

Шаг 3. Добавьте в список игру на конец квартала (просто чтобы упростить следующий шаг), например

 end Q:  ['A3', 'B3', 'A2', 'B3', 'AS', 'B2', 'AQ']
                                              ^^ --- time runs out with team A in possession
 

Шаг 4. Теперь добавьте несколько дополнительных игр без очков, чтобы заполнить квартал. В примере кода список игр дополняется до произвольной длины 10. Для реалистичности вам нужно будет назначить время для каждой игры и дополнять список, пока общее время не составит 12 минут.

 plays:  ['AK', 'A3', 'B3', 'AS', 'BS', 'A2', 'B3', 'AS', 'B2', 'AQ']
          ^^ --- team A keeps the ball, e.g. missed shot, offensive rebound
                            ^^ --- team A loses the ball
                                  ^^ --- team B loses the ball
 

Вот пример кода, который показывает, как все это объединяется, чтобы создать список из 10 игр из двух списков выигрышных игр:

 from random import shuffle
from random import randrange
from random import randint

# helper functions
def getTeam(event):
    return event[0]

def makeSameTeamItem(event, item):
    if getTeam(event) == 'A':
        return 'A'   item
    else:
        return 'B'   item

def makeOtherTeamItem(event, item):
    if getTeam(event) == 'B':
        return 'A'   item
    else:
        return 'B'   item

# start with a list of scores for each team
teamA_scores = ['A2', 'A3']
teamB_scores = ['B3', 'B3', 'B2']

# step 1: combine and shuffle to create an overall list of scores
scores = teamA_scores   teamB_scores
shuffle(scores)
print 'scores:', scores

# step 2: create list of plays including mandatory SWITCH plays
plays = []
plays.append(scores[0])
for i in range(1, len(scores)):
   if getTeam(scores[i]) == getTeam(scores[i-1]):
      plays.append(makeOtherTeamItem(scores[i], 'S'))
   plays.append(scores[i])
print 'switch:', plays

# step 3: add the end of quarter play
plays.append(makeOtherTeamItem(scores[-1], 'Q'))
print 'end Q: ', plays

# step 4: insert additional non-scoring plays to fill the quarter
quarter = 10
while len(plays) < quarter:
   index = randrange(0, len(plays))
   if quarter - len(plays) == 1 or randint(1,2) == 1:
      plays.insert(index, makeSameTeamItem(plays[index], 'K'))
   else:
      plays.insert(index, makeOtherTeamItem(plays[index], 'S'))
      plays.insert(index, makeOtherTeamItem(plays[index], 'S'))
print 'plays: ', plays
 

Комментарии:

1. Это отличный подход. Чтобы сначала записать события подсчета очков, а затем заполнить последовательные события подсчета очков одной и той же командой играми без подсчета очков. Это было не в моем вопросе, но что, если я также пытаюсь добавить «узкий диапазон» определенного события без подсчета очков? Например, если команды НБА делают 50% своих 2 указателей, количество пропущенных 2 указателей должно быть «примерно» (плюс-минус 1 или 2) таким же количеством сделанных 2 указателей. И как я могу дополнительно добавить передачи, подборы в нападении, подборы в защите? Я попытаюсь решить эту проблему самостоятельно, исходя из вашего подхода, поэтому я буду обновлять это, но если вы

2. (продолжение …) есть идея о том, как это сделать, прежде чем я это выясню, это было бы здорово!

3. @theyeinkye Это легко, если в списке много событий, не приносящих очков. Например, если есть десять событий «A2» и двадцать событий, не приносящих очков («AS» или «AK»), тогда вы можете случайным образом выбрать от 9 до 11 событий, не приносящих очков, и преобразовать их в события «AM2» (команда A пропускает 2 указателя). Вам нужно будет сделать то же самое для 3 указателей. Остальные «AS» и «AK» преобразуются в мяч за пределами поля, блок-шоты, вынос, пенальти в нападении и т. Д.

4. Это хорошая идея разделить события на выигрышные и не выигрышные и разделить их соответственно процентам событий. Я попробую, спасибо.

Ответ №2:

Я думаю, что это хороший способ играть с цепями Маркова. Взгляните сюда

Основная идея заключается в том, что для каждого текущего состояния существует вероятность перехода в следующее состояние. Вы можете создать матрицу вероятностей текущего состояния и вероятность того, каким может быть следующее состояние. Затем используйте эти веса для извлечения из random.choices . Вы можете сделать это настолько сложным, насколько захотите. Ключ в том, чтобы начать с простого, заставить его работать, затем вы можете добавить больше и приукрасить его (например, я собирался добавить состояния пропущенных 3 очка и сделанных 3 очка, (вам также нужно будет включить вероятность пропущенных 2 очка, сделанных 2 очка … иливы могли бы использовать это как расширение пропущенного удара и сделанного удара), но я решил, что я оставлю это вам для работы и добавления. Вы также можете добавить туда атакующие и оборонительные фолы, если это был фол при стрельбе в защите, вероятность того, что сделанный удар имел голевую передачу и т. Д.)

Тогда нужно всего лишь настроить несколько функций, чтобы отслеживать все и «разыгрывать» владение.

Итак, вот мой пример:

1. Создайте матрицу вероятностей для каждой команды. Индекс будет текущим состоянием, а столбцы — следующим состоянием.

Примечание: вы хотите, чтобы сумма каждой строки была равна 1

 import pandas as pd
import random

prob_matrix_A = pd.DataFrame([
[0, 0.05,   0.1,    0.375,  0.475,  0,  0,  0],
[0, 0,  0,  0,  0,  0,  0,  1],
[0, 0,  0,  0,  0,  0,  0,  1],
[0, 0.02,   0,  0,  0,  0.19,   0.79,   0],
[0, 0,  0,  0,  0,  0,  0,  1],
[1, 0,  0,  0,  0,  0,  0,  0],
[0, 0,  0,  0,  0,  0,  0,  1],
[0, 0, 0, 0, 0, 0, 0, 0]],
columns = ['Possession', 'Unforced Turnover','Steal','Miss Shot','Made Shot','Off Reb','Def Rebound', 'Change Possession'],
index = ['Possession', 'Unforced Turnover','Steal','Miss Shot','Made Shot','Off Reb','Def Rebound', 'Change Possession'])


prob_matrix_B = pd.DataFrame([
[0, 0.05,   0.1,    0.475,  0.375,  0,  0,  0],
[0, 0,  0,  0,  0,  0,  0,  1],
[0, 0,  0,  0,  0,  0,  0,  1],
[0, 0.02,   0,  0,  0,  0.09,   0.89,   0],
[0, 0,  0,  0,  0,  0,  0,  1],
[1, 0,  0,  0,  0,  0,  0,  0],
[0, 0,  0,  0,  0,  0,  0,  1],
[0, 0, 0, 0, 0, 0, 0, 0]],
columns = ['Possession', 'Unforced Turnover','Steal','Miss Shot','Made Shot','Off Reb','Def Rebound', 'Change Possession'],
index = ['Possession', 'Unforced Turnover','Steal','Miss Shot','Made Shot','Off Reb','Def Rebound', 'Change Possession'])
 

Итак, вот матрица вероятностей для команды A:

 print (prob_matrix_A.to_string())
                   Possession  Unforced Turnover  Steal  Miss Shot  Made Shot  Off Reb  Def Rebound  Change Possession
Possession                  0               0.05    0.1      0.375      0.475     0.00         0.00                  0
Unforced Turnover           0               0.00    0.0      0.000      0.000     0.00         0.00                  1
Steal                       0               0.00    0.0      0.000      0.000     0.00         0.00                  1
Miss Shot                   0               0.02    0.0      0.000      0.000     0.19         0.79                  0
Made Shot                   0               0.00    0.0      0.000      0.000     0.00         0.00                  1
Off Reb                     1               0.00    0.0      0.000      0.000     0.00         0.00                  0
Def Rebound                 0               0.00    0.0      0.000      0.000     0.00         0.00                  1
Change Possession           0               0.00    0.0      0.000      0.000     0.00         0.00                  0
 

2. Настройте, как отслеживать счет и какая команда владеет мячом:

 team_score_limit = 25


# game setup
game_dict = {'team_a':{'score':0, 'possession':False, 'prob_matrix': prob_matrix_A},
             'team_b':{'score':0, 'possession':False, 'prob_matrix': prob_matrix_B},
             'score_limit':team_score_limit}

# Do a jump ball to see who starts with ball
# Lets say team_a has 55% of getting the jump, and team_b 45%
wins_jump = random.choices(['team_a','team_b'], weights=[.55,.45], k=1)[0]
game_dict[wins_jump]['possession'] = True
print('%s: Wins the jump ball' %wins_jump)
 

3. Создайте функции для определения того, кто владеет мячом (т. Е. кто играет в нападении, защите):

 # Function to return who has possession
def possession(game_dict):
    for k, v in game_dict.items():
        if type(v) == dict:
            if v['possession'] == True:
                offense_team = k
            if v['possession'] == False:
                defense_team = k
    return offense_team, defense_team
        
 

4. Создайте функцию для разыгрывания владения мячом на основе вероятностей:

 def play(current_state, game_dict):
    # Determine who has possession and get their prob matrix
    offense, defense = possession(game_dict)
    prob_matrix = game_dict[offense]['prob_matrix']
    states_list = prob_matrix.columns.tolist()
    weights = prob_matrix.loc[current_state,:].tolist()
    
    # Based on current state weights, get the next state
    new_state = random.choices(states_list, weights=weights, k=1)[0]
    
    if 'Possession' not in new_state:
        if new_state in ['Def Rebound', 'Steal']:
            print ('%s: %s by %s' %(offense, new_state, defense))
        else:
            print ('%s: %s' %(offense, new_state))
    current_state = new_state
    

    if current_state == 'Made Shot':
        game_dict[offense]['score']  = 2
        
        # if team_a score reached the limit, we'll return False to stop the game
        if game_dict['team_a']['score'] >= game_dict['score_limit']:
            return False
 
    # if it's a change of possession, we'll update our game_dict
    if new_state == 'Change Possession':
        game_dict[offense]['possession'] = False
        game_dict[defense]['possession'] = True
    
    # else the team who currently possess the ball didn't change from the previous outcome (Ie. Offensive rebound), then they get another possession play 
    else:
        play(current_state, game_dict)
 

5. Установите начальное current_state значение для запуска «игры» и разыграйте ее:

 # Play the first quarter 
current_state = 'Possession'   
continue_play = True   
while continue_play != False:    
    continue_play = play(current_state, game_dict)    
    
    
    
print ('nEnd of 1st Quarter!n')
print ('**************************')
print ('team_a: %stteam_b: %s' %(game_dict['team_a']['score'],game_dict['team_b']['score']))
 

Вывод:

 runfile('test.py', wdir='C:/test')
team_a: Wins the jump ball
team_a: Made Shot
team_b: Miss Shot
team_b: Off Reb
team_b: Made Shot
team_a: Made Shot
team_b: Made Shot
team_a: Miss Shot
team_a: Def Rebound by team_b
team_b: Miss Shot
team_b: Def Rebound by team_a
team_a: Steal by team_b
team_b: Miss Shot
team_b: Def Rebound by team_a
team_a: Miss Shot
team_a: Def Rebound by team_b
team_b: Made Shot
team_a: Miss Shot
team_a: Def Rebound by team_b
team_b: Steal by team_a
team_a: Made Shot
team_b: Made Shot
team_a: Made Shot
team_b: Made Shot
team_a: Made Shot
team_b: Steal by team_a
team_a: Made Shot
team_b: Miss Shot
team_b: Def Rebound by team_a
team_a: Unforced Turnover
team_b: Made Shot
team_a: Steal by team_b
team_b: Made Shot
team_a: Unforced Turnover
team_b: Steal by team_a
team_a: Made Shot
team_b: Unforced Turnover
team_a: Miss Shot
team_a: Off Reb
team_a: Made Shot
team_b: Miss Shot
team_b: Def Rebound by team_a
team_a: Made Shot
team_b: Miss Shot
team_b: Def Rebound by team_a
team_a: Made Shot
team_b: Made Shot
team_a: Made Shot
team_b: Miss Shot
team_b: Def Rebound by team_a
team_a: Miss Shot
team_a: Off Reb
team_a: Made Shot
team_b: Miss Shot
team_b: Def Rebound by team_a
team_a: Made Shot

End of 1st Quarter!

**************************
team_a: 26      team_b: 16