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