Annotate RoughViz chart using Rough-Notation library - javascript

I have a simple bar chart built with RoughViz.js. The key takeaway that I'd like to highlight in the chart is the difference in height between the first bar and the third bar. To do this I'd like to use the bracket annotation from Rough Notation and set the bracket to start at a y-coordiate equal to the height of the first bar and end at a y-coordinate equal to the height of the last bar. What is the best way to accomplish this?
EDIT...
this picture illustrates what I'm trying to accomplish. The large bracket is the one that the rough-notation library is drawing in my code. Note that it wraps the entire chart. I want it to instead draw the bracket like the small dark blue one that I've mocked up. The dashed lines are also just mock up so as to better convey the desired positioning.
The external libraries are:
https://github.com/jwilber/roughViz and
https://github.com/rough-stuff/rough-notation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/rough-viz#1.0.6"></script>
<script type="module" src="https://unpkg.com/rough-notation?module"></script>
<style>
body {}
;
.item1 {
grid-area: chart;
}
.item2 {
grid-area: annotation;
}
.grid-container {
margin-top: 3rem;
display: grid;
grid-template-areas:
'chart annotation';
grid-template-rows: 1fr;
grid-template-columns: 8fr, 3fr;
}
#typedtext {
font-family: 'Waiting for the Sunrise', cursive;
font-size: 25px;
margin: 10px 50px;
letter-spacing: 6px;
font-weight: bold;
color: blue;
padding-left: 3rem;
padding-top: 30%;
height: 100%;
}
</style>
</head>
<body>
<button id="annotate-button">Click me</button>
<div class="grid-container">
<div class="item1">
<div id="viz0"></div>
</div>
<div class="item2">
<div id="typedtext"></div>
</div>
</div>
</body>
</html>
<script>
// create Bar chart from csv file, using default options
new roughViz.Bar({
element: '#viz0', // container selection
data: {
labels: ['First thing', 'Second thing', 'Third thing'],
values: [100, 50, 25]
},
width: window.innerWidth * .7,
height: window.innerHeight * .7
});
</script>
<script type="module">
import { annotate } from 'https://unpkg.com/rough-notation?module';
const e = document.querySelector('#viz0');
const annotation = annotate(e, { type: 'bracket', color: 'blue', padding: [2, 10], strokeWidth: 3 });
document.getElementById("annotate-button").addEventListener('click', function(){
annotation.show();
})
</script>
<script>
//https://css-tricks.com/snippets/css/typewriter-effect/
// set up text to print, each item in array is new line
var aText = new Array(
"This is a comment"
);
var iSpeed = 10; // time delay of print out
var iIndex = 0; // start printing array at this posision
var iArrLength = aText[0].length; // the length of the text array
var iScrollAt = 20; // start scrolling up at this many lines
var iTextPos = 0; // initialise text position
var sContents = ''; // initialise contents variable
var iRow; // initialise current row
function typewriter() {
sContents = ' ';
iRow = Math.max(0, iIndex - iScrollAt);
var destination = document.getElementById("typedtext");
while (iRow < iIndex) {
sContents += aText[iRow++] + '<br />';
}
destination.innerHTML = sContents + aText[iIndex].substring(0, iTextPos) + "_";
if (iTextPos++ == iArrLength) {
iTextPos = 0;
iIndex++;
if (iIndex != aText.length) {
iArrLength = aText[iIndex].length;
setTimeout("typewriter()", 500);
}
} else {
setTimeout("typewriter()", iSpeed);
}
}
document.getElementById("annotate-button").addEventListener('click', function() {
typewriter();
})
</script>
You can also click here for a JsFiddle with the same code. The chart renders a little better on JsFiddle. https://jsfiddle.net/hughesdan/bmyda74e/1/

You can try this. You will find additional/changed code documented properly. It would be better to test the code here: https://jsfiddle.net/fo16n8tu/.
Rough annotation plugin needs HTML/SVG element to select and annotate. Therefore, I add a dummy <rect> element and use it for annotation. Let me know if this approach could work for you.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/rough-viz#1.0.6"></script>
<script type="module" src="https://unpkg.com/rough-notation?module"></script>
<style>
body {}
.item1 {
grid-area: chart;
}
.item2 {
grid-area: annotation;
}
.grid-container {
margin-top: 3rem;
display: grid;
grid-template-areas:
'chart annotation';
grid-template-rows: 1fr;
grid-template-columns: 8fr, 3fr;
}
#typedtext {
font-family: 'Waiting for the Sunrise', cursive;
font-size: 25px;
margin: 10px 50px;
letter-spacing: 6px;
font-weight: bold;
color: blue;
padding-left: 3rem;
padding-top: 30%;
height: 100%;
}
</style>
</head>
<body>
<button id="annotate-button">Click me</button>
<div class="grid-container">
<div class="item1">
<div id="viz0"></div>
</div>
<div class="item2">
<div id="typedtext"></div>
</div>
</div>
</body>
</html>
<script>
// create Bar chart from csv file, using default options
const viz = new roughViz.Bar({
element: '#viz0', // container selection
data: {
labels: ['First thing', 'Second thing', 'Third thing'],
values: [100, 50, 25]
},
width: window.innerWidth * .7,
height: window.innerHeight * .7
});
</script>
<script type="module">
import { annotate } from 'https://unpkg.com/rough-notation?module';
const e = document.querySelector('#viz0');
/* START additional code */
// Select <g id="#viz0_svg"> element
const svgGroupWrapper = e.querySelector('[id="#viz0_svg"]');
// Get each bar <g> element
const bars = svgGroupWrapper.getElementsByClassName('viz0');
// Height difference between the first (bars[0]) and the third bar (bars[2])
const diffHeight = bars[0].getBoundingClientRect().height - bars[2].getBoundingClientRect().height;
// Get the position annotation element by horizontal x-axis
const offsetX = svgGroupWrapper.getElementsByClassName('rough-xAxisviz0')[0].getBoundingClientRect().width;
// Width of annotation element (also used in annotation options!!!)
const annotationPadding = [0, 6];
// Create dummy <rect> element to annotate on it. Annotation needs an HTML element to work
const elementToAnnotate = document.createElementNS('http://www.w3.org/2000/svg','rect');
// Set "x" attribute to offsetX - annotationPadding[1] - 1 to shift <rect> to very right, but with place for annotaion and width of rect (=1)
elementToAnnotate.setAttribute('x', offsetX - annotationPadding[1] - 1);
// Just positioning "y" to 0 so that the element start from top of the SVG
elementToAnnotate.setAttribute('y', '0');
// Set "width" to 1 to be identifable in SVG for annotation plugin
elementToAnnotate.setAttribute('width', '1');
// Set "height" to the height difference value between first and third bars
elementToAnnotate.setAttribute('height', diffHeight);
// Set "fill" to transparent so that the <rect> element is not visible for users
elementToAnnotate.setAttribute('fill', 'transparent');
// Add the element in wrapper <g> element
svgGroupWrapper.appendChild(elementToAnnotate);
/* END additional code */
// CHANGED CODE: Annotate dummy <rect> element for desired effects
const annotation = annotate(elementToAnnotate, { type: 'bracket', color: 'blue', padding: annotationPadding, strokeWidth: 3 });
document.getElementById("annotate-button").addEventListener('click', function(){
annotation.show();
})
</script>
<script>
//https://css-tricks.com/snippets/css/typewriter-effect/
// set up text to print, each item in array is new line
var aText = new Array(
"This is a comment"
);
var iSpeed = 10; // time delay of print out
var iIndex = 0; // start printing array at this posision
var iArrLength = aText[0].length; // the length of the text array
var iScrollAt = 20; // start scrolling up at this many lines
var iTextPos = 0; // initialise text position
var sContents = ''; // initialise contents variable
var iRow; // initialise current row
function typewriter() {
sContents = ' ';
iRow = Math.max(0, iIndex - iScrollAt);
var destination = document.getElementById("typedtext");
while (iRow < iIndex) {
sContents += aText[iRow++] + '<br />';
}
destination.innerHTML = sContents + aText[iIndex].substring(0, iTextPos) + "_";
if (iTextPos++ == iArrLength) {
iTextPos = 0;
iIndex++;
if (iIndex != aText.length) {
iArrLength = aText[iIndex].length;
setTimeout("typewriter()", 500);
}
} else {
setTimeout("typewriter()", iSpeed);
}
}
document.getElementById("annotate-button").addEventListener('click', function() {
typewriter();
})
</script>

You could try something like in the following example:
const lbls = ['First thing', 'Second thing', 'Third thing'];
const destination = document.getElementById("typedtext");
const empty = (data) => {
switch (true) {
case (data == null || data == 'undefined' || data == false || data == ''):
return true;
case (Array.isArray(data)):
return data.length == 0;
case (typeof data == 'object'):
return (Object.keys(data).length == 0 && data.constructor == Object);
case (typeof data == 'string'):
return data.length == 0;
case (typeof data == 'number' && !isNaN(data)):
return data == 0;
default:
return false;
}
}
const typewriter = () => {
settings.sContents = ' ';
settings.iRow = Math.max(0, settings.iIndex - settings.iScrollAt);
while (settings.iRow < settings.iIndex) {
settings.sContents += settings.aText[settings.iRow++] + '<br />';
}
if(!empty(settings.aText[settings.iIndex])) {
destination.textContent = settings.sContents + settings.aText[settings.iIndex].substring(0, settings.iTextPos) + "_";
}
if (settings.iTextPos++ == settings.iArrLength) {
settings.iTextPos = 0;
settings.iIndex++;
if (!empty(settings.aText[settings.iIndex]) && settings.iIndex != settings.aText.length) {
settings.iArrLength = settings.aText[settings.iIndex].length;
setTimeout(typewriter(), 500);
}
} else {
setTimeout(typewriter(), settings.iSpeed);
}
}
new roughViz.Bar({
element: '#viz0', // container selection
data: {
labels: lbls,
values: [100, 50, 25]
},
width: window.innerWidth * 0.7,
height: window.innerHeight * 0.7
});
var settings = {};
settings.diff = [];
lbls.forEach((label) => {
let g = document.querySelector('g[attrX=\"' + label + '\"]');
g.addEventListener('click', (e) => {
let b = parseInt(e.target.parentNode.getAttribute('attrY'));
if (e.target.getAttribute('fill') == '#ff0000') {
e.target.setAttribute('fill', 'transparent');
let index = settings.diff.indexOf(b);
settings.diff = settings.diff.filter((x, i) => i !== index);
} else {
e.target.setAttribute('fill', '#ff0000');
settings.diff.push(b);
}
});
});
document.getElementById("annotate-button").addEventListener('click', (e) => {
if (settings.diff.length == 2) {
settings.aText = [(settings.diff[0] - settings.diff[1]).toString()];
} else {
settings.aText = ['Select a pair first!'];
}
settings.iSpeed = 10; // time delay of print out
settings.iIndex = 0; // start printing array at this posision
settings.iScrollAt = 20; // start scrolling up at this many lines
settings.iTextPos = 0; // initialise text position
settings.sContents = ''; // initialise contents variable
settings.iRow = 0; // initialise current row
settings.iArrLength = settings.aText[0].length; // the length of the text array
typewriter();
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/rough-viz#1.0.6"></script>
<script type="module" src="https://unpkg.com/rough-notation?module"></script>
<script type="module">
import { annotate } from 'https://unpkg.com/rough-notation?module';
const ee = document.querySelector('#viz0');
const annotation = annotate(ee, { type: 'bracket', color: 'blue', padding: [2, 10], strokeWidth: 3 });
document.getElementById("annotate-button").addEventListener('click', function(){
annotation.show();
})
</script>
<style>
body {}
;
.item1 {
grid-area: chart;
}
.item2 {
grid-area: annotation;
}
.grid-container {
margin-top: 3rem;
display: grid;
grid-template-areas:
'chart annotation';
grid-template-rows: 1fr;
grid-template-columns: 8fr, 3fr;
}
#typedtext {
font-family: 'Waiting for the Sunrise', cursive;
font-size: 25px;
margin: 10px 50px;
letter-spacing: 6px;
font-weight: bold;
color: blue;
padding-left: 3rem;
padding-top: 30%;
height: 100%;
}
</style>
</head>
<body>
<button id="annotate-button">Click me</button>
<div class="grid-container">
<div class="item1">
<div id="viz0"></div>
</div>
<div class="item2">
<div id="typedtext"></div>
</div>
</div>
</body>
</html>
OR use this JSFiddle

Related

How to call certain chord buttons instead of all the chord buttons from the chord library?

I have been able to build a chord library that plays the chord and also says the chord name. I'm now trying to build upon the chord library. How can I have only a limited amount of chord buttons be displayed on the UI instead of all of the chords that are being populated?
The top portion of the code (that is below) is displaying the four buttons but not playing the chord sounds or saying the chord names.
The other code after is showing all the chord buttons and playing the chord sounds and chord names.
We're trying to combine the two portions of code from below so that they play the chord names and sounds for the four chords above.
//In order to run in terminal insert the following code to activate localhost: npx parcel src/index.html
import { chordType, transpose, note } from '#tonaljs/tonal';
import { chord } from '#tonaljs/chord';
import { entries } from '#tonaljs/chord-dictionary';
import { Howler, howl } from 'howler';
const buttons = document.querySelector(".buttons");
const arrayOfChordNames = ["Major", "Minor", "Augmented", "Diminished"];
arrayOfChordNames.forEach((chord) => {
let buttonElement = createElement("button", chord);
buttons.appendChild(buttonElement);
});
function createElement(element, content) {
let newElement = document.createElement(element);
newElement.textContent = content;
return newElement;
}
// code above is displaying the four buttons but not playing the chord sound and chord name
//code below is showing all the chord buttons and playing the chord sound and chord names
// we're trying combine the chord from below so that it plays the audio from the chord names and sounds for the four chords above
const sound = new Howl({
src: ['assets/pianosprite.mp3'],
onload() {
console.log('Sound file has been loaded. Do something here!');
soundEngine.init();
},
onloaderror(e, msg) {
console.log('Error', e, msg);
}
});
const startNotes = ['C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'F', 'F#', 'Gb', 'G', 'G#', 'Ab', 'A', 'A#', 'Bb', 'B'];
const startNoteSelector = document.querySelector('#start-note');
const octaveSelector = document.querySelector('#octave');
const buttons = document.querySelector('.buttons');
const intervalsInChord = document.querySelector('.intervals-in-chord');
const notesInChord = document.querySelector('.notes-in-chord');
let selectedStartNote = 'C';
let selectedOctave = '1';
const app = {
init() {
this.setupStartNotes();
this.setupOctaves();
this.setupButtons();
this.setupEventListeners();
},
setupStartNotes() {
startNotes.forEach(noteName => {
let noteNameOption = this.createElement('option', noteName);
startNoteSelector.appendChild(noteNameOption);
});
},
setupOctaves() {
for (let i = 1; i <= 6; i++) {
let octaveNumber = this.createElement('option', i);
octaveSelector.appendChild(octaveNumber);
}
},
setupButtons() {
const chordNames = entries().map(entry => {
return entry.aliases[0];
});
chordNames.forEach(chordName => {
let chordButton = this.createElement('button', chordName);
buttons.appendChild(chordButton);
});
},
setupEventListeners() {
startNoteSelector.addEventListener('change', () => {
selectedStartNote = startNoteSelector.value;
});
octaveSelector.addEventListener('change', () => {
selectedOctave = octaveSelector.value;
});
buttons.addEventListener('click', (event) => {
if (event.target.classList.contains('buttons')) {
return;
}
selectedChord = event.target.innerText;
this.displayAndPlayChord(selectedChord, event);
});
},
displayAndPlayChord(selectedChord, event) {
let chordIntervals = chord(selectedChord).intervals;
intervalsInChord.innerText = chordIntervals.join(' - ');
const startNoteWithOctave = selectedStartNote + selectedOctave;
let chordNotes = chordIntervals.map(val => {
return transpose(startNoteWithOctave, val);
});
notesInChord.innerText = chordNotes.join(' - ');
soundEngine.play(chordNotes, event);
},
createElement(elementName, content) {
let element = document.createElement(elementName);
element.innerHTML = content;
return element;
}
}
const soundEngine = {
init() {
const lengthOfNote = 2400;
let timeIndex = 0;
for (let i = 24; i <= 96; i++) {
sound['_sprite'][i] = [timeIndex, lengthOfNote];
timeIndex += lengthOfNote;
}
},
play(soundSequence, event) {
const buttons =
document.querySelector(".buttons");
const chordNameTable = {
"M": "Major",
"m": "Minor",
"dim": "diminished",
"aug": "Augmented"
}
buttons.addEventListener("click", (event) => {
})
function textToSpeech(message, chord) {
const speech = new SpeechSynthesisUtterance();
speech.lang = "en-US";
speech.text = message;
speech.volume = 1;
speech.rate = 1;
speech.pitch = 1;
window.speechSynthesis.speak(speech);
// When speaking has finished
speech.onend = function() {
playChord(chord);
}
}
function playChord(chord) {
// Wait a second (1000 miliseconds) before playing the chord
setTimeout(() => {
const chordMidiNumbers = soundSequence.map(noteName => {
return note(noteName).midi;
});
sound.volume(0.05);
chordMidiNumbers.forEach(noteMidiNumber => {
sound.play(noteMidiNumber.toString());
});
console.log("Chord to be played", chord);
}, 500);
}
const sayThis = chordNameTable[event.target.textContent];
textToSpeech(sayThis, event.target.textContent);
}
}
app.init();
const allChordNames = entries()
chordEntries.map(entry => {
return entry.aliases[0];
})
console.log(Array.isArray (allChordNames));
body {
font-family: Lato, Sans-serif;
color: #fff;
background: #000000
}
.controls{
display: flex;
justify-content: center;
}
.controls select {
margin: 0 10px;
}
.chord-notes {
display: flex;
flex-direction: column;
align-items: center;
font-size: 1.5em;
}
.chord-notes p {
margin: 10px 0;
}
/*width makes the page more condensed towards the left*/
.buttons {
display: flex;
flex-wrap: wrap;
width: 60%;
}
/*min-width divides columns into 3 instead of 4, max-width creates a set size for the button*/
.buttons button{
flex-grow: 1;
min-width: 20%;
max-width: 20%;
height: 50px;
font-size: 1em;
background: #7200CC;
border: none;
color: #fff;
margin: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chord Dictionary</title>
<link rel="stylesheet" href="styles.css">
<head>
<style>
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
li {
float: left;
}
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li a:hover {
background-color: #A400DD;
}
</style>
</head>
</head>
<ul>
<li><a class="active" href="/home">Home</a></li>
<li>Lessons</li>
<li>Practice</li>
<li>Test</li>
<li>Demo</li>
<li>Triads</li>
<li>Sign Up</li>
<li>Login</li>
</ul>
</head>
<body>
<h1>This is the Triads page!</h1>
<div class="controls">
<label for="start-note">Start note:</label>
<select name="start note" id="start-note">
</select>
<label for="octave">Octave: </label>
<select name="Octave" id="octave">
</select>
<label for="Loops">Loops: </label>
<select id="Loops">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="loop">loop</option>
</select>
</div>
<div class="chord-notes">
<p class="notes-in-chord">C3 - D3 - E3 - F#3 - A3</p>
<p class="intervals-in-chord">P1 - M3 - P5 - M7</p>
</div>
<div class="buttons">
</div>
<script type="module" src="triads.js"></script>
</body>
</html>

Show images in quiz javascript

I'm trying to create a quiz that tests users awareness of real and fake emails. What I want to do is have the question displayed at the top saying "Real or Fake", then have an image displayed underneath which the user needs to look at to decided if it's real or fake. There are two buttons, real and fake, and regardless of whether they choose the right answer I want to swap the original image with annotated version - showing how users could spot that it was fake or real.
But I'm not sure how to show the annotated version once the answer has been submitted. Could someone help?
function Quiz(questions) {
this.score = 0;
this.questions = questions;
this.questionIndex = 0;
}
Quiz.prototype.getQuestionIndex = function() {
return this.questions[this.questionIndex];
}
Quiz.prototype.guess = function(answer) {
if (this.getQuestionIndex().isCorrectAnswer(answer)) {
this.score++;
}
this.questionIndex++;
}
Quiz.prototype.isEnded = function() {
return this.questionIndex === this.questions.length;
}
function Question(text, choices, answer) {
this.text = text;
this.choices = choices;
this.answer = answer;
}
Question.prototype.isCorrectAnswer = function(choice) {
return this.answer === choice;
}
function populate() {
if (quiz.isEnded()) {
showScores();
} else {
// show question
var element = document.getElementById("question");
element.innerHTML = quiz.getQuestionIndex().text;
// show options
var choices = quiz.getQuestionIndex().choices;
for (var i = 0; i < choices.length; i++) {
var element = document.getElementById("choice" + i);
element.innerHTML = choices[i];
guess("btn" + i, choices[i]);
}
showProgress();
}
};
function guess(id, guess) {
var button = document.getElementById(id);
button.onclick = function() {
quiz.guess(guess);
populate();
}
};
function showProgress() {
var currentQuestionNumber = quiz.questionIndex + 1;
var element = document.getElementById("progress");
element.innerHTML = "Question " + currentQuestionNumber + " of " + quiz.questions.length;
};
function showScores() {
var gameOverHTML = "<h1>Result</h1>";
gameOverHTML += "<h2 id='score'> Your scores: " + quiz.score + "</h2>";
var element = document.getElementById("quiz");
element.innerHTML = gameOverHTML;
};
// create questions here
var questions = [
new Question("<img src= 'netflix_fake.jpg' />", ["Real", "Fake"], "Fake"),
new Question("<img src= 'dropbox_real.jpg' />", ["Real", "Fake"], "Real"),
new Question("<img src= 'gov_real.jpg' />", ["Real", "Fake"], "Real"),
new Question("<img src= 'paypal_fake.jpg' />", ["Real", "Fake"], "Fake"),
new Question("<img src= 'gmail.jpg' />", ["Real", "Fake"], "Fake")
];
//create quiz
var quiz = new Quiz(questions);
// display
populate();
body {
background-color: #538a70;
}
.grid {
width: 600px;
height: 500px;
margin: 0 auto;
background-color: #fff;
padding: 10px 50px 50px 50px;
border: 2px solid #cbcbcb;
}
.grid h1 {
font-family: "sans-serif";
font-size: 60px;
text-align: center;
color: #000000;
padding: 2px 0px;
}
#score {
color: #000000;
text-align: center;
font-size: 30px;
}
.grid #question {
font-family: "monospace";
font-size: 30px;
color: #000000;
}
.buttons {
margin-top: 30px;
}
#btn0,
#btn1,
#btn2,
#btn3 {
background-color: #a0a0a0;
width: 250px;
font-size: 20px;
color: #fff;
border: 1px solid #1D3C6A;
margin: 10px 40px 10px 0px;
padding: 10px 10px;
}
#btn0:hover,
#btn1:hover,
#btn2:hover,
#btn3:hover {
cursor: pointer;
background-color: #00994d;
}
#btn0:focus,
#btn1:focus,
#btn2:focus,
#btn3:focus {
outline: 0;
}
#progress {
color: #2b2b2b;
font-size: 18px;
}
<div class="grid">
<div id="quiz">
<h1>Can you spot the fake email?</h1>
<hr style="margin-bottom: 20px">
<p id="question"></p>
<div class="buttons">
<button id="btn0"><span id="choice0"></span></button>
<button id="btn1"><span id="choice1"></span></button>
</div>
<hr style="margin-top: 50px">
<footer>
<p id="progress">Question x of y</p>
</footer>
</div>
</div>
When user clicks button I trigger class and I add it second name, on second I have written to get swapped, I wrote you basically full project, and please read the whole comments, to understand logic
//Calling Elements from DOM
const button = document.querySelectorAll(".check");
const images = document.querySelectorAll(".image");
const answer = document.querySelector("h1");
//Declaring variable to randomly insert any object there to insert source in DOM Image sources
let PreparedPhotos;
//Our Images Sources and With them are its fake or not
//fake: true - yes its fake
//fake: false - no its real
const image = [
[
{
src:
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/1200px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg",
fake: true
},
{
src:
"http://graphics8.nytimes.com/images/2012/04/13/world/europe/mona-lisa-like-new-images/mona-lisa-like-new-images-custom4-v3.jpg",
fake: false
}
],
[
{
src:
"https://cdn.shopify.com/s/files/1/0849/4704/files/Creacion_de_Adan__Miguel_Angel_f5adb235-bfa8-4caa-8ffb-c5328cbad953_grande.jpg?12799626327330268216",
fake: false
},
{
src:
"https://cdn.shopify.com/s/files/1/0849/4704/files/First-image_Fb-size_grande.jpg?10773543754915177139",
fake: true
}
]
];
//Genrating Random Photo on HTML
function setRandomPhoto() {
//Random Number which will be length of our array of Object
//if you array includes 20 object it will generate random number
// 0 - 19
const randomNumber = Math.floor(Math.random() * image.length);
//Decalaring our already set variable as Array Object
PreparedPhoto = image[randomNumber];
//Our first DOM Image is Variables first object source
images[0].src = PreparedPhoto[0].src;
//and next image is next object source
images[1].src = PreparedPhoto[1].src;
}
//when windows successfully loads, up function runs
window.addEventListener("load", () => {
setRandomPhoto();
});
//buttons click
//forEach is High Order method, basically this is for Loop but when you want to
//trigger click use forEach - (e) is single button whic will be clicked
button.forEach((e) => {
e.addEventListener("click", () => {
//decalring variable before using it
let filtered;
//finding from our DOM image source if in our long array exists
//same string or not as Image.src
//if it exists filtered variable get declared with that found obect
for (let i = 0; i < image.length; i++) {
for (let k = 0; k < 2; k++) {
if (image[i][k].src === images[0].src) {
filtered = image[i][k];
}
}
}
//basic if else statement, if clicked button is Fake and image is true
//it outputs You are correct
//if clicked button is Real and Image is false it outputs Correct
//Else its false
//Our image checking comes from filtered variable
if (e.innerText === "Fake" && filtered.fake === true) {
answer.innerText = "You Are Correct";
images.forEach((image) => {
image.classList.toggle("hidden");
});
} else if (e.innerText === "Real" && filtered.fake === false) {
answer.innerText = "You Are Correct";
images.forEach((image) => {
image.classList.toggle("hidden");
});
} else {
answer.innerHTML = "You are Wrong";
images.forEach((image) => {
image.classList.toggle("hidden");
});
}
});
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100%;
min-height: 100vh;
display: flex;
justify-content: space-around;
align-items: center;
flex-direction: column;
}
.image-fluid {
display: flex;
}
.image-fluid .image {
width: 200px;
margin: 0 10px;
transition: 0.5s;
}
.image-fluid .image:nth-child(1).hidden {
transform: translateX(110px);
}
.image-fluid .image:nth-child(2).hidden {
transform: translateX(-110px);
}
<div class="container">
<div class="image-fluid">
<img src="" class="image hidden">
<img src="" class="image hidden">
</div>
<div class="button-fluid">
<button class="check">Fake</button>
<button class="check">Real</button>
</div>
</div>
<h1></h1>

Generate a multiple choice quiz with REST Countries API JSON data using Vanilla Javascript. Randomize order of answers

I'm trying to build a geography game/quiz using the Google Maps API and the REST Countries API as well. I have the Google Maps functionality working along with the REST Countries API as well. What's happening is, on each page refresh, a random country loads and data from the REST Countries API generates (it's hidden so the user will only get hints as to what country it is). The map also pans to the center of the random country(hybrid image only with no country label). This is exactly what I want to happen.
I have a loop in my Javascript pulling countries (answers) for the multiple choice section but it's only pulling from the beginning. Is there a way to randomize the label and insert the correct answer (countryData.name) in there somewhere? I had this working but my code was not dry at all and the first selection was always the right answer. Not sure how to get the loop to pull answers at random either. This is the last thing I need to do before I can start styling and fixing the point system. Sorry, had to hide my Google API key.
const countriesList = document.getElementById("countries");
let gameTitle = document.querySelector(".gameTitle");
let selectedCountry;
let updateScore = document.querySelector(".score");
let guesses = document.querySelector(".wrongAnswers");
let score = 0;
let wrongAnswers = 0;
// Rest Countries
function newQuestion() {
fetch("https://restcountries.eu/rest/v2/all")
.then(response => response.json())
.then(data => initialize(data))
.catch(error => console.log("Error:", error));
function initialize(countriesData) {
// Define countries
countries = countriesData;
// Create an empty string where you add your option value in at random as a string
let options = "";
countries.forEach(country => options += `<option value="${country.alpha3Code}">${country.name}</option>`);
countriesList.innerHTML = options;
// Random index of one instance of the API call
countriesList.selectedIndex = Math.floor(Math.random() * countriesList.length);
// Set or return the index of the selected value
// for display card
displayCountryInfo(countriesList[countriesList.selectedIndex].value);
// displayCountryInfo(countriesList.value);
}
function displayCountryInfo(countryByAlpha3Code) {
let addQuestions = document.querySelector(".question");
const countryData = countries.find(country => country.alpha3Code === countryByAlpha3Code);
selectedCountry = countryData.name;
document.querySelector("#flag-container img").src = countryData.flag;
// If the flag fails to load, display the country name
document.querySelector("#flag-container img").alt = `flag of ${countryData.name}`;
document.getElementById("country").innerHTML = countryData.name;
document.getElementById("capital").innerHTML = countryData.capital;
document.getElementById("population").innerHTML = countryData.population.toLocaleString("en-US");
document.getElementById("currencies").innerHTML = countryData.currencies.filter(c => c.name).map(c => `${c.name} (${c.code})`).join(', ');
let languages = document.getElementById("languages").innerHTML = countryData.languages.filter(l => l.name).map(l => `${l.name}`).join(', ');
document.getElementById("region").innerHTML = countryData.region;
document.getElementById("subregion").innerHTML = countryData.subregion;
document.getElementById("lat-long").innerHTML = countryData.latlng;
initMap(countryData);
addQuestions.innerHTML = `I am located in ${countryData.subregion}. There are ${countryData.languages.length} language(s) spoken here: ${languages}. My capital city is ${countryData.capital}. What's my name?`;
function multipleChoice() {
for (let i = 0; i < 7; i++) {
let $input = document.querySelector('#inputs');
$input.innerHTML = $input.innerHTML + `<input id='choice${i}' name='countries' type='radio' onchange='getValue(this)' value='${countries[i].name}'/> ${countries[i].name}`;
}
}
multipleChoice();
}
// Access Google Maps API
function initMap(country) {
// Create a variable
let myLatLng = new google.maps.LatLng(country.latlng[0], country.latlng[1]);
//object literal
//instantiate map with mapOptions object
let mapOptions = {
center: myLatLng,
zoom: 5,
disableDefaultUI: true,
mapTypeId: 'satellite',
heading: 90,
tilt: 45,
rotateControl: true,
}
let marker = new google.maps.Marker({
position: myLatLng
});
// Create map
let map = new google.maps.Map(document.getElementById("mapDiv"), mapOptions);
// Set marker
marker.setMap(map);
}
}
newQuestion();
function getValue(element) {
if (element.value === selectedCountry) {
score++;
updateScore.innerHTML = `Score: ${score}`;
newQuestion();
if (score === 10) {
gameTitle.innerHTML = "You Won!";
}
} else {
wrongAnswers++;
guesses.innerHTML = `Wrong guesses ${wrongAnswers}`
newQuestion();
if (wrongAnswers === 3) {
gameTitle.innerHTML = "Game Over!";
}
}
}
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 15px;
background: rgb(51, 45, 45);
height: 100%;
margin: 0;
padding: 0;
}
p {
padding: 0 2.5rem 2.5rem;
margin: 0;
}
h3, .score, .wrongAnswers {
text-align: center;
}
html {
font-family: 'Dosis', sans-serif;
height: 100%;
}
#mapDiv {
height: 50%;
}
#main-container {
display: none;
float: right;
width: 502px;
margin: 30px auto;
padding: 0;
}
#flag-container {
float: left;
height: 252px;
width: 502px;
background-color: rgb(19, 16, 16);
border: 10px solid rgb(32, 13, 28);
box-shadow: 2px 4px 25px rgb(27, 4, 4);
}
#flag-container img {
width: 100%;
height: 100%;
}
#quiz-container {
color: white;
background: rgb(51, 45, 45);
overflow: hidden;
}
#media screen and (max-width: 768px) {
body {
font-size: 12px;
}
#main-container {
width: 342px;
}
#flag-container {
height: 172px;
width: 50%;
}
#info-container select {
font-size: 12px; font-weight: 600;
}
}
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>MapApp Quiz</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="main.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> -->
</head>
<body>
<div id="mapDiv"><h1></h1></div>
<div id="root"></div>
<div id="flag-container">
<img src="" alt="">
</div>
<div id="main-container">
<div class="card">
<div id="info-container">
<select id="countries"></select>
<p>Country: <span id="country"></span></p>
<p>Capital: <span id="capital"></span></p>
<p>Population: <span id="population"></span></p>
<p>Currencies: <span id="currencies"></span></p>
<p>Languages: <span id="languages"></span></p>
<p>Region: <span id="region"></span></p>
<p>Subregion: <span id="subregion"></span></p>
<p>Lat/Long: <span id="lat-long"></span></p>
</div>
</div>
</div>
<div id="quiz-container">
<h3 class="gameTitle">MapApp Quiz</h3>
<h5 class="score">Score: 0</h5>
<h5 class="wrongAnswers">Wrong guesses: 0</h5>
<p class="question"></p>
<form id="inputs">
</form>
</div>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=[API_KEY]"></script>
<script src="main.js" async defer></script>
</body>
</html>
Ok, so it's not clear to me how you're selecting the "correct" answer, but let's assume you have one country that is the answer and then the list of all countries. You could make a list of 4 random countries plus the correct country like so:
function generateAnswers(answer, allCountries) {
let wrongCountries = allCountries.filter(country => country.alpha3Code !== answer.alpha3Code);
const countOfAnswers = 5;
const positionOfCorrectAnswer = Math.floor(Math.random() * (countOfAnswers + 1));
const answers = [];
for(let i=0; i< countOfAnswers; i++) {
if (i === positionOfCorrectAnswer) {
answers.push(answer);
} else {
let randomAnswer = wrongCountries[Math.floor(Math.random() * wrongCountries.length)];
wrongCountries = wrongCountries.filter(country => country.alpha3Code !== randomAnswer.alpha3Code);
answers.push({ name: randomAnswer.name, alpha3Code: randomAnswer.alpha3Code } );
}
}
return answers;
};
Ok, so this function should return you an array of 5 countries including the right answer and 4 random wrong answers. You can call this in your code that builds the options and build the HTML from it, rather than using the full list of countries.

mathjax with jquery fadein

In a html page with mathematical formulas using MathJax, I'm trying a smooth transition in the change from one formula to the another.
Here is the current code, that you can test here
<!DOCTYPE html>
<html>
<head>
<!-- https://jsfiddle.net/LnfL020r/2 -->
<title>math guided training</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
TeX: {
Macros: {
mgtMult: "",
mgtSelect: [ "\\bbox[10px,border:1px solid red]{#1}", 1],
}
}
});
</script>
<script type="text/javascript"
src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>
<style>
.results {
display: flex;
height: 4cm;
position: relative;
}
#fadeBox,
#visibleBox {
width: 100%;
height: 100%;
position: absolute;
top: 0%;
left: 0;
}
</style>
</head>
<body>
<script>
var QUEUE = MathJax.Hub.queue; // shorthand for the queue
var math = null; // the jax element
var box = null; // the box math is in
var formula = "1+2x^3";
var SHOWBOX = function () {
var a = $('#box').html();
var dstDiv = $('#visibleBox');
dstDiv.html(a);
}
var SHOWBOX_FADE = function () {
var a = $('#box').html();
var dstDiv = $('#visibleBox');
var fadeDiv = $('#fadeBox');
fadeDiv.html(a);
fadeDiv.fadeIn(2000,function() {
dstDiv.html(a);
fadeDiv.hide();
});
}
var REFRESH = function () {
QUEUE.Push(
["Text",math,formula], // == math.Text(formula), [ method, object, args... ]
SHOWBOX
);
}
var REFRESH_FADE = function () {
QUEUE.Push(
["Text",math,formula], // [ method, object, args... ]
SHOWBOX_FADE
);
}
// Get the element jax when MathJax has produced it.
QUEUE.Push(
function () {
math = MathJax.Hub.getAllJax("box")[0]; // returns a MathJax.ElementJax
math.Text(formula);
SHOWBOX();
}
);
setTimeout(function(){
SHOWBOX();
}, 2000);
window.changeIt = function() {
formula = "1 + 2 { \\left( y + 4 \\right) } ^ 3 ";
REFRESH_FADE();
}
</script>
</head>
<body>
<div id="box" style="visibility:hidden; font-size: 500%; height:1px;">
\( \)
</div>
<div class="results">
<div id="visibleBox" style="font-size: 500%;">
Loading ...
</div>
<div id="fadeBox" style="font-size: 500%; display:none;">
</div>
</div>
<button onclick='changeIt()'/>click me</button>
</body>
</html>
The problem is:
The second formula has different height than the first one, due to the parenthesis. For this reason, the common part "1 + " of the second one is printed slightly down respect to its print in the first formula.
That produces an effect of borrow during the transition. I want that the "1 + " part, common to both formulas, doesn't moves when changing from first to second.
Any hint?
maybe this will work :
var SHOWBOX = function () {
var a = $('#box').html();
var dstDiv = $('#visibleBox');
// apply margin for the first equation
dstDiv.css({"margin-top":"2px"});
dstDiv.html(a);
}
var SHOWBOX_FADE = function () {
var a = $('#box').html();
var dstDiv = $('#visibleBox');
var fadeDiv = $('#fadeBox');
fadeDiv.html(a).fadeOut();
fadeDiv.fadeIn(500,function() {
dstDiv.html(a);
// remove the margin for the second equation
dstDiv.css({"margin-top":"0"});
fadeDiv.hide();
});
}
just applying a margin to counter the slight variance in top margin
Made little change in fadeIn and fadeOut
fadeDiv.html(a);
dstDiv.fadeOut(1000,function() {
dstDiv.html(a);
fadeDiv.fadeIn(1000);
});
}
Forked fiddle
Please comment if transition is not up to expection
EDIT
Updated With requirement
Fiddle link
window.onload=function(){
var QUEUE = MathJax.Hub.queue; // shorthand for the queue
var math = null; // the jax element
var mathdef = null; // the jax element
var box = null; // the box math is in
var defaultformula = "1+";
var formula = "2x^2";
var SHOWBOX = function () {
var a = $('#box').html();
var def = $('#defbox').html();
var fixedDiv = $('#fixed');
fixedDiv.html(def);
var dstDiv = $('#visibleBox');
dstDiv.html(a);
}
var SHOWBOX_FADE = function () {
var a = $('#box').html();
var dstDiv = $('#visibleBox');
var fadeDiv = $('#fadeBox');
fadeDiv.html(a);
dstDiv.fadeOut(1000,function() {
dstDiv.html(a);
fadeDiv.fadeIn(1000);
});
}
var REFRESH = function () {
QUEUE.Push(
["Text",math,formula], // == math.Text(formula), [ method, object, args... ]
SHOWBOX
);
QUEUE.Push(
["Text",mathdef,defaultformula], // == math.Text(formula), [ method, object, args... ]
SHOWBOX
);
}
var REFRESH_FADE = function () {
QUEUE.Push(
["Text",math,formula], // [ method, object, args... ]
SHOWBOX_FADE
);
}
// Get the element jax when MathJax has produced it.
QUEUE.Push(
function () {
math = MathJax.Hub.getAllJax("box")[0]; // returns a MathJax.ElementJax
mathdef = MathJax.Hub.getAllJax("defbox")[0]; // returns a MathJax.ElementJax
mathdef.Text(defaultformula);
math.Text(formula);
SHOWBOX();
}
);
setTimeout(function(){
SHOWBOX();
}, 2000);
window.changeIt = function() {
formula = "2 { \\left( y + 4 \\right) } ^ 2";
REFRESH_FADE();
}
}//]]>
.results {
display: flex;
height: 4cm;
position: relative;
}
#fadeBox,
#visibleBox {
width: 100%;
height: 100%;
position: absolute;
top: 0%;
left: 0;
}
<!DOCTYPE html>
<html>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
TeX: {
Macros: {
mgtMult: "",
mgtSelect: [ "\\bbox[10px,border:1px solid red]{#1}", 1],
},
Macros: {
mgtMult: "",
mgtSelect: [ "\\bdefbox[10px,border:1px solid red]{#1}", 1],
}
}
});
</script>
<script type="text/javascript"
src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>
<div id="defbox" style="visibility:hidden; font-size: 500%; height:1px;padding-top:10px">
\( \)
</div>
<div id="box" style="visibility:hidden; font-size: 500%; height:1px;padding-left:200px">
\( \)
</div>
<div class="results">
<div id="fixed" style="font-size: 500%;margin-top:50x">
Loading ...
</div>
<div id="visibleBox" style="font-size: 500%;padding-left:100px">
</div>
<div id="fadeBox" style="font-size: 500%; display:none;padding-left:100px">
</div>
</div>
<button onclick='changeIt()'>click me</button>
</body>
</html>

Javascript end game when click on image

Hey this is my first time on Stackoverflow!
I am building a small javascript html5 game where you click on objects kind of like whack-a-mole.. The goal is to kill as many "gem green" and " gem blue" as possible in 10 seconds, and when you click on the "gem red".. the game ends and plays a sound.
I got most things to work, except I can't find a way to make the game end when clicking on "gem red".. I have tried lots of functions and listeners.. but to no avail.. can anyone help me figure this out?
Here is the code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>HTML 5 Gem Game</title>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
<style>
section#game {
width: 480px;
height: 800px;
max-width: 100%;
max-height: 100%;
overflow: hidden;
position: relative;
background-image: url('img/Splash.png');
position: relative;
color: #ffffff;
font-size: 30px;
font-family: "arial,sans-serif";
}
section#game .score{
display: block;
position: absolute;
top: 10px;
left: 10px;
}
section#game .time{
display: block;
position: absolute;
top: 10px;
right: 10px;
}
section#game .start{
display: block;
padding-top: 40%;
margin: 0 auto 0 auto;
text-align: center;
width: 70%;
cursor: pointer;
}
section#game .start .high-scores{
text-align: left;
}
section#game .gem{
display: block;
position: absolute;
width: 40px;
height: 44px;
cursor: pointer;
}
section#game .gem.green{
background: url('img/Gem Green.png') no-repeat top left;
}
section#game .gem.blue{
background: url('img/Gem Blue.png') no-repeat top left;
}
section#game .gem.red{
background: url('img/Gem Red.png') no-repeat top left;
}
</style>
<script>
function addEvent(element, event, delegate ) {
if (typeof (window.event) != 'undefined' && element.attachEvent)
element.attachEvent('on' + event, delegate);
else
element.addEventListener(event, delegate, false);
}
function Game(){
var game = document.querySelector("section#game");
var score = game.querySelector("section#game span.score");
var high_scores = game.querySelector("section#game ol.high-scores");
var time = game.querySelector("section#game span.time");
var start = game.querySelector("section#game span.start");
function Gem(Class, Value, MaxTTL) {
this.Class = Class;
this.Value = Value;
this.MaxTTL = MaxTTL;
};
var gems = new Array();
gems[0] = new Gem('green', 10, 1.2);
gems[1] = new Gem('blue', 20, 1);
gems[2] = new Gem('red', 50, 0.75);
function Click(event)
{
if(event.preventDefault) event.preventDefault();
if (event.stopPropagation) event.stopPropagation();
else event.cancelBubble = true;
var target = event.target || event.srcElement;
if(target.className.indexOf('gem') > -1){
var value = parseInt(target.getAttribute('data-value'));
var current = parseInt( score.innerHTML );
var audio = new Audio('music/blaster.mp3');
audio.play();
score.innerHTML = current + value;
target.parentNode.removeChild(target);
}
return false;
}
function Remove(id) {
var gem = game.querySelector("#" + id);
if(typeof(gem) != 'undefined')
gem.parentNode.removeChild(gem);
}
function Spawn() {
var index = Math.floor( ( Math.random() * 3 ) );
var gem = gems[index];
var id = Math.floor( ( Math.random() * 1000 ) + 1 );
var ttl = Math.floor( ( Math.random() * parseInt(gem.MaxTTL) * 1000 ) + 1000 ); //between 1s and MaxTTL
var x = Math.floor( ( Math.random() * ( game.offsetWidth - 40 ) ) );
var y = Math.floor( ( Math.random() * ( game.offsetHeight - 44 ) ) );
var fragment = document.createElement('span');
fragment.id = "gem-" + id;
fragment.setAttribute('class', "gem " + gem.Class);
fragment.setAttribute('data-value', gem.Value);
game.appendChild(fragment);
fragment.style.left = x + "px";
fragment.style.top = y + "px";
setTimeout( function(){
Remove(fragment.id);
}, ttl)
}
<!-- parse high score keeper -->
function HighScores() {
if(typeof(Storage)!=="undefined"){
var scores = false;
if(localStorage["high-scores"]) {
high_scores.style.display = "block";
high_scores.innerHTML = '';
scores = JSON.parse(localStorage["high-scores"]);
scores = scores.sort(function(a,b){return parseInt(b)-parseInt(a)});
for(var i = 0; i < 10; i++){
var s = scores[i];
var fragment = document.createElement('li');
fragment.innerHTML = (typeof(s) != "undefined" ? s : "" );
high_scores.appendChild(fragment);
}
}
} else {
high_scores.style.display = "none";
}
}
function UpdateScore() {
if(typeof(Storage)!=="undefined"){
var current = parseInt(score.innerHTML);
var scores = false;
if(localStorage["high-scores"]) {
scores = JSON.parse(localStorage["high-scores"]);
scores = scores.sort(function(a,b){return parseInt(b)-parseInt(a)});
for(var i = 0; i < 10; i++){
var s = parseInt(scores[i]);
var val = (!isNaN(s) ? s : 0 );
if(current > val)
{
val = current;
scores.splice(i, 0, parseInt(current));
break;
}
}
scores.length = 10;
localStorage["high-scores"] = JSON.stringify(scores);
} else {
var scores = new Array();
scores[0] = current;
localStorage["high-scores"] = JSON.stringify(scores);
}
HighScores();
}
}
function Stop(interval) {
clearInterval(interval);
}
this.Start = function() {
score.innerHTML = "0";
start.style.display = "none";
var interval = setInterval(Spawn, 750);
var count = 10;
var counter = null;
function timer()
{
count = count-1;
if (count <= 0)
{
var left = document.querySelectorAll("section#game .gem");
for (var i = 0; i < left.length; i++) {
if(left[i] && left[i].parentNode) {
left[i].parentNode.removeChild(left[i]);
}
}
Stop(interval);
Stop(counter);
time.innerHTML = "Game Over!";
start.style.display = "block";
UpdateScore();
return;
} else {
time.innerHTML = count + "s left";
}
}
counter = setInterval(timer, 1000);
setTimeout( function(){
Stop(interval);
}, count * 1000)
};
addEvent(game, 'click', Click);
addEvent(start, 'click', this.Start);
HighScores();
}
addEvent(document, 'readystatechange', function() {
if ( document.readyState !== "complete" )
return true;
var game = new Game();
});
</script>
</head>
<body>
<div id="page">
<section id="game">
<span class="score">0</span>
<span class="time">0</span>
<span class="start">START!
<ol class="high-scores"></ol>
</span>
</section>
</div>
</body>
</html>
Alessio -
You only need a few minor changes to your code to make it work. The example below should help you get started in the right direction. Good luck.
Changes:
Add an endGame() function and move the stop game logic from the timer() function into it.
Add a line to the click() function to check for red gem clicks.
if (target.className.indexOf('red') > 0) endGame("Red Gem - You win!");
Declare the count, counter, and interval variables at the top of your Game object.
The code below also has a few minor CSS changes used for debugging which you can remove.
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>HTML 5 Gem Game</title>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
<style>
section#game {
width: 480px;
height: 800px;
max-width: 100%;
max-height: 100%;
overflow: hidden;
position: relative;
background-image: url('img/Splash.png');
border: 1px red dotted;
position: relative;
color: red;
font-size: 30px;
font-family: "arial,sans-serif";
}
section#game .score{
display: block;
position: absolute;
top: 10px;
left: 10px;
}
section#game .time{
display: block;
position: absolute;
top: 10px;
right: 10px;
}
section#game .start{
display: block;
padding-top: 40%;
margin: 0 auto 0 auto;
text-align: center;
width: 70%;
cursor: pointer;
}
section#game .start .high-scores{
text-align: left;
}
section#game .gem{
display: block;
position: absolute;
width: 40px;
height: 44px;
cursor: pointer;
}
section#game .gem.green{
background: url('img/Gem Green.png') no-repeat top left;
background-color: green;
}
section#game .gem.blue{
background: url('img/Gem Blue.png') no-repeat top left;
background-color: blue;
}
section#game .gem.red{
background: url('img/Gem Red.png') no-repeat top left;
background-color: red;
}
</style>
<script>
function addEvent(element, event, delegate ) {
if (typeof (window.event) != 'undefined' && element.attachEvent)
element.attachEvent('on' + event, delegate);
else
element.addEventListener(event, delegate, false);
}
function Game(){
var game = document.querySelector("section#game");
var score = game.querySelector("section#game span.score");
var high_scores = game.querySelector("section#game ol.high-scores");
var time = game.querySelector("section#game span.time");
var start = game.querySelector("section#game span.start");
var interval, counter, count;
function Gem(Class, Value, MaxTTL) {
this.Class = Class;
this.Value = Value;
this.MaxTTL = MaxTTL;
};
var gems = new Array();
gems[0] = new Gem('green', 10, 1.2);
gems[1] = new Gem('blue', 20, 1);
gems[2] = new Gem('red', 50, 0.75);
function Click(event)
{
if(event.preventDefault) event.preventDefault();
if (event.stopPropagation) event.stopPropagation();
else event.cancelBubble = true;
var target = event.target || event.srcElement;
if(target.className.indexOf('gem') > -1){
var value = parseInt(target.getAttribute('data-value'));
var current = parseInt( score.innerHTML );
var audio = new Audio('music/blaster.mp3');
audio.play();
score.innerHTML = current + value;
target.parentNode.removeChild(target);
if (target.className.indexOf('red') > 0) endGame("Red Gem - You win!");
}
return false;
}
function Remove(id) {
var gem = game.querySelector("#" + id);
if(typeof(gem) != 'undefined')
gem.parentNode.removeChild(gem);
}
function Spawn() {
var index = Math.floor( ( Math.random() * 3 ) );
var gem = gems[index];
var id = Math.floor( ( Math.random() * 1000 ) + 1 );
var ttl = Math.floor( ( Math.random() * parseInt(gem.MaxTTL) * 1000 ) + 1000 ); //between 1s and MaxTTL
var x = Math.floor( ( Math.random() * ( game.offsetWidth - 40 ) ) );
var y = Math.floor( ( Math.random() * ( game.offsetHeight - 44 ) ) );
var fragment = document.createElement('span');
fragment.id = "gem-" + id;
fragment.setAttribute('class', "gem " + gem.Class);
fragment.setAttribute('data-value', gem.Value);
game.appendChild(fragment);
fragment.style.left = x + "px";
fragment.style.top = y + "px";
setTimeout( function(){
Remove(fragment.id);
}, ttl)
}
<!-- parse high score keeper -->
function HighScores() {
if(typeof(Storage)!=="undefined"){
var scores = false;
if(localStorage["high-scores"]) {
high_scores.style.display = "block";
high_scores.innerHTML = '';
scores = JSON.parse(localStorage["high-scores"]);
scores = scores.sort(function(a,b){return parseInt(b)-parseInt(a)});
for(var i = 0; i < 10; i++){
var s = scores[i];
var fragment = document.createElement('li');
fragment.innerHTML = (typeof(s) != "undefined" ? s : "" );
high_scores.appendChild(fragment);
}
}
} else {
high_scores.style.display = "none";
}
}
function UpdateScore() {
if(typeof(Storage)!=="undefined"){
var current = parseInt(score.innerHTML);
var scores = false;
if(localStorage["high-scores"]) {
scores = JSON.parse(localStorage["high-scores"]);
scores = scores.sort(function(a,b){return parseInt(b)-parseInt(a)});
for(var i = 0; i < 10; i++){
var s = parseInt(scores[i]);
var val = (!isNaN(s) ? s : 0 );
if(current > val)
{
val = current;
scores.splice(i, 0, parseInt(current));
break;
}
}
scores.length = 10;
localStorage["high-scores"] = JSON.stringify(scores);
} else {
var scores = new Array();
scores[0] = current;
localStorage["high-scores"] = JSON.stringify(scores);
}
HighScores();
}
}
function Stop(interval) {
clearInterval(interval);
}
function endGame( msg ) {
count = 0;
Stop(interval);
Stop(counter);
var left = document.querySelectorAll("section#game .gem");
for (var i = 0; i < left.length; i++) {
if(left[i] && left[i].parentNode) {
left[i].parentNode.removeChild(left[i]);
}
}
time.innerHTML = msg || "Game Over!";
start.style.display = "block";
UpdateScore();
}
this.Start = function() {
score.innerHTML = "0";
start.style.display = "none";
interval = setInterval(Spawn, 750);
count = 10;
counter = null;
function timer()
{
count = count-1;
if (count <= 0)
{
endGame();
return;
} else {
time.innerHTML = count + "s left";
}
}
counter = setInterval(timer, 1000);
setTimeout( function(){
Stop(interval);
}, count * 1000)
};
addEvent(game, 'click', Click);
addEvent(start, 'click', this.Start);
HighScores();
}
addEvent(document, 'readystatechange', function() {
if ( document.readyState !== "complete" )
return true;
var game = new Game();
});
</script>
</head>
<body>
<div id="page">
<section id="game">
<span class="score">0</span>
<span class="time">0</span>
<span class="start">START!
<ol class="high-scores"></ol>
</span>
</section>
</div>
</body>
</html>
For starters, you shouldn't include a style sheet and your entire HTML file since neither is relevant and you should use a canvas element instead of this chaotic use of CSS and html elements, which would allow the size of your code to be halved. Furthermore, you should be able to fix this by just changing some global boolean variable to false when the red gem is clicked and when the boolean variable is false (this if statement belongs at the end of your game loop) you call Stop(arg)/clearInterval(arg). Given that your current code doesn't seem to have a global boolean variable indicating game state (using an enumeration would generally be a cleaner solution but a simple boolean seems to suit this case)

Categories