View Full Version : Groundhog learns Python - Road to a Basketball Text Sim
Groundhog
07-20-2014, 08:32 PM
For the longest time I’ve wanted to learn a programming language. Years ago I played around a little with VB6, but I struggled for a few reasons – the biggest was probably the fact that the ‘visual’ nature of VB6 had me so worried about creating pretty forms that I didn’t spend enough time actually learning the fundamental concepts of programming languages. This time around I’ve gone with Python as it’s free and has a lot of learning resources online.
And what better way to teach myself a language than to try and create a complex sports sim, right??? :D
Although I have a grand/maybe-unrealistic vision of a web-based, multiplayer-focused basketball simulator, initially I’m looking to just put together a piece of code that simulates a game of basketball between two teams. Before that though, there’s the small matter of learning how to program!
This thread will chroncile my attempts to learn Python, as well as my attempts to convert that knowledge into the practical side of making a basketball sim.
Groundhog
07-20-2014, 10:06 PM
The first resource I picked up was 'Learn Python in 24 Hours' (http://www.amazon.com/Python-Hours-Teach-Yourself-Edition/dp/0672336871). I made it through the basics - variables, variable types, performing calculations, if/elif/else statements - with ease. I remembered enough from my brief VB6 attempts to make these early chapters more of a lesson in Python-specific syntax.
Lists/dictionaries/tuples were next and, while also quite basic, keeping my basketball sim in mind these represented the first real useful bits of knowledge re: how I envision my sim would work. Of course, every time I think that, I go on to discover a better way to do things, but that's the whole point of starting from the basics!
I had wondered about the whole issue of pulling in user/team/coach info and ratings and assigning them to variables that could be used in PbP/functions etc. It seems so basic, but until I'd seen dictionaries I didn't know how I might achieve that.
At this point, it seems to make sense to have a dictionary created for each team, with a unique ID for each player as the key, and then a list attached to that key holding the various ratings etc.
I have no idea at this stage how I would go about pulling in that information from a database or file and building the dictionaries automatically, but it's a start!
Groundhog
07-20-2014, 10:22 PM
While and For Loops mark the next useful piece of information as far as envisioning a sim-type program, as opposed to a "run a bunch of lines and exit" type programs I'd been looking at in the text book examples.
I remembered loops from VB6, and prior to the reading the chapter I knew they'd be fundamental to the sim. I imagine the main "engine" of the sim would be a loop that first reads the various game variables like time on the clock, which quarter it is, type of possession (jump ball, steal in the back court, etc.), etc. - probably many more. Based on these variables the sim would launch a function or maybe even another nested loop to generate a possession, or if the game is over, to end the loop and output results/stats etc.
I don't want to get too stuck on any specific method at this point though as I still have a lot to learn.
It's at also at this point that I'm beginning to find the Sams book less useful. Brevity and lots of code examples is the books chosen method, and although it's still easy to follow at this stage, we're about to delve into some core object-orientated programming concepts. For someone with no real exposure to this previously (like me), more detail would work better.
Groundhog
07-21-2014, 12:46 AM
After a couple of hours I theory, I like to give myself a break and try and create something based on what I know, to see if I really understand what I've been reading. It's one thing that make a quick little program that calculates the average of a range of numbers, but trying to apply what you've learnt to a real problem is much more useful.
So, the opening jumpball. This should be the single easiest aspect of the sim as far as on-court stuff goes. You have a situation where two players compete to gain possession of the ball to begin the game. There are no pre-play conditions to take into account other than who is jumping from each team and that it's the first possession of the game.
First Problem: Who is in the jump?
Most sims have this as being the C position for both teams. Often that is the case IRL, but not always. From what I observe, it's typically the best of the PFs and Cs, rarely other positions. There are exceptions to this, but it does not seem to be a situation that coaches strategise over like other aspects of the game, for a number of reasons. I'd like my sim to pick the most suitable jumper from the starting PFs and Cs.
Second Problem: Ratings?
Simply, height and jumping ability. I like a FM-style 1-20 rating system as I think it gives enough spread to cover lower-grade competitions right up to world-class. Although a 1-100 system is most common, I've always found it too granular - playing a guy with an 84 in three point ability over a guy with 81 just feels too spreadsheety to me. 1-20 also ties with the "d20" type system I have been toying with to determine contests between individuals - like hustle rebounds, charges vs blocks on drives, etc.
I had considered something like 'reach' as a rating that would be a modifier for various actions in the game, with a 10/20 basically meaning normal wingspan for a player's height. It's something that I'd probably include in the "final product".
Theory
First step is to work out how good the PFs and Cs of each team are at jumpballs. The players heights and jumping ability are added together (heights are stored as integers that treat 0 foot 1 inch as "1", and then count upwards, so 6'6 would be 78). The higher of the two frontcourt players for each team is deemed the best jumper, and variables are populated with their name and ratings to be used in the calculation for the tip. If it's a tie, it defaults to the C position.
Sticking with the d20 idea, the actual jumpball competition would be a random d20 result with the difference between the two players' height+jump ratings used as a modifier for that roll. Winning team gains possession, and on a tie, a random result is chosen. I'd like it to be clear in the PbP for all actions like this how the roll went - ie. if it's a tie, the text should be different to indicate a close result. Not as critical in a jumpball, but in other situations I think it would add some flavour to the sim.
Up next: the code...
Groundhog
07-21-2014, 07:18 AM
...or not.
I had a lot of trouble pulling individual list items out of dictionaries - it just didn't seem to be the right way to go about it. I found some info online, but for something that must be a very common part of most programs, there had to be an easier way to do this.
Instead of cheating and creating individual variables for each rating, I would instead read on to the next chapter and find my solution: the mystical realm of "classes".
This is where the book really began to fail me. I read the chapter multiple times but I still didn't feel like I had a grasp on what a class actually is. What separates it from a function? It seems obvious now, but it took a few google searches for me to grasp the concept, and suddenly everything became much clearer.
THIS is how you would store attributes for a player and then recall them as required. Although I don't yet know how, it also seems like it would be quite simple to pull these attributes from a database or file.
So I created a simple class that would be used to store player information:
class Player(object):
def __init__(self, playerid, name, height, jump):
self.playerid = playerid
self.name = name
self.height = height
self.jump = jump
This allows me to re-use this class as I keep going and add more attributes as required.
I then create a class to pull in the team information too - not really needed for this, but seemed simple enough and would make a neat little touch to the play-by-play:
class Team(object):
def __init__(self, location, city, name, record, arena):
self.location = location
self.city = city
self.name = name
self.record = record
self.arena = arena
Then I populate the classes with player and team attributes:
HomeTeam = Team(1, "Cleveland", "Cavaliers", "25-11", "Quicken Loans Arena")
AwayTeam = Team(2, "Charlotte", "Hornets", "17-16", "Time Warner Cable Arena")
PlayerHome5 = Player(1, "A. Varejao", 70, 10)
PlayerHome4 = Player(2, "T. Thompson", 68, 12)
PlayerAway5 = Player(14, "A. Jefferson", 70, 7)
PlayerAway4 = Player(15, "N. Vonleh", 69, 13)
This allows me to easily call any attribute by using PlayerAway5.height for example to then pass these values on to functions etc.
Groundhog
07-21-2014, 07:37 AM
My next problem was something called scope - which I am still getting my head around. I wrote a function that calculated the top jumper for each side and assigned it to a variable. The problem was that, after the function finishes, that variable is lost to me and I couldn't access it again.
One way around it was to use the 'global' statement as I did below. I was positive that that was not the best way to do this. I don't know a great deal about OOP, but I know that keeping objects separate and independent of each other is the fundamental 'best practice', and I think this approach violates that. Then I discovered the 'return' statement, which seems the better workaround.
I apologize in advance to anyone with any kind of programming experience as I'm sure the following is probably the least efficient way possible to generate these calculations:
def TopJumper(home_5_jump, home_5_hgt, home_4_jump, home_4_hgt, away_5_jump,
away_5_hgt, away_4_jump, away_4_hgt):
home_5_jump_abl = (home_5_jump + home_5_hgt)
home_4_jump_abl = (home_4_jump + home_4_hgt)
away_5_jump_abl = (away_5_jump + away_5_hgt)
away_4_jump_abl = (away_4_jump + away_4_hgt)
#global hometopjumper
#global awaytopjumper
if home_5_jump_abl > home_4_jump_abl:
hometopjumper = "PlayerHome5"
elif home_4_jump_abl > home_5_jump_abl:
hometopjumper = "PlayerHome4"
else:
hometopjumper = "PlayerHome5"
if away_5_jump_abl > away_4_jump_abl:
awaytopjumper = "PlayerAway5"
elif away_4_jump_abl > away_5_jump_abl:
awaytopjumper = "PlayerAway4"
else:
awaytopjumper = "PlayerAway5"
return hometopjumper, awaytopjumper
I then run the function, feeding into it the attributes stored in my player classes earlier, which gives me a variable named TopJumperResults with the best jumper for the home and road team:
TopJumperResults = TopJumper(PlayerHome5.jump, PlayerHome5.height,
PlayerHome4.jump, PlayerHome4.height,
PlayerAway5.jump, PlayerAway5.height,
PlayerAway4.jump, PlayerAway4.height)
Now, to have them square off...
SirBlurton
07-21-2014, 12:26 PM
Just wanted to chime in and say I'll be watching with great interest....a little embarrassed that you've seemingly done in one day what's taken me years to learn!
I am an absolute beginner in python, so take this with a grain of salt - but what I've done in some of my tinkering has been to collect players in a list (ie-homeTeam contains all of the player objects for the home team).
Just makes it a bit cleaner when managing the function call.
For example:
def TopJumper(homeTeam,awayTeam)...
You could then loop through those lists to find the players with the best jumping ability.
Anyway, very cool to see this on the forum! Good luck!
GoldenEagle
07-21-2014, 01:24 PM
Just wanted to chime in and say I'll be watching with great interest....a little embarrassed that you've seemingly done in one day what's taken me years to learn!
I am an absolute beginner in python, so take this with a grain of salt - but what I've done in some of my tinkering has been to collect players in a list (ie-homeTeam contains all of the player objects for the home team).
Just makes it a bit cleaner when managing the function call.
For example:
def TopJumper(homeTeam,awayTeam)...
You could then loop through those lists to find the players with the best jumping ability.
Anyway, very cool to see this on the forum! Good luck!
I haven't looked at Python in quite awhile and I just scanned through the problem but there is almost certainly a faster way to do this. Off the top of my head, you can just call a sort method on the list.
Remember that anytime you use a data structure, someone has already gone through the work of thinking about the fastest way to do things via algorithms.
But your suggestion is excellent to make the code more compact, which is critical for maintaining the code. A good philosophy to remember is DRY. Don't repeat yourself. Anytime you find yourself basically copying and pasting in programming, there is a better way to do it.
Anyways, cool thread. I will try to remember to follow long.
Groundhog
07-21-2014, 08:01 PM
Thanks for the responses guys. The first couple of posts covered a couple of days real time, so I didn't find it quite so simple.
I'll post the final part of what I came up with when I get a chance, but thanks for the tip re: sorts and lists. I'll investigate further!
Haven't ventured over to the dynasties for quite some time, but I'll be following this one closely. Very cool!
Groundhog
07-24-2014, 12:13 AM
Thanks!
I typed up a few posts offline, this weekend I should have some more updates.
korme
07-24-2014, 10:19 AM
Reading along and rooting for you, Groundhog.
Groundhog
07-25-2014, 09:23 PM
I am an absolute beginner in python, so take this with a grain of salt - but what I've done in some of my tinkering has been to collect players in a list (ie-homeTeam contains all of the player objects for the home team).
Just makes it a bit cleaner when managing the function call.
For example:
def TopJumper(homeTeam,awayTeam)...
You could then loop through those lists to find the players with the best jumping ability.
Although I don't know yet how I would sort and loop through lists to automate this, this gave me the idea to fix a problem. What I had above was a function that determined who the best jumper was and returned those values in a variable. To run the actual jumpball I'd need to pass those variables and all the player ratings again to the next function and so on... Passing information between objects is something I need to work on.
What I did was create a list within the Team and Player classes, that builds a list from all the attributes that are stored in the class. I'm sure there's a better way to do this, but for now I manually enter them in as variables. To allow me to iterate through the created list, I had to define another function in the class. Team class is now as follows:
class Team(object):
def __init__(self, location, city, name, record, arena):
self.location = location
self.city = city
self.name = name
self.record = record
self.arena = arena
self.list = [location, city, name, record, arena] #new list item
def __getitem__(self,index): #magic that I don't understand, but it works.
return self.list[index]
I don't quite understand how 'def __getitem__' works, but hey, Thanks Google! I can now pass a list of all attributes of a class through to a function by using, for example, the HomeTeam.list argument!
I don't think it makes sense either to have two separate functions to determine the best jumper and then the actual jumpball, so I combined them both in to one, using the lists created in the classes as follows.
Groundhog
07-25-2014, 10:53 PM
def jumpball(PlayerHome5, PlayerHome4, PlayerAway5, PlayerAway4,
HomeTeam, AwayTeam):
#Determine best jumper for both sides. My 1st attempt to make this
#more clever didn't work:
#home5jumpabl = sum(PlayerHome5[2:3])
#home4jumpabl = sum(PlayerHome4[2:3])
#away5jumpabl = sum(PlayerAway5[2:3])
#away4jumpabl = sum(PlayerAway4[2:3])
home5jumpabl = PlayerHome5[2] + PlayerHome5[3]
home4jumpabl = PlayerHome4[2] + PlayerHome4[3]
away5jumpabl = PlayerAway5[2] + PlayerAway5[3]
away4jumpabl = PlayerAway4[2] + PlayerAway4[3]
if home5jumpabl > home4jumpabl:
homejumper = [PlayerHome5[1], home5jumpabl]
elif home4jumpabl > home5jumpabl:
homejumper = [PlayerHome4[1], home4jumpabl]
else:
homejumper = [PlayerHome5[1], home5jumpabl]
if away5jumpabl > away4jumpabl:
awayjumper = [PlayerAway5[1], away5jumpabl]
elif away4jumpabl > away5jumpabl:
awayjumper = [PlayerAway4[1], away4jumpabl]
else:
awayjumper = [PlayerAway5[1], away5jumpabl]
#Jumpball calculations:
homeplayerd20 = randrange(1,20) + homejumper[1]
awayplayerd20 = randrange(1,20) + awayjumper[1]
randomresult = 0
if homeplayerd20 > awayplayerd20:
winner = [homejumper[0], HomeTeam[1], HomeTeam[2]]
#this is where the other gamestate variables would be set.
elif awayplayerd20 > homeplayerd20:
winner = [awayjumper[0], AwayTeam[1], AwayTeam[2]]
#as above - gamestate variables.
else:
randomresult = randrange(1,2)
if randomresult == 2:
winner = [AwayTeam[1], AwayTeam[2]]
else:
winner = [HomeTeam[1], HomeTeam[2]]
#as above - gamestate variables.
#Jumpball Commentary
print "Welcome to {}, where tonight the {} {} are set to square off against the visiting {} {}.".format(HomeTeam[4], HomeTeam[1],
HomeTeam[2], AwayTeam[1],
AwayTeam[2], AwayTeam[2])
print "{} and {} are in the middle for the opening tip.".format(homejumper[0],awayjumper[0])
print "The ball is up..."
if randomresult != 0:
print "Both players get a hand to it, but the {} {} come up with possession.".format(winner[0],winner[1])
else:
print "{} wins the tip, {} {} with the ball.".format(winner[0], winner[1], winner[2])
Running this from the Python shell returns:
>>> jumpball(PlayerHome5.list, PlayerHome4.list, PlayerAway5.list, PlayerAway4.list, HomeTeam, AwayTeam)
Welcome to Quicken Loans Arena, where tonight the Cleveland Cavaliers are set to square off against the visiting Charlotte Hornets.
A. Varejao and N. Vonleh are in the middle for the opening tip.
The ball is up...
A. Varejao wins the tip, Cleveland Cavaliers with the ball.
Woo hoo! Success! I took it out of the code above, but I was printing all the variables initially to confirm things were working as I expected them to. So there we have it, a simulated jump ball! I can already see a few ways to cut down on the amount of variables and lines of code I used above, but I was more keen to show I could make it work rather than make it work the most efficient way possible. Now, back to theory...
Young Drachma
07-26-2014, 04:32 PM
Ok this is awesome and inspiring. Keep it up.
aston217
07-26-2014, 05:14 PM
Awesome thread, keep up the work!
Groundhog
07-27-2014, 04:43 AM
Having finished the 'Python in 24 hours' book, I'd rate it a mediocre 2.5/5. It was an easy read for the fundamentals, but too often I found myself having to google after finishing a chapter to get a better explanation of the concepts it was trying to teach. I did find the very brief chapter on Developing for the Web with Flask to be interesting as far as my long-term goals go, and the SQL chapters were also pretty useful.
I moved on to an online course at Udemy - 'The Ultimate Python Programming Tutorial'. A series of short video tutorials, this has been a much better resource for me. I'm about 65% through and although it's covered a lot of the same ground as the book thus far, I've found it much easier to follow.
The lessons on functions and loops have given me an early idea on how the sim might actually function.
Groundhog
07-27-2014, 05:17 AM
The sim itself would be a loop that continues to run until the game is over. That's the easy part. There are a collection of variables that are checked each time the loop is run - team with possession, type of possession, game situation, etc. - that then fire functions based on those conditions. These functions would be things like inbound plays, halfcourt sets, free throws etc. They would write commentary to a file, keep stats, etc.
The first issue I see with doing it that way would be the amount of info I need to pass between the main program and the individual functions. Each time a play is run, for example, I'd need to pass just about every variable containing player and team information through to each function every time it fires, and then once the function is finished a lot of data will need to be sent back too - stats being the prime example. Using global variables would cut back on some of this, although I'm trying to avoid using too global variables if I can. The advantage to this method would be that the main program would be more compact.
The other method would be reserving functions just for the calculations - for example, to use the jump ball experiment above, the loop would run and check the variable that calls for the opening tip. An 'If' statement then fires and runs the actual play, including the jumpball function. The function would simply return a couple of values to indicate who was in the jump and who won, and the main program then uses the values to build the commentary and adjusts any stats as required (none, in this case), as well as set the variables that will trigger the next event - a possession for the winning team.
The advantage would be less information being passed between the main program and the numerous functions. The main disadvantage is that the program is going to get very long, very fast!
Maybe I'm way off and there's a better way to do it that I don't know yet, but right now I'm leaning towards option #2.
Groundhog
07-27-2014, 06:57 PM
I decided to put my theory behind how an actual possession would work to the test, and put together a quick proof of concept for a possession. The theory would be that each play would be a loop. A variable would be used to progress through the play, with each step pointing to the next step that would be called... In the following example I use the 'o' variable to keep the play alive, and the 'i' variable to point the play at the next step each time the loop runs.
This is a simple play that sees the C set a high pick for the PG who, if the screen is a success, will either shoot, pass, or drive:
import random
o = 0
i = ""
while o != 1:
if i == "":
#first step of the play
#offensive player's positions on the court set.
print "5 man moves to set a high screen for the 1"
#subtract time from game clock and shot clocks.
i = 1
elif i == 1:
#second step of the play
#offensive player's positions on the court set.
#calculate the success of the screen and/or any fouls.
screensuccess = random.randrange(0,2)
#subtract time from game clock and shot clocks.
if screensuccess == 0:
print "5 man sets a bad screen."
i = 99
#set a game-state variable that would mean the next possession
#is a 'broken play' with the ball in the PG's hands.
else:
print "1 runs off the pick by 5 man"
i = 2
elif i == 2:
#third step of the play
#offensive player's positions on the court set.
#check for turnover by ballhandler.
#determine if ball handler goes to the basket, shoots, or passes.
#subtract time from game clock and shot clocks.
pgdecision = random.randrange(0,3)
if pgdecision == 0:
#final step of play.
#offensive player's positions on the court set.
#check for turnover by ballhandler.
#subtract time from game clock and shot clocks.
print "1 drives to the basket."
i = 99
#set a game-state variable that would mean the next possession
#is a drive by the current ballhandler.
elif pgdecision == 1:
#final step of play.
#offensive player's positions on the court set.
#check for turnover by ballhandler.
#subtract time from game clock and shot clocks.
print "1 pulls up for the long jumper"
i = 99
#set a game-state variable that would mean the next possession
#is a jumpshot by the ballhandler.
elif pgdecision == 2:
#final step of play.
#offensive player's positions on the court set.
#check for turnover by ballhandler.
#subtract time from game clock and shot clocks.
print "1 passes to a teammate"
i = 99
#set a game-state variable that would mean the next possession
#is a 'broken play'.
elif i == 99:
o = 1
i = ""
The end result:
5 man moves to set a high screen for the 1
1 runs off the pick by 5 man
1 pulls up for the long jumper
I run it a bunch of times to make sure the results are random, and it looks good! Obviously there would be a lot more complexity involved in the final version - ie. not straight random decisions, but based on tactics and attributes. But it's a start.
Groundhog
07-27-2014, 07:01 PM
If you'd like to test for yourself: Execute Python Script Online (http://www.compileonline.com/execute_python_online.php)
Paste the above code directly in there and click 'Execute script'.
Groundhog
07-28-2014, 12:39 AM
I started work on a function to decide the outcome of the screen in my play above. As a screen is a fundamental part of many plays, the screen function could be used in this simple pick and roll play, or could be called during a longer set play, like a Triangle for example, which would then decide which step the play moves to.
One concept I'd like to build into my sim is that of good plays and bad plays. For example, a player runs off a screen, and the defender goes under the screen, leaving the ball handler with some space. The player has options here - he could shoot the long jump shot, try to drive past the sagging defender, or pass the ball. The sim should be able to look at a player's attributes and determine the best option, and then whether the player follows that option. An offensive awareness rating would be used to check this.
Down the other end of the court, the defensive awareness rating would apply in a similar fashion. The coach would decide defensive settings for each opponent on the court - depending on a coach's attributes, he may have a set philosophy that is applied to all players, or individual assignments for each. If the coach has decided that the "right play" for defending a particular player with the screen is to go under, the player's defensive awareness rating checks whether he acts as he was supposed to. Mistakes like this could be tracked against a player, and lead to coaches subbing off guys who continue to get it wrong. The settings chosen by the coach as "good plays" on this end of the court might not neccessarily be the optimal settings - that's where the coach's attributes would come into play.
It's all good and well calculating this, but it really should be in the PbP too. If a guy makes a bad decision, it should be clear in the PbP. If a guy gets yanked for making 3 or 4 mistakes, we fire up the RageSub function to get some commentary about the coach giving it to him on the sideline.
Groundhog
07-29-2014, 06:51 PM
I made a lot of progress the past few days. I started with the screen in my play above - a simple high screen from the C for the PG. What could happen on a play like this, before the point where we've even decided what the PG does next?
1) Defender commits a foul fighting through the screen.
2) Illegal/moving screen from the C.
Then I thought about how to test each of these conditions. For #1 I set a percentage of this occuring - just plucked a figure of 5% out of the air - and then took the opposing PG's foul rating, which is a rating of 1-20, and 'rolled a d20'. If the result equals or beats the foul rating, an extra 5% is added on top. These are all just arbitary figures at this stage that would need to be tweaked.
For 2 I do something similar, using the offensive awareness rating of the screener as a modifier, along with the defensive awareness of the defender.
If no fouls are triggered by either player, we progress with the actual screen. For the offense some of the factors I want to simulate are the quality of the screen, the use of that screen by the ballhandler (some guards are much better than others in this area IRL), and the creativity of the ballhandler. For the defense it's mainly about reacting to the ballhandler, as well as applying the team strategy - going under screens on fast players who can't shoot, over for good shooters, etc.
Although it's fairly complicated calculating all of the above, the advantage is that once I'm happy with it, I have a function named 'screen' that I can call whenever a screen is required from any play. I feed the required attributes - ball handler, the screener, the defender, team strategy - to the function, and it will give me a result that can then be used to determine the next step of the play.
Groundhog
07-31-2014, 11:28 PM
http://www.coachesclipboard.net/images/WisselPnRA.png
Above is first play I'm attempting to model, a simple high pick and roll with the 1 and 5 man. The first thing I did was map out the play sequence, as well as the different options that it can branch into. Each step of the play has calculations that determine the next step, as well as situations like turnover & foul opportunities.
The halfcourt is broken down into 14 logical slices - the standard sections you see on a shot chart - and the sim keeps track of player's positions on the floor each step on offense. It may seem like overkill for a text sim, but I think it will prove handy later, and opens up the possibilities of including things like shot charts, play diagrams etc. down the track........... much, much later down the track. :)
On defense I'm currently plotting out the play assuming man defense, but I have a good idea how zone will work, with defenders being responsible for an area of the court (using the same slices as above). I've also made it easy to assign defenders to specific players rather than matched with the same position for when I get to working on strategy.
For the actual play, despite being pretty simple, there are still a total of 6 scoring possibilities from the play itself - a 3pt/long 2 from the PG off the screen, a drive from the PG off the screen, a corner three from the 2, a baseline jumpshot for the 4, the rolling 5 cutting to the basket, and finally the 4 man cutting to the FT line for a possible jumpshot.
There are also multiple opportunities for fouls, turnovers, and "broken play" situations.
Right now I have each step of the play coded and running with (very) simple commentary being generated, more just to show me that it's flowing correctly. About half the choices being made are still purely random. My goal this weekend is to have every choice generated from attributes, and hopefully be able to switch out the man defense for a 2-3 zone.
My goal is to have this one play working completely - ie. complete strategy implemented for both offense and defense. Once I'm happy that the play works entirely as I'd like, I'll feel fairly safe moving on without the fear of needing a massive re-write of everything down the track...
Groundhog
08-02-2014, 02:27 AM
Another productive day of (trying to) code! I revamped the intro to give the starting lineups. Simple, but had to write a quick function to translate the heights as they are stored into "English" - for example, 6'9 is stored as 69:
Welcome to Quicken Loans Arena where the Cleveland Cavaliers (25-11) are up against the visiting Charlotte Hornets (17-16).
Starting Lineups:
Cleveland Cavaliers:
--------------------
C: A. Varejao, 6'11
PF: T. Thompson, 6'9
SF: L. James, 6'8
SG: A. Wiggins, 6'8
PG: K. Irving, 6'3
Charlotte Hornets:
--------------------
C: A. Jefferson, 6'10
PF: N. Vonleh, 6'9
SF: L. Stephenson, 6'5
SG: G. Henderson, 6'5
PG: K. Walker, 6'1
Up next is the opening tip, which has changed only slightly from what I posted earlier in the thread - mainly due to the amount of times I've changed from lists to dictionaries for storing and passing around information:
A. Varejao and N. Vonleh are in the middle for the opening tip.
The ball is up.
N. Vonleh wins the tip, possession is with Charlotte.
This result now sets Charlotte as the attacking team and Cleveland on defense. I setup a variable, either 0 (home team) or 1 (away) that is relational and used by all functions and calculations to reference the right information for the team on offense and defense. It took me a few days to work out the best way to do this, and resulted (of course) in having to rewrite a bunch of lines, but I'm really happy with how it turned out.
K. Walker brings the ball up the court for Charlotte.
K. Walker has the ball at the top of the arc, and signals the play.
A. Jefferson moves up to set the high screen for K. Walker.
K. Irving goes under the screen.
For these 4 statements there's a lot of calculations that took place behind the scenes. We checked who was defending Kemba Walker based on the strategy of the team - Cleveland are playing man defense, and Irving is assigned to guard the opposing PG. The screener and defender had a chance of committing a foul. We then checked the screen strategy for Walker, and it's set to go under the screens. We checked his defender (Kyrie) to see if he made the right decision based on that strategy. He did.
That's as far as I've got with the commentary, but the next few steps of the play are also finished.
A few more examples of what can be generated from what I've completed so far:
A. Varejao and N. Vonleh are in the middle for the opening tip.
The ball is up.
A. Varejao wins the tip, possession is with Cleveland.
K. Irving brings the ball up the court for Cleveland.
K. Irving has the ball at the top of the arc, and signals the play.
A. Varejao moves up to set the high screen for K. Irving.
A. Varejao wipes K. Walker out with a great pick!
K. Irving reaches the right side elbow with some space.
Varejao had a quality screen here, which gets Kyrie open off the pick, which will affect the next step of the play, opening up the jumpshot or drive.
K. Irving brings the ball up the court for Cleveland.
K. Irving has the ball at the top of the arc, and signals the play.
A. Varejao moves up to set the high screen for K. Irving.
A. Varejao is called for the illegal screen on K. Walker.
An offensive foul.
A. Varejao and N. Vonleh are in the middle for the opening tip.
The ball is up.
A. Varejao wins the tip, possession is with Cleveland.
K. Irving brings the ball up the court for Cleveland.
K. Irving has the ball at the top of the arc, and signals the play.
A. Varejao moves up to set the high screen for K. Irving.
K. Irving moves into the high pick, but K. Walker fights through the screen.
Similar to the quality screen above, this gives Kemba Walker an opportunity to disrupt the play and make a play on the ball in the next step of the play.
CraigSca
08-03-2014, 09:41 PM
Groundhog - will be following this and will be rooting you on.
I love programming, but I know I'm not a very good one. Have dabbled in a lot of languages and, like you, focusing on Python now since I can actually use this for work. I've always dreamed of writing a lot if sports simulations (originally baseball, but focusing on college football now since TCY has gone away). Watching you learn the language has been a pleasure - and it reminds me if my early years when programming was pure joy as it was a series of discoveries like the ones you're making. Good luck with this as I will be an avid follower.
As far as Python is concerned - know that almost ANYTHING you're thinking if doing there's either a library or code for it already. Google search exactly what you're trying to do - examples will be out there.
As far as advice - I would also crest a game class whose parameters include the teams and the current state if the game. This way you can cycle through each game in a single loop - allowing you to play all the games for that days schedule concurrently (you can add a start time and a time if day parameter to determine if the game has started or not). Later down the road, but just something to think about.
Also - another thing I've found is all these classes and lists may be better serviced as a database (I've been told mongoDB is a good, free one). You'll be able to search on all data, run calculations, etc. Again, probably not something you want to think about now.
Awesome stuff - good luck!
.
I don't quite understand how 'def __getitem__' works, but hey, Thanks Google! I can now pass a list of all attributes of a class through to a function by using, for example, the HomeTeam.list argument
Having finished the 'Python in 24 hours' book, I'd rate it a mediocre 2.5/5. It was an easy read for the fundamentals, but too often I found myself having to google after finishing a chapter to get a better explanation of the concepts it was trying to teach.
Yep, sounds like programming to me :D
Groundhog
08-03-2014, 11:40 PM
Groundhog - will be following this and will be rooting you on.
I love programming, but I know I'm not a very good one. Have dabbled in a lot of languages and, like you, focusing on Python now since I can actually use this for work. I've always dreamed of writing a lot if sports simulations (originally baseball, but focusing on college football now since TCY has gone away). Watching you learn the language has been a pleasure - and it reminds me if my early years when programming was pure joy as it was a series of discoveries like the ones you're making. Good luck with this as I will be an avid follower.
As far as Python is concerned - know that almost ANYTHING you're thinking if doing there's either a library or code for it already. Google search exactly what you're trying to do - examples will be out there.
As far as advice - I would also crest a game class whose parameters include the teams and the current state if the game. This way you can cycle through each game in a single loop - allowing you to play all the games for that days schedule concurrently (you can add a start time and a time if day parameter to determine if the game has started or not). Later down the road, but just something to think about.
Also - another thing I've found is all these classes and lists may be better serviced as a database (I've been told mongoDB is a good, free one). You'll be able to search on all data, run calculations, etc. Again, probably not something you want to think about now.
Awesome stuff - good luck!
.
Thanks, glad to have you following along!
In terms of the scheduler I guess that's kind of what I was thinking - it would be a class of team IDs and dates, and when a game is scheduled the two team IDs would be fed into the main sim engine as the primary key reference used to populate all the in-game classes/list/dictionaries etc. It's all very vague in my mind at this stage until I actually try and do it. :D
Right now I've just typed up the dictionaries, and have created the two NBA teams from above as well as a few teams from lower quality leagues to check the affect of lower rating players competing against similar competition.
Thanks also for the mongoDB tip. I covered Sqllite3 in the "Python in 24 hours" book very briefly, but I really, really hate SQL... maybe mongoDB is a better choice for me!
Groundhog
08-04-2014, 09:02 PM
I was looking for a web scraper for work, when I stumbled upon a really good online one that makes the automation of capturing names and other info from a site like Yahoo sports simple.
I setup an automated web scrape to capture the roster info of every division 1 college team roster, which gives me a list of names I could use to generate players, as well as some name-related stats - like the fact that 0.2% of players are a "John Doe, II" (0.08% are a IV), and 1.01% have hyphenated names. I very quickly generated a random hyphenated name, and naturally it came out 'T.J. Williams-Williams'. :D
About 7-8 years ago I extended the FBCB names list by manually copying and pasting every player from every roster...... The above took me about a minute to setup, and a few hours to run in the background.
Also in the "interesting but not really related to my sim" category, the top 5 most popular jersey numbers in college basketball - 5, 1, 2, 3, 11.
Groundhog
08-05-2014, 01:25 AM
Spent some time last night trying to work out the best way to calculate a few of the steps of my play. If a player comes to help on defense and leaves his man, the ballhandler has to decide to either attack himself, or look for the open man. To make the decision, I use the court location of the offensive player as well as his attributes to determine whether it's a good option to pass to that player.
To use this pick and roll play as an example, if the PG comes off the screen and finds himself open, the defenders of the 2 and 4 man have to decide whether to leave their man to hedge the ballhandler. This decision is based on strategy - there is a hedge setting for each player on the court - vs defensive awareness to see if the player correctly acts on that strategy. If either defender hedges, a check is made on the 3pt ability of the 2 man on offense, and the 4 mans inside ability from the low post to determine the "best" decision - then the players offensive awareness rating is used to determine if he makes the best decision.
I currently have the sim functioning as above, but I'm trying to add some more complexity to it. I'd like the defender to take into account the guy he is defending when deciding to hedge - for example, you probably wouldn't leave Ray Allen to pickup the ballhandler in this situation. I'd also like the ballhandler's decision to not be so simple, because maybe the guy in the corner is a good shooter, but the PG might still be better served using his own abilities to attack.
Again, the advantage to making this as complex as possible is that, once I'm happy with it, I can reuse it every posession that requires similar decisions. It's just getting it right that first time that has me pulling my hair out. :)
Groundhog
08-10-2014, 05:51 PM
Haven't been a lot of updates the past week because I decided I needed my first and hopefully only major re-do. The issue I had was that I'd stored all the player attributes in a list, which meant referencing them as a number value in the order they were listed.
For example, initially the player information was held as follows:
[id#, "A. Varejao", 70, 15, 14, 7, 7, 0, etc]
I had a key of what each attribute related to - offaware, 2pt, etc. And when I called the player's rating to use it in a calculation, I would reference it by the order it appears in the list - height would be 'player[2]' for example (counting starts from 0). Although it may not sound overly complicated, once the number of attributes hits the 20 mark it starts to get a bit messy, especially when I'm going back and reading code a few days later.
I wanted to change everything to dictionaries. Dictionaries are a kind of database that allows you to store a value against a key that can be used to reference that value. This makes it a lot easier to read. To use the example above:
{"name": "A. Varejao", "height": 70, "jump": 9, "offaware": 14, "defaware": 14, etc}
If I'm then passing a value to a calculation, say Varejao's heigh and jump ratings for a jump ball, I'm simply passing 'player["jump"]' and 'player'["height"]'.
Seems simple, but it meant rewriting nearly every function I had put together, which was also a good opportunity to clean up a few things using methods I'd learned since I began.
Groundhog
08-10-2014, 06:00 PM
I should also add that the actual player information is a dictionary within a dictionary that's within another dictionary... Hence the "kind of database" comment.
The top level of the dictionary is a value for '0' - home team, and '1' - road team. There is a variable that tracks whether the home or road team has possession - 0 or 1 - which is then used to automatically reference either the offense or defense roster and attributes when required.
So to use another example, when the high screen is called by the function, the values sent to the function will looked like:
rosterdictionary[pos][playerid]["offaware"],
rosterdictionary[defteam][playerid]["defaware"]
'pos' is the variable that is either 0 or 1 for possession, and that will point the function to the desired offensive player. 'defteam' is the opposite value of 'pos', and thus points to the defensive roster.
Playerid is calculated using another dictionary which tracks the 5 players on the court for each team. It includes the positions on the court as the keys, and the the id # of the player in that position as the value. Substitutions are a long way out yet, but this is how they will work.
BYU 14
08-10-2014, 09:43 PM
Really cool and impressive with the progress you have made in a short time.
Good luck with this, and when can we pre-order? :)
Neuqua
08-12-2014, 09:26 AM
As someone who is also trying to learn Python, this thread is fascinating to me. Best of luck to you!
Groundhog
08-14-2014, 06:08 PM
Spent a good 2 hours tracking my first bug last night. It ended up being related to a change I made a few days ago, which was to split all common sequences like screen calculations (success, fouls, etc.), shots, passes, etc. - basically things that will occur in a majority of plays - into their own sections of my sim's loop. This means I need to code them exactly once, and then direct the loop to that sequence when required. I do this using two variables, one is "gamestate" which is a sequence of decimal numbers that is used to point the loop to the correct segment of code. Whenever the loop is sent to one of these other sequences, a variable named "returnstate" is used to point the loop back to the next sequence of the play.
A non-code example of how the loop looks:
Loop
1.0 - jump ball
run jump, set possession
...
50.0 - pick n roll
send loop to "101.1" to set common variables
for the play then point it to return.
actions a, b
50.1 - pick n roll
actions c, d
send loop to "103.1" for screen and point it
to return.
...
101.1 - common variables
generate possession-specific variables, then
return to play.
103.1 - action the screen/calc fouls
calculate screen results, then return
loop to pick n roll.
The bug was to do with a variable named "isopen", which tracks whether the ballhandler is open during the each step of the play. I set the defensive strategy to go under screens, which is supposed to lead to the ballhandler being open for one step of the play. This was working fine, but once I split everything out into it's own gamestate "elif" block, the player was open coming off the screen but, if they attempted the 3, the PbP was displaying a contested shot rather than open.
The issue was that I had a small block of code that checked the defender's reaction to the screen (over, under, etc) that then determined the "isopen" value. With all the cutting/pasting I'd gone through the last couple of days, I'd sliced this function in half, which was resetting the value of "isopen" just prior to calculating the shot. This meant that the ball handler was making the decision to shoot based on the fact that they were open, but the shot itself was being calculated as a contested shot.
Groundhog
08-14-2014, 06:26 PM
Really cool and impressive with the progress you have made in a short time.
Good luck with this, and when can we pre-order? :)
Thanks, at this rate, 2018. :D
Groundhog
08-24-2014, 07:16 PM
Spent a good 6 hours coding this past weekend, working mainly on the passing logic. A few weeks ago I wrote an elif block that handled the hedge sequence if the PG comes off the screen with space. Basically it determined if the defender of the SG or PF's defenders leave their man, based on the defensive strategy (and whether they follow that strategy), and the PG then decides if he will pass to the open SG or PF if they do - based on the PGs ratings and the PF or SGs skill scoring from the position they are in. The actual pass action was in this elif block too - it could sail out of bounds, be picked off, or make it to the player.
This was great and it all worked as planned, but it was very specific to the one play. Instead, I wanted to build the logic of these two actions into more generic pass and hedge code blocks that could be called whenever they are required for any play.
For this I created an elif block and a function. The function is fed a number of variables about the current possession and uses these to determine if the player receiving the pass is open. It looks at the defensive strategy of the team so if the team is playing man-to-man defense for example, it will check if the ball handler is open and based on the location of that player and the defender, if the defender hedges in to pick up the ballhandler. If the team is playing zone, it uses the pass receiver's position on the court vs the type of zone being played to determine if the player is open. In a zone each defender is assigned different areas of the court to defend, and some slices - the mid-range baselines in the 1-3-1 for example - have a better chance of causing a player to be open than others. The defender's defensive awareness rating gives him a chance to recover to the player.
Anytime a pass action is required, I created an elif block that is called from within the play being run. The play moves the loop to the pass state, actions the pass, and based on the result (turnover, completed pass, etc) will then either return the loop to the play, or action a shot, etc.
If the pass is made to an open player around the basket, there is also a chance of the pass being an alley-oop play. The advantage of this is that it allows for alley-oops to happen outside of the scripted play being run - if a player passes to a guy near the basket who is open, there is a chance that this would be an alley-oop. The sim takes into account the ratings of the passer and the guy near the basket to determine if it's a good decision to throw the alley-oop, as well as the success of the pass and finish.
I also created a settings.ini file which stores some default values that can be tweaked to adjust frequency of things like fouls, open shot attempt %s, blocks, turnovers, alley-oop attempts, etc. Gameplay sliders I guess you could call them! They aren't raw % of events occurring because player ratings factor in as modifiers, but should hopefully allow me to tweak things easier down the track.
Next on my to-do list is an elif block to handle a ballhandler's isolation decision. It will factor in whether the player is open, the defense being faced, and based on the players ratings determine whether they attack the basket, shoot a jumpshot, or pass the ball - basically, it's an isolation play that could be called once a play breaks down, or in a play designed to get a skilled scorer the ball in a particular location. Once that's done, I want to go back and adjust a few calculations against different zone defenses, code a post play elif block, begin work on shot clock/game clocks, and then add stat recording. After that's done, much of the core "basketball" stuff will be coded and creating additional plays for the most part should be fairly straight forward.
Groundhog
09-08-2014, 02:06 AM
After that's done, much of the core "basketball" stuff will be coded and creating additional plays for the most part should be fairly straight forward.
Yeah, so, not quite.
The further I got along in my coding, and the more decisions/outcomes I had to come up with, the less happy I got with the direction I was going in.
One of the real drivers for me to make this sim is to develop a strategy-focused sim that simulates a real(ish) game of basketball. I'm a huge fan of Brian's Fastbreak sims and I think they do a great job of generating realistic results and stats. I'm looking to approach it from the other side - a sim that attempts to accurately model a game of basketball. I think the disadvantage to doing it this way is that, the more complex it gets, the harder it's going to be to get realistic results.
From watching a lot of FIBA basketball at the world cup and thinking about it from a sim perspective, I had some more ideas about how I would like my sim to work. Most teams have basic sets they run with a number of options they are specifically looking for, but a lot of the time they make a play based on what the defense gives them - ie. if a team throws a 2-3 zone at them they'll find the three point shot is there if they want it, regardless of what set they are running (often to their own detriment), etc.
Currently my sim has been built very play-specific. Obviously plays and sets will still be an important part of any strategy-focused sim, but I wanted the ability to break from a play at any given moment when a scoring opportunity presents itself.
To do this, I created a function that defines where the defenders are on the court, and another that checks both this as well as the team defensive strategy to determine which offensive players are open.
For the defender's court position, for m2m it's an easy calculation, but for zone - 2-3 and 3-2 only at this stage - I defined where each defender should be relative to the ballhandler as well as what area on the court they are responsible for on the pass. For example, in a 3-2 zone the 4 and 5 man might be covering the low post if the ball is at the top of the key, but on a pass to either corner 3, they would be expected to rotate out and pick up the shooter. The defender's speed and defensive awareness determines if they make it to the player in time to prevent him being flagged as "open", with it being more difficult the further the player is from the shooter - in this case, he would be required to move across 3 zones to reach the shooter.
Each step of a play the sim calculates where the offensive and defensive players are, and if the offensive players are open. Taking into account whether a player off the ball is open, for example against a zone or a defender hedging off his man, the play could break, and a pass be actioned to the open man. The sim takes into account the skill of the player in scoring from the position he is in and determines if it's a good play to make the pass - the offensive awareness rating then determines if the player makes the good or bad play.
Right now this is 50% coded and 50% scribbled onto my notepad, but I'm putting a lot of effort into making this part of the game as "smart" as possible - also taking into account the ease of passing from each point on the court to another - ie. if a guy has the ball in the left corner, it's much more difficult to complete the pass to the opposite corner than it is to dump it inside.
Groundhog
09-14-2014, 07:25 PM
A quick rundown on the work I've been doing re: court positions. Each step of a play, I define where the offensive players are on the court. As I've said above, the half-court is divided into 14 slices, and the below dictionary has the players position as a string - ie. "5" for the C - followed by his location on the court - ie. 6. I use strings for the player positions to make it easier for me to spot them in the code. PG/SG/etc. would've been even easier, but hey, hindsight :) :
offpos = {"pos": {"5": 6, "4": 3, "3": 14, "2": 10, "1": 12}}
To work out the defender's location, the sim first checks the defensive strategy, which is also stored in a dictionary, this is an excerpt of the home team's strategy:
strategydict = {"0": {"strategy": "m2m",
"individualstrat": {100014: {"hedge": 0, "screen": 0, "insided": 1, "2ptd": 0, "3ptd": 0},
100015: {"hedge": 0, "screen": 0, "insided": 1, "2ptd": 1, "3ptd": 0},
100016: {"hedge": 1, "screen": 0, "insided": 0, "2ptd": 0, "3ptd": 0},
100017: {"hedge": 1, "screen": 0, "insided": 0, "2ptd": 1, "3ptd": 0},
100018: {"hedge": 1, "screen": 1, "insided": 0, "2ptd": 1, "3ptd": 1}},
"guard": {"1": "1", "2": "2", "3": "3", "4": "4", "5": "5"},
"threeonly": 0, "focus": 0, "tempo": 0, "autofoul": 0},
"Strategy": "m2m" and, as it's man-to-man, "guard" are the entries that will now dictate the defensive player's positions on the court, which builds the defpos dictionary (basically, identical to offpos above, but with an entry that dictates who is defending the ballhandler). It will then take the "individualstrat" entries - the 100014 are primary keys for the players on the other team - and check each offensive players position on the court and determine whether the player is being left open or guarded based on the "insided"/"2ptd"/"3ptd" entries - the defender's defensive awareness rating is also checked to see if a guy follows the defined strategy or not. An additional "isopen" dictionary is created and (not yet coded) a check will be made each step of a play to see if a pass is made to an open player, which would break from the scripted play.
If a team is playing zone defense it's a bit trickier. The method I came up with was to script the defense's court positions relative to the ballhandler. It took some work, but I'm pretty happy with how it turned out. The sim will check the location of the ball handler, then look that up in a function that returns the defender's position on the court, the player defending the ball, and then for each of the 14 spots on the court it tracks: 1) the player responsible for that location, 2) a secondary player responsible for the position too (for some zones it could be the 4 and 5 in front of the basket, etc.), and 3) how many areas removed from that space is the defender if he has to recover to a pass - ie. in the below example from the 3-2 zone with the ball in position 14 (left corner 3), the 5 man is responsible for the right corner 3 (position 10), but he would be 3 zones removed from that location, making him unlikely to recover to that spot if the ballhandler made a miraculous pass from one corner to the other.
elif ballpos == 14:
{defpos: {"5": 1, "4": 9, "3": 3, "2": 7, "1": 8}, "ballhandler": "4"}
{1: ("5", 0, 0), 2: ("5", 0, 1), 3: ("3", 0, 0), 4: ("5", 0, 1), 5: ("5", 0, 2), 6: ("2", 0, 1), 7: ("2", 0, 0), 8: ("1", 0, 0), 9: ("4", 0, 0), 10: ("5", 0, 3), 11: ("2", 0, 1), 12: ("2", 0, 1), 13: ("1", 0, 1), 14: ("4", 0, 1)}
I also didn't want guys throwing unrealistic passes to open players - ie. the example above, a pass from one corner to the other is very difficult. I created another dictionary that again takes a player's position on the court and assigns a rating of 0, 1, 2 to each area on the court with 0 being an easy pass, 1 difficult, and 2 very difficult. This effects both the player's decision to make the pass as well as the success of that pass.
Groundhog
09-29-2014, 02:14 AM
I was really dreading learning databases. It's an unescapable fact that I need to get my SQL-fu up to scratch though, as it's going to be a huge part of my sim.
I played around with a few different database types before settling on sqlite. It's free, it's installed already with Python, and it uses the SQL language, which I'd rate my current level of understanding just above "infant".
After a lot of stackoverflow and google searches, I stumbled upon an ebook - SQLite Python tutorial (http://zetcode.com/ebooks/sqlitepython/)
It was worth the money. After 30 mins of reading I Was creating database tables and inserting/retrieving data. It's very basic stuff I know, but looking a database full of tables for teams/players/countries/coaches etc. populated with data from main engine was the first big "Wow, I can actually do that..." moment I've had. :D
Groundhog
10-09-2014, 07:28 PM
The last couple of weeks have been another big rewrite, this time purely around loop control. The basic structure of my sim was a whole bunch of "elif" blocks for each action - jump ball, pass, shot, etc. - with each block pointing the loop to the next, so a foul would lead to an inbound, etc. Apart from being messy, it's also harder to troubleshoot and to reuse elif blocks for similar actions.
I closed my text editor down and started mapping out a more structured loop control in excel, and came up something much cleaner. I have a sub-loop to the main game loop which I title the "logic_block". This is just an elif block but it controls the flow of the main game loop. Based on the game situation, which is a sub-loop to the 'logic_block', and this elif block points the loop to the correct action - action_shot_attempt, action_turnover, etc - and tells that action where to go next. What I really like about this layout is that each action is standalone - it just calculates what it needs to, and leaves everything else up to the logic_block. This makes it really easy to re-use these actions in different situations.
It probably doesn't sound exciting, but I'm very happy with how it's turned out. :)
In more basketball-ey coding, related to my post above, I have finished up the logic around non-structured offense. Every step of a play, the sim will check the offensive and defensive players positions on the court, determine if a guy is open (intentionally or not), his ability to score from where he is, and his location in comparison to the ballhandler to determine pass difficulty. All of these factors make up a "pass value" score from 1 to 4, with 1 being "Shaq open from 3" to 4 being "Shaq open in front of the basket" - ie. a must-pass. A player's offensive awareness determines how many player's pass values are checked, and the higher the value the more chance that the player will break from the scripted offense to make the pass to that player - again, offensive awareness helps aid the right decision here.
I have also put some work into mapping out distances between court positions to help with defensive recovery - if a guy is open with the ball at a particular location, this will be used to determine if the defender is able to recover from his position in time, or if the offensive player will get a chance at an open shot/drive etc. It will also allow for logic around defenders hedging off their man to pick up the ballhandler. Still lots to be done here, but I have a plan on how to implement it.
This is a text sim, but I am putting a lot of effort into tracking player positions on the court and the distance/relation between the areas on the court, because, while it's taken some work, it makes it easier to have "natural" events occur - open shots, backdoor cuts, etc.
In more basketball-ey coding, related to my post above, I have finished up the logic around non-structured offense. Every step of a play, the sim will check the offensive and defensive players positions on the court, determine if a guy is open (intentionally or not), his ability to score from where he is, and his location in comparison to the ballhandler to determine pass difficulty. All of these factors make up a "pass value" score from 1 to 4, with 1 being "Shaq open from 3" to 4 being "Shaq open in front of the basket" - ie. a must-pass. A player's offensive awareness determines how many player's pass values are checked, and the higher the value the more chance that the player will break from the scripted offense to make the pass to that player - again, offensive awareness helps aid the right decision here.
This is very cool. There was a big Kirk Goldsberry paper that came out a few months ago and it also had some measure for the relative value of passing to each player on the court (or shooting) at each "frame" of the possession. My pet peeve with it was that it didn't take prior actions into account (e.g. that based on the position of everyone on the court, Player X is only "open" for a corner three because the player with the ball is driving baseline in the opposite direction and could only get him the ball with some extremely difficult pass). That's hard to code for, but having that value for awareness (I suppose the FM equivalent would be flair) is definitely key.
Groundhog
10-09-2014, 11:05 PM
Yeah, sometimes I think I'm relying on "offensive awareness" for things like the above too much, when maybe something like "flair" or "court vision" might be more specific. The way I have envisioned it though is that off/def awareness bundle these type of court awareness attributes up and are very important - maybe the most important rating in the game. I want the development (or lack of) of these two ratings to end up being a key indicator of young guy's ceilings after a couple of years.
A guy might have strong ratings in other areas and still put up good or great stats, but over the course of a season there will be a lot of good things on the court that won't happen for his team if he has low awareness on either end.
Groundhog
11-06-2014, 11:30 PM
Been awhile since I last posted, but I've still been working away. The actual game engine is coming along nicely. I've designed a nice little code block that steps the sim through each step of a play. Each step has court positions for the offense, an attribute that determines roughly how long the step would take to run, and things like screens/passes/cuts etc.
Example:
{"playid": "quick_1_pnr", "step": 001, "offpos": {"pos": {"1": 12, "2": 14, "3": 10, "4": 4, "5": 12}}, "passtarget": 0, "shottarget": 0, "screen": ("5", "1"), "step_duration": 1, "possession_ballhandler": "1", "cut": (0,0)}
I've edited it a little as it's actually one dictionary entry embedded in a much bigger dictionary, but that should give you a fairly good idea of how it works. It may seem weird that position numbers are strings rather than integers, but it's due to something I coded while still a n00b and I actually like it now because it helps me easily pick numbers that refer to basketball positions as opposed to variable values.
The code block iterates through each value and actions anything required. The coolest thing about this is that, by storing the steps of a play in this format and separately from the main code of the game engine, in theory it opens up the door as far as allowing people to design their own plays. Obviously that's a "long term" thing, but it's something that would be cool to do if I could do it right.
Outside of that, I've been dabbling in other, more long-term, areas of the sim, especially when I get stuck on something or burnt out tracking loop control issues. I've put a lot of work into the generation side of things as far as players and teams go. After a lot of website text scrapping (thank god for rabid football-loving fans worldwide and their wiki site updating skills!) I have name files and country/city files for about 120 countries.
My idea is that you will be able to enable/disable any country's national league. Enabling it will generate a league randomly, at this stage made up of the same number of teams that the country has IRL in it's national league, as well as up to two divisions. It would assign a team to a weighted random city from that country based on population. Turning off a country would mean that there is no national league, but each country would still have a pool of players generated to make up national teams etc. Of course I would also plan on including the ability to manually edit a league to match reality, but I need to figure out how that will work for teams located in cities not included in the initial file - I have a hacky method of weighting the random choices that is quicker than the standard way to do it (given the size of the numbers that make up populations), but does not lend itself to easily allowing a user to simply append a city to a csv file.
On the player side, each country has 3 attributes that directly effects player generation - talent (1-5, from poor to NBA), height(1-short, 2-normal, 3-tall), and athleticism (similar, 1-3). So, for example, USA would be a 533 national, while China might be a 232, Australia a 322, South Korea, Spain a 422, etc. I have coded real early generators for all of the above, but until I'm at the stage where I can start simulating entire games, let alone seasons, it hasn't really been possible to judge how balanced it is.
LastWhiteSoxFanStanding
11-09-2014, 03:31 AM
Wanted to let you know I am following with great interest. Keep it up! :)
Groundhog
12-08-2014, 05:18 PM
No updates in a while, but I'm still plugging away. On the sim-side of things, it's largely just finishing off event blocks and then tying everything together via loop control to make sure the transitions between events happen as they should. I'm pretty happy with everything I've got coded so far, and I've started forcing myself to K.I.S.S. a number of times, leaving comments of '#TODO: "Insert crazy idea here"' throughout my code for things I can come back and look at trying once I've got more of the sim done. I spent about 4 days working on rebounds - tipped rebounds, FT rebounds, airballs, etc. - when I decided it was probably time to scale it back a bit until I've got more done!
One of the key features of my sim is that it will be very easy to tweak just about every calculation. I have a function called "base10" that sets the chances of an event happening for a player with the rating of 10 for the skill to be tested, and a "base10_mod" to be used as a +/- modifier applied for each point the player is rated over/under a 10. All the base10 percentages and mods are stored in an ini file with the sim pulling in the values each time it runs. This applies for every main calculation - from shooting %, alley-oop pass success, spotting open players, etc.
I did like Nol's 'flair' mention above, which led me to incorporate a court awareness rating in addition to off/def aware. At this stage it's solely related to checking for players via the pass value work I mentioned above.
I've made the switch from sqlite to SQLAlchemy for my database stuff. SQLAlchemy makes database queries more python-esque and, though I haven't yet "mastered" it for the basic stuff I'll be using it for, I find it easier to work with than using SQL statements.
I spent a bit of time the past couple of days working on player generation. I mentioned earlier that each nation has a set of attributes that affect player generation. What I worked on yesterday was generating base values for speed and strenght based on a player's height and weight. Each height has a base set of characteristics, and then a point where penalties/bonuses can apply to speed or strenght attributes following that.
I don't have the actual settings handy, by for example a 6'10 player might default to 245lbs with a rating of 11 for strength and 6 for speed. If the player in question is generated weighing 285lbs, there is a 50% chance his strength will increase and speed will decrease by 1 for each multiple of 10 over a limit - say 260lbs. So in this case there's a chance the 6'10 guy will have a 4 for speed, and 13 for strength. This is all just to create the base ratings of the guy, which would change once a template is layed over his ratings - 'outside shooter', 'rebounder', etc. so there's a chance they could jump/drop further.
CraigSca
12-08-2014, 09:04 PM
Always love seeing these updates - I'll have to check out SQLAlchemy, never heard of it.
Interesting to hear about your use of templates - in earlier days creating attributes for me was essentially a series of independent variables, which obviously doesn't happen in real life.
Keep it up!
Groundhog
12-08-2014, 09:29 PM
Thanks!
It's early days with the templates, but similar to height/weight there's a base template for each position - such as say 7 for offensive rebounds for a PF. Then there's a random range it can vary based on the nation's talent score - so a low talent country might range from -6 to +2 for that rating, while an American player might be -4 to +10. This would give some variety already, but I'd like to have a chance of additional templates to be laid over players - both positive and negative. A 'gunner' template might raise the 2point/3point aggression ratings well above the 2point/3point ability ratings etc.
The main thing I want to strive for is generated players have attributes that make sense for their position and heights/weights, but also provide variety and allow for some interesting styles of play.
Re: SQLAlchemy I'd definitely recommend it if you are more comfortable with Python than you are with SQL. Creating dbs and tables and the like is no more complex than creating Python classes.
A good tutorial:
http://www.pythoncentral.io/introductory-tutorial-python-sqlalchemy/
Groundhog
12-09-2014, 11:07 PM
Another quick update...
Finished the logic for dunks today. I'd put this one off - the other "shooting related" code was some of the first I finished off - because I had to work out the requirements for a player being able to dunk.
Initially I had a 'dunk' rating that I was going to use, which at player creation would factor in the player's jumping ability/height/athleticism etc. to come up with the rating, but I decide to scrap that and just use a mapping of height to jumping ability to determine a player's ability to dunk.
I mapped out a list of requirements for a player to be able to dunk in different situations - wide open, in traffic, alley-oop - in 3 categories of "chance" - minimum, average, and easy. I don't have the figures handy, but for example a 6'2 guy in the key might need a jump rating of 7 to dunk at all, a rating of 9 to dunk averagely, and a rating of 12 or more to dunk easily.
This is also used not just to determine the success of the dunk, but also the player's decision to attempt the dunk at all. A guy just hitting the minimum dunk requirements probably shouldn't be trying too many, and his 'offensive awareness' rating is checked to see if he lays it in instead. It's also checked by the passer in the decision whether to throw an alley-oop pass or not, again following an 'offensive awareness' check.
This is all done within the main "shot" code - a player takes his position into account and if he's at the basket, it checks to see whether to action the dunk or a regular shot. Similarly, the "pass" code factors in the recipient's position and will action an "alley oop pass" if the conditions are met.
I haven't completed the code for the alleyoop dunk finish as yet, but it will be a minor adjustment to the code I have for regular dunks.
GoldenEagle
12-12-2014, 12:53 PM
Sounds like you are having a lot of fun writing this plus getting better at programming.
Groundhog
12-15-2014, 04:39 PM
Sounds like you are having a lot of fun writing this plus getting better at programming.
Yeah, it's made learning programming 'fun', that's for sure. I bought the roughly 1,400 page 'Learning Python' by Mark Lutz and worked my way through it, and I was surprised to see how much of the book I'd already picked up through a couple of other more basic books and from just Googling. Outside of some of the mathematical stuff that I don't have much interest in, it was really only the chapters on classes that I found a lot of new content.
Groundhog
12-29-2014, 12:46 AM
I've moved away from templates for players. Now I've set 5 ranges for player abilities, from terrible to great, and set a chance for each position to fall in each range.
Here's an example of the players generated from different talent tiers. Speed/strength are affected by height and weight, but there is a chance that a player is an exceptional athlete, which can increase the strength of the player but right now is not adjusting the weight when triggered, so a 6'10 SF can be a 17 for strength while weighing just 198lbs. 3 point ability is the only rating that is being generated using the new method, and needs to be improved for talent tiers 1 through 4 as too many guards are being generated with lousy 3P ability. Age is also just a number here, eventually it will affect the players ratings positively or negatively, depending on the age.
Chad roster, talent tier = 1, player heights = taller than average:
NAME: Armand Issa POS: PF
AGE: 24 HGT: 6'10 WGT: 214
SPD: 12 STR: 17 3P: 1
NAME: Abakar Nadjibang POS: PF
AGE: 36 HGT: 6'8 WGT: 255
SPD: 6 STR: 11 3P: 1
NAME: Habib Hissein POS: C
AGE: 23 HGT: 7'1 WGT: 239
SPD: 3 STR: 12 3P: 1
NAME: Mbaimamgone Madjiyedjim POS: C
AGE: 32 HGT: 7'0 WGT: 261
SPD: 5 STR: 14 3P: 1
NAME: Dominique Aldongar POS: SF
AGE: 24 HGT: 6'9 WGT: 255
SPD: 5 STR: 11 3P: 3
NAME: Oumar Kabir POS: SG
AGE: 30 HGT: 6'2 WGT: 216
SPD: 10 STR: 3 3P: 2
NAME: Hilaire Banio POS: SG
AGE: 28 HGT: 6'3 WGT: 203
SPD: 10 STR: 6 3P: 12
NAME: Roland Teinkor POS: GF
AGE: 32 HGT: 6'7 WGT: 217
SPD: 8 STR: 7 3P: 2
NAME: Position Kédigui POS: G
AGE: 20 HGT: 6'6 WGT: 214
SPD: 8 STR: 6 3P: 9
NAME: Dominique Banio POS: PG
AGE: 30 HGT: 6'0 WGT: 193
SPD: 12 STR: 3 3P: 10
New Zealand, talent tier = 2, league height = average:
NAME: Jamie Hardie POS: PF
AGE: 22 HGT: 6'8 WGT: 254
SPD: 5 STR: 5 3P: 1
NAME: Glen Toone POS: C
AGE: 32 HGT: 6'11 WGT: 265
SPD: 4 STR: 13 3P: 5
NAME: Tom Morgan POS: PF
AGE: 27 HGT: 6'8 WGT: 255
SPD: 5 STR: 6 3P: 11
NAME: Whetu Hemopo POS: F
AGE: 23 HGT: 6'7 WGT: 233
SPD: 7 STR: 7 3P: 1
NAME: Junior Ellison POS: G
AGE: 22 HGT: 6'2 WGT: 189
SPD: 8 STR: 2 3P: 8
NAME: Liam Moeke POS: SG
AGE: 29 HGT: 6'5 WGT: 221
SPD: 12 STR: 11 3P: 5
NAME: Whetu Bates POS: SG
AGE: 22 HGT: 6'3 WGT: 213
SPD: 14 STR: 6 3P: 1
NAME: Jack Bradley POS: G
AGE: 27 HGT: 6'0 WGT: 195
SPD: 13 STR: 1 3P: 8
NAME: Andre Curtis POS: G
AGE: 35 HGT: 6'1 WGT: 175
SPD: 14 STR: 3 3P: 1
NAME: Tama Cleaver POS: PG
AGE: 28 HGT: 6'1 WGT: 207
SPD: 13 STR: 2 3P: 5
Crotia, talent tier = 3, league height = average:
NAME: Tomislav Grgic POS: PF
AGE: 23 HGT: 6'8 WGT: 258
SPD: 3 STR: 7 3P: 1
NAME: Miljenko Blazevic POS: FC
AGE: 31 HGT: 6'7 WGT: 215
SPD: 11 STR: 6 3P: 6
NAME: Stipe Situm POS: SF
AGE: 33 HGT: 6'6 WGT: 201
SPD: 9 STR: 8 3P: 1
NAME: Hrvoje Marusic POS: SF
AGE: 31 HGT: 6'4 WGT: 193
SPD: 9 STR: 8 3P: 15
NAME: Ninoslav Vucemilovic POS: GF
AGE: 26 HGT: 6'6 WGT: 233
SPD: 12 STR: 5 3P: 1
NAME: Borislav Kresinger POS: G
AGE: 25 HGT: 6'4 WGT: 206
SPD: 10 STR: 5 3P: 11
NAME: Mario Tomecak POS: GF
AGE: 22 HGT: 6'6 WGT: 213
SPD: 9 STR: 8 3P: 5
NAME: Ivan Vidakovic POS: GF
AGE: 22 HGT: 6'5 WGT: 213
SPD: 10 STR: 7 3P: 5
NAME: Boris Parazajder POS: G
AGE: 26 HGT: 6'2 WGT: 215
SPD: 13 STR: 7 3P: 17
NAME: Ante Turina POS: G
AGE: 24 HGT: 6'5 WGT: 228
SPD: 11 STR: 9 3P: 1
France, talent tier = 4, league height = average:
NAME: Guillaume Cairo POS: C
AGE: 29 HGT: 6'11 WGT: 205
SPD: 6 STR: 5 3P: 7
NAME: Julien Camerling POS: PF
AGE: 23 HGT: 6'7 WGT: 245
SPD: 7 STR: 8 3P: 6
NAME: Romuald Masson POS: SF
AGE: 22 HGT: 6'5 WGT: 192
SPD: 7 STR: 3 3P: 1
NAME: Ximun Gomis POS: GF
AGE: 27 HGT: 6'4 WGT: 208
SPD: 18 STR: 12 3P: 5
NAME: Fernando Vauvy POS: SG
AGE: 20 HGT: 6'5 WGT: 224
SPD: 14 STR: 8 3P: 12
NAME: David Hamed POS: SG
AGE: 24 HGT: 5'11 WGT: 203
SPD: 14 STR: 2 3P: 6
NAME: Carl Soubervie POS: SG
AGE: 35 HGT: 6'5 WGT: 230
SPD: 12 STR: 10 3P: 4
NAME: Ludovic Bouraoud POS: GF
AGE: 33 HGT: 6'7 WGT: 207
SPD: 13 STR: 12 3P: 4
NAME: Jacques Obriot POS: PG
AGE: 22 HGT: 5'9 WGT: 184
SPD: 16 STR: 4 3P: 2
NAME: Gerard Laurent POS: PG
AGE: 23 HGT: 6'1 WGT: 180
SPD: 14 STR: 4 3P: 12
And finally USA, tier = 5, height = above average:
NAME: Mike Harvey POS: C
AGE: 27 HGT: 7'0 WGT: 254
SPD: 7 STR: 14 3P: 10
NAME: Tanner Reed POS: C
AGE: 23 HGT: 7'1 WGT: 249
SPD: 4 STR: 11 3P: 1
NAME: Kirk Massimino POS: C
AGE: 22 HGT: 7'1 WGT: 257
SPD: 4 STR: 11 3P: 5
NAME: Christian Williams POS: SF
AGE: 30 HGT: 6'9 WGT: 212
SPD: 9 STR: 10 3P: 15
NAME: Shane Major POS: G
AGE: 37 HGT: 6'4 WGT: 210
SPD: 9 STR: 14 3P: 6
NAME: Jordan Richard POS: G
AGE: 22 HGT: 6'6 WGT: 212
SPD: 14 STR: 8 3P: 5
NAME: Robert Davis POS: G
AGE: 30 HGT: 6'8 WGT: 227
SPD: 7 STR: 7 3P: 10
NAME: Matt Lang POS: SG
AGE: 24 HGT: 6'6 WGT: 231
SPD: 10 STR: 8 3P: 11
NAME: Rasean Rashford POS: G
AGE: 29 HGT: 6'7 WGT: 217
SPD: 7 STR: 6 3P: 5
NAME: Evan Robinson POS: PG
AGE: 20 HGT: 6'3 WGT: 222
SPD: 12 STR: 4 3P: 5
Groundhog
01-05-2015, 01:47 AM
I was so sick of using excel that I decided to create my first UI for the sim, the editor screen for player generation:
http://i58.tinypic.com/flbytl.png
SirBlurton
01-05-2015, 10:33 AM
I'm blown away by how quickly you're progressing on this project - it looks fantastic!
I've been working on a much simpler project for a much longer period of time with much less to show for it!
What did you use to make you UI?
Was also wondering about the names in your samples - do you have a system for generating names based on nationality? I found a names module but it's strictly for US nationals.
Groundhog
01-05-2015, 04:23 PM
Thanks! :)
The UI was made with QT Designer, which is a very easy to use, graphical form/widget builder - it took me maybe 30 minutes to create the form above as far as layout goes. There is a Python plug-in called PyQT that then takes the QT UI file and translates it to a Python script, so all the actual functionality of the buttons/comboboxes etc. can be done in the Python language.
The functionality part of the above form is still a WIP - it forced me to re-do how I was storing the settings for the generation stuff, using Python's pickle function which I didn't bother learning before because I didn't see how it would be useful... until it turned out to be exactly what I needed, as seems to often be the case. :)
Re: the name files, I have 80 nations done so far, some I put together manually for FBB over the years, but most of the others were done by text-scraping wiki team pages for domestic soccer leagues, as a surprising number of English-language wiki pages have a detailed rosters. There's still a manual step of going back and deleting out foreign players from each league, but it's still quicker than the old "copy+paste+edit" method I used to use.
The current plan is to have 100 different nations available, but that will change when I start to look at World Cup qualifications way, way down the track, making sure I have a balanced amount of teams per the 5 FIBA regions.
johnnyshaka
01-06-2015, 02:40 PM
AWESOME READ!
I've often tried to learn Python over the past dozen years or so but always petered out after a month or two for whatever reason. Having a goal to make a game (or coding something you're obviously passionate about) makes so much sense and I wish I had thought of that during my several go arounds at learning it.
I may give this another try sometime soon but first need to find a good idea that I'm passionate about to drive it along. Thanks!
Good luck and keep us posted, GH!
Groundhog
01-06-2015, 06:20 PM
Thanks for reading! Yep, it's definitely helped being able to apply what I'm learning to something I've always wanted to do, 1000x.
Groundhog
01-08-2015, 05:26 PM
I spent far too long yesterday working on generating player's home towns/states. For international guys it's pretty simple - I have generally between 20-50 cities listed for a nation, ranked by population, and the chance of the top city being picked over the bottom ranked city is x times the chance, where x = total cities. For example, if CityA is #1, and CityB is #30, CityA is 30 times more likely to be picked. This is a simple calculation, and I can quickly pool together all the cities I'd need to populate the generated players.
It gets more complex in the US because we bring states in to play. The first thing I did was take all the data I'd scraped on all Div 1 men's college basketballers to get the frequency of players coming from the different states. Once I have the state, it's time to fetch the city.
Given how easy it is to get an enormous list of US cities with population numbers, I thought I'd be clever and setup completely realistic distribution of cities per state - ie. if 71% of people from North Dakota come from Fargo, same would be true here. This was fairly easy to do but incredibly slow - with a list of around 10,000 cities, it took 19 seconds to return 100 player home towns.
I ended up trimming the cities list considerably, 30 a state where possible, and then using the same method as with international cities. Technically it was hours of work wasted, but hey, I learnt a few things too. :)
Groundhog
01-12-2015, 04:27 AM
Fully functional UI:
http://i60.tinypic.com/o1064j.jpg
(Very) WIP upgrade to the above UI:
http://i59.tinypic.com/16acgle.jpg
Groundhog
01-26-2015, 04:49 PM
My initial plan in my earliest posts were for the sim to be browser based, rather than an installed, UI-type program. The reason I went with a regular software GUI was mainly because I figured going browser/web-based would be too difficult as I have no experience with it outside of simple HTML. With all the difficulties I was having getting my UIs to function as I wanted them to, I revisited the web/browser idea.
Python has a number of web frameworks available, and the two I experimented with were Django and Flask. Django is very simple - you can have a decent blog-type site with an admin page is no time at all, and it's pretty perfect for that kind of setup nearly out of the box. Trying to tweak it into something closer to what I need proved a bit more difficult, so I tried Flask.
Flask is a lot more lightweight than Django out of the box, and has a ton of extensions to allow you to plug in" features like login pages, admin pages, database support, etc. as/if needed. It also feels more "Python" native to me than Django did, and I was able to create a pretty simple site after a few days of reading tutorials and tweaking.
I'm far from an expert, but after one weekend I feel more comfortable with Flask than I ever did working with Qt UI forms, so that's the direction I'm going to be moving forward with.
CraigSca
01-27-2015, 05:31 AM
Interesting. I have a book that I've been looking at that's devoted entirely to Qt. Seeing that you were using it, too, I considered it a no-brainer. Will have to check out Flask - I had a friend recommend to me CherryPy as the framework du jour. So many choices...
Groundhog
01-27-2015, 04:23 PM
Let me put it this way - if I were building a desktop application in Python, I would use Qt in a heartbeat for the UI. Coupled with Qt Designer it makes designing forms and adding widgets a breeze, and a lot of the difficulties I had with it would have been solved if I had a book rather than relying on internet tutorials.
The big thing for me though is I wanted a UI that looked a bit more modern and I spent a good 3 days designing a combobox that worked like the ones in FM - a dropdown selection, coupled with left and right arrows. I ended up with a hackish widget that worked but wasn't close to what I was shooting for, and the whole time I was thinking to myself that I could do this in html in no time at all. I've seen some great UIs done in Qt, especially some of the 3D modeling apps I've seen, but that's way beyond me.
I did actually download CherryPy but I got so caught up in this detailed Flask tutorial that I never got a chance to try it: http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world
Groundhog
02-01-2015, 03:51 AM
A quick look at my first attempt at a web UI. The only part that is functional as far as actual database calls are concerned is the blue team name/info box, but I'm really just trying to get a feel for the layout at this stage. Eventually the colour of the team info box will be set per team (ie. Celtics green/white, Timberwolves blue/white), and I'd also like some fast-navigation links at the top above the team box, like: Leagues > NBA > Rosters > Cleveland, as well as an export to csv button below the roster.
http://i60.tinypic.com/34ga4og.jpg
Groundhog
02-01-2015, 04:43 AM
Updated:
http://i57.tinypic.com/rubsqv.png
CraigSca
02-02-2015, 09:55 AM
Updated:
http://i57.tinypic.com/rubsqv.png
Omg, so cool!
Groundhog
02-02-2015, 04:46 PM
The best part is that it took me about 4 hours to put that together - most of that time spent learning how to use the Bootstrap css and brushing up on html. For me to design something like this in Qt, while no doubt possible, is just way beyond me right now.
Of course, I still need to get all those tabs actually doing something... Tonight I'm going to work on loading the roster info from the database, and hopefully start on the basic player page, and having a link to it from the roster page.
GoldenEagle
02-11-2015, 11:09 AM
Are you using Flask as your web framework? I know you mentioned it, but I am not sure is that is what you settled on or not.
Groundhog
02-11-2015, 04:03 PM
Yep, I've been using Flask. It's pretty simple for the basic stuff I've been using it for - different URLs pointing to different templates and database queries, and the harder stuff I had some troubles with - authentication/authorization in particular - I've shelved for now.
Groundhog
02-11-2015, 04:28 PM
Once again I took for granted how difficult something, in this case marrying up the Jinja2 web templating and Flask web frameworks, would be. :)
What started as 'Groundhog learns Python' is now more like 'Groundhog learns Python, the Flask web framework, Jinja2 web templating, HTML, CSS, and SQL'.
On the Python/basketball simulator side of things, although there's still a long way to go, it's been awhile since I have struggled to come up with a way of doing what I wanted to. It's really just plugging away, picking up small bugs here and there, and refining a lot of the stuff I coded at the beginning. I have a big list of 'TO-DO' code blocks that I cross off as I get to them, but it has taken a back seat to learning all the other stuff, as I tend to find that each component is usually a few weeks of work in itself to get a handle on, at best.
I'll have some more screenshots of the web interface tonight. I have a fully functional roster page that pulls its data from the database. I created a couple of NBA rosters with info, ratings, and stats. One thing that I'm not happy with but haven't found a great solution for is having the roster page not re-fresh/reload from the database each time a nav button is selected. Ie. clicking the 'stats' button in the pic above doesn't just load the bottom section of the page with data already queried, it refreshes the entire page and queries just the data needed for that one section. It's not a big deal when I'm hosting it locally and running queries on my lonesome as it loads instantly, but I'm not sure how it would perform on an actual webserver with multiple people running queries.
CraigSca
02-11-2015, 07:47 PM
Looking forward to the update. Because of your work, I'm now vested in learning Flask, HTML, CSS. My goodness, what a pain. It's not easy being a single programmer anymore.
Groundhog
02-12-2015, 02:20 AM
http://i62.tinypic.com/5ofb86.png
Since we've got some folks who are interested in Flask, I might break down quickly how I put this together, with the disclaimer that it's probably not the best way to do it - it's just the way I got it to work. :D
#1: My base template that, while also providing the main header and navigation icons, also sets up the css files for all the child templates. The selections available here are determined by variables, with more options available for users that are logged in.
#2: The rest of this page is all the one template, starting with a 'well' from the Bootstrap CSS file containing the team information. The background/foreground colours are defined in the database per team, and the logo filename is built by searching for 'leaguename_team_name.png', and showing a generic logo if not found. The default Bootstrap settings make this div class too spread out, and I will be reducing the distance between the text, as well as decreasing the size of the well box.
#3: Nav tabs, with the current page triggering which nav tab is 'active' (selected). Clicking on any of these options loads a different link, which actually points to the same template html file as above, but with different variables being triggered to present a different view and setting the appropriate nav buttons to 'active'.
#4: As above - loads the same template html at different URL, which leads to different variables/SQL queries passed to the template to present different information.
#5: Dynamic links using the player's unique ID in the database to (eventually) point to the individual player pages.
Groundhog
02-16-2015, 09:40 PM
I took a break from the web stuff today to start the initial work on the finance side of the sim.
I want to include at least two different type of financial systems, one that mirrors the NBA (as close as possible, anyway), and one that tries to emulate a generic European-style of non-salary cap roster building, that scales per the quality of the league. The overall idea is to not focus on all the different individual finance rules that exist around the different basketball leagues, but to implement one system that is consistent across all non-NBA leagues, and scales neatly up and down.
Each nation is rated in a number of categories, like economy/popularity/talent, and these ratings determine arena sizes, ticket prices, average salary range for local players, etc. Cities within each nation are ranked by population size which means the biggest cities will tend to have the biggest arenas, but there is a random element to it.
Ticket sales, both in the domestic leagues as well as the Euroleague, will provide much of the income, but there will also be a 'misc income' category. For 'fluff' purposes it would cover sponsorship, concession funds, jersey sales, etc., but in game-terms it's the x-factor I will use to help keep budgets in line with the type of numbers I'm after.
On top of the basic finances, each team has an owner with some willing to provide more money out of pocket to finance a team than others - again this scales with the league quality. I'd like rare cases of teams like CSKA who are rolling out some crazy-high salaries compared to other top teams in Europe, and in my sim this would be reflected by a wealth owner offering something ridiculous like 300% of a usual team budget.
I researched as many import rules as I could for the different leagues, as well as restrictions on how many nationals are needed in some leagues, and for those that I couldn't discover I used an average. What I don't want is top teams in Spain having a ton of money used on the top international players, and then one or two Spanish players if any - this way they are forced to have 6 (from memory) Spaniards, and only 4 non-Europeans.
Also to help keep the leagues looking realistic as far as roster makeup goes is the fact that players will expect more money to play in a foreign league, making it cheaper to get a similar player who is a national than a foreign player, and players from higher-talent nations will expect more on top of that to play in a lower tier league.
Top-flight local talent from each nation will still expect top-flight money, but the range of wages for the middle-tier to low-tier talent is defined as per the quality of the league.
All of the above is currently just a series of dictionary entries in a python file, but considering this morning I had not really given this side of the sim any thought, I feel like I've got a pretty good feel for how it's going to work now.
Groundhog
02-19-2015, 04:57 PM
Bought this yesterday to help with the Flask side of things: Flask Web Development: Developing Web Applications with Python: Miguel Grinberg: 9781449372620: Amazon.com: Books (http://www.amazon.com/Flask-Web-Development-Developing-Applications/dp/1449372627)
Written by the same guy who put together the Flask tutorial I linked above. Two chapters in and already it's been a huge help.
Groundhog
02-23-2015, 05:43 PM
Add Java to the list of things to learn. I've always been hesitant to learn java because I deal with so many crappy java applets every day at work, but it seems like the easy solution to cutting down on the amount of templates required.
To use the roster page as an example again, clicking through to the roster page now loads all the tables needed for each of the Info / Stats / Ratings / Salary nav pills. Using javascript I hide the tables excluding the default selected (Info). Clicking a different nav pill sets the icon to active, hides Info, and reveals the appropriate table. This would mean a slightly largely db query as the page loads, but significantly less reloading when navigating the tabs.
I'm going to experiment with loading ALL the info for the team, including the top nav tabs (Roster / Staff / Team Info). This would mean a lot more hiding/unhiding java scripting logic as well as potentially longer load times depending on how much info is included on these other tabs, but browsing through the different tabs would be instantaneous once loaded.
I also made some changes to the navigation menus and style based on some templates found online. I'll post some more screenshots when I've cleaned it up a little bit.
Young Drachma
02-23-2015, 08:52 PM
You're making great progress! I haven't checked in in a while but looking now and you're getting places!
GoldenEagle
02-25-2015, 02:07 PM
I think you are confusing Java with JavaScript. They are two completely separate languages.
Java is what I write code at my job. It is more for enterprise back-end type stuff.
Javascript is more for the web. You will definitely need to learn for a project like this. If found the website javascriptissexy.com useful, but I think he has stopped updating it.
Groundhog
02-25-2015, 06:06 PM
You are completely correct. :D
I've really just touched on jquery and datatables right now as far as javascript goes (along with some of the simpler bootstrap-specific stuff), which is pretty simple for putting together the different views on the roster page. I think the next challenge will be working out how to link variables between Python-->Flask-->Jinja-->Java-->Flask-->Python.
GoldenEagle
02-26-2015, 10:38 AM
I would suggest looking into JSON for that.
Which will be another thing to learn :)
GoldenEagle
03-03-2015, 10:41 AM
Also, I thought you might find this interesting:
Exploring NBA Data with Python (http://www.danielforsyth.me/exploring_nba_data_in_python/?utm_content=bufferf8ac0&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer)
Groundhog
03-03-2015, 08:49 PM
That's a really good read actually. Looking forward to playing around with that tonight.
Groundhog
03-15-2015, 06:25 PM
Spent the last week focusing solely on the basketball/python aspect of this project. For the fourth or fifth time I've re-designed the main game loop entirely, and I've now got something that is consistent and I don't envision having to change it again.
I understand Thomson's rule for first-time telescope makers now, as changing the format of the loop means rewriting large portions of the code, especially keeping track of how variables are passed around the loop. I spent as much time in Visio and Google's Draw as I did in the notepad.
In case it's of any interest to anyone, this is a quick summary of the new structure:
1. Pre-loop
When the script is launched, it loads the team rosters and information. Right now the teams are pulled from the db with the team id # being coded in, but eventually the simulation code will be inside a Python Class (most likely), and passed the team IDs based on the schedule.
The league rules are also queried from the database to get things like amount of periods, period length, max fouls, 3pt shot enabled, etc.
If the DEBUG flag is set to True, the loop will be passed to whichever event I want to simulate. If False, it will print the game introduction text, before launching the game loop, pointed at the opening jumpball.
2. Game Loop
The main loop is a 'while my_variable == True' loop that keeps the loop alive until the game is finished. Underneath that, the top-level loop control is done via a variable named 'loopstate'. Each 'loopstate' else/if block is made up of related sub-else/if blocks for different events.
For example, there's a 'loopstate' named "action_midstates". Underneath this else/if block is the logic for all of the calculations to be made prior to the next step of the sim - such as subtracting time from the clock, checking for injuries, calling timeouts, etc. Once the loop is passed to "action_midstates" the block is also passed a variable named "iterator_logic" to determine which of these midstate events to simulate for that situation. For example, if there is a defensive rebound made, the loop will be passed to "action_midstates" with an "iterator_logic" value of 'defensive_rebound', which will calculate things like updating the team with possession, a check for time-outs, desperation shots, etc. but not things like substitutions that wouldn't make sense in this situation. I have a separate config file that defines all the midstates to be simmed per iterator_logic value, which makes it really quick to re-order or change.
One of the midstates will check for the end of the period, and once the final period has concluded, the top-level While loop will be set to False.
All other loopstate blocks work roughly the same way, with sub-else/if blocks that point to more specific events. There is a block for events such as shots/rebounds, for the playbook iterator, and for the strategy decisions. The last two are very WIP.
3. Post-game
Not yet implemented, this will simply write the results to the database.
JetsIn06
03-18-2015, 07:58 PM
I don't think a thread has ever made me feel so lazy. I love this, it's inspiring...keep it up!
Groundhog
03-24-2015, 09:59 PM
and I've now got something that is consistent and I don't envision having to change it again.
Tsk tsk tsk... Groundhog, when will you learn. :D
I was working my way through the tutorials hosted on the excellent Program Arcade Games With Python And Pygame (http://www.programarcadegames.com) site. Although it's geared towards developing skills using the python module 'PyGame' to make graphical games, it has some good theory around class objects, and in particular converting programs from the basic top-to-bottom python script to python classes.
This is the first time it really clicked with me. It's one thing to read about classes in books with examples on basic basic inheritance and variables 'x' and 'y', but to see how a program can be transformed from a script to a class in practice really made all the difference to me.
The good news is that nothing is really wasted re: the restructure I did. It's just now moving things around a little and changing the way my variables are stored. Although the later is quite a bit of work really as far as changing references throughout the code, a lot of the headaches I had passing these variables between my main game file and the files I used to store commentary and other common functions will become a thing of the past.
It's also simplified the structure of my game loop, with it now centering around each possession of the game, rather than one big loop that begins at opening tip, progresses through each phase of the basketball game until the end of the last period.
Groundhog
03-25-2015, 04:51 AM
I should also add that I'm leaning back towards making this a desktop application rather than web-based. Not so much due to difficulty, but I think it's a bit too ambitious at this stage.
I do really enjoy working with Flask though - it's something I would consider doing for a career to be honest.
CraigSca
03-25-2015, 09:13 AM
Just so you know - make up your mind with this stuff! There are some people around here that are following what you're doing and letting you make their decisions for them for their own projects :)
Would love to hear what tools you'll be going back to for the desktop.
Just so you know - make up your mind with this stuff! There are some people around here that are following what you're doing and letting you make their decisions for them for their own projects :)
Would love to hear what tools you'll be going back to for the desktop.
This gave me a good laugh!
Great read though, and nice work Groundhog!
Groundhog
03-25-2015, 05:23 PM
Just so you know - make up your mind with this stuff! There are some people around here that are following what you're doing and letting you make their decisions for them for their own projects :)
Would love to hear what tools you'll be going back to for the desktop.
:D
Yeah believe me, I get it. I think the usual cycle is: 1) discover new technique/module/framework. 2) Get excited, spend weeks learning it. 3) Realize that it's a lot more involved than I first thought, as far as getting it to do exactly what I want. 4) Concentrate on the pure-python sim-stuff once again.
The positive side is that I feel like I haven't actually wasted any time, either. The Flask/Jinja2 framework, and the bits and pieces of javascript/jquery and datatables I've learnt, have really impressed me and whatever form the sim takes it will need to generate web content.
Qt would still be the choice as far as desktop interfaces go, and there are books on Python & QT (http://www.amazon.com/Programming-Python-Prentice-Software-Development/dp/0132354187/ref=sr_1_1?ie=UTF8&qid=1427321272&sr=8-1&keywords=python+qt) that I'm just going to have to push through. IMO there's just nothing else (free) out there that makes better looking UIs for python programs.
As far as your own programs go, nothing has made me doubt the choices I've made so far - Python for an easy-to-learn yet still powerful programming language, Flask for web applications, and Qt for desktop applications.
Groundhog
04-12-2015, 04:17 AM
I spent the last couple of days working on a neat little debug module to make it a lot quicker to test if everything is working as it should, by scripting a bunch of scenarios for different events, which will then be simulated and checked. Doing this manually each and every time I made a change was just taking way too long.
First time I ran through the FT debug I encountered a rare bug that caused a guy to shoot FTs forever... I'm calling it the 'Josh Smith bug'. :D
Here's a quick view of some of the output:
Beginning debugs...
Beginning 'action_event' states...
Opening jumpball:
Opening jumpball.
------------------
T. Mozgov and J. Noah are in the middle for the opening tip.
The ball is up.
T. Mozgov tips the ball to K. Irving, possession is with Cleveland.
possession_begin_state: tip-middle
Fouls:
Fouls: Offensive
----------------
Off. fouler location: 9, next inbound location: inbound-opposing-sideline
Turnover assigned.
Team turnover assigned.
PF assigned.
PFA assigned.
Fouls: Defensive, no shooting
------------------------------
Ballhandler location: 6, next inbound location: inbound-own-sideline
PFA assigned.
PF assigned.
Team PF assigned.
Fouls: Defensive, shooting
---------------------------
PFA assigned.
PF assigned.
Team PF assigned.
Next possession 'free_throws'.
Fouls: Defensive, penalty fts
------------------------------
PFA assigned.
PF assigned.
Team PF assigned.
Shooting foul triggered by TF count.
Next possession 'free_throws'.
Freethrows:
FT Type 1: Standard FT, 1 shot.
-------------------------------
K. Love is at the line shooting one free throw.
The shot is good.
possession_begin_state: inbound-opposing-baseline
shooter points: 1, FTM: 1, FTA: 1
FT Type 1: Standard FT, 2 shots.
---------------------------------
L. James is at the line shooting two free throws.
The shot is no good.
The shot is good.
possession_begin_state: inbound-opposing-baseline
shooter points: 1, FTM: 1, FTA: 2
FT Type 1: Standard FT, 3 shots.
---------------------------------
D. Rose is at the line shooting three free throws.
The shot is good.
The shot is good.
The shot is no good.
rebound triggered.
possession_begin_state: free_throws
shooter points: 2, FTM: 2, FTA: 3
FT Type 2: FT no rebound, 1 shot.
----------------------------------
J. Butler is at the line shooting one free throw.
The shot is good.
possession_begin_state: inbound-own-sideline
shooter points: 1, FTM: 1, FTA: 1
FT Type 2: FT no rebound, 2 shots.
-----------------------------------
M. Dunleavy, Jnr. is at the line shooting two free throws.
The shot is good.
The shot is good.
possession_begin_state: inbound-own-sideline
shooter points: 2, FTM: 2, FTA: 2
FT Type 2: FT no rebound, 3 shots.
-----------------------------------
P. Gasol is at the line shooting three free throws.
The shot is good.
The shot is good.
The shot is no good.
possession_begin_state: inbound-own-sideline
shooter points: 2, FTM: 2, FTA: 3
FT Type 3: one-and-one, 1 shot (potentially).
----------------------------------------------
K. Love is at the line for the 1-and-1 opportunity.
The shot is good.
The shot is no good.
rebound triggered.
possession_begin_state: free_throws
shooter points: 1, FTM: 1, FTA: 2
Groundhog
07-02-2015, 08:37 PM
Been awhile since my last update, but this isn't dead. I was overseas for a month and a half, but the past week or so I've really kicked back into gear. Nothing sexy to show off, but I've made a couple of big changes to the structure of the sim that solved a few problems I was having around the timing of the functions that simulate the different events vs. the display of the play-by-play text.
just checking in :)
I know being off hurts my motivation for projects, but wanted to see what was going on if anything.
Groundhog
07-29-2015, 01:07 AM
Thanks for checking in - glad to know folks are still following along. :)
My process the past couple of weeks has been to alternate a few days at a time between the code that makes up the simulation engine, and working on other odds and ends. The past couple of days it's been the UI - something I'd put aside previously after the work I posted above.
Outside of this project I've been working on a BBCF -> FOF draft file generator with a GUI and that's helped me get comfortable with the Qt UI framework in Python via PySide.
I'm trying to come up with something that combines the clean look of the web-based UI screens I posted above with my favourite 'text sim' UI currently - Football Manager. As much as I like FM's UI, I still find it a bit cluttered - it's hard to avoid really, given the sheer amount of info and screens buried in the title - so I've been trying to simplify mine.
It's all still very WIP at the moment, but I'll post some screenshots tonight.
Groundhog
07-29-2015, 09:20 AM
Here's a peek at the roster page. The buttons are obviously placeholders. The roster info and print buttons will have rounded edges and maybe be images rather than text on a widget, and Menu 1 through Menu 3 will be images of some kind too.
The large blank panel will display the roster info... once I get my head around Qt Table Models, anyway. :D
http://i61.tinypic.com/egoob5.png
looks really good. Nice work!
Groundhog
08-01-2015, 03:38 AM
And now with a bit of functionality!
https://dl.dropboxusercontent.com/s/5yaycqj6i2f6esn/Untitled.jpg?dl=0
Young Drachma
08-01-2015, 06:52 PM
Love that you're still working on this. Very awesome and good progress!
Young Drachma
08-01-2015, 06:55 PM
I was so sick of using excel that I decided to create my first UI for the sim, the editor screen for player generation:
http://i58.tinypic.com/flbytl.png
Dola, I love this. I've wanted a game to have something like it for ages native and not within a third-party app.
Groundhog
08-02-2015, 07:09 PM
Me too! ;)
I'm working on adding the above into the new UI right now.
I spent sometime yesterday scrolling through the fonts on my PC to find something that works well with the banner-type menus. I finally found one that looked perfect, and was not surprised to see the font was named 'FBB'.... d'oh. :D
One thing I've really come to love about Qt is its use of StyleSheets. As with css in html, these really save a lot of time during the creation of UI elements, as well as making things like updates and customization a real breeze.
Every element on the screen is configured, from shape to colour to font, via the use of these StyleSheets within Python, rather than Qt Designer, which are then stored in their own 'css'-type file within the project.
The idea would be that, one day, some of the folks out there who are far more creative than me would have no trouble redesigning the UI if they wished.
Groundhog
08-03-2015, 08:36 AM
The semi-functional league generation settings screen - interested to hear any thoughts on the overall UI look:
https://dl.dropboxusercontent.com/s/mk9pmrlcjomh20q/player_gen.jpg?dl=0
Groundhog
08-22-2015, 02:10 AM
WIP of the player profile screen - with and without player profile image detected:
https://dl.dropboxusercontent.com/s/csp9ea6voqt4cnj/player1.png?dl=0
https://dl.dropboxusercontent.com/s/sz6eyoktcg92tbh/player2.png?dl=0
tarcone
08-22-2015, 06:52 AM
While this is mostly a foreign language to me, I have been following. I'm very interested in the end product and would be a play tester if needed.
Good job. This has been awesome to watch.
Young Drachma
08-22-2015, 07:04 AM
Pre-pre orders.
Groundhog
08-22-2015, 10:12 PM
Thanks guys! :)
CraigSca
09-06-2015, 06:49 PM
LOVE!
Incredible job!
Groundhog
09-08-2015, 08:04 PM
LOVE!
Incredible job!
Thanks, appreciate it! :)
And now for a boring post that might be of interest to anyone else that tries something like this...
When I started this project a little over a year ago, I felt the hardest thing would be coming up with a way to accurately simulate a game of basketball. Looking at it now, that was probably the easiest part. After 25 or so years of playing and watching the sport, I know how basketball works. Once I found out how Python syntax 'works' and learnt how to control the flow of my game loop it was just really a matter of filling in the gaps and coming up with ways to calculate different results.
Since I started working on a GUI it's been a whole new ballpark, and I've had to dig deep into documentation and other people's code to come up with solutions to a lot of difficult problems that pop up, usually relating to concepts that are completely new to me.
A good example is threading. The isn't an entirely new concept to me because it's something I'm familiar with at a high-level through my day job, but I did not expect that, for something as seemingly simple as pausing play-by-play text between results, I would need to read through a ton of documentation and spend a good part of the last two weeks trouble shooting and re-writing large sections of code.
On the plus side, it's made me a better programmer, and it's definitely improved the structure of the sim!
SirBlurton
09-09-2015, 03:15 PM
Totally agree with all the compliments, it does look amazing.
I've been struggling through the first couple of chapter of the PyQt book you referenced earlier in the thread, so I fully appreciate the amount of work it took you to get this far!
I haven't run into the threading issue yet, but my goal project is somewhat similar to yours - wondering if you could give any more detail on what resources you used to figure out what you needed to re-write?
Groundhog
09-09-2015, 06:56 PM
The PyQT book is super dry, I agree, mostly due to it's age. It's good starting point, but in general I've seen people do the same things in better ways based on posts to stackoverflow, and have opted to follow those. I'm not using PyQt, I opted for PySide for licensing reasons, but they are almost identical so it hasn't been an issue.
Keep in mind that I'm far from an expert on any of this, but I'll just detail my experience for you in case it helps - I managed to solve my issues, but there are probably better ways to do it. :)
The problem I encountered was just a problem with controlling how data is passed between classes... basically, OOP. My program's structure is one .py file with the main UI logic - navigation, configuring widgets, displaying data, etc. and then many separate .py files containing classes for different aspects of the game - one for handling the saving/loading of data, one for controlling the scoreboard, generating players, the actual simulator engine, etc.
The problem you quickly encounter is blocking - I actually ran into this first working on a BBCF->FOF utility that scrapes thousands of web pages to build player profiles. Your Qt UI is one big loop and any process that blocks the loop from updating the display makes your UI unresponsive, as if it's hung. The best way around this is to take these processes outside of your UI loop and have them run in another thread. Qt has it's QThread class which is exactly for this purpose. You inherit the QThread class*, put all your blocking code in it's run() method, then kick it off via the start() method:
def run_blocking_process(self)
athread = MyInheritedQThreadClass()
athread.start()
This worked fine for nearly everything. The ticker at the top of the simulator screen for example - it's a 'While True:' loop that displays text, sleeps, displays text, sleeps, etc. until the sim finishes. It uses signals and slots to connect to the simulator engine and retrieve stats, then connect to the main UI class and update the widget with a signal, because you should never update a widget directly from another class, even a QThread, as it will cause blocking.
Pausing the play-by-play of the simulator class would also block the UI, so obviously I needed to convert the (huge) class to a QThread and follow this same logic. I changed the simulator class to inherit from QThread, and put all the 'sim possession' logic under the QThread's run() method, to prevent the time.sleep() function after each line of PbP from blocking the UI. This was no small task but was also a good opportunity to clean up my 8 or so months worth of simulation code (that's still ongoing). The PbP text for the GUI as well as scoreboard data etc. was all sent via signals rather than by directly updating widgets, etc.
To my frustration, this did not work. The UI still blocked and I had no idea why. I made a stripped down test class, and it did work, so what was the problem.....
The problem was that my simulator class's __init__ method was loading all the player and team information from my main program class, which was making it a child of this process, rather than a true threaded process. The simulator class also directly accessed some of the variables loaded into memory by the main class - some player/team attributes, etc. - which led me to read a little bit about the difference between 'stack' and 'heap'.
So the solution was to build the simulator class, THEN load ALL the values for teams/players/etc. into that class so it doesn't reference back to the main program class - this is the OOP part I mentioned above.
This is a simplified example of what I ended up with:
def run_game(self, home_team_id, away_team_id):
simulator = Simulator()
values = self.data_handler.build_simulator_values(home_team_id, away_team_id) # Load everything needed by the simulator into a dictionary.
simulator.load_values(**values) # Unpack these values into the simulator class.
simulator.start() # Magic happens!
Hopefully some of this makes sense... Obviously feel free to pm me if you have any Qs. I can't guarantee I'll know the answers, but it's likely you'll run into the same problems along the way that I have!
*One small note about inheriting the QThread class... If you google QThreads, it seems to be popular opinion that this is not the recommended way to do this. There's another way to use QThreads, but I found this way easier to work and it seems to work fine for me.
SirBlurton
09-10-2015, 09:55 AM
Wow, thank you so much for the detailed write up - I've saved it for future reference. I was just starting to bump into some blocking issues but couldn't figure out what was happening....you've saved me a great deal of time.
I really appreciate you taking the time to give this advice and share your experience! Also wanted to say that there've been a few times where I've wondered if I should even bother continuing down the Python road because I couldn't find similar projects - your project has helped me move past that internal debate so wanted to thank you for that too!
Also appreciate the offer around pm's...will likely take you up on that - like you I've spent time on my core simulation loops and now am trying to figure out how to tie all my spaghetti together to make a cohesive program.
Groundhog
09-10-2015, 06:33 PM
Also wanted to say that there've been a few times where I've wondered if I should even bother continuing down the Python road because I couldn't find similar projects - your project has helped me move past that internal debate so wanted to thank you for that too!
To be honest with you, this is something I still think about. After getting a little better with Qt I knew Python could do what I wanted it do, functionally and graphically.
The biggest 'problem for another day' I have though is if I want to actually sell this simulator at some point. Compiling/obfuscating Python code is problematic, and just simply not the goal of the language.... There are options like Nuitka, Cython, and learning a little C or C++ to build extensions, but I haven't toyed with any of them as yet. I suspect this is the major reason why there aren't more projects like these out there...
The trade off of course is that I don't think there's another language where I could have gone from zero to where I'm at now in the time it's taken. If I'd tried learning C or C++ I'd probably still be trying to get my head around if/else statements. :)
SirBlurton
09-11-2015, 10:12 AM
I totally hear you on the if/else statements...I've dabbled with C# for Unity and I find it takes me so much more time to accomplish the same things because I'm tripping over syntax. I just like Python so much more...
I've only done a little bit of reading about compiling but you're right that it doesn't seem to be the focus...hopefully a clearer path will emerge when you need it! I have a bookmark saved for cx_Freeze as something to look into but I haven't explored it in any depth at all.
Groundhog
09-13-2015, 06:13 PM
cx_Freeze and Py2exe both work as far as freezing the python code to an .exe and including all the different modules, but they are just as easy to unfreeze. With Py2exe you can use PyInstaller to retrieve the .py files (or .pyc - I can't recall which) and the same is also true with cx_Freeze, so it works as far as packaging your program as a standalone exe, but does not protect the source code.
Groundhog
09-22-2015, 08:52 PM
I've done some work with the UI around player stats, which I'll show some screens of once I'm happy with it. Some more boring stuff now...
After spending time planning how to handle the saving/loading of data - mainly related to database queries and converting the output of these queries into the format I use within Python - dictionaries - I had an idea that seems stupidly simple in hindsight... why am I even using databases? I mean, I understand the benefits of relational databases, but why spend the time converting data between SQL and my Python dictionaries when I can just serialize the data using Python's pickle module or something similar?
There are some drawbacks to going with this method, but if I maintain my own 'foreign keys' correctly, I think the pros outweigh the cons. It also makes putting together a pure-Python editor for data very, very simple.
Groundhog
10-14-2015, 11:23 PM
cx_Freeze and Py2exe both work as far as freezing the python code to an .exe and including all the different modules, but they are just as easy to unfreeze. With Py2exe you can use PyInstaller to retrieve the .py files (or .pyc - I can't recall which) and the same is also true with cx_Freeze, so it works as far as packaging your program as a standalone exe, but does not protect the source code.
A little more on this - it sounds like Nuitka is the solution. Nuitka freezes to .exe (and some .dll files) and is not nearly as simple to decompile back to the python source. I'm not positive on how well (if at all) it compiles PySide/PyQt though.
SirBlurton
10-15-2015, 09:31 AM
Thanks for posting about Nuitka, will definitely check it out!
Groundhog
11-01-2015, 02:37 AM
Stats... I figured this would be easy, but it took me a good 3 weeks to come up with a good way to display the stats (all of which are just test data right now), calculate career totals, and restrict by leagues and regular/post season on the fly...
Added hover text to the league acronym to give a summary of what nations take part in the league, because I think this will come in handy when scouting guys for foreign leagues.
https://dl.dropboxusercontent.com/s/blbfem5c4uw2qx8/stats.png?dl=0
SirBlurton
11-02-2015, 10:09 AM
This looks awesome...I love seeing this thread every time it's updated.
Understand if it's a trade secret - but would love to know how you got it to update on the fly...
Groundhog
11-02-2015, 04:51 PM
Definitely not a trade secret! This will be a lot of info, hopefully it's helpful.
First I came up with a structure to store the stats. It's a dictionary structured like:
Player_ID > Year > League_ID > Team_ID > 'season'/'postseason' > Stats
The league selection combobox is filled each time a player is opened, the program loops through all the leagues stored in the player's stats similar to the below:
# Initialize a blank list.
leagues_played = []
for year in PLAYERSTATS[player_id]:
for league in PLAYERSTATS[year]:
leagues_played.append(league)
the 'league' variable here is the league_id value, a unique number that ties it to a key in another dictionary that stores all the leagues. I then create a 'set' structure of this list (one of those things you never think you'll need when you learn it!) of leagues to eliminate duplicates, and use it to populate the combobox options, including an 'All' option as well if the player has played in more than one league.
I have another dictionary that looks similar to this:
stats_settings = {'league': id, 'season_or_post': 'season', 'format': 'totals'}
Changing the league selections through any of the comboboxes or radiobuttons changes these settings here, and then refreshes the stats. So if the league was set to a particular league_id with 'season' stats I would query all the player's stats like:
raw_stats = []
for year in PLAYERSTATS[player_id]:
for league in PLAYERSTATS[year]:
if league == league_id_ive_selected:
for team in PLAYERSTATS[player_id][year][league]:
raw_stats.append(PLAYERSTATS[player_id][year][league]['season'])
This gives me a list of all the raw stats for a player as per the criteria set by the different selection widgets. The stats object is a list that contains the dictionary of season stats, as well as the other info needed for the row of stats that looks like [year, league_id, team_id, {'G': 82, 'GS': 82, 'FGA': 2, etc.}].
The next step is what I call 'pretty stats', which is to format the stats correctly - totals, averages, per36, advanced (not done yet). Each stat object is passed to the 'pretty stats' function to generate a list that is formatted for display. So for example if 'averages' has been selected, the pretty stats function will return something like:
[year, league_id, team_id, 82, 82, 1.5, 3.2, .401, etc.]
This is appended to a new list, and is ready to add to my datamodel for display via the QTableView widget. Last step is to come up with the totals for the selected league. This was the hardest part. A copy was made of the original raw_stats list (before any other changes were made), and I'm going to post my solution for this in full, changing a few function/variable names so it makes more sense.
I'm not necessarily happy with this solution, but it works. It's a bit messy and I'm sure there's a more efficient way to do it.
def stats_career_totals(self, raw_stats, format_):
# Take all the stats in the raw_stats object, and determine the career average or total based on format_
# attribute. Exclude the attributes we don't want to calculate first.
# TODO: Not happy with this method at all. Find a better way to do it.
calc_fields = []
return_list = []
for i in raw_stats:
i = i[4] # i[4] is the dictionary of stats - i[0]-i[3] is the info columns for each row (year/league/etc)
# x variable is a 'throw-away' list that makes up all the elements to be added together to determine the
# total.
x = []
# A list of the different categories - G/GS/FGM/etc. This is used to make sure the stats are ordered in the correct order - and ordered list -
# so I can use the index of the list to determine which stat is which.
for cat in self.STAT_CATEGORIES:
if cat not in ['FG%', 'FT%', '3P%']:
x.append(i[cat])
calc_fields.append(x)
# Add all individual elements in the lists together to get the total.
# This 'magic' line of code was the part that took me (with help from Google) a long time to come up with.
TOTALS = [sum(i) for i in zip(*calc_fields)]
# Iterate through the TOTALS, using the index to identify the FG%/3P%/FT% fields.
for index, stat in enumerate(TOTALS):
if index in [0, 1, 5, 7, 9]:
# GP and GS
if index in [0, 1]:
return_list.append(stat)
# FG%
elif index == 5:
return_list.append(self.player_profile_stats_format_shot_percentage(TOTALS[3], TOTALS[4]))
# 3P%
elif index == 7:
return_list.append(self.player_profile_stats_format_shot_percentage(TOTALS[5], TOTALS[6]))
# FTs
elif index == 9:
return_list.append(self.player_profile_stats_format_shot_percentage(TOTALS[7], TOTALS[8]))
# Don't want to calculate anything for the 'G' or 'GS' stats - just use the raw figures.
if index not in [0, 1]:
if format_ == 'average':
# Fetch the average of all games played / tallied stats.
return_list.append(self.player_profile_stats_format_average(TOTALS[0], stat)) # TOTALS[0] = 'G' stat.
elif format_ == 'per36':
# Fetch the per36 average of the tallied stats.
return_list.append(self.player_profile_stats_format_per36(stat, TOTALS[2])) # TOTALS[2] = 'MINS' stat.
elif format_ == 'totals':
# Just return the totals.
return_list.append(stat) # Append the raw stat.
return return_list
The total row is appended to the other 'pretty stats' and ready for display. These pretty stats and a list containing the headers are then built into a QAbstractTableModel, which is then assigned to the QTableView widget to be displayed. I make this part sound trivial, but the 'model view' style of programming with qtableviews and item models is in itself a few weeks of work to get a semi-decent grasp on... Definitely worth the time though, this guy's series of tutorials was invaluable:
PyQt4 Model View Tutorial Part 01_0 - YouTube (https://www.youtube.com/watch?v=mCHVI8OXDxw)
SirBlurton
11-03-2015, 02:16 PM
That's awesome - thanks for sharing!
Love the 'magic line'...list comprehensions were definitely an "ah-ha" moment for me.
Did you end up going with pickle for storing your stats?
I've been playing with JSON for my project.
I started by using .csv files to read and write my data but it was taking a full minute in some cases (which could have been due to extremely poor implementation on my part). I decided to try JSON, thinking it wouldn't make much difference, but it went from a minute to a second to complete the operation without changing anything else about the approach.
Thanks for the model view link! And for being so open about your process in general, learning a lot! Always been shy about posting but considering posting some status updates of my project as a way to keep myself honest.
Radii
11-03-2015, 04:07 PM
Couple thoughts if you are interested (and if you are not, let me know, what you're doing and sharing here is awesome, I don't want to potentially bog you down over anything that you're satisifed with)
If you are using the raw_stats dictionary in a lot of places othern than this one display screen, did you consider creating a class for it instead of using the string dictionary? Being able to refer to raw_stats.GP instead of remebering that games played is in index[0] could make the data a lot esier to work with.
Alternatively, if the dictionary is the best way to go, what about an enumeration so that you don't have to refer to the index numbers in hard coded fashion every time you touch the stats. Something that sets Stats.GP = 0, Stats.GS = 1, Stats.MINS = 2, etc. If you decide you want to change the layout of things down the road, or change the order, or alter the data based on the addition of some new advanced stats calculation, then you only have to change it in one place instead of many.
I should point out I don't use Python, just generic programming thoughts. May or may not be relevant to the exact way you're doing things :D
Keep it up, this is an awesome read and its great to see the progress you've made.
Groundhog
11-03-2015, 04:23 PM
That's awesome - thanks for sharing!
Love the 'magic line'...list comprehensions were definitely an "ah-ha" moment for me.
Did you end up going with pickle for storing your stats?
I've been playing with JSON for my project.
I started by using .csv files to read and write my data but it was taking a full minute in some cases (which could have been due to extremely poor implementation on my part). I decided to try JSON, thinking it wouldn't make much difference, but it went from a minute to a second to complete the operation without changing anything else about the approach.
Thanks for the model view link! And for being so open about your process in general, learning a lot! Always been shy about posting but considering posting some status updates of my project as a way to keep myself honest.
Please do post, I would definitely be interested in reading your progress too!
I'm using pickle now, but I'm not sure how well it's going to scale up. A pickled object of historical stats for example could end up being quite a big object and take awhile to load when needed, so I've made it easy to change this down the line if I feel I need to go a different way down the line.
I use JSON for storing all my 'settings' data. Originally I used it for everything but having to convert the type of loaded JSON from strings to other types was a bit of a pain... There are ways to handle this, but for now I've opted for pickle. I see the advantages to SQL databases and did spend a bit of time reading up on them - managing my own 'foreign keys' to link dictionaries together in particular feels like it might get out of hand pretty easily.
Groundhog
11-03-2015, 05:21 PM
Couple thoughts if you are interested (and if you are not, let me know, what you're doing and sharing here is awesome, I don't want to potentially bog you down over anything that you're satisifed with)
If you are using the raw_stats dictionary in a lot of places othern than this one display screen, did you consider creating a class for it instead of using the string dictionary? Being able to refer to raw_stats.GP instead of remebering that games played is in index[0] could make the data a lot esier to work with.
Alternatively, if the dictionary is the best way to go, what about an enumeration so that you don't have to refer to the index numbers in hard coded fashion every time you touch the stats. Something that sets Stats.GP = 0, Stats.GS = 1, Stats.MINS = 2, etc. If you decide you want to change the layout of things down the road, or change the order, or alter the data based on the addition of some new advanced stats calculation, then you only have to change it in one place instead of many.
I should point out I don't use Python, just generic programming thoughts. May or may not be relevant to the exact way you're doing things :D
Keep it up, this is an awesome read and its great to see the progress you've made.
Appreciate the tip, and I agree that that's a really good idea!
My current coding process is:
1) Try to think how something could work.
2) type away madly.
3) test through trial-and-error until I get it to work.
4) Completely rewrite/refine it from scratch to match the structure of everything else.
Right now I'm on step #4 with stats, and I'm going to do exactly what you propose. :)
Radii
11-03-2015, 07:54 PM
1) Try to think how something could work.
2) type away madly.
3) test through trial-and-error until I get it to work.
4) Completely rewrite/refine it from scratch to match the structure of everything else.
Yup! The more experience you get doing steps 2-4, the better you'll get at step 1. I am not a great designer, its not uncommon for me to start planning out class structures and to reach a point where I get stuck and can't visualize everything as well as I wish I could and just start writing something. You could easily go look at a project I finished and pick out the parts that I slammed together vs the parts that were well thought out. Always something to learn I suppose :D
Groundhog
11-04-2015, 09:03 PM
I ended up creating two new classes, one specific for working with the individual rawstats - it takes the dictionary entry of a specific season's stats in the dictionary format that it's stored, and returns it in a format ready for display - and a class that takes a player's whole statistical history and will return it (using the rawstats class per entry) depending on the restriction applied - all stats for all leagues, all stats for one league, all stats for one league in one year, all post-season stats for one league etc. as well as generate the 'career totals' row on the bottom.
The end result is much the same as what I had before these two classes, but the amount of code needed was much, much less and is much cleaner. Thanks Radii. :)
Radii
11-05-2015, 01:56 AM
The end result is much the same as what I had before these two classes, but the amount of code needed was much, much less and is much cleaner.
Awesome stuff man! Can't wait to see what's next!
Young Drachma
11-05-2015, 02:25 PM
Glad you're still trucking!
Groundhog
11-08-2015, 06:04 PM
I put some work into cleaning up the code around styling the Qt forms, as this was some of the first coding I did and it lacks consistency, especially the first forms I put together. To be less polite, it works, but it's a real mess. :)
Qt Designer is one of the best reasons to use the Qt framework, but I really only use it to lay out my forms, and do all the actual styling from within Python. I chose to go this route (you could do it through Qt - you can set StyleSheets from within the Designer and see the results instantly), but if you want to be flexible and allow custom colour schemes you will be using the .setStyleSheet method for your widgets, and using the string formatted stylesheets, which are similar to .css in html, with variables.
As an example, a simple QPushButton widget's style sheet will look like:
standard_button = '''QPushButton {{
color: rgb{};
background-color: rgb{};
border-bottom-left-radius: {}px;
border-bottom-right-radius: {}px;
border-top-left-radius: {}px;
border-top-right-radius: {}px; }}
QPushButton:hover {{
background-color: rgb{};
}}
QPushButton:disabled {{
background-color: rgb{};
color: rgb{};
}}'''
One of the downsides to Qt is that changing the original style is not enough to then go on and update all the widgets with it assigned - it's obviously just a stored string, so no signals are emitted to let all the widgets know to reapply it. I figured there would be a quick and easy way to do this, but after some google searches it seems not. To allow a user to change the colour of a push button, you will need to update the above string with the new rgb values, and then re-apply the stylesheet to all the widgets.
To handle this, I created a new class. I won't post the whole thing as it's huge, but this is a quick snippet of how it is structured for the settings screen:
class Style:
def __init__(self, app):
# The 'app' variable is my application itself, which gives the Style class access to directly modify all the widgets.
# So when I create an instance of this class from within my application, I create it as self.style = Style(self).
self.app = app
# Class attributes - this will store all the stylesheets strings.
self.attrs = {'css': {}}
self.store_ss_strings()
self.setup_shortcuts()
self.init_all_widgets()
def setup_shortcuts(self):
# The quick command to retrieve the stylesheet and current variables stored in settings.
self.mainwindow_panel = self.attrs['css']['mainwindow_panel'].format(self.retrieve_styling('mainwindow_panel'))
def store_ss_strings(self):
# Define all the stylesheets strings and store them in the self.attrs dictionary.
self.attrs['css']['mainwindow_panel'] = mainwindow_panel = '''QLabel {{
color: rgb{};
background-color: rgba{};
border-bottom-left-radius: {}px;
border-bottom-right-radius: {}px;
border-top-left-radius: {}px;
border-top-right-radius: {}px;
}}'''
def init_all_widgets(self):
# This method is called upon creation of the class instance and applies all stylesheets to the different widgets,
# via the individual method per Qt form. It uses the colours and other settings related to styling in the main
# application's 'settings' class, so if changes are made to the colour scheme, this method is run again to re-style
# everything.
self.init_settings_form()
def retrieve_styling(pointer):
# Fetch the colours from settings class.
if pointer == 'mainwindow_panel':
r, g, b, a = self.app.settings.panel_background_color[0], self.app.settings.panel_background_color[1], \
self.app.settings.panel_background_color[2], self.app.settings.panel_transparency[0]
# This dictionary supplies all the different settings needed by the stylesheet string.
lookups = {'mainwindow_panel': (self.app.settings.panel_text_color, (r, g, b, a), self.app.settings.corner_radius,
self.app.settings.corner_radius, self.app.settings.corner_radius,
self.app.settings.corner_radius)}
return lookups[pointer]
def init_settings_form(self):
# Apply the style sheets to all widgets.
self.app.ui.settingsPanel.setStyleSheet(self.mainwindow_panel)
Groundhog
11-28-2015, 08:30 PM
https://dl.dropboxusercontent.com/s/srpyvb6ckkfc1ua/nations.png?dl=0
Initial work on the nation screens. I want to make it real easy to add/remove nations, so it took me some time to work out how to best generate menus and screens 'on the fly' based on nation information stored in a csv file. The sim will read the file, populate the menus, and colour the scheme based on the foreground/background colours defined for the nation.
Leagues work in a similar way, with the menu system grouping by Region > Nation > League(s).
SirBlurton
11-30-2015, 11:56 AM
Sooooooooo pretty....Guessing it's too early to tell you to shut up and take my money?
LastWhiteSoxFanStanding
12-01-2015, 05:26 AM
Looking great. Hope you can include Singapore too :D
Groundhog
12-05-2015, 02:48 AM
https://dl.dropboxusercontent.com/s/2mnkho2luhshwk3/nations2.png?dl=0
Ask and you shall receive. :D
...although still need to put the first names/last names and city files together.
LastWhiteSoxFanStanding
12-05-2015, 05:03 AM
Talk about great customer service! :)
Can't wait to see the next update.
Groundhog
01-21-2016, 09:49 PM
No updates in a while, but it hasn’t been for lack of progress (relatively speaking). This post will more be for those of you who are interested in the programming side of things… ie. lots of text, no pics. ;)
After 100,000+ lines of code, much more if you count deleted/restarted lines of code, it was time to take a step back and look at the structure of my program. Things were getting a bit messy and, it seemed to me, overly complicated. I had been doing some reading lately on software design patterns (half because of this project, half because I think it’s something I might be interested in doing for money one day) and while reading through the different patterns, I was interested to see that without really knowing it I had drifted towards what is called the Model-View-Controller pattern. I won’t add to many details about what an MVC pattern is (there’s lots of info on the web), other than to say that it became pretty clear to me why things were getting messy, and what I needed to do to fix it. It requires re-writing a bit of code, but not excessively so.
The other major thing I’d been looking at was the interface. As monitor resolutions grow, the fixed-window style of GDS and Wolverine Studios sims began to bug me. Early on I realised why they opt to go this way, and why it's not as straight forward to make a program that will 'stretch' to fit your selected resolution, like with any graphical video game you play.
I spent some time the last couple of weeks thinking of ways around it - Qt (the UI framework I'm using) has layouts which are made for exactly the purpose of resizing components to fit a screen. The problem is they don't really suit a program like this IMO, and objects on the screen stretch and shrink and look ugly. For general purpose desktop apps I think it works great, but not for this. So I've decided to stick with what I've got so far, which is a a fixed-window, though in the standard 'widescreen' size of 1280x720 pixels. I'm going to push on with this with the idea that if I want to improve on this later, all of my presentation logic can be replaced without touching anything else (this relates directly back to the MVC stuff above).
As far as the actual simulator goes, I haven't done a great deal of work for a while because I know how it's going to work. There's no problem to solve (unlike quite a few other areas), so I haven't been focusing on it as much. I have been looking at schedules, trying to make it as flexible as I can. Initially there will be two types of schedules - round-robin, and your NBA-styled 82 game schedules. I have a good idea of how to generate both, just ironing out a few wrinkles right now.
CraigSca
01-22-2016, 10:40 PM
Always good to hear updates, Groundhog - been missing these!
Groundhog
01-27-2016, 01:32 AM
Until this point I've imported all of my data - teams, coaches, leagues, nations, etc. - in a database (sqlite) that I manually populated from csvs full of info I've gathered over the last couple of years, so I spent some time the past couple of days putting together the 'new game' process - main menu/league selections, etc.
Here's a couple of screens:
Title Screen
A quick (5 minute) title screen just to give the standard new/continue/load options.
https://dl.dropboxusercontent.com/s/al51umrxui1dc2t/main.png?dl=0
New Game Menu
This one took a lot longer - the fully functional league selection screen. All the data here is taken from csv files that can be easily modified to add/subtract/modify the leagues included - currently 44 fields per league (some repetitive, like 7 fields for each day of the week that games will be scheduled). Leagues where I've manually created teams (also done via csvs) default to 'import' teams, other leagues will be randomly generated. Active leagues will be bolded (not yet done), and I'll ditch the '#' in the Teams column header.
The Olympics/Tournaments options on the left will be changed to a table like the regional leagues, because it gives me more flexibility with how many I can include without running out of room - there's international tournaments in the Americas, Asia and Africa that I don't plan on looking at now but would probably like to look at down the track:
https://dl.dropboxusercontent.com/s/0h4qp4kuwjw9j5a/new_game.png?dl=0
Nav Bar
Lastly, the top-of-screen navigation bar - not completely functional yet, because I don't have all the screens completed. This image is at 100% actual width, to give you an idea of how wide the UI will be:
https://dl.dropboxusercontent.com/s/zkbymw67f0mjjxx/top_nav.png?dl=0
SirBlurton
02-01-2016, 04:29 PM
Super, super awesome....if you get to the point you want any testing help, I'd love to volunteer! No worries if not though.
Really impressed with your GUI skills, I just got to the point of making my first (very remedial) GUI for one of my sims the other day. Your posts are a reminder that I still have a long way to go!
Groundhog
02-01-2016, 11:51 PM
Thanks for the kind words! When I get to the point of testing, this would be the first place I would come. That point is still some ways away unfortunately... inching closer, but lots of work that needs to take place before that point. No idea of timeframes.
UI design is as foreign to me as coding was at the beginning. I just took a look at some of the programs I find I really like, tried to figure out what it was about them that I liked, and then try to figure out how replicate it, or tweak it to fit what I'm doing. Although I didn't go with a web-based program, Flask and the Bootstrap css stuff was a big influence on what I've tried to put together so far, too.
Groundhog
02-21-2016, 07:51 PM
I'm revisiting the whole player generation part of the sim now, as the rest of the logic is now all in place to create a new universe.
When I started to learn Python one of the first scripts I came up with was a random name generator, to add more flavour to the foreign recruits in my FBCB game. When putting together the player generation code it was also one of the first things I coded. I set out to make it smarter over the weekend, and allow for different settings for each nation - things like hyphenated names, no surnames, middle names, 'I, II, III, IV, Jnr' names, etc. Although names are purely cosmetic, in the world of text sims I personally find little details like this are important because it gives a bit more uniqueness to individual players which for me adds another important layer of non-technical abstraction between the user and the random number generator that's at the core of all simulators.
Each nation has 2 or 3 name files built mostly by text scraping national soccer league team pages on wiki where possible (and a much more manual process where not). In addition there is a name rules csv file in the same folder that determines % of players with no surnames (Portuguese styled sportsmen - Nene, etc.), % of players with ', II' appended at the end or hyphenated names etc., most of which I determined by data scraping every Div I college basketballer and coming up with %s. You can also specify substitute nations and a % chance of the name being chosen from there. For example, I didn't bother scraping Canada names, instead assigning USA as 100% to be chosen instead. For the USA itself there's a slight chance of a 'Puerto Rican' name, etc.
When I run the name generator now, I specify a nation, and it will produce a name, taking into account the different rules, and spit out a first name and a last name.
Player hometowns work in a similar fashion, except are weighted selections, with more populated cities in each nation a better chance of being selected. USA is slightly different as I included state distribution as well. Again using Div 1 basketball rosters I calculated the % of players from different states, and USA hometowns will select the state first, and then a weighted selection of cities from that state.
I also put together a list of US colleges, breaking them down into 5 levels of talent from top tier schools down to division 2. When a US player is generated, there is a greater chance that higher ability players will have attended higher tier schools.
I'm trying to think of a good way to handle non-US players with college experience. Some nations (like Australia) send a lot of kids to US colleges, and the pro league is largely national players who attended US colleges. This is not the norm internationally though, as most European countries in particular have stronger youth systems and there is not as much drive to play in the US college system when they can earn decent money as teens.
Young Drachma
02-27-2016, 07:33 PM
This is looking great!
Groundhog
03-23-2016, 05:52 PM
I mentioned that I was trying to make the influx of new talent each year as realistic as possible, and I've spent a couple of nights this week designing a system called 'Youth Settings'. These are a series of rules that determine the age ranges and probability of each age for incoming rookies, as well as the chance of a rookie having attended a USA college. If a generated rookie is determined to have attended college, a calculation is made based on his potential ('scouted' as opposed to 'actual') of how many years he attended college (weighted towards higher potential means less years), which will be used to determine his age instead of the range of ages mentioned earlier - so a higher rated player from the US is more likely to have spent 1 or 2 years at college and enter the league at a younger age than, say, a 3-star type player, who might be 22 or 23 as a rookie after graduation. As mentioned above, the quality of the college selected will be based on the player's scouted ability, too.
Right now I have setup 10 different categories of youth settings to cover the NBA (99.9% chance of college, to allow for the Mudiay/Jennings exceptions in theory - ie. they wouldn't actually play overseas for a year at this point, and would just be generated at 17 or 18 with 'None' for college), high level European nations (younger rookies, less chance of having attended college), mid-tier European nations (slightly higher chance of college), Latin America (above-average chance of college), etc. These are mapped out in a csv file with a reference number (or 'primary key'). I added an additional column to the csv containing all the countries information to point to the different youth settings.
It's working as expected, but further down the line I want to add more logic to it - ie. a one-star Nigerian rookie with 4 years college experience is probably not going to want to play professional basketball in Nigeria for peanuts. One way to handle this would be outside of the Youth Settings, making anyone with a college other than 'None' a lot less willing to sign in low salary leagues, or rather than just having a single % to determine how many rookies have college experience, weight it towards only the stronger players generated from that nation. I'm tabling it for now, but it's something I'll have to come back to when it comes time to think about free agency.
tarcone
04-10-2016, 12:04 PM
I have been watching from afar. I really like reading this and watching your progress. Very impressive.
Keep up the good work and I am looking forward to watching this project come to its full potential.
Groundhog
04-11-2016, 07:41 AM
Thanks!
It's been a slow couple of weeks - really struggling to come up with a player progression system that I'm happy with. Physical progression is in - players will develop in terms of height and weight (and speed/strength/jumping ability). Players will have growth spurts between 16-18, and potentially up through 20. Strength develops mostly around the 20-24 years.
What I'm struggling with now is 'basketball' attribute progression - whether to generate 'potential' per rating (which would be hidden), or have players develop semi-randomly.
My initial stance was semi-random, but I don't like how chaotic it makes the player progression system. Now I'm leaning the other way - players will be generated with a 1-5 potential rating in each attribute to indicate their ceiling, and an overall 'potential' rating that is partially masked until a certain age. I continue to tweak it.
Once I'm happy with this, I'll be working on the player generation logic per nation/league, and then initial work on the financial side of things, which is a little daunting to even think about to be honest!
Groundhog
07-08-2016, 01:52 AM
Been awhile since an update. The previous post outlines the biggest reason for that, and I basically had to shelve it for now and just push on with other aspects of the sim. Hopefully I'll come up with a solution I'm happy with further down the line.
I put the finishing touches on the play-by-play "engine" today. It's completely customisable, with all text and the chance of that text appearing being stored in an .ini file as per the following quick example:
[introduction]
option1 = "Welcome to $home_team_arena, $home_team_display_city, where the $home_team_name are up against the visiting $away_team_name.|100"
[pre_opening_tip]
option1 = "$home_team_jumper and $away_team_jumper are in the middle for the opening tip, and we're ready to go.|50"
option2 = The referees blow their whistles, $home_team_jumper and $away_team_jumper are lined up for the opening tip, and we're ready to get things underway.|50"
Every bit of text in the PbP is in the ini file (outside of misc. stat reports, information re: the scores/time left, etc.), so it allows for some added flavour to the pbp, especially for things like dunks, passes, etc.
SirBlurton
07-08-2016, 10:10 AM
Hah, I'd thought twice this week about bumping the thread for an update but tried to stay patient!
How do you end up processing the text file? That's something I haven't seen, I've been using massive dictionaries....what are the $ tags and |100 things? Codes for replacing when processed?
Groundhog
07-08-2016, 08:03 PM
It's not Python at all, just tried to come up with something that is easy to edit by someone who doesn't know Python.
The '|integer' thing is the chance of that line appearing, and the $ tags are words that will be replaced by the Python equivalent, and then I use simple string operations (split on "|" to get the chance, and then a string replace method to swap out the $ tag).
The $ tags seemed like a good idea at the time but I might just replace them with the Python syntax - "{home_team_jumper}" instead of "$home_team_jumper".
I read this in with the configparser module rather than just importing the strings, but I do then build a dictionary that puts them in an easier format to work with.
nilodor
07-20-2016, 07:01 PM
This is really awesome, I've been working on something similar, although I'm at about midway down the first page in figuring out how to get it done. I'm very inspired!
Groundhog
07-21-2016, 12:35 AM
This is really awesome, I've been working on something similar, although I'm at about midway down the first page in figuring out how to get it done. I'm very inspired!
Thanks! What language are you working in?
GoldenCrest Games
07-22-2016, 10:19 AM
Just wanted to join the rest in saying this is an awesome dynasty topic!
Love all the little details you put in. I hope you keep it going!
Balldog
07-23-2016, 06:16 AM
I've started learning Python due to this thread. What you have so far looks amazing.
Sent from my iPhone using Tapatalk
nilodor
07-27-2016, 12:57 PM
Thanks! What language are you working in?
I had started off in unity, then went to Java and now am in Python. You are way ahead of where I am but you've certainly inspired me to keep going.
GoldenCrest Games
08-08-2016, 08:35 AM
It just dawned on me that this has been a 2 year project! That's impressive.
Kudos for grinding away at it. I have such a hard time staying with projects for that long!
Groundhog
08-10-2016, 01:57 AM
Yikes, thanks, I didn't even realize. Thought it was about 18 months. Most interesting thing has probably been how much I've adopted Python at work off the back of trying to make this thing - some of which has made my team's lives a lot easier (or at least, a lot less monotonous).
Only update I have lately is I've revisited the player generation/progression logic that I put aside because I thought of a way to do it. During player generation, I determine a 'ceiling' for each player, as well as their age, athletic profile and 'personality' attributes. The players are then "grown" to their current age with all progression being applied as it would during a normal offseason.
Currently the plan is to have players only progress in the offseason, but I like the idea of having a chance of 'bonuses' applied to player's attributes over the course of a season based on court time. The bonuses would take into account the player's potential in each rating, and be lost in the offseason, but playing time would also be a factor in offseason progression. I think it kind of models what happens in reality to some players, but I'm not sure if it will make its way in initially.
GoldenCrest Games
08-10-2016, 07:52 AM
Most interesting thing has probably been how much I've adopted Python at work off the back of trying to make this thing - some of which has made my team's lives a lot easier (or at least, a lot less monotonous).
I've found the same thing. Since I started writing sports sims, I've been transferring the new programming skills to my job, writing various data crunching tools. (which has ended up helping career growth)
Funny how that works out!
nilodor
08-19-2016, 03:05 PM
Silly noob question:
I'm trying to generate random numbers so I've done
from _random import Random
A = []
for i in range(0,8):
A.append(4+Random().random())
But when I print A, I get the same random number the entire time. I'm kind of confused as to why as I thought if I don't specify a seed I should get a different random number based upon the system clock. Or is it just happening too fast, so the clock is the same?
nilodor
08-19-2016, 03:22 PM
Silly noob question:
I'm trying to generate random numbers so I've done
from _random import Random
A = []
for i in range(0,8):
A.append(4+Random().random())
But when I print A, I get the same random number the entire time. I'm kind of confused as to why as I thought if I don't specify a seed I should get a different random number based upon the system clock. Or is it just happening too fast, so the clock is the same?
Apparently _random is different than random, which has fixed my issue!
Groundhog
08-19-2016, 09:27 PM
It is - I actually have no idea what _random is, I know there's a few libraries with the underscores.
Another quick tip - you don't need the (0,8) with range, you can just:
for i in range(8):
A.append(4 + random.random())
SirBlurton
08-22-2016, 11:50 AM
You could also use a list comprehension to skip the step of appending to the created list and just create the list.
A=[(4+random.random()) for i in range(8)]
List comprehensions were one of the best things I've learned about. I had one block of about 12 overly complicated lines in an early project get replaced by one list comprehension....and that simplification flowed through everything else.
Groundhog
12-12-2016, 10:16 PM
Yes, list comprehensions are the bomb once you get a handle on them. I remember reading through one of my first Python books and my eyes just glazed over when covering that topic (similar to what used to happen to me back in school in maths, actually... I wonder if I have dyscalculia...). It was only when I saw a real simple example in someone else's code that was clearly commented that it clicked with me.
Groundhog
12-12-2016, 11:43 PM
It’s been months, so here’s a quick update:
Life and holidays got in the way for the most part since my last updates, but over the past three weeks I’ve picked this up again and got quite a bit done. One of the big changes I’ve made is just to focus entirely on the code component, and so I’ve sliced the GUI (PySide) right out at the moment. The main advantage to putting the UI aside is that it was really slowing me down, and my plan is to try and get the core functionality coded before looking at a UI again. If I’m happy with what I’ve coded I may just pay someone else to do it.
I think PySide is great, but I don’t know that I would ever be able to package it in a way that I’d like, and I also think that if I ever wanted to make money from this at some point, it makes sense to focus on the online/browser based component. This is contrary to how I myself like to play, but hey, I have massive CD and DVD collections too. I'm no design expert or anything, but I liked the overall style I came up with with my PySide GUI, and I'd probably want something very similar no matter which way I go. The code as I've been writing it will make it pretty easy to slot any UI framework over the top, so I can worry about this decision later, once I have a functioning simulator!
Groundhog
12-13-2016, 04:12 AM
Testing out the 'universe generator' code I setup ages ago to make sure it all still works after cutting my code to pieces. Right now it uses the fictional default league info I've saved into a csv and generates fictional teams for all leagues based on that info, selecting random cities (weighted towards the most populous cities) and nicknames (if enabled). There is a ton of loading of csv files and calculations to make this info, which is what impresses me about Python - it's done in less than a second (excluding players - they take about 1.5 seconds at the moment).
I want to quickly be able to spin up entire random universes with teams/coaches/players/arenas etc. quickly to help with testing down the road.
For fun, here's the most recent USA 'NBA':
San Jose Seagulls (SAJ): City: San Jose, CA, Popularity: 3, Arena: 1078, FG_Colour: #AF906A BG_Colour: #990000
Nashville Tigers (NAS): City: Nashville, TN, Popularity: 4, Arena: 1079, FG_Colour: #FFC700 BG_Colour: #00386B
Albuquerque Bruins (ALB): City: Albuquerque, NM, Popularity: 1, Arena: 1080, FG_Colour: #243760 BG_Colour: #F07422
Minneapolis Jaguars (MIN): City: Minneapolis, MN, Popularity: 2, Arena: 1081, FG_Colour: #00457C BG_Colour: #FF2222
Mesa Vikings (MES): City: Mesa, AZ, Popularity: 3, Arena: 1082, FG_Colour: #FFFFFF BG_Colour: #005D28
San Antonio Leopards (SAN): City: San Antonio, TX, Popularity: 5, Arena: 1083, FG_Colour: #003399 BG_Colour: #FF9900
San Diego Bucks (SAD): City: San Diego, CA, Popularity: 2, Arena: 1084, FG_Colour: #F8B800 BG_Colour: #000000
Philadelphia Thunderbirds (PHI): City: Philadelphia, PA, Popularity: 4, Arena: 1085, FG_Colour: #B31F29 BG_Colour: #11351A
Washington Stallions (WAS): City: Washington, DC, Popularity: 2, Arena: 1086, FG_Colour: #C41E3A BG_Colour: #321414
Virginia Beach Hornets (VIR): City: Virginia Beach, VA, Popularity: 3, Arena: 1087, FG_Colour: #BDB76B BG_Colour: #000000
Colorado Springs Ospreys (COS): City: Colorado Springs, CO, Popularity: 3, Arena: 1088, FG_Colour: #000000 BG_Colour: #FFC94B
Santa Ana Hawks (SAN): City: Santa Ana, CA, Popularity: 2, Arena: 1089, FG_Colour: #FFFFFF BG_Colour: #D2DDFC
Honolulu Salukis (HON): City: Honolulu, HI, Popularity: 3, Arena: 1090, FG_Colour: #FFFFFF BG_Colour: #990000
Arlington Eagles (ARL): City: Arlington, TX, Popularity: 3, Arena: 1091, FG_Colour: #000000 BG_Colour: #DC143C
Austin Barons (AUS): City: Austin, TX, Popularity: 3, Arena: 1092, FG_Colour: #374EA1 BG_Colour: #FFF200
Raleigh Miners (RAL): City: Raleigh, NC, Popularity: 4, Arena: 1093, FG_Colour: #000066 BG_Colour: #FFFFFF
Chicago Pirates (CHI): City: Chicago, IL, Popularity: 2, Arena: 1094, FG_Colour: #000000 BG_Colour: #CC0000
Denver Bees (DEN): City: Denver, CO, Popularity: 4, Arena: 1095, FG_Colour: #50C878 BG_Colour: #0047AB
Phoenix Nighthawks (PHX): City: Phoenix, AZ, Popularity: 3, Arena: 1096, FG_Colour: #FFFFFF BG_Colour: #0000FF
Tucson Flames (TUC): City: Tucson, AZ, Popularity: 2, Arena: 1097, FG_Colour: #000000 BG_Colour: #E00122
Saint Paul Centaurs (SAP): City: Saint Paul, MN, Popularity: 3, Arena: 1098, FG_Colour: #EF6E22 BG_Colour: #216EB7
Dallas Comets (DAL): City: Dallas, TX, Popularity: 1, Arena: 1099, FG_Colour: #FFFFFF BG_Colour: #000070
Riverside Kings (RIV): City: Riverside, CA, Popularity: 5, Arena: 1100, FG_Colour: #FFFFFF BG_Colour: #5858FA
Los Angeles Hawks (LAS): City: Los Angeles, CA, Popularity: 3, Arena: 1101, FG_Colour: #FFFDD0 BG_Colour: #960018
St. Petersburg Riverhawks (STP): City: St. Petersburg, FL, Popularity: 5, Arena: 1102, FG_Colour: #C5B358 BG_Colour: #8B0000
Houston Defenders (HOU): City: Houston, TX, Popularity: 1, Arena: 1103, FG_Colour: #613005 BG_Colour: #FF7300
Louisville Yellow Jackets (LOI): City: Louisville, KY, Popularity: 4, Arena: 1104, FG_Colour: #FF9900 BG_Colour: #000000
Milwaukee Wolves (MIL): City: Milwaukee, WI, Popularity: 3, Arena: 1105, FG_Colour: #CAB388 BG_Colour: #00285C
New York Cavaliers (NYK): City: New York, NY, Popularity: 4, Arena: 1106, FG_Colour: #990000 BG_Colour: #000066
Jersey City Falcons (JER): City: Jersey City, NJ, Popularity: 3, Arena: 1107, FG_Colour: #FFD700 BG_Colour: #000000
Usually it's very Texas heavy as I used a list of the 80ish most populated cities in the USA.
And the same for Spain:
Alicante (ALI): City: Alicante, Popularity: 1, Arena: 941, FG_Colour: #FF652B BG_Colour: #4C230E
Pamplona (PAM): City: Pamplona, Popularity: 4, Arena: 942, FG_Colour: #FFFFFF BG_Colour: #006000
Valencia (VAL): City: Valencia, Popularity: 1, Arena: 943, FG_Colour: #C41E3A BG_Colour: #321414
Vitoria-Gasteiz (VIT): City: Vitoria-Gasteiz, Popularity: 2, Arena: 944, FG_Colour: #FFFFFF BG_Colour: #00386D
Madrid (MAD): City: Madrid, Popularity: 4, Arena: 945, FG_Colour: #ED1C24 BG_Colour: #F5EE30
Valladolid (VAL): City: Valladolid, Popularity: 3, Arena: 946, FG_Colour: #CFB53B BG_Colour: #006633
A Coruña (ACO): City: A Coruña, Popularity: 4, Arena: 947, FG_Colour: #FFFFFF BG_Colour: #461D7C
Getafe (GET): City: Getafe, Popularity: 4, Arena: 948, FG_Colour: #FFAC2C BG_Colour: #240A67
Bilbao (BIL): City: Bilbao, Popularity: 3, Arena: 949, FG_Colour: #662D91 BG_Colour: #F7BE19
Oviedo (OVI): City: Oviedo, Popularity: 2, Arena: 950, FG_Colour: #EFEFEF BG_Colour: #002F30
Córdoba (COR): City: Córdoba, Popularity: 5, Arena: 951, FG_Colour: #FFFFFF BG_Colour: #00A000
Sabadell (SAB): City: Sabadell, Popularity: 2, Arena: 952, FG_Colour: #FFC61E BG_Colour: #000C03
Málaga (MAL): City: Málaga, Popularity: 3, Arena: 953, FG_Colour: #FAE051 BG_Colour: #002469
Castellón de la Plana (CAS): City: Castellón de la Plana, Popularity: 3, Arena: 954, FG_Colour: #0021A5 BG_Colour: #FF4A00
Santander (SAN): City: Santander, Popularity: 3, Arena: 955, FG_Colour: #000000 BG_Colour: #F47B20
Badajoz (BAZ): City: Badajoz, Popularity: 3, Arena: 956, FG_Colour: #818285 BG_Colour: #105D95
Lleida (LLE): City: Lleida, Popularity: 2, Arena: 957, FG_Colour: #FF652B BG_Colour: #002E4D
Vigo (VIG): City: Vigo, Popularity: 5, Arena: 958, FG_Colour: #FFCC33 BG_Colour: #000000
SirBlurton
12-14-2016, 04:25 PM
Very cool! It's interesting to watch your decision making process as you work through the project! Probably a wise move to put GUI aside, from screenshots it looks like you've done enough to know you can do that if that's the direction you want to go!
If there are ever any tasks you think you could use a hand with let me know...I'm still very much a beginner but I've been working away at building little bits and pieces like name generators and cartoon face generators. Just starting to learn about databases and flask right now.
I know that would likely be too complicated to manage and it's also "your baby" - but just wanted to put it out there if there's ever a piece you could use help on.
Continuing to watch for your updates! :)
Groundhog
12-14-2016, 09:26 PM
Yeah, and as a plus I've learnt a good deal about PySide/PyQt/Qt, and have developed UIs for a bunch of other small projects at home and in the office, and created my first "proper" python module to manage stylesheets for widgets, the creatively named qt_widgetstyler, so it has not been time wasted!
While I do think of this as "my baby" to some extent, I'm more than open to help. By no means feel obligated to tackle this, but one thing I was playing with months and months ago and couldn't get it to work quite right is a round-robin schedule creator. When I get a chance I'll post what I put together, and if you can get the dang thing to work, fantastic! It's harder than it sounds, because it needs to take into account a few factors, such as valid days of the week to schedule games, etc. :)
SirBlurton
12-15-2016, 01:53 PM
I have played with scheduling a bit and I definitely wouldn't go in thinking it's easy! But that would be cool to take a look at it...like you said, if I can't do it, it'll be something you can look at again in the future but I'd be interested in taking a crack at it!
CraigSca
12-15-2016, 03:05 PM
I don't want to steal sirBlurton's thunder, but I wouldn't mind lending a hand with things, either. I've been programming my own college football sim but it's coming in drips and drabs and wouldn't mind increasing my skill set.
Groundhog
12-15-2016, 09:15 PM
Hey, the more the merrier as far as I'm concerned :D
Here's a snippet of what I put together actually nearly 12 months ago, checking the time stamp of the file. I've made a few small changes just now, but I haven't tested anything yet, so there may be typos/errors if you try run the below currently (just working out of notepad at the moment...) :)
The part that was frustrating me was the 'get_valid_game_days' method. There's nothing in the code below for this, but I spent an hour or so playing around on the python command line trying to solve this - basically, need to take a date range, and calculate all the valid game days within this range, grouped by week. The timedate library has some cool functionality that should make this possible, but I'll be darned if I could crack it - this was that "math-level" stuff I mentioned above that I struggle with.
For example, if Friday and Saturday were the only valid days, and the date range was for 4 weeks starting this week, the output of this method should be:
[[16/12/2016, 17/12/2016], [23/12/2016, 24/12/2016], [30/12/2016, 31/12/2016], [6/1/2017, 7/1/2017]]
As a rule I generally work with all dates as ordinal numbers - datetime objects have a .toordinal() method. I just trust it more, especially when writing to databases.
class ScheduleGen:
def __init__(self, current_year=2016, season_start_month=9, season_start_day=30, season_end_month=3, season_end_day=28):
self.current_year = current_year
self.season_start_month = season_start_month
def create_round_robin_schedule(self, team_ids=None, play_each_team=3, valid_days={'monday': False, 'tuesday': False, 'wednesday': False, 'thursday': False,
'friday': True, 'saturday': True,
'sunday': True}):
matchups = self.generate_round_robin_matchups(teams=team_ids, play_each_team=play_each_team)
valid_game_days = self.get_valid_game_days(valid_game_days=valid_days)
def get_valid_game_days(self, valid_game_days):
'''
Parameters:
:valid_game_days: (dict) A dictionary of each day of the week and a True/False value.
Returns:
A list of all possible game days, in ordinal format.
'''
# This is where I had trouble. What is needed here is to use the current_year/season_start_month/season_start_day,
# the current_year+1/season_end_month/season_end_date, and the valid_game_days dictionary to compile a list of
# all available dates, grouped in weeks (to ensure teams don't always play multiple games in the same week),
# with the dates stored as ordinals (whole numbers - a feature of the timedate module), similar to this:
# valid_dates = [[734228, 734229],[734235, 734236],[734241. 734242]] etc.
pass
@staticmethod
def generate_round_robin_matchups(teams=None, play_each_team=1):
'''
Note: Bulk of this code taken from: https://gist.github.com/Makistos/7192777
Parameters:
:teams: (list) league team IDs.
:play_each_team: (int) how many times all teams play each other.
Returns:
A list of all valid matchups.
'''
matchups = []
if len(teams) % 2 == 1:
#teams = teams + ['BYE']
teams.append(False)
for i in range((len(teams) - 1) * play_each_team):
mid = round(len(teams) / 2)
l1 = teams[:mid]
l2 = teams[mid:]
l2.reverse()
# Switch sides after each round
if(i % 2 == 1):
matchups = matchups + [ zip(l1, l2) ]
else:
matchups = matchups + [ zip(l2, l1) ]
teams.insert(1, teams.pop())
return matchups
Groundhog
12-15-2016, 09:21 PM
dola
The above was written in Python 2 as well, where as I've moved to 3 now. I think the only impact this has on the above is that the 'generate_round_robin_matchups' method will returns a generator object rather than a list of lists, but this is minor.
edit: fixed some typos in the above.
CraigSca
12-18-2016, 10:39 AM
Is the valid_game_day list only for a single week or for all weeks? Meaning, do I want to state that Mondays and Tuesdays are invalid game dates for ALL weeks, or do I just want an potentially long list showing which days are invalid for the entire year?
Groundhog
12-18-2016, 05:14 PM
For all weeks - so a long list of dates, grouped by weeks, between a start date and end date. What I'm trying to achieve here is a list of all valid game days between two dates. The next step would be assigning all the matchups generated by the 'generate_round_robin_matchups' method to these dates.
Most of the "round robin" leagues around the world have set days that they usually play their games - for European leagues it's generally Sat/Sun (which makes the Euroleague/Eurocup scheduling easier), in Australia it's everyday except Tue/Weds - so that's the behaviour I'm looking for here. The leagues all have the 'valid days' attribute that defines which of these days of the week to schedule games on. This class (will eventually) take that info as a dictionary, generate a list of all the matchups, generate a list of valid game dates, and then assign those matchups to those dates.
CraigSca
12-19-2016, 09:16 AM
For all weeks - so a long list of dates, grouped by weeks, between a start date and end date. What I'm trying to achieve here is a list of all valid game days between two dates. The next step would be assigning all the matchups generated by the 'generate_round_robin_matchups' method to these dates.
Most of the "round robin" leagues around the world have set days that they usually play their games - for European leagues it's generally Sat/Sun (which makes the Euroleague/Eurocup scheduling easier), in Australia it's everyday except Tue/Weds - so that's the behaviour I'm looking for here. The leagues all have the 'valid days' attribute that defines which of these days of the week to schedule games on. This class (will eventually) take that info as a dictionary, generate a list of all the matchups, generate a list of valid game dates, and then assign those matchups to those dates.
Stupid question - and now I see why working in teams can sometimes be hard - when you generate the list of invalid game days (the "falses" in the list) can't you just generate the "trues" as a list for the entire year, keep it in memory and just access it when needed?
Groundhog
12-19-2016, 06:23 PM
No, not a stupid question at all!
Ignore the invalid dates, because we only need those to determine which days are valid, and they will be skipped over. The False/True flags just tell the method that it needs to compile a list of all True days for each week between a range of dates. So if we feed the class a starting date of June 30 2016 up until January 31 2017, the method would first check which days are valid - let's say Saturday and Sunday - and then split up the the period between those dates into units of weeks, and append all the dates of Saturday and Sundays to a list. When we assign the matchups to days, it would be a random.choice() type of operation to randomly assign the individual matchups to these valid dates.
This could all be stored in memory, but this calculation will need to be run for all 70ish leagues once per offseason, as they all have their own unique True/False flags for valid game days, and obviously the dates will also change from year to year.
The final output of this would be a list of all games in a season, with the date, league id, home team id, and road team id, which would then be written to the database.
An instance of this class would be used inside another method somewhere along the lines of (and this is probably full of syntax errors, but just to give you a quick idea):
def generate_all_schedules(dict_of_all_leagues, dict_of_all_teams, current_year=2016, season_start_month=9, season_start_day=30, season_end_month=3,
season_end_day=28):
# dict_of_all leagues: A dictionary with league_id as the key, all league attributes as values in another dict.
# dict_of_all_teams: A dictionary with the league_id as the key, and all team ids in a list as value.
season_schedule = []
sg = ScheduleGen(current_year, season_start_month, season_start_day, season_end_month, season_end_day)
for league in dict_of_all_leagues:
# Create a 'shortcut' reference directly to the league to be processed
l = dict_of_all_leagues[league]
# Create a 'shortcut' reference to list of teams in league
t = dict_of_all_teams[l['id']]
league_sched = sg.create_round_robin_schedule(team_ids=dict_of_all_teams[l['id']], play_each_team=l['play_each_team'], valid_game_days=l['valid_game_days'])
season_schedule.append(league_sched)
return season_schedule
Groundhog
12-19-2016, 08:08 PM
dola
Had another thought. Maybe the way to approach it is to just create a dictionary once of every week and day of the week in that date range, and then use that info to build all the schedules.
CraigSca
12-20-2016, 10:11 AM
No, not a stupid question at all!
Ignore the invalid dates, because we only need those to determine which days are valid, and they will be skipped over. The False/True flags just tell the method that it needs to compile a list of all True days for each week between a range of dates. So if we feed the class a starting date of June 30 2016 up until January 31 2017, the method would first check which days are valid - let's say Saturday and Sunday - and then split up the the period between those dates into units of weeks, and append all the dates of Saturday and Sundays to a list. When we assign the matchups to days, it would be a random.choice() type of operation to randomly assign the individual matchups to these valid dates.
This could all be stored in memory, but this calculation will need to be run for all 70ish leagues once per offseason, as they all have their own unique True/False flags for valid game days, and obviously the dates will also change from year to year.
The final output of this would be a list of all games in a season, with the date, league id, home team id, and road team id, which would then be written to the database.
An instance of this class would be used inside another method somewhere along the lines of (and this is probably full of syntax errors, but just to give you a quick idea):
def generate_all_schedules(dict_of_all_leagues, dict_of_all_teams, current_year=2016, season_start_month=9, season_start_day=30, season_end_month=3,
season_end_day=28):
# dict_of_all leagues: A dictionary with league_id as the key, all league attributes as values in another dict.
# dict_of_all_teams: A dictionary with the league_id as the key, and all team ids in a list as value.
season_schedule = []
sg = ScheduleGen(current_year, season_start_month, season_start_day, season_end_month, season_end_day)
for league in dict_of_all_leagues:
# Create a 'shortcut' reference directly to the league to be processed
l = dict_of_all_leagues[league]
# Create a 'shortcut' reference to list of teams in league
t = dict_of_all_teams[l['id']]
league_sched = sg.create_round_robin_schedule(team_ids=dict_of_all_teams[l['id']], play_each_team=l['play_each_team'], valid_game_days=l['valid_game_days'])
season_schedule.append(league_sched)
return season_schedule
Yeah - getting back to my question - does that mean the list of true/false flags are ONLY for days of the week, or could you have something like false for all Saturdays and also Christmas day?
Groundhog
12-20-2016, 05:27 PM
Yeah - getting back to my question - does that mean the list of true/false flags are ONLY for days of the week, or could you have something like false for all Saturdays and also Christmas day?
Currently, only for days of the week. I did think about things like holidays, but at the same time its a lot of work tracking down holidays/special days for 68 different nations, to have things like no Christmas day games in Australia, or no Sunday games in Israel, etc.
Young Drachma
01-31-2017, 04:49 AM
Spam aside, glad you're still at this.
GoldenCrest Games
03-05-2017, 05:27 PM
Nice to see you're still chugging along!
nilodor
07-07-2017, 10:24 AM
Until this point I've imported all of my data - teams, coaches, leagues, nations, etc. - in a database (sqlite) that I manually populated from csvs full of info I've gathered over the last couple of years, so I spent some time the past couple of days putting together the 'new game' process - main menu/league selections, etc.
Here's a couple of screens:
Title Screen
A quick (5 minute) title screen just to give the standard new/continue/load options.
https://dl.dropboxusercontent.com/s/al51umrxui1dc2t/main.png?dl=0
New Game Menu
This one took a lot longer - the fully functional league selection screen. All the data here is taken from csv files that can be easily modified to add/subtract/modify the leagues included - currently 44 fields per league (some repetitive, like 7 fields for each day of the week that games will be scheduled). Leagues where I've manually created teams (also done via csvs) default to 'import' teams, other leagues will be randomly generated. Active leagues will be bolded (not yet done), and I'll ditch the '#' in the Teams column header.
The Olympics/Tournaments options on the left will be changed to a table like the regional leagues, because it gives me more flexibility with how many I can include without running out of room - there's international tournaments in the Americas, Asia and Africa that I don't plan on looking at now but would probably like to look at down the track:
https://dl.dropboxusercontent.com/s/0h4qp4kuwjw9j5a/new_game.png?dl=0
Nav Bar
Lastly, the top-of-screen navigation bar - not completely functional yet, because I don't have all the screens completed. This image is at 100% actual width, to give you an idea of how wide the UI will be:
https://dl.dropboxusercontent.com/s/zkbymw67f0mjjxx/top_nav.png?dl=0
What did you use to make these screens? They look great, much better than what I'm capable of in PyQt
Groundhog
07-09-2017, 06:44 AM
What did you use to make these screens? They look great, much better than what I'm capable of in PyQt
Just a combination of the Designer app to layout all the widgets, and then applying different stylesheets to each widget within Python. Most of the "magic" is just labels with rounded corners and partially transparent background colours (rgba with an alpha channel that acts as the transparency %), so the background image shows through.
nilodor
07-19-2017, 03:15 PM
Just a combination of the Designer app to layout all the widgets, and then applying different stylesheets to each widget within Python. Most of the "magic" is just labels with rounded corners and partially transparent background colours (rgba with an alpha channel that acts as the transparency %), so the background image shows through.
To get the rounded push buttons did you use something like:
self.pushButton.setPalette(palette)
self.pushButton.setStyleSheet("background-color: rgb(170, 255, 255); border-style:solid; border-color:black; border-radius:20px;border-width:2px;")
self.pushButton.setObjectName("pushButton")
I can't seem to get rounded edges this way
Groundhog
07-19-2017, 10:10 PM
standard_button = '''QPushButton {{
color: rgb{};
background-color: rgb{};
border-bottom-left-radius: {}px;
border-bottom-right-radius: {}px;
border-top-left-radius: {}px;
border-top-right-radius: {}px;
}}
QPushButton:hover {{
background-color: rgb{};
}}
QPushButton:checked {{
background-color: rgb{};
color: rgb{};
}}
QPushButton:disabled {{
color: rgb{};
background-color: rgb{};
border: none;
}}'''
I can't remember if there's a good reason why I used individual corner radius %s vs just a general border-radius setting. Swap out the {} for values and that's straight out of my code.
I didn't use the setPalette or .setObjectName methods for any of my widgets - again, can't remember if that was for a reason or if I just never saw those methods. I created this library (https://pypi.python.org/pypi/qt_widgetstyler/0.1) to let me group widgets and make it easier to manage colour schemes on different team pages, etc. It's super amateur and I'm sure Qt has a way of doing the same thing (possibly .setObjetcName), but it was an easier "python" way for me to handle it.
nilodor
07-20-2017, 09:15 AM
standard_button = '''QPushButton {{
color: rgb{};
background-color: rgb{};
border-bottom-left-radius: {}px;
border-bottom-right-radius: {}px;
border-top-left-radius: {}px;
border-top-right-radius: {}px;
}}
QPushButton:hover {{
background-color: rgb{};
}}
QPushButton:checked {{
background-color: rgb{};
color: rgb{};
}}
QPushButton:disabled {{
color: rgb{};
background-color: rgb{};
border: none;
}}'''
I can't remember if there's a good reason why I used individual corner radius %s vs just a general border-radius setting. Swap out the {} for values and that's straight out of my code.
I didn't use the setPalette or .setObjectName methods for any of my widgets - again, can't remember if that was for a reason or if I just never saw those methods. I created this library (https://pypi.python.org/pypi/qt_widgetstyler/0.1) to let me group widgets and make it easier to manage colour schemes on different team pages, etc. It's super amateur and I'm sure Qt has a way of doing the same thing (possibly .setObjetcName), but it was an easier "python" way for me to handle it.
Weird, doing each corner separately seems to work. I think that you have to use the palette command if you're using PyQt5. Like how the QMainWindow moved from PyGui to QtWidgets.
One thing I'm going to have to work on is the ability to batch import. I've got it set up that I can read in constants from a text file for athlete generation, but I'm not sure how to do it to create a generic style sheet based on the country the player is using.
Maybe I'll actually post in my thread to discuss it better. :)
Groundhog
07-20-2017, 10:02 PM
Happy to show you how I do it, PM me if you want a quick example.
CrescentMoonie
07-20-2017, 10:21 PM
Enjoyed reading through this. I'm at the very beginning of my journey learning languages, starting with a couple of web developer courses through Udemy (nearly everything on the site was $10 around July 4). After that I'm looking at Ruby on Rails, I already bought an intro course on it, and maybe Python after that. This dynasty will definitely help in my sections on Python in the two developer courses.
Groundhog
06-25-2019, 06:43 PM
Just an update on this, because I get the odd PM here and there, especially from folks who find this thread via google. The two questions I get are:
Did I ever finish this? No. Real life, in particular a promotion at work and having a kid, made this begin to feel like work to be completely honest. I was spending 2-3 hours a night coding in bed, and I was beginning to have dreams involving Python functions, which was a bit strange. I decided to take a break from it and never got the urge to pick it up again. I'm sure I will down the line however, and I've still kept up with Python and use it from time-to-time at work.
Is it possible to program something like this in Python? Absolutely yes, and probably a lot better than how I was doing it. Even the GUI I think looked OK, but as I touched on a few times in this thread I would ignore this component it if I did this again and look at a web GUI (and probably paying someone to build one rather than learning it myself).
Groundhog
04-27-2026, 12:44 AM
So, it's been a minute!
About 2 years ago I picked this up again, focusing just on python and the simulation engine. I got it to the point where I could simulate a full 48 minute NBA-style game, or a slower paced FIBA 40 minute game with generated rosters and produce stats that were within the realm of reality. I'd started sketching out some free agency rules and scheduling when the age of AI 'vibecoding' arrived. I'd been using Claude code already to build some simple web apps (listbuilders for some of my favourite tabletop wargames) as well as some dumb browser-based games.
What if I connected Claude up to my basketball text sim though...
<code>https://drive.google.com/thumbnail?id=152u6eMRsPzihbIUii396Q6Hks8IRiQmb&sz=w1000</code>
Welp. 6 months of pretty consistent dabbling later, I essentially have a working single-player basketball simulator. You can generate an entire world full of fictional players/teams/coaches, select a team in any league (a mix of real & fictional leagues), hire coaches, sign players, set rotations. There's a tiered financial model in place for the international leagues, and an NBA-style model for the US league. Both still need work, particularly the NBA-style model, but they are functional.
https://drive.google.com/thumbnail?id=1htPUl9hLCbEduX1QpJvI4LK6ylIjzQov&sz=w1000
Internationally you can manage a team, select a 15-man roster from eligible (and willing) players, then compete to qualify for and then to win gold at FIBA World Cup and Olympics events. As with the league financial models it's a bit bare-bones at the moment and a focus over the next few months, particularly the UI elements.
https://drive.google.com/thumbnail?id=1FviFNHDf5tzU4GsqLMqRhc9SafzO21Ic&sz=w1000
https://drive.google.com/thumbnail?id=1rX_4FNM1wnxaL-HCbE0OLyksR7nUUJEb&sz=w1000
One recent feature I'm real happy with is that, due to the number of leagues/teams/players, simming a whole season initially took a long time - once I'd implemented all offseason tasks, it was about 4 hours beginning-to-end. I managed to cut that into a third, but I still wasn't happy with the speed so I worked on 3 separate engines that can be selected per league when you generate a new universe.
First is the "standard" engine, where each possession is simulated in detail. My goal with the engine was something both simple but realistic - if FBCB is on one end of the spectrum and JBL on the other, I'm shooting for single player-only FBCB+. There are plays with branching options, but a good amount of "abstraction" - an honest way to put it is that the focus is on having the end-results look realistic while being influenced by a number of different attributes, factors, and random dice rolls - to pick one example, each play doesn't track where each player is on the court at each step, however there is a chance each possession of a negative modifier to a PF or C's defensive rebounding ability if he's guarding a stretch big.
Next there's a "fastsim" engine. This is a simplified version of the standard engine built for speed, and strategy and coach settings matter less. It's much quicker though, and results have been tuned to give essentially like-for-like stats for players and teams between the standard and fastsim engines.
And finally there's the background sim engine which essentially just calculates stats from ratings - very simple, very quick, and again tuned to try and replicate the same individual stats as the standard and fastsim engines. This allows for having every single league in the world simulating games and producing stats - doing a day of games for 90 background leagues takes seconds - rather than only having about 10 leagues active which was the largest manageable amount previously.
There's still a whole let left to do and no end in sight, but thought I'd provide an update here considering this is where it all began!
dgmulligan
04-28-2026, 12:01 AM
Whoa! This is looking great!
Any plans to get this up on Steam? I'd be really interested in grabbing a game like this as the only comparable competitor, Pro Basketball Manager, is not quite nailing the realism from simming results that I want to see.
Groundhog
04-28-2026, 05:57 PM
Thanks! Eventually I would like to put this up on Steam if I can get it to a state I'm satisfied with. I've also tried Pro Basketball Manager, although quite a few years ago now, and agree. It had promise but doesn't look like it's really changed much at all over the years.
dgmulligan
04-28-2026, 11:42 PM
That's great, keep posting as you progress and if it ever makes it to Steam I'll try and buy early access.
We always need more sports games filling different niches.
:)
And if you ever need any help please reach out. I worked on the DB team for Pro Basketball Manager and also have done edits on OOTP and Jump Shot Basketball. Love helping improve sports sims.
Young Drachma
05-04-2026, 01:57 PM
Hell yeah way to go getting back to this. It's so much easier to do this stuff now. Yours looks like it's heading in good shape and I trust us text sim dorks better than most to make these games. Keep it up! I think FBCB+ is a good target too. the resilience of that sim was that it was customizeable but didn't try to do more than it did. All most of us need is a sandbox RNG that's better than pen/paper/dice.
Groundhog
05-06-2026, 08:57 PM
Hell yeah way to go getting back to this. It's so much easier to do this stuff now. Yours looks like it's heading in good shape and I trust us text sim dorks better than most to make these games. Keep it up! I think FBCB+ is a good target too. the resilience of that sim was that it was customizeable but didn't try to do more than it did. All most of us need is a sandbox RNG that's better than pen/paper/dice.
Cheers and agree - the customization and sandboxiness of this was about 90% of the effort even before AI came along. Nearly 100% of the effort at this point is ironing out free agency logic and single player logic for the international leagues, before I dive into the NBA which has not really been a focus as yet outside of the getting the CPU-controlled teams to manage themselves in a realistic-ish way, but is currently a dull human-controlled experience with no trade UI and no enforcement of salary rules. That's the biggest hurdle to tackle before I think about release.
One other thing I worked on last weekend was building a new universe menu - WWBM has a number of cvs files that dictate which leagues are active/background, which leagues have real team/league information available, which are fictional, etc. I would need to manually update these files when testing different configurations - ie. only 1 or 2 active leagues when I want to quick-sim a number of seasons, a handful of low prestige leagues when I want to test low prestige team Free Agency, etc. Claude basically one-shot this based on the structure of the cvs, and all I did was tweak the styling of the page and add filters:
<code>https://drive.google.com/thumbnail?id=14Fb8v8PNgmWRiCNX25mEv2OBTB3SR5YQ&sz=w1000</code>
The way I liked to play FBCB was also ultra slow-paced, coaching every single game - making subs, adjusting strategy, setting priority offensive player, etc. Given it takes some time to get through a season, I feel this is probably what WWBM best caters to as well. This required a pretty significant change to the sim engine to allow for stepping through each possession, wait for user input (click 'next possession'), and then proceed to sim the next possession.
I made a start on this a little while ago, creating a "live sim" screen with a scoreboard that updates as the game - I mocked the scoreboard up in Google Draw and made the images for all the numbers/characters using GIMP in about 5 mins as a starting point but I would eventually like to improve both.
Claude also one shot this - not sure why at this point I continue to be surprised by stuff like this. I put fluro green boxes where I wanted to overlay the digital numbers/letters, and Claude absolutely nailed it based on those placeholders - I had to correct the penalty icons, but otherwise it was flawless. The little hamburger icon on the top right of the scoreboard options up coach settings - I've added the options I eventually want in place, but it's just placeholder buttons at this point with no functionality:
<code>https://drive.google.com/thumbnail?id=1EgKT-C-JEMqc4P1m0qU6Al8eg9MkNn2F&sz=w1000</code>
Young Drachma
05-07-2026, 12:02 AM
yeah this looks solid for sure. Super excited you're doing this.
Groundhog
06-02-2026, 05:39 AM
Spent weeks trying to get non-NBA free agency quirks ironed out, and happy with where it's at now although still troubleshooting something I noticed where an extra year is being subtracted from contracts during the offseason which is annoying, and I haven't squished it yet, but I think it's related to the multi-season sim function only.
For fun, let's take a deep-dive look at the 2032 Olympics. There's a qualification tournament the offseason prior, with the host automatically qualifying. A total of 12 teams qualify - 2 from Asia/Oceania, 2 from Africa, 3 from the Americas, and 4 from Europe.
2032 Olympics
Host Country: Czech Republic
Africa Region:
South Sudan (FIBA Rank: #17)
I configured diaspora players for nations such as South Sudan to represent the large population of players that end up based in Australia, Europe, and the USA originating from or as 2nd generation from these nations, but one thing I haven't yet configured is the chance that they'll represent their "original" nation. This makes South Sudan weaker than they would be otherwise, and they are led by a handful of domestic players with no players in their squad of 15 with experience outside of South Sudan. 19-year old PF Michael Makuei was named captain, and at 7'1 he absolutely dominated the (fictional) South Sudanese league, picking up MVP honours for the Aweil Rogues behind 20.0 PPG and 12.9 RPG. It's fair to say that the talent level of the South Sudan league is well below the calibre of players South Sudan will face at this level however, and Makuei and the rest of the fairly young South Sudanese struggle will likely struggle to win a game in the group stages.
Tunisia (FIBA Rank: #20)
The biggest news for Tunisia was their leading scorer from the qualification tournament, 22-year old guard Iyadh Hamdi, not making himself available for the Olympics. Players have a patriotism rating that impacts how likely they are to make themselves available, with the top USA players electing to not participate more frequently than other nations. I will likely adjust to make it more likely players will miss qualification tournaments than Olympics/World Cups. Even with Hamdi winning a game would have been an uphill task. The squad of 15 are all domestic players, with another young big, 23-year old Ridha Marzouki, being named captain. Marzouki is a solid defender and can stretch the floor a little, but won't be enough to make up for the lack of quality in the backcourt for Tunisia.
Americas Region
Brazil (FIBA Rank: #7)
Brazil and Canada have been neck and neck behind the USA in the Americas, with Canada missing out due to a tiebreaker this year. Brazil are led by 23-year old PF Lucas Rocha, who initially went undrafted before earning a minimum contract with Orlando (who seem to love signing internationals!), and is now entering season #4 with OKC. Former 2nd round draft pick Paulo Silva, the highest overall rated Brazilian, did not make himself available, however Brazil have a good mix of veterans and young talent on NBA rosters. They should be in the medal picture, even without Silva.
Puerto Rico (FIBA Rank: #10)
Puerto Rico are in the third-tier of American nations, but are still a chance at making some noise this tournament. José Flores, a 23-year old SG entering his 4th season with the Cleveland Cavs are being selected #21 in the 2028 draft, had taken a back seat to more senior players previously for Puerto Rico, but figures to see a bigger role this year. He is joined by SF Eric Rosario, a 25-year old who just signed with the Utah Jazz this past offseason after dominating the Puerto Rico league, and should give this squad a nice 1-2 punch to make some noise. Whether it's enough to get them out of the pools will depend a lot on matchups, as Puerto Rico do not have the same type of quality inside as they do on the perimeter, and there are a lot of quality bigs in this tournament who might take advantage.
Team USA (FIBA Rank: #2)
Looking for revenge after the last Olympics, Team USA has a solid core of their best players available - outside of at PG, interestingly. This squad includes my favourite guy in this universe - 39-year old, 7'6 Kethan Taylor Jr. At age 38 for Sacramento last year he averaged 16.4-6.9-4.9, and is one of the smartest players in the game on both ends. For whatever reason though, NBA PGs were not interested in Olympics basketball. With 3 PGs on the squad, 2 of them are playing in the top leagues in Europe, and one is 6th man for Indiana. Despite this, talent-wise this is clearly the best team in the tournament, although not by a whole lot - Serbia and Australia are close. A lot of the talent does overlap however, with the frontcourt in particular having a lot of similar players who will take minutes away from each other. I'm predicting Silver or Bronze and hopefully some PGs decide to play next tournament!
Asia Region
Australia (FIBA Rank: #1)
Believe it or not I didn't set out to make Australia over-powered... Most universes I've simulated they've roughly equated to where they are right now in real life, but Australia has had some real lucky rolls in this universe. Leading the way for Australia is undersized pivot Dion Burns. The 6'7 all-rounder spent the last 8 seasons with Orlando Magic before signing with an Australian team this offseason at age 33. In fact all 15 of the Australian squad have spent at least one season in the NBA, with 4 on a current NBA roster and 1 playing in Spain. Australia are the reigning Olympics gold medalists, having beaten Canada in the Gold Medal game in 2028 - a year when Canada knocked Team USA out in the elimination round. They're a medal favourite for sure, and anything less than Bronze would be a disappointment.
China (FIBA Rank: #14)
China have been consistently strong, and along with New Zealand and Australia make up the clear best 3 teams in Asia. They were lucky to not have to face New Zealand in qualification, and both sides finished undefeated with total point differential being the decider. China are led by Xin Lin, a 31-year old SG who averaged 17.1 PPG for the San Antonio Spurs last season, and a mainstay of their international team. He elected not to play at these Olympics however. China will instead be relying heavily on veteran wing Nan Weiwei - Weiwei had a very brief NBA career and is currently playing in Japan after a few seasons in a couple of the top international leagues (France, Turkey), and has always played well for his country, averaging 17PPG in 13 appearances. It's a limited ceiling for this China squad despite some former NBA talent on the squad, but depending on matchups they may sneak out of the pool stage.
Europe Region:
Czech Republic (FIBA Rank: #11)
The host nation may have auto-qualified, but they would have been a fighter's chance without it. Although they have a few guys with previous NBA experience, this is a veteran squad with most of their projected rotation all starting in Spain and other strong leagues like Greece and Lithuania. 35-year old guard Daniel Adamčík is still going strong for his age, and I think this is a squad that might over-achieve if they get the right pool.
France (FIBA Rank: #15)
Safe to say France have underachieved in this universe, despite a wealth of talent. A 4-0 result in the qualification tournament shows what they are capable of however. Veteran pivot Amatégué Akobi had a lengthy career as a role player in the NBA with Orlando before making his way back to France to finish out his career, where he is sort of a poor man's Jokic, averaging 17.3-7.9-4.2 in his most recent season for Boulazac. He is support by a talented young core of players in their early 20s on NBA rosters. The future looks bright for France, and failing to get out of their pool would be a massive underachievement.
Italy (FIBA Rank: #16)
Having Italy sit just one rank behind France shows how brutal being in Europe is, especially with the only way to accrue FIBA points currently being through the qualification tournaments & tournament appearances. Italy have a good mix of veterans with NBA experience, such as captain Charles Baffour who averaged 21 PPG for Toronto last season before signing back in Italy this year at 35 years of age. They also have a talented younger crop of players led by current NBA players PG Pietro Cappelluzzo and forward Dario Amenta. They are not a team anyone would be happy to see in their pool and should advance to the elimination round.
Serbia (FIBA Rank: #3)
Although Jokic doesn't exist in this universe, Serbia still have probably the best overall talent level outside of the USA. 25-year old guard Bojan Marković has been dropping over 20PPG for Phoenix in the NBA and doing much same in 14 appearances for Serbia. 23-year old PG Filip Đorđević is another fun one to watch. After going undrafted and seeing spot minutes for New York, he dominated the Spanish league and earnt himself a nice $12m contract with Toronto this past offseason. I'm expecting a Spain-USA Gold medal showdown.
Turkey (FIBA Rank: #13)
In a similar boat to Italy & France, Turkey have for the most part under-performed despite a talented assortment of players. They are led by veteran forward Emre Çetin who is sort of like a Boris Diaw type, if Boris Diaw was 6'11 and super athletic. He spent his 20s with Orlando, with his best season being in 2026 where he averaged a career-best 10.1 PPG to go with 8.9 RPG and 4.3 APG, and has been playing for Manisa in Turkey the past 4 seasons. Outside of Cetin Turkey has a handful of guys in the NBA, with 24-year old Yusuf Jade working his way back into the NBA this offseason. Originally the #15 pick in the draft, he had a solid if erratic start to his NBA career, traded twice and ending up seeing his production dip in his 4th season. He dominated the Turkish league the last 2 seasons and earnt an $18m/3 deal with Orlando this past offseason. I put Turkey in a similar bucket to Italy & France - they have the talent to make some noise, but it would take some luck for them to medal.
Groundhog
06-02-2026, 06:25 AM
https://drive.google.com/thumbnail?id=1BZJpIH_BKDkcLb6IMpm4_Pg0JWIULOMa&sz=w1000
Group A
It was fairly smooth sailing for Team USA. Puerto Rico stuck with them for 3 quarters but were let down by a 15-point 3rd. I'd say that was maybe a worrying result for Team USA, but as we'll see other results were more favourable to their chances... China beat down hapless Tunisia, but were just outclassed by both Puerto Rico and Team USA, which was no surprise. 6th man Magnum Dunleavy, the Pacers 6th man PG, has done admirable for Team USA at PG it must be said - not the weakness it perhaps first seemed. Dunleavy had 14 points and 7 assists against Puerto Rico.
Group B
As expected Australia-Brazil was a cracker, with Australia jumping out to an early lead and then holding off the Brazilian comeback. Rocha led the way for Brazil with 26 points and 9 rebounds, but fouled out late which helped seal Brazil's fate, while Burns had 20 points and 9 rebounds for Australia. The host nation were fairly underwhelming, getting annihilated by Australia to open the tournament, doing marginally better vs Brazil, and then curb stomping poor South Sudan, they are a shot to make it though depending on other results. Disappointing showing for the hosts overall though. As expected, South Sudan just not enough talent. Young captain Makuei struggled as expected, managing just 8.3ppg and 1.7rpg across the group stage games.
Group C
Well wasn't this a mess. Hosts aside, all European teams were placed in the same Group, and just beat up on each other... well, Italy aside. Serbia's disappointing 24-point loss to Turkey to open the tournament meant they were starting well behind the 8-ball and could just not catch up, losing the 3-way tie, although they will still advance with a poor matchup likely - Australia or Team USA at a guess. Despite dropping a game to France, I think Turkey overall look like the biggest threat out of this group. Italy were not really competitive and would be disappointed in their tournament.
As a total of 8 teams qualify, the "next best" 2 teams to make the knockout stages are: Hosts Czech Republic and ... Serbia!
Groundhog
06-02-2026, 07:16 AM
Quarterfinals
Australia defeat Czech Republic 101-79
The hosts started strong, leading 33-27 after the first quarter, but managed just a total of 28 points in the 2nd half versus 56 for Australia in the same. Adamčík led CR with 24 points on 9-20 shooting but didn't get a lot of help, while a balanced attack for Australia saw 6 players in double-digits, led by Orlando Magic starting SG Paul Matthews with 20 points and 6 rebounds.
Team USA defeat Serbia 122-77
Youch. Serbia were just completely annihilated right out of the gate, down 36-15 at quarter time and it didn't get much better for them. Offensively Serbia got some nice contributions from a few players, but you aren't going to win many games when you let your opponent shoot .623 from the floor. Kethan Taylor Jr top scored for USA with 19 points, while Dunleavy continued to impress with a near triple-double - 8 points, 7 rebounds, and 8 assists. The end of a disappointing tournament for Serbia, and although USA will be feeling confident, they are now matched up against Australia for a chance at either gold or bronze.
Puerto Rico defeat Turkey 100-90
I was really surprised by this result. It was a close game until Puerto Rico pulled away in the 4th Q behind the Pedro Fox show. The 39-year old veteran big dominated Turkey inside - I underestimated how well Fox would play, as despite his age he had not played for his country before this tournament and had just put up monster scoring numbers in Puerto Rico, however I didn't notice that earlier in his career he had a season in Japan and Australia and did similar damage - high 20s in points. He looks like one of the best guys to never get an NBA shot. Pedro averaged 26 ppg in the group stages, and sent Turkey home without a medal with 28 points on 10-17 shooting in this one. Unfortunate result for Turkey.
France defeats Brazil 108-101
This was a really good game, with France jumping out to a 34-18 lead, before Brazil fought back in the 3rd Q, with France just doing enough to keep the game out of reach. French SG Pascal Kameni Fru had his best game of the tournament with 23 points, and the French team shot 12-23 from deep, which effectively countered the incredible 30-30 FT shooting for Brazil. That would have to be a record - Brazil have an unusual amount of insanely good FT shooters in their squad, a statistical anomaly. France continue to overachieve but will have their work cut out of them with a 2nd round matchup against Puerto Rico.
Semi Finals
Australia defeats Team USA 101-93
Heartbreak for Team USA as they find themselves now playing for Bronze Australia maintained a narrow lead for most of the game, but a good 3rd quarter from Team USA meant Australia were clinging on for dear life, needing to hit free throws late to keep it out of reach. Another great showing for Dunleavy even in defeat, managing 16 points, 6 rebounds and 5 assists to lead USA, while Paul Matthews had another strong game with 21 points and 6 rebounds to give Australia a shot at back-to-back gold medals!
Puerto Rico defeat France 110-103
Pedro Fox is a bad, bad man. 33 points on 12-19 shooting. Youch. It took a massive 40 point 3rd quarter, but Puerto Rico held off a determined French side who got contributions across the board with 6 players in double-digits. Puerto Rico with a chance at gold, and the first time they've ever finished top 3 in a tournament.
Bronze Medal Game
Team USA defeat France 98-81
It was a valiant effort for France, who entered halftime with a narrow 42-40 lead. Team USA just had too much depth in the end, thoroughly outplaying France in the 2nd half to take home what would be a disappointing Bronze medal. Defensive specialist wing Hakeem Godbold was the star in this one, with 18 points and 3 steals. Charlotte big Robert Faure led the way for France with 25 points and 12 rebounds. France would be disappointed to lose, but fair to say they overachieved, especially consider how the rest of the European teams fared.
Gold Medal Game
Australia defeat Puerto Rico 85-78
Both team's leaned on their defenses in this one, with Australia managing to do what no one else had done all tournament - contain Fox. Pedro ended up with 19 points on just 7-19 shooting, his only subpar performance in the tournament. Aussie veteran big Shaun Volkman definitely the MVP of the game, dropping 21 points and 11 rebounds while matching up on Fox down the other end of the court. Back to back Gold medals for Australia, and Puerto Rico with Silver and by far the biggest surprise of the tournament. Despite Fox's performance in the final, he is named overall MVP for the tournament, averaging 26.7 PPG on a blistering .561 shooting.
https://drive.google.com/thumbnail?id=1HmPKJjuXkLij9okFxidyxLXMF6OZsdDQ&sz=w1000
Young Drachma
06-02-2026, 12:56 PM
the diaspora nations thing is a big one, FM does the second and third nationalities better than any text sim and it's a nice touch.
How do you feel about the sim engine itself? Optimizing for realism, a bit of randomness? Both? I've opted largely in my game to stay out of the weeds because no one else is going to play them but me, but I feel like I need to look at it and have it feel like the sport.
Groundhog
06-03-2026, 02:27 AM
Agree re: FM. That's one aspect of the game I tried to capture (along with the global FA market).
For me re: realism/randomness, a little bit of both. The halfcourt loop is something like:
* Determine how quick the offense wants to play
* Determine if the defense "lets" them (offensive awareness vs defensive awareness, coaches play a roll along with randomness)
* Select a branching finishing play based on random chance/different shooting aggression types, and then the play branches based on a number of factors - drive aggression, play making ability, defense, etc.
Plenty of random rolls along the way and tests for fouls/turnovers, but it's important to me that there's some method to the madness. The background engine is purely results driven - ratings generate stats, and those stats are tuned against the real engine to be comparable, however there is no halfcourt loop, strategy doesn't apply, coaches don't matter, etc.
vBulletin v3.6.0, Copyright ©2000-2026, Jelsoft Enterprises Ltd.