What is the recommendation to "fix" ConditionalExpression/BlockStatement for a while loop when using Stryker? - stryker

What is the recommendation when Stryker hits a time-out error when mutating a while loop conditional? Toy example:
const a: number = 0;
while (a < 10) {
console.log(a);
a++;
}
ConditionalExpression mutates this to:
const a: number = 0;
while (true) {
console.log(a);
a++;
}
while BlockStatement mutates this to:
const a: number = 0;
while (a < 10) {}
both of which fall into infinite loops and trigger Timeout due to hit limit reached.
Obviously I can add mutator exclusions for the while loop:
const a: number = 0;
// Stryker disable next-line LogicalOperator,ConditionalExpression,BlockStatement: Developer who makes similar change will see infinite loop
while (a < 10) {
console.log(a);
a++;
}
I would have to ignore this for every while loop created since these mutators are always applied by Stryker... I am wondering is the expectation that I should be creating unit tests which check for an infinite loop (or could Stryker just not apply these mutators to while loops)?

Related

How to batch the calls in google app script

I was trying to optimize runtime some code that ran really slowly, when searching it up google came up with this: https://developers.google.com/apps-script/guides/support/best-practices#:~:text=Use%20batch%20operations,-Scripts%20commonly%20need&text=Alternating%20read%20and%20write%20commands,data%20out%20with%20one%20command.
it shows an example of inefficient code:
var cell = sheet.getRange('a1');
for (var y = 0; y < 100; y++) {
xcoord = xmin;
for (var x = 0; x < 100; x++) {
var c = getColorFromCoordinates(xcoord, ycoord);
cell.offset(y, x).setBackgroundColor(c);
xcoord += xincrement;
}
ycoord -= yincrement;
SpreadsheetApp.flush();
}
and efficient code:
var cell = sheet.getRange('a1');
var colors = new Array(100);
for (var y = 0; y < 100; y++) {
xcoord = xmin;
colors[y] = new Array(100);
for (var x = 0; x < 100; x++) {
colors[y][x] = getColorFromCoordinates(xcoord, ycoord);
xcoord += xincrement;
}
ycoord -= yincrement;
}
sheet.getRange(1, 1, 100, 100).setBackgroundColors(colors);
I tried to understand how this code works and tried running it but first of all "cell" doesn't seem to get used and I do not understand the code at all. What is a version of the code that actually works and how does this make it more efficient? And what part of this code batches the calls and how can I use this in my own coding?
Basically, what it does it reduce the number of calls by its methods.
The inefficient code above calls offset and setBackgroundColor every loop thus making it resource intensive and time consuming.
For the efficient code, it uses the loop to build the 2d array which then will be used on a single call on the method setBackgroundColors to execute it by bulk.
What takes much of the run time is the method calls, so reducing it would be very beneficial. This would make use of loops to build 2d arrays for the array versions of the methods single execution.
e.g.:
setValue -> setValues
setBackgroundColor -> setBackgroundColors
Pseudocode:
from:
loop {
setValue(x[i][j])
}
to:
loop {
x[i][j] = data
}
setValues(x)

Simple For loops not working on cs6?

In my code I have a few for loops in the main timeline that look like this
for (i = 0; i<2*speedY; i++)
{
code
}
I've done this exact syntax many times and there have been no issues, however when I did it it gave me the error that i is undefined. I then tried the same loops defining i as var i:int; however now it just gave me a namespace error. What am I doing wrong here?
To avoid the undefined error, you have to define your variable i, but also to avoid the namespace error you should do that once. So you can do that like this :
var i:int;
// 1st for loop
for(i = 0; i < 5; i++){
trace('1 : '+i);
}
// 2nd for loop
for(i = 5; i > 0; i--){
trace('2 : ' + i);
}
If you only need the variable i within the loop itself and don't need the variable beyond the scope of the loop, you can also declare it within the loop parameters:
for(var i:int = 0; i < 5; i++) {
trace(i);
}
In terms of performance it's a marginal difference, however it's generally a good practice to declare variables only within the scope in which they will be used.

Contiguous Block Plotting/Removal Logic

I've been attempting to write some logic for a program I have been working on and have run into some difficulty.
Essentially what I'm creating on my stage programmatically is a 4x4 grid (16 blocks), which looks just like this:
The user's task is to plot a contiguous shape onto the grid by clicking on the blocks and their shape should feature no gaps and no diagonally plotted blocks, for example the following would be a legal shape:
However, the following shape wouldn't be and would throw out an error to the user in the form of a pop-up graphic:
The plotting process for the grid is associated with a 4x4 virtual representation of the grid in Boolean Array form and looks like this:
public static var ppnRowArray1:Array = [false,false,false,false];
public static var ppnRowArray2:Array = [false,false,false,false];
public static var ppnRowArray3:Array = [false,false,false,false];
public static var ppnRowArray4:Array = [false,false,false,false];
public static var ppnColumnArray:Array = [ppnRowArray1,ppnRowArray2,ppnRowArray3,ppnRowArray4];
As the user clicks and selects a block, changing the colour property to brown, the relevant boolean property in my 'virtual grid representation' array will change from false to true. If a plot is illegally made then this property is changed back to false and the user is then invited to try their next plot again.
I have managed to write the code which forces the user to plot a legal shape and works out when an illegal plot has been made, but I now need to write the logic for when a user de-selects a block from an existing legal shape, making it non-contiguous and this is where my problem lies.
Here is the working solution as it stands.
//---------------------------------------------------------------------
public static function ppnCountSetCells():int
{
//Count Each 4x4 Grid Cell
var count:int = 0;
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row])
{
count++;
}
}
}
return count;
}
//---------------------------------------------------------------------
public static function ppnBlockValid():Boolean
{
if (ppnCountSetCells() > 1)
{
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row] == true)
{
// Check if we are connected to another set square
var validNeighbours:int = 0;
// Check North
if (row > 0)
{
if (ppnColumnArray[col][row - 1] == true)
{
validNeighbours++;
}
}
// Check South
if (row < 3)
{
if (ppnColumnArray[col][row + 1] == true)
{
validNeighbours++;
}
}
// Check West
if (col > 0)
{
if (ppnColumnArray[col - 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------------------------
// Check East
if (col < 3)
{
if (ppnColumnArray[col + 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------
if (validNeighbours < 1)
{
return false;
}
//-----------------------------------------------------------------------
}
}
}
}
return true;
}
//---------------------------------------------------------------------
function addBlock(e:MouseEvent):void
{
//trace("You Have Clicked On Grid Block Number: " + e.currentTarget.id);
if (InterfaceButtons.panelOpen == false)
{
//Listen to see if the block click is adjoining and pass back to see if it is valid on the grid
var col:int = (e.currentTarget.id - 1) % 4;
var row:int = (e.currentTarget.id - 1) / 4;
ppnColumnArray[col][row] = true;
addOrRemove = "add";
ppnBlockValid();
//Get the Block Valid Result (True or False) and pass it into a Boolean variable to use later
ppnGridError = ppnBlockValid();
trace("Is This Valid? " + ppnBlockValid());
//----------------------------------------------------------------------------------------------
//Push Blocks Selected into Array
ppnShapeArray[e.currentTarget.id] = true;
trace(ppnShapeArray);
//----------------------------------------------------------------------------------------------
//Add 1 to the block count which directly effects the final outcome depending on ++ or --
ppnBlocksSelected++;
PlantPopNitDesignPlot.ppnPlotMade = false;
//Hide Block to Reveal Brown One
e.currentTarget.alpha = 0;
//-----------------------------------------------------------------------------------------------
//Output an error if one is present on Click based on gridError Boolean Variable
ppnOutputAddError();
if (ppnGridError == false)
{
//Restore the block's alpha property as it isn't allowed to be selected, removing counter by one -- and changing final output accordingly
e.currentTarget.alpha = 1;
ppnBlocksSelected--;
ppnColumnArray[col][row] = false;
ppnShapeArray[e.currentTarget.id] = false;
ppnPopulateTotalSiteUnitsTxt();
}
//Update final total
ppnPopulateTotalSiteUnitsTxt();
//Call again to do dynamic colour font change should total exceed 10
ppnPopulateTotalSiteUnitsTxt();
//Added in to make sure it executes every time if an error is made.
if (ppnGridError == true)
{
e.currentTarget.removeEventListener(MouseEvent.CLICK, addBlock);
e.currentTarget.addEventListener(MouseEvent.CLICK, removeBlock);
}
}
}
function removeBlock(e:MouseEvent):void
{
if (InterfaceButtons.panelOpen == false)
{
var col:int = (e.currentTarget.id - 1) % 4;
var row:int = (e.currentTarget.id - 1) / 4;
ppnColumnArray[col][row] = false;
addOrRemove = "remove";
ppnBlockValid();
ppnGridError = ppnBlockValid();
trace("Is This Removal Valid? " + ppnBlockValid());
//trace("You Have Clicked On Grid Block Number: " + e.currentTarget.id);
e.currentTarget.alpha = 1;
ppnShapeArray[e.currentTarget.id] = false;
//trace("ppnShapeArray - " + ppnShapeArray);
//---------------------------------------------------------------------
ppnBlocksSelected--;
PlantPopNitDesignPlot.ppnPlotMade = false;
//Output an error if one is present on Click based on gridError Boolean Variable
ppnOutputRemoveError();
if (ppnGridError == false)
{
//Restore the block's alpha property as it isn't allowed to be selected, removing counter by one -- and changing final output accordingly
e.currentTarget.alpha = 0;
ppnBlocksSelected--;
ppnColumnArray[col][row] = true;
ppnShapeArray[e.currentTarget.id] = true;
ppnPopulateTotalSiteUnitsTxt();
}
//Update Final Total
ppnPopulateTotalSiteUnitsTxt();
//Call again to do dynamic colour font change should total falls below 10
ppnPopulateTotalSiteUnitsTxt();
//Added in to make sure it executes every time.
if (ppnGridError == true)
{
e.currentTarget.addEventListener(MouseEvent.CLICK, addBlock);
e.currentTarget.removeEventListener(MouseEvent.CLICK, removeBlock);
}
}
}
}
}
//---------------------------------------------------------------------
Now, for most occurences this logic works and detects illegal plots when adding or removing a block to the shape, however recently I discovered that when I have 5 > blocks in the shape, the logic for detecting an error on removal fails in certain circumstances.
A few examples of shapes being declared true and legal when they are not (when a block has been removed) are as follows:
I can see that it is the logic written in my 'ppnBlockValid():Boolean' function that needs adjusting to compensate for these outputs. It seems you can only remove a block providing that the neighbouring blocks are still joined to something else. While this works for smaller shapes, larger shapes (e.g. 5 blocks or more) can theoretically be split down the middle, so I think the code needs adjusting to account for this.
But how? Any help on this would be greatly appreciated.
Many thanks in advance and if you need any further information from me please let me know.
Cheers,
Joel
Edited
Thank you very much for providing that enhanced code and explanation #dhc I really appreciate it, but I'm still a little confused about how to implement all this properly.
Here is my current 'ppnBlockValid' function code based on your suggestion below:
public static function ppnBlockValid():Boolean
{
ppnIslands = [];
ppnAddNewIsland = [];
ppnAddToExistingIsland = [];
if (ppnCountSetCells() > 1)
{
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row] == true)
{
var addedToIsland = false;
// Check if we are connected to another set square
var validNeighbours:int = 0;
// Check North
if (row > 0)
{
if (ppnColumnArray[col][row - 1] == true)
{
validNeighbours++;
}
//----------------------------------
//ISLAND CHECK
if (ppnColumnArray[col][row - 1])
{
ppnAddToExistingIsland.push([col,row - 1],[col,row]);
addedToIsland = true;
}
}
// Check South
if (row < 3)
{
if (ppnColumnArray[col][row + 1] == true)
{
validNeighbours++;
}
}
// Check West
if (col > 0)
{
if (ppnColumnArray[col - 1][row] == true)
{
validNeighbours++;
}
//----------------------------------
//ISLAND CHECK
if (ppnColumnArray[col - 1][row])
{
ppnAddToExistingIsland.push([col - 1,row],[col,row]);
addedToIsland = true;
}
}
//-----------------------------------------------------------------------------------------
// Check East
if (col < 3)
{
if (ppnColumnArray[col + 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------
if (! addedToIsland)
{
ppnIslands.push([col,row]);
}
//-----------------------------------------------------------------------
if (ppnIslands.length >= 2 && addOrRemove == "remove")
{
trace("TWO ISLANDS HAVE BEEN FORMED AND AN ERROR SHOULD BE OUTPUT");
validNeighbours--;
}
/**/
//return (ppnIslands.length<=1);// 0 islands is valid also!
//-----------------------------------------------------------------------
if (validNeighbours < 1)
{
return false;
}
//-----------------------------------------------------------------------
}
}
}
}
return true;
}
//---------------------------------------------------------------------
I have been using the following shape as my experiment with the code:
Based on the above code and example shape, my current trace output is:
ppnIsland = |0,0|,0,2
ppnAddNewIsland =
ppnAddToExistingIsland = 0,0, 1,0, 1,0, 2,0, 2,0, 3,0, 1,0, 1,1, 1,1, 1,2, 0,2, 1,2, 1,2, 2,2, 2,2, 3,2
It appears that despite the shape being contiguous, the code I've tried interpreting from you is finding an additional island, in this case 'Col: 0, Row: 2', before a block removal has even taken place? Is this right?
This code of course does output an error if I try to remove the middle (red) block as the 'ppnIsland' array contains > 1 island, however I don't think its detecting the correct output?
Do I need to cross-reference the 'ppnIsland' and 'ppnAddToExistingIsland' arrays using the indexOf command to check if either element is part of an existing island?
Joel
You could track "islands" as separate lists as you process (in ppnBlockValid). So, for instance (caps are selected tiles):
a B c d
e F g H
i J k L
m n o p
When you process row one, you create an island for B. When you process row 2, you find B connected to F, so the island becomes [B,F]. Then, when you encounter H, which is not connected, you have two islands: [B,F],[H]. On row 3, your islands would look like [B,F,J],[H,L]. If K were selected, you'd find J and L connected, and you'd consolidate them into one island. You only have to check the current row for connections between islands, so you'd only have to check J and L in this case.
If you end up with one island, you're fine, otherwise not. This is, unfortunately, an order n**2 search, but your grid is small so it probably doesn't mean much.
Something like this (WARNING: not debugged code):
private var islands : Array;
public function ppnBlockValid():Boolean {
islands = [];
if (ppnCountSetCells() > 1) {
for (var row=0; row<=3; row++) {
for (var col=0; col<=3; col++) {
if (ppnColumnArray[col][row]) {
var addedToIsland = false;
// Check if we are connected to another set square
// Check North
if (row > 0) {
if (ppnColumnArray[col][row-1]) {
addToExistingIsland([col,row-1],[col,row]);
addedToIsland = true;
}
}
// Check South - not needed since next row will catch this on North
// Check West
if (col > 0) {
if (ppnColumnArray[col-1][row]) {
addToExistingIsland([col-1,row],[col,row]);
addedToIsland = true;
}
}
// Check East - not needed since next col will catch this on West
if (!addedToIsland) { addNewIsland([col,row]); }
}
}
}
}
return (islands.length<=1); // 0 islands is valid also!
}
You just have to implement "addNewIsland" and "addToExistingIsland". addNewIsland is easy: just add a new element to the islands array. You may want to use a string ID for the tiles instead of an array (e.g. "01" instead of [0,1]) since it may make checking for existing elements easier (e.g. you can use indexOf to find a tile in an island).
addToExistingIsland is a bit trickier: you have to check if either element is part of an existing island, and consolidate those islands if so. If not, the new tile just gets appended to the island.
You don't need to maintain 3 arrays, just one island array that, at the end of ppnBlockValid, will either be length > 1 (invalid), or less (valid).
You cannot just populate the "ppnAddNewIsland" and "ppnAddToExistingIsland" arrays-- these should be functions. If ppnIslands is a class variable, then your addNewIsland function may look something like this:
private function addNewIsland(who) {
ppnIslands.push([who]);
}
... the only optimization I'd probably make is to turn who (passed as an array [col,row]) into a character string, as I mentioned, to make finding whether a tile exists in an island easier.
Then, you need a function like:
private function addToExistingIsland( existing_island_tile, new_tile ) {
}
This function must:
1. check whether these two tiles already exist in the same island (just return if so)
2. check whether new_tile is already on an island (consolidate the two islands into one if so)
3. otherwise, just add new_tile to the island that existing_island_tile is a member of
The code I presented should work pretty much as-is-- although you could add back in the "validNeighbours" logic if you want a quick exit for isolated tiles.

ActionScritp 3 removeChild in loop

I have two arrays, fireballs and gombas (enemies).
When I create fireball and gomba, I add them into arrays using push();
I shoot fireballs and move gombas using foor loop
for (var f:int = 0; f < fireballs.length; f++)
{
// move fireballs
}
I also remove fireballs if it goes too far, for example
if (myFireball.x + 1000 < player.x)
{
if (myFireball.parent)
{
myFireball.parent.removeChild(myFireball);
fireballs.splice(myFireball, 1);
}
}
I have no problem removing fireballs, but if fireball hitTestObject gomba, I want to remove both, fireball and gomba, ant thats my problem.
I tried on this way and I get error, a term is undefined and has no properties
for (i = 0; i < fireballs.length; i++)
{
for (var m = 0; m < gombas.length; m++)
{
if (fireballs[i].hitTestObject(gombas[m]))
{
if (fireballs[i].parent)
{
fireballs[i].parent.removeChild(fireballs[i]);
// same for gombas
}
}
}
}
If I use same loop but just check if fireballs and gombas are visible, if fireballs hit gombas I set visible to false and it works ok. Why it wont work with removeChild.
As #itcouldevenbeaboat says, when looping through an array and removing objects, it's better to loop backwards; that way, you won't have index issues.
For example, with the array ["one","two","three"], if you looped through and removed like so:
for( var i:int = 0; i < myArray.length; i++ )
myArray.splice( i, 1 );
What's happening, is this:
i starts at 0, which points to "one"
we remove the object at position 0 from myArray
myArray is now ["two","three"]
at the end of the loop, i is incremented, and becomes 1
we start the next iteraction, and i is 1, which points to "three"
In this example, we've skipped over the "two" String, because the splice has modified the array we're iterating. If we went backwards:
for( var i:int = myArray.length - 1; i >= 0; i-- )
myArray.splice( i, 1 );
Then we get this:
i starts at 2, which points to "three"
we remove the object at position 2 from myArray
myArray is now ["one","two"]
at the end of the loop, i is decremented, and becomes 1
we start the next iteraction, and i is 1, which points to "two"
Thus we remove all the objects from myArray, no problem.
For your particular example, as you're removing objects from the two arrays, you should iterate backwards, which should give you something like:
outer: for ( var i:int = fireballs.length - 1; i >= 0; i-- )
{
for ( var m:int = gombas.length - 1; m >= 0; m-- )
{
if (fireballs[i].hitTestObject(gombas[m]))
{
// remove our fireball and gombas graphics
if( fireballs[i].parent != null )
fireballs[i].parent.removeChild( fireballs[i] );
if( gombas[m].parent != null )
gombas[m].parent.removeChild( gombas[i] );
// remove the fireball and gombas from the array; this is assuming
// that once a fireball hits a gomba, they both disappear
gombas.splice( m, 1 );
fireballs.splice( i, 1 );
// break to the outer loop; the fireballs one, as we don't need
// to go any further in the inner one
break outer;
}
}
}
Note the outer label on the fireballs loop; this lets us jump out once we detect a collision between a fireball and a gomba.
If you want a fireball to hit multiple gombas, then just don't remove the graphics or splice the array.

Unknown logical error

I'm assuming it's a logical error because it doesn't return any compiler or run-time errors.
my functions:
function closetest() {
tooclose=false;
for (i=0; i<10; i++) {
if (Math.abs(entry[0]-entry[i])<100) {
tooclose=true;
}
}
}
function xassignment() {
for (i=0; i<10; i++) {
entry[i+1]=entry[i];
}
do {
entry[0] = int(Math.random()*(stage.stageWidth - 30));
closetest();
} while (tooclose == false);
}
Here is where the function is called
mcMain.addEventListener(Event.ENTER_FRAME, moveChar);
function moveChar(event:Event):void {
if (gameOver == false) {
if (enemyTime < enemyLimit) {
enemyTime++;
} else {
var newEnemy = new Enemy();
xassignment();
newEnemy.y=-1*newEnemy.height;
newEnemy.x=entry[0];
addChild(newEnemy);
enemyTime=0;
}
}
}
I'm making a game that involves objects being dropped from a randomly generated x coordinate, I made these functions to make sure that objects aren't being dropped too close to each other but it doesn't appear to have any effect.
You haven't described what effect you are observing, but I noticed this:
for (i=0; i<10; i++) {
if (Math.abs(entry[0]-entry[i])<100) {
tooclose=true;
}
}
This will result in tooclose always being true, because your loop begins at 0, therefore you compare entry[0] with itself. Try starting the loop at 1 instead.
You haven't declared or changed tooclose within xassignment. You should instead return a Boolean from closetest() which will be queried. Another logical error is, you are advancing previous values by 1 in your array, while incrementing the index. So you have i=0, entry[1]=entry[0]. Then i is incremented, entry[2]=entry[1] - bam, entry[2] too starts being requal to entry[0]! And so on, so the entire array of entries now equals to what was rolled on the previous attempt, instead of storing 10 previous values. Fixed that too.
function closetest():Boolean {
for (var i:int=1; i<10; i++) {
if (Math.abs(entry[0]-entry[i])<100)
return true;
}
return false;
}
function xassignment() {
for (i=10; i>0; i--) entry[i]=entry[i-1];
do {
entry[0] = int(Math.random()*(stage.stageWidth - 30));
} while(closetest());
}
Note though, if your closetest implementation will receive a [X,100,300,500,700] array the loop will be endless, as there's no X position that will satisfy your range of 100 for absolute difference. So, fix it with the following:
function xassignment() {
for (i=10; i>0; i--) entry[i]=entry[i-1];
var loops:int=10;
do {
entry[0] = int(Math.random()*(stage.stageWidth - 30));
loops--;
} while((loops>0)&&closetest());
}
This will force an exit from that loop if we tried 10 randoms and none satisfied - the last attempt is returned.