If you are looking for a short tutorial on how to use D3.js (D3), you may want to reference the official docs or the ample number of tutorials available on the internet. If you are looking to read about my experiences with a short and fun weekend project working with D3, then keep on scrolling!
Or test my app out here: stackafour.herokuapp.com
Looking through the list of suggestions provided to us, I decided on D3, a robust low-level library frequently used for data visualization. The examples on the D3 homepage looked beautiful and intriguing, but most involved data analysis. I wanted to explore other possibilities that one could create using such a powerful tool. My first plan involved an implementation of minesweeper, as it was a game that essentially mirrored a data table. However, given the complexity of the game and the short weekend time frame for this project, I opted for something I thought would be more manageable, connect four. This idea proved to be somewhat inaccurate, as creating an implementation of connect four had its own host of difficulties.
To start, I began looking through the documentation to build an understanding on the use cases and methods for D3. The initial sections of the official tutorial explained how to select and manipulate elements to create a bar chart or a set of circles. These steps were helpful in getting a foothold on how to use D3, but I realized that making a game board with dynamic pieces would not be quite as simple.
After googling for more advanced creations, I figured if I could render a grid using D3, I would be in a pretty good place. I found a tutorial on how to create a grid using HTML table and row elements, which provided a good foundation for me to begin tinkering and adjusting. Through much trial and error, I was able to use the grid layout as a backbone to render circle elements, albeit with very poor spacing. From here I had to determine how to differentiate between the board and pieces themselves.
I was able to find an existing example project that someone made, similarly using D3 to work create connect four. However, he built his implementation strictly as an animation, with a game being randomly “played out” as soon as the page loaded.
His existing code was useful as a building point, but I wanted to take my implementation even further, allowing user interaction to trigger D3 manipulation of board elements. I needed to extrapolate the rendering of the graphics and animations and then build in my own functions and game logic.
Additionally, from the start of the project, I had imagined a version of connect four where users would be able to expand the board size (I’ve always found it to be very unsatisfying to play a game to a draw, filling out the entire board). As a result, the already inefficient method of hard coding outcomes would become futile.
Having very recently worked with linked lists and stacks, I figured I not only needed to, but also could do better than such a solution. I first wrote my game board to initialize as a multitude of linked lists, assuming that space complexity was the lesser of my concerns. Even with the help of such an abstract data type, I still needed to optimize my “game engine,” to prevent for redundant checks (e.g. checking for a winning combination for non-winning patterns). I do admit there is still much more room to improve on in other aspects of redundancy (i.e. leveraging memoization), but I was happy to start with a somewhat optimized solution.
Connect four is no easy matter
Throughout the project, I did encounter some major challenges while working with D3, outside of trying to learn how to work with it for the first time. One major problem was rendering the board’s iconic blue frame properly, with corresponding white space for empty slots.
In my first iteration, I struggled animating the addition of pieces, as the original white space of the piece “disappeared” to “make way” for the piece. In retrospect, I can see that the example animation by Maguire had a comparable implementation, but I was unable to catch his solution while parsing through his code.
Ultimately, I decided to overlay two different grid SVGs rendered by D3, with one acting as the board and the other retaining game data and representing incoming pieces. In doing so, I was able to fix my issue with disappearing space, but was met with a familiar theme in the world of software engineering, where I had now created a bunch of other issues. Without a P.h.D in CSS, my best solution for overlaying SVGs was forcing absolute positioning on both elements, making the math of a customizable board size and its corresponding coordinates (for edges as well as circles) much more complicated. For the sake of time, I decided to table customization for a future feature.
Additionally, I had also hoped to allow players to click anywhere within a column and have pieces appear in the correct location. This feature would have provide the best user experience; however, I struggled to bend D3’s methods and callbacks to my will. I was able to use D3 on click methods to manipulate the target element, but could not figure out how to pass along a separate space (which would be connected via a linked list). I apologize to my large player base, who will have to wait for v2.0 for this future feature.
Success at last!
Out of all the challenges in manipulating D3, I was still able to find a success in at least one feature, providing users with some indication if their selected space would be a legal move.
For a weekend’s work, I was content with how the final product turned out, even though I had not gotten to all the features I had hoped to implement (unfortunately no expanded board!).
A few takeaways…
I learned some important lessons along the way about spending too much time optimizing a specific item, as well on over-planning for a given time frame. There were definitely a few extra hours spent trying to achieve a a “Tier-2” feature before I had built out the rest of the app’s functionality.
Nonetheless, the game works if you wish to play against yourself, or a friend. Check it out here!