diff --git a/SimHoc.py b/SimHoc.py index 3496e0f..911af7b 100644 --- a/SimHoc.py +++ b/SimHoc.py @@ -1,4 +1,5 @@ -import os, player, tweepy, twitHandler, time +import os, player, tweepy, twitHandler, time, skillContests, random +from attributes import normalDis if __name__ == "__main__": #for name in ["Vivi", "Artemis", "Laika", "Sharks", "Dragons", "Melua", "Sabriina", "Jorts (Buttered)", "Jorts (Unbuttered)"]: @@ -8,16 +9,57 @@ if __name__ == "__main__": # print(atr) # print("----------") - twitter = twitHandler.TwitHandler() - if os.path.exists(os.path.join("Data", "lastID.twt")): - with open(os.path.join("Data", "lastID.twt")) as idFile: - lastID = idFile.readline().strip() - else: - lastID = 0 + atkPlayer = player.Player("Vivi") + defPlayer = player.Player("Artemis") + for plyr in [atkPlayer, defPlayer]: + print(f"{plyr.name}:") + for atr in plyr.attributes: + print(atr) + print("----------") + + + def skillContest(atkPlayer:player.Player, defPlayer:player.Player, params:skillContests.SkillContestParams): + """Contests the two players with the given stats and stat weights. Returns True on offensive success.""" + if params.override is not None: + print(params.override) + else: + atkValue = 0 + defValue = 0 + for attr, weight in params.atkStats: + atkValue += 95 * weight/100 + for attr, weight in params.defStats: + defValue += 35 * weight/100 + + print(f"Attack: {atkValue}") + print(f"Defense:{defValue}") + + success = 0 + total = 5000 + + for i in range(0,5000): + atkRoll = normalDis(atkValue, atkValue/2, 0) + defRoll = normalDis(defValue, defValue/2, 0) + success += (atkRoll-defRoll) > 0 + print(f"Success {round(success/total*100,3)}% of the time.") + + defAction = player.DefAction.Poke + for atkAction in [player.AtkAction.SkateF]: + params = skillContests.SkillContestParams(atkAction, defAction, skillContests.Situations.EvenStrength) + skillContest(atkPlayer, defPlayer, params) + + #twitter = twitHandler.TwitHandler() + #if os.path.exists(os.path.join("Data", "lastID.twt")): + # with open(os.path.join("Data", "lastID.twt")) as idFile: + # lastID = idFile.readline().strip() + #else: + # lastID = 0 + + #while True: + # twitter.scanForMention(lastID) + # time.sleep(30) + # with open(os.path.join("Data", "lastID.twt")) as idFile: + # lastID = idFile.readline().strip() + + + #twitter.sendTextTweet(player.Player("Amogus").twitterString()) - while True: - twitter.scanForMention(lastID) - time.sleep(30) - with open(os.path.join("Data", "lastID.twt")) as idFile: - lastID = idFile.readline().strip() - #twitter.sendTextTweet(player.Player("Amogus").twitterString()) \ No newline at end of file diff --git a/SimHoc.pyproj b/SimHoc.pyproj index e3fa69d..6bd775e 100644 --- a/SimHoc.pyproj +++ b/SimHoc.pyproj @@ -23,6 +23,15 @@ + + Code + + + Code + + + Code + Code diff --git a/attributes.py b/attributes.py index f5f3a37..07b26ed 100644 --- a/attributes.py +++ b/attributes.py @@ -76,13 +76,20 @@ class Attribute: -def normalDis(generator:random.Random, mean:float, stdDev:float, min:float=-math.inf, max:float=math.inf) -> float: - """Generates random number from normal distribution with given mean and standard deviation. Optionally takes min and max values.""" +def seededNormalDis(generator:random.Random, mean:float, stdDev:float, min:float=-math.inf, max:float=math.inf) -> float: + """Generates random number from seeded normal distribution with given mean and standard deviation. Optionally takes min and max values.""" num = generator.gauss(mean, stdDev) while min > num or num > max: num = generator.gauss(mean, stdDev) return num +def normalDis(mean:float, stdDev:float, min:float=-math.inf, max:float=math.inf) -> float: + """Generates random number from normal distribution with given mean and standard deviation. Optionally takes min and max values.""" + num = random.gauss(mean, stdDev) + while min > num or num > max: + num = random.gauss(mean, stdDev) + return num + #Attributes to generate #["sample name", mean, stdDev, min(optional), max(optional)] masterList = [ @@ -92,9 +99,9 @@ masterList = [ ["Strength", 40, 60, 0, 100], #power of slapshot/body checking (Defense) ["Dexterity", 50, 25, 10, 100], #power of wrist shot/dekes (Forward) ["Shot Accuracy", 40, 25, 10, 100], #accuracy on shots (Secondary/All Skaters) - ["Pass Accuracy", 60, 35, 0, 100], #does what you think it does (Defense) - ["Wisdom", 40, 50, 0, 100], #takes better shots/makes safer passes (Secondary/All) - ["Stickhandling", 50, 35, 0, 100], #dekes/checking (Secondary/All) + ["Pass Accuracy", 60, 20, 20, 100], #does what you think it does (Defense) + ["Wisdom", 60, 50, 0, 100], #takes better shots/makes safer passes (Secondary/All) + ["Stickhandling", 75, 20, 30, 100], #dekes/checking (Secondary/All) 35-95 ["Discipline", 75, 60, 0, 100], #Higher means less penalties ["Intelligence", 50, 50, 0, 100], #Skill at positioning (as skater or goalie) (Secondary/All) ["Constitution", 60, 20, 0, 100] #Stamina/injury resilience @@ -111,5 +118,5 @@ def attributesFromName(name:str): generator.seed(name) atrs = [versionNumber] #in case of stat changes, record which number to use for template in masterList: - atrs.append(Attribute(template[0], normalDis(generator, *template[1:]))) + atrs.append(Attribute(template[0], seededNormalDis(generator, *template[1:]))) return atrs \ No newline at end of file diff --git a/game.py b/game.py new file mode 100644 index 0000000..a89b94e --- /dev/null +++ b/game.py @@ -0,0 +1,63 @@ +import random, team, player +from team import Team +from player import Player, AtkAction, DefAction +from skillContests import SkillContestParams, Situations + +class Game(object): + """A game of hockey!""" + + def __init__(self, awayTeam:Team, homeTeam:Team, threes:bool=False): + self.away = awayTeam + self.home = homeTeam + + 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.playerInPossession = None + self.teamInPossession = None + + self.skatersHome = [] #LW LD C RD RW + self.skatersAway = [] + + self.penaltyBoxAway = [] + self.penaltyBoxHome = [] + self.pulledGoalieAway = False + self.pulledGoalieHome = False + + self.period = 1 + self.clock = 60*20 #clock will be given in seconds + + self.eventLog = [] + + def defendingTeam(self): + if teamInPossession == self.home: + return self.away + else: + return self.home + + def homeAttacking(self): + return teamInPossession == self.home + + 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) * weight/100 + for attr, weight in params.defStats: + defValue += defPlayer.getAttribute(attr) * weight/100 \ No newline at end of file diff --git a/player.py b/player.py index 6385ec4..4ce9fa8 100644 --- a/player.py +++ b/player.py @@ -1,4 +1,5 @@ import attributes +from enum import Enum class CreationError(Exception): pass @@ -27,6 +28,12 @@ class Player(object): send += "\n" return send + def getAttribute(self, shortname:str): + for attr in self.attributes: + if attr.name.lower().startswith(shortname.lower()): + return attr.value + return None + def __eq__(self, value): if isinstance(value, Player): return self.name == value.name @@ -41,4 +48,25 @@ class Skater(Player): class Goalie(Player): - """A hockey player that *is* a goalie.""" \ No newline at end of file + """A hockey player that *is* a goalie.""" + +class AtkAction(Enum): + SkateB = 0 + SkateF = 1 + SkateT = 2 + SkateA = 3 + PassS = 4 + PassF = 5 + PassB = 6 + Dump = 7 + ShotS = 8 + ShotW = 9 + +class DefAction(Enum): + Steal = 0 + Poke = 1 + BlockLn = 2 + Body = 3 + Force = 4 + Pin = 5 + BlockSlot = 6 \ No newline at end of file diff --git a/skillContests.py b/skillContests.py new file mode 100644 index 0000000..2b2e6c2 --- /dev/null +++ b/skillContests.py @@ -0,0 +1,43 @@ +from player import AtkAction, DefAction +from enum import Enum + +class Situations(Enum): + EvenStrength = 0 + PP1 = 1 #4v3, 5v4, or 6v5 + PP2 = 2 #5v3 or 6v4 + SH1 = -1 #3v4, 4v5, 5v6 + SH2 = -2 #3v5 or 4v6 + +class SkillContestParams(object): + """Basic structure for contests of skill.""" + atkStats = [] + defStats = [] + override = None + + def __init__(self, atkAction:AtkAction, defAction:DefAction, situation:Situations): + """Determines which skills to test, and how strongly.""" + if situation == Situations.EvenStrength: + result = evenTable[atkAction.value][defAction.value] + if isinstance(result, bool): + self.override = result + return + self.atkStats = result[0] + self.defStats = result[1] + + + + +#Bool overrides, or [List of (stat,weight) , List of (stat,weight)] +evenTable = [ + #Steal #Poke #block lane #Body check #Force off puck #Pin to wall #Body block + [[[('Sti',100)],[('Sti',20)]], [[('Dex',75),('Sti',35)],[('Sti',40)]], True, [[('Agi',110)],[('Spe',100)]],[[('Agi',110)],[('Str',100)]], [[('Agi',110)],[('Str',100)]], True], #Skate back + [[[('Sti',100)],[('Sti',40)]], [[('Dex',50),('Sti',30)],[('Sti',100)]], True, [[('Str',100)],[('Str',100)]],[[('Spe',60),('Str',40)],[('Str',100)]],[[('Spe',60),('Str',40)],[('Str',100)]],True], #Skate forward + [[[('Dex',75),('Sti',25)],[('Sti',50)]], [[('Dex',70),('Sti',30)],[('Sti',100)]], [[('Ref',50),('Sti',50)],[('Sti',100)]],[[('Dex',100)],[('Str',100)]],True, True, [[('Sti',100)],[('Ref',100)]]], #Skate through + [[[('Sti',100)],[('Sti',30)]], [[('Dex',70),('Sti',30)],[('Sti',100)]], True, [[('Agi',100)],[('Str',100)]],[[('Sti',100)],[('Str',100)]], [[('Sti',100)],[('Str',100)]], True], #Skate around + [True, [[('Pas',100)],[('Sti',100)]], [[('Pas',100)],[('Ref',100)]], True, [[('Str',80),('Pas',20)],[('Str',100)]],[[('Str',80),('Pas',20)],[('Str',100)]],True], #Stretch pass + [[[('Pas',100)],[('Ref',20),('Sti',40)]],[[('Pas',100)],[('Sti',100)]], [[('Pas',100)],[('Ref',100)]], True, [[('Str',100)],[('Str',100)]], [[('Str',100)],[('Str',100)]], [[('Pas',100)],[('Ref',100)]]], #Forward pass + [[[('Pas',100)],[('Int',20)]], True, True, True, True, [[('Sti',100)],[('Str',100)]], True], #Backward pass + [True, True, [[('Wis',100)],[('Ref',100)]], True, True, True, True], #Dump/ice + [False, False, True, False, True, False, [[('Sho',100)],[('Ref',70),('Int',30)]]], #Slapshot + [[[('Dex',100)],[('Sti',40)]], [[('Dex',100)],[('Sti',100)]], [[('Sho',100)],[('Ref',100)]], [[('Dex',100)],[('Str',100)]],[[('Dex',100)],[('Str',100)]], [[('Dex',100)],[('Str',100)]], True] #Wrist shot +] \ No newline at end of file diff --git a/team.py b/team.py new file mode 100644 index 0000000..9d76f41 --- /dev/null +++ b/team.py @@ -0,0 +1,10 @@ +import player +from random import sample + +class Team(object): + """A team of either 6 or 10 skaters and 1-3 goalies.""" + roster = [] #ordered, first line then second line; (#, name) + goalies = [] # (#, name) + + def chooseGoalie(self): + return sample(goalies,1) \ No newline at end of file