From cc0487469aa0bc8b25c005e71f16e544d2e29c17 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 9 Jan 2022 16:11:34 -0500 Subject: [PATCH] Implemented basic action matrix, set weights to initial values --- SimHoc.py | 40 ++-------------------- SimHoc.pyproj | 3 ++ attributes.py | 18 ++++++++-- game.py | 46 ++++++++++++++----------- hocTests.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ player.py | 19 ++++++++--- skillContests.py | 22 ++++++------ 7 files changed, 161 insertions(+), 75 deletions(-) create mode 100644 hocTests.py diff --git a/SimHoc.py b/SimHoc.py index 911af7b..6358ea6 100644 --- a/SimHoc.py +++ b/SimHoc.py @@ -1,5 +1,6 @@ import os, player, tweepy, twitHandler, time, skillContests, random from attributes import normalDis +from hocTests import AttributeTest if __name__ == "__main__": #for name in ["Vivi", "Artemis", "Laika", "Sharks", "Dragons", "Melua", "Sabriina", "Jorts (Buttered)", "Jorts (Unbuttered)"]: @@ -9,43 +10,8 @@ if __name__ == "__main__": # print(atr) # print("----------") - 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) + test = AttributeTest() + test.allTests() #twitter = twitHandler.TwitHandler() #if os.path.exists(os.path.join("Data", "lastID.twt")): diff --git a/SimHoc.pyproj b/SimHoc.pyproj index 6bd775e..417a52b 100644 --- a/SimHoc.pyproj +++ b/SimHoc.pyproj @@ -26,6 +26,9 @@ Code + + Code + Code diff --git a/attributes.py b/attributes.py index 07b26ed..6118448 100644 --- a/attributes.py +++ b/attributes.py @@ -27,7 +27,7 @@ class Attribute: return f"{valueString} - {self.name}" def __str__(self): - return f"{self.name}: {int(self.value)}" + return f"{self.name} - {int(self.value)}" #Comparison helpers def __eq__(self, other): #can compare attributes by name, or find attribute by value @@ -95,7 +95,7 @@ def normalDis(mean:float, stdDev:float, min:float=-math.inf, max:float=math.inf) masterList = [ ["Speed", 60, 25, 0, 100], #sprint speed (Forward) ["Agility", 50, 35, 0, 100], #changing directions/goaltending slide (Goalie) - ["Reflexes", 50, 35, 0, 100], #deflections/goaltending blocks/intercepting passes (Goalie) + ["Reflexes", 50, 35, 20, 100], #deflections/goaltending blocks/intercepting passes (Goalie) ["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) @@ -119,4 +119,16 @@ def attributesFromName(name:str): atrs = [versionNumber] #in case of stat changes, record which number to use for template in masterList: atrs.append(Attribute(template[0], seededNormalDis(generator, *template[1:]))) - return atrs \ No newline at end of file + return atrs + +def singleAttribute(shortname:str): + """Generates a tuple of (name,value) of an attribute with given shortname""" + for atr in masterList: + if atr[0].lower().startswith(shortname.lower()): + return (atr[0], normalDis(*atr[1:])) + +def attributeMinMax(shortname:str): + """Retrieves a single attribute's minimum and maximum values. Returns (min, max).""" + for atr in masterList: + if atr[0].lower().startswith(shortname.lower()): + return (atr[3], atr[4]) \ No newline at end of file diff --git a/game.py b/game.py index a89b94e..5634ecf 100644 --- a/game.py +++ b/game.py @@ -2,36 +2,38 @@ import random, team, player from team import Team from player import Player, AtkAction, DefAction from skillContests import SkillContestParams, Situations +from attributes import normalDis class Game(object): """A game of hockey!""" def __init__(self, awayTeam:Team, homeTeam:Team, threes:bool=False): - self.away = awayTeam - self.home = homeTeam + if awayTeam is not None and homeTeam is not None: + 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.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.goalieHome = self.home.chooseGoalie() + self.goalieAway = self.away.chooseGoalie() - self.playerInPossession = None - self.teamInPossession = None + self.playerInPossession = None + self.teamInPossession = None - self.skatersHome = [] #LW LD C RD RW - self.skatersAway = [] + self.skatersHome = [] #LW LD C RD RW + self.skatersAway = [] - self.penaltyBoxAway = [] - self.penaltyBoxHome = [] - self.pulledGoalieAway = False - self.pulledGoalieHome = False + self.penaltyBoxAway = [] + self.penaltyBoxHome = [] + self.pulledGoalieAway = False + self.pulledGoalieHome = False - self.period = 1 - self.clock = 60*20 #clock will be given in seconds + self.period = 1 + self.clock = 60*20 #clock will be given in seconds - self.eventLog = [] + self.eventLog = [] def defendingTeam(self): if teamInPossession == self.home: @@ -58,6 +60,10 @@ class Game(object): atkValue = 0 defValue = 0 for attr, weight in params.atkStats: - atkValue += atkPlayer.getAttribute(attr) * weight/100 + atkValue += atkPlayer.getAttribute(attr).value * weight/100 for attr, weight in params.defStats: - defValue += defPlayer.getAttribute(attr) * weight/100 \ No newline at end of file + defValue += defPlayer.getAttribute(attr).value * weight/100 + + atkRoll = normalDis(atkValue, atkValue/2, 0) + defRoll = normalDis(defValue, defValue/2, 0) + return atkRoll-defRoll > 0 \ No newline at end of file diff --git a/hocTests.py b/hocTests.py new file mode 100644 index 0000000..06ddc82 --- /dev/null +++ b/hocTests.py @@ -0,0 +1,88 @@ +import skillContests, player, team, game, attributes + +class AttributeTest(object): + def __init__(self): + self.atkAction = player.AtkAction.ShotW + self.defAction = player.DefAction.BlockSlot + + self.atkPlayer = player.Player(None) + self.defPlayer = player.Player(None) + + self.fakeGame = game.Game(None, None) + + self.params = skillContests.SkillContestParams(self.atkAction, self.defAction) + + def lowStats(self): + """Tests attacker and defender with minimum stats.""" + for i in [0,1]: + setPlayer = [self.atkPlayer, self.defPlayer][i] + statSet = [self.params.atkStats, self.params.defStats][i] + for shortname, weight in statSet: + longname, value = attributes.singleAttribute(shortname) + setPlayer.setAttribute(longname, attributes.attributeMinMax(longname)[0]+5) + self.getAvg("mutual minimum stats") + + def lowAtkHighDef(self): + for shortname, weight in self.params.atkStats: + longname, value = attributes.singleAttribute(shortname) + self.atkPlayer.setAttribute(longname, attributes.attributeMinMax(longname)[0]+5) + for shortname, weight in self.params.defStats: + longname, value = attributes.singleAttribute(shortname) + self.defPlayer.setAttribute(longname, attributes.attributeMinMax(longname)[1]-5) + self.getAvg("bad attack, good defence") + + def highAtkLowDef(self): + for shortname, weight in self.params.atkStats: + longname, value = attributes.singleAttribute(shortname) + self.atkPlayer.setAttribute(longname, attributes.attributeMinMax(longname)[1]-5) + for shortname, weight in self.params.defStats: + longname, value = attributes.singleAttribute(shortname) + self.defPlayer.setAttribute(longname, attributes.attributeMinMax(longname)[0]+5) + self.getAvg("good attack, bad defence") + + def highStats(self): + for i in [0,1]: + setPlayer = [self.atkPlayer, self.defPlayer][i] + statSet = [self.params.atkStats, self.params.defStats][i] + for shortname, weight in statSet: + longname, value = attributes.singleAttribute(shortname) + setPlayer.setAttribute(longname, attributes.attributeMinMax(longname)[1]-5) + self.getAvg("mutual maximum stats") + + def randomStats(self): + success = 0 + total = 0 + for i in range(0, 5000): + for shortname, weight in self.params.atkStats: + self.atkPlayer.setAttribute(*attributes.singleAttribute(shortname)) + for shortname, weight in self.params.defStats: + self.defPlayer.setAttribute(*attributes.singleAttribute(shortname)) + total += 1 + success += self.fakeGame.skillContest(self.atkPlayer, self.defPlayer, self.params) + print(f"Testing random stats...") + print(f"Success rate: {str(round(success/total*100,2))}%") + print("-------") + + + def allTests(self): + self.lowStats() + self.lowAtkHighDef() + self.highAtkLowDef() + self.highStats() + self.randomStats() + + def getAvg(self, testName:str): + success = 0 + total = 0 + for i in range(0, 5000): + total += 1 + success += self.fakeGame.skillContest(self.atkPlayer, self.defPlayer, self.params) + print(f"Testing {testName}...") + print("Attacker stat values:") + for attr, w in self.params.atkStats: + print(self.atkPlayer.getAttribute(attr)) + print("Defender stat values:") + for attr, w in self.params.defStats: + print(self.defPlayer.getAttribute(attr)) + print(f"Success rate: {str(round(success/total*100,2))}%") + print("-------") \ No newline at end of file diff --git a/player.py b/player.py index 4ce9fa8..0da3156 100644 --- a/player.py +++ b/player.py @@ -8,10 +8,13 @@ class Player(object): """A hockey player with attributes and various functions.""" def __init__(self, name:str): - if len(name) > 30: + if name is None: + self.attributes = [] + elif len(name) > 30: raise CreationError("Player name too long.") - self.name = name - self.attributes = self.loadAttributes() + else: + self.name = name + self.attributes = self.loadAttributes() def loadAttributes(self): """Generates attributes based on name, or loads from database if present.""" @@ -29,11 +32,19 @@ class Player(object): return send def getAttribute(self, shortname:str): + """Returns an Attribute object with given shortname.""" for attr in self.attributes: if attr.name.lower().startswith(shortname.lower()): - return attr.value + return attr return None + def setAttribute(self, shortname:str, value:float): + for attr in self.attributes: + if attr.name.lower().startswith(shortname.lower()): + attr.value = value + return True + self.attributes.append(attributes.Attribute(attributes.singleAttribute(shortname)[0],value)) + def __eq__(self, value): if isinstance(value, Player): return self.name == value.name diff --git a/skillContests.py b/skillContests.py index 2b2e6c2..0762900 100644 --- a/skillContests.py +++ b/skillContests.py @@ -14,7 +14,7 @@ class SkillContestParams(object): defStats = [] override = None - def __init__(self, atkAction:AtkAction, defAction:DefAction, situation:Situations): + def __init__(self, atkAction:AtkAction, defAction:DefAction, situation:Situations=Situations.EvenStrength): """Determines which skills to test, and how strongly.""" if situation == Situations.EvenStrength: result = evenTable[atkAction.value][defAction.value] @@ -30,14 +30,14 @@ class SkillContestParams(object): #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 + [[[('Sti',100)],[('Sti',20)]], [[('Dex',110),('Sti',35)],[('Sti',30)]], True, [[('Agi',11)],[('Spe',8)]], [[('Agi',20)],[('Str',8)]], [[('Agi',20)],[('Str',10)]], True], #Skate back + [[[('Sti',100)],[('Sti',40)]], [[('Dex',70),('Sti',30)],[('Sti',100)]], True, [[('Str',80)],[('Str',100)]], [[('Spe',60),('Str',40)],[('Str',80)]], [[('Spe',60),('Str',40)],[('Str',100)]],True], #Skate forward + [[[('Dex',75),('Sti',25)],[('Sti',50)]], [[('Dex',50),('Sti',30)],[('Sti',100)]], [[('Ref',20),('Sti',60)],[('Sti',100)]],[[('Dex',70)],[('Str',100)]], [[('Spe',60),('Str',40)],[('Str',120)]],True, [[('Sti',1)],[('Ref',3)]]], #Skate through + [[[('Sti',100)],[('Sti',40)]], [[('Dex',70),('Sti',30)],[('Sti',100)]], True, [[('Agi',100)],[('Str',100)]],[[('Sti',100)],[('Str',100)]], [[('Sti',100)],[('Str',110)]], True], #Skate around + [True, [[('Pas',70)],[('Sti',30)]], [[('Pas',80)],[('Ref',110)]], True, [[('Pas',100)],[('Str',80)]], [[('Pas',100)],[('Str',90)]], True], #Stretch pass + [[[('Pas',100)],[('Ref',40),('Sti',30)]],[[('Pas',70)],[('Sti',30)]], [[('Pas',80)],[('Ref',110)]], True, [[('Pas',100)],[('Str',80)]], [[('Pas',100)],[('Str',100)]], [[('Pas',1)],[('Ref',3)]]], #Forward pass + [[[('Pas',100)],[('Int',30)]], True, True, True, True, [[('Sti',100)],[('Str',100)]], True], #Backward pass + [True, True, [[('Wis',100)],[('Ref',20)]], True, True, True, True], #Dump/ice + [False, False, True, False, True, False, [[('Sho',120)],[('Ref',70),('Int',30)]]], #Slapshot + [[[('Dex',120)],[('Sti',40)]], [[('Dex',100)],[('Sti',40)]], [[('Sho',50)],[('Ref',25)]], [[('Dex',80)],[('Str',60)]], [[('Dex',100)],[('Str',90)]], [[('Dex',100)],[('Str',100)]], [[('Sho',1)],[('Ref',2),('Int',1)]]] #Wrist shot ] \ No newline at end of file