Question

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()));
        }
    }
}
Was it helpful?

Solution

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:

OTHER TIPS

I have no to few experience with game development. Hence consider this answer as a newbies view. Perhaps it will help to find the proper approach. The main idea is model a complex character turn by a class. The game loop does not care about simple or complex turns. Hence, in each step of the loop, it calls processTurn() to forward the game. The method processTurn() consults the CharacterState which tracks the state of the complex turn and forwards it one step. If a complex turn is completed, it returns true to indicate there is nothing left to do. The game loop continues to evaluate the new game state which results in a GameState. Then it renders the game world to refekct the characters turn. The game loop checks for each step if the game is over, and if, terminates.

public class Game implements Runnable {

    private GameWorld gameWorld;
    private CharacterState characterState;

    public Game(int value, Difficulty novice) {}

    @Override public void run() {
        while (gameWorld.getGameState() != GameState.COMPLETE) {
            Turn turn = processTurn();
            turn.apply(gameWorld);
            renderGame();
        }
    }

    /**
     * Processes the next step of a complex turn
     */
    private Turn processTurn() {
        Turn turn = characterState.processTurn();
        if (characterState.isDone()) {
            characterState = nextCharactersTurn();
        }
        return turn;
    }

    /**
     * Computes which character has to go next and returns its turn state beginning with the first
     * step of the complex turn.
     * 
     * @return the next characters complex turn
     */
    private CharacterState nextCharactersTurn() {
        throw new UnsupportedOperationException("not yet implemented");
    }

    /**
     * Renders the game world
     */
    private void renderGame() {
        throw new UnsupportedOperationException("not yet implemented");
    }
}

class GameWorld {

    /**
     * Returns the state of the game derived from the state of the world.
     * 
     * @return
     *      the game state
     */
    public GameState getGameState() {
        throw new UnsupportedOperationException("not yet implemented");
    }
}

/**
 * Tracks the complex state of each character turn
 */
class CharacterState {

    /**
     * Processes one step of a complex turn
     * 
     * @return the next turn
     */
    public Turn processTurn() {
        throw new UnsupportedOperationException("not yet implemented");
    }

    /**
     * @return true, if no more turn ar possible for this complex turn, otherwise false.
     */
    public boolean isDone() {
        throw new UnsupportedOperationException("not yet implemented");
    };
}

/**
 * An sequence of commands to be executed atomically.
 * 
 * Each sequence has to be composed of a minimal set of commands representing a transition from one
 * consistent game state to another consistent game state.
 */
class Turn {

    /**
     * Applies all changes encapsulated by this turn to the game world.
     * 
     * Applying the changes may change the game state as a side effect
     * 
     * @param world
     *      to apply the changes at
     */
    public void apply(GameWorld world) {}
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top