npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tap.io

v0.0.0-40

Published

A Node.js platform for building distributed real-time systems with browser-based clients.

Downloads

61

Readme

News

10-2012: The example game is live. Head on over to http://bumbleskunk.tap.io.jit.su/

Browser-based Multiplayer Games with tap.io

Platform for building browser-based, distributed simulations/games. This is all possible thanks to the recent explosion of innovation in browser technology, including WebSockets, WebGL, and socket.io.

Tap.io systems use socket.io for communication. They can use anything (WebGL, canvas, jQuery) to render the game.

Running the Example

After cloning the repository, run sudo node example/snake/server/server.js. Then open up http://localhost/snake.html in a couple different windows. To use a port other than 80 (the reason for sudo), then open up example/snake/server/server.js and change the port there.

Working with tap.io

To get started with tap.io, there is an example game in example/snake. The project is structured into three subdirectories:

  1. shared: contains engine code shared by the client and server, the meat of the application
  2. client: contains client-only code, the render javascript and the html file that hosts the game
  3. server: contains server-only code, the socket.io app which also serves client code over http

Writing an Engine

In the example game, there is only one shared file, shared/snake-engine.js. It implements all the logic of the game. It defines an object called SnakeEngine that meets a specific engine interface required by the framework.

(window || exports).SnakeEngine = (function(){
  ...
})();

Note: The (window || exports) idiom is used to write javascript that can run on both server AND client. When it executes on a browser, window exists so window.SnakeEngine gets defined. When it executes in node.js, window does not exist so exports.SnakeEngine gets defined. Node.js developers will recognize that the exports object is the standard way to export functionality from a node module.

The SnakeEngine object exposes three important functions:

  1. update(state) function
  2. validate(state, event) function
  3. handle(state, event) function

Every game must have at least one object that implements these interfaces.

Engine.update(state)

The Engine.update(state) function takes an input/output parameter state that is the entire state of the game. It performs an in-place update of this state according to the game rules. Put a different way, the Engine.update(state) function takes an input state and applies one timestep of physics to it.

For example, if the game has the notion of gravity, the gravity physics would take place in the Engine.update(state) function. It might look like this

MyEngine.update = function(state){
  // some stuff...

  // gravity
  for(var i = 0; i < state.players.length; i++)
    if(state.players[i].falling == true)
      state.players[i].y -= 10;

  // more stuff...
};

Determinism! It is crucial that the results of this function be deterministic. For tap.io purposes, deterministic means that two different clients executing the update function with the same input state should arrive at the same output state. This does not mean it is impossible for a game to use pseudo-random numbers. An ARC4 RNG is included in tap.io whose state is folded into the overall game state, allowing clients to generate the same sequence of pseudo random numbers deterministically.

Engine.handle(state, event)

The handle function takes an input/output parameter state and an event to be applied to that state. It performs an in-place update of the state according to the nature of the event.

For example, user input is modeled with events. The Engine.handle(state, event) function might contain something like this:

MyEngine.handle = function(state, event){
  if(event.type == Events.CUSTOM){
    switch(event.data.which){
      case 'keyDown':
        state.players[event.data.sessionId].yVelocity += 1;
        break;
      case 'keyUp':
        state.players[event.data.sessionId].yVelocity -= 1;
        break;
    }
  }
}

This function must also be deterministic.

It's important to note the event type when handling it. Standard event types can be found in lib/shared/constants.js. The CUSTOM type is used for all custom events.

Engine.validate(state, event)

The validate function should throw an exception if event is invalid, given the state. This can be used, for example, to prevent users from modifying each others positions by checking to see if event.senderSessionId (secure field set by the server) matches event.data.playerId*.

This function must also be deterministic.

Writing a Server

tap.io does not provide an executable node.js application, only libraries for networking and graphics. It is up to API users to determine how to serve their game content (HTML, JS, CSS) to clients.

For an example of such an application, see the server for the snake game in server/snake-engine.js. It is essentially a node.js server that hosts client files over http and invokes the tap.io framework. Here are the steps taken (roughly):

var server = require('http').createServer(function(){ // serve ordinary http requests here });
server.listen(80);

// please include this line for safety
global.window = false;

// require framework libraries
var Network = require('../../../lib/server/network.js').Network,
    Game = require('../../../lib/server/game.js').Game,
    Engine = require('../../../lib/shared/engine.js').Engine,
    SnakeEngine = require('../shared/snake-engine.js').SnakeEngine; // <-- engine implementation!

// plug in game engine
Engine.plugins.push(SnakeEngine);

// start the networking
var socket = new Network(server);
socket.start();

// start the game manager
var game = new Game(socket);

// ...
// initialize game.state here
// ...

// start the game itself
game.start();

Writing a Client

Clients are browsers, and browsers need to see some HTML to get started. That means serving an HTML page with some javascript on it to bootstrap the game. The first thing the client needs is the socket.io javascript:

<script type="text/javascript" src="http://your.socket.server/socket.io/socket.io.js"></script>

The next thing is to include every script under lib/shared plus any custom client JS (this includes the shared game engine object discussed earlier). A few more lines will tie everything together:

window.Engine.plugins.push(window.SnakeEngine); // <-- engine implementation

var socket = window.io.connect(window.location.hostname),
    game = new window.Game(socket),
    renderer = new window.SnakeRenderer(), // <-- renderer implementation
    renderLoop = new window.RenderLoop(game, renderer);

To send an event such as user input, make the following call:

game.send(Constants.Events.CUSTOM, { ...data... });

The second argument to that call, the event data, will be available in the Engine.handle call under event.data.

Renderer

To get the game to actually show up in the browser, a renderer is required. The interface is an object with a render function. Here is an example from the snake project:

window.SnakeRenderer = (function(){

  var SnakeRenderer = function(){ };

  SnakeRenderer.prototype.render = function(playerSessionId, state){
    // draw some canvas/webgl
  };

  return SnakeRenderer;
  
})();

The render() function takes the playerSessionId of the user whose perspective to render and the game state.