0

I have created a flip tile memory game. A sequence of tiles will flip over displaying a colour. The user must remember the sequence and repeat it. When the user selects correctly a correct Mp 3 is played. On the Iphone if the tiles are selected quickly the audio isn't being played for each touch, its as though the audio is being skipped for some. link

const elements = {
    gameContainer: $('#game-container'),
    gameMenu: $('#game-menu'),
    audioPlayer: document.querySelector('#player'),
    audioPlayer2: document.querySelector('#player2'),
    audioPlayer3: document.querySelector('#player3'),
    tiles: $('.tile'),
    correctAlert: $('#correct-alert'),
    wrongAlert: $('#wrong-alert'),
    failAlert: $('#fail-alert'),
    alertModal: $('#alert-modal'),
    stageNumber: $('.stage-number'),
    maxStageNumber: $('.max-stage-number'),
    gamemodeCheckbox: $('#gamemode-checkbox'),
    stageProgress: $('#stage-progress'),
    waitText: $('#wait-text'),
    wonAlert: $('#won'),
    goText: $('#go-text')
};

function tileClicked(tile) {
  console.dir(tile)
  // only allow clicking on tiles when game is started and game is not showing pattern
  if (!game.showing && game.started && !tile.classList.contains('flip-card-onclick')) {

    flipTile(tile);

    // check if game reached maximum number of stages i.e. game has been won
    if (game.playerMove <= game.maxStageNumber) {

      // check if current move (tile clicked) matches the tile in the generated pattern
      if (parseInt(tile.id) == game.currentGame[game.playerMove]) {
        // increase the pattern pointer
        game.playerMove++;

        // play sound when correct tile has been clicked
        elements.audioPlayer.pause();
        elements.audioPlayer.currentTime = 0;
        elements.audioPlayer.play();



        // check if we reached the end of the current pattern
        if (game.playerMove == game.currentGame.length) {
          // update the progress bar
          elements.stageProgress.css('width', `${(game.currentGame.length / game.maxStageNumber) * 100}%`);

          // show alert prompting user to go to the next stage
          elements.correctAlert.modal('show');
        }
        // current move did not match current pattern, wrong move
      } else {

        if (game.strictGamemode) {
          elements.audioPlayer2.play();
          // show fail alert and prompt to restart or exit game if strict mode has been selected
          elements.failAlert.modal('show');
        } else {
          // show wrong move alert and prompt to show pattern again
          elements.audioPlayer2.play();
          elements.wrongAlert.modal('show');
        }
      }
    }
  }
}
<!--Audio Player-->
<audio controls id="player" class="d-none">
         <source id="player-src" src="assets/audio/correct.mp3">
      </audio>
<audio controls id="player2" class="d-none">
         <source id="player-src-2" src="assets/audio/incorrect.mp3">
      </audio>
<audio controls id="player3" class="d-none">
         <source id ="player-src-3" src="assets/audio/won.mp3">
   </audio>
  • It's hard to tell without being able to recreate the situation. Could you make such a reproducible example? – Emiel Zuurbier Apr 14 '20 at 12:44
  • [link](https://drive.google.com/file/d/1NhC_jWI-mqLL4NxLwcvxAMheZVJfiMOL/view?usp=sharing) @EmielZuurbier heres a link to a screen recording of it on the iphone. I want the correct sound to play with each tap on the tile. As seen in the video we only listen to it once. – Lily Rawlings Apr 14 '20 at 13:52
  • Okay, add the video link to your question so that others can see it too. Can you be more specific of which sound is expected at what moment. How do you play the other sounds in your JS file? – Emiel Zuurbier Apr 14 '20 at 15:32
  • @EmielZuurbier I added more of the js code to my post. Im fetching the audio player via ID. – Lily Rawlings Apr 14 '20 at 15:42

1 Answers1

0

It is very hard to tell where your bug comes from, so your solution may not be so easy to find. Some research might tell you some things but otherwise there is an alternative you could try.

The Web Audio API is an interface where you can get more control over the audio you play. So in your case instead of manipulating the <audio> element, use the Web Audio API to play your audio files.

Down here I've created a snippet which utilizes this API. It currently selects all of your <audio> elements and extracts the sound into a node which then can use to play the sound. This gives you control over how the sound plays.

So here it creates an object, which is stored in the sounds constant, that holds all the names as keys and the players as values. Something like this:

const sounds {
  'correct': MediaElementAudioSourceNode,
  'incorrect': MediaElementAudioSourceNode,
  'won': MediaElementAudioSourceNode
};

Each of those MediaElementAudioSourceNode are the sounds which can be played. Later in the script there is a playSound function, which plays one of the sounds found in your sounds object.

const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();

const audioElements = document.querySelectorAll('audio');

const createAudioSources = audioElements => {
    const audioSources = {};
    for (const audioElement of audioElements) {
        const name = audioElement.dataset.name;
        const track = audioContext.createMediaElementSource(audioElement);
        audioSources[name] = track;
    }
    return audioSources;
};

const sounds = createAudioSources(audioElements);

function playSound(track) {
    const sound = sounds[track];
    if (sound === undefined) return;
    sound.connect(audioContext.destination);
    sound.start(audioContext.currentTime);
};

playSound('correct');
playSound('incorrect');
playSound('won');

So all of this could be added above your original script so that the sound files are loaded and ready for use. And then use the playSound() function anywhere in your script whenever you want to play anyone of the sounds. Example below:

function tileClicked(tile) {
  console.dir(tile)
  // only allow clicking on tiles when game is started and game is not showing pattern
  if (!game.showing && game.started && !tile.classList.contains('flip-card-onclick')) {

    flipTile(tile);

    // check if game reached maximum number of stages i.e. game has been won
    if (game.playerMove <= game.maxStageNumber) {

      // check if current move (tile clicked) matches the tile in the generated pattern
      if (parseInt(tile.id) == game.currentGame[game.playerMove]) {
        // increase the pattern pointer
        game.playerMove++;

        // play sound when correct tile has been clicked
        playSound('correct');

        // check if we reached the end of the current pattern
        if (game.playerMove == game.currentGame.length) {
          // update the progress bar
          elements.stageProgress.css('width', `${(game.currentGame.length / game.maxStageNumber) * 100}%`);

          // show alert prompting user to go to the next stage
          elements.correctAlert.modal('show');
        }
        // current move did not match current pattern, wrong move
      } else {

        if (game.strictGamemode) {
          playSound('incorrect');
          // show fail alert and prompt to restart or exit game if strict mode has been selected
          elements.failAlert.modal('show');
        } else {
          // show wrong move alert and prompt to show pattern again
          playSound('incorrect');
          elements.wrongAlert.modal('show');
        }
      }
    }
  }
}

Also, add a data-name attribute to each <audio> element so that JavaScript knows how to call each player and it's accompanying sound.

<audio controls id="player" class="d-none" data-name="correct">
  <source id="player-src" src="assets/audio/correct.mp3">
</audio>
<audio controls id="player2" class="d-none" data-name="incorrect">
  <source id="player-src-2" src="assets/audio/incorrect.mp3">
</audio>
<audio controls id="player3" class="d-none" data-name="won">
  <source id ="player-src-3" src="assets/audio/won.mp3">
</audio>

All my code above is untested and might throw an error, or worse, makes no difference at all. But hey, at least it's worth a shot.

Emiel Zuurbier
  • 19,095
  • 3
  • 17
  • 32