Right now I'm creating a Quest Requirement Checker in order to find if a player is able to accomplish a mission. The type of mission that I'm working with right now is the "Have An Item In Inventory" which, as you can see, will get done if the player has one or more specified items inside his/her inventory.
Now, what is my exact problem? Well, the... items.
First of all. Item is a class with the next structure:
public int ID { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public Item(int id, string name, double price)
{
ID = id;
Name = name;
Price = price;
}
And there is a class named Tool which extends the Item class:
public string Material { get; set; }
public string Classification { get; set; }
public Tool
(
int id,
string name,
double price,
string material,
string classification
) : base(id, name, price)
{
Material = material;
Classification = classification;
}
Now, this is how I create every Tool:
Items.Tool tool = new Items.Tool(1, "Shovel", 100, "Wood", "Poor");
My player object has and List of items like this one:
public List<Items.Item> InventoryItems { get; set; }
And it works as its inventory. Also, to add a new item to the list, I use this function:
player.AddItem(tool, 1);
public void AddItem(Items.Item item, int quantity)
{
for(int i = 0; i < quantity; i++)
{
InventoryItems.Add(item);
}
}
On the other hand, my current quest type "Has Items In Inventory" has a property that is, at the same time, a list of items:
public List<Items.Item> RequiredItems { get; set; }
And this is how I add items to this list:
quest.AddRequiredItem(tool, 1);
public void AddRequiredItem(Items.Item item, int quantity)
{
for(int i = 0; i < quantity; i++)
{
RequiredItems.Add(item);
}
}
In order to fulfill this quest, the player must have the same amount (or more) of items that the RequiredItems list have. So, if this quest ask the player to look around for 3 Poor Wooden Shovels, it should have at least 3 Poor Wooden Shovels on its InventoryItems list.
My quest, which is a class named HaveItemsInInventory implements the next function in order to evaluate that condition:
override public bool Accomplish()
{
bool questAccomplished = true;
foreach (var group in RequiredItems.GroupBy(x => x))
{
if (Application._player.InventoryItems.Count
(
x =>
(
x.Name == group.Key.Name &&
x.Material == group.Key.Material &&
x.Classification == group.Key.Classification
)
) < group.Count())
{
questAccomplished = false;
break;
}
}
return questAccomplished;
}
And this where all my problems appear. This two lines or code are wrong:
x.Material == group.Key.Material &&
x.Classification == group.Key.Classification
Because there's no such thing as a Material or Classification in a Item.
What i want to do is to implement different types of evaluations.
If an quest ask for a Glass of Water, I should look for properties living inside my Beberage class.
If the quest ask for just a Sword. I should look for a item with the Name "Sword" in its inventory.
If the quest ask for a Diamond Legendary Sword, well... You get my point.
Is there a way to look for these extended classes properties within my system? I can't find a way to do so.
PD: Sorry for my bad english, not a native speaker.
EDIT: I've edited my answer to address the idea of a generic task method.
If you want the task to be generic across multiple different types, you probably want to implement an IsSame or IsEquivalent method on Items.Item, and then inherit that method. You could even override the Object.Equals method (and that might be a more appropriate way).
class Item
{
public virtual bool IsSame(Item comp){ return comp.Name == Name; }
}
class Tool: Item
{
public override bool IsSame(Item comp)
{
return base.IsSame(comp) && (comp is Tool) && ((Tool)comp).Material == Material && ((Tool)comp).Classification == Classification;
}
}
Then in your accomplish iteration:
override public bool Accomplish()
{
bool questAccomplished = true;
foreach (var group in RequiredItems.GroupBy(x => x))
{
if (Application._player.InventoryItems.Count
(
x =>
(
x.IsSame(group.Key)
)
) < group.Count())
{
questAccomplished = false;
break;
}
}
return questAccomplished;
}
Related
This is my object
public class Totals {
public int Total1 { get; set; }
public int Total2 { get; set; }
public int Total3 { get; set; }
public int Total4 { get; set; }
}
Incrementing the values of Total1 and Total2 using calculateTotals method
private Totals calculateTotals(Totals t) {
if (//condition) {
t.Total1 += 1;
} else {
t.Total2 += 1;
}
return t;
}
**Incrementing value of Total3 and Total4 of the same object with same conditions at a different location using different method calculateOtherTotals, at this point I only need to update Total3 and Total4 **
private Totals calculateOtherTotals(Totals t) {
if (//condition) {
t.Total3 += 1;
} else {
t.Total4 += 1;
}
return t;
}
I am new to c# , I need to increment the values Total1,Total2 and Total3,Total4 separately and the code which I have is working fine
Is there a way to improve my code?, how can I avoid creating two different methods which pretty much does the same logic on different properties? is there a way to create only 1 method to achieve my functionality?
You could do it this way, but essentially the amount of code doesn't change.
This adds a judgment:
Totals calculateTotals(Totals t, bool Flag)
{
//function1:
if (Flag)
{
if (true)
{ //(condition) {
t.Total1++;
}
else
{
t.Total2++;
}
}
//function2:
else
{
if (true)
{ //(condition) {
t.Total3++;
}
else
{
t.Total4++;
}
}
return t;
}
Call it like this:
Totals totals = new Totals();
totals.Total1=0;
totals.Total2=0;
totals.Total3=0;
totals.Total4=0;
calculateTotals(totals,true);//function1:
calculateTotals(totals,false);//function2:
Reflection is one way, though its slow and not a Domain Specific Language:
Type totalsType = typeof(Totals);
var totalToIncrement = condition;
PropertyInfo prop = totalsType.GetProperty("Total" + totalToIncrement);
prop.SetValue(null, 76);
Or perhaps you want to abstract the properties you're incrementing:
private Totals calculateTotals(Totals t)
{
bool condition = true;
AbstractAdds(ref t.Total1, ref t.Total2, condition);
return t;
}
private void AbstractAdds(ref int a, ref int b, bool condition = false)
{
if (condition)
{
a++;
}
else
{
b++;
}
}
}
public class Totals
{
public int Total1;//{ get; set; }
public int Total2;//{ get; set; }
public int Total3;//{ get; set; }
public int Total4;//{ get; set; }
}
I'd personally have a List<int> or int[3] and make the condition calculate the index 0-3:
var index = calcCondition;
Totals[index]++;
This way its extensible for more totals and you get inbuilt functions like LINQ, eg Totals.Sum().
Is there a way to improve my code?, how can I avoid creating two different methods which pretty much does the same logic on different properties? is there a way to create only 1 method to achieve my functionality?
Then it depends on how you want your method (function) to be. (E.g., how you define what your function will do and how your class and properties are characteristic—which, currently, many who want to help you still wonder about.)
Let me give another clear example.
Assume that you answer your additional requirement are:
My object has only 4 properties of "Total"
I want these new function to increment value only 1 when call, no need to add more than 1
This function is called from another class to modify my object value
I want my cool function name calculateOtherTotals being private, because of some unexplained reason such as “I don't like others knowing it exists”.
Then
public OtherClass{
Public Totals ExposeThePrivateCalculateOtherTotals(Totals t, bool IncrementT1 , bool IncrementT2 , bool IncrementT3, bool IncrementT4)
{
calculateOtherTotals(t, IncrementT1 , IncrementT2 , IncrementT3, IncrementT4);
}
Private Totals calculateOtherTotals(Totals t, bool IncrementT1 , bool IncrementT2 , bool IncrementT3, bool IncrementT4) {
if( IncrementT1 ) t.Total1 += 1; //choose your style
if( IncrementT2==true ) ++t.Total2;//choose your style
if( IncrementT3!=false ) t.Total3++; //choose your style
t.Total4 += IncrementT4==true?1:0;//choose your style
return t;
}
}
//In main (how to use)
Totals t= new Totals();
OtherClass doMyFunc = new OtherClass();
t = doMyFunc.ExposeThePrivateCalculateOtherTotals(t, true, false,false,false); // result of operation => t.total1 += 1;
t = doMyFunc.ExposeThePrivateCalculateOtherTotals(t, false, true,false,false); // result of operation => t.total2 += 1;
i want do some action if value of property in object is equal something.
My object:
//abstract class Tile.cs
namespace DungeonGame.Tiles
{
public abstract class Tile
{
public abstract string Type { get; set; }
}
}
//Item.cs that iherits from Tile.cs
namespace DungeonGame
{
public class Item : Tile
{
public override string Type { get; set; }
public string Name { get; set; }
public Item(string name)
{
this.Type = "item";
this.Name = name;
}
}
}
And i have several objects in public Object[,] BoardTiles { get; private set; }, I had to store different types of tiles in same place so i had to use Object type array.
What i'm trying to do is replace array index with different type of object, depending of the value of property (In this case I set value depending of object type not his value of property):
public void movefillRight(int playerPositionRowIndex, int playerPositionColumnIndex)
{
for (int r = 0; r < Rows; r++)
{
for (int c = 0; c < Cols; c++)
{
if (BoardTiles[playerPositionRowIndex, playerPositionColumnIndex ].GetType().ToString() == ("DungeonGame.Item"))
{
/* placePlayerOnTheBoard(playerPositionRowIndex, playerPositionColumnIndex, PlayerTile);
*/ BoardTiles[playerPositionRowIndex, playerPositionColumnIndex] = Item1;
BoardTiles[playerPositionRowIndex, playerPositionColumnIndex - 1] = Floor;
}
else if (BoardTiles[playerPositionRowIndex, playerPositionColumnIndex-1].GetType().ToString() == ("DungeonGame.Item"))
{
BoardTiles[playerPositionRowIndex, playerPositionColumnIndex-1] = Item1;
placePlayerOnTheBoard(playerPositionRowIndex, playerPositionColumnIndex, PlayerTile);
}
else
{
placePlayerOnTheBoard(playerPositionRowIndex, playerPositionColumnIndex, PlayerTile);
BoardTiles[playerPositionRowIndex, playerPositionColumnIndex - 1] = Floor;
}
}
}
Console.WriteLine(BoardTiles[playerPositionRowIndex, playerPositionColumnIndex].GetType().ToString());//log
}
The functionality of the function is to leave the same object on the same index when the player walks over it, so the problem is that code is ok when I have one Item, for example only sword, but when there are more items i can replace index always with same object but not with object witch different properties, when the player walks over a good sword and bad sword (same object, but another properties) he leave behind just sword... I have idea how to solve this problem but i need to refer to property value which is at specific index of my object array.
After you check that the item in the array is of a specific type you can then cast it.
if (BoardTiles[playerPositionRowIndex, playerPositionColumnIndex ].GetType().ToString() == ("DungeonGame.Item"))
{
var item = (DungeonGame.Item)BoardTiles[playerPositionRowIndex, playerPositionColumnIndex];
Console.Writeline(item.Name);
}
I am learning C# and made a simple "Player" class. But I struggle having multiple overload.
Here's my best solution but I feel like it could be done simpler/better.
class Player : Entity
{
public Player() {
Name = "Player";
XP = 0;
LVL = 1;
XPToLvlUp = 10;
XpRank = 10;
}
public Player(string name) : this() {
Name = name;
}
public Player(string name, int _Hp, int _Mp) : this(name) {
HP = _Hp;
MP = _Mp;
}
public Player(string name, int _Hp, int _Mp, int _Xp, int _Lvl) : this(name, _Hp, _Mp) {
XP = _Xp;
LVL = _Lvl;
}
public Player(string name, int _Hp, int _Mp, int _Xp, int _Lvl, int XpByRank) : this(name, _Hp, _Mp, _Xp, _Lvl) {
XpRank = XpByRank;
}
//deleted code for better reading
private int XPToLvlUp;
private int XpRank;
public int XP;
public int LVL;
public string Name;
}
Is it good and if not please tell me why.
Thanks for your responses!
I think it's fine as is. One question to ask yourself: Are each of those methods actually likely to be called?
One option is to just let the programmer set those values after they've instantiated the class:
var myPlayer = new Player();
myPlayer.XP = 5;
However, there are situations where you really want all the info up front, so that may not be suitable.
Another option could be an options class that is passed to the ctor:
public class PlayerSettings
{
public Name = "Player";
public XP = 0;
public LVL = 1;
public XPToLvlUp = 10;
public XpRank = 10;
}
Then your ctors looks like this:
public Player() : this(new PlayerSettings())
{
}
public Player(PlayerSettings settings)
{
//Fill in appropriate variables here
}
That option would be called in this way:
var playerSettings = new PlayerSettings() { XP = 5 };
var myPlayer = new Player(playerSettings());
In the end, I'm not sure one is "better" than the other, it largely depends on your needs.
Your class is almost good and acceptable.
Short story: use Properties.
Long story:
First of all make or follow the naming rules, it will make your code more friendly to read. It's up to you, just a suggestion. For complex names consisting of multiple words you may use CamelCasedNames. And avoid shorten names for all types of data where it maybe useful. For example you may expand Lvl to Level but Xp to Experience will look as something odd. It's up to you too.
string name; // local Variable, first character lower cased
private string _name; // private Field, first character is lower cased with leading "_"
public string Name { get; set; } // public Property, first character is upper cased
I'll show you alternatives to overriden constructors and will follow the naming rules.
1) Default values for constructor (with a part of your class to keep it simple)
class Player
{
public Player(string name = "Player", int xp = 0, int level = 1)
{
Name = name;
Xp = xp;
Level = level;
}
// Properties instead of Fields
public int Xp { get; private set; } // restrict modification of the property outside of a class but reading is available
public int Level { get; private set; }
public string Name { get; set; }
}
2) Properties without constructor with default values
First Property purpose is restrict access to data to keep internal object data consistent. Even you make mistakes in the code. Good way to avoid some bugs.
Second property purpose is executing code while you're getting or setting one. For example, making properties dependent on each other to store less and only unique data.
class Player
{
public int Xp { get; private set; } = 0;
public int Level { get; private set; } = 1;
public string Name { get; set; } = "Player";
}
Usage
Player player = new Player() { Name = "KillerPWNZ", Level = 100, Xp = 999999 };
Bonus: Another Property feature
You can execute any code in get or set clause.
Let's assume that each next player's level require doubled amount of xp from previous but 2nd level requre 100 XP. And you decided to invoice to the 1st leveled player 1000 XP. Obviously you'll need to bump the Level few times. Assuming that Xp contains relative to Level value.
The invoice
player.Xp += 1000;
The Property with code
private int _xp = 0;
public int Level { get; private set; } = 1;
public int Xp
{
get => _xp; // same as: get { return _xp; }
set
{
_xp = value; // here value is keyword containing data you want to set
while (_xp >= GetXpPerLevel(Level))
{
_xp -= GetXpPerLevel(Level);
Level++;
}
while (_xp < 0 && Level > 1)
{
_xp += GetXpPerLevel(Level - 1);
Level--;
}
}
}
// helper method
private int GetXpPerLevel(int level)
{
if (level < 1) return 0;
// int result = 100;
// for (int i = 1; i < level; i++) result *= 2;
// return result;
// or the same with some binary shift magic :)
return 100 << (level - 1);
}
I'm currently doing some unit testing of a copy function and I need to compare the elements of the objects between the old list, and the newly copied list.
It works fine, but I was wondering if I can do it in a way that doesn't involve a for loop.
Here is my object:
new NaturePointObject
{
SId = 1,
Name = "Test",
Category = NaturePointCategory.Category1,
CreatorType = CreatorTypeEnum.1,
NaturR = NaturR.Bn,
Description = "Test",
Kumulation = Kumulation.EnEjendom,
Id = 1
}
My old list contains "NaturePointObject" and is called naturPointList, and it will be copied to a list called newNaturePointList.
Here is how I Assert to know if it copied succesfully:
Assert.AreEqual(naturPointList.Count,newNaturePointList.Count);
for (var i = 0; i < newNatureList.Count; i++)
{
Assert.AreEqual(naturPointList[i].Category, newNaturePointList[i].Category);
Assert.AreEqual(naturPointList[i].Description, newNaturePointList[i].Description);
Assert.AreEqual(naturPointList[i].Kumulation, newNaturePointList[i].Kumulation);
Assert.AreEqual(naturPointList[i].Name, newNaturePointList[i].Name);
Assert.AreEqual(naturPointList[i].CreatorType, newNaturePointList[i].CreatorType);
Assert.AreEqual(naturPointList[i].NaturR, newNaturePointList[i].NaturR);
Assert.AreNotEqual(naturPointList[i].SId, newNaturePointList[i].SId);
}
As you can see not all elements of the object must be equal. And I don't care about the "Id" of the object.
Is there a shorter way to do this, than run a for loop?
Probably you want to use CollectionAssert:
CollectionAssert.AreEqual(naturPointList, newNaturePointList, NaturePointObject.CategoryCreatorTypeComparer);
The only thing you need to take in mind is that you need to implement IComparer, to use in the Assert method:
public class NaturePointObject
{
private static readonly Comparer<NaturePointObject> CategoryCreatorTypeComparerInstance = new CategoryCreatorTypeRelationalComparer();
private sealed class CategoryCreatorTypeRelationalComparer : Comparer<NaturePointObject>
{
public override int Compare(NaturePointObject x, NaturePointObject y)
{
// compare fields which makes sense
if (ReferenceEquals(x, y)) return 0;
if (ReferenceEquals(null, y)) return 1;
if (ReferenceEquals(null, x)) return -1;
var categoryComparison = string.Compare(x.Category, y.Category, StringComparison.Ordinal);
if (categoryComparison != 0) return categoryComparison;
return string.Compare(x.CreatorType, y.CreatorType, StringComparison.Ordinal);
}
}
public static Comparer<NaturePointObject> CategoryCreatorTypeComparer
{
get
{
return CategoryCreatorTypeComparerInstance;
}
}
public int SId { get; set; }
public string Category { get; set; }
//other properties
public string CreatorType { get; set; }
}
You can try
Assert.IsTrue(naturPointList.SequenceEqual(newNaturePointList));
If you want to ignore the Id, you can create other classes (without Ids).
Later edit: you could overwrite the Equals method and ignore the Id.
How does one retrieve the list in a model?
This is what I'm trying:
private void cbxPlayers_SelectedValueChanged(object sender, EventArgs e)
{
List<Record> records = new List<Record>();
string selectedPlayer = cbxPlayers.SelectedItem.ToString();
using (ProgressRecordContext context = new ProgressRecordContext())
{
records = (from Player in context.Players
where Player.Name == selectedPlayer
select Player.Records).ToList<Record>();
}
}
That doesn't work however, what am I missing?
These are the models in case they're needed:
public class Player
{
[Key][DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AccountNumberId { get; set; }
public string Name { get; set; }
public virtual List<Record> Records { get; set; }
}
public class Record
{
public int RecordId { get; set; }
public int AccountNumberId { get; set; }
public double Level { get; set; }
public int Economy { get; set; }
public int Fleet { get; set; }
public int Technology { get; set; }
public int Experience { get; set; }
public DateTime TimeStamp { get; set; }
public virtual Player Player { get; set; }
}
EDIT: Here's the error messages:
Error 1 'System.Linq.IQueryable>' does not contain a definition for 'ToList' and the best extension method overload 'System.Linq.ParallelEnumerable.ToList(System.Linq.ParallelQuery)' has some invalid arguments
Error 2 Instance argument: cannot convert from 'System.Linq.IQueryable>' to 'System.Linq.ParallelQuery'
EDIT:
I see that I probably wasn't very clear with what I was trying to do. I eventually worked out a way to do what I wanted and here it is:
private void cbxPlayers_SelectedValueChanged(object sender, EventArgs e)
{
lstvRecords.Items.Clear();
if(cbxPlayers.SelectedIndex == -1)
{
return;
}
string selectedPlayer = cbxPlayers.SelectedItem.ToString();
using (ProgressRecordContext context = new ProgressRecordContext())
{
var records = from Player in context.Players
from Record in context.Records
where Player.Name == selectedPlayer &&
Player.AccountNumberId == Record.AccountNumberId
select new
{
Level = Record.Level,
Economy = Record.Economy,
Fleet = Record.Fleet,
Technology = Record.Technology,
Experience = Record.Experience,
TimeStamp = Record.TimeStamp
};
foreach (var element in records)
{
string[] elements = {element.Level.ToString(),
element.Economy.ToString(),
element.Fleet.ToString(),
element.Technology.ToString(),
element.Experience.ToString(),
element.TimeStamp.ToString()
};
ListViewItem lvi = new ListViewItem(elements);
lstvRecords.Items.Add(lvi);
}
}
}
Is there a better way to write that query or is the way that I've done it correct?
No idea why you're getting ParallelQuery - unless you've got some wacky usings in your source file.
In any case, you appear to have an enumerable of enumerables - try SelectMany (note you need using System.Linq; for this to work as an extension method, too):
records = (from Player in context.Players
where Player.Name == selectedPlayer
select Player.Records).SelectMany(r => r).ToList();
Also - unless you intend to add/remove to/from that list, you should just use an array, i.e. use .ToArray().
As pointed out by #Tim S (+1) - if you expect only a single player here then you should be using SingleOrDefault() to get the single player - whose Records you then turn into an array/list.
Your problem is that Player.Records is a List<Record>, and you are getting an IEnumerable<List<Record>> (i.e. 0 to many player's records) from your query, so .ToList() gets you a List<List<Record>>. If there are multiple players with the same name and you want it to collect the records from all of them, use Andras Zoltan's solution. If you want to ensure (via throwing an exception if there are 0 or more than 1 results) that exactly one player has the given name, and only his records are returned, use one of these solutions: (key change being .Single() - also take a look at SingleOrDefault to see if it fits your needs better)
//I prefer this solution for its conciseness and clarity.
records = context.Players.Single(Player => Player.Name == selectedPlayer).Records;
//if you'd like to use the LINQ query format, I'd recommend this.
records = (from Player in context.Players
where Player.Name == selectedPlayer
select Player).Single().Records;
//this is more similar to your original query.
records = (from Player in context.Players
where Player.Name == selectedPlayer
select Player.Records).Single().ToList();
If you change
List<Record> records = new List<Record>();
to
var records = new List<List<Record>>();
Does it work? If a Player has a list of Records, it looks like your query is returning a List of a List of Records.
Edit:
There, fixed the return list... either way this is probably not the solution you're looking for, just highlighting what the problem is.
You could try refactoring your query
records = context.Players.First(player => player.Name == selectedPlayer).Records.ToList();