This documentation should aid in the comprehsion in this case of this being digested in such a medium, making for a smooth transition and easy pick-up from what is already provided.
- Poetry
- Linux
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3
- Windows
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python3 -
- Or (Python)
python3 -m pip install poetry
- Modulated classes for easy serilization and/or importing as a dependancy.
- For this reason, please resort to doing this rather than calling the
main()
function as a standalone.
- For this reason, please resort to doing this rather than calling the
-
class Game: def __init__(self, teams): self.teams = teams
Declaring local class variables imported from the defintion made on each creation...
self.TEAM_SIZE = len(self.teams[0])
Allowing for the customization of the the
TEAM_SIZE
variable.self.innings = 9
Setting the default innings to nine as this parameter should not generally be changed...
self.hits = ["singles", "doubles", "triples", "homes"]
Defining types of hits that could be found; creating a dictionary.
self.flip = lambda: random.choice([True, False]) self.chance = lambda p: (False, True)[random.random() < p]
Creation of two anonymous lambda function to caculate chance; later to be used in the generation of the statistics.
for i in range(len(self.teams)): if len(self.teams[i]) != self.TEAM_SIZE: print("Please input a teams array with the correct amount of players!") exit(1)
Checking the each team has the correct amount of players, using the given
TEAM_SIZE
variable.def dump(self, statType): makeshift, out = [], []
Declaring two temporary arrays to be used in the processing of the
teams
array.exec_string = "player_list.append(self.teams[{}][{}].{})" for i in range(len(self.teams)): player_list = []
Cycling through the
teams
array with thei
index. Templateexec_string
to specifically grab a certain property from eachplayer
.for j in range(len(self.teams[i])): run_str = exec_string.format(i, j, statType) exec(run_str) makeshift.append(player_list)
Cycling through each
player
in theteams
array with thej
index. Eachplayer
with have the input string,statType
extracted from them, appended to themakeshift
array.(Comment: Please don't hurt me for using exec() in 2021. I just did it for the high amounts of customizability and extensibility. Much luv <3)
return makeshift
Returning the concatenated array of
player
properties on a per-team basis.def play(self): for i in range(0, self.innings + 1):
Very basic cycle to replicate the effect of running through the pre-determined amount of
innings
.outs = 0 while outs < 6: if self.flip(): self.hit()
Running our class's
hit()
function on the probabilty of a coin-flip. This is how we replicate a real ball-game.else: outs += 1
The simple other end of the flip, causing the outs of the given hitter to be incremented.
def hit(self): for i in range(len(self.teams)): hitType = random.choice(self.hits)
If called from the
play()
function, a randomhitType
will be assigned while cycling throughteams
.scored = { "singles": self.chance(0.10), "doubles": self.chance(0.20), "triples": self.chance(0.50), "homes": True } random.choice(self.teams[i]).recordHit(hitType, scored[hitType])
With each
hitType
that is defined, a proportional chance of scoring is assigned, then randomly given to a player on the given team ini
. -
class Player: def __init__(self, fName, lName):
New class declaration taking in a
fName
andlName
to build thePlayer
persona.self.hits = { "singles": 0, "doubles": 0, "triples": 0, "homes": 0 }
The definition of
hits
that aPlayer
can make, stored for each individual instance of ateams[i]
.self.runs = { "singles": 0, "doubles": 0, "triples": 0, "homes": 0 }
While ignoring the fact this could be done in a single dictionary with corrisponding nested properties, we will say this repetition is for the sake of verbosity and readability.
self.total = { "hits": 0, "runs": 0 }
Once again an instanciation of a
total
dictiory for the simple purpose of easy-access for future statistics manipulation.self.percentage = 0 self.update()
Declaring easy-access to a
percentage
variable, and calling the built-inupdate()
function on __init__.def recordHit(self, hitType, runnerScored): self.hits[hitType] += 1
Taking in two parameters,
hitType
&runnerScored
, incrementing ourhits[...]
with thehitType
parameter.if runnerScored: self.runs[hitType] += 1 self.update()
Incrementing if was scored; then updating the variables with the
update()
function.def scoringPercentage(self): if self.total["runs"] > 0: return round((self.total["runs"] / self.total["hits"]) * 100, 2)
Calculating the
runs/hits
percentage if any runs exist; rounding to the second decimal place.else: return "N/A"
Having a default of
"N/A"
to return in the case of noruns
recorded.def update(self): hitNum, runNum = 0, 0
The infamous
update()
function, declaringhitNum
andrunNum
right off the bat, later to be used for temporary logging.for value in self.hits.values(): hitNum += value for value in self.runs.values(): runNum += value
Running through the value pairs in both variables, logging
hits
andruns
, being loaded into our temp variables.self.total["hits"] = hitNum self.total["runs"] = runNum self.percentage = self.scoringPercentage() return 0
Finally updating our
total
dictionary with the recurred values. Calculating percentage and rolling the most future calculation in as well, and returning a value of0
to signfiy a successful run as for the context it being used in.
- Now this is where "functional" elements begin to faulter, even though working on both a
win32
andlinux
distrobution, tests onOSX
are still limited and might break if not carefully ported.- Once again, because of this, I do recommed the basic useage of the framework layed out, specifically the structure and concept, rather than actually applications.
- For this last section once again, and a common theme throughout this whole project, is the lack of good-ness. Don't take this seriously, and with that, less in-dept commentary as well ;)
-
teams = [] TEAM_SIZE = 9 first, last = [], []
Declarations for variables to be used later on, sake of readability.
textfile_directory = os.path.join(os.path.join(os.pardir, os.getcwd()), "txt") if platform != "darwin" else "../txt"
Some python magic allowing good-forsaken things to happen with my directory paths. Essentially evaluates to our wordlist source directory:
../txt
.files = { "first": "firstnames.txt", "last": "lastnames.txt" } for key in files: path = os.path.join(textfile_directory, files[key]) arr = [] with open(path, "r") as f: for lines in f: arr.append(lines.rstrip()) random.shuffle(arr) files[key] = arr[:(TEAM_SIZE * 2)]
Randomizing our wordlists and acredditing it to their proportionate dictionary key/value. Unfortunately having to shuffle before splicing down the array, no bully.
k = 0 for i in range(0, 2): player_list = [] for j in range(TEAM_SIZE): player_list.append(Player(files["first"][k], files["last"][k])) k += 1 teams.append(player_list)
More magic that commenting on at 4AM is most probably not the greatest idea. Not doing it too much credit, but creating the player lists that will eventually get pasted in as the
teams
array. Quite fond of it either way :)game = Game(teams) game.play()
Creating the new
Game
object and starting it with the predefinedteams
array, as mentioned. Simply letting the code handle it all now.table = texttable.Texttable() table.set_cols_align(["c", "c", "c", "c", "c"]) table.add_row(["Last", "First", "Hits", "Runs", "Percentage"]) # f'{round(float(game.dump("percentage")[i][j]), 2)}%' # game.dump("percentage")[i][j] for i in range(len(game.teams)): for j in range(len(game.dump("firstName")[i])): percentage = game.dump("percentage")[i][j] table.add_row([game.dump("lastName")[i][j], game.dump("firstName")[i][j], game.dump("total['hits']")[i][j], game.dump("total['runs']")[i][j], percentage if isinstance(percentage, str) else f'{round(float(percentage), 2)}%']) main = table.draw()
Using the texttable to format output data; take a look at it yourself if your curious, just some basic to semi-advanced use of the library.
table.add_row([game.dump("lastName")[i][j], game.dump("hits['singles']")[i][j], game.dump("hits['doubles']")[i][j], game.dump("hits['triples']")[i][j], game.dump("hits['homes']")[i][j], percentage if isinstance(percentage, str) else f'{round(float(percentage), 2)}%'])
Dissecting one of the lines of code and it's usage with the pre-built function we used earlier
dump()
.As we can see, we use
game.dump(...)
, inputting our property we want to extract and the two indices, representing theteam
andplayer
number.The using of the
table.add_row(...)
, as well while also setting our proportionate columns and centerings.full = table.draw() # print(main) # print(stats) print(full)
Eventually we get to the point that we can print out our table, something in the form:
Disclaimer: If you got to the end of this, congrats. This just being a fun little write-up in markdown, I wrote this in a couple hours on a fresh install of VSCode. If you do, magically, want to install this, simply...
git clone "https://github.com/WizardDaHacker/game-n-score"
...and then...
cd "game-n-score"
poetry install
...and finally to run...
poetry run main