Best way to compare variables between instances of the same class? - c#

This is a game based on certain elemental combos, I'm trying to find a way to have two elements compare to each other in order to reduce hard coding. I do have a working solution provided at the end, but I'm trying to learn any ways that would be more simple, straight forward, and readable.
//Wizard is a class
Wizard player1;
Wizard player2;
player1.health = 3;
player2.health = 3;
//Elements is an enum that allows fire/water/air to be selected
player1.elementSelected = Elements.fire;
player2.elementSelected = Elements.water;
Wizard[] bothPlayers = { player1, player2 };
I want to search for the active element in bothPlayers so I can effect the Player's health. I know this doesn't work, but I was wondering if I could do something like:
if (bothPlayers.Contains(Wizard.elementSelected.Elements.fire) && bothPlayers.Contains(Wizard.elementSelected.Elements.water))
Alternatively I was thinking of just setting it to a new array but that wont let me call back to effect player health unless I set them up to a new variable like:
Elements[] bothSelectedElements = { player1.elementSelected, player2.elementSelected };
if (bothSelectedElements.Contains(Elements.fire) && bothSelectedElements.Contains(Elements.water))
{
Wizard playerWithFire; // = player who selected fire. Can't set without hardcoding
Wizard playerWithWater; // = player who selected water. Can't set without hardcoding
playerWithFire.health--;
playerWithWater.waterStrength++;
}
//CURRENT WORKING SOLUTION
//set each Wizard container to null at the start of each check
Wizard fire = null;
Wizard water = null;
Wizard air = null;
//add check to make sure same elements aren't selected. Then assign the players to the containers
foreach (Wizard player in bothPlayers)
{
if (player.elementSelected == Elements.fire)
{
fire = player;
}
if (player.elementSelected == Elements.water)
{
water = player;
}
if (player.elementSelected == Elements.air)
{
air = player;
}
}
//then do the actual check
if (fire != null && water != null)
{
fire.health--;
water.waterStrength++;
}
//repeat with other if statement comparisons

This is a fiddly problem, but thankfully you are working with a language that has all the tools you need to hide the complexity away.
var elementWizards = wizards.GroupBy(w => w.elementSelected).ToDictionary(g => g.Key);
var elements = elementWizards.Keys.ToHashSet(); // gives us access to SetEquals
if (elements.SetEquals(new[] { Elements.Fire, Elements.Water }))
{
foreach (var wizard in elementWizards[Elements.Fire]) wizard.health--;
foreach (var wizard in elementWizards[Elements.Water]) wizard.waterStrength++;
}
else if (elements.SetEquals(new[] { Elements.Earth, Elements.Fire }))
{
// more effects...
}
Note that SetEquals doesn't care about the order of items, so you don't need to worry about handling water/fire instead of fire/water.
Footnote: in the real world I would define some static HashSet<Element> objects and call if(foo.SetEquals(elementWizards.Keys)), but I kept things simple for this answer.

Solution with Linq:
Check if there are water and fire wizard (with Linq)
Foreach to search and add health etc...
if (bothPlayers.Count(wizard => wizard.elementSelected == Elements.fire) > 0 &&
bothPlayers.Count(wizard => wizard.elementSelected == Elements.water) > 0)
{
bothPlayers.ForEach(wizard =>
{
var _ = wizard.elementSelected == Elements.water ? wizard.waterStrength++ :
wizard.elementSelected == Elements.fire ? wizard.health-- : 1;
});
}
you can change the wizards you are searching for and the properties that depends on it

There are multiple ways of finding item in a collection. For example :
// using Array.Find method
Wizard fire = Array.Find(bothPlayers, player => player.elementSelected == Elements.fire);
// or using System.Linq FirstOrDefault extension
Wizard water = bothPlayers.FirstOrDefault(player => player.elementSelected == Elements.water);
if (fire != null && water != null)
{
fire.health--;
water.waterStrength++;
}
For more than two players, a Lookup can be used to separate the players into groups :
var lookup = bothPlayers.ToLookup(player => player.elementSelected);
if (lookup.Contains(Elements.fire) && lookup.Contains(Elements.water))
{
foreach (Wizard fire in lookup[Elements.fire] ) fire.health--;
foreach (Wizard water in lookup[Elements.water]) water.waterStrength++;
}

Related

C# Mapping a list to numbered variables

Yes, I'm well aware Not to do this, but I have no choice. I'd agree that it's an XYZ issue, but since I can't update the service I have to use, it's out of my hands. I need some help to save some time, maybe learn something handy in the process.
I'm looking to map a list of models (items in this example) to what is essentially numbered variables of a service I'm posting to, in the example, that's the fields a part of new 'newUser'.
Additionally, there may not be always be X amount items in the list (On the right in the example), and yet I have a finite amount (say 10) of numbered variables from 'newUser' to map to (On the left in the example). So I'll have to perform a bunch of checks to avoid indexing a null value as well.
Current example:
if (items.Count >= 1 && !string.IsNullOrWhiteSpace(items[0].id))
{
newUser.itemId1 = items[0].id;
newUser.itemName1 = items[0].name;
newUser.itemDate1 = items[0].date;
newUser.itemBlah1 = items[0].blah;
}
else
{
// This isn't necessary, but this effectively what will happen
newUser.itemId1 = string.Empty;
newUser.itemName1 = string.Empty;
newUser.itemDate1 = string.Empty;
newUser.itemBlah1 = string.Empty;
}
if (items.Count >= 2 && !string.IsNullOrWhiteSpace(items[1].id))
{
newUser.itemId2 = items[1].id;
newUser.itemName2 = items[1].name;
newUser.itemDate2 = items[1].date;
newUser.itemBlah2 = items[1].blah;
}
// Removed the else to clean it up, but you get the idea.
// And so on, repeated many more times..
I looked into an example using Dictionary, but I'm unsure of how to map that to the model without just manually mapping all the variables.
PS: To all who come across this question, if you're implementing numbered variables in your API, please don't- it's wildly unnecessary and time consuming.
As an alternative to fiddling with the JSON, you could get down and dirty and use Reflection.
Given the following test data:
const int maxItemsToSend = 3;
class ItemToSend {
public string
itemId1, itemName1,
itemId2, itemName2,
itemId3, itemName3;
}
ItemToSend newUser = new();
record Item(string id, string name);
Item[] items = { new("1", "A"), new("2", "B") };
Using the rules you set forth in the question, we can loop through the projected fields as so:
// If `itemid1`,`itemId2`, etc are fields:
var fields = typeof(ItemToSend).GetFields();
// If they're properties, replace GetFields() with
// .GetProperties(BindingFlags.Instance | BindingFlags.Public);
for(var i = 1; i <= maxItemsToSend; i++){
// bounds check
var item = (items.Count() >= i && !string.IsNullOrWhiteSpace(items[i-1].id))
? items[i-1] : null;
// Use Reflection to find and set the fields
fields.FirstOrDefault(f => f.Name.Equals($"itemId{i}"))
?.SetValue(newUser, item?.id ?? string.Empty);
fields.FirstOrDefault(f => f.Name.Equals($"itemName{i}"))
?.SetValue(newUser, item?.name ?? string.Empty);
}
It's not pretty, but it works. Here's a fiddle.

Sorting Gameobjects and set their SiblingIndex in Hierarchy

i have a friendsystem and want to sort it by the Users Worldrank (via PlayFab).
I want the best at the Top and the worst at the bottom.
I tried it with the following Script
private void sortfriendlist()
{
GameObject[] Friendcolumns = GameObject.FindGameObjectsWithTag("Friendcolumn");
for (int i = 0; i < Friendcolumns.Length; i++)
{
//If ranked
if (Friendcolumns[i].name.Substring(0, 1) != "F")
{
//Set Sibling (Rank)
int rank = int.Parse(Friendcolumns[i].name.Substring(0, 1));
Friendcolumns[i].transform.SetSiblingIndex(rank);
}
//If unranked
else
{
Friendswithoutrank[i] = Friendcolumns[i];
}
}
Debug.Log(Friendswithoutrank.Length + " Friends without Rank");
for(int i = 0; i < Friendswithoutrank.Length; i++)
{
Debug.Log(Friendswithoutrank[i].name + " has no Rank");
Friendcolumns[i].transform.SetAsLastSibling();
}
}
But that doesnt work as you can see here
First Im getting all Friends and store them in an Array. After that Im checking if the Player even has a Rank by checking if there is an "F" at the beginning of the GameObjects Name (Friends without a Rank have no Ranknumber in front of the Gameobject Name so the Name start with an "F"). Than Im getting the Rank of the Friend and set the SiblingIndex to the Rank. So normally it should be sorted now as i think. But that idea doesn´t seem to work.
If you have any idea how to fix it or make it better please let me know!
Sorting can actually be done much easier using Linq (also see this thread):
using System.Linq;
using UnityEngine;
private void sortfirendlist()
{
var Friendcolumns = GameObject.FindGameObjectsWithTag("Friendcolumn");
// Use linq to get the Objects with and without Rank (not starting / starting with "F")
// This works a bit similar to sql code
// -> if the "Where" returns true -> object stays in the list
// using string.StartsWidth which is a
// better choice for what you are doing with Substring(0,1)== ...
var withoutRank = Friendcolumns.Where(obj => obj.name.StartsWith("F"));
var withRank = Friendcolumns.Where(obj => !obj.name.StartsWith("F"));
// Sort the ranked by name (again using Linq)
var rankedSorted = withRank.OrderBy(go => go.name);
// Now we have our ordered arrays -> we just have to apply that to the scene
// set sibling index for the ranked
foreach (var t in rankedSorted)
{
// I simply send them one by one to the bottom
// => when we are finished they are all sorted at the bottom
t.transform.SetAsLastSibling();
}
// Do the same for the unranked to also send them to the bottom
Debug.Log(withoutRank.Length + " Friends without Rank");
foreach (var t in withoutRank)
{
Debug.Log(t.name + " has no rank");
t.transform.SetAsLastSibling();
}
// Now the object we sent to the bottom first
// (first element in withRank) should be on top and everything sorted below it
}

Grouping Arbitrary Number of Polygons by Intersection - C#

I have multiple lists of polygons that each represent an physical object. For example:
List<CurveLoop> A could represent a rectangle with a hole in it. One curve within this list would be the outline of the rectangle, and another curve would be the hole.
I want a method that will return a list of lists, where each list contains all the objects that intersect.
I already have a method that will return whether two of the objects intersect:
bool _CurveLoopsIntersect(List<CurveLoop> curveLoopsA, List<CurveLoop> curveLoopsB) {...}
will return true if any any two curves within the two lists touch.
Below is the code I have so far, but it just gives me a single pass. I think I need multiple passes, so that if object A and B intersect, and B and C intersect, then they would form set { A, B, C }. I need an arbitrary number of passes though, and sometimes the objects won't intersect at all, or be part of different sets, such as {A, B, C} and {D, E} and {F}.
public List<CurveLoop> _MergeCurveLoops(List<List<CurveLoop>> elementCurveLoops, View view)
{
// ...
// Preprocessing
var listOfLists = new List<List<CurveLoop>>();
foreach (var elementCurveLoop in elementCurveLoops)
{
var newList = elementCurveLoops.FindAll(x => _CurveLoopsIntersect(x, elementCurveLoop));
listOfLists.Add(newList);
}
}
private bool _CurveLoopsIntersect(List<CurveLoop> curveLoopsA, List<CurveLoop> curveLoopsB)
{
foreach (var curveLoopA in curveLoopsA)
{
foreach (var curveA in curveLoopA)
{
foreach (var curveLoopB in curveLoopsB)
{
foreach (var curveB in curveLoopB)
{
var result = curveA.Intersect(curveB);
if (result == SetComparisonResult.Overlap ||
result == SetComparisonResult.Subset ||
result == SetComparisonResult.Superset ||
result == SetComparisonResult.Equal)
{
return true;
}
}
}
}
}
return false;
}
This can be implemented using some code like this psuedu
set = a,b,c, ...
While(set not empty) {
Create newSet
Add set.first to new list
Remove set.first from set // this line isnt necessary if a curve doesnt intersect with self
For (i = 0 , i < newset.length , i++)
{
newSet.add(set.FindAll(x => _CurveLoopsIntersect(x, newSet[i]));
set.removeRange(newSet); // this line may have error that the first element doesnt exist in set
}
Add newSet to set of sets
}
Thanks, you put me in the right direction. You were right, using a Set was the right approach. I used a set in combination with a recursive function (similar to your while loop).
The code I wrote is below:
static List<Polygon> _RecursiveMergePolygons(List<Polygon> polygons, View view)
{
HashSet<Polygon> initialSet = new HashSet<Polygon>(polygons);
HashSet<Polygon> finalSet = new HashSet<Polygon>(polygons);
foreach (var polygon in initialSet)
{
// Should always return at least 1 instance
var polys = polygons.FindAll(x => _PolygonsIntersect(x, polygon));
// if it's greater than 1, then merge them and restart the recursion, otherwise continue
if (polys.Count > 1)
{
foreach (var poly in polys)
{
finalSet.Remove(poly);
}
var mergedPolygon = new Polygon(polys, view);
finalSet.Add(mergedPolygon);
break;
}
}
if (finalSet.Count == initialSet.Count)
{
return finalSet.ToList();
}
return _RecursiveMergePolygons(finalSet.ToList(), view);
}

Can a var = 2 things

I've been working with Unity for a while now and been scripting with C# a bit.
I'm working on a script that checks if selected target is a NPC or a Craft(station).
As the base of the script is calling upon many other scripts and variables, I don't want to duplicate all of the scripts in order to make the other kind of NPC which is , in my case, the Craft(station).
Let me explain in detail with examples now:
void UpdateNpcTrading(Player player) {
// only if visible
if (!npcTradingPanel.activeSelf) return;
// npc trading
if (player.target != null && player.target is Craft &&
Vector3.Distance(player.transform.position, player.target.transform.position) <= player.talkRange) {
var npc = (Craft)player.target; // Here is the var I need to edit
// items for sale
for (int i = 0; i < npcTradingContent.childCount; ++i) {
var entry = npcTradingContent.GetChild(i).GetChild(0);
// get the item
if (i < npc.saleItems.Length) {
var item = npc.saleItems[i];
There is more to it ( feel free to ask for it if needed )
So the line : var npc = (Craft)player.target; identifies npc as Craft because player.Target = Craft.
But I'd like to edit it so It checks if the player.target = Craft then, Set var npc = Craft
but if player.target = Npc , Set var npc = Npc instead.
Not sure if I make myself clear or not. Please feel free to ask for more details and I'll do my best.
Any help is most appreciated, Mike
EDIT :
Ok so I've scripted it like this. Still when I run in game it doesnt looks like it knows what to do.. ( But compilers doesnt show syntax error )
// npc trading
if (player.target != null && player.target is Craft &&
Vector3.Distance(player.transform.position, player.target.transform.position) <= player.talkRange) {
var npc = (Craft)player.target;
}else if (player.target != null && player.target is Npc &&
Vector3.Distance(player.transform.position, player.target.transform.position) <= player.talkRange) {
var npc = (Npc)player.target;
// items for sale
for (int i = 0; i < npcTradingContent.childCount; ++i) {
var entry = npcTradingContent.GetChild(i).GetChild(0);
// get the item
if (i < npc.saleItems.Length) {
var item = npc.saleItems[i];
Where am I wrong
It looks like you are looking for the is operator.
if (player.target is Craft)
{
var npc = (Craft)player.target;
// do other 'Craft' logic here
}
else if (player.target is Npc)
{
var npc = (Npc)player.target;
// do other 'Npc' logic here
}
I'm assuming that player.target can either be an Npc or a Craft? If this is the case then they both inherit from whatever base type player.target is. You can check if it is either a Craft or Npc with the is operator before you cast.

Remove duplicates and put the list into a listbox

This is an uni assignment and I am having problem with part of it. This is the code;
namespace Assignment_1
{
public partial class Classifier : System.Web.UI.Page // We are using a web form as stated
{
protected void Page_Load(object sender, EventArgs e) // No variables are initiated for the beginning
{
}
protected void ButtonClassify_Click(object sender, EventArgs e)
{
if (this.TextBox1.Text != "")
{
List<string> numbersText = this.TextBox1.Text.Split(',').ToList<string>();
foreach (var item in numbersText)
{
int num = int.Parse(item);
if (RadioButtonList1.SelectedValue == "Both")
{
if (num % 2 == 0)
{
if (CheckBoxDuplicate.Checked == true)
{
List<int> evenNumbers = new List<int>();
evenNumbers.Add(num);
List<int> distinctEvenNumbers = evenNumbers.Distinct().ToList();
ListBoxEvenNumbers.DataSource = distinctEvenNumbers;
}
else
{
//Put the results into the respective boxes
ListBoxEvenNumbers.Items.Add(num.ToString());
}
}
else
{
//Put the results into the respective boxes
ListBoxOddNumbers.Items.Add(num.ToString());
}
}
if (RadioButtonList1.SelectedValue == "Even")
{
if (num % 2 == 0)
{
//Put the results into the respective boxes
ListBoxEvenNumbers.Items.Add(num.ToString());
}
}
if (RadioButtonList1.SelectedValue == "Odd")
{
if (num % 2 == 1)
{
//Put the results into the respective boxes
ListBoxOddNumbers.Items.Add(num.ToString());
}
}
Let me explain the question and what I have done. User inserts list of numbers into a text box and then has 3 options (radiolistbutton). He can list even, odd or both type of numbers. They display in even and odd listboxes(2 listboxes). I have done up to this part.
There is a checkbox to remove duplicates and the user can check it if he wishes to. If the button is checked, the code should remove the duplicates. I tried to do this part in the 4th "if-else" "if (CheckBoxDuplicate.Checked == true)". The way I understand it, I check if the number is even and then check the CheckboxDuplicate button. if it is checked I put the values in a new list and then delete repeated values. Then put into EvenNumbers listbox. For some reason, this part doesn't work.
If you would like to help me, please don't post just your answer. This is my first project in C# and it is difficult for me to understand an elegant solution yet. If you have time, please check my code and let me know where I made a mistake. Thanks for your time in advance.
Sprinkle a bit of linq magic on it, and you're done.
var my_list = new List<int>{1,2,3,4,5,5,6,7};
var pair = my_list.Where (n => n%2 ==0); // 2,4,6
var odd = my_list.Where (n => n%2 ==1); // 1,3,5,5,7
var unique_pair = pair.Distinct(); // stays the same
var unique_odd = odd.Distinct(); // 1,3,5,7
From here is just adding it to your appropriate calls and GUI containers
From your comment, here are a couple of things:
Change the if to if - else if, since only one will apply.
you can do it the way you do, but it's not the most efficient. If you go that way, you'll have to figure out which numbers you've added in order to not have duplicates.
alternatively, you can simply create the lists like I've done in the code above, and then assign them at the end. It'll save you time and code.
Here's some more help, with no code, since I believe i covered it.
Step 1: get the user input, and create a list of ints. (call it: input_list).
Step 2: According to what he chose (even, odd, both), you want to assign to each listbox, a list of numbers. Look at my above code, it'll do that bit for you.
Step 3: If user choses unique, you pass to those listboxes the Distinct list, again, look at my above code for that.
You can apply the unique on the event of the checkbox being selected if you prefer.
Notes:
Keep the list of ints (the input_list) as a variable, so you don't need to parse it whenever he changes his selection.
public enum PairOddEnum
{
Evens,
Odds,
Both
}
public void BindControl(PairOddEnum type)
{
if (this.textBox1.Text != "")
{
List<string> numbersText = this.textBox1.Text.Split(',').ToList<string>();
var evens = numbersText.Where(t => int.Parse(t) % 2 == 0).Distinct();
var odds = numbersText.Where(t => int.Parse(t) % 2 == 1).Distinct();
if (type == PairOddEnum.Evens)
{
ListBoxEvenNumbers.DataSource = evens.ToList();
}
else if (type == PairOddEnum.Odds)
{
ListBoxOddNumbers.DataSource = odds.ToList();
}
else
{
ListBoxEvenNumbers.DataSource = evens.ToList();
ListBoxOddNumbers.DataSource = odds.ToList();
}
}
}
protected void ButtonClassify_Click(object sender, EventArgs e)
{
if (RadioButtonList1.SelectedValue == "Both")
{
BindControl(PairOddEnum.Both);
}
if (RadioButtonList1.SelectedValue == "Even")
{
BindControl(PairOddEnum.Evens);
}
if (RadioButtonList1.SelectedValue == "Odd")
{
BindControl(PairOddEnum.Odds);
}
}

Categories

Resources