tags:

views:

348

answers:

8

I'm currently working on an emulation server for a flash-client based game, which has a "pets system", and I was wondering if there was a simpler way of going about checking the level of specified pets.

Current code:

public int Level
{
    get
    {
        if (Expirience > 100) // Level 2
        {
            if (Expirience > 200) // Level 3
            {
                if (Expirience > 400) // Level 4 - Unsure of Goal
                {
                    if (Expirience > 600) // Level 5 - Unsure of Goal
                    {
                        if (Expirience > 1000) // Level 6
                        {
                            if (Expirience > 1300) // Level 7
                            {
                                if (Expirience > 1800) // Level 8
                                {
                                    if (Expirience > 2400) // Level 9
                                    {
                                        if (Expirience > 3200) // Level 10
                                        {
                                            if (Expirience > 4300) // Level 11
                                            {
                                                if (Expirience > 7200) // Level 12 - Unsure of Goal
                                                {
                                                    if (Expirience > 8500) // Level 13 - Unsure of Goal
                                                    {
                                                        if (Expirience > 10100) // Level 14
                                                        {
                                                            if (Expirience > 13300) // Level 15
                                                            {
                                                                if (Expirience > 17500) // Level 16
                                                                {
                                                                    if (Expirience > 23000) // Level 17
                                                                    {
                                                                        return 17; // Bored
                                                                    }
                                                                    return 16;
                                                                }
                                                                return 15;
                                                            }
                                                            return 14;
                                                        }
                                                        return 13;
                                                    }
                                                    return 12;
                                                }
                                                return 11;
                                            }
                                            return 10;
                                        }
                                        return 9;
                                    }
                                    return 8;
                                }
                                return 7;
                            }
                            return 6;
                        }
                        return 5;
                    }
                    return 4;
                }
                return 3;
            }
            return 2;
        } 
        return 1;
    }
}

Yes, I'm aware I've misspelt Experience, I had made the mistake in a previous function and hadn't gotten around to updating everything.

A: 

You can un-nest the if statements:

if (Expirience > 100) // Level 2
{
    return 2;
}

if (Expirience > 200) // Level 3
{
    return 3;
}

... and so on, ending with

return 1;

EDIT: Of course, you'll have to start with the highest level and end with the lowest, not the other way around...

Rawling
Yeah, but that still leaves somewhat messy code. This value will be grabbed quite frequently.
Scott
Thats not really good, if Expience == 201, you will return 2 and not 3.. you need to go the opposite way (first test higher values)
Itsik
See my edit. Of course, that doesn't excuse my being an idiot >.>
Rawling
+21  A: 

Use a SortedList<int, int> and iterate over it until you find a value that is higher than the value you are searching for. You can do it using a simple iteration as in the answer you have already accepted. Or it can be done elegantly using LINQ (at a slight performance cost):

SortedList<int, int> levels = new SortedList<int, int>
    {
        {0, 1},
        {100, 2},
        {200, 3},
        {400, 4},
        {600, 5},
    };

public int Experience;
public int Level
{
    get
    {
        return levels.Last(kvp => Experience >= kvp.Key).Value;
    }
}

Note that storing the 'level' is actually not strictly necessary as you can derive it from the index of the item in the list. It may be advantageous to use a simple List<int> that is sorted instead to prevent errors where you accidentally miss out a level, as in the solution you have already accepted.

If you want better performance you could use List.BinarySearch but I think the extra complexity is not worth it unless you have performance profiled and found that this is the bottleneck.

List<int> levels = new List<int> { 0, 100, 200, 400, 600 /* etc... */ };

int index = levels.BinarySearch(Experience);
int level;
if (index < 0)
{
    level = ~index;
}
else
{
    level = index + 1;
}
return level;
Mark Byers
indeed, my solution as well. +1
Sem Dendoncker
Definitely. This is called a table-driven approach. What you've got there is a bunch of data which you have written out as control statements. Write it as data instead.
Omer Raviv
Maybe correct the spelling of their variable? It just seems so sloppy to me.
ChaosPandion
+2  A: 

You're going from most inclusive to most exclusive. If you go the other direction, you don't need all the nesting.

if (Expirience > 23000) // Level 17
  {
    return 17; // Bored
  }
  else if (Expirience > 17500) // Level 16
  {
    return 16;
  }
  else if (Expirience > 13300) // Level 15
  {
    return 15;
  }
  ...
Matt M
Alternatively, use this same aproach with a Switch statement instead of all the if...else statements.
AllenG
@AllenG: C#'s `switch` statement doesn't support ranges IIRC.
KennyTM
You don't even need the `else`'s
Patrick
+13  A: 
int[] levelCutoffs = new int[] {0, 100, 200, 400, 600 /*...*/};

for (int level = 0; level < levelCuttoffs.size; ++level) {
    if (Experience < levelCuttofs[level])
        return level;
}
return levelCuttoffs.size;

Edit: altered to use Bradley Mountford's suggestion.

TreDubZedd
This is probably the most scalable way.
DeadMG
Instead of return 17 for the fallback, you will likely want to return levelCuttoffs.size so that it scales dynamically.
Bradley Mountford
Yeah, this looks like the best solution to my problem for now, as I only need to return integers, not strings. Thanks TreDubZedd.
Scott
LBushkin's version has O(log n) search time. this one has O(n) which is less scalable.
David
@David: That's true if N is huge. For small arrays a simple linear search could be better than a binary search. I don't think a game should offer 1,000 levels. (That said, it's safer to use built-in functions than write a loop explicitly yourself).
KennyTM
@KennyTM: the worst case for binary search is still O(log n) while iteration's worst case is O(n). I think you are referring to the "insertion" overhead of constructing a binary search tree. After the initial lists are created, BinarySearch should have the same performance or better than iteration even with a single node. Not sure if the BinarySearch method reconstructs the tree for each call.
David
@David: I'm not referring to the "insertion overhead". The list is already sorted so there's no need to construct a BST. I'm referring to the hidden constant and locality of memory access.
KennyTM
Considering the most I'll ever possibly need is 20 levels (due to a client restriction in the game), I'm sure this will suffice. Looking more for speed than scalability.
Scott
@Scott: either will work fine since the N is trivial, but the point is BinarySearch *is* faster than iteration, thus it scales better for higher N.
David
+3  A: 

@Mark's suggestion is a reasonable one. You could also reverse the order of evaluating the experience to un-nest the ifs:

if (Expirience > 23000) return 17; 
if (Expirience > 17500) return 16;
//... and so on.

But I would probably just use an regular C# array and the BinarySearch method, which can return the index of the matching item or the 2's complement of the least item that is just larger than the value you've searched for:

int[] levelThresholds = new[] { 100, 200, 400, 600, 1000, ..., 23000 };

int experience = 11403;
int index = Array.BinarySearch( levelThresholds, experience );
// returns either the index, or the 2's complement of the 
// first index greater than the value being sought
int level = index < 0 ? ~index : index+1;
LBushkin
+1 BinarySearch is O(log n) and considering the level marks are fixed, this will perform better than just iterating a list of ifs or a list of level marks.
David
-1. There are a 17 items, not 17K. I doubt there would be a difference in timing for this that could would show up with less than millions of 'pets loaded', if even then. It has to make a BinarySearch class, do all sorts of runtime checking, make *more* classes, etc. Considering it makes it more complicated than the simple,clear solution, why do it?
Andrew Backer
@Andrew Backer: `BinarySearch` is not a class. It's a method of Array (as well as `List<T>`) and it doesn't create any additional objects. Furthermore, all of the complexity is encapsulated in the .NET implementation. If we had to write our own binary search algorithm, I would agree with you - but why not use something already provided by the BCL? Keep in mind, part of the value of answering questions is that folks looking at this in the future can help arrive at solutions to their own, similar problems. So demonstrating alternative approaches has its own merit.
LBushkin
+2  A: 

I would take Mark Byers answer a step further. Since is slightly confusing (I'd forget which int is which) Instead make a sorted list of

SortedList<UserLevel>

That way you can define much more than just a required number of experience points to each level. you could also assign a Name, i.e. "Uber Elite Super Level" and perhaps even a custom welcome message at each level.

Neil N
+2  A: 

If the experience algorithm can be reduced to a function, it should use functional calculation, ie:

return (Expirience/200); // if each level was 200 xp etc

However your nested if's above don't seem to apply to any function curve, there is the ? operator:

return
 (Expirience > 23000) ? 17 :
 (Expirience > 17500) ? 16 :
 (Expirience > 13300) ? 15 : 
 .. etc ..
 (Expirience > 100) ? 2 : 1;
David
+3  A: 

How about a simple formula, based on a logarithmic function?

Something like

return Math.Floor(LinearScale * Math.Log(Expirience, LogBase));
Wikser