Last active
August 29, 2015 14:07
-
-
Save rudiedirkx/d99d885744ac886cd4ab to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<title>Multiplayer</title> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=300, height=500, initial-scale=1" /> | |
<style> | |
* { box-sizing: border-box; font-size: 20px; font-family: arial; margin: 0; padding: 0; } | |
html { height: 100%; border: solid 20px black; } | |
html.active { border-color: green; } | |
html.error { border-color: red; } | |
html, body { | |
overflow: hidden; | |
} | |
body { | |
height: 100%; | |
position: relative; | |
padding: 20px; | |
-webkit-user-select: none; | |
} | |
p { | |
margin: 0 0 20px 0; | |
} | |
.client { | |
position: absolute; | |
width: 30px; | |
height: 30px; | |
background: none #ddd; | |
z-index: 2; | |
margin-left: -15px; | |
margin-top: -15px; | |
} | |
.client.me-client { | |
border: solid 5px #000; | |
z-index: 3; | |
cursor: pointer; | |
} | |
</style> | |
<script> | |
function _log() { | |
return console.log.apply(console, arguments); | |
} | |
(function(P) { | |
P.on = P.addEventListener; | |
P.sendJSON = function(data, callback) { | |
return this.send(JSON.stringify(data), callback); | |
}; | |
P.sendCmd = function(cmd, data) { | |
data || (data = {}); | |
data.cmd = cmd; | |
return this.sendJSON(data); | |
}; | |
})(WebSocket.prototype); | |
</script> | |
</head> | |
<body onload="init()"> | |
<!-- <form onsubmit="socket.sendCmd('eval', {code: this.elements.code.value}); return false"> | |
<p>Eval:<br><textarea name="code" rows="5" style="width: 100%">this.wsServer.connections.forEach(function(client) { | |
_log(client.data); | |
});</textarea></p> | |
<p><button>Run</button></p> | |
</form> --> | |
<div id="clients"></div> | |
<script> | |
window.onerror = function(e) { | |
alert(e); | |
}; | |
var html = document.documentElement, | |
body = document.body, | |
clients = document.querySelector('#clients'), | |
bsize = [0, 0], | |
boffset = [0, 0]; | |
function createClient(data, me) { | |
el = document.createElement('div'); | |
el.dataset.id = data.id; | |
el.textContent = ''; | |
el.className = 'client'; | |
me && (el.className += ' me-client'); | |
el.style.backgroundColor = data.color; | |
el.style.left = data.coords[0] + '%'; | |
el.style.top = data.coords[1] + '%'; | |
clients.appendChild(el); | |
return el; | |
} | |
function updateClientCoords(x, y, relative) { | |
var el = document.querySelector('.client.me-client'); | |
if ( relative ) { | |
x = parseInt(el.style.left) + x; | |
y = parseInt(el.style.top) + y; | |
} | |
x = x / bsize[0] * 100; | |
y = y / bsize[1] * 100; | |
el.style.left = x + '%'; | |
el.style.top = y + '%'; | |
socket.sendCmd('move', { | |
coords: [x, y], | |
}); | |
} | |
var commands = { | |
// The server accepts me and tells me where to start | |
join: function(msg) { | |
if ( !msg.coords ) { | |
throw "Missing 'coords' in message."; | |
} | |
var el = document.querySelector('.client.me-client'); | |
if ( el ) { | |
// Server sent 'join' more than once = bad, but accept new coords | |
el.style.left = msg.coords[0] + '%'; | |
el.style.top = msg.coords[1] + '%'; | |
} | |
else { | |
createClient(msg, true); | |
msg.others.forEach(function(other) { | |
createClient(other); | |
}); | |
console.timeEnd('Joined game.'); | |
} | |
}, | |
// Someone else joined the game | |
party: function(msg) { | |
createClient(msg); | |
}, | |
// Someone left the game | |
unparty: function(msg) { | |
var client = document.querySelector('.client[data-id="' + msg.id + '"'); | |
client.style.display = 'none'; | |
}, | |
// Someone moved | |
partymove: function(msg) { | |
var client = document.querySelector('.client[data-id="' + msg.id + '"'); | |
client.style.left = msg.coords[0] + '%'; | |
client.style.top = msg.coords[1] + '%'; | |
}, | |
}; | |
var url = location.protocol.replace('http', 'ws') + "//" + location.hostname + ":8084", | |
socket; | |
function init() { | |
var cr = body.getBoundingClientRect(); | |
boffset[0] = cr.left; | |
boffset[1] = cr.top; | |
bsize[0] = cr.width; | |
bsize[1] = cr.height; | |
console.time('Websocket initialized.'); | |
console.time('Joined game.'); | |
_log('initializing socket...'); | |
socket = new WebSocket(url); | |
socket.on("open", function(e, name) { | |
_log('on open'); | |
html.classList.add('active'); | |
console.timeEnd('Websocket initialized.'); | |
var tracking = false; | |
body.addEventListener('mousedown', function(e) { | |
if ( e.target.classList.contains('me-client') ) { | |
tracking = true; | |
} | |
}); | |
body.addEventListener('mousemove', function(e) { | |
if ( tracking ) { | |
updateClientCoords(e.x - boffset[0], e.y - boffset[1]); | |
} | |
}); | |
body.addEventListener('mouseup', function(e) { | |
tracking = false; | |
}); | |
body.addEventListener('touchstart', function(e) { | |
e.preventDefault(); | |
if ( e.target.classList.contains('me-client') ) { | |
tracking = true; | |
} | |
}); | |
body.addEventListener('touchmove', function(e) { | |
e.preventDefault(); | |
if ( tracking ) { | |
var x = e.targetTouches[0].pageX - boffset[0]; | |
var y = e.targetTouches[0].pageY - boffset[1]; | |
updateClientCoords(x, y); | |
} | |
}); | |
body.addEventListener('touchend', function(e) { | |
tracking = false; | |
}); | |
}); | |
socket.on("message", function(e) { | |
_log('message', e); | |
try { | |
var msg = JSON.parse(e.data); | |
if ( !msg.cmd ) { | |
throw "Missing 'cmd' in message."; | |
} | |
if ( !commands[msg.cmd] ) { | |
throw "Invalid cmd '" + msg.cmd + "'."; | |
} | |
} | |
catch (ex) { | |
_log('INVALID INCOMING:'); | |
_log(e.data); | |
return; | |
} | |
var cmd = commands[msg.cmd]; | |
try { | |
_log("Executing '" + msg.cmd + "'", msg); | |
console.time("Executed '" + msg.cmd + "'"); | |
cmd.call(this, msg); | |
console.timeEnd("Executed '" + msg.cmd + "'"); | |
} | |
catch (ex) { | |
console.error("Error while executing '" + msg.cmd + "': " + ex); | |
} | |
}); | |
socket.on("error", function(e) { | |
_log('on error', e); | |
}); | |
socket.on("close", function(e) { | |
_log('on close', e); | |
html.classList.remove('active'); | |
html.classList.add('error'); | |
}); | |
} | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var websocket = require('websocket'), | |
rwebsocket = require('./rwebsocket.js'); | |
function _log(msg) { | |
console.log.apply(console, arguments); | |
console.log(''); | |
} | |
// `this` is a WebSocketConnection, which has a `wsServer`, which has a `connections` | |
var commands = { | |
// eval: function(msg) { | |
// eval(msg.code); | |
// }, | |
_open: function() { | |
var x = Math.random() * 100, | |
y = Math.random() * 100, | |
coords = [x, y], | |
color = '#' + Math.random().toString(16).substr(-6); | |
this.data.coords = coords; | |
this.data.color = color; | |
var msg = { | |
id: this.data.id, | |
coords: coords, | |
color: color, | |
}; | |
var others = []; | |
// Notify all other clients about this new client | |
this.withAllOtherClients(function(client) { | |
client.sendCmd('party', msg); | |
others.push({ | |
id: client.data.id, | |
coords: client.data.coords, | |
color: client.data.color, | |
}); | |
}); | |
// Notify the client about hisself and all other clients | |
msg.others = others; | |
this.sendCmd('join', msg); | |
}, | |
_close: function() { | |
this.withAllOtherClients(function(other) { | |
other.sendCmd('unparty', {id: this.data.id}); | |
}); | |
}, | |
move: function(msg) { | |
this.data.coords[0] = msg.coords[0]; | |
this.data.coords[1] = msg.coords[1]; | |
msg.id = this.data.id; | |
this.withAllOtherClients(function(other) { | |
other.sendCmd('partymove', msg); | |
}); | |
} | |
}; | |
var options = { | |
port: 8084, | |
commands: commands, | |
} | |
var rws = rwebsocket(options, websocket); | |
var wsServer = rws.wsServer; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var http = require('http'), | |
util = require('util'); | |
function _log(msg) { | |
console.log.apply(console, arguments); | |
console.log(''); | |
} | |
function _inspect(ass, depth) { | |
undefined === depth && (depth = 3); | |
if ( typeof ass != 'string' && typeof ass != 'number' ) { | |
ass = util.inspect(ass, false, depth, true); | |
} | |
return _log(ass); | |
} | |
function _id() { | |
var id = String(Math.random()).substr(2, 12); | |
if ( id[0] == '0' ) { | |
return _id(); | |
} | |
return id; | |
} | |
/** | |
* Options: | |
* - port | |
* - commands | |
*/ | |
module.exports = function(options, websocket) { | |
var HTTPServer = http.Server; | |
var WebSocketServer = websocket.server; | |
var WebSocketConnection = websocket.connection; | |
var commands = options.commands || {}; | |
// Extend all client objects | |
WebSocketConnection.prototype.sendCmd = function(cmd, data) { | |
data || (data = {}); | |
data.cmd = cmd; | |
return this.send(JSON.stringify(data)); | |
}; | |
WebSocketConnection.prototype.withAllOtherClients = function(callback) { | |
this.wsServer.connections.forEach(function(client) { | |
if ( client != this ) { | |
callback.call(this, client); | |
} | |
}, this); | |
}; | |
WebSocketConnection.prototype.withAllOtherClientsInGame = function(callback, game) { | |
if ( game == null ) { | |
game = this.data.game; | |
} | |
this.wsServer.connections.forEach(function(client) { | |
if ( client.data.game == game && client != this ) { | |
callback.call(this, client); | |
} | |
}, this); | |
}; | |
// Dfine client event handlers here, so they exist only once | |
var onMessage = function(message) { | |
if (message.type === 'utf8') { | |
try { | |
var msg = JSON.parse(message.utf8Data); // THROWS | |
if ( !msg.cmd ) { | |
throw "Missing 'cmd' in message."; // THROWS | |
} | |
_log('Incoming:'); | |
_inspect(msg); | |
var cmd = commands[msg.cmd]; | |
if ( !cmd ) { | |
throw "'" + msg.cmd + "' is not a valid command."; // THROWS | |
} | |
try { | |
cmd.call(this, msg); // THROWS | |
} | |
catch (ex) { | |
throw "Error while executing '" + msg.cmd + "': " + ex.message; // THROWS | |
} | |
} | |
catch (ex) { | |
_log('Invalid cmd:'); | |
_log(message.utf8Data); | |
_log(ex); | |
// this.sendCmd('error', {error: ex.message}); | |
} | |
} | |
}; | |
var onClose = function(reasonCode, description) { | |
var hours = (Date.now() - this.socket._opened) / 1000 / 60 / 60; | |
_log('Client leaves: ' + this.data.id + ', after ' + (Math.round(hours * 100) / 100) + ' hours'); | |
if ( commands._close ) { | |
commands._close.call(this, {}); | |
} | |
}; | |
// Create HTTP and WebSocket servers | |
var httpServer = new HTTPServer().listen(options.port, function() { | |
_log('Server is listening on port ' + options.port); | |
}); | |
var wsServer = new WebSocketServer({ | |
httpServer: httpServer, | |
}); | |
wsServer.on('request', function(request) { | |
// We take anyone | |
var client = request.accept(); | |
client.wsServer = wsServer; | |
client.socket._opened = Date.now(); | |
// Custom data | |
client.data || (client.data = {}); | |
client.data.id = _id(); | |
client.data.game = 1; | |
_log('New client: ' + client.data.id); // client.socket._peername.port | |
client.on('message', onMessage); | |
client.on('close', onClose); | |
// Optionally attach more client event listeners | |
if ( commands._on ) { | |
for ( var type in commands._on ) { | |
client.on(type, commands._on[type]); | |
} | |
} | |
// And then run the client init/open command | |
if ( commands._open ) { | |
commands._open.call(client, {}); | |
} | |
}); | |
return { | |
httpServer: httpServer, | |
wsServer: wsServer, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment