Javascript Snake Game (food spawn location) - javascript

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.

Related

2D visibility algorithm not works well for overlapped case

I find the visibility algorithm in one great website
Since it is quiet long ago article, I am not sure that the author will give the answer.
As you can see, the interactive playground(try it yourself) does not work well in right upper side, especially overlapping edges.
// Pseudocode of main algorithm
var endpoints; # list of endpoints, sorted by angle
var open = []; # list of walls the sweep line intersects
loop over endpoints:
remember which wall is nearest
add any walls that BEGIN at this endpoint to 'walls'
remove any walls that END at this endpoint from 'walls'
figure out which wall is now nearest
if the nearest wall changed:
fill the current triangle and begin a new one
Here is also part of working JavaScript code
// Main algorithm with working Javascript code
export const calculateVisibility = (origin, endpoints) => {
let openSegments = [];
let output = [];
let beginAngle = 0;
endpoints.sort(endpointCompare);
for(let pass = 0; pass < 2; pass += 1) {
for (let i = 0; i < endpoints.length; i += 1) {
let endpoint = endpoints[i];
let openSegment = openSegments[0];
if (endpoint.beginsSegment) {
let index = 0
let segment = openSegments[index];
while (segment && segmentInFrontOf(endpoint.segment, segment, origin)) {
index += 1;
segment = openSegments[index]
}
if (!segment) {
openSegments.push(endpoint.segment);
} else {
openSegments.splice(index, 0, endpoint.segment);
}
} else {
let index = openSegments.indexOf(endpoint.segment)
if (index > -1) openSegments.splice(index, 1);
}
if (openSegment !== openSegments[0]) {
if (pass === 1) {
let trianglePoints = getTrianglePoints(origin, beginAngle, endpoint.angle, openSegment);
output.push(trianglePoints);
}
beginAngle = endpoint.angle;
}
}
}
return output;
};
In my opinion, it happens since the algorithm thinks right edge is shown whole part when it is the closest edge although it can be hidden from whole edges.
I think I can modify the algorithm to use the current closest edge and newer current closest edge but not sure...
How should I fix it?

How to stop running loop when identical array is found?

I am making a tic tac toe game. Each square on the board has an index from 0 to 8. To check the winner, I have a two-dimensional array winningPlays that contains all potential winning combinations. I have two arrays that contain plays by Xs or by Os - xPlays and oPlays, respectively. After sorting xPlays, I want to compare it to winningPlaysto see if any of the arrays match. If they do, I want to console.log('X wins'). I can't seem to find a way to execute the console.log at the right time to determine the winner. Here is the piece of problem code:
const winningPlays = [
[0,1,2], //across top
[3,4,5], //across middle
[6,7,8], //across bottom
[0,3,6], //left down
[1,4,7], //middle down
[2,5,8], //right down
[0,4,8], //top left to bottom right
[2,4,6] // top right to bottom left
]; //length == 8
function checkForWinner() {
for(let i = 0; i < winningPlays.length; i++){
for(let j = 0; j < winningPlays[i].length; j++){
if (xPlays.length < 3) {
return;
} else if (winningPlays[i][j] !== xPlays[j]) {
console.log(winningPlays[i][j])
console.log(xPlays[j])
return;
}
console.log('win') // executes every time that xPlays.length >= 3
}
}
};
And here is a link to my codepen draft: https://codepen.io/CDLWebDev/pen/gOawjvE
You have several problems.
First, you return from the function as soon as you find a mismatch, but a later element of winningPlays could match.
Second, you're expecting xPlays to exactly match one of the winningPlays elements. But X could have additional plays. For instance, if xPlays = [2, 3, 4, 5], that should match[3, 4, 5]. What you really want to test is if all the elements of one of thewinningPlayselements are included inxPlays`, they don't have to have the same index.
function checkForWinner() {
if (xPlays.length < 3) {
return;
}
for (let i = 0; i < winningPlays.length; i++) {
let win = true;
for (let j = 0; j < winningPlays[i].length; j++) {
if (!xPlays.includes(winningPlays[i][j])) {
win = false;
break;
}
}
if (win) {
console.log('win');
break;
}
}
}
Go through each sub-array
Check if all the items of that subArray are present in players array
function checkWinner() {
return winningPlays.some(list => {
return list.every(item => {
return xPlays.includes(item);
});
});
}
I would make it simpler by doing like following
const winningPlays = [
"0,1,2", //across top
"3,4,5", //across middle
"6,7,8", //across bottom
"0,3,6", //left down
"1,4,7", //middle down
"2,5,8", //right down
"0,4,8", //top left to bottom right
"2,4,6" // top right to bottom left
]; //length == 8
function checkForWinner(xPlays) {
var convertXPlays = xPlays.toString(); //if there are spaces in your array, make sure to remove it
if (winningPlays.indexOf(convertXPlays) > -1)
{
console.log('win!');
}
};

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

Blackjack javascript game infinite loop

I have created an utterly simple blackjack game that stores the first value of a shuffled array of cards into corresponding players' arrays, dealing them as actual hands. For some odd reason, I can't seem to find a way to execute the core part of the code multiple times without getting an infinite loop. For the time being, I have only tried running the quite commonplace "for" command which is meant for multiple statements, but just doesn't seem to work here.
The programm on its primitive form is as follows...
var dealerCards = [];
var playerCards = [];
var firstDeck = [];
function shuffle(o){
for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
}
function createShuffledDeckNumber(array, x) {
for (i = 0; i < 4*x; i++) {
array.push(1,2,3,4,5,6,7,8,9,10,11,12,13);
}
shuffle(array);
}
function drawCard(playersHand, playerSoft, playerHard) {
playersHand.push(firstDeck[0]);
firstDeck.shift();
}
function checkDeckDrawOne(playersHand) {
if (firstDeck.length === 0) {
createShuffledDeckNumber(firstDeck, 1);
drawCard(playersHand);
}else{
for (i = 0; i < 1; i++) {
drawCard(playersHand);
}
}
}
for (i = 0; i < 4; i++) {
dealerCards = [];
playerCards = [];
checkDeckDrawOne(dealerCards);
checkDeckDrawOne(dealerCards);
checkDeckDrawOne(playerCards);
checkDeckDrawOne(playerCards);
console.log("dealerCards",dealerCards,"playerCards",playerCards);
console.log("firstDeckDrawn", firstDeck, "Number", firstDeck.length);
}
Additional Notes;
The presumed objective could be performing calculations to figure out the winner by imitating the effect of consecutive computing rounds based on a finite number of values stored in each player's array. Although, I've tried a seried of different things when it comes to emulating the real life circumstances of actually playing blackjack, this version seems to do just that, by also giving the programmer the ability to introduce counting systems like KO or HiLo. The main logic behind the whole thing is fairly simple; order x shuffled decks whenever a command that involves drawing a card is being executed unless the deck has at least one card.
It's rather fair to ponder why should I possibly bother creating multiple rounds in such a game. Reason is, that I want to create an autoplayer application that provides me with percentages on processed data.
Your variable i in function checkDeckDrawOne() has global scope, meaning it alters the value of i in the main loop:
for (i = 0; i < 4; i++) {
dealerCards = [];
playerCards = [];
checkDeckDrawOne(dealerCards);
checkDeckDrawOne(dealerCards);
checkDeckDrawOne(playerCards);
checkDeckDrawOne(playerCards);
console.log("dealerCards",dealerCards,"playerCards",playerCards);
console.log("firstDeckDrawn", firstDeck, "Number", firstDeck.length);
}
Change this:
for (i = 0; i < 1; i++) {
drawCard(playersHand);
}
to this:
for (var i = 0; i < 1; i++) {
drawCard(playersHand);
}
although why you need a loop here anyway is baffling.

HTML5 Snake Game - add multiple food

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

Categories