Collision detection bug in JS snake game - javascript

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();

Related

How to move an object across a game board. Multiple Objects

I am an extreme beginner in the field of P5.JS. I am way more knowledgeable in Java which will be apparent in my broken code. I have attempted a multitude of Java approaches to cycling through objects of the same class and applying methods to them. My attempt is to implement buttons that will move them forward as of now and once I understand that move on to more complicated interactions.
https://openprocessing.org/sketch/1564721 6
This is the link to the code I am attempting to fix it currently, but if you have any tips or advice for streamlining this please let me know.
var gridCellSizeX = 80;
var gridCellSizeY = 100;
var gridAlpha = 1000;
let img;
var gridCellSizeSlider;
turnValue = 1;
var count = 1;
var gameOn = true;
var forward = false;
function preload() {
img = loadImage('vagg02.jpg');
}
function setup() {
createCanvas(800, 600);
image(img, 0,0,800,500);
image( img ,0, 500, 800,100);
stroke(0, gridAlpha);
for (var x = 0; x < width; x += gridCellSizeX) {
line(x, 0, x, 500);
}
for (var y = 0; y < height; y += gridCellSizeY) {
line(0, y, width, y);
}
token1 = new token(15, 5, 100, 5000, 'reb', 1)
token2 = new token(15, 205, 100, 5000,'reb', 2 )
token3 = new token(15, 405, 100, 5000, 'reb', 3)
token4 = new token(735, 5, 100, 5000,'union', 4)
token5 = new token(735, 205, 100, 5000, 'union', 5)
token6 = new token(735, 405, 100, 5000, 'union', 6)
const tokens = new Array(token1, token2, token3, token4, token5, token6);
forwardButton = createButton('Move -->')
forwardButton.position(300, 525);
forwardButton.mousePressed(moveForward());
//button = createButton('Move -->'); button.position(300, 525); button.mousePressed();
button2 = createButton('Move Up');
button2.position(400, 525);
button2.mousePressed();
button3 = createButton('Move Down');
button3.position(500, 525);
button3.mousePressed();
button4 = createButton('Move <--');
button4.position(600, 525);
button4.mousePressed();
}
function draw() {
if(forward == true) token1.x =+ 500;
//for(let i = 0; gameOn == true; i++){ if(i > 6){ i = 0; }}
}
function mousePressed() {
if (mouseX >= 300 && mouseX <= 325 &&
mouseY >= 525 && mouseY <= 500){
token1.moveForward();
}
return false;
}
// new file token
class token {
constructor(xpos, ypos, t, a, s, turn) {
this.x = xpos;
this.y = ypos;
this.troopSize = t / 2;
this.accuracy = a / 100.0;
this.side = s;
this.moveTurn = turn;
this.loaded = true;
if(this.side == 'reb'){
fill('red');
rect(this.x, this.y, this.troopSize, 90)
}
if(this.side == 'union'){
fill('blue');
rect(this.x, this.y, this.troopSize, 90)
}
}
getToken(){
if(count > 6){
count = 1;
}
if(count == 1) {
return token1;
}
else if(count == 2){
return token2;
}
else if(count == 3){
return token3;
}
else if(count == 4){
return token4;
}
else if(count == 5){
return token5;
}
else if(count == 6){
return token6;
}
count++;
}
///////// Beginning of Basic Functions for the TOKEN CLASS
moveForward(){
if(this.side == 'reb'){
token1.x += 50;
redraw(token1);
}
if(this.side == 'union'){
this.x += -50;
redraw();
}
}
////
moveBackward(){
if(this.side == 'reb'){
this.x = this.x - 50;
}
if(this.side == 'union'){
this.x = this.x + 50;
}
}
/////
fire(){
if(loaded == true){
}
}
//////
load(){
}
///////
fixBayonet(){
}
////////
charge(){
}
///////// END of Basic Functions for the TOKEN CLASS
}

I am making an Atari Breakout clone & I am wondering how to tell if the ball hits a certain side of a block

I have gotten the part where when the ball collides with the block it deletes the block, but I am also wanting to tell if the ball hits the top or bottom or left or right of the block and bounce accordingly. I have attempted it, but it's not working quite right. It just freaks out jumping around. I have deleted that portion from the code below as it does not work. Can anyone help me out with this problem? Maybe give an example or tell me how it would work?
<canvas id="can" height="500" width="1000"></canvas>
var c = document.getElementById("can");
var ctx = c.getContext("2d");
var blocks= [];
var paddle = {x:450,y:480,h:10,w:100};
var ball = {r:7,x:500,y:469};
var rows=[0,1,2,3,4];
var px = paddle.x, py = paddle.y;
var pxv=0;
var by = ball.y, bx = ball.x;
var bxv = -1.5, byv = -1.5;
function Block(h,w,x,y,c) {
this.h = h;
this.w = w;
this.x = x;
this.y = y;
this.c = c;
}
for(var i =0, len=rows.length;i<len;i++){
for(var j=0; j<20;j++) {
blocks.push(new Block(20,50,j*50,i*20,rows[i]))
}
}
document.addEventListener("keydown",keyPush);
document.addEventListener("keyup",keyRelease);
function keyRelease(evt) {
switch(evt.keyCode) {
case 37:
pxv=0;
break;
case 39:
pxv=0;
break;
}
}
function keyPush(evt) {
switch(evt.keyCode) {
case 37:
pxv=-5;
break;
case 39:
pxv=5
break;
}
}
function AABBIntersect(ax, ay, aw, ah, bx, by, bw, bh) {
return ax < bx+bw && bx < ax+aw && ay < by+bh && by < ay+ah;
};
function draw(){
ctx.clearRect(0,0,1000,500)
bx+=bxv;
by+=byv;
px+=pxv;
if(px > 900) {
px = 900;
}
else if(px < 0) {
px = 0;
}
for(var i = 0, len=blocks.length;i<len;i++) {
var bl = blocks[i];
if(AABBIntersect(bx,by,ball.r,ball.r,bl.x,bl.y,bl.w,bl.h)) {
blocks.splice(i,1);
i--;
len--;
}
}
if(bx < 0) {
bxv = bxv*-1;
}
if(bx > 1000) {
bxv = bxv*-1;
}
ctx.fillStyle = "#ff4b38"
ctx.fillRect(px,py,paddle.w,paddle.h);
for(var i = 0, len=blocks.length; i<len; i++){
if(blocks[i].c === 0) {
ctx.fillStyle = "#ff4b38"
}
else if(blocks[i].c === 1) {
ctx.fillStyle = "#ffba19"
}
else if(blocks[i].c === 2) {
ctx.fillStyle = "#fcee25"
}
else if(blocks[i].c === 3) {
ctx.fillStyle = "#26db02"
}
else if(blocks[i].c === 4) {
ctx.fillStyle = "#2d69ff"
}
ctx.fillRect(blocks[i].x,blocks[i].y,blocks[i].w,blocks[i].h);
ctx.beginPath();
ctx.arc(bx,by,ball.r,0,2*Math.PI,false);
ctx.fillStyle = "gray";
ctx.fill();
}
}
setInterval(draw,10);
I'm sure there are more ways to do it but this is how I would do it.
Inside your collision detection function you should have if-statements to detect if from the x or y side. You made need to tweak it as not sure if it will error later but the brunt of it all is like this:
function AABBIntersect(ax, ay, aw, ah, bx, by, bw, bh) {
var bool = ax < bx + bw && bx < ax + aw && ay < by + bh && by < ay + ah;
if(bool){
if(ax == bx || ax == bx + bw){
bxv *= -1;
cl("x");
}else{
byv *= -1;
cl("y");
}
}
return bool;
};
Your next issue is you have no paddle collision detection, so it will bounce back but it will go through your paddle. So you can do the following in your draw() function. I put it after your if(bx > 1000):
if(bx >= px && bx <= px + paddle.w && by >= py && by <= py + paddle.h){
byv *= -1;
}
I would also put your setInterval on a var so you can clear it when either all the blocks are gone or your ball goes below the paddle. Otherwise it's just going to go everywhere infinitely.

HTML5 canvas particle explosion

I'm trying to get this particle explosion working. It's working but it looks like some frames does not get rendered. If I click many times to call several explosions it starts to uhm.. "lag/stutter". Is there something I have forgotten to do? It may look like the browser hangs when I click many times. Is it too much to have 2 for loops inside each other?
Attached my code so you can see.
Just try to click many times, and you will see the problem visually.
// Request animation frame
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
// Canvas
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
// Set full-screen
c.width = window.innerWidth;
c.height = window.innerHeight;
// Options
var background = '#333'; // Background color
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
var explosions = [];
var fps = 60;
var now, delta;
var then = Date.now();
var interval = 1000 / fps;
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
function draw() {
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length == 0) {
return;
}
for (var i = 0; i < explosions.length; i++) {
var explosion = explosions[i];
var particles = explosion.particles;
if (particles.length == 0) {
explosions.splice(i, 1);
return;
}
for (var ii = 0; ii < particles.length; ii++) {
var particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size < 0) {
particles.splice(ii, 1);
return;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
var xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(new explosion(xPos, yPos));
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (var i = 0; i < particlesPerExplosion; i++) {
this.particles.push(new particle(x, y));
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
if (positive == false) {
var num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
} else {
var num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function(e) {
clicked(e);
});
draw();
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>
You are returning from iterating over the particles if one is too small. This causes the other particles of that explosion to render only in the next frame.
I have a working version:
// Request animation frame
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
// Canvas
const c = document.getElementById('canvas');
const ctx = c.getContext('2d');
// Set full-screen
c.width = window.innerWidth;
c.height = window.innerHeight;
// Options
const background = '#333'; // Background color
const particlesPerExplosion = 20;
const particlesMinSpeed = 3;
const particlesMaxSpeed = 6;
const particlesMinSize = 1;
const particlesMaxSize = 3;
const explosions = [];
let fps = 60;
const interval = 1000 / fps;
let now, delta;
let then = Date.now();
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
function draw() {
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length === 0) {
return;
}
for (let i = 0; i < explosions.length; i++) {
const explosion = explosions[i];
const particles = explosion.particles;
if (particles.length === 0) {
explosions.splice(i, 1);
return;
}
const particlesAfterRemoval = particles.slice();
for (let ii = 0; ii < particles.length; ii++) {
const particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size <= 0) {
particlesAfterRemoval.splice(ii, 1);
continue;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
explosion.particles = particlesAfterRemoval;
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
let xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(
new explosion(xPos, yPos)
);
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (let i = 0; i < particlesPerExplosion; i++) {
this.particles.push(
new particle(x, y)
);
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
let num;
if (positive === false) {
num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) === 1 ? 1 : -1;
} else {
num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function (e) {
clicked(e);
});
draw();
<!DOCTYPE html>
<html>
<head>
<style>* {margin:0;padding:0;overflow:hidden;}</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>
Loops, break and continue.
The problem was caused when you checked for empty particle arrays and when you found a particle to remove.
The bugs
The following two statements and blocks caused the problem
if (particles.length == 0) {
explosions.splice(i, 1);
return;
}
and
if (particles.size < 0) {
explosions.splice(ii, 1);
return;
}
The returns stopped the rendering of particles, so you would sometimes return before drawing a single particle was rendered just because the first explosion was empty or first particle was too small.
Continue and break
You can use the continue token in javascript to skip the rest of a for, while, do loop
for(i = 0; i < 100; i++){
if(test(i)){
// need to skip this iteration
continue;
}
// more code
// more code
// continue skips all the code upto the closing }
} << continues to here and if i < 100 the loop continues on.
Or you can completely break out of the loop with break
for(i = 0; i < 100; i++){
if(test(i)){
// need to exit the for loop
break;
}
// more code
// more code
// break skips all the code to the first line after the closing }
}
<< breaks to here and if i remains the value it was when break was encountered
The fix
if (particles.length == 0) {
explosions.splice(i, 1);
continue;
}
and
if (particles.size < 0) {
explosions.splice(ii, 1);
continue;
}
Your example with the fix
Your code with the fix. Befor I found it I started changing stuff.
Minor stuff.
requestAnimationFrame passes a time in milliseconds so to an accuracy of micro seconds.
You were setting then incorrectly and would have been losing frames. I changed the timing to use the argument time and then is just set to the time when a frame is drawn.
There are some other issues, nothing major and more of a coding style thing. You should capitalise objects created with new
function Particle(...
not
function particle(...
and your random is a overly complex
function randInt(min, max = min - (min = 0)) {
return Math.floor(Math.random() * (max - min) + min);
}
or
function randInt(min,max){
max = max === undefined ? min - (min = 0) : max;
return Math.floor(Math.random() * (max - min) + min);
}
randInt(100); // int 0 - 100
randInt(10,20); // int 10-20
randInt(-100); // int -100 to 0
randInt(-10,20); // int -10 to 20
this.xv = randInt(-particlesMinSpeed, particlesMaxSpeed);
this.yv = randInt(-particlesMinSpeed, particlesMaxSpeed);
this.size = randInt(particlesMinSize, particlesMaxSize);
And if you are using the same name in variables a good sign to create an object
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
Could be
const settings = {
particles : {
speed : {min : 3, max : 6 },
size : {min : 1 : max : 3 },
explosionCount : 20,
},
background : "#000",
}
Anyways your code.
var c = canvas;
var ctx = c.getContext('2d');
// Set full-screen
c.width = innerWidth;
c.height = innerHeight;
// Options
var background = '#333'; // Background color
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
var explosions = [];
var fps = 60;
var now, delta;
var then = 0; // Zero start time
var interval = 1000 / fps;
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
// as time is passed you need to start with requestAnimationFrame
requestAnimationFrame(draw);
function draw(time) { //requestAnimationFrame frame passes the time
requestAnimationFrame(draw);
delta = time - then;
if (delta > interval) {
then = time
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length == 0) {
return;
}
for (var i = 0; i < explosions.length; i++) {
var explosion = explosions[i];
var particles = explosion.particles;
if (particles.length == 0) {
explosions.splice(i, 1);
//return;
continue;
}
for (var ii = 0; ii < particles.length; ii++) {
var particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size < 0) {
particles.splice(ii, 1);
// return;
continue;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
var xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(new explosion(xPos, yPos));
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (var i = 0; i < particlesPerExplosion; i++) {
this.particles.push(new particle(x, y));
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
if (positive == false) {
var num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
} else {
var num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function(e) {
clicked(e);
});
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>

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>

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

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.

Categories

Resources