Game of Battleships - Javascript guidance - javascript

I am in a little need of guidance for a game of Battleships written purely in javascript.
My game consists of pre-made grid of 7 table rows with 7 table datas (7x7) which is built up like this:
[00][01][02][03][04][05][06]
[10][11][12][13][14][15][16]
[20][21][22][23][24][25][26]
[30][31][32][33][34][35][36]
[40][41][42][43][44][45][46]
[50][51][52][53][54][55][56]
[60][61][62][63][64][65][66]
I have only one ship at the moment which goes somewhere on the map randomly and it set horizontally on 3 fields. The code to make that happen goes like this:
var location1 = Math.floor(Math.random() * 48);
var location2 = location1+1
var location3 = location2+1
I then have a code so I have to click each tile to find where the ship is and I get a message if it's a hit/miss.
My questions is, I have to have 3 ships put on the map horizontally/vertically and I need some guidance in what would be the best way to do so. I'm sure I could create few more variables called location 4,5,6 and so on but I think here would be best to put the 3 ships in an array.
Could you provide me with some help what would be the best approach to wrap 3 ships in simpler code?

You can use objects to keep everything neatly organized. You can also create a mtheod to place the ship on the field inside that object. There is way more you can do to improve your code, but take this as a start:
// direction: true = hor, false = vert - nicer solutions than "magic numbers" are possible
function Ship (size, direction) {
this.coveredFields = [];
this.place = function (sizeY, sizeX) { // sizeX & sizeY: size of fields in both dimensions
// pick randomly within our limits
var locationX;
var locationY;
if (direction) {
locationX = Math.floor(Math.random() * (sizeX - 1 - size));
locationY = Math.floor(Math.random() * (sizeY - 1));
} else {
locationX = Math.floor(Math.random() * (sizeX - 1));
locationY = Math.floor(Math.random() * (sizeY - 1 - size));
}
// TODO: check that we don't cross/overlap other ships
// ...
// setting locations
for (var i = 0 ; i < size ; i++) {
if (direction) {
this.coveredFields.push(locationY * 10 + locationX + i)
} else {
this.coveredFields.push((locationY + i) * 10 + locationX)
}
}
}
}
You can then create your 3-field long horizontal ship using var ship1 = new Ship(3,false); place it un your field using ship1.place(7,7); and access the fields that are covered using ship1.coveredFields (which is an array that contains all fields)
If this is too complex for you, just use an array for each chip's locations (var ship1 = [location1, location2, ... ]) - but OOP is most likely the nicest way to do this.
Another recommendation: describe your field by a two-dimensional array (that is, an array that contains arrays). then, store your ships inside that array, instead of the coordinates inside your ships. This also makes placing without overlapping easier, and checking for hit or miss.
If you don't want to do that because it's above your current skill level, you should consider to store at least each dimension separately instead of generating one number out of both. You can use either arrays with two elements each for each point (first value being, x, second y), or objects that look like this: {x : 5, y : 3}

Related

(how) can I tell my JavaScript to append a variable/column to the data Qualtrics exports?

I've implemented a task in Qualtrics that randomly selects a number between 0 and 3, and then selects a corresponding word pool to sample 5 words from. To be able to analyze these data, though, I need to know which 5 words (or at minimum, the index number or name of the word pool being sampled from) is presented to each respondent. Is there a way to implement the recording of this information within JavaScript? Ideally this information would show up when I use Qualtrics' native "export" options, but if I have to somehow create a second spreadsheet with this treatment data, that works just fine as well.
Qualtrics.SurveyEngine.addOnload(function()
{
// first, create four arrays for the four word pools used in task
var wordpool1 = []
var wordpool2 = []
var wordpool3 = []
var wordpool4 = []
// assemble word list arrays into one array, with index 0-3
let masterwordlist = [wordpool1, wordpool2, wordpool3, wordpool4]
// function that randomly chooses an integer between x and y
function randomInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// function that shuffles (randomizes) a word list array (Fisher-Yates shuffle )
function shuffle(target){
for (var i = target.length - 1; i > 0; i--){
var j = Math.floor(Math.random() * (i + 1));
var temp = target[i];
target[i] = target[j];
target[j] = temp;
}
return target;
}
// function that chooses 5 words from a shuffled word list array, returns those 5 words as array
function pickWords(target) {
var randomwords = shuffle(target)
return randomwords.slice(0, 5);
}
// top-level function
function genWords(masterlist){
var x = randomInteger(0, 3)
return pickWords(masterlist[x])
}
// actually running the function
randomwords = genWords(masterwordlist)
// save final output as embedded qualtrics data
Qualtrics.SurveyEngine.setEmbeddedData("randomwords", randomwords);
Is there a way I can have this code record (within Qualtrics or otherwise) which values var x or var randomwords take on?
EDIT: I found another answer on here which may be relevant. According to this answer, though, it looks like I have all the code needed to record my variable selection; do I simply need to set embedded data within the survey flow, as well?
See here: Is it possible to save a variable from javascript to the qualtrics dataset?
Yes, you need to define the embedded data field randomwords in the survey flow.

Property returns undefined even though the value exists...?

I have this object resources:
var resources = { //Handles resources of all kinds.
number: 100,
money: 23000000,
science: 1000,
popularity: {
amount: 0,
upgProd: 0 //Amount produced from upgrades.
}
};
This looks like a normal object.
However, I'm trying to display a certain quantity popularity. Every time I try to display it, I get a NaN instead of a number.
I try to return console.log(resources.popularity.upgProd); but I still end up getting an undefined. I have no clue why since I define the variable but I still get undefined...
No errors in the IDE or in the console, just undefined when I console.log().
EDIT: Here is some surrounding context... this is my update function, that updates every 1/7 second. ALSO, the first value of resources.popularity.upgProd is 0, then the next become NaN.
function update() {
buffer++;
if (buffer == 35) {
checkVisibilityOnBuildings();
checkVisiblityOnUpgrades();
checkVisibilityOnResources();
buffer = 0;
} // Every 5 seconds (35 ticks) visibility will be checked and updated.
/* Number increasing. A bit tedious but no other way to do it yet. */
resources.number +=
((BUILDINGS[0].numProdBase * BUILDINGS[0].count) + //Now it's easy! Just do UPGRADES[n+1] for the next building.
(BUILDINGS[1].numProdBase * BUILDINGS[1].count) +
(BUILDINGS[2].numProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].numProdBase * BUILDINGS[3].count));
//Science gained per tick. Var used to make the "scienceProductionTotalDisp" work properly
var scienceTotalPerTick =
(BUILDINGS[2].sciProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].sciProdBase * BUILDINGS[3].count);
resources.science += scienceTotalPerTick;
//Display vars for html so that rounding errors don't happen.
var numDisp = Math.floor(resources.number);
var sciDisp = Math.floor(resources.science * 100) / 100;
var popDisp = Math.floor(resources.popularity.amount * 100) / 100;
console.log(Number(resources.popularity.upgProd));
var moneyTotalPerTick = Math.pow(resources.number, (1/(player.moneyRatio))) + 1; //Cash flow per 143ms (7n for total / sec ish)
var popularityTotalPerTick = (Number(resources.popularity.upgProd)) + 0;
resources.popularity += popularityTotalPerTick;
console.log(resources.popularity.upgProd);
resources.money += moneyTotalPerTick;
getId('moneyProductionTotalDisp').innerHTML = numFormat(Math.floor(moneyTotalPerTick * 7));
getId('moneyDisp').innerHTML = numFormat(Math.round(resources.money * 100) / 100);
getId('numberDisp').innerHTML = numFormat(numDisp);
getId('scienceDisp').innerHTML = numFormat(sciDisp);
getId('popularityDisp').innerHTML = numFormat(popDisp);
getId('scienceProductionTotalDisp').innerHTML =
numFormat(Math.floor(scienceTotalPerTick * 700) / 100);
getId('popularityProductionTotalDisp').innerHTML =
numFormat(Math.floor(popularityTotalPerTick * 700) / 100);
Thank you!
Here is your problem:
resources.popularity += popularityTotalPerTick;
popularity is an object, and that doesn't do what you want.
Since you overwrite it with the result of an object added by a value, you assign it s string [object Object]9 where the last digit is whatever was in popularityTotalPerTick.
You get NaN (Not a number) since you are using Number(x)in console.log(Number(resources.popularity.upgProd));. Why are you doing that?
Does getId do a lookup of the element in the dom every time your function is called? Have the object changed or are you querying the DOM for the same element 7 times per second?
Some thoughts about the other tings in your code:
resources.number +=
((BUILDINGS[0].numProdBase * BUILDINGS[0].count) +
(BUILDINGS[1].numProdBase * BUILDINGS[1].count) +
(BUILDINGS[2].numProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].numProdBase * BUILDINGS[3].count));
I'm assuming that BUILDINGS is an array with all the buildings, and that you want to calculate the number of all buildings in the array. There is a function for that: reduce that takes two parameters: a function and the start value:
resources.number += // do you really want += here and not just = ?
BUILDINGS.reduce( (sum, item) => sum + (item.numProdBase * item.count), 0 );
If your aren't familiar with arrow-functions it could be replaced with:
function (sum, item) { return sum + (item.numProdBase * item.count) }
var scienceTotalPerTick =
(BUILDINGS[2].sciProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].sciProdBase * BUILDINGS[3].count);
I'm not sure why you are only doing it for two buildings, and not for all, but you could use reduce here too, with slice
var scienceTotalPerTick =
BUILDINGS.slice(2,4).reduce( (sum, item) => sum + (item.sciProdBase * item.count), 0);
Notice that the parameters to slice is begin to end (end not included), therefor 2,4 gives you element 2 and 3.
With this part of the code...
getId('moneyProductionTotalDisp').innerHTML = numFormat(Math.floor(moneyTotalPerTick * 7));
getId('moneyDisp').innerHTML = numFormat(Math.round(resources.money * 100) / 100);
getId('numberDisp').innerHTML = numFormat(numDisp);
getId('scienceDisp').innerHTML = numFormat(sciDisp);
getId('popularityDisp').innerHTML = numFormat(popDisp);
I assume that getId is a function that fetches the element via document.getElementById or document.querySelector, and that you do this for every frame, and you get the same element every time. Now imagine that the element is a box, and that I demand that you get it from the warehouse that is on the other side of the world. When you deliver the box, I open the box, replace the contents and send it back to the warehouse on the other side of the world. When you come back, I demand that you get the same box again, and you now have to travel to the other side of the world once again... and when you come back the story repeats...
My point here is that it is very wasteful to travel to the other side of the world each time to get the same box, or to get the same element from the DOM at each update. You could cache the elements by looking them up ONE time before you start the update, and you can put the result in an object:
function getElementsToUpdate() {
return {
moneyProductionTotalDisp: getId('moneyProductionTotalDisp'),
moneyDisp: getId('moneyDisp'),
// and so on...
}
}
If you name the property in the object the same as the id, you can put all the names in an array, and then reduce it to an object. This saves you some typing, and hard to find bugs because of a misspelled name:
function getElementsToUpdate() {
return [
'moneyProductionTotalDisp',
'moneyDisp',
'numberDisp',
'scienceDisp',
'popularityDisp',
// and so on....
].reduce(
(out, id) => { out[id] = getId(id); return out; }, {}
)
}
NOTE: This function should be run ONE time at startup, not for every update of the frame. It returns an object with the elements.
Somewhere in your code I assume that you use setInterval to call your update function. Since setInteval can take extra parameters that will be given to the called function, you can do something like this:
var timerHandle = setInterval( update, 1000/7, getElementsToUpdate() );
where update is your function, 1000/7 gives you the interval for 7 times a second, and getElementsToUpdate is the function that makes the time-expensive call to get the elements from the DOM one time.
You need to change the update function to take a parameter (the name is not important, but should be short and descriptive, so I use elem). This is the object that getElementsToUpdate() have returned with all the html-elements.
function update(elem) {
// ... your code ....
// Old code, that makes an expensive lookup into the DOM, and start a HTML parser.
// getId('numberDisp').innerHTML = numFormat(numDisp);
// New code, that gets the pre-looked up element and set the text.
elem.numberDisp.textContent = numFormat(numDisp);
}
I'm not a fan of using .innerHTML when it isn't html that is inserted. Always use .textContent instead, if is isn't html. In this case it would be better if you use the output-element, and set the .value property.
try using console.log(resources["popularity"]["upgProd"])

Identifying edge cases of a one-dimensional array in Javascript

I'm creating a 2-dimensional heat map which has functionality when you click on any pixel. It grabs data associated with the index of every pixel (including adjacent pixels) and plots it. It currently looks like this:
The problem that I'm encountering is when I click on a left or right edge pixel, since it grabs data from adjacent pixels, it can retrieve data from the opposite side of the graph since it is all within a one-dimensional array. I am trying to create a conditional which checks if the clicked pixel is an edge case, and then configures the magnified graph accordingly to not show points from the other side of the main graph. This is the code I have so far:
// pushes all dataMagnified arrays left and right of i to magMainStore
var dataGrabber = function(indexGrabbed, arrayPushed) {
// iterates through all 5 pixels being selected
for (var b = -2; b <= 2; b++) {
var divValue = toString(i / cropLength + b);
// checks if selected index exists, and if it is not in the prior row, or if it is equal to zero
if (dataMagnified[indexGrabbed + b] != undefined && (& divValue.indexOf(".")!=-1)) {
dataMagnified[indexGrabbed + b].forEach(function(z) {
arrayPushed.push(z);
})
}
}
};
I am trying to get the same result as if I had a two dimensional array, and finding when the adjacent values within a single array is undefined. This is the line where I'm creating a conditional for that
if (dataMagnified[indexGrabbed + b] != undefined && (& divValue.indexOf(".")!=-1)) {
The second condition after the and is my attempts so far trying to figure this out. I'm unsure if I can even do this within a for loop that iterates 5 times or if I have to create multiple conditions for this. In addition, here's an image displaying what I'm trying to do:
Thank you!
Your approach looks overly complex and will perform rather slowly. For example, converting numbers to strings to be able to use .indexOf() to find a decimal point just for the sake of checking for integer numbers doesn't seem right.
A much simpler and more elegant solution might be the following function which will return the selection range bounded by the limits of the row:
function getBoundedSelection(indexGrabbed, selectionWidth) {
return dataMagnified.slice(
Math.max(Math.floor(indexGrabbed/cropLength) * cropLength, indexGrabbed - selectionWidth),
Math.min(rowStartIndex + cropLength, indexGrabbed + selectionWidth)
);
}
Here, to keep it as flexible as possible, selectionWidth determines the width of the selected range to either side of indexGrabbed. This would be 2 in your case.
As an explanation of what this does, I have broken it down:
function getBoundedSelection(indexGrabbed, selectionWidth) {
// Calculate the row indexGrabbed is on.
var row = Math.floor(indexGrabbed/cropLength);
// Determine the first index on that row.
var rowStartIndex = row * cropLength;
// Get the start index of the selection range or the start of the row,
// whatever is larger.
var selStartIndex = Math.max(rowStartIndex, indexGrabbed - selectionWidth);
// Determine the last index on that row
var rowEndIndex = rowStartIndex + cropLength;
// Get the end index of the selection range or the end of the row,
//whatever is smaller.
var selEndIndex = Math.min(rowEndIndex, indexGrabbed + selectionWidth);
// Return the slice bounded by the row's limits.
return dataMagnified.slice(selStartIndex, selEndIndex);
}
So I discovered that since the results of the clicked position would create a variable start and end position in the for loop, the only way to do this was as follows:
I started the same; all the code is nested in one function:
var dataGrabber = function(indexGrabbed, arrayPushed) {
I then create a second function that takes a start and end point as arguments, then passes them as the for loop starting point and ending condition:
var magnifyCondition = function (start, end) {
for (var b = start; b <= end; b++) {
if (dataMagnified[indexGrabbed + b] != undefined) {
dataMagnified[indexGrabbed + b].forEach(function (z) {
arrayPushed.push(z);
})
}
}
};
After that, I created 5 independent conditional statements since the start and end points can't be easily iterated through:
if (((indexGrabbed - 1) / cropLength).toString().indexOf(".") == -1) {
magnifyCondition(-1, 2);
}
else if ((indexGrabbed / cropLength).toString().indexOf(".") == -1) {
magnifyCondition(0, 2);
}
else if (((indexGrabbed + 1) / cropLength).toString().indexOf(".") == -1) {
magnifyCondition(-2, 0);
}
else if (((indexGrabbed + 2) / cropLength).toString().indexOf(".") == -1) {
magnifyCondition(-2, 1);
}
else {
magnifyCondition(-2, 2);
}
};
Lastly, I pass the index grabbed (i of the on clicked function) and an arbitrary array where the values get stored.
dataGrabber(i, magMainStore);
If there's a better way instead of the if statements, please let me know and I'd be happy to organize it better in the future!

Reversible functions in Javascript

I make a lot of choropleth maps for which I need a custom function that accepts values from the dataset and returns the corresponding colors. For example, data that is distributed logarithmically from 1 to 10,000 might require this function:
var colors = ["#ffffcc","#c2e699","#78c679","#31a354","#006837"];
function val_to_index(val) {
var index = Math.floor(Math.log(val) / Math.log(10));
return colors[index];
}
When automatically building out the text for the legend, meanwhile, I need the reverse function:
function index_to_val(index) {
return Math.pow(10, index);
}
var legend_labels = [0,1,2,3,4].map(index_to_val);
When it's something as simple as a log/exponent, it's no trouble to write both functions. But when it's more complex, it gets really tedious. For example:
// divisions of 50,100,500,1000,5000,etc
function val_to_index(v) {
var lg = Math.log(v) / Math.log(10);
var remainder = lg % 1 > (Math.log(5) / Math.log(10)) ? 1 : 0;
var index = return Math.floor(lg) * 2 - 3 + remainder;
return colors[index];
}
function index_to_val(index) {
index += 3;
return Math.pow(10, Math.floor(index/2)) * Math.pow(5, index%2);
}
In middle school algebra, we learned to invert functions automatically by reversing the x and y variables and solving for y (which, of course, was only possible for certain functions). My question is this: Is there an equivalent operation in computer science?
Automatically finding a function's inverse would require a computer algebra solver (CAS) library of some sort. If you always do the forward operation, first, though, there may be a simpler way; instead of discarding the original data, wrap the original value and the result of the computation together in an object. This way, computing the inverse merely requires retrieving the field containing the original value.

Google Maps API V3 - Showing progress along a route

I want to show a custom route on a route along with the current progress. I've overlayed the a Polyline to show the route and I am able to find the LatLng of the current postion and place a marker. What I want to do now is to highlight the traveled part of the Polyline using a different colour. I'm pretty sure it's not possible to have multiple colours for one Polyline so I plan to overlay a second Polyline over the first the first to give this effect. Here's my question:
Knowing the LatLng of the current position and armed with the array of LatLng points used to create the orginal Polyline how best can I create the second 'Progess' route?
Thanks ahead.
With a few assumptions:
Your path is quite dense (if not, you could interpolate intermediate points)
Your route doesn't overlap with itself (wouldn't work with a repeating circular path, say)
..one crude way would be as follows:
Using Python'y pseudo-code, say you have a route like this:
points = [LatLng(1, 1), LatLng(2, 2), LatLng(3, 3), LatLng(4, 4)]
You draw this as a PolyLine as usual.
Then, given your current position, you would find the nearest point on the route:
cur_pos = LatLng(3.1, 3.0123)
nearest_latlng = closest_point(points, to = cur_pos)
Then nearest_latlng would contain LatLng(3, 3)
Find nearest_latlng in the list, then simply draw a second polyline up to this point. In other words, you truncate the points list at the current LatLng:
progress_points = [LatLng(1, 1), LatLng(2, 2), LatLng(3, 3)]
..then draw that on the map
As mentioned, this will break if the path loops back on itself (closest_point would only ever find the first or last point)
If you know how far has been travelled, there is an epoly extension which gives a few methods that could be used, mainly:
.GetIndexAtDistance(metres)
Returns the vertex number at or after the specified distance along the path.
That index could be used instead of the closest_point calculated one above
Typescript example:
getRouteProgressPoints(directionResult: google.maps.DirectionsResult | undefined, targetDistance: number) {
if (!directionResult || targetDistance <= 0) return [];
const route = directionResult.routes[0];
const leg: google.maps.DirectionsLeg = route.legs[0];
const routePoints: google.maps.LatLng[] = [];
// collect all points
leg.steps.forEach((step) => {
step.path.forEach((stepPath) => {
routePoints.push(stepPath);
});
});
let dist = 0;
let olddist = 0;
const points = [];
// go throw all points until target
for (let i = 0; i < routePoints.length && dist < targetDistance; i++) {
const currentPoint = routePoints[i];
// add first point
if (!i) {
points.push(currentPoint);
continue;
}
const prevPoint = routePoints[i - 1];
olddist = dist;
// add distance between points
dist += google.maps.geometry.spherical.computeDistanceBetween(prevPoint, currentPoint);
if (dist > targetDistance) {
const m = (targetDistance - olddist) / (dist - olddist);
const targetPoint = new google.maps.LatLng(
prevPoint.lat() + (currentPoint.lat() - prevPoint.lat()) * m,
prevPoint.lng() + (currentPoint.lng() - prevPoint.lng()) * m,
);
points.push(targetPoint);
} else {
points.push(currentPoint);
}
}
return points;
}
That is going to be really difficult unless you already have a way of determining that the vehicle has passed certain points already. If you are aware that it has passed certain points you just create the second polyline with the passed points and the current marker as the end of the polyline. You will probably want to change the width of the polyline in order to make it viewable when it is overlaying the initial route.
All of that being said if you don't know if the points have been passed I am not sure what you can do other than write some sort of code that determines if your current position is past a certain set of points, but I don't know how you do that if a route zigzags all over the place possibly..... Now if your points go from straight west to straight east etc, than coding something will be easy, but I doubt that is the case.
Hope this helps somewhat....

Categories