Animating a commit based Sudoku game using Puppeteer
Tags: showdev, puppeteer, sudoku, gif
Published: 2018-10-31 15:46:00

---
title: Animating a commit based Sudoku game using Puppeteer
published: true
description: Adventures in generating an animated GIF from Git history
tags: showdev,puppeteer,sudoku,GIF
---

A few months ago I came across an intriguing GitHub repo:

{% github xiegeo/commit-sudoku %} 

It said it was *"accepting pull requests to collectively finish a Sudoku puzzle"*. I was fascinated. The idea was simple. The author had set up an empty [Sudoku](https://en.wikipedia.org/wiki/Sudoku) board and anyone could add a number to it as long as it was a valid Sudoku move (& max 1 move/day/author). Pretty soon I found myself sending out PRs - adding a [4 here](https://github.com/xiegeo/commit-sudoku/commit/9a1934f61821835167f4533f13db911299e66e36), or a [7 there](https://github.com/xiegeo/commit-sudoku/commit/bad1ae0c46289740e23f986f9962dc1449546a43).

It was a bit difficult to specify your intended move because the board was represented as a simple HTML table in the repo's `README.md` file.

```html
<table>
  <tr>
    <td>1
    <td>&nbsp;
    <td>7
    <td>&nbsp;
    <td>9
    <td>&nbsp;
    <td>&nbsp;
    <td>&nbsp;
    <td>4
    ...
```

So I sent an enhancement PR [embedding row numbers in the board](https://github.com/xiegeo/commit-sudoku/pull/32/files) - and lo! - not only was the PR accepted, but the author [@xiegeo](https://github.com/xiegeo) even offered me commit access to the main repo! I was psyched! Soon along with sending out "move" PR's I also started checking and merging PR's sent out by other contributors and doing general housekeeping tasks.

When I went back to the repo, after a longer delay, I noticed that the board was a lot more filled than I remembered. This got me thinking - how had the board evolved over time since the first move? So I decided to try and **animate the evolution of the Sudoku board state from commit history**.

## Step 1: Get a list of commits

First, I needed to get a list of commit SHAs that were made to the repo in reverse (oldest first) order.

```js
const commitSHAs = execSync('git log --reverse --no-merges --format=format:%H')
  .toString().split('\n');
```

## Step 2: Get Sudoku state for each commit

Next, for each commit, I need to extract the state of the board from the repo's `README.md` file. I used `git show` to do this.

```js
const readmeContents = execSync(`git show ${commitSHA}:README.md`)
  .toString().split('\n');
```

## Step 3: Render each state in Puppeteer

Now comes the interesting part. I have the state of the board as HTML for each commit that I need to render as a PNG. For this, I used [Puppeteer](https://pptr.dev) - a high level Node.js API for controlling headless Chrome.

```js
await page.goto(`data:text/HTML,${HTMLHeader} ${tableState}`);
await page.screenshot({ path: `tmp/T${new Date().getTime()}.png` });
```

Here `tableState` contains the HTML representing table state retrieved from the `README.md` file in the previous step. `HTMLHeader` is a little boilerplate to apply GitHub's style.

`data:text/HTML,...` is very interesting. It's called a [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs). Basically you can put any HTML after it and the browser will render it. For example, click on this URL: [`data:text/HTML,<h1>hello, world!</h1>`](data:text/HTML,<h1>hello, world!</h1>). 

*Note: The spec recommends `text/html` (lowercase) not `text/HTML` (uppercase), but I was having issues rendering it correctly in this blog. Both seem to work.*

## Step 4: Create animated GIF

Finally, I used the excellent [`gifencoder `](https://github.com/eugeneware/gifencoder) package to generate an animated GIF from the list of PNG files that were generated from the previous step.

```js
pngFileStream(`tmp/T*.png`)
  .pipe(encoder.createWriteStream({ repeat: 0, delay: 200, quality: 50 }))
  .pipe(fs.createWriteStream(outputGIF));
```

# Result

So here is the resulting animation showing how the game played out:

![animation](https://commit-sudoku.surge.sh/output.gif)


## Step 5 (Bonus): Hook up to TravisCI and Surge.sh

I did not want to be running this script manually on my machine on every commit to update the animation. To begin with I had missed a few commits to the repo and that's why I started down this path in the first place. Luckily getting TravisCI to run this script on every commit (to master) was a breeze.

```yaml
after_success:
  - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then cd animation && npm install && npm run generate; fi
```

This tells TravisCI to only generate the animation if there's an actual commit to master and if all tests pass (i.e. Sudoku board is in a good state). 

Finally, I upload the resulting GIF to [Surge.sh](https://surge.sh) - which offers simple static asset hosting - so that it can be showcased in the repo's `README.md`.

```bash
SURGE_LOGIN=xxxxxxx@xxxx.com SURGE_TOKEN=xxxxxxxxxx npx surge folder my-endpoint.surge.sh
```

And that's it!

There are a few details that were skipped in this post for brevity - but you can dig into the complete code [here](https://github.com/xiegeo/commit-sudoku/tree/master/animation) - including how you can run it locally.

Let me know what you thought of the post!

Cheers!