DEV Community

Cover image for Build a Tic-Tac-Toe Game In the GitHub README.md File
Sridhar Subramani
Sridhar Subramani

Posted on

Build a Tic-Tac-Toe Game In the GitHub README.md File

Pushing the boundaries of the markdown file using Go

What would be your initial response when someone asked “is it possible to play a tic-tac-toe game from Github README.md file” ?

If your answer is “yes”, then you are in for a delight.

Let’s dive in and see how it’s possible to make a Github README.md file as a frontend for a tic-tac-toe game.


How It All Started

You may ask why I even went on this journey, and the following are a few of the reasons behind it.

  • I wanted to push the boundaries of a markdown file and make it as interactive as possible.

  • to uniquely identify and track the traffic coming to my individual GitHub project page.

Why did I even get these wild ideas in the first place? Well, it’s because whenever I add an image or image link to the GitHub readme markdown file, it’s always getting replaced with a proxy URL when I try to open the image(proxy URL looks like this: https://camo.githubusercontent.com/some-hash ).

I wondered why it was happening like that. I mean, it’s okay to proxy an external URL, but why are even static images placed in repositories getting a proxy URL when they’re referenced from a readme file? What is GitHub trying to achieve here?

Then I thought about what would happen if there was no proxy server involved while serving the images. If there is no proxy, then the HTTP request to fetch the image will directly come to our server(where we host the image) and we can read the HTTP request and try to get a user IP address, which can be used for any tracking purposes and GitHub will have no control over it.

So I kind of understood the reason behind why Github added the image proxy server. At this stage, I wanted to know whether it’s possible to get any unique identification from the HTTP request made from the user machine as part of rendering the markdown file.

I hoped there would be a way to uniquely identify each session/machine. So I thought of building a simple game that can be played from within the markdown file itself to test that. The idea was to build a multiplayer game where each player would have their own state saved against a unique id. “Tic-tac-toe” game seems to be a good fit for this, since it’s a well-known game, and it’s fairly straightforward to implement.

After I started the development, I quickly found out there was no way we could get a unique id from the http request fired from the readme markdown file. This is because each time a request comes from a random proxy server, and on top of that, cookies are not allowed either.

So, without a unique id, there won’t be any states saved for each player, and individual gameplay is impossible. Therefore, only a single gameplay will be shared with the entire internet. (In other words, anyone in the world can see and play the game, but only one gameplay is visible to everyone; changes made by one are reflected to all.)

At this stage, I thought of abandoning the quest, but playing a game from the Github README.md file itself seems like a cool idea, even when the gameplay is shared with the entire internet. So I ran this idea by my friends, and they seemed interested in seeing how this would turn out, so I thought of investing some time in developing this.


Quick Recap

  • GitHub uses a proxy server to serve media content, and each time a random proxy server will be used to serve media (when the cache is disabled).

  • The proxy server doesn’t allow us to store cookies.

  • And we would like to make markdown interactive with a dynamic UI.


The Life Cycle of an HTTP Request From a GitHub Markdown File

Let’s consider the below Github project structure — where we have localImageFile.png and README.md file.

The README.md file shows two images, one from the repository itself and the other from a remote server.

Github project structureGithub project structure

The below sequence diagram is illustrated for loading localImageFile.png from the same repo:

Local image loading sequenceLocal image loading sequence

The below sequence diagram is illustrated for loading remoteImage.png from the remote server:

Remote image loading sequenceRemote image loading sequence


Let’s Build the Tic-Tac-Toe

  • Building APIs for the game — We are going to build a backend server that exposes APIs to start the game, restart the game, read the current state of the game, make a move from the user, and read the history of the game state.

  • How do we call these APIs from a Github Readme Markdown file — Well, we are going to create CTAs (call to action) for each of the APIs and call the APIs whenever the CTAs are clicked.

  • How do we create CTAs on Markdown files — For CTAs, we will use images, and when the image is clicked, an HTTP request will be fired from the markdown file.

  • How do we show the tic-tac-toe board on UI — You might have already guessed it. We will use images to reflect the current state of the game.

In short, the backend will return a set of images we can use as CTA, and when clicking the images, we fire the corresponding API which will manipulate the game state.

As I mentioned earlier, since we can’t identify the request made from the same system, only one gameplay will be shared with the entire internet. Hence, the backend maintains the game state of the 3x3 tic-tac-toe in a single variable. The APIs that are exposed will basically try to modify this single game state and reflect the same on the UI (i.e. the README file) using images sent from the server.

Image assets stored on the backend server

Please keep the svg file names in mind as we will go over the uses for each image file shortly.

[Image assets stored on the backend server](https://github.com/sridhar-sp/tic-tac-toe-backend/tree/main/assets)Image assets stored on the backend server

The backend server is written in Go:

func main() {
    port := getPort()
    log.Println("Starting tic-tac-toe service at port ", port)

    http.Handle("/", http.HandlerFunc(onHome))
    http.Handle("/renderCell", http.HandlerFunc(onRenderCell))
    http.Handle("/clickCell", http.HandlerFunc(onClickCell))
    http.Handle("/renderPlayControls", http.HandlerFunc(onRenderPlayControl))
    http.Handle("/clickPlayControls", http.HandlerFunc(onPlayControlClick))
    http.Handle("/renderActivities", http.HandlerFunc(renderActivities))

    http.ListenAndServe(":"+port, nil)
}
Enter fullscreen mode Exit fullscreen mode

Let’s look at each API and see what it does!

Render cell

  • The renderCell API takes cell index as a URL parameter and returns the cell image. For a 3x3 tic-tac-toe game, we have nine cells to render. The backend returns the corresponding cell image based on the cell’s state.

  • rect_x.svg is returned when the user has pressed that cell.

  • rect_o.svg is returned when the computer has pressed that cell.

  • rect_empty.svg is returned when no one has pressed that cell.

  • rect_x_selected.svg is returned to highlight the area won by the user.

  • rect_o_selected.svg is returned to highlight the area won by the computer.

Click cell

  • The clickCell API takes the cell index as a URL parameter and marks the cell as selected by the user (provided the cell is empty).

  • The clickCell API is attached to the anchor link of each cell that is rendered by the renderCell API.

Please see the screenshot below to see the markdown file content and its preview:

[Markdown file content with a preview](https://github.com/sridhar-sp/tic-tac-toe)Markdown file content with a preview

  • When each cell is clicked, the server modifies the game state and reloads the page so that the new modified state can be read from the server.

  • Page reloading is a mandatory step. Since this is a markdown file, this is the only way to get the modified state from the server.

Reload the page

This is one of the crucial steps. Remember how GitHub uses an image proxy server to serve media files? Well, that image proxy caches the response for a certain amount of time. So we need to add the cache control response header as ‘no-cache,no-store,must-revalidate’ to disable this cache mechanism.

If we don’t disable the cache mechanism, then the proxy server will simply return the cached response and will not call our backend till the cache expires. Therefore, we won’t be able to reflect the current game state back to the user, and we will end up showing stale data (not a good user experience).

    const REDIRECT_URL = "https://github.com/sridhar-sp/tic-tac-toe"

    responseWriter.Header().Set("Content-Type", "image/svg+xml") 

    responseWriter.Header().Set("Cache-Control", "no-cache,
    no-store,must-revalidate")

    responseWriter.Header().Set("expires", "0") 
    responseWriter.Header().Set("pragma", "no-cache")

    http.Redirect(
     responseWriter, req, 
     REDIRECT_URL, http.StatusMovedPermanently
    )
Enter fullscreen mode Exit fullscreen mode

Render play controls

  • The renderPlayControls API returns computer_start_button.svg when the game is yet to begin and restart_button.svg when the game is finished.

Click play controls

  • This API is attached as an anchor to renderPlayControls

  • When the play control button is pressed, the corresponding action takes place on the backend, such as restarting the game or letting the computer make the first move.

Please see the screenshot below to see the play control markdown file content and its preview:

Play controls previewPlay controls preview

Render game activities

Please see the screenshot below to see the game activities markdown file content and its preview:

Game activities previewGame activities preview


Demo

Player ‘X’ is a human, and Player ‘O’ is a computer.

Demo

Please see the screenshot below to see the markdown file content and its preview:

Click here to see Markdown file with its interactive preview


Source Code

Here are the GitHub repositories for the final frontend application and the backend written in Go:


Reference

Library used

Top comments (0)