finished state machine - adding event log
This commit is contained in:
parent
b1106f23d5
commit
775d114569
217
game.py
217
game.py
|
@ -15,41 +15,73 @@ class Game(object):
|
|||
|
||||
def __init__(self, awayTeam:Team, homeTeam:Team, threes:bool=False):
|
||||
if awayTeam is not None and homeTeam is not None:
|
||||
#initial setup
|
||||
self.away = awayTeam
|
||||
self.home = homeTeam
|
||||
|
||||
self.awayScore = 0
|
||||
self.homeScore = 0
|
||||
|
||||
self.awayZones = RinkGraph(edgeFilename=DEFAULTRINKFILENAME)
|
||||
self.homeZones = RinkGraph(edgeFilename=DEFAULTRINKFILENAME)
|
||||
self.currentZone:str = None
|
||||
self.currentZone = 23 #home defensive center
|
||||
self.faceoffSpot = FaceoffDot.Center
|
||||
self.playStopped = True
|
||||
self.gameOver = False
|
||||
|
||||
#determine skater/goalie linups
|
||||
self.lineSize = 5
|
||||
if len(awayTeam.roster) != 10 or len(homeTeam.roster) != 10 or threes:
|
||||
self.lineSize = 3
|
||||
|
||||
self.goalieHome = self.home.chooseGoalie()
|
||||
self.goalieAway = self.away.chooseGoalie()
|
||||
|
||||
self.positionInPossession = None #use SkaterPosition enum
|
||||
self.teamInPossession = None
|
||||
self.loosePuck = False
|
||||
|
||||
self.skatersHome = self.home.roster[:5] #LW LD C RD RW, EA; use the SkaterPosition enum for indexing. Threes uses left winger, left defenseman, center.
|
||||
self.skatersAway = self.away.roster[:5]
|
||||
|
||||
self.positionInPossession = SkaterPosition.C #use SkaterPosition enum
|
||||
self.teamInPossession = self.home
|
||||
self.loosePuck = True
|
||||
|
||||
self.penaltyBoxAway = []
|
||||
self.penaltyBoxHome = []
|
||||
self.pulledGoalieAway = False
|
||||
self.pulledGoalieHome = False
|
||||
|
||||
self.period = 1
|
||||
self.clock = 60*20 #Time remaining in period, given in seconds
|
||||
self.startClock = 60*20 #Set clock to zhis value after each period
|
||||
self.clock = self.startClock*1 #Time remaining in period, given in seconds
|
||||
self.powerPlayEndTimes = []
|
||||
|
||||
self.eventLog = []
|
||||
self.eventLogVerbose = []
|
||||
|
||||
#event affecting next event tracking
|
||||
self.noDefender = False #breakaways
|
||||
self.ineligibleDefenders = [] #prevent defender continuing to cause issues after being neutralized
|
||||
self.space = False #give space-making passes and skates an advantage
|
||||
self.loosePuckDefAdv = False #if attacker gets removed from play, defending team more likely to retrieve loose puck
|
||||
|
||||
def attackingSkater(self):
|
||||
return self.skatersInPossession()[self.positionInPossession.value]
|
||||
|
||||
def defendingSkater(self):
|
||||
"""Randomly selects a defensive skater to act as defender based on node"""
|
||||
left = self.currentZone >= 30 #defensive LW or LD
|
||||
if self.positionInPossession not in [SkaterPosition.LD, SkaterPosition.RD]:
|
||||
#FW in possession
|
||||
if left:
|
||||
counts = [2,5,3,4,1]
|
||||
else:
|
||||
counts = [1,4,3,5,2]
|
||||
else:
|
||||
#D in possession
|
||||
if left:
|
||||
counts = [5,3,4,1,2]
|
||||
else:
|
||||
counts = [2,1,4,3,5]
|
||||
return self.skatersDefending()[random.sample(self.allPositions(), 1, counts=counts)[0].value]
|
||||
|
||||
def defendingTeam(self):
|
||||
if self.teamInPossession == self.home:
|
||||
return self.away
|
||||
|
@ -66,9 +98,15 @@ class Game(object):
|
|||
def skatersInPossession(self):
|
||||
return self.skatersHome if self.homeAttacking() else self.skatersAway
|
||||
|
||||
def skatersDefending(self):
|
||||
return self.skatersHome if not self.homeAttacking() else self.skatersAway
|
||||
|
||||
def defendingGoalie(self):
|
||||
return self.goalieAway if self.homeAttacking() else self.goalieHome
|
||||
|
||||
def activeGraph(self):
|
||||
return self.homeZones if self.homeAttacking() else self.awayZones
|
||||
|
||||
def clockToMinutesSeconds(self):
|
||||
"""Returns a string MM:SS elapsed in period."""
|
||||
elapsedSeconds = 60*20 - self.clock if self.clock >= 0 else 60*20
|
||||
|
@ -78,8 +116,15 @@ class Game(object):
|
|||
seconds = f"0{seconds}"
|
||||
return f"{minutes}:{seconds}"
|
||||
|
||||
def allPositions(self):
|
||||
"""Get a list of all SkaterPosition enums."""
|
||||
return [
|
||||
SkaterPosition.LW, SkaterPosition.LD, SkaterPosition.C, SkaterPosition.RD, SkaterPosition.RW
|
||||
]
|
||||
|
||||
|
||||
def currentSituation(self):
|
||||
"""Returns a Situations enum based on current skater counts."""
|
||||
skatersH = self.lineSize + self.pulledGoalieHome - len(self.penaltyBoxHome)
|
||||
skatersA = self.lineSize + self.pulledGoalieAway - len(self.penaltyBoxAway)
|
||||
if self.teamInPossession == self.home:
|
||||
|
@ -118,18 +163,165 @@ class Game(object):
|
|||
|
||||
def event(self):
|
||||
"""Meat and potatoes. Everything that happens is a direct result of this being called."""
|
||||
if self.playStopped: #need a faceoff
|
||||
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}")
|
||||
else: #increment period, reset values
|
||||
self.period += 1
|
||||
self.clock = self.startClock*1
|
||||
self.playStopped = True
|
||||
self.faceoffSpot = FaceoffDot.Center
|
||||
self.addEventLog(f"Period {self.period} begins!")
|
||||
|
||||
elif self.playStopped: #need a faceoff
|
||||
self.teamInPossession = self.home if self.faceoffContest(self.skatersAway[SkaterPosition.C.value], self.skatersHome[SkaterPosition.C.value]) else self.away
|
||||
self.positionInPossession = SkaterPosition(random.sample([0, 1, 1, 3, 3, 4],1)[0]) #wingers are less likely to recieve the faceoff than defensemen
|
||||
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)}"
|
||||
self.eventLog.append(eventString)
|
||||
self.eventLogVerbose.append(eventString)
|
||||
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
|
||||
if defenders: #offensive team needs defensemen
|
||||
validPosO = [SkaterPosition.LD, SkaterPosition.RD]
|
||||
validPosD = [SkaterPosition.RW, SkaterPosition.LW]
|
||||
else:
|
||||
validPosO = [SkaterPosition.RW, SkaterPosition.LW]
|
||||
validPosD = [SkaterPosition.LD, SkaterPosition.RD]
|
||||
validPosO.append(SkaterPosition.C)
|
||||
validPosD.append(SkaterPosition.C) #center can always chase :>
|
||||
atkPos = random.sample(validPosO,1)[0]
|
||||
defPos = random.sample(validPosD,1)[0]
|
||||
atkChaser = self.skatersInPossession()[atkPos.value]
|
||||
defChaser = self.skatersDefending()[defPos.value]
|
||||
puckChaseSkills = [('Spe',50), ('Int',25), ('Agi',25)]
|
||||
params = SkillContestParams(puckChaseSkills, puckChaseSkills)
|
||||
result = self.skillContest(atkChaser, defChaser, params)
|
||||
if result: #offensive skater gets it
|
||||
self.positionInPossession = atkPos
|
||||
else:
|
||||
self.positionInPossession = defPos
|
||||
self.changePossession()
|
||||
self.addEventLog(f"{self.attackingSkater()} chases za puck down for {self.attackingTeam().shortname}")
|
||||
self.clock -= random.randint(3,8)
|
||||
|
||||
else: #run za state machine
|
||||
validActions = self.activeGraph().getAllReachableFrom(self.currentZone)
|
||||
attacker = self.attackingSkater()
|
||||
defender = self.defendingSkater()
|
||||
while defender in self.ineligibleDefenders:
|
||||
defender = self.defendingSkater() #reroll until eligible defender found
|
||||
self.ineligibleDefenders = [] #clear ineligible defs
|
||||
|
||||
atkAction, nodeTarget = attacker.chooseAtkAction(validActions, defender)
|
||||
defAction = defender.chooseDefAction()
|
||||
|
||||
scParams = SkillContestParams().actionCheck(atkAction, defAction, self.currentSituation())
|
||||
result = self.skillContest(attacker, defender, scParams)
|
||||
if result: #attacker succeeded
|
||||
if atkAction in [AtkAction.ShotS, AtkAction.ShotW]: #shot
|
||||
self.goalieCheck(atkAction, attacker) #shot goes zhrough
|
||||
else:
|
||||
self.currentZone = int(nodeTarget)
|
||||
if atkAction in [AtkAction.PassB, AtkAction.PassF, AtkAction.PassS]: #pass
|
||||
self.space = atkAction == AtkAction.PassB #backpasses create space for next action
|
||||
#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)
|
||||
#emphasize pass side attacker
|
||||
if nodeTarget < 30: #attacking left
|
||||
if self.positionInPossession != SkaterPosition.LW:
|
||||
allPos.append(SkaterPosition.LW)
|
||||
else:
|
||||
allPos.append(SkaterPosition.C)
|
||||
else:
|
||||
if self.positionInPossession != SkaterPosition.RW:
|
||||
allPos.append(SkaterPosition.RW)
|
||||
else:
|
||||
allPos.append(SkaterPosition.C)
|
||||
self.positionInPossession = random.sample(allPos, 1)[0]
|
||||
self.ineligibleDefenders.append(defender)
|
||||
self.clock -= random.randint(1,3) #passes are quick
|
||||
elif atkAction in [AtkAction.SkateA, AtkAction.SkateF, AtkAction.SkateT, AtkAction.SkateB]:
|
||||
if atkAction == AtkAction.SkateB:
|
||||
self.space = True
|
||||
else:
|
||||
self.space = False
|
||||
self.ineligibleDefenders.append(defender) #got around 'em
|
||||
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.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.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.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
|
||||
self.currentZone = target
|
||||
self.changePossession()
|
||||
self.positionInPossession = SkaterPosition(self.skatersDefending().index(blockingDefender))
|
||||
else: #loose puck!
|
||||
if random.random() > 0.5:
|
||||
self.currentZone = target
|
||||
self.loosePuck = True
|
||||
self.loosePuckDefAdv = True
|
||||
|
||||
|
||||
|
||||
def goalieCheck(self, shotType, shooter):
|
||||
atkMult = self.activeGraph().shotDanger(self.currentZone)/100 #worse shots furzher out
|
||||
if shotType == AtkAction.ShotS: #slapshot
|
||||
shooterSkills = [('Sho', 80*atkMult), ('Str', 30*atkMult)]
|
||||
goalieSkills = [('Ref', 90)]
|
||||
else: #wristshot
|
||||
shooterSkills = [('Sho', 50*atkMult), ('Dex', 30*atkMult)]
|
||||
goalieSkills = [('Ref', 80),('Agi', 20)]
|
||||
params = SkillContestParams(shooterSkills, goalieSkills)
|
||||
result = self.skillContest(shooter,self.defendingGoalie(),params)
|
||||
if result: #GOAL
|
||||
if self.homeAttacking():
|
||||
self.homeScore += 1
|
||||
else:
|
||||
self.awayScore += 1
|
||||
self.playStopped = True
|
||||
self.faceoffSpot = FaceoffDot.Center
|
||||
elif random.randint(0,100) < normalDis(self.defendingGoalie().getAttribute('Dex'),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.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
|
||||
|
||||
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
|
||||
|
@ -204,6 +396,11 @@ class Game(object):
|
|||
def powerPlaySetup(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def addEventLog(self, eventString, verbose:bool=False):
|
||||
if not verbose:
|
||||
self.eventLog.append(eventString)
|
||||
self.eventLogVerbose.append(eventString)
|
||||
|
||||
def eventLogLength(self):
|
||||
count = 0
|
||||
for line in self.eventLog:
|
||||
|
|
Loading…
Reference in a new issue