Related
I have a class called Bullet which is essentially a div on a space invader webpage. When this bullet gets 'fired' I call a method which gradually moves the 'bullet' up the screen.
When the bullet gets to the edge of the screen I want to remove the whole bullet object from memory. However, the setTimeout loop continues to run even after I've deleted it (I think).
I'm sure there is a better way to do this! Perhaps it's foolish to run the loop like this?
TIA
this.bulletmove = new CustomEvent("bulletmove",{detail:this.name});
...
/**
* moves the bullet up the screen gradually
*/
fire(){
var that = this;
setTimeout(function(){
that.moveUp();
window.dispatchEvent(that.bulletmove);
that.fire();
},50);
}
The event is picked up in a controller script which checks if the bullet has reached the edge of the screen at which point it is deleted:
window.addEventListener('bulletmove', function(evt) {
checkCollision(evt);
},false);
...
/**
*Check if the bullet has gone off screen and deletes it
**/
function checkCollision(e){
var bulletName = e.detail;
var bullet = bullets[bulletName];
//check if the bullet has gone off screen
if (bullet.bottom < 0){
bullet.destroy;
delete bullets[e.detail];
bullet=null;
}
}
Have you tried a clearTimeout method to stop the setTimeout from firing?
https://www.freecodecamp.org/news/javascript-settimeout-how-to-set-a-timer-in-javascript-or-sleep-for-n-seconds/
const fireBullet = setTimeout(function(){
that.moveUp();
window.dispatchEvent(that.bulletmove);
that.fire();
},50);
clearTimeout(fireBullet)
I think you should use setInterval instead of calling fire() again - by calling that function, a new setTimeout is created (with a new handler); before the removal of the object, you call obj.halt(), and that clears the setInterval correctly.
const obj = {
name: "objName",
bulletmove() {
return new CustomEvent("bulletmove", {
detail: this.name
})
},
halt() {
clearInterval(this.intervalHandler)
},
intervalHandler: null,
fire() {
const handler = setInterval(() => {
// this.moveUp()
// console.log("move up")
window.dispatchEvent(this.bulletmove())
// this.fire()
}, 500)
this.intervalHandler = handler
},
}
let i = 0
window.addEventListener('bulletmove', function(e) {
// this if-else if mocks the collision detection
// expected: log obj.name 5 times, then clear the interval,
// then event should not be called anymore
if (i < 5) {
console.log(i, e.detail)
} else if (i < 8) {
obj.halt()
console.log(i)
} else if (i < 100) {
console.log(i, e.detail)
}
i++
})
obj.fire()
ANOTHER WAY
A cleaner approach would be if the fire method returned its own "clear function", and you could use that in the event handling:
const obj = {
name: "objName",
bulletmove() {
return new CustomEvent("bulletmove", {
detail: this.name
})
},
fire() {
const handler = setInterval(() => {
// this.moveUp()
// console.log("move up")
window.dispatchEvent(this.bulletmove())
// this.fire()
}, 500)
return () => clearInterval(handler)
},
}
let i = 0
const fireHandler = obj.fire()
const eventHandler = (clearFn) => (e) => {
// this if-else if mocks the collision detection
// expected: log obj.name 5 times, then clear the interval,
// then event should not be called anymore
if (i < 5) {
console.log(i, e.detail)
} else if (i < 8) {
clearFn()
console.log(i)
} else if (i < 100) {
console.log(i, e.detail)
}
i++
}
const eventHandlerWithRemoveFn = eventHandler(fireHandler)
window.addEventListener('bulletmove', eventHandlerWithRemoveFn)
The drawback of this method is that you need to add each object's event handler separately to the window, its benefit is more control, cleaner code (no need to save that handler in the object).
A MODIFIED VERSION FOR MULTIPLE INTERVALS
This is a version of the previous solution, where the clearing functions are stored in the window object:
const eventHandler = (e) => {
const i = e.detail.eventCounter
if (i < 3) {
console.log(i, e.detail.name)
} else if (i < 4) {
window.bulletIntervals[e.detail.name]()
console.log(i, e.detail.name + " is halted")
} else if (i < 100) {
console.log(i, e.detail.name)
}
}
const getBullet = (i) => ({
eventCounter: i, // only for mocking!
name: `objName-${i}`,
bulletmove() {
return new CustomEvent("bulletmove", {
detail: {
name: this.name,
eventCounter: this.eventCounter,
}
})
},
fire() {
const handler = setInterval(() => {
window.dispatchEvent(this.bulletmove())
this.eventCounter++
}, 500)
if (!window.bulletIntervals) window.bulletIntervals = {}
window.bulletIntervals[this.name] = () => clearInterval(handler)
},
})
const bullets = [
getBullet(0),
getBullet(1),
getBullet(2),
]
const fireAll = (bullets) => {
window.addEventListener("bulletmove", eventHandler)
bullets.forEach((bullet) => {
bullet.fire()
})
}
fireAll(bullets)
I would use RxJS to monitor the progress of your bullets.
In the example below I have three different bullets. Each within its own boundary. Once fired, they will immediately stop when they exit their box.
For each bullet we have an "animation frame" observable that emits when such a frame is made available by the browser (internally RxJS uses requestAnimationFrame for this). At that point we check whether the bullet is still within its parent bounding box. If it is we move it otherwise we don't and the subscription to the animation frame stream automatically ends.
const rightPos = el => el.getBoundingClientRect().right;
const moveBullet = (sel, pos) =>
document.querySelector(sel)
.style.left = `${pos}px`;
const fire = (bullet) => {
const el = document.querySelector(bullet);
const parentPos = rightPos(el.parentNode);
return animationFrames().pipe(
map(() => rightPos(el)),
takeWhile(pos => pos < parentPos)
);
}
const bullet1$ = fire('#bullet1');
const bullet2$ = fire('#bullet2');
const bullet3$ = fire('#bullet3');
const fire$ = fromEvent(document.querySelector('button'),'click');
fire$.subscribe(() => {
bullet1$.subscribe(pos => moveBullet('#bullet1', pos+1));
bullet2$.subscribe(pos => moveBullet('#bullet2', pos+1));
bullet3$.subscribe(pos => moveBullet('#bullet3', pos+1));
});
div {
height: 30px;
border: 1px solid black;
margin: 5px;
position: relative;
}
span { position: absolute; }
<script src="https://unpkg.com/rxjs#7.5.7/dist/bundles/rxjs.umd.min.js"></script>
<script>
const {animationFrames, fromEvent} = rxjs;
const {map, takeWhile} = rxjs.operators;
</script>
<div style="width:150px"><span id="bullet1">🏉</span></div>
<div style="width:300px"><span id="bullet2">🥏</span></div>
<div style="width:450px"><span id="bullet3">⚽️</span></div>
<button>Fire!</button>
In the following code, an outer div forms the boundaries of a playfield and all game elements inside the playfield are represented by divs.
Game state consists of an array of game elements (in this instance: one ship and zero or more bullets). Once an element is no longer visible (here: simply off the right-hand side of the playfield), it is removed from game state.
The game loop uses requestAnimationFrame to repeatedly render the game state to the playfield.
Bullet position is calculated using the time of firing and the time elapsed (I added a little randomness to bullet velocity just for fun).
Game elements such as bullets have an associated generator function called as part of the game loop, to retrieve the next state of the element (a bullet "moves by itself" after the initial appearance).
Firing a bullet in this design is as simple as creating a new bullet object with an initial position and an instance of a generator function to account for its trajectory; and then adding that pair to the game state.
const elem = ({ kind = 'div', classN = '' }) => {
const el = document.createElement(kind)
el.classList.add(classN)
return el
}
const applyStyle = (el, style) =>
(Object.entries(style)
.forEach(([k, v]) => el.style[k] = v), el)
const cssPixels = (str) => +(str.slice(0, -2))
const isVisible = (left) =>
cssPixels(left) < cssPixels(playfield.style.width)
const createPlayfield = () =>
applyStyle(elem({ classN: 'playfield' }), { width: '300px' })
const createShip = (startLeft, width) =>
[{ classN: 'ship', style: { left: startLeft, width } }, null]
const createBullet = (startLeft) => {
const b = {
classN: 'bullet',
style: { left: startLeft },
firingTime: +new Date(),
velocity: 0.5,
velocitySeed: Number('1.' + ~~(Math.random() * 9)),
startLeft
}
const g = bulletStateGen(b)
return [ b, () => g.next() ]
}
const bulletPos = ({ firingTime,
startLeft,
velocity,
velocitySeed }, now = +new Date()) =>
`${~~(velocity * (now - firingTime) * velocitySeed + cssPixels(startLeft))}px`
const bulletStateGen = function*(b) {
while (1) {
const left = bulletPos(b)
if (!isVisible(left))
break
b.style = { left }
yield(b)
}
}
const fire = (startLeft) =>
state.unshift(createBullet(startLeft))
const tick = () =>
state = state.reduce((acc, [o, next]) => {
if (!next)
return acc.push([o, next]), acc
const { value, done } = next()
if (done)
return acc
return acc.push([value, next]), acc
}, [])
const blank = () => playfield.innerHTML = ''
const render = () => {
blank()
state.forEach(([{ classN, style = {} }]) =>
playfield.appendChild(applyStyle(elem({ classN }), style)))
}
let ship = createShip('10px', '50px')
let state = [ship]
let playfield = createPlayfield()
const gameLoop = () =>
(render(), tick(), requestAnimationFrame(gameLoop))
const init = () => {
document.body.appendChild(playfield)
document.body.onkeyup = (e) =>
e.key === " "
&& fire(`${cssPixels(ship[0].style.left) + cssPixels(ship[0].style.width)}px`)
}
init()
gameLoop(state, playfield)
.playfield {
height: 300px;
background-color: black;
position: relative;
}
.ship {
top: 138px;
height: 50px;
background-color: gold;
position: absolute;
border-radius: 7px 22px 22px 7px;
}
.bullet {
top: 163px;
width: 10px;
height: 2px;
background-color: silver;
position: absolute;
}
Click on the game to focus it, and then press spacebar to fire!
I want to play videos in a loop. For some reason I don't want change video src on ended event.
So I created video elements for each video in a loop. Also I have video src and durations in array.
here is my idea:
Only current playing video tag can be visible. Others will be hided.
Instead of using ended event, I want to use setTimeout function. Video's duration will be delay parameter.
But they all play together. I couldn't make them play in order.
Here is what I done so far:
videoArray = [
{"video":video1.mp4, "duration": 5},
{"video":video2.mp4, "duration": 7},
{"video":video3.mp4, "duration": 9},
{"video":video4.mp4, "duration": 10},
]
for (var j = 0; j < videoArray.length; j++){
var video = document.createElement("video");
video.src=videoArray[j];
video.id="video-"+j;
video.preload = "metadata";
video.type="video/mp4";
video.autoplay = true;
video.style.display="none";
document.body.appendChild(video);
}
for (var count = 0; count < videoArray.length; count++) {
(function(num){
setTimeout(function() {
videoArray[num].video.style.display="block";
videoArray[num].video.play();
}, 1000 * videoArray[num].duration);
videoArray[num].video.style.display="none";
})(count);
}
Disclaimer
I know that the question was asked without the ended event, but I do not think that set time out is the way to go.
Think of the scenario where you have video buffering, or slowing down for any reason, your setTimeout will be out of sync.
At the bottom I've added another solution that answers the requirement of not using the ended event, but again, I do not recommend using it.
Solution
The idea is to have an event listener to the end of the video, in that case, even if you run the video on a different speed you are still going to run the next video regardless of the duration.
Another benefit is that you do not need to know the duration of the videos in the first place.
PS.
the event listener that you need to listen to is video.addEventListener("ended", callback);
You are more than welcome to run the code or to have a look at a working example I've created for you
Working Example
const videoUrls = [
'https://videos-play-loop.netlify.com/video1.mp4',
'https://videos-play-loop.netlify.com//video2.mp4',
'https://videos-play-loop.netlify.com//video3.mp4',
];
const createVideo = ({id, src, width, cls = 'video', display = 'block', playbackRate = 1, muted = true, type = 'video/mp4', autoplay = false, controls = true}) => {
const videoElement = document.createElement("video");
videoElement.id = id;
videoElement.src = src;
videoElement.classList.add(src);
videoElement.type = type;
videoElement.autoplay = autoplay;
videoElement.controls = controls;
videoElement.style.display = display;
videoElement.muted = muted;
videoElement.playbackRate = playbackRate;
return videoElement;
};
const addVideos = (container, videoUrls) => {
const videos = videoUrls.map((url, index) => {
const first = index === 0;
const display = first ? 'block' : 'none';
return createVideo({id: `video-${index}`, src: url,display, width: 640, autoplay: first, playbackRate: 3});
});
videos.forEach((video, index) => {
const last = index === videos.length - 1;
const playNext = (element) => {
element.target.style.display = "none";
const nextElementIndex = last ? 0 : index + 1;
const nextElement = videos[nextElementIndex];
nextElement.autoplay = true;
nextElement.style.display="block";
nextElement.load();
};
video.addEventListener("ended", playNext);
container.appendChild(video)
});
};
const videoWrapper = document.getElementById('video-wrapper');
addVideos(videoWrapper, videoUrls);
#video-wrapper video {
max-width: 600px;
}
<div id="video-wrapper"></div>
Working solution with setTimeout (please use the solution above)
const videoUrls = [{
url: `https://videos-play-loop.netlify.com/video3.mp4`,
duration: 3,
},
{
url: `https://videos-play-loop.netlify.com/video2.mp4`,
duration: 4
},
{
url: `https://videos-play-loop.netlify.com/video1.mp4`,
duration: 5
}
];
const createVideo = ({
id,
src,
width,
cls = 'video',
display = 'block',
duration,
playbackRate = 1,
muted = true,
type = 'video/mp4',
autoplay = false,
controls = true
}) => {
const videoElement = document.createElement("video");
videoElement.id = id;
videoElement.src = src;
videoElement.classList.add(src);
videoElement.type = type;
videoElement.autoplay = autoplay;
videoElement.controls = controls;
videoElement.style.display = display;
videoElement.muted = muted;
videoElement.playbackRate = playbackRate;
videoElement.setAttribute('data-duration', duration);
return videoElement;
};
const playNext = (videos, index) => {
const current = videos[index];
const activeVideoDuration = parseInt(current.dataset.duration) * 1000;
setTimeout(() => {
const last = index === videos.length - 1;
current.style.display = "none";
current.pause();
const activeVideoIndex = last ? 0 : index + 1;
const next = videos[activeVideoIndex];
next.autoplay = true;
next.style.display = "block";
next.load();
next.play();
playNext(videos, activeVideoIndex);
}, activeVideoDuration);
};
const addVideos = (container, videoUrls) => {
const videos = videoUrls.map((video, index) => {
const {
url,
duration
} = video;
const first = index === 0;
const display = first ? 'block' : 'none';
return createVideo({
id: `video-${index}`,
src: url,
duration,
display,
width: 640,
autoplay: first,
});
});
videos.forEach(video => container.appendChild(video));
playNext(videos, 0);
};
const videoWrapper = document.getElementById('video-wrapper');
addVideos(videoWrapper, videoUrls);
#video-wrapper video {
max-width: 600px;
}
<div id="video-wrapper"></div>
You could hold the duration of the videos in a variable and the accumulate this variable with the previous video duration and set this a the setTimeOut duration.
Note that the time of the videos is in seconds. And for the first video to play user has to interact otherwise the video will not play.
Working Example:
function startVideos(event) {
event.target.style.display= "none";
(function() {
videoArray = [
{
video:
"http://techslides.com/demos/sample-videos/small.mp4",
duration: 5
},
{
video:
"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
duration: 60
},
{
video:
"https://mobamotion.mobatek.net/samples/sample-mp4-video.mp4",
duration: 120
},
{
video:
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
duration: 600
}
];
let videArrayElem = [];
for (var j = 0; j < videoArray.length; j++) {
var video = document.createElement("video");
video.src = videoArray[j].video;
video.id = "video-" + j;
video.preload = "metadata";
video.type = "video/mp4";
video.autoplay = false;
video.controls= true;
video.style.display = "none";
videArrayElem.push(video);
document.body.appendChild(video);
}
let prviousVideoDuration = 0;
for (var count = 0; count < videoArray.length; count++) {
(function(num) {
setTimeout(function() {
videArrayElem[num].style.display = "block";
videArrayElem[num].play();
}, prviousVideoDuration);
prviousVideoDuration += 1000 * videoArray[num].duration;
videArrayElem[num].style.display = "none";
})(count);
}
})();
}
video {
height: 100px;
width: 100px;
display: inline-block;
margin: 4px;
float: left;
}
<button type="button" onclick="startVideos(event)">Start Video Demo</button>
Aside from the facts that you are trying to achieve something strange, and that your code example will not compile. As I see it, you've got it.
The only missing thing is pause() and display = "none". With minimum edits you have what you need.
const videoArray = [
{"video":"video1.mp4", "duration": 5}, // of cause you need ""
{"video":"video2.mp4", "duration": 7},
{"video":"video3.mp4", "duration": 9},
{"video":"video4.mp4", "duration": 10},
]
for (let j = 0; j < videoArray.length; j++){
const video = document.createElement("video");
// you need to save DOM nodes somewhere to use them in the second loop
videoArray[j].video_el = video
video.src=videoArray[j].video; // `.video` is added
video.id="video-"+j;
video.preload = "metadata";
video.type="video/mp4";
video.autoplay = true;
video.style.display="none";
document.body.appendChild(video);
}
for (var count = 0; count < videoArray.length; count++) {
(function(num){
setTimeout(function() {
// add this to hide previous video node and stop video from playing
if( num ) {
videoArray[num-1].video_el.style.display="none";
videoArray[num-1].video_el.pause()
}
// videoArray[num].video - is a string, not a DOM node
// so, you need to change this:
// videoArray[num].video.style.display="block";
// for this:
videoArray[num].video_el.style.display="block";
// no need. `autoplay` is set to `true` in the first loop
// videoArray[num].video_el.play();
}, 1000 * videoArray[num].duration);
// no need. already done in the first loop
// videoArray[num].video_el.style.display="none";
})(count);
}
But there is a lot of flaws:
setTimeout does not care about network delays and other time related issues, so most probably your video sequence will not play seamlessly.
Because of the first flow and because I doubt that duration values are exact you should use pause().
As #IslamElshobokshy noted in comments to your question using play/pause is not so simple.
If user opens the page and does nothing more, you'll get:
Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.
in Chrome. And
Autoplay is only allowed when approved by the user, the site is activated by the user, or media is muted.
NotAllowedError: The play method is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
in Firefox.
So you'd probably be better off with the ended event after all, and with muted attribute (to mitigate the last issue):
for (let j = 0; j < videoArray.length; j++){
const video = document.createElement("video")
videoArray[j].video_el = video
video.src = videoArray[j].video
video.id = "video-"+j
video.preload = "metadata"
video.type = "video/mp4"
video.autoplay = true
// show the first video right away
if( j !== 0 ) video.style.display = "none"
// set muted attribute
video.muted = "muted"
// play next video after the previous had ended
video.addEventListener('ended', (num => function() {
this.style.display = "none";
if( num !== videoArray.length-1 ) {
videoArray[num+1].video_el.style.display="block";
// only needed if `muted` is not set
videoArray[num+1].video_el.play();
}
})(j), false);
document.body.appendChild(video);
}
If muted attribute is not the option, you can add some event listeners on the body to call play() on the first video as soon as the user starts interaction with the page. And maybe you would like to read more about autoplay here: https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide
Auto-complete is not getting fired when pressing white-space in the editor, Is there an option to fire Autocomplete when white space is pressed?
Please have a look at this demo:
[jsfiddle] https://jsfiddle.net/xbaha/cbfe6tx4/53/
I want the auto complete to be fired when i type "my name is " (note there is a space after "is"), then using onchange event i added the suggestions that should pop up, but it does not work, i must type a letter character so that autocomplete is getting fired.
is there a solution for this?
Instead of using enableLiveAutocomplete option you can use a custom handler for afterExec event https://github.com/ajaxorg/ace/blob/v1.4.7/lib/ace/ext/language_tools.js#L149
var editor;
var fieldsList = [];
function initAceEditor() {
editor = ace.edit("aceEditor", {
theme: "ace/theme/solarized_light",
mode: "ace/mode/text",
showPrintMargin: false,
fontSize: "24px",
minLines: 3,
maxLines: 8,
wrap: true,
// do not use live autocomplete since we want to invoke more often
// enableLiveAutocompletion: true,
enableBasicAutocompletion: true,
});
var langTools = ace.require("ace/ext/language_tools");
var stepLineCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
var completions = [];
console.log('before foreach....', this.fields, fieldsList);
var cursor = session.selection.cursor
var line = session.getLine(cursor.row).slice(0, cursor.column - prefix.length)
var fieldsList
if (line.endsWith("name is ")) {
fieldsList = ["bob", "james", "alex", "jimmy"];
} else {
fieldsList = ["my name is", "completion2", "completion3", "one more"]
}
fieldsList.forEach(function(w) {
completions.push({
value: w,
});
});
callback(null, completions);
}
}
langTools.setCompleters([stepLineCompleter]);
langTools.addCompleter(stepLineCompleter);
var Autocomplete = ace.require("ace/autocomplete").Autocomplete
var doLiveAutocomplete = function(e) {
var editor = e.editor;
var hasCompleter = editor.completer && editor.completer.activated;
var session = editor.session
var cursor = session.selection.cursor
var line = session.getLine(cursor.row).slice(0, cursor.column)
// We don't want to autocomplete with no prefix
if (e.command.name === "backspace") {
// do not hide after backspace
} else if (e.command.name === "insertstring") {
if (!hasCompleter) {
// always start completer
var completer = Autocomplete.for(editor);
// Disable autoInsert
completer.autoInsert = false;
completer.showPopup(editor);
}
}
};
editor.commands.on('afterExec', doLiveAutocomplete);
}
initAceEditor()
#aceEditor {
position: absolute;
top: 10%;
right: 0;
bottom: 30%;
left: 0;
}
<script src="https://rawgithub.com/ajaxorg/ace-builds/master/src/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="https://rawgithub.com/ajaxorg/ace-builds/master/src/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
<div id="aceEditor"></div>
I have a dat.gui user interface in which I want to run some math calculations using the value of one slider input (numberOne), and then show the result in a message output (resultOne).
I can't figure out how to get the calculation result into the dat.gui message field.
import * as Calc from './components/Calc.js';
function init() {
let groupA = {
valA1: 0,
valA2: 10
};
let groupB = {
valB1: 3,
valB2: 5.6
};
let calc = Calc.SomeCalculations(groupA, groupB); // Invokes a function in another JS file.
const controller = new function() {
this.numberOne = 0;
this.resultOne = calc.resultOne;
}();
const gui = new GUI( {width: 300 });
const f1 = gui.addFolder('My inputs');
f1.add(controller, 'numberOne', 0, 100).onChange( function() {
// What goes here?
} );
f1.open();
const f2 = gui.addFolder('My results');
f2.add(controller, 'resultOne');
f2.open();
gui.open();
}
Nevermind, I just realized that I need to use .setValue()
const gui = new GUI( {width: 300 });
const f1 = gui.addFolder('My inputs');
var gui_numberOne = f1.add(controller, 'numberOne', 0, 100);
f1.open();
const f2 = gui.addFolder('My results');
var gui_resultOne = f2.add(controller, 'resultOne');
gui_numberOne.onChange( function() {
gui_resultOne.setValue( Calc.SomeCalculations(groupA, groupB).myResult );
} );
f2.open();
gui.open();
CodePen
On line 19, the event listener calls an anonymous function, I'd like to break this out into its own function, but I also think I need to be passing a 3rd argument in. possibly the padz class, or maybe the audio tags.. maybe a combination of both, but not sure of the syntax to do that.
JavaScript
var clickCounter = 0;
var currentLevel = 0;
var simon_sSequence = [];
var player_sInput = [];
const audioTapper = document.querySelector("#audioT");
const audioError = document.querySelector("#audioE");
const levelDisplay = document.getElementById("level_Display");
const simonzSequence = document.getElementById("simon_sSequence");
const playerInput = document.getElementById("player_sInput");
const start_Tapper = document.querySelector(".startTapper");
const pads = document.querySelectorAll(".padz");
// Convert the padz list to an array that we can iterate over using .forEach()
Array.from(pads).forEach((pad, index) => {
// Get the associated audio element nested in the padz-div
const audio = pad.querySelector("audio");
// Add a click listener to each pad which
// will play the audio, push the index to
// the user input array, and update the span
pad.addEventListener("click", () => {
player_sInput.push(index);
if (player_sInput[clickCounter] !== simon_sSequence[clickCounter]) {
audioError.play();
$("body").animate({ backgroundColor: "red" }, 100);
$("#container").effect( "shake", {times:100}, 1000, 'linear' );
$("body").animate({ backgroundColor: "gray" }, 5000);
//console.log("wrong");
} else { //end of if
audio.play();
playerInput.textContent = "Player's reply " + player_sInput;
clickCounter++;
//console.log(clickCounter);
} //end of else
}); //end of EventListener
}); //end of Array.from(pads).forEach((pad, index)
start_Tapper.addEventListener("click", (resetStart)); //end of EventListener
function resetStart() {
//generate a random number & push it to simon_sSequence
simon_sSequence.push(Math.floor(Math.random() * 4));
simonzSequence.textContent = "Simon says " + simon_sSequence;
playerInput.textContent = "Player's reply " + player_sInput;
//for each in the array set time interval(300ms);
//dipslay hover effect
//play pad sound
audioTapper.play();
player_sInput = []; //clear player's input for this turn
clickCounter = 0;
currentLevel++;
levelDisplay.textContent = "Level: " + currentLevel + " " + simon_sSequence.length + " " + player_sInput.length;
}