began work on tuning the stat weights for various actions

This commit is contained in:
Sakimori 2022-01-08 19:42:11 -05:00
parent e647a33499
commit de46f85dfe
7 changed files with 222 additions and 20 deletions

View file

@ -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__": if __name__ == "__main__":
#for name in ["Vivi", "Artemis", "Laika", "Sharks", "Dragons", "Melua", "Sabriina", "Jorts (Buttered)", "Jorts (Unbuttered)"]: #for name in ["Vivi", "Artemis", "Laika", "Sharks", "Dragons", "Melua", "Sabriina", "Jorts (Buttered)", "Jorts (Unbuttered)"]:
@ -8,16 +9,57 @@ if __name__ == "__main__":
# print(atr) # print(atr)
# print("----------") # print("----------")
twitter = twitHandler.TwitHandler() atkPlayer = player.Player("Vivi")
if os.path.exists(os.path.join("Data", "lastID.twt")): defPlayer = player.Player("Artemis")
with open(os.path.join("Data", "lastID.twt")) as idFile: for plyr in [atkPlayer, defPlayer]:
lastID = idFile.readline().strip() print(f"{plyr.name}:")
else: for atr in plyr.attributes:
lastID = 0 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()
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()) #twitter.sendTextTweet(player.Player("Amogus").twitterString())

View file

@ -23,6 +23,15 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="attributes.py" /> <Compile Include="attributes.py" />
<Compile Include="game.py">
<SubType>Code</SubType>
</Compile>
<Compile Include="skillContests.py">
<SubType>Code</SubType>
</Compile>
<Compile Include="team.py">
<SubType>Code</SubType>
</Compile>
<Compile Include="twitHandler.py"> <Compile Include="twitHandler.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>

View file

@ -76,13 +76,20 @@ class Attribute:
def normalDis(generator:random.Random, mean:float, stdDev:float, min:float=-math.inf, max:float=math.inf) -> float: def seededNormalDis(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.""" """Generates random number from seeded normal distribution with given mean and standard deviation. Optionally takes min and max values."""
num = generator.gauss(mean, stdDev) num = generator.gauss(mean, stdDev)
while min > num or num > max: while min > num or num > max:
num = generator.gauss(mean, stdDev) num = generator.gauss(mean, stdDev)
return num 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 #Attributes to generate
#["sample name", mean, stdDev, min(optional), max(optional)] #["sample name", mean, stdDev, min(optional), max(optional)]
masterList = [ masterList = [
@ -92,9 +99,9 @@ masterList = [
["Strength", 40, 60, 0, 100], #power of slapshot/body checking (Defense) ["Strength", 40, 60, 0, 100], #power of slapshot/body checking (Defense)
["Dexterity", 50, 25, 10, 100], #power of wrist shot/dekes (Forward) ["Dexterity", 50, 25, 10, 100], #power of wrist shot/dekes (Forward)
["Shot Accuracy", 40, 25, 10, 100], #accuracy on shots (Secondary/All Skaters) ["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) ["Pass Accuracy", 60, 20, 20, 100], #does what you think it does (Defense)
["Wisdom", 40, 50, 0, 100], #takes better shots/makes safer passes (Secondary/All) ["Wisdom", 60, 50, 0, 100], #takes better shots/makes safer passes (Secondary/All)
["Stickhandling", 50, 35, 0, 100], #dekes/checking (Secondary/All) ["Stickhandling", 75, 20, 30, 100], #dekes/checking (Secondary/All) 35-95
["Discipline", 75, 60, 0, 100], #Higher means less penalties ["Discipline", 75, 60, 0, 100], #Higher means less penalties
["Intelligence", 50, 50, 0, 100], #Skill at positioning (as skater or goalie) (Secondary/All) ["Intelligence", 50, 50, 0, 100], #Skill at positioning (as skater or goalie) (Secondary/All)
["Constitution", 60, 20, 0, 100] #Stamina/injury resilience ["Constitution", 60, 20, 0, 100] #Stamina/injury resilience
@ -111,5 +118,5 @@ def attributesFromName(name:str):
generator.seed(name) generator.seed(name)
atrs = [versionNumber] #in case of stat changes, record which number to use atrs = [versionNumber] #in case of stat changes, record which number to use
for template in masterList: for template in masterList:
atrs.append(Attribute(template[0], normalDis(generator, *template[1:]))) atrs.append(Attribute(template[0], seededNormalDis(generator, *template[1:])))
return atrs return atrs

63
game.py Normal file
View file

@ -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

View file

@ -1,4 +1,5 @@
import attributes import attributes
from enum import Enum
class CreationError(Exception): class CreationError(Exception):
pass pass
@ -27,6 +28,12 @@ class Player(object):
send += "\n" send += "\n"
return send 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): def __eq__(self, value):
if isinstance(value, Player): if isinstance(value, Player):
return self.name == value.name return self.name == value.name
@ -42,3 +49,24 @@ class Skater(Player):
class Goalie(Player): class Goalie(Player):
"""A hockey player that *is* a goalie.""" """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

43
skillContests.py Normal file
View file

@ -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
]

10
team.py Normal file
View file

@ -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)