Tic Tac Toe in Terminal
How this simple paper game made me almost give up on an exercise
In the Object Oriented Programming lesson from Launch school, course 120, you will again write a Tic Tac Toe game, but now in OO approach. And it was a ride for me I must say.
Because I have already built the game in procedural programming, I very well remembered all the extra features you can add. Because I didn’t finish all the extras last time, I wanted to accept the challenge and, … oh boy, it took time.
Here is what I did and why it took me almost two weeks to write a game which runs in a Terminal window.
You can checkout my code here, read it, and play the game if you like.
The game is using 4 custom classes and 2 subclasses. The diagram Classes of TTTGame shows the structure of classes in the game.
I had to make few big decisions based on which I have created the structures. Let’s start with the Board class.
Usually, the Board for a normal 3x3 game has fixed rows and columns, but the extras section challenge you to create a Board with different sizes. This game is capable of displaying any kind of size you can imagine, like 4x9, 2x254, 53x87 etc. Because the game is displayed in a Terminal, I had to restrict the sizes where colmuns and rows are the same, and only up to 9 by 9 grid.
The biggest challenge I had was with the Board’s grid data structure. My thinking went like this. The real world board on a paper does not care where the Sqaures are placed, in which shape, size, or how they are marked, so initially, the Board had almost no instance variables and methods, and the grid data structure was an array. I wanted all the logic to be in the Square class and Player class for decision making. So any player can take a look at Squares, see the position of a Square an do anything based on that. Also each Square should know what neighbor Squares are around it so it can react and give you a feedback that there is an opportunity to win, or a need to block the neighboring Square.
It turned out to be a very bad design as I was forced to recreate the Board’s grid every time Human player wanted to mark a Square, calculate if there was a winner, or make Computer to mark some Square.
I then decided, that grid should know where the Square is placed. I’ve created a grid structure to be a hash where key is a position of Square and value is the Square itself. For a grid of 3 by 3 this looked like this:
[0, 0]: #<Square:0x007fbe0f98e5c0>
[1, 0]: #<Square:0x007fbe0f98d738>
[2, 0]: #<Square:0x007fbe0f98cf40>
[0, 1]: #<Square:0x007fbe0f98cbd0>
[1, 1]: #<Square:0x007fbe0f98c950>
[2, 1]: #<Square:0x007fbe0f98c4f0>
[0, 2]: #<Square:0x007fbe0f98c3b0>
[1, 2]: #<Square:0x007fbe0f98c360>
[2, 2]: #<Square:0x007fbe0f98c310>
Now I was much more free as I only asked for the grid from the Board and worked my way through.
I was able to create a method in TTTGame class which can display the grid of any size and I could freely change the shape of the grid and it would apply for any size. So my grid could look like this:
/ \ / \ / \
| | | | | |
\ / \ / \ /
- + - + -
/ \ / \ / \
| | | | | |
\ / \ / \ /
- + - + -
/ \ / \ / \
| | | | | |
\ / \ / \ /
That’s horrible, who would want to play on this right? But if there is anyone, they could.
Becasue my Board class now knew about the position of Squares, I could ask it if there is a possible winner, or if there is no available square to be marked by any player to determine if the board is full, or I could ask it to get me player’s markers, as all markers are there, on Board.
If I’d had different data structure, I for certain could ask a board if there is available square and determine the full board, but I’d have hard time to estimate a winer. I could ask each Player to check if they won, but I would need to recreate the structure of the board. Or, there was a possibility that each Player will remember their moves and Squares they marked, and they would know that they won. Exactly as in a real world where the paper grid does not tell you, ‘hey, you there, you won!’. It was my original intention, but the code looked very bad as I needed to keep track of two different data structures holding the same objects. And the biggest problems started when I introduced different grid sizes.
With 3 by 3 game you know exactly which moves will win, so you can say that if these combinations of squares have the same marker, then there is a winer. When you have a board of 9 by 9 and you say that any 3 subsequent Squares in any direction can win, you will get 224 possible combinations in any row, column or diagonal. This would be too dificult to write all by hand imagining that there are infinite Board sizes and subsequent Squares that can win.
So I let the computer compute all possibilities and because of my decision having the data structure of the grid to be a hash where array is the possition of Square as a key and the Square is the value, I was able to select every row, column, and diagonal of any number of subsequent squares, and get in return only the Squares at certain position.
Here I was thinking very hard how I could do it. To select rows and columns was easy. The position array of my grid structure
[0, 0] was telling me x and y coordinates, so I could easily select all rows and columns. But how do you select all diagonals? Let’s visualize:
In this example, I have a grid of 5 by 5 and I need 3 subsequent Squares. So there are 10 diagonals for me to check if any contains the subsequent Squares.
I was thinking mathematically how to get the necessary squares from the grid. And then I realized, I am working with Ruby, so there is probably a Ruby way to determine a diagonal. But not only the main diagonal from
[0, 0] to
[4, 4] but also diagonals which can contain possible consequence Squares, so from
[2, 0] to
Only when I took paper and pencil and saw the structure, I realized I can offset the rows to the right. And then I can get the columns with method I already have, then this will represent the diagonals from left to right.
And I could do the same to offset the rows to the left, and take columns from different diagonal direction. But I already knew how to get the columns when I offset the rows to the right. And it turns out that when you get columns and put them in the place of rows and reverse them, you will get a kind of a mirrored structure in a diagonal sense. Then you can use the same methods for offsetting the rows to the right, and get the correct columns representing diagonals from right to left.
So once I had all rows, columns and diagonals, I could split them to give me every subsenquences of Squares. So a row of 5 squares would give me three possible subsequent Squares, (1 to 3), (2 to 4), and (3 to 5). Then I had all possible combinations of 3 subsequnet Squares in any direction.
With this, I could do a lot of things, like determine the correct move for the computer to block Human player, or offens the play if the Computer have a possibility to win. And this solution works on any grid size, for any subsequent Squares you decide.
Also I could use this structure to determine winer by picking an array with Squares of the same marker from all possible subsequences. But I haven’t done that, as I come up with different algorithm to estimate a winer and I left it in the code. If I got rid of that, the code would be shorter by approximately 100 lines.
This game was really hard to complete. There are still issues, like the the computer doesn’t always mark the best possible Square. Maybe the presentation could be better. When you play on bigger board sizes then 3 by 3, I display helping numbers so you can easily pick the correct Square, but it is harder to see all posible moves you can do as a player.
Experimenting with data structures, coming up with solutions and re-writing the game couple of times took two weeks. And I could continue, I could make adjustmenst and make the game better. But I think i have learned a lot over the course of two weeks, and I think if I would continue, I would find so many other things I could do differently, add, change, improve, that I would spent with this game so much longer.
And I think this is the beauty of mastery learning, that you can never get enough improvements, better ideas, better code and better yourself. Even now, when I reflected my work in this blog, I had so many ideas to better manage the game, to improve the structure of the Classes, to rewrite this and that, and maybe, if I took enough time, I could shrink the number of lines by half.
And this is my question. Should I continue going further and get better to start writing good code from very beginning of every new challenge which comes next? And get better with every challenge which comes after that? Or should I improve one game and one assignment to the extreme to make it perfect?