diff --git a/index.php b/index.php index cb64fa5..4efb465 100644 --- a/index.php +++ b/index.php @@ -1,11 +1,12 @@ addVersionToJsImports($version); } diff --git a/src/assets/styles/virtual-fretboard.css b/src/assets/styles/virtual-fretboard.css index c8e9243..f814731 100644 --- a/src/assets/styles/virtual-fretboard.css +++ b/src/assets/styles/virtual-fretboard.css @@ -1,10 +1,14 @@ @media (min-width: 100px) { + #treble-clef-output { + display: none; + } + #fretboard { display: flex; flex-direction: column; - width: 80%; + width: 100%; margin: auto; - border-right: 3px solid lightgrey; + padding-right: 40px; } .string { @@ -15,15 +19,21 @@ justify-content: space-between; position: relative; height: 30px; + border-right: 3px solid #b7b7b7; + } .fret { - width: calc(100% / 12); + flex-grow: 1; height: 100%; border-left: 1px solid white; border-right: 1px solid white; margin: 1px 0; cursor: pointer; + position: relative; + } + .fret:hover, .string-name:hover{ + background-color: rgba(255, 255, 255, 0.1); } .string hr { @@ -33,4 +43,43 @@ width: 100%; height: 1px; } + + .string-name{ + position: absolute; + right: -40px; + font-size: 20px; + color: #d0d0d0; + width: 40px; + cursor: pointer; + } + .fret-helper{ + top: -10px; + /*left: 50%;*/ + max-width: 10px; + height: 10px; + background-color: #868686; + border-radius: 50%; + position: absolute; + left: 50%; + transform: translateX(-50%); + } + .key-note-indicator{ + max-width: 10px; + height: 10px; + background-color: #005400; + border: 1px solid #129b00; + /*border-radius: 50%;*/ + bottom: 50%; + position: absolute; + left: 50%; + transform: translateX(-50%) translateY(50%); + z-index: 2; + } + +} + +@media (min-width: 961px) { + #fretboard { + width: 80%; + } } diff --git a/src/features/detected-note/detected-note-verifier.js b/src/features/detected-note/detected-note-verifier.js index f6d6b31..74df881 100644 --- a/src/features/detected-note/detected-note-verifier.js +++ b/src/features/detected-note/detected-note-verifier.js @@ -1,5 +1,5 @@ -import {DetectedNoteVisualizer} from "./detected-note-visualizer.js?v=1.2.6"; -import {NoteCombinationVisualizer} from "../game-core/game-ui/note-combination-visualizer.js?v=1.2.6"; +import {DetectedNoteVisualizer} from "./detected-note-visualizer.js?v=1.3.0"; +import {NoteCombinationVisualizer} from "../game-core/game-ui/note-combination-visualizer.js?v=1.3.0"; export class DetectedNoteVerifier { // Variable is set in note-combination-coordinator each time new note is displayed diff --git a/src/features/game-core/frequency-bars/frequency-bars-controller.js b/src/features/game-core/frequency-bars/frequency-bars-controller.js index 1df2c09..c6a797a 100644 --- a/src/features/game-core/frequency-bars/frequency-bars-controller.js +++ b/src/features/game-core/frequency-bars/frequency-bars-controller.js @@ -1,4 +1,4 @@ -import {FrequencyBarsVisualizer} from "./frequency-bars-visualizer.js?v=1.2.6"; +import {FrequencyBarsVisualizer} from "./frequency-bars-visualizer.js?v=1.3.0"; export class FrequencyBarsController { diff --git a/src/features/game-core/game-initialization/core-game-coordination-initializer.js b/src/features/game-core/game-initialization/core-game-coordination-initializer.js index fa8d6a0..c5e7b27 100644 --- a/src/features/game-core/game-initialization/core-game-coordination-initializer.js +++ b/src/features/game-core/game-initialization/core-game-coordination-initializer.js @@ -1,19 +1,14 @@ -import {CoreGameCoordinator} from "../game-start/core-game-coordinator.js?v=1.2.6"; +import {CoreGameCoordinator} from "../game-start/core-game-coordinator.js?v=1.3.0"; import { FretboardNoteGameCoordinator -} from "../../game-modes/note-on-fretboard/fretboard-note-game-coordinator.js?v=1.2.6"; -import {NoteInKeyGameCoordinator} from "../../game-modes/note-in-key/note-in-key-game-coordinator.js?v=1.2.6"; -import {GameConfigurationManager} from "./game-configuration-manager.js?v=1.2.6"; -import {GameElementsVisualizer} from "../game-ui/game-elements-visualizer.js?v=1.2.6"; -import {MetronomePracticeCoordinator} from "../../game-modes/metronome/metronome-practice-coordinator.js?v=1.2.6"; +} from "../../game-modes/note-on-fretboard/fretboard-note-game-coordinator.js?v=1.3.0"; +import {NoteInKeyGameCoordinator} from "../../game-modes/note-in-key/note-in-key-game-coordinator.js?v=1.3.0"; +import {GameConfigurationManager} from "./game-configuration-manager.js?v=1.3.0"; +import {MetronomePracticeCoordinator} from "../../game-modes/metronome/metronome-practice-coordinator.js?v=1.3.0"; export class CoreGameCoordinationInitializer { - /** - * @param {GameInitializer} gameInitializer - */ - constructor(gameInitializer) { - this.gameInitializer = gameInitializer; + constructor() { this.coreGameCoordinator = new CoreGameCoordinator(this); } @@ -22,69 +17,55 @@ export class CoreGameCoordinationInitializer { * All game coordinators MUST implement destroy(), play() and stop() methods (setup is in constructor) */ setCorrectAndInitGameCoordinator() { - // Remove game mode specific elements / reset for new game mode - if (this.coreGameCoordinator.gameCoordinator !== null) { - this.coreGameCoordinator.gameCoordinator?.destroy(); - // Reset game area - GameElementsVisualizer.hideGameElementsAndDisplayInstructions(); - } + // Remove game mode options from previous game mode document.querySelector('#game-mode-options').innerHTML = ''; // Make sure that the start button is enabled document.querySelector('#start-stop-btn').disabled = false; - // Default values - // Enable tuner that detects note to true as default value - this.coreGameCoordinator.noteDetectorEnabled = true; - // Figure out which game mode should be started // All game coordinators MUST implement a play() and stop() method. // Initialization is done in the constructor if (document.querySelector('#metronome-game-mode input').checked) { this.coreGameCoordinator.gameCoordinator = new MetronomePracticeCoordinator(); - this.coreGameCoordinator.noteDetectorEnabled = false; - this.coreGameCoordinator.metronomeEnabled = true; - this.coreGameCoordinator.scoreEnabled = false; - this.coreGameCoordinator.progressBarEnabled = false; + this.setMetronomeGameModeVariables(); } else if (document.querySelector('#fretboard-note-game-mode input').checked) { this.coreGameCoordinator.gameCoordinator = new FretboardNoteGameCoordinator(); - this.coreGameCoordinator.metronomeEnabled = true; - this.coreGameCoordinator.scoreEnabled = true; - this.coreGameCoordinator.progressBarEnabled = true; + this.setFretboardNoteGameModeVariables(); } else if (document.querySelector('#note-in-key-game-mode input').checked) { this.coreGameCoordinator.gameCoordinator = new NoteInKeyGameCoordinator(); - this.coreGameCoordinator.metronomeEnabled = false; - this.coreGameCoordinator.scoreEnabled = false; - this.coreGameCoordinator.progressBarEnabled = true; + // Needs to be in own function for no guitar change event handler + this.setNoteInKeyGameModeVariables(); } else { // If no game mode is selected, disable the play button document.querySelector('#start-stop-btn').disabled = true; } // All game coordinators MUST implement a play() and stop() method - // Disable metronome and note detector if no guitar option is selected - this.noGuitarOption(); - // Init game mode options after they have been added via instantiation of the correct gameModeCoordinator above GameConfigurationManager.initGameModeOptions(); } - noGuitarOption(){ - // Overwrite noteDetectorEnabled and metronomeEnabled if the user has selected "no guitar" - const noGuitarCheckbox = document.querySelector('#no-guitar-option input'); - noGuitarCheckbox?.addEventListener('change', function () { - disableNoteDetectorIfNoGuitarChecked(); - }); - const self = this; - const disableNoteDetectorIfNoGuitarChecked = function() { - if (noGuitarCheckbox?.checked) { - self.coreGameCoordinator.metronomeEnabled = false; - self.coreGameCoordinator.noteDetectorEnabled = false; - } - } - disableNoteDetectorIfNoGuitarChecked(); + setMetronomeGameModeVariables() { + this.coreGameCoordinator.noteDetectorEnabled = false; + this.coreGameCoordinator.metronomeEnabled = true; + this.coreGameCoordinator.scoreEnabled = false; + this.coreGameCoordinator.progressBarEnabled = false; + } + + setFretboardNoteGameModeVariables() { + this.coreGameCoordinator.metronomeEnabled = true; + this.coreGameCoordinator.scoreEnabled = true; + this.coreGameCoordinator.progressBarEnabled = true; + this.coreGameCoordinator.noteDetectorEnabled = true; } + setNoteInKeyGameModeVariables() { + this.coreGameCoordinator.metronomeEnabled = false; + this.coreGameCoordinator.scoreEnabled = false; + this.coreGameCoordinator.progressBarEnabled = true; + this.coreGameCoordinator.noteDetectorEnabled = true; + } /** * Start and stop game or metronome diff --git a/src/features/game-core/game-initialization/game-configuration-manager.js b/src/features/game-core/game-initialization/game-configuration-manager.js index 46706bc..988fd6b 100644 --- a/src/features/game-core/game-initialization/game-configuration-manager.js +++ b/src/features/game-core/game-initialization/game-configuration-manager.js @@ -1,4 +1,4 @@ -import {GameProgressVisualizer} from "../game-progress/game-progress-visualizer.js?v=1.2.6"; +import {GameProgressVisualizer} from "../game-progress/game-progress-visualizer.js?v=1.3.0"; export class GameConfigurationManager { diff --git a/src/features/game-core/game-initialization/game-initializer.js b/src/features/game-core/game-initialization/game-initializer.js index 7bbb097..6970914 100644 --- a/src/features/game-core/game-initialization/game-initializer.js +++ b/src/features/game-core/game-initialization/game-initializer.js @@ -1,6 +1,7 @@ -import {GameConfigurationManager} from "./game-configuration-manager.js?v=1.2.6"; -import {CoreGameCoordinationInitializer} from "./core-game-coordination-initializer.js?v=1.2.6"; -import {VisibilityChangeHandler} from "./visibility-change-handler.js?v=1.2.6"; +import {GameConfigurationManager} from "./game-configuration-manager.js?v=1.3.0"; +import {CoreGameCoordinationInitializer} from "./core-game-coordination-initializer.js?v=1.3.0"; +import {VisibilityChangeHandler} from "./visibility-change-handler.js?v=1.3.0"; +import {GameElementsVisualizer} from "../game-ui/game-elements-visualizer.js?v=1.3.0"; export class GameInitializer { constructor() { @@ -27,6 +28,12 @@ export class GameInitializer { this.coreGameCoordinationInitializer.setCorrectAndInitGameCoordinator(); document.addEventListener('game-mode-change', (e) => { + // Remove game mode specific elements / reset for new game mode + if (this.coreGameCoordinationInitializer.coreGameCoordinator.gameCoordinator !== null) { + this.coreGameCoordinationInitializer.coreGameCoordinator.gameCoordinator?.destroy(); + // Reset game area + GameElementsVisualizer.hideGameElementsAndDisplayInstructions(); + } this.coreGameCoordinationInitializer.setCorrectAndInitGameCoordinator(); }); } @@ -44,7 +51,8 @@ export class GameInitializer { // Check if the target is or if a parent of the target is
(to avoid catching dblclicks in // header, but only if the modal is not open) if ((e.target === document.body || e.target.closest('main')) - && (e.target.id !== 'modal' && !e.target.closest('#modal') && e.target.nodeName !== 'INPUT')) { + && (e.target.id !== 'modal' && !e.target.closest('#modal') && e.target.nodeName !== 'INPUT') + && !e.target.closest('#fretboard')) { self.coreGameCoordinationInitializer.startOrStopButtonActionHandler(); } }); diff --git a/src/features/game-core/game-loader.js b/src/features/game-core/game-loader.js index 2e85a13..076444c 100644 --- a/src/features/game-core/game-loader.js +++ b/src/features/game-core/game-loader.js @@ -1,4 +1,4 @@ -import {GameInitializer} from "./game-initialization/game-initializer.js?v=1.2.6"; +import {GameInitializer} from "./game-initialization/game-initializer.js?v=1.3.0"; export class GameLoader { constructor() { diff --git a/src/features/game-core/game-progress/game-progress-updater.js b/src/features/game-core/game-progress/game-progress-updater.js index 6d9fe97..92d19ce 100644 --- a/src/features/game-core/game-progress/game-progress-updater.js +++ b/src/features/game-core/game-progress/game-progress-updater.js @@ -1,4 +1,4 @@ -import {GameProgressVisualizer} from "./game-progress-visualizer.js?v=1.2.6"; +import {GameProgressVisualizer} from "./game-progress-visualizer.js?v=1.3.0"; /** * Progress update for games that use the metronome and have challenging diff --git a/src/features/game-core/game-start/core-game-coordinator.js b/src/features/game-core/game-start/core-game-coordinator.js index e723acc..fb4c3b6 100644 --- a/src/features/game-core/game-start/core-game-coordinator.js +++ b/src/features/game-core/game-start/core-game-coordinator.js @@ -1,8 +1,8 @@ -import {MetronomeOperator} from "../metronome/metronome-operator.js?v=1.2.6"; -import {TuneOperator} from "../tuner/tune-operator.js?v=1.2.6"; -import {FrequencyBarsController} from "../frequency-bars/frequency-bars-controller.js?v=1.2.6"; -import {GameElementsVisualizer} from "../game-ui/game-elements-visualizer.js?v=1.2.6"; -import {ScreenWakeLocker} from "../wake-lock/screen-wake-locker.js?v=1.2.6"; +import {MetronomeOperator} from "../metronome/metronome-operator.js?v=1.3.0"; +import {TuneOperator} from "../tuner/tune-operator.js?v=1.3.0"; +import {FrequencyBarsController} from "../frequency-bars/frequency-bars-controller.js?v=1.3.0"; +import {GameElementsVisualizer} from "../game-ui/game-elements-visualizer.js?v=1.3.0"; +import {ScreenWakeLocker} from "../wake-lock/screen-wake-locker.js?v=1.3.0"; export class CoreGameCoordinator { metronomeOperator = new MetronomeOperator(); @@ -23,8 +23,8 @@ export class CoreGameCoordinator { gameRunning = false; manuallyPaused = false; - constructor() { - + constructor(coreGameInitializer) { + this.coreGameInitializer = coreGameInitializer; // Listen for game stop event to stop game document.addEventListener('game-stop', this.stopGame.bind(this)); document.addEventListener('game-start', this.startGame.bind(this)); @@ -40,6 +40,9 @@ export class CoreGameCoordinator { this.gameRunning = true; + // Disable metronome and note detector if no guitar option is selected + this.noGuitarOption(); + // Start metronome only if tuner is fully started (if it needs to) this.startTuner().then(() => { if (this.metronomeEnabled) { @@ -126,4 +129,22 @@ export class CoreGameCoordinator { this.stopAndResumeAfterVisibilityChange = true; } } + noGuitarOption(){ + // Overwrite noteDetectorEnabled and metronomeEnabled if the user has selected "no guitar" + const noGuitarCheckbox = document.querySelector('#no-guitar-option input'); + noGuitarCheckbox?.addEventListener('change', function () { + toggleNoteDetectorIfNoGuitarChecked(); + }); + const self = this; + const toggleNoteDetectorIfNoGuitarChecked = function() { + if (noGuitarCheckbox?.checked) { + self.metronomeEnabled = false; + self.noteDetectorEnabled = false; + } else { + // Init game core game logic such as metronome, note detector + self.coreGameInitializer.setNoteInKeyGameModeVariables(); + } + } + toggleNoteDetectorIfNoGuitarChecked(); + } } \ No newline at end of file diff --git a/src/features/game-core/game-ui/game-elements-visualizer.js b/src/features/game-core/game-ui/game-elements-visualizer.js index 9852fe0..b25b10a 100644 --- a/src/features/game-core/game-ui/game-elements-visualizer.js +++ b/src/features/game-core/game-ui/game-elements-visualizer.js @@ -1,4 +1,4 @@ -import {GameLevelTracker} from "../game-progress/game-level-tracker.js?v=1.2.6"; +import {GameLevelTracker} from "../game-progress/game-level-tracker.js?v=1.3.0"; export class GameElementsVisualizer { static hideGameElementsAndDisplayInstructions() { diff --git a/src/features/game-core/game-ui/note-combination-visualizer.js b/src/features/game-core/game-ui/note-combination-visualizer.js index 94b5a85..3c7c69d 100644 --- a/src/features/game-core/game-ui/note-combination-visualizer.js +++ b/src/features/game-core/game-ui/note-combination-visualizer.js @@ -1,4 +1,4 @@ -import {TrebleClefVisualizer} from "../../treble-clef/treble-clef-visualizer.js?v=1.2.6"; +import {TrebleClefVisualizer} from "../../treble-clef/treble-clef-visualizer.js?v=1.3.0"; export class NoteCombinationVisualizer { @@ -11,7 +11,14 @@ export class NoteCombinationVisualizer { this.frequencyBarsController.updateFrequencyBarsFillStyle(color); } - static displayCombination(stringName, noteName, displayInTrebleClef = false, displayTrebleClefAndNoteName = false) { + static displayCombinationWithNoteNumber(stringName, noteNumber, noteName = null){ + const noteSpan = document.getElementById('note-span'); + noteSpan.dataset.noteName = noteName; + noteSpan.innerHTML = noteNumber; + document.getElementById('string-span').innerHTML = stringName; + } + + static displayCombinationWithNoteName(stringName, noteName, displayInTrebleClef = false, displayTrebleClefAndNoteName = false) { // Adds the treble-clef-enabled class if displayInTrebleClef is truthy, removes it otherwise document.getElementById('string-span').classList .toggle('treble-clef-enabled', displayInTrebleClef || displayTrebleClefAndNoteName); diff --git a/src/features/game-modes/metronome/metronome-practice-coordinator.js b/src/features/game-modes/metronome/metronome-practice-coordinator.js index c0850d9..d867dac 100644 --- a/src/features/game-modes/metronome/metronome-practice-coordinator.js +++ b/src/features/game-modes/metronome/metronome-practice-coordinator.js @@ -1,5 +1,5 @@ -import {MetronomePracticeInitializer} from "./metronome-practice-initializer.js?v=1.2.6"; -import {MetronomePracticeTimer} from "./metronome-practice-timer.js?v=1.2.6"; +import {MetronomePracticeInitializer} from "./metronome-practice-initializer.js?v=1.3.0"; +import {MetronomePracticeTimer} from "./metronome-practice-timer.js?v=1.3.0"; export class MetronomePracticeCoordinator { diff --git a/src/features/game-modes/metronome/metronome-practice-initializer.js b/src/features/game-modes/metronome/metronome-practice-initializer.js index 6d8ff89..25ab18e 100644 --- a/src/features/game-modes/metronome/metronome-practice-initializer.js +++ b/src/features/game-modes/metronome/metronome-practice-initializer.js @@ -1,6 +1,6 @@ -import {GameConfigurationManager} from "../../game-core/game-initialization/game-configuration-manager.js?v=1.2.6"; -import {MetronomePracticeTimer} from "./metronome-practice-timer.js?v=1.2.6"; -import {LevelUpVisualizer} from "../../game-core/game-ui/level-up-visualizer.js?v=1.2.6"; +import {GameConfigurationManager} from "../../game-core/game-initialization/game-configuration-manager.js?v=1.3.0"; +import {MetronomePracticeTimer} from "./metronome-practice-timer.js?v=1.3.0"; +import {LevelUpVisualizer} from "../../game-core/game-ui/level-up-visualizer.js?v=1.3.0"; export class MetronomePracticeInitializer { // Changed in metronome-practice-coordinator diff --git a/src/features/game-modes/note-in-key/note-in-key-game-coordinator.js b/src/features/game-modes/note-in-key/note-in-key-game-coordinator.js index 57a7d45..dbfe0d7 100644 --- a/src/features/game-modes/note-in-key/note-in-key-game-coordinator.js +++ b/src/features/game-modes/note-in-key/note-in-key-game-coordinator.js @@ -1,5 +1,5 @@ -import {NoteInKeyGameInitializer} from "./note-in-key-game-initializer.js?v=1.2.6"; -import {NoteInKeyGameNoGuitar} from "./note-in-key-game-no-guitar.js?v=1.2.6"; +import {NoteInKeyGameInitializer} from "./note-in-key-game-initializer.js?v=1.3.0"; +import {NoteInKeyGameNoGuitar} from "./note-in-key-game-no-guitar.js?v=1.3.0"; export class NoteInKeyGameCoordinator { string; @@ -37,7 +37,8 @@ export class NoteInKeyGameCoordinator { // Init event listeners that will automatically call displayNotes() when correct note has been played this.noteDisplayer.beingGame(); - NoteInKeyGameNoGuitar.playNoGuitarNoteInKey(); + // Start no guitar game if no guitar option is checked + NoteInKeyGameNoGuitar.playNoGuitarNoteInKey(this.noteInKeyGenerator.diatonicNotesOnStrings, this.keyString, this.keyNote); // Start the timer this.timerInterval = setInterval(() => { @@ -53,6 +54,9 @@ export class NoteInKeyGameCoordinator { this.gameIsRunning = false; // Hide current key and string document.querySelector('#current-key-and-string').style.display = 'none'; + if (document.querySelector('#fretboard')) { + document.querySelector('#fretboard').style.display = 'none'; + } } destroy() { diff --git a/src/features/game-modes/note-in-key/note-in-key-game-initializer.js b/src/features/game-modes/note-in-key/note-in-key-game-initializer.js index 15ae3f5..4fc5f09 100644 --- a/src/features/game-modes/note-in-key/note-in-key-game-initializer.js +++ b/src/features/game-modes/note-in-key/note-in-key-game-initializer.js @@ -1,14 +1,14 @@ -import {NoteInKeyGameCoordinator} from "./note-in-key-game-coordinator.js?v=1.2.6"; -import {LevelUpVisualizer} from "../../game-core/game-ui/level-up-visualizer.js?v=1.2.6"; -import {GameConfigurationManager} from "../../game-core/game-initialization/game-configuration-manager.js?v=1.2.6"; -import {GameProgressVisualizer} from "../../game-core/game-progress/game-progress-visualizer.js?v=1.2.6"; -import {NoteInKeyGenerator} from "./note-in-key-generator.js?v=1.2.6"; -import {PracticeNoteDisplayer} from "../../practice-note-combination/practice-note-displayer.js?v=1.2.6"; -import {NoteInKeyGameNoGuitar} from "./note-in-key-game-no-guitar.js?v=1.2.6"; +import {NoteInKeyGameCoordinator} from "./note-in-key-game-coordinator.js?v=1.3.0"; +import {LevelUpVisualizer} from "../../game-core/game-ui/level-up-visualizer.js?v=1.3.0"; +import {GameConfigurationManager} from "../../game-core/game-initialization/game-configuration-manager.js?v=1.3.0"; +import {GameProgressVisualizer} from "../../game-core/game-progress/game-progress-visualizer.js?v=1.3.0"; +import {NoteInKeyGenerator} from "./note-in-key-generator.js?v=1.3.0"; +import {PracticeNoteDisplayer} from "../../practice-note-combination/practice-note-displayer.js?v=1.3.0"; +import {NoteInKeyGameNoGuitar} from "./note-in-key-game-no-guitar.js?v=1.3.0"; export class NoteInKeyGameInitializer { - - possibleKeysOnStrings = { + // Possible keys + availableNotesOnStrings = { // String name: [possible keys for string] 'E': ['E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B', 'C'], 'A': ['A', 'A♯', 'B', 'C', 'C♯', 'D', 'D♯', 'E', 'F'], @@ -17,7 +17,7 @@ export class NoteInKeyGameInitializer { 'B': ['B', 'C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G'], 'E2': ['E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B', 'C'], }; - possibleKeysOnStringsFullFretboard = { + availableNotesOnStringsFullFretboard = { // String name: [possible keys for string] 'E': ['E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B', 'C', 'C♯', 'D', 'D♯'], 'A': ['A', 'A♯', 'B', 'C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯'], @@ -54,7 +54,7 @@ export class NoteInKeyGameInitializer { // Note in key generator initialized here in case the user clicks "pause" and wants to continue the game this.noteInKeyGameCoordinator.noteInKeyGenerator = new NoteInKeyGenerator(); - this.setPossibleKeysOnStrings(); + this.setAvailableNotesOnStrings(); // Instantiate object with note displayer function that will be called when a new note should be displayed // after a correct one has been played. @@ -68,10 +68,8 @@ export class NoteInKeyGameInitializer { document.addEventListener('leveled-up', this.levelUpEventHandler); // Init no guitar game option - if (document.querySelector('#no-guitar-option input')?.checked) { - // Add no-guitar classname to game-container - document.querySelector('#game-container').classList.add('no-guitar'); - NoteInKeyGameNoGuitar.initNoGuitarGameOption(); + if (document.querySelector('#no-guitar-option input')) { + NoteInKeyGameNoGuitar.initNoGuitarGameOption(this.noteInKeyGameCoordinator); } } @@ -80,7 +78,7 @@ export class NoteInKeyGameInitializer { const stringOptions = document.querySelectorAll('#note-in-key-game-strings-div input'); stringOptions.forEach((stringOption) => { stringOption.addEventListener('change', () => { - this.setPossibleKeysOnStrings(); + this.setAvailableNotesOnStrings(); this.reloadKeyAndStringEventHandler(); }); }); @@ -154,14 +152,14 @@ export class NoteInKeyGameInitializer { document.dispatchEvent(new Event('game-start')); } - setPossibleKeysOnStrings() { + setAvailableNotesOnStrings() { let notesOnStrings; if (document.querySelector('#note-in-key-entire-fretboard-option input')?.checked) { // Using the spread operator to create a copy of this.possibleKeysOnStrings otherwise it would // be a reference, and the original object would be modified - notesOnStrings = {...this.possibleKeysOnStringsFullFretboard}; + notesOnStrings = {...this.availableNotesOnStringsFullFretboard}; } else { - notesOnStrings = {...this.possibleKeysOnStrings}; + notesOnStrings = {...this.availableNotesOnStrings}; } // Remove strings that were not selected from notesOnStrings const strings = document.querySelectorAll('#note-in-key-game-strings-div input'); @@ -175,7 +173,9 @@ export class NoteInKeyGameInitializer { }); console.debug('Possible keys on strings', notesOnStrings); - this.noteInKeyGameCoordinator.noteInKeyGenerator.possibleStringsAndKeys = notesOnStrings; + this.noteInKeyGameCoordinator.noteInKeyGenerator.availableNotesOnStrings = notesOnStrings; + NoteInKeyGameNoGuitar.availableNotesOnStrings = notesOnStrings; + } /** @@ -243,7 +243,7 @@ export class NoteInKeyGameInitializer { // Add note in key entire fretboard option event listener document.querySelector('#note-in-key-entire-fretboard-option input') .addEventListener('change', () => { - this.setPossibleKeysOnStrings(); + this.setAvailableNotesOnStrings(); this.reloadKeyAndStringEventHandler(); }); @@ -252,6 +252,7 @@ export class NoteInKeyGameInitializer { // Has to be added before key and string are reloaded as they depend on this div existence + // Add current-key-and-string if it doesn't already exist document.querySelector('#game-progress-div').insertAdjacentHTML('afterend', ``); diff --git a/src/features/game-modes/note-in-key/note-in-key-game-no-guitar.js b/src/features/game-modes/note-in-key/note-in-key-game-no-guitar.js index eda7d63..0c23df7 100644 --- a/src/features/game-modes/note-in-key/note-in-key-game-no-guitar.js +++ b/src/features/game-modes/note-in-key/note-in-key-game-no-guitar.js @@ -1,22 +1,44 @@ -import {GameProgressVisualizer} from "../../game-core/game-progress/game-progress-visualizer.js?v=1.2.6"; +import {GameProgressVisualizer} from "../../game-core/game-progress/game-progress-visualizer.js?v=1.3.0"; export class NoteInKeyGameNoGuitar { - static initNoGuitarGameOption() { + static diatonicNotesOnStrings; + static availableNotesOnStrings; + + static initNoGuitarGameOption(noteInKeyGameCoordinator) { document.querySelector('#no-guitar-option input').addEventListener('click', () => { console.log('no guitar option changed'); + document.dispatchEvent(new Event('game-stop')); GameProgressVisualizer.resetProgress(); + noteInKeyGameCoordinator.reloadKeyAndString(); + + if (document.querySelector('#no-guitar-option input').checked) { + // Add no-guitar classname to game-container + document.querySelector('#game-container').classList.add('no-guitar'); + } else { + this.destroyNoGuitarGameOption(); + } }); } static destroyNoGuitarGameOption() { document.querySelector('#game-container').classList.remove('no-guitar'); // By removing the fretboard, the event listeners are also removed - document.querySelector('#fretboard').remove(); + document.querySelector('#fretboard')?.remove(); } - static playNoGuitarNoteInKey() { + static playNoGuitarNoteInKey(diatonicNotesOnStrings, keyString, keyNote) { + this.diatonicNotesOnStrings = diatonicNotesOnStrings; + this.keyString = keyString; + this.keyNote = keyNote; + if (document.querySelector('#no-guitar-option input')?.checked) { + + // If the fretboard already exists, remove it before adding it again in case the string options changed + if (document.querySelector('#fretboard')) { + this.destroyNoGuitarGameOption(); + } + // Add no-guitar classname to game-container document.querySelector('#game-container').classList.add('no-guitar'); this.addVirtualFretboard(); @@ -28,34 +50,113 @@ export class NoteInKeyGameNoGuitar { static addVirtualFretboard() { document.querySelector('#game-container').insertAdjacentHTML('beforeend', `
`); const fretboard = document.querySelector('#fretboard'); - let numberOfStrings = 6; - let numberOfFrets = 12; - - for (let i = 1; i <= numberOfStrings; i++) { + /** + * diatonicNotesOnStrings looks like this. + * Object is for e.g. {noteName: "G", number: 3}: + * + * E = Array(3) [{noteName: "G", number: 3}, Object, Object] + * A = Array(3) [Object, Object, Object] + * D = Array(3) [Object, Object, Object] + * G = Array(3) [Object, Object, Object] + * B = Array(3) [Object, Object, Object] + * E2 = Array(3) [Object, Object, Object] + */ + let stringIndex = 0; + // Construct fretboard with available notes on strings + for (const [stringName, notes] of Object.entries(this.availableNotesOnStrings)) { let string = document.createElement('div'); string.className = 'string'; - string.id = 'string-' + i; + string.dataset.stringName = stringName; + + // Create a new div for the string name + let stringNameDiv = document.createElement('div'); + let stringNameSpan = document.createElement('span'); + stringNameDiv.className = 'string-name'; + // Set the text content to the string name + stringNameSpan.textContent = stringName; + // Set the noteName data attribute to the first note of the string + stringNameSpan.dataset.noteName = stringName; + // Append the string name div to the string div + stringNameDiv.appendChild(stringNameSpan); + string.appendChild(stringNameDiv); - for (let j = 1; j <= numberOfFrets; j++) { + // Reverse the note array to so that the fretboard starts on the right side + let reversedNotes = [...notes].reverse(); + // Remove the last note from the reversed array copy as it's the string name + reversedNotes.pop(); + + // Store the total number of frets on the string to place indicator helpers + let totalFrets = reversedNotes.length; + + for (const index in reversedNotes) { let fret = document.createElement('div'); fret.className = 'fret'; - fret.id = 'fret-' + i + '-' + j; + fret.dataset.noteName = reversedNotes[index]; string.appendChild(fret); + + // Highlight key note + if (reversedNotes[index] === this.keyNote && stringName === this.keyString) { + let keyNoteIndicator = document.createElement('span'); + keyNoteIndicator.className = 'key-note-indicator'; + fret.appendChild(keyNoteIndicator); + } + + // If the fret index is 3, 5, 7, or 9 and the string is the first string, add a circle to the fret + // Calculate the fret number from the right + let fretNumberFromRight = totalFrets - parseInt(index); + // If the fret number from the right is 3, 5, 7, or 9 and the string is the first string, add a circle to the fret + if ([3, 5, 7, 9].includes(fretNumberFromRight) && stringIndex === 0) { + let fretHelper = document.createElement('span'); + fretHelper.className = 'fret-helper'; + fret.appendChild(fretHelper); + } + } + + // If note number 1 is the open string, color the string letter to green + if (stringName === this.keyString && this.keyNote === stringName) { + stringNameSpan.style.color = 'green'; } let hr = document.createElement('hr'); string.appendChild(hr); - fretboard.appendChild(string); - } + stringIndex++; + } } static addEventListenersToVirtualFretboard() { + // Add event listeners to the frets document.querySelectorAll('.fret').forEach(fret => { - fret.addEventListener('click', function () { - console.log('Fret clicked', fret.id); - }); + fret.addEventListener('click', this.noteClickedEventHandler); }); + // Add event listeners to the strings + document.querySelectorAll('.string-name').forEach(string => { + string.addEventListener('click', this.noteClickedEventHandler); + }); + } + + static wrongColorTimeout; + + static noteClickedEventHandler(event) { + // Clear the wrong color timeout + clearTimeout(this.wrongColorTimeout); + + // If fret indicator helper is clicked, get the note name from the parent fret + const noteName = event.target.dataset.noteName ?? event.target.closest('.fret').dataset.noteName; + const stringName = event.target.closest('.string').dataset.stringName; + // Output note and string name to console + console.log(`Note ${noteName} on string ${stringName} clicked`); + // Check if the pressed note was correct + if (document.querySelector('#note-span').dataset.noteName === noteName + && document.querySelector('#string-span').textContent === stringName) { + document.dispatchEvent(new Event('correct-note-played')); + } else { + // Color note-span orange for 700ms before reverting to default + document.querySelector('#note-span').style.color = '#a96f00'; + this.wrongColorTimeout = setTimeout(() => { + document.querySelector('#note-span').style.color = null; + }, 2000); + } } } \ No newline at end of file diff --git a/src/features/game-modes/note-in-key/note-in-key-generator.js b/src/features/game-modes/note-in-key/note-in-key-generator.js index aac6a90..834e190 100644 --- a/src/features/game-modes/note-in-key/note-in-key-generator.js +++ b/src/features/game-modes/note-in-key/note-in-key-generator.js @@ -1,8 +1,8 @@ -import {ArrayShuffler} from "../../shuffler/array-shuffler.js?v=1.2.6"; +import {ArrayShuffler} from "../../shuffler/array-shuffler.js?v=1.3.0"; export class NoteInKeyGenerator { - notesOnStrings; - possibleStringsAndKeys; + diatonicNotesOnStrings; + availableNotesOnStrings; combinationsToBeShuffled = []; shuffledCombinations; // Index of the current combination to be displayed from the shuffledCombinations array @@ -15,11 +15,11 @@ export class NoteInKeyGenerator { * Return random string and random key from that string */ getNewStringAndKey() { - console.log(`All available strings and notes`, this.possibleStringsAndKeys); - const strings = Object.keys(this.possibleStringsAndKeys); + console.debug(`All available strings and notes`, this.availableNotesOnStrings); + const strings = Object.keys(this.availableNotesOnStrings); this.string = strings[Math.floor(Math.random() * strings.length)]; // Keys for given string - const keys = this.possibleStringsAndKeys[this.string]; + const keys = this.availableNotesOnStrings[this.string]; // Get random key from possible keys for string this.key = keys[Math.floor(Math.random() * keys.length)]; return {keyString: this.string, keyNote: this.key}; @@ -33,8 +33,8 @@ export class NoteInKeyGenerator { */ loadShuffledCombinations(keyString, keyNote) { // Convert the range of possibleKeysOnStrings to the diatonic scale of the given keyNote - let diatonicNotesOnStrings = this.getPossibleStringsAndKeysInDiatonicScale( - keyNote, this.possibleStringsAndKeys + let diatonicNotesOnStrings = this.getAvailableNotesOnStringsInDiatonicScale( + keyNote, this.availableNotesOnStrings ); // const keyIndex = this.possibleKeysOnStrings[keyString].indexOf(keyNote); @@ -46,7 +46,7 @@ export class NoteInKeyGenerator { console.debug(`Diatonic notes`, diatonicNotesOnStrings); // Remove notes that are not nearby the key note, according to the difficulty level - this.notesOnStrings = this.removeNotesAccordingToDifficultyLevel(diatonicNotesOnStrings, keyIndex, difficulty); + this.diatonicNotesOnStrings = this.removeNotesAccordingToDifficultyLevel(diatonicNotesOnStrings, keyIndex, difficulty); // Fill the combinationsToBeShuffled array with all possible combinations this.createArrayWithCombinationsToBeShuffled(); @@ -61,11 +61,11 @@ export class NoteInKeyGenerator { createArrayWithCombinationsToBeShuffled() { this.combinationsToBeShuffled = []; // Shuffle the notes on each string - for (let string in this.notesOnStrings) { + for (let string in this.diatonicNotesOnStrings) { // Fretboard game mote shuffler cannot be taken as it shuffles creates combinations with all strings with // every note. // We want combinations with only specific notes going with each string. - this.combinationsToBeShuffled.push(...this.notesOnStrings[string].map(noteObject => [string, noteObject.noteName])); + this.combinationsToBeShuffled.push(...this.diatonicNotesOnStrings[string].map(noteObject => [string, noteObject.noteName])); } console.debug('Combinations to be shuffled', this.combinationsToBeShuffled); } @@ -95,21 +95,21 @@ export class NoteInKeyGenerator { // Split the string into an array containing the string and the note // let [string, note] = combination.split('|'); // Get the note number from the note object - let noteNumber = this.notesOnStrings[string].find(noteObject => noteObject.noteName === note).number; + let noteNumber = this.diatonicNotesOnStrings[string].find(noteObject => noteObject.noteName === note).number; return {stringName: string, noteName: {noteName: note, number: noteNumber}}; - const strings = Object.keys(this.notesOnStrings); + const strings = Object.keys(this.diatonicNotesOnStrings); const stringToPlayNote = strings[Math.floor(Math.random() * strings.length)]; // Available note numbers - const notes = this.notesOnStrings[stringToPlayNote]; + const notes = this.diatonicNotesOnStrings[stringToPlayNote]; // Get the number of the object chosen by randomness const noteNameAndNumber = notes[Math.floor(Math.random() * notes.length)]; return {stringName: stringToPlayNote, noteName: noteNameAndNumber}; } - getPossibleStringsAndKeysInDiatonicScale(keyNote, possibleKeysOnStrings) { + getAvailableNotesOnStringsInDiatonicScale(keyNote, possibleKeysOnStrings) { let diatonicScale = this.generateDiatonicScale(keyNote); let diatonicNotesOnStrings = []; // For each string, filter-out notes that should not be playable @@ -133,20 +133,21 @@ export class NoteInKeyGenerator { * @return {*[]} */ removeNotesAccordingToDifficultyLevel(diatonicNotesOnStrings, keyIndex, difficulty) { - let possibleNotesOnStrings = []; + let strippedDiatonicNotes = []; // Remove notes that are hard to reach, according to the difficulty level, // Calculate the start and end indices for the slice method let start = Math.max(0, keyIndex - difficulty); let end = keyIndex + difficulty; - console.log(`Key index ${keyIndex} Start ${start} End ${end}`); + console.debug(`Key index: ${keyIndex}; Start: ${start}; End ${end}`); + // console.log() for (let string in diatonicNotesOnStrings) { // Use the slice method to get the nearby notes +1 because the end index is not included in the slice - possibleNotesOnStrings[string] = diatonicNotesOnStrings[string].slice(start, end + 1); + strippedDiatonicNotes[string] = diatonicNotesOnStrings[string].slice(start, end + 1); } - console.debug(`Notes after removal. Difficulty ${difficulty}`, possibleNotesOnStrings); - this.notesOnStrings = possibleNotesOnStrings; - return possibleNotesOnStrings; + console.debug(`Notes after removal. Difficulty ${difficulty}`, strippedDiatonicNotes); + this.diatonicNotesOnStrings = strippedDiatonicNotes; + return strippedDiatonicNotes; } /** diff --git a/src/features/game-modes/note-in-key/practice-progress-updater.js b/src/features/game-modes/note-in-key/practice-progress-updater.js index 368c439..6428441 100644 --- a/src/features/game-modes/note-in-key/practice-progress-updater.js +++ b/src/features/game-modes/note-in-key/practice-progress-updater.js @@ -1,4 +1,4 @@ -import {GameProgressVisualizer} from "./game-progress-visualizer.js?v=1.2.6"; +import {GameProgressVisualizer} from "./game-progress-visualizer.js?v=1.3.0"; export class GameProgressUpdater { diff --git a/src/features/game-modes/note-on-fretboard/fretboard-note-game-combination-generator.js b/src/features/game-modes/note-on-fretboard/fretboard-note-game-combination-generator.js index 84d9dc8..dedd1be 100644 --- a/src/features/game-modes/note-on-fretboard/fretboard-note-game-combination-generator.js +++ b/src/features/game-modes/note-on-fretboard/fretboard-note-game-combination-generator.js @@ -1,5 +1,5 @@ -import {NoteShuffler} from "../../shuffler/note-shuffler.js?v=1.2.6"; -import {NoteCombinationVisualizer} from "../../game-core/game-ui/note-combination-visualizer.js?v=1.2.6"; +import {NoteShuffler} from "../../shuffler/note-shuffler.js?v=1.3.0"; +import {NoteCombinationVisualizer} from "../../game-core/game-ui/note-combination-visualizer.js?v=1.3.0"; export class FretboardNoteGameCombinationGenerator { constructor(strings, notes) { diff --git a/src/features/game-modes/note-on-fretboard/fretboard-note-game-coordinator.js b/src/features/game-modes/note-on-fretboard/fretboard-note-game-coordinator.js index cc14b70..d01e771 100644 --- a/src/features/game-modes/note-on-fretboard/fretboard-note-game-coordinator.js +++ b/src/features/game-modes/note-on-fretboard/fretboard-note-game-coordinator.js @@ -1,10 +1,10 @@ // const notes = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B']; // const strings = ['D', 'E', 'G', 'A', 'B']; -import {GameNoteDisplayer} from "../../game-note-combination/game-note-displayer.js?v=1.2.6"; -import {FretboardGameChallengingNotesProvider} from "./fretboard-game-challenging-notes-provider.js?v=1.2.6"; -import {FretboardNoteGameCombinationGenerator} from "./fretboard-note-game-combination-generator.js?v=1.2.6"; -import {FretboardNoteGameInitializer} from "./fretboard-note-game-initializer.js?v=1.2.6"; +import {GameNoteDisplayer} from "../../game-note-combination/game-note-displayer.js?v=1.3.0"; +import {FretboardGameChallengingNotesProvider} from "./fretboard-game-challenging-notes-provider.js?v=1.3.0"; +import {FretboardNoteGameCombinationGenerator} from "./fretboard-note-game-combination-generator.js?v=1.3.0"; +import {FretboardNoteGameInitializer} from "./fretboard-note-game-initializer.js?v=1.3.0"; /** * Game mode "note-on-fretboard" core logic diff --git a/src/features/game-modes/note-on-fretboard/fretboard-note-game-initializer.js b/src/features/game-modes/note-on-fretboard/fretboard-note-game-initializer.js index 88fbc3c..717e381 100644 --- a/src/features/game-modes/note-on-fretboard/fretboard-note-game-initializer.js +++ b/src/features/game-modes/note-on-fretboard/fretboard-note-game-initializer.js @@ -1,7 +1,7 @@ -import {GameLevelTracker} from "../../game-core/game-progress/game-level-tracker.js?v=1.2.6"; -import {GameElementsVisualizer} from "../../game-core/game-ui/game-elements-visualizer.js?v=1.2.6"; -import {LevelUpVisualizer} from "../../game-core/game-ui/level-up-visualizer.js?v=1.2.6"; -import {GameConfigurationManager} from "../../game-core/game-initialization/game-configuration-manager.js?v=1.2.6"; +import {GameLevelTracker} from "../../game-core/game-progress/game-level-tracker.js?v=1.3.0"; +import {GameElementsVisualizer} from "../../game-core/game-ui/game-elements-visualizer.js?v=1.3.0"; +import {LevelUpVisualizer} from "../../game-core/game-ui/level-up-visualizer.js?v=1.3.0"; +import {GameConfigurationManager} from "../../game-core/game-initialization/game-configuration-manager.js?v=1.3.0"; export class FretboardNoteGameInitializer { constructor() { diff --git a/src/features/game-note-combination/game-note-displayer.js b/src/features/game-note-combination/game-note-displayer.js index b8c3642..6f477db 100644 --- a/src/features/game-note-combination/game-note-displayer.js +++ b/src/features/game-note-combination/game-note-displayer.js @@ -1,7 +1,7 @@ -import {GameProgressUpdater} from "../game-core/game-progress/game-progress-updater.js?v=1.2.6"; -import {DetectedNoteVerifier} from "../detected-note/detected-note-verifier.js?v=1.2.6"; -import {NoteCombinationVisualizer} from "../game-core/game-ui/note-combination-visualizer.js?v=1.2.6"; -import {GameProgressVisualizer} from "../game-core/game-progress/game-progress-visualizer.js?v=1.2.6"; +import {GameProgressUpdater} from "../game-core/game-progress/game-progress-updater.js?v=1.3.0"; +import {DetectedNoteVerifier} from "../detected-note/detected-note-verifier.js?v=1.3.0"; +import {NoteCombinationVisualizer} from "../game-core/game-ui/note-combination-visualizer.js?v=1.3.0"; +import {GameProgressVisualizer} from "../game-core/game-progress/game-progress-visualizer.js?v=1.3.0"; /** * Note display coordinator when playing the "game" which @@ -98,7 +98,7 @@ export class GameNoteDisplayer { } // Display next note and string and if with treble clef - NoteCombinationVisualizer.displayCombination(stringName, noteNumber ?? noteName, + NoteCombinationVisualizer.displayCombinationWithNoteName(stringName, noteNumber ?? noteName, document.querySelector('#fretboard-note-game-treble-clef input').checked, document.querySelector('#fretboard-note-game-treble-clef-and-name input').checked ); diff --git a/src/features/practice-note-combination/practice-note-displayer.js b/src/features/practice-note-combination/practice-note-displayer.js index 044415e..f91a21f 100644 --- a/src/features/practice-note-combination/practice-note-displayer.js +++ b/src/features/practice-note-combination/practice-note-displayer.js @@ -1,6 +1,6 @@ -import {DetectedNoteVerifier} from "../detected-note/detected-note-verifier.js?v=1.2.6"; -import {NoteCombinationVisualizer} from "../game-core/game-ui/note-combination-visualizer.js?v=1.2.6"; -import {GameProgressVisualizer} from "../game-core/game-progress/game-progress-visualizer.js?v=1.2.6"; +import {DetectedNoteVerifier} from "../detected-note/detected-note-verifier.js?v=1.3.0"; +import {NoteCombinationVisualizer} from "../game-core/game-ui/note-combination-visualizer.js?v=1.3.0"; +import {GameProgressVisualizer} from "../game-core/game-progress/game-progress-visualizer.js?v=1.3.0"; /** * Note displayer for "practice" mode, which means @@ -76,7 +76,7 @@ export class PracticeNoteDisplayer { noteName = noteName.noteName; } // Display next note and string - NoteCombinationVisualizer.displayCombination(stringName, noteNumber ?? noteName); + NoteCombinationVisualizer.displayCombinationWithNoteNumber(stringName, noteNumber ?? noteName, noteName); // console.debug(`Displaying combination ${stringName}|${noteName}`); this.detectedNoteVerifier.noteToPlay = noteName; }; @@ -87,6 +87,8 @@ export class PracticeNoteDisplayer { if (firstCall) { displayNoteCombination(); } else { + // Color spans and detected note in green when correct + document.querySelector('#note-span').style.color = 'green'; setTimeout(() => { displayNoteCombination(); }, 700);