This is part one in my series about writing a checkers webapp
Why Checkers?
Actually, it's about reactJS.
I started this project because I wanted to brush up on my react and redux, and learn redux-saga. Checkers was kind of incidental. However, when I make decisions in a hurry, it has a way of coming back to bite me. So I spent a lot longer on the actual checkers part than I expected.
The code is available on github, but it's still changing so check back. Its here.
so... Checkers.
Yeah, so checkers is an interesting game. I chose it because I felt like I didn't need to explain the rules, but I'll try and give a very abbreviated version here.
- players move stones (checkers) on a chess board using only the dark squares
- checkers move one hop diagonally (always on the dark squares) towards the opponents side of the board
- checkers capture an opposing stone by jumping over it to the dark square on the other side, if it's clear
- checkers must jump if possible
- a checker that is jumping must continue jumping until there are no more jumps available
- when a checker reaches the final row on the board, it is promoted to a king
- a king moves just like a regular checker but can move backward in addition to forward
- The game ends when one side has no checkers left
The game itself is reasonably straightforward. I wanted to make something simple that would let me play with various libraries. Because I wasn't thinking too hard about the actual design of the system, I ran into some interesting issues. If you're thinking about trying this at home, I a) heartily recommend it, and b) suggest trying Reversi instead. Without giving it almost any thought at all, I think the rules are simpler with less exceptions.
The Checkers Part
If you're following along at home, you can check out the code here
There's basically three interesting endpoints here. move
lets you move a piece, jump
lets you start a series of jumps, and game
gets you information about the game. There's other stuff, but I want to look at these three.
game
is interesting because of the way I stored the board layout. Rather than write a JSON blob into the db, I figured I'd just stick everthing in a string and put it in a 32 char field. The starting layout looks like this:
bbbbbbbbbbbb wwwwwwwwwwww
There's only 32 because I've cut out the other 32 squares that a checker can't land on. This works.
The good part about this is that when you write to the DB it's straightforward and when you send across the network it's relatively small. What I should have done is written a conversion and handled everything using x,y coordinates. Instead, I just used the string representation and built an adjacency matrix to manage movement. It's pretty messy. I think I might have been sick at the time, high on cold medicine. Let me tell you, editing a string like that by hand is a pain in the tuchas. Also, you can't just set the values in the django admin because it trims trailing spaces (and empty squares are spaces in that string). Ask me how long it took me to figure out why my board kept getting corrupted.
I may rewrite it to use cartesian coordinates so that moves can be (+1, +1)
or (-1, +1)
which is super simple. That'll make all the logic so much easier. If I do that I'll post about it so you can see the change.
Now let's talk about move
and jump
. One of my goals was not to reimplement all of the move logic on the react side (see my mess above) and just let the backend handle all the move logic. So maybe you're wondering why we need separate move
and jump
endpoints? Why not just move
and if you're trying to jump, it handles it appropriately? That's an excellent question and fixing that may involve a small rewrite. Right now, the frontend just checks if your move is more than 1 (diagonal) square away and picks an endpoint based on that.
Whew. Moving on.
Interesting Problems
So I mentioned that I was trying to keep the actual checkers on the backend, rather than having rule implementations in the client javascript as well as the server python code. This is fine for moving a checker. You send a start and an end position and you get back either 200 ok
or 400 bad request
. All the checkers logic is neatly contained in the backend.
Now consider what happens when you jump. You send a start and an end, and the backend can validate that jump. So far so good. Unfortunately for me, a checker can have a whole chain of jumps in one turn. Now we either have to calculate a whole series of jumps on the frontend and send them to the backend, or we have to send one jump at a time and hope the backend can preserve our move sequence and know when our turn is complete.
My implementation loosely follows this pattern. You selects a jump and sends it to the jump
endpoint. The jump
endpoint checks if the jump is ok, then checks to see if more jumps are possible from that piece. Remember from the rules that you have to make jumps if they are available. The response to a move includes the turn number, from which both sides (frontend/backend) calculate whose turn it is (black moves first on turn 0 so even numbers are black's turn). A player who submits a jump can continue submitting jumps until the turn ends.
Here's another interesting question to consider. When a checker is making a series of jumps and it lands on the back row, it becomes a king. Now, technically its turn is over, but if you're not careful becoming a king can suddenly open up new jumps to continue the process.
System Design
Really I can talk about whatever I want here because this is my space, but since this post is meant to lead into a series I'll say a little about the design of the app as a whole. The server is built on django-rest-framework, which powers the API. In addition the server provides a single page javascript frontend which communicates with the API and provides the user interface.
The javascript frontend is built using react/redux and all the communication is done with redux-saga and axios. Thanks to a bug, you can just move all the pieces because the backend doesn't check to see if you're moving your own pieces (I'm leaving this alone for now).
To support multiple players, I need to fix the above bug and build a mechanisim to notify you when it's your turn. Currently the system has a big button labeled 'refresh' which requests the state of the board. My plan is to replace that with a websockets/django channels based notification system that can run all the time.
I have some thoughts for a post on redux-saga and another on django channels/websockets. Once the system is somewhat more robust and has an actual login, I'll post a link to create an account and play here.