I am trying to build a Tetris game in javaScript and HTML. I ran into problems with my function for removing the full lines. It sort of works, but not every time and not as I expected. I am not a professional programmer, I am learning to code just for fun. My knowledge of javaScript is limited and my methods are primitive. My board is made of div tags. Each of them represents one square and has an id with unique number. Currently falling shape is stored by an array called “brick”. It contains four numbers, which corresponds to the div ids.
For example brick = [2, 2, 3, 5] means Tetris “l” piece positioned horizontally. There are 12 squares in a row. The first and last has the class “fender,” which means, that they cannot became part of the brick, and so the shape could not leave the board.
The fender squares are made invisible by CSS. The brick is falling by adding 12. From [2, 3, 4, 5] it is [14, 15, 16, 17], etc. The first four rows have display set to “none,” so the visible board starts by square number 63.
On the beginning, every square of the board (except the fender squares) has a class “free,” which means that it is permeable for falling shape. When it does not have the class, the shape can’t move through it. When the brick hits the bottom or another fallen shape, it is added to another variable, called pile.
This is done by a function called touchdown(). It does several other things. The color of the brick is stored in another variable called colorPile. For example when the blue brick [2, 3, 4, 5] lands, it adds to colorPile variable four times “blue”. Touchdown() also removes the class “free” from all the squares of the brick, so the next brick can’t fall through the occupied space. Then it calls for the creation of a new brick.
The deleting process starts with function called testLines():
// Look through all board lines
for (var i = 62; i < 300; i += 12) {
// Prepare empty array
line = [];
// Put numbers of occupied squares in the array.
for (var j = i; j < i + 10; j++) {
if (document.getElementById("sq" + j).classList.contains("free") === false) {
line.push(j);
}
}
// When the line is full, put its number in “numb” variable and call for deleting function
if (line.length == 10) {
numb = i;
clearLine(numb);
}
}
}
}
It goes through all lines of the board and look for the full ones. If there is a full line, it stores its number in a variable called “numb” and calls another function named clearLine():
function clearLine() {
// Put the indexes of the squares in the full line in a special array
indexes = [];
for (var i = numb; i < numb + 10; i++) {
indexes.push(pile.indexOf(i));
}
// Remove numbers on the indexes
pileindexor = indexes.length;
while (pileindexor--) {
pile.splice(indexes[pileindexor], 1);
}
// Add 12 to all numbers greater than the line number (= move them on row down)
for (var j = 0; j < pile.length; j++) {
if (pile[j] < numb) {
pile[j] = (pile[j] + 12);
}
}
clearColorPile();
paintPile();
clearBoard();
lines++;
testLines();
}
It also calls function called clearColorPile(), which removes colors on same indexes from the "colorPile" variable. Next it calls another function called paintPile(). The paintPile() function changes color of all squares in the "pile" variable according to the modified "colorPile" variable and removes “free” class from them.
Than the clearLine() calls another function called clearBoard(). It paints all squares not belonging to the pile white and ads the “free” class to them. Finally, the clearLine() function calls again the testLines() to look for more full rows.
This procedure is behaving as I expected for most of the time. But sometimes, the full line is not cleared completely. My game is here. You can see for yourselves. What have I done wrong? How to delete the full line?
Related
I am pretty new to JavaScript and PIXI. I am making a little game that requires the player to navigate and grab a key (move onto the same space as the key). This takes them to the next level. My problem is that whenever a new level is generated, it somehow uses the x and y coordinates for the key of the previous level to skew the logic. Because of this, the first level works fine, but every level afterwards is messed up.
The levels get skewed up and to the left by the respective values of the previous key. I have a 2d array in the background which holds values of where and where not the player can (holds values for things like walls, turrets, and the key). I randomly generate the key coordinates and for example set grid[9][12] = 3; for the key space. The next level visually looks perfect, with the walls and key generating, and the player being in the top left. However because of the skew, the top left of the visuals is really at [max_X - 9][max_Y - 12] and the player can go off screen to invisibly access the rest of the maze. So I can then phase through walls because the logic has the walls in a different place than the visuals, and the actual key space usually ends up off screen.
Here is my code that generates a new goal (key) object
function createGoal(scene) {
while (true) {
let x = getRandomInt(APP_WIDTH);
let y = getRandomInt(APP_HEIGHT);
//if space is not already occupied, populate
if (grid[x][y] == 0) {
console.log(x);
console.log(y);
goal = null;
goal = new Goal(TILE_WIDTH, x, y);
scene.addChild(goal);
grid[x][y] = 3;
break;
}
}
}
And here is the grid initialization:
const grid = new Array(APP_WIDTH);
for (let i = 0; i < APP_WIDTH; i++) {
grid[i] = new Array(APP_HEIGHT);
for (let j = 0; j < APP_HEIGHT; j++) {
grid[i][j] = 0;
}
}
Any help would be greatly appreciated.
I have an array of objects that I am editing.
This array varies on user selection, but generally looks like this:
[{boxID:0,name:"Apple Bread",weight:2}]
This array is declared as $scope.boxes
At a high level, this array is derived from a previously called function, the point of which is to pack items (such as Apple Bread) into boxes by weight, with no box exceeding a total weight of (in this case) 6 pounds. This array is thus filled with objects which represent the item, its weight, and the boxID (0 being the first box) that it is packed into.
My problem is, I am inserting in functionality to have the user edit the box, so that they can shuffle things around and customize their box, while still respecting the total weight limit.
To than end I have a function that utilizes drag-and-drop functionality to let the users edit and move items around their boxes. Once they are done, I need to validate what item belongs where. But I can't even get the ondragstart function to work properly. The idea is ondragstart to remove the item in question from the array, and then recalculate all the boxes, through the following:
$scope.boxyboxybox2 = function() {
$scope.displayBoxes = [];
for (var i = 0; i < $scope.boxes.length; i++) {
if (i == 0) {
$scope.displayBoxes.push({
box: $scope.boxes[i].boxID,
contents: [{
name: $scope.boxes[i].name,
weight: $scope.boxes[i].weight
}]
});
console.log($scope.displayBoxes);
continue;
}
for (var z = 0; z < $scope.displayBoxes.length; z++) {
if ($scope.boxes[i].boxID == $scope.displayBoxes[z].box) {
$scope.displayBoxes[z].contents.push({
name: $scope.boxes[i].name,
weight: $scope.boxes[i].weight
})
console.log($scope.boxes[i].boxID, $scope.displayBoxes[z].box, $scope.displayBoxes);
} else {
$scope.displayBoxes.push({
box: $scope.boxes[i].boxID,
contents: [{
name: $scope.boxes[i].name,
weight: $scope.boxes[i].weight
}]
});
console.log($scope.displayBoxes);
break;
}
}
}
console.log($scope.displayBoxes);
}
The above doesn't work, I'm not utilizing breaks or something properly, because variations of this either get an infinite loop or I have an array of objects (in displayBoxes which is what goes back to the DOM) where I have duplicates, as in I have multiple instances of Box 3, for no apparent reason.
Having a hard time figuring this out. I can make a plunker if necessary but I'm hoping its just a small oversight that is apparent here, as I am quite close to my intended result.
Edit: Plunker as requested: https://plnkr.co/edit/Bq6rgeOx26QTTu8d7AXh?p=preview
The answer was to hard code z as less than the length of the displayBoxes array.
for (var z = $scope.displayBoxes.length-1; z < $scope.displayBoxes.length; z++) {
This allowed the break; to be effective while maintaining the desired functionality.
I am currently trying to write a script to determine the minimum number of "jumps" you can make in an array to get back to the initial starting number. The starting number is always the biggest number in the array.
As an example if the array was [2, 3, 5, 6, 1] then the starting number would be 6 because it is the biggest. You can then jump 6 positions either left or right (you can choose) and where you end up is your next number. Lets say we went right, we would end up at 1 because we would loop back to the beginning of the array. You could then go 1 left and end up back at the 6 again. Therefore the minimum number of jumps is 2.
I wrote the below functions to take the input array and determine the minimum number of jumps, however, the recursion in the search() function is not working as I would hope / expect.
Each search function call calls itself 2 times (once for left, and once for right), however the constantly incrementing count variable gets "confused". Once one line of recursion ends (when count>arr.length) I would expect it to go back to the previous step and do the right line of recursion. But the count variable does not revert to the previous step (it stays as it is) and therefore the entire function just stops.
TL DR: How do you make a javascript functions variables maintain what they are even when the function is called again and the variables are overwritten?
function ArrayJumping(arr) {
//Find the largest number
var startingPos=arr.indexOf(Math.max.apply(null, arr));
//Find the multiple we are searching for
var multiple=arr.length;
return search(startingPos,multiple,arr,startingPos+1,0);
}
function search(pos,mult,arr,target,count) {
if (count>arr.length) {
return false;
}
var tpos=pos+1;
if ((tpos==target && count!=0) || (tpos%target==0 && count!=0)) {
return count;
}
for (var direction=-1;direction<=1;direction+=2) {
nPos=getRealPos(pos+(direction*arr[pos]),arr);
var result=search(nPos,mult,arr,target,count+1);
if (result!=false) {
return result;
}
}
}
function getRealPos(n,numArr) {
if (n>=0) {
while (n>numArr.length) {
n-=numArr.length;
}
} else {
while (n<0) {
n+=numArr.length;
}
}
return n;
}
Let's say that I'd like to store all the bullets, that anyone is shooting in my game to calculate new position every frame etc.
If there are 10 players and everyone's shot rate is 10 shots per second we would possibly need to track 1000 objects after only 10 seconds.
We do know, that iteration over array is very efficient.
should I add new bullets like this?
// "bullets" is an array
bullets.push({
x_position: 5, // x position from which bullet was shot
y_position: 10, // same as above but for y
x_speed: 2, // count of pixels that bullet is travelling on x axis per frame
y_speed: 10 // as above but for y
});
should I remove bullets, that hit the bound, or another player like this?
delete bullets[i] // i -> currently processed bullet index
Cause if i try to take out the element from bullets array, it's not very efficient with long arrays.
Honestly i haven't had any better idea to solve bullets problem. Iteration through this kind of array can be painful after couple of minutes because if we delete old bullets, the array length just stays the same and we end up with iterating over milions of records, from which 99% are just empty.
I believe that you want to implement a linked list instead of using a JavaScript array.
The truth about JS arrays
First, you may have a misconception about arrays. When we think of JavaScript arrays, we're really talking about hashmaps where the keys just happen to be integers. That's why arrays can have non-numeric indices:
L = [];
L[1] = 4
L["spam"] = 2;
Iteration is fast for arrays (in the C/C++ sense, at least), but iteration through a hashmap is rather poor.
In your case, some browsers might implement your array a real array if certain constraints are met. But I'm fairly certain you don't want a real array either.
Array performance
Even a real array isn't particularly amenable to what you want to do (as you pointed out, your array just keeps filling up with undefined elements, even as you delete bullets!)
And imagine if you did want to delete bullets from a real array and remove the undefined elements: the most efficient algorithm I can think of involves creating a new array after a full sweep of bullets, copying all the bullets which haven't been deleted into this new array. This is fine, but we can do better.
Linked Lists in JavaScript!
Based on your problem, I think you want the following:
fast creation
fast iteration
fast deletion
A simple data structure which provides constant time creation, iteration, and deletion is a linked list. (That said, linked lists don't allow you to get random bullets quickly. If that is important to you, use a tree instead!)
So how do you implement a linked list? My favorite way is to give each object a next reference, so that each bullet points or "links" to the next bullet in the list.
Initializing
Here's how you might start the linked list off:
first_bullet = {
x_position: 5,
y_position: 10,
x_speed: 2,
y_speed: 10,
next_bullet: undefined, // There are no other bullets in the list yet!
};
// If there's only one bullet, the last bullet is also the first bullet.
last_bullet = first_bullet;
Appending
To add a bullet to the end of the list, you'll want to set the next reference of the old last_bullet, then move last_bullet:
new_bullet = {
x_position: 42,
y_position: 84,
x_speed: 1,
y_speed: 3,
next_bullet: undefined, // We're going to be last in the list
};
// Now the last bullet needs to point to the new bullet
last_bullet.next_bullet = new_bullet;
// And our new bullet becomes the end of the list
last_bullet = new_bullet;
Iterating
To iterate through the linked list:
for (b = first_bullet; b; b = b.next_bullet) {
// Do whatever with the bullet b
// We want to keep track of the last bullet we saw...
// you'll see why when you have to delete a bullet
old = b;
}
Deleting
Now for deletion. Here, b represents the bullet being deleted, and old represents the bullet just before it in the linked list --- so old.next_bullet is equivalent to b.
function delete_bullet(old, b) {
// Maybe we're deleting the first bullet
if (b === first_bullet) {
first_bullet = b.next_bullet;
}
// Maybe we're deleting the last one
if (b === last_bullet) {
last_bullet = old;
}
// Now bypass b in the linked list
old.next_bullet = b.next_bullet;
};
Notice that I didn't delete the bullet using delete b. That's because delete doesn't do what you think it does.
Here is my approach to removing the bullets or any objects in collections. Never found it to be a bottleneck of a game - it is always the rendering.
Game
game = {
dirty: false,
entities: [ ],
clean: function() {
this.dirty = false;
clean(this.entities, "_remove");
},
step: function() {
if(this.dirty) this.clean();
}
}
Bullet
Bullet = function() {
this._remove = false;
}
Bullet.prototype = {
kill: function() {
this.game.dirty = true;
this._remove = true;
}
}
Cleaning function
function clean(array, property) {
var lastArgument = arguments[arguments.length - 1];
var isLastArgumentFunction = typeof lastArgument === "function";
for(var i = 0, len = array.length; i < len; i++) {
if(array[i] === null || (property && array[i][property])) {
if(isLastArgumentFunction) {
lastArgument(array[i]);
}
array.splice(i--, 1);
len--;
}
}
}
I'm trying to write a simple Javascript(jQuery) function that randomly displays 6 Divs out of a possible 11. The code sort-of-works, it does randomly display around about half of the Divs, but it varaies between 4 and 8.
Can anyone tell me where I'm going wrong? It seems it should be so simple yet I'm completely lost!
My code:
<div class="offer">Offer 1</div>
<div class="offer">Offer 2</div>
... snip
<div class="offer">Offer 11</div>
<script src="query.min.js" type="text/javascript"></script>
<script>
var changed = 0;
while (changed < 6) {
$('.offer').each(function(index) {
if (changed < 6) {
var showOrNot = Math.floor(Math.random() * 2);
if (showOrNot == 1) {
$(this).addClass('offershow');
changed += 1;
$(this).text(changed); //debugging looking for the current value of changed
}
}
})
}
</script>
The problem as it stands now is that you have a bunch of unrelated attempts. If you have a bucket with 11 balls and have a 50% chance to remove each ball, you could end up with any number of balls between 0 and 11. Probability is skewed toward the center, but you don't get six and exactly six each time.
What you want is to remove six, and exactly six, balls, selected arbitrarily.
Try something more like this:
var offers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
for (var i = 0; i < 6; i += 1) {
// choose a remaining offer at random
var index = Math.floor(Math.random() * offers.length);
// retrieve the item being shown
var item = $('.offer').eq(offers[index]);
item.addClass('offerShow');
// remove this offer from the list of possibilities
offers.splice(index, 1);
}
EDIT: In the comments, the OP clarified that what he really wants is to take an arbitrarily-sized list of offers and show six of them. The code provided below addresses that need, rather than the strict request in the original question. I'm leaving the original code for reference.
var OFFERS_TO_SHOW = 6; // config, of sorts
// make sure no offers are shown now
$('.offer').removeClass('offerShow');
// show offers selected at random
for (var i = 0; i < OFFERS_TO_SHOW; i += 1) {
// get a list of offers not already being shown
var candidates = $('.offer').not('.offerShow');
// select one from this list at random
var index = Math.floor(Math.random() * offers.length);
// show this offer by adding the offerShow class
candidates.eq(index).addClass('.offerShow');
}
I think the problem is that you're not excluding divs that you've already set to show. So it's possible that your code to pick the next div to show is choosing one that's already been shown. If that makes sense? Trying changing your selector.....
$('.offer').not('.offershow').each(........
Keep in mind that addClass doesn't remove the existing classes, so your original selector will still prove true even though you've added the offershow class.
You're actually not checking wheter the selected div is already shown or not. Means when you're looping through all divs, there is always a possibility of setting a div visible twice. The script now thinks it was a new one and sets changed += 1;
Try adding something like this:
if(!$(this).hasClass('offershow')) {
[[rest of code here]]
}