How can I make snake game detect collision with its tail? - javascript

I am currently struggling with collision detection while writing a JavaScript Snake game using 2d canvas. Which is fairly new to me. The collision detection towards the edges works as expected. However, I run into trouble when it comes to detecting whether the snake hits itself.
I have been looking at other similar topics, but did not find them directly applicable to my case.
Below you'll find the game loop, snake object, and the collision detection functions. Any pointers to why the collision detection does not trigger when the snake hits itself, would be much appreciated.
GitHub Repository
// Game loop
function GameLoop() {
map.DrawMap();
map.DrawSnake(snake);
map.DrawFood(food);
RenderScore();
snake.IncrementTail();
snake.DirectionChange(map);
if (snake.EatFood(food, map)){
IncrementScore();
snake.maxLength++;
food = Food.GetRandomFood(map);
}
if (!snake.isOutOfBounds(map) && !snake.IsTouchingItself()) {
setTimeout(GameLoop, REFRESH_RATE);
}
}
At the end of the GameLoop, I the game should check for snake.isOutOfBounds(map) and snake.IsTouchingItself(). Second of which, is not triggering as expected.
// Snake object
export class Snake {
constructor(map) {
this.positions = [{
x: map.GetBlockSize() * 5,
y: map.GetBlockSize() * 5
},
{
x: map.GetBlockSize() * 4,
y: map.GetBlockSize() * 5
},
{
x: map.GetBlockSize() * 3,
y: map.GetBlockSize() * 5
}],
this.colorBody = '#45b6fe',
this.colorHead = '#0e2433',
this.d,
this.maxLength = 3,
this.moves = 0
}
isOutOfBounds(map){
if ( this.positions[0].x >= map.width
|| this.positions[0].x < 0
|| this.positions[0].y >= map.height
|| this.positions[0].y < 0
){
console.log('Snake is attempting to flee.');
return true;
}
}
EatFood(food, map){
if ( this.positions[0].x == food.x
&& this.positions[0].y == food.y){
return true;
} else {
return false;
}
}
IncrementTail(){
var position = { x: this.positions[0].x,
y: this.positions[0].y };
this.positions.unshift(position);
if (this.positions.length > this.maxLength){
this.positions.pop()
}
}
IsTouchingItself(){
for (var i = 2; i < this.positions.length; i++){
if (this.positions[0].x === this.positions[i].x
&& this.positions[0].y === this.positions[i].y){
console.log('Snake is touching itself!');
return true;
} else {
console.log('Snake is well raised.');
return false;
}
}
}
}

Try to check if the head's position matches a body part's position.
//I guess the snake's head is the first element in this.positions (index 0)
isSnakeDead() {
//Creating the variables for simplicity
let headX = this.positions[0].x;
let headY = this.positions[0].y;
//The for loop starts from 1 because the head is index 0
//and you don't want to check if the head collides with itself
for (let i = 1; i < this.positions.length; ++i) {
//Another variables for simplicity
let currentX = this.positions[i].x;
let currentY = this.positions[i].y;
if (headX === currentX && headY === currentY) {
//The head collides with the current body part
return true;
}
}
//If the function reaches here then
//the head does not collide with any of the other parts
return false;
}

Related

Travel around the city in 10 steps

We travel around the city in 4 cardinal directions and can only take 10 steps and must return to the place where we started. My program breaks on tests (didn't come to the place where I started from). Where is the mistake? I know that it can be solved easier, but I would like to know what my mistake is
function isValidWalk(walk) {
let x = 0, y = 0;
let result = 0;
if(walk.length !== 10) {
return false;
}
for (let i = 0; i < 10; i++) {
if(walk[i] === 'n') {
x += 1;
}
else if(walk[i] === 's') {
x += -1
}
else if(walk[i] === 'w') {
y += -1;
}
else if(walk[i] === 'e') {
y += 1;
}
}
result = x + y;
if(result === 0) {
return true;
}
return false;
}
You need ot check x and y directly. Their values should be zero, because you can not go north and west to get a neutral position.
if (x === 0 && y === 0) return true;
return false;
For example look to this walk
NWSSEN
There you have a balanced north and south, as well west and east.

My code gets into an infinite loop when setting up in p5

I am having real trouble finding where my loop is. I run the code and it hangs. I am trying to make a circuits game where there are circuits for the user to connect. But I am getting stuck at square one even setting up the map to make sure it is solvable. There is an infinite loop but I can't find it I looked and looked... here is the code.`
//maxLength of circut board is the board
let theHighestMaxLength = 10;
let board;
let gridX = 10;
let gridY = 10;
let perX = 10;
let perY = 10;
//s is here so we don't have to pass it everywhere the square we are looking at in functions
let s=null;
//length and usedsquares and begin are here to be used across functions
let length=0;
let usedSquares=0;
let begin=null;
class Asquare {
constructor(x, y) {
this.x = x;
this.y = y;
this.isCircut = false;
this.isWire = false;
this.OtherCircut = null;
this.Left = null;
this.Right = null;
this.Top = null;
this.Bottom = null;
this.otherCircut = null;
this.isBlank = false;
}
drawSelf() {
if (this.isCircut) {
rectMode(CENTER)
var xx = max(this.otherCircut.x, this.x);
var yy = max(this.otherCircut.y, this.y);
fill(color(xx * 25, yy * 25, 0));
square((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
}
else if (this.isWire) {
fill(color(this.otherCircut.x * 20, this.otherCircut.y * 20, 0));
circle((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
}
else if (this.isBlank) {
fill(color(0, 204, 0))
circle((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
}
}
}
function handleWireMove(){
s.isWire = true;
//remember the starting circut
s.otherCircut = begin;
informAll(s);
//the length is used
length++;
}
function informAll() {
//tell all the other squares of s
if (s.x - 1 >= 0) {
board[s.x - 1][s.y].Right = s;
}
if (s.x + 1 < gridX) {
board[s.x + 1][s.y].Left = s;
}
if (s.y - 1 >= 0) {
board[s.x][s.y - 1].Bottom = s;
}
if (s.y + 1 < gridY) {
board[s.x][s.y + 1].Top = s;
}
//the used squares is now higher
usedSquares++;
}
function setup() {
createCanvas(gridX * perX, gridY * perY);
noLoop();
//fill the board with squares
board = new Array(gridX).fill(0).map(() => new Array(gridY).fill(0));
for (var x = 0; x < gridX; x++) {
for (var y = 0; y < gridY; y++) {
board[x][y] = new Asquare(x, y);
}
}
//the number of squares in the grid used
usedSquares = 0;
//till the board is full
while (usedSquares < gridX * gridY) {
//get a random x y
var rx = floor(random(gridX));
var ry = floor(random(gridY));
//create an s and begin var s for every nw square and begin for the first
s = board[rx][ry];
//if this square is already in use
if(s.isBlank||s.isCircut||s.isWire){continue;}
//begin at this square
begin = s;
//if there is some way to go
if (s.Left == null || s.Right == null || s.Top == null || s.Bottom == null) {
// get a random length to try and reach
var maxLength = floor(random(1, theHighestMaxLength))
//the starting length
length = 0;
begin.isCircut = true;
//inform all the surounding squares and increase the number of used
informAll();
//while the length isn't full
while (length <= maxLength) {
//add an option count for left right top botoom
var numOption = s.Left == null ? 1 : 0;
numOption = s.Right == null ? numOption + 1 : numOption;
numOption = s.Top == null ? numOption + 1 : numOption;
numOption = s.Bottom == null ? numOption + 1 : numOption;
//if there are no toptions to move we must stop here ot if the maxLength is reached
if (numOption == 0 || length == maxLength) {
//this is a circut the beigin circut is begin the begin other circut is this final one
s.isCircut = true;
s.isWire = false;
s.otherCicut = begin;
begin.otherCircut = s;
//make sure the loop ends
length=9999;
break;
}
//pick a random direction number
var randomChoiseNumber = floor(random(numOption));
//if left is an option
if (s.Left == null) {
//if r is already 0 that means we picked left
if (randomChoiseNumber == 0) {
//while left is an option and the maxlength isn't hit and left isn't off the map
while (s.Left == null && length < maxLength && s.x - 1 >= 0) {
//set s to the left
s = board[s.x - 1][s.y];
//handleWireMove the move
handleWireMove()
}
//continue we used the direction
continue;
} else {
//we passed an option reduce the number
randomChoiseNumber--;
}
}
//if right is an option
if (s.Right == null) {
//if this is the zero choice
if (randomChoiseNumber == 0) {
//if right is not off the map and an option while the length is not hit
while (s.Right == null && length < maxLength && s.x + 1 < gridX) {
//set s to right
s = board[s.x + 1][s.y];
//handleWireMove the move
handleWireMove();
}
continue;
} else {
//reduce the number as we passed an option
randomChoiseNumber--;
}
}
//if top is an option
if (s.Top == null) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while top is a choise and the length is not reached and top is not off the map
while (s.Top == null && length < maxLength && s.y - 1 >= 0) {
//move to the top
s = board[s.x][s.y - 1];
//handleWireMove the move
handleWireMove();
}
//continue the direction is used up
continue;
} else {
//we passed a number reduce the choise number
randomChoiseNumber--;
}
}
//if bottom is an option
if (s.Bottom == null) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while bottom is a choise and the length is not reached and it is not off the map
while (s.Bottom == null && length < maxLength && s.y + 1 < gridY) {
//go to the bottom
s = board[s.x][s.y + 1];
//handleWireMove the move
handleWireMove();
}
}
}
}
}
else {
//if there was no way to go the square is blank tell the others and increace usedSquares
s.isBlank = true;
informAll();
}
}
}
function drawAll() {
board.forEach(a => a.forEach(b => b.drawSelf()));
}
function draw() {
background(gridX * perX);
drawAll();
}
The problem is in the setup function but I can't find it. Please help
The problem is that you compute the number of available numOption only based on whether the randomly selected grid square has unconnected slots (Left, Top, Right, or Bottom) without considering whether the value of s.x and s.y make it possible to select one of those directions). As a result your inner loop repeats infinitely because it never calls handleWireMove() and thus never increases length.
The code below is modified with some extra logging and it becomes obvious where the infinite loop is (it eventually prints 'we failed to make a choice. that is not good' forever):
//the number of squares in the grid used
usedSquares = 0;
//till the board is full
while (usedSquares < gridX * gridY) {
console.log(usedSquares);
//get a random x y
var rx = floor(random(gridX));
var ry = floor(random(gridY));
//create an s and begin var s for every nw square and begin for the first
s = board[rx][ry];
//if this square is already in use
if (s.isBlank || s.isCircut || s.isWire) {
continue;
}
//begin at this square
begin = s;
//if there is some way to go
if (
s.Left == null ||
s.Right == null ||
s.Top == null ||
s.Bottom == null
) {
// get a random length to try and reach
var maxLength = floor(random(1, theHighestMaxLength));
//the starting length
length = 0;
begin.isCircut = true;
//inform all the surounding squares and increase the number of used
informAll();
//while the length isn't full
console.log('beggining inner loop');
while (length <= maxLength) {
//add an option count for left right top botoom
var numOption = s.Left == null ? 1 : 0;
numOption = s.Right == null ? numOption + 1 : numOption;
numOption = s.Top == null ? numOption + 1 : numOption;
numOption = s.Bottom == null ? numOption + 1 : numOption;
//if there are no toptions to move we must stop here ot if the maxLength is reached
if (numOption == 0 || length == maxLength) {
//this is a circut the beigin circut is begin the begin other circut is this final one
s.isCircut = true;
s.isWire = false;
s.otherCicut = begin;
begin.otherCircut = s;
//make sure the loop ends
console.log('no options, abort');
length = 9999;
break;
}
//pick a random direction number
var randomChoiseNumber = floor(random(numOption));
//if left is an option
if (s.Left == null) {
//if r is already 0 that means we picked left
if (randomChoiseNumber == 0) {
//while left is an option and the maxlength isn't hit and left isn't off the map
while (s.Left == null && length < maxLength && s.x - 1 >= 0) {
//set s to the left
s = board[s.x - 1][s.y];
//handleWireMove the move
handleWireMove();
}
//continue we used the direction
continue;
} else {
//we passed an option reduce the number
randomChoiseNumber--;
}
}
//if right is an option
if (s.Right == null) {
//if this is the zero choice
if (randomChoiseNumber == 0) {
//if right is not off the map and an option while the length is not hit
while (s.Right == null && length < maxLength && s.x + 1 < gridX) {
//set s to right
s = board[s.x + 1][s.y];
//handleWireMove the move
handleWireMove();
}
continue;
} else {
//reduce the number as we passed an option
randomChoiseNumber--;
}
}
//if top is an option
if (s.Top == null) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while top is a choise and the length is not reached and top is not off the map
while (s.Top == null && length < maxLength && s.y - 1 >= 0) {
//move to the top
s = board[s.x][s.y - 1];
//handleWireMove the move
handleWireMove();
}
//continue the direction is used up
continue;
} else {
//we passed a number reduce the choise number
randomChoiseNumber--;
}
}
//if bottom is an option
if (s.Bottom == null) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while bottom is a choise and the length is not reached and it is not off the map
while (s.Bottom == null && length < maxLength && s.y + 1 < gridY) {
//go to the bottom
s = board[s.x][s.y + 1];
//handleWireMove the move
handleWireMove();
}
}
}
console.log('we failed to make a choice. that is not good');
}
console.log('exited inner loop');
} else {
//if there was no way to go the square is blank tell the others and increace usedSquares
s.isBlank = true;
informAll();
}
}
Here is a working version that correctly checks not only if there is an opening to the Left/Top/Right/Bottom, but also if that direction is possible (not off the edge of the grid):
//maxLength of circut board is the board
let theHighestMaxLength = 10;
let board;
let gridX = 10;
let gridY = 10;
let perX = 10;
let perY = 10;
//s is here so we don't have to pass it everywhere the square we are looking at in functions
let s = null;
//length and usedsquares and begin are here to be used across functions
let length = 0;
let usedSquares = 0;
let begin = null;
class Asquare {
constructor(x, y) {
this.x = x;
this.y = y;
this.isCircut = false;
this.isWire = false;
this.OtherCircut = null;
this.Left = null;
this.Right = null;
this.Top = null;
this.Bottom = null;
this.otherCircut = null;
this.isBlank = false;
}
drawSelf() {
if (this.isCircut) {
rectMode(CENTER);
var xx = max(this.otherCircut.x, this.x);
var yy = max(this.otherCircut.y, this.y);
fill(color(xx * 25, yy * 25, 0));
square((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
} else if (this.isWire) {
fill(color(this.otherCircut.x * 20, this.otherCircut.y * 20, 0));
circle((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
} else if (this.isBlank) {
fill(color(0, 204, 0));
circle((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
}
}
}
function handleWireMove() {
s.isWire = true;
//remember the starting circut
s.otherCircut = begin;
informAll(s);
//the length is used
length++;
}
function informAll() {
//tell all the other squares of s
if (s.x - 1 >= 0) {
board[s.x - 1][s.y].Right = s;
}
if (s.x + 1 < gridX) {
board[s.x + 1][s.y].Left = s;
}
if (s.y - 1 >= 0) {
board[s.x][s.y - 1].Bottom = s;
}
if (s.y + 1 < gridY) {
board[s.x][s.y + 1].Top = s;
}
//the used squares is now higher
usedSquares++;
}
function setup() {
createCanvas(gridX * perX, gridY * perY);
noLoop();
//fill the board with squares
board = new Array(gridX).fill(0).map(() => new Array(gridY).fill(0));
for (var x = 0; x < gridX; x++) {
for (var y = 0; y < gridY; y++) {
board[x][y] = new Asquare(x, y);
}
}
//the number of squares in the grid used
usedSquares = 0;
//till the board is full
while (usedSquares < gridX * gridY) {
console.log(usedSquares);
//get a random x y
var rx = floor(random(gridX));
var ry = floor(random(gridY));
//create an s and begin var s for every nw square and begin for the first
s = board[rx][ry];
//if this square is already in use
if (s.isBlank || s.isCircut || s.isWire) {
continue;
}
//begin at this square
begin = s;
//if there is some way to go
if (
s.Left == null ||
s.Right == null ||
s.Top == null ||
s.Bottom == null
) {
// get a random length to try and reach
var maxLength = floor(random(1, theHighestMaxLength));
//the starting length
length = 0;
begin.isCircut = true;
//inform all the surounding squares and increase the number of used
informAll();
//while the length isn't full
console.log('beggining inner loop');
while (length <= maxLength) {
//add an option count for left right top botoom
var numOption = s.Left == null && s.x - 1 >= 0 ? 1 : 0;
numOption = s.Right == null && s.x + 1 < gridX ? numOption + 1 : numOption;
numOption = s.Top == null && s.y - 1 >= 0 ? numOption + 1 : numOption;
numOption = s.Bottom == null && s.y + 1 < gridY ? numOption + 1 : numOption;
//if there are no toptions to move we must stop here ot if the maxLength is reached
if (numOption == 0 || length == maxLength) {
//this is a circut the beigin circut is begin the begin other circut is this final one
s.isCircut = true;
s.isWire = false;
s.otherCicut = begin;
begin.otherCircut = s;
//make sure the loop ends
console.log('no options, abort');
length = 9999;
break;
}
//pick a random direction number
var randomChoiseNumber = floor(random(numOption));
//if left is an option
if (s.Left == null && s.x - 1 >= 0) {
//if r is already 0 that means we picked left
if (randomChoiseNumber == 0) {
//while left is an option and the maxlength isn't hit and left isn't off the map
while (s.Left == null && length < maxLength && s.x - 1 >= 0) {
//set s to the left
s = board[s.x - 1][s.y];
//handleWireMove the move
handleWireMove();
}
//continue we used the direction
continue;
} else {
//we passed an option reduce the number
randomChoiseNumber--;
}
}
//if right is an option
if (s.Right == null && s.x + 1 < gridX) {
//if this is the zero choice
if (randomChoiseNumber == 0) {
//if right is not off the map and an option while the length is not hit
while (s.Right == null && length < maxLength && s.x + 1 < gridX) {
//set s to right
s = board[s.x + 1][s.y];
//handleWireMove the move
handleWireMove();
}
continue;
} else {
//reduce the number as we passed an option
randomChoiseNumber--;
}
}
//if top is an option
if (s.Top == null && s.y - 1 >= 0) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while top is a choise and the length is not reached and top is not off the map
while (s.Top == null && length < maxLength && s.y - 1 >= 0) {
//move to the top
s = board[s.x][s.y - 1];
//handleWireMove the move
handleWireMove();
}
//continue the direction is used up
continue;
} else {
//we passed a number reduce the choise number
randomChoiseNumber--;
}
}
//if bottom is an option
if (s.Bottom == null && s.y + 1 < gridY) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while bottom is a choise and the length is not reached and it is not off the map
while (s.Bottom == null && length < maxLength && s.y + 1 < gridY) {
//go to the bottom
s = board[s.x][s.y + 1];
//handleWireMove the move
handleWireMove();
}
}
}
console.log('we failed to make a choice. that is not good');
}
console.log('exited inner loop');
} else {
//if there was no way to go the square is blank tell the others and increace usedSquares
s.isBlank = true;
informAll();
}
}
}
function drawAll() {
board.forEach((a) => a.forEach((b) => b.drawSelf()));
}
function draw() {
background(gridX * perX);
drawAll();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
One closing though: this bug is a symptom of this code being horribly complicated and violating fundamental principles of good design such as not having shared global state that is updated as a side effect of function calls.

Javascript canvas, collision detection via array not working

Basically, I have a program that makes a square, and stores the left, right, top and bottoms in an array. When it makes a new square, it cycles through the array. If the AABB collision detection makes the new square overlap with another square, it should make sure that the square is not displayed, and tries again. Here is a snippet of the code I made, where I think the problem is:
var xTopsBotsYTopsBotsSquares = [];
//Makes a randint() function.
function randint(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function checkForOccupiedAlready(left, top, right, bottom) {
if (xTopsBotsYTopsBotsSquares.length == 0) {
return true;
}
for (i in xTopsBotsYTopsBotsSquares) {
if (i[0] <= right || i[1] <= bottom ||
i[2] >= left || i[3] >= top) {/*Do nothing*/}
else {
return false;
}
}
return true;
}
//Makes a new square
function makeNewsquare() {
var checkingIfRepeatCords = true;
//DO loop that checks if there is a repeat.
do {
//Makes the square x/y positions
var squareRandomXPos = randint(50, canvas.width - 50);
var squareRandomYPos = randint(50, canvas.height - 50);
//Tests if that area is already occupied
if (checkForOccupiedAlready(squareRandomXPos,
squareRandomYPos,
squareRandomXPos+50,
squareRandomYPos+50) == true) {
xTopsBotsYTopsBotsSquares.push([squareRandomXPos,
squareRandomYPos,
squareRandomXPos+50,
squareRandomYPos+50]);
checkingIfRepeatCords = false;
}
}
while (checkingIfRepeatCords == true);
}
Any help is much appreciated.
Your loop is incorrect I think, since you use i as a value, whereas it is a key:
for (i in xTopsBotsYTopsBotsSquares) {
if (i[0] <= right || i[1] <= bottom ||
i[2] >= left || i[3] >= top) {/*Do nothing*/}
else {
return false;
}
}
Could become:
for (var i = 0, l < xTopsBotsYTopsBotsSquares.length; i < l; i++) {
var data = xTopsBotsYTopsBotsSquares[i];
if (data[0] <= right || data[1] <= bottom ||
data[2] >= left || data[3] >= top) {/*Do nothing*/}
else {
return false;
}
}

collision detection on game

I am trying to make a simple Javascript and Jquery game in which a user controls a blue ball and has to avoid red balls. I attempted to make a collision detection system that would get rid of the red balls when one was touched. This works however it occasionally detects a collision when there isn't one and I cannot figure out why. I made a jsfiddle of the game. By the way, I commented out the function which makes the red balls move so that it would be easier to test the collisions. Thanks
https://jsfiddle.net/clayjames/bxcffwty/4/
Here is the javascript for the game so far:
var keyDown = function() {
$(document).keydown(function(e) {
switch(e.which) {
case 37 :
$("#blue").css("marginLeft","-=40");
blue.xcordinate = blue.xcordinate - 40;
check();
break;
case 38 :
$("#blue").css("marginTop","-=40");
blue.ycordinate = blue.ycordinate - 40;
check();
break;
case 39 :
$("#blue").css("marginLeft","+=40");
blue.xcordinate = blue.xcordinate + 40;
check();
break;
case 40 :
$("#blue").css("marginTop","+=40");
blue.ycordinate = blue.ycordinate + 40;
check();
break;
};
});
};
var redX = [0];
var redY = [0];
var loop = 0;
var createRed = function() {
var randTop = Math.floor(1+Math.random()*600);
var randLeft = Math.floor(1+Math.random()*1308);
$("body").append($("<div></div>").addClass("red").css("marginTop",randTop).css("marginLeft",randLeft));
redX[loop] = randLeft;
redY[loop] = randTop;
loop++;
}
var redMovement = function(){
var redDirection = Math.floor(1 + Math.random() * 4);
switch(redDirection) {
case 1 :
$(".red").animate({"marginLeft": "-=20"});
break;
case 2 :
$(".red").animate({"marginLeft": "+=20"});
break;
case 3 :
$(".red").animate({"marginTop": "-=20"});
break;
case 4 :
$(".red").animate({"marginTop": "+=20"});
};
};
var blue = {
xcordinate: 630,
ycordinate: 320
}
var xMatch = false;
var yMatch = false;
var check = function() {
for(x = 0; x < redX.length; x++){
if(((redX[x] - 25) <= blue.xcordinate + 25 && blue.xcordinate + 25 <= (redX[x] + 25)) || ((redX[x] - 25) <= blue.xcordinate - 25 && blue.xcordinate - 25 <= (redX[x] + 25))) {
xMatch = true;
};
};
for(y = 0; y < redY.length; y++){
if(((redY[y] - 25) <= blue.ycordinate + 25 && blue.ycordinate + 25 <= (redY[y] + 25)) || ((redY[y] - 25) <= blue.ycordinate - 25 && blue.ycordinate - 25 <= (redY[y] + 25))) {
yMatch = true;
};
};
if (xMatch === true && yMatch === true) {
$(".red").remove();
};
};
$(document).ready(function() {
$("#blue").hide()
$(".red").remove()
$("#border").hide()
setInterval(function(){
$("h5").fadeOut(500).fadeIn(500)
},1000);
$("body").keydown(function(e){
if(e.which == 13) {
var clayRocks = setInterval(createRed,10000);
$("h1, h5").remove();
$("#blue, #border").show();
};
});
keyDown();
//setInterval(redMovement,500);
});
It is probably easier to just use absolute value, like this:
for (x = 0; x < redX.length; x++) {
if (Math.abs(redX[x] - blue.xcordinate) <= 25) {
xMatch = true;
};
};
Demo
testing the collision between 2 balls is easy (compared to others shapes), you have to calculates the distance of the 2 balls (using pythagore théorem) and see if this distance is less than the sum of the radius of the 2 balls.
I don't know if that's the only issue, but your check function is flawed. What it currently does is check if there is a collision on the x-coordinate with any red ball and a collision on the y-coordinate with any (other!) red ball. It does not enforce that both collisions occur on the same red ball.
By the way, this is a rather strange and inaccurate of way of detecting collisions between spherical balls: the natural one is to check if the euclidean distance between the centers is less than the sum of the radiuses, i.e
function collides(blueX, blueY, blueR, redX, redY, redR) {
return Math.sqrt(Math.pow(blueX - redX, 2) + Math.pow(blueY - redY, 2)) < blueR + redR;
}

Creating 2d platforms using JavaScript

I'm developing a HTML5 Canvas game using EaselJS and I've written a function that allows me to create "blocks" just by setting one or more images, size and position.
and by "blocks", what I mean is:
I'm doing this using two methods:
First method:
With this method the blocks are created in the available space inside the location I've set, using the images randomly.
Second method:
The blocks are created inside the location I've set using specific images for the top left corner, top side, top right corner, left side, center, right side, bottom left corner, bottom side and bottom right corner, and there can be more than a single image for each one of those parts (so the system uses a random one to avoid repeating the same image multiple times).
Ok, but what's the problem?
This function uses a zillion 77 lines (131 lines counting with the collision-detection-related part)! I know there's a better way of doing this, that will take about a half or less lines than it's taking now, but I don't know how to do it and when someone show me, I'll use the "right way" for the rest of my life. Can you help me?
What I want:
A possible way to use less lines is to use a single "method" that allows me to create blocks that are compound by blocks that are compound by the 9-or-more images (I just don't know how to do it, and I know it's difficult to understand. Try to imagine the third image being used 9 times). // This part of the question makes it on-topic!
Note that this question isn't subjective, since the goal here is to use less lines, and I'm not using the EaselJS tag because the question isn't EaselJS-specific, anyone with JavaScript knowledge can answer me.
Here's my incredibly big JavaScript function:
var Graphic = function (src, blockWidth, blockHeight) {
return {
createBlockAt: function (x, y, blockGroupWidth, blockGroupHeight, clsdir, alpha) {
for (var blockY = 0; blockY < blockGroupHeight / blockHeight; blockY++) {
for (var blockX = 0; blockX < blockGroupWidth / blockWidth; blockX++) {
var obj = new createjs.Bitmap(src[Math.floor(Math.random() * src.length)]);
obj.width = blockWidth;
obj.height = blockHeight;
if (typeof alpha !== 'undefined') {
obj.alpha = alpha; // While debugging this can be used to check if a block was made over another block.
}
obj.x = Math.round(x + (blockWidth * blockX));
obj.y = Math.round(y + (blockHeight * blockY));
stage.addChild(obj);
}
}
}
}
}
var complexBlock = function (topLeft, topCenter, topRight, middleLeft, middleCenter, middleRight, bottomLeft, bottomCenter, bottomRight, blockWidth, blockHeight) {
return {
createBlockAt: function (x, y, blockGroupWidth, blockGroupHeight, clsdir, alpha) {
for (var blockY = 0; blockY < blockGroupHeight / blockHeight; blockY++) {
for (var blockX = 0; blockX < blockGroupWidth / blockWidth; blockX++) {
if (blockY == 0 && blockX == 0) {
var obj = new createjs.Bitmap(topLeft[Math.floor(Math.random() * topLeft.length)]);
}
if (blockY == 0 && blockX != 0 && blockX != (blockGroupWidth / blockWidth - 1)) {
var obj = new createjs.Bitmap(topCenter[Math.floor(Math.random() * topCenter.length)]);
}
if (blockY == 0 && blockX == (blockGroupWidth / blockWidth - 1)) {
var obj = new createjs.Bitmap(topRight[Math.floor(Math.random() * topRight.length)]);
}
if (blockY != 0 && blockY != (blockGroupHeight / blockHeight - 1) && blockX == 0) {
var obj = new createjs.Bitmap(middleLeft[Math.floor(Math.random() * middleLeft.length)]);
}
if (blockY != 0 && blockY != (blockGroupHeight / blockHeight - 1) && blockX != 0 && blockX != (blockGroupWidth / blockWidth - 1)) {
var obj = new createjs.Bitmap(middleCenter[Math.floor(Math.random() * middleCenter.length)]);
}
if (blockY != 0 && blockY != (blockGroupHeight / blockHeight - 1) && blockX == (blockGroupWidth / blockWidth - 1)) {
var obj = new createjs.Bitmap(middleRight[Math.floor(Math.random() * middleRight.length)]);
}
if (blockY == (blockGroupHeight / blockHeight - 1) && blockX == 0) {
var obj = new createjs.Bitmap(bottomLeft[Math.floor(Math.random() * bottomLeft.length)]);
}
if (blockY == (blockGroupHeight / blockHeight - 1) && blockX != 0 && blockX != (blockGroupWidth / blockWidth - 1)) {
var obj = new createjs.Bitmap(bottomCenter[Math.floor(Math.random() * bottomCenter.length)]);
}
if (blockY == (blockGroupHeight / blockHeight - 1) && blockX == (blockGroupWidth / blockWidth - 1)) {
var obj = new createjs.Bitmap(bottomRight[Math.floor(Math.random() * bottomRight.length)]);
}
obj.width = blockWidth;
obj.height = blockHeight;
if (typeof alpha !== 'undefined') {
obj.alpha = alpha; // While debugging this can be used to check if a block was made over another block.
}
obj.x = Math.round(x + (blockWidth * blockX));
obj.y = Math.round(y + (blockHeight * blockY));
stage.addChild(obj);
}
}
}
}
}
var bigDirt = complexBlock(["http://i.imgur.com/DLwZMwJ.png"], ["http://i.imgur.com/UJn3Mtb.png"], ["http://i.imgur.com/AC2GFM2.png"], ["http://i.imgur.com/iH6wFj0.png"], ["http://i.imgur.com/wDSNzyc.png", "http://i.imgur.com/NUPhXaa.png"], ["http://i.imgur.com/b9vCjrO.png"], ["http://i.imgur.com/hNumqPG.png"], ["http://i.imgur.com/zXvJECc.png"], ["http://i.imgur.com/Whp7EuL.png"], 40, 40);
bigDirt.createBlockAt(0, 0, 40*3, 40*3);
Okay... Lots of code here, how do I test?
Here we go: JSFiddle
I don't see an easy way to reduce the number of lines given the nine possible branches, but you can substantially reduce the repetition in your code:
function randomImage(arr) {
return new createjs.Bitmap(arr[Math.floor(Math.random() * arr.length)]);
}
if (blockY == 0 && blockX == 0) {
var obj = randomImage(topLeft);
} // etc
Re: the nine possible branches, you should note that they are mutually exclusive, so should be using else if instead of just if, and that they are also naturally grouped in threes, suggesting that they should be nested.
EDIT in fact, there is a way to reduce the function size a lot. Note that for X and Y you have three options each (nine in total). It is possible to encode which image array you want based on a two-dimensional lookup table:
var blocksHigh = blockGroupHeight / blockHeight;
var blocksWide = blockGroupWidth / blockWidth;
var blockSelector = [
[topLeft, topCenter, topRight],
[middleLeft, middleCenter, middleRight],
[bottomLeft, bottomCenter, bottomRight]
];
for (var blockY = 0; blockY < blocksHigh; blockY++) {
var blockSY = (blockY == 0) ? 0 : blockY < (blocksHigh - 1) ? 1 : 2;
for (var blockX = 0; blockX < blocksWide; blockX++) {
var blockSX = (blockY == 0) ? 0 : blockY < (blocksWide - 1) ? 1 : 2;
var array = blockSelector[blockSY][blockSX];
var obj = randomImage(array);
...
}
}
Note the definitions of blocksHigh and blocksWide outside of the loop to reduce expensive repeated division operations.
See http://jsfiddle.net/alnitak/Kpj3E/
Ok, it's almost a year later now and I decided to come back here to improve the existing answers. Alnitak's suggestion on creating a "2-dimensional lookup table" was genius, but there's a even better way of doing what I was asking for.
Sprite Sheets
The problem core is the need for picking lots of separated images and merge them in order to create a bigger mosaic. To solve this, I've merged all images into a sprite sheet. Then, with EaselJS, I've separated each part of the platform (topLeft, topCenter, etc) in multiple animations, and alternative images of the same platform part that would be used randomly are inserted within it's default part animation, as an array (so topLeft can be five images that are used randomly).
This was achieved by making a class that creates an EaselJS container object, puts the sprite sheet inside this container, moves the sprite sheet to the correct position, caches the frame and updates the container cache using the "source-overlay" compositeOperation — which puts the current cache over the last one — then it does this again until the platform is finished.
My collision detection system is then applied to the container.
Here's the resulting JavaScript code:
createMosaic = function (oArgs) { // Required arguments: source: String, width: Int, height: Int, frameLabels: Object
oArgs.repeatX = oArgs.repeatX || 1;
oArgs.repeatY = oArgs.repeatY || 1;
this.self = new createjs.Container();
this.self.set({
x: oArgs.x || 0,
y: oArgs.y || 0,
width: ((oArgs.columnWidth || oArgs.width) * oArgs.repeatX) + oArgs.margin[1] + oArgs.margin[2],
height: ((oArgs.lineHeight || oArgs.height) * oArgs.repeatY) + oArgs.margin[0] + oArgs.margin[3],
weight: (oArgs.weight || 20) * (oArgs.repeatX * oArgs.repeatY)
}).set(oArgs.customProperties || {});
this.self.cache(
0, 0,
this.self.width, this.self.height
);
var _bmp = new createjs.Bitmap(oArgs.source);
_bmp.filters = oArgs.filters || [];
_bmp.cache(0, 0, _bmp.image.width, _bmp.image.height);
var spriteSheet = new createjs.SpriteSheet({
images: [_bmp.cacheCanvas],
frames: {width: oArgs.width, height: oArgs.height},
animations: oArgs.frameLabels
});
var sprite = new createjs.Sprite(spriteSheet);
this.self.addChild(sprite);
for (var hl = 0; hl < oArgs.repeatY; hl++) {
for (var vl = 0; vl < oArgs.repeatX; vl++) {
var _yid = (hl < 1) ? "top" : (hl < oArgs.repeatY - 1) ? "middle" : "bottom";
var _xid = (vl < 1) ? "Left" : (vl < oArgs.repeatX - 1) ? "Center" : "Right";
if(typeof oArgs.frameLabels[_yid + _xid] === "undefined"){
oArgs.frameLabels[_yid + _xid] = oArgs.frameLabels["topLeft"];
} // Case the expected frameLabel animation is missing, it will default to "topLeft"
sprite.gotoAndStop(_yid + _xid);
if (utils.getRandomArbitrary(0, 1) <= (oArgs.alternativeTileProbability || 0) && oArgs.frameLabels[_yid + _xid].length > 1) { // If there are multiple frames in the current frameLabels animation, this code choses a random one based on probability
var _randomPieceFrame = oArgs.frameLabels[_yid + _xid][utils.getRandomInt(1, oArgs.frameLabels[_yid + _xid].length - 1)];
sprite.gotoAndStop(_randomPieceFrame);
}
sprite.set({x: vl * (oArgs.columnWidth || oArgs.width), y: hl * (oArgs.lineHeight || oArgs.height)});
this.self.updateCache("source-overlay");
}
}
this.self.removeChild(sprite);
awake.container.addChild(this.self);
};
Usage:
createMosaic({
source: "path/to/spritesheet.png",
width: 20,
height: 20,
frameLabels: {
topLeft: 0, topCenter: 1, topRight: 3,
middleLeft: 4, middleCenter: [5, 6, 9, 10], middleRight: 7,
bottomLeft: 12, bottomCenter: 13, bottomRight: 15
},
x: 100,
y: 100,
repeatX: 30,
repeatY: 15,
alternativeTileProbability: 75 / 100
});
I would recommend using the "createMosaic" as a function returned by a constructor that passes the required arguments to it, so you'll not need to write the source image path, width, height and frameLabels every time you want to create a dirt platform, for example.
Also, this answer may have more LoC than the others that came before, but it's made this way in order to have more structure.

Categories