Related
let's say I have a grid i.e. 2d array
const grid = [
[0, 0, A, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, B, 0],
[D, E, 0, C, F],
[0, 0, 0, 0, 0],
]
if some cell in the grid can visit all adjacent cells 4-diredtionally, for example, C is at [3, 3] so it can visit [3, 3 + 1], [3 - 1, 3],[3 +1, 3]``[3, 3 - 1], so normally I would have to hard code this like
// 👇 hard-coded directions
const dirs = [
[1, 0],
[-1, 0],
[0, 1],
[0, -1],
]
const possibleMoves = []
for (const [dx, dy] of dirs) {
possibleMoves.push([dx + x, dy +y])
}
then if it can move 8-directionally then you have to hard code more directions
const dirs = [[1, 0], [-1, 0] , [0,1], [0,-1], [1,1], [-1,1], [-1,-1], [1,-1]]
Is there a smarter way to generate the dirs array for the next moves?
Yes!
First: any time you're doing grid-logic, start by checking what Amit Patel has to say.
Honestly, that link has everything you could ever need.
The short version is: if you know the grid width and cell layout, you can easily calculate coordinate offsets of any cell neighbor for any definition of "neighbor."
That logic can be implemented as a pure function that requires both the grid dimensions and the coordinates of the cell whose neighbors you want (aka the "target" cell):
let myNeighbors = getCellNeighbors(
{ x: 2, y: 2 }, // coords of target cell
{ width: 10, height: 10 } // grid dimensions, in cells
)
Or you can create a stateful thing that takes the grid dimensions at creation and calculates the offsets once, to be re-used for all getNeighbors calls:
let myGrid = new Grid(10, 10)
let myNeighbors = myGrid.getNeighbors(2, 5)
let myBiggerGrid = new Grid(25, 25)
let otherNeighbors = myBiggerGrid(2, 5)
I am currently learning 3d computer graphics and normalising parallel projection into canocial view volume(LookAt Matrix as the familiar name). I try to implement it to the code using pure javascript as the parameter below.
var VRP = new Vertex(0,0,0);
var VPN = new Vertex(0,0,1);
var VUP = new Vertex(0,1,0);
var PRP = new Vertex(8,8,100);
var Window = [-1,17,-1,17];
var F = 1, B = -1;
Now, here is my attempt. I converted it first to canocial view volume.
NOTE: You can skip these steps directly to the code here and help me to fix the code to move the cube forward to camera(the screen) instead of moving away
1. Translate VRP to origin
var TVRP = [];
TVRP[0] = [1, 0, 0, -VRP.x];
TVRP[1] = [0, 1, 0, -VRP.y];
TVRP[2] = [0, 0, 1, -VRP.z];
TVRP[3] = [0, 0, 0, 1];
2. Rotate VRC such that n-axis,u-axis and v-axis align with z-axis, x-axis, and y-axis in order
function normalizeViewPlane(VPN) {
var unitVector = calculateUnitVector(VPN); //VPN/|VPN|
return normalizeVector(VPN,unitVector);
}
function normalizeViewUp(VUP, n) {
var dtProd = dotProduct(n,VUP);
var nVUP = new Vertex(n.x*dtProd, n.y*dtProd, n.z*dtProd);
VUP = new Vertex(VUP.x-nVUP.x, VUP.y-nVUP.y, VUP.z-nVUP.z);
var unitVector = calculateUnitVector(VUP); //VUP/|VUP|
return normalizeVector(VUP,unitVector);
}
function normalizeUVN(n,u) {
var V = crossProduct(n,u);
var unitVector = calculateUnitVector(V); //V/|V|
return normalizeVector(V,unitVector);
}
var n = normalizeViewPlane(VPN);
var v = normalizeViewUp(VUP, n);
var u = normalizeUVN(v, n);
var RVRC = [];
RVRC[0] = [u.x, u.y, u.z, 0];
RVRC[1] = [v.x, v.y, v.z, 0];
RVRC[2] = [n.x, n.y, n.z, 0];
RVRC[3] = [0, 0, 0, 1];
//Perform matrix multiplication 4x4 R.T(-VRP)
var res = multiplyMatrix4x4(RVRC, TVRP);
3. Shear DOP becomes parallel to z-axis
function shearDOP(PRP, uMaxMin, vMaxMin) {
var CW = new Vertex(uMaxMin,vMaxMin,0);
var mPRP = new Vertex(PRP.x,PRP.y,PRP.z);
return new Vertex(CW.x - mPRP.x, CW.y - mPRP.y, CW.z - mPRP.z);
}
var uMaxMin = (Window[1]+Window[0])/2;
var vMaxMin = (Window[3]+Window[2])/2;
var DOP = shearDOP(PRP,uMaxMin,vMaxMin);
var HX = (DOP.x/DOP.z)*-1;
var HY = (DOP.y/DOP.z)*-1;
var Hpar = [];
Hpar[0] = [1,0,HX,0];
Hpar[1] = [0,1,HY,0];
Hpar[2] = [0,0,1,0];
Hpar[3] = [0,0,0,1];
//res = R.T(-VRP)
res = multiplyMatrix4x4(Hpar,res);
4. Translate to front center of the view volume origin
var Tpar = [];
Tpar[0] = [1,0,0,-uMaxMin];
Tpar[1] = [0,1,0,-vMaxMin];
Tpar[2] = [0,0,1,-F];
Tpar[3] = [0,0,0,1];
//res=Hpar.R.T(-VRP)
res = multiplyMatrix4x4(Tpar,res);
5. Scale such that view volume becomes bounded by plane
var uMaxMin2 = 2/(Window[1]-Window[0]);
var vMaxMin2 = 2/(Window[3]-Window[2]);
var Spar = [];
Spar[0] = [uMaxMin2, 0, 0, 0];
Spar[1] = [0, vMaxMin2, 0, 0];
Spar[2] = [0, 0, 1 / (F - B), 0];
Spar[3] = [0, 0, 0, 1];
//res=Tpar.Hpar.R.T(-VRP)
res = multiplyMatrix4x4(Spar, res);
After convert it to the canocial view volume, I decided to multiply cube vertices to this final result transformation matrix.
//res=Spar.Tpar.Hpar.R.T(-VRP)
p = multiplyMatrix1x4(res,p);
//M is the parameter of cube vertice
M.x = p[0];
M.y = p[1];
M.z = p[2];
Thus, I had my cube is moving away from the camera as it is illustrated in image below.
However, I expect that cube is move closest to the camera instead of moving away as it is explained in image below(the object is house)
Am I missing the step or misunderstanding the algorithm of converting to canocial view volume? Which function or variable I shall modify to make the cube like the house above?
JSFiddle: https://jsfiddle.net/Marfin/hL2bmvz5/20/
Reference: https://telin.ugent.be/~sanja/ComputerGraphics/L06_Viewing_Part2_6pp.pdf
in general, if your cam is looking at the box and you want the cam to move towards the box, get the vector between cam and box and move the cam towards this direction:
cam += (box-cam)
So I have sprites that I want to connect using p2.js' revolute constraints. My current implementation applys force to the sprites as soon as the constraint is created.
How can I avoid this behavior?
If it can't be avoided is there another way to connect 2 sprites with each other horizontally?EDIT:
var Game = {
preload: function() {
game.load.image('tree00', './imgs/tree/tree-00.png');
game.load.image('tree01', './imgs/tree/tree-01.png');
game.load.image('tree02', './imgs/tree/tree-02.png');
game.load.image('tree03', './imgs/tree/tree-03.png');
game.load.image('tree04', './imgs/tree/tree-04.png');
game.load.image('tree05', './imgs/tree/tree-05.png');
game.load.spritesheet('present', './imgs/dude.png', 32, 48);
},
create: function() {
game.physics.startSystem(Phaser.Physics.P2JS);
game.physics.p2.gravity.y = 300;
game.physics.startSystem(Phaser.Physics.ARCADE);
game.stage.backgroundColor = '#aaffee';
treeCollsionGroup = game.physics.p2.createCollisionGroup();
presentCollisionGroup = game.physics.p2.createCollisionGroup();
this.createPresent(game.world.width * 0.21, game.world.height * 0.6);
this.createTree(6, game.world.width * 0.2, game.world.height * 0.8);
//presentCollisionGroup.collides(treeCollsionGroup);
connection[0] = game.physics.p2.createRevoluteConstraint(treeParts[treeParts.length - 1], [(treeParts[treeParts.length - 1].width)/2, treeParts[treeParts.length - 1].height], present, [-present.width/2, treeParts[treeParts.length - 1].height], maxForce);
connection[1] = game.physics.p2.createRevoluteConstraint(treeParts[treeParts.length - 1], [(treeParts[treeParts.length - 1].width)/2, 0], present, [-present.width/2, 0], maxForce);
},
createTree: function(length, xAnchor, yAnchor) {
var lastSprite;
for (var i = 0; i < length; i++) {
newSprite = game.add.sprite(xAnchor, yAnchor - i*100, 'tree0' + i);
newSprite.scale.x = game.world.width/1920;
newSprite.scale.y = game.world.width/1920;
game.physics.p2.enable(newSprite, true);
if (i != length-1) {
newSprite.body.setRectangle(game.world.width * 0.10, newSprite.height * 0.15);
} else {
newSprite.body.setRectangle(newSprite.width * 0.8, newSprite.height * 0.8);
}
newSprite.body.setCollisionGroup(treeCollsionGroup);
if(i === 0) {
newSprite.body.static = true;
}
if (lastSprite) {
switch(i) {
case 1: constraint = game.physics.p2.createRevoluteConstraint(newSprite, [0, 0], lastSprite, [0, -lastSprite.height * 0.62], maxForce);
treeConstraints.push(constraint);
break;
case 2: constraint = game.physics.p2.createRevoluteConstraint(newSprite, [0, 0], lastSprite, [0, -lastSprite.height * 0.285], maxForce);
treeConstraints.push(constraint);
break;
case 3: constraint = game.physics.p2.createRevoluteConstraint(newSprite, [0, 0], lastSprite, [0, -lastSprite.height * 0.425], maxForce);
treeConstraints.push(constraint);
break;
case 4: constraint = game.physics.p2.createRevoluteConstraint(newSprite, [0, 0], lastSprite, [0, -lastSprite.height * 0.4], maxForce);
treeConstraints.push(constraint);
break;
case 5: constraint = game.physics.p2.createRevoluteConstraint(newSprite, [0, 0], lastSprite, [0, -lastSprite.height * 0.55], maxForce);
treeConstraints.push(constraint);
break;
}
}
lastSprite = newSprite;
treeParts.push(newSprite);
newSprite.body.collides(treeCollsionGroup);
}
},
createPresent: function(xAnchor, yAnchor) {
present = game.add.sprite(game.world.width * 0.21, game.world.height * 0.6, 'present');
game.physics.p2.enable(present, true);
present.scale.x = game.world.width/1920;
present.scale.y = game.world.width/1920;
present.body.setRectangle(present.width, present.height);
present.body.data.gravityScale = 0;
present.body.setCollisionGroup(presentCollisionGroup);
}
}
I cut the less important code out so it would not be too much (it already is). Basically what I'm doing is: I create a tree and connecting the parts using revolute constraints so they behave like a tree in the real world (for example in the wind).
Than I create the present which is basically a sprite that should be horizontally connected to the top of the tree. Therefore I use 2 revolute constraint one for the topmost point between the sprites and one for the bottommost. (I know it's kind of dirty code)
After I create these constraints and the present gets connected to the top of the tree the tree starts shaking and collapses (like it should). But I don't want this behaviour.Maybe a lock constraint is what I'm looking for, I have to look into this.
Edit 2:
After taking a look at lock constraints I realized that this is what I'm looking for. But even lock constraints are collapsing the tree.
I can't really understand your issue but have you tried Prismatic or even Lock Constraint? Can give us some code or even codepen example so we may be able to help you better?
I am working on my first full program with two weeks of programming under my belt, and have run into a road block I can't seem to figure out. I am making a connect 4 game, and have started by building the logic in JavaScript before pushing to the DOM. I have started to make it with cell objects made by a constructor, that are then pushed into a game object in the form of a 2D array. I have managed to create a function that makes the play each time, and changes the value of the cell at the lowest point of that column with a 2 day array. However, I am not sure how to get my check for wins function to operate.
So far my logic is that, for each point in the 2D array, you can check by row, by column, and by diagonals. I understand the logic of how to check for win, but I don't understand how to traverse through the arrays by row and column. In the example below, this.cellsArray is an array of cell objects in the Board Constructor. The array has 7 column arrays, with 6 rows each, as I flipped the typical row column logic to account for Connect Four's column based nature. However I can't access the array like this.cellsArray[col][row], as col and row aren't defined, and I'm not sure how to define an index value? Any help would be appreciated!
Connect 4
Example:
//array location is equal to an instance of this.cellsArray[col][row]
Board.prototype.checkRowRight = function (arrayLocation) {
if ((arrayLocation[i+1][i].value === arrayLocation.value) && (arrayLocation[i+2][i]=== arrayLocation.value) && (arrayLocation[i+3][i].value === arraylocation.value)){
this.winner = this.currentPlayer;
this.winnerFound = true;
console.log('Winner has been found!')
}
};
Referencing back to my logic found here and refactoring out the winning line detection code, this can easily be converted into Javascript as follows:
function chkLine(a,b,c,d) {
// Check first cell non-zero and all cells match
return ((a != 0) && (a ==b) && (a == c) && (a == d));
}
function chkWinner(bd) {
// Check down
for (r = 0; r < 3; r++)
for (c = 0; c < 7; c++)
if (chkLine(bd[r][c], bd[r+1][c], bd[r+2][c], bd[r+3][c]))
return bd[r][c];
// Check right
for (r = 0; r < 6; r++)
for (c = 0; c < 4; c++)
if (chkLine(bd[r][c], bd[r][c+1], bd[r][c+2], bd[r][c+3]))
return bd[r][c];
// Check down-right
for (r = 0; r < 3; r++)
for (c = 0; c < 4; c++)
if (chkLine(bd[r][c], bd[r+1][c+1], bd[r+2][c+2], bd[r+3][c+3]))
return bd[r][c];
// Check down-left
for (r = 3; r < 6; r++)
for (c = 0; c < 4; c++)
if (chkLine(bd[r][c], bd[r-1][c+1], bd[r-2][c+2], bd[r-3][c+3]))
return bd[r][c];
return 0;
}
And a test call:
x =[ [0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 1, 2, 2, 2, 0],
[0, 1, 2, 2, 1, 2, 0] ];
alert(chkWinner(x));
The chkWinner function will, when called with the board, return the first (and only, assuming each move changes only one cell and you're checking after every move) winning player.
The idea is to basically limit the checks to those that make sense. For example, when checking cells to the right (see the second loop), you only need to check each row 0-6 starting in each of the leftmost four columns 0-3.
That's because starting anywhere else would run off the right hand side of the board before finding a possible win. In other words, column sets {0,1,2,3}, {1,2,3,4}, {2,3,4,5} and {3,4,5,6} would be valid but {4,5,6,7} would not (the seven valid columns are 0-6).
This is an old thread but i'll throw my solution into the mix since this shows up as a top search result for "how to calculate connect4 win javascript"
I tackled this problem by using matrix addition.
Assume your game board is stored in memory as a 2D array like this:
[ [0, 0, 0, 0, 0, 0, 0],
[0, 0, Y, 0, 0, 0, 0],
[0, 0, Y, 0, 0, 0, 0],
[0, 0, R, 0, 0, 0, 0],
[0, 0, Y, 0, 0, 0, 0],
[0, 0, R, R, R, 0, 0] ];
On each "Coin Drop" you should call a function passing the x/y position of the coin.
THIS is where you calculate weather the user has won the game
let directionsMatrix = {
vertical: { south: [1, 0], north: [-1, 0] },
horizontal: { east: [0, 1], west: [0, -1] },
backward: { southEast: [1, 1], northWest: [-1, -1] },
forward: { southWest: [1, -1], northEast: [-1, 1] },
};
NOTE: "South" in matrix notation is [1,0], meaning "Down 1 cell, Right 0 cells"
Now we can loop through each Axis/Direction to check if there is 4 in a row.
const playerHasWon = (colnum, rowNum, playerColor, newGrid) => {
//For each [North/South, East/West, NorthEast/Northwest, SouthEast/Southwest]
for (let axis in directionsMatrix) {
// We difine this variable here so that "East" and "West" share the same count,
// This allows a coin to be dropped in a middle cell
let numMatches = 1;
// For each [North, South]
for (let direction in directionsMatrix[axis]) {
// Get X/Y co-ordinates of our dropped coin
let cellReference = [rowNum, colnum];
// Add co-ordinates of 1 cell in test direction (eg "North")
let testCell = newGrid[cellReference[0]][cellReference[1]];
// Count how many matching color cells are in that direction
while (testCell == playerColor) {
try {
// Add co-ordinates of 1 cell in test direction (eg "North")
cellReference[0] += directionsMatrix[axis][direction][0];
cellReference[1] += directionsMatrix[axis][direction][1];
testCell = newGrid[cellReference[0]][cellReference[1]];
// Test if cell is matching color
if (testCell == playerColor) {
numMatches += 1;
// If our count reaches 4, the player has won the game
if (numMatches >= 4) {
return true;
}
}
} catch (error) {
// Exceptions are to be expected here.
// We wrap this in a try/catch to ignore the array overflow exceptions
// console.error(error);
break;
}
}
// console.log(`direction: ${direction}, numMatches: ${numMatches}`);
// If our count reaches 4, the player has won the game
if (numMatches >= 4) {
return true;
}
}
}
// If we reach this statement: they have NOT won the game
return false;
};
Here's a link to the github repo if you wish to see the full code.
Here's a link to a live demo
Been trying to sort this out for a few days and I am not sure if the CSS matrix is different from standard graphics matrices, or if I have something wrong (likely I have something wrong).
I am primarily trying to figure out how to rotate on the X and Y axis. When I use "transform: rotateX(2deg) rotateY(2deg) translate3d(0px, -100px, 0px);" and I use javascript to grab the matrix style, this is what I am able to output.
0.9993908270190958, -0.001217974870087876, -0.03487823687206265, 0,
0, 0.9993908270190958, -0.03489949670250097, 0,
0.03489949670250097, 0.03487823687206265, 0.9987820251299122, 0,
0, -99.93908270190957, 3.489949670250097, 1
But if I try to calculate the matrix using javascript (with 2 degrees on both X and Y) I get
0.9993908270190958, 0, -0.03489949670250097, 0,
-0.001217974870087876, 0.9993908270190958, -0.03487823687206265, 0,
0.03487823687206265, 0.03489949670250097, 0.9987820251299122, 0,
0.1217974870087876, -99.93908270190957, 3.487823687206265, 1
Now while several numbers are different in the second one, I believe one number is causing the problem. Note the numbers in row 1/column 2 and in row 2/column 1, for both matrices. The "-0.001217974870087876" looks to be switched. And if I understand how everything is calculated that is likely throwing off all the other numbers.
Here's the code I am using to create the second matrix
var basematrix = [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, -100, 0, 1]
];
function RotateWorld(y, x)
{
var halfrot = Math.PI / 180;
var xcos = Math.cos(x * halfrot);
var xsin = Math.sin(x * halfrot);
var ycos = Math.cos(y * halfrot);
var ysin = Math.sin(y * halfrot);
var ymatrix = [
[ycos, 0, -ysin, 0],
[0, 1, 0, 0],
[ysin, 0, ycos, 0],
[0, 0, 0, 1]
];
var xmatrix = [
[1, 0, 0, 0],
[0, xcos, xsin, 0],
[0, -xsin, xcos, 0],
[0, 0, 0, 1]
];
var calcmatrix = MatrixMultiply(ymatrix, basematrix);
calcmatrix = MatrixMultiply(xmatrix, calcmatrix);
calcmatrix = TransMultiply(calcmatrix);
for (var i = 0; i < 4; i++)
{
for (var j = 0; j < 4; j++)
{
document.getElementById('info').innerHTML += calcmatrix[i][j] + ', ';
}
}
}
function MatrixMultiply(matrixa, matrixb)
{
var newmatrix = [];
for (var i = 0; i < 4; ++i)
{
newmatrix[i] = [];
for (var j = 0; j < 4; ++j)
{
newmatrix[i][j] = matrixa[i][0] * matrixb[0][j]
+ matrixa[i][1] * matrixb[1][j]
+ matrixa[i][2] * matrixb[2][j]
+ matrixa[i][3] * matrixb[3][j];
}
}
return newmatrix;
}
function TransMultiply(matrix)
{
var newmatrix = matrix;
var x = matrix[3][0];
var y = matrix[3][1];
var z = matrix[3][2];
var w = matrix[3][3];
newmatrix[3][0] = x * matrix[0][0] + y * matrix[1][0] + z * matrix[2][0];
newmatrix[3][1] = x * matrix[0][1] + y * matrix[1][1] + z * matrix[2][1];
newmatrix[3][2] = x * matrix[0][2] + y * matrix[1][2] + z * matrix[2][2];
newmatrix[3][3] = x * matrix[0][3] + y * matrix[1][3] + z * matrix[2][3] + newmatrix[3][3];
if (newmatrix[3][3] != 1 && newmatrix[3][3] != 0)
{
newmatrix[3][0] = x / w;
newmatrix[3][1] = y / w;
newmatrix[3][2] = z / w;
}
return newmatrix;
}
My code is a bit verbose as I am just trying to learn how to work with the CSS matrix. But hopefully someone can help me get that one number into the right place.
Edit
I hate to bump a post but I am running out of places to ask, so I am hoping a few more people will see it with a chance of getting an answer. I have tried every possible search to figure this out (unique questions don't get ranked very high in Google). I have probably read over 20 articles on working with matrices and they are yielding nothing. If I need to add more information please let me know. Also if there is a better place to ask let me know that as well. I would assume by now several people have looked at the code and the code must be ok, maybe my assumption that CSS is the culprit is a possibility, if so how does one track that down?
Take a look at this page, it explains how css 3dmatrix work. Also here you have an implementation in JS of CSSMatrix object, very similar to WebKitCSSMatrix which is already included in your (webkit) browser for your use.
You have a bug in your implementation of function TransMultiply(matrix) { .. }
var newmatrix = matrix;
That isn't cloning your matrix, that's setting newmatrix to refer to your original matrix! Anything using this method is going to have the original matrix and new matrix messed up. You might want to use a method that creates new 4x4 matricies, like:
function new4x4matrix(){
return [[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]];
}
and then wherever you need a new matrix, do:
var newmatrix = new4x4matrix();
Edit: err, but you may actually need a clone method: fine.
function cloneMatrix(matrixa)
{
var newmatrix = [];
for (var i = 0; i < 4; ++i)
{
newmatrix[i] = [];
for (var j = 0; j < 4; ++j)
{
newmatrix[i][j] = matrixa[i][j];
}
}
return newmatrix;
}
and instead, for TransMultiply do:
var newmatrix = cloneMatrix(matrix);