While attempting to build an inventory system including stackables. The inventory system works great, except for the stackables part.
Right now, I've hit a full stop due to an infinite loop.
Here's the code, with comments, so hopefully you'll be able to understand what I was trying to accomplish.
void AddItem(int id, int count)
{
while(count > 0) // Continue running as long as there is an item to be added
{
for(int i = 0; i < inventory.Count; i++) // Search through the inventory
{
if(inventory[i].itemID == id) // We've found an item with the appropriate ID
{
int maxAdd = inventory[i].itemMaxStack - inventory[i].itemCurrStack; // Figure out how much we can add to this stack
if(count > maxAdd) // There's not enough room to fit the entire stack, so add what we can and continue the -for- loop
{
inventory[i].itemCurrStack = inventory[i].itemMaxStack;
count -= maxAdd;
continue;
}
if(count <= maxAdd) // There's enough room to fit the entire stack, so add it in.
{
inventory[i].itemCurrStack += count;
count = 0;
}
if(inventory[i].itemCurrStack == inventory[i].itemMaxStack) // We found a stack, but it's already full, so continue the -for- loop
{
continue;
}
} else if(inventory[i].itemName == null) // There were no items with the specified ID, so let's create one.
{
for(int j = 0; j < database.items.Count; j++)
{
if(database.items[j].itemID == id)
{
inventory[i] = database.items[j];
break; // Break out of the -for- loop, since we've found what we're looking for.
}
}
}
}
}
}
You don't really need to do this with a loop. You can just stack the difference between the source and the target's max, and if there's any left, just dump it in the inventory as a new stack.
Edit: Added a little more plumbing to clarify, based on your comment. This is significantly different than your original question, but it demonstrates my point.
public abstract class Loot
{
public int Count { get; set; }
public virtual int MaxCount { get; set; }
}
public class Inventory : ICollection<Loot>
{
public void Stack(Loot source, Loot target)
{
var availableOnTarget = target.MaxCount - target.Count;
var amountToStack = Math.Min(availableOnTarget, source.Count);
target.Count += amountToStack;
source.Count -= amountToStack;
if (target.Count == target.MaxCount && source.Count > 0)
{
this.Add(source);
}
}
// ICollection implementation...
}
// This could be in Inventory, or the Player, or a gameplay manager...
// Personally I'd implement it in the Inventory class, if there was only
// one player with only one inventory. I'm sticking to the semantics of
// my first version, though.
public class Caller
{
public void TryAddItemToInventory<TLoot>(Inventory inventory, TLoot itemToAdd) where TLoot:Loot
{
var sourceType = itemToAdd.GetType();
var stackTarget = inventory.OfType<TLoot>().First(i => i.Count < i.MaxCount);
if (stackTarget != null)
{
inventory.Stack(itemToAdd, stackTarget);
}
else
{
inventory.Add(itemToAdd);
}
// You need to check if the inventory exists, if it has enough room to accommodate
// the item, what happens to overflow, etc. Left all that out for brevity.
}
}
Related
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; }
}
So I have an item class, an inventory class and the UIController scripts. When the player goes over the item on the field the player should have the item added to the inventory, if he/she has the item it should increase the amount by one.
The issue I'm having is the numbers on the UI are random and sometimes items add amounts other times they just use a new slot. I have added the three scripts I mentioned above.
public Item()
{
amount = 0;
}
public int GetItemAmount()
{
return amount;
}
public void IncreaseAmount(int amt)
{
amount += amt;
}
public void SpawnItem()
{
Instantiate(prefab);
}
public Inventory()
{
inventory = new List<Item>();
}
// Add item to inventory. if item is already in inventory, increase amount.
public void AddItem(Item item)
{
if (inventory.Count == 0)
{
inventory.Add(item);
}
else
{
for (int i = 0; i < GetInventory().Count; i++)
{
if (this.inventory[i] == item)
{
inventory[i].IncreaseAmount(1);
break;
}
else
{
inventory.Add(item);
}
break;
}
}
}
void Start()
{
player = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
inventoryItems = CreateInventoryItemUI(itemSlotImage);
}
private void Update()
{
var playerItems = player.inventory.GetInventory();
for (int i = 0; i <= playerItems.Count ; i++)
{
//Debug.Log("The value of i is : " + i);
if (playerItems[i] != null)
{
inventoryItems[i].GetComponentInChildren<Image>().sprite = playerItems[i].imageIcon;
var amountUI = inventoryItems[i].transform.Find("ItemSlotAmount").GetComponent<Text>();
amountUI.gameObject.SetActive(true);
amountUI.text = playerItems[i].GetItemAmount().ToString();
Debug.Log("The items being added will be: " + amountUI.text);
inventoryItems[i].gameObject.SetActive(true);
}
}
}
I think I just another set of eyes. I debugged and checked reference of the increase amount method and it's only used in the inventory script.
Any ideas would be very helpful.
You are checking if the added Item is exactly the same object as one in the list, as in the same reference. I'm assuming this is probably not what you want, please post the part of the code calling AddItem otherwise.
Two suggestions:
First, add an Id property to Item and compare if the item being added has the same Id, instead of comparing the references.
Second, this would probably be cleaner and more efficient if you used a Dictionary instead of a List for storing the items. Give it a try!
I have created a simple list class from scratch. This is for a class assignment that I have been working on for about a week - very new to lists. We can not use generics so trying to research my question below has not been fruitful. Although I did get to watch 7 tutorials on youtube by BetterCoder and I found some stuff in my book but nothing with an example of "merging".
I have three classes - my node, my list, and my program. In my list class, I am working on building a Merge() method which eventually will compare the data in the two lists and merge them into an ordered list.
Right now for some reason my Merge method - which is very basic to help me understand what is happening - is not working correctly. It has both lists passed to it, and is adding the data from listTwo to listOne BUT for some reason when it's printing to the console the second Node's Data shows twice :
EX: 1 -> 2 -> 2
instead of printing the head (1), the next (2) and then the next (3) which it should be.
EX: 1 -> 2 -> 3
In the program class I have proven with a write line that (listOne.firstNode.Next.Next.Data) = 3 . Which it should be.
Can someone help me figure out if the nodes in list one aren't pointing to each other correctly or whatever is going on?
My Merge Method must be passed both list objects (listOne and listTwo) and eventually I need to make those passed as references but I haven't figured that out quite yet and will focus on that later I suppose.
namespace LinkedList
{
//This is my Node Class
class Node
{
public object Data { get; set; }
public Node Next { get; set; }
public Node(object dataValue) : this(dataValue, null) { }
public Node(object dataValue, Node nextNode)
{
Data = dataValue;
Next = nextNode;
}
}
//This is my List Class
class List
{
public Node firstNode;
public int count;
public List()
{
firstNode = null;
}
public bool Empty
{
get { return this.count == 0; }
}
public int Count
{
get { return this.count; }
}
public object Add(int index, object o)
{
if (index < 0)
throw new ArgumentOutOfRangeException("Index: " + index);
if (index > count)
index = count;
Node current = this.firstNode;
if (this.Empty || index == 0)
{
this.firstNode = new Node(o, this.firstNode);
}
else
{
for (int i = 0; i < index - 1; i++)
current = current.Next;
current.Next = new Node(o, current.Next);
}
count++;
return o;
}
public object Add(object o)
{
return this.Add(count, o);
}
public object Merge(List a, List b)
{
a.Add(b.firstNode.Data);
return a;
}
public void Print()
{
while (this.count > 0)
{
Console.Write(firstNode.Data + "->");
if(firstNode.Next != null)
firstNode.Data = firstNode.Next.Data;
count--;
}
}
}
//And here is my Program
class Program
{
static void Main(string[] args)
{
List listOne = new List();
List listTwo = new List();
listOne.Add(1);
listOne.Add(2);
listTwo.Add(3);
listTwo.Print();
Console.WriteLine("");
listOne.Merge(listOne, listTwo);
Console.WriteLine("");
listOne.Print();
//This line below shows that the data "3" from listTwo is being added to listOne in the list Merge Method
//Console.WriteLine(listOne.firstNode.Next.Next.Data);
Console.ReadKey();
}
}
}
Actual problem in your print method
public void Print()
{
Node node = firstNode;
for (int i = 0; i < this.count; i++)
{
Console.Write(node.Data + "->");
if (node.Next != null)
node = node.Next;
}
}
Alex Sikilinda , you are right the merge method is incomplete.
public object Merge(List a, List b)
{
Node bNode = b.firstNode;
while (bNode != null)
{
a.Add(bNode.Data);
bNode = bNode.Next;
}
return a;
}
I would write it this way:
public void Merge(List b)
{
Node lastNode = GetLastNode();
if (lastNode != null)
{
lastNode.Next = b.firstNode;
}
else
{
this.firstNode = b.firstNode;
}
}
// this method is used to find the last node in current list
private Node GetLastNode()
{
if (this.firstNode == null)
{
return null;
}
Node current = this.firstNode;
while (current.Next != null)
{
current = current.Next;
}
return current;
}
First of all, I changed signature of Merge from public object Merge(List a, List b) to public void Merge(List b). Now we can use it like this:
listOne.Merge(listTwo);
This will link listOne's last element with the first element of listTwo and thus they are merged.
Now we need to change Print method since current version modifies the list, which shouldn't happen:
public void Print()
{
Node currentNode = this.firstNode;
while(currentNode != null)
{
Console.Write(currentNode.Data + ' ');
currentNode = currentNode.Next;
}
}
Instead of assigning the data back to first node I assign the
firstNode = firstNode.Next;
Please check the below Print Code
public void Print()
{
while (this.count > 0)
{
Console.Write(firstNode.Data + "->");
if (firstNode.Next != null)
firstNode = firstNode.Next;
count--;
}
}
I have a multilined textbox where I can write missons for a drone.
Example:
10 levantar
10 goto 50,40
10 goto 20,20
10 mayday
10 aterrar
I want to create a list that does this missoes step by step. Something like: takeoff, after takeoff goto that position and when reaches that positon goto the next, etc..
My question is: Is there a way to group this text on a list and when it finishes that task i simply remove the first position of the list and does the next?
private void executa_missao()
{
string[] linhas_separadas = null;
string[] pontos_separados = null;
for (int i = 0; i < tb_missoes.Lines.Length; i++)
{
linhas_separadas = tb_missoes.Lines[i].Split(null);
for(int k=0;k<drone.Length;k++)
{
listas_posicoes[k] = new List<PointF>();
if (linhas_separadas[0] == drone[k].ip_drone_final)
{
if (linhas_separadas[1] == "goto")
{
pontos_separados = linhas_separadas[2].Split(',');
drone[k].posicao_desejada = new PointF(Convert.ToSingle(pontos_separados[0]), Convert.ToSingle(pontos_separados[1]));
//guarda na lista as posicoes pretendidas
listas_posicoes[k].Add(new PointF(Convert.ToSingle(pontos_separados[0]), Convert.ToSingle(pontos_separados[1])));
}
else if (linhas_separadas[1] == "levantar")
{
drone[k]._droneClient.FlatTrim();
drone[k]._droneClient.Takeoff();
drone[k].subir_ate_altura = true;
}
else if (linhas_separadas[1] == "aterrar")
{
drone[k]._droneClient.Land();
}
}
}
Atm it's trying to do every step at the same time. I want to make step-by-step.
Use a Queue<T> instead of a List<T>
Then you can use the .Dequeue() function to get your current command, and remove it from the queue.
Creating sample working code for this behaviour can get very complicated and would take me a while, but the basic pattern would look as such:
public abstract class Command
{
public abstract bool IsComplete { get; }
public abstract void Execute();
}
public static class CommandExecutor
{
public static Queue<Command> commands;
public static Command current;
public static void Update()
{
if (commands.Count > 0
&& (current == null || current.IsComplete))
{
current = commands.Dequeue();
current.Execute();
}
}
}
Where the Update() method is called in recurring intervals.
I have an Octet class that is suppose to "package" eight samples and then send them forward. It has methods to add a new sample, check if it is already full, and to extract a Frame datastructure built from the eight values from the Octet.
The Octet class throws two kinds of exceptions: "cannot extract because not yet full" and "cannot add sample because already full". For that, the client code should check if full before calling Add, and extract as soon as is full, as well as to reset it (quite a lame class contract, to be honest).
The problem is: I am getting the two kinds of errors, even though the client class - the only one using Octet - seems to be performing the checks correctly before the operations that throw, but even though the error conditions are being hit. To make matters worse, when I check the values when debugger breaks, they are correct, that is, the exceptions should not be throwing!
public class Client
{
private Octet _octet = new Octet();
void ProcessNewSamples(IEnumerable<int> newSamples)
{
foreach (int sample in newSamples)
{
if (!_octet.IsFull)
{
_octet.Add(sample);
}
if (_octet.IsFull)
{
var frame = _octet.ExtractFrame();
this.SendElsewhere(frame);
_octet.Reset();
}
}
}
}
public class Octet
{
const int ARRAY_SIZE = 8;
int[] _samples = new int[ARRAY_SIZE];
int _index = 0;
public bool IsFull { get { return _index >= 8; } }
public void Add(int sample)
{
if (IsFull)
{
throw new InvalidOperationException();
}
else
_samples[_index++] = sample;
}
public Frame<int> ExtractFrame()
{
if (!IsFull)
throw new InvalidOperationException();
else
return new Frame<int>(_samples);
}
public void Reset()
{
_samples = new int[ARRAY_SIZE];
_index = 0;
}
}
As mentioned in the comment, you should place a lock if your function is accessed in parallel.
If SendElsewhere is fast, I simply would place the lock around the function:
void ProcessNewSamples(IEnumerable<int> newSamples)
{
lock (this)
{
foreach (int sample in newSamples)
{
if (!_octet.IsFull)
{
_octet.Add(sample);
}
if (_octet.IsFull)
{
var frame = _octet.ExtractFrame();
this.SendElsewhere(frame);
_octet.Reset();
}
}
}
}
Otherwise I would collect all frames and send them afterwards:
void ProcessNewSamples(IEnumerable<int> newSamples)
{
var frames = new List<Frame>();
lock (this)
{
foreach (int sample in newSamples)
{
if (!_octet.IsFull)
{
_octet.Add(sample);
}
if (_octet.IsFull)
{
var frame = _octet.ExtractFrame();
frames.Add(frame);
_octet.Reset();
}
}
}
foreach (var frame in frames)
{
this.SendElsewhere(frame)
}
}