When I was in high school, I needed to have a graphing calculator for my math classes, so my mom got me a shiny new TI-84. It wasn’t long before I learned that you could add your own programs to it, and what else is a bored kid going to do between classes? I wish I could say that I was a super bright, inquisitive kid who immediately started writing little games on it, but my first small programs were strictly utilities—things like unit converters or area calculators to take out some of the drudgery of science worksheets.
This calculator, however, also came preloaded with a few games on them, and I wanted to try my hand at that. How hard could it be, right? These games ran on my calculator so they must have been written on the calculator. Unfortunately I learned way too late that this wasn’t true, in my defense I didn’t even know what programming was or the fact that I was doing it—it was hard to conceptualize someone writing a game in C and compiling it to whatever machine code the TI ran on.
But along the way I found some functions that could draw arbitrary graphics by plotting lines and ovals and such in the calculator’s graphing mode, which gave me something to work with. So I set out to make the simplest game I could: tic-tac-toe, written in incredibly clunky TI-BASIC.
Curse you, TI-BASIC
And here’s the thing about TI-BASIC: it’s not fun to write. The calculator-as-a-keyboard interface didn’t make things easy—you had a number pad along with a bunch of math function keys above and around it. Pressing a modifier (ALPHA I think?) let you access letters, which were arranged alphabetically across all the math function keys and digits. All of the language keywords were separate—you wouldn’t type E-L-S-E to get an ELSE, you’d have to go into a menu in the program editor and find the ELSE on your own each time. And maybe there was a space key at the bottom? Whatever, I can’t remember—it was a while ago. Point is, it sucked.
Given all that, you can probably see why young me wanted to avoid as much time as possible fiddling around with most normal coding constructs. Writing a For-loop felt like a whole process.
Tic-tac-toe didn’t give me much trouble, thankfully. The “game loop” showed you the grid, prompted you to enter a spot (1-9 on the number pad corresponded to the 9 positions on the grid), made a quick check to see if that spot was occupied, then drew a new X or O on the board depending on whose turn it was. The one thing that’s missing here is the check to see if the game is over.
In tic-tac-toe, you win by getting 3-in-a-row on the 3x3 grid. That’s 3 ways of winning horizontally, 3 ways of winning vertically, and 2 diagonally. It’s simple but ugly to write, the sort of thing that has way more details than you would expect and would be a good way to try to trip up junior engineers in an interview. Instead of giving in and daring to write ugly code on my own calculator, I went about figuring out something that felt more elegant to write.
Wait, we have technology
You can’t have creativity without constraints, and my disdain for writing loops lead to me eventually coming up with a solution that I actually would enjoy writing. My reasoning was something like this—what if I could view the board as just a number instead of a more complex data structure? Instead of needing to iterate through the grid each time I wanted to check to see where the X’s are, I would just incrementally update the “X number” and use that to check for victory cases. Numbers are really easy to work with in the calculator, lists are a massive pain—surely I don’t need a list for something so simple.
And if you’re thinking “oh, we’re going to use bitmasks!” you’re wrong. Wouldn’t know that shit for another few years. Remember, not that inquisitive. But I did know what prime numbers were.
The strategy: each spot on the board is assigned a separate prime number—2, 3, 5, etc. up to the 9th prime, 23.
2 3 5
7 11 13
17 19 23
Now, take all the spots where an X is and multiply their corresponding primes together to get a number—let’s call it x.
2 3 5 | O X 5
7 11 13 | X X X -> 7 11 13 -> x = 5005
17 19 23 | O O
x isn’t divisible by 2, the top-left prime. This allows us to see that there isn’t an X in the top-left, otherwise we’d have gotten an even number. What about 7, 11, and 13? There’s an X in all of those spots, which means x will be divisible by 7*11*13 = 1001. And so this gives us a pretty easy way to check for winning positions—just keep an x and o variable with a running product of everywhere an X or O is, and check to see if it’s divisible by 8 magic numbers (one for each row, column, and diagonal) that I just hardcoded.
So in the end, you get some pretty short code with a bunch of magic numbers and more importantly, only one For-loop to write:
" (This is sort of what TI-BASIC looks like)
" Somewhere inside the game loop - N stored the spot (1-9)
" that the X player just moved to, L1 stores the primes in
" those spots. Not shown: the other player's number getting
" updated and checked in the same way.
P -> L1(N)
X -> X * P
" L2 stores the hardcoded winning divisibility checklist
For(J, 1, 8)
C -> L2(J)
" Pretty sure I didn't know what mod was back then, I
" most likely checked for divisibility like this.
If Floor(X / C) = X / C
Disp "X wins!"
Stop
End
End
But why
I was pretty proud of myself for figuring this all out and avoiding doing the annoying part of code. But it’s not like it made the code any better. Maybe it was faster, but that’s a pretty bad metric for a tic-tac-toe program. I definitely had more fun writing it and learned while doing so, but I don’t think most people reading code care that the writer was having a good time. Point is I traded boring, understandable code for fun, incomprehensible code.
Nowadays I’m experienced enough to know to stick to writing boring code more. If I had to write some production-grade tic-tac-toe, it wouldn’t have any uncalled-for clever solutions—it would look like code that played tic-tac-toe, and anyone taking a cursory glance at it wouldn’t need to think about what was going on. If my manager told me “we’re spending too much money on For-loops, we need to cut costs somewhere” I’d make sure to leave a long, annoying comment about exactly how the new code works and why it has to work that way.
Maybe this post is that long-overdue comment, on some long-lost code that no longer exists anywhere anymore. Certainly there’s better ways of documenting, but at least it’s better than a comment saying // It works, don't ask me how.