I am trying to make a "Battleship" like game. Most of my board is supposed to be "missed" tiles, and only a few are supposed to be tiles with submarines on them for a "hit". The problem is that whenever I run my program, I cannot tell if it is ignoring my bool or if it isn't understanding what I coded, because everything I click is a "hit".
var cellClick=function(clicked)
{
var cell=clicked.target;
if(! cell.isEmpty) return;
if(cell.isSub=false)
{
cell.style.backgroundImage='url("missed.png")';
cell.style.backgroundSize='constrain';
}
else
{
cell.style.backgroundImage='url("hit.png")';
cell.style.backgroundSize='constrain';
cell.exploded=true;
}
cell.isEmpty=false;
console.log('click');
};
var waterCell=[[],[],[],[],[],[],[],[],[],[]];
for(var row=0;row<10;row++)
{
for(var column=0;column<10;column++)
{
waterCell[row][column]=document.createElement("div");
waterCell[row][column].style.width='10%';
waterCell[row][column].style.height='10%';
waterCell[row][column].style.position='absolute';
waterCell[row][column].style.left=(column*10)+'%';
waterCell[row][column].style.top=(row*10)+'%';
waterCell[row][column].style.backgroundImage='url("water_cell.jpg")';
waterCell[row][column].style.backgroundSize='contain';
gameBoard.appendChild(waterCell[row][column]);
waterCell[row][column].row=row;
waterCell[row][column].column=column;
waterCell[row][column].isEmpty=true;
waterCell[row][column].isSub=false;
waterCell[row][column].exploded=false;
}
}
//trying to make random subs
for(var i=0;i<5;i++)
{
row=Math.round(Math.random()*10);
column=Math.round(Math.random()*10);
waterCell[row][column].isSub=true;
}
gameBoard.addEventListener('click',cellClick,false);
Your code
if(cell.isSub=false) {
is assigning false to the property isSub of cell. The assignment's result is then tested by if, which is false. That's why the condition is never met and the else-branch is processed. Obviously, your intention was
if(cell.isSub==false) {
Maybe try swapping the branches of if statement and test for truthy value in cell.isSub instead making the code simpler:
if ( cell.isSub ) {
// it's a hit
} else {
// it's a miss
}
If you require to keep this order of branches testing for falsy values can be simplified as well by using negation operator:
if ( !cell.isSub ) {
// it's a miss
} else {
// it's a hit
}
One last tip: don't set custom properties of a DOM element as those perform poorly. Even worse, they are perfect soil for memory leakages when putting Javascript objects or functions. Thus, keep the two worlds - your Javascript and your HTML/DOM - as separated as possible and train yourself not to put additional data into Javascript-representations of objects managed in context of DOM. Use a separate description in pure Javascript (e.g. two-dimensional array) for tracking position of your subs and create a DOM representing that internal data set, only.
Related
So I know that Javascript Maps have a set amount of keys that they can store ( around 16.7 M ).
I was trying to test if I can ( in a very ugly way ) remove the oldest elements from the array. I noticed that no matter what I do it is actually not the Map size that was a limiting factor but it was rather the amount of operations I have done that were limiting me.
Below is an example code:
const map = new Map();
let i = 0;
while (true) {
i++;
set(i, i);
if (i % 1000 === 0)
console.log('INSERTED: ', i, 'KEYS', 'MAP SIZE :', map.size);
}
function set(key, value) {
if (map.size > 16770000) {
Array.from(map.keys()).slice(0, 10000).forEach(key => map.delete(key));
console.log('DELETED, current map size:', map.size);
}
try {
map.set(key, value);
} catch (e) {
console.log('MAP SIZE:', map.size, 'INSERTED:', key);
throw e;
}
}
When you run the snippet, just check your console. What you should notice is at the end ( when the exception is thrown ) you will get the Map Size and the INSERTED. Map Size will be a variable ( depending on how many elements you remove, which in this case is 10000) but INSERTED will always be the same value. So how come if I am not reaching the limit of the Map.... I am somehow reaching a limit. Is this some kind of reference issue that I am missing?
EDIT: As mentioned by #CRice if you increase the items deleted to around 10,000,000 then the cycle continues on seemingly forever.
EDIT 2: Here is an answer from one of the V8 devs talking about the limit of 16.7M keys: https://stackoverflow.com/a/54466812/5507414
EDIT 3: See answer: https://stackoverflow.com/a/63234302/5507414. We still need a V8 developer or someone with further knowledge in the engine to clarify this.
I adapted your script (see below) to see how many items had to be deleted before it could insert keys again in the Map.
The result is 8388608 (= 16777216/2) with node v12.18.1 (built on Chrome's V8 JavaScript engine).
It reminded me of a usual pattern where the underlying data structure doubles in size when it's almost full.
So I looked for the actual Map implementation in the V8 engine.
Here's what V8 development blog says about it:
ECMAScript 2015 introduced several new data structures such as Map, Set, WeakSet, and WeakMap, all of which use hash tables under the hood.
And here's an interesting comment in V8 source code:
HashTable is a subclass of FixedArray that implements a hash table
that uses open addressing and quadratic probing.
In order for the quadratic probing to work, elements that have not
yet been used and elements that have been deleted are
distinguished. Probing continues when deleted elements are
encountered and stops when unused elements are encountered.
- Elements with key == undefined have not been used yet.
- Elements with key == the_hole have been deleted.
Basically, when the script deletes a key, it seems that it's just marked as deleted. It becomes a "hole", as the V8 code comment puts it.
It's actually deleted only when the engine actually rebuilds the underlying data structure (that's what happens when the script deletes half of the elements).
Anyway, that's my understanding. We would need to delve into V8 code in order to clarify all the details.
Other interesting references:
Maximum number of entries in Node.js Map and FixedArray limit
Open addressing and Lazy deletion
map = new Map();
let i = 0;
while (true) {
i++;
try {
map.set(i, i);
} catch (e) {
console.log(e);
break;
}
if (i % 100000 === 0)
console.log('inserted: ', i);
}
console.log('max map size:', map.size, 'inserted:', i);
let j = 0;
while (true) {
j++;
map.delete(j);
if (j % 100000 === 0) {
console.log('deleted: ', j, 'map size: ', map.size);
if (map.size == 0) {
break;
}
}
try {
map.set(i, i);
} catch(e) {
continue;
}
break;
}
console.log('deleted before inserting again: ', j);
I dug into the ECMA language spec to take a look at Maps (Link). It seems that the behavior you are seeing is consistent with spec, and comes out of the spec'd definition for Map's delete prototype.
When a Map element is deleted with Map.prototype.delete(key), the spec only requires that the element with the matching key be set to empty.
Here's the definition copied and pasted from the ECMA spec:
3.1.3.3 Map.prototype.delete ( key )
The following steps are taken:
Let M be the this value.
Perform ? RequireInternalSlot(M, [[MapData]]).
Let entries be the List that is M.[[MapData]].
For each Record { [[Key]], [[Value]] } p that is an element of entries, do
a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then
i. Set p.[[Key]] to empty.
ii. Set p.[[Value]] to empty.
iii. Return true.
Return false.
The most important piece to us here is 4a.
When deleting an element, Map.prototype.delete checks each record p for an element where p.[[Key]] matches the provided key argument.
When found, p.[[Key]] and p.[[Value]] are both set to empty.
This means that, while the key and value are gone and are no longer stored or retrievable, the space, the element itself where the key and value were stored, may indeed be left in the Map's storage, and still takes up space behind the scenes.
While the specification contains the following note about its use of "empty"...
The value empty is used as a specification device to indicate that an entry has been deleted. Actual implementations may take other actions such as physically removing the entry from internal data structures.
...it's still leaving the door open for implementations to simply wipe the data without reclaiming the space, which is apparently what is occurring in your example here.
What about Map.prototype.set(key, value)?
In the case of set(), the function checks first for an existing element with a matching key to mutate the value of, and skips over all empty elements in the process. If none is found, then "Append p [<key, value>] as the last element of entries".
What, then, about Map.prototype.size?
In the case of size, the spec loops over all elements in the Map, and simply increments a counter for all non-empty elements it encounters.
I found this really interesting... If I had to hazard a guess, I suppose that the overhead of finding and removing empty elements is seen as unnecessary in most cases, since the quantities that must be reached to fill the structure are so large, ie. since maps hold so much. I wonder how large the time and space overhead of removing an empty element would be for a dataset large enough for it to be needed.
please check this i made some change in code now its working please let me know if it still not working
i accept this is not best way to do this, but reinitializing map
object will let us add some more data, but it's also slow down
operation speed,
please open console to see output
var map = new Map();
let i = 0;
var ke=[]
while (true) {
i++;
set(i, i,map.size);
if (i % 1000 === 0)
console.log('INSERTED: ', i, 'KEYS', 'MAP SIZE :', map.size);
}
function set(key, value,s) {
if (s >= 16730000) {
var arr= ke.slice(0, 10000)
ke.splice(0, 10000)
arr.forEach(key => map.delete(key));
console.log('DELETED, current map size:', map.size);
map= new Map(map);
arr=[]
}else{
try {
ke.push(key)
map.set(key, value);
} catch (e) {
console.log('MAP SIZE:', map.size, 'INSERTED:', key);
throw e;
}
}
}
I am using jasmine/protractor to run test suites against multiple sites that my company manages. (The sites are order form/checkout sites) Currently, I have a separate test suite set up for each site that uses functions I've created and stored inside of a helper to fill out each form so that I can just call those functions in my specs, and run through the order process to place a test order, this currently works and calls to the helpers go through without issue.
Protractor: v5.1.2
Jasmine: v2.6
Node: v8.0.0
The trouble here is that each site tends to use any number of field identifiers for each field in a given form, and I have ended up writing a specific function for a specific form on a particular site. With each site using anywhere between 2 and 4 forms, the end result is hundreds of the same functions being repeated with only difference in the selector. So I am trying to refactor my code so that I can just have one function for each form used on every site.
Enter my issue: I am having trouble figuring out how to make my tests check the value of the elements loading on the page against a list of possible elements. basically what I need it to do is this:
Open page
check element(by.name('someName') against the list I have
once a match is found, use the match as the value for someName
sendKeys('someValue') to that value
repeat for all fields on the form
What I have:
My Spec:
This is the actual spec in my test file.
it('Checks for fieldName and then fills in the form', function(done) {
cQualify();
done();
});
cQualify function:
The function is stored in a helper called formFill.js.
cQualify = function() {
findElementByFieldName(cartData.fName).sendKeys('Jimmy');
findElementByFieldName(cartData.cGender).sendKeys('Male');
findElementByFieldName(cartData.cAge).sendKeys('34');
findElementByFieldName(cartData.cZip).sendKeys('33071');
//more fields here and a submit button
};
findElementByFieldName function:
This function is stored in a helper called arrayLoop.js and is my latest attempt to make this work. Originally this was more along the lines of:
browser.driver.findElement(by.name('someName')).sendKeys('nameToSend');
//repeated for each field on the form
Here is the function:
findElementByFieldName = function(fieldName) {
if (fieldName.constructor === Array) {
console.log('Array found, looping values for array: ' + fieldName);
for(var i=0; i < fieldName.length; i++) {
expect(element(by.name(fieldName[i])).isDisplayed()).toBe(true);
console.log('Checking if page element ' + fieldName[i] + ' exists');
}
//some code that checks if the current value of fieldName[i] is in the page DOM
//and if so; returns that and exits the loop
} else {
return browser.driver.findElement(by.name(fieldName));
}
}
List of possible elements:
The possible elements are stored inside of a helper called formData.js (Note: only those with multiple possible values are in an array; the others I am not having trouble with)
cartData = {
fName: ['cShipFname', 'zang_fname', 'fname'],
lName: ['cShipLname', 'zang_lname', 'lname'],
cZip: ['cShipZip', 'zang_zip', 'zip'],
cGender: 'zang_gender',
cAge: 'zang_age',
cProblem: 'zang_problem'
//lots of additional values here
};
Result:
When I run this as is, the test loops through all values contained within cartData.fName, considers them all displayed, and then fails when trying to sendKeys with:
Failed: Cannot read property 'sendKeys' of undefined
So here is where I am stuck. Not only do I need the loop to check if the value from the array is on the page, I need it to stop looping once a match is found and return that so that I can use it the way it's laid out in the cQualify() function. I've tried a few different things such as using isDisplayed() inside of an if, but it seems this can only be used with expect. I've also tried putting my spec inside of a function and then looping that function directly in the test- but this had similar results and would also defeat the purpose of formFill.js
Update:
I found another question on SO that handles something similar: here
The code from the accepted answer is:
var link = element.all(by.css('a')).reduce(function (result, elem, index) {
if(result) return result;
return elem.getText().then(function(text){
if(text === "mylink") return elem;
});
}).then(function(result){
if(!result) throw new Error("Element not found");
return result;
});
Though I am not able to figure out (so far) how I might adapt it to suit my needs.
Instead of making it more complex, you can simply construct a dynamic XPath expression with multiple or conditions. Look at below example code.
function getElement(nameList) {
if(nameList.constructor != Array){
nameList=[nameList]
}
var xpathExpression = ".//*["
nameList.forEach(function(name,index){
xpathExpression += "#name='"+name+"'";
if(index != nameList.length-1){
xpathExpression+=" or ";
} else {
xpathExpression+= "]";
}
});
return element(by.xpath(xpathExpression));
}
So if you want to find the element for fName: ['cShipFname', 'zang_fname', 'fname'], you can simply call getElement and it will return you the web element based on the matched XPath expression.The XPath expression for fname is,
.//*[#name='cShipFname' or #name='zang_fname' or #name='fname']
Though it's answered, I'm not really a fan of XPath. Therefore I'd like to provide an alternative approach using filter() and indexOf() or includes().
First with using indexOf()
function getElement(nameList) {
//keeping it an array for all calls (thumbs up for this thought of #SudharsanSevaraj)
if(nameList.constructor != Array){
nameList=[nameList]
}
//selecting all elements with a name-attribute
//then filter the one(s), that is/are present in nameList and use first
return $$('[name]').filter(function(elem, index){
return elem.getAttribute('name').then(function(elName){
//return the element, if its name-attribute is present in nameList
return nameList.indexOf('elName') !== -1
});
}).first();
};
And the same slightly different, using includes() this time.
function getElement(nameList) {
//keeping it an array for all calls (thumbs up for this thought of #SudharsanSevaraj)
if(nameList.constructor != Array){
nameList=[nameList]
}
//selecting all elements with a name-attribute,
//then filter the one(s), that is/are present in nameList and use first
return $$('[name]').filter(function(elem, index){
return elem.getAttribute('name').then(function(elName){
return nameList.includes(elName);
});
}).first();
};
I know there are several issues with JavaScript Array.push() method posted here, but I can't get the following example to work:
First of all, i got a global Array:
var results = new Array();
for (var i = 0; i < X.length; i++) {
results[i] = new Array();
}
Now this array should be filled on a button-event. All I want is to fill the Array with new Arrays and push some data to them. The following code should check if results[x][y] already is an Array (and create one if it's not) and push data to it.
So, in the end, there should be an Array (result) that contains X.length Arrays filled with an unknown number of new Arrays, each of them containing an unknown number of data:
function pushResult(result) {
if (typeof results[currentStim][currentDist] == 'undefined') {
results[currentStim][currentDist] = new Array();
}
results[currentStim][currentDist].push(result);
}
Problem is: It just doesn't work. I made sure that currentStim is never out of bounds, I made sure that the "if"-Statement is only accessed when needed (so the Array isn't overwritten with a new one) and I watched the return-value of push(), always throwring back a number representing the new array length. As expected, this number increases evertime a value is pushed to an Array.
However, when I finally call:
document.getElementById('results').value = JSON.stringify(results);
to pass results to my PHP-script, something like this will be passed:
[[],[[1]],[],[]]
push()was called MUCH more often than once (at least "1" is one of the results I wanted to be stored) and, as described, always returned an increasing arrayLength. How does that work? What happened to my data?
I testet this on Chrome as well as on Firefox, same result. It might be interesting that a seperate loop draws to a Canvas the same time, but that shouldn't interupt Array-Handling and onKey-Events, right?
Hope u can help me,
MA3o
EDIT:
pushResult is called like this:
// Handles Space-Events
function pushed(event) {
if (event.which == 32 && stimAlive) {
pushResult(1);
hitCurrent = true;
}
Where hitCurrent and stimAlive are just flags set somewhere else. Some code further the function pushedis registered as an event listener:
document.onkeydown = function(event) { pushed(event)}
All the functions are called correctly. Adding console.log(results) to every loop just shows the right Array, as far as I can see.
According to the comments, the problem might be that "currentDist" can be a float value.
Originally i had a remove function like this:
function ViewWorkflowDetail(btn, workflowId) {
$("#workflowDetailPanel").remove();
if (document.getElementById("workflowDetailPanel") == null) {
// Do something usefull here
}
}
Which worked brilliantly. Yet (in the spirit of using as much JQuery as possible) I changed it to:
function ViewWorkflowDetail(btn, workflowId) {
$("#workflowDetailPanel").remove();
if ($("#workflowDetailPanel") == null) {
// Do something usefull here
}
}
But right now $("#workflowDetailPanel") is never null anymore. If i change it back again (to document.getElementById), then there is no problem anymore. Why does the second option keeps on finding that div? Are the JQuery objects somehow maintained in some sort of cache?
Note: Exactly the same setup/data were used to test both cases.
It will never be null, since jQuery returns an empty array if the element does not exist, you must check the length of the array
if ($("#workflowDetailPanel").length > 0) {
// Do something usefull here
}
I have a program that we use at my work, which outputs its data in to XML files (several of them). I am trying to develop an HTA (yes an HTA, i'm sorry) to read these files and process their data. Unfortunately there are a number of XML files and I only need to read a few specific ones, so I am trying to write a generic "XML to array" function.
I got it to read the XML file and now I want to process the file into a 2d Array. However, since I am using a recursive function I seem to lose data. Here is the function:
NodesToArray = function (xmlDOC){
//Must redeclare "i" with each recursion, or it won't work correctly. ie: for(VAR i = 0...
for(var i = 0; i < xmlDOC.length ; i++){
//Just because it has a child still do the check.
if(xmlDOC[i].childNodes.length > 1){
//Recursively run the function.
var ReturnArray = NodesToArray(xmlDOC[i].childNodes);
//alert(ReturnArray + " " );
if(ReturnArray) return ReturnArray;
}else{
//Check to see if the node has a child node, if not and a child node is called, it will error out and stop
if(xmlDOC[i].hasChildNodes() == true){
return xmlDOC[i].firstChild.nodeValue;
}
}
}
}
Where I return the first child value I put an alert and was able to see all the data I wanted. Of course when I set it up I found it wasn't keeping the data. I've done a ton of reading and have been pounding my head against my desk and still can't come up with anything.
I've googled, searched this site, and consulted many forums, and can't find anything that would work for me. I post here reluctantly as I am at a dead end. Thanks for any help and I will provide any additional information as I can.
Just a note, but I would like to be able to do this without any libraries (specifically jQuery). The HTA doesn't seem to support a lot of newer Javascript. I'm not a professional coder by any means and learn by doing everything from scratch.
Not sure how to set the solution, but I found it
function NodesToArray(xmlDOC, returnArray){
for(var i = 0; i < xmlDOC.length ; i++){
if(xmlDOC[i].childNodes.length > 1){
returnArray[returnArray.length] = NodesToArray(xmlDOC[i].childNodes, []);
}else{
if(xmlDOC[i].hasChildNodes() == true){
returnArray[returnArray.length] = (xmlDOC[i].firstChild.nodeValue);
}
}
}
return returnArray;
}
getArray = NodesToArray(getXML.getElementsByTagName(tagName)[0].childNodes,[]);
Thanks for the help!
The general way of retrieving data recursively in the same container is to write two function:
First one is the one that you call and which returns the array
Second one is called by first function and does the recursion. To be able to put the data in the same array that function has to take it as parameter.
Here is some pseudo code
getData(node) {
_2D_array = new array[][];
getData(node, _2D_array, 0);
return array;
}
getData(node, _2D_array, depth) {
if(node) { // end of recursion ?
_2D_array[depth].add(...); // populate from node
getData(node.next, _2D_array, depth++);
}
}
Your program exits when the first element is processed because the function returns. A function can only return once. You need to move the return statements outside the loop so that the loop completes.