Random value not in the range of array/dictionary - javascript

I just started learning Javascript this week to write a wordgame to help my kid. The idea is that it should show and play a random word from a dictionary, she writes it the entry is checked and random gets deleted from the dictionary. The game continues until the dictionary length===0, and the wrong words are summarised if there are any. Somehow, the program is unpredictable, it literally works 1 in 7 times, and I can't understand why. Giving the following error:
Uncaught (in promise) TypeError: Cannot read property 'word' of undefined
I do think it has something the do with the way I delete random, or check if the dictonary is empty. Underneath the code is pasted links to screenshots of the console.log; 1 is the result of the program completely finishing, the other of one that doesn't. The interesting thing is that the error also is unpredictable, sometimes after just 1 word, sometimes 2. The only thing I do is refresh the page and the program behaves differently. I also tried running it on different browsers.
Being a total noob, I was quite surprised that I get different results while trying to do the same thing. It was quite frustrating to find out what was happening :-)
<html>
<head>
<title>Aliyah's dictee spel</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="header">
<h1>Hej! Velkommen til Aliyahs diktee!</h1>
</div>
<div id="Random_word">
<h2 id="Empty">Click start to start</h2>
<button id="startGame">Start</button>
<button id="editList">Edit word list</button>
<h3 id="correctWord"></h3>
</div>
<script>
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
var dictionary = [
{ word: "apple",
audio: 'apple.mp3',
},
{
word: "baby",
audio: 'baby.mp3',
},
{
word: "car",
audio: 'car.mp3'
}
];
var wordsWrong = [];
var wordsCorrectCounter = 0;
var wordsWrongCounter = 0;
//var cheer = new Audio('correct.mp3');
//var boo = new Audio('wrong.mp3');
function editWords(){
console.log("under construction");
};
function startGame(){
document.getElementById("startGame").remove();
document.getElementById("editList").remove();
newWord(dictionary);
};
async function newWord(dictionary)
{
if (Object.entries(dictionary).length === 0){
endGame();
}
else {
var random = Math.floor(Math.random() * dictionary.length);
document.getElementById("Empty").innerHTML = dictionary[random].word;
console.log(dictionary[random].word);
console.log(dictionary);
await sleep(2000);
document.getElementById("Empty").innerHTML = " ";
createInputField(random);
}
};
function createInputField(random)
{
var entry = document.createElement("input");
entry.setAttribute("type", "text");
entry.id = "inputfield";
document.body.appendChild(entry);
let btn = document.createElement("button");
btn.id = "okBtn";
btn.innerHTML = "ok";
btn.type = "submit";
btn.name = "answerBtn";
document.body.appendChild(btn);
document.getElementById("okBtn").addEventListener("click", () => checkAnswer(random, entry.value));
};
function checkAnswer(random, entry)
{var answer = entry.toLowerCase();
if (dictionary[random].word == answer){
//cheer.play();
wordsCorrectCounter += 1;
document.getElementById("okBtn").remove();
document.getElementById("inputfield").remove();
delete dictionary[random];
console.log(dictionary);
newWord(dictionary);
}
else{
wordsWrong.push(dictionary[random].word);
wordsWrongCounter += 1;
document.getElementById("okBtn").remove();
document.getElementById("inputfield").remove();
//boo.play();
document.body.style.backgroundColor = "red";
document.getElementById("correctWord").innerHTML = dictionary[random].word;
let btn = document.createElement("button");
btn.id = "contBtn";
btn.innerHTML = "Continue";
btn.type = "submit";
btn.name = "continueBtn";
document.body.appendChild(btn);
document.getElementById("contBtn").addEventListener("click", () => wrongAnswer(random));
}
};
function wrongAnswer(random){
document.getElementById("contBtn").remove();
document.getElementById("correctWord").innerHTML = " "
delete dictionary[random];
newWord(dictionary);
};
function endGame()
{
document.getElementById("Empty").innerHTML = "you are done!" + "Correct: " + wordsCorrectCounter + "Wrong: " + wordsWrongCounter
+ "These words were wrong: " + wordsWrong;
};
function Refresh() {
window.parent.location = window.parent.location.href;
};
document.getElementById("startGame").addEventListener("click", () => startGame());
</script>
</body>
</html>
Console.log when program is able to finish
Console.log when program gets stuck

Short Explain
dictionary = [
{ word: "apple", audio: 'apple.mp3', },
{ word: "baby", audio: 'baby.mp3', },
{ word: "car", audio: 'car.mp3' }
];
you genrate a random index let's say 2
you can jsut use 2 and extract the word by dictionary[2].word
if you pass the random index
it could be invalid after you remove item from dictionary
which is why you get the error
for example:
you have the old random index 2
but you have already remove the item
the current dictionary is
dictionary = [
{ word: "apple", audio: 'apple.mp3', },
{ word: "baby", audio: 'baby.mp3', },
];
and then you try to access dictionary[2]
which doesn't exist anymore
Remove Item from Array
you can use Array.filter()
let dictionary = [
{ word: "apple", audio: 'apple.mp3', },
{ word: "baby", audio: 'baby.mp3', },
{ word: "car", audio: 'car.mp3' }
];
// only the item that item.word != "apple" will pass this
dictionary = dictionary.filter(item => item.word != "apple");
console.log(dictionary);
String and Variable
Template literals (Template strings)
use this to set string, it more readable & easier to edit
you can put variable in it by use ${variable_name}
example:
let x = "test";
console.log(`this is a ${x}`);
Find specific item in Array
I see you have audio file
If you need to find the item in dictionary
You can use Array.find()
Because we only pass word now
We can use it to find it
Let's say you want to find audio for apple
It will be like this
let dictionary = [
{ word: "apple", audio: 'apple.mp3', },
{ word: "baby", audio: 'baby.mp3', },
{ word: "car", audio: 'car.mp3' }
],
target = dictionary.find(item => item.word=="apple"),
audio = false;
if(target) audio = target.audio;
console.log(audio);
Complete Answer
I have add the comment in, you can check it
<html>
<head>
<title>Aliyah's dictee spel</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="header">
<h1>Hej! Velkommen til Aliyahs diktee!</h1>
</div>
<div id="Random_word">
<h2 id="Empty">Click start to start</h2>
<button id="startGame">Start</button>
<button id="editList">Edit word list</button>
<h3 id="correctWord"></h3>
</div>
<script>
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
var dictionary = [
{ word: "apple", audio: 'apple.mp3', },
{ word: "baby", audio: 'baby.mp3', },
{ word: "car", audio: 'car.mp3' }
];
var wordsWrong = [];
var wordsCorrectCounter = 0;
var wordsWrongCounter = 0;
//var cheer = new Audio('correct.mp3');
//var boo = new Audio('wrong.mp3');
function editWords() {
console.log("under construction");
}
function startGame() {
document.getElementById("startGame").remove();
document.getElementById("editList").remove();
newWord();
}
// dictionary is global variable, you don't need to pass it to access it
async function newWord() {
// I add this so the color will be reset after click continue
document.body.style.backgroundColor = "";
if (Object.entries(dictionary).length === 0) {
endGame();
} else {
var random = Math.floor(Math.random() * dictionary.length),
// get the random word here when it still exist in dictionary
random_word = dictionary[random].word;
document.getElementById("Empty").innerHTML = random_word;
console.log(random_word);
console.log(dictionary);
await sleep(2000);
document.getElementById("Empty").innerHTML = " ";
// direct pass the ramdom word, not the ramdom index
// ramdom index could be invalid after you remove item from dictionary
// which is why you get the error
createInputField(random_word);
}
}
function createInputField(random_word) {
var entry = document.createElement("input");
entry.setAttribute("type", "text");
entry.id = "inputfield";
document.body.appendChild(entry);
let btn = document.createElement("button");
btn.id = "okBtn";
btn.innerHTML = "ok";
btn.type = "submit";
btn.name = "answerBtn";
document.body.appendChild(btn);
document.getElementById("okBtn").addEventListener("click", () => checkAnswer(random_word, entry.value));
}
function checkAnswer(random_word, entry) {
var answer = entry.toLowerCase();
if (random_word == answer) {
//cheer.play();
// if you only need +1, you can use ++
wordsCorrectCounter++;
document.getElementById("okBtn").remove();
document.getElementById("inputfield").remove();
// use Array.filter() to remove random_word(answer) from the dictionary
// only word != random_word will pass
dictionary = dictionary.filter(item => item.word != random_word);
console.log(dictionary);
newWord();
} else {
// I didn't see this, so I add it
// if you only need +1, you can use ++
wordsWrongCounter++;
// because we pass the random_word(answer) now, we can just push it
wordsWrong.push(random_word);
document.getElementById("okBtn").remove();
document.getElementById("inputfield").remove();
//boo.play();
document.body.style.backgroundColor = "red";
document.getElementById("correctWord").innerHTML = random_word;
let btn = document.createElement("button");
btn.id = "contBtn";
btn.innerHTML = "Continue";
btn.type = "submit";
btn.name = "continueBtn";
document.body.appendChild(btn);
document.getElementById("contBtn").addEventListener("click", () => wrongAnswer(random_word));
}
}
function wrongAnswer(random_word) {
document.getElementById("contBtn").remove();
document.getElementById("correctWord").innerHTML = " ";
// same as above
// use Array.filter() to remove correct_word(answer) from the dictionary
// only word != correct_word will pass
dictionary = dictionary.filter(item => item.word != random_word);
newWord();
}
function endGame() {
/*
use `` to set string, it more readable & easier to edit
you can put variable in it by use ${variable_name}
example:
let x = "test";
console.log(`this is a ${x}`);
result:
this is a test
*/
document.getElementById("Empty").innerHTML =
`you are done! Correct: ${wordsCorrectCounter} Wrong: ${wordsWrongCounter} These words were wrong: ${wordsWrong}`;
}
function Refresh() {
window.parent.location = window.parent.location.href;
}
document.getElementById("startGame").addEventListener("click", () => startGame());
</script>
</body>
</html>

I just got the issue in your code, it's a bit conceptual thing and nothing much.
See what's going wrong in this screenshot of console
You can see there the length of the dictionary(array) is still the same after deleting the word(object).
Because you have used: delete keyword which deletes an item and replaces it with empty and size of the array remains the same.
Hence the new dictionary after deleting the first word became: [empty, {...}, {...}]
Now whenever you will try to get dictionary[0].word gives you an error: Cannot read property of undefine, because it's empty
Instead of using delete keyword you can simply use dictionary.splice(random, 1)
See the console screenshot after using splice
<html>
<head>
<title>Aliyah's dictee spel</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="header">
<h1>Hej! Velkommen til Aliyahs diktee!</h1>
</div>
<div id="Random_word">
<h2 id="Empty">Click start to start</h2>
<button id="startGame">Start</button>
<button id="editList">Edit word list</button>
<h3 id="correctWord"></h3>
</div>
<script>
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
var dictionary = [{
word: "apple",
audio: 'apple.mp3',
},
{
word: "baby",
audio: 'baby.mp3',
},
{
word: "car",
audio: 'car.mp3'
}
];
var wordsWrong = [];
var wordsCorrectCounter = 0;
var wordsWrongCounter = 0;
var cheer = new Audio('correct.mp3');
var boo = new Audio('wrong.mp3');
function editWords() {
console.log("under construction");
};
function startGame() {
document.getElementById("startGame").remove();
document.getElementById("editList").remove();
newWord(dictionary);
};
async function newWord(dictionary) {
if (Object.entries(dictionary).length === 0) {
endGame();
} else {
var random = Math.floor(Math.random() * dictionary.length);
console.log(random)
document.getElementById("Empty").innerHTML = dictionary[random].word;
console.log(dictionary[random].word);
console.log(dictionary);
await sleep(2000);
document.getElementById("Empty").innerHTML = " ";
createInputField(random);
}
};
function createInputField(random) {
var entry = document.createElement("input");
entry.setAttribute("type", "text");
entry.id = "inputfield";
document.body.appendChild(entry);
let btn = document.createElement("button");
btn.id = "okBtn";
btn.innerHTML = "ok";
btn.type = "submit";
btn.name = "answerBtn";
document.body.appendChild(btn);
document.getElementById("okBtn").addEventListener("click", () => checkAnswer(random, entry.value));
};
function checkAnswer(random, entry) {
var answer = entry.toLowerCase();
if (dictionary[random].word == answer) {
cheer.play();
wordsCorrectCounter += 1;
document.getElementById("okBtn").remove();
document.getElementById("inputfield").remove();
dictionary.splice(random, 1);
console.log(dictionary);
newWord(dictionary);
} else {
wordsWrong.push(dictionary[random].word);
wordsWrongCounter += 1;
document.getElementById("okBtn").remove();
document.getElementById("inputfield").remove();
boo.play();
document.body.style.backgroundColor = "red";
document.getElementById("correctWord").innerHTML = dictionary[random].word;
let btn = document.createElement("button");
btn.id = "contBtn";
btn.innerHTML = "Continue";
btn.type = "submit";
btn.name = "continueBtn";
document.body.appendChild(btn);
document.getElementById("contBtn").addEventListener("click", () => wrongAnswer(random));
}
};
function wrongAnswer(random) {
document.getElementById("contBtn").remove();
document.getElementById("correctWord").innerHTML = " "
delete dictionary[random];
newWord(dictionary);
};
function endGame() {
document.getElementById("Empty").innerHTML = "you are done!" + "Correct: " + wordsCorrectCounter + "Wrong: " + wordsWrongCounter +
"These words were wrong: " + wordsWrong;
};
function Refresh() {
window.parent.location = window.parent.location.href;
};
document.getElementById("startGame").addEventListener("click", () => startGame());
</script>
</body>
</html>

Related

Audio doesn't play in javascript - when alert is triggered

I am basically trying to edit this extension (AlertForWords-1.0.0) so that if it finds a specific word, it will alert me and play a sound. But the problem is, it doesn't produce any sounds at all. Here is the code:
let text = document.body.innerText
let textToFind = ""
chrome.storage.sync.get({
text: '',
time: 1000
}, (items) => {
textToFind = items.text;
let regexStr = "("
textToFind.split("\n").forEach((word, index) => {
if (index != textToFind.split("\n").length-1) {
regexStr += word + "|"
} else {
regexStr += word
}
});
regexStr += ")"
let regex = new RegExp(regexStr, "gi")
const mp3_url = 'https://media.geeksforgeeks.org/wp-content/uploads/20190531135120/beep.mp3';
let src = 'https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3';
let audio = new Audio(src);
setTimeout(() => {
let found = text.match(regex)
if (found) {
//new Audio(mp3_url).play();
audio.play();
alert(`Found Word --`)
}
}, items.time)
});
Any suggestions are appreciated. I've been trying a few different things but nothing seems to work.

How do I iterate over an async function without completely skipping over all the steps in the function?

Here is the link to my repo's github page, so you can properly see what I mean.
I am currently having an issue with my triviaGame function when trying to make it recursive, but it's sort of "backfiring" on me in a sense.
You'll notice after you answer the first question, everything seems fine. It goes to the next question fine. After that though, it seems like the iterations of it double? The next answer it skips 2. After that, 4. And finally the remaining 2 (adding up to 10, due to how I am iterating over them).
How might I be able to correctly iterate over a recursive function, so it correctly calls all 10 times, and then returns when it is done?
Been struggling with this for hours, and just can't seem to get it to work. My javascript code is below, sorry for any headaches that it may give you. I know I make some questionable programming decisions. Ignore some of the commented out stuff, it's not finished code yet. I'm a beginner, and hope that once I learn what's going on here it will stick with me, and I don't make a stupid mistake like this again.
const _URL = "https://opentdb.com/api.php?amount=1&category=27&type=multiple";
const _questionHTML = document.getElementById("question");
const _answerOne = document.getElementById("answer-1");
const _answerTwo = document.getElementById("answer-2");
const _answerThree = document.getElementById("answer-3");
const _answerFour = document.getElementById("answer-4");
const btns = document.querySelectorAll("button[id^=answer-]");
var runCount = 1;
var correct = 0;
// Credits to my friend Jonah for teaching me how to cache data that I get from an API call.
var triviaData = null;
async function getTrivia() {
return fetch("https://opentdb.com/api.php?amount=1&category=27&type=multiple")
.then((res) => res.json())
.then((res) => {
triviaData = res;
return res;
});
}
// anywhere I want the trivia data:
// const trivia = await getTrivia() --- makes the call, or uses the cached data
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
async function triviaGame() {
const trivia = await getTrivia();
async function appendData() {
let totalAnswers = [
...trivia.results[0].incorrect_answers,
trivia.results[0].correct_answer,
];
// Apparently I need 2 different arrays to sort them because array variables are stored by reference? Learn something new everyday I guess.
let totalAnswers2 = [...totalAnswers];
let sorted = shuffleArray(totalAnswers2);
// Ensures the proper symbol shows instead of the HTML entities
const doc = new DOMParser().parseFromString(
trivia.results[0].question,
"text/html"
);
_questionHTML.textContent = doc.documentElement.textContent;
console.log(trivia.results[0].correct_answer, "- Correct Answer");
// Appends info to the DOM
_answerOne.textContent = sorted[0];
_answerTwo.textContent = sorted[1];
_answerThree.textContent = sorted[2];
_answerFour.textContent = sorted[3];
}
async function checkAnswer() {
btns.forEach((btn) => {
btn.addEventListener("click", (event) => {
console.log(runCount);
if (event.target.textContent === trivia.results[0].correct_answer) {
event.target.style.backgroundColor = "#52D452";
// Disables all buttons after one has been clicked.
btns.forEach((btn) => {
btn.disabled = true;
});
setTimeout(() => {
if (runCount === 10) {
return;
}
runCount++;
correct++;
btns.forEach((btn) => {
btn.disabled = false;
});
btn.style.backgroundColor = "";
document.getElementById(
"amount-correct"
).textContent = `${correct}/10`;
triviaGame();
}, 2000);
} else {
event.target.style.backgroundColor = "#FF3D33";
btns.forEach((btn) => {
btn.disabled = true;
});
// document.getElementById("correct-text").textContent =
// trivia.results[0].correct_answer;
// document.getElementById("correct-answer").style.visibility =
// "visible";
setTimeout(() => {
if (runCount === 10) {
return;
}
// document.getElementById("correct-answer").style.visibility =
// "hidden";
btns.forEach((btn) => {
btn.disabled = false;
btn.style.backgroundColor = "";
});
runCount++;
triviaGame();
}, 3500);
}
});
});
}
checkAnswer();
appendData();
}
triviaGame();
Any/All responses are much appreciated and repsected. I could use any help y'all are willing to give me. The past 6 hours have been a living hell for me lol.
It's skipping questions once an answer is clicked because every time a button is clicked, another event listener is added to the button, while the original one is active:
On initial load: triviaGame() runs which makes checkAnswer() run which adds event listeners to each of the buttons.
Event listeners on buttons: 1.
Answer button is clicked, triviaGame() runs which makes checkAnswer() run which adds event listeners to each of the buttons.
Event listeners on buttons: 2.
Answer button is clicked, triviaGame() runs twice (from the 2 listeners attached) which makes checkAnswer() run twice where both invocations adds event listeners to each of the buttons.
Event listeners on buttons: 4.
etc.
To fix this, I moved the content of checkAnswer() outside of any functions so it only ever runs once. However, doing this, it loses reference to the upper scope variable trivia. To resolve this, I used the triviaData variable instead which checkAnswer() would have access to and I change references in appendData() to match this. Now, triviaGame() function only exists to call appendData() function inside it; there is little point in this so I merge the two functions together into one function, instead of two nested inside each other.
const _URL = "https://opentdb.com/api.php?amount=1&category=27&type=multiple";
const _questionHTML = document.getElementById("question");
const _answerOne = document.getElementById("answer-1");
const _answerTwo = document.getElementById("answer-2");
const _answerThree = document.getElementById("answer-3");
const _answerFour = document.getElementById("answer-4");
const btns = document.querySelectorAll("button[id^=answer-]");
var runCount = 1;
var correct = 0;
// Credits to my friend Jonah for teaching me how to cache data that I get from an API call.
var triviaData = null;
async function getTrivia() {
return fetch("https://opentdb.com/api.php?amount=1&category=27&type=multiple")
.then((res) => res.json())
.then((res) => {
triviaData = res;
return res;
});
}
// anywhere I want the trivia data:
// const trivia = await getTrivia() --- makes the call, or uses the cached data
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
async function appendData() {
triviaData = await getTrivia();
let totalAnswers = [
...triviaData.results[0].incorrect_answers,
triviaData.results[0].correct_answer,
];
// Apparently I need 2 different arrays to sort them because array variables are stored by reference? Learn something new everyday I guess.
let totalAnswers2 = [...totalAnswers];
let sorted = shuffleArray(totalAnswers2);
// Ensures the proper symbol shows instead of the HTML entities
const doc = new DOMParser().parseFromString(
triviaData.results[0].question,
"text/html"
);
_questionHTML.textContent = doc.documentElement.textContent;
console.log(triviaData.results[0].correct_answer, "- Correct Answer");
// Appends info to the DOM
_answerOne.textContent = sorted[0];
_answerTwo.textContent = sorted[1];
_answerThree.textContent = sorted[2];
_answerFour.textContent = sorted[3];
}
btns.forEach((btn) => {
btn.addEventListener("click", (event) => {
console.log(runCount);
if (event.target.textContent === triviaData.results[0].correct_answer) {
event.target.style.backgroundColor = "#52D452";
// Disables all buttons after one has been clicked.
btns.forEach((btn) => {
btn.disabled = true;
});
setTimeout(() => {
if (runCount === 10) {
return;
}
runCount++;
correct++;
btns.forEach((btn) => {
btn.disabled = false;
});
btn.style.backgroundColor = "";
document.getElementById(
"amount-correct"
).textContent = `${correct}/10`;
appendData();
}, 2000);
} else {
event.target.style.backgroundColor = "#FF3D33";
btns.forEach((btn) => {
btn.disabled = true;
});
// document.getElementById("correct-text").textContent =
// trivia.results[0].correct_answer;
// document.getElementById("correct-answer").style.visibility =
// "visible";
setTimeout(() => {
if (runCount === 10) {
return;
}
// document.getElementById("correct-answer").style.visibility =
// "hidden";
btns.forEach((btn) => {
btn.disabled = false;
btn.style.backgroundColor = "";
});
runCount++;
appendData();
}, 3500);
}
});
});
appendData();
<div id="amount-correct"></div>
<h1 id="question"></h1>
<button id="answer-1"></button>
<button id="answer-2"></button>
<button id="answer-3"></button>
<button id="answer-4"></button>

Event listener fires 2 functions at the same time but partially

I am making a list where you can edit or delete items. I am listening for 2 events on the same table. One for edit and one for delete, I am listening on the table and not the actual buttons as they are created dinamycally. Edit and Delete both have the same id, the id of the product, which I am using later for the http requests.
,
Now when I press edit the console.logs from the delete functions fire up,but nothing happens, if I am trying to save the item the http request doesnt work, it will not take the id (but it logs it to the console)
If I press the delete button once nothing happnes, if I press it a second time the page refreshes and the item is deleted.
Is there a way to listen for those events separately or for them to not interfere with one another? All I want is if I press the edit button the respective item's values to go to the input field and when I press Add Item update the product in the JSON file as well, and on delete press, to delete the item from the JSON file and remove from the html document.
Update: Managed to resolve edit function
Here is my code:
import { http } from "./http.js";
import { ui } from "./ui.js";
const productsURL = "https://61363d1a8700c50017ef54c1.mockapi.io/product";
// const addProductBtn = document.querySelector('.new-product-btn');
const adminContainer = document.querySelector('.admin-container');
const addItem = document.querySelector('.admin-add-item-btn');
const imgInput = document.getElementById('image');
const nameInput = document.getElementById('name');
const priceInput = document.getElementById('price');
const stockInput = document.getElementById('stock');
const categoryInput = document.getElementById('category');
const typeInput = document.getElementById('type');
const descriptionInput = document.getElementById('description');
const validSvg = document.querySelectorAll('.valid_input_svg');
// const adminForm = document.getElementById('admin-form');
const adminTable = document.getElementById('admin-tbody');
const editBtn = document.querySelectorAll('.edit-btn');
const adminBtn = document.querySelectorAll('.admin-delete-btn');
const cancel = document.getElementById('cancel');
let productToEdit;
let edit = false;
let id;
document.addEventListener('DOMContentLoaded', listAdminProducts);
// adminForm.addEventListener('submit', validateInput);
addItem.addEventListener('click', addOrEditProducts);
// adminTable.addEventListener('click', editOrDeleteItem);
adminTable.addEventListener('click', deleteProduct);
adminTable.addEventListener('click', editProduct);
cancel.addEventListener('click', cancelEdit);
function listAdminProducts() {
http.get(productsURL).then(products => {
ui.showAllAdminProducts(products);
});
};
function addOrEditProducts() {
if (edit === true && validateInput() === true) {
productToEdit = {
image: imgInput.value,
name: nameInput.value,
price: priceInput.value,
stock: stockInput.value,
category: categoryInput.value,
type: typeInput.value,
description: descriptionInput.value,
};
http
.put(`${productsURL}/${id}`, productToEdit)
.then(() => listAdminProducts());
console.log(`${productsURL}/${id}`)
ui.clearFields();
id = '';
edit = false;
return;
} else if (edit === false && validateInput() === true) {
const product = {
image: imgInput.value,
name: nameInput.value,
price: priceInput.value,
stock: stockInput.value,
category: categoryInput.value,
type: typeInput.value,
description: descriptionInput.value
};
http.post(productsURL, product).then(() => listAdminProducts());
ui.clearFields();
};
};
function editProduct(e) {
console.log('works');
if (e.target.classList.contains('edit-btn')) {
edit = true;
id = e.target.getAttribute('id');
console.log(id);
console.log(e.target)
http.get(`${productsURL}/${id}`).then((data) => {
imgInput.value = data.image;
nameInput.value = data.name;
priceInput.value = data.price;
stockInput.value = data.stock;
categoryInput.value = data.category;
typeInput.value = data.type;
descriptionInput.value = data.description;
});
console.log(`${productsURL}/${id}`)
};
// id = '';
}
function deleteProduct(e) {
console.log(e.target);
if (e.target.className === 'admin-delete-btn') {
console.log(e.target);
id = e.target.getAttribute('id');
console.log(id);
http
.delete(`${productsURL}/${id}`)
.then(() => listAdminProducts())
.catch("Error on delete");
id = '';
}
ui.showSuccessMessage('Product deleted', adminContainer);
}
function cancelEdit() {
ui.clearFields;
imgInput.className = '';
nameInput.className = '';
priceInput.className = '';
stockInput.className = '';
categoryInput.className = '';
edit = false;
}
function validateInput() {
let valid = true;
if (imgInput.value == '') {
if (imgInput.classList.contains('input-invalid')) {
imgInput.classList.remove('input-invalid');
};
ui.showAdminMessage('Must contain a link to an image', 0);
imgInput.classList.add('input-invalid');
valid = false;
} else {
imgInput.classList.add('input-valid');
validSvg[0].style.display = "block";
removeClass(imgInput, 0);
};
if (nameInput.value === '') {
if (nameInput.classList.contains('input-invalid')) {
nameInput.classList.remove('input-invalid');
};
ui.showAdminMessage('Name is requierd', 1);
nameInput.classList.add('input-invalid');
valid = false;
} else {
// stockInput.classList.remove('input-invalid');
nameInput.classList.add('input-valid');
validSvg[1].style.display = "block";
removeClass(nameInput, 1);
};
if (priceInput.value == "" || isNaN(priceInput.value) || priceInput.value < 0) {
if (priceInput.classList.contains('input-invalid')) {
priceInput.classList.remove('input-invalid');
};
ui.showAdminMessage('Price must be a number greater then 0', 2);
priceInput.classList.add('input-invalid');
valid = false;
} else {
// stockInput.classList.remove('input-invalid');
priceInput.classList.add('input-valid');
validSvg[2].style.display = "block";
removeClass(priceInput, 2);
};
if (stockInput.value == "" || isNaN(stockInput.value) || stockInput.value < 0) {
if (stockInput.classList.contains('input-invalid')) {
stockInput.classList.remove('input-invalid');
};
ui.showAdminMessage('Stock must be a number greater then 0', 3);
stockInput.classList.add('input-invalid');
valid = false;
} else {
// stockInput.classList.remove('input-invalid');
stockInput.classList.add('input-valid');
validSvg[3].style.display = "block";
removeClass(stockInput, 3);
};
if (categoryInput.value === 'barware' || categoryInput.value === 'spirits') {
// categoryInput.classList.remove('input-invalid');
categoryInput.classList.add('input-valid');
validSvg[4].style.display = "block";
removeClass(categoryInput, 4);
} else {
ui.showAdminMessage('Category must be barware or spirits', 4);
categoryInput.classList.add('input-invalid');
valid = false;
};
return valid;
};
function removeClass(element, index) {
// console.log(element, index);
setTimeout(() => {
element.className = '';
validSvg[index].style.display = "none";
}, 3000)
}
Finally managed to resolve it, I had to put e.preventDefault() in both functions, so the page will not reload first

Discord.js message.embeds[0] not outputting anything;

I've been trying to program a discord bot to play hangman except I'm doing it in embeds to learn how they work. Unfortunately, I've been at a roadblock trying to resend my embed after they got a letter right or not.
I tried to follow the discord.js guide and use the message.embeds[0] command but it doesn't output anything. The example embed just outputs with the field I added after.
Here's the important part:
const gameEmbed = new Discord.MessageEmbed()
.setColor(embedColour)
.setTitle('Hangman')
.setDescription('Play hangman in discord!')
.addFields(
{ name: chosenWord.length.toString() + '-letter word', value: seenWord },
);
const receivedEmbed = message.embeds[0];
const exampleEmbed = new Discord.MessageEmbed(receivedEmbed).addFields({ name: chosenWord.length.toString() + '-letter word', value: seenWord });
message.channel.send(gameEmbed);
collector.on('collect', m => {
if (!knownLetters.includes(m.content.toLowerCase()) || !incorrectLetters.includes(m.content.toLowerCase())) {
if (wordLetters.includes(m.content.toLowerCase())) {
message.channel.send('Congratulations! You guessed a letter correctly!');
knownLetters[wordLetters.indexOf(m.content.toLowerCase())] = m.content.toLowerCase();
updateWord();
message.channel.send(exampleEmbed);
}
And the whole program, in case there's anything you need:
module.exports = {
name: 'hangman',
description: 'Play hangman in discord!',
run(message, args, Discord) {
const words = require('./words.json');
const chosenWord = words.words[Math.floor(Math.random() * 981)];
const wordLetters = new Array();
const knownLetters = new Array();
const incorrectLetters = new Array();
let seenWord = '';
let i;
const embedColour = Math.floor(Math.random() * 16777215).toString(16);
for (i = 0; i < chosenWord.length; i++) {
wordLetters.push(chosenWord[i]);
}
for (i = 0; i < chosenWord.length; i++) {
knownLetters.push('\\_', ' ');
}
function updateWord() {
for (i = 0; i < knownLetters.length; i++) {
seenWord += knownLetters[i];
}
}
updateWord();
const filter = m => m.author.id === m.author.id && !m.author.bot && m.content.toLowerCase().length == 1;
const collector = new Discord.MessageCollector(message.channel, filter, {
time: 1000 * 30,
});
const gameEmbed = new Discord.MessageEmbed()
.setColor(embedColour)
.setTitle('Hangman')
.setDescription('Play hangman in discord!')
.addFields(
{ name: chosenWord.length.toString() + '-letter word', value: seenWord },
);
const receivedEmbed = message.embeds[0];
const exampleEmbed = new Discord.MessageEmbed(receivedEmbed).addFields({ name: chosenWord.length.toString() + '-letter word', value: seenWord });
message.channel.send(chosenWord);
message.channel.send(gameEmbed);
collector.on('collect', m => {
if (!knownLetters.includes(m.content.toLowerCase()) || !incorrectLetters.includes(m.content.toLowerCase())) {
if (wordLetters.includes(m.content.toLowerCase())) {
message.channel.send('Congratulations! You guessed a letter correctly!');
knownLetters[wordLetters.indexOf(m.content.toLowerCase())] = m.content.toLowerCase();
updateWord();
message.channel.send(exampleEmbed);
}
else {
message.channel.send('I\'m sorry, that letter is not in the word. Try again!');
}
}
else {
message.chanel.send('You have already guessed that letter. Try again!');
}
});
collector.on('end', (collected) => {
message.channel.send(`done, collected ${collected.size} messages`);
});
},
};
message.embeds is an empty array as the user only sends a simple message, so you can't use that. What you can do is... change the field you added to your embed and send that again:
gameEmbed.fields[0] = {
name: chosenWord.length.toString() + '-letter word',
value: seenWord,
};
message.channel.send(gameEmbed);
I've made quite a few changes (and added comments) in your code, but at least it seems to be working now:
const words = require('./words.json');
const chosenWord = words.words[Math.floor(Math.random() * words.length)];
// destructuring is easier than a for loop
const wordLetters = [...chosenWord];
// don't use new Array(), use [] instead
const knownLetters = [];
const incorrectLetters = [];
let seenWord = '';
for (let i = 0; i < chosenWord.length; i++) {
knownLetters.push('\\_', ' ');
}
function updateWord() {
// you need to reset seenWord, not just add new characters to it
seenWord = '';
for (let i = 0; i < knownLetters.length; i++) {
seenWord += knownLetters[i];
}
}
updateWord();
// m.author.id === m.author.id doesn't make sense
const filter = (m) => !m.author.bot && m.content.toLowerCase().length == 1;
const collector = new Discord.MessageCollector(message.channel, filter, {
time: 1000 * 30,
});
const gameEmbed = new Discord.MessageEmbed()
// you can use RANDOM to set the colour to random :)
.setColor('RANDOM')
.setTitle('Hangman')
.setDescription('Play hangman in discord!')
.addFields({
name: chosenWord.length.toString() + '-letter word',
value: seenWord,
});
message.channel.send(chosenWord);
message.channel.send(gameEmbed);
collector.on('collect', (m) => {
const letter = m.content.toLowerCase();
// try to return early to avoid ifs inside ifs inside ifs
if (knownLetters.includes(letter) || incorrectLetters.includes(letter)) {
return message.chanel.send(
'You have already guessed that letter. Try again!',
);
}
if (!wordLetters.includes(letter)) {
// don't forget to add letter to incorrectLetters
incorrectLetters.push(letter);
return message.channel.send(
"I'm sorry, that letter is not in the word. Try again!",
);
}
message.channel.send('Congratulations! You guessed a letter correctly!');
// you need to double the index if you added spaces between the underscores
// TODO: it only replaces the first occurrence, you need to replace all!
knownLetters[wordLetters.indexOf(letter) * 2] = letter;
updateWord();
// simply update the first field
gameEmbed.fields[0] = {
name: chosenWord.length.toString() + '-letter word',
value: seenWord,
};
// and send the same embed
message.channel.send(gameEmbed);
});
collector.on('end', (collected) => {
message.channel.send(`done, collected ${collected.size} messages`);
});

How can i replace an array element multiple times?

console.log("Start file 1 =========================================");
function Letter (letter) {
this.letter = letter;
this.guess = false;
this.answer = function () {
if (!this.guess) {
return "_";
}
else if (this.guess === true) {
return this.letter;
}
}
this.letterTest = function (guessed) {
if (guessed === this.letter) {
this.guess = true;
// this.letter = guessed;
} else {
this.letter = "_";
}
}
};
module.exports = Letter;
console.log("End file 1 =========================================");
console.log("Start file 2 =========================================");
var Letter = require("./letter.js");
function Word (word) {
this.splitWord = [];
for (var i = 0; i < word.length; i++) {
var eachLetter = new Letter (word[i]);
this.splitWord.push(eachLetter);
}
this.stringRep = function () {
var testedWord = [];
for (var i = 0; i < this.splitWord.length; i++) {
testedWord.push(eachLetter.answer());
}
testedWord = testedWord.join(" ");
// console.log(testedWord);
return testedWord;
};
this.eachGuess = function (input) {
for (var i = 0; i < this.splitWord.length; i++) {
this.splitWord[i].letterTest(input);
}
}
}
module.exports = Word;
console.log("End file 2 =========================================");
console.log("Start file 3 =========================================");
var Word = require("./word.js");
var inquirer = require('inquirer');
var remainingGuesses = 10;
var mainGame;
var currentWord;
var liveWord = [];
completeWord = null;
let countryPicker;
let testedWord;
var europe = ["Albania", "Austria", "Belgium", "Bulgaria", "Croatia", "Cyprus", "Denmark", "England", "France", "Greece", "Germany",
"Hungary", "Iceland", "Italy", "Lithuania", "Monaco", "Norway", "Poland", "Portugal", "Romania", "Serbia", "Slovakia", "Spain", "Sweden",
"Switzerland", "Ukraine"];
// Function that picks a random country
function pickCountry () {
countryPicker = europe[Math.floor((Math.random() * europe.length) + 1)];
var mainGame2 = new Word (countryPicker);
mainGame = mainGame2;
// Display word
currentWord = mainGame.stringRep();
currentWord = JSON.stringify(currentWord);
console.log("Word: " + currentWord);
};
pickCountry();
// Delete this after
console.log("1: " + countryPicker);
console.log("2: " + currentWord);
inquirer.prompt([
{
type: "input",
name: "game",
message: "Guess a letter!"
}
]).then(function(inquirerResponse) {
liveWord = mainGame.splitWord;
mainGame.eachGuess(inquirerResponse.game);
for (let i = 0; i < liveWord.length; i++) {
console.log(mainGame.splitWord[i].letter);
}
});
I built a hangman game using constructor functions. I have set it up so when a random word is chosen, each letter will be displayed as an underscore.
I used the inquirer package to ask for a letter. When the first letter is guessed correctly it successfully replaces the the underscore with the letter in the liveWord array. The problem is, it only works for one letter. I need to make it work until the full word is guessed. FYI File 1 and 2 are only there for reference, my problem is only in file 3. No need to look at the first 2. Any tips?
There are several issues in your code:
In the letterTest method you set the letter property to underscore when the guess is different from the letter. This is wrong, because then you'll never know again what the correct letter is. Remove this. It is enough to have the right value for the guess property
In the stringRep method you refer to the variable eachLetter in the loop, which has nothing to do there. Instead you should use this.splitWord[i].
There is no loop that allows the user to make a second guess, so it is only normal it only works once. There needs to be a loop that decreases the value of your variable remainingGuesses, which you didn't use so far.
You should not display splitWord. This is related to point 1. Instead you should display stringRep() which takes into account whether or not the letter was already guessed based on the guessed property. And that is exactly what you need to output.
Logic is missing that detects whether the word was found completely. In that case a message would be appropriate and the guessing cycle (which was not implemented) should be interrupted.
To facilitate the looping, I would suggest to use the async/await syntax for dealing with the promise returned by inquirer.
Here is your code with the above-listed points corrected. For the purpose of making it runnable in this snippet widget, I did not split it into modules as you did nor included inquirer (I put a simplified replacement for it inline).
I also did not attempt to make many other improvements, so that you could still recognise your own work. All changes are commented:
function Letter (letter) {
this.letter = letter;
this.guess = false;
this.answer = function () {
if (!this.guess) {
return "_";
}
else { // No need to check whether this.guess is true here. It is the logical consequence...
return this.letter;
}
}
this.letterTest = function (guessed) {
if (guessed === this.letter) {
this.guess = true;
}
// Don't change this.letter, as you will forever forget what the correct letter is!
// (code was deleted from here)
}
}
function Word (word) {
this.splitWord = [];
for (var i = 0; i < word.length; i++) {
var eachLetter = new Letter (word[i]);
this.splitWord.push(eachLetter);
}
this.stringRep = function () {
var testedWord = [];
for (var i = 0; i < this.splitWord.length; i++) {
// Don't push eachLetter, but use the i-variable as index!
testedWord.push(this.splitWord[i].answer());
}
testedWord = testedWord.join(" ");
return testedWord;
}
this.eachGuess = function (input) {
for (var i = 0; i < this.splitWord.length; i++) {
this.splitWord[i].letterTest(input);
}
}
}
// Simplified implementation of inquirer for this snippet only:
var inquirer = {
prompt([{type, name, message}]) {
return new Promise((resolve) => {
const input = document.querySelector(`[name=${name}]`);
input.value = "";
input.placeholder = message;
input.onchange = e => resolve({ [name]: input.value });
});
}
}
var remainingGuesses = 10;
var mainGame;
var currentWord;
var liveWord = [];
completeWord = null;
let countryPicker;
let testedWord;
var europe = ["Albania", "Austria", "Belgium", "Bulgaria", "Croatia", "Cyprus", "Denmark", "England", "France", "Greece", "Germany",
"Hungary", "Iceland", "Italy", "Lithuania", "Monaco", "Norway", "Poland", "Portugal", "Romania", "Serbia", "Slovakia", "Spain", "Sweden",
"Switzerland", "Ukraine"];
function pickCountry () {
countryPicker = europe[Math.floor((Math.random() * europe.length) + 1)];
var mainGame2 = new Word (countryPicker);
mainGame = mainGame2;
currentWord = mainGame.stringRep();
currentWord = JSON.stringify(currentWord);
console.log("Word: " + currentWord);
}
pickCountry();
// Delete this after
console.log("1: " + countryPicker);
console.log("2: " + currentWord);
(async function () { // Use an async function to allow the use of AWAIT
while (remainingGuesses--) { // You need a loop to repeat guess/response cycle
// Use AWAIT -- simpler to use than THEN
const inquirerResponse = await inquirer.prompt([{
type: "input",
name: "game",
message: "Guess a letter!"
}]);
const liveWord = mainGame.splitWord;
mainGame.eachGuess(inquirerResponse.game);
// Don't display splitWord here, but stringRep:
const output = mainGame.stringRep();
console.log(output);
// Detect that the word has been found, and exit if so
if (!output.includes("_")) {
console.log("You found it!");
return;
}
}
// The maximum number of guesses was not enough to find the word
console.log('What a pity. You ran out of guesses.');
})();
Letter:
<input name="game">
<button>Guess</button>
I'll not help You with code in Your question, it's annoying to debug, rewrite somebody's code (especially when it's long).
But I'll give You an example:
const words = [
'Azerbaijan', 'America', 'blablabla', 'Argentina', 'lambada', 'Australia', 'schmetterling', 'krankenwagen', 'kugelschreiber'
];
let word, result, charIndexes;
const start = () => {
word = words[parseInt(Math.random()*words.length)];
charIndexes = {};
result = new Array(word.length+1).join('_');
word.split('').forEach((char, i) => {
char = char.toLowerCase();
if (!charIndexes[char]) charIndexes[char] = []
charIndexes[char].push(i);
});
info.innerHTML = '';
output.innerHTML = result;
input.focus();
};
const checkChar = (char) => {
result = result.split('');
if (charIndexes[char]) {
const indexes = charIndexes[char];
indexes.forEach(i => {
result[i] = word.charAt(i);
});
delete charIndexes[char];
}
result = result.join('');
output.innerHTML = result;
if (Object.keys(charIndexes).length === 0) {
info.innerHTML = 'Congratulations!';
}
};
const input = document.getElementById('letter');
const output = document.getElementById('word');
const info = document.getElementById('info');
input.onkeyup = (e) => {
const char = e.key.toLowerCase();
input.value = '';
checkChar(char);
};
start();
<input type="text" id="letter" value="">
<button onclick="start()">reset</button>
<br/><br/>
<div id="word"></div>
<div id="info"></div>

Categories