How to set an item limit on localstorage items Javascript HTML5 - javascript

I am trying to set a limit of 6 items to localstorage and also retrieve the items back. So for example if a user types in data into an input box with id #gsc-i-id2 and clicks the .gsc-search-button-v2, the data gets stored in localstorage which all works fine but I want to limit this to the 6 most recent input's, in the code i've tried using .shift but i'm very new to localstorage and js and i'm not sure what i'm doing wrong here. It does display only 6 items but doesn't shit the items up. any help would be very much appreciated.
(function() {
var propertiesList = document.getElementById('list');
var myProperties;
var obj;
if (window.localStorage.getItem('Address')) {
displayStorage();
}
// function to display localStorage contents on page load
function displayStorage() {
var data = "";
myProperties = JSON.parse(localStorage.getItem('Address'));
// if localStorage array is not empty
if (myProperties.length !== 0) {
for (var i = 0; i < myProperties.length; i++) {
var li = document.createElement('li');
data += myProperties[i].address + " ";
li.innerHTML = data;
data = '';
propertiesList.appendChild(li);
}
}
}
$("body").on('click', '.gsc-search-button-v2', function(index) {
var address = document.getElementById('gsc-i-id2');
obj = {};
obj.address = address.value;
myProperties = JSON.parse(localStorage.getItem('Address')) || [];
myProperties.push(obj);
localStorage.setItem('Address', JSON.stringify(myProperties));
addToPropertiesList();
})
function addToPropertiesList(e) {
var len = myProperties.length - 1;
var li = document.createElement('li');
var data = '';
data += myProperties[len].address + " ";
li.innerHTML = data;
if(len > 5){
myProperties.shift(li);
} else{
propertiesList.appendChild(li);
}
}
}());
<input autocomplete="off" type="text" size="10" class="gsc-input" name="search" title="search" id="gsc-i-id2" dir="ltr" spellcheck="false" style="width: 100%; padding: 0px; border: none; margin: -0.0625em 0px 0px; height: 1.25em; background: rgb(255, 255, 255); outline: none;">
<td class="gsc-search-button"><button class="gsc-search-button gsc-search-button-v2"><svg width="13" height="13" viewBox="0 0 13 13"><title>search</title><path d="m4.8495 7.8226c0.82666 0 1.5262-0.29146 2.0985-0.87438 0.57232-0.58292 0.86378-1.2877 0.87438-2.1144 0.010599-0.82666-0.28086-1.5262-0.87438-2.0985-0.59352-0.57232-1.293-0.86378-2.0985-0.87438-0.8055-0.010599-1.5103 0.28086-2.1144 0.87438-0.60414 0.59352-0.8956 1.293-0.87438 2.0985 0.021197 0.8055 0.31266 1.5103 0.87438 2.1144 0.56172 0.60414 1.2665 0.8956 2.1144 0.87438zm4.4695 0.2115 3.681 3.6819-1.259 1.284-3.6817-3.7 0.0019784-0.69479-0.090043-0.098846c-0.87973 0.76087-1.92 1.1413-3.1207 1.1413-1.3553 0-2.5025-0.46363-3.4417-1.3909s-1.4088-2.0686-1.4088-3.4239c0-1.3553 0.4696-2.4966 1.4088-3.4239 0.9392-0.92727 2.0864-1.3969 3.4417-1.4088 1.3553-0.011889 2.4906 0.45771 3.406 1.4088 0.9154 0.95107 1.379 2.0924 1.3909 3.4239 0 1.2126-0.38043 2.2588-1.1413 3.1385l0.098834 0.090049z"></path></svg></button></td>
<ul id="list"></ul>

I understood that you want to only save 6 recent items on localstorage
Since you can't directly set rules on localstorage, I suggest my solution as to how you should save the data on localstorage in this case:
check the length of the data array after you add an item to the array
remove the first item of the array if the length of the array exceeds 6
save the array data on localstorage
I provide the snippet for the process above.
// add code here for adding an item to the array
if(exceedsPropertiesLengthLimit(myProperties)) {
myProperties.shift();
}
// add code here for saving the array data on localstorage
function exceedsPropertiesLengthLimit(properties) {
let limitLength = 6;
let exceedsLimit = false;
if(properties.length > limitLength) {
exceedsLimit = true;
}
return exceedsLimit;
}

I understood the problem. If you only want to print the latest 5 addresses, then you may try something like this:
if(len>5){
for(var j = len; j>= len - 5; j++){
.... // Write here what you want to do with the last five values
}
}

Related

How to display XML content in HTML file with Javascript

This is my xml code . I want to display the contents in a html page with Javascript.
<businesses>
<business bfsId="" id="41481">
<advertHeader>Welding Supplies, Equipment and Service Business</advertHeader>
<Price>265000</Price>
<catalogueDescription>Extremely profitable (Sales £500k, GP £182k) business</catalogueDescription>
<keyfeature1>
Well established 25 year business with excellent trading record
</keyfeature1>
<keyfeature2>
Consistently high levels of turnover and profitability over last 5 years
</keyfeature2>
</business>
<business bfsId="" id="42701">
<broker bfsRef="1771" ref="003">Birmingham South, Wolverhampton & West Midlands</broker>
<tenure>freehold</tenure>
<advertHeader>Prestigious Serviced Office Business</advertHeader>
<Price>1200000</Price>
<reasonForSale>This is a genuine retirement sale.</reasonForSale>
<turnoverperiod>Annual</turnoverperiod>
<established>28</established>
<catalogueDescription>This well-located and long-established serviced office</catalogueDescription>
<underOffer>No</underOffer>
<image1>https://www.business-partnership.com/uploads/business/businessimg15977.jpg</image1>
<keyfeature1>other connections</keyfeature1>
<keyfeature2> Investment Opportunity</keyfeature2>
<keyfeature3>Over 6,000 sq.ft.</keyfeature3>
<keyfeature4>Well-maintained </keyfeature4>
<keyfeature5>In-house services & IT provided</keyfeature5>
</business>
</businesses>
This is the original xml file https://alpha.business-sale.com/bfs.xml I have just took a short portion to describe the situation.
Requirements
Print a row for every <business> element
For every <business> pick some specific child element and print column only for those element.( Not all ). For an example in this case I only want to print the value for <advertHeader> ; <Price> and <description> and want to ignore other elements.
Only print the row those <business> where value of <Price> is > 10000 . if it is then less than 10000 do not print that row
pagination after every 10 row
This is the html table
<table id="MainTable"><tbody id="BodyRows"></tbody></table>
And this is the javascript code that i have tried .
window.addEventListener("load", function() {
getRows();
});
function getRows() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("get", "2l.xml", true);
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
showResult(this);
}
};
xmlhttp.send(null);
}
function showResult(xmlhttp) {
var xmlDoc = xmlhttp.responseXML.documentElement;
removeWhitespace(xmlDoc);
var outputResult = document.getElementById("BodyRows");
var rowData = xmlDoc.getElementsByTagName("business");
addTableRowsFromXmlDoc(rowData,outputResult);
}
function addTableRowsFromXmlDoc(xmlNodes,tableNode) {
var theTable = tableNode.parentNode;
var newRow, newCell, i;
console.log ("Number of nodes: " + xmlNodes.length);
for (i=0; i<xmlNodes.length; i++) {
newRow = tableNode.insertRow(i);
newRow.className = (i%2) ? "OddRow" : "EvenRow";
for (j=0; j<xmlNodes[i].childNodes.length; j++) {
newCell = newRow.insertCell(newRow.cells.length);
if (xmlNodes[i].childNodes[j].firstChild) {
newCell.innerHTML = xmlNodes[i].childNodes[j].firstChild.nodeValue;
} else {
newCell.innerHTML = "-";
}
console.log("cell: " + newCell);
}
}
theTable.appendChild(tableNode);
}
function removeWhitespace(xml) {
var loopIndex;
for (loopIndex = 0; loopIndex < xml.childNodes.length; loopIndex++)
{
var currentNode = xml.childNodes[loopIndex];
if (currentNode.nodeType == 1)
{
removeWhitespace(currentNode);
}
if (!(/\S/.test(currentNode.nodeValue)) && (currentNode.nodeType == 3))
{
xml.removeChild(xml.childNodes[loopIndex--]);
}
}
}
But this code prints columns for all the nodes under <business> element. And the number of child elements under <business> are different . So the result comes like this
I dont want that. I want to only display the value of specific nodes under <business> element (in this case only include <advertHeader> ; <Price> and <description> ) so that the number of columns are equal in every row. How to do that?
Try finding the <business>-element with the most values and build your table around that. Here's an example snippet that does that for the data you presented.
{
const xml = new DOMParser()
.parseFromString(getData(), `text/xml`);
// the <business>-elements from xml
const businessElems = [...xml.querySelectorAll(`business`)];
// the nodeNames will be the header. While we're at it,
// we can count the number of headers (len) for later use
const headersFromXml = businessElems.map( v =>
[...v.querySelectorAll(`*`)]
.map( v => v.nodeName) )
.map( v => ( {len: v.length, headers: v} )
);
// now determine the longest header using a reducer
const businessElemWithMostNodes = headersFromXml
.reduce( (acc, v) => v.len > acc.len ? v : acc, {len: 0});
// utility to create a tablecell/header and append it to a row
const createCell = (rowToAppendTo, cellType, value) => {
const cell = document.createElement(cellType);
cell.innerHTML = value;
rowToAppendTo.appendChild(cell);
}
// utility to create a datarow and append it to a table
const createDataRow = (tableToAppendTo, businessElem) => {
const row = document.createElement(`tr`);
const rowValues = [];
// create values using the businessElemWithMostNodes order
businessElemWithMostNodes.headers
.forEach( head => {
const valueElem = businessElem.querySelector(`${head}`);
rowValues.push(valueElem ? valueElem.innerHTML : `-`);
});
rowValues.forEach( v => createCell(row, `td`, v) );
tableToAppendTo.appendChild(row);
};
// now we know enough to create the table
const table = document.createElement(`table`);
// the headerRow first
const headRow = document.createElement(`tr`);
businessElemWithMostNodes.headers.forEach( hv => createCell(headRow, `th`, hv) );
table.appendChild(headRow);
// next create and append the rows
businessElems.forEach(be => createDataRow(table, be));
// finally, append the table to document.body
document.body.appendChild(table);
// your xml string
function getData() {
return `
<businesses>
<business bfsId="" id="41481">
<advertHeader>Welding Supplies, Equipment and Service Business</advertHeader>
<Price>265000</Price>
<catalogueDescription>Extremely profitable (Sales £500k, GP £182k) business</catalogueDescription>
<keyfeature1>
Well established 25 year business with excellent trading record
</keyfeature1>
<keyfeature2>
Consistently high levels of turnover and profitability over last 5 years
</keyfeature2>
</business>
<business bfsId="" id="42701">
<broker bfsRef="1771" ref="003">Birmingham South, Wolverhampton & West Midlands</broker>
<tenure>freehold</tenure>
<advertHeader>Prestigious Serviced Office Business</advertHeader>
<Price>1200000</Price>
<reasonForSale>This is a genuine retirement sale.</reasonForSale>
<turnoverperiod>Annual</turnoverperiod>
<established>28</established>
<catalogueDescription>This well-located and long-established serviced office</catalogueDescription>
<underOffer>No</underOffer>
<image1>https://www.business-partnership.com/uploads/business/businessimg15977.jpg</image1>
<keyfeature1>other connections</keyfeature1>
<keyfeature2> Investment Opportunity</keyfeature2>
<keyfeature3>Over 6,000 sq.ft.</keyfeature3>
<keyfeature4>Well-maintained </keyfeature4>
<keyfeature5>In-house services & IT provided</keyfeature5>
</business>
</businesses>`;
}
}
body {
margin: 1rem;
font: 12px/15px normal consolas, verdana, arial;
}
.hidden {
display: none;
}
th {
background-color: black;
color: white;
border: 1px solid transparent;
padding: 2px;
text-align: left;
}
td {
border: 1px solid #c0c0c0;
padding: 2px;
vertical-align: top;
}

Getting a position of an element in a 2D array

So I'm building a a turn based board game which needs to contain 2 player that can move across the map. I'm stuck at getting the position of the player(which is just a simple div element) inside of that 2D array. I've tried using indexOf, but even tho it's placed inside an onclick function, always returns 0.
The html code contains just of few div's with col classes:
And here is the JavaScript code (btw it contains some unnecessary stuff that I've just added for test purposes) :
let row = document.querySelector('.row');
let fields = document.getElementsByClassName('col-md-2')
let fieldsArr = Array.from(fields);
let header = document.getElementById("clicked");
let cols = header.getElementsByClassName("col-md-2");
let player = document.getElementById('player');
let player2 = document.getElementById('player2');
let blockedField = document.getElementsByClassName('blocked');
fieldsArr.sort(function() {
return 0.5 - Math.random();
}).forEach(function(el) {
row.appendChild(el);
});
// ADD AN EVENT LISTENER AND LISTEN FOR COLS ID
function replyClick(e) {
e = e || window.event;
e = e.target || e.srcElement;
if (e.nodeName === 'DIV') {
let changable = e.id;
//console.log(changable);
}
}
// CREATE A 2D ARRAY (MAP)
var map = [];
while (fieldsArr.length) map.push(fieldsArr.splice(0, 6));
// ON CLICK ADD A CLASS OF ACTIVE
for (var i = 0; i < cols.length; i++) {
cols[i].addEventListener("click", function() {
var current = document.getElementsByClassName("active");
current[0].className = current[0].className.replace(" active", "");
this.className += " active";
});
}
// MOVE PLAYER ONE ACROSS THE MAP
function movePlayer(multWidth, multHeight) {
$(".active").append(player);
if ((row).click > multWidth) {
alert(1)
}
}
// MOVE PLAYER 2 ACROSS THE MAP
function movePlayer2() {
$(".active").append(player2);
}
// MAKE GRAYED OUT FIELD UNAVALIABLE AND SHOW AN ALERT
$(blockedField).css("pointer-events", "none");
// APPEND PLAYER1(2) TO THE FIRST(LAST) FIELD ON THE MAP
map[0][0].appendChild(player);
map[5][5].appendChild(player2);
// GET PLAYERS CURRENT POSITION
$(row).click(function() {
let current = player.offsetTop;
});
const widthAllowed = 3 * 156;
const heightAllowed = 3 * 146;
// LIMIT PLAYER MOVES
let player1Moves = 3;
player2Moves = 3;
$(row).click(function() {
movePlayer();
let remainingMoves = player1Moves -= 1;
if (remainingMoves === 0) {
alert("You don't have any more moves. Player's 2 turn.");
$(player).css("pointer-events", "none");
$(row).click(movePlayer2);
}
})
for (var x = 0; x < map.length; x++) {
for (var y = 0; y < map[x].length; y++) {
console.log(x, y);
}
}
console.log(map);
console.log(map[2][5]);
console.log(map[5][0]);
You should take some beginner jquery/javascript course, since your question is very simple and you will find the whole programming thing way easier with a few basic concepts (like selectors, events and callbacks)
That said, here is a basic example of how to return the div element that contains the player element and how to use event attachment instead of inline events.
let row = $('.row');
row.on('click', replyClick);
function replyClick(e) {
var targetRow = $(e.target);
$('.row > div.active').removeClass('active');
targetRow.addClass('active');
var player = $('.row div.player');
alert(player.parent().attr('id'));
};
.player {
width: 20px;
height: 20px;
background: red;
}
.row > div {
padding: 10px;
width: 20px;
height: 20px;
border: 1px solid red;
}
.row > div.active {
background: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="row">
<div id="col1" class="col-md-2">
<div class="player"></div>
</div>
<div id="col2" class="col-md-2 blocked"></div>
<div id="col3" class="col-md-2 active"></div>
<div id="col4" class="col-md-2"></div>
</div>
</div>

Javascript Can Push() and Pop() and Image Replacement Work within an Array

Can Push() and Pop() and Image Replacement Work within an Array?
8th Gr math teacher attempting to create a slide show of question images that pop() and push() through an image array based on student responses. If the student answers correctly the question is popped, but if they answer incorrectly it is added to the end of the queue. Additionally, since deleting elements in the DOM is bad, I am replacing the current image's src and id with that of the next element in queue. The array is then popped and pushed along, but whenever I enter in the incorrect answer twice the same image appears.
I have moved the global variable that holds the array, domEls, inside of the function retrieveAnsForImage to force it to randomize the images in the array. When I do this, the images change correctly so I believe it is the push() and pop() commands.
I included a snippet that doesn't work here, but works like a champ in Notepad ++. I just took a crash course in Javascript, HTML and CSS last month on Codecademy, I am very new to this. Thank you for reading.
//Jquery
$(document).ready(function() {
$(function() {
$('img.card').on('contextmenu', function(e) {
e.preventDefault();
//alert(this.id);
openPrompt(this.id);
});
});
});
//Provide and Shuffle array function
function shuffleImgs() {
var imgArr = [
"image1",
"image2",
"image3",
"image4",
"image5",
"image6",
"image7",
"image8",
"image9"
];
var currentIndex = imgArr.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = imgArr[currentIndex];
imgArr[currentIndex] = imgArr[randomIndex];
imgArr[randomIndex] = temporaryValue;
}
alert("shuffle");
return imgArr;
}
function arrStack() {
var imgArr = shuffleImgs();
//Map over the array to create Dom elements
var domElements = imgArr.map(function (imgName, index) {
var cardDiv = document.createElement('div');
var cardImage = document.createElement('img');
//Add img id and class
cardImage.id = imgName;
cardImage.classList.add('card');
//Set img source
cardImage.src = `images/${imgName}.jpg`;
//Put it all together
cardDiv.appendChild(cardImage);
return cardDiv;
});
//this notation to call nested function for Global var stack
this.nDomElements = function () {
stackDomEl = domElements;
return stackDomEl;
}
//Display last element in array
//this notation to call the nested function from outside the function
this.nDisplayLastArr = function displayLastArr() {
var lastImgArr = domElements[domElements.length - 1];
//alert(lastImgArr);
//Append the elements to the DOM
var modal = document.querySelector('div.modal');
modal.appendChild(lastImgArr);
return lastImgArr; //Use brackets when your are returning more than one variable
}
}
//Function called from Jquery to open prompt to answer question
function openPrompt(imageId) {
var userAns = prompt("Please enter your answer below and click OK");
if (userAns == null || userAns == "") {
alert("User cancelled the prompt. Exit and please try again!");
}
else {
/*Vain hope that I can pass imageId from click event through the user prompt
to the answer checking function retrieveAnsForImage*/
retrieveAnsForImage(imageId, userAns); //out of scope?
}
}
//Global variable
func = new arrStack();
window.domEls = func.nDomElements();
//Compare user responses with the question image by use of the click image id
function retrieveAnsForImage(imageId, userAns) {
//Change these variables to the correct answer whenever this website is reused in other assignments
var ansImage1 = "1";
var ansImage2 = "2";
var ansImage3 = "3";
var ansImage4 = "4";
var ansImage5 = "5";
var ansImage6 = "6";
var ansImage7 = "7";
var ansImage8 = "8";
var ansImage9 = "9";
//Give students a second chance to retry a question
//var hintCounter = 0; //include a while statement above the if statements to allow students a retry
/*Compare user response with correct case answer and correct clicked image.
Students may enter the right answer for the wrong image hence the &&.
Images will always be refered to as image1, image2, etc.*/
if (userAns === ansImage1 && imageId === "image1") {
correctAns(imageId);
}
else if (userAns === ansImage2 && imageId === "image2") {
correctAns(imageId);
}
else if (userAns === ansImage3 && imageId === "image3") {
correctAns(imageId);
}
else if (userAns === ansImage4 && imageId === "image4") {
correctAns(imageId);
}
else if (userAns === ansImage5 && imageId === "image5") {
correctAns(imageId);
}
else if (userAns === ansImage6 && imageId === "image6") {
correctAns(imageId);
}
else if (userAns === ansImage7 && imageId === "image7") {
correctAns(imageId);
}
else if (userAns === ansImage8 && imageId === "image8") {
correctAns(imageId);
}
else if (userAns === ansImage9 && imageId === "image9") {
correctAns(imageId);
}
else {
window.alert("Incorrect Answer");
incorrectAns();
}
function correctAns(){
//Second to last element in array
var SecLastElArr = domEls[domEls.length - 2];
//Pull image id from second to last element in array
var nextImgId = SecLastElArr.querySelector("div > img").id;
//Pull image id from document
var imgId = document.querySelector("div > img").id;
//Student incorrect answer change im
document.getElementById(imgId).src = `images/${nextImgId}.jpg`;
document.getElementById(imgId).id = nextImgId;
domEls.pop();
//Think about when the array is completely gone
//while domEls.length !== 0;
}
function incorrectAns(){
//Last element in array
var LastElArr = domEls[domEls.length - 1];
//Second to last element in array
var SecLastElArr = domEls[domEls.length - 2];
//Pull image id from second to last element in array
var nextImgId = SecLastElArr.querySelector("div > img").id;
//Pull image id from document
var imgId = document.querySelector("div > img").id;
//Student incorrect answer change image src and id to next element in queue
document.getElementById(imgId).src = `images/${nextImgId}.jpg`;
document.getElementById(imgId).id = nextImgId;
//Remove last element in array
domEls.pop();
//move the last element to the first element in the array for another attempt
domEls.push(LastElArr);
alert(domEls.length);
}
}
function overlay() {
var el = document.getElementById("overlay");
el.style.visibility = (el.style.visibility == "visible") ? "hidden" : "visible";
}
#overlay {
visibility: hidden;
position: absolute;
left: 0px;
top: 0px;
width:100%;
height:100%;
text-align:center;
z-index: 1000;
background-color: rgba(0,191, 255, 0.8);
}
#overlay div {
width:70%;
margin: 10% auto;
background-color: #fff;
border:1px solid #000;
padding:15px;
text-align: center;
}
body {
height:100%;
margin:0;
padding:0;
}
#close-img {
float: right;
clear: right;
width: 30px;
height: 30px;
bottom: 0;
right: 0;
}
<!DOCTYPE html>
<html>
<head>
<title></title>
<span> "Left click to view any questions. Right click (two finger tap) to answer the question and claim the tile. Each player must claim 4 tiles to successfully complete the assignment."</span>
<link href="https://fonts.googleapis.com/css?family=Oswald:300,700|Varela+Round" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="Stack Rnd Temp.css">-->
<script type="text/javascript" src="Stack Rnd Temp.js"></script>
<script src="jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="StackRndTempjq.js"></script>
</head>
<body>
<div class="title">
<h1></h1>
</div>
<div id="gameboard"> <!--Container for all nine divs-->
<a href='#' onclick='overlay()'>Click here to show the overlay</a>
</div>
<div class="modal" id="overlay">
<p> "Right click to answer the question"</p>
<script>
func = new arrStack();
func.nDisplayLastArr();
</script>
<img src="images/close.png" id="close-img" onclick="overlay()">
</div>
</body>
</html>
Your issue is that pop removes the last element from the array while push adds the element to end of the array.
What you probably want to do is use shift to remove the the first element from the array and pop it back to the end if the answer is wrong.
Alternately, you could pop the last element and use unshift to insert back into the beginning of you want to work in the other direction.
Here's a quick mockup without images.
var currentTest = null;
function getTest() {
$('#answer').html("").hide();
if (tests.length > 0) {
currentTest = tests.shift(); // remove the first question
$('#question').fadeIn(450).html(currentTest.q);
return currentTest;
} else {
$('#answer').html("Finished").fadeIn(500);
$('#btnCorrect').unbind();
$('#btnWrong').unbind();
}
}
var tests = [];
for (var i = 0; i < 5; i++) {
var question = "Question " + i;
var answer = "Answer " + i;
tests.push({
q: question,
a: answer
});
}
$('#btnCorrect').click(function() {
$('#question').hide();
$('#answer').fadeIn(450).html("Correct!");
window.setTimeout(getTest, 750);
});
$('#btnWrong').click(function() {
$('#question').hide();
tests.push(currentTest); // put the question back in the array
$('#answer').fadeIn(450).html("Incorrect!");
window.setTimeout(getTest, 750);
});
$(document).ready(function() {
getTest();
})
* {
font-family: arial;
}
#panel {
height: 50px;
}
#answer {
border: 1px solid #cccccc;
background: #dedede;
width: 400px;
}
#question {
border: 1px solid #999999;
background: #dedede;
width: 400px;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<div id="panel">
<div id="answer"></div>
<div id="question"></div>
</div>
<input id="btnCorrect" value="Mock Correct Answer" type="button">
<input id="btnWrong" value="Mock Wrong Answer" type="button">
</body>
</html>

Checking function for sliding puzzle javascript

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..

Why is my AI acting strange in a game of tic-tac-toe?

The organisation of the data for the game of tic-tac-toe is simple.
There is an array that contains a lot of HTML buttons.
The program remembers if a button is used by using the isUsed array.
It checks what image to display on the button using the isX Boolean.
The program contains a list of winning combinations that are available to everyone using freeWins array. When a human picks 5, all the winning combos containing 5 are moved to an array called yourWins. Then, the positions owned by the human will be replaced from yourWins.
Code:
<!DOCTYPE html>
<html>
<title> 1Player </title>
<style>
#stage{
width: 400px;
height: 400px;
padding: 0px;
position: relative;
}
.square{
user-select : none;
-webkit-user-select: none;
-moz-user-select: none;
width: 64px;
height: 64px;
background-color: gray;
position: absolute;
}
</style>
<body>
<div id="stage">
</div>
</body>
<script>
const ROWS = 3;
const COLS = 3;
const GAP = 10;
const SIZE = 64;
var stage = document.querySelector("#stage");
var lotOfButtons = [];
var isUsed = [false,false,false,false,
false,false,false,false,false];
var myPositions = "";
var yourPositions = "";
var youPicked = "";
var iPicked = "";
var isX = true;
var isFirstMove = true;
var myWins = [];
var yourWins = [];
var freeWins = ["123","159","147",
"258",
"369","357",
"456",
"789"];
prepareBoard();
stage.addEventListener("click",clickHandler,false);
function prepareBoard(){ //Prepare the board on which the users will play.
for(var row = 0;row<ROWS;row++){ // Prepare the rows.
for(var col = 0;col < COLS;col++){ //Prepare the columns.
var square = document.createElement("button"); //Prepare the button which represents a square.
square.setAttribute("class","square"); //Make it a square 'officially'.
stage.appendChild(square); //Add the square to the play area..
lotOfButtons.push(square); //Keep a record of squares.
square.style.left = col * (SIZE+GAP) + "px"; //Properly set the horizontal spacing.
square.style.top = row * (SIZE+GAP) + "px"; //Properly set the vertical spacing.
}
}
}
function clickHandler(event){
var index = lotOfButtons.indexOf(event.target);
if(index=== -1 || isX === false){
return;
}
isX = false;
isUsed[index] = true;
event.target.style.backgroundImage = "url(../img/X.png)";
yourMove(index+1);
}
function yourMove(someIndex){
console.log("Finding Human Winning Moves::: ");
yourWins = findWinnindMoves(yourWins,someIndex);
console.log(yourWins);
console.log("Clearing Human Owned Positions::: ");
yourWins = clearOwnedPositions(yourWins,someIndex);
console.log(yourWins + "\n");
myMove();
}
function myMove(){
var isFifthSquareUsed = isFiveUsed();
if(isFifthSquareUsed === false){ //Is fifth square used ?? --- NO!
var selectedSquare = lotOfButtons[4];
selectedSquare.style.backgroundImage = "url(../img/O.PNG)";
isUsed[4] = true;
isFirstMove = false;
isX = true;
console.log("Finding AI Winning Moves::: ");
myWins = findWinnindMoves(myWins,4);
console.log(myWins);
console.log("Clearing AI Owned Positions::: ");
myWins = clearOwnedPositions(myWins,4);
console.log(myWins + "\n");
}else if(isFifthSquareUsed && isFirstMove){ //Is fifth square used ?? --- YES, but it is first move.
//TODO add logic
}else{ //Some random square has already been chosen. Now it is time to use strategy.
//TODO add logic
}
}
function findWinnindMoves(winsArray,someIndex){//using which combination can a user or AI win ?
for(var i = 0;i<freeWins.length;i++){
if(freeWins[i].indexOf(someIndex) != -1){
winsArray.push(freeWins[i]);
i--;
freeWins.splice(i,1);
}
}
return winsArray;
}
function clearOwnedPositions(winsArray,someIndex){ //Reduce the number of squares needed to get triplets.
for(var i=0;i<winsArray.length;i++){
if(winsArray[i].indexOf(someIndex) != -1){
winsArray[i] = winsArray[i].replace(someIndex,"");
}
}
return winsArray;
}
function isFiveUsed(){ //Is AI able to get the center square ?
return isUsed[4];
}
</script>
</html>
Problem
The determination of the winning moves for the user and the AI works properly if I click the second or the third row and not on first row.
Output on Row 2
Finding Human Winning Moves:::
["147", "456"]
Clearing Human Owned Positions:::
17,56
Finding AI Winning Moves:::
["147", "456"]
Clearing AI Owned Positions:::
17,56
Output on Row 1
Finding Human Winning Moves:::
["123", "123", "123", "123", "123", "123", "123", "123"]
Clearing Human Owned Positions:::
23,23,23,23,23,23,23,23
Finding AI Winning Moves:::
[]
Clearing AI Owned Positions:::
Also, after clearing the owned squares the array brackets are gone.
Why are the two problems happening?
To Answer your second question about the Arrays, You cannot initialize a blank Array and at the same time .push() in the same area. Every time you run the JavaScript it re-creates the Array and that means goodbye to the stuff you are putting into it. Maybe you should initialize the Array outside a function and put the rest of the code inside the function. As for your game AI, I am not entirely sure. Hope this helps somewhat.

Categories