Skip to content

Instantly share code, notes, and snippets.

@camme
Last active November 10, 2017 07:58
Show Gist options
  • Save camme/8c741b95d696915b45251b4e82021a5e to your computer and use it in GitHub Desktop.
Save camme/8c741b95d696915b45251b4e82021a5e to your computer and use it in GitHub Desktop.
NodeConf EU 2017 - Super Co-Op Game
/*
* 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