i've just started to learn unity and i'm having some troubles to go ahead with my exercises.
I need to move 2 cars using coordinates stored in a .txt file called "positions.txt".
The cars have to move at the same time.
Inside the "cartesian" list there are numbers which have to be splitted in groups of 4 (time, x, y, z).
I'd like to create 2 arrays for each car: a time array and a Vector3 array.
First of all, i need to read the file and store the numbers in the arrays. I found several options and i tried this one without success: https://allison-liem.medium.com/unity-reading-external-json-files-878ed0978977
Which approach should i follow?
Thank you.
File "positions.txt"
[
{
"id":"car_1",
"position":{
"cartesian":[
1,0,0,0,
2,0,0,2,
3,1,0,3,
4,1,0,6,
5,2,1,9,
6,2,0,11,
7,3,1,13,
8,3,0,15,
9,3,1,18,
10,4,1,20]
}
},
{
"id":"car_2",
"position":{
"cartesian":[
1,0,0,0,
2,0,0,2,
3,1,0,3,
4,1,0,6,
5,2,1,9,
6,2,0,11,
7,3,1,13,
8,3,0,15,
9,3,1,18,
10,4,1,20]
}
}
]
So first of all you have a JSON so see Serialize and Deserialize Json and Json Array in Unity
I would recommend the Newtonsoft Json.NET package and do e.g.
[Serializable]
public class Position
{
// you can also go for array if you liked
// in JSON it is the same
public List<int> cartesian;
}
[Serializable]
public class Car
{
public string id;
public Position position;
}
and then
var json = File.RealAllText(FILEPATH);
// again could also go for array here
List<Car> cars = JsonConvert.DeserializeObject<List<Car>>(json);
However, the cartesian arrays are a bit "stupid" - have in mind that once this is handled as a list/array there are no line breaks and no good meaning for your redundant indices (1, 2, 3, 4, ...) at the beginning of each set.
I'd like to create 2 arrays for each car: a time array and a Vector3 array.
his wouldn't make sense in my eyes - if something that information clearly belongs coupled together so splitting it into individual arrays would be bad.
Either way you will have to manually extract your values when iterating over the list/array
Go in sets of 4 items
Ignore the first - it seems to be a redundant index
take the last three items and fill them into x,y,z of a vector
like e.g.
[Serializable]
public class Position
{
// you can also go for array if you liked
// in JSON it is the same
public List<int> cartesian;
public List<Vector3> GetPositions()
{
var count = cartesian.Count / 4;
var result = new List<Vector3>(count);
for(var resultIndex = 0; resultIndex < count; resultIndex++)
{
var cartesianIndex = resultIndex * 4;
var position = new Vector3(cartesian[cartesianIndex + 1], cartesian[cartesianIndex + 2], cartesian[cartesianIndex + 3]);
result.Add(position);
}
}
}
or if you actually need also that first value I would use a custom type like e.g.
public readonly struct TimeFrame
{
public int Time { get; }
public Vector3 Position { get; }
public TimeFrame(int time, Vector position)
{
Time = time;
Position = position;
}
}
and then adjust accordingly
[Serializable]
public class Position
{
// you can also go for array if you liked
// in JSON it is the same
public List<int> cartesian;
public List<TimeFrame> GetPositions()
{
var count = cartesian.Count / 4;
var result = new List<TimeFrame>(count);
for(var resultIndex = 0; resultIndex < count; resultIndex++)
{
var cartesianIndex = resultIndex * 4;
var time = cartesian[cartesianIndex];
var position = new Vector3(cartesian[cartesianIndex + 1], cartesian[cartesianIndex + 2], cartesian[cartesianIndex + 3]);
result.Add(new TimeFrame(time, position));
}
}
}
I saw that the medium article you're referring to uses Unity's built in JSON parsing library which does not support array parsing, as pointed out by this answer here https://stackoverflow.com/a/36244111/13489126.
That is the reason why your approach was not working.
I would recommend you to use Newtonsoft Json Parser instead of Unity's.
Related
A few of my Scriptable Objects(used for seeding my initial game data) contain large 2 dimensional arrays (like 10x10), the data of which i am generating with an external Python script. I do use the Odin inspector plugin as well, to serialize the 2d array for me and provide me with a nice representation of that array inside the Unity editor.
I am simply doing it like this :
[TableMatrix()]
public int[,] table = new int[10, 10];
and this is just an Odin SerializedScriptableObject class.
The problem is, I really want to avoid having to add the 10x10 elements by hand using the Unity editor and also I want my objects to have variable 2d array sizes, one could beb (10,10), another could be (5,5). Is there a way to populate my scriptable objects programmatically to achieve that ? (Or does the Odin inspector plugin support something like that if anyone knows ?)
Thanks !
Sorry for the late response #Spyros.
My approach will be similar to #D Manokhin approach, but instead of using jagged array I'll use a multidimensional array (cause you can build a custom editor script to visualize them, I'm pretty sure you can visualize jagged arrays on editor without plugins, I've never used Odin plugin).
So I delcare one class who will store the structs called TileData:
using UnityEngine;
using System.Collections;
[System.Serializable]
public class TileData
{
/*
* rows
[rowData][rowData][rowData]
[cell] ->value
->type
[cell] ->value
->type
[cell] ->value
->type
*/
[System.Serializable]
public struct cell
{
public float value;
public string type;
}
[System.Serializable]
public struct rowData
{
public cell[] row;
}
public rowData[] rows;
}
I used value and type as an "examples", it's absolutly up to you, you can also store Vector3 if you want!
Then, how to call and use this kind of structure? Let's see:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Matrix : MonoBehaviour {
//declare the structure that will store the data
public TileData tileData = new TileData();
//those are public so you could make the matrix with the size you want..
//..ideally dynamically call it from your python file requisits
public int horizontalMatrixSize = 10;
public int verticalMatrixSize = 10;
// Use this for initialization
void Start()
{
//Create the matrix structure and fill it
tileData.rows = new TileData.rowData[horizontalMatrixSize];
for (int i = 0; i < tileData.rows.Length; i++)
{
tileData.rows[i].row = new TileData.cell[verticalMatrixSize];
for (int u = 0; u < tileData.rows[i].row.Length; u++)
{
tileData.rows[i].row[u].value = GetValuesFromPythonFileOrWhatever();
tileData.rows[i].row[u].type = GetValuesFromPythonFileOrWhatever();
}
}
}
}
But if you really like the jagged structure, you can still use it (but remember that will not be represented on editor) like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Matrix : MonoBehaviour {
//declare the structure that will store the data
[SerializeField] private float[,] grayscaleBidimensional = null;
//those are public so you could make the matrix with the size you want..
//..ideally dynamically call it from your python file requisits
public int horizontalMatrixSize = 10;
public int verticalMatrixSize = 10;
// Use this for initialization
void Start()
{
//Create the matrix structure and fill it
tileData.rows = new TileData.rowData[horizontalMatrixSize];
grayscaleBidimensional = new float[horizontalMatrixSize, verticalMatrixSize];
for (int x = 0; x < horizontalMatrixSize; x++)
{
for (int y = 0; y < verticalMatrixSize; y++)
{
grayscaleBidimensional[x, y] = GetValuesFromPythonFileOrWhatever();
}
}
}
}
Remember that you can make the values of the objects as you want, so they don't need to have an static size!
To change an individual value, you can do table[x,y] = (your value). Lets say you want to fill every value with the number one, you could do:
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 10; x++)
{
table[x, y] = 1;
}
}
Hope that answers your question and sorry if the code is wrong - I don't have access to Unity at the moment, so you may need to fiddle around with it.
You should serialize the arrays out to files in python, and then use a Scripted Importer to import them into Unity, construct the arrays in the ScriptableObject, and fill in their values.
https://docs.unity3d.com/Manual/ScriptedImporters.html
Im making a game inside Unity3D but im stuck with something what I try to find the solution on it.
Its a part of a stock system where you can store things in it like "items or materials".
the following variables / classes are as follows :
[Serializable] public class ItemStorage
{
public ExtendedList.Items item;
public List<MaterialStorage> material;
}
[Serializable] public class MaterialStorage
{
public ExtendedList.Ingots material;
public int materialCount;
}
Above are the classes.
public List<ItemStorage> itemstorage;
public List<MaterialStorage> materialstorage;
Above are the variables.
and under this are the enums what i bassicly use :
public enum Items : int
{
[Description("Helmet")]
HELMET = 0,
[Description("Chestplate")]
CHESTPLATE = 4,
[Description("Platelegs")]
PLATELEGS = 8,
[Description("Gaunlets")]
GAUNLETS = 12,
[Description("Boots")]
BOOTS = 16,
[Description("Sword")]
SWORD = 20,
[Description("Shield")]
SHIELD = 24,
[Description("Wheel")]
WHEEL = 28,
[Description("Axe")]
AXE = 32
}
public enum Ingots : int
{
[Description("Bronze Ingot")]
BRONZE = 0,
[Description("Iron Ingot")]
IRON = 5,
[Description("Steel Ingot")]
STEEL = 10,
[Description("Silver Ingot")]
SILVER = 15,
[Description("Platinum Ingot")]
PLATINUM = 20
}
but I have a code where I call it in the "Awake" to initialize the / those list(s) :
for (int i = 0; i < Enum.GetValues(typeof(ExtendedList.Ingots)).Length; i++)
{
if (GameManager.Instance.PlayerLevel >= ExtendedList.GetIngotValue(i))
{
materialstorage.Add(new MaterialStorage() { material = (ExtendedList.Ingots)ExtendedList.GetIngotValue(i) });
}
}
for (int i = 0; i < Enum.GetNames(typeof(ExtendedList.Items)).Length; i++)
{
if (GameManager.Instance.PlayerLevel >= ExtendedList.GetItemValue(i))
{
itemstorage.Add(new ItemStorage() { item = (ExtendedList.Items)ExtendedList.GetItemValue(i), material = materialstorage });
}
}
On start it initializes as perfect. So at material list i get (bronze) and in the item list I get (helmet) but when I level up I call the function. So in the material list i get (bronze, bronze) and in the item list I get (helmet, helmet) instead of (bronze) and (helmet).\
edit :
(forgot) : when the player is level 0 it only initializes bronze. but when it reaches level 5 its adding a new list.
but than instead of bronze its iron.
so i want to add a new list when the player level is equalls to the value what i give to iron, steel, silver, platinum, etc.
I hope you guys can help me with this sticky situation.
Best wishes, And have a good day.
EDIT
I was slightly mistaken in how the dictionary was being populated, the reason you are seeing "1","2" is because of the values being inserted into the dictionary, not the key. Take this line you have:
for (int i = 0; i < Enum.GetValues(typeof(ExtendedList.Ingots)).Length; i++)
The variable i will loop from 0 -> 4, because you have 5 elements in ExtendedList.Ingots. That means that when the following bit of code is run:
int key = ExtendedList.GetLevel((ExtendedList.Ingots)i);
ExtendedList.Ingots value = (ExtendedList.Ingots)i;
levelToMaterialsMap.Add(key, value);
The variable value will only ever be BRONZE,1,2,3,4. This is because when you cast an integer to an enum as in the second line above, even if it doesn't find the value in your Enum set, the cast will still work but value will now equal to i , that's just how enums works in .NET. So in the first loop, the cast is (ExtendedList.Ingots)(0) -> BRONZE because it found the value 0 in the enum set. From the second iteration onward, it doesn't find the value so you get (ExtendedList.Ingots)(1) -> 1, (ExtendedList.Ingots)(2) -> 2 and so on.
I have found the solution of my problem with ur method (Sasang)
the the console is giving now the correct key / values.
in the for loop is everything the same except the line with calculating the value line.
the code looks right now like this :
ExtendedList.Ingots value = (ExtendedList.Ingots)Enum.GetValues(typeof(ExtendedList.Ingots)).GetValue(i);
The reason you are getting [bronze, bronze] at level2 in your materialstorage list (instead of just [bronze]) is because you are adding values to your list with materialstorage.Add, it seems to me that what you really want is to initialize a new list every time that method runs, so before you iterate through your enums in the for loops, set the materialstorage and itemstorage to new lists.
Also i dont entirely understand the reason for having materialstorage list defined twice if they always appear to be the same value. You have it defined at the gamemanager level and also again in each itemStorage object. If i understand your edit statement correctly, i seems like you want a list of lists where the number of lists is equal to the number of level increments like so:
0: [[bronze]]
5: [[bronze], [bronze,iron]]
10: [[bronze], [bronze,iron], [bronze,iron,steel]]
...
In this case what you really need is a Dictionary, one which maps player level to a separate list of materials. You can initialize this at the in your main GameManager class. Where the number of keys in the dictionary equals to the number of elements in your enum, like so:
Dictionary<int, List<MaterialStorage>> levelToMaterialsMap =
new Dictionary<int, List<MaterialStorage>>();
for (int i = 0; i < Enum.GetValues(typeof(ExtendedList.Ingots)).Length; i++)
{
int key = ExtendedList.GetIngotValue(i);
List<MaterialStorage> value = new List<MaterialStorage>();
levelToMaterialsMap.add(key, value);
}
Now every time you call your looping method, check to see if the player level is one of the levels in which you need to add materials for, if it is then create a new materialstorage list, add all the materials to it, then set it into the Dictionary for that specific level key. Something like:
if(levelToMaterialMap.containsKey(GameManager.Instance.PlayerLevel))
{
List<MaterialStorage> materialstorage = new List<MaterialStorage>();
//Do all the stuff you need to add materials for this player level
//...
//Then
levelToMaterialMap[GameManager.Instance.PlayerLevel] = materialstorage;
}
I'm new at C# and I want to create a multidimensional array like this:
(source: parks.ca.gov)
But in 8x8x4 cells.
I want to store maze cells.
{
{1,1,1,0}, {1,0,0,0}, {1,1,1,1}, {1,0,0,0}, {1,1,0,1}, {1,1,0,1}, {0,1,0,0}, {1,0,0,1},
...
{0,1,1,0}, {1,0,1,0}, {0,0,1,1}, {1,0,1,0}, {1,0,0,1}, {0,1,0,1}, {1,1,1,0}, {1,1,0,1},
}
int[,,] table = new int[8,8,4]; // is this right?
table[0,0] = {0, 0, 1, 1}; // I want to fill it this way.
I'm aware it does not explicitly answer the question, but in my opinion you're shooting yourself in the foot by working with 3D arrays. C# is an OO language, so it really helps if you think OO.
Instead of working with a multidimensional array representing cells for you 3d Maze (if it is really a 3d maze you want), why not create a List of classes named Cell, each one containing their position and other stuff you need, like :
public class Cell
{
public Cell (int x, int y, int z)
{
X = x;
Y = y;
Z = z;
}
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
public bool IsExplored { get; set; }
}
Then you can have a simple List<Cell> that you can iterate over.
You can also remove the x,y,z and create a Position class.
For walls, you can create an Enum and use bitwise operations, or store a list of Enum. Since you're a beginner, I'd suggest you the list of enums. You would have to add this Enum in the code, and this property to the Cell class :
public Enum WallPosition
{
Top,
Left,
Bottom,
Right
}
public List<WallPosition> walls { get; set;} //This goes into the Cell class
That way, every operation will be much much easier to do. For example, if you need to explore every cell at the column #3, you can do
foreach (Cell cell in yourCellList.Where(c => c.Y == 3))
{
cell.IsExplored = true;
}
Need to render every explored cell differently?
foreach (Cell cell in yourCellList.Where(c => c.IsExplored) { //Do stuff }
And so on.
No need for complicated for loops with your 3 dimensions, and a simple foreach is in my opinion far more readable than a
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
for (int k = 0; k < 4; k++)
every time you need to access your table.
The only ugly part would be to fill you list (By creating new Cell instances), but it would still be far more readable than a huge wall of { { { 0, 1, 0, 1 }, {1, 1, 1, 0} [...]
I'd also suggest that you read an introduction to OO principles.
With multidimensional arrays, you can either set them all at once (using basically the syntax you showed):
int[,,] table = {
{ { 1, 1, 1, 0 }, { 1, 0, 0, 0 } },
{ { 0, 1, 1, 0 }, { 1, 0, 1, 0 } }
};
or you can set the items one by one:
int[,,] table = new int[8,8,4];
table[0,0,0] = 0;
there is nothing in between. The best you could do is to write an extension method that would work something like this:
table.Set(0, 0, new int[] { 0, 0, 1, 1 });
As an alternative, you could use 2D array of arrays:
int[,][] table = {
{ new[] { 1, 1, 1, 0 }, new[] { 1, 0, 0, 0 } },
{ new[] { 0, 1, 1, 0 }, new[] { 1, 0, 1, 0 } }
};
or you could use almost the syntax you proposed:
int[,][] table = new int[8,8][];
table[0,0] = new[] { 0, 0, 1, 1 };
A disadvantage of this approach is that it doesn't force the inner arrays to be all the same length.
As proposed in comments, another option would be use a custom type like Cell and have a 2D array of those. Doing that makes it clearer what the array actually means, table[0,0].Left is certainly more readable than table[0,0,1].
If the wall can be there or not, you shouldn't use int values 0 and 1, you should use bool values false and true. If you want to have more states, an enum might be appropriate.
Your structure contains a lot of duplication, since bottom of a cell is the same as top of the cell below it (unless you want to have one way walls). This means the structure can get into an inconsistent state, which is often hard to debug (“The wall isn't there? But I just looked and it is there.”).
One way to avoid that would be store walls instead of cells. Instead of 8×8 cells, you would have 8×9 horizontal walls and 9×8 vertical walls. You could then have methods that would abstract this away, so you could easily look up walls of a particular cell.
An array initializer for a 3D array would look like this:
int[,,] table = {
{ {1,1,1,0}, {1,0,0,0}, ... },
{ {0,1,1,0}, {1,0,1,0}, ... },
...
};
The first line is right. The second line won't work. The closest thing is to use a 2D array of arrays:
int[,][] table = new int[8,8][];
table[0,0] = new int[] {0, 0, 1, 1};
Like #tigrou and #Pierre-Luc Pineault suggested, it would be a lot cleaner to encapsulate each cell in an object instead of a plain array.
Consider storing the data in an external file and reading it in instead of hardcoding the 256 numbers.
I'll start by saying I am very much a beginner programmer, this is essentially my first real project outside of using learning material. I've been making a 'Simon Says' style game (the game where you repeat the pattern generated by the computer) using C# and XNA, the actual game is complete and working fine but while creating it, I wanted to also create a 'top 10' scoreboard. The scoreboard would record player name, level (how many 'rounds' they've completed) and combo (how many buttons presses they got correct), the scoreboard would then be sorted by combo score. This led me to XML, the first time using it, and I eventually got to the point of having an XML file that recorded the top 10 scores. The XML file is managed within a scoreboard class, which is also responsible for adding new scores and sorting scores. Which gets me to the point... I'd like some feedback on the way I've gone about sorting the score list and how I could have done it better, I have no other way to gain feedback =(. I know .NET features Array.Sort() but I wasn't too sure of how to use it as it's not just a single array that needs to be sorted. When a new score needs to be entered into the scoreboard, the player name and level also have to be added. These are stored within an 'array of arrays' (10 = for 'top 10' scores)
scoreboardComboData = new int[10]; // Combo
scoreboardTextData = new string[2][];
scoreboardTextData[0] = new string[10]; // Name
scoreboardTextData[1] = new string[10]; // Level as string
The scoreboard class works as follows:
- Checks to see if 'scoreboard.xml' exists, if not it creates it
- Initialises above arrays and adds any player data from scoreboard.xml, from previous run
- when AddScore(name, level, combo) is called the sort begins
- Another method can also be called that populates the XML file with above array data
The sort checks to see if the new score (combo) is less than or equal to any recorded scores within the scoreboardComboData array (if it's greater than a score, it moves onto the next element). If so, it moves all scores below the score it is less than or equal to down one element, essentially removing the last score and then places the new score within the element below the score it is less than or equal to. If the score is greater than all recorded scores, it moves all scores down one and inserts the new score within the first element. If it's the only score, it simply adds it to the first element. When a new score is added, the Name and Level data is also added to their relevant arrays, in the same way. What a tongue twister. Below is the AddScore method, I've added comments in the hope that it makes things clearer O_o. You can get the actual source file HERE. Below the method is an example of the quickest way to add a score to follow through with a debugger.
public static void AddScore(string name, string level, int combo)
{
// If the scoreboard has not yet been filled, this adds another 'active'
// array element each time a new score is added. The actual array size is
// defined within PopulateScoreBoard() (set to 10 - for 'top 10'
if (totalScores < scoreboardComboData.Length)
totalScores++;
// Does the scoreboard even need sorting?
if (totalScores > 1)
{
for (int i = totalScores - 1; i > - 1; i--)
{
// Check to see if score (combo) is greater than score stored in
// array
if (combo > scoreboardComboData[i] && i != 0)
{
// If so continue to next element
continue;
}
// Check to see if score (combo) is less or equal to element 'i'
// score && that the element is not the last in the
// array, if so the score does not need to be added to the scoreboard
else if (combo <= scoreboardComboData[i] && i != scoreboardComboData.Length - 1)
{
// If the score is lower than element 'i' and greater than the last
// element within the array, it needs to be added to the scoreboard. This is achieved
// by moving each element under element 'i' down an element. The new score is then inserted
// into the array under element 'i'
for (int j = totalScores - 1; j > i; j--)
{
// Name and level data are moved down in their relevant arrays
scoreboardTextData[0][j] = scoreboardTextData[0][j - 1];
scoreboardTextData[1][j] = scoreboardTextData[1][j - 1];
// Score (combo) data is moved down in relevant array
scoreboardComboData[j] = scoreboardComboData[j - 1];
}
// The new Name, level and score (combo) data is inserted into the relevant array under element 'i'
scoreboardTextData[0][i + 1] = name;
scoreboardTextData[1][i + 1] = level;
scoreboardComboData[i + 1] = combo;
break;
}
// If the method gets the this point, it means that the score is greater than all scores within
// the array and therefore cannot be added in the above way. As it is not less than any score within
// the array.
else if (i == 0)
{
// All Names, levels and scores are moved down within their relevant arrays
for (int j = totalScores - 1; j != 0; j--)
{
scoreboardTextData[0][j] = scoreboardTextData[0][j - 1];
scoreboardTextData[1][j] = scoreboardTextData[1][j - 1];
scoreboardComboData[j] = scoreboardComboData[j - 1];
}
// The new number 1 top name, level and score, are added into the first element
// within each of their relevant arrays.
scoreboardTextData[0][0] = name;
scoreboardTextData[1][0] = level;
scoreboardComboData[0] = combo;
break;
}
// If the methods get to this point, the combo score is not high enough
// to be on the top10 score list and therefore needs to break
break;
}
}
// As totalScores < 1, the current score is the first to be added. Therefore no checks need to be made
// and the Name, Level and combo data can be entered directly into the first element of their relevant
// array.
else
{
scoreboardTextData[0][0] = name;
scoreboardTextData[1][0] = level;
scoreboardComboData[0] = combo;
}
}
}
Example for adding score:
private static void Initialize()
{
scoreboardDoc = new XmlDocument();
if (!File.Exists("Scoreboard.xml"))
GenerateXML("Scoreboard.xml");
PopulateScoreBoard("Scoreboard.xml");
// ADD TEST SCORES HERE!
AddScore("EXAMPLE", "10", 100);
AddScore("EXAMPLE2", "24", 999);
PopulateXML("Scoreboard.xml");
}
In it's current state the source file is just used for testing, initialize is called within main and PopulateScoreBoard handles the majority of other initialising, so nothing else is needed, except to add a test score.
I thank you for your time!
As a programming tip, you should replace the 3 different arrays with a single array of Score Objects, each of these objects would have all 3 properties you mentioned (Name, Level and Score). You can then create a comparator-like function (based on the score attribute) for it which can be used to sort it via the Arrays.Sort() method.
If you want to keep you current 3 arrays then you can look up sorting algorithms here: http://en.wikipedia.org/wiki/Sorting_algorithm#Comparison_of_algorithms and just make it so that any changes are done to the 3 arrays simultaneously instead of one by one (since you keep the data in them synchronized by index).
You can consider placing your user name, level, and score in a separate class (let's call it ScoreBoardEntry) that inherits from IComparable and then write a comparator. This will allow you to use the built in sort function of List and will be a neater solution than just writing your own.
Your code should look something like this:
1) Create/load a List < ScoreBoardEntry> with all the previous scores
2) push the new score into it
3) Sort List
4) Print only first 10 entries
I wrote some code for you. I've been learning some new-ish type C# stuff like linq, IEnumerable, and yield and kind of wanted to put them together, so hopefully it helps.
you might like the snippet editor i used - linqpad - it's good for learning/experimenting
void Main()
{
string [] playerNames = {"Aaron", "Rick", "Josie"};
var sim = new GameSimulator(playerNames, 1000000, 5000);
var scores = sim.GetScores(5);
var sorted = scores.OrderBy(c=>c.Score).Reverse();
var top = sorted.Take(2);
// uncomment if using linq pad
//scores.Dump("scores");
//sorted.Dump("sorted");
//top.Dump("top");
}
class GameSimulator
{
public GameSimulator(string [] playerNames, int maxScore, int maxLevel)
{
this.playerNames = playerNames;
this.maxScore = maxScore;
this.maxLevel = maxLevel;
}
private string [] playerNames;
private int maxScore;
private int maxLevel;
public IEnumerable<ScoreBoardEntry> GetScores(int numGames)
{
for (int i = 0; i < numGames; i++)
{
string randomName = playerNames[random.Next(0, playerNames.Length-1)];
int randomScore = random.Next(1, maxScore);
int randomLevel = random.Next(1, maxLevel);
yield return new ScoreBoardEntry(randomName, randomLevel, randomScore);
}
}
static private Random random = new Random();
}
class ScoreBoardEntry
{
public ScoreBoardEntry(string playerName, int levenNumber, int score)
{
PlayerName = playerName;
LevelNumber = levenNumber;
Score = score;
}
public string PlayerName { set; get; }
public int LevelNumber { set; get; }
public int Score { set; get; }
}
I have an ArrayList of MCommand objects (cmdList) and I want to sort it so that shapes with closest points are next to each other in the ArrayList. For example, say I have three lines in the ArrayList:
line(xs, ys, zs, xe, ye, ze)
cmdList[0] = line1(1.3, 2.5, 3, 4, 5, 6)
cmdList[1] = line2(1, 5, 6.77, 7, 8, 2)
cmdList[2] = line3(1, 6, 3, 1, 1.1, 1)
Points that need to be close are LastPosition of line with BeginPosition of other line.
LastPosition of line is (xe, ye, ze) and BeginPosition of line is (xs, ys, zs).
I now do my sorting by executing a built in sorting:
cmdList.Sort(new MCommandComparer());
This is how my MCommand looks like and how i calculate distance of two points:
public abstract class MCommand
{
//...
public abstract Point3 LastPosition { get; }
public abstract Point3 BeginPosition { get; }
public double CompareTo(Object obj)
{
Point3 p1, p2;
p1 = this.BeginPosition;
p2 = ((MCommand)obj).LastPosition;
return Math.Sqrt(Math.Pow((p2.x - p1.x), 2) +
Math.Pow((p2.y - p1.y), 2) +
Math.Pow((p2.z - p1.z), 2));
}
}
This is the comparer i use:
public class MCommandComparer : IComparer
{
private MCommand prev;
double distanceFromPrev = 0;
double distanceFromCurr = 0;
public int Compare(object o1, object o2)
{
if ((MCommand)o2 == prev)
return 0;
if (prev != null)
distanceFromPrev = ((MCommand)o1).CompareTo(prev);
distanceFromCurr = ((MCommand)o1).CompareTo(o2);
prev = (MCommand)o2;
return (int)(distanceFromCurr - distanceFromPrev);
}
}
I've tried many ways and got lost... This doesnt sort shapes the way I want to. My question is, what I could be doing wrong? Should i try writing sorting from scratch? My ArrayList can contain couple thousands elements, and i need to have an efficient sort.
What could I be doing wrong?
You're assuming the elements will be presented to you in a particular order - you're remembering the "previous" element, which is a huge red flag.
The way various sorts work won't do this at all. Basically your comparer should be stateless. It sounds like you don't really have a total ordering here - there's no way of taking any two arbitrary elements and saying which should be before or after the other one.
I don't know exactly how you'd do whatever you need, but I don't think the standard sorting built into .NET is going to help you much.
You could make your MCommand class subscribe to IComparable. In doing this you would allow your list to sort your shapes without the need for additional Comparer Object. All the sorting functionality would be handled by the list and the objects within it.