Wupato: an exploration of golang, websockets and javascript, part 1

I’ve been reading about golang for a while now, ever since it was announced by google. One of the downsides of my previous job was that we were totally focused on building our software in a way that would be compatible with ALL of our client’s OS/Hardware combinations. This led me to focus mostly on C++ and Java, yet the itch to study golang remained.

One very nice book I’ve read is Go Programming Blueprints, which covers various aspects of programming web applications and services in golang.

I’ve since written a couple of programs: stuff that monitors my router for weird behaviour and a couple of one-off programs. Why not try something more interesting.

One of the first programs that one typically writes to try out a new programming language is a tetris clone. Now, github is full of these so why not try something more akin to what suits golang best: an online game where people can play simultaneously, interacting with each other. Not exactly a new concept, yet a nice project.

First of all we need to define the basic workflow of the game:

  • A player connects via browser on his homepage
  • If the player is not registered, the possibility to register is offered, where he can enter an alias
  • A list of games waiting for players is displayed, if the player is registered, he can join a game
  • The possibility to create a new game is offered
  • The player who creates a game can start the game
  • The browser client connects to the server via a websocket. The client sends events like horizontal and vertical movements, rotations etc, while it receives updates on the other player’s board situation
  • Players receive new blocks as fast as they can
  • When a block is fixed to the bottom, a random special block is assigned on a regular basis
  • When a player clears a line containing a special block, it becomes available to the player
  • A player can send the special block to another player, potentially helping or hurting his gameplay
  • The last player standing wins

We won’t be writing everything from scratch, as there is an existing, reasonably documented implementation of the basic gameplay at the codeincomplete blog.

So we start by serving files using golang. To do this, we first split up the files so that we can serve static content from a different subdirectory.

We create the following directory structure:

static/
 /css
 /img
 /js

We split the css portion of index.html into a file called wupato.css in the static/css directory, all javascript code except the run(); call in a file called wupato.js and place both that and stats.js into the static/js directory, copy the texture.jpg image into the static/img directory.

We place what’s left of index.html in a newly created templates directory.

Now we need to fix some paths, we add a reference to our css in the index.html file like this:

<link rel="stylesheet" href="static/css/wupato.css">

then we change the javascript code to load the javascript from the wupato.js file

  <script src="static/js/wupato.js"></script>
  <script>run();</script>

Finally, we fix the path to the texture.jpg file in wupato.css to point to ../img/texture.jpg as the path is relative to our css file

Now that we have the files ready, we can add some go code to serve them.

We will be using templates for the html files in the future, so we start with a simple template handler type that implements the http.Handler interface. We use the sync.One object to load the template once the first time the type’s ServeHTTP method is called.

// templ represents a single template
type templateHandler struct {
	once     sync.Once
	filename string
	templ    *template.Template
}

// ServeHTTP handles the HTTP request.
func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	t.once.Do(func() {
		t.templ =
			template.Must(template.ParseFiles(filepath.Join("templates",
				t.filename)))
	})
	t.templ.Execute(w, nil)
}

Then we use the gorilla/mux muxer to serve static files and the index.html file and call the http.ListenAndServe function to accept incoming connections.

func main() {
	r := mux.NewRouter()
	r.Handle("/", &templateHandler{filename: "index.html"})
	s := http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))
	r.PathPrefix("/static/").Handler(s)
	// start the web server
	if err := http.ListenAndServe(":8080", r); err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

We also want to have logging of what goes on so we add the CombinedLoggingHandler from gorilla/handlers to our existing handlers.
The final code is:

func main() {
	r := mux.NewRouter()
	r.Handle("/", handlers.CombinedLoggingHandler(os.Stdout, &templateHandler{filename: "index.html"}))
	s := http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))
	r.PathPrefix("/static/").Handler(handlers.CombinedLoggingHandler(os.Stdout, s))
	// start the web server
	if err := http.ListenAndServe(":8080", r); err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

The code for the server is available on github.

Leave a Reply

Your email address will not be published. Required fields are marked *