I am coding a simple JavaScript version of the classic boardgame "Mastermind".
I have some (for sure fundamental) problems, 99% with JavaScript arrays and referencing their values or elements. These issues am I "solving" currently for quite long time, so I decided to ask.
Facts:
my game pegs, and the game board at all is made in HTML table, pegs are implemented like this(a row contains 4 pegs and a td containing the results image):
<td>
<a href="javascript:void(0)"
onClick="changePegColor('0','0'); return false"
onFocus="this.blur()">
<img src="img/void.png" width=22 height=22 name="peg_0_0">
</a>
</td>
my default array declaration looks this (showing both variants tried, none of them worked for me):
var pegsAI = ['pegAI_0', 'pegAI_1', 'pegAI_2', 'pegAI_3'];
var pegsAI = new Array('pegAI_0', 'pegAI_1', 'pegAI_2', 'pegAI_3');
Setting AI's pegs, which is the player going to guess works this way (this is working, no problem with array):
pegsAI[position] = Math.floor((Math.random() * possibleColorsNumber));
And here are my issues:
At the moment of clicking Submit button, there is a check if every peg in a row has a colour this way (this does neither work, nor throws an error in chrome F12):
...
for (var position = 0; position <= 3; position++) {
if (document["peg_" + currentRow + "_" + position].src === "img/void.png") {
alert('Finish color picking!');
return false;
}
}
...
After this check, there is function that should convert players pegs to numbers and save it to an array and there is probably a problem, because it doesn't work (array got undefined values in result):
function convertToNumbers() {
for (var position = 0; position <= 3; position++) { //4 pegs in row, var 'position' declares peg's position
if (document["peg_" + currentRow + "_" + position].src === possibleColors[index] ) { //if a peg has an color (his img's src is equal to an element of array 'possibleColors', which contains possible img's src's), then ->
pegsPlayer[position] = index; // -> then index of this color saves to pegsPlayer[] array
}
}
}
///added for explanation
my function for calculating score:
var goodPegPlayer = [false, false, false, false];
var goodPegAI = [false, false, false, false];
function calcSkore() {
convertToNumbers();
alert("array values" + pegsPlayer[0] + "_" + pegsPlayer[1] + "_" + pegsPlayer[2] + "_" + pegsPlayer[3]);
for (var position = 0; position <= 3; position++) {
goodPegPlayer[position] = false;
goodPegAI[position] = false;
if (pegsPlayer[position] === pegsAI[position]) {
skoreBlack++;
goodPegPlayer[position] = true;
goodPegAI[position] = true;
}
}
for (var position = 0; position <= 3; position++) {
if (goodPegPlayer[position] === false) {
for (var positionAI = 0; positionAI <= 3; positionAI++) {
if ((position !== positionAI) && (goodPegPlayer[position] === false) && (goodPegAI[positionAI] === false)) {
if (pegsPlayer[position] === pegsAI[positionAI]) {
skoreWhite++;
goodPegPlayer[position] = true;
goodPegAI[positionAI] = true;
}
}
}
}
}
resultsSubmit();
}
!! right after using converToNumber() function in this function, an alert() is used to check if the values are correct.
You're not accessing the DOM correctly. Try using the id attribute instead of name to identify your images and update your JavaScript as follows:
for (var position = 0; position <= 3; position++) {
var id = "peg_" + currentRow + "_" + position;
if (document.getElementById(id).src === "img/void.png") {
alert('Finish color picking!');
return false;
}
}
I wrote my own version of Mastermind back in 2004.
The code is a bit outdated, but it still works in modern browsers. I'm not sure if it's helpful to you, but feel free to take a look!
The code is MIT licensed, which means you can freely use it (or parts of it) anywhere you want!
Resources
Demo
Github repository
Related
I currently have these two conditions that must be met for a modal to display on screen:
if($("#id_form-0-calculated").val() === $("#id_form-0-modified").val()) {
if($("#id_form-0-calculated").val() === $("#id_form-0-modified").val()) {
$("#Modal").modal();
}
}
This works perfectly for the first element (N = 0), the problem is that the web page I am editing can have N elements with the following pattern:
id_form-N-calculated / id_form-N-modified
How could I make the if statements dynamic and work for all existing N values instead of just one static value?
I don't know how will you do this, but I regularly use the following:
var N = 0;
if($("#id_form-"+N+"-calculated_ad_pathogenicity").val() === $("#id_form-"+N+"-modified_ad_pathogenicity").val()) {
if($("#id_form-"+N+"-calculated_ar_pathogenicity").val() === $("#id_form-"+N+"-modified_ar_pathogenicity").val()) {
$("#Modal").modal();
}
}
Most of the time, N is used in either loops or functions.
You could write a function something like
function check(n){
if($("#id_form-" + n + "-calculated_ad_pathogenicity").val() === $("#id_form-" + n + "-modified_ad_pathogenicity").val()) {
if($("#id_form-" + n + "-calculated_ar_pathogenicity").val() === $("#id_form-" + n + "-modified_ar_pathogenicity").val()) {
$("#Modal").modal();
}
}
}
Adding it to the checking flow. For example
const n = 4;
for(let i = 0; i < n; i++){
check(i);
}
This code takes an integer and returns the amount of 1s that are present.
function countOnes(i) {
let str = i.toString();
let ones = 0;
for(let x = 0; x < i.length; x++) {
if(str.charAt(x) === '1') ones++;
}
return ones;
}
console.log(countOnes(111000));
But it only appears to work in certain executors of JavaScript. If I enter this code into p5.js or Mozilla MDN, I will receive the desired output of 3.
But if I use the console in my browser and some other websites emulating that, 0 will be returned with every given value.
Why is this the case?
you cant loop on i.length, i its still a 'Number' type,
you should loop on "str.length" instead.
you better give more meaningful names... i should be num,
str should be numStr, ones should be counter.
try this:
function countOnes(num) {
var counter = 0;
var numsArray = Array.from((num + ''))
numsArray.forEach(num => {
return (num == 1)? counter++ : ''
})
return counter
}
console.log(countOnes(1110010)); // 4
I have some inputs in my app: <_input code/> + <_input code/> = <_input code/>.
Let's imagine first input name is a, appropriately second and third inputs' names are b and c. I filled my inputs:
7 + x = 12
Is there any way to calculate x value?
What do I want from my script:
It finds variable in inputs' values.
It checks all fields of my form filled properly.
It finds variable in inputs' values.
From given information script calculates value of variable.
How many inputs will be doesn't matter. I just want to find value of x. Is there any library to do this?
function calculcateA(b,c){
return c-b;
}
if(inputA === 'x'){
alert(calculateA(inputB,inputC));
}
And so on... there is nothing wrong with this functions, but I want to automate this proccess like WolframAplha.
The best thing for you, I guess would be to find some library for solving equations. If you are in need to solve bigger sets of equations then maybe something related to linear algebra.
Can't really tell you an exact solution so you will have to search for yourself.
Here is some code that should solve the problem.
function calculate() {
var varIndex = -1;
//Ensure that at least two three arguements are passed
if (arguments.length < 3) {
throw "You need at least three parameters to make an equation";
}
//Make sure that there is only one variable
for (var i = 0; i < arguments.length; i++) {
if (isNaN(arguments[i])) {
if (varIndex != -1) {
throw "You can't have two variables";
return;
}
varIndex = i;
}
}
//If variable has been found
if (varIndex != -1) {
var answer = 0;
//If variable is at the last position, add all constants
if (varIndex == types.length - 1) {
for (var j = 0; j < arguments.length - 1; j++) {
answer = answer + j;
}
} else {
//Otherwise Deduct all values from the last
answer = arguments[arguments.length - 1];
for (var k = 0; k < arguments.length - 1; k++) {
if (k == varIndex) { continue; }
answer = answer - j;
}
}
//Return Result
return { variable: arguments[varIndex], answer: answer };
}
else {
throw "You need at least one variable";
return;
}
}
You would use the above as follows:
var a = document.querySelector("input[name=a]");
var b = document.querySelector("input[name=b]");
var c = document.querySelector("input[name=c]");
var calcBtn = document.getElementById("calculate");
calcBtn.addEventListener("click", function () {
try {
var result = calculate(a.value, b.value, c.value);
console.log("The value of " + result.variable + " is " + result.answer);
} catch (e) {
console.log(e);
}
});
I am have an issue with a JS script I am trying to put together. I have an HTML table with somewhere in the neighborhood of 300 rows in it. I have made a sort function that will make the table headers clickable and launch my sort function. I would like to integrate a progress bar because in larger tables (500 - 1000 rows) after a header is clicked the table takes a bit of time to sort (IE is a big offender). The progress bar would tell them how much time remains before the sort is complete. The method I had in mind was a div element that I would resize based on the progression of the sort loop. The problem is that I can't seem to figure out how to integrate such a routine into my loop.
I've researched the issue and taken note of this: How to change progress bar in loop?
and this: Using setTimeout to update progress bar when looping over multiple variables
The second topic has a few demos that do essentially what I would like to do as far as the progress bar goes. However, anytime I try to implement the solutions shown in those two posts I either:
A - Crash the browser
B - Progress bar appears to work, but goes from 0 - 100% instantly, not progressively.
I am hoping someone can lead me in the right direction on what to do. This table sort progress indicator must be done using native JS because the contents must be available offline, hence I can't include any jQuery libraries via CDN and bloating the document with the entire jQuery library isn't desired.
I've created a JS fiddle with my code in it. I've stripped out what I had for progress bar code because I kept crashing the browser so all that is there as far as scripts go is the sorting-related code. jsfiddle
Here is the JS itself:
//Change this variable to match the "id" attribute of the
//table that is going to be operated on.
var tableID = "sortable";
/**
* Attach click events to all the <th> elements in a table to
* call the tableSort() function. This function assumes that cells
* in the first row in a table are <th> headers tags and that cells
* in the remaining rows are <td> data tags.
*
* #param table The table element to work with.
* #return void
*/
function initHeaders(table) {
//Get the table element
table = document.getElementById(table);
//Get the number of cells in the header row
var l = table.rows[0].cells.length;
//Loop through the header cells and attach the events
for(var i = 0; i < l; i++) {
if(table.rows[0].cells[i].addEventListener) { //For modern browsers
table.rows[0].cells[i].addEventListener("click", tableSort, false);
} else if(table.rows[0].cells[i].attachEvent) { //IE specific method
table.rows[0].cells[i].attachEvent("onclick", tableSort);
}
}
}
/**
* Compares values in a column of a table and then sorts the table rows.
* Subsequent calls to this function will toggle a row between ascending
* and descending sort directions.
*
* #param e The click event passed in from the browser.
* #return mixed No return value if operation completes successfully, FALSE on error.
*/
function tableSort(e) {
/**
* Checks to see if a value is numeric.
*
* #param n The incoming value to check.
* #return bool TRUE if value is numeric, FALSE otherwise.
*/
tableSort.isNumeric = function (n) {
var num = false;
if(!isNaN(n) && isFinite(n)) {
num = true;
}
return num;
}
//Get the element from the click event that was passed.
if(e.currentTarget) { //For modern browsers
e = e.currentTarget;
} else if(window.event.srcElement) { //IE specific method
e = window.event.srcElement;
} else {
console.log("Unable to determine source event. Terminating....");
return false;
}
//Get the index of the cell, will be needed later
var ndx = e.cellIndex;
//Toggle between "asc" and "desc" depending on element's id attribute
if(e.id == "asc") {
e.id = "desc";
} else {
e.id = "asc";
}
//Move up from the <th> that was clicked and find the parent table element.
var parent = e.parentElement;
var s = parent.tagName;
while(s.toLowerCase() != "table") {
parent = parent.parentElement;
s = parent.tagName;
}
/*
Executes two different loops. A "for" loop to control how many
times the table rows are passed looking for values to sort and a
"while" loop that does the actual comparing of values. The "for"
loop also controls how many times the embedded "while" loop will
run since each iteration with force at least one table row into
the correct position.
*/
//var interval = setInterval( function () { progress.updateProgress() } , 100);
var rows = parent.tBodies[0].rows.length; //Isolate and count rows only in the <tbody> element.
if(rows > 1) { //Make sure there are enough rows to bother with sorting
var v1; //Value 1 placeholder
var v2; //Value 2 placeholder
var tbody = parent.tBodies[0]; //Table body to manipulate
//Start the for loop (controls amount of table passes)
for(i = 0; i < rows; i++) {
var j = 0; //Counter for swapping routine
var offset = rows - i - 1; //Stops next loop from overchecking
// WANT TO UPDATE PROGRESS BAR HERE
//Start the while loop (controls number of comparisons to make)
while(j < offset) {
//Check to make sure values can be extracted before proceeding
if(typeof tbody.rows[j].cells[ndx].innerHTML !== undefined && typeof tbody.rows[j + 1].cells[ndx].innerHTML !== undefined) {
//Get cell values and compare
v1 = tbody.rows[j].cells[ndx].innerHTML;
v2 = tbody.rows[j + 1].cells[ndx].innerHTML;
if(tableSort.isNumeric(v1) && tableSort.isNumeric(v2)) {
//Dealing with two numbers
v1 = new Number(v1);
v2 = new Number(v2);
if(v1 > v2) {
if(e.id == "asc") { //v1 moves down
tbody.insertBefore(tbody.rows[j + 1], tbody.rows[j]);
}
} else {
if(e.id == "desc") { //v1 moves down
tbody.insertBefore(tbody.rows[j + 1], tbody.rows[j]);
}
}
} else if(tableSort.isNumeric(v1) && !tableSort.isNumeric(v2)) {
//v2 is a string, v1 is a number and automatically wins
if(e.id == "asc") { //v1 moves down
tbody.insertBefore(tbody.rows[j + 1], tbody.rows[j]);
}
} else if(!tableSort.isNumeric(v1) && tableSort.isNumeric(v2)) {
//v1 is a string, v2 is a number and automatically wins
if(e.id == "desc") { //v1 moves down
tbody.insertBefore(tbody.rows[j + 1], tbody.rows[j]);
}
} else {
//Both v1 and v2 are strings, use localeCompare()
if(v1.localeCompare(v2) > 0) {
if(e.id == "asc") { //v1 moves down
tbody.insertBefore(tbody.rows[j + 1], tbody.rows[j]);
}
} else {
if(e.id == "desc") { //v1 moves down
tbody.insertBefore(tbody.rows[j + 1], tbody.rows[j]);
}
}
}
j++;
} else {
console.log("One of the values turned up undefined");
}
}
}
}
}
//Wait until DOM is ready and then initialize the table headers.
window.onload = function () {
initHeaders(tableID);
}
Thanks in advance to anyone who can point me in the right direction.
-----
EDIT:
----- Okay so after reading the answers here and making some hefty modifications to how I was going about things I have come up with a much better solution. The progress bar isn't exactly what I wanted, but it is close. (Although I believe that it is showing up on the page just as the sorting is getting ready to finish up.)
I modified my loop to do a O(n log n) quick sort and instead of modifying the DOM directly I instead isolate the table rows to sort and copy them into an array. I then do the sort directly on the array and once it is finished I rebuild the rows and then remove the old rows and append the new ones in. The sort time has been reduced significantly.
Have a look: http://jsfiddle.net/jnBmp/5/
And here is the new JS code:
//Change this variable to match the "id" attribute of the
//table that is going to be operated on.
var tableID = "sortable";
/**
* Attach click events to all the <th> elements in a table to
* call the tableSort() function. This function assumes that cells
* in the first row in a table are <th> headers tags and that cells
* in the remaining rows are <td> data tags.
*
* #param table The table element to work with.
* #return void
*/
function initHeaders(table) {
//Get the table element
table = document.getElementById(table);
//Get the number of cells in the header row
var l = table.rows[0].cells.length;
//Loop through the header cells and attach the events
for(var i = 0; i < l; i++) {
if(table.rows[0].cells[i].addEventListener) { //For modern browsers
table.rows[0].cells[i].addEventListener("click", tableSort, false);
} else if(table.rows[0].cells[i].attachEvent) { //IE specific method
table.rows[0].cells[i].attachEvent("onclick", tableSort);
}
}
}
function tableSort(e) {
var runs = 0;
var pix = 0;
var ndx = 0;
var dir = "right";
var interval = false;
//Get the element from the click event that was passed.
if(e.currentTarget) { //For modern browsers
e = e.currentTarget;
} else if(window.event.srcElement) { //IE specific method
e = window.event.srcElement;
} else {
console.log("Unable to determine source event. Terminating....");
return false;
}
//Get the index of the cell, will be needed later
ndx = e.cellIndex;
//Toggle between "asc" and "desc" depending on element's id attribute
if(e.id == "asc") {
e.id = "desc";
} else {
e.id = "asc";
}
//Move up from the <th> that was clicked and find the parent table element.
var parent = e.parentElement;
var s = parent.tagName;
while(s.toLowerCase() != "table") {
parent = parent.parentElement;
s = parent.tagName;
}
//Get the rows to operate on as an array
var rows = document.getElementById("replace").rows;
var a = new Array();
for(i = 0; i < rows.length; i++) {
a.push(rows[i]);
}
//Show progress bar ticker
document.getElementById("progress").style.display = "block";
/**
* Show the progress bar ticker animation
*
* #param pix The current pixel count to set the <div> margin at.
*/
function updateTicker(pix) {
var tick = document.getElementById("progressTicker");
document.getElementById("progressText").style.display = "block";
document.getElementById("progressText").innerHTML = "Sorting table...please wait";
if(dir == "right") {
if(pix < 170) {
pix += 5;
tick.style.marginLeft = pix + "px";
} else {
dir = "left";
}
} else {
if(pix > 0) {
pix -= 5;
tick.style.marginLeft = pix + "px";
} else {
dir = "left";
}
}
interval = window.setTimeout( function () { updateTicker(pix); }, 25);
}
updateTicker(pix);
/**
* Checks to see if a value is numeric.
*
* #param n The incoming value to check.
* #return bool TRUE if value is numeric, FALSE otherwise.
*/
isNumeric = function (n) {
var num = false;
if(!isNaN(n) && isFinite(n)) {
num = true;
}
return num;
}
/**
* Compares two values and determines which one is "bigger".
*
* #param x A reference value to check against.
* #param y The value to be determined bigger or smaller than the reference.
* #return TRUE if y is greater or equal to x, FALSE otherwise
*/
function compare(x, y) {
var bigger = false;
x = x.cells[ndx].textContent;
y = y.cells[ndx].textContent;
//console.log(e.id);
if(isNumeric(x) && isNumeric(y)) {
if(y >= x) {
bigger = (e.id == "asc") ? true : false;
} else {
bigger = (e.id == "desc") ? true : false;
}
} else {
if(y.localeCompare(x) >= 0) {
bigger = (e.id == "asc") ? true : false;
} else {
bigger = (e.id == "desc") ? true : false;
}
}
return bigger;
}
/**
* Performs a quicksort O(n log n) on an array.
*
* #param array The array that needs sorting
* #return array The sorted array.
*/
function nlognSort(array) {
runs++
if(array.length > 1) {
var big = new Array();
var small = new Array();
var pivot = array.pop();
var l = array.length;
for(i = 0; i < l; i++) {
if(compare(pivot,array[i])) {
big.push(array[i]);
} else {
small.push(array[i]);
}
}
return Array.prototype.concat(nlognSort(small), pivot, nlognSort(big));
} else {
return array;
}
}
//Run sort routine
b = nlognSort(a);
//Rebuild <tbody> and replace new with the old
var tbody = document.createElement("tbody");
var l = b.length;
for(i = 0; i < l; i++) {
tbody.appendChild(b.shift());
}
parent.removeChild(document.getElementById("replace"));
parent.appendChild(tbody);
tbody.setAttribute("id","replace");
setTimeout(function () {
document.getElementById("progress").style.display = "none";
document.getElementById("progressText").style.display = "none";
clearTimeout(interval);
},1500);
}
window.onload = function() {
initHeaders(tableID);
}
Thanks again everyone!!
Take a look at the following:
http://jsfiddle.net/6JxQk/
The idea here is to replace your for loop with an asynchronous loop that uses setTimeout(), so you would go from the following:
for (var i = 0; i < rows; i++) {
// do stuff
}
... to this:
var i = 0;
(function doSort() {
// update progress
// do stuff
i++;
if (i < rows) {
setTimeout(doSort, 0);
}
})();
Although as you can see, this significantly slows down your sorting routine because in addition to updating the progress bar, this will reorder the rows of your table. With this in mind I think you are better off just using a built-in sort rather than your own implementation, and dropping the progress bar.
It may not be exactly what you are looking for - IMHO a progress bar must be used when you have an estimate of how much time a particular operation is going to take or how many bytes need to be transferred. In other non-deterministic cases you must be showing a spinner :-)
EDIT**
In a game I am creating I use the next question button to move onto other questions in the grid if the user is having trouble with the current one. At the moment I have had real problems with it as it keeps on crashing my program, and not giving any console errors. The last problem I had with it was that it said "too much recursion". Since then I thought I had sorted the problem, but I have just done a few tests and it crashes every time.
This is the click event for the button...
//Next question click event
$('.next-question').bind("click", function() {
$('td').removeClass('highlight-problem');
shuffleEqually(listOfWords);
shuffleEqually(nextWordIndexes);
var rndWord = nextWordIndexes[Math.floor(Math.random())];
var rndWord = nextWordIndexes[2];
//Adds and removes nesesary classes
$('td[data-word="' + listOfWords[rndWord].name + '"]').addClass('highlight-problem');
$('td[data-word=' + word + ']').removeClass('wrong-letter').removeClass('wrong-word').removeClass('right-letter');
var spellSpace = $('td[data-word="' + listOfWords[rndWord].name + '"]').hasClass('right-word');
if (spellSpace) {
$('.next-question').trigger('click');
} else {
$("#hintSound").attr('src', listOfWords[rndWord].audio);
hintSound.play();
$("#hintPic").attr('src', listOfWords[rndWord].pic);
$('#hintPicTitle').attr('title', listOfWords[rndWord].hint);
}
});
I think it may have something to do with the if statement, but have tried changing it to this..
if (spellSpace == false) {
$("#hintSound").attr('src', listOfWords[rndWord].audio);
hintSound.play();
$("#hintPic").attr('src', listOfWords[rndWord].pic);
$('#hintPicTitle').attr('title', listOfWords[rndWord].hint);
}
and it makes it even worse
ShuffleEqually:
//Shuffles words to randomize
shuffleEqually(nextWordIndexes);
var shuffledWords = [];
shuffledWords = chosenWords.sort(function () {
return 0.5 - Math.random();
});
function shuffleEqually(a1, a2) {
var arrays = [];
if (typeof a1 === 'object' && a1.length > 0) {
arrays.push(a1);
}
if (typeof a2 === 'object' && a2.length > 0) {
arrays.push(a2);
}
var minLength = arrays[0].length;
jQuery.each(arrays, function (i, a) {
minLength = a.length < minLength ? a.length : minLength;
});
var randoms = [];
for (i = 0; i < minLength; i++) {
randoms.push(Math.random());
}
jQuery.each(arrays, function (i, a) {
var i = minLength;
while (i--) {
var p = parseInt(randoms[i] * minLength);
var t = a[i];
a[i] = a[p];
a[p] = t;
}
});
};
Hint sound:
var hintSound = $("#hintSound")[0];
Your issue is an infinite loop, plain and simple.
$('.next-question').bind("click", function() {
// binds click...
...
if (spellSpace) {
$('.next-question').trigger('click');
// triggers click ON THE SAME ELEMENT COLLECTION (same selector)
You want to refine this. I assume you want the trigger to work on the next question, so I suggest changing the second statement to:
$(".next-question").eq(($(".next-question").index($(this)) + 1) % $(".next-question").length).trigger("click");
You have a second infinite loop in shuffleEqually:
jQuery.each(arrays, function (i, a) {
var i = minLength;
while (i--) {
var p = parseInt(randoms[i] * minLength);
var t = a[i];
a[i] = a[p];
a[p] = t;
}
Change the while condition to have a limiting value, or it will loop endlessly (as a decrement operation always succeeds).