HTML5 Snake Game - add multiple food - javascript

I have followed this guide to make my snake game.
But now I want to add more then one food into the map.
Well, I tried putting rows with makeFoodItem(); instead of just the default 1 row.
So like:
makeFoodItem();
makeFoodItem();
makeFoodItem();
makeFoodItem();
makeFoodItem();
Which also made 5 foods. But the score wasn't added when the snake got them, and they was just removed, and didn't added a new one when it was taken.
So I went into looking at the Function makeFoodItem(), which looks like this:
function makeFoodItem(){
suggestedPoint = [Math.floor(Math.random()*(canvas.width/gridSize))*gridSize, Math.floor(Math.random()*(canvas.height/gridSize))*gridSize];
if (snakeBody.some(hasPoint)) {
makeFoodItem();
} else {
ctx.fillStyle = "rgb(10,100,0)";
ctx.fillRect(suggestedPoint[0], suggestedPoint[1], gridSize, gridSize);
};
}
but I could not really figure out what to do there.

function spawnMultipleFood () {
for (let i = 0; i < spawnMoreFoodX.length; i++) {
let spawnMultipleX = spawnMoreFoodX[i]
for (let j = 0; j < spawnMoreFoodY.length; j++) {
let spawnMultipleY = spawnMoreFoodY[i]
innerBoard.fillStyle = "purple";
innerBoard.fillRect(spawnMultipleX * grid, spawnMultipleY * grid, gridSize, gridSize)
}
}
}
My global vars for spawnMoreFoodX and spawnMoreFoodY are an array that I loop through and set that to another variable, then plug that var in to fillRect().

Related

Uncaught TypeError: this.activeArray[i] is undefined

I have been having a problem. I am trying to make Conway's Game of Life with javascript using one big class for the game. However, I get this error when I run my finished product despite using the function in the exact same way in another function. I declared the array here:
class Game {
constructor() {
this.cellSize = 5;
this.aliveColor = "#FF756B";
this.deadColor = "#181818";
this.rows = Math.floor(canvas.height / this.cellSize);
this.columns = Math.floor(canvas.width = this.cellSize);
//Variables for two lifecycles, active and past iteration
this.inactiveArray = [];
this.activeArray = [];
And call on the array in these two methods:
this.drawCells = () => {
for (let i=0; i<this.rows; i++) {
for (let j=0; j<this.columns; j++) {
let color;
if (this.activeArray[i][j] === 1) {
color = this.aliveColor;
} else {
color = this.deadColor;
}
ctx.fillStyle = color;
ctx.fillRect(i * this.cellSize, j * this.cellSize, this.cellSize, this.cellSize);
}
}
}
this.arraysInit = () => {
for(let i=0; i<this.rows; i++) {
this.activeArray[i] = [];
for(let j=0; j<this.columns; j++) {
this.activeArray[i][j] = 0;
}
}
this.inactiveArray = this.activeArray;
}
(Sorry for the bad indentation, this is my first post) The arraysInit function runs with no errors for sure as I have tried debugging all of it (to no prevail). An error is thrown in drawCells() on the line "if (this.activeArray[i][j] === 1) {" and I have no clue why, as I have clearly defined the array and used it.
One thing I noticed, Cidom10, is that your line this.columns = Math.floor(canvas.width = this.cellSize); has an = sign where it looks like it should be a division / sign. Not sure if that is just a typo or what.
But, I believe the real problem lies with the line this.inactiveArray = this.activeArray; which sets this.inactiveArray to be a reference to this.activeArray. It doesn't copy the array, which I believe you are trying to do with that line.
To copy the activeArray, you can use something like Object.assign:
this.inactiveArray = Object.assign([], this.activeArray);
My bet is that you are modifying this.inactiveArray at some point which is also modifying this.activeArray and causing some indexing issues and that's where those errors stem from.
Without more code to go off of, though, this is all in theory.

setInterval won't call function

I'm trying to create a sort of ecosystem where objects spawn over time. However, when I try using setInterval to increase the amount it doesn't work. It works when I call the function on its own, but not when I use setInterval.
var plantSpawn = 5;
function createPlants() {
setInterval(reproducePlants, 5000);
for(var i=0; i<plantSpawn; i++){
var plant = new Object();
plant.x = Math.random() * canvas.width;
plant.y = Math.random() * canvas.height;
plant.rad = 2;
plant.skin = 'green';
myPlants[i] = plant;
}
}
function reproducePlants() {
plantSpawn += 5;
}
My goal for this is for every 5 seconds, 5 new plants appear. However, when I use the reproducePlants function with setInterval it does not work.
Note: I am calling createPlants() later in my code which makes the first 5 plants show up, but the next 5 won't show up. I am just showing the code that I'm trying to fix
The creation code must be moved inside the function that is repeatedly called.
NOTE: This is not an efficient way if you are going to call reproducePlants infinitely many times, since the myPlants array is reconstructed every time.
// Init with 0, because we increment it inside reproduce Plants
var plantSpawn = 0;
var myPlants = [];
function createPlants() {
reproducePlants();
setInterval(reproducePlants, 5000);
}
function reproducePlants() {
const canvas = document.getElementsByTagName('canvas')[0];
plantSpawn += 5;
for(var i = 0; i < plantSpawn; i++) {
var plant = new Object();
plant.x = Math.random() * canvas.width;
plant.y = Math.random() * canvas.height;
plant.rad = 2;
plant.skin = 'green';
myPlants[i] = plant;
}
}
You don't necessarily need to call the createPlants function from the reproducePlants function. You could add a call after both the functions. If I understand what you are trying to achieve that should do it.
You need to move the code that creates the plants (the for() chunck) inside the function that is called every 5 seconds (reproduce plants). So each time the function is called it will create the plants.
If you are trying to add only the 5 new plants every 5 seconds to your plants array you shouldn't recreate the array each time. It's better to keep track of the last index you have added and then continue right after that.
I created a variable called lastCreatedIndex so you can understand better what is going on. So the first time the code will run plants[i] from 0 to 4, the second 5 to 9...
var myPlants = [];
var lastCreatedIndex;
var plantSpawn;
function createPlants() {
plantSpawn = 5; //Initialize with 5 plants
lastCreatedIndex = 0; // Starts from index 0
reproducePlants();
setInterval(reproducePlants, 5000);
}
function reproducePlants() {
for(var i = 0 + lastCreatedIndex; i < plantSpawn; i++) {
var plant = new Object();
plant.x = Math.random() * canvas.width;
plant.y = Math.random() * canvas.height;
plant.rad = 2;
plant.skin = 'green';
myPlants[i] = plant;
console.log(i); // Output the number of the plant that has been added
}
lastCreatedIndex = i; //Update the last index value
plantSpawn += 5;
}

Javascript Snake Game (food spawn location)

Assume I have a "grid" of 20ish by 20ish. The way that I randomly spawn food is with the following formula:
Math.floor(Math.random() * ((max - min) / 20) + 1) + 20 * min;
This basically ensures that food will spawn neatly within one of the grid cells.
I want to avoid spawning food on top of the snake's body. I haven't found the best way to do this while maintaining distributed randomness.
Solutions that don't quite work:
I've seen some people do this but it doesn't work because it doesn't guarantee that food will never spawn on the snake, it just makes it less likely. getRandomPoint() might still get me a random point on the snake.
for (let i = 0; i < snake.length; i++) {
if (foodPosition.x === snake[i].x && foodPosition.y === snake[i].y) {
foodPosition.x = getRandomPoint();
foodPosition.y = getRandomPoint();
}
}
This next one doesn't work either because food can still spawn on any cell of the snake that hasn't been checked yet. For example, when i = 0, it only checks if foodPosition.x and foodPosition.y are equal to snake[0].x and snake[0].y. On this iteration of the loop when i = 0, food can still spawn on the cell occupied by snake[2].x, snake[2].y for instance (I think?).
for (let i = 0; i < snake.length; i++) {
while (foodPosition.x === snake[i].x && foodPosition.y === snake[i].y) {
foodPosition.x = getRandomPoint();
foodPosition.y = getRandomPoint();
}
}
There are some other things I've tried but I'll leave it at that. What is a good way to do the kind of thing I'm trying to do?
I would generate a list of possible locations where the food is allowed to spawn. Something like the set of all locations minus the set of snake body locations.
I've done something similar with Minesweeper in JavaScript and p5.js. Two mines cannot occupy the same cell. As I generated mines, I removed their location from the list of possible locations.
let numRows = 20;
let numColumns = 20;
let possibleFoodLocations = [];
for (let row = 0; row < numRows; row++) {
for (let column = 0; column < numColumns; column++) {
let isLocationValid = snake.every(bodyPart => {
return row !== bodyPart.y || column !== bodyPart.x;
});
if (isLocationValid) {
possibleFoodLocations.push({ x: column, y: row });
}
}
}
Note that this will take numRows * numColumns * snake.length * 2 operations, so it will become inefficient to repeatedly perform when any of those values are large. The best method, I think, would be a combination of this when these values are large, and choosing a random point when these values are small.
I'd add a flag in your solution and then it will work
let colliding = true;
let new_x, new_y;
while(colliding) {
colliding = false;
new_y = getRandomPoint();
new_x = getRandomPoint();
for (let box of snake) {
if (new_x == box.x && new_y == box.y) {
colliding = true;
break;
}
}
}
foodPosition.x = new_x;
foodPosition.y = new_y;
Would it work for you to get your random point and then check to make sure it doesn't match any of the snake's points? If it does, you could just get another random point.

Bomberman Vanilla JS

I'm trying to make bomberman using vanilla JS, for my examination project.
I am a little stuck right now with how to take out the bombs in the array and push them into the array again after they explode.
They need to explode after 2 seconds.
My code for bombs:
function bombPlayerOne() {
let ss = new createjs.SpriteSheet(game.q.getResult('bomb'))
let temp = new createjs.Sprite(ss, "bombIt");
temp.x = playerOne.x;
temp.y = playerOne.y;
game.stage.addChild(temp);
powerUps.bombs.push(temp);
console.log("player one placed a bomb");
for (var i = powerUps.bombs.length - 1; i > 0; i--) {
powerUps.bombs.splice;
// TODO : tween bomber ud...
powerUps.bombs.push;
}
}
function bombPlayerTwo() {
let ss = new createjs.SpriteSheet(game.q.getResult('bomb'))
let temp = new createjs.Sprite(ss, "bombIt");
temp.x = playerTwo.x;
temp.y = playerTwo.y;
game.stage.addChild(temp);
powerUps.bombs.push(temp);
console.log("player two placed a bomb");
for (var i = powerUps.bombs.length - 1; i > 0; i--) {
powerUps.bombs.splice;
// TODO : tween bomber ud...
powerUps.bombs.push;
}
}
So you have a few options, and FYI this isn't necessarily a javascript question so much as how do you handle game logic/code design type of question.
1) A bomb when placed contains a reference back to it's owner. ie
bomb.owner = playerOne
2) You have a manager that controls the state of a level, which keeps track of bombs
LevelManager.player1Bombs = ....
3) You have an array of bombs placed belonging to each player, which you then update during your logic update calls.
function gameUpdate(long milliSecondsSinceLastFrame){
for(bomb in playerOne.placedBombs){
if(bomb.isExploded){
//do other cleanup
playerOne.availableBombs ++;
}
}
//... do same for player 2 etc
}
All of them have their own advantages/disadvantages.

Optimize looping through 2 arrays javascript canvas game

I'm working on my first javascript canvas game, and I wonder is there a better way for comparing collisons between objects in 2 arrays. For example i have an array with rockets, and array with enemies, the code is working, but i think when arrays length becomes much larger it will have effect on the performance. Example 100 rockets through 100 enemies is 10000 iterations per frame
for (i in rockets){
rockets[i].x+=projectile_speed;
for (j in enemies){
if(collision(rockets[i], enemies[j])){
enemies[j].health-=5;
sound_hit[hit_counter-1].play();
hit_counter--;
if (hit_counter==0){
hit_counter=5;
}
rockets.splice(i,1);
if (enemies[j].health <= 0) {
score += enemies[j].score;
sound_explode[Math.floor(Math.random()*25)].play();
enemies[j].isDead = true;
}
} else if(rockets[i].x >= width){
rockets.splice(i,1);
}
}
}
If you want to test every rocket on every player its not really possible to do differently, without knowing more about position of players and rockets.
If you keep the collision function fast, this should though be no problem at all.
I can only think of two easy improvements on this:
when a collision is found use continue since looping over the rest of players should not be necessary (unless players is allowed to collide)
instead of splice'ing the rockets array multiple times, build a new one, excluding all "dead" rockets.
You should also consider using forEach, map and filter to make the code a bit easier to read:
rockets = rockets.filter(function(rocket) {
rocket.x+=projectile_speed;
if(rocket.x >= width) {
return false;
}
var enemy = enemies.find(function(enemy) { return collision(rocket, enemy) });
if(enemy) {
enemy.health-=5;
sound_hit[--hit_counter].play();
if (hit_counter==0){
hit_counter=5;
}
if (enemy.health <= 0) {
score += enemy.score;
sound_explode[Math.floor(Math.random()*25)].play();
enemy.isDead = true;
}
return false;
}
return true;
});
What you could try to do is to reduce the number of tests by grouping the enemies and rockets, so that you only have to test the elements in the same group.
Here is a simple implementation to show what I mean, this only partitions in X-direction, because your rockets only seem to travel horizontally:
var groupWidth = 100; // do some experiments to find a suitable value
var rocketGroups = [];
var enemyGroups = [];
// initalize groups, not shown (array of array of rocket/enemy),
// but here are some other function examples...
function addToGroups(element, groups) {
groups[element.x / groupWidth].push(element);
}
function move(element, groups, distance) {
if (element.x / groupWidth != (element.x + distance) / groupWidth) {
// remove element from the old group and put it in the new one
}
element.x += distance;
}
// Note: this is only to show the idea, see comments about length
function checkCollisions() {
var i,j,k;
for (i = 0; i < rocketGroups.length; i++) {
for (j = 0; j < rocketGroups[i].length; j++) {
for (k = 0; k < enemyGroups[i].length; k++) {
// only compares elements in the same group
checkPossibleCollision(rocketGroups[i], enemyGroups[i], j, k);
}
}
}
}

Categories