Battles in my game run fully automatically and return a combat log afterwards. My main game loop looks like this:
public BattleResult run() throws BattleException {
BattleResult result = null;
while((result = this.determineWinners()) == null) {
// TODO: execute the turn an move on to the next character
this.history.add(new BattleLog(this.currentState.copy()));
}
return result;
}
Basically, as long as the game isn't over determineWinners
will return null and the main loop continues. Once a win condition is reached (usually all characters of either one or both teams have died), the method wraps the battle history together with the winners and returns them, causing the loop to break.
The important part is handling each individual turn, which right now consists of several phases:
- turn start: set
state.currentCharacter
tostate.nextCharacter
andstate.playedCard
to null. If the current character is stunned, skip to the last phase - draw card: instruct the current characters deck to return the next card and store it in
state.playedCard
. If the deck is empty, null is returned, causing the character to die and skip to the last phase - play card: Retrieve the card from state and resolve its effects
- (not implemented yet) apply on-turn-end effects
- set
state.nextCharacter
to the next character in order or leave it as-is, if the current character gets to take another turn. Dead characters are skipped over
Right now, i'm implementing my turns like this:
public BattleResult run() throws BattleException {
BattleResult result = null;
while((result = this.determineWinners()) == null) {
Phase phase = this.startingPhase;
while(phase != null) {
phase = phase.execute(this.currentState, this.effectFactory);
}
this.history.add(new BattleLog(this.currentState.copy()));
}
return result;
}
@FunctionalInterface
public interface Phase {
public Phase execute(BattleState state, EffectFactory factory) throws BattleException;
}
public final class DefaultPhases {
public static final Phase TURN_START = new TurnStart();
public static final Phase DRAW_CARD = new DrawCard();
public static final Phase PLAY_CARD = new PlayCard();
// ...
public static final Phase SET_NEXT = new SetNext();
}
public class TurnStart implements Phase {
@Override
public Phase execute(BattleState state, EffectFactory factory) throws BattleException {
state.advanceCharacter();
state.setPlayedCard(null);
return DefaultPhases.DRAW_CARD;
}
}
public class DrawCard implements Phase {
@Override
public Phase execute(BattleState state, EffectFactory factory) throws BattleException {
CharacterState character = state.getCurrentCharacter();
Card card = character.drawCard();
if(card == null) {
character.setLife(0);
return DefaultPhases.SET_NEXT;
}
state.setPlayedCard(card);
return DefaultPhases.PLAY_CARD;
}
}
public class PlayCard implements Phase {
@Override
public Phase execute(BattleState state, EffectFactory factory) throws BattleException {
factory.getEffect(state.getPlayedCard().getId()).resolve(state);
return DefaultPhases.SET_NEXT;
}
}
public class SetNext implements Phase {
@Override
public Phase execute(BattleState state, EffectFactory factory) throws BattleException {
// some logic to determine the next character
return null;
}
}
So, every phase executes its logic (setting up the turn, drawing a card, playing it, etc) and returns the next one. Once a phase returns null, the turn ends, the current state is saved into history and the next turn begins (unless the game is over).
In general i like this system, as it allows me to dynamically skip or add phases, depending on whatever i need and i can also completely change the rules of a battle by setting startingPhase
to something else than DefaultPhases.TURN_START
.
But, since this is my first time designing a system like this, i would like to get some feedback about my design or if i should better use something different.