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):
"""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]:
#FW in possession
if left:
@ -162,11 +162,11 @@ class Game(object):
return str(lookupList[self.faceoffSpot.value])
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.period >= 3 and self.homeScore != self.awayScore: #game over
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
self.period += 1
self.clock = self.startClock*1
@ -180,14 +180,14 @@ class Game(object):
self.playStopped = False
winningPlayer = self.skatersInPossession()[SkaterPosition.C.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.clock -= random.randint(2,5)
self.currentZone = self.zoneAfterFaceoff()
elif self.loosePuck: #who gets it
#first pick skaters chasing puck
defenders = self.currentZone % 10 <= 3
defenders = int(self.currentZone) % 10 <= 3
if defenders: #offensive team needs defensemen
validPosO = [SkaterPosition.LD, SkaterPosition.RD]
validPosD = [SkaterPosition.RW, SkaterPosition.LW]
@ -208,7 +208,8 @@ class Game(object):
else:
self.positionInPossession = defPos
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)
else: #run za state machine
@ -226,7 +227,7 @@ class Game(object):
result = self.skillContest(attacker, defender, scParams)
if result: #attacker succeeded
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
else:
self.currentZone = int(nodeTarget)
@ -235,11 +236,13 @@ class Game(object):
#successful pass, determine new possession
allPos = self.allPositions()
allPos.remove(self.positionInPossession) #cant pass to yourself
if nodeTarget % 10 >= 6: #D wouldnt be behind net, ever
allPos.remove(SkaterPosition.RD)
allPos.remove(SkaterPosition.LD)
if int(nodeTarget) % 10 >= 6: #D wouldnt be behind net, ever
if self.positionInPossession != SkaterPosition.RD:
allPos.remove(SkaterPosition.RD)
if self.positionInPossession != SkaterPosition.LD:
allPos.remove(SkaterPosition.LD)
#emphasize pass side attacker
if nodeTarget < 30: #attacking left
if int(nodeTarget) < 30: #attacking left
if self.positionInPossession != SkaterPosition.LW:
allPos.append(SkaterPosition.LW)
else:
@ -251,7 +254,7 @@ class Game(object):
allPos.append(SkaterPosition.C)
self.positionInPossession = random.sample(allPos, 1)[0]
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
elif atkAction in [AtkAction.SkateA, AtkAction.SkateF, AtkAction.SkateT, AtkAction.SkateB]:
if atkAction == AtkAction.SkateB:
@ -259,41 +262,41 @@ class Game(object):
else:
self.space = False
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
else: #dumped puck
raise NotImplementedError
else: #defender won
if defAction in [DefAction.Force, DefAction.Steal, DefAction.Body]: #actions zat grant defender puck at start of action
self.changePossession()
self.positionInPossession = SkaterPosition(self.skatersDefending().index(defender))
self.positionInPossession = SkaterPosition(self.skatersInPossession().index(defender))
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:
self.addEventLog(f"{defender.name} takes it away cleanly.")
self.addEventLog(f"{defender} steals it from {attacker}.")
self.clock -= random.randint(4,6)
elif defAction in [DefAction.Pin, DefAction.Poke]: #actions zat cause loose puck at start of action
self.loosePuck = True
self.loosePuckDefAdv = defAction == DefAction.Poke
self.currentZone = int(random.sample(self.activeGraph().getAdjacentNodes(), 1)[0])
self.addEventLog(f"{defender.name} forces za puck loose!")
self.currentZone = int(random.sample(self.activeGraph().getAdjacentNodes(self.currentZone), 1)[0])
self.addEventLog(f"{defender} forces za puck loose!")
self.clock -= random.randint(2,4)
elif defAction == DefAction.BlockSlot: #grants defender puck at end of action
self.currentZone = nodeTarget
self.changePossession()
self.positionInPossession = SkaterPosition(self.skatersDefending().index(defender))
self.addEventLog(f"{defender.name} blocks a shot and picks up za puck!")
self.positionInPossession = SkaterPosition(self.skatersInPossession().index(defender))
self.addEventLog(f"{defender} blocks a shot and picks up za puck!")
self.clock -= random.randint(1,3)
elif defAction == DefAction.BlockLn: #pass fuckery
self.passCheck(nodeTarget, defender, atkAction)
self.clock -= random.randint(3,6)
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.changePossession()
self.positionInPossession = SkaterPosition(self.skatersDefending().index(blockingDefender))
self.addEventLog(f"{blockingDefender.name} intercepts a pass and takes it cleanly!")
self.positionInPossession = SkaterPosition(self.skatersInPossession().index(blockingDefender))
self.addEventLog(f"{blockingDefender} intercepts a pass!")
else: #loose puck!
if random.random() > 0.5:
self.currentZone = target
@ -319,26 +322,26 @@ class Game(object):
else:
self.awayScore += 1
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
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)
else: #blocked shot
self.loosePuck = True
self.loosePuckDefAdv = True
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)
def changePossession(self):
self.teamInPossession = self.away if self.homeAttacking() else self.home
#gotta flip node
self.currentZone = 47 - self.currentZone
self.currentZone = 47 - int(self.currentZone)
def saveMadeStop(self, shootingPlayer, shotType):
"""Stops play due to a save made by a goalie, and sets the faceoff dot to be used."""
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.eventLogVerbose.append(eventText)
options = [FaceoffDot.AwayZoneLeft, FaceoffDot.AwayZoneRight] if self.homeAttacking() else [FaceoffDot.HomeZoneLeft, FaceoffDot.HomeZoneRight]
@ -410,9 +413,9 @@ class Game(object):
raise NotImplementedError()
def addEventLog(self, eventString, verbose:bool=False):
self.eventLogVerbose.append(f"{self.clockToMinutesSeconds()} - {eventString}")
if not verbose:
self.eventLog.append(eventString)
self.eventLogVerbose.append(eventString)
def eventLogLength(self):
count = 0
@ -422,8 +425,8 @@ class Game(object):
def eventLogOut(self):
outList = []
while len(self.eventLog) > 0:
outList.append(self.eventLog.pop(0))
while len(self.eventLogVerbose) > 0:
outList.append(self.eventLogVerbose.pop(0))
return outList
class FaceoffDot(Enum):

View file

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

View file

@ -1,6 +1,6 @@
import attributes
from enum import Enum
from random import sample
from random import sample, randint
from skillContests import AtkAction, DefAction, SkillContestParams
class CreationError(Exception):
@ -72,7 +72,7 @@ class Player(object):
return False
def __str__(self):
return f"#{str(self.number)} {self.initials()}"
return self.idString()
def initials(self):
names = self.name.split()
@ -87,13 +87,22 @@ class Player(object):
def predictOpposingAction(self, opposingSkater, graph, currentNode):
oppAttributes = opposingSkater.getAttributes()
#TODO: Fuzzy opponent attributes based on wisdom
return self.chooseDefAction(statsOverride=oppAttributes)
raise NotImplementedError()
return self.chooseDefAction(currentNode, graph, statsOverride=oppAttributes)
def chooseAtkAction(self, actionDic, currentNode, graph, opposingSkater):
"""TODO: Make actual AI. Picks an action/target node combo."""
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]]
ovr = SkillContestParams().actionCheck(action,predAction).override
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
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:
result = evenTable[atkAction.value][defAction.value]
if isinstance(result, bool):
self.override = result
return
return self
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)]
evenTable = [