- Home /
Enumeration over grid states
In my problem, I have a series of "states" I need to be able to iterate over.
Each state holds a 2-dimensional array of objects. Each object has a boolean and an int, both initialized to random values.
The next state depends on the current and, for any given state, there is only one possible next state.
Assuming the initial state is considered to be 0, how would I implement IEnumerator or IEnumerable (or both, or neither), in order to iterate over these states.
Ideally, I would be able to use syntax like this:
State++;
or:
if(State == State + 3) {
DoSomething();
}
Or maybe even use a slider to move between states.
I'm fairly new to Unity and C# and I have been beating my head against a wall trying to figure this out. I'm not even sure if these interfaces are the way to go, or if another method would be more appropriate. Any help is highly appreciated.
Below is a skeleton of what I already have. It seems the StateData class would be my IEnumerable(?) because it holds what I want to iterate over. This seems to fulfill IEnumerator.MoveNext(), though; so, I'm confused.
MonoState.cs
public class MonoState : MonoBehaviour {
public StateData currentState;
private void Start() {
// here's where I initialize the data.
}
private void Update() {
// here's where refresh my scene to match the data
}
}
StateData.cs
public class StateData {
ItemData[][] data;
public StateData(ItemData[][] initalState) {
data = initialState;
}
public ItemData[][] GetNextState() {
// here's where I calculate the next state
// and I assign it to data.
}
}
ItemData.cs
public class ItemData {
public bool Flag;
public int Value;
}
Answer by Namey5 · Jan 25, 2021 at 08:01 AM
First, a clarification.
//This is a jagged array (an array of arrays)
ItemData[][] data;
//This is a multidimensional array
ItemData[,] data;
The difference between these types is that a multidimensional array exists as a fixed block, whereas a jagged array can have different array lengths per element. If your item data has fixed lengths on both axes I would suggest using a multidimensional array instead.
Another thing to point out is reference types vs. value types. If you compare two classes, chances are you aren't comparing the values held within them, rather just comparing if they point to the same object (this goes for arrays too). For that reason, I would recommend using structs in this case (or at least overloading the comparison operators);
public struct ItemData
{
public bool Flag;
public int Value;
}
public struct StateData : IEquatable<StateData>
{
public ItemData[][] data;
public StateData(ItemData[][] initalState)
{
data = initialState;
}
public ItemData[][] GetNextState()
{
// here's where I calculate the next state
// and I assign it to data.
}
//Compare the values of the array rather than the array itself
public bool Equals (StateData a_Other)
{
if (data.Length != a_Other.data.Length)
return false;
for (int i = 0; i < data.Length; i++)
{
if (data[i].Length != a_Other.data[i].Length)
return false;
for (int j = 0; j < data[i].Length; j++)
{
if (data[i][j] != a_Other.data[i][j])
return false;
}
}
return true;
}
public static bool operator == (StateData a_LHS, StateData a_RHS) => return a_LHS.Equals (a_RHS);
public static bool operator != (StateData a_LHS, StateData a_RHS) => return !a_LHS.Equals (a_RHS);
}
Finally, to do what you require I would then have another class on top that keeps a list of StateData objects which you can enumerate over;
public class States
{
private List<StateData> states;
public States (ItemData initialState)
{
states = new List<StateData>();
states.Add (new StateData (initialState));
}
//This lets you access a States object as though it were an array, i.e.
// States states = new States();
// StateData data = states[3];
public StateData this[i]
{
get
{
//If we don't have enough states, start generating and caching them until we do
while (states.Count <= i)
{
states.Add (new StateData (Current.GetNextState ()));
i++;
}
return states[i];
}
}
//Helper properties to access the current and next states
public int CurrentIndex => states.Count - 1;
public StateData Current => states[CurrentIndex];
public StateData Next => this[CurrentIndex + 1];
public StateData FutureState (int a_Index) => this[CurrentIndex + a_Index];
//To get syntax equivalent to your 'state + 3'
public static StateData operator + (States a_States, int a_Index) => a_States.FutureState (a_Index);
}
Now you should be able to access it like you showed above, i.e.
States states = new States ( /* ... */ );
//Grab the current state data;
ItemData[][] data = states.Current.data;
//Compare states;
if (states.Current == (states + 3))
DoSomething();
You can still inherit from IEnumerable (just point to to the 'List<StateData>'), however I felt it may be easier to understand if done without first.
Thank you. As I was researching, I was getting the idea that these interfaces aren't necessary (especially after illustrating my problem better in the comment above), and I could probably implement my own solution. Being able to foreach()
over all the states probably isn't something I need to do. Neither is using a coroutine to iterate over the states (although that might be cool, these states can go on infinitely, so there is no end without checking for certain conditions).
What ultimately answered my question is that, yes, a third class that will hold the list is what I needed. I pretty much already had it all coded, I just need to move the state management to the other class, and forget about using IEnumerable
and IEnumerator
for now.
The code above was just a snippet, and after checking, yes I am actually using ItemData[,] multidimensional array, and yes I have an indexer that accesses the data. I did make some implicit operators that cast my ItemData[,] to and from StateData, and then both can be implicitly cast from their $$anonymous$$onoBehavior (just because, lol).
$$anonymous$$y ItemData class has a bunch of helper properties that deter$$anonymous$$e what that individual item's next state will be. To get the next state, StateData loops through each ItemData, setting that item's current state to what it's next state should be. Instead, I could just generate a temporary StateData that's cached ahead of time before its values are set to the current state.
I don't know why I didn't think of just overloading the + operator. I usually wrap my List.Add() calls in an operator overload soI don't know why I didn't think I could use the same method here. Silly me. I can also just overload the ++ operator to wrap GetNextState() which would give me the State++; functionality. Sweet!
Thanks again for setting me on the right track.
I'll update my question above when I finish applying this solution. :)
Answer by WakingDragon · Jan 24, 2021 at 12:43 PM
I am not quite sure if this is what you mean, but the issue seems to be that you have an ordered list of "things" (state objects) and you want to go through them in order, and to be able to perform comparisons (e.g. if the "state" you are in now is equal to the third "state" then do something).
If you create State as a class (not derived from Monobehaviour) then you can populate instances of State into an array (i.e. State[] if you know the total number of states at the point you create the array) or a List.
If you want to see the list/array of states in the inspector then you can set the State class as serializable. But if there are a lot of them then only do this whilst you are working on the logic as I find a large number of these will be slow on performance.
Does that help?
Kind of. Thank you, but maybe I wasn't clear enough.
OK, here's what I have. I have a StateData class and an ItemData class. Neither of which are $$anonymous$$onoBehaviours. The ItemData class is simply a boolean and an int, but that's not really important. The StateData class holds an ItemData[][] array, but it could just as easily be a bool[][] or int[][].
The current "state" is the state of all values in the array. In the initial state, all the ItemData would be set to some arbitrary values. The next state is deter$$anonymous$$ed by the current state via a set of rules. This deter$$anonymous$$ation is handled by the StateData class. This seems like I should be able to enumerate to the next StateData, storing each one I visit as I go, so that I could later revert to a previous state or reset it to the initial state during runtime.
I have separate $$anonymous$$onoBehaviours that display the data.
I found this snippet online that computes Fibonacci numbers. It's essentially what I'm going for but, not quite right.
First, it is a method instead of a class (I'm not sure if that matters or not).
Second, it uses Linq statements that are cool, but probably not necessary. I just need it to work (I'm not sure how to adapt them to my 2-d object array, anyway).
Finally, it doesn't perform a lookup to see if previous values have already been calculated and stored (that part's not hard, though).
public static IEnumerable Fib(int n) { List fibs = new List(); Enumerable.Range(0, n) .ToList() .ForEach(f => fibs.Add( (f <= 1 ? 1 : fibs[f - 2] + fibs[f - 1])) ); return fibs; }
In my class, Element 0 in the list would be my initial state. If I request an index out of range, I would need to "generate" states up to that index, and then return that state. If I request an index that is in range, I just load the state from the List.
The process works fine in my head. I'm just not sure how to implement it in code.
Your answer
Follow this Question
Related Questions
Enumerations Varibles 1 Answer
How do I serialize my array in my base using Custom Editor? 1 Answer
Use custom class like array 2 Answers