James Williams
LinkedInMastodonGithub

Creating a Word Search with Raphael

Tags: HTML5 Gaming

In this post, we will cover the basic steps needed to create a word search game using Raphaël.js. The idea for this post came from a word search game that I orginally coded in Java using Pulpcore. I wanted to be able to run the app on my Chromebook but it needed to be rewritten, as the Java plugin isn't supported. Due to the low numbers of sprites drawn on the screen at any given time and their rather static nature, I decided to use SVG as the display technology. As we'll see later in this post, while Canvas is perfectly capable of rising to the task, SVG gives us a couple of built-in advantages.

Raphaël Basics

The Raphael function creates a SVG object that we can further build upon using JavaScript. We'll use this object in the coming chapters to draw rectangles, text, and paths. The most basic constructor for a Raphael object is listed in the snippet below. The default constructor adds the SVG node to the end of the page. Although we won't cover them here, there are options to draw inside a particular HTML div as well.

var paper = Raphael(50, 50, 640, 480);

This snippet in particular creates an object starting at (50,50) with a width of 640 pixels and a height of 480 pixels.

Wordsearch Basics

The gameplay for a word search is fairly simple. A list of words are hidden in typically a rectangular grid of characters. Words can be hidden vertically, horizontally, or diagonally. When one locates a word, it is circled or struck through in the grid and crossed out in the word list. Play continues until all words are found. Sometimes series of numbers will be hidden in puzzles as well. To create the game, we only need to know about a few different kinds of Raphaël functions: text, print, rect, and path. In the following sections, we'll cover them all in a bit more detail.

Drawing Text with Raphaël.js

Raphaël has two distinct commands for drawing text: text and print. Both methods were used to create the game. print slightly edges out text for its ability to use packaged fonts.

The `text` function

The text function shown in the snippet below, takes only a text string to dray and x and y positions.

title = paper.text(100, 100, "Spooky things");

Although it doesn't give you an option to do so when declaring the variable you are able to set the following attributes on a text object.

  • font (String)
  • font-family (String)
  • font-size (Number)
  • font-weight (String)
  • text-anchor ('start', 'middle', 'end')

The text function using the default font and font-size unless modified by the attributes listed above. You are also limited to the fonts installed on the client's machine.

The `print` function

Before you can use custom fonts with Raphaël, you'll need to convert them to a format that Raphaël can read. Cufòn is a project that converts TrueType and OpenType fonts to SVG fonts and VML fonts(IE8 and below). The web interface is fairly straightforward. Simply upload a font, select what styles to export and grab the JavaScript file it outputs. Google Web Fonts is a great source for free and open source fonts to use in your applications. Provided we have included a Droid Sans font on our path, listed below is the code to display a string using the font with a 28pt font size.

helloText = paper.print (x, y, "Hello", paper.getFont("Droid Sans"), 28)

At first glance, this might seem like a more concise way to do what the text function already does. The key difference is while the text function injects a text object into the SVG DOM, the print function creates a series of paths. A text object has the same attributes for all its characters whereas a the output of print is a group of paths each capable of its own attributes. Paths have a greater level of flexibility. They can b e morphed intoone another or transformed in many different ways. Each character can be referenced individually. If we wanted to change the color of the "o" to red, we would execute:

helloText[4].attr({fill:'red'})

Paths

While I won't go into the minutae here, at the most basic level, paths are lists of instructions to draw objects. You can draw lines and curves to create more complex objects. For the purposes of the word search game, we'll be using them in a very constrained manner. After successfully locating a word a path will cross it out in the grid. The following snippet shows the CoffeeScript code to draw a line from (x1,y1) to (x2,y2).

pathData = "M#{x1} #{y1} L#{x2} #{y2}"

You can find list of the possible path instructions in my book or on the W3C reference site.

Putting it all together

So far, we have talked about the elements that allow up to draw text and paths on the screen. One missing element is interaction. Every element in Raphaël can respond to mouse events. mouseout, mouseover, click, and pretty much any other mouse-based event you'd expect to be supported is built-in. In the word search game, I used events to indicate which letters were being selected and where the mouse was hovering. The snippet below shows the mouseDown function to start tracking and change the color of the letters when the mouse is down. When the mouse is released, it checks to see if the accumulated letters are in the word list and clears the states from the letters in the grid.

downHandler = (event) ->
        #start tracking position 
        state = game.isSelecting

        if state is no
            game.tempWord = new LetterSet()
            game.isSelecting = yes 
            game.tempWord.push self   
            self.text.attr {fill: "red"}
        else 
            game.isSelecting = no
            game.findWord()
            game.clearStates()

For a mouse event to work, it depends on the event firing while you are in the confines of the element. For some fonts, it's very difficult to do. We can increase the area around the letter by drawing another object and having its events be tied to the letter. I chose to use rectangle although any other shape or path will serve the same purpose. The snippet below shows the creation of a rectangle and adding a mousedown function to it.

rect = paper.rect (x, x, sideLength, sideLength)

rect.mouseDown(downHandler)

Source Code

Now that you've learned how it works, download the source code here.