HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Finding the closest value in a collection in a game-server plugin implementation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thecollectionfindingvalueplugingameclosestserverimplementation

Problem

I have an array of a instances of an enum type. A random double variable is initialized with a value between 0.... to 1, and should be used to get the closest value's enum instance to that randomly set double value.

The enum looks like this:

public enum TargetScope {

    CLEAN(1, 5040),
    FIRST(.888, 5046),
    SECOND(.777,5052),
    THIRD(.666,5058),
    FOURTH(.555, 5064),
    FIFTH(.444, 5070),
    SIXTH(.333, 5076),
    SEVENTH(.222, 5082),
    EIGHTH(.001, 5088);
    
    private int interfaceBase;
    private double time;
    
    TargetScope(double time,int interfaceIdBase) {
        this.time = time;
        this.interfaceBase = interfaceIdBase;
    }
    
    public int getTime() {
        return (int) this.time * TargetManager.TARGET_SEARCH_WAIT;
    }
    
    public int getInterfaceBase() {
        return this.interfaceBase;
    }
    
    public double getPercent() {
        return this.time;
    }
}


I have implemented an algorithm which does that, using the array and few temp variables:

public TargetScope getInterfaceScopeByTime() {
    double alpha = (double) this.time / TargetManager.TARGET_SEARCH_WAIT;

    TargetScope[] scopes = TargetScope.values();
    
    double delta = 1;
    TargetScope scope = null;
    
    for (int i = 0; i < scopes.length; i++) {
        double distance = Math.abs(scopes[i].getPercent() - alpha);
        if (distance < delta) {
            delta = distance;
            scope = scopes[i];
        }
    }
    
    return scope;
}


Basically it gets the closest Enum.getPercent() value in the list to alpha, and then returns the final closest TargetScope instance.

But I really feel bad about using this, where Java has an advanced collection API, which can be used better for this case, but I am not really as experienced with Collections API to use them for that.

Is there really a shorter way to perform this?
The idea behind this

I am working on a game-server plugin, which basically makes

Solution

This problem can be solved mathematically much better than it can with a list, or collection.

What you have is 9 states, and a timer that counts down. The timer can be expressed as a proportion of 1. Because the states are all equally spaced on 1/9 intervals, you can just do math....

So, if the time is \$x\$ and the total time is \$y\$, then the current portion is \$\frac{x}{y}\$ If you multiply this value by 9, you get something on the scale of 0 to 9 inclusive. you really want:

public int tileID(int currentTime, int totalTime, int tileCount) {
    // we want the shift to happen at less than half-the-time (in the middle of the period).
    double shift = (1.0 / tileCount) / 2;
    double progress = (double)currentTime / (double)(totalTime);
    int tile = (int)(progress * tileCount + shift);
    return tile;
}


shift is needed to make the (int) truncation work. Your comment has made me think, and, in reality, it is not the most readable/understandable code. In fact it is broken, and shift should just be 0.5 always... Let me re-do it in the form of a round() instead of a truncation:

public int tileID(int currentTime, int totalTime, int tileCount) {
    double progress = (double)currentTime / (double)totalTime;
    int tile = (int)Math.round(progress * tileCount + 0.3);
    // because your tiles are in the opposite order: tileCount - 1 - tile
    return tileCount - 1 - tile;
}


Then, you can use this with:

TargetScope tile = TargetScope.values[tileId(this.time,
              TargetManager.TARGET_SEARCH_WAIT, TargetScope.values[].length)];

Code Snippets

public int tileID(int currentTime, int totalTime, int tileCount) {
    // we want the shift to happen at less than half-the-time (in the middle of the period).
    double shift = (1.0 / tileCount) / 2;
    double progress = (double)currentTime / (double)(totalTime);
    int tile = (int)(progress * tileCount + shift);
    return tile;
}
public int tileID(int currentTime, int totalTime, int tileCount) {
    double progress = (double)currentTime / (double)totalTime;
    int tile = (int)Math.round(progress * tileCount + 0.3);
    // because your tiles are in the opposite order: tileCount - 1 - tile
    return tileCount - 1 - tile;
}
TargetScope tile = TargetScope.values[tileId(this.time,
              TargetManager.TARGET_SEARCH_WAIT, TargetScope.values[].length)];

Context

StackExchange Code Review Q#61944, answer score: 3

Revisions (0)

No revisions yet.