Frogger in Hyperapp

Gareth Dunne
Hyperapp
Published in
9 min readApr 25, 2018

--

Hyperapp allows a developer to use all of the functional programming-based concepts from popular frameworks, such as React. It enables the implementation of a similar pattern to Redux, while including fully implemented lifecycle events.

The Hyperapp flow and paradigm has received a lot of coverage (see this great article here).

But how can it be used to provide a solution to an application’s requirements?

In this post, in order to find out, I will recreate the beloved arcade classic, Frogger. I thought this would be an interesting vehicle to map out the requirements, and demonstrate how to achieve them, using Hyperapp.

Before we start, go ahead and play the game online or browse the source code on GitHub.

It’s important to note that while Hyperapp is an ultra lightweight library, its explicit pattern allows the developer to create versatile applications and projects with no ambiguity. The pattern it uses serves as a single state of truth for an application.

One of the benefits of having a store that follows this pattern, is that it makes it much easier to query, debug and serialize our overall application state.

This feeds into a few objectives we will need to complete for this game:

  • Tracking the position of our movable avatar.
  • Tracking the position of our enemy sprites.
  • Detecting when a collision has taken place.
  • Updating the position of our avatar on keypress.
  • Deducting lives upon collision.
  • Navigating to Lose Screen via Hyperapp’s Router.

But before we begin, we need to take a step back and ask — what are the advantages of building a game using Hyperapp’s architecture?

Well for one thing, we are taking the logic and data properties of our game and storing them all in our state. This means that every piece of utility and information in our game will be described by our state and will not be held in our view.

This is in stark contrast to how a game may have previously been made in vanilla JavaScript. Where everything was directly manipulated through the DOM.

Every change in our application will take place as a result of our actions, which in turn will manipulate our state and from this will update our view.

This results in a data flow that is not only easy to follow, but allows a clean separation of concerns. It also ensures that only the necessary components are re-rendered.

Define State

So, when planning an application like this, we must keep take all of the possible changes that might occur into consideration. Once we have a good grasp of what our application will do, we can map this information in our state.js.

All of these properties represent what needs to be described in our application. For example, we specify that there will be 14 enemies based on our grid size. This list is flexible and can change over time.

Layout

So lets think about the layout here. What will the structure looks like? Well, thankfully Frogger doesn’t have too many different screens that it shows its users.

So we can easily divide up the components we’ll need:

  • Menu Screen
  • Home Screen
  • Game Screen
  • Lose Screen

Nothing too complicated here, each one of these can represent a component. They will interact with each other via Hyperapp’s state and actions.

In order to start this application, we will be using a boilerplate. I will use HyperappOne so that all of our dependencies and compilers for Hyperapp already configured.

HyperappOne has a very strict ES Linter for cleaning up your code if you would prefer a setup without this you can use its less strict HyperappOne Parcel.

In my app.css file, I’m going to import a nice font that resembles the original Frogger:

The scope of the application may not warrant using Hyperapp’s Router but it is a great package for navigation and relatively simple to implement so I thought it would be worth including here.

Simply install it using the command:

npm i @hyperapp/router

And import it into our MainScreen.js file:

MainScreen.js

This will be the first screen the user sees so we will import all our component’s here.

Notice the props coming into this component:

The first object represents different properties from our state, whereas the second object represents a reference to an action that we’ll use to start our game.

This example demonstrates how we don’t have to use the whole state in the component, but rather can use a partial state to pick our chosen properties.

We are sending down values of score and lives to our Header component.

These values will flow down into that component and will be displayed in our header:

This is probably the most straightforward example in which no actions are required from us. Every update to the score and lives state values will also update in the Header component through its props.

Game Component

Of course, this wouldn’t be a frontend framework article without some competitive undertones. So in our Game.js component, the enemy sprites will represent other frontend frameworks.

So our Game.js component in full looks like this:

We are defining our enemy loop and grid loop here. These are based on properties from our state.

We are also manipulating the styling JSX position properties bottom and marginLeft. These properties are constantly getting updated by our moveEnemies() action, which I will demonstrate later.

Moving the Sprites

Lets think about defining some actions here. Our Frog avatar is going have to navigate the x and y axis in fixed increments.

Something along the lines of :

  • Move up
  • Move down
  • Move left
  • Move right

We need to add an event listener in our application to listen for the user input, specifically when they are pressing an arrows key to move the Hyperapp sprite.

We want to avoid creating this listener in our actions, instead we will use interop.

Lets navigate to the entry point of our application:

This is the where our actions, views and state of the application are put together.

From here, we want to use the idea of interoperability to invoke actions on our application. This might sound more complicated then it really is. In this case, the variable interop acts as reference to our application as a whole including all its wired actions.

From here, we can access specify what actions to run when the arrow key is pressed.

Since we are using multiple routes in our application, it is important to remove this listener in components that its not being used. Left in the DOM, the listener would be inefficient and could cause us problems.

Our action handleKeyDown will look something like this:

Depending on the arrow key we press when we are incrementing or decrementing, our x and y state values will determine the position of our Hyperapp frog avatar.

We are increasing the score state by 10 every time the avatar moves up one position on the grid.

Any change of this state will be noticed by the Hyperapp compiler and it will flow down the application and refresh our avatars position.

Always remember that you will need to make our new state immutable. This is one of the most important factors in defining a single source of truth throughout an application.

This updated information will make its way down our component hierarchy to our Game.js Component and will move the Hyperapp sprite.

I have chosen to manipulate our avatar via JSX styling props. However, we could have just as easily achieved this by drawing on the canvas and moving the spites that way.

I thought that using this would be more akin to how you would manipulate styles using Hyperapp in a traditional web application.

In order for us to move the framework sprites when we enter the game screen, we must under what lifecycle event we want to use.

Moving Enemies

The main focus of the application is to move enemies across the screen. This can be achieved in a few steps.

First we need to have a Hyperapp event that initiates the game to start.

So in our Game.js component we are using oncreate() lifecycle event within the parent div of the component which will be fired when you enter the screen:

This will run the startGame() action which looks like this:

This is what a standard action in Hyperapp looks like. It tends to pass in state and actions for you to access and perform your logic. This logic will be refreshed in parts of the application that are affected by the changes in state.

As this action will also be called after every restart gameOver and lives are set to default values.

We then want to set the default positions of our enemy sprites using our setPosition() action. An equal distribution down the rows of our y axis. And on our x axis, half the enemy sprites starting on the left side of the screen and the other half on the right.

We then take this altered information and apply return it in our state.

Remember, the objective of our actions is to always return a new state.

At this point the default position are set. So we run our game loop:

This is a standard JavaScript game loop that will continuously update the positions of our enemy sprites.

The main logic of this takes place in our moveEnemies():

Let me break down what’s happening in this section for you:

We are looping through all our enemies in the state while incrementing the timestepAccumlator each time. We create a change variable that will randomly generate a number that we want to increase or decrease the position of our enemy sprites.

We are then using ternary operators to check if the sprite has passed the borders of the width of our window. If it has, we reset its value to either the left or right hand of the screen, otherwise we apply the acceleration change.

Our hasCollided function sends in each enemy with the current position of our Hyperapp frog avatar.

This will compare both of these positions against each other to see if there has been a match. It will indicate whether the positions are true or false as well as offsetting those positions by width and height of both the enemy and frog position in order ascertain complete accuracy of when to trigger a collision.

Game Over

Once we have collided more than 3 times, we want to update our gameOver state to true.

Once changed this information will flow back down to the MainScreen.js component and change our current Route to the LoseScreen via HyperApp Router.

As you can see, from this ternary operator our route will change to the LoseScreen if gameOver is equal to true.

In our LoseScreen.js, we give the user options to restart our game which will invoke our game restart actions.

LoseScreen.js

As is inherent in Hyperapp’s redux-like pattern, the single state is the single source of truth and so this will always keep our application up to date. So when we navigate back to the game screen the state of our game will be reset.

Conclusion

Overall, the advantages are clear when tracking everything that is likely to move in your DOM. Hyperapp provides us with a fast and lightweight solution for this.

The objectives for Frogger are easily achievable but are also versatile enough to be applied to a variety of frontend problems with the use of Hyperapp.

The recent announcement of Hyperapp 2.0 holds promise of addressing the few issues that might arise in an application such as this, gold-plating the process.

If we had access to Hyperapp 2.0 now we would have the separation of interoperability. We could then use the new subscriptions API, to external input, mousemove/down/up, keypress, etc.

This mean we won’t have to use interop in the entry point of the application which admittedly caused me some initial confusion.

Further improvements will also exist in Hyperapp 2.0:

  • Easy to test — By making actions and effects pure, testing will be a piece of cake.
  • All Things Dynamic — First class support for code splitting and dynamically loading actions and views using import().
  • Cleaner Action API — Eliminate the confusing concept of “wired actions”. The new actions will be regular, unwired & untapped JavaScript functions.
  • Subscriptions — Introduce a subscriptions API inspired by Elm.
  • Types — Make Hyperapp typing simpler and easier to get right.

I recommend you check it out here.

My name is Gareth, I’m a web developer from Dublin and I run JSdiaries, a JavaScript blog and business.

--

--