1

// EDIT: This is not a duplicate to: "When should I use a List vs a LinkedList". Check the answer I've provided below. LinkedList might be useful though if someone wants to insert some positions at a specific place after we have a properly ordered List (in that case - LinkedList) already - how to make one, check my answer below. //

How would you iterate backwards (with pauses for player input) through a set of randomly generated numbers?

I'm trying to build a turn-based game. The order of actions is determined by a result of something like that:

int randomPosition = Random.Range(1,31) + someModifier;

// please note that someModifier can be a negative number!
// There is no foreseeable min or max someModifier.
// Let's assume we can't set limits.

I have a List of KeyValue pairs already containing names of objects and their corresponding randomPosition. // Update: Values of it are already sorted by a custom Sort() function, from highest to lowest.

// A list of KeyValue pairs containing names of objects and their corresponding randomPosition.
public List<KeyValuePair<string, int>> turnOrder = new List<KeyValuePair<string, int>> ();

// GameObject names are taken from a list of player and AI GameObjects.
List <GameObject> listOfCombatants = new List<GameObjects>();

// Each GameObject name is taken from listOfCombatants list.
listOfCombatants[i].name;

I thought, maybe let's add player and AI GameObjects to a list on Index positions equal to each of their randomPosition. Unfortunately, a Generic List can't have "gaps" in it. So we can't create it, let alone iterate it backwards.

Also, I'm not sure how we'd stop a for loop to wait for player input. I have a button, pressing which will perfom an action - switch state and run some functions.

Button combat_action_button;
combat_action_button.onClick.AddListener (AttackButton);

// When player takes his turn, so in TurnState.PLAYER_ACTION:

    public void AttackButton() {
switch(actionState) {
case PlayerAction.ATTACK:
        Debug.Log (actionState);
        // Do something here - run function, etc. Then...
        currentState = TurnState.ENEMY_ACTION;
    break;
}

To make things worse, I've read that "pausing" a while loop isn't good for performance. That it's better to take player input out of loops. So maybe I should create more for loops, iterate a new loop from position to position until the last GameObject acted, and use some delegates/events, as some things players/AI can do are in different scripts (classes). This is not a real-time project, so we can't base anything on time (other than potential max turn time). Another thing is, we don't know how many GameObjects will take turns.

But maybe there's some collection type that can store GameObjects with gaps between their index positions and iterate a loop from highest to lowest with no problem?... I want to make it as simple as possible.

A guy
  • 71
  • 11
  • Solution to your lists not able to have gaps issue: use a - 1 for each supposed "gap" and when referencing the list use LINQ to query all data except for - 1 – aguertin Dec 07 '16 at 03:02
  • 2
    Suppose you were iterating backwards through a list of random integers. **How could you tell it was backwards?** They're random. A list of random coin flips looks just as random backwards as forwards. – Eric Lippert Dec 07 '16 at 03:03
  • Is what you want simply a sorted dictionary? – Eric Lippert Dec 07 '16 at 03:04
  • @Eric Lippert Oh, I forgot - I Sort(). The KeyValuePair.Values are already sorted via special function. From highest to lowest. – A guy Dec 07 '16 at 03:05
  • @aguertin I can't use LINQ I'm afraid, there may be iOS problems with it. So maybe without LINQ. – A guy Dec 07 '16 at 03:07
  • @sarr Yes then do it manually without linq. – aguertin Dec 07 '16 at 03:09
  • @Eric Lippert Hmm. How to pause a loop with already sorted KeyValuePair.Values? I need to have player input there. – A guy Dec 07 '16 at 03:10
  • Possible duplicate of [When should I use a List vs a LinkedList](http://stackoverflow.com/questions/169973/when-should-i-use-a-list-vs-a-linkedlist) – Chuck Savage Dec 07 '16 at 04:11
  • @Chuck Savage not really a duplicate (in my opinion), but a some useful suggestion probably... I've never used them. So, if I undestand it right, LinkedList has null as default value. – A guy Dec 07 '16 at 04:20
  • ^ to the above, I meant, I thought I could .Insert at any Index number of that list without causing problems. But no, no .Insert function possible here. Anyway, good to know LinkedList functions, thanks. – A guy Dec 07 '16 at 04:31
  • 1
    @Sarr - Don't put spaces in the user names with the `@` notifications. That can cause the notification to fail. – Enigmativity Dec 07 '16 at 08:05
  • @Enigmativity thanks, good to know. – A guy Dec 07 '16 at 08:29

2 Answers2

0

For the issue of simultaneously having user input and looping, I recommend looking into background workers or threading/tasks. This should facilitate your problem with simultaneously doing two things at once.

For your list problem I personally prefer the lists as well which is why I would designate each "gap" with a specific character like - 1, and when referencing the data ignore the - 1. To ignore the gaps I recommend LINQ queries but if you don't want to use LINQ that should not be a problem.

EDIT*

I know very little about Unity but from what people have been saying it sounds like running multiple threads is or can be an issue. I looked into this issue and it sounds like you just cannot call the unity api from a background thread. So basically, as long as you do not reference the unity api from the background thread, you should be ok. With that said, you may possibly need/want to make a call to the api inside of the background worker. To do this you need to either invoke the call before or after the background worker thread. I am pretty sure there is also a simultaneous invocation from the main thread by setting the workers step property to true.

aguertin
  • 496
  • 4
  • 18
  • The only problem is that with this approach I would need to insert a really unrealistic number into those "gaps", like maybe 1000 or -1000. And I would need to run a loop to insert 1000 (or -1000) to those "gaps" between existing index positions, like -1, 4, 15, 22, 30 (each randomly generated for each GameObject taking part in game combat). So, not sure if not better to create an int Array with pre-existing 1000 positions with 1000 (or -1000) on every index number... Which is a bit silly since most randomPosition will be between -5 and 30. – A guy Dec 07 '16 at 03:18
  • Oh, and probably we're a bit off topic - I intended to have less steps, so I wanted to insert GameObjects on those index positions... So the List or Array would optimally be of type . So we should probably insert "null" into gaps. Then check if not null... Still worried about the need to create gaps. But maybe there's no better way. – A guy Dec 07 '16 at 03:22
  • 2
    Your first part of the answer is not very applicable to Unity3d, background workers and tasks are not something that is normally done in the Unity game engine. – Scott Chamberlain Dec 07 '16 at 03:27
  • You can use a number array if you are worried about gap sizes. Compare the number array to your list of objects and go from there. – aguertin Dec 07 '16 at 03:39
  • What's the reasoning behind using negative numbers in the array? Just curious. – aguertin Dec 07 '16 at 03:55
  • @aguertin it's just because someModifier may be a negative number. – A guy Dec 07 '16 at 04:18
  • @Sarr Would it be alright if negative numbers weren't allowed? So if someModifier is in fact negative(only in regards to the list/array), you flip it back to positive? – aguertin Dec 07 '16 at 04:53
  • @aguertin I'm afraid that would break the order, so can't really do that. But thanks for your answer, I've clicked on upvote, just someone else downvoted. – A guy Dec 07 '16 at 07:12
0

I've decided to share my own solutions as I think I've developped some accurate answers to my questions.

enter image description here

Solution #1. First, declare an array, 2 variables and 1 function:

int[] arrayOrderedCombatants;
int min = 100000;
int max;

public void SomePlayerAction() {
    Debug.Log ("This is some player or AI action.");
}

public void IterateThroughTurnOrderPositions() {
    for (int i=max; i >= min; i--) {
        if (arrayOrderedCombatants [i] >= 0 && arrayOrderedCombatants [i] >= min) {
            Debug.Log ("This is an existing position in turn order: " + arrayOrderedCombatants [i]);
            foreach (var position in turnOrder) {
                if (position.Value == arrayOrderedCombatants [i]) {
                    max = (arrayOrderedCombatants [i] - 1);
                    goto ExitLoop;
                }
            }
        }
    }
    ExitLoop:
    SomePlayerAction ();
}

Then, for our testing purposes let's use an Update() method triggered by Input.GetKeyDown:

if (Input.GetKeyDown (KeyCode.O)) {
        arrayOrderedCombatants = new int[turnOrder[0].Value + 1];

        Debug.Log ("This is arrayOrderedCombatants Length: " + arrayOrderedCombatants.Length);

        foreach (var number in turnOrder) {             
            if (number.Value < min)
                min = number.Value;     
        }
        Debug.Log ("This is min result in random combat order: " + min);

        for (int i=0; i < arrayOrderedCombatants.Length; i++)
            arrayOrderedCombatants[i] = min -1;

        foreach (var combatant in turnOrder) {
            if (combatant.Value >= 0) {
                arrayOrderedCombatants [combatant.Value] = combatant.Value;
            }
        }
        max = turnOrder [0].Value;
        while (max >= min)
            IterateThroughTurnOrderPositions ();
    }

The above code answers my question. Unfortunately, problems with this solution may be two. First - you can't have a negative index position. So if someModifier makes randomPosition go below 0, it won't work. Second problem - if you have more than 1 occurance of any value from randomPosition, it will be added to the arrayOrderedCombatants only once. So it will be iterated once too. But that's obvious - you can't have more than one value occupying an int type Arrays' index position.

So I will provide you a better solution. It's a different approach, but works like it should.

Solution #2. First, declare a list of GameObjects:

List<GameObject> orderedCombatants = new List<GameObject> ();

Then, in Update() method:

    if (Input.GetKeyDown (KeyCode.I)) {
        orderedCombatants.Clear ();
        foreach (var combatant in initiative) { 
            Debug.Log (combatant);
            for (int i=0; i < listOfCombatants.Count; i++) {
                if (listOfCombatants[i].name.Contains(combatant.Key)) {
                    orderedCombatants.Add(listOfCombatants[i]);
                }
            }
        }
        foreach (var combatant in orderedCombatants) {
            Debug.Log (combatant.name);
        }
    }

The above creates a new list of GameObjects already set in the right order. Now you can iterate through it, easily access each GameObject and perform any actions you need.

A guy
  • 71
  • 11