Ending a turn-based game that allows one action per turn is fairly trivial - you can just have a boolean value update when various win or loss conditions are met, and check the boolean's value every time you loop through a turn to figure out when the game ends.
The game I'm writing, however, involves more complex turns, with a player taking several actions per character with multiple characters that may result in victory or loss, and several computer-run updates that occur between character turns that may result in loss. Obviously it's necessary to interrupt the turn when a win condition is reached.
The options I thought of:
Just continue checking for completion once per loop. This option doesn't really work - you'd have to finish out all the actions for a turn even if you've won (which may not even be possible), and you'd have to include special handlers to make sure one finish condition isn't overwritten by another in the same turn.
Throw an exception through the stack until you're back in the main method, then catch the exception, parse it, and provide win/loss messages. Incredibly crass implementation, and not really what exceptions are for.
Using observer/listener models or event handlers to just throw another method call onto the stack, instead of the program gracefully extracting itself from the game loop. Seems to be more for inserting a few quick lines of code or shooting messages off to other threads, not ending the current game loop.
Putting the game loop in its own thread, terminating whenever a win condition has been reached. Main method is waiting in a separate loop for a change in the game state, and handles as necessary. The problem with this approach is that it seems (in Java, anyway) that implementing Runnable doesn't actually allow for stopping the running thread from elsewhere (you'd have to return from the run() method), and even extending Thread (which shouldn't be done anyway) and calling this.interrupt() when a condition is met doesn't actually stop the game code from continuing to run. While you could poll the thread's interrupt flag to drive the logic, that just gives us the same problem all over again of not really working as an interrupt.
Some code:
public static void main(String[] args) {
Game game = new Game(2, Difficulty.NOVICE);
game.run();
while(game.getGameState() == State.INCOMPLETE){
//Hold while waiting for game to complete.
}
}
public class Game extends Thread{
public void checkState(){
//Let's presume a win condition was thrown:
state = State.WON;
this.interrupt();
}
public void randomMethod(){
//This method might contain some code that triggers a win condition, so we immediately call checkState()
checkState();
}
@Override
public void run() {
//Lots of different methods called in a single turn, including for example:
randomMethod();
}
}
I'm sure there's a well-known industry standard for doing this sort of thing, but I didn't find it spelled out anywhere, and I'm at a loss for what it might be.
Solution: The State model referenced by Balder appears to be just the ticket. The major gist of this is to track the state of a given turn, and have the game loop perform one action that can alter the game that dynamically changes depending on what state the game is currently in. There are lots of ways to do this - following the links in his answer demonstrates a few of them. Here's the implementation I ended up using:
while(gameState_ == GameState.INCOMPLETE){
turnState_.update();
checkWin();
}
public void changeTurnState(TurnState state){
turnState_.exit();
turnState_ = state;
turnState_.enter();
}
public abstract class TurnState{
private Game game;
public TurnState(Game game){
this.game = game;
}
public void enter(){
}
public void exit(){
}
public Game getGame(){
return game;
}
public void update(){
}
}
With this setup, I can extend TurnState to produce any state I wish, with whatever needs to happen during a single action of that state. For example, consider a state for flooding one or more regions on the board. Remember that we need each change to the game to occur by itself so we can check for win conditions in between - so this particular state tracks how many regions we have left to flood, and moves on to the next state once the last necessary region is flooded.
public class FloodState extends TurnState {
private int remainingFloods;
/**
* @param game
*/
public FloodState(Game game) {
super(game);
remainingFloods = getGame().getFloodRate();
ForbiddenIsland.logger.info("Flooding {} tiles", remainingFloods);
}
public void update(){
//Draw and resolve a flood card, then decrement remainingFloods
getGame().flood();
remainingFloods--;
//If no more floods remaining, jump to next state
if(remainingFloods == 0){
getGame().changeTurnState(new ActionState(getGame()));
}
}
}
If I understand you correctly, you are trying to implement some kind of State Pattern for you game. Instead of calling while(game.getGameState() == State.INCOMPLETE)
during each loop cycle, I recommend to move the game state logic entirely away from the main loop and have an abstract state class instead:
public abstract class AbstractGameState{
protected abstract void update(float delta);
}
Then you could have another abstract state class for your turns, that only executes the updates, if the game is not yet won. Otherwise it will end the game.
public abstract class TurnGameState extends AbstractGameState{
protected final void updateState(float delta){
if (isWinConditionSatisfied()) {
// end the game by setting the final state
TurnBasedGame.currentState = new EndGameState();
} else{
update(delta);
}
}
protected abstract void update(float delta);
private boolean isWinConditionSatisfied() {
// somehow check if the game is won
return false;
}
}
Each part of your turn-based logic can represent one TurnGameState
, that will be updated from the main game loop, and you have an additional EndGameState
, that is entered once the game is won:
public class EndGameState extends AbstractGameState{
@Override
protected void updateState(float delta) {
// this simple implementation just ends the game loop
TurnBasedGame.gameIsRunning = false;
}
}
Here is a very simplified game loop, that uses this concept:
public class TurnBasedGame {
private static boolean gameIsRunning;
private static AbstractGameState currentState;
public static void main(String[] args) {
while (gameIsRunning) {
// very simplified game loop...
// somehow handle the delta for the game loop...
float delta;
// update the game logic
doGameUpdates(delta);
// render game graphics here...
render();
}
}
private static void doGameUpdates(float delta) {
currentState.update(delta);
}
}
Of course, all the code I have posted is very simplified and just tries to illustrate the general concept. E.g. you may perhaps want to have a GameStateManager
class, that handles adding and removing of states, and you will of course have a more complex main game loop etc.
For an overview of the involved concepts, have a look at the following resources:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With