Today I solved that main bug in my mob coding app once and for all! I also had lunch with my dad and stepmom, stopped by the new Wilshire Grand building for the first time, had a super long nap, went rock climbing late at night, and learned a bit more about UML state machines.
11:10am: Off to a slow start today, but at least I took care of a bunch of emails and event planning work. I’m still sleepy but coffee is helping, and I’m looking forward to a nice lunch with my dad and stepmom soon. I found some cool stuff on the internet, some of which I’m saving in the “gathering string” section of this blog post. And now I want to take another look at that bug from yesterday and try to sketch out a couple other scenarios for what might happen when the current player disconnects before the end of their turn in my mob coding app.
11:35am: I sketched out a couple more scenarios and identified another small solution that might actually work: instead of tracking the index of the previous and current player on the server, maybe I should just track their IDs! That way, the server will should have the ID of any player who just disconnected, and the client-side checks for whether to fork/edit or only edit should be accurate! (Right now, the server can either send
undefined for the previous player ID or it can send the wrong previous player ID!) I wonder if that will solve it. Ah, I feel so hungry and so sleepy right now! But I just can’t let this one go. I want to understand this crazy system that I’ve created, even though I know there are more efficient ways to solve this bug.
Here’s another important piece of the puzzle that I missed yesterday: the owner of the Gist is always the previous player who successfully completed their turn. So if there are three players in the game, and player #1 finished their turn but player #2 disconnected before their turn ended, then when the turn passes control to player #3, the Gist owner is still player #1! In those scenarios, things actually work out fine most of the time – again, just by happy accident! But what if multiple players in a row get disconnected before their turn ends?
Scenario if multiple players disconnect on their turns:
Three players are in the game, and Player #1 completes their turn. (Gist owner: #1)
Player #2 disconnects before their turn ends. (Gist owner: #1)
Player #3 also disconnects before their turn ends. (Gist owner: #1)
Player #1 begins their turn, and they should not fork the Gist since they own it! But the game state looks like this: previous player is #3, current player is #1, and since they don’t match, Player #1 should fork and edit the Gist. And that leads to an error from GitHub!
This little thought experiment shows that changing the server-side code to track player IDs still wouldn’t solve the problem, as long as my condition for deciding whether to fork a Gist relies on comparing the previous player to the current player! So here’s one possible solution: redefine “previous player” to mean “the previous player who successfully completed their turn”, because that’s really the condition that decides what should happen next. In other words, who’s the owner of the Gist?
11:53am: I still have some doubts as to whether my logic is correct here, because like I mentioned, I’m hungry and sleepy. But I’m in luck, because it’s time to leave for lunch! I just need to walk up the street, but that does take a few minutes. Hopefully by the time I’m back from lunch (and maybe a nap), the solution to this problem will crystalize in my mind and all doubts about my logic will be resolved. Taking a step back usually helps with that stuff. Just like in IT, sometimes all you need to do is turn it off and back on again!
1:51pm: Back from a nice lunch with my folks, followed by a visit to the new Wilshire Grand building. The hotel lobby on the 70th floor has an amazing view! I definitely want to check out their rooftop bar on the 73rd floor sometime. Maybe even today! Nah, probably not. Then we stopped by the coworking space and they met a couple of my friends and here I am again, back at my computer. I feel a spike of anxiety right now, probably just anticipating the entire rest of the day, being anxious about the possibility of future anxiety attacks. I wish that sort of infinite loop was easier to fix. Maybe that’s why I like computer programming: a sense of control! Anyway, I’m practicing my new response to this anxious feeling: diving into work. With time I’ll hopefully become more confident that it really does help. Right now, I’m still not sure. Anyway, OK, back to where I left off, at least for a little while before I take a nap.
So I’m pretty sure that all the work I did the other day to resolve issue #20 (“Game state should track previous and current player for Gist forking/editing logic”) and issue #24 (“If current player disconnects, next turn can trigger unwanted Gist fork”) was for nothing! I made an assumption and ran with it, but that assumption turned out to be entirely incorrect! The assumption was that the Gist forking/editing logic depended on the turn logic (the changing of previous and current players), but it doesn’t! Well, it does, but only indirectly.
Instead of starting from first principles and looking at the root of the other actions in this system, I got caught up in the tangled mess of secondary effects, treating the symptoms instead of the cause! I think the situation can be summed up well this way: instead of thinking of my app as one complicated state machine, it’s actually more like two or three smaller state machines that communicate with each other. My code should reflect that!
I could be stubborn and implement a solution that increments the “previous player” only when each player successfully completes their turn, but that’s just a bunch of extra logic and extra work to accomplish exactly what I identified as a solution the other day: I should just check if the current player is the owner of the Gist or not! I’m already tracking the current Gist URL as part of the game state, so it wouldn’t be much extra work to also track its owner. OK, problem solved! Now I just need to clean up my GitHub issues, add this as a new issue, and close my previous pull request.
2:38pm: Done! I also spent some time looking through my code to identify exactly where I need to change things, and I made a nice list of tasks with checkboxes inside the new issue. I also updated my project log in the README for the project, just to keep this all straight in my head. I think I can see it all clearly now, finally! I’m ready for a nap soon, but first I’ll start on editing the code.
3:19pm: All done and pretty thoroughly tested, and it works like a charm! Here’s the updated flowchart (just one tiny change for one of those conditional statements):
Woohoo! OK, I think I’m ready for my nap now. When I return, I might work on the state machine diagram some more or just take care of some more little refactoring tasks.
7:34pm: That was an epic nap! I wish I didn’t sleep so long, but maybe I needed it. When I woke up, it felt like the morning and I had a craving for cereal and milk and waffles and eggs and bacon. 😅
Anyway, I forgot to mention yet another edge case that I hadn’t considered until after fixing this bug once and for all: what if the same user logs into the app multiple times? Surely my previous logic wouldn’t work, because regardless of the state of the turn-changing mechanic, a user can never fork their own Gist! This brings up a larger issue (which I just posted as a GitHub issue here): there’s ultimately no way for me to prevent players from cheating, because even with a full login system and sessions and all that, users could still create multiple accounts and cheat that way. Some tools for moderation will be important later on.
7:51pm: Before heading out to do some rock climbing, I’ll take a quick look into state machine diagrams again. I’m not sure how best to visualize multiple state machines that need to communicate with each other, but I remember reading something about Harel state charts being useful for more complex state machine diagrams. Looks like they’re part of the UML specs now. Cool!
Notes on Harel state charts and UML state machines
According to Wikipedia, it’s well-known that finite state machines suffer from “state and transition explosion”, meaning that “the complexity of a traditional FSM tends to grow much faster than the complexity of the system it describes.”
UML state machines introduce “hierarchically nested states” so they’re often called HSMs (hierarchical state machines) instead of FSMs. Yay acronyms!
This is of interest to me: “Orthogonal regions address the frequent problem of a combinatorial increase in the number of states when the behavior of a system is fragmented into independent, concurrently active parts.”
These “orthogonal regions” can also “communicate and synchronize their behaviors” by sending events to each other – perfect for modeling my mob coding app!
8:01pm: Time to go rock climbing, so I’ll pick up where I left off later! I’m really looking forward to learning more about this.
11:42pm: That was a great workout! After a quick breakfast for dinner at home and a little TV time, I opened some mail, paid some bills, answered some emails, and got a couple pieces of dark chocolate to savor while I return to reading about hierarchical state machines. It’s the perfect way to end the evening!
Here’s the actual 1987 paper on “statecharts” by David Harel (PDF)! I love the examples so much, I had to share one on Twitter:
I love the idea of “zooming in” and “zooming out” for these hierarchical state machine diagrams. I already have an idea of what the high-level diagram of my mob coding app might look like now. I skimmed over the rest of the paper but quickly realized I don’t need all the details right now.
12:23am: I’m also looking through these slides on UML state machines (PDF) to review some simple examples. I really like the breakdown of events on slide #64, which points out that events causing a transition between states involve several steps:
- First, execute any functions that belong to State 1.
- Check the condition that leads to a possible state change form State 1 to State 2.
- If the condition is true, execute any exit functions of State 1.
- Then execute any transition functions.
- Then execute any entry functions of State 2.
- Finally, execute the functions that belong to State 2.
The idea of entry and exit functions is very important for my mob coding app, because I kept getting them mixed up! (For example, during a
turnChange event, which actions and state changes need to happen before the turn changes, and which need to happen after the turn has changed?)
I also like the notation for “concurrent transitions” on slide #90 based on the idea of a “fork” or a “join” across multiple concurrent states!
12:50am: Just browsing through more examples and skimming over more articles. I wanted to find more examples of how to visualize communication between separate regions or composite states. So far, this article seems handy, especially the example at the very end that shows two concurrent/orthogonal regions with fork and join pseudo-states to show how they branch otu and sync back up again later. As for the notation for events, the technical details don’t seem very useful to me right now! Oh yeah, before I forget: I also really liked this example of a UML diagram of a human’s life on slide #12 of this slidedeck (PDF)! The “human life” is a composite state made up of two orthogonal regions, one for relationship status and one for career status. (A bit oversimplified, but still accurate!)
1:14am: After sketching a bit, I realized that maybe the turn system is not a state machine at all! Maybe it’s just a simple function: on every
turnChange event (when the timer goes off), increment the current player index by 1 (wrapping around the end of the player list). That’s it! So where is the state of my application? Well, there are clearly a couple of game states: off or on. There are events that trigger other events based on certain conditions – do those count as state? Certainly each client must have a couple of different states – either it’s their turn or it isn’t, either they’re logged in or they aren’t. Maybe that’s where I should start. I’m getting a little sleepy now though, finally.
Today I learned that Chrome Canary has a “Coverage” tab that shows which CSS rules are actually being used on a website!
I love the examples in this article and want to return to it again for inspiration later: Style Guides as Products