HTML 5 canvas element internals not displayed when using external JS - javascript

I am creating a chrome extension and I have a canvas element inside of the popup.html file and some js in an external js file as Chrome does not allow for inline scripts on chrome extension pages. The canvas element loads sucessfully as I can see the border that I put around it but the code that fills it in is executing but not doing anything. I have tested the code using inline js on a plain html file using same parameters and it works. What am I doing wrong?
HTML file:
<button id="playTetrisButton" onclick="executeGame()">Play now!</button>(this button does not actually do anything as I stripped away its function for debugging and code will run without it)
<center><canvas width="320" height="500" id="game"></canvas></center>
<script src="tetris game code.js"></script>
JS FILE:
// This is the game code for tetris so not part of the actual website
// https://tetris.fandom.com/wiki/Tetris_Guideline
// get a random integer between the range of [min,max]
document.addEventListener('DOMCONTENTLOADED',domloaded,false);
function domloaded(){
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// generate a new tetromino sequence
// #see https://tetris.fandom.com/wiki/Random_Generator
function generateSequence() {
const sequence = ['I', 'J', 'L', 'O', 'S', 'T', 'Z'];
while (sequence.length) {
const rand = getRandomInt(0, sequence.length - 1);
const name = sequence.splice(rand, 1)[0];
tetrominoSequence.push(name);
}
}
// get the next tetromino in the sequence
function getNextTetromino() {
if (tetrominoSequence.length === 0) {
generateSequence();
}
const name = tetrominoSequence.pop();
const matrix = tetrominos[name];
// I and O start centered, all others start in left-middle
const col = playfield[0].length / 2 - Math.ceil(matrix[0].length / 2);
// I starts on row 21 (-1), all others start on row 22 (-2)
const row = name === 'I' ? -1 : -2;
return {
name: name, // name of the piece (L, O, etc.)
matrix: matrix, // the current rotation matrix
row: row, // current row (starts offscreen)
col: col // current col
};
}
// rotate an NxN matrix 90deg
// #see https://codereview.stackexchange.com/a/186834
function rotate(matrix) {
const N = matrix.length - 1;
const result = matrix.map((row, i) =>
row.map((val, j) => matrix[N - j][i])
);
return result;
}
// check to see if the new matrix/row/col is valid
function isValidMove(matrix, cellRow, cellCol) {
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] && (
// outside the game bounds
cellCol + col < 0 ||
cellCol + col >= playfield[0].length ||
cellRow + row >= playfield.length ||
// collides with another piece
playfield[cellRow + row][cellCol + col])
) {
return false;
}
}
}
return true;
}
// place the tetromino on the playfield
function placeTetromino() {
for (let row = 0; row < tetromino.matrix.length; row++) {
for (let col = 0; col < tetromino.matrix[row].length; col++) {
if (tetromino.matrix[row][col]) {
// game over if piece has any part offscreen
if (tetromino.row + row < 0) {
return showGameOver();
}
playfield[tetromino.row + row][tetromino.col + col] = tetromino.name;
}
}
}
// check for line clears starting from the bottom and working our way up
for (let row = playfield.length - 1; row >= 0; ) {
if (playfield[row].every(cell => !!cell)) {
// drop every row above this one
for (let r = row; r >= 0; r--) {
for (let c = 0; c < playfield[r].length; c++) {
playfield[r][c] = playfield[r-1][c];
}
}
}
else {
row--;
}
}
tetromino = getNextTetromino();
}
// show the game over screen
function showGameOver() {
cancelAnimationFrame(rAF);
gameOver = true;
context.fillStyle = 'black';
context.globalAlpha = 0.75;
context.fillRect(0, canvas.height / 2 - 30, canvas.width, 60);
context.globalAlpha = 1;
context.fillStyle = 'white';
context.font = '36px monospace';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('GAME OVER!', canvas.width / 2, canvas.height / 2);
}
const canvas = document.getElementById('game');
const context = canvas.getContext('2d');
const grid = 32;
const tetrominoSequence = [];
// keep track of what is in every cell of the game using a 2d array
// tetris playfield is 10x20, with a few rows offscreen
const playfield = [];
// populate the empty state
for (let row = -2; row < 20; row++) {
playfield[row] = [];
for (let col = 0; col < 10; col++) {
playfield[row][col] = 0;
}
}
// how to draw each tetromino
// #see https://tetris.fandom.com/wiki/SRS
const tetrominos = {
'I': [
[0,0,0,0],
[1,1,1,1],
[0,0,0,0],
[0,0,0,0]
],
'J': [
[1,0,0],
[1,1,1],
[0,0,0],
],
'L': [
[0,0,1],
[1,1,1],
[0,0,0],
],
'O': [
[1,1],
[1,1],
],
'S': [
[0,1,1],
[1,1,0],
[0,0,0],
],
'Z': [
[1,1,0],
[0,1,1],
[0,0,0],
],
'T': [
[0,1,0],
[1,1,1],
[0,0,0],
]
};
// color of each tetromino
const colors = {
'I': 'cyan',
'O': 'yellow',
'T': 'purple',
'S': 'green',
'Z': 'red',
'J': 'blue',
'L': 'orange'
};
let count = 0;
let tetromino = getNextTetromino();
let rAF = null; // keep track of the animation frame so we can cancel it
let gameOver = false;
// game loop
function loop() {
rAF = requestAnimationFrame(loop);
context.clearRect(0,0,canvas.width,canvas.height);
// draw the playfield
for (let row = 0; row < 20; row++) {
for (let col = 0; col < 10; col++) {
if (playfield[row][col]) {
const name = playfield[row][col];
context.fillStyle = colors[name];
// drawing 1 px smaller than the grid creates a grid effect
context.fillRect(col * grid, row * grid, grid-1, grid-1);
}
}
}
// draw the active tetromino
if (tetromino) {
// tetromino falls every 35 frames
if (++count > 35) {
tetromino.row++;
count = 0;
// place piece if it runs into anything
if (!isValidMove(tetromino.matrix, tetromino.row, tetromino.col)) {
tetromino.row--;
placeTetromino();
}
}
context.fillStyle = colors[tetromino.name];
for (let row = 0; row < tetromino.matrix.length; row++) {
for (let col = 0; col < tetromino.matrix[row].length; col++) {
if (tetromino.matrix[row][col]) {
// drawing 1 px smaller than the grid creates a grid effect
context.fillRect((tetromino.col + col) * grid, (tetromino.row + row) * grid, grid-1, grid-1);
}
}
}
}
}
// listen to keyboard events to move the active tetromino
document.addEventListener('keydown', function(e) {
if (gameOver) return;
// left and right arrow keys (move)
if (e.which === 37 || e.which === 39) {
const col = e.which === 37
? tetromino.col - 1
: tetromino.col + 1;
if (isValidMove(tetromino.matrix, tetromino.row, col)) {
tetromino.col = col;
}
}
// up arrow key (rotate)
if (e.which === 38) {
const matrix = rotate(tetromino.matrix);
if (isValidMove(matrix, tetromino.row, tetromino.col)) {
tetromino.matrix = matrix;
}
}
// down arrow key (drop)
if(e.which === 40) {
const row = tetromino.row + 1;
if (!isValidMove(tetromino.matrix, row, tetromino.col)) {
tetromino.row = row - 1;
placeTetromino();
return;
}
tetromino.row = row;
}
});
}

Related

Filter out duplicate Hough Lines Opencvjs

I'm trying to filter out all lines as a result from HoughLine function in opencv. Currently just using getLines() function to get all lines then sorting them according to their rho value for each line. Then applying for loop to remove duplicates, problem is as follows:
In code if in (r1, θ1) and (r2, θ2) which I'm comparing in inner for loop for both either (r1,r2) positive or (r1,r2) negative (based on html canvas) then it's easy to filter them. But if either of them is positve or negative respectively then that line also gets included in finalLines array. Which can be explained through following figure:
function:
function getLines(minthresh = 8) {
let t = 300,
j = 0;
linesMat = new cv.Mat();
let copy = org_img.clone();
while (t > 0 && j < minthresh) {
try {
cv.HoughLines(tmp, linesMat, 1, Math.PI / 180, t);
j = linesMat.matSize[0]
console.log(`t=${t} j=${j}`, linesMat.matSize);
} catch (err) {
console.log(`error for t=${t}`);
j = 0;
} finally {
t = t - 10;
}
}
console.log('hough lines', linesMat.rows);
let bool = true;
let lines = linesMat.data32F;
let rows = linesMat.rows;
let foundLines = [];
finalLines = [];
for (let i = 0; i < rows; i++) {
let l = [lines[i * 2], lines[i * 2 + 1]];
foundLines.push(l);
}
foundLines.sort((a, b) => a[0] - b[0]);
console.log('filtering lines starts, sorted found lines:', foundLines);
for (let [index, line] of foundLines.entries()) {
let rho = line[0];
let theta = line[1];
let a = Math.cos(theta);
let b = Math.sin(theta);
let x0 = a * rho;
let y0 = b * rho;
let startPoint = {
x: x0 - 1000 * b,
y: y0 + 1000 * a
};
let endPoint = {
x: x0 + 1000 * b,
y: y0 - 1000 * a
};
let color = [random(), random(), random(), 255];
cv.line(copy, startPoint, endPoint, color);
bool = false;
for (let [jindex, linet] of foundLines.slice(index + 1).entries()) {
console.log(`for index=${index} jindex=${jindex+1} linet=${linet} line=${line}`);
bool = false;
if (linet[0] !== line[0]) {
k = [Math.abs(linet[0] - line[0]) < 50, Math.abs(linet[1] - line[1]) < 0.5];
console.log(`k=${k}`);
if (k[0] && k[1]) {
bool = true;
break;
}
}
}
if (bool) {
continue;
}
finalLines.push(line);
console.log(`push for i=${index}`);
console.log(`%c rho=${rho} deg=${theta * (180/Math.PI)}`, `background: rgba(${color[0]}, ${color[1]}, ${color[2]}, 1); color: black`);
}
cv.imshow('op', copy);
}

Merge sort visualisation using recursion and Promises

I've written a merge sort visualisation in p5.js which shows the steps of merge sort. This works fine as a sequential visualisation, but I'd quite like to show this as a true representation, where you can see each part of the array being sorted at the same time (with multiple sections being visualised sorting at the same time, to truly reflect the recursion). The code itself is relatively simple:
// Split the array recursively
let mid = Math.floor((right + left) / 2);
if (right - left < 1) {
return;
}
// My attempt to visualise this properly
await Promise.all([mergeSortSlice(array, left, mid), mergeSortSlice(array, mid + 1, right)]);
// THIS WORKS, but only for sequential sorting
// await mergeSortSlice(array, left, mid);
// await mergeSortSlice(array, mid + 1, right)
// Putting sleep(200) here also works, but doesn't show the steps of the sort as they are happening, just the result of each stage of the sort.
leftCounter = 0;
rightCounter = 0;
l = left;
r = mid + 1;
valuesStartIndex = l;
let leftArray = array.slice(left, r);
let rightArray = array.slice(r, right + 1);
while (rightCounter < rightArray.length && leftCounter < leftArray.length) {
if (leftArray[leftCounter] < rightArray[rightCounter]) {
array.splice(l + rightCounter, 1);
array.splice(valuesStartIndex, 0, leftArray[leftCounter]);
l++;
leftCounter++;
valuesStartIndex++;
await sleep(200);
} else {
array.splice(r, 1);
array.splice(valuesStartIndex, 0, rightArray[rightCounter]);
r++;
rightCounter++;
valuesStartIndex++;
await sleep(200);
}
}
The problem with using Promise.all is that the split parts of the array are getting mixed up, I believe due to the recursion? This is resulting in the array not getting sorted properly.
My timeout function:
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
The setup function and draw loop:
let values = [50, 10, 80, 56, 30, 25, 15]
function setup() {
createCanvas(600, 190);
frameRate(60);
mergeSort(values)
}
function draw() {
rectWidth = 10;
background(23);
stroke(0);
fill(255);
for (let i = 0; i < values.length; i++) {
rect(i * rectWidth, height - values[i], rectWidth, values[i]);
}
}
The combination of async functions and recursion makes it difficult for me to come up with a solution for this. Any help/advice would be much appreciated.
You were actually very close to having a working solution. Your issue is that you are creating a bunch of global variables inside your mergeSortSlice function:
// These were all missing the let keyword
// And were therefore either assigning or implicitly declaring
// globally scoped variables.
let leftCounter = 0;
let rightCounter = 0;
let l = left;
let r = mid + 1;
let valuesStartIndex = l;
let leftArray = array.slice(left, r);
let rightArray = array.slice(r, right + 1);
When two instances of a function invocation being run as part of a Promise each of which await on timeouts, their execution is going to be interleaved (which you want so you can graphically represent the theoretical parallelism). However, when those functions alter global variables this is a classic shared memory multi-threading bug.
Here's an adaptation of your code with the bug fixed, highlighting added, and a slightly different delay strategy:
function merge_sort(p) {
const Mode = {
Shuffling: 0,
Sorting: 1
};
const spacing = 5;
let array = [...Array(40)].map((_, i) => i);
let highlights = [];
let itemWidth;
let itemHeight;
let currentMode = Mode.Shuffling;
let iterator;
let frameRate = 8;
let redrawPromise;
let signalRedraw;
p.setup = function() {
p.createCanvas(p.windowWidth, p.windowHeight);
p.frameRate(frameRate * 5);
itemWidth = (p.width - (spacing * (array.length + 1))) / array.length;
itemHeight = p.height - spacing * 2;
iterator = shuffle();
initRedrawPromise();
};
function initRedrawPromise() {
redrawPromise =
new Promise(resolve => {
signalRedraw = resolve;
});
redrawPromise.then(() => initRedrawPromise());
}
p.draw = function() {
p.background('white');
// draw
for (let i = 0; i < array.length; i++) {
if (highlights[i]) {
p.fill(highlights[i]);
} else {
p.fill('blue');
}
let fractionalHeight = (array[i] + 1) / array.length;
let pixelHeight = fractionalHeight * itemHeight;
p.rect(
(i + 1) * spacing + i * itemWidth,
spacing + (itemHeight - pixelHeight),
itemWidth,
pixelHeight
);
}
signalRedraw();
if (currentMode === Mode.Shuffling) {
// update
let next = iterator.next();
if (next.value) {
// Done suffle, switch to sort
currentMode = Mode.Sorting;
p.frameRate(frameRate);
sort().then(() => {
// switch back to shuffling
currentMode = Mode.Shuffling;
p.frameRate(frameRate * 5);
iterator = shuffle();
});
}
}
};
p.keyPressed = function(e) {
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
frameRate++;
p.frameRate(frameRate);
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
frameRate = Math.max(0, frameRate - 1);
p.frameRate(frameRate);
}
}
// shuffle the array. yield false for each step where the array is not yet shuffled. yield true once the array is shuffled.
function* shuffle() {
// for each position in the array (except the last position),
// if the chosen item is not the current item, swap the two items.
for (let i = 0; i < array.length - 1; i++) {
highlight(i);
yield false;
let j = randomInt(i, array.length);
if (j !== i) {
highlight(i, j);
yield false;
swap(i, j);
highlight(j, i);
yield false;
} else {
highlight(i);
yield false;
}
}
yield true;
}
function sort() {
highlights = [];
return sortSlice(0, array.length - 1);
}
async function sortSlice(left, right) {
if (right - left < 1) {
return;
}
// Split the array recursively
let mid = Math.floor((right + left) / 2);
await Promise.all([sortSlice(left, mid), sortSlice(mid + 1, right)]);
for (let ix = left; ix <= right; ix++) {
highlights[ix] = undefined;
}
let leftCounter = 0;
let rightCounter = 0;
let l = left;
let r = mid + 1;
let valuesStartIndex = l;
let leftArray = array.slice(left, r);
let rightArray = array.slice(r, right + 1);
while (rightCounter < rightArray.length && leftCounter < leftArray.length) {
if (leftArray[leftCounter] < rightArray[rightCounter]) {
array.splice(l + rightCounter, 1);
array.splice(valuesStartIndex, 0, leftArray[leftCounter]);
highlights[valuesStartIndex] = 'green';
highlights[r] = 'red';
l++;
leftCounter++;
valuesStartIndex++;
} else {
array.splice(r, 1);
array.splice(valuesStartIndex, 0, rightArray[rightCounter]);
highlights[valuesStartIndex] = 'green';
r++;
rightCounter++;
valuesStartIndex++;
highlights[l + rightCounter] = 'red';
}
// at each merge step wait for a redraw that shows this step
await redrawPromise;
highlights[valuesStartIndex - 1] = 'gray';
for (let ix = valuesStartIndex; ix <= right; ix++) {
highlights[ix] = undefined;
}
}
}
function swap(i, j) {
const tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
function randomInt(lowerBound, upperBound) {
return lowerBound + Math.floor(Math.random() * (upperBound - lowerBound));
}
function highlight(i, j) {
highlights = [];
if (i !== undefined) {
highlights[i] = 'green';
}
if (j !== undefined) {
highlights[j] = 'red';
}
}
}
sketch = new p5(merge_sort);
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

JavaScript create two dimensional array

I'm new to JavaScript, I'm trying to solve leetcode question 37. I need to a create a blank two dimensional array, I initially used the method in the comments; however, it doesn't work correctly, it will change all the value. Then, I used the for loop method to create array and currently it worked correctly. But I still cannot figured out why this will happen, could anyone explain the reason why this will happen, is this because of shallow copy?
var solveSudoku = function (board) {
// let rows = new Array(9).fill(new Array(10).fill(0)),
let rows = new Array(9);
for (let i = 0; i < 9; i++) {
rows[i] = new Array(10).fill(0);
}
let cols = new Array(9);
for (let i = 0; i < 9; i++) {
cols[i] = new Array(10).fill(0);
}
let boxes = new Array(9);
for (let i = 0; i < 9; i++) {
boxes[i] = new Array(10).fill(0);
}
// let cols = new Array(9).fill(new Array(10).fill(0)),
// boxes = new Array(9).fill(new Array(10).fill(0));
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
let c = board[i][j];
if (c !== '.') {
let n = parseInt(c),
bx = Math.floor(j / 3),
by = Math.floor(i / 3);
// 0代表为使用,1为使用过
rows[i][n] = 1;
console.log(i, n)
cols[j][n] = 1;
// box索引
boxes[by * 3 + bx][n] = 1;
}
}
}
fill(board, 0, 0)
function fill(board, x, y) {
// 完成填充条件
if (y === 9) return true;
// 下一个点的坐标
let nx = (x + 1) % 9,
// 判断进入是否下一行
ny = (nx === 0) ? y + 1 : y;
// 如果已经填充,则进入下一个点
if (board[y][x] !== '.') return fill(board, nx, ny);
// 没有被填充过
for (let i = 1; i <= 9; i++) {
let bx = Math.floor(x / 3),
by = Math.floor(y / 3),
box_key = by * 3 + bx;
if (!rows[y][i] && !cols[x][i] && !boxes[box_key][i]) {
rows[y][i] = 1;
cols[x][i] = 1;
boxes[box_key][i] = 1;
board[y][x] = i.toString();
console.log(board[y][x])
// 递归向下一个点求解
if (fill(board, nx, ny)) return true;
// 恢复初始状态
board[y][x] = '.';
boxes[box_key][i] = 0;
rows[y][i] = 0;
cols[x][i] = 0;
}
}
return false;
}
console.log(board);
};
The problem with fill(), at least with object, is that it passes the same object, by reference, to all element of the array. So if you mutate this object, then it will mutate every object of every arrays.
Note that in your case, you are creating a new Array object using it's constructor ( new Array() ) which makes them objects.
const matrix = new Array(5).fill(new Array(5).fill(0));
console.log(matrix);
In the previous snippet, you can see that the values of the other rows, from the second one to the end, are reference to the initial row.
To get around that, you can fill you array with empty values and then use the map() to create unique object for each position in the array.
const matrix = new Array(5).fill().map(function() { return new Array(5).fill(0); });
console.log(matrix);
As you can see in the previous snippet, all the rows are now their unique reference.
This is the reason all of your values were changed.
I've applied this solution to your code. I wasn't able to test it, because I wasn't sure of the initial parameters to pass.
I've also used anonymous function here ( function() { return; } ), but I would success using arrow function ( () => {} ) instead, if you are comfortable with them. It's cleaner.
var solveSudoku = function (board) {
let rows = new Array(9).fill().map(function() { return new Array(10).fill(0); }),
cols = new Array(9).fill().map(function() { return new Array(10).fill(0); }),
boxes = new Array(9).fill().map(function() { return new Array(10).fill(0); });
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
let c = board[i][j];
if (c !== '.') {
let n = parseInt(c),
bx = Math.floor(j / 3),
by = Math.floor(i / 3);
// 0代表为使用,1为使用过
rows[i][n] = 1;
console.log(i, n)
cols[j][n] = 1;
// box索引
boxes[by * 3 + bx][n] = 1;
}
}
}
fill(board, 0, 0)
function fill(board, x, y) {
// 完成填充条件
if (y === 9) return true;
// 下一个点的坐标
let nx = (x + 1) % 9,
// 判断进入是否下一行
ny = (nx === 0) ? y + 1 : y;
// 如果已经填充,则进入下一个点
if (board[y][x] !== '.') return fill(board, nx, ny);
// 没有被填充过
for (let i = 1; i <= 9; i++) {
let bx = Math.floor(x / 3),
by = Math.floor(y / 3),
box_key = by * 3 + bx;
if (!rows[y][i] && !cols[x][i] && !boxes[box_key][i]) {
rows[y][i] = 1;
cols[x][i] = 1;
boxes[box_key][i] = 1;
board[y][x] = i.toString();
console.log(board[y][x])
// 递归向下一个点求解
if (fill(board, nx, ny)) return true;
// 恢复初始状态
board[y][x] = '.';
boxes[box_key][i] = 0;
rows[y][i] = 0;
cols[x][i] = 0;
}
}
return false;
}
console.log(board);
};

New to Reactjs, Building a Game of Life, but the old board is updating with the new board

First I want to pull a random board so the game starts running as soon as it renders, but for this case I have a pre-assigned layout for debugging. This three square arrangement will switch from horizontal to vertical and back infinitely.
getInitialState: function() {
var state = {
rows: 5,
cols: 5,
generation: 0,
active: false
};
var cellMatrix = [];
var cellRow = [];
/* for (var i = 0; i < state.rows; i++) {
for (var j = 0; j < state.cols; j++) {
cellRow.push(Math.round(Math.random()));
}
cellMatrix.push(cellRow);
cellRow = [];
}*/
cellMatrix = [
[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]
];
state.board = cellMatrix;
return state;
},
Pretty simple so far. I render the page with a button that allows me to execute this.gameStep, which simulates another generation. Updated values are assigned to newBoard and the state.board is updated after the loop. The problem is that the state.board is updating to the current board state every time it loops (25 times in this case). If I were to insert a (newBoard == this.state.board) boolean, it returns true every iteration of the loop.
gameStep: function() {
var newBoard = this.state.board;
for (var i = 0; i < this.state.rows; i++) {
for (var j = 0; j < this.state.cols; j++) {
console.log(this.state.board);
var check = this.checkNeighborSum(i, j, this.state.board);
if (check == 3) {
newBoard[i][j] = 1;
} else if (check == 4) {
newBoard[i][j] = this.state.board[i][j];
} else {
newBoard[i][j] = 0;
}
}
}
this.setState({
board: newBoard
});
},
For reference, checkNeighborSum is just the actual math function. 3 is guaranteed life, 4 is life only if it is already alive (aka copy the status over), anything else is dead.
checkNeighborSum: function(x, y, board) { // x is row index, y is column index
var xMinus = x - 1;
var xPlus = x + 1;
var yMinus = y - 1;
var yPlus = y + 1;
if (x === 0) {
xMinus = this.state.cols - 1;
}
if (x === this.state.cols - 1) {
xPlus = 0;
}
if (y === 0) {
yMinus = this.state.rows - 1;
}
if (y === this.state.rows - 1) {
yPlus = 0;
}
return (board[xMinus][yMinus] +
board[xMinus][y] +
board[xMinus][yPlus] +
board[x][yMinus] +
board[x][y] +
board[x][yPlus] +
board[xPlus][yMinus] +
board[xPlus][y] +
board[xPlus][yPlus]);
},
Link incase you would rather see the whole page: link
In gameStep, you are changing this.state.board itself. I'd recommend using immutable structures, e.g. immutable.js. However, since you are only beginning, you can try cloning the state into newState using Object.assign -
var newBoard = Object.assign({}, this.state.board);
This should duplicate the board when adding it to newBoard.
Maybe check this : How do I correctly clone a JavaScript object?
var newBoard = this.state.board just create an another reference to the same variable. It doesn't clone your object

Canvas animation loop

I am continuing to animate a model of a transportation system. The code builds stations, connects them with lines, builds "pods" to run along them, then works out the shortest path between random start and end stations.
jsfiddle
The problems I am having are
a) the pods are traveling one at a time, rather than simultaneously.
b) the pod color is supposed to be related to the number of passengers, but this is clearly not happening.
c) I suspect I am producing a bottleneck in calculating the animation points, as it seems to run more hesitantly than it should.
I would also like to be able to flag the arrival of each pod, so that it can be reloaded, and start a new journey.
Something is wrong with the animation section, but I can't work out what. Any help would be appreciated!
animate();
var lastTime = 0;
var speed = 1; // higher is slower
function animate(time) {
for (var i = 0; i < podArray.length; i++) {
var aPod = podArray[i];
// calculate incremental points along the path
var points = calcWaypoints(aPod.wayStations);
// return if the desired time hasn't elapsed
if ((time - lastTime) < speed) {
requestAnimationFrame(animate);
return;
}
lastTime = time;
//function animate(){
ctx3.clearRect(0, 0, layer3.width, layer3.height);
if (t < points.length - 1) {
requestAnimationFrame(animate);
}
// draw pod from the last waypoint to the current waypoint
ctx3.beginPath();
ctx3.moveTo(aPod.startX, aPod.startY); //(points[t - 1].x, points[t - 1].y);
ctx3.arc(points[t].x, points[t].y, 4, 0, Math.PI * 2, true);
ctx3.fillStyle = aPod.color;
ctx3.fill();
t++;
}
}
a) Your calcWaypoints function takes a parameter that is a list of waystation, and then you ignore that parameter and calculate all points for all pods. This is creating one big array of all points, starting with the first pod's points, then the 2nd's, etc. This is why it starts with the first, then the 2nd then the third.
function calcWaypoints(nextArray) {
var frams = 100;
var waypoints = [];
for (var i = 1; i < nextArray.length; i++) {
var pt0 = nextArray[i - 1];
var pt1 = nextArray[i];
var dx = pt1[0] - pt0[0];
var dy = pt1[1] - pt0[1];
for (var j = 0; j < frams; j++) {
var x = pt0[0] + dx * j / frams //+ dxOff;
var y = pt0[1] + dy * j / frams //+ dyOff;
waypoints.push({
x: x,
y: y
});
};
}
return waypoints;
}
b) This has the same cause as the previous, because you are using the same points for each pod, the pods are written over one another. In addition, you are clearing the canvas between each point. This clear should be moved out of the loop. ctx3.clearRect(0, 0, layer3.width, layer3.height);
c) For some reason you are re-calculating all points with every call to animate. You should calculate them all upfront once and then not in animate. You are also creating way too many calls to requestAnimationFrame in your loop.
I hacked together a few of the above changes and it is working. You should still clean it up a lot more:
layer1 = document.getElementById('layer1');
ctx1 = layer1.getContext('2d');
layer2 = document.getElementById('layer2');
ctx2 = layer2.getContext('2d');
layer3 = document.getElementById('layer3');
ctx3 = layer3.getContext('2d');
window.requestAnimFrame = (function (callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
//STATIONS*************************************
var station = [
['A', 150, 100],
['B', 300, 100],
['C', 200, 175],
['D', 100, 250],
['E', 300, 250],
['G', 400, 250],
['F', 350, 200],
['Airport', 500, 200],
['Central', 500, 350]
];
function draw2() {
for (var i = 0; i < station.length; i++) {
var radius = 10;
ctx2.beginPath();
ctx2.arc(station[i][1], station[i][2], radius, 0, 2 * Math.PI);
ctx2.stroke();
ctx2.fillStyle = 'yellow';
ctx2.fill();
//ADD STATION LETTERS
ctx2.font = '10pt Calibri';
ctx2.fillStyle = 'black';
ctx2.textAlign = 'center';
ctx2.fillText(station[i][0], station[i][1], (station[i][2]) + 4);
}
}
draw2();
//END STATIONS*************************************
//START LINES**************************************
var lineArray = [
['A', 'B', ],
['B', 'A', 'C'],
['C', 'B', 'D', 'E'],
['D', 'C', 'E'],
['E', 'D', 'F', 'G', 'Central'],
['F', 'E', 'G'],
['G', 'F', 'E', 'Airport'],
['Airport', 'G', 'Central'],
['Central', 'E', 'Airport']
];
function drawLines() {
for (m = 0; m < lineArray.length; m++) {
ctx1.lineWidth = 1;
ctx1.beginPath();
for (p = 1; p < lineArray[m].length; p++) {
var startStat = lineArray[m][0];
var stat = lookUp(startStat);
var beginX = station[stat][1];
var beginY = station[stat][2];
ctx1.moveTo(beginX, beginY);
var endStat = lineArray[m][p];
var endSt = lookUp(endStat);
var closeX = station[endSt][1];
var closeY = station[endSt][2];
ctx1.lineTo(closeX, closeY);
}
ctx1.stroke();
}
}
drawLines();
//END LINES*************************************
//SHORTEST PATH*********************************
function Graph() {
var neighbors = this.neighbors = {}; // Key = vertex, value = array of neighbors.
this.addEdge = function (u, v) {
if (neighbors[u] === undefined) { // Add the edge u -> v.
neighbors[u] = [];
}
neighbors[u].push(v);
if (neighbors[v] === undefined) { // Also add the edge v -> u in order
neighbors[v] = []; // to implement an undirected graph.
} // For a directed graph, delete
neighbors[v].push(u); // these four lines.
};
return this;
}
function shortestPath(graph, source, target) {
var stationPath = [];
var coordPath = [];
if (source == target) { // Delete these four lines if
print(source); // you want to look for a cycle
return; // when the source is equal to
} // the target.
var queue = [source],
visited = {
source: true
},
predecessor = {},
tail = 0;
while (tail < queue.length) {
var u = queue[tail++], // Pop a vertex off the queue.
neighbors = graph.neighbors[u];
for (var i = 0; i < neighbors.length; ++i) {
var v = neighbors[i];
if (visited[v]) {
continue;
}
visited[v] = true;
if (v === target) { // Check if the path is complete.
var path = [v]; // If so, backtrack through the path.
while (u !== source) {
path.push(u);
u = predecessor[u];
}
path.push(u);
path.reverse();
print(path.join(' → '));
//console.log('Path: ' + path);
for (s = 1; s < path.length; s++) {
stationPath.push(path[s]);
}
//console.log('Waypoints: ' + stationPath);
for (t = 0; t < stationPath.length; t++) {
staCo = lookUp(stationPath[t]);
staCoX = station[staCo][1];
staCoY = station[staCo][2];
coordPath.push([staCoX, staCoY]);
}
//console.log(coordPath);
return coordPath;
}
predecessor[v] = u;
queue.push(v);
}
}
print('there is no path from ' + source + ' to ' + target);
}
function print(s) { // A quick and dirty way to display output.
s = s || '';
document.getElementById('display').innerHTML += s + '<br>';
}
function findShortestPath(s1, s2) {
var graph = new Graph();
for (w = 0; w < lineArray.length; w++) {
var baseStation = lineArray[w][0];
for (z = 1; z < lineArray[w].length; z++) {
graph.addEdge(baseStation, lineArray[w][z]);
}
}
return (shortestPath(graph, s1, s2));
};
function lookUp(sta) {
//console.log(sta);
for (n = 0; n < station.length; n++) {
if (sta == station[n][0]) {
return n;
break;
}
}
}
//BUILD PODS*************************************
var podArray = [];
function Pod(startX, startY, wayStations, riders, color) {
this.startX = startX;
this.startY = startY;
this.wayStations = wayStations;
this.riders = riders;
this.color = color;
}
var colorArray = ['gold', 'orange', 'red', 'green', 'blue', 'black'];
function randomPass() {
occ = 1 + Math.floor(Math.random() * 6);
return occ;
}
//PROGRAM PODS*********************************************
for (i = 0; i < 3; i++) { //NUMBER OF PODS
//Start Station
var startOff = Math.floor(Math.random() * station.length);
var begSta = station[startOff][0];
var fromX = station[startOff][1];
var fromY = station[startOff][2];
//END STATION
var destNum = Math.floor(Math.random() * station.length);
while (startOff == destNum) {
destNum = Math.floor(Math.random() * station.length);
}
var endSta = station[destNum][0];
function getWayStations(beg, end) {
var fsp = findShortestPath(beg, end);
var nextArray = [];
nextArray.push([fromX, fromY]);
for (var f = 0; f < fsp.length; f++) {
nextArray.push(fsp[f]);
}
return nextArray;
}
//LOAD POD DATA
podArray.push(new Pod(
startX = fromX,
startY = fromY,
wayStations = getWayStations(begSta, endSta),
riders = randomPass(),
color = colorArray[riders - 1]))
}
//END PROGRAM PODS*********************************************
// calc waypoints traveling along nextArray
function calcWaypoints(nextArray) {
var frams = 100;
var waypoints = [];
for (var i = 1; i < nextArray.length; i++) {
var pt0 = nextArray[i - 1];
var pt1 = nextArray[i];
var dx = pt1[0] - pt0[0];
var dy = pt1[1] - pt0[1];
for (var j = 0; j < frams; j++) {
var x = pt0[0] + dx * j / frams //+ dxOff;
var y = pt0[1] + dy * j / frams //+ dyOff;
waypoints.push({
x: x,
y: y
});
};
}
return (waypoints);
}
animate();
var lastTime = 0;
var speed = 1; // higher is slower
function animate(time) {
// return if the desired time hasn't elapsed
if ((time - lastTime) < speed) {
requestAnimationFrame(animate);
return;
}
lastTime = time;
ctx3.clearRect(0, 0, layer3.width, layer3.height);
var callAgain = false;
for (var i = 0; i < podArray.length; i++) {
var aPod = podArray[i];
// calculate incremental points along the path
var points = calcWaypoints(aPod.wayStations);
if (t < points.length - 1) {
callAgain = true;
}
// draw pod from the last waypoint to the current waypoint
if(points.length > t){
ctx3.beginPath();
ctx3.moveTo(aPod.startX, aPod.startY); //(points[t - 1].x, points[t - 1].y);
ctx3.fillStyle = aPod.color;
ctx3.arc(points[t].x, points[t].y, 4, 0, Math.PI * 2, true);
ctx3.fill();
}
}
t++;
if(callAgain) requestAnimationFrame(animate);
}
<canvas id='layer1' style='z-index: 2;
position:absolute;
left:0px;
top:0px;
' height='600px' width='1000'>This text is displayed if your browser does not support HTML5 Canvas.</canvas>
<canvas id='layer2' style='z-index: 3;
position:absolute;
left:0px;
top:0px;
' height='600px' width='1000'>This text is displayed if your browser does not support HTML5 Canvas.</canvas>
<canvas id='layer3' style='z-index: 1;
position:absolute;
left:0px;
top:0px;
' height='600px' width='1000'>This text is displayed if your browser does not support HTML5 Canvas.</canvas>
<div id="display">
</id>

Categories

Resources