Why isn't pop method working in my snake game - javascript

So here's is a very brief description on how my snake game works.
For loop is used to draw the snake.Newhead is an object, and after first iteration newhead is now the new element occupying the index 0 since we used .unshift() method to add newhead to the front of our snake array. To avoid having our new element obtain the same position during each iteration of the setInterval loop we add or increment the x coordinate by 20 (which affects the rect in the for loop later since snake[0] will now change ie the x will be 60 instead of 40 and snake[1] has taken the element of the old snake[0]) using snakex.
But what I don't get is why isn't snake.pop removing the last element or object in our array ie removing one rect from the snake
var canvas = document.querySelector("canvas");
var right = document.querySelector("right");
var left = document.querySelector("left");
var up = document.querySelector("left");
var down = document.querySelector("left");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight - 200;
var ctx = canvas.getContext("2d");
width = 20;
height = 20;
var snake = [{
x: 40,
y: 0
}, {
x: 20,
y: 0
}];
function move() {
for (var i = 0; i < snake.length; i++) {
console.log(i);
//lets just say i stands for index number
ctx.strokeStyle = "orange";
ctx.strokeRect(snake[i].x, snake[i].y, width, height);
}
snakex = snake[0].x;
snakey = snake[0].y;
snakex += 20;
var newhead = {
x: snakex,
y: snakey
};
snake.pop();
snake.unshift(newhead);
ctx.clearRect(0, 0, width, height);
}
setInterval(move, 800);
<canvas></canvas>
<button id="right">right</button>
<button id="down">down</button>
<button id="left">left</button>
<button id="up">up</button>

It is popping correctly, the issue is the clearRect call, you pass it width and height but those are set to 20, so only the top 20x20 are getting cleared, so you see al the frames printed on screen.
Working Example: https://jsfiddle.net/vLxmws9d/
Edit: don't know why the SO snippet wasn't running so I used a jsfiddle instead.
var canvas= document.querySelector("canvas");
var right=document.querySelector("right");
var left=document.querySelector("left");
var up=document.querySelector("left");
var down=document.querySelector("left");
canvas.width=window.innerWidth;
canvas.height=window.innerHeight-200;
var ctx= canvas.getContext("2d");
width= 20;
height=20;
var snake=[{x:40, y:0}, {x:20, y:0}];
function move(){
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0; i<snake.length; i++){
//lets just say i stands for index number
ctx.strokeStyle="orange";
ctx.strokeRect(snake[i].x, snake[i].y, width, height);
}
snakex=snake[0].x;
snakey=snake[0].y;
snakex+=20;
var newhead={x:snakex, y:snakey};
snake.unshift(newhead);
snake.pop();
}
setInterval(move, 800);

Related

Why does my triangle on canvas have artifacts? [duplicate]

I'm doing a Pong game in javascript in order to learn making games, and I want to make it object oriented.
I can't get clearRect to work. All it does is draw a line that grows longer.
Here is the relevant code:
function Ball(){
this.radius = 5;
this.Y = 20;
this.X = 25;
this.draw = function() {
ctx.arc(this.X, this.Y, this.radius, 0, Math.PI*2, true);
ctx.fillStyle = '#00ff00';
ctx.fill();
};
}
var ball = new Ball();
function draw(){
player.draw();
ball.draw();
}
function update(){
ctx.clearRect(0, 0, 800, 400);
draw();
ball.X++;
}
I've tried to put the ctx.clearRect part in the draw() and ball.draw() functions and it doesn't work.
I also tried fillRect with white but it gives the same results.
How can I fix this?
Your real problem is you are not closing your circle's path.
Add ctx.beginPath() before you draw the circle.
jsFiddle.
Also, some tips...
Your assets should not be responsible for drawing themselves (their draw() method). Instead, perhaps define their visual properties (is it a circle? radius?) and let your main render function handle canvas specific drawing (this also has the advantage that you can switch your renderer to regular DOM elements or WebGL further down the track easily).
Instead of setInterval(), use requestAnimationFrame(). Support is not that great at the moment so you may want to shim its functionality with setInterval() or the recursive setTimeout() pattern.
Your clearRect() should be passed the dimensions from the canvas element (or have them defined somewhere). Including them in your rendering functions is akin to magic numbers and could lead to a maintenance issue further down the track.
window.onload = function() {
var cvs = document.getElementById('canvas');
var ctx = cvs.getContext('2d');
var cvsW = cvs.Width;
var cvsH = cvs.Height;
var snakeW = 10;
var snakeH = 10;
function drawSnake(x, y) {
ctx.fillStyle = '#FFF';
ctx.fillRect(x*snakeW, y * snakeH, snakeW, snakeH);
ctx.fillStyle = '#000';
ctx.strokeRect(x*snakeW, y * snakeH, snakeW, snakeH);
}
// drawSnake(4, 5)
//create our snake object, it will contain 4 cells in default
var len = 4;
var snake = [];
for(var i = len -1; i >=0; i--) {
snake.push(
{
x: i,
y: 0
}
)
};
function draw() {
ctx.clearRect(0, 0, cvsW, cvsH)
for(var i = 0; i < snake.length; i++) {
var x = snake[i].x;
var y = snake[i].y;
drawSnake(x, y)
}
//snake head
var snakeX = snake[0].x;
var snakeY = snake[0].y;
//remove to last entry (the snake Tail);
snake.pop();
// //create a new head, based on the previous head and the direction;
snakeX++;
let newHead = {
x: snakeX,
y: snakeY
}
snake.unshift(newHead)
}
setInterval(draw, 60);
}
my clearRect is not working, what's the problem and solution?

How can I hover over a shape in canvas and change the color if I have multiple shapes?

I want to be able to hover my mouse over different rectangles and have the rectangle change color when hovered, what I have now works for the last rectangle but the others get cleared. The rectangles are created using a class/constructor, an array, and a loop. Code is below:
/*Variables*/
let canvas = document.querySelector('#canvas'),
ctx = canvas.getContext('2d'),
square;
/*Board Class*/
class Board {
constructor(startX, startY, height, width, angle) {
this.startX = startX;
this.startY = startY;
this.height = height;
this.width = width;
this.angle = angle;
}
drawBoard() {
let canvasWidth = window.innerWidth * .95,
drawWidth = canvasWidth * this.width,
drawHeight = canvasWidth * this.height,
drawStartX = canvasWidth * this.startX,
drawStartY = canvasWidth * this.startY;
square = new Path2D();
ctx.rotate(this.angle * Math.PI / 180);
square.rect(drawStartX, drawStartY, drawHeight, drawWidth);
ctx.fillStyle = 'red';
ctx.fill(square);
}
}
/*Event Listener for changing rectangle color and redrawing*/
canvas.addEventListener('mousemove', function(event) {
if (ctx.isPointInPath(square, event.offsetX, event.offsetY)) {
ctx.fillStyle = 'white';
}
else {
ctx.fillStyle = 'red';
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fill(square);
});
/*Instantiate Array*/
let b = [];
/*Loop to create boards and push to array*/
for(let i = 1; i < 11; i++){
b.push(new Board(.05 * i, .25, .04, .03, 0));
}
/*Function to loop through array and draw boards when page loads*/
function loadFunctions(){
background.draw();
b.forEach(function(board){
board.drawBoard();
})
}
This is my first project with the Canvas API and it's giving me a lot of trouble, normally I could identify the shape by class/id if it where made with a regular HTML element but I'm not sure where to go from here...
I've tried looping through the array that contains the board info but cannot get anything to work. Any help is appreciated!
Thanks
Let's step through your code, to get a better picture of what's going on.
As soon as you move your mouse over the canvas, the mousemove listener gets fired and executes it's associated callback function.
Inside this callback function we'll find this as the very first line:
if (ctx.isPointInPath(square, event.offsetX, event.offsetY))
So this if-statement checks it the current mouse position is inside of square. Well, the big question is: what is square actually?
If we look over your code a bit more, we'll find out that it's a global variable, which gets some value inside the Board class drawBoard() function as:
square = new Path2D();
square.rect(drawStartX, drawStartY, drawHeight, drawWidth);
Apparently it's a Path2D holding the rectangle of one of the bars - but which one actually?
Let's take a look at this function:
for (let i = 0; i < 10; i++) {
b.push(new Board(0.05 * i, 0.25, 0.04, 0.03, 0));
}
and
function loadFunctions() {
b.forEach(function(board) {
board.drawBoard();
})
}
In the first loop, you're populating the array b with ten instances of Board and in the forEach loop, you're calling each Board's drawBoard() function.
What does all this mean? Yes, square will always hold a reference to the bar, which's drawBoard() function has been called the last time - which will always be the last Board in your array.
To summarize: the only bar your checking in the mousemove callback is always the last one in the array.
So:
if (ctx.isPointInPath(square, event.offsetX, event.offsetY)) {
ctx.fillStyle = 'white';
}
else {
ctx.fillStyle = 'red';
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fill(square);
translated to plain english means: if the point is in square's bound, set the fillStyle to red, clear the whole screen and afterwards fill one bar with red.
What you need to do instead is checking the mouse position with every Board instance from the array. It ain't to hard though - just make the Path2D a class variable of Board and inside the callback function loop over the whole array and compare the mouse position with each Board's .square property.
Here's an example (just click on 'Run code snippet'):
let canvas = document.querySelector('#canvas'),
ctx = canvas.getContext('2d');
let b = [];
class Board {
constructor(startX, startY, height, width, angle) {
this.startX = startX;
this.startY = startY;
this.height = height;
this.width = width;
this.angle = angle;
this.square = new Path2D();
}
drawBoard() {
let canvasWidth = window.innerWidth * 0.95,
drawWidth = canvasWidth * this.width,
drawHeight = canvasWidth * this.height,
drawStartX = canvasWidth * this.startX,
drawStartY = canvasWidth * this.startY;
ctx.rotate(this.angle * Math.PI / 180);
this.square.rect(drawStartX, drawStartY, drawHeight, drawWidth);
ctx.fillStyle = 'red';
ctx.fill(this.square);
}
}
canvas.addEventListener('mousemove', function(event) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let currentSquare;
for (let i = 0; i < b.length; i++) {
currentSquare = b[i].square;
if (ctx.isPointInPath(currentSquare, event.offsetX, event.offsetY)) {
ctx.fillStyle = 'white';
} else {
ctx.fillStyle = 'red';
}
ctx.fill(currentSquare);
}
});
for (let i = 0; i < 10; i++) {
b.push(new Board(0.05 * i, 0.25, 0.04, 0.03, 0));
}
function loadFunctions() {
b.forEach(function(board) {
board.drawBoard();
})
}
loadFunctions();
<canvas id="canvas" width=500 height=300></canvas>

Make a shape move up on a canvas

Currently, I have a canvas which is the width and height of your browser. Using this code:
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
var canvas = document.getElementById("canvas");
var width = window.innerWidth;
var height = window.innerHeight;
var circle = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
for(var i = 0; i < numofcirc; i++)
{
name = "circleno" + i;
var name = new Array(3);
name = [height, rndwidth, rndradius, vel]
circles[i] = name;
}
var vel = 2;
var circles = [];
var numofcirc = 1;
var name;
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
}
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Move();
DrawCircle();
I am able to create a circle placed randomly at the bottom of your screen. The bit of the code that isn't working is this:
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Fireworks();
When DrawCircle(); is called, the circle is drawn on the canvas. Then Move(); is called. Becuase it uses requestAnimationFrame the function Move(); repeats over and over again. I want this code to move that circle drawn ealier up by 6, so it looks like the circle moving up.
If I add the circle.translate(0,6); to the DrawCircle(); function and change the DrawCircle(); function to this:
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
requestAnimationFrame(Move);
}
DrawCircle();
then it keeps on drawing rows of circles across the screen which are all separated by 6.
Is there any way I can just make one single circle move up on your screen when it is drawn?
Thank you for you help #HelderSepu !
You should look at examples and build from that...
Here is one simple case:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = canvas.height = 170;
var circles = []
circles.push({color:"red", x:120, y:120, r:15, speed:{x: 0, y: -0.5}})
circles.push({color:"blue", x:80, y:120, r:20, speed:{x: -0.5, y: -2.5}})
circles.push({color:"green", x:40, y:120, r:5, speed:{x: -1.5, y: -1.0}})
function DrawCircle() {
context.clearRect(0, 0, canvas.width, canvas.height);
circles.forEach(function(c) {
c.x += c.speed.x;
c.y += c.speed.y;
context.beginPath();
context.arc(c.x, c.y, c.r, 0, 2 * Math.PI);
context.fillStyle = c.color;
context.fill();
if (c.x + c.r < 0) c.x = canvas.width + c.r
if (c.y + c.r < 0) c.y = canvas.height + c.r
});
window.requestAnimationFrame(DrawCircle);
}
DrawCircle();
<canvas id="canvas"></canvas>
But if you are going to do a lot more animations you should consider using a game engine, there are a lot of great open source ones:
https://github.com/collections/javascript-game-engines
Since you're getting a sequence of circles, it looks like you're not clearing the canvas when a frame is drawn. Simply draw a white rectangle that fills the canvas whenever a new frame is requested, then draw your circle.
The method you provide as an argument to requestAnimationFrame is responsible for drawing a complete image on the canvas which replaces whatever was there during the previous frame.

Adding Image to an Object with Javascript Canvas [duplicate]

I am trying to move an image from the right to the center and I am not sure if this is the best way.
var imgTag = null;
var x = 0;
var y = 0;
var id;
function doCanvas()
{
var canvas = document.getElementById('icanvas');
var ctx = canvas.getContext("2d");
var imgBkg = document.getElementById('imgBkg');
imgTag = document.getElementById('imgTag');
ctx.drawImage(imgBkg, 0, 0);
x = canvas.width;
y = 40;
id = setInterval(moveImg, 0.25);
}
function moveImg()
{
if(x <= 250)
clearInterval(id);
var canvas = document.getElementById('icanvas');
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
var imgBkg = document.getElementById('imgBkg');
ctx.drawImage(imgBkg, 0, 0);
ctx.drawImage(imgTag, x, y);
x = x - 1;
}
Any advice?
This question is 5 years old, but since we now have requestAnimationFrame() method, here's an approach for that using vanilla JavaScript:
var imgTag = new Image(),
canvas = document.getElementById('icanvas'),
ctx = canvas.getContext("2d"),
x = canvas.width,
y = 0;
imgTag.onload = animate;
imgTag.src = "http://i.stack.imgur.com/Rk0DW.png"; // load image
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
ctx.drawImage(imgTag, x, y); // draw image at current position
x -= 4;
if (x > 250) requestAnimationFrame(animate) // loop
}
<canvas id="icanvas" width=640 height=180></canvas>
drawImage() enables to define which part of the source image to draw on target canvas. I would suggest for each moveImg() calculate the previous image position, overwrite the previous image with that part of imgBkg, then draw the new image. Supposedly this will save some computing power.
Here's my answer.
var canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var myImg = new Image();
var myImgPos = {
x: 250,
y: 125,
width: 50,
height: 25
}
function draw() {
myImg.onload = function() {
ctx.drawImage(myImg, myImgPos.x, myImgPos.y, myImgPos.width, myImgPos.height);
}
myImg.src = "https://mario.wiki.gallery/images/thumb/c/cc/NSMBUD_Mariojump.png/1200px-NSMBUD_Mariojump.png";
}
function moveMyImg() {
ctx.clearRect(myImgPos.x, myImgPos.y, myImgPos.x + myImgPos.width, myImgPos.y +
myImgPos.height);
myImgPos.x -= 5;
}
setInterval(draw, 50);
setInterval(moveMyImg, 50);
<canvas id="canvas" class="canvas" width="250" height="150"></canvas>
For lag free animations,i generally use kinetic.js.
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var hexagon = new Kinetic.RegularPolygon({
x: stage.width()/2,
y: stage.height()/2,
sides: 6,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4
});
layer.add(hexagon);
stage.add(layer);
var amplitude = 150;
var period = 2000;
// in ms
var centerX = stage.width()/2;
var anim = new Kinetic.Animation(function(frame) {
hexagon.setX(amplitude * Math.sin(frame.time * 2 * Math.PI / period) + centerX);
}, layer);
anim.start();
Here's the example,if you wanna take a look.
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-animate-position-tutorial/
Why i suggest this is because,setInterval or setTimeout a particular function causes issues when large amount of simultaneous animations take place,but kinetic.Animation deals with framerates more intelligently.
Explaining window.requestAnimationFrame() with an example
In the following snippet I'm using an image for the piece that is going to be animated.
I'll be honest... window.requestAnimationFrame() wasn't easy for me to understand, that is why I coded it as clear and intuitive as possible. So that you may struggle less than I did to get my head around it.
const
canvas = document.getElementById('root'),
btn = document.getElementById('btn'),
ctx = canvas.getContext('2d'),
brickImage = new Image(),
piece = {image: brickImage, x:400, y:70, width:70};
brickImage.src = "https://i.stack.imgur.com/YreH6.png";
// When btn is clicked execute start()
btn.addEventListener('click', start)
function start(){
btn.value = 'animation started'
// Start gameLoop()
brickImage.onload = window.requestAnimationFrame(gameLoop)
}
function gameLoop(){
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Draw at coordinates x and y
ctx.drawImage(piece.image, piece.x, piece.y)
let pieceLeftSidePos = piece.x;
let middlePos = canvas.width/2 - piece.width/2;
// Brick stops when it gets to the middle of the canvas
if(pieceLeftSidePos > middlePos) piece.x -= 2;
window.requestAnimationFrame(gameLoop) // Needed to keep looping
}
<input id="btn" type="button" value="start" />
<p>
<canvas id="root" width="400" style="border:1px solid grey">
A key point
Inside the start() function we have:
brickImage.onload = window.requestAnimationFrame(gameLoop);
This could also be written like: window.requestAnimationFrame(gameLoop);
and it would probably work, but I'm adding the brickImage.onload to make sure that the image has loaded first. If not it could cause some issues.
Note: window.requestAnimationFrame() usually loops at 60 times per second.

Simply canvas animation

I have a simple canvas animation: two rectangles move in two different directions. However, I feel this could be simplified more.
http://jsfiddle.net/tmyie/R5wx8/6/
var canvas = document.getElementById('canvas'),
c = canvas.getContext('2d'),
x = 10,
y = 15,
a = 20,
b = 50;
function move() {
c.clearRect(0, 0, 500, 300);
c.fillRect(0, y, 5, 5),
c.fillRect(b, 5, 15, 15);
x++;
y++;
b++
if (y > canvas.height || x > canvas.width) {
y = 0;
x = 0;
}
}
setInterval(move, 100);
For example, what happens if I wanted to create another three shapes? At the moment, I'd have to create more variables for each coordinate:
x++;
y++;
b++
Is there a way I could turn each rectangle into its own object?
You can certainly turn them into objects, for example:
function Rect(x, y, w, h, dltX, dltY, color) {
var me = this;
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.deltaX = dltX || 0; /// and deltas can be optional
this.deltaY = dltY || 0;
this.color = color || '#000'; /// color is optional
this.update = function(ctx) {
me.x += me.deltaX;
me.y += me.deltaY;
ctx.fillStyle = me.color;
ctx.fillRect(me.x, me.y, me.width, me.height);
}
}
The deltaX and deltaY are how much you want to move the rectangle for each update. If you set these to for example 1 then x and y will be increased with 1 each time update() is called.
Using deltas makes it easy to create bounces (see demo below) by simply reversing the delta value (ie. delta = -delta) as well as things such as acceleration, variate speed, you can feed them through trigonometric functions to have the object move in a specific angle and so forth.
You can used fixed values if you desire but you will discover that deltas are beneficial in the long run (ref. comment: it's actually a very classic method used in for instance the first Pong games :-) ).
Online demo here
Now that we have defined the object we can simply create instances of it and store them in an array:
var rects = [
new Rect(10, 10, 100, 100, 1, -2),
new Rect(100, 1, 50, 50, 2, 1, '#f00'),
...
]
From here it's simply a matter of iterating the array to update each object:
function move() {
ctx.clearRect(0, 0, width, height);
for(var i = 0, r; r = rects[i]; i++) {
/// check any conditions here
r.update(ctx);
}
requestAnimationFrame(move);
}
requestAnimationFrame(move); /// start loop
Here's a slightly simpler version, though in the long term I'd recommend Ken's. In mine the rects are still just property bags, with no behavior on their own.
var canvas = document.getElementById('canvas'),
c = canvas.getContext('2d'),
rects = [{x:0, y:15, w:5, h:5, vx:0, vy:1},
{x:50, y:5, w:15, h:15, vx:1, vy:0}];
function move() {
c.clearRect(0, 0, 500, 300);
for (var i=0; i < rects.length; i++) {
var rect = rects[i];
c.fillRect(rect.x, rect.y, rect.w, rect.h),
rect.x += rect.vx;
rect.y += rect.vy;
}
}
setInterval(move, 100);

Categories

Resources