Building a Game With TypeScript. Input System 3/3

Greg Solo
Greg Solo
Jan 12 · 8 min read

Chapter V in the series of tutorials on how to build a game from scratch with TypeScript and native browser APIs

Image for post
Image for post
Computer vector created by catalyststuff — www.freepik.com

Welcome to the final installment of this Chapter! In the last article, we introduced OnclickComponent, an abstract class that serves as a “bridge” between notifier (GameInputComponent) and actual receivers. Today we are going to add one such receiver to our happy family and, of course, cover GameInputComponent with unit tests.

In Chapter V “Input system”, we are going to build a simple system that will give the Player an opportunity to communicate with the game. You can find other Chapters of this series here:

Feel free to switch to the input-2 branch of the repository. It contains the working result of the previous posts and is a great starting point for this one.

Table of Contents

  1. Introduction
  2. Introducing Grid Onclick Component
  3. Testing Grid
  4. Testing Grid Onclick Component
  5. Testing Game Input Component
  6. Injecting dependencies into the Game
  7. Conclusion

Introduction

Our goal in this Chapter is to verify that our Input System works. We agreed that the way we are doing this is by temporarily marking Node as “active” when the Player clicks it. Now, when we have a robust notification system in place, we can introduce something like NodeOnclickComponent which would extend OnclickComponent and actually set the IsActive flag. But we can also do the same one level up, introducing GridOnclickComponent.

There are 2 reasons why I want to go this way. First, Grid has access to all Nodes at once, which gives me a way to “deactivate” other Nodes but the one currently clicked. Second, our GameInputComponent notifies only direct children of the Game, which are, at this moment only Grid and two Fleets. Of course, we could proxy the event down to Node and Ship if we want to, but we don’t really have to at this point. Let’s keep things simple. Fortunately, extending this functionality should be a trivial task.

Introducing Grid Onclick Component

With that in mind, we start by adding the very first component for Grid entity:

We extend OnclickComponent which asks us to implement Awake and Update (per IComponent requirement) but also ClickOn (per OnclickComponent requirement). Before we go any further, let’s make sure we have all barrels in place:

If you recall, in the very first article of this Chapter we added the method Occupies to the Node entity. This method checks if the provided point is indeed within the boundaries of this Node. We can utilize this check again and set the IsActive flag of the Node:

And then attach the component to the Grid to make it work:

And that’s it! If you start your game with npm start and open a browser, you should be able to turn Nodes on and off by clicking on them:

Image for post
Image for post

That was easy. But we are not done yet, we have a few tests to write to cement our victory.

Testing Grid

This one is easy and should be familiar. We did similar testing a dozen times by now. I’ll start by creating a mock:

And update respective barrel file:

Now we can update all tests that used to instantiate Griddirectly. First, Fleet mock factory:

And then, spec itself:

Nice! And we landed right in the perfect place. Let’s improve testing coverage for Grid by verifying all its components being awakened and updated:

All this should look familiar. At this point, our code should successfully compile with npm start and all tests should pass with npm t:

Image for post
Image for post

Testing Grid Onclick Component

Awesome! Our next stop is the test for GridOnclickComponent. First thing we should do is prepare the spec:

Nothing tricky here: we instantiate the component and support it with the entity. We also make sure Grid is awakened and has done all the necessary preps.

Image for post
Image for post
Business vector created by vectorjuice — www.freepik.com

Now, having all mock data in place, we can simply expect that very first Node becomes active when we click within its boundaries:

Again, we should be able to successfully compile with npm start and pass all tests with npm t:

Image for post
Image for post

Testing Game Input Component

This is a cherry on top of our cake for today. We’ve been delaying testing GameInputComponent for a while but no longer! I'll start with introducing spec as usual:

Just like with GridOnclickComponent, I instantiated the entity, component itself, and attached them to each other. The component does not have much responsibility, it merely “handles click”:

But how exactly we can test that? HandleClick is a private method, and we cannot just invoke it. And that’s legit because how exactly the component performs its duties is its implementation details, which hardly concerns us. What we care about is that every child entity of Game that has any OnclickComponent attached gets notified when we click within the canvas.

Image for post
Image for post
Folder vector created by stories — www.freepik.com

We can easily fake native mouse click. We can also effortlessly mock Canvas to return a fake point for us:

But how can we verify that the child’s OnclickComponent was indeed called? Well, we know that Grid has this component attached. We also know that it’s added as a first child to the Game. We can gamble on that and request the very first item from Game.Entities list:

And this will work! As soon as you run npm t you should see tests pass:

Image for post
Image for post

But there is a catch. We depend on the order of children within Game. This is not a concern of this test at all. Moreover, if for some reason, we change the order of items, our test will fail. And this is a problem since we did not change any logic that is relevant to this test. In other words, we now get ourselves false negatives.

There are many ways we can go about it. I would prefer direct access to a child I expect to have the necessary component (in this case, Grid) and build my test around it. However, Game does not provide us with such functionality. We can solve this by utilizing dependency injection.

DI is an old friend of testing. It is one of the easiest and powerful ways to improve the testability of your code. I won’t spend time explaining in detail how it works. Gladly, there are a plethora of articles, books, podcasts, and videos on this subject. I will continue under the assumption that you, dear reader, know and understand how DI works.

Injecting dependencies into Game

Dependencies we are concerned with in this case are children of Game : Grid and Fleet. To make them “injectable”, I move them from Awake and mark as a required parameters of constructor instead:

This will require updating main file:

As well as game.mock:

Now, back in spec we can directly provide reference to the Grid mock and spy on it:

And it works! Your code should compile with npm start and all tests should pass with npm t now. But there is another catch…

Our tests pass as long as Grid has GridOnclickComponent. This assumption is again out of the hands of this particular test. At any time we can remove it from Grid and get ourselves another false negatives.

Image for post
Image for post
Man vector created by pch.vector — www.freepik.com

To solve this, we have to ensure mocked Grid has some OnclickComponent. We cannot rely on GridOnclickComponent so we should define the fake one:

Nicely done! Our test now covers only what is supposed to. At this point, our code should successfully compile with npm start and all tests should pass with npm t:

Image for post
Image for post

You can find the complete source code of this post in the input-3 branch of the repository.

Conclusion

This concludes this short Chapter. We learned how we can populate click events from DOM body down to the specific Node and make them active. We introduced an abstract OnclickComponent that gives us the flexibility to delegate an event without the necessity to hold information about specific responders. And of course, we covered all new functionality with proper tests.

The Next Chapter is all about movement. We are going to think deeply about the mechanics of our game. The main question for us is: “How can Players move their Ships?” I am planning to publish it in February 2021, looking forward to meeting you then!

I would really love to hear your thoughts! If you have any comments, suggestions, questions, or any other feedback, don’t hesitate to send me a private message or leave a comment below! If you enjoy this series, please share it with others. It really helps me keep working on it. Thank you for reading, and I’ll see you next time!

This is Chapter N in the series of tutorials “Building a game with TypeScript”. Other Chapters are available here:

The Startup

Medium's largest active publication, followed by +755K people. Follow to join our community.

Greg Solo

Written by

Greg Solo

Software Engineer with about 15 years of experience in front- and back-end web development, and I know how to cook this dish!

The Startup

Medium's largest active publication, followed by +755K people. Follow to join our community.

Greg Solo

Written by

Greg Solo

Software Engineer with about 15 years of experience in front- and back-end web development, and I know how to cook this dish!

The Startup

Medium's largest active publication, followed by +755K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store