Our goal-choosing code tries to find the best possible goal to score in given a field state. Field state is specified by a list of goal tuples, where each index in the list is matched with the corresponding goal number. A goal tuple has 3 elements (bottom, middle, top). Each element is either 0 for none, 1 for ours, or 2 for opponent. In addition to the field state, the goal-choosing code will also consider the robot's current position and the distances to all of the goals.

To choose a goal, we first determine the 9 possible field states that would result from scoring on any one goal. For each possible field state, we determine the signed difference between our score and our opponent's score. We then use the distance to the goal and whether scoring on it changes ownership as tiebreakers between close score differences. Finally, we pick the strategy which results in the highest total score, with these tiebreakers considered.

def strategy(field, pos):
  """strategy choosing
  field: the current field state. Each index is a goal, 
  each goal has a tuple with balls bottom-to-top. 0=none, 1=us 2=opponent
  pos: (x, y) coordinates wrt goal 0
  Field looks like this:
  --> x+
  0 1 2
  3 4 5
  6 7 8
  y+
  """

  scores = []
  for i, goal in enumerate(field):
    # given no scoring in full goals
    # can be changed if we can consistently descore
    if goal[2]:
      scores.append(-999999)
      continue
    # possible score if we add on top
    pField = field.copy()
    pField[i] = scoreOn(goal)
    our, opponent = scoreField(pField)
    # goal ownership score
    if goalOwner(goal) != 1:
      our += 1.5
    scores.append(our - opponent)
  
  # add in fractional time cost
  costPerInch = 0.001
  locs = [(0,0), (72,0), (144,0), (0,72), (72,72), (144,72), (144,0), (144,72), (144,144)]
  for i in range(len(scores)):
    scores[i] -= dist(pos, locs[i]) * costPerInch

  # choose max score
  print(scores)
  return scores.index(max(scores))

def dist(pos1, pos2):
  x1, y1 = pos1
  x2, y2 = pos2
  return ((x2-x1)**2 + (y2-y1)**2)**0.5

def scoreOn(goal):
  tempList = list(goal)
  for i, val in enumerate(goal):
    if not val:
      tempList[i] = 1
      break
  return tuple(tempList)

def scoreField(field):
  ourPoints = 0
  opponentPoints = 0

  # 1point per scored ball
  for goal in field:
    for spot in goal:
      if spot == 1:
        ourPoints += 1
      elif spot == 2:
        opponentPoints += 1
  
  # connected rows
  rows = [(0,1,2), (3,4,5), (6,7,8), (0,3,6), (1,4,7), (2,5,8), (0,4,8), (2,4,6)]
  for possible in rows:
    possibleIsScored = True
    testOwner = goalOwner(field[possible[0]])
    if not testOwner:
      continue
    for req in possible:
      if goalOwner(field[req]) != testOwner:
        possibleIsScored = False
    if possibleIsScored:
      if testOwner == 1:
        ourPoints += 6
      elif testOwner == 2:
        opponentPoints += 6
  
  return (ourPoints, opponentPoints)      

def goalOwner(goal):
  for spot in goal[::-1]:
    if spot:
      return spot
  return 0

# test scoring
if __name__ == "__main__":
  testField = [(2,0,0),(0,0,0),(0,0,0),
                (0,0,0),(2,0,0),(0,0,0),
                (0,0,0),(0,0,0),(2,0,0)]
  testPos = (72, 62)
  print(strategy(testField, testPos))