How to fit an image fully into HTML5 canvas? - javascript

I have this code of a sliding puzzle game.
When I use exactly 500x500 pixel image everything works fine but when I insert a large image, only the left upper corner of the image is visible and only this part is devided into 16 puzzle parts. How do I fit the whole image on the canvas?
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<style>
.picture {
border: 1px solid black;
}
</style>
</head>
<body>
<div id="title">
<h2>Sliding Puzzle</h2>
</div>
<div id="slider">
<form>
<label>Easy</label>
<input type="range" id="scale" value="4" min="3" max="5" step="1">
<label>Hard</label>
</form>
<br>
</div>
<div id="main" class="main">
<canvas id="puzzle" width="500px" height="500px"></canvas>
</div>
<script>
var context = document.getElementById('puzzle').getContext('2d');
var img = new Image();
img.src = 'http://www.antilimit.com/nature/i/37.jpg';
img.addEventListener('load', drawTiles, false);
var boardSize = document.getElementById('puzzle').width;
var tileCount = 4
var tileSize = boardSize / tileCount;
var clickLoc = new Object;
clickLoc.x = 0;
clickLoc.y = 0;
var emptyLoc = new Object;
emptyLoc.x = 0;
emptyLoc.y = 0;
var solved = false;
var boardParts;
setBoard();
document.getElementById('scale').onchange = function() {
tileCount = this.value;
tileSize = boardSize / tileCount;
setBoard();
drawTiles();
};
document.getElementById('puzzle').onclick = function(e) {
clickLoc.x = Math.floor((e.pageX - this.offsetLeft) / tileSize);
clickLoc.y = Math.floor((e.pageY - this.offsetTop) / tileSize);
if (distance(clickLoc.x, clickLoc.y, emptyLoc.x, emptyLoc.y) == 1) {
slideTile(emptyLoc, clickLoc);
drawTiles();
}
if (solved) {
setTimeout(function() {alert("You solved it!");}, 500);
}
};
function setBoard() {
boardParts = new Array(tileCount);
for (var i = 0; i < tileCount; ++i) {
boardParts[i] = new Array(tileCount);
for (var j = 0; j < tileCount; ++j) {
boardParts[i][j] = new Object;
boardParts[i][j].x = (tileCount - 1) - i;
boardParts[i][j].y = (tileCount - 1) - j;
}
}
emptyLoc.x = boardParts[tileCount - 1][tileCount - 1].x;
emptyLoc.y = boardParts[tileCount - 1][tileCount - 1].y;
solved = false;
}
function drawTiles() {
context.clearRect ( 0 , 0 , boardSize , boardSize );
for (var i = 0; i < tileCount; ++i) {
for (var j = 0; j < tileCount; ++j) {
var x = boardParts[i][j].x;
var y = boardParts[i][j].y;
if(i != emptyLoc.x || j != emptyLoc.y || solved == true) {
context.drawImage(img, x * tileSize, y * tileSize, tileSize, tileSize,
i * tileSize, j * tileSize, tileSize, tileSize);
}
}
}
}
function distance(x1, y1, x2, y2) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
function slideTile(toLoc, fromLoc) {
if (!solved) {
boardParts[toLoc.x][toLoc.y].x = boardParts[fromLoc.x][fromLoc.y].x;
boardParts[toLoc.x][toLoc.y].y = boardParts[fromLoc.x][fromLoc.y].y;
boardParts[fromLoc.x][fromLoc.y].x = tileCount - 1;
boardParts[fromLoc.x][fromLoc.y].y = tileCount - 1;
toLoc.x = fromLoc.x;
toLoc.y = fromLoc.y;
checkSolved();
}
}
function checkSolved() {
var flag = true;
for (var i = 0; i < tileCount; ++i) {
for (var j = 0; j < tileCount; ++j) {
if (boardParts[i][j].x != i || boardParts[i][j].y != j) {
flag = false;
}
}
}
solved = flag;
}
</script>
</body></html>

The image you are using in your example is 1000px by 1000px, so dividing this into sixteen tiles requires each the image to be split into sixteen sections where each section will be 250px by 250px. Currently you are using sixteen sections each of which is 125px by 125px and so your are only copying the top left quarter of the image.
Take a rectangular image of width W and height H, then starting at the top left corner you can take a square image of imgSize = W if W<=H or of imgSize = H if H
You find the scaling factor imgScale = imgSize/500
In your example imgSize = 1000 and so imgScale = 2 so you want to copy sections twice the width and height of the tiles
Change the drawTiles function to
function drawTiles() {
var imgSize=Math.min(img.width,img.height);
var imgScale=imgSize/500;
context.clearRect ( 0 , 0 , boardSize , boardSize );
for (var i = 0; i < tileCount; ++i) {
for (var j = 0; j < tileCount; ++j) {
var x = boardParts[i][j].x;
var y = boardParts[i][j].y;
if(i != emptyLoc.x || j != emptyLoc.y || solved == true) {
context.drawImage(img, x * tileSize*imgScale, y * tileSize*imgScale, tileSize*imgScale, tileSize*imgScale,
i * tileSize, j * tileSize, tileSize, tileSize);
}
}
}
}
Here is a jsfiddle

Related

Need help optimizing my Javascript for canvas rendering

I've written this quick script for random moving lines for the background for my portfolio. It works smoothly alone but when I start working with other CSS animations and stuff, there's a frame drop at the beginning (later it runs smooth). At least on my PC, struggles on low-end PC.
Some tips to optimize it would be helpful.
Here's my code:
/*Random Line Render Script aka Mini Browser Crasher */
/*XD Can't Revise This Script. Rofl Drains Memory. May crash low-end pc :V */
var c = document.getElementById("graph");
var dimension = [document.documentElement.clientWidth, document.documentElement.clientHeight];
c.width = dimension[0];
var ctx = c.getContext("2d");
var ctx2 = c.getContext("2d");
var posx = [100, 200, 150, 100, 0];
var posy = [100, 200, 300, 100, -100];
var posx2 = [600, 400, 200, 600];
var posy2 = [500, 200, 100, 150, 500, 500];
var posx3 = [];
var posy3 = [];
/*Generate random values for array( random starting point ) */
for (var i = 0; i < 2; i++) {
posx2.push(500 + Math.round(Math.random() * 700));
posy2.push(Math.round(Math.random() * 900));
}
for (var i = 0; i < 5; i++) {
posx3.push(1000 + Math.round(Math.random() * 300));
posy3.push(0 + Math.round(Math.random() * 1000));
}
var posx_len = posx.length;
var posx2_len = posx2.length;
var posx3_len = posx3.length;
var xa, ya;
var opa = 1;
var amount = 0.01;
var sinang = 0;
var distance1 = 0;
var distance2 = 0;
document.body.addEventListener('mousemove', (function(event) {
xa = event.clientX;
ya = event.clientY;
}));
/*Render Lines */
function draw() {
ctx.clearRect(0, 0, 10000, 10000);
ctx.beginPath();
ctx.moveTo(posx[0], posy[0]);
for (var i = 0; i < posx_len; i++) {
ctx.lineTo(posx[i], posy[i]);
ctx.strokeStyle = 'rgba(255,255,255,' + opa + ')';
ctx.stroke();
ctx.arc(posx[i], posy[i], 5, 0, 2 * Math.PI, false);
}
if (opa > 1) {
amount = -0.01 * Math.random();
}
if (opa < 0) {
amount = 0.01 * Math.random();
}
opa = opa + amount;
ctx.moveTo(posx2[0], posy2[0]);
for (var i = 0; i < posx2_len; i++) {
ctx.lineTo(posx2[i], posy2[i]);
ctx.strokeStyle = 'rgba(255,255,255,' + opa + ')';
ctx.stroke();
ctx.arc(posx2[i], posy2[i], 5, 0, 2 * Math.PI, false);
}
ctx.moveTo(posx3[0], posy3[0]);
for (var i = 0; i < posx3_len; i++) {
ctx.lineTo(posx3[i], posy3[i]);
ctx.strokeStyle = 'rgba(255,255,255,' + opa + ')';
ctx.stroke();
ctx.arc(posx3[i], posy3[i], 5, 0, 2 * Math.PI, false);
}
sinang = sinang + 0.01;
/*Frame Render Ends here*/
/*Calculation for next frame*/
for (var i = 0; i < posx_len; i++) {
posx[i] = posx[i] + (Math.cos(sinang) * i) / 2; /* Sin curve for smooth value transition. Smooth assss Butter */
posy[i] = posy[i] + (Math.cos(sinang) * i) / 2;
/* Can't believe Distance Formula is useful ahaha */
distance1 = Math.sqrt(Math.pow((posx[i] - xa), 2) + Math.pow((posy[i] - ya), 2));
if (distance1 <= 500) {
ctx.moveTo(posx[i], posy[i]);
ctx.lineTo(xa, ya);
}
for (var j = 0; j < posx2_len; j++) {
distance12 = Math.sqrt(Math.pow((posx[i] - posx2[j]), 2) + Math.pow((posy[i] - posy2[j]), 2));
if (distance12 <= 500) {
ctx.moveTo(posx[i], posy[i]);
ctx.lineTo(posx2[j], posy2[j]);
}
}
for (var j = 0; j < posx3_len; j++) {
distance13 = Math.sqrt(Math.pow((posx[i] - posx3[j]), 2) + Math.pow((posy[i] - posy3[j]), 2));
if (distance13 <= 500) {
ctx.moveTo(posx[i], posy[i]);
ctx.lineTo(posx3[j], posy3[j]);
}
}
}
posx[posx.length - 1] = posx[0];
posy[posy.length - 1] = posy[0];
/*Repeat Above Steps. Should have done this in Multi-dimensional array. Ugh I feel sad now*/
for (var i = 0; i < posx2_len; i++) {
posx2[i] = posx2[i] + (Math.sin(sinang) * i) / 2;
posy2[i] = posy2[i] - (Math.sin(sinang) * i) / 2;
distance2 = Math.sqrt(Math.pow((posx2[i] - xa), 2) + Math.pow((posy2[i] - ya), 2));
if (distance2 <= 500) {
ctx.moveTo(posx2[i], posy2[i]);
ctx.lineTo(xa, ya);
}
for (var j = 0; j < posx3_len; j++) {
distance22 = Math.sqrt(Math.pow((posx2[i] - posx3[j]), 2) + Math.pow((posy2[i] - posy3[j]), 2));
if (distance22 <= 500) {
ctx.moveTo(posx2[i], posy2[i]);
ctx.lineTo(posx3[j], posy3[j]);
}
}
}
posx2[posx2.length - 1] = posx2[0];
posy2[posy2.length - 1] = posy2[0];
for (var i = 0; i < posx3_len; i++) {
posx3[i] = posx3[i] - (Math.sin(sinang) * i) / 1.2;
posy3[i] = posy3[i] - (Math.sin(sinang) * i) / 1.2;
distance2 = Math.sqrt(Math.pow((posx3[i] - xa), 2) + Math.pow((posy3[i] - ya), 2));
if (distance2 <= 500) {
ctx.moveTo(posx3[i], posy3[i]);
ctx.lineTo(xa, ya);
}
}
posx3[posx3.length - 1] = posx3[0];
posy3[posy3.length - 1] = posy3[0];
ctx.restore();
ctx.stroke();
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
body {
background: #1f1f1f;
}
<canvas height="1080px" width="1100px" id="graph">
</canvas>
So, what I've done is used square colliders instead of circular(distance formula) and it has faster runtime now. (not much but still)
<html>
<head>
</head>
<style>
body{
background: #1f1f1f;
}
canvas{
}
</style>
<body >
<canvas height="1080px" width="1100px" id="graph">
</canvas>
</body>
<script>
/*Random Line Render Script aka Mini Browser Crasher */
/*XD Can't Revise This Script. Rofl Drains Memory. May crash low-end pc :V */
var c = document.getElementById("graph");
var dimension = [document.documentElement.clientWidth, document.documentElement.clientHeight];
c.width = dimension[0];
var ctx = c.getContext("2d");
var posx = [100,200,150,100,0];
var posy = [100,200,300,100,-100];
var posx2 = [600,400,200,600];
var posy2 = [500,200,100,150,500,500];
var posx3 = [];
var posy3 = [];
/*Generate random values for array( random starting point ) */
for(var i=0; i<2;i++){
posx2.push(500+Math.round(Math.random()*700));
posy2.push(Math.round(Math.random()*900));
}
for(var i=0; i<5;i++){
posx3.push(1000+Math.round(Math.random()*300));
posy3.push(0+Math.round(Math.random()*1000));
}
var posx_len = posx.length;
var posx2_len = posx2.length;
var posx3_len = posx3.length;
var xa,ya;
var opa =1;
var amount = 0.01;
var sinang = 0;
var distance1 = 0;
var distance2 = 0;
var t1, t2;
document.body.addEventListener('mousemove', (function (event) {
xa = event.clientX;
ya = event.clientY;
}));
/*Render Lines */
function draw(){
t1 =performance.now();
ctx.clearRect(0, 0, 1920,1080);
ctx.beginPath();
ctx.moveTo(posx[0], posy[0]);
for(var i= 0; i<posx_len;i++){
ctx.lineTo(posx[i], posy[i]);
ctx.arc(posx[i],posy[i], 5, 0, 2 * Math.PI, false);
}
if(opa>1){
amount = -0.01*Math.random();
}
if(opa<0){
amount =0.01*Math.random();
}
opa =opa +amount;
ctx.moveTo(posx2[0], posy2[0]);
for(var i = 0; i<posx2_len;i++){
ctx.lineTo(posx2[i], posy2[i]);
ctx.arc(posx2[i],posy2[i], 5, 0, 2 * Math.PI, false);
}
ctx.moveTo(posx3[0], posy3[0]);
for(var i = 0; i<posx3_len;i++){
ctx.lineTo(posx3[i], posy3[i]);
ctx.arc(posx3[i],posy3[i], 5, 0, 2 * Math.PI, false);
}
sinang = sinang+0.01;
/*Frame Render Ends here*/
/*Calculation for next frame*/
for(var i = 0;i<posx_len;i++){
posx[i] = posx[i]+ (Math.cos(sinang)*i)/2;/* Sin curve for smooth value transition. Smooth assss Butter */
posy[i] = posy[i]+ (Math.cos(sinang)*i)/2;
/* Can't believe Distance Formula is useful ahaha */
if(Math.abs(posx[i]-xa)<500 && Math.abs(posy[i]-ya)<500){
ctx.moveTo(posx[i],posy[i]);
ctx.lineTo(xa, ya);
}
for(var j = 0;j<posx2_len;j++){
if(Math.abs(posx[i]-posx2[j])<500 && Math.abs(posy[i]-posy2[j])<500){
ctx.moveTo(posx[i],posy[i]);
ctx.lineTo(posx2[j], posy2[j]);
}
}
for(var j = 0;j<posx3_len;j++){
if(Math.abs(posx[i]-posx3[j])<500 && Math.abs(posy[i]-posy3[j])<500){
ctx.moveTo(posx[i],posy[i]);
ctx.lineTo(posx3[j], posy3[j]);
}
}
}
posx[posx.length-1]=posx[0];
posy[posy.length-1] = posy[0];
/*Repeat Above Steps. Should have done this in Multi-dimensional array. Ugh I feel sad now*/
for(var i = 0;i<posx2_len;i++){
posx2[i] = posx2[i]+ (Math.sin(sinang)*i)/2;
posy2[i] = posy2[i]-(Math.sin(sinang)*i)/2;
if(Math.abs(posx2[i]-xa)<500 && Math.abs(posy2[i]-ya)<500){
ctx.moveTo(posx2[i],posy2[i]);
ctx.lineTo(xa, ya);
}
for(var j = 0;j<posx3_len;j++){
if(Math.abs(posx2[i]-posx3[j])<500 && Math.abs(posy2[i]-posy3[j])<500){
ctx.moveTo(posx2[i],posy2[i]);
ctx.lineTo(posx3[j], posy3[j]);
}
}
}
posx2[posx2.length-1]=posx2[0];
posy2[posy2.length-1] = posy2[0];
for(var i = 0;i<posx3_len;i++){
posx3[i] = posx3[i]- (Math.sin(sinang)*i)/1.2;
posy3[i] = posy3[i]-(Math.sin(sinang)*i)/1.2;
if(Math.abs(posx3[i]-xa)<500 && Math.abs(posy3[i]-ya)<500){
ctx.moveTo(posx3[i],posy3[i]);
ctx.lineTo(xa, ya);
}
}
posx3[posx3.length-1]=posx3[0];
posy3[posy3.length-1] = posy3[0];
ctx.restore();
ctx.strokeStyle = 'rgba(255,255,255,'+opa+')';
ctx.stroke();
window.requestAnimationFrame(draw);
t2=performance.now();
console.log(t2-t1);
}
window.requestAnimationFrame(draw);
</script>
</html>

How to have two different text elements moving randomly on a page in Javascript

So I've been reading this post which I found is the closest to what I want. Basically, I want two different text elements (two different words) moving around the page randomly and also bouncing off each other when they come in contact.
I have tried to edit the code so that it's black and have only one text element. However how can I also include a second text element (a different word) moving randomly as well?
Also how can I change it to Roboto Mono font?
// Return random RGB color string:
function randomColor() {
var hex = Math.floor(Math.random() * 0x1000000).toString(16);
return "#" + ("000000" + hex).slice(0);
}
// Poor man's box physics update for time step dt:
function doPhysics(boxes, width, height, dt) {
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Update positions:
box.x += box.dx * dt;
box.y += box.dy * dt;
// Handle boundary collisions:
if (box.x < 0) {
box.x = 0;
box.dx = -box.dx;
} else if (box.x + box.width > width) {
box.x = width - box.width;
box.dx = -box.dx;
}
if (box.y < 0) {
box.y = 0;
box.dy = -box.dy;
} else if (box.y + box.height > height) {
box.y = height - box.height;
box.dy = -box.dy;
}
}
// Handle box collisions:
for (let i = 0; i < boxes.length; i++) {
for (let j = i + 1; j < boxes.length; j++) {
var box1 = boxes[i];
var box2 = boxes[j];
var dx = Math.abs(box1.x - box2.x);
var dy = Math.abs(box1.y - box2.y);
// Check for overlap:
if (2 * dx < (box1.width + box2.width ) &&
2 * dy < (box1.height + box2.height)) {
// Swap dx if moving towards each other:
if ((box1.x > box2.x) == (box1.dx < box2.dx)) {
var swap = box1.dx;
box1.dx = box2.dx;
box2.dx = swap;
}
// Swap dy if moving towards each other:
if ((box1.y > box2.y) == (box1.dy < box2.dy)) {
var swap = box1.dy;
box1.dy = box2.dy;
box2.dy = swap;
}
}
}
}
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// Initialize random boxes:
var boxes = [];
for (var i = 0; i < 1; i++) {
var box = {
x: Math.floor(Math.random() * canvas.width),
y: Math.floor(Math.random() * canvas.height),
width: 50,
height: 20,
dx: (Math.random() - 0.5) * 0.3,
dy: (Math.random() - 0.5) * 0.3
};
boxes.push(box);
}
// Initialize random color and set up interval:
var color = randomColor();
setInterval(function() {
color = randomColor();
}, 450);
// Update physics at fixed rate:
var last = performance.now();
setInterval(function(time) {
var now = performance.now();
doPhysics(boxes, canvas.width, canvas.height, now - last);
last = now;
}, 50);
// Draw animation frames at optimal frame rate:
function draw(now) {
context.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Interpolate position:
var x = box.x + box.dx * (now - last);
var y = box.y + box.dy * (now - last);
context.beginPath();
context.fillStyle = color;
context.font = "40px 'Roboto Mono'";
context.textBaseline = "hanging";
context.fillText("motion", x, y);
context.closePath();
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
<!DOCTYPE html>
<html>
<body>
<head>
<link rel="stylesheet" href="test.css">
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
</head>
<canvas id="canvas"></canvas>
<script src="test.js"></script>
</body>
</html>
Multiple text boxes is easy :
Change the initialize boxes part to include more than one.
// Initialize random boxes:
var boxes = [];
var boxCount = 2
for (var i = 0; i < boxCount; i++) {
Second your random color is flawed , try this :
function randomColor() {
var hex = Math.floor(Math.random() * 0x1000000).toString(16);
return "#" + ("000000" + hex).substr(hex.length);
}
I don't think that roboto is available in the canvas element. (I could be wrong)
monospace is available though.
Edit
If you want different words you could use another array for them:
var words = ['motion','designer']
for (var i = 0; i < words.length; i++) {
as you create boxes use the words array.
You can't hard code the size of the box if you want have the words to collide. Height should be at least the font height 40px and if you want to get the width of the text box there is a context helping function :
box.width = context.measureText(words[i]).width;
See the snippet for usage :
// Return random RGB color string:
function randomColor() {
var hex = Math.floor(Math.random() * 0x1000000).toString(16);
return "#" + ("000000" + hex).substr(hex.length);
}
// Poor man's box physics update for time step dt:
function doPhysics(boxes, width, height, dt) {
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Update positions:
box.x += box.dx * dt;
box.y += box.dy * dt;
// Handle boundary collisions:
if (box.x < 0) {
box.x = 0;
box.dx = -box.dx;
} else if (box.x + box.width > width) {
box.x = width - box.width;
box.dx = -box.dx;
}
if (box.y < 0) {
box.y = 0;
box.dy = -box.dy;
} else if (box.y + box.height > height) {
box.y = height - box.height;
box.dy = -box.dy;
}
}
// Handle box collisions:
for (let i = 0; i < boxes.length; i++) {
for (let j = i + 1; j < boxes.length; j++) {
var box1 = boxes[i];
var box2 = boxes[j];
var dx = Math.abs(box1.x - box2.x);
var dy = Math.abs(box1.y - box2.y);
// Check for overlap:
if (2 * dx < (box1.width + box2.width ) &&
2 * dy < (box1.height + box2.height)) {
// Swap dx if moving towards each other:
if ((box1.x > box2.x) == (box1.dx < box2.dx)) {
var swap = box1.dx;
box1.dx = box2.dx;
box2.dx = swap;
}
// Swap dy if moving towards each other:
if ((box1.y > box2.y) == (box1.dy < box2.dy)) {
var swap = box1.dy;
box1.dy = box2.dy;
box2.dy = swap;
}
}
}
}
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// Initialize random boxes:
var boxes = [];
var words = ["designer","motion","hi"]
for (var i = 0; i < words.length; i++) {
var box = {
x: Math.floor(Math.random() * canvas.width),
y: Math.floor(Math.random() * canvas.height),
width: 50, // Will be dynamic
height: 42,
dx: (Math.random() - 0.5) * 0.3,
dy: (Math.random() - 0.5) * 0.3
};
boxes.push(box);
}
// Initialize random color and set up interval:
var color = randomColor();
setInterval(function() {
color = randomColor();
}, 450);
// Update physics at fixed rate:
var last = performance.now();
setInterval(function(time) {
var now = performance.now();
doPhysics(boxes, canvas.width, canvas.height, now - last);
last = now;
}, 50);
// Draw animation frames at optimal frame rate:
function draw(now) {
context.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Interpolate position:
var x = box.x + box.dx * (now - last);
var y = box.y + box.dy * (now - last);
box.width = context.measureText(words[i]).width;
context.beginPath();
context.fillStyle = color;
context.font = "normal 40px monospace";
context.textBaseline = "hanging";
context.fillText(words[i], x, y);
context.closePath();
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
<!DOCTYPE html>
<html>
<body>
<head>
<link rel="stylesheet" href="test.css">
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
</head>
<canvas id="canvas"></canvas>
<script src="test.js"></script>
</body>
</html>

Cannot set property "color" of undefined

I'm trying to make a simple candyCrush-like game but I keep getting this error and don't know what to do.
Uncaught TypeError: Cannot set property 'color' of undefined
at testForClick (numbercrunch2.html:50)
at update (numbercrunch2.html:62)
To recreate the error just put some numbers into the text boxes, hit the starting button and click on a random tile.
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var tilesX = 0,
tilesY = 0,
tilesWidthX, tilesWidthY, space = 3;
var numb = [];
var mousepos = {
x: 0,
y: 0
},
click = false;
function init(rows, cols) {
tilesWidthX = (canvas.height - rows * 3) / rows;
tilesWidthY = (canvas.height - cols * 3) / cols;
tilesX = rows;
tilesY = cols;
for (var i = 0; i < tilesX; i++) {
numb[i] = [];
for (var j = 0; j < tilesY; j++) {
numb[i][j] = {
val: 1 + Math.round(Math.random() * 6),
color: "grey"
};
ctx.beginPath();
ctx.fillStyle = numb[i][j].color;
ctx.fillRect(space + i * (tilesWidthY + space), space + j * (tilesWidthX + space), tilesWidthY, tilesWidthX);
ctx.closePath();
ctx.beginPath();
ctx.fillStyle = "white";
ctx.font = "20px Arial";
ctx.fillText(numb[i][j].val, 15 + i * (tilesWidthY + space), 30 + j * (tilesWidthY + space), 50);
ctx.closePath();
}
}
}
function testForClick(x, y) {
if (space + x * (tilesWidthX + space) <= mousepos.x && space + y * (tilesWidthY + space) <= mousepos.y && (x + 1) * (tilesWidthX + space) >= mousepos.x && (y + 1) * (tilesWidthY + space) >= mousepos.y) {
numb[x][y].color = "green"; //line 50
}
}
function drawTile(x, y) {
}
function update() {
for (var i = 0; i < 9; i++) {
numb[i] = [];
for (var j = 0; j < 9; j++) {
testForClick(i, j);
drawTile(i, j);
}
}
}
setInterval(update, 300);
function mouseposition(e) {
}
document.getElementById("canvas").addEventListener("click", function(e) {
mousepos.x = e.clientX;
mousepos.y = e.clientY;
}, false);
<canvas id="canvas" height="600" width="600"></canvas>
<form name="formname">
Rows: <input type="text" name="rows"> Columns: <input type="text" name="columns">
<input type="button" value="Start" onClick="init(this.form.rows.value, this.form.columns.value)">
</form>
<div id="t"></div>
Every time you run update() you literally update your numb variable by replacing the previous object for an empty Array object thus when calling testForClick() it has no object in it throwing the error.
function update() {
for (var i = 0;i < 9;i++) {
numb[i] = [];
for ( var j = 0;j < 9;j++) {
testForClick(i,j);
drawTile(i,j);
}
}
} setInterval(update,300);
Should be:
function update()
{
for (var i = 0; i < 9; i++)
{
for (var j = 0; j < 9; j++)
{
testForClick(i, j);
drawTile(i, j);
}
}
};
I created a snippet showing how commenting (or removing) the numb[i] = []; line from update() will make it work.
I updated your script:
// Also, I renamed your 'c' var to 'canvas' because in your init() you are calling it canvas and not 'c'.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var tilesX = 0, tilesY = 0, tilesWidthX, tilesWidthY, space = 3;
var numb = [];
var mousepos = {x: 0, y: 0}, click = false;
function init(rows, cols)
{
tilesWidthX = (canvas.height - rows * 3) / rows;
tilesWidthY = (canvas.height - cols * 3) / cols;
tilesX = rows;
tilesY = cols;
for (var i = 0; i < tilesX; i++)
{
numb[i] = [];
for (var j = 0; j < tilesY; j++)
{
numb[i][j] = {
val: 1 + Math.round(Math.random() * 6),
color: 'grey'
};
ctx.beginPath();
ctx.fillStyle = numb[i][j].color;
ctx.fillRect(space + i * (tilesWidthY + space), space + j * (tilesWidthX + space), tilesWidthY, tilesWidthX);
ctx.closePath();
ctx.beginPath();
ctx.fillStyle = 'white';
ctx.font = '20px Arial';
ctx.fillText(numb[i][j].val, 15 + i * (tilesWidthY + space), 30 + j * (tilesWidthY + space), 50);
ctx.closePath();
}
}
}
function testForClick(x, y)
{
// Now your 'numb' variable hasn't been changed.
console.log('testForClick', numb);
if (space + x * (tilesWidthX + space) <= mousepos.x && space + y * (tilesWidthY + space) <= mousepos.y && (x+1) * (tilesWidthX + space) >= mousepos.x && (y+1) * (tilesWidthY + space) >= mousepos.y)
{
numb[x][y].color = 'green'; //line 50
}
}
function drawTile(x, y)
{
}
function update()
{
for (var i = 0; i < 9; i++)
{
// Every time you update you replace your object with 'val' and 'color' for an empty array.
// that is the reason why every time you call testForClick you have no object in it.
// Just comment/delete this line so your object never disappears.
// numb[i] = [];
for (var j = 0; j < 9; j++)
{
testForClick(i, j);
drawTile(i, j);
}
}
};
// Changed the interval because I didn't have enough time to enter the rows and columns.
setInterval(update, 5000);
function mouseposition(e)
{
}
document.getElementById('canvas').addEventListener('click', function(e){
mousepos.x = e.clientX;
mousepos.y = e.clientY;
}, false);
SNIPPET:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var tilesX = 0, tilesY = 0, tilesWidthX, tilesWidthY, space = 3;
var numb = [];
var mousepos = {x: 0, y: 0}, click = false;
function init(rows, cols)
{
tilesWidthX = (canvas.height - rows * 3) / rows;
tilesWidthY = (canvas.height - cols * 3) / cols;
tilesX = rows;
tilesY = cols;
for (var i = 0; i < tilesX; i++)
{
numb[i] = [];
for (var j = 0; j < tilesY; j++)
{
numb[i][j] = {
val: 1 + Math.round(Math.random() * 6),
color: 'grey'
};
ctx.beginPath();
ctx.fillStyle = numb[i][j].color;
ctx.fillRect(space + i * (tilesWidthY + space), space + j * (tilesWidthX + space), tilesWidthY, tilesWidthX);
ctx.closePath();
ctx.beginPath();
ctx.fillStyle = 'white';
ctx.font = '20px Arial';
ctx.fillText(numb[i][j].val, 15 + i * (tilesWidthY + space), 30 + j * (tilesWidthY + space), 50);
ctx.closePath();
}
}
}
function testForClick(x, y)
{
// Now your 'numb' variable hasn't been changed.
console.log('testForClick', numb);
if (space + x * (tilesWidthX + space) <= mousepos.x && space + y * (tilesWidthY + space) <= mousepos.y && (x+1) * (tilesWidthX + space) >= mousepos.x && (y+1) * (tilesWidthY + space) >= mousepos.y)
{
numb[x][y].color = 'green'; //line 50
}
}
function drawTile(x, y)
{
}
function update()
{
for (var i = 0; i < 9; i++)
{
for (var j = 0; j < 9; j++)
{
testForClick(i, j);
drawTile(i, j);
}
}
};
setInterval(update, 5000);
function mouseposition(e)
{
}
document.getElementById('canvas').addEventListener('click', function(e){
mousepos.x = e.clientX;
mousepos.y = e.clientY;
}, false);
<!DOCTYPE html>
<html>
<head>
<title>numbercrunch 2</title>
<meta charset="utf-8">
<style>
canvas {
border: 2px solid black;
}
</style>
</head>
<body>
<canvas id="canvas" height="600" width="600"></canvas>
<form name="formname">
Rows: <input type="text" name="rows">
Columns: <input type="text" name="columns">
<input type="button" value="Start" onClick="init(this.form.rows.value, this.form.columns.value)">
</form>
<div id="t"></div>
</body>
</html>

Sliding puzzle (javascript & HTML5) - need to animate moving elements

I've got a sliding puzzle where an image is split into a grid and you can move squares into an empty space. I'd like for this to animate, so to actually slide into the empty space, not just appear there. Here's the codepen I'm working from: http://codepen.io/akshivictor/pen/jPPypW and the javascript below.
var context = document.getElementById('puzzle').getContext('2d');
var img = new Image();
img.src = 'http://i.imgur.com/H1la3ZG.png?1';
img.addEventListener('load', drawTiles, false);
var boardSize = document.getElementById('puzzle').width;
var tileCount =3;
var tileSize = boardSize / tileCount;
var clickLoc = new Object;
clickLoc.x = 0;
clickLoc.y = 0;
var emptyLoc = new Object;
emptyLoc.x = 0;
emptyLoc.y = 0;
var solved = false;
var boardParts;
setBoard();
document.getElementById('scale').onchange = function() {
tileCount = this.value;
tileSize = boardSize / tileCount;
setBoard();
drawTiles();
};
document.getElementById('puzzle').onclick = function(e) {
clickLoc.x = Math.floor((e.pageX - this.offsetLeft) / tileSize);
clickLoc.y = Math.floor((e.pageY - this.offsetTop) / tileSize);
if (distance(clickLoc.x, clickLoc.y, emptyLoc.x, emptyLoc.y) == 1) {
slideTile(emptyLoc, clickLoc);
drawTiles();
}
};
function setBoard() {
boardParts = new Array(tileCount);
for (var i = 0; i < tileCount; ++i) {
boardParts[i] = new Array(tileCount);
for (var j = 0; j < tileCount; ++j) {
boardParts[i][j] = new Object;
boardParts[i][j].x = (tileCount - 1) - i;
boardParts[i][j].y = (tileCount - 1) - j;
}
}
emptyLoc.x = boardParts[tileCount - 1][tileCount - 1].x;
emptyLoc.y = boardParts[tileCount - 1][tileCount - 1].y;
solved = false;
}
function drawTiles() {
context.clearRect(0, 0, boardSize, boardSize);
for (var i = 0; i < tileCount; ++i) {
for (var j = 0; j < tileCount; ++j) {
var x = boardParts[i][j].x;
var y = boardParts[i][j].y;
if (i != emptyLoc.x || j != emptyLoc.y || solved == true) {
context.drawImage(img, x * tileSize, y * tileSize, tileSize, tileSize,
i * tileSize, j * tileSize, tileSize, tileSize);
}
}
}
}
function distance(x1, y1, x2, y2) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
function slideTile(toLoc, fromLoc) {
if (!solved) {
boardParts[toLoc.x][toLoc.y].x = boardParts[fromLoc.x][fromLoc.y].x;
boardParts[toLoc.x][toLoc.y].y = boardParts[fromLoc.x][fromLoc.y].y;
boardParts[fromLoc.x][fromLoc.y].x = tileCount - 1;
boardParts[fromLoc.x][fromLoc.y].y = tileCount - 1;
toLoc.x = fromLoc.x;
toLoc.y = fromLoc.y;
checkSolved();
}
}
function checkSolved() {
var flag = true;
for (var i = 0; i < tileCount; ++i) {
for (var j = 0; j < tileCount; ++j) {
if (boardParts[i][j].x != i || boardParts[i][j].y != j) {
flag = false;
}
}
}
solved = flag;
}
I've made adaptations from the sliding puzzle in this article http://www.sitepoint.com/image-manipulation-with-html5-canvas-a-sliding-puzzle-2/
The general way to do translational animation is :
animate(object):
while(object.not_in_place())
object.move(small_amount)
sleep(some_time)
draw(object)
repeat
Hence, move your images a small amount (few pixels) in the right direction and repeat that movement fast enough (though with sleeps in between) to get a sliding animation.
In case you need ease-in or ease-out animations, your translation amount will change with the duration.
For repeated calls after a fixed interval, use setTimeout in JavaScript.
More Details
Lets say you are moving from (a, b) to (c, b) (which are coordinates in 2 dimensional Cartesian plane), then the distance moved in x-direction is (c - b) and y-direction is 0.
So, divide the distance (c - b) in equal amounts to say x.
Let the total duration of the animation be T, then divide that into same number of intervals say t.
Hence, your algorithms becomes:
while(object.x != c)
object.x += x
sleep(t)
object.draw()
repeat
Similarly, you can do for the y-direction case. Note that in your game you dont need to move in any arbitrary direction, only either horizontally or vertically which simplifies the implementation.
While, JavaScript has no equivalent sleep() routine, you can use setTimeout like this :
function x() {
id = setTimeout(x, 1000); // x will be called every 1 seconds ( = 1000 ms)
}
clearTimeout(id); // this removes the timeout and stops the repeated function calls

canvas slide animation

Below is my code for a puzzle game that I found online Puzzle game. I have modified the code slightly in order to get it to work with jQuery and everything is working fine.
I would like to be able to animate the tile that is clicked so it slides to the empty tile instead of being drawn there right away.
I'm fairly certain that its the draw function that I need to change but im not sure how. I was hoping that you guys could steer me in the right direction or provide me with some hints.
Thank you in advance for taking the time to read my question.
var context = puzzle.getContext('2d');
var img = new Image();
img.src = 'images/images.jpg';
img.addEventListener('load', drawTiles, false);
var puzzle = $('#puzzle')[0];
var scale = $('#scale')[0];
var boardSize = puzzle.width;
var tileCount = scale.value;
var tileSize = boardSize / tileCount;
var clickLoc = new Object;
clickLoc.x = 0;
clickLoc.y = 0;
var emptyLoc = new Object;
emptyLoc.x = 0;
emptyLoc.y = 0;
var solved = false;
var boardParts = new Object;
setBoard();
scale.onchange = function() {
tileCount = this.value;
tileSize = boardSize / tileCount;
setBoard();
drawTiles();
};
puzzle.onmousemove = function(e) {
clickLoc.x = Math.floor((e.pageX - this.offsetLeft) / tileSize);
clickLoc.y = Math.floor((e.pageY - this.offsetTop) / tileSize);
};
puzzle.onclick = function() {
if (distance(clickLoc.x, clickLoc.y, emptyLoc.x, emptyLoc.y) == 1) {
slideTile(emptyLoc, clickLoc);
drawTiles();
}
if (solved) {
setTimeout(function() {alert("You solved it!");}, 500);
}
};
function setBoard() {
boardParts = new Array(tileCount);
for (var i = 0; i < tileCount; ++i) {
boardParts[i] = new Array(tileCount);
for (var j = 0; j < tileCount; ++j) {
boardParts[i][j] = new Object;
boardParts[i][j].x = (tileCount - 1) - i;
boardParts[i][j].y = (tileCount - 1) - j;
}
}
emptyLoc.x = boardParts[tileCount - 1][tileCount - 1].x;
emptyLoc.y = boardParts[tileCount - 1][tileCount - 1].y;
solved = false;
}
function drawTiles() {
context.clearRect ( 0 , 0 , boardSize , boardSize );
for (var i = 0; i < tileCount; ++i) {
for (var j = 0; j < tileCount; ++j) {
var x = boardParts[i][j].x;
var y = boardParts[i][j].y;
if(i != emptyLoc.x || j != emptyLoc.y || solved == true) {
context.drawImage(img, x * tileSize, y * tileSize, tileSize, tileSize,
i * tileSize, j * tileSize, tileSize, tileSize);
}
}
}
}
function distance(x1, y1, x2, y2) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
function slideTile(toLoc, fromLoc) {
if (!solved) {
boardParts[toLoc.x][toLoc.y].x = boardParts[fromLoc.x][fromLoc.y].x;
boardParts[toLoc.x][toLoc.y].y = boardParts[fromLoc.x][fromLoc.y].y;
boardParts[fromLoc.x][fromLoc.y].x = tileCount -1;
boardParts[fromLoc.x][fromLoc.y].y = tileCount -1;
toLoc.x = fromLoc.x;
toLoc.y = fromLoc.y;
checkSolved();
}
}
function checkSolved() {
var flag = true;
for (var i = 0; i < tileCount; ++i) {
for (var j = 0; j < tileCount; ++j) {
if (boardParts[i][j].x != i || boardParts[i][j].y != j) {
flag = false;
}
}
}
solved = flag;
}
First, you will need to create an update function that is called by setTimeout or setInterval and has access to your puzzle object. This will allow you to control the number of frames drawn per second which will determine how fast your animation appears to be. This update method will want to call drawTiles and probably a modified slideTile.
Next, you will need to modify slideTile to slowly move the tile instead of quickly moving it. You will also probably want to have a flag to prevent users from clicking on another tile when one tile is sliding.
Here is a fairly good starting point for basic canvas animations.

Categories