I recently came across the concept of a scripting language which got me really excited. The language is called autohotkey and it has the ability to automate different tasks a user might otherwise perform on a repetitive basis. It is apparently pretty powerful and some boast it can do anything the user can do. In my first experiment with it I used to to auto-compile and run two projects that go together to form an instance of CypherPoker. After I got that working I thought I would try my hands at a tic-tac-toe bot. The below is how I attempted to solve the problem.
(Here is a link to the full code)
Creating Scripts is Awesome!
Autohotkey is free to use and download and is a very light program.
The editor I used is SciTE4AutoHotkey:
Efficiency by Means of Automating Repetitive tasks
Here's a few lines of code I wrote that made me VERY happy:
I haven't done a ton coding in my lifetime but programming isn't completely new to me. I have always had the problem of hobby projects being either too complex to solve or so simple that there are already coded implementations of the solution available (ie you can just download an app that does what you want to code up). What was great with autohotkey is that I could quickly create a script that compiled an auto ran my CypherPoker projects such that I am now able to save myself from a significant amount of time and especially ATTENTION as well as repetitive click which were necessary to test any changes to the CypherPoker code whatsoever. The script wasn't too complex yet it came out as something very useful.
Previously I had to double click a project file and click compile, wait 30+ seconds for it to finish, and then double click the send project file and do the same (which usually takes a minute or more to compile), and then click run.
Sounds lazy? It is, sort of. It's really nice to make a change in the code have it basically auto-test, or at least compile and run, while giving myself a chance to digest the changes I have made. The compiling/running process takes a few minutes which is sort perfect to play a game of Halo.
Well not really, but here's the thing. Often as a programmer you try to test your changes and you immediately realizes you forget or misplaced or mistyped one character and you need to re-compile immediately. I'm probably a lot sloppier and certain not very knowledgeable at programming and so I often need to re-compile many times in a row. Typically I would have to sit in front of the screen for an hour perhaps when I'm really only making a dozen or so small changes and doing the compiling process 10 or 20 times.
So I go from being stuck in front of the screen, to having 10 or so compiling events that allow me to function around the house and multi-task with either useful or recreational tasks.
It's not so lazy from this perspective.
Tic-Tac-Toe
I thought about doing solitaire first but it was more complicated than I initially realized and I wasn't sure how much help I would find online along the way to solving a solitaire bot. In the past I have considered about what an optimal strategy to tic-tac-toe. I always had the thought of trying popularize a coffee table book that flips through all the optimal moves and responses. Tic-tac-toe is a lot simpler (almost binary) and a quick search in the windows app store brought a perfect version for testing my code:
The Solution
“Solving” a game in this sense is interesting to me. We aren't even necessarily solving the game (more complex games might be out of the reach of man without AI) rather the idea is to create a set of rules by which the code can take inputs on the gameplay and follow a decision tree that accounts for all possible inputs.
Loosely put, I don't know the optimal gameplay and solutions for tic-tac-toe. I'm not doing any sort of complex AI style analysis and decision making that might for example involve project different scenarios and playing them out before making a decision. But that doesn't take the fun away.
My basic solution went as follows:
-Read the board.
-Check if winning move can be made and take it if it can.
-Otherwise check if we can block and opponents winning move and block it if we can.
-Otherwise take the center.
-If the center is taken then take one of the corners.
-Otherwise take on of the middle-side squares.
That covers any scenario but there are still some possible optimizations discussed at the end of this article.
Note the third rule implicitly covers the need to optimize a situation where our opponent has TWO possible winning moves since we can only block one possible path we lose against a perfect opponent regardless of our decision.
Reading the Board
The game board looks like this:
There are different ways to tackle this and I had different ideas that I evolved mostly because I couldn't figure out how to get them to work. In the end I choose to save snippets (images) of each empty square and one each of an 0 and of an X.
Here are a few samples of the empty squares:
And here are the samples I used for the X's and O's respectively:
For the empty squares I just needed to capture an aspect of each square that is different than the rest (my thought was the background might repeat so I coded as if it were true that it does). The X's and O's just need to be a small snippet. I could use a function to get the pixel color instead-either way was a learning experience so I chose the former.
From there reading the board is fairly simple and admittedly I did it in a crude fashion:
The script searches the coordinates associated with each tic-tac-toe quadrant and if it finds and Oh image then it changes the variable that represents that square to 1. Then we do the same check but this time for the X image but set the variable for each square to 2:
Since the variables are each initialized to zero, we now have every square represented with a variable that is either a 0, 1, or a 2 which represents empty, Oh, or X respectively:
Processing Board and Running Through The Rule Set
Now that we have a representation of the board the processing, although crude, is quite easy. For example, in order to check if there are any winning moves our bot can take we simply check if the variable for the first square is an X and compare it with the variable for each square that could potentially lead to a winning move. If the first square is an X and the second square is also an X, then the third square, if empty, would give us the win. There is no need to check anything else we can just win immediately by taking the first winning option avail-be so we can be sequential.
So it's a set of nested “if's” which in human languages goes “If this square has an X check if that square has an X and if the winning square is empty:
If we have a winning move then we can take it by moving the mouse to the associated quadrant and clicking it:
Once we have that solution we can basically generalize it for our other rules. If we didn't find any winning moves with the previous code then we should check if our opponent has any winning moves (if we pass the action to our opponent and don't block the winning moves we can expect to lose). The code for this is basically the same except we will check for Oh's instead of X's (this suggests the possibility for a function that would optimize the code from a developer's view):
The rest of rules are even easier. We just check if the center is empty and take it if it isn't, then check the corners respectively and take the first one we find, and then move to the final squares (somewhere here there can possibility be optimizations I missed as, for example, some corners might be better than others depending on the board).
Final Touches
It wasn't so easy but looking back on the code it's pretty slick to have a working implementation of a tic-tac-toe bot that plays relatively decently. We can notice a “goto loopstart” line which is at the end of every decision we make as well as loopstart2 which is used to loop the following:
The code checks if its hero's turn by checking the “Player 1” image which is highlighted by the game when action is on us. If it's not our turn it the code loops and waits a few seconds.
Before this the code checks if there are any game finished windows from a previous session and it closes them if there are:
Test = Success!
All that in a while loop set to run indefinitely runs a tic tac toe bot that seems to play pretty well. Here is a video of the code in action:
Optimizations
One optimization I have thought of, other than picking optimal corners, is that the code should check if it can trap the opponent by taking a move that gives us potential to win in two directions thus securing us the future win (provided we don't give up a loss we could have prevented which simply means a new block of code that runs AFTER we check if we can block any of the opponents winning moves).
What's Next?
I'm quite interested in thinking about codifying solutions or strategies to games and I have already been working on how I would create a script that would play solitaire. It wouldn't be much different except I think I would use arrays (and perhaps arrays of arrays) to read the board and process the data. It would be a good exercise for me which would help me with me with an aspect of the CypherPoker project I am playing around with (displaying arrays of chips!). If I can successfully create script for solitaire then I'll probably stop pursuing this direction and move towards philosophizing about how to solve games in this way in general. I'm very happy to have come across AutoHotkey-playing with the language and software has changed the way I think!