I'm not entirely sure whats going on here. When you shoot all three bullets and the last one leaves the screen the "asteroids" array also resets.
EDIT:
Is it because the bullets array ends up being spliced to an undefined when they all leave the screen? Or will it return the base empty array? Even then, it doesn't explain why the asteroids array is voided as well. I also found that the asteroids don't even start falling unless I've shot at least once.
Code on p5web editor
What is it causing this to happen? This is the first time I've coded something this large, code amount wise, but I made sure to use obvious variable names for the most part.
The problem is in your asteroids class:
check(bullets) {
for (let i = 0; i < bullets.length; i++) {
if (dist(bullets[i].x, bullets[i].y, this.pos.x, this.pos.y) < this.r) {
return false;
} else {
return true;
}
}
}
If there are no bullets to check, this function returns undefined implicitly, which is taken as falsey by the calling code, which promptly destroys the asteroid as if it had collided with a bullet.
Also, if there are bullets to check and the first one happens to not collide, the loop breaks prematurely with else { return true; }, possibly missing collisions.
Change it to:
check(bullets) {
for (let i = 0; i < bullets.length; i++) {
if (dist(bullets[i].x, bullets[i].y, this.pos.x, this.pos.y) < this.r) {
return false;
}
}
return true; // no collisions (or bullets.length === 0)
}
Having said this, the function name check is pretty unclear. I'd rename it as collidesWith(bullets) and invert the boolean--it makes more sense to say "true, yes, we did collide with a bullet" than "false, yes, we did collide with a bullet". We can also make use of the for ... of loop construct. This gives us:
collidesWith(bullets) {
for (const bullet of bullets) {
if (dist(bullet.x, bullet.y, this.pos.x, this.pos.y) < this.r) {
return true;
}
}
return false;
}
You could shorten this further to:
collidesWith(bullets) {
return bullets.some(e => dist(e.x, e.y, this.pos.x, this.pos.y) < this.r);
}
Similarly, I'd change bullet.check() to bullet.outOfBounds() or similar.
Another tip: iterate in reverse over any arrays you plan to slice the current element from:
for (let j = asteroids.length - 1; j >= 0; j--) {
// code that could call `.splice(j, 1)`
}
Otherwise, after splicing, your loop will skip an element and you might miss a collision.
A minor design point, but player.controls() seems strange--why should the player be responsible for listening for keypresses? I'd listen in the keyPressed() function and send the changes to update the player position from there. I'd also break up draw into smaller functions. But these are minor design decisions so the above is enough to get you rolling.
Here's a first revision for the draw function, adjusted to match the modified booleans:
function draw() {
background(0);
scene();
if (tick <= 0) {
if (asteroids.length < asteroidsLimit) {
asteroids.push(new Asteroid());
}
tick = 60;
}
for (let i = asteroids.length - 1; i >= 0; i--) {
if (asteroids[i].collidesWith(bullets)) {
asteroids.splice(i, 1);
}
else {
asteroids[i].update();
asteroids[i].display();
}
}
for (let i = bullets.length - 1; i >= 0; i--) {
if (bullets[i].outOfBounds()) {
bullets.splice(i, 1);
}
else {
bullets[i].update();
bullets[i].display();
}
}
image(ship, player.x, player.y);
player.controls();
tick--;
}
Related
I'm stuck at the lock_pairs function of Tideman. I already wrote the code for it and it looks good. It also behaves as it should when running it through the debugger; however, every time I try it with check50 it gives me an error on "lock pair skips final pair if it creates a cycle".
In order to try it out, I've used examples where the final pair is the one which should be skipped and everything goes as planned when I watch the whole process step by step on the debugger, so I really don't know what I'm missing here.
Here's the explanation of the problem: https://cs50.harvard.edu/x/2022/psets/3/tideman/
void lock_pairs(void)
{
// TODO
// When no_cycle is 0, it indicates that there is no cycle and that locking the pair is safe
int no_cycle = 0;
for (int i = 0; i < pair_count; i++)
{
for (int j = 0; j < pair_count; j++)
{
// We check each pair, looking for a locked pair where the loser of the pair we're checking is the winner.
if (!locked[pairs[i].loser][pairs[j].loser])
{
no_cycle = 0;
continue;
}
else
{
// If the result is true, we check for a possible cycle using the cycle_check function
if (cycle_check(i, j) == false)
{
// If the result is false, we lock the pair and move on
no_cycle = 0;
break;
}
else
{
// If the result is true (indicating a cycle), we change the value of no_cycle to avoid locking the pair
no_cycle = 1;
break;
}
}
}
if (no_cycle == 0)
{
locked[pairs[i].winner][pairs[i].loser] = true;
}
else continue;
}
return;
}
bool cycle_check(i, j)
{
// We check if the loser of the locked pair is the same as our pair's winner or starting point (the end of the chain is also the begining)
if (pairs[j].loser == pairs[i].winner)
{
return true;
}
else
{
//If it isn't, we go through each locked pair, following the chain which could lead to the cycle
for (int h = 0; h < pair_count; h++)
{
if (locked[pairs[j].loser][pairs[h].loser])
{
// Once we find the next link in the chain, we start over
j = h;
if (cycle_check(i, j) == true)
{
// Once we find that there is indeed a cycle, we break recursion
return true;
}
}
}
// If we follow the chain but don't find a cycle, the function returns false and the pair is locked
return false;
}
}
Been stuck forever on this. Any help would be much appreciated.
Btw, english is not my primary language, so please excuse any grammar errors.
So here im checking for when a bullet hits an enemy ship in my game. Im trying to check the type of enemy in the array by object name to do specific things for that enemy, code is below.
for (var i = bullets.length - 1; i >= 0; i--) {
for (var j = enemies.length - 1; j >= 0; j--) {
if (_bullets[i].hitTestObject(enemies[j])) {
if (enemies[j] == EnemyYellow) {
trace("do something");
}
stage.removeChild(enemies[j]);
stage.removeChild(bullets[i]);
bullets.splice(i, 1);
enemies.splice(j, 1);
return;
}
}
}
This is something like I thought would work, but I would appreciate if anyone could help me out as im not sure how to do it.
if (enemies[j] == EnemyYellow) {
trace("do something");
}
You can use the keyword is
if (enemies[j] is EnemyYellow) {
trace("do something");
}
You can also add a method getType to the Enemy class. This solution is not better for this particular case but may be useful in some other cases. For example, you can have enemies of the same class but returning different types.
if (enemies[j].getType() == EnemyType.ENEMY_YELLOW) // do something
I am developing a game that has a winning combination array:
var allwinning = [
['000','010','020'],
['000','100','200'],
['000','001','002'],
['000','101','202'],
['000','011','022'],
['000','110','220']];
The player will need to pick more than 3 numbers randomly. If all numbers are within any of the combinations in allwinning, the player wins.
For example, if the player picks '111','110','000','220', the player will win because allwinning[5] has the combination['000','110','220'].
My question is, what is the best way to do this winning loop? I cannot figure out the optimum way to do this.
Currently, I have a playerpick array to keep what player had picked and possiblewin array:
var playerpick = new Array(['111','110','000','220']);
var playerpicksingle = playerpick[0];
var possiblewin = new Array([]);
Then I go through a loop to capture out the possible win combination first:
for(var i=0 ; i < allwinning.length - 1 ; i++)
{
for(var j=0 ; j <3 ; j++)
{
if(allwinning[i][j]==playerpicksingle)
{
possiblewin.Push(allwinning[i]);
}
}
}
Then I am stuck at this point. I really don't know what else to do.
I can think of two ways. One requires you to change your data structure and the other doesn't.
Without changes:
Sort the user input:
pickedNumbers.sort();
and start comparing. By sorting the values beforehand you know when you can back out and continue with the next set of numbers, i.e. you can back out early and don't have to compare all the values (in the average case).
function wins(picked, winning) {
var winningSet = [];
for (var i = 0; i < winning.length && winningSet.length < 3; i++) {
var set = winning[i];
winningSet = [];
var j = 0;
var k = 0;
while (j < set.length && k < picked.length && winningSet.length < 3) {
if (picked[k] === set[j]) {
winningSet.push(set[j]);
j++; // advance to next element in winning set
} else if (picked[k] > set[j]) {
// continue with the next set
break;
}
// maybe the next element in players picks will match
k++;
}
}
return winningSet.length === 3 ? winningSet : false;
}
The worst case scenario of this solution is O(n*m*l), but since the input is sorted, the average case will be better.
DEMO
With Array#some and Array#every the code becomes much more concise, though it looses the advantage of using sorted input. If your arrays are small it won't make a difference though:
function wins(picked, winning) {
return winning.some(function(set) {
return set.every(function(val) {
return picked.indexOf(val) !== -1;
});
});
}
It also won't give you the actual numbers that matched. The runtime is the same.
The second way would be to build some kind of trie instead of using an array of arrays:
var allwinning = {
'000': {
'010': {
'020': true
},
'100': {
'200': true
},
// ...
}
};
The structure should also be sorted, i.e. the keys of a level are all smaller then the keys of its sublevel etc.
Sort the user input as well and iterate over it. Whenever you found a matching key, you go one level deeper until you have three matches:
function wins(picked, winning) {
var winningSet = [];
for (var i = 0; i < picked.length && winningSet.length < 3; i++) {
if (picked[i] in winning) {
winningSet.push(picked[i]);
winning = winning[picked[i]];
}
}
return winningSet.length === 3 ? winningSet : false;
}
This solution has the worst case scenario of O(n), where n is the number of values the user picked (not taking into account the time it takes to test whether an object contains a specific property name. Often this is assumed to constant).
DEMO
I have two arrays, namely combo and truecombo. The user fills the combo with MovieClips by clicking on various buttons on the stage, truecombo is the correct combination.
At any given point (enterFrame) Flash is checking whether the two are the same, if yes, then do some stuff. For the time being this is my code (altered several times, like with Typecasting the indices, adding .parent at the end of combo[o] etc. 2 things will happen, either one or the other.
Either the statement will not be satisfied, at which point the adding and chopping of the combo array will continue, or the condition will be instantly met when combo.length = 6. Check my code.
UPDATE: I have a dropbox file with my current code. Click this for FLA link and here is the SWF link stripped down as always for ease and security.
/*stage.*/addEventListener(Event.ENTER_FRAME, checkthis);
function checkthis(e:Event)
{
for(var o:int=0;o<= combo.length; o++)
{
if((combo[o] == truecombo[o]) && (combo.length==truecombo.length))
{
equal=true;
}
}
if (equal==true)
{
stage.removeEventListener(Event.ENTER_FRAME, checkthis);
endSeq();
}
}
function endSeq():void
{
bravo.play();
for (var i:int = 0; i < combo.length; i++)
{
var element:DisplayObject = combo[i];
element.parent.removeChild(element);
}
firebb.gotoAndPlay(2);
windbb.gotoAndPlay(2);
spiritbb.gotoAndPlay(2);
earthbb.gotoAndPlay(2);
}
This is how I push my new elements to the combo array.
function add(element:DisplayObject)
{
twist.gotoAndPlay(2);
element.width = WIDTH;
element.height = HEIGHT;
if (this.combo.length >= MAX_ELEMENTS)
{
removeChild(this.combo.shift());
}
this.combo.push(element as DisplayObject);
this.addChild(element);
this.reorder();
}
function reorder()
{
for (var i:int = 0; i < combo.length; i++)
{
var element:DisplayObject = combo[i];
element.x = OFFSET_X + (i * SEP_X);
element.y = OFFSET_Y;
}
}
And this is how I have my truecombo and its contents created.
var fireb:firebtn = new firebtn();
var spiritb:spiritbtn = new spiritbtn();
var earthb:earthbtn = new earthbtn();
var windb:windbtn = new windbtn();
var combo:Array=new Array();
const truecombo:Array = [fireb,windb,spiritb,windb,earthb,fireb];
Sorry for the lack of comments, I'd guess it's pretty self-explanatory. Thanks in advance.
I believe combo[o] & truecombo[o] are two instances of the same class & you want them to be matched. If that is the case you may consider :
getQualifiedClassName(combo[o]) == getQualifiedClassName(truecombo[o])
To match the way you did, you must ensure the objects lying inside truecombo be referring to the same ones on stage & not new instances.
EDIT:
It seems you do not break the loop when the match is a success. Use this instead :
function checkthis(e:Event)
{
for(var o:int=0;o<= combo.length; o++)
if((combo[o] == truecombo[o]) && (combo.length==truecombo.length)) {
equal=true;
break;
}
if (equal) {
stage.removeEventListener(Event.ENTER_FRAME, checkthis);
endSeq();
}
}
Here's a really simple loop:
var equal:Boolean=true
if(combo.length == truecombo.length) {
for(var i:int=0; i<combo.length; i++) {
if(combo[i] != truecombo[i]) {
equal=false;
break;
}
}
} else {
equal=false;
}
if(equal) {
//do whatever
}
This assumes both are equal, until we find out otherwise. So if the lengths are different, they are not equal. If the ith element is different, they are not equal.
In the end, you check if your flag equal is true and do whatever you want to do.
I am trying to test the collisions between a bullet and an array of enemies in Actionscript 2. However it is not sensing a collision. This is the code in the bullet.
onClipEvent(load)
{
facing = _root.player.facing;
speed = 1;
i = 0;
}
onClipEvent(enterFrame)
{
if (this._name != "bullet")
{
this._x += facing * speed;
while (i < _root.enemyID)
{
if (Math.abs(this._x - _root.enemies[i]._x)<10)
{
trace("hit enemy");
}
i++;
}
}
}
The variable i looks like it is being set to 0 only on load. So it will be checking all the enemies on the first frame, but since i will now always be greater than enemyID, it will never go into the loop again.
Try setting i = 0; just before the while loop.