OK. There’s a lot here to improve upon. Keep in mind that this is not a primer for how to write good code, but some answers for your stated desires based on this piece of code. Some could be more efficient, some could be more readable, and some will just be “smarter” computing. Much here is better explored and explained with other resources and should not go any further into depth than this, here. It may even be beyond the scope of this site’s goals (I’m sure we’ll find out). This is a long post, so be prepared.
Style Improvements
Multiple var statements
Avoid this type of declaring, unless there’s a reason that they should be this way (for clearer understanding). JavaScript allows you to declare and initialize multiple variables with one statement.
var tempX = 0,
tempY = 0,
totalValue = 0;
This helps readability and if necessary allows you to comment specific variables according to whichever style you prefer.
Vars in loops and conditions
Avoid declaring vars in any loop and most conditions. This is misleading and some interpreters actually mishandle this into odd bugs. The only var
in a for
loop that is acceptable and universally handled correctly in the parens. Example:
var currCoin,
currValue;
for (var iCoin = 0; iCoin < allCoins.length; iCoin++) {
currCoin = allCoins[iCoin];
currValue = currCoin.value;
...
...
}
Variable Names
A variable name should be exactly as long as it needs to be to describe exactly what it is. It’s that simple. If you have two iterators being used at the same time, then differentiate between them. If not, i
is standard and clear and can be reused later. Long variable names are just a hazard of the job.
What is caching?
Caching is when you get some data and store it for one of two reasons:
- For faster, or easier later access (not manipulation)
- To reduce load or processing.
This can be in a simple file format, a variable or even to local storage. The idea is that, by storing this information, it is instantly available until it changes or we don’t need it anymore. In the particular case that I gave, the length
property which normally requires a full array is stored so that we can compare against a potentially new later array. If it changes, then we update it and do our thing. But if not, our previous information is still valid. It should be noted that while it is pretty reliable, it is not foolproof. If for some reason, a coin is added at the exact same frame that one is removed, then it is possible it won’t register the change. However, those cases are extremely rare and I have never encountered it in this game (so far).
Efficiency
There are lots of micro-optimizations that one can do that are just as readable… however, mileage varies widely between interpreters. So, these won’t be addressed. However, here are a few things which are good to know based on both a) your questions and b) smart conventions.
Single Objects vs. Multiple Primitives (ints, floats, strings, etc)
Objects are handy… super handy. In general, we create objects when properties and/or methods are related to a single concept. There are a few exceptions, however:
- When the properties will never be referenced beyond the first time
AND
- If they aren’t going to be returned together
So, some of your objects could definitely be multiple vars. But, some should probably stay as objects.
For loops and Objects/Arrays
In general, using objects in the setup for a for loop is actually bad. In some cases, it is more readable, but it is ALWAYS slower to the point that many even discourage such use.
for (var iCoin = 0; iCoin < allCoins.length; iCoin++) {
...
}
The above checks allCoins.length everytime the loop iterates. This means that even though you type it once, it is access length
number of times. This is generally (not always better).
var nCoins = allCoins.length;
for (var iCoin = 0; iCoin < nCoins; iCoin++) {
...
}
Still quite readable, and a lot of performance gains. If order is not important, a reverse can be beneficial and limit the need of another variable. Some find it confusing because of misunderstanding of difference between ++var and var++. I use this often, but never would I recommend it. I only add it for completeness.
for (var iCoin = allCoins.length; --iCoin >= 0; ) {
...
}
This saves a variable and is generally readable. If novices are expected to be using your code, or you could confuse yourself, don’t use this.
Redundancy of Call Conditions
This depends on need but Generally, if you can, only check a condition once per opportunity for it to change. If it is possible for it to change, but the condition is still needed, definitely check it again. If its value or state is not changed between the checks, it should only be checked the once.
// This form can also save computations, especially if in a loop.
if (condition1) {
if (condition2) {
// some code
}
else {
// some code
}
}
An additional note is that your most common result should generally be at the top. This not only improves performance in the majority of interpreters, but also signifies to you and other coders that this is the most expected result. Of course, there are always exceptions, but this is a standard that one should adhere to more often than not.
Unnecessary Variables
A variable is only unnecessary when it adds no value to the code or removes from the readability. While there are many reasons to use (and avoid) them, most fall within these 3 core reasons:
- The result of a computation is needed for later
- The result of a computation (data, operation or function) is used more than twice.
- A concept should be illustrated by name (hence, more easily understood by sight)
In other words, no one can tell you if you need a variable. Something to keep in mind, however, especially with function calls:
var d = this.distanceTo(object);
var result = d * d;
// ... is much, much more efficient than ...
var result = this.distanceTo(object) * this.distanceTo(object);
Although the above is demonstrated with a function, it is also true for objects. Many devs will actually create vars
for object properties that they are accessing more than a couple of times. This is especially true for nested object properties.
Consider:
var obj = {
pos: {
x: value1,
y: value2
}
};
You can easily access the properties via obj.pos.x
. But if you are doing this more than once, it is actually faster and more readable to do the following:
var pos1 = obj.pos;
pos.x;
While in many cases, you can save more by sometimes doing:
var tmpPos = obj.pos;
var xPos = tmpPos.x;
… this will sometimes detract from both readability and performance. It all depends on how often you are accessing pos
and x
. If you use x
a lot, but ignore pos
, then this might be preferrable: var xPos = obj.pos.x;
The second you access more than two properties from an object, that object should be a variable, if performance is the concern. Now there is an exception, because of how JavaScript works. Any variable that is discarded immediately, could probably be simply changed. This is because JavaScript is not hard-typed. Here’s an example from your own code.
if(leastCoinDistWeighted<15){// (3)
var v2={x:currCoin.pos.x-myPos.x,y:currCoin.pos.y-myPos.y};
var v2Norm=Math.sqrt(v2.x*v2.x+v2.y*v2.y);
var cosineV1V2=(v1.x*v2.x+v1.y*v2.y)/(v1Norm*v2Norm);
var coefTowardGoldBary=3+cosineV1V2;
var currCoinDistWeighted2=currCoinDistWeighted/coefTowardGoldBary;
if(currCoinDistWeighted2<leastCoinDistWeighted2){
leastCoinDistWeighted2=currCoinDistWeighted2;
nextCoin=currCoin;
}
}else{
nextCoin=currCoin;
} //(3)
The cosineV1V2
could be unnecessary. It is never referenced again. You could simply do the following:
if(leastCoinDistWeighted<15){// (3)
var v2={x:currCoin.pos.x-myPos.x,y:currCoin.pos.y-myPos.y};
var v2Norm=Math.sqrt(v2.x*v2.x+v2.y*v2.y);
// Here is the change!
var coefTowardGoldBary =(v1.x*v2.x+v1.y*v2.y)/(v1Norm*v2Norm);
coefTowardGoldBary += 3;
var currCoinDistWeighted2=currCoinDistWeighted/coefTowardGoldBary;
if(currCoinDistWeighted2<leastCoinDistWeighted2){
leastCoinDistWeighted2=currCoinDistWeighted2;
nextCoin=currCoin;
}
}else{
nextCoin=currCoin;
} //(3)
The Hard Execution Limit
This is a tough subject because many thing will contribute to HEL that could be otherwise inlined directly by the interpreter. What I mean is, the interpreter optimizes much of your code as it is run. But CodeCombat doesn’t necessarily work this way since it uses a transpiler and code magic to achieve its ends. This means that some inline statements could indeed increment the execution count multiple times (in fact, I have found this to be a possibility with several things). The best way to avoid HEL is to keep your code simple and not count on in-lining. This generally means all of the above that I already mentioned. It actually helps in most circumstances.
The Golden Rule
All of the above are suggestions based on your requested answers. The truth is, it is your code and it always depends on your needs. If you need to share code with a community or get feedback, then style guidelines are probably important. If not, you can ignore them freely.