Add a blinking effect to single character of array - javascript

I am trying to achieve blinking effect to a single character of an array.
For example, if "text" get load inside paragraph <p> tag or container then first character of text should blink and when user types the blinking character in input area blinking effect must move to blink on next character.
I need assistance in solving this problem. Any instructions or help will be so grateful.
Here what I've tried so far:
let displayElem = document.getElementById("me");
const inputElem = document.getElementById("input");
const text = "Hey It's bad day, not a bad life,you'll be okay...!"
text.split('').forEach(char => {
const chrspan = document.createElement('span')
chrspan.innerText = char;
displayElem.appendChild(chrspan);
});
inputElem.addEventListener('input', () => {
var vl = document.getElementById("input").value;
const arrayq = displayElem.querySelectorAll('span')
const arrayv = inputElem.value
let correct = true;
arrayq.forEach((chSpan, index) => {
const char = arrayv[index];
if (char == null) {
correct = false;
} else if (char === chSpan.innerText) {
chSpan.classList.add('blink-bg')
} else {
chSpan.classList.remove('blink-bg')
correct = false
}
})
})
.blink-bg {
text-align: center;
color: #fff;
display: inline-block;
border-radius: 3px;
animation: blinkingBackground 1s infinite;
}
#keyframes blinkingBackground {
from { background-color: #f1ebeb; }
to { background-color: #080808; }
}
<p id="me"></p>
<input id="input" type="input" />
let displayElem = document.getElementById("me");
const inputElem = document.getElementById("input");
const text = "Hey It's bad day, not a bad life,you'll be okay...!"
text.split('').forEach(char => {
const chrspan = document.createElement('span')
chrspan.innerText = char;
displayElem.appendChild(chrspan);
});
inputElem.addEventListener('input', () => {
var vl = document.getElementById("input").value;
const arrayq = displayElem.querySelectorAll('span')
const arrayv = inputElem.value
let correct = true;
arrayq.forEach((chSpan, index) => {
const char = arrayv[index];
if (char == null) {
correct = false;
} else if (char === chSpan.innerText) {
chSpan.classList.add('blink-bg')
// document.getElementById("p_id").innerHTML = chSpan.innerText;
} else {
chSpan.classList.remove('blink-bg')
correct = false
}
})
})

This might be a good starting point for you. I changed a couple things,
I made a helper function $ to grab items from the dom as a personal prefrence.
I created an array of all the spans on the document, making it easier to keep track of which element has the blinking class. I created a helper function to grab what the activeText is from the txtArr instead of checking the text content. This way I can avoid using the rendered screen as a storage area for information, and instead have the screen mirror what is happening in my js.
On input I check the last character entered, and if it is the character that is blinking, I increment increment which span is blinking.
This is meant to be a simple demo of how to accomplish this task, you may want to have different functionality, but hopefully this helps as a starting point!
const $ = str => [...document.querySelectorAll(str)];
let displayElem = $("#me")[0];
const inputElem = $("#input")[0];
const text = "Hey! It's a bad day, not a bad life, you'll be okay...!"
const txtArr = [...text];
const txtSpans = txtArr.map(char => {
const span = document.createElement("span");
span.innerText = char;
return span;
});
let activeIndex = -1;
const activeText = () => txtArr[activeIndex];
function renderSpans() {
displayElem.innerHtml = "";
txtSpans.forEach(span => displayElem.appendChild(span));
};
function updateActive() {
const firstRun = activeIndex == -1;
if (!firstRun)
txtSpans[activeIndex].classList.remove("blink-bg");
activeIndex++;
if (activeIndex == txtSpans.length) return;
txtSpans[activeIndex].classList.add("blink-bg");
}
updateActive();
renderSpans();
inputElem.addEventListener('input', e => {
const val = e.target.value;
if (val == "") return;
const lastChar = val[val.length - 1];
if (lastChar == activeText()) updateActive();
})
.blink-bg {
text-align: center;
color: #fff;
display: inline-block;
border-radius: 3px;
animation: blinkingBackground 1s infinite;
}
#keyframes blinkingBackground {
from { background-color: #f1ebeb; }
to { background-color: #080808; }
}
<p id="me"></p>
<input id="input" type="input" />

Related

JavaScript Minesweeper - Opening whole mine-free area at once not working properly (flood-fill)

I am trying to build a simple minesweeper game in Javascript. It works propererly apart from the function to open the entire mine-free area when clicking on a mine-free tile. It starts checking the neighbouring tiles, but stops when the first neighbouring tile has a mine.
As you can see on the screenshot below (after clicking on tile 1/5) only the tiles until the first "1" are opened. It should actually open a much larger area:
It seems I am pretty close. THis is my code:
const gridSize = 10
// generate grid
const board = document.querySelector("#minesweeper");
// loop over num for rows
let header = 0;
for(let i = 0; i < gridSize+1; i++) {
const row = document.createElement("tr");
// loop over num for cols
for (let j = 0; j < gridSize+1; j++) {
// add col to row
if ( i === 0 ) {
row.insertAdjacentHTML("beforeend", `<th>${header}</th>`);
header += 1;
} else if (j === 0) {
row.insertAdjacentHTML("beforeend", `<th>${header-10}</th>`);
header += 1;
} else {
row.insertAdjacentHTML("beforeend", `<td class='unopened' dataset-column=${j}></td>`);
};
};
// add row to board
board.append(row);
};
// functions -------------------
function getNeighbour(tile, i, j) {
const column = tile.cellIndex; // so the columns get the cellIndex
const row = tile.parentElement.rowIndex; // row gets the rowIndex(tr)
const offsetY = row + i;
const offsetX = column + j;
return document.querySelector(`[data-row="${offsetY}"][data-column="${offsetX}"]`);
}
// count mines of neighbours
function countMines(tile) {
let mines = 0;
for(i = -1; i <= 1; i++) {
for(j = -1; j <= 1; j++ ) {
// check if neighbour has mine
// get cell values from neighbour in DOM
nb = getNeighbour(tile, i, j);
if (nb && nb.classList.contains('has-mine') || (nb && nb.classList.contains('mine'))) mines += 1; // if nb exists and has a mine increase mines
}
}
// write into DOM
if (mines === 0) {
tile.classList.add(`opened`);
} else {
tile.classList.add(`neighbour-${mines}`);
}
tile.classList.remove(`unopened`);
// if mines are 0, go to neigbours and count mines there
// console.log(tile.classList);
if (mines === 0) {
// alert("mines are zero");
for (i = -1; i <= 1; i+=1) {
for (j = -1; j <= 1; j+=1) {
nb = getNeighbour(tile, i, j);
if (nb && nb.classList.contains("unopened")) {
countMines(nb);
}
}
}
}
return mines;
}
// function open tile on click
function openTile(event) {
const tile = event.currentTarget;
// if there is a mine you lose
if (tile.classList.contains("has-mine")) {
document.querySelectorAll(".has-mine").forEach((cell) => {
cell.classList.remove("has-mine", "unopened");
cell.classList.add("mine", "opened");
});
alert("booooooooom!");
} else {
countMines(tile);
}
}
const tiles = document.querySelectorAll("td");
tiles.forEach((td) => {
td.dataset.column = td.cellIndex; // so the columns get the cellIndex
td.dataset.row = td.parentElement.rowIndex; // row gets the rowIndex(tr)
// add mines randomly
const freq = 0.1;
if (Math.random() < freq) {
td.classList.add("has-mine");
}
// eventlisteners per tile
td.addEventListener("click", openTile);
});
I have been thinking hours about it but could not find a way to work on with this code. Not sure if I am close or if I would need to modify the whole approach?
Many thanks for any ideas!
the principle is simple, for each empty cell, you must add all the adjacent empty cells.
it is also necessary to collect the number of adjacent mines of each cell
a) list the 8 adjacent cells, except for the cells placed at the edge
this is the prxElm() function in my code
b) count the mines present around a cell -> prxMne()
starting from the first cell
1- we count (a) nearby mines
2- it becomes the first element of a stack of cells to be mapped
3- if its number of nearby mines is zero, repeat this operation for all adjacent cells
the particularity of this algorithm is to use only one stack to accumulate the coordinates to be mapped.
it places the elements with adjacent mines at the top of the stack, and those with none at the end of the stack.
as there can be several cells without adjacent mines, we keep an indexiExp of the last empty cell processed.
of course when you add a cell with mines nearby at the start of the stack, this index is shifted.
the algorithm also take care not to add duplicate the same cell by checking before if this cell is not in the stack.
see .filter(x=>!explor.some(e=>e.p===x.p))
this ends when the exploration index iExp reaches the end of the stack.
here is the whole code, it is not completely finalized, but the essentials are there.
const
MinesCount = 17 // adjusted values to fit this snippet display area
, gridSz = { r:7, c:20 } // grid rows x cols
, gridMx = gridSz.r * gridSz.c
, proxim = [ {v:-1,h:-1}, {v:-1,h:0}, {v:-1,h:+1}, {v:0,h:-1}, {v:0,h:+1}, {v:+1,h:-1}, {v:+1,h:0}, {v:+1,h:+1} ]
, prxElm = (r,c) => proxim.reduce((a,{v,h})=>
{
let rv = r+v, ch = c+h;
if (rv>=0 && ch>=0 && rv<gridSz.r && ch<gridSz.c) a.push({p:((rv * gridSz.c) + ch), r:rv, c:ch} )
return a
},[])
, GenNbX = (nb,vMax) => [null].reduce(arr=>
{
while (arr.length < nb)
{
let numGen = Math.floor(Math.random() * vMax)
if (!arr.includes(numGen)) arr.push(numGen);
}
return arr //.sort((a,b)=>a-b)
},[])
, minesP = GenNbX( MinesCount, gridMx )
, prxMne = (r,c) => prxElm(r,c).reduce((a,{p})=>minesP.includes(p)?++a:a,0) // count mines arroub=nd
, td2rcp = td =>
{
let r = td.closest('tr').rowIndex -1 // -1 for thead count of rows
, c = td.cellIndex
, p = (r * gridSz.c) +c
return {r,c,p}
}
, p2rc = p =>({r: Math.floor(p / gridSz.c), c: (p % gridSz.c)})
, { timE, cFlags, minesArea } = drawTable('mines-area', gridSz, MinesCount )
;
const chrono = (function( timeElm )
{
const
one_Sec = 1000
, one_Min = one_Sec * 60
, twoDgts = t => (t<10) ? `0${t}` : t.toString(10)
, chronos =
{ timZero : null
, timDisp : timeElm
, timIntv : null
, running : false
}
, obj =
{ start()
{
if (chronos.running) return
chronos.timDisp.textContent = '00:00'
chronos.running = true
chronos.timZero = new Date().getTime()
chronos.timIntv = setInterval(() =>
{
let tim = (new Date().getTime()) - chronos.timZero
chronos.timDisp.textContent = `${Math.floor(tim/one_Min)}:${twoDgts(Math.floor((tim % one_Min)/one_Sec))}`
}
, 250);
}
, stop()
{
if (!chronos.running) return
chronos.running = false
clearInterval( chronos.timIntv )
}
}
return obj
}(timE))
function drawTable(tName, gSz, mines )
{
let table = document.getElementById(tName)
// table.innerHTML = '' // eraze table
let tHead = table.createTHead()
, tBody = table.createTBody()
, xRow = tHead.insertRow()
, timE = xRow.insertCell()
, cFlags = xRow.insertCell()
;
timE.setAttribute('colspan', gSz.c -4)
timE.className = 'time'
timE.textContent = '0:00'
cFlags.setAttribute('colspan', 4)
cFlags.className = 'flag'
cFlags.textContent = ' 0/' + mines
for (let r=gSz.r;r--;)
{
xRow = tBody.insertRow()
for(let c = gSz.c;c--;) xRow.insertCell()
}
return { timE, cFlags, minesArea: tBody }
}
minesArea.onclick = ({target}) =>
{
if (!target.matches('td')) return
if (target.hasAttribute('class')) return // already done
chrono.start()
let {r,c,p} = td2rcp(target)
if (minesP.includes(p)) // you are dead!
{
chrono.stop()
minesArea.className = 'Boom'
minesP.forEach(p=> // show mines
{
let {r,c} = p2rc(p)
let td = minesArea.rows[r].cells[c]
if (!td.hasAttribute('class')) td.className = 'mineOff'
})
minesArea.rows[r].cells[c].className = 'mineBoom' // this one is for you
minesArea.querySelectorAll('td:not([class]), td.flag') // jusr disable click
.forEach(td=>td.classList.add('off')) // and cursor
}
else
{
let explor = [ {p, r, c, m:prxMne(r,c) } ]
, iExp = 0
;
while (iExp < explor.length && explor[iExp].m === 0) // Open mine-free area
{
prxElm(explor[iExp].r,explor[iExp].c) // look around
.filter(x=>!explor.some(e=>e.p===x.p)) // if not already in
.forEach(x=>
{
M = prxMne(x.r,x.c)
if (M>0 ) { explor.unshift( { p:x.p, r:x.r, c:x.c, m:M} ); iExp++ }
else explor.push( { p:x.p, r:x.r, c:x.c, m:M} ) // mine-free space
})
iExp++
}
explor.forEach(({r,c,m})=>minesArea.rows[r].cells[c].className = 'm'+m )
}
if (checkEnd()) // some kind of victory!?
{
chrono.stop()
minesArea.querySelectorAll('td.flag').forEach(td=>td.classList.add('off'))
minesArea.className = 'win'
}
}
minesArea.oncontextmenu = e => // Yes, there is a right click for flag mines
{
if (!e.target.matches('td')) return
e.preventDefault()
let {r,c,p} = td2rcp( e.target)
, cell_rc = minesArea.rows[r].cells[c]
;
if (!cell_rc.hasAttribute('class')) cell_rc.className = 'flag'
else if (cell_rc.className === 'flag') cell_rc.removeAttribute('class')
let nbFlags = minesArea.querySelectorAll('td.flag').length
cFlags.textContent = ` ${nbFlags} / ${MinesCount}`
}
function checkEnd()
{ // what about us ?
let count = 0
, reject = 0
, tdNotSeen = minesArea.querySelectorAll('td:not([class])')
, flagPos = minesArea.querySelectorAll('td.flag')
;
cFlags.textContent = ` ${flagPos.length} / ${MinesCount}`
if (tdNotSeen.length > MinesCount ) return false
flagPos.forEach(td=>
{
let {r,c,p} = td2rcp(td)
if (minesP.includes(p)) count++ // correct place
else reject++
})
tdNotSeen.forEach(td=>
{
let {r,c,p} = td2rcp(td)
if (minesP.includes(p)) count++
else reject++ // no mines there
})
if (count != MinesCount || reject != 0 ) return false
tdNotSeen.forEach(td=>
{
let {r,c,p} = td2rcp(td)
minesArea.rows[r].cells[c].className = 'mineOff'
})
cFlags.textContent = ` ${MinesCount} / ${MinesCount}`
return true
}
body { background-color: #383947; } /* dark mode ? ;-) */
table {
border-collapse : collapse;
margin : 1em auto;
--szRC : 18px;
font-family : Arial, Helvetica, sans-serif;
}
table td {
border : 1px solid #1a1a1a80;
text-align : center;
}
table thead {
font-size : .8em;
background-color : #c3c5db;
}
table tbody {
background-color : #a39999;
cursor : cell;
}
table tbody td {
width : var(--szRC);
height : var(--szRC);
overflow : hidden;
}
.m0, .m1, .m2, .m3, .m4, .m5, .m6, .m7, .m8 { background-color: whitesmoke; font-size: 12px; font-weight: bold; cursor: default; }
.m1::after { content: '1'; color: #0000ff; }
.m2::after { content: '2'; color: #008000; }
.m3::after { content: '3'; color: #ff0000; }
.m4::after { content: '4'; color: #000080; }
.m5::after { content: '5'; color: #800000; }
.m6::after { content: '6'; color: #008080; }
.m7::after { content: '7'; color: #000000; }
.m8::after { content: '8'; color: #808080; }
.off { cursor: default; }
.Boom { background-color: yellow; cursor: default; }
.mineOff { cursor: default; padding: 0; }
.flag { background-color: lightgray; padding: 0; }
.mineBoom { color: crimson; padding: 0; }
.mineOff::after,
.mineBoom::after { content: '\2738'; }
.flag::before { content: '\2691'; color: crimson; }
.time::before { content: 'Time elapsed : '; color: darkblue; }
.win td { border-color: gold;}
<table id="mines-area"></table>
I don't think a recursive method is suitable for this kind of problem.
It requires having a complex strategy for exploring empty spaces.
For example, spiraling around the starting point.
But this strategy comes up against the problem of the island hindering this progress, and which requires, once crossed, to carry out a new spiral advance to recover the points hidden during the previous spiral, but having to avoid the points already explored during the previous spiral.
You can also move forward by sectors from an initial point, but you will still encounter the same problem of the islet and its backtracking, which also multiply here with each roughness on the explored shores.
This requires complex calculations that are difficult to master and debug, not to mention the fact that recursive methods intensively use call stacks which it adds up for each new branch.
(Without neglecting the risk of going crazy, by striving to develop its recursive algorithms.)
The larger the grid and the more its recursive lists will conflict with each other, and more the computationnal time will be affected.
Of course there are already winning heuristics on this type of strategy, they are of a very high level, and we are here just on a minesweeper game where hundreds of lines of code have nothing to do.
My method only uses a single stack, its algorithm is easy to understand and requires a few lines of code.
What more ?

Javascript display div only once

So I have a calculator with an error message that displays, if they press the "calculate" button if the input is NaN. The error message keeps getting created everytime the user presses calculate. How do i make it so it only shows even after pressing "calculate" multiple times?
function displayErr() {
const formBox = document.querySelector("form");
const errorBox = document.createElement("div");
errorBox.className = "errorBox";
const errorText = document.createTextNode("Those are not numbers!");
errorBox.appendChild(errorText);
formBox.appendChild(errorBox);
}
if ((isNaN(billInput)) || (isNaN(peopleAmount)) || (billInput === "") || (peopleAmount === "")) {
displayErr();
}
The most straightforward way is to check if the element already exists.
function displayErr() {
// Get error element
const errorElement = document.getElementsByClassName('errorBox');
// If it already exists
if (errorElement && errorElement.length > 0) {
// Dont add another one
return;
}
// Add new errorBox
const formBox = document.querySelector("form");
const errorBox = document.createElement("div");
errorBox.className = "errorBox";
const errorText = document.createTextNode("Those are not numbers!");
errorBox.appendChild(errorText);
formBox.appendChild(errorBox);
}
Another option would to be using css classes to 'hide' the element;
Always render the element, but hide it with display: none
In the displayErr(), make the element visible with something like document.getElementsByClassName('errorBox')[0].style.display = block;
a better way of doing this is
to show and hide the element using CSS classes
create the element and hide it using
display: none;
and show it by adding a class to the element
display: block;
const element = document.getElementById("myDIV");
const button = document.getElementById("btn");
button.addEventListener("click", () => element.classList.toggle("show"));
#myDIV {
display: none;
}
.show {
display: block !important;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<button id="btn">Try it</button>
<div id="myDIV">
This is a DIV element.
</div>
</body>
</html>
For what it's worth, here is a pure JavaScript example of a show/hide interpretation:
function CheckInput() {
const billInput = document.getElementById("b").value;
const peopleAmount = document.getElementById("p").value;
if ((isNaN(billInput)) || (isNaN(peopleAmount)) || (billInput === "") || (peopleAmount === "")) {
showErr();
}
else{
hideErr();
}
}
function hideErr(){
console.log("hide");
const el = document.getElementById("error");
el.style.display = "none";
}
function showErr(){
console.log("show");
const el = document.getElementById("error");
el.style.display = "block";
el.innerHTML = "Hey sorry wrong input";
}
window.onload = function() {
hideErr();
}
You can see the HTML and try the code here: https://jsfiddle.net/0mrx5ev7/
You can pass a parameter to your displayErr function, then use it to set the hidden HTML attribute and textContent of a single target div, identified by its HTML id.
This way, the functionality becomes reusable, and you can set/unset the error message whenever you need.
const input = document.querySelector('#input')
const errDisplay = document.querySelector('#err-display')
function displayErr(msg) {
errDisplay.textContent = msg || ''
errDisplay.hidden = msg ? null : 'hidden'
}
input.addEventListener('input', () => {
displayErr(isNaN(input.value) ? "Not a number" : null)
})
#err-display {
font-family: sans-serif;
background: red;
color: white;
margin: .5em 0;
padding: .5em;
}
<input id='input' placeholder='Start typing'>
<div id='err-display' hidden></div>
try to use a counter. like if int i == 0 --> do the function. i would do so
int i = 0;
function displayErr() {
const formBox = document.querySelector("form");
const errorBox = document.createElement("div");
errorBox.className = "errorBox";
const errorText = document.createTextNode("Those are not numbers!");
errorBox.appendChild(errorText);
formBox.appendChild(errorBox);
}
if ((isNaN(billInput)) && i == 0 || (isNaN(peopleAmount)) && i == 0 ||
(billInput === "") && i == 0 || (peopleAmount === "") && i == 0)
{
displayErr();
i += 1;
}
now it will display an error only once, because i is never going to be 0 anymore

How would you increase a variables value every second using a function?

I am trying to make a variable increase every second. What should I include inside the function autoClicker, so that the variable clicks increase by 1 every second? Also, if there are any more problems in the code, could you point them out to me? Sorry if this question seems basic, I am still quite new to JavaScript.
// The variable we are trying to increase
var clicks = 0;
var upgrade1 = 1;
function getClicks() {
clicks += upgrade1;
document.getElementById("clicks").innerHTML = clicks;
};
function buyAutoClicker() {
if (clicks >= 50) {
clicks -= 50
autoClicker()
} else {
alert = "Sorry, you don't have enough clicks to buy this";
}
}
// The function I will use to increase clicks
function autoClicker() {}
You could create an AutoClicker class that has a start, pause, ad update function. It will be in charge of managing the setInterval id.
Edit: I updated it to include upgrade buttons and the target can now be manually clicked.
const upgrades = [{
cost: 50,
rate: 2
}, {
cost: 100,
rate: 4
}];
const main = () => {
const target = document.querySelector('.auto-clicker');
const span = document.querySelector('.info > span');
const btn = document.querySelector('.btn-toggle');
const clicker = new AutoClicker(target, 1000, (clicks) => {
span.textContent = clicks;
}).start();
initializeUpgrades(clicker, upgrades);
btn.addEventListener('click', (e) => {
e.target.textContent = clicker.isRunning() ? 'Start' : 'Pause';
clicker.toggle();
});
};
const initializeUpgrades = (clicker, upgrades) => {
const upgradeContainer = document.querySelector('.upgrades');
upgrades.forEach(upgrade => {
const btn = document.createElement('button');
btn.textContent = upgrade.cost;
btn.value = upgrade.rate;
btn.addEventListener('click', (e) => {
let cost = parseInt(e.target.textContent, 10);
let value = parseInt(e.target.value, 10);
if (clicker.clicks >= cost) {
clicker.clicks -= cost;
clicker.step = value
} else {
console.log(`Cannot afford the ${value} click upgrade, it costs ${cost} clicks`);
}
});
upgradeContainer.appendChild(btn);
});
};
class AutoClicker {
constructor(target, rate, callback) {
if (typeof target === 'string') {
target = document.querySelector(target);
}
this.target = target;
this.rate = rate;
this.callback = callback;
this.init();
}
init() {
this.step = 1;
this.clicks = 0;
this._loopId = null;
this.target.addEventListener('click', (e) => {
this.update();
});
}
isRunning() {
return this._loopId != null;
}
toggle() {
this.isRunning() ? this.pause() : this.start();
}
update() {
this.clicks += this.step;
if (this.callback) {
this.callback(this.clicks);
}
}
start() {
this.update(); // Update immediately
this._loopId = setInterval(() => this.update(), this.rate);
return this;
}
pause() {
clearInterval(this._loopId);
this._loopId = null;
return this;
}
}
main();
.wrapper {
width: 10em;
text-align: center;
border: thin solid grey;
padding: 0.5em;
}
.auto-clicker {
width: 4em;
height: 4em;
background: #F00;
border: none;
border-radius: 2em;
}
.auto-clicker:focus {
outline: none;
}
.auto-clicker:hover {
background: #F44;
cursor: pointer;
}
.info {
margin: 1em 0;
}
.upgrades {
display: inline-block;
}
.upgrades button {
margin-right: 0.25em;
}
<div class="wrapper">
<button class="auto-clicker"></button>
<div class="info">Clicks: <span class="clicks"></span></div>
<button class="btn-toggle">Pause</button>
<div class="upgrades"></div>
</div>
// The variable we are trying to increase
var clicks = 0;
var upgrade1 = 1;
function getClicks() {
clicks += upgrade1;
document.getElementById("clicks").innerHTML = clicks;
};
function buyAutoClicker() {
if (clicks >= 50) {
clicks -= 50
autoClicker()
} else {
alert = "Sorry, you don't have enough clicks to buy this";
}
}
// The function I will use to increase clicks
setInterval(function(){ clicks++;console.log(clicks); }, 1000);
Use setInterval to run a function at a specified interval. This will run increaseClicks every 1000 milliseconds (every second):
function increaseClicks() {
clicks++;
}
var interval = setInterval(increaseClicks, 1000);
Use clearInterval to stop running it:
clearInterval(interval);
You can omit var interval = if you don't want to use clearInterval:
setInterval(increaseClicks, 1000);
There might be several things to improve this code
the use of textContent is preferable to innerHTML, it checks if there are no html tags in the text
then using inline functions like ()=>{} are more useful but in this program it does'nt make a difference, where you to use it in object oriented context you could use it several ways
you don't need document.getElementById, you could just use id.
And finaly (this is just à random tip which has nothing to do with much of anything) you may consider branchless programming because ifs are expensive.
Stackoverflow Branchless Programming Benefits
But anyways you should have fun :)
var clicks = 0;
var upgrade1 = 1;
function getClicks() {
clk.textContent = (clicks += upgrade1);
};
function buyAutoClicker() {
if (clicks >= 50) {
clicks -= 50
setInterval(()=>{getClicks();},1000);
} else {
alert("Sorry, you don't have enough clicks to buy this");
}
}
clk.onclick=()=>{getClicks();};
b.onclick=()=>{buyAutoClicker();};
html,body{height:100%;width:100%;margin:0;}
p{height:50px;width:50px;background:red;}
<p id="clk"></p>
<p id="b"></p>

Variable tracking user attempts does not update

I am working on a quiz game, and I have been having this issue for a while and I just can't figure out what I am doing wrong. Ask any question if you are confused by my explanation, i will be monitoring this post
How to recreate the problem - Type in the name displayed on the screen until you see "Game over bro!" -
Problem:
when I type in the name in the input field and click "Answer" to check if the input field value matches the name retrieved from the API, there is a variable(var attempts = 5) tracking how many times the user has attempted the question,but this variable(attempts) reduces it's value by one when the answer is correct, it should only do that when the answer is incorrect.
Also, let me know what you think about the JS code, is it bad code?
I am asking because the code in newReq function i wrote it twice, one loads and displays the data retrieved from the API when the page loads, the code inside the newReq function loads a new character when "New character" button is clicked.I was thinking about DRY the whole time, but i'm not sure how to load a new character without re-writing the code
var attemptsPara = document.querySelector("#attempts"),
attempts = 5,
scorePara = document.querySelector("#score"),
score = 0,
feedBackDiv = document.querySelector("#feedBack"),
newCharacterBtn = document.querySelector("#newCharacter"),
answerBtn = document.querySelector("#answer"),
input = document.querySelector("input");
scorePara.textContent = `Score is currently: ${score}`;
attemptsPara.textContent = `${attempts} attempts remaining`;
var feedBackText = document.createElement("p");
var characterPara = document.querySelector("#character");
//click new character button to load new character
// newCharacterBtn.addEventListener("click", () => {
// answerBtn.disabled = false;
// attempts = 5;
// attemptsPara.textContent = `${attempts} attempts remaining`;
// });
//function that displays retrieved data to the DOM
function displayCharacters(info) {
let englishName = info.attributes.name;
characterPara.textContent = `This is the character's name: ${englishName}`;
console.log(englishName, randomNumber);
}
//load new character
var randomNumber = Math.round(Math.random() * 100 + 2);
var request = new XMLHttpRequest();
request.open(
"GET",
"https://kitsu.io/api/edge/characters/" + randomNumber,
true
);
request.send();
request.onload = function() {
var data = JSON.parse(this.response);
var info = data.data;
displayCharacters(info);
//checks if the input value matches name retrieved
answerBtn.addEventListener("click", () => {
let englishName = info.attributes.name;
if (input.value === englishName) {
feedBackText.textContent = `${input.value} is correct`;
feedBackDiv.append(feedBackText);
feedBackDiv.style.backgroundColor = "green";
feedBackDiv.style.display = "block";
setTimeout(() => {
feedBackDiv.style.display = "none";
}, 3000);
score = score + 5;
scorePara.textContent = `Score is currently: ${score}`;
attempts = 5;
attemptsPara.textContent = `${attempts} attempts remaining`;
input.value = "";
newReq(); //call function to load and display new character
} else {
feedBackText.textContent = `${input.value} is wrong`;
feedBackDiv.append(feedBackText);
feedBackDiv.style.backgroundColor = "red";
feedBackDiv.style.display = "block";
input.focus();
setTimeout(() => {
feedBackDiv.style.display = "none";
}, 2000);
attempts = attempts - 1;
attemptsPara.textContent = `${attempts} attempts remaining`;
if (attempts <= 0) {
answerBtn.disabled = true;
attemptsPara.textContent = `Game over bro!`;
}
}
console.log(attempts); //check how many attempts remaining every time answerBtn is clicked
});
};
newCharacterBtn.addEventListener("click", newReq);
//function to make a new request and display it the information on the DOM,when New character button is clicked
function newReq() {
rand = randomNumber = Math.round(Math.random() * 100 + 2);
var request = new XMLHttpRequest();
request.open(
"GET",
"https://kitsu.io/api/edge/characters/" + randomNumber,
true
);
request.send();
request.onload = function() {
var data = JSON.parse(this.response);
var info = data.data;
displayCharacters(info);
answerBtn.addEventListener("click", () => {
let englishName = info.attributes.name;
if (input.value === englishName) {
feedBackText.textContent = `${input.value} is correct`;
feedBackDiv.append(feedBackText);
feedBackDiv.style.backgroundColor = "green";
feedBackDiv.style.display = "block";
//settimeout to hide feedBack div
setTimeout(() => {
feedBackDiv.style.display = "none";
}, 3000);
score = score + 5;
scorePara.textContent = `Score is currently: ${score}`;
attempts = 5;
attemptsPara.textContent = `${attempts} attempts remaining`;
input.value = "";
newReq();
} else if (input.value != englishName) {
feedBackText.textContent = `${input.value} is wrong`;
feedBackDiv.append(feedBackText);
feedBackDiv.style.backgroundColor = "red";
feedBackDiv.style.display = "block";
input.focus();
//settimeout to hide feedBack div
setTimeout(() => {
feedBackDiv.style.display = "none";
}, 2000);
attempts = attempts - 1;
attemptsPara.textContent = `${attempts} attempts remaining`;
if (attempts <= 0) {
answerBtn.disabled = true;
attemptsPara.textContent = `Game over bro!`;
}
}
});
console.log(attempts);
};
}
body {
margin: 0;
padding: 0;
background: black;
}
#imageHolder {
height: 560px;
width: 1100px;
background: #098;
margin: 10px auto;
}
#buttonHolder {
/* background: #453; */
width: 160px;
margin: 0 auto;
}
p,
h3 {
color: yellowgreen;
text-align: center;
}
h3 {
text-decoration: underline;
}
img {
width: 100%;
height: 100%;
}
button,
input {
margin: 10px 10px;
border: none;
background: #098;
display: block;
}
input {
background: white;
}
/* for the question and awnswer game */
#feedBack {
background: #098;
height: 120px;
width: 320px;
margin: 10px auto;
display: none;
}
<p id="score"></p>
<p id="character"></p>
<input type="text"> <button id="answer">Answer</button> <button id="newCharacter">New Character</button>
<p id="attempts"></p>
<div id="feedBack">
</div>
Your problem arises from calling answerBtn.addEventListener every time the answer returns - which adds more and more listeners.
Add a console log at the beginning of the click event and you'll see that after the 2nd answer the click event happens twice, then three times on the 3rd answer, etc'.
This means that on the first click the result is correct, but then on the rest of the clicks it is incorrect and must be causing the bug.
You should only listen to the event once, the variables that the event uses change and that should be sufficient. I can't fix the code for you at this time, apologies.

Player interaction in a while javascript

i've encountered a few errors while working on a round by round script for a game on browser, sorry for my spelling mistakes.
I have a function with a while separated in three parts as the following (full javascript):
function turn(player, monster){
//player and monster are object
turns=2;
//few other unimportant variable
while(//as long as they have >0 hp){
if(turns==2){
//listen to click on html to know if player used a spell
// if he didn't in a few seconds, then he'll just use a normal
// attack
startingTurn = settimeout(startTurn, 2000);
sleep(2000);
turns=1;
}
if(turns==1 ){
//Turn of the player (working perfectly)
...
turns=0;
}
if(turns==0 and hp>0){
//Turn of the monster (working perfectly)
...
turns=2;
}
}
}
The problem that i've encountered is that in the listening of an action, i have to set a pause so that the player will have time to click on one of the spell.
To do this, i've tryed using "sleep" function to let 2 seconds for the player to act and a settimeout() to set a normal attack if there were no spell clicked.
The sleep function that i used looked like this one: https://www.sitepoint.com/delay-sleep-pause-wait/
Both those methods gave me errors:
-The settimeout() were effective only after the whole script(while) was finished (all at once).
-The "sleep" function caused the whole page to freeze, not only the while but also the html wasn't clickable.
So I'm looking for a way of leting the player interact while the function is running, if this isn't possible i will probably have to give up the interaction part.
I've looked online for quite a long time and didn't found any solution so i hope you can help me there!.
EDIT
I have tried to use the methods from Jacob (with async function and a loop() )because it was what looks the most clear for me.
But then i found the same first problem wich is that the settimeout() are executed after the loop() function here's where i think the problem is:
function attaqueBasic (attacker, defender) {
console.log("basic attack");
return new Promise((res, rej) => {
setTimeout(() => {
var dodge = dodge(defender);
if(dodge == false){
console.log("attack damage :"+attacker.dmg);
defender.life = defender.life - attacker.dmg;
console.log("life defender: "+defender.vie);
if(attacker.hasOwnProperty("bonus")==true && defender.life>0){
attackBonus(attacker, defender);
}
}
res()
}, 2000);
})
}
This is the function that let the player do a basic attack but it doesn't wait for the resolve to continue.
In the console i find the "basic attack" while the loop is running but never the following wich is in the settimeout and is executed only after all loops are done. Sorry if i made a big mistake here.
Here's the function loop() just in case i made some mistake:
function loop(perso, monstre){
nbtour += 1
if(nbtour>=6){
nbtour=0;
return;
}
console.log('---New tour'+nbtour+'---');
Turn(perso,monstre)
.then(checkWin(perso,monstre))
.then(Turn(monstre,perso))
.then(checkWin(perso,monstre))
.then(loop(perso, monstre))
.catch(() => {
console.log('End of combat, number of tours ='+nbtour);
nbtour=0;
})
}
Thanks for your time.
I assume this is on a browser or similar platform.
You can't have reasonably have meaningful input from the user within a while loop (I'm intentionally ignoring the unreasonable prompt function). Instead, handle an event that occurs when a player moves, and have that event handler process the changes the event causes. At the end of the handler's logic, check what used to be the loop termination condition ("as long as they have >0 hp") and if the termination condition has been reached, modify the page to say that the game is over or whatever (and probably disable the buttons).
For instance, here's a simple example using number guessing:
var number = Math.floor(Math.random() * 10) + 1;
var guesses = 3;
var btn = document.getElementById("guess");
var input = document.getElementById("guess-value");
btn.addEventListener("click", function() {
var value = input.value.trim();
if (!value) {
guessError("Please fill in a value");
return;
}
var guess = +value;
if (isNaN(guess)) {
guessError("Please only fill in simple numbers");
return;
}
if (guess < 1 || guess > 10) {
guessError("What part of 'between 1 and 10, inclusive' did you not understand? ;-)");
return;
}
if (guess === number) {
console.log("Congrats! You won!");
input.disabled = btn.disabled = true;
} else {
console.log("Nope, it's not " + guess);
if (--guesses === 0) {
console.log("You're out of guesses, the computer wins.");
input.disabled = btn.disabled = true;
} else {
input.value = "";
input.focus();
}
}
});
console.log("Guess a whole number between 1 and 10 inclusive");
input.focus();
function guessError(msg) {
console.log(msg);
input.focus();
}
<input type="text" id="guess-value">
<input type="button" id="guess" value="Guess">
That's very quick-and-dirty, but the key thing is that instead of a while loop with the number of times the user is allowed to guess, we just respond to them guessing and count the number of times they guess, and change the state of the page when they run out (or win).
First, we can not use while for game loop, because it will freeze browser, user would can not interact with game.
We will use recursion , something like this:
function loop () {
loop()
}
Second, we have to use async to stop loop to wait user operation, otherwise the recursion would have no difference with iteration, something like this:
setTimemout(attack, 2000)
Third, we have to chain all of the code in loop to make it run one by one, because they are async, otherwise the order cann't be guaranteed, like this:
firstAttack()
.then(secondAttack)
.then(thirdAttack)
.catch(gameOver())
Here is a simple demo.
const php = document.querySelector('.p-hp')
const mhp = document.querySelector('.m-hp')
const output = document.querySelector('.output')
const fireballEl = document.querySelector('.fireballEl')
let isAbility = false
let hp = ''
fireballEl.addEventListener('click' , e => {
isAbility = true
fireballEl.disabled = true
e.preventDefault()
})
function playerTurn () {
if (isAbility) {
isAbility = false
return fireball()
}
return slash()
}
function slash () {
return new Promise((res, rej) => {
setTimeout(() => {
const dmg = getRandomInt(1, 200)
hp.m -= dmg
if (hp.m < 0) {
hp.m = 0
}
mhp.textContent = hp.m
output.innerHTML += `<li class="player-info">You slashed monster which damaged ${dmg} hp</li>`
res()
}, 2000);
})
}
function fireball () {
return new Promise((res, rej) => {
setTimeout(() => {
const dmg = getRandomInt(260, 400)
hp.m -= dmg
if (hp.m < 0) {
hp.m = 0
}
mhp.textContent = hp.m
fireballEl.disabled = false
output.innerHTML += `<li class="player-info ability">You thrown a fireball to monster which damaged ${dmg} hp</li>`
res()
}, 2000);
})
}
function bite () {
return new Promise((res, rej) => {
setTimeout(() => {
const dmg = getRandomInt(6, 20)
hp.p -= dmg
if (hp.p < 0) {
hp.p = 0
}
php.textContent = hp.p
output.innerHTML += `<li class="monster-info">Monster bite you for ${dmg} hp</li>`
res()
}, 2000);
})
}
function check () {
if (hp.p <= 0) {
output.innerHTML += '<li class="lose">System terminated, rebuild enviroment, monster is ready,experiment can be continued... </li>'
return Promise.reject(1)
}
if (hp.m <= 0) {
output.innerHTML += '<li class="win">Hooray! Now the princess is yours.</li>'
return Promise.reject(0)
}
return Promise.resolve(1)
}
function init () {
output.innerHTML = ''
php.textContent = 100
mhp.textContent = 1000
return {
p: 100,
m: 1000
}
}
function updateDom () {
php.textContent = hp.p
mhp.textContent = hp.m
}
function loop () {
output.innerHTML += '<li class="bar">=====================</li>'
playerTurn()
.then(updateDom)
.then(check)
.then(bite)
.then(updateDom)
.then(check)
.then(loop)
.catch(() => {
startEl.disabled = false
fireballEl.disabled = true
})
}
function getRandomInt(min, max) {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min)) + min
}
const startEl = document.querySelector('.start')
startEl.addEventListener('click', e => {
hp = init()
fireballEl.disabled = false
startEl.disabled = true
loop()
})
* {
margin: 0;
padding: 0;
}
ul {
list-style: none;
}
.stage {
overflow:hidden;
margin: 0 auto;
max-width: 600px;
padding: 15px;
}
.player, .monster, .split {
width: 33%;
float: left;
}
.split {
font-size: 50px;
font-weight: 900;
}
h1 {
font-size: 16px;
margin-top: 0;
}
.output {
max-width: 600px;
margin: 0 auto;
border-top: 1px solid #333;
}
.output .player-info {
background-color: forestgreen;
color: #333;
}
.output .monster-info {
background-color:fuchsia;
color: #333;
}
.output .bar {
color: #eee;
/* margin: 3px 0; */
}
.output .ability {
background-color:yellow;
font-size: 16px;
}
.output .win {
font-size: 30px;
}
.output .lose {
font-size: 30px;
}
<div class="stage">
<div class="player">
<h1>Self-confident Player</h1>
<p>HP: <span class="p-hp">100</span></p>
<div class="abl">
Spell:
<button class="fireballEl" disabled>Inaccurate fireball</button>
</div>
</div>
<div class="split">
VS
<div>
<button class="start">Start to fight</button>
</div>
</div>
<div class="monster">
<h1>Young Monster</h1>
<p>HP: <span class="m-hp">1000</span></p>
</div>
</div>
<ul class="output">
</ul>

Categories