In 1956, a 13-year old Bobby Fischer announced his chess genius to the world, winning one of the finest games in chess history. In fact, Chess Review called it “The Game of the Century“.
In a game full of beauty and surprise, Fischer overcame his much older, more experienced opponent, the International Master Donald Byrne.
In this video and blog post, we create a single formula in Google Sheets that plays through this game!
Backstory
Earlier this month, doing research for my video about conditional formatting, I came across this comment on my blog post on this topic:
This comment really piqued my interest.
I experimented with it, then remembered that I can generate chess pieces with the CHAR function.
So after an hour of messing around with formulas, I had a single formula that created a chessboard. I shared it on LinkedIn and Twitter, where it was well received.
But me being me, I wasn’t quite done yet. I wondered if I could make the pieces move.🤔
Here’s how it went…
Creating the Chessboard Background
Use one of these conditional formatting rule to create the background board:
=ISODD(ROW()+COLUMN())
or
=ISEVEN(ROW()+COLUMN())
depending on which cell you start your 8 by 8 board from.
If the row + column number of this top left cell add to an even number, use the ISODD rule (e.g. A1 is 1st row, 1st column, which add to 2, which is even). However, if the row number + column number add to an odd number, use the ISEVEN rule.
Note: you can of course manually change the background colors 😉
Add the Chess Pieces
The first thing we need is the CHAR function, which generates the chess pieces.
E.g. this CHAR formula generates the white king:
=CHAR(9812)
In our Google Sheet, it looks like this:
Further, all the pieces can be created with the numbers 9812 through 9823:
The next function we need is the MAKEARRAY function, which generates an array r rows by c columns, with a custom function applied to each element of the array.
Here’s a simple example, which creates an 8 by 8 board with a white rook on each square (generated by CHAR(9814)):
=MAKEARRAY(8,8,LAMBDA(r,c,CHAR(9814)))
In our Sheet, the output is:
Before we get carried away, we use a LET function to give each of the chess piece numbers with a label.
For example, the number 9812 is labeled “wk” to indicate the White King. We do this because it makes it much easier to understand the setup and moves of the pieces within the formula.
Thus, our formula starts:
=LET(wr,9814, wn,9816, wb,9815, wq,9813, wk,9812, wp,9817, br,9820, bn,9822, bb,9821, bq,9819, bk,9818, bp,9823,
To this, we add the MAKEARRAY function as the formula expression to complete the LET formula:
=LET(wr,9814, wn,9816, wb,9815, wq,9813, wk,9812, wp,9817, br,9820, bn,9822, bb,9821, bq,9819, bk,9818, bp,9823, MAKEARRAY(8,8,LAMBDA(r,c,wr)))
Note, how we write “wr” for the white rook in the MAKEARRAY instead of the number 9814. This gives the same output as above, an 8 by 8 board with a white rook on each square.
Different Pieces for Different Squares
Within each square of the MAREARRAY, we have access to the row number and the column number.
We use these to specify different pieces using an IFS function to work through each row. Nested CHOOSE functions handle the column variations.
=LET(
wr,9814,wn,9816,wb,9815,wq,9813,wk,9812,wp,9817,
br,9820,bn,9822,bb,9821,bq,9819,bk,9818,bp,9823,
MAKEARRAY(8,8,LAMBDA(r,c,
IFS(
r=1,CHOOSE(c,br,bn,bb,bq,bk,bb,bn,br),
r=2,bp,
OR(r=3,r=4,r=5,r=6),0,
r=7,wp,
r=8,CHOOSE(c,wr,wn,wb,wq,wk,wb,wn,wr)))))
This outputs a grid of chess piece numbers. The last thing we need to do is convert them into chess pieces again with the CHAR function. We wrap it with an IFERROR function to handle the 0 cases too:
=LET(
wr,9814,wn,9816,wb,9815,wq,9813,wk,9812,wp,9817,
br,9820,bn,9822,bb,9821,bq,9819,bk,9818,bp,9823,
MAKEARRAY(8,8,LAMBDA(r,c,
IFERROR(CHAR(N("Start position")+IFS(
r=1,CHOOSE(c,br,bn,bb,bq,bk,bb,bn,br),
r=2,bp,
OR(r=3,r=4,r=5,r=6),0,
r=7,wp,
r=8,CHOOSE(c,wr,wn,wb,wq,wk,wb,wn,wr)))))))
Note, I also snuck a comment into the formula, using the N function.
N(“some text”) equals 0, so we can happily include this without affecting the number values. 9812 + 0 is still 9812!
At this stage, we have a static chessboard set up:
Now, shall we play a game? White or black?
Moving the Pieces
The key to making the pieces move is to remember two things:
- We have access to the row and column number of every grid square, and
- The pieces are encoded by numbers, so we can add and subtract them.
We designate a cell outside the chessboard to be the move counter, in my case I moved the board down a few rows and use E3. Assume it’s zero in the starting position.
When we change the value of this cell to 1, we can use an IF function inside the MAKEARRAY to update the board:
+IF(E3>0, IFS(AND(r=3,c=1),bp, AND(r=2,c=1),-bp, TRUE,0))
If A1 has a value 1, then the IF conditional is TRUE. So the IFS statement is in play (no pun intended).
For row 3, column 1, we add a “bp” (with value 9823) to the existing 0 value from the MAKEARRAY. Then for row 2, column 1, we subtract a “bp”, i.e. subtract 9823 from the old value of 9823, so it becomes a 0. All the other grid squares are left alone.
We add subsequent moves in the same way, with the IF test checking if the move counter has incremented by 1:
+IF(E3>1,IFS(AND(r=4,c=2),bp,AND(r=2,c=2),-bp,TRUE,0))
Finally, we use the N trick to add comments to these formulas.
So the 2-move game formula becomes:
=LET(
wr,9814,wn,9816,wb,9815,wq,9813,wk,9812,wp,9817,
br,9820,bn,9822,bb,9821,bq,9819,bk,9818,bp,9823,
MAKEARRAY(8,8,LAMBDA(r,c,
IFERROR(CHAR(N("Start position")+IFS(
r=1,CHOOSE(c,br,bn,bb,bq,bk,bb,bn,br),
r=2,bp,
OR(r=3,r=4,r=5,r=6),0,
r=7,wp,
r=8,CHOOSE(c,wr,wn,wb,wq,wk,wb,wn,wr))
+IF(E3>0,IFS(AND(r=3,c=1),bp,AND(r=2,c=1),-bp,TRUE,N("Move 1")))
+IF(E3>1,IFS(AND(r=4,c=2),bp,AND(r=2,c=2),-bp,TRUE,N("Move 2"))))))))
The following image helps illustrate these two moves:
Full Play-It-Through Chess Formula
From here, we simply add each move with a new IF statement.
Not going to lie, this took me rather a long time and was somewhat tedious. Feel free to copy the formula below or grab the template at the end of this post instead!
=LET(
wr,9814,wn,9816,wb,9815,wq,9813,wk,9812,wp,9817,
br,9820,bn,9822,bb,9821,bq,9819,bk,9818,bp,9823,
MAKEARRAY(8,8,LAMBDA(r,c,IFERROR(CHAR(
N("Start")+IFS(
r=1,CHOOSE(c,br,bn,bb,bq,bk,bb,bn,br),
r=2,bp,
OR(r=3,r=4,r=5,r=6),0,
r=7,wp,
r=8,CHOOSE(c,wr,wn,wb,wq,wk,wb,wn,wr))
+IF(E3>0,IFS(AND(r=6,c=6),wn,AND(r=8,c=7),-wn,TRUE,N("Move 1: Nf3"))
+IF(E3>1,IFS(AND(r=3,c=6),bn,AND(r=1,c=7),-bn,TRUE,N("Move 2: Nf6"))
+IF(E3>2,IFS(AND(r=5,c=3),wp,AND(r=7,c=3),-wp,TRUE,N("Move 3: c4"))
+IF(E3>3,IFS(AND(r=3,c=7),bp,AND(r=2,c=7),-bp,TRUE,N("Move 4: c4"))
+IF(E3>4,IFS(AND(r=6,c=3),wn,AND(r=8,c=2),-wn,TRUE,N("Move 5: Nc3"))
+IF(E3>5,IFS(AND(r=2,c=7),bb,AND(r=1,c=6),-bb,TRUE,N("Move 6: Bg7"))
+IF(E3>6,IFS(AND(r=5,c=4),wp,AND(r=7,c=4),-wp,TRUE,N("Move 7: Bg7"))
+IF(E3>7,IFS(AND(r=1,c=6),br,AND(r=1,c=8),-br,AND(r=1,c=7),bk,AND(r=1,c=5),-bk,TRUE,N("Move 8: O-O"))
+IF(E3>8,IFS(AND(r=5,c=6),wb,AND(r=8,c=3),-wb,TRUE,N("Move 9: Bf4"))
+IF(E3>9,IFS(AND(r=4,c=4),bp,AND(r=2,c=4),-bp,TRUE,N("Move 10: d5"))
+IF(E3>10,IFS(AND(r=6,c=2),wq,AND(r=8,c=4),-wq,TRUE,N("Move 11: Qb3"))
+IF(E3>11,IFS(AND(r=5,c=3),bp-wp,AND(r=4,c=4),-bp,TRUE,N("Move 12: dxc4"))
+IF(E3>12,IFS(AND(r=5,c=3),wq-bp,AND(r=6,c=2),-wq,TRUE,N("Move 13: Qxc4"))
+IF(E3>13,IFS(AND(r=3,c=3),bp,AND(r=2,c=3),-bp,TRUE,N("Move 14: c6"))
+IF(E3>14,IFS(AND(r=5,c=5),wp,AND(r=7,c=5),-wp,TRUE,N("Move 15: e4"))
+IF(E3>15,IFS(AND(r=2,c=4),bn,AND(r=1,c=2),-bn,TRUE,N("Move 16: Nbd7"))
+IF(E3>16,IFS(AND(r=8,c=4),wr,AND(r=8,c=1),-wr,TRUE,N("Move 17: Rd1"))
+IF(E3>17,IFS(AND(r=3,c=2),bn,AND(r=2,c=4),-bn,TRUE,N("Move 18: Nb6"))
+IF(E3>18,IFS(AND(r=4,c=3),wq,AND(r=5,c=3),-wq,TRUE,N("Move 19: Qc5"))
+IF(E3>19,IFS(AND(r=5,c=7),bb,AND(r=1,c=3),-bb,TRUE,N("Move 20: Bg4"))
+IF(E3>20,IFS(AND(r=4,c=7),wb,AND(r=5,c=6),-wb,TRUE,N("Move 21: Bg5"))
+IF(E3>21,IFS(AND(r=5,c=1),bn,AND(r=3,c=2),-bn,TRUE,N("Move 22: Na4"))
+IF(E3>22,IFS(AND(r=6,c=1),wq,AND(r=4,c=3),-wq,TRUE,N("Move 23: Qa3"))
+IF(E3>23,IFS(AND(r=6,c=3),bn-wn,AND(r=5,c=1),-bn,TRUE,N("Move 24: Nxc3"))
+IF(E3>24,IFS(AND(r=6,c=3),wp-bn,AND(r=7,c=2),-wp,TRUE,N("Move 25: bxc3"))
+IF(E3>25,IFS(AND(r=5,c=5),bn-wp,AND(r=3,c=6),-bn,TRUE,N("Move 26: Nxe4"))
+IF(E3>26,IFS(AND(r=2,c=5),wb-bp,AND(r=4,c=7),-wb,TRUE,N("Move 27: Bxe7"))
+IF(E3>27,IFS(AND(r=3,c=2),bq,AND(r=1,c=4),-bq,TRUE,N("Move 28: Qb6"))
+IF(E3>28,IFS(AND(r=5,c=3),wb,AND(r=8,c=6),-wb,TRUE,N("Move 29: Bc4"))
+IF(E3>29,IFS(AND(r=6,c=3),bn-wp,AND(r=5,c=5),-bn,TRUE,N("Move 30: Nxc3"))
+IF(E3>30,IFS(AND(r=4,c=3),wb,AND(r=2,c=5),-wb,TRUE,N("Move 31: Bc5"))
+IF(E3>31,IFS(AND(r=1,c=5),br,AND(r=1,c=6),-br,TRUE,N("Move 32: Rfe8+"))
+IF(E3>32,IFS(AND(r=8,c=6),wk,AND(r=8,c=5),-wk,TRUE,N("Move 33: Kf1"))
+IF(E3>33,IFS(AND(r=3,c=5),bb,AND(r=5,c=7),-bb,TRUE,N("Move 34: Be6"))
+IF(E3>34,IFS(AND(r=3,c=2),wb-bq,AND(r=4,c=3),-wb,TRUE,N("Move 35: Bxb6"))
+IF(E3>35,IFS(AND(r=5,c=3),bb-wb,AND(r=3,c=5),-bb,TRUE,N("Move 36: Bxc4+"))
+IF(E3>36,IFS(AND(r=8,c=7),wk,AND(r=8,c=6),-wk,TRUE,N("Move 37: Kg1"))
+IF(E3>37,IFS(AND(r=7,c=5),bn,AND(r=6,c=3),-bn,TRUE,N("Move 38: Ne2+"))
+IF(E3>38,IFS(AND(r=8,c=6),wk,AND(r=8,c=7),-wk,TRUE,N("Move 39: Kf1"))
+IF(E3>39,IFS(AND(r=5,c=4),bn-wp,AND(r=7,c=5),-bn,TRUE,N("Move 40: Nxd4+"))
+IF(E3>40,IFS(AND(r=8,c=7),wk,AND(r=8,c=6),-wk,TRUE,N("Move 41: Kg1"))
+IF(E3>41,IFS(AND(r=7,c=5),bn,AND(r=5,c=4),-bn,TRUE,N("Move 42: Ne2+"))
+IF(E3>42,IFS(AND(r=8,c=6),wk,AND(r=8,c=7),-wk,TRUE,N("Move 43: Kf1"))
+IF(E3>43,IFS(AND(r=6,c=3),bn,AND(r=7,c=5),-bn,TRUE,N("Move 44: Nc3+"))
+IF(E3>44,IFS(AND(r=8,c=7),wk,AND(r=8,c=6),-wk,TRUE,N("Move 45: Kg1"))
+IF(E3>45,IFS(AND(r=3,c=2),bp-wb,AND(r=2,c=1),-bp,TRUE,N("Move 46: axb6"))
+IF(E3>46,IFS(AND(r=5,c=2),wq,AND(r=6,c=1),-wq,TRUE,N("Move 47: Qb4"))
+IF(E3>47,IFS(AND(r=5,c=1),br,AND(r=1,c=1),-br,TRUE,N("Move 48: Ra4"))
+IF(E3>48,IFS(AND(r=3,c=2),wq-bp,AND(r=5,c=2),-wq,TRUE,N("Move 49: Qxb6"))
+IF(E3>49,IFS(AND(r=8,c=4),bn-wr,AND(r=6,c=3),-bn,TRUE,N("Move 50: Nxd1"))
+IF(E3>50,IFS(AND(r=6,c=8),wp,AND(r=7,c=8),-wp,TRUE,N("Move 51: h3"))
+IF(E3>51,IFS(AND(r=7,c=1),br-wp,AND(r=5,c=1),-br,TRUE,N("Move 52: Rxa2"))
+IF(E3>52,IFS(AND(r=7,c=8),wk,AND(r=8,c=7),-wk,TRUE,N("Move 53: Kh2"))
+IF(E3>53,IFS(AND(r=7,c=6),bn-wp,AND(r=8,c=4),-bn,TRUE,N("Move 54: Nxf2"))
+IF(E3>54,IFS(AND(r=8,c=5),wr,AND(r=8,c=8),-wr,TRUE,N("Move 55: Re1"))
+IF(E3>55,IFS(AND(r=8,c=5),br-wr,AND(r=1,c=5),-br,TRUE,N("Move 56: Rxe1"))
+IF(E3>56,IFS(AND(r=1,c=4),wq,AND(r=3,c=2),-wq,TRUE,N("Move 57: Qd8+"))
+IF(E3>57,IFS(AND(r=1,c=6),bb,AND(r=2,c=7),-bb,TRUE,N("Move 58: Bf8"))
+IF(E3>58,IFS(AND(r=8,c=5),wn-br,AND(r=6,c=6),-wn,TRUE,N("Move 59: Nxe1"))
+IF(E3>59,IFS(AND(r=4,c=4),bb,AND(r=5,c=3),-bb,TRUE,N("Move 60: Bd5"))
+IF(E3>60,IFS(AND(r=6,c=6),wn,AND(r=8,c=5),-wn,TRUE,N("Move 61: Nf3"))
+IF(E3>61,IFS(AND(r=5,c=5),bn,AND(r=7,c=6),-bn,TRUE,N("Move 62: Ne4"))
+IF(E3>62,IFS(AND(r=1,c=2),wq,AND(r=1,c=4),-wq,TRUE,N("Move 63: Qb8"))
+IF(E3>63,IFS(AND(r=4,c=2),bp,AND(r=2,c=2),-bp,TRUE,N("Move 64: b5"))
+IF(E3>64,IFS(AND(r=5,c=8),wp,AND(r=6,c=8),-wp,TRUE,N("Move 65: h4"))
+IF(E3>65,IFS(AND(r=4,c=8),bp,AND(r=2,c=8),-bp,TRUE,N("Move 66: h5"))
+IF(E3>66,IFS(AND(r=4,c=5),wn,AND(r=6,c=6),-wn,TRUE,N("Move 67: Ne5"))
+IF(E3>67,IFS(AND(r=2,c=7),bk,AND(r=1,c=7),-bk,TRUE,N("Move 68: Kg7"))
+IF(E3>68,IFS(AND(r=8,c=7),wk,AND(r=7,c=8),-wk,TRUE,N("Move 69: Kg1"))
+IF(E3>69,IFS(AND(r=4,c=3),bb,AND(r=1,c=6),-bb,TRUE,N("Move 70: Bc5+"))
+IF(E3>70,IFS(AND(r=8,c=6),wk,AND(r=8,c=7),-wk,TRUE,N("Move 71: Kf1"))
+IF(E3>71,IFS(AND(r=6,c=7),bn,AND(r=5,c=5),-bn,TRUE,N("Move 72: Ng3+"))
+IF(E3>72,IFS(AND(r=8,c=5),wk,AND(r=8,c=6),-wk,TRUE,N("Move 73: Ke1"))
+IF(E3>73,IFS(AND(r=5,c=2),bb,AND(r=4,c=3),-bb,TRUE,N("Move 74: Bb4+"))
+IF(E3>74,IFS(AND(r=8,c=4),wk,AND(r=8,c=5),-wk,TRUE,N("Move 75: Kd1"))
+IF(E3>75,IFS(AND(r=6,c=2),bb,AND(r=4,c=4),-bb,TRUE,N("Move 76: Bb3+"))
+IF(E3>76,IFS(AND(r=8,c=3),wk,AND(r=8,c=4),-wk,TRUE,N("Move 77: Kc1"))
+IF(E3>77,IFS(AND(r=7,c=5),bn,AND(r=6,c=7),-bn,TRUE,N("Move 78: Ne2+"))
+IF(E3>78,IFS(AND(r=8,c=2),wk,AND(r=8,c=3),-wk,TRUE,N("Move 79: Kb1"))
+IF(E3>79,IFS(AND(r=6,c=3),bn,AND(r=7,c=5),-bn,TRUE,N("Move 80: Nc3+"))
+IF(E3>80,IFS(AND(r=8,c=3),wk,AND(r=8,c=2),-wk,TRUE,N("Move 81: Kc1"))
+IF(E3>81,IFS(AND(r=7,c=3),br,AND(r=7,c=1),-br,TRUE,N("Move 82: Rc2#"))
)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
Adding Controls to the Chess Game
We’re into the endgame now…
All that remains is to add controls to advance the move counter or automatically advance it every minute.
The first step is to update the spreadsheet settings to make this possible:
Spreadsheet Settings
For this formula to work, you must have iterative calculations enabled and recalculation set to every minute.
Go to: File > Settings > Calculation
Make sure Recalculation is set to “On change and every minute”.
Make sure Iterative Calculations are ON and set to 1 iteration.
Automatic Play: One Move per Minute
We set the Google Sheet to recalculate every minute, and we can take advantage of this to move the game move counter along every minute.
Let’s put a checkbox in cell A3.
And this formula in cell B3:
=IF(A3,,{ HOUR(LAMBDA(x,x)(NOW())), MINUTE(LAMBDA(x,x)(NOW())) })
I’m going to save the full explanation of this for a future post, but essentially, when the checkbox is unchecked, it puts the current hour and minute values into adjacent cells. The key thing is that they are then static, i.e. they don’t change.
Using a regular NOW function, which changes every minute because of the spreadsheet settings, we can calculate the difference and watch the game move number (green cell) increment each minute (GIF is sped up):
Player Controls with Checkbox
Using Tyler Robertson’s ingenious checkbox-as-a-button technique, we can increment the counter using a checkbox:
Notice the green game move number increment by 1 each time I click the checkbox.
The full formulas for both of these techniques are in the template, which can be copied below.
And that’s it!
Now we combine one of these control systems with the full LET formula above to enjoy “The Game of the Century” play out in our Google Sheet:
Play-It-Through Chess Template
🔗 Click here to open a view-only copy >>
Make your own copy: File > Make a copy
Note: If you are unable to open this file, it’s probably because it’s from an outside organization and my Google Workspace domain is not whitelisted at your organization. Ask your Google Workspace administrator about this. In the meantime, feel free to open it in an incognito window and you should be able to view it.
Also, be sure to check the spreadsheet settings as detailed in the previous section of this post above.
Other Google Sheets Projects
Check out these other wacky projects:
I noticed the checkbox as a button to increment a counter and remembered that I had done a similar thing with apps script before for those who need that functionality without having to change to iterative calculations. Here’s a view only link for anybody that wants to make a copy – https://docs.google.com/spreadsheets/d/1yYtl-tc1FnufL3APY47vkn19brUpf85uuM3lpAU0SL0/edit?usp=sharing
Very cool! Wonderful to work through the logic of what is happening. It ends up being simpler than I expected, but a very impressive approach and formula…
Thanks, Kirk! Definitely not rocket science once you see how it’s built.
Hi Ben, thanks for this outline. Very nice.
Ẅhen I entered the CHAR codes in Google Sheets I found out that CHAR(9823) renders an odd black pawn. Is there a fix to that? Regards, André
Wow, I didn’t know that something like that was even possible. Very interesting, but I’m out. Chess is not my world 😉