$(function() { // Return a string safe to display in HTML var safe = function(html) { return String(html) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); }; // Return a uniquer identifier var uniqueId = function() { return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r, v; r = Math.random() * 16 | 0; v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }; // MessageBus ENTER callback var enter = function(name, opts) { if (opts && opts.check) { var found = false; $('#walkers li').each(function() { found = found || ($(this).text().trim() === name.trim()); if (found && opts.me) { $(this).remove(); found = false; } return !found; }); if (found) { return; } } var li = $('
  • '); li.text(name); if (opts && opts.me) { li.addClass("me"); $('#walkers').prepend(li); } else { $('#walkers').append(li); } }; // MessageBus LEAVE callback var leave = function(name) { $.post('/leave', { name: name }); MessageBus.stop(); $('#bus-status').text('stopped'); }; // Assign player to a team var assign_random_team = function() { var teams = ['cavity', 'musclel', 'muscler', 'stringl', 'stringr', 'wingl', 'wingr']; var team = teams[Math.floor(Math.random()*teams.length)]; $('#team').text(team); return team; }; // When the DOM is fully loaded, start playing $(document).ready(function() { var name = uniqueId(), // Uniquely identify client n_walkers = 0, // Number of clients n_steps = 0, // Number of steps for this client last_step = $.now(), // Timestamp for last step has_accel = false, // Whether this device has an accelerometer accel_n = 0, // Acceleration (vector norm) accel = { x: 0, y: 0, z: 0, n: 0 }, // Acceleration with Kalman filter MIN_N = 10.3, // Minimum threshold for step detection MAX_N = 20; // Maximum threshold // SVG animation var map, cavity, cavity_b, musclel, musclel_b, muscler, muscler_b, stringl, stringl_b, stringr, stringr_b, wingl, wingl_b, wingr, wingr_b, flight_path, flight_path_l, last_point; var team = assign_random_team(); var svgp, bbox; map = Snap('#left-bits'); /* cavity = map.select('#cavity'); cavity_b = cavity.getBBox(); musclel = map.select('#musclel'); musclel_b = musclel.getBBox(); muscler = map.select('#muscler'); musclel_b = muscler.getBBox(); stringl = map.select('#stringl'); stringl_b = stringl.getBBox(); stringr = map.select('#stringr'); stringl_b = stringr.getBBox(); wingl = map.select('#wingl'); wingl_b = wingl.getBBox(); wingr = map.select('#wingr'); wingr_b = wingr.getBBox(); */ flight_path = map.select('#path'); flight_path_l = Snap.path.getTotalLength(flight_path); // console.log('flight_path_length ' + flight_path_l); last_point = flight_path.getPointAtLength(flight_path_l); // Start/pause MessageBus when clicking on status $('#bus-status').click(function() { switch($('#bus-status').text()) { case 'started': MessageBus.pause(); break; case 'paused': MessageBus.resume(); break; case 'stopped': MessageBus.start(); break; default: break; } }); var play_move = function(jump, team=false) { if (svgp = map.select('#' + (team || $('#team').text()))) { bbox = svgp.getBBox(); if (jump > flight_path_l) { // console.log('GAME OVER'); return; } Snap.animate(jump, flight_path_l, function() { var p = Snap.path.getPointAtLength( flight_path, jump ); x = p.x; y = p.y; svgp.transform('translate(' + x + ',' + y + ') rotate(' + (p.alpha - 90) + ', ' + bbox.cx + ', ' + bbox.cy + ')'); },1000, mina.easeout); } } // Update MessageBus status (debug) var statusInfo = setInterval(function() { $('#bus-status').text(MessageBus.status()); $('#name').text(name); // should be static }, 2000); // Configuration MessageBus.headers["X-NAME"] = name; // Who's connected /* MessageBus.subscribe("/presence", function(msg) { if (msg.enter) { enter(msg.enter, { check: true }); } if (msg.leave) { $('#walkers li').each(function() { if ($(this).text() === msg.leave) { $(this).remove(); leave(msg.leave); } }); } }); */ // Count clients /* MessageBus.subscribe('/n-walkers', function(data) { if (data && data.count) { n_walkers = data.count; $('#n-walkers').text(n_walkers); } }, n_walkers); */ // Update client steps MessageBus.subscribe("/walk", function(data) { if (data.name) { jump = data.n * data.step; if (data.name == name) { $('#accel-n').text(data.n); $('#n-steps').text(data.step); $('#current-team').text(data.team); } if (jump > flight_path_l) { $.post('/walk', { n: data.n, name: data.name, step: 0, team: data.team }); } else { play_move(jump, data.team); } } }); // last id is zero, so getting backlog // If we have accelerometer, play the game if (window.DeviceMotionEvent != undefined) { // Enter the game /* $.post("/enter", { name: name }, null, "json" ).success(function(data) { $.each(data.walkers, function(idx, name) { enter(name); }); name = data.name; enter(name, { check: true, me: true }); }); // Leave the game window.onbeforeunload = function() { $.post("/leave", { name: name }); }; */ // Compute acceleration or simulate it var accel_norm = function(e) { // has_accel = (window.DeviceMotionEvent != undefined && e != undefined && e.type == 'devicemotion' && e.acceleration.x != null); var norm; if (!has_accel) { // console.log("This device does not support DeviceMotionEvent"); var min = 5, max = 15; norm = Math.floor(Math.random()*(max-min+1)+min); } else { var ax, ay, az; ax = e.accelerationIncludingGravity.x; ay = e.accelerationIncludingGravity.y; az = e.accelerationIncludingGravity.z; accel = detect_footstep(ax, ay, az); norm = Math.sqrt(ax * ax + ay * ay + az * az); } $('#accel-n').text(safe(norm)); // console.log("accel-n " + norm); return norm; } // Detect footsteps with a Kalman filter var detect_footstep = function(ax, ay, az) { var acc = { x: ax, y: ay, z: az }; var cnt = 0; var x_0 = $V([acc.x, acc.y, acc.z]); // vector. Initial accelerometer values // P prior knowledge of state var P_0 = $M([ [1,0,0], [0,1,0], [0,0,1] ]); // identity matrix. Initial covariance. Set to 1 var F_k = $M([ [1,0,0], [0,1,0], [0,0,1] ]); // identity matrix. How change to model is applied. Set to 1 var Q_k = $M([ [0,0,0], [0,0,0], [0,0,0] ]); // empty matrix. Noise in system is zero var KM = new KalmanModel(x_0,P_0,F_k,Q_k); var z_k = $V([acc.x, acc.y, acc.z]); // Updated accelerometer values var H_k = $M([ [1,0,0], [0,1,0], [0,0,1] ]); // identity matrix. Describes relationship between model and observation var R_k = $M([ [2,0,0], [0,2,0], [0,0,2] ]); // 2x Scalar matrix. Describes noise from sensor. Set to 2 to begin var KO = new KalmanObservation(z_k,H_k,R_k); // each 1/10th second take new reading from accelerometer to update var getNewPos = window.setInterval(function() { KO.z_k = $V([acc.x, acc.y, acc.z]); //vector to be new reading from x, y, z KM.update(KO); accel = { x: KM.x_k.elements[0], y: KM.x_k.elements[1], z: KM.x_k.elements[2], n: accel.n }; accel.n = Math.sqrt(accel.x * accel.x + accel.y * accel.y + accel.z * accel.z); $('#accel-n-kalman').text(accel.n); }, 100); return accel; }; // If we can use the accelerometer, use it... window.ondevicemotion = function(e) { // console.log('ondevicemotion'); has_accel = (window.DeviceMotionEvent != undefined && e != undefined && e.type == 'devicemotion' && e.acceleration.x != null); if (has_accel) { // console.log('has_accel'); $('#accel-support').text("with accelerometer support"); walk(e); } else { $('#accel-support').text('with simulated accelerometer'); setInterval(walk, 100); } } // ... otherwise simulate it // if (!has_accel) { // $('#accel-support').text('with simulated accelerometer'); // setInterval(walk, 100); // } // Count steps (ballpark figure) var walk = function(e) { var val = accel_norm(e); // console.log('walk setting val to ' + safe(val)); if (has_accel) { // Use Kalman filter val = accel.n; // console.log('walk setting val to ' + safe(val) + ' (K)'); } // console.log("walk " + (has_accel?'ACCEL ':'NOCEL ') + val); // Avoid noise if (val < MIN_N || val > MAX_N) { // console.log('walk NOISE'); return; } // Take step frequency into account if (($.now() - last_step) > 400) { n_steps += 1; $.post('/walk', { n: val, name: name, step: n_steps, team: $('#team').text() }); last_step = $.now(); } }; } // DeviceMotionEvent }); // DOM Ready }); // Anonymous function