switch to simple version

This commit is contained in:
Kai Mosebach 2022-08-13 15:31:29 +01:00
parent 646af978f9
commit 2e5225bfac
2 changed files with 524 additions and 20 deletions

439
game-full.js Normal file
View File

@ -0,0 +1,439 @@
const { QLog, undefinedOrNull } = require('quanto-commons');
const player = require('node-wav-player');
const TelegramBot = require('node-telegram-bot-api');
const fs = require('fs');
const { exec } = require("child_process");
if(process.platform !== 'darwin') var gpio = require('rpi-gpio');
const telegramBotToken = '5472909337:AAGH269wNGa9u99ekJqHqXEHfi0knpV7IFU';
//const groupid='@HotSoup';
const groupid='-1001703800044';
const GlobalLog = QLog.scope('Global');
const TelLog = QLog.scope('Telegram');
GlobalLog.enableLogs(['debug']);
TelLog.enableLogs(['debug']);
GlobalLog.headPadding = 30;
TelLog.headPadding = 30;
// const bot = new Telegraf(telegramBotToken);
const bot = new TelegramBot(telegramBotToken, {polling: true});
// debounce der buttons in ms
const bounceMS = 800;
// Game Reset Start after ms
const startReset = 4000;
// ist an wenn im kind mode (erw LED is aus)
const childLED = 37;
// ist and wenn im erwachsenen Mode (kind LED ist aus)
const erwLED = 38;
// status : ist an wenn in ruhe position, blinkt, wenn gestartet, ist aus bei fail
const statusLED = 40;
// setze auf HIGH wenn fail (klingel relais)
const failSwitch = 35;
// warte auf spielstart wenn auf HIGH
const startButton = 32;
const startButton_GPIO = 12;
// beende spiel wenn auf HIGH
// const finishButton = 31;
const finishButton = 22;
const finishButton_GPIO = 25;
// faile spiel wenn auf HIGH
// const failButton = 29;
const failButton = 21;
const failButton_GPIO = 9;
// wechsle kind/erwachen Modus + LED
const toggleButton = 33;
const toggleButton_GPIO = 13;
// wird je nach modus gesetzt und abgespielt wenn start
const backgroundGrown = "jaws.wav";
const jeopardy = "jeopardy.wav";
const backgroundChild = "northsouth.wav";
// wird zufallig zu zufaelligen momenten abgespielt (erw mode)
const erschreckSounds = [ "ziege.wav", "scream.wav", "laugh.wav", "cavallerie.wav", "gulp.wav" ];
// wird nach fester zeit + random oder spaeter nach der besten zeit abgespielt (erw mode)
// const stresserSound = "sharks.wav";
// wird bei fail abgespielt (zonk)
const failSound = "zonk.wav";
// hurra wird bei finish abgespielt
const finishSound = "kidscheering.wav";
const GROWN=1;
const CHILD=2;
// LOW EDGE
let preStart = true;
// game running?
let isRunning = false;
const STATES = {
STARTED: 'started',
FINISH: 'finshed',
FAIL: 'failed'
};
let best;
const bounce = {};
var STATE = STATES.FINISH;
var MODE = GROWN;
var GAME = {
events: [],
runTime: 0
};
var chatId;
function sendBotGroupImg(chat_id, photo, caption) {
GlobalLog.info( chat_id, photo);
bot.sendPhoto(chat_id, photo, {
caption: caption
});
}
function sendBotMsg(msg) {
if(chatId) {
bot.sendMessage(chatId, msg);
}
}
function setup() {
// bot.start((ctx) => {
// let message = `Bot aktiv und beobachtet. Benutze /stats um die Bestenliste zu sehen`;
// ctx.reply(message);
// TelLog.note('Bot started');
// botCTX=ctx;
// });
// bot.command('stats', async (ctx) => {
// await ctx.replyWithPhoto({ source: '/root/hotwire-js/img/bestgrown.png' });
// await ctx.reply(`Bester Erwachsener mit nur ${best.grown} sek`);
// await ctx.replyWithPhoto({ source: '/root/hotwire-js/img/bestchild.png' })
// await ctx.reply(`Bestes Kind mit nur ${best.child} sek`);
// });
// bot.launch();
fs.readFile('stats.json', 'utf8', (err, res) => {
if(err) {
GlobalLog.info("No stats found. Using defaults");
best = {
grown: 25,
child: 37
}
} else {
GlobalLog.info("Stats loaded.");
best = JSON.parse(res);
}
});
GlobalLog.note('Setting up GPIOs');
if(process.platform === 'darwin') {
setTimeout(start, 500);
setTimeout(fail, 3500);
setTimeout(start, 4000);
setTimeout(finish, 9000);
setTimeout(fail, 10000);
setTimeout(finish, 11000);
return;
}
gpio.setup(failSwitch, gpio.OUT, () => {
gpio.output(failSwitch, false);
});
gpio.setup(statusLED, gpio.OUT, () => {
gpio.output(statusLED, true);
});
gpio.setup(childLED, gpio.OUT, () => {
gpio.output(childLED, false);
});
gpio.setup(erwLED, gpio.OUT, () => {
gpio.output(erwLED, true);
});
gpio.setup(finishButton, gpio.DIR_IN, gpio.EDGE_FALLING, () => {
exec(`raspi-gpio set ${finishButton_GPIO} pu`);
});
gpio.setup(toggleButton, gpio.DIR_IN, gpio.EDGE_FALLING, () => {
exec(`raspi-gpio set ${toggleButton_GPIO} pu`);
});
gpio.setup(startButton, gpio.DIR_IN, gpio.EDGE_BOTH, () => {
exec(`raspi-gpio set ${startButton_GPIO} pu`);
});
gpio.setup(failButton, gpio.DIR_IN, gpio.EDGE_FALLING, () => {
exec(`raspi-gpio set ${failButton_GPIO} pu`);
});
gpio.on('change', (channel, value) => {
if(bounce[channel] === true) return;
GlobalLog.debug('Change',channel, value);
bounce[channel] = true;
setTimeout(() => { bounce[channel] = false; GlobalLog.debug('Debounced', channel); }, bounceMS);
switch(channel) {
case startButton:
start(value);
break;
case finishButton:
finish(value);
break;
case failButton:
fail(value);
break;
case toggleButton:
toggle(value);
break;
default:
GlobalLog.warn('Alert! Unknown button change',channel);
}
});
GlobalLog.note('Done. Game logic started. Waiting for input');
}
function gameTimer() {
GlobalLog.debug('game timer called');
GAME.events.forEach((el) => {
if(el.start === GAME.runTime) {
GlobalLog.debug('Event :', el);
playSound(el.sound);
}
});
GAME.runTime++;
GAME.led = !GAME.led;
gpio.output(statusLED, GAME.led);
}
function failTimer() {
GAME.led = !GAME.led;
gpio.output(statusLED, GAME.led);
gpio.output(childLED, GAME.led);
gpio.output(erwLED, GAME.led);
}
function successTimer() {
GAME.led = !GAME.led;
gpio.output(statusLED, GAME.led);
gpio.output(childLED, !GAME.led);
gpio.output(erwLED, GAME.led);
}
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
function start(value) {
if(preStart === false || isRunning) {
GlobalLog.note('Prestart detect.');
preStart = true;
if(GAME.startplayer) GAME.startplayer.stop();
if(GAME.failTimer) clearInterval(GAME.failTimer);
if(GAME.successTimer) clearInterval(GAME.successTimer);
if(GAME.countTimer) clearInterval(GAME.countTimer)
if(player) player.stop();
if(MODE === GROWN) {
gpio.output(childLED, false);
gpio.output(erwLED, true);
} else {
gpio.output(childLED, true);
gpio.output(erwLED, false);
}
if(preStart === false) return;
}
if(isRunning === true) {
GlobalLog.note('Game reset.');
if(GAME.startplayer) GAME.startplayer.stop();
if(player) player.stop();
STATE = STATES.FINISH;
clearInterval(GAME.countTimer)
preStart = false;
isRunning = false;
if(GAME.failTimer) clearInterval(GAME.failTimer);
if(GAME.successTimer) clearInterval(GAME.successTimer);
gpio.output(statusLED, true);
GAME = {
events: [],
runTime: 0
}
}
if(GAME.failTimer) clearInterval(GAME.failTimer);
if(GAME.successTimer) clearInterval(GAME.successTimer);
if(MODE === GROWN) {
gpio.output(erwLED, true);
} else {
gpio.output(childLED, true);
}
GAME.runTime = 0;
if(STATE === STATES.FINISH || STATE === STATES.FAIL) {
STATE = STATES.STARTED;
// set all params for next game, then start counter and event timer
if(MODE === GROWN) {
if(getRandomInt(7) > 2) {
GAME.events.push({ sound: jeopardy, start: 1});
} else {
GAME.events.push({ sound: backgroundGrown, start: 1});
}
GAME.events.push({ sound: erschreckSounds[getRandomInt(5)], start: 15+getRandomInt(10) });
GAME.events.push({ sound: erschreckSounds[getRandomInt(5)], start: 30+getRandomInt(10) });
GAME.events.push({ sound: erschreckSounds[getRandomInt(5)], start: 50+getRandomInt(10) });
GAME.mode = GROWN;
GAME.fail = failSound;
GAME.finish = finishSound;
GAME.runtime = 0;
}
if(MODE === CHILD) {
GAME.events.push({ sound: backgroundChild, start: 0});
GAME.events.push({ sound: erschreckSounds[getRandomInt(2)], start: 30+getRandomInt(10)});
GAME.mode = CHILD;
GAME.fail = failSound;
GAME.finish = finishSound;
GAME.runtime = 0;
}
GlobalLog.note('Starting new game', JSON.stringify(GAME, 2, null));
isRunning = true;
GAME.countTimer = setInterval(gameTimer, 1000);
sendBotMsg('new game startet');
} else {
GlobalLog.warn('START not in state', STATE);
}
}
function finish(value) {
GlobalLog.debug('Finish');
if(STATE === STATES.STARTED) {
GlobalLog.note('You made it in', GAME.runTime, 'seconds');
GlobalLog.note(GAME);
player.stop();
playSound(GAME.finish);
STATE = STATES.FINISH;
clearInterval(GAME.countTimer)
GAME.successTimer = setInterval(failTimer, 600);
GlobalLog.note('Getting new image!')
exec(`fswebcam -r 1024x768 /tmp/last.png`, (err) => {
let rt = GAME.runTime;
if(err) {
GlobalLog.error('Could not get image', err);
}
GAME.events = [];
var msg;
var img;
if(MODE === GROWN) {
if(GAME.runTime < best.grown) {
best.grown = rt;
msg = `Gewinner in nur ${rt} Sekunden!\nNeue Bestzeit bei den Erwachsenen!`;
fs.copyFile('/tmp/last.png', '/tmp/bestgrown.png', (err) => {});
} else {
msg = `Gewinner in nur ${rt} Sekunden!`;
}
} else {
if(GAME.runTime < best.child) {
sendBotMsg(`Neue Bestzeit bei den Kindern!`);
best.child = rt;
msg = `Gewinner in nur ${rt} Sekunden!\nNeue Bestzeit bei den Kindern!`;
fs.copyFile('/tmp/last.png', '/tmp/bestchild.png', (err) => {});
} else {
msg = `Gewinner in nur ${rt} Sekunden!`;
}
}
sendBotGroupImg(groupid, '/tmp/last.png', msg);
GAME.led = true;
});
preStart = false;
isRunning = false;
GAME = {
events: [],
runTime: 0
}
} else {
GlobalLog.warn('FINISH not in state', STATE);
}
// exec(`killall aplay`);
}
function fail(value) {
if(STATE === STATES.STARTED) {
GlobalLog.note('You failed after', GAME.runTime, 'seconds');
GAME.startplayer.stop();
player.stop();
playSound(GAME.fail);
STATE = STATES.FAIL;
clearInterval(GAME.countTimer)
GAME.failTimer = setInterval(failTimer, 100);
GAME.events = [];
preStart = false;
isRunning = false;
GAME = {
events: [],
runTime: 0
}
} else {
GlobalLog.debug('FAIL not in state', STATE);
}
}
function toggle(value) {
if(STATE === STATES.FINISH || STATE === STATES.FAIL) {
if(GAME.failTimer) clearInterval(GAME.failTimer);
if(GAME.successTimer) clearInterval(GAME.successTimer);
if(MODE === GROWN) {
gpio.output(erwLED, true);
} else {
gpio.output(childLED, true);
}
GlobalLog.info('Toggle, old', MODE);
if(MODE === CHILD) {
MODE = GROWN;
gpio.output(childLED, false);
gpio.output(erwLED, true);
// set all values for GROWN mode
} else {
MODE = CHILD;
gpio.output(childLED, true);
gpio.output(erwLED, false);
// set all values for CHILD mode
}
} else {
GlobalLog.debug('Toggle not in state', STATE);
}
}
function playSound(file, runTime){
if(!file) { GlobalLog.error('Sound not defined.'); return; }
GlobalLog.info("Play sound : ", file);
if(GAME.playing) {
GlobalLog.debug("Stopping old soundfile", GAME.playing);
player.stop();
}
// Do not stop background sound
if(GAME.runTime > 1) {
GAME.playing = file;
} else {
GAME.startplayer = player;
}
player.play({ path: `wav/${file}`, sync: true }).then(() => { delete GAME.playing })
}
setup();
TelLog.start('Starting bot...');
bot.on('message', function (msg) {
const chatId = msg.chat.id;
const resp = msg;
TelLog.info(`${chatId}: ${resp.text}`);
if(resp.text === '/stats') {
TelLog.info(`Dump stats to channel ${groupid}`);
if (fs.existsSync('/tmp/bestchild.png')) {
sendBotGroupImg(groupid, '/tmp/bestchild.png', `Bestes Kind mit ${best.child} Sekunden.`);
}
if (fs.existsSync('/tmp/bestgrown.png')) {
sendBotGroupImg(groupid, '/tmp/bestgrown.png', `Bester Erwachsener mit ${best.grown} Sekunden.`);
}
}
});

105
game.js
View File

@ -12,8 +12,8 @@ const groupid='-1001703800044';
const GlobalLog = QLog.scope('Global');
const TelLog = QLog.scope('Telegram');
GlobalLog.enableLogs(['warn']);
TelLog.enableLogs(['warn']);
GlobalLog.enableLogs(['debug']);
TelLog.enableLogs(['debug']);
GlobalLog.headPadding = 30;
TelLog.headPadding = 30;
@ -67,22 +67,28 @@ const finishSound = "kidscheering.wav";
const GROWN=1;
const CHILD=2;
// LOW EDGE
let preStart = true;
// game running?
let isRunning = false;
const STATES = {
STARTED: 'started',
FINISH: 'finshed',
FAIL: 'failed'
};
const best = {
grown: 25,
child: 37
}
let best;
const bounce = {};
var STATE = STATES.FINISH;
var MODE = GROWN;
var GAME = {};
var GAME = {
events: [],
runTime: 0
};
var chatId;
@ -113,6 +119,18 @@ function setup() {
// await ctx.reply(`Bestes Kind mit nur ${best.child} sek`);
// });
// bot.launch();
fs.readFile('stats.json', 'utf8', (err, res) => {
if(err) {
GlobalLog.info("No stats found. Using defaults");
best = {
grown: 25,
child: 37
}
} else {
GlobalLog.info("Stats loaded.");
best = JSON.parse(res);
}
});
GlobalLog.note('Setting up GPIOs');
@ -144,8 +162,8 @@ function setup() {
gpio.setup(toggleButton, gpio.DIR_IN, gpio.EDGE_FALLING, () => {
exec(`raspi-gpio set ${toggleButton_GPIO} pu`);
});
gpio.setup(startButton, gpio.DIR_IN, gpio.EDGE_FALLING, () => {
exec(`raspi-gpio set ${startButton_GPIO} pu`);
gpio.setup(startButton, gpio.DIR_IN, gpio.EDGE_BOTH, () => {
exec(`raspi-gpio set ${startButton_GPIO} pu`);
});
gpio.setup(failButton, gpio.DIR_IN, gpio.EDGE_FALLING, () => {
exec(`raspi-gpio set ${failButton_GPIO} pu`);
@ -207,6 +225,39 @@ function getRandomInt(max) {
}
function start(value) {
if(preStart === false || isRunning) {
GlobalLog.note('Prestart detect.');
preStart = true;
if(GAME.startplayer) GAME.startplayer.stop();
if(GAME.failTimer) clearInterval(GAME.failTimer);
if(GAME.successTimer) clearInterval(GAME.successTimer);
if(GAME.countTimer) clearInterval(GAME.countTimer)
if(player) player.stop();
if(MODE === GROWN) {
gpio.output(childLED, false);
gpio.output(erwLED, true);
} else {
gpio.output(childLED, true);
gpio.output(erwLED, false);
}
if(preStart === false) return;
}
if(isRunning === true) {
GlobalLog.note('Game reset.');
if(GAME.startplayer) GAME.startplayer.stop();
if(player) player.stop();
STATE = STATES.FINISH;
clearInterval(GAME.countTimer)
preStart = false;
isRunning = false;
if(GAME.failTimer) clearInterval(GAME.failTimer);
if(GAME.successTimer) clearInterval(GAME.successTimer);
gpio.output(statusLED, true);
GAME = {
events: [],
runTime: 0
}
}
if(GAME.failTimer) clearInterval(GAME.failTimer);
if(GAME.successTimer) clearInterval(GAME.successTimer);
if(MODE === GROWN) {
@ -214,10 +265,9 @@ function start(value) {
} else {
gpio.output(childLED, true);
}
GAME = {
events: [],
runTime: 0
};
GAME.runTime = 0;
if(STATE === STATES.FINISH || STATE === STATES.FAIL) {
STATE = STATES.STARTED;
// set all params for next game, then start counter and event timer
@ -244,6 +294,7 @@ function start(value) {
GAME.runtime = 0;
}
GlobalLog.note('Starting new game', JSON.stringify(GAME, 2, null));
isRunning = true;
GAME.countTimer = setInterval(gameTimer, 1000);
sendBotMsg('new game startet');
@ -252,7 +303,7 @@ function start(value) {
}
}
async function finish(value) {
function finish(value) {
GlobalLog.debug('Finish');
if(STATE === STATES.STARTED) {
GlobalLog.note('You made it in', GAME.runTime, 'seconds');
@ -264,6 +315,7 @@ async function finish(value) {
GAME.successTimer = setInterval(failTimer, 600);
GlobalLog.note('Getting new image!')
exec(`fswebcam -r 1024x768 /tmp/last.png`, (err) => {
let rt = GAME.runTime;
if(err) {
GlobalLog.error('Could not get image', err);
}
@ -272,25 +324,31 @@ async function finish(value) {
var img;
if(MODE === GROWN) {
if(GAME.runTime < best.grown) {
best.grown = GAME.runTime;
msg = `Gewinner in nur ${GAME.runTime} Sekunden!\nNeue Bestzeit bei den Erwachsenen!`;
best.grown = rt;
msg = `Gewinner in nur ${rt} Sekunden!\nNeue Bestzeit bei den Erwachsenen!`;
fs.copyFile('/tmp/last.png', '/tmp/bestgrown.png', (err) => {});
} else {
msg = `Gewinner in nur ${GAME.runTime} Sekunden!`;
msg = `Gewinner in nur ${rt} Sekunden!`;
}
} else {
if(GAME.runTime < best.child) {
sendBotMsg(`Neue Bestzeit bei den Kindern!`);
best.child = GAME.runTime
msg = `Gewinner in nur ${GAME.runTime} Sekunden!\nNeue Bestzeit bei den Kindern!`;
best.child = rt;
msg = `Gewinner in nur ${rt} Sekunden!\nNeue Bestzeit bei den Kindern!`;
fs.copyFile('/tmp/last.png', '/tmp/bestchild.png', (err) => {});
} else {
msg = `Gewinner in nur ${GAME.runTime} Sekunden!`;
msg = `Gewinner in nur ${rt} Sekunden!`;
}
}
sendBotGroupImg(groupid, '/tmp/last.png', msg);
GAME.led = true;
});
preStart = false;
isRunning = false;
GAME = {
events: [],
runTime: 0
}
} else {
GlobalLog.warn('FINISH not in state', STATE);
}
@ -307,6 +365,12 @@ function fail(value) {
clearInterval(GAME.countTimer)
GAME.failTimer = setInterval(failTimer, 100);
GAME.events = [];
preStart = false;
isRunning = false;
GAME = {
events: [],
runTime: 0
}
} else {
GlobalLog.debug('FAIL not in state', STATE);
}
@ -348,6 +412,7 @@ function playSound(file, runTime){
// Do not stop background sound
if(GAME.runTime > 1) {
GAME.playing = file;
} else {
GAME.startplayer = player;
}
player.play({ path: `wav/${file}`, sync: true }).then(() => { delete GAME.playing })