Related
I have an algorithm that will modify a 10x10 2D array for a BattleShip game to randomly place ships down in a random direction.
The board looks like this:
var board= [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // 0
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // 1
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // 2
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // 3
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // 4
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // 5
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // 6
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // 7
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // 8
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // 9
];
The algorithm will turn the 0's into 1's and place a random ship at a random index, and at a random direction. The array is like this: var array = [5, 4, 3, 3, 2] (The numbers represent the length e.g. five 1's, four 1's, etc.) The algorithm works good most times and will run until the array is empty.
The problem is, I keep occasionally running into Uncaught TypeError: Cannot read property of <some integer> undefined ONLY when trying to place the ship in either the UP or DOWN direction as it reaches out of bounds. The error occurs for the if statements that check if the direction chosen is out of bounds like so:
// check out of bounds for UP direction
if (x - 1 < 0 || array[x - i][y] == undefined) {
break;
}
// check out of bounds for DOWN direction
if (x + 1 > 9 || array[x + i][y] == undefined) {
break;
}
I believe the error is occurring with something to do with the negative index from trying to perform array[x - i][y] and array[x + i][y]. I thought I already fixed this by adding my additional OR check with x - 1 < 0 and x + 1 > 9 but still I am running into this exception.
Per the comments, the problem is there is no bounds checking on array[x + i] and array[x - i]. Because of that, the calls to array[x +/- i][y] result in a cannot read property ... of undefined error.
A 5 by 5 pixel image data is something like this in linearized imagedata array-
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 255, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
So, the 3x3 pixel data is- 0 0 0 255. How can I get the adjacent pixel positions? Left and right adjacent ones are easy, just minus 4 and plus 4 respectively.
Accessing pixel data
The pixel data from .getImageData().data is a TypedArray of type Uint8ClampedArray. When reading the values they will be in the range 0-255 and in the order Red, Green, Blue, Alpha. If the value of alpha is zero then red, green, and blue will also be zero.
To get the index of a pixel
const imageData = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height);
var index = (x + y * imageData.width) * 4;
const red = imageData.data[index];
const green = imageData.data[index + 1];
const blue = imageData.data[index + 2];
const alpha = imageData.data[index + 3];
To move down one pixel
index += imageData.width * 4;
To move up one
index -= imageData.width * 4;
To move left.
index -= 4;
To move right
index += 4;
If you are on the left or right edge and you move in the direction of the edge you will wrap around, on the line above and to the right if moving left and the line below and on the left if moving down.
When setting the image data the values will be floored and clamped to 0-255
imageData.data[index] = 29.5
console.log(imageData.data[index]); // 29
imageData.data[index] = -283
console.log(imageData.data[index]); // 0
imageData.data[index] = 283
console.log(imageData.data[index]); // 255
If you set an index that is outside the array size it will be ignored
imageData.data[-100] = 255;
console.log(imageData.data[-100]); // Undefined
imageData.data[imageData.data.length + 4] = 255;
console.log(imageData.data[imageData.data.length + 4]); // Undefined
You can speed up access and processing by using different array types. For example all of a pixel's channels as one value using Uint32Array
const imageData = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height);
const pixels = new Uint32Array(imageData.data.buffer);
var index32 = x + y * imageData.width; // note there is no 4*;
const pixel = pixels[index32];
The channels are stored in bits 31-24 Alpha, 23-16 Blue, 15-8 Green, 7-0 Red.
You can set a pixel using a hex value
pixels[x + y * imageData.width] = 0xFF0000FF; // red
pixels[x + y * imageData.width] = 0xFF00FF00; // Green
pixels[x + y * imageData.width] = 0xFFFF0000; // Blue
pixels[x + y * imageData.width] = 0xFF000000; // Black
pixels[x + y * imageData.width] = 0xFFFFFFFF; // White
pixels[x + y * imageData.width] = 0; // Transparent
You can set all the pixels in a single call
pixels.fill(0xFF000000); // all pixels black
You can copy array data onto the array with
// set 3 pixels in a row at x,y Red, Yellow, White
pixels.set([0xFF0000FF,0xFF00FFFF,0xFFFFFFFF], x+y * imageData.width);
Warning
If the canvas has any pixel/s that are from an untrusted source it will be tainted and you will not be able to read the pixel data. Trusted sources are same domain or images served with the appropriate CORS header information. Images that are on the file system can not have their pixels accessed. Once a canvas is tainted it can not be cleaned.
A tainted canvas will throw an error when you call ctx.getImageData(0,0,1,1,) MDN does not list this exception for some reason. You will see "SecurityError" DOMException; in the DevTools console and there are plenty of answered question here in StackOverflow on the subject.
You could calculate the index with the width of the matrix and the length of one unit of 4.
The access is zero based.
function getPos(array, x, y, width) {
var p = 4 * (x + y * width);
return array.slice(p, p + 4);
}
var array = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 255, 0, 0, 0, 240, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 241, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
// element above
console.log(JSON.stringify(getPos(array, 2, 1, 5))); // [0, 0, 0, 240]
// actual element
console.log(JSON.stringify(getPos(array, 2, 2, 5))); // [0, 0, 0, 255]
// element below
console.log(JSON.stringify(getPos(array, 2, 3, 5))); // [0, 0, 0, 241]
I'm writing a script that will generate a square room within a larger map. The code looks like this:
var mapSize = 10;
var map = [];
for (var x = 0; x < mapSize; x++) {
map[x] = [];
for (var y = 0; y < mapSize; y++) {
map[x][y] = 0
};
};
//Make square room within map
var roomSize = 3;
var roomType = "Kitchen"
var paintRoom = function(mapX, mapY) {
for (var j = 0; j < roomSize; j++) {
map[mapX + j][mapY] = roomType;
map[mapX][mapY + j] = roomType;
};
};
paintRoom(3, 4);
console.log(map);
The result that I want is this:
[
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, "Kitchen", "Kitchen", "Kitchen", 0, 0, 0],
[0, 0, 0, 0, "Kitchen", "Kitchen", "Kitchen", 0, 0, 0],
[0, 0, 0, 0, "Kitchen", "Kitchen", "Kitchen", 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
But instead I end up with this:
[
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, "Kitchen", "Kitchen", "Kitchen", 0, 0, 0],
[0, 0, 0, 0, "Kitchen", 0, 0, 0, 0, 0],
[0, 0, 0, 0, "Kitchen", 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
What am I missing here? I'm just starting out, and would like to figure out as much as possible on my own, so if anyone could give me a hint instead of answering outright, I would really appreciate it.
It is doing what you've asked, if you follow the code carefully. It just needs an extra loop. Since what you're doing is 2 dimensional then you'll need 2 loops as well...
var paintRoom = function(mapX, mapY) {
for (var x = 0; x < roomSize; x++) {
for (var y = 0; y < roomSize; y++) {
map[mapX + x][mapY + y] = roomType;
}
};
};
you a missing 1 loop:
var mapSize = 10;
var map = [];
for (var x = 0; x < mapSize; x++) {
map[x] = [];
for (var y = 0; y < mapSize; y++) {
map[x][y] = 0
};
};
//Make square room within map
var roomSize = 3;
var roomType = "Kitchen"
var paintRoom = function(mapX, mapY) {
for (var j = 0; j < roomSize; j++) {
for (var k = 0; k < roomSize; k++) {
map[mapX + j][mapY + k] = roomType;
}
};
};
paintRoom(3, 4);
console.log(map);
https://jsfiddle.net/nfnpesmd/
I'm trying to fill a jqPlot Chart with percentage values for a stacked bar chart. I get the data via mysql and COUNT. For example, if I have 4 categories and 12 months, I am able to produce:
var s1 = [0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, ];
var s2 = [0, 5, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, ];
var s3 = [0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
var s4 = [0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, ];
Which produces a stacked bar chart with numbers, every number of each variable is a months value.
Now I want to show a stacked bar chart where the values of each month are percentages. I must somehow be able to make a percentage calculation with the values of the array. For example: add all values from position two (February) (100/(2+5+3+3)) and then multiply with positon two.
I am nowhere near a solution.
EDIT: Well, thanks for the fast answers.
I will try to explain better. I get the data from the 'MySQL' query, then to a PHP array, then convert it to a string to paste it to the JavaScript for plotting:
Try a for loop to calculate the percent you wanted to get and put it inside an array again
$(document).ready(function(){
var s1 = [0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, ];
var s2 = [0, 5, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, ];
var s3 = [0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
var s4 = [0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, ];
var s5 = new Array();
for (var i = 0; i < s1.length; i++){
//Or use your own formula on getting the value you wanted.
s5.push(100/(s1[i]+s2[i]+s3[i]+s4[i]);
}
// Can specify a custom tick Array.
// Ticks should match up one for each y value (category) in the series.
var ticks = ['January','February','March','April','May','June','July','August','September','October','November','December'];
var plot1 = $.jqplot('chart1', [s1, s2, s3, s4, s5], {
// The "seriesDefaults" option is an options object that will
// be applied to all series in the chart.
seriesDefaults:{
renderer:$.jqplot.BarRenderer,
rendererOptions: {fillToZero: true}
},
// Custom labels for the series are specified with the "label"
// option on the series option. Here a series option object
// is specified for each series.
series:[
{label:'Hotel'},
{label:'Event Regristration'},
{label:'Airfare'}
],
// Show the legend and put it outside the grid, but inside the
// plot container, shrinking the grid to accomodate the legend.
// A value of "outside" would not shrink the grid and allow
// the legend to overflow the container.
legend: {
show: true,
placement: 'outsideGrid'
},
axes: {
// Use a category axis on the x axis and use our custom ticks.
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
ticks: ticks
},
// Pad the y axis just a little so bars can get close to, but
// not touch, the grid boundaries. 1.2 is the default padding.
yaxis: {
pad: 1.05,
tickOptions: {formatString: '$%d'}
}
}
});
});
<script>
/* are there to be more than 4 arrays? */
var s1 = [0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, ];
var s2 = [0, 5, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, ];
var s3 = [0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
var s4 = [0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, ];
/* add new arrays to this array */
var arrs=[s1,s2,s3,s4];
var pc=[];
for( var i=0; i < arrs[0].length; i++ ){
var a=0;
for( var j=0; j < arrs.length; j++ ){
if( !isNaN( arrs[ j ][ i ] ) ) a+=arrs[ j ][ i ];
}
console.log( 'i=%d, j=%d, a=%d', i, j, a );
/* confused by the `then multiply with positon two` */
pc.push( a > 0 ? Math.round( 100 / a ) : 0 );
}
alert( pc );
</script>
If I understood correctly, it will work in your case :
function getPercent(array){
var return_array = [];
var total_sum = 0; // total sum of data
for(var i = 0; i < array.length; i++){
total_sum += array[i].reduce(function(pv, cv) { return pv + cv; }, 0);
}
for( var i=0; i < array[0].length; i++ ){
var sum = 0; // month sum
for( var j=0; j < array.length; j++ ){
sum += array[j][i];
}
return_array[i] = sum*100/total_sum; // percent calculation of the month
}
return return_array;
}
var s1 = [0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, ];
var s2 = [0, 5, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, ];
var s3 = [0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
var s4 = [0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, ];
var data=[s1,s2,s3,s4];
var percent = getPercent(data);
console.log(percent);
And results are :
[0, 61.904761904761905, 4.761904761904762, 4.761904761904762, 19.047619047619047, 9.523809523809524, 0, 0, 0, 0, 0, 0]
I'm very much an amateur enthusiast trying to make a basic 2d game map with html canvas. I've done this before by using arrays to create div/img tags and position them. I'm now trying to do this with canvas, not with images but simply drawing squares with fillRect().
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var map =
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 1, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 1, 0, 0]
[1, 1, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0];
window.addEventListener("load", function()
{
update();
}, false);
function update()
{
window.requestAnimationFrame(update, canvas);
render();
}
function render()
{
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var row = 0; row < map.length; row++)
{
for(var column = 0; column < map[0].length; column++)
{
switch(map[row][column])
{
case 0:
ctx.fillStyle = #ffffff;
ctx.fillRect
(
row * 64, column * 64, 64, 64
);
break;
case 1:
ctx.fillStyle = #009900;
ctx.fillRect
(
row * 64, column * 64, 64, 64
);
break;
}
}
}
}
I'm getting the error: 'Uncaught SyntaxError: Unexpected token ILLEGAL' on line 46 which is 'ctx.fillStyle = #ffffff;'.
I'm a bit stuck as to why it's giving this error, I'm wondering if you can't use the context methods in this way with an array but I can't understand why.
If anyone has any advice, I would be very grateful.