DEV Community

Cover image for Coding Chess with TDD
Luke Garrigan
Luke Garrigan

Posted on • Updated on

Coding Chess with TDD

TDD is a pretty simple idea, you write your tests before you write any code and you write just enough code to make that failing test pass.

There are three Laws of TDD

You must write a failing test before you write any production code
You must not write more of a test than is sufficient to fail or fail to compile.
You must not write more production code than is sufficient to make the currently failing test pass.
Recently, I picked up the book Clean Coder by Uncle Bob – among many brilliant points made in the book I was immediately intrigued by the enthusiasm placed on TDD.

Using TDD

Lately I've been playing quite a lot of chess, however, I'm pretty shite. I thought, what better way to learn the game than to code it? And whilst I'm at it I'll give that TDD lark a good stab.

All the code I wrote is open-source and can be found on my GitHub.

The framework I'm using for writing tests is Jest and the canvas library is p5.js.

Creating the tiles

So, what do I need? I need a board which has tiles and there needs to be 8 tiles per row, let's create a failing test.

Note: the cycle time was a bit smaller than I'm showing in these examples, I would write just enough code to produce a failing test and then write just enough production code to make that test pass — so in the example below I'd have created the board class straight after writing new Board().
Image of first failing test
Now we've got ourselves a failing test let's write the code to get that test to pass.

Brilliant, the test is now passing and we've got ourselves a two dimensional array that represents the chess board!

Displaying the board
I should note that I didn't write any tests for actually rendering the board as p5.js does that heavy lifting for me — which also explains why the coverage is not quite 100%.

Creating Pieces

The next logical step was to get some pieces on the board. Let's start with pawns.

Firstly let's start with writing a failing test to check that the Pawn exists on the board at the start of the game:

Let's now write just enough code to get this test to pass.
Brilliant, I then repeated the process for the white pawn.

And we have ourselves some pawns on the board!
Chess board with pawns

The next logical step is to find possible moves for the pawn, but before we do that I need some way to distinguish between the black and the white pieces. So let's right a test to ensure that the pawns at the bottom are white and the pawns at the top are black.
So in this test I've introduced a new constant for the colour of the pieces. Next I need to write just enough code to make this pass, so the simplest path here is to add the new colour property to the Pawn class, and doing that will make the test pass. Now that I've got this test in place I can refactor, I know that every piece is going to require a colour, so it would make sense — rather than repeating this code in each chess piece (Bishop, King, Queen, Rook, Knight) — to create a base class called Piece that deals with this.
And I simply know this works by just re-running my test suite, TDD gives you the power to refactor with confidence!

Finding possible moves

So, in chess what can a Pawn do?

  1. It can move forward 1 square
  2. Move diagonally capturing an enemy piece
  3. Move two squares if it's the first move

And a couple moves I'll ignore for now:

  • Promotion - when you reach the end of the board
  • Can perform an En passant which is a move you make out of principle to show your opponent that, yes, I know what En passant is.

Let's write our first test to check when a Pawn has just one move:
Pawn has one move code
So I've added a couple things here, a new flag which denotes whether a Pawn has moved or not and a new method on Pawn class which should find the legal moves for us, let's write the production code to make this test pass:

Just enough production code to make the Pawn test pass
So here we're just checking to see if a piece exists in front of the pawn and if it does that means we can't move there which also means we can't move two spaces ahead if it were our first move!

One might think I was a little naughty here as I'd written too much production code just to get the test passing, and you'd be right. This code is enough to get the following tests to pass too.

should find two possible moves if the white pawn hasnt moved

Should not find a legal move if there is a piece in front of white
This is one of the key lessons I've learnt from practicing TDD, don't get ahead of yourself — write just enough code to get the test to pass and nothing more.

A good image and explanation from in their blog Red, Green, Refactor

  • Red — think about what you want to develop
  • Green — think about how to make your tests pass
  • Refactor — think about how to improve your existing implementation

Image of Red, green, refactor

If you get ahead of yourself like I did then you miss the "Refactor" step. Yes, you can still refactor after you've written all the production code, but refactoring just 3 lines rather than 30 is surely a simpler operation, TDD enforces this.

Now that we've covered a pawn moving forward and a pawn moving two squares on its intitial move, let's add a test to cover attacking.

Should show 3 legal moves if enemy pawn diagonal left to current pawn that hasnt moved yet
Let's write the production code to return the diagonal left attacking move:

diagonal left attacking move
Brilliant, this test is passing but what happens if our pawn is on the very left of the board? I'm pretty sure the code would error because it'll try to get the value from tiles[-1][y], let's write a test to check this:
Should show 2 legal if a side pawn

Just as I expected:
TypeError: Cannot read property '5' of undefined

Let's remedy this by adding a check to see if the current Pawn is at the end of the board:
Added an if check to make sure it is not at the edge of the board
Brilliant, now our test passes! I then repeat the previous steps for diagonal right, you can imagine what that looked like.

Now we have pawns that can move, I added a little visual code so that when you select a pawn it displays the possible moves.
Shows the possible moves in green

Rinse and repeat

I then repeated the steps that I took for the Pawn in finding its possible moves for the rook:
Rooks added
And then the Bishops:
Bishops added

And the Knights, Kings and Queens:
Knights, King and Queen added

And prettied them up a little, who knew Unicode had chess pieces?

Unicode chess pieces


I continued the process of writing the tests before writing any code and by the end of it I've got a functioning Chess game, yes there are definitely some minor things I've likely missed, but this was just merely an exercise to put TDD to practice. The one takeaway I've learnt from this — and isn't mentioned enough — is that TDD is fun, and I mean a lot of fun. There's nothing more gratifying than seeing your broken test flick to green. The instant release of endorphins makes TDD is almost addictive. Having a reliable suite of tests that runs in under a second gives you certainty when refactoring or adding new code, it's a massive safety net. And because you've written your tests before any production code you can be confident the holes are minimal, and if any, most certainly would have been there if you weren't using TDD.

I hope this blog inspires you to give TDD a go, I know I'm going to be using it by default in the future, as I said, It's bloody fun.

Thank you, if you like my rambling check out my personal blogging site at

Top comments (4)

turutupa profile image
Turutupa • Edited

Good post! I also finished reading Clean Coder like a week ago or so and absolutely loved it! I too want to learn how to code using TDD and I think your post is a great starting point to take as an example.


lukegarrigan profile image
Luke Garrigan

Thank you, I appreciate that. Yeah it's a very well-written book with some really good tips in there. I'm glad my post helped!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.