Last active
November 10, 2017 07:58
-
-
Save camme/8c741b95d696915b45251b4e82021a5e to your computer and use it in GitHub Desktop.
NodeConf EU 2017 - Super Co-Op Game
This file contains 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
/* | |
* Game created for the awesome badges at NodeConf EU 2017 | |
* One player will see what the other player should press and tell them that | |
* And the other player will see what the first player needs to press | |
* For every successfull level, the combination of buttons gets harder | |
* and you have less and less time to complete. You win or loose together! | |
* | |
* It doesnt work perfect though. | |
* | |
* To use it, you need to get the addresses of each of the two badges and put them | |
* into the addresses array | |
*/ | |
// var advertisement = | |
var myAddress = NRF.getAddress(); | |
var addresses = ['fd:17:8c:65:ca:2c', 'ec:7f:41:34:32:94']; | |
var otherAddress = addresses.reduce((result, badge) => { | |
if (badge !== myAddress) { | |
result = badge; | |
} | |
return result; | |
}, null); | |
var role = ''; | |
var level = 0; | |
// Are we busy? | |
var busy = false; | |
var sending = false; | |
var received = ''; | |
var primaryService = false; | |
var sendService = false; | |
var recieveService = false; | |
var inputs = []; | |
// The device, if we're connected | |
var connected = false; | |
var gatt = false; | |
var txCharacteristic = false; | |
//var rxCharacteristic = false; | |
var localGoal = ''; | |
var remoteGoal = ''; | |
var currentGoal = []; | |
var goalTime = 20000; | |
var timer = goalTime; | |
var goalTimeout = 0; | |
var status = 'init'; | |
var watches = {}; | |
var screen = ['', '', '', '']; | |
function wt(text, index) { | |
g.clear(); | |
screen[index] = text; | |
g.drawString(screen[0], 0, 0); | |
g.drawString(screen[1], 0, 15); | |
g.drawString(screen[2], 0, 30); | |
g.drawString(screen[3], 0, 45); | |
g.flip(); | |
} | |
//wt('Starting', 0); | |
function connect() { | |
console.log('try to connect'); | |
role = 'client'; | |
if (connected) { | |
return; | |
} | |
if (!busy) { | |
if (!connected) { | |
busy = true; | |
wt('Connecting to "' + otherAddress + '"', 0); | |
console.log('1'); | |
NRF.connect(otherAddress + ' random') | |
//NRF.requestDevice({ filters: [{ name: otherBadge }] }) | |
//NRF.requestDevice({ filters: [{ name: otherBadge }] }).then(device => device.gatt) | |
.then(function(g) { | |
gatt = g; | |
connected = true; | |
wt('Get custom service.', 0); | |
console.log('3'); | |
return gatt.getPrimaryService(0xBCDE); | |
}) | |
.then(function(s) { | |
recieveService = s; | |
console.log('4'); | |
wt('Get send service.', 0); | |
return gatt.getPrimaryService('6e400001-b5a3-f393-e0a9-e50e24dcca9e'); | |
}) | |
.then(function(s) { | |
sendService = s; | |
console.log('5'); | |
wt('Get tx characteristic.', 0); | |
return sendService.getCharacteristic("6e400002-b5a3-f393-e0a9-e50e24dcca9e"); | |
}) | |
.then(function(c) { | |
console.log('6'); | |
wt('Get custom characteristic.', 0); | |
txCharacteristic = c; | |
return recieveService.getCharacteristic(0xABCD); | |
}) | |
.then(function(c) { | |
console.log('7'); | |
wt('Setup listeners.', 0); | |
c.on('characteristicvaluechanged', onData); | |
setupButtons(); | |
wt('Setup complete!', 0); | |
return c.startNotifications(); | |
}) | |
.then(function() { | |
console.log('8'); | |
busyOff(); | |
sendRemoteStart(); | |
setTimeout(start, 500); | |
//start(); | |
}) | |
.catch(function(e) { | |
wt(e, 0); | |
console.log('connect error', e); | |
if (gatt) gatt.disconnect(); | |
connected = false; | |
gatt = false; | |
busyOff(); | |
}); | |
} | |
} else { | |
} | |
} | |
function busyOff() { | |
busy = false; | |
console.log('busy off'); | |
} | |
let buttons = [ | |
{ char: 'a', id: BTNA }, | |
{ char: 'b', id: BTNB }, | |
{ char: 'u', id: BTNU }, | |
{ char: 'd', id: BTND }, | |
{ char: 'l', id: BTNL }, | |
{ char: 'r', id: BTNR }, | |
]; | |
function setupButtons() { | |
console.log('setup buttons'); | |
buttons.forEach(b => { | |
//clearW[k]; | |
watches[b.char] = setWatch(checkInput.bind(this, b.char), b.id, { edge:"rising", debounce:50, repeat: true }); | |
}); | |
} | |
function clearW(id) { | |
if (watches[id]) { | |
clearWatch(watches[id]); | |
watches[id] = 0; | |
} | |
} | |
/* | |
setInterval(function() { | |
if (status === 'started') { | |
wt(timer / 1000 + 's left!', 2); | |
if (timer > 0) { | |
timer -= 1000; | |
} | |
} | |
}, 1000); | |
*/ | |
function lost() { | |
status = 'lost'; | |
wt('LOST', 0); | |
wt('', 1); | |
wt('', 2); | |
if (goalTimeout) { | |
clearTimeout(goalTimeout); | |
goalTimeout = 0; | |
} | |
buttons.forEach(b => clearW[b.char]); | |
if (role === 'client') { | |
wt('Press A to restart', 0); | |
if (!watches.restart) { | |
watches.restart = setWatch(restart, BTNA, { edge:"rising", debounce:50, repeat: true }); | |
} | |
} | |
} | |
function restart() { | |
wt('Restarting', 0); | |
wt('', 1); | |
if (role === 'client') { | |
clearW('restart'); | |
send('s', 'restart'); | |
} | |
setupButtons(); | |
level = 0; | |
inputs = []; | |
goalTime = 20000; | |
timer = goalTime; | |
remoteGoal = ''; | |
currentGoal = []; | |
start(); | |
status = 'started'; | |
} | |
function packMessage(type, content) { | |
if (type === 'g') { | |
return type + ':' + content.join(''); | |
} | |
return type + ':' + content; | |
} | |
function unpackMessage(message) { | |
let parsed = message.split(':'); | |
let type = parsed[0]; | |
let content = parsed[1]; | |
wt(type + ',' + content, 1); | |
if (type === 'g') { | |
return { type: type, content: content.split('') }; | |
} | |
return { type: type, content: content }; | |
} | |
// Function to call 'toggle' on the other Puck | |
function send(type, value) { | |
let message = packMessage(type, value); | |
if (!busy) { | |
console.log('send', message); | |
busy = true; | |
if (connected) { | |
//console.log(message); | |
if (role === 'client') { | |
// console.log('send as client', message); | |
txCharacteristic.writeValue("da('" + message + "')\n").then(function() { | |
console.log('send done', message); | |
//wt('Sent "' + message + '"', 0); | |
busyOff(); | |
}).catch(function(e) { | |
console.log('send err', e); | |
wt('Send Error', 0); | |
busyOff(); | |
}); | |
} else { | |
console.log('send as server'); | |
//wt('Sent "' + message + '"', 1); | |
NRF.updateServices({ | |
0xBCDE : { | |
0xABCD : { | |
value : message, | |
notify: true | |
} | |
} | |
}); | |
busyOff(); | |
} | |
} else { | |
console.log('not connected c', connected); | |
busyOff(); | |
} | |
} else { | |
console.log('busy, resend', message); | |
setTimeout(function() { | |
send(type, value); | |
}, 1000); | |
} | |
} | |
// This function is just called "da", to save si`ze when sending messages | |
function da(value) { | |
let message = unpackMessage(value); | |
//wt('value: ' +value, 1); | |
parseData(message); | |
} | |
function onData(e) { | |
let value = String.fromCharCode.apply(this, e.target.value.buffer); | |
wt(value, 3); | |
let message = unpackMessage(value); | |
parseData(message); | |
} | |
function parseData(message) { | |
if (message.type === 'g') { | |
remoteGoal = message.content; | |
wt('GOAL: ' + remoteGoal.join(', ').toUpperCase(), 2); | |
} else if (message.type === 's' && message.content === 'lost') { | |
lost(); | |
} else if (message.type === 's' && message.content === 'start') { | |
start(); | |
} else if (message.type === 's' && message.content === 'restart') { | |
restart(); | |
} else if (message.type === 't') { | |
setTimers(message.content.split(',')); | |
} | |
} | |
function checkInput(input) { | |
inputs.push(input); | |
let start = Math.max(inputs.length - currentGoal.length, 0); | |
inputs = inputs.slice(start); | |
console.log('CHECK', inputs.join(','), currentGoal.join(',')); | |
if (inputs.join(',') === currentGoal.join(',')) { | |
correct(); | |
} | |
} | |
function setTimers(g, t) { | |
goalTime = parseInt(g); | |
timer = parseInt(t); | |
} | |
function correct() { | |
console.log('correct', currentGoal.join(',')); | |
wt('Correct!', 0); | |
status = 'started'; | |
level++; | |
goalTime -= 1000; | |
goalTime = Math.max(goalTime, 2000); | |
timer = goalTime; | |
//send('t', goalTime + ',' + timer); | |
newGoal(); | |
} | |
function newGoal() { | |
currentGoal = getInstructions(level); | |
if (goalTimeout) { | |
clearTimeout(goalTimeout); | |
goalTimeout = 0; | |
} | |
console.log('NEW GOAL', currentGoal.join('')); | |
send('g', currentGoal); | |
goalTimeout = setTimeout(function() { | |
if (goalTimeout) { | |
clearTimeout(goalTimeout); | |
goalTimeout = 0; | |
} | |
lost(); | |
send('s', 'lost'); | |
}, goalTime); | |
setTimeout(function() { | |
wt('Enter the secret', 0); | |
},500); | |
} | |
function start() { | |
status = 'started'; | |
newGoal(); | |
} | |
NRF.setServices({ | |
0xBCDE : { | |
0xABCD : { | |
value : packMessage('s', 'init'), | |
maxLen : 30, | |
notify: true | |
} | |
} | |
}); | |
E.on('errorFlag', function(e) { | |
E.getErrorFlags(); | |
//console.log('nrf e', e); | |
busyOff(); | |
}); | |
NRF.on('connect', function() { | |
console.log('NRF connect'); | |
//if (role !== 'client') { | |
role = 'server'; | |
connected = true; | |
clearWatch(startWatchId); | |
setupButtons(); | |
wt('Connected', 3); | |
//} | |
}); | |
function sendRemoteStart() { | |
console.log('SEND REMOTE START'); | |
send('s', 'start'); | |
} | |
NRF.on('disconnected', function() { | |
role = 'server'; | |
wt('Diconnected', 0); | |
}); | |
let startWatchId = 0; | |
function init(){ | |
role = 'client'; | |
wt('Press A to connect to ' + otherAddress, 0); | |
startWatchId = setWatch(function() { | |
connect(); | |
clearWatch(startWatchId); | |
}, BTNA, { edge: "rising", debounce:50, repeat: true }); | |
//NRF.restart(); | |
} | |
let inputList = [ | |
{char: 'a', level: 0 }, | |
{char: 'b', level: 0 }, | |
{char: 'u', level: 1 }, | |
{char: 'd', level: 1 }, | |
{char: 'l', level: 1 }, | |
{char: 'r', level: 1 },/* | |
{char: 'q', level: 2 }, | |
{char: 'w', level: 2 }, | |
{char: 'e', level: 2 }, | |
{char: 'r', level: 2 }, | |
{char: 't', level: 2 }, | |
{char: 'y', level: 2 }, | |
{char: 'Q', level: 3 }, | |
{char: 'W', level: 3 }, | |
{char: 'E', level: 3 }, | |
{char: 'R', level: 3 }, | |
{char: 'T', level: 3 }, | |
{char: 'Y', level: 3 },*/ | |
]; | |
let maxes = [0,0,0,0,1,1,1,1,1,2,2,2,2,2,3,3,3,3,4,4,4,5,5,6,6]; | |
function getInstructions(level) { | |
let available = inputList.filter(input => input.level <= level); | |
let max = level >= maxes.length ? maxes[maxes.length - 1] : maxes[level]; | |
let combosMax = max; //Math.round(Math.random() * (max - 1)); | |
let instructions = []; | |
for ( let i = 0; i <= combosMax; i++) { | |
let index = Math.round(Math.random() * (available.length - 1)); | |
instructions.push(available[index].char); | |
} | |
return instructions; | |
} | |
init(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment