From 775d114569d2f675eeaf6d0652a9fcca8a0d80b7 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Wed, 14 Aug 2024 20:53:52 -0400 Subject: [PATCH] finished state machine - adding event log --- game.py | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 207 insertions(+), 10 deletions(-) diff --git a/game.py b/game.py index 10a9a38..96de52b 100644 --- a/game.py +++ b/game.py @@ -15,40 +15,72 @@ 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: @@ -65,9 +97,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.""" @@ -77,9 +115,16 @@ class Game(object): if len(seconds) == 1: 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,17 +163,164 @@ 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.""" @@ -203,6 +395,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