I am in the process of making a farming/tower defense game and I am very new at programming. I seem to have a major problem with using Lists<> or arrays in XNA. I cannot get it to return the index that I want from the list.
The main question is inside my planting engine. I have successfully implemented a planting system that can generate a list of plants (spriteobjects) with varying properties and place them on the map. Now, I need a way to access a specific plant in the plant list based upon mouseclicking on that plant. I feel like I am very close, but I ended up with a ArgumentOutOfRangeException that I cannot solve. Here is a walkthrough of the code:
Initialization
public void Addplants()
{
switch (Mode)
{
case "Wotalemon":
NewPlant = new Plant(Texture, msRect);
NewPlant.AddAnimation("seed", 0, 16, 64, 64, 1, 0.1f);
NewPlant.AddAnimation("sprout", 64, 16, 64, 64, 1, 0.1f);
NewPlant.AddAnimation("wota", 128, 16, 64, 64, 1, 1.0f);
NewPlant.CurrentAnimation = "seed";
NewPlant.DrawOffset = new Vector2(32, 48);
NewPlant.Position = Position;
NewPlant.Type = "wotalemon";
NewPlant.Birthday = Days;
NewPlant.IsSelected = false;
plants.Add(NewPlant);
thisPlant = NewPlant;
//various plants after this
Update/Draw
I use some simple foreach loops to update and draw the plants, no problems here.
GetInfo (this method uses the spriteobject's hitbox property and a mouseRectangle)
public void GetInfo(Rectangle ms)
{
msRect = ms;
for (int i = 0; i < plants.Count; i++)
{
foreach (Plant NewPlant in plants)
{
if (NewPlant.BoundingBox.Intersects(msRect))
{
SelectedIndex = i;
NewPlant.Tint = Color.Black;
}
else
NewPlant.Tint = Color.White;
}
}
}
finally, here is the problem:
public void SelectPlant()
{
//if (SelectedIndex != null)
if (SelectedIndex > plants.Count | SelectedIndex < 0)
SelectedIndex = plants.Count;
SelectedPlant = plants[SelectedIndex];
}
The exception is thrown in this line:
SelectedPlant = plants[SelectedIndex];
The debugger shows the value as 0. I have tried various methods to try to prevent the index from being null. I feel like something in the Getinfo() method is key here. I am convinced that I am very close to success because the color test that I have inserted in there works perfectly. When I mouseover a plant, it turns black, when I remove the mouse, it returns to normal.
This is EXACTLY the type of behavior I want except that I want it to set selectedIndex to the index of the plant that I am mousing over. Any advice would be greatly appreciated.
Firstly make this a proper or || and check for >= plants.Count - remember that the list is indexed at 0. Then as suggested set it to count - 1:
if (SelectedIndex >= plants.Count || SelectedIndex < 0)
SelectedIndex = plants.Count - 1
I'll add this as a new answer since this is tackling a whole different issue. Taking a look at this code:
msRect = ms;
for (int i = 0; i < plants.Count; i++)
{
foreach (Plant NewPlant in plants) // <-- this is redundant
{
if (NewPlant.BoundingBox.Intersects(msRect))
{
SelectedIndex = i;
NewPlant.Tint = Color.Black;
}
else
NewPlant.Tint = Color.White;
}
}
You are looping through 'plants' twice inside each other! Once using an index (for (int i = 0 ...) and then inside that again using an iterator (foreach (Plant NewPlant ...).
Your options are to either change GetInfo to set the right index by using a single loop:
msRect = ms;
for (int i = 0; i < plants.Count; i++)
{
Plant NewPlant = plants[i];
if (NewPlant.BoundingBox.Intersects(msRect))
{
SelectedIndex = i;
NewPlant.Tint = Color.Black;
}
else
NewPlant.Tint = Color.White;
}
Or do the same thing and short circuit the need for SelectPlant() and SelectedIndex in the first place:
msRect = ms;
foreach (Plant NewPlant in plants) // no need for indexes
{
if (NewPlant.BoundingBox.Intersects(msRect))
{
SelectedPlant = NewPlant; // this is everything you need
NewPlant.Tint = Color.Black;
}
else
NewPlant.Tint = Color.White;
}
You do however need to be careful using a 'global' variable like SelectedPlant to capture this logic. You're better off changing the whole GetInfo method to return the selected plant, rather than having it modify SelectedPlant directly. That is, change the method signature to return Plant not void, and change SelectPlant = NewPlant in the code above to return NewPlant. Or for even more fun as a single line:
return plants.Where(p => p.BoundingBox.Intersects(ms))
Related
Hello I have an array of enumerations, and I'm trying sort that array by their enumeration value and get the array index's of the top
private enum Values
{
LOW,
MEDIUM,
HIGH
}
private Values[] Settings = new Values[10];
Settings[0] = LOW;
Settings[1] = HIGH;
Settings[2] = MEDIUM;
Settings[3] = LOW;
Settings[4] = LOW;
Settings[5] = LOW;
Settings[6] = LOW;
Settings[7] = LOW;
Settings[8] = MEDIUM;
Settings[9] = MEDIUM;
Basically now, with what I have above, I need to sort the settings array by enumeration value and get the array indexes of the top (let's say 3) items;
So I'd get back values 1, 2, 8
The platform I'm using does not support LINQ so those handy functions are not available.
I've been trying to wrap my head around this but it would help to have another pair of eyes.
thanks.
Implement a wrapper reference type,
class ValueWrapper : IComparable<ValueWrapper>
{
public Values Value { get; set; }
public int Index { get; set; }
public int CompareTo(ValueWrapper other)
{
return this.Value.CompareTo(other.Value) * -1; // Negating since you want reversed order
}
}
Usage -
ValueWrapper[] WrappedSettings = new ValueWrapper[10];
for(int i = 0; i < WrappedSettings.Length; i++)
{
WrappedSettings[i] = new ValueWrapper { Value = Settings[i], Index = i };
}
Array.Sort(WrappedSettings);
WrappedSettings will be sorted as you specified, preserving the indexes they were in the original array.
how about this:
Values first = Values.Low,second = Values.Low,third = Values.Low;
int firstI = -1,secondI = -1, thirdI = -1;
for(int i = 0;i < Settings.Length;i++)
{
if(Settings[i] > first || firstI == -1)
{
third = second;
thirdI = secondI;
second= first;
secondI= firstI;
first = Settings[i];
firstI = i;
}
else if(Settings[i] > second || secondI == -1)
{
third = second;
thirdI = secondI;
second = Settings[i];
secondI = i;
}
else if(Settings[i] > third || thirdI == -1)
{
third = Settings[i];
thirdI = i;
}
}
So, since you say you work in an environment where Linq is not available, I assume that other things like generics, nullables and so on will not be available either. A very low-tech solution.
Basic idea:
For every possible enum value, from highest to lowest, go through the list. If we find that value, output it and remember how many we have output. If we reach 3, stop the algorithm.
So, we first look for HIGH in the list, then for MEDIUM and so on.
class Program
{
private enum Values
{
LOW,
MEDIUM,
HIGH
}
static void Main(string[] args)
{
// Sample data
Values[] settings = new Values[10];
settings[0] = Values.LOW;
settings[1] = Values.HIGH;
settings[2] = Values.MEDIUM;
settings[3] = Values.LOW;
settings[4] = Values.LOW;
settings[5] = Values.LOW;
settings[6] = Values.LOW;
settings[7] = Values.LOW;
settings[8] = Values.MEDIUM;
settings[9] = Values.MEDIUM;
// Get Values of the enum type
// This list is sorted ascending by value but may contain duplicates
Array enumValues = Enum.GetValues(typeof(Values));
// Number of results found so far
int numberFound = 0;
// The enum value we used during the last outer loop, so
// we skip duplicate enum values
int lastValue = -1;
// For each enum value starting with the highest to the lowest
for (int i= enumValues.Length -1; i >= 0; i--)
{
// Get this enum value
int enumValue = (int)enumValues.GetValue(i);
// Check whether we had the same value in the previous loop
// If yes, skip it.
if(enumValue == lastValue)
{
continue;
}
lastValue = enumValue;
// For each entry in the list where we are searching
for(int j=0; j< settings.Length; j++)
{
// Check to see whether it is the currently searched value
if (enumValue == (int)settings[j])
{
// if yes, then output it.
Console.WriteLine(j);
numberFound++;
// Stop after 3 found entries
if (numberFound == 3)
{
goto finished;
}
}
}
}
finished:
Console.ReadLine();
}
}
Output is as requested 1,2,8
I'm not sure if this is exactly what you want because it doesn't sort the original array, but one way to get the indexes of the top three values is to simply store the indexes of the top values in another array. Then we can loop through the original array, and for each item, see if it's larger than any of the items at the indexes we've stored so far. If it is, then swap it with that item.
For example:
// Start the topIndexes array with all invalid indexes
var topIndexes = new[] {-1, -1, -1};
for (var settingIndex = 0; settingIndex < Settings.Length; settingIndex++)
{
var setting = Settings[settingIndex];
var topIndexLessThanSetting = -1;
// Find the smallest topIndex setting that's less than this setting
for (int topIndex = 0; topIndex < topIndexes.Length; topIndex++)
{
if (topIndexes[topIndex] == -1)
{
topIndexLessThanSetting = topIndex;
break;
}
if (setting <= Settings[topIndexes[topIndex]]) continue;
if (topIndexLessThanSetting == -1 ||
Settings[topIndexes[topIndex]] < Settings[topIndexes[topIndexLessThanSetting]])
{
topIndexLessThanSetting = topIndex;
}
}
topIndexes[topIndexLessThanSetting] = settingIndex;
}
// topIndexes = { 1, 2, 8 }
So how can I update the position every time I call the StartRandomizingRightSpikePosition
private bool CheckOverlap(GameObject o1, GameObject o2)
{
return spikeRight.Select(t => t.GetComponent<Collider>().bounds.Intersects(t.GetComponent<Collider>().bounds)).FirstOrDefault();
}
public void StartRandomizingRightSpikesPosition()
{
foreach (var t in spikeRight)
{
foreach (var t1 in spikeRight)
{
if (t == t1) continue;
if (!CheckOverlap(t, t1)) continue;
yPosition = Random.Range(-7, 7);
var position = t1.transform.position;
desiredPosition = new Vector3(position.x, yPosition, position.z);
t1.transform.position = desiredPosition;
Debug.Log(t.gameObject + " intersects " + t1.gameObject);
}
}
}
The short answer is yes but I'm not sure you would want too. I'm not sure you're going to find a way to do this efficiently and you might be better off finding a way to generate the objects such that this step is not necessary.
I can't tell from your question how the objects are actually stored so I'm going to provide some sample code that just deals with a simple array of Rectangles. You should be able to adapt it to your specifics.
I tried to make it slightly more efficient by not checking both t1 == t and t == t1.
const int Bounds = 1000;
static bool RemovedOverlappingRects(Rectangle[] rects)
{
for (int pos = 0; pos < rects.Length; ++pos)
{
for (int check = pos +1; check < rects.Length; ++check)
{
var r1 = rects[pos];
var r2 = rects[check];
if (r1.IntersectsWith(r2))
{
r2.Y = Rng.Next(1, Bounds);
rects[check] = r2;
Console.WriteLine($"{pos} overlaps with {check}");
return true;
}
}
}
return false;
}
Once we've randomly generated a new rectangle we have to start over. Which means invoking the above method in a loop.
var rects = GetRandomeRects(20).ToArray();
while (RemovedOverlappingRects(rects))
;
Because of the random movement I'm not certain you can guarantee this will always end. If you can deal with the non-random look of the results changing it to stack the overlaps would I believe always finish. That would be this:
r2.Y = r1.Y + r1.Height + 1;
in place of
r2.Y = Rng.Next(1, Bounds);
But even then you're still dealing with a very unpredictable run time due to the random input.
Maybe someone else can show us a more efficient way...
EDIT: OP here, its answered. Can't accept my own answer for 2 days? Dunno I'm a stack noob. Thanks to people that helped.
I want to have a loop that generates a random coordinate and adds it to a list only if that coordinate does not already exist in the list.
And just keeps looping until the correct number of coordinates are in the list.
while (spawnPositions.Count < myPlayer.myPlayerUnits.Count)
{
Debug.Log("While");
int[,] rnd = new int[UnityEngine.Random.Range(minX, maxX), UnityEngine.Random.Range(minZ, maxZ)];
if (spawnPositions.Contains(rnd) == false)
{
Debug.Log("SpawnPos Added!");
spawnPositions.Add(rnd);
}
}
Problem is the if statement is always true. Console output shows the loop loops X amount of times and the if statement is also true X amount of times.
Is this just not possible to do in while-loop. Or am i doing something wrong? Thanks!
EDIT: Oh and to be clear yes duplicates are added. I'm trying to generate 5 unique coordinates on a 3x3 area and there is almost always duplicates!
If I understand you right you want to compare the content of multidimensional arrays or maybe only their dimensions. But the .Contains() method looks for the reference equality and this is always unique because you are calling "new".
I defined a extension method for it.
public static bool IsContentEqualTo(this int[,] array1, int[,] array2)
{
if (array1 == null) throw new ArgumentNullException(nameof(array1), "Array cannot be null");
if (array2 == null) throw new ArgumentNullException(nameof(array2), "Array cannot be null");
if (array1.GetLowerBound(0) != array2.GetLowerBound(0)) return false;
if (array1.GetUpperBound(0) != array2.GetUpperBound(0)) return false;
if (array1.GetLowerBound(1) != array2.GetLowerBound(1)) return false;
if (array1.GetUpperBound(1) != array2.GetUpperBound(1)) return false;
var xMin = array1.GetLowerBound(0);
var xMax = array1.GetUpperBound(0);
var yMin = array1.GetLowerBound(1);
var yMax = array1.GetUpperBound(1);
for (var x = xMin; x <= xMax; x++)
for (var y = yMin; y <= yMax; y++)
{
if (array1[x, y] != array2[x, y]) return false;
}
return true;
}
And you can call it like that.
if (!spawnPositions.Any(array => array.IsContentEqualTo(rnd)))
{
Debug.Log("SpawnPos Added!");
spawnPositions.Add(rnd);
}
EDIT: OP here, its answered. Can't accept my own answer for 2 days? Dunno I'm a stack noob. Thanks to people that helped.
I fixed it, its not pretty but it works. An array of booleans to keep track if the value have been used already. And not actaully adding/removing values from the list from within the while-loop (had a few infinite-loops trying out things :S).
bool[,] tileOccupied = new bool[maxX, maxZ];
for (int i = 0; i < myPlayer.myPlayerUnits.Count; i++)
{
int tileValue = UnityEngine.Random.Range(0, myPlayer.mySpawnArea.Count);
int[,] spawnTile = myPlayer.mySpawnArea[tileValue];
if (tileOccupied[spawnTile.GetLength(0), spawnTile.GetLength(1)] == true)
{
do
{
tileValue = UnityEngine.Random.Range(0, myPlayer.mySpawnArea.Count - 1);
spawnTile = myPlayer.mySpawnArea[tileValue];
} while (tileOccupied[spawnTile.GetLength(0), spawnTile.GetLength(1)] == true);
}
tileOccupied[spawnTile.GetLength(0), spawnTile.GetLength(1)] = true;
spawnPositions.Add(spawnTile);
}
Picture 1.
Picture 2.
I made method for checking if all pieces are in base or goal if yes it returns true,now i need another method.
If its like on Picture 1. i need to change number of throws to 3 ,if its ordered like on Picture 2 i can allow only 1 throw.
I got 4 goalPositions and 4 piecePositions,and need to check if pieces are ordered on them properly from 54-51 path positions(path is array of 55 fields 0-54) ,if yes return true if not return false.
I am new to C# never had chance to work with order checking till now.
I was trying to do it with 3 int lists goalPositions (populated with 51,52,53,54 path positions),piecePositions(populated with pieces positions with getPosition() method),and piecesOnGoal (reserved for counting pieces on goal positions). but no luck with that.
I'll add some code. Part of Player class with that lists and method for checking pieces in goal or base
class Player {
protected PieceSet[] pieces;
Color color;
int numberOfThrows;
Dice dice;
public List<int> goalPositions;
public List<int> piecePositions;
public List<int> piecesOnGoal;
public enum Color
{
Yellow, Green, Blue, Red
}
public Player(Color color)
{
int[] path = new int[55];
this.color = color;
dice = new Dice();
numberOfThrows = 3;
switch (color)
{
case Color.Yellow:
path = BoardHelper.getYellowPath();
break;
case Color.Green:
path = BoardHelper.getGreenPath();
break;
case Color.Blue:
path = BoardHelper.getBluePath();
break;
case Color.Red:
path = BoardHelper.getRedPath();
break;
}
pieces = new PieceSet[4];
pieces[0] = new PieceSet(path, 0);
pieces[1] = new PieceSet(path, 1);
pieces[2] = new PieceSet(path, 2);
pieces[3] = new PieceSet(path, 3);
piecePositions = new List<int>(4);
piecePositions.Add(pieces[0].getPosition());
piecePositions.Add(pieces[1].getPosition());
piecePositions.Add(pieces[2].getPosition());
piecePositions.Add(pieces[3].getPosition());
goalPositions = new List<int>(4);
goalPositions.Add(51);
goalPositions.Add(52);
goalPositions.Add(53);
goalPositions.Add(54);
piecesOnGoal =new List<int>();
}
public bool isAllPiecesInBaseOrGoal()
{
if ((pieces[0].getPosition() < 4 || pieces[0].getPosition() > 50) &&
(pieces[1].getPosition() < 4 || pieces[1].getPosition() > 50) &&
(pieces[2].getPosition() < 4 || pieces[2].getPosition() > 50) &&
(pieces[3].getPosition() < 4 || pieces[3].getPosition() > 50))
return true;
else
return false;
}
And this is how I was thinking to solve my problem: Check if goalPositions contains piecePositions. If yes, add that position into piecesOnGoal. Now I need somehow to check if these piecesOnGoal are ordered. If yes, return true. If not, false.
I am open for any suggestion.
public bool isAllPiecesAreOrderedInGoal()
{
for (int i = 0; i < 4; i++)
{
if (goalPositions.Contains(piecePositions[i]))
{
piecesOnGoal.Add(piecePositions[i]);
}
}
}
Any help is appreciated.
I would suggest a method that checks if any move is possible. A move is possible if there are pieces outside the home or goal or if there are pieces in the goal which have an empty spot before them:
bool IsMovePossible()
{
// check every piece
for(int iPiece = 0; iPiece < 4; ++iPiece)
{
// if it is outside of home or goal, there is a possible move
if(piecePositions[iPiece] > 3 && piecePositions[iPiece] < goalPositions.First())
return true;
// if it is in the goal, check the next position
if(piecePositions[iPiece] >= goalPositions.First()
&& piecePositions[iPiece] < goalPositions.Last()
&& !piecePositions.Any(p => p == piecePositions[iPiece] + 1))
return true;
}
// we have found no piece with a possible move
return false;
}
Then, decide how often the user can roll the dice based on this method's return value. Note that the last check (piecePositions.Any) could be done more efficiently if you maintain an inverted map (i.e. for every position, store which piece is on that position). But checking four pieces should be fine.
I am developing an App which uses recursion.
void Keres(char[,] array, int width, int height)
{
_found = Search(array, height, width, _words);
if (_found.Count < 6)
{
_found.Clear();
Keres(array, width, height);
}
}
Search is a recursive method, and it gives back a string List. And I need the count of it to be bigger than 5. But if it's not, I have to call again and again the Keres method, until it's count is 6 or greater, but my app freezes.
Here is where I call Keres method:
if ((string)appSettings["gamelanguage"] == "english")
{
szo = EngInput(3, 3); //szo is a char[,] array
Keres(szo, 3, 3);
}
What can I do to avoid the recursion, or avoid the crash, and get my >6 items?
Edit: Search method
List<string> Search(char[,] letter_table, int height, int width, List<string> words_list)
{
List<string> possible_words = new List<string>();
char[,] _tmp_letter_table = _tmp_letter_table = new char[height, width];
bool possible = false;
foreach (String word in words_list)
{
possible = false;
Array.Copy(letter_table, _tmp_letter_table, width * height);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
if (_tmp_letter_table[i, j] == word[0])
{
if (IsNeighborTest(word, i, j, height, width, _tmp_letter_table, 0) == true)
{
possible = true;
break;
}
else
{
Array.Copy(letter_table, _tmp_letter_table, width * height);
}
}
}
if (possible == true)
{
possible_words.Add(word);
break;
}
}
}
return possible_words;
}
your code is not a correct recursion, actually you are calling always the same method, each time the recursive method is called something must have been changed, obviously in your code you are never exiting the method and the application freezes.
I think, if I understood what you want, that the problem you are facing is not solvable with recursion.
Maybe the array is something that changes and until is changed to >6 you want to check with the Keres method? Then recursion is not the way to do it.
You can avoid the recursion with a simple loop:
void Keres(char[,] array, int width, int height)
{
do
{
_found = Search(array,height,width,_words);
} while (_found.Count < 6)
}
But if the app is freezing with recursion, it may likely freeze without it since they should do the same thing (this method may avoid a StackOverflow Exception however if this takes many iterations to complete)