2022-02-28 21:16:41 +00:00
|
|
|
|
|
|
|
const FREQUENCY = 440;
|
|
|
|
|
|
|
|
var DOT_TIME = 300;
|
|
|
|
var DASH_TIME = DOT_TIME * 3;
|
|
|
|
var SYMBOL_BREAK = DOT_TIME;
|
|
|
|
var LETTER_BREAK = DOT_TIME * 3;
|
|
|
|
var WORD_BREAK = DOT_TIME * 7;
|
|
|
|
|
|
|
|
|
|
|
|
let note_context;
|
|
|
|
let note_node;
|
|
|
|
let gain_node;
|
|
|
|
|
|
|
|
let audioContextInitialized = false;
|
|
|
|
|
|
|
|
async function initializeAudioContext() {
|
|
|
|
note_context = new AudioContext();
|
|
|
|
await note_context.resume();
|
|
|
|
note_node = note_context.createOscillator();
|
|
|
|
gain_node = note_context.createGain();
|
|
|
|
note_node.frequency.value = FREQUENCY.toFixed(2);
|
|
|
|
gain_node.gain.value = 0;
|
|
|
|
note_node.connect(gain_node);
|
|
|
|
gain_node.connect(note_context.destination);
|
|
|
|
note_node.start();
|
|
|
|
audioContextInitialized = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function startNotePlaying() {
|
|
|
|
// Pass a start time of 0 so it starts ramping up immediately.
|
|
|
|
gain_node.gain.setTargetAtTime(0.1, 0, 0.001)
|
2023-03-11 20:42:10 +00:00
|
|
|
$("#beepLamp").addClass("lamp-on")
|
2022-02-28 21:16:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function stopNotePlaying() {
|
|
|
|
// Pass a start time of 0 so it starts ramping down immediately.
|
|
|
|
gain_node.gain.setTargetAtTime(0, 0, 0.001)
|
2023-03-11 20:42:10 +00:00
|
|
|
$("#beepLamp").removeClass("lamp-on")
|
2022-02-28 21:16:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function sleep(ms) {
|
|
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Global strictly increasing play counter to avoid multiple replays at the same time.
|
|
|
|
var playCounter = 0;
|
|
|
|
|
|
|
|
async function playDash(currentPlayCounter) {
|
|
|
|
if (currentPlayCounter != playCounter) { return; }
|
|
|
|
startNotePlaying();
|
|
|
|
await sleep(DASH_TIME);
|
|
|
|
stopNotePlaying();
|
|
|
|
}
|
|
|
|
|
|
|
|
async function playDot(currentPlayCounter) {
|
|
|
|
if (currentPlayCounter != playCounter) { return; }
|
|
|
|
startNotePlaying();
|
|
|
|
await sleep(DOT_TIME);
|
|
|
|
stopNotePlaying();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* message is something like '---'
|
|
|
|
*/
|
|
|
|
async function playLetter(letter, currentPlayCounter) {
|
|
|
|
// console.log('playSentence with', playCounter, currentPlayCounter);
|
|
|
|
if (!audioContextInitialized) {
|
|
|
|
initializeAudioContext();
|
|
|
|
}
|
|
|
|
// console.log('playing', letter);
|
|
|
|
for (let i = 0; i < letter.length; i++) {
|
|
|
|
if (currentPlayCounter != playCounter) { return; }
|
|
|
|
if (letter[i] == '-') {
|
|
|
|
await playDash(currentPlayCounter);
|
|
|
|
} else if (letter[i] == '.') {
|
|
|
|
await playDot(currentPlayCounter);
|
|
|
|
}
|
|
|
|
await sleep(SYMBOL_BREAK);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Word is an array of letters, like ['.', '.-', '-']
|
|
|
|
async function playWord(word, currentPlayCounter) {
|
|
|
|
// console.log('playSentence with', playCounter, currentPlayCounter);
|
|
|
|
for (let i = 0; i < word.length; i++) {
|
|
|
|
if (currentPlayCounter != playCounter) { return; }
|
|
|
|
await playLetter(word[i], currentPlayCounter);
|
|
|
|
await sleep(LETTER_BREAK);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sentence is an array of words.
|
|
|
|
// Ex. "dog is good" -> [['-..', '---', '--.'], ['..', '...'], ['--.', '---', '---', '-..']]
|
|
|
|
async function playSentence(sentence, currentPlayCounter) {
|
|
|
|
// console.log('playSentence with', playCounter, currentPlayCounter);
|
|
|
|
if (currentPlayCounter != playCounter) { return; }
|
|
|
|
// Slight pause before it starts
|
|
|
|
// console.log('playSentence with', playCounter, currentPlayCounter);
|
|
|
|
await sleep(LETTER_BREAK);
|
|
|
|
// console.log('playSentence with', playCounter, currentPlayCounter);
|
|
|
|
for (let i = 0; i < sentence.length; i++) {
|
|
|
|
if (currentPlayCounter != playCounter) { return; }
|
|
|
|
await playWord(sentence[i], currentPlayCounter);
|
|
|
|
await sleep(WORD_BREAK);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// asciiChar is something like 'd'
|
|
|
|
// Assumes [a-z0-9]
|
|
|
|
function convertAsciiCharToMorse(asciiChar) {
|
|
|
|
return MORSE_MAP[asciiChar];
|
|
|
|
}
|
|
|
|
|
|
|
|
// asciiWord is something like 'dog'
|
|
|
|
// Assumes [a-z0-9]
|
|
|
|
function convertAsciiWordToMorse(asciiWord) {
|
|
|
|
return asciiWord.split('').map(convertAsciiCharToMorse);
|
|
|
|
}
|
|
|
|
|
|
|
|
// asciiSentence is something like 'dog is good'
|
|
|
|
// Assumes words are separated by spaces.
|
|
|
|
function convertAsciiSentenceToMorse(asciiSentence) {
|
2022-03-11 17:51:24 +00:00
|
|
|
let splitSentence = asciiSentence.toUpperCase().split(' ');
|
2022-02-28 21:16:41 +00:00
|
|
|
return splitSentence.map(convertAsciiWordToMorse);
|
|
|
|
}
|
|
|
|
|
2024-09-03 02:35:20 +00:00
|
|
|
function getRandomSymbol() {
|
|
|
|
const randomIndex = Math.floor(Math.random() * SYMBOLS.length);
|
|
|
|
return SYMBOLS[randomIndex];
|
|
|
|
}
|
|
|
|
|
2022-02-28 21:16:41 +00:00
|
|
|
function getRandomLetter() {
|
|
|
|
const randomIndex = Math.floor(Math.random() * 26);
|
|
|
|
return Object.keys(MORSE_MAP)[randomIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
function getRandomWord() {
|
|
|
|
const randomIndex = Math.floor(Math.random() * EASY_WORDS.length);
|
|
|
|
return EASY_WORDS[randomIndex];
|
|
|
|
}
|
|
|
|
|
2024-05-09 03:16:39 +00:00
|
|
|
function getRandomMorseWord() {
|
|
|
|
const randomIndex = Math.floor(Math.random() * MORSE_WORDS.length);
|
|
|
|
return MORSE_WORDS[randomIndex];
|
|
|
|
}
|
|
|
|
|
2022-02-28 21:16:41 +00:00
|
|
|
function getRandomEasyWords() {
|
|
|
|
const totalLength = Math.floor(Math.random() * 3) + 2;
|
|
|
|
let finalSentence = ''
|
|
|
|
for (let i = 0; i < totalLength; i++) {
|
|
|
|
finalSentence += EASY_WORDS[Math.floor(Math.random() * EASY_WORDS.length)];
|
|
|
|
if (i < totalLength - 1) {
|
|
|
|
finalSentence += ' ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return finalSentence;
|
|
|
|
}
|
|
|
|
|
2024-05-09 03:16:39 +00:00
|
|
|
function getRandomMorseWords() {
|
2022-02-28 21:16:41 +00:00
|
|
|
const totalLength = Math.floor(Math.random() * 4) + 2;
|
|
|
|
let finalSentence = ''
|
|
|
|
for (let i = 0; i < totalLength; i++) {
|
2024-05-09 03:16:39 +00:00
|
|
|
finalSentence += MORSE_WORDS[Math.floor(Math.random() * MORSE_WORDS.length)];
|
2022-02-28 21:16:41 +00:00
|
|
|
if (i < totalLength - 1) {
|
|
|
|
finalSentence += ' ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return finalSentence;
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateSpeed() {
|
|
|
|
const useFarnsworth = document.querySelector('input[name="farnsworth"]:checked').value == 'yes';
|
|
|
|
// Get the difficulty and update the constants.
|
|
|
|
const difficulty = document.querySelector('input[name="speed"]:checked').value
|
|
|
|
if (useFarnsworth) {
|
|
|
|
DOT_TIME = 60;
|
|
|
|
DASH_TIME = DOT_TIME * 3;
|
|
|
|
SYMBOL_BREAK = DOT_TIME;
|
|
|
|
let letterBreakMultiplier;
|
|
|
|
switch (difficulty) {
|
|
|
|
case 'easy':
|
|
|
|
letterBreakMultiplier = 24;
|
|
|
|
break;
|
|
|
|
case 'medium':
|
|
|
|
letterBreakMultiplier = 12;
|
|
|
|
break;
|
|
|
|
case 'hard':
|
|
|
|
letterBreakMultiplier = 6;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
letterBreakMultiplier = 3;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
LETTER_BREAK = DOT_TIME * letterBreakMultiplier;
|
|
|
|
WORD_BREAK = DOT_TIME * (letterBreakMultiplier * 2.5);
|
|
|
|
} else {
|
|
|
|
switch (difficulty) {
|
|
|
|
case 'easy':
|
|
|
|
DOT_TIME = 300;
|
|
|
|
break;
|
|
|
|
case 'medium':
|
|
|
|
DOT_TIME = 200;
|
|
|
|
break;
|
|
|
|
case 'hard':
|
|
|
|
DOT_TIME = 100;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DOT_TIME = 60;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
DASH_TIME = DOT_TIME * 3;
|
|
|
|
SYMBOL_BREAK = DOT_TIME;
|
|
|
|
LETTER_BREAK = DOT_TIME * 3;
|
|
|
|
WORD_BREAK = DOT_TIME * 7;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getTarget() {
|
|
|
|
let target;
|
|
|
|
// Get the difficulty and assign a random target.
|
|
|
|
const difficulty = document.querySelector('input[name="difficulty"]:checked').value
|
|
|
|
switch (difficulty) {
|
|
|
|
case 'easy':
|
|
|
|
target = getRandomLetter();
|
|
|
|
break;
|
|
|
|
case 'medium':
|
|
|
|
target = getRandomWord();
|
|
|
|
break;
|
|
|
|
case 'hard':
|
|
|
|
target = getRandomEasyWords();
|
|
|
|
break;
|
2024-05-09 03:16:39 +00:00
|
|
|
case 'morse-easy':
|
|
|
|
target = getRandomMorseWord();
|
|
|
|
break;
|
|
|
|
case 'morse-hard':
|
|
|
|
target = getRandomMorseWords();
|
|
|
|
break;
|
2024-09-03 02:35:20 +00:00
|
|
|
case 'symbols':
|
|
|
|
target = getRandomSymbol();
|
|
|
|
console.log(target);
|
|
|
|
break;
|
2022-02-28 21:16:41 +00:00
|
|
|
default:
|
|
|
|
target = getRandomWords();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
|
|
|
|
var startButton = document.getElementById('playGame');
|
|
|
|
|
|
|
|
class ListeningGame {
|
|
|
|
constructor(wordInput, statusElement, submitButton, resetButton) {
|
|
|
|
this.stopped = false;
|
|
|
|
this.messageFound = false;
|
|
|
|
|
|
|
|
// DOM elements we'll interact with
|
|
|
|
this.wordInput = wordInput;
|
|
|
|
this.statusElement = statusElement;
|
|
|
|
this.submitButton = submitButton;
|
|
|
|
this.resetButton = resetButton;
|
|
|
|
|
|
|
|
// Bind listeners to `this`.
|
|
|
|
this.inputListener = this.inputListener.bind(this);
|
|
|
|
this.submit = this.submit.bind(this);
|
|
|
|
this.resetButtonListener = this.resetButtonListener.bind(this);
|
|
|
|
|
|
|
|
// Initialize listeners
|
|
|
|
this.wordInput.addEventListener('keyup', this.inputListener);
|
|
|
|
this.submitButton.addEventListener('click', this.submit);
|
|
|
|
this.resetButton.addEventListener('click', this.resetButtonListener);
|
|
|
|
this.wordInput.removeAttribute('disabled');
|
|
|
|
this.submitButton.removeAttribute('disabled');
|
|
|
|
this.resetButton.removeAttribute('disabled');
|
|
|
|
|
|
|
|
this.target = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
startNewGame() {
|
|
|
|
// Start listening
|
|
|
|
if (!audioContextInitialized) {
|
|
|
|
initializeAudioContext();
|
|
|
|
}
|
|
|
|
// Focus on the word input box.
|
|
|
|
this.wordInput.focus();
|
|
|
|
// Update speed based on difficulty.
|
|
|
|
updateSpeed();
|
|
|
|
// Get a new target
|
|
|
|
this.target = getTarget();
|
|
|
|
// console.log('target: ', this.target);
|
|
|
|
// Reset
|
|
|
|
this.messageFound = false;
|
|
|
|
this.wordInput.value = '';
|
|
|
|
this.statusElement.textContent = 'Waiting...';
|
|
|
|
this.playTarget();
|
|
|
|
}
|
|
|
|
|
|
|
|
submit(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
if (this.messageFound) {
|
|
|
|
// If we found it already, get a new target and play it.
|
|
|
|
this.startNewGame();
|
|
|
|
} else {
|
|
|
|
const enteredWord = this.wordInput.value;
|
|
|
|
// console.log('enteredWord: ', enteredWord, 'target: ', this.target);
|
2024-09-03 01:33:30 +00:00
|
|
|
if (enteredWord.trim().toUpperCase() == this.target.trim().toUpperCase()) {
|
2022-02-28 21:16:41 +00:00
|
|
|
// If they got the word, show message and switch event listener.
|
|
|
|
this.statusElement.textContent = 'Correct! Press enter to play a new round.';
|
|
|
|
this.messageFound = true;
|
|
|
|
// Extra playCounter increase to stop any current playing.
|
|
|
|
this.stopCurrentPlaying();
|
|
|
|
} else {
|
|
|
|
// If they didn't, clear the text box and let them try again.
|
|
|
|
this.statusElement.textContent = 'Not quite! Try again.';
|
|
|
|
this.replay();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stopCurrentPlaying() {
|
|
|
|
// console.log('stopping current playing');
|
|
|
|
// Also increase playCounter to stop any current playing. Wastes a count, but we have the bits to spare.
|
|
|
|
playCounter += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
async replay() {
|
|
|
|
if (this.messageFound) {
|
|
|
|
this.startNewGame();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Stop the current playing, and play the target again.
|
|
|
|
this.stopCurrentPlaying();
|
|
|
|
this.wordInput.focus();
|
|
|
|
this.playTarget();
|
|
|
|
}
|
|
|
|
|
|
|
|
resetButtonListener(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
this.replay();
|
|
|
|
}
|
|
|
|
|
|
|
|
inputListener(event) {
|
|
|
|
if (this.messageFound) {
|
|
|
|
this.startNewGame();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (event.key == 'Control') {
|
|
|
|
this.replay();
|
|
|
|
} else if (event.key == 'Enter') {
|
|
|
|
this.submit(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
playTarget() {
|
|
|
|
playCounter += 1;
|
|
|
|
// console.log('playing with counter', playCounter)
|
|
|
|
playSentence(convertAsciiSentenceToMorse(this.target), playCounter);
|
|
|
|
}
|
|
|
|
|
|
|
|
stopGame() {
|
|
|
|
this.stopCurrentPlaying();
|
|
|
|
this.wordInput.value = '';
|
|
|
|
this.statusElement.textContent = 'Press Start to begin.';
|
|
|
|
this.wordInput.removeEventListener('keyup', this.inputListener);
|
|
|
|
this.submitButton.removeEventListener('click', this.submit);
|
|
|
|
this.resetButton.removeEventListener('click', this.resetButtonListener);
|
|
|
|
this.wordInput.setAttribute('disabled', 'disabled');
|
|
|
|
this.submitButton.setAttribute('disabled', 'disabled');
|
|
|
|
this.resetButton.setAttribute('disabled', 'disabled');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var currentGame;
|
|
|
|
|
|
|
|
var playListeningGameButton = document.getElementById("playListeningGame");
|
|
|
|
|
|
|
|
async function playListeningGame() {
|
|
|
|
// Check if there's a currentGame already. If there is, stop it!
|
|
|
|
if (currentGame) {
|
|
|
|
currentGame.stopGame();
|
|
|
|
}
|
|
|
|
|
|
|
|
const wordInput = document.getElementById('wordInput');
|
|
|
|
const status = document.getElementById('status');
|
|
|
|
const submitButton = document.getElementById('submitButton');
|
|
|
|
const resetButton = document.getElementById('resetButton');
|
|
|
|
|
|
|
|
currentGame = new ListeningGame(wordInput, status, submitButton, resetButton);
|
|
|
|
|
|
|
|
currentGame.startNewGame();
|
|
|
|
|
|
|
|
playListeningGameButton.removeEventListener('click', playListeningGame);
|
|
|
|
playListeningGameButton.addEventListener('click', stopListeningGame);
|
|
|
|
playListeningGameButton.textContent = 'Stop';
|
|
|
|
}
|
|
|
|
|
|
|
|
async function stopListeningGame() {
|
|
|
|
if (currentGame) {
|
|
|
|
// Stop current game
|
|
|
|
currentGame.stopGame();
|
|
|
|
}
|
|
|
|
|
|
|
|
playListeningGameButton.removeEventListener('click', stopListeningGame);
|
|
|
|
playListeningGameButton.addEventListener('click', playListeningGame);
|
|
|
|
playListeningGameButton.textContent = 'Start';
|
|
|
|
}
|
|
|
|
|
|
|
|
playListeningGameButton.addEventListener('click', playListeningGame);
|
|
|
|
|
|
|
|
class InputGame {
|
|
|
|
constructor(signalButton, startOverButton, targetDisplay, inputDisplay, inputStatus) {
|
|
|
|
this.signalButton = signalButton;
|
|
|
|
this.startOverButton = startOverButton;
|
|
|
|
this.targetDisplay = targetDisplay;
|
|
|
|
this.inputDisplay = inputDisplay;
|
|
|
|
this.inputStatus = inputStatus;
|
|
|
|
|
|
|
|
this.target = '';
|
|
|
|
|
|
|
|
// Stack for keeping track of dots and dashes that make up letter
|
|
|
|
this.dotAndDashStack = [];
|
|
|
|
// Stack for keeping track of letters that make up word
|
|
|
|
this.letterStack = [];
|
|
|
|
|
|
|
|
this.lastKeyDownTime = 0;
|
|
|
|
this.lastKeyUpTime = 0;
|
|
|
|
this.letterTimeout = null;
|
|
|
|
this.spaceTimeout = null;
|
|
|
|
|
|
|
|
this.matchFound = false;
|
|
|
|
|
|
|
|
// Keep track of where the spacebar is to prevent duplicate keydowns when you hold it down.
|
|
|
|
this.spacebarDown = false;
|
|
|
|
// Keep track of if the tap key is down or not to prevent weird state issues when restarting game.
|
|
|
|
// this.keyIsDown = false;
|
|
|
|
|
|
|
|
this.documentKeydownListener = this.documentKeydownListener.bind(this);
|
|
|
|
this.documentKeyupListener = this.documentKeyupListener.bind(this);
|
|
|
|
this.buttonMousedownListener = this.buttonMousedownListener.bind(this);
|
|
|
|
this.buttonMouseupListener = this.buttonMouseupListener.bind(this);
|
|
|
|
this.startOver = this.startOver.bind(this);
|
|
|
|
this.handleLetterTimeout = this.handleLetterTimeout.bind(this);
|
|
|
|
this.handleSpaceTimeout = this.handleSpaceTimeout.bind(this);
|
|
|
|
|
|
|
|
document.addEventListener('keydown', this.documentKeydownListener);
|
|
|
|
document.addEventListener('keyup', this.documentKeyupListener);
|
|
|
|
this.signalButton.addEventListener('mousedown', this.buttonMousedownListener);
|
|
|
|
this.signalButton.addEventListener('mouseup', this.buttonMouseupListener);
|
|
|
|
this.signalButton.addEventListener('touchstart', this.buttonMousedownListener);
|
|
|
|
this.signalButton.addEventListener('touchend', this.buttonMouseupListener);
|
|
|
|
this.startOverButton.addEventListener('click', this.startOver);
|
|
|
|
}
|
|
|
|
|
|
|
|
startNewGame() {
|
|
|
|
this.matchFound = false;
|
|
|
|
this.startOver();
|
|
|
|
// Update speed based on difficulty.
|
|
|
|
updateSpeed();
|
|
|
|
// Get target word
|
|
|
|
this.target = getTarget();
|
|
|
|
// console.log('target: ', this.target);
|
|
|
|
// Update target display with the new target
|
|
|
|
this.targetDisplay.textContent = 'Target: ' + this.target;
|
|
|
|
this.inputDisplay.textContent = '';
|
|
|
|
this.inputStatus.textContent = '';
|
|
|
|
|
|
|
|
this.signalButton.removeAttribute('disabled');
|
|
|
|
this.startOverButton.removeAttribute('disabled');
|
|
|
|
// Focus on the tap button
|
|
|
|
this.signalButton.focus();
|
|
|
|
|
|
|
|
// Start listening
|
|
|
|
if (!audioContextInitialized) {
|
|
|
|
initializeAudioContext();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
keyDown() {
|
|
|
|
if (this.matchFound) {
|
|
|
|
// console.log('match found! doing nothing on keydown')
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
startNotePlaying();
|
|
|
|
const currentTime = Date.now();
|
|
|
|
// Update the marker for the last signal ending
|
|
|
|
this.lastKeyDownTime = currentTime;
|
|
|
|
// Get the total silence time since we last had a signal
|
|
|
|
const silenceDelta = currentTime - this.lastKeyUpTime;
|
|
|
|
this.handleSilence(silenceDelta);
|
|
|
|
// We also want to clear any pending intervals so we don't duplicate them.
|
|
|
|
clearTimeout(this.letterTimeout);
|
|
|
|
clearTimeout(this.spaceTimeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
keyUp() {
|
|
|
|
if (this.matchFound) {
|
|
|
|
this.startNewGame();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// console.log('keyup!');
|
|
|
|
stopNotePlaying();
|
|
|
|
const currentTime = Date.now();
|
|
|
|
// Update the marker for the last silence ending
|
|
|
|
this.lastKeyUpTime = currentTime;
|
|
|
|
// Get the total signal time since we last had silence
|
|
|
|
const signalDelta = currentTime - this.lastKeyDownTime;
|
|
|
|
this.handleSignal(signalDelta);
|
|
|
|
|
|
|
|
// TODO: start timer
|
|
|
|
this.letterTimeout = setTimeout(this.handleLetterTimeout, LETTER_BREAK);
|
|
|
|
this.spaceTimeout = setTimeout(this.handleSpaceTimeout, WORD_BREAK);
|
|
|
|
}
|
|
|
|
|
|
|
|
handleLetterTimeout() {
|
|
|
|
this.handleSilence(LETTER_BREAK);
|
|
|
|
}
|
|
|
|
|
|
|
|
handleSpaceTimeout() {
|
|
|
|
this.handleSilence(WORD_BREAK)
|
|
|
|
}
|
|
|
|
|
|
|
|
handleSignal(signalDelta) {
|
|
|
|
// console.log('signalDelta: ', signalDelta)
|
|
|
|
// See if it's a dot or a dash
|
|
|
|
if (signalDelta < DASH_TIME) {
|
|
|
|
this.dotAndDashStack.push('.');
|
|
|
|
} else {
|
|
|
|
this.dotAndDashStack.push('-');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleSilence(silenceDelta) {
|
|
|
|
// If there's no input yet, don't do anything
|
|
|
|
if (this.dotAndDashStack.length == 0 && this.letterStack.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// console.log('silenceDelta: ', silenceDelta, 'LETTER_BREAK: ', LETTER_BREAK);
|
|
|
|
// If we have at least a letter break, try to add a new character if we have any dots or dashes
|
|
|
|
if (silenceDelta >= LETTER_BREAK && this.dotAndDashStack.length != 0) {
|
|
|
|
// TODO: handle letter being done.
|
|
|
|
const currentLetterCombo = this.dotAndDashStack.join('');
|
|
|
|
if (currentLetterCombo in REVERSE_MORSE_MAP) {
|
2024-09-03 03:33:23 +00:00
|
|
|
if (REVERSE_MORSE_MAP[currentLetterCombo] == "clear") {
|
|
|
|
this.letterStack = [];
|
|
|
|
} else {
|
|
|
|
this.letterStack.push(REVERSE_MORSE_MAP[currentLetterCombo]);
|
|
|
|
}
|
2022-02-28 21:16:41 +00:00
|
|
|
} else {
|
|
|
|
// Push a question mark
|
|
|
|
this.letterStack.push('?');
|
|
|
|
}
|
|
|
|
// Clear current stack.
|
|
|
|
this.dotAndDashStack = [];
|
|
|
|
}
|
|
|
|
// If we have more than a word break, we may also want to add a space after doing the above.
|
|
|
|
// We may want to do this without dots and dashes (e.g. the timeout), but only if the last char
|
|
|
|
// isn't a space
|
|
|
|
if (silenceDelta >= WORD_BREAK &&
|
|
|
|
this.letterStack.length != 0 &&
|
|
|
|
this.letterStack[this.letterStack.length - 1] != ' ') {
|
|
|
|
this.letterStack.push(' ');
|
|
|
|
}
|
|
|
|
// Update the display.
|
|
|
|
this.inputDisplay.textContent = this.letterStack.join('');
|
|
|
|
this.checkForMatch();
|
|
|
|
}
|
|
|
|
|
|
|
|
documentKeydownListener(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
// console.log('document keydown');
|
|
|
|
if (event.key == ' ' && !this.spacebarDown) {
|
|
|
|
// console.log('sending keydown');
|
|
|
|
this.spacebarDown = true;
|
|
|
|
this.keyDown();
|
|
|
|
} else if (event.key == 'Control') {
|
|
|
|
this.startOver();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
documentKeyupListener(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
if (this.matchFound) {
|
|
|
|
this.spacebarDown = false;
|
|
|
|
this.startNewGame();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// console.log('document keyup');
|
|
|
|
// console.log(this);
|
|
|
|
if (event.key == ' ') {
|
|
|
|
this.spacebarDown = false;
|
|
|
|
this.keyUp();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buttonMousedownListener(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
// console.log('button mousedown');
|
|
|
|
// console.log(this);
|
|
|
|
this.keyDown();
|
|
|
|
}
|
|
|
|
|
|
|
|
buttonMouseupListener(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
// console.log('button mouseup');
|
|
|
|
// console.log(this);
|
|
|
|
this.keyUp();
|
|
|
|
}
|
|
|
|
|
|
|
|
startOver(event) {
|
|
|
|
if (event) {
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
if (this.matchFound) {
|
|
|
|
this.startNewGame();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.inputDisplay.textContent = '';
|
|
|
|
this.inputStatus.textContent = '';
|
|
|
|
this.dotAndDashStack = [];
|
|
|
|
this.letterStack = [];
|
|
|
|
this.lastKeyDownTime = 0;
|
|
|
|
this.lastKeyUpTime = 0;
|
|
|
|
|
|
|
|
clearTimeout(this.letterTimeout);
|
|
|
|
clearTimeout(this.spaceTimeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
checkForMatch() {
|
|
|
|
// console.log('checking for match');
|
|
|
|
// console.log('target: \'' + this.target + '\'');
|
|
|
|
// console.log('this.letterStack.join(\'\'): \'' + this.letterStack.join('') + '\'');
|
|
|
|
// console.log(this.letterStack.join('') == this.target);
|
2024-05-09 03:50:09 +00:00
|
|
|
if (this.letterStack.join('').toUpperCase() == this.target.toUpperCase()) {
|
2022-02-28 21:16:41 +00:00
|
|
|
// console.log('match!');
|
|
|
|
// Clear intervals
|
|
|
|
clearTimeout(this.letterTimeout);
|
|
|
|
clearTimeout(this.spaceTimeout);
|
|
|
|
|
|
|
|
// Show message
|
|
|
|
this.inputStatus.textContent = 'You got it! Press any key or click either button to start a new round.';
|
|
|
|
this.matchFound = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stopGame() {
|
|
|
|
this.startOver();
|
|
|
|
this.targetDisplay.textContent = 'Press Start to begin.';
|
|
|
|
this.inputStatus.textContent = '';
|
|
|
|
document.removeEventListener('keydown', this.documentKeydownListener);
|
|
|
|
document.removeEventListener('keyup', this.documentKeyupListener);
|
|
|
|
this.signalButton.removeEventListener('mousedown', this.buttonMousedownListener);
|
|
|
|
this.signalButton.removeEventListener('mouseup', this.buttonMouseupListener);
|
|
|
|
this.signalButton.removeEventListener('touchstart', this.buttonMousedownListener);
|
|
|
|
this.signalButton.removeEventListener('touchend', this.buttonMouseupListener);
|
|
|
|
this.startOverButton.removeEventListener('click', this.startOver);
|
|
|
|
this.signalButton.setAttribute('disabled', 'disabled');
|
|
|
|
this.startOverButton.setAttribute('disabled', 'disabled');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var playInputGameButton = document.getElementById("playInputGame");
|
|
|
|
|
|
|
|
async function playInputGame() {
|
|
|
|
// TODO: check if there's a currentGame already. If there is, stop it!
|
|
|
|
if (currentGame) {
|
|
|
|
currentGame.stopGame();
|
|
|
|
}
|
|
|
|
|
|
|
|
const signalButton = document.getElementById('signalButton');
|
|
|
|
const startOverButton = document.getElementById('startOverButton');
|
|
|
|
const targetDisplay = document.getElementById('targetDisplay');
|
|
|
|
const inputDisplay = document.getElementById('inputDisplay');
|
|
|
|
const inputStatus = document.getElementById('inputStatus');
|
|
|
|
|
|
|
|
currentGame = new InputGame(signalButton, startOverButton, targetDisplay, inputDisplay, inputStatus);
|
|
|
|
|
|
|
|
currentGame.startNewGame();
|
|
|
|
|
|
|
|
playInputGameButton.removeEventListener('click', playInputGame);
|
|
|
|
playInputGameButton.addEventListener('click', stopInputGame);
|
|
|
|
playInputGameButton.textContent = 'Stop';
|
|
|
|
}
|
|
|
|
|
|
|
|
async function stopInputGame() {
|
|
|
|
if (currentGame) {
|
|
|
|
// Stop current game
|
|
|
|
currentGame.stopGame();
|
|
|
|
}
|
|
|
|
|
|
|
|
playInputGameButton.removeEventListener('click', stopInputGame);
|
|
|
|
playInputGameButton.addEventListener('click', playInputGame);
|
|
|
|
playInputGameButton.textContent = 'Start';
|
|
|
|
}
|
|
|
|
|
|
|
|
playInputGameButton.addEventListener('click', playInputGame);
|
|
|
|
|
|
|
|
// For iOS, you need a touch event before you can play audio!
|
|
|
|
var initializeAudioOnTouch = function() {
|
|
|
|
if (!audioContextInitialized) {
|
|
|
|
initializeAudioContext();
|
|
|
|
}
|
|
|
|
document.removeEventListener('touchend', initializeAudioOnTouch);
|
|
|
|
}
|
2024-05-09 03:16:39 +00:00
|
|
|
document.addEventListener('touchend', initializeAudioOnTouch);
|