Building Tic Tac Toe, Mental Obstacles, and the Benefits of React
5 min read · August 22, 2017
It is so easy to be overwhelmed with the magnitude of the problem, that it seems like it works by magic and is unattainable.
Another project is in the books. I completed the Tic Tac Toe game using the React framework with 5 days and around 12 hours of active development. You can check out the live version here and the code on Github. By far the most complex part of this project was creating the computer AI component. The idea of it had me in analysis paralysis for a day or two. This project really emphasized the need to break down the problems into bite sized, even seemingly insignificant pieces. For a while, I struggled with even starting on the logic for the computer player. It is so easy to be overwhelmed with the magnitude of the problem, that it seems like it works by magic and is unattainable. This is exactly what happened to me as I was struggling to assimilate concepts like minimax algorithms in search of the perfect implementation for the computer AI.
While minimax algorithms may be able to create an unbeatable AI in Tic Tac Toe, my inability to grasp it meant that I wasn't moving anywhere and becoming increasingly discouraged. As I grew increasingly frustrated, the self doubt of course kicks in and I started wondering if I would actually be able to figure it out and move forward. I'm not saying that's a logical response, but emotions are powerful and can have a huge impact on mental state and the ability to approach a problem. A wise and more experienced developer talked with me at a Free Code Camp meetup and politely told me I was making things too complicated; minimax would solve the problem, but I didn't have to use it. He told me to try making the computer just pick a random empty cell and then slowly build it out from there. At first I shrugged it off and thanked him for the input, wanting to solve the problem the best way. After some more staring at the screen, I decided to follow his advice so I would at least make some progress.
Once I decided to follow his advice the whole project came together within about 4 hours over a couple coding sessions. It was easy enough to have the computer select a random empty cell. I also had already built out a method for checking for winning combinations to determine a winner. I was able to take that concept to create a function where the computer would check for two of the three cells being controlled by the opposing player, and then return the position of the empty cell to block. Creating the logic to win was then a matter of refactoring that function to allow the logic to check for either the player or computer controlling two of the cells and then either block or win. Finally, I created a basic hierarchy of logic to run through the functions and select the most beneficial option and the computer logic was done. It may not be the unbeatable AI of the minimax algorithm, but it is definitely playable and fully functional.
How React Improved Building out the Project
The basic structure of the app was easy to build in a couple hours using an HTML table. I created a cell component wherein each one has an ID property between 0 and 9. This corresponds to a single dimensional array stored in the App state which manages whether the cell is available, or is controlled by a particular player. The index of this cellValues state is passed on to each individual cell as a property so that the component will render if it is controlled by X or O. If the value is E, the logic within the component converts the variable to display a blank string. All of this is contained within one component and then an instance of the component is called for each cell. This speeds up development because I only needed to write the logic once and if any changes are needed, I only need to update the one component rather than 9 hard coded instances of it.
React really was useful when it came to the logic of claiming cells and checking for winning combinations due to React's use of properties. When a user clicks on a cell it passes the ID of the cell as a parameter, which again references the index in the cellValues array for that cell. The method called first checks if the cell's value is "E", if the current player is the user, and if the game is not over. If all of these are true it changes the cellValues array at that index to the marker of the player and sets the state. Since the cell derives it's display value from the App state, it recognizes the change and handles the re-rendering to display it as claimed.
Component lifecycle hooks were also a big benefit for the completion of this project. Within the main app component, the ComponentDidUpdate lifecycle hook handles all of the turn based logic and checks for a winner or tie. If there is a winner or a tie it calls a clearBoard method which displays who won if applicable and returns the board to it's initial state after a slight delay. If the game is not over and the current player stored in state is the computer, the method for the computer selecting a cell is called. Another helpful lifecycle hook that was really easy to use and makes the app more performant is the shouldComponentUpdate hook. By default all children components will re-render if their parent component re-renders. If nothing has changed, this render is unnecessary and can reduce performance, especially at larger scale. One way that I have found to combat unnecessary re-rendering has been including this hook on children components and specifying the times that they should not render. For this particular project it was a very simple couple lines of code which compare the properties the component already had to the ones it is receiving. Specifically I am looking for changes in the cellValues property passed down to each component. If there is no change in the properties, the value of the who controls the cell has not changed and therefore there is no need to re-render the component. In that case the method returns false which tells the component not to render. If there is a difference between the properties it returns true and triggers a re-render.
I am really enjoying React and find that the benefits it brings to development are multi-fold even on smaller projects. It probably isn't best for something that is very small and can be easily handled with Vanilla JavaScript, but even projects of this size have definitely seen some gains from it.
Thanks for checking out my first semi-technical post. If you enjoyed it or would like to see more of something like it, please let me know via the contact form or on Twitter. Thank you, and happy coding.