- Home /
How to stop infinite loops in turn based game.
Unity freezes (presumably because there is an infinite loop) when I run this code.
My logic is: I call updateTurn, which organizes the players by speed and calls actionPhase on the next fastest unit. actionPhase sets the turnState to 1, which turns on the actionPhase GUI. At this point the player clicks the move button, which sets the turnState to 2. While turnState is 2, update polls the users mouseclicks until a valid move is made, at which point turnState is updated to 9, ending the turn.
All methods work properly on their own EXCEPT actionPhase, which always freezes the game. How can I stop the infinite loop from happening?
Sorry about the formatting. I swear it's impossible to copy/paste code :(
//Update turn orders players by speed and then calls action phase on them
void updateTurn()
{
int playerSize = players.Count;
int index = 0;
PlayerStats ps;
//turnState = 0;
print("Turn Started");
//calculate fastest player
sortPlayersBySpeed();
index = 0;
//ps = players[index].GetComponent<PlayerStats>();
index = 0;
while(index < playerSize)
{
print("entering loop");
ps = players[index].GetComponent<PlayerStats>();
if(ps.dead == false)
{
print("Starting Action Phase " + index);
actionPhase(players[index]);
print("Ended Action Phase" + index);
}
index++;
}
}
void actionPhase(GameObject player){
turnState = 1;
while(turnState != 9)
{
switch(turnState)
{
case 0:
//No player selected
break;
case 1:
//player selected, actionphase begun, wait for GUI
break;
case 2:
//move selected, update is getting new click positions
print("move selected");
if(moveValid(player, clickPosition) == true)
{
StartCoroutine(moveObject(player, clickPosition));
turnState = 9;
}
break;
case 9:
print("turns over");
return;
}
}
}
void OnGUI () {
if(turnState == 1 || turnState == 2 || turnState == 8)
{
if (GUI.Button (new Rect (10,10, 100, 50), moveButton))
{
print ("you clicked move");
if(turnState == 1)
{
turnState = 2;
}
}
}
}
// Update is called once per frame
void Update () {
if(turnState == 2 || turnState == 8)
{
clickPosition = getTaggedClickPos("Ground");
print(clickPosition);
}
}
You should properly indent your code and you won't have as much confusion with the brackets as you have in the code above.
Answer by Owen-Reynolds · May 11, 2014 at 04:28 PM
After reading Benprod's reply, it seems like the whole turn sequencing is borked, and needs an overhaul.
So, there's an "action queue" like finalFantasy Tactics, of who goes when. The loop at the bottom of updateTurn goes through the whole list, giving each player a turn using actionPhase. actionPhase completely runs a player's turn, returning when the player has finished.
BUT this should all happen over many frames!! So actionPhase and updateTurn should be coroutines -- yield return null;
in actionPhase, and yield startCoroutine(actionPhase..)
in updateTurn.
But otherwise the style of programming doesn't feel like a coroutine. As normal code, get rid of all the loops. ActionPhase would just check what you did this frame, and if it was the "confirm action" button, say that player is done. UpdateTurn just runs actionPhase for the current player. If they were done, resort the list (and check the new 1st player next frame.)
I think you have the right solution (using coroutines), but I don't fully understand why this is needed.
It should happen over many frames, which is why actionPhase (or updateTurn) is not called within update. Ins$$anonymous$$d the parts of these methods that need to be checked are contained within GUI and Update.
The reason I am looping through actionPhase is because I need to check for the clicks of the users, which may or may not happen in the exact frame that method is called. I need that loop to run because the location will be decided AFTER that method is called.
How would using actionPhase and updateTurn as co-routines solve my problem?
This might help: Users enter input during frames, and code runs between frames. If you check input more than once during a frame, you're just rereading last frame's input, over and over.
That does help, it explains what's happening pretty well. Now to just figure out how to fix it....
Answer by Benproductions1 · May 11, 2014 at 09:44 AM
Just desk-checking your code in actionPhase
(should actually be ActionPhase
. We capitalize functions in C#) simply and plainly reveals your infinite loop:
set
turnState
to1
Begin a
while
loop, whileturnState
is not equal to9
turnState
is equal to1
, so go tocase 1
and...break
.Now, we are still in the
while
loop, so continue from 2. (`turnState` is still1
)
Always remember, break
is one level only. In a switch
, break
will break out of the switch. Nothing more, nothing less.
turnState is updated in the other methods. As soon as a player clicks on the GUI move button it changes the turnState to 2. This however is never allows to happen as Unity refuses to run.
I can see where the infinite loop is, I explained that. I don't understand WHY it is an infinite loop, as it is being edited within onGUI, which calls every frame.
I am going to seperate turnState into GUIState and turnState so that the code is easier to understand.
Answer by sandbaydev · May 11, 2014 at 08:12 PM
You have quite many while loops & thingies there.
Instead of "waiting for state 9...", you could consider something like here. This is partially pseudo code, so plenty of figuring it out here. But hopefully this gives some idea about what you could consider doing: break the code into smaller pieces, treat each turn phase in separate blocks. This isn't the final code, you still need to think how you want to arrange your code.
Currently your breaks in line 45, where it can never leave the "while" loop... here's bit of code to hopefully help you out :)
void updateTurn() {
switch currentTurnPhase {
case "newturn":
initTurn();
currentPlayer = getNextPlayer(); //this one finds the next alive player on the list
currentTurnPhase = "playerturn";
break;
case "playerturn":
if (clickedMoveSomewhere) {
currentPlayer = getNextPlayer(); //returns null in case this was the last chap
}
if (currentPlayer == null) {
//nobody left, let's end the turn
currentTurnPhase = "endturn";
}
break;
case "endturn":
// do something
...
// start a new turn
currentTurnPhase = "newturn"
break;
}
}
Thanks. That is actually almost exactly what I currently have, the problem is that each "case" becomes long enough to be their own method, which is why I have separated them. The extra states are required because I am planning on having actions, movement, and attacks all occurring within the turn.
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
apply something on enum 1 Answer
Illuminating a 3D object's edges OnMouseOver (script in c#)? 1 Answer