How to use except and any with strings instead of classes - c#

Like said above how would I, I have some code using linq and except and any(). Currently I use items(a class), to be identified and such, but this is causing problems, so I would like them to be identified by strings instead, but im not sure how. The two functions with the code are: ContainsAllItems, and the other one is in the craft function.
Thanks!
EDIT; To make more clear just in case, right now it checks the items in two lists, instead of that what im asking is how to make it that it checks the itemname of the items in the lists instead, but still make it have the same effect, so it would destroy items and check items and make sure when destroying that not more than needed are destroyed.
If you would not want to look through the code here are the two lines:
public static bool ContainsAllItems(List<Item> a, List<Item> b)
{
return !b.Except(a).Any();
}
and
List<Item> list = new List<Item>();
list = InventoryItems.Except(Recipe.InputItem).ToList();
InventoryItems = list;
Code:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class PlayerInventory : MonoBehaviour {
public int MaxInventory = 26;
public List<Item> InventoryItems = new List<Item>();
void OnGUI(){
if (GUILayout.Button ("+5 Wood")) {
AddItem(ItemConstructors.Wood());
AddItem(ItemConstructors.Wood());
AddItem(ItemConstructors.Wood());
AddItem(ItemConstructors.Wood());
AddItem(ItemConstructors.Wood());
}
if (GUILayout.Button ("Craft Wall")) {
Craft(CraftingRecipes.WoodenWall());
}
}
public void AddItem(Item _item){
if (InventoryItems.Count < MaxInventory) {
InventoryItems.Add(_item);
//SaveItems();
}
}
public void RemoveItem(Item _item){
InventoryItems.Remove (_item);
}
public void UseItem(Item _item){
if (_item.ItemType == "Food") {
ConsumeItem(_item);
}
SaveItems();
}
public void ConsumeItem(Item _item){
if (_item.ItemDur > 1) {
_item.ItemDur--;
}
else if(_item.ItemDur <= 1){
RemoveItem(_item);
}
}
public void SaveItems(){
for (int i = 0; i<MaxInventory; i++) {
PlayerPrefs.SetString("Itemn"+i,InventoryItems[i].ItemName);
PlayerPrefs.SetString("Itemt"+i,InventoryItems[i].ItemType);
PlayerPrefs.SetInt("Itemv"+i,InventoryItems[i].ItemValue);
PlayerPrefs.SetInt("Itemd"+i,InventoryItems[i].ItemDur);
}
//LoadItems ();
}
public void LoadItems(){
InventoryItems.Clear();
for (int i = 0; i<MaxInventory; i++) {
Item it = new Item();
it.ItemName = PlayerPrefs.GetString("Itemn"+i);
it.ItemType = PlayerPrefs.GetString("Itemt"+i);
it.ItemValue = PlayerPrefs.GetInt("Itemv"+i);
it.ItemDur = PlayerPrefs.GetInt("Itemd"+i);
if(it.ItemName.Length <= 2 && it.ItemType.Length <= 2){
}
else{
InventoryItems.Add(it);
}
}
}
public void Craft(CraftingItem Recipe){
bool canCraft = ContainsAllItems (InventoryItems,Recipe.InputItem);
if(canCraft){
if(InventoryItems.Count < MaxInventory){
List<Item> list = new List<Item>();
list = InventoryItems.Except(Recipe.InputItem).ToList();
InventoryItems = list;
AddItem(Recipe.OutputItem);
}
else{
Debug.Log("Not enough space!");
}
}
else{
Debug.Log("Invalid Items.");
}
}
public static bool ContainsAllItems(List<Item> a, List<Item> b)
{
return !b.Except(a).Any();
}
}

Related

Need to change values in classes without changing its reference

I am currently making an application which tracks information on players and monsters for a tabletop game.
Currently I've got classes for "Monsters". This class contains information such as its name, maxHP, speed, attacks etc. I've managed to make a Database which contains the default values for each type of monster. What I currently need to do is make it possible to change things such as name (Monster > Monster 1, Monster 2 etc), change its HP, and some other things.
I understand that I need to make a copy of such, but I am uncertain on how to do this.
What I currently tried is the following:
public class DatabaseService
{
public List<Player> Players { get; set; }
public List<Monster> AllMonsters { get; set; }
public List<Monster> ActiveMonsters = new List<Monster>();
public bool RollForHP = false;
//Main Database Service
public DatabaseService()
{
Players = GetPlayers();
AllMonsters = GetAllMonsters();
}
public void DoStuff()
{
AddMonsterByName("Goblin", 2);
AddMonsterByName("Adult White Dragon", 1);
AddMonsterByName("Duergar", 4);
foreach (Monster monster in ActiveMonsters) { Console.WriteLine(monster.Name); }
}
//Converts the .json list with all players to Classes, which are then stored in the list "Players" if the "IsInParty" is true
private List<Player> GetPlayers()
{
var path = #"C:\Users\MyName\source\repos\DndAdvancedInitiativeTracker\Logic\Database\Players.json";
var json = File.ReadAllText(path);
var players = JsonConvert.DeserializeObject<List<Player>>(json);
List<Player> inPartyPlayers = new List<Player>();
foreach (var player in players)
{
if (player.IsInParty == true) { inPartyPlayers.Add(player); }
}
return inPartyPlayers;
}
//Converts the .json list with all monsters to Classes, which are then stored in the list "AllMonsters"
private List<Monster> GetAllMonsters()
{
var path = #"C:\Users\MyName\source\repos\DndAdvancedInitiativeTracker\Logic\Database\Monsters.json";
var json = File.ReadAllText(path);
var monsters = JsonConvert.DeserializeObject<List<Monster>>(json);
return monsters;
}
//Adds a given monster to the "ActiveMonsters" list
public void AddMonsterByName(string monsterName, int amountOfMonsters)
{
for (int i = 0; i < amountOfMonsters; i++)
{
List<Monster> DatabaseCopy = AllMonsters.Clone();
DatabaseCopy = AllMonsters;
Monster monster = DatabaseCopy.Find(x => x.Name == monsterName);
Console.WriteLine(monster.Name);
var number = CheckIfNameExistsInList(monsterName);
monster.Name = monsterName + " " + (number + i).ToString();
ActiveMonsters.Add(monster);
}
}
private int CheckIfNameExistsInList(string monsterName)
{
var counter = 1;
foreach (var monster in ActiveMonsters)
{
if (monster.Name.Contains(monsterName))
{
counter += 1;
}
}
return counter;
}
}
In the "DoStuff" Method, I try to add 2 goblins, then a dragon, then a goblin again. The first goblin is named to "Goblin 1" correctly, but the second loop fails, because the AllMonsters' name for goblins is now "Goblin 1" because of the reference type, therefore, the second "Goblin" search in AllMonsters is never found, and returns null.
I'm not sure why you're copying your database (and doing so for every iteration of a for loop which is quite wasteful), but your current check code CheckIfNameExistsInList will always return 1 even if there are no matches in the list.
You can simplify your AddMonstersByName (and use a simple check for previous monster entries) as follows:
public void AddMonstersByName(string name, uint number = 1)
{
var count = AllMonsters.Count(x => x.Name.Contains(name));
for (var i = 1; i <= number; i++)
{
var num = count + i;
AllMonsters.Add(new Monster(){Name= name+num.ToString()});
}
}
This was tested in a simple Console App:
var dataService = new DataService();
dataService.AddMonstersByName("Goblin", 2);
dataService.AddMonstersByName("Dragon", 2);
dataService.AddMonstersByName("Goblin", 2);
foreach (var monster in dataService.AllMonsters)
{
Console.WriteLine($"{monster.Name}");
}
where
public class DataService
{
public List<Monster> AllMonsters = new List<Monster>();
public void AddMonstersByName(string name, uint number = 1)
{
var count = AllMonsters.Count(x => x.Name.Contains(name));
for (var i = 1; i <= number; i++)
{
var num = count + i;
AllMonsters.Add(new Monster(){Name= name+num.ToString()});
}
}
}
public class Monster
{
public string Name { get; set; }
}

if both values are equal to each other then do this c#

First of all, here's my script:
using UnityEngine;
using System.Collections;
using Steamworks;
public class Achievements : MonoBehaviour {
public static int currentScore=0;
public static int score300 = 300;
public static int score1000 = 1000;
public static int score3600 = 3600;
public static int score18000 = 18000;
public static int score72000 = 72000;
public static int score180000 = 180000;
void Start() {
if(SteamManager.Initialized) {
string name = SteamFriends.GetPersonaName();
Steamworks.SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_1_0");
Steamworks.SteamUserStats.StoreStats();
Debug.Log(name);
}
}
void Update()
{
currentScore = PlayerPrefs.GetInt("highscore");
if (currentScore == score300 && SteamManager.Initialized){
Steamworks.SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_5_0");
Steamworks.SteamUserStats.StoreStats();
}
if (currentScore == score1000 && SteamManager.Initialized) {
Steamworks.SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_6_0");
Steamworks.SteamUserStats.StoreStats();
}
if (currentScore == score3600 && SteamManager.Initialized) {
Steamworks.SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_7_0");
Steamworks.SteamUserStats.StoreStats();
}
if (currentScore == score18000 && SteamManager.Initialized) {
Steamworks.SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_8_0");
Steamworks.SteamUserStats.StoreStats();
}
}
}
As you can see, I have public integers that hold variety of numbers. I am also using current steamworks.net, and I'm trying to see if I can match both "highscore" (which is already set up and working properly) with scoreXXX. If that happens, I want script to drop an achievement.
Am I executing if(x=x) function wrong? Can someone please help?
The problem is you arent checking if the score is greater than the score benchmarks, only that its equal.
You could simplify your code a bit by putting these values into a Dictionary<int, string>:
private static Dictionary<int, string> highScoreDictionary = new Dictionary<int, string>()
{
{ 300, "NEW_ACHIEVEMENT_5_0" },
{ 1000, "NEW_ACHIEVEMENT_6_0" },
{ 3600, "NEW_ACHIEVEMENT_7_0" },
{ 18000, "NEW_ACHIEVEMENT_8_0" },
{ 72000, "NEW_ACHIEVEMENT_9_0" },
{ 180000, "NEW_ACHIEVEMENT_10_0" }
};
void Update()
{
currentScore = PlayerPrefs.GetInt("highscore");
if(SteamManager.Initialized)
{
//Order by high score, descending
foreach(var score in highScoreDictionary.OrderByDescending(x => x.Key))
{
//If the score is greater than or equal to the benchmark
//Then add the achievement
if(currentScore >= score.Key)
{
Steamworks.SteamUserStats.SetAchievement(score.Value);
Steamworks.SteamUserStats.StoreStats();
break;
}
}
}
}
I made a fiddle here. Its obviously modified a bit since I dont have access to Unity libraries there, but you can see the logic in action.

Whats the best way to iterate a list if you are destroying its contents as you go

I have a list of objects that I want to remove each element in turn which changes the length of the list as it goes so basically doesn't work. It's hard to explain so I'll just show the code:
public List<GameObject> ActiveChunks = new List<GameObject>();
public static void DeActivate(GameObject Obj)
{
//Do lots of other stuff based on the Obj
....
....
instance.ActiveChunks.Remove(Obj);
}
public static void DeActivateAllChunks()
{
for (int c = 0; c < instance.ActiveChunks.Count; c++)
{
DeActivate(instance.ActiveChunks[c], false);
}
}
As you can see the for loop goes through my active chunks list calling a deactivate function which does some stuff on the object and then removes the
item from the list. My question is, is there a better way to iterate through a List that is being modified?
Option 1) You can store the list into a new variable and loop the original list and then remove the items in the temp variable . At last use the temp variable.
public List<GameObject> ActiveChunks = new List<GameObject>();
var tempActiveChunks = ActiveChunks; //temp variable
public static void DeActivate(GameObject Obj)
{
//Do lots of other stuff based on the Obj
....
....
tempActiveChunks.Remove(Obj);
}
public static void DeActivateAllChunks()
{
for (int c = 0; c < instance.ActiveChunks.Count; c++)
{
DeActivate(instance.ActiveChunks[c], false);
}
instance.ActiveChunks = tempActiveChunks;
}
Option 2) Also you can iterate from backwards like below
public static void DeActivateAllChunks()
{
for (int c = instance.ActiveChunks.Count - 1; c >=0 ; c--)
{
DeActivate(instance.ActiveChunks[c], false);
}
}
Option 3 Thanks to Sac, You can copy the List into a temp variable and then iterate over the temp variable and remove the item from the actual list.
public List<GameObject> ActiveChunks = new List<GameObject>();
var tempActiveChunks = ActiveChunks; //temp variable
public static void DeActivate(GameObject Obj)
{
//Do lots of other stuff based on the Obj
....
....
instance.ActiveChunks.Remove(Obj);
}
public static void DeActivateAllChunks()
{
for (int c = 0; c < tempActiveChunks.Count; c++)
{
DeActivate(instance.ActiveChunks[c], false);
}
}
You can walk through them backwards:
public static void DeActivateAllChunks()
{
for (int c = instance.ActiveChunks.Count - 1; c >=0 ; c--)
{
DeActivate(instance.ActiveChunks[c], false);
}
}
Change your for loop to count backwards rather than forwards. That way the deletion / destruction doesn't effect the next row you're going to get.
for (int c = instance.ActiveChunks.Count - 1; c >= 0; c--)
{
DeActivate(instance.ActiveChunks[c], false);
}
You don't need for loop and additional index variable. The following approach is better:
while(ActiveChunks.Count > 0) {
var theLastIndex = ActiveChunks.Count - 1;
var temp = ActiveChunks[theLastIndex];
ActiveChunks.RemoveAt(theLastIndex);
// deactivate object removed from the list
DeActivate(temp);
}
I think this is the flexible way, try out this
public static List<GameObject> ActiveChunks = new List<GameObject>();
public static bool DeActivate(GameObject Obj)
{
if(true)//your condition
{
return true;//do not remove the object
}
else
{
return false;//remove the object
}
}
public static void DeActivateAllChunks()
{
ActiveChunks = ActiveChunks.Where(c => DeActivate(c)).ToList();
}

Get a count of items with a certain condition within a StackPanel's Children

I have an custom UserControl I created
public class MyObject : UserControl
{
public MyObject()
{
}
public bool IsFinished { get; set; }
}
I added lets say 10(will be dynamic) MyObject's to a StackPanel and every other item i set IsFinished to true.
private void DoSomething()
{
StackPanel panel = new StackPanel();
for (int i = 0; i <= 10; i++)
{
int rem = 0;
Math.DivRem(i, 2, out rem);
MyObject newObj = new MyObject();
if (rem == 0)
{
newObj.IsFinished = true;
}
panel.Children.Add(newObj);
}
}
Now I can add the following and get the answer I am looking for (5)
int FinishedItems = 0;
foreach (object o in panel.Children)
{
if (o.GetType() == typeof(MyObject))
{
if (((MyObject)o).IsFinished)
{
FinishedItems++;
}
}
}
2 Questions:
A. Is there a more eloquent way? maybe with Linq I'm still learning how to use that. From what I understand, that is what LINQ technically does.
B. Am I wrong about LINQ?
So you want to count the finished items:
int FinishedItems = panel.Children.OfType<MyObject>()
.Count(mo => mo.IsFinished);
You may try:
int FinishedItems = panel.Children.OfType<MyObject>().Where(mo=>mo.IsFinished).Count();

How can I add a specified number of list elements to an ILIST?

I have a class as follows:
public class ABC {
public IList<TextFillerDetail> TextFillerDetails
{ get { return _textfillerDetails; } }
private List<TextFiller> _textfillerDetails = new List<TextFiller>();
}
I instantiate this class and add some TextDetails to it:
var ans = new ABC();
ans.TextDetails.Add(new TextDetail());
ans.TextDetails.Add(new TextDetail());
ans.TextDetails.Add(new TextDetail());
ans.TextDetails.Add(new TextDetail());
Is there a way that I could do this in one step by adding some code to the class such as a different kind of constructor. For example by passing in a number 5 to request that five elements be added?
var ans = new ABC(5);
You could add it as a constructor argument:
public class ABC()
{
public ABC(int count)
{
for (int i = 0; i < count; i++)
{
TextDetails.Add(new TextDetail());
}
}
// Stuff
}
Sure, you could use a constructor that will initialize the list:
public class ABC
{
public ABC(int count)
{
if (count < 1)
{
throw new ArgumentException("count must be a positive number", "count");
}
_textfillerDetails = Enumerable
.Range(1, count)
.Select(x => new TextDetail())
.ToList();
}
public IList<TextFillerDetail> TextFillerDetails { get { return _textfillerDetails; } }
private List<TextFiller> _textfillerDetails;
}
Sure:
public class ABC {
public IList<TextFillerDetail> TextFillerDetails
{ get { return _textfillerDetails; } }
public ABC(int capacity)
{
_textfillerDetails = new List<TextFiller>(capacity);
}
private List<TextFiller> _textfillerDetails;
}
There are a few ways:
Use an initializer; it saves a little typing:
var ans = new ABC{
new TextDetail(),
new TextDetail(),
new TextDetail(),
new TextDetail(),
new TextDetail(),
}
Better idea: Use Linq to repeat an initialization lambda:
var ans = Enumerable.Repeat(0,5).Select(x=>new TextDetail()).ToList();
You could put in an overloaded constructor that takes the number of items to add as a parameter.
But why do you need to do that? Couldn't you just add TextDetail objects to your list as necessary?
Just for this task , Yes,
private List<TextFiller> _textfillerDetails = new List<TextFiller>();
public ABC(int capacity)
{
for(int index = 0; index < capacity; index ++)
_textfillerDetails.Add(new TextDetail());
}
You can use a for loop or linq:
public class ABC
{
public IList<TextFillerDetail> TextFillerDetails { get; private set }
public ABC() : this(0)
{
}
public ABC(int count)
{
TextFIllerDetails = Enumerable.Range(0,count)
.Select(x => new TextFillerDetail())
.ToList();
}
}
Consider using also,
IEnumerable or ICollection or IQueryable objects.
Ray Akkanson

Categories