Move item building and dice roll logic out of CardGame

This commit is contained in:
Arne Keller 2020-03-23 10:49:27 +01:00
parent fe77053ac9
commit cc4e75f64e
4 changed files with 144 additions and 86 deletions

View File

@ -39,6 +39,21 @@ public enum Card implements RequireDice {
*/
THUNDERSTORM;
/**
* Return value indicating that the player successfully fought against an animal.
*
* @see #activate(CardGame, int, int)
* @see CardCategory#ANIMAL
*/
public static final String SURVIVED = "survived";
/**
* Return value indicating that the player lost against an animal.
*
* @see #activate(CardGame, int, int)
* @see CardCategory#ANIMAL
*/
public static final String LOSE = "lose";
/**
* Activate this card. Behaviour depends on the {@link #category category} of this card.
*
@ -50,7 +65,7 @@ public enum Card implements RequireDice {
game.givePlayerCard(this);
break;
case ANIMAL: // animal cards trigger an encounter
game.startEncounter(this);
game.startDiceRoll(this);
break;
case CATASTROPHE:
game.deletePlayerResources();
@ -61,6 +76,24 @@ public enum Card implements RequireDice {
}
}
@Override
public String activate(CardGame game, int diceSize, int roll) throws LogicException {
if (!this.minimumDiceRollNeeded().isPresent()) {
throw new IllegalStateException("can not process dice roll");
} else if (this.diceSizeNeeded().get() != diceSize) {
throw new LogicException("unexpected dice size");
} else if (roll > diceSize || roll < 1) {
throw new LogicException("impossible roll");
}
if (roll + game.getFightingBonus() >= this.minimumDiceRollNeeded().get()) {
return SURVIVED;
} else {
game.deletePlayerResources();
return LOSE;
}
}
/**
* Get the category of this card.
*

View File

@ -28,29 +28,6 @@ public class CardGame {
* @see #build(Item)
*/
public static final String OK = "OK";
/**
* Return value indicating that the player won.
*
* @see #build(Item)
* @see #rollDice(int, int)
* @see ItemCategory#ESCAPE
*/
public static final String WIN = "win";
/**
* Return value indicating that the player successfully fought against an animal.
*
* @see #rollDice(int, int)
* @see CardCategory#ANIMAL
*/
public static final String SURVIVED = "survived";
/**
* Return value indicating that the player lost against an animal or failed to escape.
*
* @see #rollDice(int, int)
* @see CardCategory#ANIMAL
* @see ItemCategory#ESCAPE
*/
public static final String LOSE = "lose";
/**
* Card stack used.
@ -120,16 +97,16 @@ public class CardGame {
}
/**
* Start a new encounter. {@link #rollDice} has to be used next.
* Wait for a dice roll to activate the specified object. {@link #rollDice} has to be used next.
*
* @param card (animal) card
* @param requireDice something that requires a dice roll
*/
public void startEncounter(Card card) {
if (!card.diceSizeNeeded().isPresent()) {
throw new IllegalArgumentException("card does not require dice");
public void startDiceRoll(RequireDice requireDice) {
if (!requireDice.diceSizeNeeded().isPresent()) {
throw new IllegalArgumentException("object does not require dice");
}
requireDice = card;
phase = CardGame.Phase.ENCOUNTER;
this.requireDice = requireDice;
phase = Phase.AWAITING_DICE_ROLL;
}
/**
@ -156,51 +133,38 @@ public class CardGame {
*
* @param size dice size
* @param roll dice result
* @return result of the throw ({@link #SURVIVED survived}, {@link #LOSE lose} or {@link #WIN win})
* @return result of the dice roll (see {@link RequireDice#activate})
* @throws LogicException if not expecting dice roll or dice roll is invalid
*/
public String rollDice(int size, int roll) throws LogicException {
if (requireDice == null) {
throw new LogicException("not expecting dice roll");
}
// compare with currently expected dice
final int sizeNeeded = requireDice.diceSizeNeeded().get();
final int minimumNeeded = requireDice.minimumDiceRollNeeded().get();
if (sizeNeeded != size) {
} else if (requireDice.diceSizeNeeded().get() != size) {
throw new LogicException("unexpected dice size");
} else if (roll > sizeNeeded || roll < 1) {
} else if (roll > size || roll < 1) {
throw new LogicException("impossible roll");
}
// no longer waiting for dice roll
// leave encounter/endeavor phase
phase = Phase.SCAVENGE;
// compute the result of the dice roll
final String result = requireDice.activate(this, size, roll);
// no longer waiting
requireDice = null;
if (phase == Phase.ENCOUNTER) {
// calculate fighting bonus, selecting the most powerful item the player owns
final int bonus = inventory.getItems().stream().mapToInt(Item::fightingBonus).max().orElse(0);
phase = Phase.SCAVENGE;
if (roll + bonus >= minimumNeeded) {
return SURVIVED;
} else {
inventory.clearResources();
return LOSE;
}
} else { // attempting to escape
// do not remove item used to escape
if (roll >= minimumNeeded) {
endGame();
phase = Phase.WON;
return WIN;
} else {
phase = Phase.SCAVENGE;
return LOSE;
}
}
return result;
}
/**
* @return the player's fighting bonus against {@link CardCategory#ANIMAL animals}
*/
public int getFightingBonus() {
return inventory.getItems().stream().mapToInt(Item::fightingBonus).max().orElse(0);
}
/**
* Attempt to build the specified item.
*
* @param item item to build
* @return building result ({@link #OK} or {@link #WIN win}, or null if item can not be built
* @return building result (see {@link Item#activate(CardGame)}), or null if item can not be built
* @throws LogicException if in wrong phase or item already built
*/
public String build(Item item) throws LogicException {
@ -214,22 +178,18 @@ public class CardGame {
if (!inventory.build(item)) {
throw new LogicException("can not build item: missing resources/items");
}
if (item.category() == ItemCategory.ESCAPE) {
if (item.diceSizeNeeded().isPresent()) {
// player needs to roll dice to escape
requireDice = item;
phase = Phase.ENDEAVOR;
} else {
// player won
endGame();
phase = Phase.WON;
return WIN;
}
}
return OK;
return item.activate(this);
}
}
/**
* End the game by forcing the player to win.
*/
public void winGame() {
endGame();
phase = Phase.WON;
}
/**
* Check whether this game ever received a {@link #start start} command.
*
@ -254,10 +214,12 @@ public class CardGame {
* @see #gameLost
*/
private void checkLost() {
// can not draw new cards
if (cardStack != null && cardStack.isEmpty()
// obviously, player can only lose after starting the game
if (gameStarted()
// can not draw new cards
&& (cardStack == null || cardStack.isEmpty())
// can not roll dice
&& phase != Phase.ENCOUNTER && phase != Phase.ENDEAVOR
&& phase != Phase.AWAITING_DICE_ROLL
// can not build item
&& Arrays.stream(Item.values()).noneMatch(inventory::canBuild)
&& phase != Phase.WON) {
@ -359,19 +321,12 @@ public class CardGame {
*/
SCAVENGE,
/**
* Player has to fight an animal.
* Player has to fight an animal or attempt to escape using {@link #rollDice(int, int)}.
*
* @see #rollDice
* @see CardCategory#ANIMAL
*/
ENCOUNTER,
/**
* Player is attempting to escape.
*
* @see #rollDice
* @see ItemCategory#ESCAPE
*/
ENDEAVOR,
AWAITING_DICE_ROLL,
/**
* Player won.
*/

View File

@ -49,6 +49,65 @@ public enum Item implements RequireDice {
*/
BALLON;
/**
* Return value indicating success.
*
* @see #activate
*/
public static final String OK = "OK";
/**
* Return value indicating that the player won.
*
* @see #activate
* @see ItemCategory#ESCAPE
*/
public static final String WIN = "win";
/**
* Return value indicating that the player failed to escape.
*
* @see #activate(CardGame, int, int)
* @see ItemCategory#ESCAPE
*/
public static final String LOSE = "lose";
/**
* Activate this item on the specified game.
*
* @param game card game to use
* @return result (either {@link #OK} or {@link #WIN})
*/
public String activate(CardGame game) {
if (this.category() == ItemCategory.ESCAPE) {
if (this.diceSizeNeeded().isPresent()) {
// player needs to roll dice to escape
game.startDiceRoll(this);
} else {
// player won
game.winGame();
return WIN;
}
}
return OK;
}
@Override
public String activate(CardGame game, int diceSize, int roll) throws LogicException {
if (!this.minimumDiceRollNeeded().isPresent()) {
throw new IllegalStateException("can not process dice roll");
} else if (this.diceSizeNeeded().get() != diceSize) {
throw new LogicException("unexpected dice size");
} else if (roll > diceSize || roll < 1) {
throw new LogicException("impossible roll");
}
if (roll >= this.minimumDiceRollNeeded().get()) {
game.winGame();
return WIN;
} else {
return LOSE;
}
}
/**
* Get the resources needed to build this item, in no particular order.
*

View File

@ -22,4 +22,15 @@ public interface RequireDice {
* @return minimum dice roll needed to use this item (empty if dice not required)
*/
Optional<Integer> minimumDiceRollNeeded();
/**
* Activate this object in a game, if the dice roll is good enough.
*
* @param game card game to use
* @param diceSize size of the dice rolled
* @param rolled result of the dice roll
* @return activation result
* @throws LogicException if dice roll is incorrect
*/
String activate(CardGame game, int diceSize, int rolled) throws LogicException;
}