Reputation: 83
I'm working on a game in Unity that is played on a hex-board with units that you control. When a unit is selected, hexes around it are lit up, indicating (by color - Blue to move, orange to attack), that you can move to them, or attack whatever is on them. Here's a screenshot for reference:
Here is my function to show the hexes:
//breadth first search
//has built in range checker so you dont have to check the path length of every individual node
//works whether attackRange or moveRange is longer
public void showUnitsHexes(ArmyUnit unitScript, bool trueShowFalseHide)
{
//HIDING
if(trueShowFalseHide == false)
{
foreach(TileNode node in nodesToHide)
{
//change the mat back to the default white
comReader.changeNodeMat(node, CommandReader.hexMats.defaultt);
node.Hide();
}
nodesToHide.Clear();
return;
}
//SHOWING
//Only show if its your turn (return on enemy turn)
if ((comReader.playerNum == getWhichButtonAndPlayer.playerType.P1 && comReader.CurrPlayerTurn == 2)
|| (comReader.playerNum == getWhichButtonAndPlayer.playerType.P2 && comReader.CurrPlayerTurn == 1))
return;
#region SETUP
//these were originally parameters but it's easier to just set them in the funct itself
TileNode startNode = unitScript.node;
int attackRange = unitScript.attackRange;
int moveRange = unitScript.moveRange;
//safety-net
if (startNode == null || (attackRange < 1 && moveRange < 1))
return;
//how to know when you're done
int finishedRadius;
if (moveRange > attackRange)
finishedRadius = moveRange;
else
finishedRadius = attackRange;
#endregion
//get the list of nodes not to go over twice, and the queue of nodes to go through
List<TileNode> nodesPassedAlready = new List<TileNode>();
nodesPassedAlready.Add(startNode);
Queue<TileNode> nodesToGoToInCurrentRadius = new Queue<TileNode>();
Queue<TileNode> nodesToGoToInNextRadius = new Queue<TileNode>();
int currentRadius = 1;
bool finished = false;
//add the 6 nodes surrounding the initial node to the queue to start things off
foreach (TileNode n in startNode.nodeLinks)
nodesToGoToInCurrentRadius.Enqueue(n);
//while the queue is not empty...
while (finished == false)
{
//END CHECK
//if done in current radius, need to go to next radius
if (nodesToGoToInCurrentRadius.Count < 1)
{
if (currentRadius == finishedRadius)
{
finished = true;
continue;
}
else
{
currentRadius++;
nodesToGoToInCurrentRadius = nodesToGoToInNextRadius;
nodesToGoToInNextRadius = new Queue<TileNode>();
}
}
//...get the next node and...
TileNode n = nodesToGoToInCurrentRadius.Dequeue();
//...if there's no issue...
#region safety check
//...if the node is null, has already been shown, is inhabited by a non-unit, don't bother with it
if (n == null || nodesPassedAlready.Contains(n) || comReader.hexIsInhabited(n, false, true))
continue;
//...if is inhabited and outside of attackRange, or inhabited by a friendly unit, don't bother with it
//NOTE: rather than combining this with the above if check, leave it separated (and AFTER) to avoid errors when n == null)
ArmyUnit currUnit = comReader.CurrentlySelectedUnit;
if (comReader.hexIsInhabited(n, true, true) && (currentRadius > currUnit.attackRange || comReader.unitIsMine(comReader.getUnitOnHex(n).GetComponent<ArmyUnit>())))
continue;
#endregion
//...1) show it
#region show nodes
//show as requested, add it to the list
//change the mat to whatever color is relevant: if n is inhabited and in attack range, color atkColor, otherwise color moveColor
if (comReader.hexIsInhabited(n, true, true) && currentRadius <= attackRange)
{
Debug.Log("attackRange: " + attackRange);
Debug.Log(n.name);
if (n.name.Equals("node345"))
Debug.Log("checked it");
comReader.changeNodeMat(n, CommandReader.hexMats.attack);
n.Show();
nodesToHide.Add(n);
}
//make sure hex is in moveRange. possible that it isnt, if attack range > moveRange
else if (moveRange >= currentRadius)
{
comReader.changeNodeMat(n, CommandReader.hexMats.move);
n.Show();
nodesToHide.Add(n);
}
//do not take n.show() out of those braces. If you do, it will sometimes show white nodes and you don't want to show them
#endregion
//...2) don't go over it a second time
nodesPassedAlready.Add(n);
//...and 3) add all surrounding nodes to the queue if they haven't been gone over yet AND ARE NOT ALREADY IN THE QUEUE
foreach (TileNode adjacentNode in n.nodeLinks)
{
#region safety check
//...if the node is null or has already been shown or is inhabited by a non-unit, don't bother with it
if (adjacentNode == null || nodesPassedAlready.Contains(adjacentNode) || comReader.hexIsInhabited(adjacentNode, false, true))
continue;
//...if is inhabited and outside of attackRange, don't bother with it
//NOTE: rather than combining this with the above if check, leave it separated (and AFTER) to avoid errors when n == null)
//currUnit already defined in the above safetycheck
if (comReader.hexIsInhabited(adjacentNode, true, true) && currentRadius > currUnit.attackRange)
continue;
#endregion
nodesToGoToInNextRadius.Enqueue(adjacentNode);
}
}
}
My problem is that when I set the attack range longer than the map's size (set to 30, map is 12x19), I get the error:
InvalidOperationException: Operation is not valid due to the current state of the object System.Collections.Generic.Queue`1[TileNode].Peek()
Likewise, when I set the range to 17 or 18 -- enough to be able to attack the enemy base from where you see my unit in the picture -- the node for that base is never even looked at in the function, despite it being in attack range.
What does this error message mean? Where is my logic error? Sorry if this is sloppily written -- I'll be happy to answer any questions you may have. Thank you!
Upvotes: 1
Views: 970
Reputation: 2693
Is there any chance that nodesToGoToInNextRadius
is empty?
Queue.Dequeue calls Queue.Peek underneath and throws InvalidOperationException when the queue is empty. You assigning nodesToGoToInNextRadius to nodesToGoToInCurrentRadius:
nodesToGoToInCurrentRadius = nodesToGoToInNextRadius;
and not checking again if there is anything in the queue.
if (nodesToGoToInCurrentRadius.Count < 1) // that's fine
{
if (currentRadius == finishedRadius)
{
finished = true;
continue;
}
else
{
currentRadius++;
//1. now you are swapping queues
nodesToGoToInCurrentRadius = nodesToGoToInNextRadius;
nodesToGoToInNextRadius = new Queue<TileNode>();
}
}
//2. and calling Dequeue on swapped queue without checking if it's empty.
TileNode n = nodesToGoToInCurrentRadius.Dequeue();
Upvotes: 2