import random, team, player, os, math from team import Team from player import Player, AtkAction, DefAction, Skater, Goalie from skillContests import SkillContestParams, Situations from attributes import normalDis from hocUtils import RinkGraph from enum import Enum import networkx as nx RINKDIRPATH = os.path.join("Rinks","Graphs") DEFAULTRINKFILENAME = "defaultedges.nx" class Game(object): """A game of hockey!""" def __init__(self, awayTeam:Team, homeTeam:Team, threes:bool=False): if awayTeam is not None and homeTeam is not None: self.away = awayTeam self.home = homeTeam self.awayZones = RinkGraph(edgeFilename=DEFAULTRINKFILENAME) self.homeZones = RinkGraph(edgeFilename=DEFAULTRINKFILENAME) self.currentZone = None self.faceoffSpot = FaceoffDot.Center self.playStopped = True 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.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.penaltyBoxAway = [] self.penaltyBoxHome = [] self.pulledGoalieAway = False self.pulledGoalieHome = False self.period = 1 self.clock = 60*20 #Time remaining in period, given in seconds self.eventLog = [] def defendingTeam(self): if self.teamInPossession == self.home: return self.away else: return self.home def attackingTeam(self): """Alias for teamInPossession, to match defendingTeam()""" return self.teamInPossession def homeAttacking(self): return self.teamInPossession == self.home def skatersInPossession(self): return self.skatersHome if self.homeAttacking() else self.skatersAway def clockToMinutesSeconds(self): """Returns a string MM:SS elapsed in period.""" elapsedSeconds = 60*20 - self.clock if self.clock >= 0 else 60*20 minutes = str(int(math.modf(elapsedSeconds/60)[1])) seconds = str(elapsedSeconds % 60) if len(seconds) == 1: seconds = f"0{seconds}" return f"{minutes}:{seconds}" def currentSituation(self): skatersH = self.lineSize + self.pulledGoalieHome - len(self.penaltyBoxHome) skatersA = self.lineSize + self.pulledGoalieAway - len(self.penaltyBoxAway) if self.teamInPossession == self.home: return Situations(skatersH - skatersA) else: return Situations(skatersA - skatersH) def skillContest(self, atkPlayer:Player, defPlayer:Player, params:SkillContestParams): """Contests the two players with the given stats and stat weights. Returns True on offensive success.""" if params.override is not None: return params.override else: atkValue = 0 defValue = 0 for attr, weight in params.atkStats: atkValue += atkPlayer.getAttribute(attr).value * weight/100 for attr, weight in params.defStats: defValue += defPlayer.getAttribute(attr).value * weight/100 atkRoll = normalDis(atkValue, atkValue/2, 0) defRoll = normalDis(defValue, defValue/2, 0) return atkRoll-defRoll > 0 def faceoff(self, awayPlayer:Skater, homePlayer:Skater): """Hold a faceoff! True indicates home win, False is away win.""" faceoffSkills = [('Dex',50),('Sti',50),('Pas', 10)] params = SkillContestParams(faceoffSkills, faceoffSkills) return self.skillContest(homePlayer, awayPlayer, params) if random.random() > 0.4 else random.sample([True, False], 1)[0] def event(self): """Meat and potatoes. Everything that happens is a direct result of this being called.""" if self.playStopped: #need a faceoff self.teamInPossession = self.home if self.faceoff(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] self.eventLog.append(f"{self.clockToMinutesSeconds()} - {self.teamInPossession.shortname} {str(winningPlayer)} wins faceoff.") self.clock -= random.randint(2,5) class FaceoffDot(Enum): """All orientations are given from the perspective of the defending team.""" AwayZoneLeft = -4 AwayZoneRight = -3 AwayNeutralLeft = -2 AwayNeutralRight = -1 Center = 0 HomeNeutralRight = 1 HomeNeutralLeft = 2 HomeZoneRight = 3 HomeZoneLeft = 4 class SkaterPosition(Enum): """Allows easy indexing to the active skaters lists for each team.""" LW = 0 LD = 1 C = 2 RD = 3 RW = 4 EA = 5