- Joined
- Dec 6, 2015
Before I get some sleep, I thought I'd share a bit of programming 101 and what R60 can teach us about how not to write code.
I dug a bit in to R60's AI, if selecting a random move from a list can be called AI. Here's the core AI routine from the decompiled code:
Now, that's a bit messy. So I'll rephrase it in C, with comments for the non-programmers inspired to follow along at home:
The more astute among you will have noticed that our new array, called normalised_weights is just keeping a running total of the weights we've added up... that's not really necessary is it? Right you are, we can save a bit of space by just keeping the total in the comparison loop. Now our function looks like this:
If you're even more on the ball, you'll have noticed I put "normalised" in inverted commas. 'Cos there's no actual normalisation going on here. Normalised weight values would mean the total weight always adds up to 1, and that's how a real programmer would do it. Either the weights would be normalised in the class constructor only once, or they'd be normalised in the data files themselves so that no CPU time need be expended on the task. So our function really ought to look like this:
There, wasn't that simpler?
You can tell this whole thing was written by people with no practical programming experience. It reminds me a lot of the code I used to write when I was a kid in BASIC, no real structure or reason to it.
Anyway, enough programming sperg. I must sleep.
I dug a bit in to R60's AI, if selecting a random move from a list can be called AI. Here's the core AI routine from the decompiled code:
Code:
function int ReturnRandomAnimationIndex()
{
local int I, arrayLength, Index;
local float weightsTotal, randomWeight;
local array<float> NormalizedWeights;
weightsTotal = 0.0;
arrayLength = RandomAnimations.Length;
// End:0x170
if(arrayLength > 0)
{
I = 0;
J0x3D:
// End:0xB3 [Loop If]
if(I < arrayLength)
{
weightsTotal = weightsTotal + RandomAnimations[I].AnimationChance;
NormalizedWeights.AddItem(weightsTotal);
++ I;
// [Loop Continue]
goto J0x3D;
}
// End:0xE8
if(weightsTotal == 0.0)
{
Index = Rand(arrayLength);
return Index;
}
// End:0x16D
else
{
randomWeight = FRand() * weightsTotal;
I = 0;
J0x10A:
// End:0x16D [Loop If]
if(I < arrayLength)
{
// End:0x15F
if(randomWeight <= NormalizedWeights[I])
{
Index = I;
return Index;
}
++ I;
// [Loop Continue]
goto J0x10A;
}
}
}
// End:0x176
else
{
return -1;
}
//return ReturnValue;
}
Now, that's a bit messy. So I'll rephrase it in C, with comments for the non-programmers inspired to follow along at home:
Code:
// returns a (weighted) random animation index
int random_animation_index(const animation_set* animations_list, int num_animations) {
float weights_total;
float normalised_weights[num_animations];
// "normalise" the animation weights by summing them and adding the sums to a list
for (int i = 0; i < num_animations; i++) {
weights_total += animations_list[i].weight;
normalised_weights[i] = weights_total;
}
// if the list is unweighted, choose a random index
if (weights_total == 0)
return rand(num_animations);
// otherwise, choose a random number beween 0 and weights_total, select that item's index and return it.
float random_weight = frand() * weights_total;
for (int i = 0; i < num_animations; i++) {
if (random_weight <= normalised_weights[i])
return i;
}
return -1;
}
The more astute among you will have noticed that our new array, called normalised_weights is just keeping a running total of the weights we've added up... that's not really necessary is it? Right you are, we can save a bit of space by just keeping the total in the comparison loop. Now our function looks like this:
Code:
// returns a (weighted) random animation index
int random_animation_index(const animation_set* animations_list, int num_animations) {
float weights_total;
// "normalise" the animation weights by summing them and adding the sums to a list
for (int i = 0; i < num_animations; i++) {
weights_total += animations_list[i].weight;
}
// if the list is unweighted, choose a random index
if (weights_total == 0)
return rand(num_animations);
// otherwise, choose a random number beween 0 and weights_total, select that item's index and return it.
float random_weight = frand() * weights_total;
float running_total = 0;
for (int i = 0; i < num_animations; i++) {
running_total += animations_list[i].weight;
if (random_weight <= running_total)
return i;
}
return -1;
}
If you're even more on the ball, you'll have noticed I put "normalised" in inverted commas. 'Cos there's no actual normalisation going on here. Normalised weight values would mean the total weight always adds up to 1, and that's how a real programmer would do it. Either the weights would be normalised in the class constructor only once, or they'd be normalised in the data files themselves so that no CPU time need be expended on the task. So our function really ought to look like this:
Code:
// returns a (weighted) random animation index
int random_animation_index(const animation_set* animations_list, int num_animations) {
float random_weight = frand();
float running_total = 0;
for (int i = 0; i < num_animations; i++) {
running_total += animations_list[i].weight;
if (random_weight <= running_total)
return i;
}
return -1;
}
There, wasn't that simpler?
You can tell this whole thing was written by people with no practical programming experience. It reminds me a lot of the code I used to write when I was a kid in BASIC, no real structure or reason to it.
Anyway, enough programming sperg. I must sleep.