SimHoc/hocUtils.py
2024-08-15 15:15:58 -04:00

171 lines
7.4 KiB
Python

import os, itertools
import networkx as nx
from skillContests import DefAction
class RinkGraph(object):
"""Base class for a graph of nodes representing a hockey rink. Description of nodes found in design documents."""
G = nx.empty_graph()
def __init__(self, nodeFilename:str=None, edgeFilename:str=None):
if nodeFilename is not None:
with open(os.path.join("Rinks","Graphs",nodeFilename)) as nodeFile:
nodeListS = [node.strip() for node in nodeFile.readlines()]
self.G = nx.empty_graph(create_using=nx.DiGraph)
edges = itertools.permutations(nodeListS,2)
self.G.add_edges_from(edges)
elif edgeFilename is not None:
self.G = nx.readwrite.edgelist.read_edgelist(os.path.join("Rinks","Graphs", edgeFilename), create_using=nx.DiGraph)
def writeGraph(self, writeFilename:str):
nx.readwrite.edgelist.write_edgelist(self.G, os.path.join("Rinks","Graphs",writeFilename))
def nameToZones(self, name:str):
column = int(name[1])
row = int(name[0])
def adjacencyRule(self):
for node1, node2 in list(self.G.edges):
self.G.edges[node1, node2]['actions'] = []
if (abs(int(node1[0]) - int(node2[0])) <= 1 and abs(int(node1[1]) - int(node2[1])) <= 0) or (abs(int(node1[0]) - int(node2[0])) <= 0 and abs(int(node1[1]) - int(node2[1])) <= 1):
self.G.edges[node1, node2]['adjacent'] = 1
elif node1 == "01" and node2 == "10" or node1 == "10" and node2 == "01" or node1 == "06" and node2 == "17" or node1 == "17" and node2 == "06":
self.G.edges[node1, node2]['adjacent'] = 1
elif node1 == "41" and node2 == "30" or node1 == "30" and node2 == "41" or node1 == "46" and node2 == "37" or node1 == "37" and node2 == "46":
self.G.edges[node1, node2]['adjacent'] = 1
else:
self.G.edges[node1, node2]['adjacent'] = 0
def backSkateRule(self):
for node1, node2 in list(self.G.edges):
if node1[1] in ['1','2','3','4','6']:
if abs(int(node1[0]) - int(node2[0])) <= 1 and int(node2[1]) == int(node1[1])-1:
self.G.edges[node1, node2]['actions'].append('SkateB')
def forwardSkateRule(self):
for node1, node2 in list(self.G.edges):
access = False
if node1[1] in ['0','1','2']:
access = True
distance = 2
elif node1[1] in ['3','4']:
access = True
distance = 1
if access and node2[1] >= node1[1]:
atkDistance = int(node2[1]) - int(node1[1])
horzDistance = abs(int(node2[0]) - int(node1[0]))
if (atkDistance+horzDistance) <= distance:
self.G.edges[node1, node2]['actions'].append('SkateF')
def throughSkateRule(self):
for node1, node2 in list(self.G.edges):
if node1[1] in ['5','6','7']:
if node1[0] not in ['0','4']:
if node2 == '26':
self.G.edges[node1, node2]['actions'].append('SkateT')
else:
if node1[0] == '0' and node2 == '16' or node1[0] == '4' and node2 == '36':
self.G.edges[node1, node2]['actions'].append('SkateT')
def aroundSkateRule(self):
for node1, node2 in list(self.G.edges):
if node1[1] in ['5','6'] and node1[0] != '2' and node2 == '27':
self.appendAction(node1, node2, 'SkateA')
def stretchPassRule(self):
for node1, node2 in list(self.G.edges):
if node1[1] in ['1','2'] and node2[1] == '5':
self.appendAction(node1, node2, "PassS")
def forwardPassRule(self):
for node1, node2 in list(self.G.edges):
if node1[1] in ['1','2', '3', '5', '6']:
if int(node2[1]) >= int(node1[1]) and int(node2[1]) - int(node1[1]) <= 2:
self.appendAction(node1, node2, 'PassF')
elif node1[1] == '4' and node2[1] in ['4','5']:
self.appendAction(node1, node2, 'PassF')
elif node1[1] == '7' and node2[1] in ['5','6']:
self.appendAction(node1, node2, 'PassF')
def backwardPassRule(self):
for node1, node2 in list(self.G.edges):
if node1[1] not in ['0','5','7']: #back pass from column 7 is treated as forward pass for difficulty
if int(node1[1]) - int(node2[1]) == 1 and node1[0] == node2[0]:
self.appendAction(node1, node2, 'PassB')
def appendAction(self, node1, node2, actionString):
self.G.edges[node1, node2]['actions'].append(actionString)
def allRules(self):
self.adjacencyRule()
self.backSkateRule()
self.forwardSkateRule()
self.throughSkateRule()
self.aroundSkateRule()
self.stretchPassRule()
self.forwardPassRule()
self.backwardPassRule()
self.writeGraph("defaultedges.nx")
def getAllReachableFrom(self, nodeName):
"""Returns a dictionary where the keys are all reachable nodes, and the values are the list of actions that can reach the key node."""
if isinstance(nodeName, int):
if nodeName < 10:
nodeName = '0'+str(nodeName)
else:
nodeName = str(nodeName)
allConnected = dict(self.G[nodeName])
possibleReachable = {}
for otherNode in allConnected:
if allConnected[otherNode]['actions'] != []:
possibleReachable[otherNode] = allConnected[otherNode]['actions']
return possibleReachable
def getAdjacentNodes(self, nodeName):
"""Returns a list of all nodes marked as adjacent by the current map."""
if isinstance(nodeName, int):
if nodeName < 10:
nodeName = '0'+str(nodeName)
else:
nodeName = str(nodeName)
allConnected = dict(self.G[nodeName])
adjacents = []
for otherNodeName, nodeDic in allConnected.items():
if nodeDic['adjacent']:
adjacents.append(otherNodeName)
return adjacents
def getPossibleDefensiveActions(self, nodeName):
if isinstance(nodeName, int):
if nodeName < 10:
nodeName = '0'+str(nodeName)
else:
nodeName = str(nodeName)
actions = [DefAction.Steal, DefAction.Poke, DefAction.BlockLn, DefAction.Body, DefAction.Force]
if int(nodeName) < 10 or int(nodeName) > 40 or int(nodeName[1]) == 0 or int(nodeName[1]) == 7: #on wall
actions.append(DefAction.Pin)
if int(nodeName[1]) >= 5: #in defensive end
actions.append(DefAction.BlockSlot)
return actions
def shotDanger(self, nodeName):
"""Returns an int indicating the danger of a shot from that zone. 0 is no danger, 100 is 26-Offensive Low Slot"""
if isinstance(nodeName, int):
if nodeName < 10:
nodeName = '0'+str(nodeName)
else:
nodeName = str(nodeName)
if int(nodeName[1]) < 5:
return 0
elif nodeName == '26':
return 100
elif nodeName == '25':
return 70
elif nodeName[0] in ['1','3'] or nodeName == '27': #27 is included for tuck/michigan
return 40
else:
return 20