Snake body rendering (body doesn't follow the head) - javascript

For some practice, I decided to make a snake game. But there's one problem: if you eat a fruit, it won't add a body part to the snake!(l 41 and further) I have a canvas so don't worry about that and I will optimize my code later. I know there are other ways to do it but I did it like this and I need your help to fix the issue. So when you eat a fruit, it is supposed to add a body part but instead it shrinks one and adds one so it stays the same. (exept the first time, then it add one!) I don't know why it works the first time and not for the other times. If you want to test, copy the code and add this HTML line to it:(with html tags!!!)
var ctx = document.getElementById("ctx").getContext("2d"); //get the canvas
var WIDTH = 600;
var HEIGHT = 600;
var framecount = 0;
var fruitList = {};
var bodyparts = {};
var bodyparts2 = {};
var score = 0;
var bodyLenght = 1;
var bodyLenght2 = 0;
ctx.font = '30px Arial';
var head = { //head of snake
x: 300,
y: 300,
spdX: 10,
spdY: 5,
color: 'blue',
width: 20,
lastX: 0,
lastY: 0,
};
var body1 = {
x: 300,
y: 320,
color: 'blue',
width: 20,
lastX: null,
lastY: null,
}
var tail = {
x: 300,
y: 340,
color: 'blue',
width: 20,
lastX: null,
lastY: null,
}
bodyparts[1] = body1;
bodyparts2[1] = tail;
Body = function() { // snake's body
var body = {
x: null,
y: null,
id: bodyLenght,
color: 'blue',
width: 20,
lastX: null,
lastY: null,
};
bodyparts[bodyLenght] = body;
bodyparts2[bodyLenght2] = body;
}
Fruit = function(id, x, y, color) { // fruits
var fruit = {
x: x,
y: y,
id: id,
color: 'orange',
width: 20,
};
fruitList[id] = fruit;
}
setbodyPos = function(id1, id2) {
id1.lastX = id1.x;
id2.x = id1.lastX;
id1.lastY = id1.y;
id2.y = id1.lastY;
}
randomFruit = function() { // random coordinates for fruit
var x = Math.random() * (WIDTH - 20);
var y = Math.random() * (HEIGHT - 20);
var id = Math.random;
Fruit(id, x, y);
}
testCollisionRectRect = function(rect1, rect2) {
return rect1.x <= rect2.x + rect2.width && rect2.x <= rect1.x + rect1.width && rect1.y <= rect2.y + rect2.width && rect2.y <= rect1.y + rect1.width;
}
updatePosition = function() {
document.onkeydown = function(event) { //check if key is pressed
if (event.keyCode === 68) //d
head.pressingRight = true;
else if (event.keyCode === 83) //s
head.pressingDown = true;
else if (event.keyCode === 81) //q
head.pressingLeft = true;
else if (event.keyCode === 90) // z
head.pressingUp = true;
}
if (head.pressingRight) { //move head
setbodyPos(bodyparts[bodyLenght], tail);
for (var key in bodyparts) {
for (var key2 in bodyparts2) {
setbodyPos(bodyparts[key], bodyparts2[key2])
}
}
setbodyPos(head, body1);
head.x += head.width;
}
if (head.pressingLeft) {
setbodyPos(bodyparts[bodyLenght], tail);
for (var key in bodyparts) {
for (var key2 in bodyparts2) {
setbodyPos(bodyparts[key], bodyparts2[key2])
}
}
setbodyPos(head, body1);
head.x -= head.width;
}
if (head.pressingDown) {
setbodyPos(bodyparts[bodyLenght], tail);
for (var key in bodyparts) {
for (var key2 in bodyparts2) {
setbodyPos(bodyparts[key], bodyparts2[key2])
}
}
setbodyPos(head, body1);
head.y += head.width;
}
if (head.pressingUp) {
setbodyPos(bodyparts[bodyLenght], tail);
for (var key in bodyparts) {
for (var key2 in bodyparts2) {
setbodyPos(bodyparts[key], bodyparts2[key2])
}
}
setbodyPos(head, body1);
head.y -= head.width;
}
if (head.x > (WIDTH - head.width)) { // checks if it is a valid position
head.x = WIDTH - head.width;
};
if (head.x < 0) {
head.x = 0;
};
if (head.y > (HEIGHT - head.width)) {
head.y = HEIGHT - head.width;
};
if (head.y < 0) {
head.y = 0;
};
head.pressingDown = false; // no key is pressed
head.pressingUp = false;
head.pressingLeft = false;
head.pressingRight = false;
}
drawEntity = function(id) { // draw rectangle in canvas
ctx.save;
ctx.fillStyle = id.color;
ctx.fillRect(id.x, id.y, id.width, id.width);
ctx.restore;
}
update = function() { //update canvas
ctx.clearRect(0, 0, WIDTH, HEIGHT);
framecount++;
if (framecount % 125 === 0) { // 5 sec
randomFruit();
}
for (var key in fruitList) {
drawEntity(fruitList[key]);
if (testCollisionRectRect(head, fruitList[key]) === true) {
delete fruitList[key];
bodyLenght++;
bodyLenght2++;
Body();
score++;
tail.x = tail.lastX;
tail.y = tail.lastY;
};
}
updatePosition();
drawEntity(head);
drawEntity(tail);
for (var key in bodyparts) {
drawEntity(bodyparts[key]);
}
ctx.fillStyle = 'black';
ctx.fillText('Score:' + score, 0, 30)
}
setInterval(update, 40);
<canvas id="ctx" width="600" height="600" style="border:1px solid #000000;"></canvas>

There's a lot going on here, so let me point you to a game I wrote to learn javascript years ago:
https://github.com/david0178418/Snake
Also, after I finished this, my coworker challenged me to turn it into a "worm" game. The challenge was accepted:
https://github.com/david0178418/Worm
This is very old code (and when I was still a junior/mid level developer and entire js noob), but hopefully you can glean an answer from it.

Related

How can i make a single straight line in HTML Canvas which is bound to the input of the mouse

I've got a question which i can't seem to figure out the right way. For some context. I'm trying to make a sort of connect the dots game. People can click on a circle and link the circle to the next one. Connecting the dots is working fine, but i wanted to add a function that when clicked on the first dot, users get a perfect straight line from the clicked dot to the mouse input. This way the users get some feedback on which dot they clicked, and how they can link to each other.
Here is a codePen that shows what i want to achieve. The only thing different is that the dots in this "pen" are animated, and i want them to stand still.
I tried a lots of things so far, and found multiple StackOverflow articles about this subject. So far i got connecting the dots working. Also the user draws a line when a dot is selected. The big issue however is that every movement of the user is resulting in drawing a line on the canvas. This line which is only used as feedback which dot is selected and how the user can draw his cursor to the next dot, is filling the screen with thousands of instances in no-time. I only want to show them one single line at a time, leading to their mouse cursor as the endpoint. Just like the codePen i did mention earlier on.
Here is my JS code so far.
var dotColor = "#FF0000";
var strokeColor = "#FF0000";
var mouse = {
x: undefined,
y: undefined
};
var obj;
var data = {
canvas: null,
ctx: null,
clickedDot: null,
dots: [{
x: 180,
y: 50
}, {
x: 20,
y: 50
}]
};
window.addEventListener('mousemove', function(e) {
mouse.x = e.x;
mouse.y = e.y;
renderActiveLink();
});
function circleCollision(c1, c2) {
var a = c1.r + c2.r,
x = c1.x - c2.x,
y = c1.y - c2.y;
if (a > Math.sqrt((x * x) + (y * y))) return true;
else return false;
}
function prepCanvas() {
var res = window.devicePixelRatio || 1,
scale = 1 / res;
data.canvas = document.getElementById('dots');
data.ctx = data.canvas.getContext('2d');
data.canvas.width = 500;
data.canvas.height = 300;
data.ctx.scale(res, res);
data.canvas.addEventListener('mousedown', function(e) {
checkForDot(e);
});
data.canvas.addEventListener('mouseup', function(e) {
checkForDot(e);
});
}
function drawDots() {
var i = 0;
for (; i < data.dots.length; i++) {
var d = data.dots[i];
data.ctx.beginPath();
data.ctx.arc(d.x, d.y, 5, 0, 2 * Math.PI);
data.ctx.fillStyle = dotColor;
data.ctx.fill();
data.ctx.closePath();
}
}
function drawLine(toDot) {
data.ctx.beginPath();
data.ctx.moveTo(data.clickedDot.x, data.clickedDot.y);
data.ctx.lineTo(toDot.x, toDot.y);
data.ctx.lineWidth = 5;
data.ctx.strokeStyle = strokeColor;
data.ctx.stroke();
data.ctx.closePath();
}
function checkForDot(e) {
var i = 0,
col = null;
for (; i < data.dots.length; i++) {
var d = data.dots[i],
c1 = {
x: d.x,
y: d.y,
r: 50
},
c2 = {
x: e.pageX,
y: e.pageY,
r: 50
};
if (circleCollision(c1, c2)) {
col = d;
}
}
if (col !== null) {
if (data.clickedDot !== null) drawLine(col);
data.clickedDot = col;
obj = col;
} else data.clickedDot = null;
}
function renderActiveLink() {
data.ctx.beginPath();
data.ctx.lineWidth = 5;
data.ctx.shadowBlur = 0;
data.ctx.moveTo(obj.x, obj.y);
data.ctx.lineTo(mouse.x, mouse.y);
data.ctx.strokeStyle = '#000000';
data.ctx.stroke();
}
prepCanvas();
drawDots();
*{
margin:0;
padding: 0;
}
#dots {
border: 1px solid black;
}
<canvas id="dots"></canvas>
Also to be seen in this JSFiddle.
Hope someone here can help me out. If any more information is needed, i will be glad to answer your questions :D.
This code just to help you to use canvas.
I added a start status in the data.
var data = {
start: false, // initial value is false
canvas: null,
ctx: null,
clickedDot: null,
dots: [{
x: 180,
y: 50
}, {
x: 20,
y: 50
}]
};
And i changed start state in the mousedown and mouseup listeners.
data.canvas.addEventListener('mousedown', function(e) {
data.start = true
checkForDot(e);
});
data.canvas.addEventListener('mouseup', function(e) {
data.start = false
checkForDot(e);
});
so the renderActiveLink method, Only works when the start position is true.
and Clears everything before drawing each line.
function renderActiveLink() {
if(!data.start) return;
data.ctx.clearRect(0, 0, data.canvas.width, data.canvas.height);
drawDots();
...
Final code:
var dotColor = "#FF0000";
var strokeColor = "#FF0000";
var mouse = {
x: undefined,
y: undefined
};
var obj;
var data = {
start: false,
canvas: null,
ctx: null,
clickedDot: null,
dots: [{
x: 180,
y: 50
}, {
x: 20,
y: 50
}]
};
window.addEventListener('mousemove', function(e) {
mouse.x = e.x;
mouse.y = e.y;
renderActiveLink();
});
function circleCollision(c1, c2) {
var a = c1.r + c2.r,
x = c1.x - c2.x,
y = c1.y - c2.y;
if (a > Math.sqrt((x * x) + (y * y))) return true;
else return false;
}
function prepCanvas() {
var res = window.devicePixelRatio || 1,
scale = 1 / res;
data.canvas = document.getElementById('dots');
data.ctx = data.canvas.getContext('2d');
data.canvas.width = 500;
data.canvas.height = 300;
data.ctx.scale(res, res);
data.canvas.addEventListener('mousedown', function(e) {
data.start = true
checkForDot(e);
});
data.canvas.addEventListener('mouseup', function(e) {
data.start = false
checkForDot(e);
});
}
function drawDots() {
var i = 0;
for (; i < data.dots.length; i++) {
var d = data.dots[i];
data.ctx.beginPath();
data.ctx.arc(d.x, d.y, 5, 0, 2 * Math.PI);
data.ctx.fillStyle = dotColor;
data.ctx.fill();
data.ctx.closePath();
}
}
function drawLine(toDot) {
data.ctx.beginPath();
data.ctx.moveTo(data.clickedDot.x, data.clickedDot.y);
data.ctx.lineTo(toDot.x, toDot.y);
data.ctx.lineWidth = 5;
data.ctx.strokeStyle = strokeColor;
data.ctx.stroke();
data.ctx.closePath();
}
function checkForDot(e) {
var i = 0,
col = null;
for (; i < data.dots.length; i++) {
var d = data.dots[i],
c1 = {
x: d.x,
y: d.y,
r: 50
},
c2 = {
x: e.pageX,
y: e.pageY,
r: 50
};
if (circleCollision(c1, c2)) {
col = d;
}
}
if (col !== null) {
if (data.clickedDot !== null) drawLine(col);
data.clickedDot = col;
obj = col;
} else data.clickedDot = null;
}
function renderActiveLink() {
if(!data.start) return;
data.ctx.clearRect(0, 0, data.canvas.width, data.canvas.height);
drawDots();
data.ctx.beginPath();
data.ctx.lineWidth = 5;
data.ctx.shadowBlur = 0;
data.ctx.moveTo(obj.x, obj.y);
data.ctx.lineTo(mouse.x, mouse.y);
data.ctx.strokeStyle = '#000000';
data.ctx.stroke();
}
prepCanvas();
drawDots();
*{
margin:0;
padding: 0;
}
#dots {
border: 1px solid black;
}
<canvas id="dots"></canvas>

How to make a smooth car moving animation?

I created a RaceTrack game, where you simply drive a car, collect yellow bonuses and avoid black obstacles.
The problem is that the animation of my car is not smooth ( When u try to go left or right etc. )
Can someone help me understand how can I do a smooth animation when steering a car ?
How could I add a spontanious curves instead of straight road?
Ps. I know my ColissionChecker() functions aren't perfect, but that is a problem for another day.
I don't know why in snippet's Full Page the canvas is very small and you can't see anything.
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var LineWidth = 10;
var LineHeight = 80;
var boundaryTopOffset = 5;
var boundaryLeftOffset = 2;
var boundaryPadding = 50;
var boundaryMiddleOffset = 2;
var speed = 50;
let executedTimer = false;
let dateDiff;
let currentScore = 0;
var leftBoundary = [];
var rightBoundary = [];
var middleBoundary = [];
var bonuses = [];
var obstacles = [];
var car = {
x: 1200,
y: 800
}
document.addEventListener('keydown', function(event) {
let key = event.which
if(key === 37) {
car.x -= speed;
} else if(key === 39) {
car.x += speed;
} else if(key === 38) {
car.y -= speed;
} else if(key === 40) {
car.y += speed;
}
})
for (x = 0; x < 8; x++) {
leftBoundary[x] =
{
offset: boundaryLeftOffset + 400,
topOffset: 0,
width: LineWidth,
height: LineHeight,
color: "red"
};
}
for (x = 0; x < 8; x++) {
middleBoundary[x] =
{
offset: boundaryMiddleOffset + 890,
topOffset: 0,
width: LineWidth,
height: LineHeight,
color: "white"
};
}
for (x = 0; x < 8; x++) {
rightBoundary[x] =
{
offset: boundaryLeftOffset + 1400,
topOffset: 0,
width: LineWidth,
height: LineHeight,
color: "red"
};
}
var cycle = 0,
totalCycle = LineHeight + boundaryPadding;
window.requestAnimationFrame(draw);
function draw() {
if(executedTimer == false) {
obstacles.push({x: Math.floor((Math.random() * 1000) + 450), y: 10});
timerStart();
}
drawCanvas(boundaryLeftOffset-2, 0, canvas.width, canvas.height, 'grey');
cycle = (cycle + 4) % totalCycle;
for (boundary of [leftBoundary, rightBoundary, middleBoundary]) {
for (i = 0; i < boundary.length; i++) {
boundary[i].topOffset = cycle + (i-1) * totalCycle;
drawBoundary(boundary[i], boundary[i].color);
}
}
if(dateDiff >= 1000) {
obstacles.push({x: Math.floor((Math.random() * 900) + 490), y: 10});
bonuses.push({x: Math.floor((Math.random() * 900) + 490), y: 10})
}
drawScore();
drawObstacle();
drawBonus();
drawCar();
obstacleColissionChecker();
bonusColissionChecker();
timerCheck();
window.requestAnimationFrame(draw);
}
function drawBoundary(x, elementColor) {
c.fillStyle = elementColor;
c.fillRect(x.offset+100, x.topOffset, x.width, x.height);
}
function drawCanvas(posX, posY, width, height, elementColor) {
c.fillStyle = elementColor;
c.fillRect(posX, posY, width, height);
}
function drawCar() {
c.fillStyle = "blue";
c.fillRect(car.x, car.y, 100, 150);
c.fillStyle = "black";
for(var i = 0; i < 101; i+=100){
c.beginPath();
c.ellipse(car.x + i, car.y + 10, 10, 15, Math.PI, 0, 2 * Math.PI);
c.ellipse(car.x + i, car.y + 140, 10, 15, Math.PI, 0, 2 * Math.PI);
c.fill();
c.closePath();
}
}
function timerStart() {
date1 = new Date();
executedTimer = true;
}
function timerCheck() {
var date2 = new Date();
dateDiff = Math.abs(date1 - date2);
if(dateDiff >= 1000)date1 = date2;
}
function drawScore() {
c.font='25px Verdana';
c.fillStyle = 'hsl('+ 0 +', 100%, 50%)';
c.fillText('Score : ' + currentScore, 100, 80);
}
function drawObstacle() {
c.fillStyle = "#080D23";
for(obstacle of [obstacles]) {
for (i = 0; i < obstacles.length; i++) {
c.fillRect(obstacle[i].x, obstacle[i].y+= 5, 80, 50);
}
}
}
function drawBonus() {
c.fillStyle = "#F2C14A";
for(bonus of [bonuses]) {
for (i = 0; i < bonuses.length; i++) {
c.beginPath();
c.arc(bonuses[i].x, bonuses[i].y+= 5, 20, 0, Math.PI * 2, false);
c.fill();
c.closePath();
}
}
}
function obstacleColissionChecker() {
for (i = 0; i < obstacles.length; i++) {
if(car.y + 20 - obstacles[i]?.y + 20 > 0 && car.y - 20 - obstacles[i]?.y + 20 < 100
&& car.x + 100 - obstacles[i]?.x + 20 > 0 && car.x - 100 - obstacles[i]?.x - 20 < 200) {
currentScore--;
}
}
}
function bonusColissionChecker() {
for (i = 0; i < bonuses.length; i++) {
if(car.y + 20 - bonuses[i]?.y + 20 > 0 && car.y - 20 - bonuses[i]?.y + 20 < 100
&& car.x + 100 - bonuses[i]?.x + 20 > 0 && car.x - 100 - bonuses[i]?.x - 20 < 200) {
currentScore++;
}
}
}
canvas {
border: 1px solid black;
margin: 0 !important;
padding: 0 !important;
}
body {
margin: 0;
}
<canvas></canvas>
In your code the speed is constant.
The car is either moving at that speed or is not moving.
This is the problem : you need to introduce acceleration.
You should car.x += speed on every frame and alter the speed in the key press handler. It would be a good start for you.

Why the nextButton/startButton does not work?

I am working on a project on Khan Academy in which I have to create a game with at least 3 levels. I have developed most of the game but when I tried to proceed from one level to next the game somehow stops.
Here is the full project:
Project Link
/**
* Contains 3 levels
*
*
* Changed Ground
* Brown rectangle is replaced with Dirt Block.
*
* Scoring system changed
* Collecting Good sticks gets 1 point.
* Collecting Bad sticks gets -1 point. (i.e. loses point).
* Hitting rocks will lose 1 point.
*
**/
var level = 0;
var nosOfSticks = 5;
var target = 0;
var speed = 1;
var endLevel = false;
var buttonClicked = false;
var levelButtonEnabled = false;
var startButtonEnabled = true;
var Beaver = function(x, y) { // Beaver Constructor
this.x = x;
this.y = y;
this.img = getImage("creatures/Hopper-Happy");
this.sticks = 0;
};
Beaver.prototype.draw = function() { // Draw function to draw beaver
fill(255, 0, 0);
this.x = constrain(this.x, 0, width-40);
this.y = constrain(this.y, 0, height-50);
image(this.img, this.x, this.y, 40, 40);
};
Beaver.prototype.hop = function() { // Hop function to make beaver hop
this.img = getImage("creatures/Hopper-Jumping");
this.y -= speed * 5;
};
Beaver.prototype.hopLeft = function() {
this.img = getImage("creatures/Hopper-Jumping");
this.x -= speed * 5;
};
Beaver.prototype.hopRight = function() {
this.img = getImage("creatures/Hopper-Jumping");
this.x += speed * 5;
};
Beaver.prototype.fall = function() { // fall function makes beaver fall on the ground
this.img = getImage("creatures/Hopper-Happy");
this.y += speed * 5;
};
Beaver.prototype.checkForStickGrab = function(stick) { // function that checks sticks grab
if ((stick.x >= this.x && stick.x <= (this.x + 40)) &&
(stick.y >= this.y && stick.y <= (this.y + 40))) {
stick.y = -400;
this.sticks++;
}
};
Beaver.prototype.checkForBadStickGrab = function(badstick) { // function that checks badsticks grab
if ((badstick.x >= this.x && badstick.x <= (this.x + 40)) &&
(badstick.y >= this.y && badstick.y <= (this.y + 40))) {
badstick.y = -400;
this.sticks--;
}
};
Beaver.prototype.checkForRockHit = function(rock) { // function that checks rocks hit
if ((rock.x >= this.x - 40 && rock.x <= (this.x + 40)) &&
(rock.y >= this.y - 30 && rock.y <= (this.y + 40))) {
rock.x = -400;
this.sticks--;
}
};
// Drawing Sticks
var Stick = function(x, y) { // Stick constructor
this.x = x;
this.y = y;
};
Stick.prototype.draw = function() { // Draw function to draw sticks
fill(0, 0, 0);
rectMode(CENTER);
rect(this.x, this.y, 5, 40);
};
var Badstick = function(x, y) { // Bad Sticks constructor
Stick.call(this, x, y);
};
//Badstick.prototype = Object.create(Stick);
Badstick.prototype.draw = function() { //Draw function to draw badsticks
fill(255, 0, 13);
rectMode(CENTER);
rect(this.x, this.y, 5, 40);
};
// Drawings Rocks
var Rock = function(x, y) { // rocks constructor
this.x = x;
this.y = y;
this.img = getImage("cute/Rock");
};
Rock.prototype.draw = function(x, y) { // function to draw rocks
fill(0, 0, 0);
image(this.img, this.x, this.y, 40, 40);
};
var beaver = new Beaver(200, 300);
var sticks = [];
for (var i = 0; i < nosOfSticks; i++) {
sticks.push(new Stick(i * 100 + 400, random(20, 260)));
}
var badSticks = [];
for (var i = 0; i < nosOfSticks/2; i++) {
badSticks.push(new Badstick(i * 200 + 400, random(20, 270)));
}
var rocks = [];
for ( var i = 0; i < nosOfSticks * 0.375; i++) {
rocks.push(new Rock(random(0, 375), i * random() - (i * 100)));
}
var grassXs = [];
for (var i = 0; i < 25; i++) {
grassXs.push(i*20);
}
var blockXs = [];
for (var i = 0; i < 25; i++) {
blockXs.push(i*20);
}
var Button = function (x, y, w, h, color, text, size, font, textcolor, best) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
this.text = text;
this.size = size;
this.font = font;
this.textcolor = textcolor;
this.best = best;
};
Button.prototype.draw = function() {
rectMode(CORNER);
fill(this.color);
rect(this.x, this.y, this.w, this.h);
fill(this.textcolor);
stroke(this.textcolor);
textFont(this.font, this.size);
text(this.text, this.x + (this.w/2 - this.w/2.5), this.y + (this.h/2 + this.size/2.5));
/*textFont(this.font, this.size / 2);
text("Best : " + this.best, this.x + 10, this.y + 90);*/
};
Button.prototype.clicked = function() {
if(mouseIsPressed && mouseX >= this.x && mouseX <= this.x + this.w && mouseY >= this.y && mouseY <= this.y + this.h ) {
return true;
}
};
var nextButton = new Button(315, 360, 75, 30, color(0, 255, 0), "Next Level", 12, "Aerial Bold", color(0, 0, 0));
var startButton = new Button(315, 360, 75, 30, color(0, 255, 0), "Start Again", 12, "Aerial Bold", color(0, 0, 0));
var playButton = new Button(140, 250, 120, 50, color(0, 0, 0), "PLAY", 40, "Aerial Bold", color(255, 255, 255));
var level1Button = new Button(30, 120, 100, 100, color(0, 0, 0), "Level 1", 25, "Aerial Bold", color(255, 255, 255));
var level2Button = new Button(140, 120, 100, 100, color(0, 0, 0), "Level 2", 25, "Aerial Bold", color(255, 255, 255));
var level3Button = new Button(250, 120, 100, 100, color(0, 0, 0), "Level 3", 25, "Aerial Bold", color(255, 255, 255));
var drawWin = function() {
fill(255, 0, 0);
textSize(36);
text("YOU WIN!!!!", 100, 200);
nextButton.draw();
};
var drawLoss = function() {
fill(255, 0, 0);
textSize(36);
text("YOU LOSE!!!!", 100, 200);
startButton.draw();
};
var movement = function() {
if (keyIsPressed) {
if(keyCode === UP) {
beaver.hop();
} /*else if(keyCode === LEFT) {
beaver.hopLeft();
} else if(keyCode === RIGHT) {
beaver.hopRight();
} */
} else { beaver.fall();}
};
var drawScore = function() {
fill(0, 255, 0);
textSize(18);
text("Score: " + beaver.sticks, 10, 390);
};
var isWin = function() {
if(beaver.sticks >= target) {
drawWin();
speed = 1;
return true;
}
};
var isLoss = function() {
if (beaver.sticks < target ) {
speed = 1;
drawLoss();
return true;
}
};
var drawBackground = function() {
//static
speed = 1;
background(227, 254, 255);
stroke(0, 0, 0);
rectMode(CORNER);
rect(0, height*0.90, width, height*0.10);
for (var i = 0; i < grassXs.length; i++) {
image(getImage("cute/GrassBlock"), grassXs[i], height*0.85, 35, 20);
image(getImage("cute/DirtBlock"), grassXs[i], height*0.85, 35, 60);
grassXs[i] -= speed;
if (grassXs[i] <= - 20) {
grassXs[i] = width;
}
}
};
var drawSticks = function() {
for (var i = 0; i < sticks.length; i++) {
sticks[i].draw();
beaver.checkForStickGrab(sticks[i]);
sticks[i].x -= speed;
}
};
var drawBadSticks = function() {
for (var i = 0; i < badSticks.length; i++) {
badSticks[i].draw();
beaver.checkForBadStickGrab(badSticks[i]);
badSticks[i].x -= speed;
}
};
var drawRocks = function() {
for (var i = 0; i < rocks.length; i++) {
rocks[i].draw();
beaver.checkForRockHit(rocks[i]);
rocks[i].y += speed;
}
};
var drawLevel = function() {
speed = 1;
drawBackground();
if (level === 1) {
target = 1;
drawSticks();
}
if (level === 2) {
target = 1;
drawSticks();
drawBadSticks();
}
if (level === 3) {
target = 1;
drawBadSticks();
drawSticks();
drawRocks();
}
beaver.draw();
movement();
drawScore();
if (sticks[nosOfSticks - 1].x < -5) {
isWin();
isLoss();
}
};
var drawLevels = function() {
level = "l";
background(0, 0, 0);
level1Button.draw();
level2Button.draw();
level3Button.draw();
if (level1Button.clicked() && level === "l") {
level = 1;
drawLevel();
} else if (level2Button.clicked() && level === "l") {
level = 2;
drawLevel();
} else if (level3Button.clicked() && level === "l") {
level = 3;
drawLevel();
}
};
var drawStart = function() {
level = 0;
background(0);
text("Hoppy Beaver", 75, 50);
text("Extreme", 120, 100);
playButton.draw();
if (playButton.clicked() && level === 0) {
levelButtonEnabled = false;
drawLevels();
}
};
//drawStart();
mouseClicked = function() {
if (nextButton.clicked() || startButton.clicked()) {
if (beaver.sticks >= 1) {
if (level === 0) {
level = 1;
sticks = [];
draw();
isWin = false;
}
if (level === 1) {
level = 2;
sticks = [];
draw();
isWin = false;
}
if (level === 2) {
level = 3;
sticks = [];
draw();
isWin = false;
}
if (level === 3) {
level = 1;
sticks = [];
isWin = false;
draw();
}
} else if (beaver.sticks < 1) {
if (level === 1) {
level = 1;
sticks = [];
drawLevel();
isLoss = false;
}
if (level === 2) {
level = 2;
sticks = [];
drawLevel();
isLoss = false;
}
if (level === 3) {
level = 3;
sticks = [];
drawLevel();
isLoss = false;
}
}
}
};
draw = function() {
speed = 1;
if (level === 1) {
drawLevel();
} else if (level === 2) {
drawLevel();
} else if (level === 3) {
drawLevel();
} else if (level === "l") {
drawLevels();
} else { drawStart(); }
};
welcome to stackoverflow. The problem with your code is this bit right here in the drawLevel function.
if (sticks[nosOfSticks - 1].x < -5) {
isWin();
isLoss();
}
At the start of your program you initialize the sticks array with some stick objects in line 124. When level 1 ends and the next button is clicked, you set the sticks array to an empty array sticks=[] in the mouseClicked function.However, you never re-add anything into the sticks array. Thus, when that block of code runs, the element at position nosOfSticks-1 is undefined, leading to your problem.My suggestion is to make a for loop after sticks=[] to refill the sticks array just like in line 124.
Good Luck!
Also, take a look at this guide for debugging help, how to debug small programs.

Collision detection bug in JS snake game

I'm trying to write a variation of Snake where the snake "bounces" off the walls.
It works most of the time, but occasionally the snake "escapes" and I can't figure out why. Initially I had the inequalities in the collison detection function set to strictly < or > which I thought was the cause of the problem, but I've changed them to <= and >= and the problem persists.
Can anyone explain why this is happening please? (You usually have to play for a minute or so before the snake escapes...)
<canvas id="canvas" width=500 height=500 style="display: block; border: 1px solid green; margin: auto;"></canvas>
<script>
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = '30px Arial';
var HEIGHT = 500;
var WIDTH = 500;
var SEGMENT_WIDTH = 30;
var snakeVelocity = {
i: 1,
j: 0
};
var snakeArray = createSnake();
function createSnake() {
var snakeArray = [];
var length = 5; // Initial length of snake
for (var i = 0; i < length; i++) {
snakeArray.push({
x: i + 1,
y: 1
});
}
return snakeArray;
}
function moveSnake(arr) {
var head = arr.slice(-1)[0];
var tail = arr[0];
var newHead = arr.shift();
// check for wall collision, which also updates velocity if needed
snakeWallCollision(head);
newHead.x = head.x + snakeVelocity.i;
newHead.y = head.y + + snakeVelocity.j;
arr.push(newHead);
return arr;
}
function snakeWallCollision(obj) {
var collision = false;
if (obj.x >= WIDTH / SEGMENT_WIDTH || obj.x <= 0) {
snakeVelocity.i *= -1;
collision = true;
}
if (obj.y >= HEIGHT / SEGMENT_WIDTH || obj.y <= 0) {
snakeVelocity.j *= -1;
collision = true;
}
return collision;
}
function drawSnake() {
console.log(snakeArray[0]);
for (var i = 0; i < snakeArray.length; i++) {
var segment = snakeArray[i];
ctx.fillText('S', segment.x * SEGMENT_WIDTH, segment.y * SEGMENT_WIDTH + 30);
}
}
function update() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
moveSnake(snakeArray);
drawSnake();
}
function checkKey(e) {
e = e || window.event;
if ([38, 40, 37, 39].includes(e.keyCode)) {
e.preventDefault();
}
if (e.keyCode == '38') {
snakeVelocity = {
i: 0,
j: -1
};
} else if (e.keyCode == '40') {
snakeVelocity = {
i: 0,
j: 1
};
} else if (e.keyCode == '37') {
snakeVelocity = {
i: -1,
j: 0
};
} else if (e.keyCode == '39') {
snakeVelocity = {
i: 1,
j: 0
};
}
}
document.onkeydown = checkKey;
setInterval(update, 1000 / 20);
drawSnake();
</script>
The problem occurs if you change direction away from the wall just before you hit the wall.
Say for example the snake's head has just moved to x = 0 and moving left and the user keys right arrow just before the next update frame. Now the snakeVelocity.i is set to 1 away from the wall.
You then test the wall
if (obj.x >= WIDTH / SEGMENT_WIDTH || obj.x <= 0) {
snakeVelocity.i *= -1; // negate the direction
// but the direction is already away from the
// wall due to user input. This will turn it back
// onto the wall
}
Same happens for up and down.
You need to have the collision test know what direction the snake is heading and then based on that test if that move will result in a collision.
Change the test function to find the next position the snake's head will be if allowed to move as its current state dictates. Only if that move results in the head being outside the bounds of the game do you change the direction.
function snakeWallCollision(head) {
var x = head.x + snakeVelocity.i; // find out where the head will be
var y = head.y + snakeVelocity.j; // next frame
if (x > WIDTH / SEGMENT_WIDTH || x < 0) {
snakeVelocity.i *= -1;
return true;
}
if (y > HEIGHT / SEGMENT_WIDTH || y < 0) {
snakeVelocity.j *= -1;
return true;
}
return false;
}
What happens if you swap these lines around:
newHead.x = head.x + snakeVelocity.i;
newHead.y = head.y + + snakeVelocity.j;
// check for wall collision, which also updates velocity if needed
snakeWallCollision(head);
The problem seems to be that if the user presses a key synchronized with your collision check the directions changes twice and the snake goes beyond the wall.
Maybe this one helps you out (i added a test variable USER_ACTION to check if the user has pressed the key and if so, don't do the collision check):
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = '30px Arial';
var HEIGHT = 500;
var WIDTH = 500;
var SEGMENT_WIDTH = 30;
var USER_ACTION = false;
var snakeVelocity = {
i: 1,
j: 0
};
var snakeArray = createSnake();
function createSnake() {
var snakeArray = [];
var length = 5; // Initial length of snake
for (var i = 0; i < length; i++) {
snakeArray.push({
x: i + 1,
y: 1
});
}
return snakeArray;
}
function moveSnake(arr) {
var head = arr.slice(-1)[0];
var tail = arr[0];
var newHead = arr.shift();
// check for wall collision, which also updates velocity if needed
snakeWallCollision(head);
newHead.x = head.x + snakeVelocity.i;
newHead.y = head.y + +snakeVelocity.j;
arr.push(newHead);
return arr;
}
function snakeWallCollision(obj) {
if (!USER_ACTION) {
var collision = false;
if (obj.x >= WIDTH / SEGMENT_WIDTH || obj.x <= 0) {
snakeVelocity.i *= -1;
collision = true;
}
if (obj.y >= HEIGHT / SEGMENT_WIDTH || obj.y <= 0) {
snakeVelocity.j *= -1;
collision = true;
}
} else {
USER_ACTION = false;
}
return collision;
}
function drawSnake() {
console.log(snakeArray[0]);
for (var i = 0; i < snakeArray.length; i++) {
var segment = snakeArray[i];
ctx.fillText('S', segment.x * SEGMENT_WIDTH, segment.y * SEGMENT_WIDTH + 30);
}
}
function update() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
moveSnake(snakeArray);
drawSnake();
}
function checkKey(e) {
USER_ACTION = true;
e = e || window.event;
if ([38, 40, 37, 39].includes(e.keyCode)) {
e.preventDefault();
}
if (e.keyCode == '38') {
snakeVelocity = {
i: 0,
j: -1
};
} else if (e.keyCode == '40') {
snakeVelocity = {
i: 0,
j: 1
};
} else if (e.keyCode == '37') {
snakeVelocity = {
i: -1,
j: 0
};
} else if (e.keyCode == '39') {
snakeVelocity = {
i: 1,
j: 0
};
}
}
document.onkeydown = checkKey;
setInterval(update, 1000 / 20);
drawSnake();

Snake game: how to check if the head collides with its own body

You may have played Snake, a game where you have to eat food to grow and you fail if you collide with the snake's body or certain obstacles. The first part was easy, but the latter seems impossible to achieve.
I have tried to make a for loop check if the last element of my snake array is colliding with its other parts. My condition was like this: if the x position of the last item in my array is bigger than any of the array items' x position, and smaller than their x position plus their width, and so on. That didn't work.
Here's my code :
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="myCanvas" width="200px" height="200px" style="border:1px solid black"/>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var yPos = 20;
var width = 15;
var variable = 1;
var currentDir = 1;
//var xPos = (width+5)*variable;
var xPos = 20;
var myArr = [{myX:xPos,myY:yPos},{myX:xPos,myY:yPos},{myX:xPos,myY:yPos}];
var downPressed = false;
var upPressed = false;
var leftPressed = false;
var rightPressed = false;
var first = [0,20,40,60,80,100,120,140,160,180];
var firstX = Math.floor(Math.random()*10);
var firstY = Math.floor(Math.random()*10);
var okayed = first[firstX];
var notOkayed = first[firstY];
var maths = myArr[myArr.length-1];
function drawFood() {
ctx.beginPath();
ctx.rect(okayed,notOkayed,15,15);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
}
function drawRectangle() {
ctx.clearRect(0,0,200,200);
drawFood();
for(var i = 0;i<myArr.length;i++) {
ctx.beginPath();
ctx.rect(myArr[i].myX,myArr[i].myY,width,15);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
requestAnimationFrame(drawRectangle);
}
setInterval("calledin()",100);
function calledin() {
var secondX = Math.floor(Math.random()*10);
var secondY = Math.floor(Math.random()*10);
var newobj = {myX:myArr[myArr.length-1].myX+20,myY:myArr[myArr.length-1].myY};
var newobjTwo = {myX:myArr[myArr.length-1].myX,myY:myArr[myArr.length-1].myY+20};
var newobjLeft = {myX:myArr[myArr.length-1].myX-20,myY:myArr[myArr.length-1].myY};
var newobjUp = {myX:myArr[myArr.length-1].myX,myY:myArr[myArr.length-1].myY-20};
var okayNewObj = {myX:myArr[1].myX - 20,myY:myArr[1].myY};
if(myArr[myArr.length-1].myX > 180 || myArr[myArr.length-1].myX < 0 || myArr[myArr.length-1].myY > 180 || myArr[myArr.length-1].myY < 0)
{alert("Game Over");window.location.reload();}
if(myArr[myArr.length-1].myX > okayed-5 && myArr[myArr.length-1].myX < okayed+20 && myArr[myArr.length-1].myY < notOkayed+20 &&
myArr[myArr.length-1].myY > notOkayed-5) {
okayed = first[secondX];
notOkayed = first[secondY];
myArr.unshift(okayNewObj);
}
if(currentDir == 1) {
myArr.push(newobj);
myArr.shift();}
if(currentDir == 2) {
myArr.push(newobjTwo);
myArr.shift();
}
if(currentDir == 4) {
myArr.push(newobjLeft);
myArr.shift();
}
if(currentDir == 3) {
myArr.push(newobjUp);
myArr.shift();
}
for(var i = 0;i<myArr.length-2;i++) {
if(myArr[myArr.length-1].myX > myArr[i].myX &&
myArr[myArr.length-1].myX < myArr[i].myX + 15 && myArr[myArr.length-1].myY > myArr[i].myY && myArr[myArr.length-1].myY > myArr[i].myY + 15)
{alert("Game over");window.location.reload();}
}
}
function downed(e) {
if(e.keyCode==40) {if(currentDir != 3) {currentDir = 2;}}
if(e.keyCode==38) {if(currentDir != 2) {currentDir = 3;}}
if(e.keyCode==39) {if(currentDir != 4) {currentDir = 1;}}
if(e.keyCode==37) {if(currentDir != 1) {currentDir = 4;}}
}
function upped(e) {
if(e.keyCode == 40) {downPressed = false;}
}
document.addEventListener("keydown",downed,false);
document.addEventListener("keyup",upped,false);
drawRectangle();
</script>
</body>
</html>
Suppose the snake is represented by an array called snake in which the head is at index snake.length - 1. We have to compare the position of the head against the positions of the body segments at indices 0 through snake.length - 2.
The following code sets okay to false if the snake head has collided with a body segment. Otherwise, okay remains true.
var head = snake[snake.length - 1],
x = head.x,
y = head.y,
okay = true;
for (var i = snake.length - 2; i >= 0; --i) {
if (snake[i].x == x && snake[i].y == y) {
okay = false;
break;
}
}
Below is a snippet in which I have modified your code to clarify the game logic and to simplify many of the calculations.
Instead of working directly with canvas coordinates, I represent each position with the column index x and row index y of a virtual grid cell. This lets us calculate the neighboring grid positions by adding 1 or -1 to x or y. When it comes time to paint the canvas, we multiply the virtual coordinates by the cell size.
I have replaced most of your literal values with variables. For example, instead of setting the canvas dimensions to 200 by 200, we can do this:
canvas.width = numCols * cellSize;
canvas.height = numRows * cellSize;
This lets us change numCols and numRows in one place to resize the whole game grid. All the calculations work out because they evaluate variables instead of using literals.
I altered the key-event handling to recognize the key codes for the W-A-S-D keys in addition to the arrow keys. When the game is embedded in a long web page, as it is here, you'll probably want to use the W-A-S-D keys so that the page doesn't scroll up and down while you're playing.
var canvas,
ctx,
currentDir,
startX = 1,
startY = 1,
startSnakeLength = 3,
snake,
cellSize = 18,
cellGap = 1,
foodColor = '#a2302a',
snakeBodyColor = '#2255a2',
snakeHeadColor = '#0f266b',
numRows = 10,
numCols = 10,
canvasWidth = numCols * cellSize,
canvasHeight = numRows * cellSize;
var food = {};
function placeFood() {
// Find a random location that isn't occupied by the snake.
var okay = false;
while (!okay) {
food.x = Math.floor(Math.random() * numCols);
food.y = Math.floor(Math.random() * numRows);
okay = true;
for (var i = 0; i < snake.length; ++i) {
if (snake[i].x == food.x && snake[i].y == food.y) {
okay = false;
break;
}
}
}
}
function paintCell(x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x * cellSize + cellGap,
y * cellSize + cellGap,
cellSize - cellGap,
cellSize - cellGap);
}
function paintCanvas() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
paintCell(food.x, food.y, foodColor);
var head = snake[snake.length - 1];
paintCell(head.x, head.y, snakeHeadColor);
for (var i = snake.length - 2; i >= 0; --i) {
paintCell(snake[i].x, snake[i].y, snakeBodyColor);
}
}
function updateGame() {
var head = snake[snake.length - 1],
x = head.x,
y = head.y;
// Move the snake.
var tail = snake.shift();
switch (currentDir) {
case 'up':
snake.push(head = { x: x, y: y - 1 });
break;
case 'right':
snake.push(head = { x: x + 1, y: y });
break;
case 'down':
snake.push(head = { x: x, y: y + 1 });
break;
case 'left':
snake.push(head = { x: x - 1, y: y });
break;
}
paintCanvas();
x = head.x;
y = head.y;
// Check for wall collision.
if (x < 0 || x >= numCols || y < 0 || y >= numRows) {
stopGame('wall collision');
return;
}
// Check for snake head colliding with snake body.
for (var i = snake.length - 2; i >= 0; --i) {
if (snake[i].x == x && snake[i].y == y) {
stopGame('self-collision');
return;
}
}
// Check for food.
if (x == food.x && y == food.y) {
placeFood();
snake.unshift(tail);
setMessage(snake.length + ' segments');
}
}
var dirToKeyCode = { // Codes for arrow keys and W-A-S-D.
up: [38, 87],
right: [39, 68],
down: [40, 83],
left: [37, 65]
},
keyCodeToDir = {}; // Fill this from dirToKeyCode on page load.
function keyDownHandler(e) {
var keyCode = e.keyCode;
if (keyCode in keyCodeToDir) {
currentDir = keyCodeToDir[keyCode];
}
}
function setMessage(s) {
document.getElementById('messageBox').innerHTML = s;
}
function startGame() {
currentDir = 'right';
snake = new Array(startSnakeLength);
snake[snake.length - 1] = { x: startX, y: startY };
for (var i = snake.length - 2; i >= 0; --i) {
snake[i] = { x: snake[i + 1].x, y: snake[i + 1].y + 1 };
}
placeFood();
paintCanvas();
setMessage('');
gameInterval = setInterval(updateGame, 200);
startGameButton.disabled = true;
}
function stopGame(message) {
setMessage(message + '<br> ended with ' + snake.length + ' segments');
clearInterval(gameInterval);
startGameButton.disabled = false;
}
var gameInterval,
startGameButton;
window.onload = function () {
canvas = document.getElementById('gameCanvas'),
ctx = canvas.getContext('2d');
canvas.width = numCols * cellSize;
canvas.height = numRows * cellSize;
Object.keys(dirToKeyCode).forEach(function (dir) {
dirToKeyCode[dir].forEach(function (keyCode) {
keyCodeToDir[keyCode] = dir;
})
});
document.addEventListener("keydown", keyDownHandler, false);
startGameButton = document.getElementById('startGameButton');
startGameButton.onclick = startGame;
}
body {
font-family: sans-serif;
}
#gameCanvas {
border: 1px solid #000;
float: left;
margin-right: 15px;
}
#startGameButton, #messageBox {
font-size: 16px;
margin-top: 15px;
}
#messageBox {
line-height: 24px;
}
<canvas id="gameCanvas"></canvas>
<button id="startGameButton">Start game</button>
<div id="messageBox"></div>

Categories

Resources