PROJECTS BY LAUREN NISHIZAKI

Trois: a game

2018.09.14
Screenshots taken at the end of a game. From left to right: game board with tiles; game controls; statistics regarding game play.

I’d like to introduce you to my first React application and my first ever game.
See it live / View the source code

Background

Trois is a React.js implementation and adaptation of Threes!, the iOS puzzle game. Gameplay starts with 6 tiles randomly distributed across a 4x4 grid. Clicking the arrow buttons in the dashboard slides all tiles across the board by one spot. Pushing tiles together makes larger numbers, but be careful! After every move, a new tile with a random value gets added to the board.

Trois locally stores the history of the current game, which allows a player to undo their last move (and the move before that, and the move before that, until the game is in its start configuration). Restarting the game clears the game history and randomly generates a new start configuration.

Tile movements are animated using CSS classes and keyframes. Keyboard shortcuts are implemented for the directional and undo buttons, and the player can toggle the visibility of letter mappings. Instructions and gameplay statistics can be accessed by expanding the appropriate tabs. Statistical information is displayed using programmatically generated SVG graphs.

What is React?

React is a JavaScript library for building applications and user interfaces. The official React documentation has a good writeup on how to think about React applications, but I’ve summarized some of what I consider the key points (and how they apply to my game) below.

React components

A core principle of React is its components. Components are functions that return JSX (which get translated into elements in an HTML webpage). Following the React way of thinking, a single application is broken down into components, which are in turn comprised of more components. Each component can receive information (props) from its parent (the entity that invokes it), and can use that information to customize how it gets rendered. A component can also store state: updatable and persistent values that are tied to the component. A component will usually output JSX, which gets translated into DOM nodes.

In Trois, I use a Game component to track the game’s history, possible moves remaining, and user input; all of these values are stored as state within this component. Game renders a Board component and passes Board the information needed to draw the game board. This includes the current and previous game tile locations, the user’s move direction, whether the previous user move was “Undo”, and whether the game is over. Board determines the types of animations needed for each tile, and tells each of its children (which are GameTile elements) the values to display. If the game is over, Board tells all GameTile components to display the score. A GameTile component creates a DOM element for each requested layer of the animation and assigns each layer a CSS class name.

an in-progress game of Trois
Screenshot of a game in progress, with the game board on the left and the dashboard on the right.

A good practice when creating components is to try and make them reusable, thus minimizing duplicate logic. For example, I use the GameTile component to draw the tiles in both the board grid and the “Next tile” dashboard section. Because I use the same component, the components are automatically drawn with the same colors; tiles with a “1” have an orange background, tiles with a “2” have a blue background, and all other tiles are white with a dark grey border.

The DOM and JSX

The DOM (Document Object Model) is the representation of an HTML document that a web browser creates when it loads a page. JavaScript code within the page can modify that representation, and nearly all modern web apps rely on this ability.

JSX (JavaScript XML) is a lightweight DOM-like data language. React components return JSX, and React converts that JSX into real DOM nodes.

React looks at the JSX from each component and figures out the minimum set of changes to make to the DOM in order to bring the two into sync. If React components returned DOM nodes directly, they would have to recreate those DOM nodes from scratch each time they were called. This would make React slower, because creating DOM nodes is expensive (in compute power) compared to creating JSX.

JSX-to-HTML example

This is a snippet of JSX that I use to draw the 4 arrow buttons on the Trois dashboard (excuse all the parenthesis and curly braces, they’re necessary and are more readable in a color-coded text editor):

const directions = [
  { name: "up", featherName: "arrow-up", shortcut: "W", canMove: true },
  { name: "right", featherName: "arrow-right", shortcut: "D", canMove: true },
  { name: "down", featherName: "arrow-down", shortcut: "S", canMove: false },
  { name: "left", featherName: "arrow-left", shortcut: "A", canMove: true }
];

<div className='GameControl'>
  {directions.map(dir => (
    <button
      className={dir.name}
      onClick={() => handleClick(dir.name)}
      disabled={!dir.canMove}
      title={`Move tiles ${dir.name}`}
      key={dir}
    >
      <Icon name={dir.featherName}/>
      <KeyboardShortcut shortcut={dir.shortcut}/>
    </button>
  ))}
</div>

Here is the HTML code that gets generated from the JSX for one of the buttons:

<div class="GameControl">
  <button class="up" title="Move tiles up">
    <svg class="Icon">
      <use xlink:href="./static/feather-sprite.svg#arrow-up"></use>
    </svg>
    <span class="KeyboardShortcut">W</span>
  </button>

  [[ ... other buttons ... ]]

</div>

Some notes on the JSX code:

Offline functionality

When a React project is prepared for web deployment, all of the JavaScript gets bundled up into a couple of files. This bundle of code (typically minified so it requires less bandwidth to download) is served up to visitors of the site.

Trois is designed to run offline after files are downloaded: the game’s JavaScript bundle contains all game logic, instructions, and statistics needed to play the game. Once the page is loaded, no further requests are made to the web server.

Try it for yourself: open up the game then turn off Wi-Fi and/or disconnect your ethernet cable. You will still be able to play the entire game (including game restart). Just don’t refresh or reload the page: doing so will issue a request to the server for the web page, but since you are offline, this will fail.

Traditional apps rely heavily on a web server to construct pages for the browser. Typically, these pages are HTML documents that includes both the page content and information about how to display it. But with React, it’s common to define all of the “presentation logic” of the app in JavaScript, and rely on the server only to deliver the content, usually through an API.

For example, in order to render an SVG chart, a React app might request a dataset in JSON format from the server. The server returns the JSON data as requested, but doesn’t have any idea how to turn that data into a chart. This responsibility is left to the client, which implements the chart “presentation logic” in JavaScript.

What is an API?

An API (Application Programming Interface) is an interface that allows a computer program to communicate with other programs or users. Here are a few kinds of computer interfaces you may have run across:

Acronym Name Definition
API Application Programming Interface Interface that one program uses to interact with another program
CLI Command-Line Interface Command-line (text-based) interface that a human user uses to interact with a program (typically using a keyboard)
GUI Graphical User Interface Graphical interface that a human user uses to interact with a program (typically using a mouse and a keyboard)

React libraries

There are many packages and libraries that provide useful functionality for JavaScript programs. These packages can be as varied as capturing user keyboard presses (keybindings), compiling Markdown text to HTML, and minifying JavaScript files to allow for fast downloads. Many of these modules can be installed via npm (Node Package Manager), a package manager for JavaScript code.

npm allows a developer to install (usually via the command line) a particular set of package dependencies and versions that is unique and local to a project. For example, Trois uses version 1.2.0 of the package gh-pages. This local package installation will not affect or corrupt a different project and development environment that installs gh-pages version 1.1.0, even if both projects are being developed on the same computer.

All user-installed packages are listed in a file called package.json that lives at a project’s root directory. A second file, package-lock.json, is a list of all packages used by the project, and all of the package dependencies. For example, the package.json file for Trois contains the line "gh-pages": "^1.2.0" (version 1.2.0 or higher), but the generated package-lock.json specifies the exact software version of gh-pages as well as the exact software versions of all packages that are required for gh-pages to run.

Implementation notes

I learned the basics of React by completing the official React tutorial. The tutorial walked through the steps of implementing tic-tac-toe, which inspired me to try building my own more complicated game.

I built this game in the development environment set up by Create React App.

Libraries and tools

Feather is a library of SVG icons. Trois uses arrows and the open/closed eyes from this library, imported using an SVG sprite sheet. An SVG sprite sheet is a single SVG file that contains multiple icons or sprites. Every icon can be individually addressed and included into the webpage.

gh-pages is a package that enables easy deployment of a Github project to Github Pages.

Lodash is a JavaScript library that adds math functions; I use it to import functions that act on numbers in a 2D array, such as finding the maximum number in an array.

Mousetrap is a JavaScript library that adds keybindings to the project. I use it to bind user keyboard input (specifically, the “W”, “A”, “S”, “D”, and “U” keys) to the appropriate functions that update game state.

React Developer Tools is a Chrome extension that adds React debugging tools to the web inspector environment. It is incredibly useful for debugging component state and props (values passed to a child component from its parent).

SASS is a powerful CSS extension that gives a developer tools to organize and eliminate redundancy in style sheets. To compile SASS (*.scss) files into CSS (*.css) files, I added the node-sass-chokidar package and followed the Create React App instructions. Only CSS style sheets can be served with a website.

Tile animations

I used the FLIP animation technique to animate tiles on the board. FLIP stands for “First Last Invert Play”, and it deals with transitioning an object’s appearance in response to a state change.

This animation technique allows for instantaneous update of an object’s state. In other words, the duration of an animation does not delay game state updates. For example, in Trois, when a user clicks an arrow key, the position of all tiles in game memory immediately updates to reflect the post-move location. However, using CSS, I make the tiles appear to be in their pre-input position, and then animate a slide to the new position. The animation (which takes 0.2 seconds to run) does not affect any aspect of game memory. Therefore, if the user clicks a new arrow key while the previous animation is still running, that action does not corrupt or confuse the state of the game. The new position is saved, a new animation is applied, and the old animation is terminated; the only indication of this termination may be a small jump in the tiles’ positions.

When updating game state, I assign tiles one of four classifications:

When a user makes a move, each grid on the board falls into one of the following situations, and its appearance is implemented using a combination of tile types:

Situation Tile combination
A tile slides into place move-X
2 tiles combine: the sliding tile overlaps the existing tile, and after they overlap, a tile with the combined value appears no-animate, move-X, fade-in (with a delay)
A tile is added to the board fade-in
A tile is removed from the board (when the user clicks “Undo”) fade-out
A tile does not move no-animate

If no tile moves into a game board spot and there is no stationary tile at that spot, then no tile gets drawn.

Get a favicon to show up (almost) everywhere

The favicon is the little icon that is associated with a website. It gets displayed in Safari’s bookmarks and on Chrome’s tabs, and sometimes appears in the browser’s nav bar (and in various places on other browsers).

favicon with the number 3

To generate the favicon, I first created an SVG icon using Inkscape. I exported this square SVG to PNG at two different resolutions: 32x32 and 64x64 pixels. To combine the two images into one file with extension .ico, I used the command line tool ImageMagick.

Safari also uses a mask-icon, which takes the form of a single-color SVG. This icon is displayed if the tab is pinned to the browser’s nav bar and appears on the touch bar of new MacBooks.

There are still a couple of bugs to work out regarding the favicon (for example, no image displays on Safari’s favorites page), but this is an issue that I will iron out at a later time. For reference, Real Favicon Generator knows what it’s doing and works; I just want to know WHY everything works, so I’ll do it all manually.

Website aesthetics

I designed and coded every part of the website and its appearance.

Trois does not use externally-loaded fonts; it only uses fonts available on the visitor’s machine. The website requests Century Gothic, but if that font isn’t available, the website resorts to one of the web safe fonts.

In a modern browser, the main page layout is implemented using CSS grid elements; this allowed me to specify the exact layout of elements on different sized screens or windows. I included graceful fallbacks in case CSS grid is unavailable.

Screenshot of the game on a device without CSS grid. Notice that the game is still playable, even though it is not as polished as in a modern browser.

Hosting details

The site is hosted using GitHub Pages, a free solution for hosting static sites. Content for the site is served directly from the Trois GitHub project, using a branch (gh-pages) that contains the compiled site files. The project’s uncompiled source code is on the master branch.

Concluding thought

Give Trois a play!

When in doubt, remember: 1+2=3, 3+3=6, … How high can you go?