MMOWAM: Massively Multiplayer Online Whack-A-Mole

Aug. 15, 2016
protect

This blog is based on my talk from NodePDX 2016. You can watch the full video of my session, or read my other writings on my personal blog.

Building a Casual Gaming MMO

Today I'm going to build a casual Massively Multiplayer Online game. Something that can have 50 or more people playing at the same time.  And I’m going to do it without writing any server side code. Yes, no server code at all. Your first question is probably: Why on earth would anyone want to do this? Why are you going to make an MMO without any server side code?

In my day job I work on realtime networks for a startup in San Francisco. Most of our customers use our DSN (Data Stream Network) for chat applications and vehicle tracking. This makes sense, people like to communicate and they want to know when the bus is coming. When the RFP came up for NodePDX (Portland's Premier JavaScript conference) one of my co-workers suggested I build a game  instead of the usual chat app.

The challenge is I'm not really a gamer anymore. I don't have the time to get deep into games, and certainly not the time to become competitive with guys who spend hours and hours honing their craft. Instead I like playing games with people around me.  I like board games and Mario Kart. Games anyone can pick up and enjoy, with no prior training. So my challenge was to create a game that's both realtime and can accommodate far more people than a board game could handle. What would it be like to play with 20 people at once, when you're all in the same room?  That led me to one of my all-time favorite boardwalk games: Whack-A-Mole.

Whack-A-Mole is awesome. It requires no learning; even little kids can play it. It does have some skill, but you can get pretty far knowing nothing at all.  And most importantly, it's fun whether are winning or losing. Everyone loves smashing cute cartoon creatures. (It also happens to be fairly easy to code. No 3D modeling required.) Now imagine if you could play Whack-A-Mole on a big screen TV or projector, but let everyone use their existing smartphone as their controller.  This is the dream.

While Whack-A-Mole is conceptually simple, actually getting a bunch of people to seamlessly connect to a game introduces it's own challenges.  To keep things simple I introduced a couple of requirements:

  • web only The game will be on the web, using only standard web tech (JS, CSS, webfonts, etc.) Trying to build a game for every app store would be a headache, as would asking everyone to go to their store, find the app, install it, then load it up. Instead I can just give everyone a URL to load. Boom, you're in!

  • no accounts I want a game that can run on a TV at a party. If someone wants to join in they should be able to do it in five seconds. No accounts or logins. No verifying email. No synchronizing worlds. Joining should be a simple and immediate action.

  • no server This is a free game so I don't want any server infrastructure for synchronizing games and managing state. I have a static web server for hosting the HTML, but that's it. Nothing to keep running and monitor. I don't have the resources or time for that. Synchronizing will be minimal and use a service.

User Interface

My game has two pages, one for the big screen and one for the player's phones.  The big screen is the dashboard. It shows when a round starts and who's winning.  The phone UI shows the game board. It tells the player when to start playing, then gives them a grid of holes to tap on. It currently shows the moles by turning a circle red. A better version would have had little cartoon moles, but I didn't have time before the conference. I'll add that later. BTW, the source to all of this is in my MMOWAM github repo.

Here’s what the dashboard looks like on a large screen, say a TV or a projector at a conference.

Dashboard Screenshot

Here’s what the client looks like on a phone.

Since you're reading this on a game developer website I assume you have your own favorite framework, so I won't go through all of the code for the actual game UI parts. It's all pretty straight forward web coding. I didn't use any game frameworks. The dashboard is plain HTML with CSS and a few property animations. The client uses bog standard HTML Canvas. They share a few common utility functions for event handling and animation in the common directory of the source repo.

The Networking

Now that we have a basic app, how do we get actually connect players together?  The magic part is a Data Stream Network. This is a service which runs high performance low-latency data stream channels over the global Internet. The data all goes through their network, not your own server, so we can call it from static HTML. It sends JSON messages, sort of like web-sockets, but at a massive scale. They handle keeping latency down, reliability up, etc. PubNub provides a high volume free tier so traffic won't be a problem. The DSN handles the basic mechanics of connecting players but we still have a few problems to solve:

  • There could be more than one TV showing a dashboard at a once. How can they each host their own game without having a real server?

  • How do players connect specifically to the dashboard they see in front of them, not some other random dashboard (alternatively, how do we prevent people who aren't actually at the event from joining the game)

  • How do we make sure players don't mistype the URL if it's a long with with game host information in it. Long URLs are error prone to type in on a phone.

  • If there aren't any accounts or usernames, then how do players know which person they are on the dashboard?

  • How do we synchronize the game boards in realtime without sending a lot of data, including starting each round at the exact same time so no one can cheat.

Let's take these one by one.

Channel Names for Multiple Games

First, we need some sense of a game world that players can join. Channels in the DSN each have unique names, so we can generate a random channel name and use that for our game world. If everyone connects to the same channel then they will be playing the same game. This also lets us encode the game world into a parameter of a link. Yes a fully shareable link that can go anywhere on the web (or email, or twitter, or messaging, or...) Of course typing a long random number by hand is annoying, so instead let's generate a random series of words instead.

For this game I connect to the PubNub network with my publish and subscribe keys, then  generate a channel name by randomly picking three values from a set of colors, flavors, and animals.


<script type="text/javascript" src="https://cdn.pubnub.com/pubnub-3.15.1.js"></script>
<script type=”text/javascript”>
pubnub = PUBNUB({
   publish_key:"pub-c-f68c149c-2149-48dc-aeaf-ee3c658cfb8a",
   subscribe_key:"sub-c-51b69c64-3269-11e6-9060-0619f8945a4f",
   uuid:'dashboard'
});








var state = {};




var COLORS = ["red","green","blue","orange","black","white","purple",'brown','gray'];
var FLAVORS = ["salty",'sweet','spicy','tangy','sour','bland'];
var ANIMALS = ['bear','cat','chipmunk','dog','wolf','fox','lion','tiger','elephant'];
state.channelName = pick(COLORS)+'-'+pick(FLAVORS) + '-' + pick(ANIMALS);

Now I can publish messages for the players to receive like this:


pubnub.publish({
   channel:state.channelName,
   message: { "type":"action",  "action":"start" }
});

A player can join the game by typing the channel name into a text box when they load up the page. Alternatively we could store the channel in the URL and tell them to just load up a particular URL.

Typing in Long URLs

This introduces a new problem, though. Suppose we want the player to type in a long URL like this:


http://joshondesign.com/p/apps/mmowam/player/?channel=gray-salty-elephant

One mistake and you get a 404 error, or worse you get to the page but to a slightly different channel and you don’t realize it. Suddenly you are playing the wrong game! But there's a better way: use a QR Code using this awesome JS library from Sangmin Shim.


// thanks Sangmin Shim
// https://github.com/davidshimjs/qrcodejs




new QRCode(document.getElementById("qrcode"), {
   width: 128,
   height: 128,
   colorDark : "#444400",
   colorLight : "#ffff00",
   text:BASE_URL+state.channelName
});

Using this amazing library in just a few lines of code you can generate the QR code in the browser with HTML Canvas. If we display this URL on the dashboard then someone can just scan it to immediately join the game. I love the immediacy of QR codes. Scan and you are ready to play. (now, if only Apple would recognize QR codes in their default camera app).

Now we can connect all the players, but how does one player know who they are on the dashboard if we don't have accounts or user names. How do we show who wins? When in doubt, randomly generate something.

Here's what it looks like:


var ICONS = ['elephant','giraffe','hippo','monkey','panda','parrot','penguin','pig','rabbit','snake'];
var ADJECTIVES = ['Saucy',"Sweet","Snarky",'Silly','Sassy'];




playerState.uuid = PubNub.uuid();
playerState.adjective = calculateAdjective();
playerState.icon = calculateAnimal();
img.src = "../images/sqr/"+playerState.icon+".png";




function calculateAdjective() {
   var key = 'mmowam-player-adjective';
   if(!sessionStorage.getItem(key)) sessionStorage.setItem(key, pick(ADJECTIVES));
   return sessionStorage.getItem(key);
}
//calculateAnimal() looks similar

Notice that I also generate a real UUID for distinguishing each player. This is great for the computer to really know who a message came from, just compare to previous UUIDs; but it's horrible for the human players. We don’t want to say: "Congratulations player 45823XD6. You’ve won!""  

In addition the UUID, the code above makes a random animal name, icon, and adjective to easily distinguish players. Later we can print this name at the top of the player's screen so they can just match the dashboard with what they see on their phone.

Also note that I saved the player info in session storage so if the user hits reload they still get the same player settings.

Filling in the Dashboard

Now we get to some real networking code. After the player's screen loads and generates unique info for them, we'll send this player state to the dashboard through the common channel. We could send this as regular messages but what happens if a player loads their screen just a tiny bit before the dashboard, or if it has to reload. The dashboard might miss a player message. We need some sort of persistent state.

In a traditional system this would be the job of the central server to handle identity, but we don’t want a central server. It turns out the DSN also has the ability to store a little bit of state.  Using the state() function we can add extra values we want to store like this.

JikGuard.com, a high-tech security service provider focusing on game protection and anti-cheat, is committed to helping game companies solve the problem of cheats and hacks, and providing deeply integrated encryption protection solutions for games.

Read More>>