How to compute all possible paths on a grid - javascript

I've recently seen a challenge picture on brillant.org's Instagram account:
The instructions:
The robot takes 4 random steps (can't go diagonal).
In which area is it most likely to land?
Obviously there are 44 = 256 possible paths for the robot.
I tried to write a program (Javascript) to solve that problem but my approaches didn't work.
Actually I don't have useful code to show here because I got stuck pretty early on.
So my question:
How would you write a program that:
Checks all 256 possible paths and
Tells me how many (%) landed in which area

This is a very cool question!
And thanks for letting me discover brillant.org's Instagram account.
So, I would proceed as following:
Write a function to calculate all possible permutation with repetition (n^k)
Generate a map where to execute all possible moves calculated in the step 1
Check the area where the robot would land on with the final step and store it
Calculate the percentage based on the counting in step 3
The first step is a problem by itself, and it's not part of this scope. You can use or adapt the code here: https://rosettacode.org/wiki/Permutations_with_repetitions
Then, to generate the map, I simply used an array:
const map = [
0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 2, 1, 1, 0, 0,
0, 1, 1, 2, 2, 2, 1, 1, 0,
1, 1, 3, 3, 2, 3, 3, 1, 1,
0, 1, 1, 3, 3, 3, 1, 1, 0,
0, 0, 1, 1, 3, 1, 1, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0,
];
This is a representation of the image you gave, each area is marked with a different number, that we will reuse later.
At this point I defined an array of the 4 possible moves:
const moves = [
-1, // left
1, // right,
-9, // top
9, // bottom
];
The values indicates the offset needed to move in the direction wrote in in the comment: left and right I guess are self explanatory. For the top and bottom, since we're using an array as "matrix", we basically need to translate the y value to a index value in the array. The formula is simple: index = x + y * width there fore it means if you want to specify a y to move up by one cell you have -1 * 9, and to move down is 1 * 9.
For the same reason the robot's starting position (at the center of the map) is calculate as follow: 4 + 4 * 9.
Now I calculate all the possible moves combination with the permutation function:
const allmoves = permutationsWithRepetition(4, moves);
And create an array to store the results:
let results = [0, 0, 0, 0];
After that, I just iterate all the possible moves array, and calculate the position at the end of the moves:
for (let j = 0; j < allmoves.length; j++) {
// set the robot's initial position at the center
// before executing any moves' list
let pos = 4 + 4 * 9;
// calculate the new position using the current moves
for (let i = 0; i < moves.length; i++) {
let move = allmoves[j][i];
pos += move;
}
// now `map[pos]` points to a number between 1 and 3
// that identify the area where the robot is.
// we use that number as index of the results
// to increment its value.
// Notice that it's impossible land to any 0 area
// if we start from the center and we can only perform
// 4 moves.
// We use that number as `index` for our `results`
results[map[pos]]++;
}
Now in results you will have how many times the robot ended up in which area:
console.log(results); // [0, 60, 100, 96]
As mentioned is impossible given the starting position and the number of moves for the robot to land in any of the 0 area, so the first index would have 0 as value.
You can see that it landed in the area 1 (the orange one) 60 times, in the area 2 100 times (the smallest area, the green / aqua one), and in the area 3, 96 times (the blue / purple one).
At this point you can calculate the percentage (times / total * 100) and display it with a proper formatting:
// we skip the first element of results since
// it's not an area (and we'll be always 0)
for (let k = 1; k < results.length; k++) {
console.log(k, `${(results[k] / allmoves.length * 100).toFixed(2)}%`)
}
And you'll get:
1 "23.44%"
2 "39.06%"
3 "37.50%"
You can also do an empiric check, and actually generate ten thousands of moves randomly and make the program apply those instead of allmoves, and you'll see that you end always with similar number (obviously, but that also the fun part of math, verify that is actually what you will expect!).
Here the a working code that also implement the permutation code mentioned at the beginning, from rosettacode.org, and the code explained in this post: https://codepen.io/zer0/pen/RwWPZmE
(You need to open the console to see the results)

I would create different objects representing the different possibilities like below:
function Path(x, y, movesLeft) {
this.x = x;
this.y = y;
this.movesLeft = movesLeft;
this.paths = [];
if (movesLeft > 0) {
this.paths.push(new Path(x - 1, y, movesLeft - 1));
this.paths.push(new Path(x + 1, y, movesLeft - 1));
this.paths.push(new Path(x, y - 1, movesLeft - 1));
this.paths.push(new Path(x, y + 1, movesLeft - 1));
}
this.getArray = function() {
if (this.movesLeft > 0) {
var output = [];
for (var i = 0; i < this.paths.length; i++) {
output = output.concat(this.paths[i].getArray());
}
return output;
}
return [this];
}
}
Now, you can create an object and test the results:
var endPosArray = new Path(0, 0, 4).getArray();
All you need to do is loop through the array and calculate the chances.

Related

2D array replacing values according conditions

I got a 10 x 10 array with the following values:
[[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, 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],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
In this 2D array, I have to randomly choose six values of 0, either horizontally or vertically (also random) and replace them with the value 6.
I've done this with the following code:
function tekenGrootstSchip(bord) {
rand = Math.floor((Math.random() * 10));
rand2 = Math.floor((Math.random() * 10));
directie = Math.floor((Math.random() * 2));
counter = 0;
if (directie == 0) {
for(y = rand2; y < 10; y++) {
if(counter < 6) {
bord[rand][y] = 6;
counter++;
}
}
for(y = rand2; y > 0; y--) {
if(counter < 6) {
bord[rand][y] = 6;
counter++;
}
}
} else {
for(x = rand; x < 10; x++) {
if(counter < 6) {
bord[x][rand2] = 6;
counter++;
}
}
for(x = rand; x > 0; x--) {
if(counter < 6) {
bord[x][rand2] = 6;
counter++;
}
}
}
}
After doing this for the value 6, I also have to do this for the value 4. The rules for value 4 are a bit different however. You can't place a 4 on a 6, neither can you place a 4 next to a 6. And the value 4 only takes four places (so 4x1, while a 6 is 6x1)
So if my randomly generated direction is horizontal, my bord[x-1][y], bord[x][y] and bord[x+1][y], with x and y initialized at the random value, with y going up to y+1, y+2, y+3, y+4 (4x1). All these values have to be checked against == 0, if true, replacing the zeroes with fours can be initialized otherwise not. If so, I have to generate a new [x][y] and check these conditions again until I can change four zeroes in my 2D array succesfully.
Although I have a general idea of implementing this, I would have a bug that, if for instance one null-value would be replaced with a four, but the one next to it can't be replaced since it's next to a != 0 value, I would be stuck with a "illegal" four.
If anyone could help me out in the right direction I would appreciate it.
feasibility
We can (trivially) show that whatever the 6 configuration chosen, we can always put the 4s.
Indeed, the maximal cover area of a 6 is 9 (a 3x3 square)
000
060
000
(we can't put a 4 in the border surrounding the 6).
So a bound for the maximal 6 configuration covered area is 6*9=54
000000xxxx
060060xxxx
000000xxxx
000000xxxx
060060xxxx
000000xxxx
000000xxxx
060060xxxx
000000xxxx
xxxxxxxxxx
and we can put as many 4 in the squares containing 'x' (which is way more than 4)
6-generation
Let's assume the board is indiced as follow:
0 1 2 3 ... 9
10 11 12 ... 19
...
90... 99
let x towards the bottom and y to the right
A square holding value id can be found at (x,y)=((id - id%10)/10, id%10) and reciprocally
id:(x,y)->x*10+y
So we will only consider numbers between 0 and 99 (since we can find back their associated position(x,y) in the grid)
I will use the copy-pasted below getRandomInt taken shamelessly from mdn
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
}
Finally we can draw between 0 and 99. If number already exists then discard it, otherwise take it
let indices = new Set
while(indices.size < 6){
let idx = getRandomInt(0,99)
if(!indices.has(idx)){
indices.add(idx)
}
}
return [...indices]
4-generation
We can apply the same stragy as before: draw a random number between 0 and 99 and discard it as long as it is not valid, until 4 valid positions are found
build the invalid set due to the 6 positionned
let invalids = new Set
indices.forEach(idx=>{
let [x,y] = [(idx - idx%10)/10, idx%10]
//add the adjacent squares
for(let i = -1; i<=1; ++i){
for(let j = -1; j<= 1; ++j){
if( 0<=x+i<10 && 0 <= y+j < 10){//if the cell in the board....
invalids.add( (x+i)*10 + y+j )
}
}
}
})
draw except from the invalid set
let out = []
while(out.length < 4){
let idx = getRandomInt(0,99)
if(!invalids.has(idx)){
invalids.add(idx)
out.push(idx)
}
}
return out
That approach may be not that efficient in worst case: we would have about 54% probability of drawing an invalid number!
We can thus consider an array of only the valid numbers, and draw from it
let valids = Array(100).fill(0).reduce((oks, x,i)=>{
if(invalids.has(i)) return oks
return oks.push(i),oks
},[])
//take numbers from that array
let out = []
for(let i = 0; i<4; ++i){
let idx = getRandomInt(0,valids.length)
//notice that here we take the elem from valids
//not just the idx from getRandomInt
out.push(valids[idx])
//and we takeout the element from valids
valids.splice(idx, 1);
}
return out

Issues with displaying a map in p5.js

I am currently attempting to display a map in processing using a 2d array.
Currently I have this down:
var start_map = [
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
[1, 1, 1, 1, 1]
];
function drawMap(map) {
for (var x = 0; x < 5; x++) {
for (var y = 0; y < 5; y++) {
if (map[x][y] == 0) {
fill(51, 153, 51);
rect((10 + 50*x), (10 + 50*y), 50, 50);
}
else if (map[x][y] == 1) {
fill(0, 102, 0);
rect((10 + 50*x), (10 + 50*y), 50, 50);
}
}
}
}
But, while I do get a map displayed, it appears to be rotated 90 degrees clockwise. What is causing this and how can I fix this?
Think about how the indexes of a 2D array work. Let's look at a simpler example:
var map = [
[1, 2],
[3, 4]
];
Where is map[0][1]? Where is map[1][0]?
A 2D array is an array of arrays. The first index selects the subarray at that index, and the second index selects the element in that subarray.
So in the simple example above, map[0] selects the subarray at index 0, which is [1, 2]. Then map[0][1] selects the element in that subarray at index 1, which is 2.
This might seem a bit surprising if you were treating the indexes as an x, y pair. In that case, you'd expect 0, 1 to give you 3, right? But it's not an x, y pair. It's an index into the outer array, then an index into the subarray.
In other words, it's actually a y, x pair. So to fix your problem, you can actually just swap the order of your indexes:
`map[y][x]`
Now you use the y value to select which subarray you want (which row you want), and the x value to select which element in that subarray you want (which column you want).

How can I add faces to an indexed THREE.BufferGeometry?

Say that I had generated a THREE.BufferGeometry from a THREE.Geometry named oldGeom like so:
// using WebGLRenderer
var geometry = new THREE.BufferGeometry();
var indices = new Uint16Array(oldGeom.vertices.length);
var vertices = new Float32Array(oldGeom.vertices.length * 3);
for (var i = 0; i < oldGeom.vertices.length; i++) {
indices[i] = i;
vertices[i * 3 + 0] = oldGeom.vertices[i].x;
vertices[i * 3 + 1] = oldGeom.vertices[i].y;
vertices[i * 3 + 2] = oldGeom.vertices[i].z;
}
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
Hopefully I have the indexing right. At this point, how could I add a face using the indices? I'm planning to loop through the faces of oldGeom to add them all here, but I can't find any documentation on this. Thanks!
Similar to this question, but with an indexed geometry.
From the documentation for BufferGeometry:
index (itemSize: 3)
Allows for vertices to be re-used across multiple triangles; this is called using "indexed triangles," and works much the same as it does in Geometry: each triangle is associated with the index of three vertices. This attribute therefore stores the index of each vertex for each triangular face. If this attribute is not set, the renderer assumes that each three contiguous positions represent a single triangle.
The way "indexed triangles" work is that "position" is an array of numbers, with every consecutive set of 3 numbers representing one vertex (x, y, z). "Index" is an array of numbers, where every consecutive set of 3 numbers represents one face, by referring to the indices of vertices in the "position" array.
You might have an array of vertices like this:
var vertices = [0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0];
You can think of this array as sets of XYZ coordinates like this:
var vertices = [
0, 0, 0, // vertex index 0
1, 0, 0, // vertex index 1
1, 1, 0, // vertex index 2
0, 1, 0 // vertex index 3
];
Now if you have an index array like this:
var indices = [0, 1, 2, 1, 2, 3];
It represents two triangles:
var indices = [
0, 1, 2, // face with vertices at indices 0, 1, 2
1, 2, 3 // face with vertices at indices 1, 2, 3
];
So triangle #1 has vertices at XYZ (0, 0, 0), (1, 0, 0), (1, 1, 0) while triangle #2 has vertices at XYZ (1, 0, 0), (1, 1, 0), (0, 1, 0).
On the other hand you can define vertices without using an index. The power of indexing is that it lets you reuse vertices defined in the array instead of listing them redundantly every time they appear in a triangle. If you have a single array, vertices, then quite simply, every set of 9 numbers in the array is one triangle (three sets of consecutive vertices, each with three consecutive XYZ values).
Going back to your original question, if you want to add triangles to your BufferedGeometry, I see two basic options:
Add the triangles to the original oldGeom object, and then convert it. It's a lot easier to add triangles to Geometry than it is BufferGeometry. Remember that the whole point of BufferGeometry is that it's not supposed to change! You would also be able to take advantage of .fromGeometry() because the new faces are already defined in oldGeom.
Make an indices array that's larger than necessary for the original indices and manually define triangles there. If you're defining new vertices that don't exist in the vertices array then you'd have to add them in there too. What a pain in the butt.

How to draw walls in ThreeJS from path or 2d array?

I need to draw a 3d house model (walls only) from a 2d path or array (explained later) I receive from FabricJS editor I've built. The type of data sent from 2d to 3d views doesn't matter.
My first (and only quite close to what I want to get) attempt was to create the array of 1s and zeros based on the room I want to draw, and then render it in ThreeJS as one cuboid per 'grid'. I based this approach on this ThreeJS game demo. So if the array look like this:
var map = [ //1 2 3 4 5 6 7 8
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1,],
[1, 1, 0, 0, 0, 0, 0, 1, 1, 1,], // 1
[1, 1, 0, 0, 1, 0, 0, 0, 0, 1,], // 2
[1, 0, 0, 0, 1, 1, 0, 0, 0, 1,], // 3
[1, 0, 0, 1, 1, 1, 1, 0, 0, 1,], // 4
[1, 0, 0, 0, 1, 1, 0, 0, 1, 1,], // 5
[1, 1, 1, 0, 0, 0, 0, 1, 1, 1,], // 6
[1, 1, 1, 0, 0, 1, 0, 0, 1, 1,], // 7
[1, 1, 1, 1, 1, 1, 0, 0, 1, 1,], // 8
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1,],
];
I iterate through the array and render one block for every 1, and calculate it's position from indexes from the 2d 'map' (my array).
var UNITSIZE = 250, units = mapW;
for (var i = 0; i < mapW; i++) {
for (var j = 0, m = map[i].length; j < m; j++) {
if (map[i][j]) {
var wall = new t.Mesh(cube, material);
wall.position.x = (i - units/2) * UNITSIZE;
wall.position.y = WALLHEIGHT/2;
wall.position.z = (j - units/2) * UNITSIZE;
scene.add(wall);
}
}
}
It worked great till I wanted to place other models (.obj, but it doesn't matter. Let's call them furniture) near the walls. Each piece of furniture has it's (x=0, y=0, z=0) point in the center of the model, and since walls are cubes (with the same coord system, with 0 point in the center), furniture are rendered in the center of the wall (when we place it in the corner, only 1/4 of the model is visible). This is more/less how it looks like:
(black - how the walls should look like, blue - each cuboid of the wall, red - piece of furniture)
Thats why I would like to render walls as planes, probably from a 2d closed patch (I can export it from Fabric without a problem). I don't need walls to be thick nor to be visible "from behind", when camera moves through the wall. Any clues on how to achieve something like this?
"Help me StackOverflow, your my only hope."
You can manually populate the vertex and face arrays of a THREE.js mesh, so if you can export the closed path you need for example as an array of coordinates, you can iterate over it, and push needed information to your wall object.
Something like this
var coordArray = [...]; //Array of corner points of a closed shape from your source. Here assumed to be THREE.Vector2() for simplicity.
var walls = new THREE.Geometry();
for(var i = 0; i < coordArray.length(); i++){ //iterate over the coordinate array, pushing vertices to the geometry
var coordinates = coordArray[i];
walls.vertices.push(new THREE.Vector3(coordinates.x, coordinates.y, 0)); //vertex at floor level
walls.vertices.push(new THREE.Vector3(coordinates.x, coordinates.y, 10)); //vertex at the top part of the wall, directly above the last
}
var previousVertexIndex = walls.vertices.length - 2; // index of the vertex at the bottom of the wall, in the segment we are creating faces for
for(var i = 0; i < walls.vertices.length; i += 2){
walls.faces.push(new THREE.Face3(i, i + 1, previousVertexIndex));
walls.faces.push(new THREE.Face3(i + 1, previousVertexIndex + 1, previousVertexIndex));
previousVertexIndex = i;
}
walls.computeVertexNormals();
walls.computeFaceNormals();
scene.add(new THREE.Mesh(walls, new THREE.MeshLambertMaterial());

Calculate normal vector of a polygon - Newells Method

I'm trying to calculate the surface normal of a 2D polygon. I am using Newell's method from the OpenGL wiki to calculate the surface normal. https://www.opengl.org/wiki/Calculating_a_Surface_Normal From my understanding the normal should be in the y direction but it always returns [0, 0, 0]. The y value gets changed to -1 on the second iteration and back to zero on the fourth iteration.
p = [[0, 0, 0]
[1, 0, 0]
[0, 0, 1]
[1, 0, 1]]
function calcNormal(p) {
var normal = [0, 0, 0];
for(var i = 0; i < p.length; i++) {
var j = (i + 1) % (p.length);
normal[0] += (p[i][1] - p[j][1]) * (p[i][2] + p[j][2]);
normal[1] += (p[i][2] - p[j][2]) * (p[i][0] + p[j][0]);
normal[2] += (p[i][0] - p[j][0]) * (p[i][1] + p[j][1]);
}
return normal;
}
You're using a degenerate polygon for testing. If you draw it in the xz-plane, with the vertices numbered from 0 to 3, it looks like this:
2 ---- 3
\ /
\ /
\/
/\
/ \
/ \
0 ---- 1
This polygon does not have a well defined normal, since it changes orientation in the middle, and folds over itself.
If you swap the last two vertices:
p = [[0, 0, 0]
[1, 0, 0]
[1, 0, 1]
[0, 0, 1]]
It will look like this, and you should get much more meaningful results:
3 ---- 2
| |
| |
| |
0 ---- 1
OpenGL's version is failing for some cases especially when Polygon is 2D and you providing more than 3 vertices for calculation (4 in your case). If you provide only 3 vertices it will calculate correctly (also consider to use vector product to get normal).
Here is the link to Game Development Stack Exchange to the similar question with different approaches to this problem.

Categories

Resources