Programmatically remove an object array, JavaScript - javascript

I'm trying to programmatically remember an object array but I'm lacking skills and I don't seem to find answers on the web either.
So, I want to be able to, while drawing a cardObj, to remove its reverse(upsidedown) alternative. Note that I wanna do the same if the reverse version is drawer first - remove the upright one from the deck.
Here is the code:
Cardz Exerz!EXERZ!Past, Present & FutureSpreadPASTPRESENTFUTURE// -----------JAVASCRIPT--------------//
// CARDS OBJECT ------------------------------------------------------------------------------------>
let cardObj = [
{name: "0_Fool", imgUrl: "data/0_Fool.jpg"},
{name: "0_Fool_R", imgUrl: "data/0_Fool_R.jpg"},
//{name: "1_Magician", imgUrl: "data/1_Magician.jpg"},
//{name: "1_Magician_R", imgUrl: "data/1_Magician_R.jpg"},
];
//--- ------------------------------------------------------------------------------------>
//SHUFFLE CARDS ------------------------------------------------------------------------------------>
function shuffle(array){
let currentIndex = array.length, randomIndex;
while(currentIndex != 0){
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
}
return array;
};
shuffle(cardObj);
//--- ------------------------------------------------------------------------------------>
//DRAW ------------------------------------------------------------------------------------>
function drawCard(id){
if(cardObj.length === 0){
return;
}
let castCard = document.getElementById(id);
let card = document.createElement('img');
card.setAttribute('src', cardObj[0].imgUrl);
card.setAttribute('height', '272');
card.setAttribute('width', '185');
card.innerHTML = cardObj[0].imgUrl;
if(castCard.id === 'imgPast'){
document.getElementById("btnPast").replaceChild(card, castCard);
}else if(castCard.id === 'imgPresent'){
document.getElementById("btnPresent").replaceChild(card, castCard);
}else if(castCard.id === 'imgFuture'){
document.getElementById("btnFuture").replaceChild(card, castCard);
}
if(cardObj[0].name === cardObj[0].name){
cardObj = cardObj.filter(function(f) {return f !== cardObj.name + "_R"});
}
cardObj.shift();
return false;
}
//--- ----------------------------------------------------------------------------------</script></body></html>

I am quite confused about your question here but I'll assume what you want is to remove the "opposite" element of the array depending on the card that's drawn.
let cardObj = [
{name: "0_Fool", imgUrl: "data/0_Fool.jpg"},
{name: "0_Fool_R", imgUrl: "data/0_Fool_R.jpg"},
{name: "1_Magician", imgUrl: "data/1_Magician.jpg"},
{name: "1_Magician_R", imgUrl: "data/1_Magician_R.jpg"},
];
function drawCard(id){
if(cardObj.length === 0) return;
// assuming that all 'reverse' cards end with '_R' and that 'id' is the 'name'
const isReverse = id.endsWith('_R');
// construct opposite card ID
const oppositeCardID = isReverse ? id.substring(0, id.lastIndexOf('_')) : (id + '_R');
// Find opposite card index in the array
const oppositeCardIndex = cardObj.findIndex(card => card.name == oppositeCardID);
if (oppositeCardIndex < 0) return; // opposite card not found
// Remove opposite card from deck
cardObj.splice(oppositeCardIndex, 1);
}
console.log("Cards before drawing:", JSON.stringify(cardObj));
console.log("Drawing card 1_Magician");
drawCard("1_Magician");
console.log("Cards after drawing:", JSON.stringify(cardObj));

Related

Array of buttons does not work in JavaScript when storing and calling a button

So I have this problem, the goal of this code is that when I select 5 buttons from the table ( maximum number of clickable buttons) the 6th one that I will click, will make the first of five I clicked to be disabled, and then let the current one to be clicked. When the body is loaded, a table is created with buttons via js Example : I select the numbers : 1-2-3-4-5 and if I click the number 6 number 1 gets disabled or at least does not count between the 5 selected and gets the green class removed. I need to make this thing progressive so I will also need to track what is the "first" button I selected and I thought that the "best" way would be store the first one into an array of buttons.
That seems not to work because I get this error when the 5th button is clicked :
Uncaught TypeError: Cannot read properties of undefined (reading 'button')
at button.onclick
And that makes me think that when I access the array it actually can't store the buttons or either does not work. Am I trying to access the array wrongly? (firstButton = array_btn[previous].button) Assuming the input checks already work to verify that no more than 5 buttons are clicked how can I accomplish this goal? Thank you for the help in advance :) I apologize if some part of the code is still in Italian I did my best to set it as understandable as possible.
HTML :
<!DOCTYPE html>
<html lang="it">
<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>Lotto</title>
<link rel="stylesheet" href="stile.css" />
<style>
#import url("https://fonts.googleapis.com/css2?family=Josefin+Sans&display=swap");
#import url("https://fonts.googleapis.com/css2?family=Inter&display=swap");
</style>
<script src="lotto.js"></script>
</head>
<body onload="createTable()">
<h1>Benvenuto all'Enalotto !</h1>
<p class="istruzioni">
Seleziona dalla tabella sottostante i numeri che vuoi giocare (MASSIMO 5
!)
</p>
<br />
<table id="tab-punt" class="tabella"></table>
<br /><br />
<p id="stampa" class="punt">Stai puntando i seguenti numeri :</p>
<button onclick="randomArray(); check();" class="bottone" id="btnvinto">
Scopri se hai vinto !
</button>
<p id="vincenti" class="vincenti"></p>
<p id="vinto" class="perso"></p>
<br /><br />
</body>
</html>
JavaScript :
let num = 1;
function createTable() {
let tabella = document.getElementById("tab-punt");
for (let i = 0; i < 10; i++) {
let nuovaRiga = tabella.insertRow(-1);
for (let j = 0; j < 9; j++) {
let nuovaCella = nuovaRiga.insertCell(j);
nuovaCella.appendChild(createButton(num));
num += 1;
}
}
}
//Creating arrays to store user input
const input = new Array(5);
var click_counter = 0;
var stringa_out = "";
var i = 0;
let lastButton;
let array_btn = [];
let previous;
//Function to create every single button
function createButton(index) {
let button = document.createElement("BUTTON");
button.classList.add("bot-tab");
//All buttons will share this class
button.classList.add("click");
var text = document.createTextNode(index);
button.appendChild(text);
button.onclick = function () {
let firstButton;
let currentButton;
currentButton = this;
array_btn.push({ currentButton });
input[i] = index;
i += 1;
button.classList.add("verde");
click_counter += 1;
if (click_counter === 5) {
firstButton = array_btn[previous].button;
firstButton.disabled = true;
//lastButton = button;
firstButton.classList.remove("verde");
click_counter -= 1;
previous+=1;
}
};
return button;
}
The below code should fix you issue, if it does not, let me know your end goal, since the way you are going about this is complex, it could be way more simple.
Let me know the goal, I would be able to give you a more simply code to achieve it.
Notes:
Only the code from 'let lastButton; onwards is included
See the comment lines starting with // <- for explanation
let lastButton;
let array_btn = [];
let previous = 0; // <- This should be initialised
//Function to create every single button
function createButton(index) {
let button = document.createElement("BUTTON");
button.classList.add("bot-tab");
//All buttons will share this class
button.classList.add("click");
var text = document.createTextNode(index);
button.appendChild(text);
button.onclick = function () {
let firstButton;
let currentButton;
currentButton = this;
array_btn.push({ button: currentButton }); // <- you need to assign button property as it is accessed later on
input[i] = index;
i += 1;
button.classList.add("verde");
click_counter += 1;
if (click_counter === 5) {
console.log(array_btn[previous]);
firstButton = array_btn[previous].button; // <- 'button' property accessed here
firstButton.disabled = true;
//lastButton = button;
firstButton.classList.remove("verde");
click_counter -= 1;
previous+=1;
}
};
return button;
}
You don't need any previous, lastButton etc variables, just a smarter way to add/remove HTMLButtonElements from your array of currently active (selected) buttons. Here's a possible solution that uses the simpler CSS Grid instead of a Table:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const els = (sel, par) => (par || document).querySelectorAll(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Enalotto game:
const elGrid = el("#buttons");
// Array to store the active HTMLButtonElements
const playedButtons = [];
const playButton = (elBtn) => {
const idx = playedButtons.indexOf(elBtn);
// Remove button if already in array
if (idx > -1) {
playedButtons.splice(idx, 1);
elBtn.classList.remove("is-active");
return; // Exit function here.
}
// Remove first clicked
if (playedButtons.length === 5) {
const elBtnFirst = playedButtons.shift();
elBtnFirst.classList.remove("is-active");
}
// Add to array
playedButtons.push(elBtn);
elBtn.classList.add("is-active");
};
const newButton = (index) => elNew("button", {
type: "button",
textContent: index + 1,
className: "bot-tab click",
onclick() {
playButton(this);
}
});
const createButtons = () => {
const buttons = [...Array(90).keys()].map(newButton);
elGrid.append(...buttons);
};
// Init game:
createButtons();
#buttons {
display: grid;
grid-template-columns: repeat(9, 2rem);
grid-template-rows: repeat(10, 2rem);
}
.is-active {
background: green;
}
<div id="buttons"></div>
With the above code, when you want to get an array of the values, lotto numbers the user selected all you need is:
const userNumbers = playedButtons.map(elBtn => elBtn.textContent);
// which gives i.e:
// ["12", "45", "2", "5", "86"]

update one list instead of duplicating it

I have a list of names with points assigned to each one, each time i click on a button related to that name it adds a point then it ranks them in the html file when pressing the rank button, everything works fine but when i make changes and click rank again it adds another list instead of updating the existing one:
let characters = [
{name: document.getElementById("name 1").textContent, points: 0},
{name: document.getElementById("name 2").textContent, points: 0},
{name: document.getElementById("name 3").textContent, points: 0}
];
function choice (button) {
const buttonCharacterObject = characters.find(obj => obj.name === button.textContent);
buttonCharacterObject.points += 1;
const ranking = characters.sort((a, b) => (a.points < b.points ? 1 : -1));
console.log(ranking);
}
function rank() {
characters.forEach(function (character) {
document.getElementById('rank').innerHTML += '<li>' + character.name + '</li>
})}
<button id="name 1" onclick="choice(this)">Martin</button>
<button id="name 2" onclick="choice(this)">Sam</button>
<button id="name 3" onclick="choice(this)">John</button>
<button onclick="rank()">Rank Characters</button>
<ol id="rank">
</ol>
As discussed below the question, the problem was that list items were being added and the existing items remained. The solution was to clear the list beforehand:
function rank() {
var el = document.getElementById('rank')
el.innerHTML = ""
characters.forEach(function (character) {
el.innerHTML += '<li>' + character.name + '</li>
})}

infinite algorithm. javascript

i'm pretty new to js. i'm sorry if this sounds dumb.I have such an algorithm for javascript. when executed, it loops. Why? What is his mistake?
I have such source data
enter image description here
let incomesArray = [];
incomesArray[0] = {
income_id: incomeList[0].id,
worker_id: incomeList[0].worker_id,
worker_surname: incomeList[0].worker_surname,
worker_name: incomeList[0].worker_name,
incomeList: [
{
provider_name: incomeList[0].provider_name,
product_category: incomeList[0].product_category_name,
product_name: incomeList[0].product_name,
product_count: incomeList[0].product_count,
purchase_price: incomeList[0].purchase_price
}
],
incomeDate: incomeList[0].date,
total: incomeList[0].total
}
if(incomesArray.length > 0 ){
for(let i = 1; i < incomeList.length; i++) {
for(let j = 0; j < incomesArray.length; j++){
if(incomeList[i].id == incomesArray[j].id){
for(let k = 0; k < incomesArray[j].incomeList.length; k++){
incomesArray[j].incomeList.push({
provider_name: incomeList[i].provider_name,
product_category:
incomeList[i].product_category_name,
product_name: incomeList[i].product_name,
product_count: incomeList[i].product_count,
purchase_price: incomeList[i].purchase_price
})
}
} else if (incomesArray[j].id !== incomeList[i].id) {
incomesArray.push({
income_id: incomeList[i].id,
worker_id: incomeList[i].worker_id,
worker_surname: incomeList[i].worker_surname,
worker_name: incomeList[i].worker_name,
incomeList: [
{
provider_name: incomeList[i].provider_name,
product_category: incomeList[i].product_category_name,
product_name: incomeList[i].product_name,
product_count: incomeList[i].product_count,
purchase_price: incomeList[i].purchase_price
}
],
incomeDate: incomeList[i].date,
total: incomeList[i].total
})
}
}
}
}
I will be grateful for any help or hint.
It's your else part that causes the problem. For every item in incomesArray that has a different id than the item in incomeList you add another entry to incomesArray; while you're iterating over that array. Like this gif:
Extending the track while you're driving on it.
Second, your comparison: if(incomeList[i].id == incomesArray[j].id) but incomesArray[j] has no id, only a income_id, so you'll always run into the else part where you add more items to incomesArray.
I've restructured your code a bit:
const incomesArray = [];
for (const row of incomeList) {
let target = incomesArray.find(item => item.income_id === row.id);
if (!target) {
incomesArray.push(target = {
income_id: row.id,
worker_id: row.worker_id,
worker_surname: row.worker_surname,
worker_name: row.worker_name,
incomeList: [],
incomeDate: row.date,
total: row.total
});
}
target.incomeList.push({
provider_name: row.provider_name,
product_category: row.product_category_name,
product_name: row.product_name,
product_count: row.product_count,
purchase_price: row.purchase_price
});
}

How to display a result in .innerHTML from a chosen answer in an object

I want to make a website which asks some questions and then will generate 3 random locations based on the users answers. The part I am struggling with is getting the end result to output the locations based on the first question in the object which is:
const questions = [
{
"question": "What area of the world are you thinking of discovering next?",
"answer1": "Europe",
"answer1Array": "europeArray",
"answer2": "Asia",
"answer2Array": "asiaArray",
"answer3": "America",
"answer3Array": "americaArray",
"answer4": "Australasia",
"answer4Array": "australasiaArray"
},
let europeArray1 = ['Algarve - Portugal', 'Asturias and Covadonga - Spain', 'Mdina & Zebbug - Malta']
let asiaArray1 = ['Yakushima Island - Japan', 'Mount Wudang Shan - China', 'Bantayan Island - Philippines'] etc...
I have then created a function to give me back a random location:
let finalEuropeArray1 = europeArray1.sort(function() {
return 0.5 - Math.random();
});
and finally a function to output 3 results
function randomHoliday (array) {
if (array == europeArray1) {
return finalEuropeArray1.slice(europeArray1,3);
} else if (array == americaArray1) {
return finalAmericaArray1.slice(americaArray1,3);
} else if (array == asiaArray1 {
return finalAsiaArray1.slice(asiaArray1,3);
} else {
return finalAustralasiaArray1.slice(australasiaArray1,3);
}
I am able to return 3 separate values with a function calling the first element in each:
let resultA = function seperateArrayA (randomHoliday) {
return `1. ${randomHoliday[0]}`;}
let resultB = function seperateArrayB (randomHoliday) {
return `2. ${randomHoliday[1]}`;}
let resultC = function seperateArrayC (randomHoliday) {
return `3. ${randomHoliday[2]}`;}
but I do not know how to output this in my final .innerHTML based on the first question in the questions object. I can only get it to work by putting in a manual entry e.g. ${resultA(australasiaArray1)}
if(currentQuestion == totalQuestions) {
container.style.display = 'none';
result.innerHTML =
`<h1 class="final-score">Our Top 3 Recommendations:</h1>
<div class="summary">
<p><br></br></p>
<p>${resultA(australasiaArray1)}</p>
<p>${resultB(australasiaArray1)}</p>
<p>${resultC(australasiaArray1)}</p>
</div>
<button class="restart">Restart Quiz</button>
`
};
The setup can be simplified by using nested objects for related answers, checkout the demo below:
const questions = [
{
"question": "What area of the world are you thinking of discovering next?",
"answers": {
"Europe": {
"label": "Europe",
"locations": ['Algarve - Portugal', 'Asturias and Covadonga - Spain', 'Mdina & Zebbug - Malta']
},
"Asia": {
"label": "Asia",
"locations": ['Yakushima Island - Japan', 'Mount Wudang Shan - China', 'Bantayan Island - Philippines']
}
}
}
];
let getRandomLocationForAnswer = function (question, answer) {;
const possibleLocations = question.answers[answer].locations;
const randomIndex = Math.floor((Math.random()*possibleLocations.length));
return possibleLocations[randomIndex];
}
let buildSelect = function(questions){
return questions.map((question, index) => {
const possibleAnswers = Object.keys(question.answers);
return`<select class="question" data-question-index="${index}">${possibleAnswers.map(l => `<option value="${l}">${l}</option>`)}</select>`;
}).join('');
}
let showResults = function(questions, questionIndex, answer){
const question = questions[questionIndex];
document.querySelector('#results').innerHTML =
`<h1 class="final-score">Our Top 3 Recommendations:</h1>
<div class="summary">
<p><br></br></p>
<p>${getRandomLocationForAnswer(question, answer)}</p>
<p>${getRandomLocationForAnswer(question, answer)}</p>
<p>${getRandomLocationForAnswer(question, answer)}</p>
</div>
<button class="restart">Restart Quiz</button>
`
}
document.querySelector('#questions').innerHTML += buildSelect(questions);
document.querySelectorAll('.question').forEach(question => {
question.addEventListener('change', e => {
const select = e.currentTarget;
showResults(questions, parseInt(select.dataset.questionIndex), select.value);
});
});
<section id="questions">
</section>
<section id="results">
</section>
resultA, resultB, resultC are functions, not values. if you put them in the template you will get a function reference and not the result of the function. To get the result you need to call the function(which you do in the last code snippet).
in order to get it to work simply define variables and set them to the function result:
if(currentQuestion == totalQuestions) {
container.style.display = 'none';
var l_resultA = resultA(australasiaArray1)
...
result.innerHTML =
`<h1 class="final-score">Our Top 3 Recommendations:</h1>
<div class="summary">
<p><br></br></p>
<p>${l_resultA}</p>
....
</div>
<button class="restart">Restart Quiz</button>`
};

How to avoid repeats after drawing in javascript?

After clicking on the button "go" one of eight words appears. I was wondering how to write a function which prohibits making repeats, so no word will be shown twice. Any ideas? Thanks for all help :)
<!DOCTYPE html>
<html>
<head></head>
<body>
<button id= "go">go</button>
<div id="word"></div>
<script type="text/javascript">
var words = ["Michael", "Simon", "Peter", "Mark", "Jason", "Paul", "Steve", "George"];
var btn1 = document.getElementById("go");
btn1.addEventListener("click", fill_in);
function getRandomItem(array) {
return array[Math.floor(Math.random() * array.length)]
}
function fill_in(){
var randomWord = getRandomItem(words);
document.getElementById('word').innerHTML += randomWord + " ";
}
</script>
</body>
</html>
You should write method getRandomItem this way.
function getRandomItem(array) {
return array.splice(Math.floor(Math.random() * array.length), 1)[0];
}
Basically, you need to remove the element after displaying it. Fortunately, Array#splice perfectly fits your case since it will both remove the element and return an array containing it.
To avoid repetition, you can remove the words from the array each time it is returned. Lets say getRandomItem returns 'Peter', so remove 'Peter' from the array 'words' before calling getRandomItem again.
To remove the element, you can use below code:
var words = ["Michael", "Simon", "Peter", "Mark", "Jason", "Paul", "Steve", "George"];
var index = words.indexOf("Peter");
if (index > -1) {
words.splice(index, 1);
}
To avoid repetition you should remove the name from the array after it has been displayed on the screen. To remove the name from the array you should find the index of the word, and then remove one item from the array at this index.
To do this you would use the following code
var word = words.indexOf(randomWord);
if(word != -1) {
words.splice(i, 1);
}
You should add this code to your fill_in function and remove the word, after the randomWord has been generated from the array. Once the array is empty you should stop printing to the screen. To check if the array is empty you should check the length of the words array.
function fill_in(){
var randomWord = getRandomItem(words);
var word = words.indexOf(randomWord);
if(word != -1) {
words.splice(i, 1);
}
if(words.length != 0){
document.getElementById('word').innerHTML += randomWord + " ";
}
}
Please see below to see the complete code in action.
var words = ["Michael", "Simon", "Peter", "Mark", "Jason", "Paul", "Steve", "George"];
var btn1 = document.getElementById("go");
btn1.addEventListener("click", fill_in);
function getRandomItem(array) {
return array[Math.floor(Math.random() * array.length)]
}
function fill_in() {
var randomWord = getRandomItem(words);
var i = words.indexOf(randomWord);
if (i != -1) {
words.splice(i, 1);
}
if(words.length != 0) {
document.getElementById('word').innerHTML += randomWord + " ";
}
}
<button id="go">go</button>
<div id="word"></div>

Categories

Resources