I created a sliding puzzle with different formats like: 3x3, 3x4, 4x3 and 4x4. When you run my code you can see on the right side a selection box where you can choose the 4 formats. The slidingpuzzle is almost done. But I need a function which checks after every move if the puzzle is solved and if that is the case it should give out a line like "Congrantulations you solved it!" or "You won!". Any idea how to make that work?
In the javascript code you can see the first function loadFunc() is to replace every piece with the blank one and the functions after that are to select a format and change the format into it. The function Shiftpuzzlepieces makes it so that you can move each piece into the blank space. Function shuffle randomizes every pieces position. If you have any more question or understanding issues just feel free to ask in the comments. Many thanks in advance.
Since I don't have enough reputation I will post a link to the images here: http://imgur.com/a/2nMlt . These images are just placeholders right now.
Here is the jsfiddle:
http://jsfiddle.net/Cuttingtheaces/vkyxgwo6/19/
As always, there is a "hacky", easy way to do this, and then there is more elegant but one that requires significant changes to your code.
Hacky way
To accomplish this as fast and dirty as possible, I would go with parsing id-s of pieces to check if they are in correct order, because they have this handy pattern "position" + it's expected index or "blank":
function isFinished() {
var puzzleEl = document.getElementById('slidingpuzzleContainer').children[0];
// convert a live list of child elements into regular array
var pieces = [].slice.call(puzzleEl.children);
return pieces
.map(function (piece) {
return piece.id.substr(8); // strip "position" prefix
})
.every(function (id, index, arr) {
if (arr.length - 1 == index) {
// last peace, check if it's blank
return id == "blank";
}
// check that every piece has an index that matches its expected position
return index == parseInt(id);
});
}
Now we need to check it somewhere, and naturally the best place would be after each move, so shiftPuzzlepieces() should be updated to call isFinished() function, and show the finishing message if it returns true:
function shiftPuzzlepieces(el) {
// ...
if (isFinished()) {
alert("You won!");
}
}
And voilĂ : live version.
How would I implement this game
For me, the proper way of implementing this would be to track current positions of pieces in some data structure and check it in similar way, but without traversing DOM or checking node's id-s. Also, it would allow to implement something like React.js application: onclick handler would mutate current game's state and then just render it into the DOM.
Here how I would implement the game:
/**
* Provides an initial state of the game
* with default size 4x4
*/
function initialState() {
return {
x: 4,
y: 4,
started: false,
finished: false
};
}
/**
* Inits a game
*/
function initGame() {
var gameContainer = document.querySelector("#slidingpuzzleContainer");
var gameState = initialState();
initFormatControl(gameContainer, gameState);
initGameControls(gameContainer, gameState);
// kick-off rendering
render(gameContainer, gameState);
}
/**
* Handles clicks on the container element
*/
function initGameControls(gameContainer, gameState) {
gameContainer.addEventListener("click", function hanldeClick(event) {
if (!gameState.started || gameState.finished) {
// game didn't started yet or already finished, ignore clicks
return;
}
if (event.target.className.indexOf("piece") == -1) {
// click somewhere not on the piece (like, margins between them)
return;
}
// try to move piece somewhere
movePiece(gameState, parseInt(event.target.dataset.index));
// check if we're done here
checkFinish(gameState);
// render the state of game
render(gameContainer, gameState);
event.stopPropagation();
return false;
});
}
/**
* Checks whether game is finished
*/
function checkFinish(gameState) {
gameState.finished = gameState.pieces.every(function(id, index, arr) {
if (arr.length - 1 == index) {
// last peace, check if it's blank
return id == "blank";
}
// check that every piece has an index that matches its expected position
return index == id;
});
}
/**
* Moves target piece around if there's blank somewhere near it
*/
function movePiece(gameState, targetIndex) {
if (isBlank(targetIndex)) {
// ignore clicks on the "blank" piece
return;
}
var blankPiece = findBlankAround();
if (blankPiece == null) {
// nowhere to go :(
return;
}
swap(targetIndex, blankPiece);
function findBlankAround() {
var up = targetIndex - gameState.x;
if (targetIndex >= gameState.x && isBlank(up)) {
return up;
}
var down = targetIndex + gameState.x;
if (targetIndex < ((gameState.y - 1) * gameState.x) && isBlank(down)) {
return down;
}
var left = targetIndex - 1;
if ((targetIndex % gameState.x) > 0 && isBlank(left)) {
return left;
}
var right = targetIndex + 1;
if ((targetIndex % gameState.x) < (gameState.x - 1) && isBlank(right)) {
return right;
}
}
function isBlank(index) {
return gameState.pieces[index] == "blank";
}
function swap(i1, i2) {
var t = gameState.pieces[i1];
gameState.pieces[i1] = gameState.pieces[i2];
gameState.pieces[i2] = t;
}
}
/**
* Handles form for selecting and starting the game
*/
function initFormatControl(gameContainer, state) {
var formatContainer = document.querySelector("#formatContainer");
var formatSelect = formatContainer.querySelector("select");
var formatApply = formatContainer.querySelector("button");
formatSelect.addEventListener("change", function(event) {
formatApply.disabled = false;
});
formatContainer.addEventListener("submit", function(event) {
var rawValue = event.target.format.value;
var value = rawValue.split("x");
// update state
state.x = parseInt(value[0], 10);
state.y = parseInt(value[1], 10);
state.started = true;
state.pieces = generatePuzzle(state.x * state.y);
// render game
render(gameContainer, state);
event.preventDefault();
return false;
});
}
/**
* Renders game's state into container element
*/
function render(container, state) {
var numberOfPieces = state.x * state.y;
updateClass(container, state.x, state.y);
clear(container);
var containerHTML = "";
if (!state.started) {
for (var i = 0; i < numberOfPieces; i++) {
containerHTML += renderPiece("", i) + "\n";
}
} else if (state.finished) {
containerHTML = "<div class='congratulation'><h2 >You won!</h2><p>Press 'Play!' to start again.</p></div>";
} else {
containerHTML = state.pieces.map(renderPiece).join("\n");
}
container.innerHTML = containerHTML;
function renderPiece(id, index) {
return "<div class='piece' data-index='" + index + "'>" + id + "</div>";
}
function updateClass(container, x, y) {
container.className = "slidingpuzzleContainer" + x + "x" + y;
}
function clear(container) {
container.innerHTML = "";
}
}
/**
* Generates a shuffled array of id-s ready to be rendered
*/
function generatePuzzle(n) {
var pieces = ["blank"];
for (var i = 0; i < n - 1; i++) {
pieces.push(i);
}
return shuffleArray(pieces);
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
}
body {
font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Helvetica, Arial, sans-serif;
font-size: 12px;
color: #000;
}
#formatContainer {
position: absolute;
top: 50px;
left: 500px;
}
#formatContainer label {
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
}
#formatContainer select {
display: block;
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
}
#formatContainer button {
display: inline-block;
width: 100%;
}
.piece {
width: 96px;
height: 96px;
margin: 1px;
float: left;
border: 1px solid black;
}
.slidingpuzzleContainer3x3,
.slidingpuzzleContainer3x4,
.slidingpuzzleContainer4x3,
.slidingpuzzleContainer4x4 {
position: absolute;
top: 50px;
left: 50px;
border: 10px solid black;
}
.slidingpuzzleContainer3x3 {
width: 300px;
height: 300px;
}
.slidingpuzzleContainer3x4 {
width: 300px;
height: 400px;
}
.slidingpuzzleContainer4x3 {
width: 400px;
height: 300px;
}
.slidingpuzzleContainer4x4 {
width: 400px;
height: 400px;
}
.congratulation {
margin: 10px;
}
}
<body onload="initGame();">
<div id="slidingpuzzleContainer"></div>
<form id="formatContainer">
<label for="format">select format:</label>
<select name="format" id="format" size="1">
<option value="" selected="true" disabled="true"></option>
<option value="3x3">Format 3 x 3</option>
<option value="3x4">Format 3 x 4</option>
<option value="4x3">Format 4 x 3</option>
<option value="4x4">Format 4 x 4</option>
</select>
<button type="submit" disabled="true">Play!</button>
</form>
</body>
Here we have the initGame() function that starts everything. When called it will create an initial state of the game (we have default size and state properties to care about there), add listeners on the controls and call render() function with the current state.
initGameControls() sets up a listener for clicks on the field that will 1) call movePiece() which will try to move clicked piece on the blank spot if the former is somewhere around, 2) check if after move game is finished with checkFinish(), 3) call render() with updated state.
Now render() is a pretty simple function: it just gets the state and updates the DOM on the page accordingly.
Utility function initFormatControl() handles clicks and updates on the form for field size selection, and when the 'Play!' button is pressed will generate initial order of the pieces on the field and call render() with new state.
The main benefit of this approach is that almost all functions are decoupled from one another: you can tweak logic for finding blank space around target piece, to allow, for example, to swap pieces with adjacent ids, and even then functions for rendering, initialization and click handling will stay the same.
$(document).on('click','.puzzlepiece', function(){
var count = 0;
var imgarray = [];
var test =[0,1,2,3,4,5,6,7,8,'blank']
$('#slidingpuzzleContainer img').each(function(i){
var imgalt = $(this).attr('alt');
imgarray[i] = imgalt;
count++;
});
var is_same = (imgarray.length == test.length) && imgarray.every(function(element, index) {
return element === array2[index];
});
console.log(is_same); ///it will true if two array is same
});
try this... this is for only 3*3.. you pass the parameter and makethe array value as dynamically..
Related
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 ?
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.
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>
I get date and clock.
var mydate = new Date();
var clock = tarih.getHours();
var minute = tarih.getMinutes();
And want this;
if (clock> 5) {
add this class, if have id "menu" = "menu_edit" (i dont know how can i do)
}
How can I do that?
If 'menu' is id of element:
document.querySelector('#menu').className += " menu_edit";
UPD:
According to your comment:
document.querySelector('.class1').className += ' class2';
Or if there are several elements:
var elems = document.querySelectorAll('.class1');
elems.forEach = [].forEach;
elems.forEach(function(el){
el.className += ' class2';
});
https://developer.mozilla.org/docs/Web/API/Document/querySelector - about function.
http://www.w3schools.com/cssref/css_selectors.asp - about selectors.
Maybe something like this. Please see the comments for an explanation.
checkout https://babeljs.io/ for info on compiling ES6 to ES5
const menu = document.querySelector('#menu')
const menuClasses = [
'menu--morning',
'menu--afternoon',
'menu--evening'
]
// helper function to toggle classes like a radio button
// this uses currying to lock in the classes and element, but
// allow us to change the active class dynamically
const toggleClasses = (classes, element) => clazz => {
element.classList.remove(...classes)
element.classList.add(clazz)
}
// create the toggle function and pass in the classes and the element
const toggleMenuClass = toggleClasses(menuClasses, menu)
// run a timer every n seconds
const timer = () => {
const date = new Date()
// I'm using seconds for the example as you will see the change
// but you should change this to hours
const second = date.getSeconds()
if (second < 20) {
toggleMenuClass('menu--morning')
}
else if (second < 40) {
toggleMenuClass('menu--afternoon')
}
else {
toggleMenuClass('menu--evening')
}
// just to show the current time for the example
menu.textContent = second
// call the timer function again after 500 milliseconds
setTimeout(timer, 500)
}
// init the timer on load
timer()
#menu {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transition: 1s background;
font-size: 5em;
font-family: sans-serif;
color:#fff;
text-align: center;
line-height: 100vh;
}
#menu.menu--morning {
background: #AED6F1;
}
#menu.menu--afternoon {
background: #ABEBC6;
}
#menu.menu--evening {
background: #B95A75;
}
<div id="menu">clock</div>
Thanks everyone. I solved my problem with pHp.
Instead of adding class, i try take a different css page. Thanks for all things.
Have a nice day. :)
$clock = date('H');
if ($clock > 14) {
echo "<link rel='stylesheet' type='text/css' href='css/night.css'>";
}
Sorry about the confusing title, I'll explain better.
I have a 20x20 grid of div's, so its 400 of them each with an id, going from 0 to 399.
Each div is given one of three random values - red, green or blue - and when a div is clicked, a function is run to check if the div to the left, right, over and under are of the same value, if it is of the same value it will be simulated a click and the same function will run again.
The problem, is that the function sets vars, so if it finds that the div below has the same value, it will overwrite the vars set by the first click, hence never click any of the others.
JSfiddle - http://jsfiddle.net/5e52s/
Here is what I've got:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>untiteled</title>
<style>
body {
width: 420px;
}
.box {
width: 19px;
height: 19px;
border: 1px solid #fafafa;
float: left;
}
.box:hover {
border: 1px solid #333;
}
.clicked {
background: #bada55 !important;
}
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
$().ready(function(){
var colors = ['red', 'green', 'blue'];
var i = 0;
while(i<400){
var color = colors[Math.floor(Math.random() * colors.length)];
$('.test').append('<div class="box" id="'+i+'" value="'+color+'" style="background:'+color+';">'+i+'</div>');
i++;
}
$('.box').click(function(){
var t = $(this);
t.addClass('clicked');
id = t.attr('id');
val = t.attr('value');
//Set color
up = parseInt(id) - 20;
right = parseInt(id) + 1;
down = parseInt(id) + 20;
left = parseInt(id) - 1;
clickup = false;
clickdown = false;
if($('#'+down).attr('value') === val){
clickdown = true;
}
if(up > -1 && ($('#'+up).attr('value') === val)){
clickup = true;
}
if(clickdown == true){
$('#'+down).click();
}
if(clickup == true){
$('#'+up).click();
}
});
});
</script>
</head>
<body>
<div class="test">
</div>
</body>
I think the biggest root cause of your problem is you don't check if it already has class 'clicked' or not. That could make the infinite recursive. For example, if you click on the div#2 then the div#1 receives a simulated click, and div#2 receives a simulated click from div#1.
$('.box').click(function(){
var t = $(this);
if(t.hasClass('clicked')) {
return;
}
t.addClass('clicked');
var id = t.attr('id');
var val = t.attr('value');
//Set color
var up = parseInt(id) - 20;
var right = (id%20 != 19) ? ((0|id) + 1) : 'nothing' ;
var down = parseInt(id) + 20;
var left = (id%20 != 0) ? ((0|id) - 1) : 'nothing';
console.log(up, right, down, left);
if($('#'+down).attr('value') === val) {
$('#'+down).click();
}
if($('#'+right).attr('value') === val) {
$('#'+right).click();
}
if($('#'+up).attr('value') === val) {
$('#'+up).click();
}
if($('#'+left).attr('value') === val) {
$('#'+left).click();
}
});
You can schedule the clicks onto the event loop instead of calling them directly, eg:
if(clickdown == true){
setTimeout(function () {
$('#'+down).click();
});
}
I don't think that's your root cause though, it's probably a combination of global vars and scope issues. Try reformatting as such:
$('.box').click(function (event){
var t = $(this), id, val, up, right, down, left, clickup, clickdown;
//...
Your variables id and val are not in a var statement, thus are implicitly created as members of the window object instead of being scoped to the local function. Change the semicolon on the line before each to a comma so that they become part of the var statement, and your code should begin working.