Basketball GM is a single-player basketball management simulation game. Set your roster, make trades, draft prospects, manage your finances, and try to build a dynasty. Play it in your browser now, completely 100% free!
Basketball GM 4.0 was released a week ago. It made game simulation about 10x faster. After releasing it, I was very curious how players would respond. If they played the same amount of time, they could simulate 10 times more seasons. Or they could play 1/10 of the time, but simulate the same amount of seasons. Or something in between. Or maybe they’d even change how they play, like focusing more or less on the details of the game.
Let’s look at some numbers.
|Last Week||This Week||% Change|
|Pages / Session||70.5||90.01||27.67%|
|Avg Session Duration (minutes)||30.25||27.5||-9.09%|
The biggest change is that nearly twice as many seasons were played. And since the number of new leagues went up only 51%, it suggests that people are playing more seasons per league, which makes sense.
The other interesting thing is that while pageviews per session are up 28%, suggesting that users are doing more stuff per session, the actual time played per session is down by 3 minutes. So more is being done in less time.
How has version 4.0 changed how you play? Let me know on Reddit.
Google made me do it.
Basketball GM has always allowed you to open up the same league in multiple tabs, so you can easily view multiple different screens. This was originally implemented by running the entire game in each tab. Game data was always saved to disk via IndexedDB. And when an action resulted in a change to the data (such as playing a game, signing a contract, trading a player, etc), then a signal was sent to all other tabs telling them to update their data. This was kind of a crude approach, but it worked.
When life hands you lemons, make lemonade. Since Google decided to totally invalidate the tradeoffs I had considered when designing Basketball GM, I decided to re-evaluate. I came up with two ideas:
These would both be major changes to the core of the game code. I had considered doing both previously, but never made a serious attempt because it was so much work. But now, with Google fucking up multi-tab play, I really needed to do something. So I began “Project Iverson” (get it, speed?) and set off with great optimism working on the first task, replacing most database calls with a cache.
The basic idea of my cache is that all data needed for “normal” gameplay should be stored in memory. Database access should only be needed for viewing historical data. For example, all non-retired players are stored in memory, but retired players are not. So game simulation can happen without database access, and most pages can be displayed without database access. Only viewing pages that display retired players requires reading data from disk.
It sounds simple, but it’s easier said than done. Some of the challenges included:
Ultimately, I came up with (IMHO) a pretty decent caching module and started using it everywhere. I started with game simulation, the most CPU intensive part of the codebase. Results were promising – a 50% speedup! But as I converted more and more of the UI to my cache, that 50% speedup gradually disappeared. Even worse, the UI became less and less responsive to the point where it actually felt worse than it did originally! Hey, what gives?
This was not good. I had just spent a lot of time working on this thing, and it just made things worse! But did I give up? No! On to phase 2, leveraging Workers!
Based on that, I started with a Web Worker, because it’s simple and globally supported. However, Web Workers do not make it easy to support multi-tab play, and that was originally the whole point of all this, right? So my plan was to get it working in a Web Worker, and then add support for Shared Workers in browsers that support it.
This also required a ton of work! Because Basketball GM runs entirely client-side in your browser, it was easy for some bad habits to creep in. There was fairly tight coupling between the UI (code to display pages, handle links, update UI) and the backend (game logic, data manipulation). But I wanted to run the backend in a Worker, which required entirely splitting the UI and backend, and defining an API for them to communicate. I wrote a nifty little library called promise-worker-bi to make communication easier. But there was a serious technical problem, a more troubling incantation of a problem I had dealt with in the past: IndexedDB and Promises don’t play nice.
Let’s do some more technical digression. As mentioned above, my cache still ultimately saved game data to IndexedDB. IndexedDB has a ridiculously horrible and painful API which can only be rescued by wrapping it in Promises, which is the new standard way of handling asynchronous operations (IndexedDB has a crazy amount of asynchronous stuff going on). I wrote a library called backboard which does a decent job of this, and I’ve been using it in Basketball GM for a while.
However, it doesn’t work inside a Worker! Well, it does in Chrome, but not in Firefox! Why? Because Promises in Firefox don’t play nice with IndexedDB. Previously I had hacked around this by using a third party Promise implementation rather than native Promises. However the tricks that these third party Promise implementations use to play nice with IndexedDB do not work inside Workers! Fuck.
I spent a while trying to figure out a nice way around this, but there wasn’t one. Fortunately, the cache meant that I didn’t actually have that much code doing complicated stuff with IndexedDB. So I very carefully went over all of my database code and ensured that it didn’t mix IndexedDB and Promises.
Eventually I got it to run. I decided to do a quick benchmark and see if now, after all this, it actually has decent performance. I was optimistically hoping to recover that 50% speedup I initially saw after implementing the cache. Instead, I saw a 10x increase in performance! Fuck. I was really excited. I made a vague post on /r/BasketballGM entitled HOLY SHIT YOU GUYS HAVE NO IDEA WHAT I JUST ACCOMPLISHED and celebrated my accomplishment by taking my girl out to McDonald’s where I got a well-deserved Grand Mac and she got a less well-deserved Mac Jr.
But wait, how was that possible? 10x performance boosts don’t just fall out of thin air. I think a big part of the answer is that I (like many others) assumed that IndexedDB being asynchronous meant that it would not interfere with the UI, that I could do IndexedDB stuff in the main thread without impeding performance. But I never actually tested that. I did notice that Nolan Lawson showed it was actually not true, but I still wasn’t expecting a 10x performance increase! Moral of the story: if you are doing IndexedDB, put that shit in a Worker! Even if you have to suffer through IndexedDB/Promise incompatibilities. 10x is worth it.
Around this time, there was even more good news. Safari has long had very serious issues with IndexedDB, so Basketball GM never ran in Safari. And because Apple is evil and won’t let anyone else release a browser on iOS (no, Chrome is not real Chrome on iOS, it’s just Safari with a different UI), Basketball GM has never worked on iPhones and iPads. But then Apple released Safari 10.0.3, which was good enough to partially play Basketball GM. It would let you get up to the playoffs, at least. But on my development version, it actually worked really well! Sure it was a bit slower than other browsers, but finally there were no weird errors! So I figured I was going to release a new version of Basketball GM that (1) has fixed Chrome multi-tab support; (2) is way faster; and (3) works in Safari. I released a beta, to much critical acclaim.
But wait, I hadn’t actually fixed multi-tab support. The beta only let you open a league in one tab, which kind of sucked. My plan for that was to move from a Web Worker to a Shared Worker, but that wouldn’t work in Safari because Safari doesn’t support Shared Workers. Crap. I decided to implement Shared Worker support, but keep the Web Worker mode around as a fallback. I put a lot more work into promise-worker-bi to make it seamlessly work with either a Shared Worker or a Web Worker. I rewrote a lot of backend code that made the assumption that it was only talking to one instance of the UI. Humorously, I introduced a new limitation: instead of letting you only open one tab for a league, you could only open one league (but in as many tabs as you want). I figured that is a more palatable constraint, and it would be a lot of work to make the backend support having multiple leagues loaded at the same time.
And all this worked! The Shared Worker did run a bit slower than the Web Worker, for whatever reason. But it was still way faster than before. And multi-tab play was back, even in Chrome! Because the Shared Worker is shared by all tabs, the whole “simulation running in a background tab” thing never happens. It’s always in the active tab!
I released another beta for people to test. By the way, I really appreciate all the testing people did, a lot of bugs were found in the beta. Special shout out to the homie Jerick Don San Juan for finding the most.
While I was waiting for people to test it, some other stuff happened:
Some people on the subreddit asked for table filtering, so I added it. It was actually very easy to add, and it was purely a UI thing so it didn’t interfere with any of the other in-progress work.
And then, Apple. Safari 10.1 was released, which boasted much improved IndexedDB support. I was excited! Are the bugs really all gone? Is it going to be as fast as Chrome and Firefox? Initial reports were that it was good enough to fully run the old version of Basketball GM. But what about the beta? After forcing my girlfriend to update her iPhone, I eagerly waited to see the result. And the result was… fuck, a hard crash of the browser? Are you fucking kidding me? Jesus, Apple, get your shit together!
Well I wasn’t about to hold up the 4.0 release just for Apple, so I said fuck it and released version 4.0 on April 1, an anti April Fool’s Day joke. The real joke is that, after several years of Safari being shitty in various ways, there were actually a couple weeks where it worked either on the beta (Safari 10.0.3) or on the old version of Basketball GM (Safari 10.1). But now it’s back to not working. What else is new? So the race remains – will Apple or Microsoft be first to make a browser good enough to consistently run Basketball GM? Time will tell…
If you somehow made it to the end of this post, go check out Basketball GM on GitHub. Play around with the code. Contributors are always welcome. At least give me a damn star!
This is not an April Fool’s Day joke! Basketball GM 4.0 is really here, and it’s awesome. For those who haven’t been following along, here are the biggest changes:
I’ll make another post with more technical details soon, for those who are interested in such things.
Thanks you everyone for testing the first beta! All of the bugs people found have been fixed. The biggest change from the first beta is that multi-tab play is back. There are two caveats. First, you can only have one league open at a time. I figure this is not a huge problem, but let me know if you often do have multiple leagues open. Second, it is a little slower than the first beta, but still much faster than it used to be.
Also, multi-tab play does not work in Safari, so you can either play one tab per league in Safari or use another browser.
With that being said, here’s the link to beta 2, please give it a try. To make sure you’re on beta 2 and not the original beta, look at the bottom of the page. It should have a date of 2017.03.24.1059 or later.
I’ll make another post with more details later, but right now, the beta brings some good and some bad:
Good: Performance, performance, performance. Depending on your computer and browser, you will find it about 10 times faster. That is a lot.
Good: It works in Safari now, although it’s not that well tested.
Bad: You can only open a league in one tab at a time. I do have plans to restore multi-tab play, but I didn’t want to hold up the beta for it. And single-tab mode will be needed as a fallback mode in some browsers, so it will be good to test it. Please do give me feedback on this. If single-tab mode is horrible, I will prioritize multi-tab mode.
(Also, accounts don’t work on the beta. They aren’t supposed to. That’s not a bug.)
On March 18, 2015, Basketball GM passed 1 million seasons played.
On December 28, 2015, Basketball GM passed 2 million seasons played.
And today, we have crossed the 3 million seasons threshold!
Here are some interesting statistics at this milestone:
This applies to new leagues only. If you want to change it in existing leagues, enable God Mode (in the Tools menu).
Additionally, all of the financial parts of the game (like TV revenue, merchandising, etc) have been scaled up too, and they will automatically adjust as you change the salary cap. Previously they didn’t change at all, so it was very difficult to run a profitable team in a league with a high salary cap, and too easy with a low salary cap.
Let me know if you think this has introduced any balance issues. It is supposed to just be a cosmetic change, with every dollar amount 50% higher.
Until today, Basketball GM was hardcoded to always have 2 conferences, 6 divisions, and 16 teams in the playoffs. If you wanted to set up a league with a different structure, you were out of luck.
But today that has changed! You can have any number of conferences and divisions, and the number of teams in the playoffs can be any power of 2 (2, 4, 8, 16, 32, etc). Unfortunately this is only customizable by making a league JSON file, but it’s not that hard, I promise. Here is the documentation and here is an example file with 3 conferences, 6 divisions, 12 teams, and a 4 team playoff.
Please let me know if you notice bugs related to this!
Previously, player names in Basketball GM were generated based on a list of name frequencies in the US in 1990 published by the US Census Bureau. In some ways this was awesome – it was a huge list of names, so there was a lot of variety. However the US population in 1990 does not exactly correspond to global basketball talent. There should be more African American names and there should be international names from basketball-loving countries.
I never fixed this problem because there wasn’t any data I could find that was nearly as good as the census data I used previously. But now I think I have a better solution: DraftExpress. DraftExpress is a website about the NBA draft. It has player profiles for basically every NBA prospect in recent history, even fringe guys like minor college players and roleplayers in overseas leagues. That’s a pretty good sample of the distribution of basketball talent, right? Maybe not perfect, but probably good enough to be better than the previous names list.
So I used my trusty wget to scrape draftexpress.com, and then I wrote a script to parse names and countries for all players in their database. After a little work to clean up the data (splitting names into first and last names while handling extra spaces like Nando De Colo; fixing typos in country names), I filtered the list of countries to get rid of those with less than 5 names because they would just become too repetitive. So sorry Suriname, you and your 2 names are gone. That left me with 28,377 names from 85 countries. To generate a player, the game randomly picks a country and then randomly picks first and last names from that country.
Particularly cool things about the new names:
This is live now, even in existing leagues new draft prospects will be generated with this new naming method. And you can see the countries of all the players in your league by going to the Player Ratings page.
The default teams now have logos! Thank you Elliott Strauss for all your hard work!
Currently you’ll only see the logos on the roster page, which doesn’t really do them justice. Eventually I will do a redesign to better utilize them. In the mean time, check them out here in their full glory: