C# - List of Objects - Acces an object's parameter - c#

I am currently in the middle of trying to create a simple rpg C# game and I am stuck fairly early on in the map creation section.
I created a class for my locations:
public class Location
{
private int ID;
private string Name;
public Location(int id, string name)
{
ID = id;
Name = name;
}
}
And I also created a method which fills a list with my locations. Outside in the main program area I created the list so it is accesible by everything:
List<Location> map = new List<Location>();
public void CreateMap()
{
Location start = new Location(1, "START");
Location forest = new Location(2, "FOREST");
Location city = new Location(3, "CITY");
map.Add(start);
map.Add(forest);
map.Add(city);
}
CreateMap();
However, I am stuck now, because I do not know how to access the parameters of my Location objects inside the list, as I only found how to access a string from a list on the internet, or very complicated and confusing answers that I did not at all understand. I wanted to somehow use a static class as I learned that they are usefull when we do not want to access the information inside the class, but I reckon I didn't quite grasp their concept.
tl;dr:I want to take my ID/Name of a specific object out of my list, but I do not know how.

Change your Location class to use public properties:
public class Location
{
public int Id {get;set;}
public string Name {get;set;}
}
Your CreateMap would then look like this:
List<Location> map = CreateMap();
public List<Location> CreateMap()
{
return new List<Location> {
new Location {Id=1, Name="START"},
new Location {Id=2, Name="FOREST"},
new Location {Id=3, Name="CITY"}
};
}
You can then reference Locations like this:
map.First(m=>m.Id==1).Name
Although, I would suspect you will be doing a lot of lookups by Id, and your List should more than likely be a Dictionary, or a simple array where the index is the Id instead, which will make location lookups much faster. In that case, you can easily convert to a dictionary like this:
Dictionary<int,Location> mapDict = CreateMap.ToDictionary(k=>k.Id, v=>v);
Then you can access location by id like this:
var location = mapDict[1];
var name = location.Name;

Declare Location members as public so you can use List.Find(), for example:
Location start = map.Find(l => l.Name == "START");
Some useful information about List.Find() here.

If your Location class properties are public then you can iterate the list via foreach or for loop as shown below:
foreach(var location in map)
{
var locationId = location.ID;
var locationName = location.Name;
}
OR
for(var i; i< map.Count; i++)
{
var locationId = map[i].ID;
var locationName = map[i].Name;
}
If Location class properties are private, then you will need to add a public function to Location class to do the job. But it won't be an elegant solution:
public class Location
{
private int ID;
private string Name;
public Location(int id, string name)
{
ID = id;
Name = name;
}
public Tuple<int, string> GetById(int id)
{
if (id == this.ID)
{
return new Tuple<int, string>(this.ID, this.Name);
}
else
{
return new Tuple<int, string>(-1, "Not Found");
}
}
}
and then outside you can access it like:
var map = new List<Location>();
map.Add(new Location(10, "A"));
map.Add(new Location(20, "B"));
map.Add(new Location(30, "C"));
var bFound = false;
Tuple<int, string> locationTuple;
foreach (var location in map)
{
var byId = location.GetById(20);
if (byId.Item1 > -1)
{
locationTuple = new Tuple<int, string>(byId.Item1, byId.Item2);
break;
}
}

Related

Turn array into a POCO object

I'm having trouble converting the following string array into a POCO object.
Given the following:
string files = [
"./Folder/file.ext",
"./Folder/file2.ext",
"./Folder/file3.ext",
"./Folder/nestedfolder/file.ext",
"./Folder2/file1.ext",
"./Folder2/file2.ext",
"./file1.ext",
"./file2.ext",
"./file3.ext",
];
I would like to convert it to something like:
public class HierarchicalSource{
public List<HierarchicalSource> Children = new List <HierarchicalSource> ();
public bool folder { get; set; }
public string FullPath;
public HierarchicalSourceSource(string path) {
this.FullPath = path;
}
}
Where HierarchicalSource is the root, and has a list of children
UPDATE:
I ended up changing the list to a dictionary. There must be a more efficient way to do this, but I did as follows:
string fileList = files.Select(x => x.Remove(0, 2)).ToArray();
var root = new HierarchicalSource("root");
foreach(var f in fileList){
var current = root;
string[] splitFile = f.Split('/');
foreach(var s in splitFile){
if(!current.Children.ContainsKey(s)){
current.Children.Add(s, new List<HierarchicalSource>{ new HierarchicalSource(s) });
}
current = current.Children[s].Last();
}
}
POCO:
public class HierarchicalSource{
public string name;
public Dictionary<string, List<HierarchicalSource>> Children = new Dictionary<string, List<HierarchicalSource>>();
public HierarchicalSource(string name){
this.name = name;
}
}
If I understand you correctly, this requires looping through the array, but it'll allow you to parse each item in the array so you can generate the HierarchicalNode object's values.
var node = new HierarchicalSource();
foreach(var str in files)
{
var pathParts = str.Split('/').ToList();
node.Children.Add(new HierarchicalNode()
{
FullPath = str,
Folder = pathParts[1] // you may need to do some debugging to see what the results for pathParts are instead of just [#]
});
}
Since the FullPath member in HierarchicalNode is public you can set that value without having to go through any constructor.
// using the above code for reference
node.FullPath = whateverThePathYouNeedIs;
Also update that property in the class to use getters and setters
public string FullPath { get; set; }

Why is my game serializing this class?

So I'm making a game, and it saves users' progress on the computer in a binary file. The User class stores a few things:
Integers for stat values (Serializable)
Strings for the Username and the skin assets
Lists of both the Achievement class and the InventoryItem class, which I have created myself.
Here are the User fields:
public string Username = "";
// ID is used for local identification, as usernames can be changed.
public int ID;
public int Coins = 0;
public List<Achievement> AchievementsCompleted = new List<Achievement>();
public List<InventoryItem> Inventory = new List<InventoryItem>();
public List<string> Skins = new List<string>();
public string CurrentSkinAsset { get; set; }
The Achievement class stores ints, bools, and strings, which are all serializable. The InventoryItem class stores its name (a string) and an InventoryAction, which is a delegate that is called when the item is used.
These are the Achievement class's fields:
public int ID = 0;
public string Name = "";
public bool Earned = false;
public string Description = "";
public string Image;
public AchievmentDifficulty Difficulty;
public int CoinsOnCompletion = 0;
public AchievementMethod OnCompletion;
public AchievementCriteria CompletionCriteria;
public bool Completed = false;
And here are the fields for the InventoryItem class:
InventoryAction actionWhenUsed;
public string Name;
public string AssetName;
The source of the InventoryAction variables are in my XNAGame class. What I mean by this is that the XNAGame class has a method called "UseSword()" or whatever, which it passes into the InventoryItem class. Previously, the methods were stored in the Game1 class, but the Game class, which Game1 inherits from, is not serializable, and there's no way for me to control that. This is why I have an XNAGame class.
I get an error when trying to serialize: "The 'SpriteFont' class is not marked as serializable", or something like that. Well, there is a SpriteFont object in my XNAGame class, and some quick tests showed that this is the source of the issue. Well, I have no control over whether or not the SpriteFont class is Serializable.
Why is the game doing this? Why must all the fields in the XNAGame class be serializable, when all I need is a few methods?
Keep in mind when answering that I'm 13, and may not understand all the terms you're using. If you need any code samples, I'll be glad to provide them for you. Thanks in advance!
EDIT: One solution I have thought of is to store the InventoryAction delegates in a Dictionary, except that this will be a pain and isn't very good programming practice. If this is the only way, I'll accept it, though (Honestly at this point I think this is the best solution).
EDIT 2: Here's the code for the User.Serialize method (I know what I'm doing in inefficient, and I should use a database, blah, blah, blah. I'm fine with what I'm doing now, so bear with me.):
FileStream fileStream = null;
List<User> users;
BinaryFormatter binaryFormatter = new BinaryFormatter();
try
{
if (File.Exists(FILE_PATH) && !IsFileLocked(FILE_PATH))
{
fileStream = File.Open(FILE_PATH, FileMode.Open);
users = (List<User>)binaryFormatter.Deserialize(fileStream);
}
else
{
fileStream = File.Create(FILE_PATH);
users = new List<User>();
}
for (int i = 0; i < users.Count; i++)
{
if (users[i].ID == this.ID)
{
users.Remove(users[i]);
}
}
foreach (Achievement a in AchievementsCompleted)
{
if (a.CompletionCriteria != null)
{
a.CompletionCriteria = null;
}
if (a.OnCompletion != null)
{
a.OnCompletion = null;
}
}
users.Add(this);
fileStream.Position = 0;
binaryFormatter.Serialize(fileStream, users);
You cannot serialize a SpriteFont by design, actually this is possible (.XNB file) but it hasn't been made public.
Solution:
Strip it off your serialized class.
Alternatives:
If for some reasons you must serialize some font, the first thing that comes to my mind would be to roll-out your own font system such as BMFont but that's a daunting task since you'll have to use it everywhere else where you might already do ...
Generate a pre-defined amount of fonts (i.e. Arial/Times/Courier at size 10/11/12 etc ...) using XNA Content app (can't recall its exact name); then store this user preference as two strings. With a string.Format(...) you should be able to load the right font back quite easily.
Alternative 2 is certainly the easiest and won't take more than a few minutes to roll-out.
EDIT
Basically, instead of saving a delegate I do the following:
inventory items have their own type
each type name is de/serialized accordingly
their logic does not happen in the main game class anymore
you don't have to manually match item type / action method
So while you'll end up with more classes, you have concerns separated and you can keep your main loop clean and relatively generic.
Code:
public static class Demo
{
public static void DemoCode()
{
// create new profile
var profile = new UserProfile
{
Name = "Bill",
Gold = 1000000,
Achievements = new List<Achievement>(new[]
{
Achievement.Warrior
}),
Inventory = new Inventory(new[]
{
new FireSpell()
})
};
// save it
using (var stream = File.Create("profile.bin"))
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, profile);
}
// load it
using (var stream = File.OpenRead("profile.bin"))
{
var formatter = new BinaryFormatter();
var deserialize = formatter.Deserialize(stream);
var userProfile = (UserProfile) deserialize;
// set everything on fire :)
var fireSpell = userProfile.Inventory.Items.OfType<FireSpell>().FirstOrDefault();
if (fireSpell != null) fireSpell.Execute("whatever");
}
}
}
[Serializable]
public sealed class UserProfile
{
public string Name { get; set; }
public int Gold { get; set; }
public List<Achievement> Achievements { get; set; }
public Inventory Inventory { get; set; }
}
public enum Achievement
{
Warrior
}
[Serializable]
public sealed class Inventory : ISerializable
{
public Inventory() // for serialization
{
}
public Inventory(SerializationInfo info, StreamingContext context) // for serialization
{
var value = (string) info.GetValue("Items", typeof(string));
var strings = value.Split(';');
var items = strings.Select(s =>
{
var type = Type.GetType(s);
if (type == null) throw new ArgumentNullException(nameof(type));
var instance = Activator.CreateInstance(type);
var item = instance as InventoryItem;
return item;
}).ToArray();
Items = new List<InventoryItem>(items);
}
public Inventory(IEnumerable<InventoryItem> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
Items = new List<InventoryItem>(items);
}
public List<InventoryItem> Items { get; }
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
var strings = Items.Select(s => s.GetType().AssemblyQualifiedName).ToArray();
var value = string.Join(";", strings);
info.AddValue("Items", value);
}
#endregion
}
public abstract class InventoryItem
{
public abstract void Execute(params object[] objects);
}
public abstract class Spell : InventoryItem
{
}
public sealed class FireSpell : Spell
{
public override void Execute(params object[] objects)
{
// using 'params object[]' a simple and generic way to pass things if any, i.e.
// var world = objects[0];
// var strength = objects[1];
// now do something with these !
}
}
Okay, so I figured it out.
The best solution was to use a Dictionary in the XNAGame class, which stores two things: an ItemType (an enumeration), and an InventoryAction. Basically, when I use an item, I check it's type and then look up it's method. Thanks to everyone who tried, and I'm sorry if the question was confusing.

Looping to Create and Add New Objects to ArrayList

Edit to save you from reading through this whole post
tldr: an object's fields should not be static unless you want all instances of that object to have the same value for that field
I'm trying to create and populate an ArrayList of Blog objects. I do know the generic way do this:
create ArrayList of Blogs
loop (some condition)
create new Blog
add this Blog to AL
However, when I attempt to do so within the while(datareader.read()) loop, all of the elements in the ArrayList are exactly the same Blog. Specifically, I end up with an ArrayList filled with multiple pointers to the very last Blog object from the database table. Here is my code:
public static ArrayList AllBlogs()
{
SqlDataReader dr = anonPage.ExecuteReader("SELECT * FROM Kristina_Blogs");
ArrayList allBlogs = new ArrayList();
if (dr.HasRows)
{
while (dr.Read())
{
Blog b = new Blog();
//grab a row from Kristina_Blogs and assign those attributes to b
b.setTitle(dr["title"].ToString());
b.setMessage(dr["message"].ToString());
b.setId(dr["id"]);
allBlogs.Add(b);
}
}
dr.Close();
return allBlogs;
}
As I said before, the result of this is an ArrayList filled with pointers to the very last blog from the Kristina_Blogs table. I imagine the ArrayList allBlogs looks like [b, b, b, ... b] and therefore they ALL get updated when I say b.setTitle() etc. But how can this be the case if I am creating a NEW Blog object at the beginning of each iteration?
Here is some extra info that you don't have to read but it might clear up some confusion about the structure of the problem:
Blog object has id, title, and message fields and their respective getter/setters
Kristina_Blogs is a table representing these blogs with columns for id, title, message
The suggestions say to include a tag for my DB engine but I can't find a tag for it: Microsoft SQL Server Management Studio
This code works perfectly when I use an ArrayList of Strings instead of Blogs
Edit: Including the code from Blog class
public class Blog
{
public App myApp;
public static string Title;
public static string Message;
public static int Id;
//constructors
public Blog() { }
public Blog(App App) { this.myApp = App; }
//all getters and setters look like this
public string getTitle() { return Title; }
public void setTitle(string t) { Title = t; }
}
The main problem you have, as I mentioned in comments is your member variables are static, so when you set the value, they change in all instances. you should change your code this way:
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string Message { get; set; }
}
And fill your list this way, don't forget to add using System.Linq;:
var result = new List<Blog>();
var connection = #"your connection string";
var command = "SELECT * FROM Kristina_Blogs";
var adapter = new System.Data.SqlClient.SqlDataAdapter(command, connection);
var dataTable = new DataTable();
//Get data
adapter.Fill(dataTable);
dataTable.Rows.Cast<DataRow>().ToList()
.ForEach(row =>
{
var b = new Blog();
b.Id = row.Field<int>("Id");
b.Title = row.Field<string>("Title");
b.Message = row.Field<string>("Message");
result.Add(b);
});
return result;
Note:
When you create a member static, it is shared between all instances of that calss.
In C# you can use property to get or set values, you don't need to setX or setY, when you get the value of a property, the get code of that property will execute and when you assign a value to a property the set part of it will execute. you can define properties this way:
Property:
private int id;
public int Id
{
get
{
return id;
}
set
{
id = value;
}
}
or more simple:
public int Id { get; set; }
All of the fields in your Blog class are static, meaning they're shared between all object instances. You want them to be instance field (meaning not static) so that each object has its own copy of each of those values.
Remove the static attributes from your class:
public class Blog
{
public App myApp;
public String Title;
public String Message;
public int Id;
//constructors
public Blog() { }
public Blog(App App) { this.myApp = App; }
//all getters and setters look like this
public String getTitle() { return Title; }
public String getMessage() { return Message; }
public void setTitle(String t) { Title = t; }
public void setMessage(String m) { Message = m; }
}
When you use static variables, all instances of an object will contain the same values in those variables. By removing the static keyword, you are allowing different instances of the object to hold different values.
Now, every time you create a blog object, that object's Title and Message etc, will contain its own information.
I would make a quick method to prevent null value from throwing error
public static string GetSafeString(SqlDataReader reader, int index)
{
if (!reader.IsDBNull(index))
return reader.GetString(index);
else
return string.Empty;
}
Replace this code:
while (dr.Read())
{
Blog b = new Blog();
//grab a row from Kristina_Blogs and assign those attributes to b
b.setTitle(dr["title"].ToString());
b.setMessage(dr["message"].ToString());
b.setId(dr["id"]);
allBlogs.Add(b);
}
With This Code:
while (dr.Read())
{
Blog b = new Blog();
//grab a row from Kristina_Blogs and assign those attributes to b
b.setId(dr.GetInt32(0));
b.setTitle(GetSafeString(dr, 1);
b.setMessage(GetSafeString(dr, 2);
allBlogs.Add(b);
}
Where the number is the index of field in the record and assuming "id" is an integer. Also consider moving creation of "Blog" object outside of loop and just change values.

TList and Creating New Class that is a List of Class of my own is updating Tlist

I have a WizardInfo class which as several TLists as properties, this then populates as the user goes through the wizard on the last screen I query the Tlists and make them into Lists and private fields
I then create Lists of DefaultItems from these lists. This is my own class and as name and Id as its property.
He is some code
public class DefaultItem
{
public int ID {get;set;}
public string Name {get;set;}
}
private List<DefaultItem> _defaultList = null;
_defaultList = new List<DefaultItem>();
defaultValue = PopulateDefaultList(_asmgps, defaultList);
private int PopulateDefaultList(
List<ASGMP> asmgps,
ref List<DefaultItem> defaultList)
{
int isdefault = -1;
foreach (ASGMP asgmp in asgmps)
{
if (asgmp.IsChecked)
{
if (asgmp.IsDefault)
{
isdefault = asgmp.ID;
}
DefaultItem defaultItem = new DefaultItem();
defaultItem.ID = asgmp.ID;
defaultItem.Name = GetMPTName(asgmp.ID);
defaultList.Add(defaultItem);
}
}
return isdefault;
}
private string GetMPTName(int ID)
{
try
{
SGMP sgmp = DataRepository.SGMPProvider.GetByASGMPID(ID)
if (serviceGroupMailPresentation != null)
{
MPT mpt DataRepository.MPTProvider.GetByMPTID(SGMP.MPTID);
if (mailPresentationType != null)
{
return mpt.Name;
}
}
return string.Empty;
}
catch (Exception ex)
{
WindowsEventLog.Write(ex);
throw;
}
}
The problem i am having is when i remove a item from the defaultList it affects asgmp.
I have found the answer. When I get the mpt name I get asgmp from the database this is where Codesmith does a strange thing and connects the usage of the List and the DefaultList. By querying the original List instead of going to the database it now works fine.
It is being removed because List<T> is derived from object, and is a Reference type. Reference types are passed by reference, i.e. when you pass your list, you are passing a pointer to its location in memory. So any changed you make on the copied reference, will also be reflected on the original object.
In order to make a copy you can change this like:
defaultValue = PopulateDefaultList(_asmgps, defaultList);
to this:
defaultValue = PopulateDefaultList(_asmgps.ToList(), defaultList);
This will enumerate the collection as IEnumerable<T> and return is as a list. This will effectivlly create a copy.
erm, instead of PopulateDefaultList why not just do,
var defaultList = asgmps
.Where(asgmp => asgmp.IsChecked)
.Select(asgmp => new
{
IsDefault = asgmp.IsDefault,
Item = new DefaultItem
{
ID = asgmp.ID,
Name = GetMPTName(asgmp.ID)
}
}).ToList();
of course, naming a collection defaultList that contains non-defaults seems counter intuitive.
I found out that this is because of ntiers instead of using the database the to get the ID I should of used the in List of T in

Dictionary with two keys?

I am keeping track of values in a console. Two people "duel" against each other and I was using a dictionary to keep the names recorded along with damage done.
var duels = new Dictionary<string, string>();
duels.Add("User1", "50");
duels.Add("User2","34");
I'm trying to store both users in the same dictionary row, so it could be verified as User1 is dueling against User2. This way if another duel started, it would not interfere with User1 or User2.
duels.Add("KeyUser1","KeyUser2","50","34",.../*Other attributes of the duel*/);
I need two keys so I can check where the user's damage will go. The damage will always go to the other key--vice versa.
What can I do to make this work?
Thank you.
public class Duel
{
public string User1 {get; protected set;}
public string User2 {get; protected set;}
public Duel(string user1, string user2)
{
User1 = user1;
User2 = user2;
}
public HashSet<string> GetUserSet()
{
HashSet<string> result = new HashSet<string>();
result.Add(this.User1);
result.Add(this.User2);
return result;
}
//TODO ... more impl
}
Let's make some duels. CreateSetComparer allows the dictionary to use the values of the set for equality testing.
List<Duel> duelSource = GetDuels();
Dictionary<HashSet<string>, Duel> duels =
new Dictionary<HashSet<string>, Duel>(HashSet<string>.CreateSetComparer());
foreach(Duel d in duelSource)
{
duels.Add(d.GetUserSet(), d);
}
And finding a duel:
HashSet<string> key = new HashSet<string>();
key.Add("User1");
key.Add("User2");
Duel myDuel = duels[key];
You could try making a custom data type for the key:
class DualKey<T> : IEquatable<DualKey<T>> where T : IEquatable<T>
{
public T Key0 { get; set; }
public T Key1 { get; set; }
public DualKey(T key0, T key1)
{
Key0 = key0;
Key1 = key1;
}
public override int GetHashCode()
{
return Key0.GetHashCode() ^ Key1.GetHashCode();
}
public bool Equals(DualKey<T> obj)
{
return (this.Key0.Equals(obj.Key0) && this.Key1.Equals(obj.Key1))
|| (this.Key0.Equals(obj.Key1) && this.Key0.Equals(obj.Key0));
}
}
Then use a Dictionary<DualKey<string>, string>;
Something quick.
class UserScores {
public string Key { get; set; }
public int User1Score { get; set; }
public int User2Score { get; set; }
public UserScores(string username1, string username2)
{
Key = username1 + ":" + username2;
}
}
void Main()
{
var userScore = new UserScores("fooUser", "barUser");
var scores = new Dictionary<string, UserScores>();
scores.Add(userScore.Key, userScore);
// Or use a list
var list = new List<UserScores>();
list.Add(userScore);
list.Single (l => l.Key == userScore.Key);
}
Although a proper solution in my opinion would use a better thought out UserScores object that tracks that particular "duel" session.
Since a single person can be involved in at most one duel at a time, you can use a single dictionary to directly "index" both endpoints in all duels, something like this:
class Duel {
public Duel(string user1, string user2) {
Debug.Assert(user1 != user2);
User1 = user1;
User2 = user2;
}
public readonly string User1;
public readonly string User2;
public int User1Score;
public int User2Score;
}
class Program {
static void Main(string[] args) {
var dict = new Dictionary<string, Duel>();
// Add a new duel. A single duel has two keys in the dictionary, one for each "endpoint".
var duel = new Duel("Jon", "Rob");
dict.Add(duel.User1, duel);
dict.Add(duel.User2, duel);
// Find Jon's score, without knowing in advance whether Jon is User1 or User2:
var jons_duel = dict["Jon"];
if (jons_duel.User1 == "Jon") {
// Use jons_duel.User1Score.
}
else {
// Use jons_duel.User2Score.
}
// You can just as easily find Rob's score:
var robs_duel = dict["Rob"];
if (robs_duel.User1 == "Rob") {
// Use robs_duel.User1Score.
}
else {
// Use robs_duel.User2Score.
}
// You are unsure whether Nick is currently duelling:
if (dict.ContainsKey("Nick")) {
// Yup!
}
else {
// Nope.
}
// If Jon tries to engage in another duel while still duelling Rob:
var duel2 = new Duel("Jon", "Nick");
dict.Add(duel2.User1, duel); // Exception! Jon cannot be engaged in more than 1 duel at a time.
dict.Add(duel2.User2, duel); // NOTE: If exception happens here instead of above, don't forget remove User1 from the dictionary.
// Removing the duel requires removing both endpoints from the dictionary:
dict.Remove(jons_duel.User1);
dict.Remove(jons_duel.User2);
// Etc...
}
}
This is just a basic idea, you might consider wrapping this functionality in your own class...

Categories

Resources