first version of "player AI" implemented

This commit is contained in:
Sakimori 2024-08-15 15:15:58 -04:00
parent ce3fda2638
commit 6f41a1215e
4 changed files with 68 additions and 43 deletions

65
game.py
View file

@ -67,7 +67,7 @@ class Game(object):
def defendingSkater(self): def defendingSkater(self):
"""Randomly selects a defensive skater to act as defender based on node""" """Randomly selects a defensive skater to act as defender based on node"""
left = self.currentZone >= 30 #defensive LW or LD left = int(self.currentZone) >= 30 #defensive LW or LD
if self.positionInPossession not in [SkaterPosition.LD, SkaterPosition.RD]: if self.positionInPossession not in [SkaterPosition.LD, SkaterPosition.RD]:
#FW in possession #FW in possession
if left: if left:
@ -162,11 +162,11 @@ class Game(object):
return str(lookupList[self.faceoffSpot.value]) return str(lookupList[self.faceoffSpot.value])
def event(self): def event(self):
"""Meat and potatoes. Everything that happens is a direct result of this being called.""" """Meat and potatoes. Everyzhing zat happens is a direct result of zis being called."""
if self.clock < 0: #period/game over if self.clock < 0: #period/game over
if self.period >= 3 and self.homeScore != self.awayScore: #game over if self.period >= 3 and self.homeScore != self.awayScore: #game over
self.gameOver = True self.gameOver = True
self.addEventLog(f"Final score: {self.away.shortname} {self.awayScore} - {self.homeScore} {self.home.shortname}") self.addEventLog(f"Final score: {self.away.name} {self.awayScore} - {self.homeScore} {self.home.name}")
else: #increment period, reset values else: #increment period, reset values
self.period += 1 self.period += 1
self.clock = self.startClock*1 self.clock = self.startClock*1
@ -180,14 +180,14 @@ class Game(object):
self.playStopped = False self.playStopped = False
winningPlayer = self.skatersInPossession()[SkaterPosition.C.value] winningPlayer = self.skatersInPossession()[SkaterPosition.C.value]
receivingPlayer = self.skatersInPossession()[self.positionInPossession.value] receivingPlayer = self.skatersInPossession()[self.positionInPossession.value]
eventString = f"{self.clockToMinutesSeconds()} - {self.teamInPossession.shortname} {str(winningPlayer)} wins faceoff to {str(receivingPlayer)}" eventString = f"{self.teamInPossession.shortname} {winningPlayer} wins faceoff to {receivingPlayer}"
self.addEventLog(eventString) self.addEventLog(eventString)
self.clock -= random.randint(2,5) self.clock -= random.randint(2,5)
self.currentZone = self.zoneAfterFaceoff() self.currentZone = self.zoneAfterFaceoff()
elif self.loosePuck: #who gets it elif self.loosePuck: #who gets it
#first pick skaters chasing puck #first pick skaters chasing puck
defenders = self.currentZone % 10 <= 3 defenders = int(self.currentZone) % 10 <= 3
if defenders: #offensive team needs defensemen if defenders: #offensive team needs defensemen
validPosO = [SkaterPosition.LD, SkaterPosition.RD] validPosO = [SkaterPosition.LD, SkaterPosition.RD]
validPosD = [SkaterPosition.RW, SkaterPosition.LW] validPosD = [SkaterPosition.RW, SkaterPosition.LW]
@ -208,7 +208,8 @@ class Game(object):
else: else:
self.positionInPossession = defPos self.positionInPossession = defPos
self.changePossession() self.changePossession()
self.addEventLog(f"{self.clockToMinutesSeconds()} - {self.attackingSkater()} chases za puck down for {self.attackingTeam().shortname}") self.addEventLog(f"{self.attackingSkater()} chases za puck down for {self.attackingTeam().shortname}")
self.loosePuck = False
self.clock -= random.randint(3,8) self.clock -= random.randint(3,8)
else: #run za state machine else: #run za state machine
@ -226,7 +227,7 @@ class Game(object):
result = self.skillContest(attacker, defender, scParams) result = self.skillContest(attacker, defender, scParams)
if result: #attacker succeeded if result: #attacker succeeded
if atkAction in [AtkAction.ShotS, AtkAction.ShotW]: #shot if atkAction in [AtkAction.ShotS, AtkAction.ShotW]: #shot
self.addEventLog(f"{attacker.name} takes a shot!") self.addEventLog(f"{attacker} takes a shot!")
self.goalieCheck(atkAction, attacker) #shot goes zhrough self.goalieCheck(atkAction, attacker) #shot goes zhrough
else: else:
self.currentZone = int(nodeTarget) self.currentZone = int(nodeTarget)
@ -235,11 +236,13 @@ class Game(object):
#successful pass, determine new possession #successful pass, determine new possession
allPos = self.allPositions() allPos = self.allPositions()
allPos.remove(self.positionInPossession) #cant pass to yourself allPos.remove(self.positionInPossession) #cant pass to yourself
if nodeTarget % 10 >= 6: #D wouldnt be behind net, ever if int(nodeTarget) % 10 >= 6: #D wouldnt be behind net, ever
allPos.remove(SkaterPosition.RD) if self.positionInPossession != SkaterPosition.RD:
allPos.remove(SkaterPosition.LD) allPos.remove(SkaterPosition.RD)
if self.positionInPossession != SkaterPosition.LD:
allPos.remove(SkaterPosition.LD)
#emphasize pass side attacker #emphasize pass side attacker
if nodeTarget < 30: #attacking left if int(nodeTarget) < 30: #attacking left
if self.positionInPossession != SkaterPosition.LW: if self.positionInPossession != SkaterPosition.LW:
allPos.append(SkaterPosition.LW) allPos.append(SkaterPosition.LW)
else: else:
@ -251,7 +254,7 @@ class Game(object):
allPos.append(SkaterPosition.C) allPos.append(SkaterPosition.C)
self.positionInPossession = random.sample(allPos, 1)[0] self.positionInPossession = random.sample(allPos, 1)[0]
self.ineligibleDefenders.append(defender) self.ineligibleDefenders.append(defender)
self.addEventLog(f"{attacker.name} passes to {self.attackingSkater().name}.") self.addEventLog(f"{attacker} passes to {self.attackingSkater()}.")
self.clock -= random.randint(1,3) #passes are quick self.clock -= random.randint(1,3) #passes are quick
elif atkAction in [AtkAction.SkateA, AtkAction.SkateF, AtkAction.SkateT, AtkAction.SkateB]: elif atkAction in [AtkAction.SkateA, AtkAction.SkateF, AtkAction.SkateT, AtkAction.SkateB]:
if atkAction == AtkAction.SkateB: if atkAction == AtkAction.SkateB:
@ -259,41 +262,41 @@ class Game(object):
else: else:
self.space = False self.space = False
self.ineligibleDefenders.append(defender) #got around 'em self.ineligibleDefenders.append(defender) #got around 'em
self.addEventLog(f"{attacker.name} skates around.", verbose=True) self.addEventLog(f"{attacker} skates to node {nodeTarget}.", verbose=True)
self.clock -= random.randint(3,6) #skating is slow self.clock -= random.randint(3,6) #skating is slow
else: #dumped puck else: #dumped puck
raise NotImplementedError raise NotImplementedError
else: #defender won else: #defender won
if defAction in [DefAction.Force, DefAction.Steal, DefAction.Body]: #actions zat grant defender puck at start of action if defAction in [DefAction.Force, DefAction.Steal, DefAction.Body]: #actions zat grant defender puck at start of action
self.changePossession() self.changePossession()
self.positionInPossession = SkaterPosition(self.skatersDefending().index(defender)) self.positionInPossession = SkaterPosition(self.skatersInPossession().index(defender))
if defAction == DefAction.Body: if defAction == DefAction.Body:
self.addEventLog(f"{defender.name} bodies {attacker.name} off za puck.") self.addEventLog(f"{defender} bodies {attacker} off za puck.")
else: else:
self.addEventLog(f"{defender.name} takes it away cleanly.") self.addEventLog(f"{defender} steals it from {attacker}.")
self.clock -= random.randint(4,6) self.clock -= random.randint(4,6)
elif defAction in [DefAction.Pin, DefAction.Poke]: #actions zat cause loose puck at start of action elif defAction in [DefAction.Pin, DefAction.Poke]: #actions zat cause loose puck at start of action
self.loosePuck = True self.loosePuck = True
self.loosePuckDefAdv = defAction == DefAction.Poke self.loosePuckDefAdv = defAction == DefAction.Poke
self.currentZone = int(random.sample(self.activeGraph().getAdjacentNodes(), 1)[0]) self.currentZone = int(random.sample(self.activeGraph().getAdjacentNodes(self.currentZone), 1)[0])
self.addEventLog(f"{defender.name} forces za puck loose!") self.addEventLog(f"{defender} forces za puck loose!")
self.clock -= random.randint(2,4) self.clock -= random.randint(2,4)
elif defAction == DefAction.BlockSlot: #grants defender puck at end of action elif defAction == DefAction.BlockSlot: #grants defender puck at end of action
self.currentZone = nodeTarget self.currentZone = nodeTarget
self.changePossession() self.changePossession()
self.positionInPossession = SkaterPosition(self.skatersDefending().index(defender)) self.positionInPossession = SkaterPosition(self.skatersInPossession().index(defender))
self.addEventLog(f"{defender.name} blocks a shot and picks up za puck!") self.addEventLog(f"{defender} blocks a shot and picks up za puck!")
self.clock -= random.randint(1,3) self.clock -= random.randint(1,3)
elif defAction == DefAction.BlockLn: #pass fuckery elif defAction == DefAction.BlockLn: #pass fuckery
self.passCheck(nodeTarget, defender, atkAction) self.passCheck(nodeTarget, defender, atkAction)
self.clock -= random.randint(3,6) self.clock -= random.randint(3,6)
def passCheck(self, target, blockingDefender, passType): def passCheck(self, target, blockingDefender, passType):
if passType == AtkAction.PassS or random.random()*100 < normalDis(blockingDefender.getAttribute("Ref"),30,20,80): #stretch pass always intercepted, chance for interception based on defender's reflexes if passType == AtkAction.PassS or random.random()*100 < normalDis(blockingDefender.getAttribute("Ref").value,30,20,80): #stretch pass always intercepted, chance for interception based on defender's reflexes
self.currentZone = target self.currentZone = target
self.changePossession() self.changePossession()
self.positionInPossession = SkaterPosition(self.skatersDefending().index(blockingDefender)) self.positionInPossession = SkaterPosition(self.skatersInPossession().index(blockingDefender))
self.addEventLog(f"{blockingDefender.name} intercepts a pass and takes it cleanly!") self.addEventLog(f"{blockingDefender} intercepts a pass!")
else: #loose puck! else: #loose puck!
if random.random() > 0.5: if random.random() > 0.5:
self.currentZone = target self.currentZone = target
@ -319,26 +322,26 @@ class Game(object):
else: else:
self.awayScore += 1 self.awayScore += 1
self.playStopped = True self.playStopped = True
self.addEventLog(f"{self.clockToMinutesSeconds()} - {shooter.name} scores! New score: {self.away.shortname} {self.awayScore} - {self.homeScore} {self.home.shortname}") self.addEventLog(f"{shooter} scores wizh a {shotType.name}! New score: {self.away.shortname} {self.awayScore} - {self.homeScore} {self.home.shortname}")
self.faceoffSpot = FaceoffDot.Center self.faceoffSpot = FaceoffDot.Center
elif random.randint(0,100) < normalDis(self.defendingGoalie().getAttribute('Dex'),75,0,100): #caught puck elif random.randint(0,100) < normalDis(self.defendingGoalie().getAttribute('Dex').value,75,0,100): #caught puck
self.saveMadeStop(shooter, shotType) self.saveMadeStop(shooter, shotType)
else: #blocked shot else: #blocked shot
self.loosePuck = True self.loosePuck = True
self.loosePuckDefAdv = True self.loosePuckDefAdv = True
self.currentZone = random.sample(self.activeGraph().getAdjacentNodes(27),1)[0] self.currentZone = random.sample(self.activeGraph().getAdjacentNodes(27),1)[0]
self.addEventLog(f"{self.clockToMinutesSeconds()} - shot knocked aside by {self.defendingGoalie().name}.") self.addEventLog(f"{shotType.name} shot knocked aside by {self.defendingGoalie()}.")
self.clock -= random.randint(2,6) self.clock -= random.randint(2,6)
def changePossession(self): def changePossession(self):
self.teamInPossession = self.away if self.homeAttacking() else self.home self.teamInPossession = self.away if self.homeAttacking() else self.home
#gotta flip node #gotta flip node
self.currentZone = 47 - self.currentZone self.currentZone = 47 - int(self.currentZone)
def saveMadeStop(self, shootingPlayer, shotType): def saveMadeStop(self, shootingPlayer, shotType):
"""Stops play due to a save made by a goalie, and sets the faceoff dot to be used.""" """Stops play due to a save made by a goalie, and sets the faceoff dot to be used."""
self.playStopped = True self.playStopped = True
eventText = f"{self.clockToMinutesSeconds()} - {str(self.defendingGoalie)} saves shot from {str(shootingPlayer)}, stops play." eventText = f"{self.defendingGoalie()} saves shot from {shootingPlayer}, stops play."
self.eventLog.append(eventText) self.eventLog.append(eventText)
self.eventLogVerbose.append(eventText) self.eventLogVerbose.append(eventText)
options = [FaceoffDot.AwayZoneLeft, FaceoffDot.AwayZoneRight] if self.homeAttacking() else [FaceoffDot.HomeZoneLeft, FaceoffDot.HomeZoneRight] options = [FaceoffDot.AwayZoneLeft, FaceoffDot.AwayZoneRight] if self.homeAttacking() else [FaceoffDot.HomeZoneLeft, FaceoffDot.HomeZoneRight]
@ -410,9 +413,9 @@ class Game(object):
raise NotImplementedError() raise NotImplementedError()
def addEventLog(self, eventString, verbose:bool=False): def addEventLog(self, eventString, verbose:bool=False):
self.eventLogVerbose.append(f"{self.clockToMinutesSeconds()} - {eventString}")
if not verbose: if not verbose:
self.eventLog.append(eventString) self.eventLog.append(eventString)
self.eventLogVerbose.append(eventString)
def eventLogLength(self): def eventLogLength(self):
count = 0 count = 0
@ -422,8 +425,8 @@ class Game(object):
def eventLogOut(self): def eventLogOut(self):
outList = [] outList = []
while len(self.eventLog) > 0: while len(self.eventLogVerbose) > 0:
outList.append(self.eventLog.pop(0)) outList.append(self.eventLogVerbose.pop(0))
return outList return outList
class FaceoffDot(Enum): class FaceoffDot(Enum):

View file

@ -113,7 +113,10 @@ class RinkGraph(object):
def getAllReachableFrom(self, nodeName): 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.""" """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 isinstance(nodeName, int):
nodeName = str(nodeName) if nodeName < 10:
nodeName = '0'+str(nodeName)
else:
nodeName = str(nodeName)
allConnected = dict(self.G[nodeName]) allConnected = dict(self.G[nodeName])
possibleReachable = {} possibleReachable = {}
for otherNode in allConnected: for otherNode in allConnected:
@ -124,7 +127,10 @@ class RinkGraph(object):
def getAdjacentNodes(self, nodeName): def getAdjacentNodes(self, nodeName):
"""Returns a list of all nodes marked as adjacent by the current map.""" """Returns a list of all nodes marked as adjacent by the current map."""
if isinstance(nodeName, int): if isinstance(nodeName, int):
nodeName = str(nodeName) if nodeName < 10:
nodeName = '0'+str(nodeName)
else:
nodeName = str(nodeName)
allConnected = dict(self.G[nodeName]) allConnected = dict(self.G[nodeName])
adjacents = [] adjacents = []
for otherNodeName, nodeDic in allConnected.items(): for otherNodeName, nodeDic in allConnected.items():
@ -134,7 +140,10 @@ class RinkGraph(object):
def getPossibleDefensiveActions(self, nodeName): def getPossibleDefensiveActions(self, nodeName):
if isinstance(nodeName, int): if isinstance(nodeName, int):
nodeName = str(nodeName) if nodeName < 10:
nodeName = '0'+str(nodeName)
else:
nodeName = str(nodeName)
actions = [DefAction.Steal, DefAction.Poke, DefAction.BlockLn, DefAction.Body, DefAction.Force] 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 if int(nodeName) < 10 or int(nodeName) > 40 or int(nodeName[1]) == 0 or int(nodeName[1]) == 7: #on wall
actions.append(DefAction.Pin) actions.append(DefAction.Pin)
@ -145,7 +154,10 @@ class RinkGraph(object):
def shotDanger(self, nodeName): 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""" """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 isinstance(nodeName, int):
nodeName = str(nodeName) if nodeName < 10:
nodeName = '0'+str(nodeName)
else:
nodeName = str(nodeName)
if int(nodeName[1]) < 5: if int(nodeName[1]) < 5:
return 0 return 0

View file

@ -1,6 +1,6 @@
import attributes import attributes
from enum import Enum from enum import Enum
from random import sample from random import sample, randint
from skillContests import AtkAction, DefAction, SkillContestParams from skillContests import AtkAction, DefAction, SkillContestParams
class CreationError(Exception): class CreationError(Exception):
@ -72,7 +72,7 @@ class Player(object):
return False return False
def __str__(self): def __str__(self):
return f"#{str(self.number)} {self.initials()}" return self.idString()
def initials(self): def initials(self):
names = self.name.split() names = self.name.split()
@ -87,13 +87,22 @@ class Player(object):
def predictOpposingAction(self, opposingSkater, graph, currentNode): def predictOpposingAction(self, opposingSkater, graph, currentNode):
oppAttributes = opposingSkater.getAttributes() oppAttributes = opposingSkater.getAttributes()
#TODO: Fuzzy opponent attributes based on wisdom #TODO: Fuzzy opponent attributes based on wisdom
return self.chooseDefAction(statsOverride=oppAttributes) return self.chooseDefAction(currentNode, graph, statsOverride=oppAttributes)
raise NotImplementedError()
def chooseAtkAction(self, actionDic, currentNode, graph, opposingSkater): def chooseAtkAction(self, actionDic, currentNode, graph, opposingSkater):
"""TODO: Make actual AI. Picks an action/target node combo.""" """TODO: Make actual AI. Picks an action/target node combo."""
predAction = self.predictOpposingAction(opposingSkater, graph, currentNode) predAction = self.predictOpposingAction(opposingSkater, graph, currentNode)
targetNode = sample(actionDic.keys(),1)[0] #random target node #first check if shot is possible and if to take it
if int(currentNode) % 10 >= 5 and (int(currentNode) not in [17,37]): #27 is valid wristshot (tuck)
shotroll = randint(0,100)
if shotroll < self.getAttribute('Sho'): #fuckin go for it buddy
if int(currentNode) % 10 == 5 and SkillContestParams().actionCheck(AtkAction.ShotS,predAction).override: #blueline and not autolose slapshot
action = sample([AtkAction.ShotS, AtkAction.ShotW, AtkAction.ShotW],1)[0]
else:
action = AtkAction.ShotW
targetNode = 26 #goal node
return (action, targetNode)
targetNode = sample(list(actionDic.keys()),1)[0] #random target node
action = AtkAction[sample(actionDic[targetNode],1)[0]] action = AtkAction[sample(actionDic[targetNode],1)[0]]
ovr = SkillContestParams().actionCheck(action,predAction).override ovr = SkillContestParams().actionCheck(action,predAction).override
while ovr is not None and ovr is False: #don't pick an autofail while ovr is not None and ovr is False: #don't pick an autofail

View file

@ -39,14 +39,15 @@ class SkillContestParams(object):
self.defStats = defStats self.defStats = defStats
def actionCheck(self, atkAction:AtkAction, defAction:DefAction, situation:Situations=Situations.EvenStrength): def actionCheck(self, atkAction:AtkAction, defAction:DefAction, situation:Situations=Situations.EvenStrength):
"""Determines which skills to test, and how strongly.""" """Determines which skills to test, and how strongly. Returns itself."""
if situation == Situations.EvenStrength: if situation == Situations.EvenStrength:
result = evenTable[atkAction.value][defAction.value] result = evenTable[atkAction.value][defAction.value]
if isinstance(result, bool): if isinstance(result, bool):
self.override = result self.override = result
return return self
self.atkStats = result[0] self.atkStats = result[0]
self.defStats = result[1] self.defStats = result[1]
return self
#Bool overrides, or [List of (stat,weight) , List of (stat,weight)] #Bool overrides, or [List of (stat,weight) , List of (stat,weight)]
evenTable = [ evenTable = [