A close friend was intrigued by a Tetris clone I recently wrote about, but was having difficulty getting an initial project up and running. CoderX and I recorded a step by step guide which shows just how simple and fast it is to start, build and publish an interactive webpage using ClojureScript.
Write: Make your own version! Try a bigger board? Smarter computer? Different look?
Transcript of screencast:CoderX here
If you aren't using ClojureScript to make web pages, you are missing out.
This style of programming is fast, fun, simple and rewarding.
And when I say fast, I mean really fast.
It is a quantum leap from any other style of programming.
You really have to experience it to appreciate it.
Today Timothy and I will take you step by step through the process of building a tic tac toe game from start to finish.
Follow along with us, and publish a web page to show your friends.
We’ll be coding in ClojureScript.
You will need Java, Leiningen, and your favourite text editor installed.
To create a new Clojure project open a Terminal and type
`lein new figwheel tictactoe -- --reagent`
Go into the tictactoe project directory
We have a readme file, a project file, a style sheet, index.html and core.cljs source file.
Type `lein figwheel` to start the process that builds and notifies the browser of file changes.
Position your browser on the left and navigate to `localhost:3449`
Wait until you see the “Successfully compiled” message in the terminal, and reload the browser.
The “Hello world” page indicates that everything is working so far.
Open up the dev console in your browser.
We are ready to start coding now.
Open up the core.cljs file from the source directory under tictactoe.
Position your text editor on the right so you can see the code and your page side by side.
Edit the print string on line 6 of the source code to write a new message.
The browser console logs the output immediately.
The current state of our game will be kept inside an `atom` called `app-state`.
This is a Reagent atom, which causes our web page to be re-rendered whenever the `app-state` changes.
“Def once” is not re-evaluated when the code reloads, so app-state can only be updated by either code execution or refreshing the browser.
Let’s update the app-state text with “Hi”.
The app renders our new message.
The `on-js-reload` function is called whenever new code is loaded from any namespace.
When working with multiple namespaces, it is a good idea to re-render the application here.
For now we can ignore it as we only need to work in the core namespace.
Rename the “hello-world” component to “tictactoe”.
Reagent components are functions that return HTML in hiccup form.
Hiccup looks quite similar to HTML but is much more compact.
The first element is the tag, followed by a map of attributes, then any children nodes.
Let’s add a SVG element to our page, with a circle in it of radius 10.
It looks more like a quarter circle, because the center is in the top left of the drawing area.
In your browser, right click and “inspect element” on the quarter circle.
The code has produced DOM elements, just like normal HTML.
When we set the center coordinates to 10, we immediately see the full circle.
Wrap everything we have in a `center` tag,
and set the SVG width and height to 500 to make a good sized square area to work with.
SVG has a neat feature called viewbox which allows you to specify the coordinate frame.
When we assign a viewbox of size 30, and draw a circle of size 30 at location 30, the center of the circle is at the far bottom right of the SVG area.
The viewbox allows us to use whatever coordinate system is convenient, regardless of the SVGs size.
We now have the basics in place to draw our game board.
How are we going to model our game?
A game looks like this:
There are nine cells where you can mark your move.
So we can draw 9 rectangles.
When the player clicks on any of these rectangles,
we will check if that location is a valid move.
We need to add a game board to our app state.
“new-board” is a function that returns a matrix;
a vector of vectors.
We’ll initialize all the positions with zero.
And print out the board to the console.
Now let’s place a rectangle in each of the nine locations.
This `for` expression creates a sequence containing every combination of i and j in the range of our board size.
We set the view-box to be 0 to 3 so each square is of size 1.
Black looks pretty intense, how about green?
O.K. now we want to be able to click on the rectangles.
Attach an `on-click` function that just prints out a message.
I like to give my handler functions a name, so I can understand error stacktraces when they happen.
We can now see our message when we click the rectangle.
Now we need to change the board in the app-state.
To start with we will increment the current value at the coordinates we click.
These warnings are because we have a lazy sequence of non-keyed elements.
I will explaining what that means in future screencasts,
but for now let’s just put all these elements into the parent svg using the into function.
Now when we click the rectangle, we can see the app state board is updated.
Let’s make the fill color of the rectangle dependent on the number in the board matrix.
Now when we click on squares they turn yellow.
Instead of just green and yellow, what we really want are rectangles, crosses, or circles.
Remember a reagent component is a function that returns hiccup.
So let’s make a separate function for each of those three components we need.
Now for every cell in the matrix,
When it contains a 0, we will draw a blank.
When it contains a 1, we will draw a circle.
When it contains a 2, we will draw a cross.
Reagent components are nested in vectors.
So instead of calling blank, circle or cross directly by placing them inside parentheses,
we place them inside square brackets.
Make a blank function by cutting and pasting the rectangle code.
Make a circle function that returns the hiccup syntax for a circle element.
Make a cross function, just a div for now until we figure that part out.
If I save the code with an error in it, I see the problem reported at the bottom of the browser.
It tells me the line number of the code with the problem.
O.K. let’s position this circle a little better…
and try some different colors…
Now for the cross shape.
We need two lines inside a group element.
Update the app-state to have a cross at 0,0
Ah we need to use a stroke color for the lines,
and make the stroke-width smaller.
We can adjust the position using translations and scaling.
Set the stroke-linecap property to “rounded”, and we have a pretty good looking cross.
Let’s add a button to reset the app-state to an empty board.
And now a function “computer-move” for the computer player logic.
For now the computer will always try to take 0,0
Using 0 1 2 to indicate blank, player and computer is a bit cryptic.
Let’s refactor those to “B” for blank, “P” for player, and “C” for computer.
When we click on a position, the computer now takes 0,0
When we print the board out on the console, it’s a little easier to interpret.
We’ll improve the computer player slightly by choosing randomly any unused cell.
Repeating 3 all over the place is a sign we should declare the board-size.
Groovy, now when we click on a blank location, the computer randomly selects any remaining blank position. We almost have a playable game.
Let’s take a look at what our game would be like with a different board size.
This is kind of like the game “Go”.
When possible, I recommend writing functions that take state and return new state instead of modifying it directly. “computer-move” currently does too much state manipulation, so let’s refactor it to take a board and return a new board instead of updating the app state.
Oh no, I introduced a bug somewhere!
Let’s add some logging to see how our function behaves.
Aha! I have :board in the path, but it is not necessary anymore because we are now updating a board, not the entire app-state.
Defining tests is very similar to Clojure.
In future screencasts I’ll explain how to incorporate unit testing in your workflow;
but for now don’t worry too much that they seem to be silent.
Let’s move on to implementing the victory condition for tic tac toe.
We want to detect a straight line of cells owned by the same player.
For every cell we can check for 3 in a row to the right, down, diagonal right down, and diagonal right up.
We can check each direction with a single function if we pass dx dy parameters which define the direction of the step we want to travel in.
A straight is detected if every cell has the owner we are evaluating as a potential winner.
When we look up a value outside of a vector, we get nil, which is falsy.
So we don’t need to explicitly check the bounds of our board.
Our win condition is if there is some true value in any combination of starting location with direction, such that we detect 3 owned cells in a row.
Let’s check that this logic is working by printing out to the console.
Given a board of a single cell owned by “P”, and looking for straights of length 1,
we expect that checking for the `win?` condition will return true.
But when looking for straights of length 2, it should return false.
Let’s see if it works with a 2x2 board, checking for length 2 on a diagonal.
Yes, cool. This is looking promising.
Our game status can be player victory, computer victory, draw or in progress.
A draw occurs when the board is full, but there is no victor.
The definition of full is that every cell is in the set of “P” and “C”.
Time to hook up our game-status check to the player click event.
After the player moves, check for victory.
If the player has not won yet, the computer should try to move.
We always need to check for a draw.
Add the game-status to the app-state.
Modify the tictactoe component to display different text messages according to the game-status.
Move the new game button up near the message text.
Great, winning was detected…
but oh no, the draw did not work.
The problem is that our computer-move throws an exception when there are no possible moves, let’s fix that.
Our game is working!
Let’s look over our code.
We have a function to create a new board.
We have a computer-move function.
A straight line detector, which is used to detect a winning condition.
We have a way to update the game status.
We have functions to draw a blank rectangle, a circle, and a cross.
Actually let’s make the circle a zero by setting its fill to none and setting the stroke-width.
This cross could be a little bit smaller.
The tictactoe component displays the game status, new game button, and the board.
Time to publish our web page.
Github pages is an easy way to get your code online.
We need to copy the index.html page up to the project directory.
And point the minified build target to the project directory as well.
“git init” our project.
“lein clean” to get rid of intermediate compiled files.
Add and commit all our files.
Make a gh-pages branch. Github publishes what it finds on this branch.
Navigate to username.github.io/tictactoe to see your page!
Share the link with your friends, so they can see what you built.
Let’s review what we did today.
We setup a new ClojureScript project.
When we changed the code, it loaded in the browser immediately.
We debugged some code by printing to the browser console.
`defonce` prevented `app-state` from changing when new code was loaded.
Reagent components are functions. They return HTML in a compact hiccup syntax.
Reagent components are nested inside a vector instead of a function call.
Most importantly, we are able to quickly and interactively build a game.
I am super excited about how easy it is to build interactive pages in ClojureScript. Please follow these steps through and publish your own version, I would love to see what you make, so drop a comment here with the link to your published version, and any questions that come up for you.
Until next time, keep coding.