refactor: restructure code and fix some events not being applied

This commit is contained in:
punchready 2021-06-03 02:19:10 +02:00
parent e0af4cf01c
commit 3bb6d81b27
4 changed files with 371 additions and 364 deletions

View File

@ -27,7 +27,7 @@ public class GameInstance {
/** Constructs a new {@link GameInstance}. */ /** Constructs a new {@link GameInstance}. */
public GameInstance(PartyConfig partyConfig, CharacterConfig characterConfig, ScenarioConfig scenarioConfig) { public GameInstance(PartyConfig partyConfig, CharacterConfig characterConfig, ScenarioConfig scenarioConfig) {
_state = new GameState(partyConfig, characterConfig, scenarioConfig); _state = new GameState(partyConfig, characterConfig, scenarioConfig);
this.state = new GameStateView(_state); state = new GameStateView(_state);
manager = new GameStateManager(_state); manager = new GameStateManager(_state);
} }
@ -36,16 +36,11 @@ public class GameInstance {
* @param requests The requests to check * @param requests The requests to check
* @return The list of resulting {@link Event}s or `null` if the check failed * @return The list of resulting {@link Event}s or `null` if the check failed
*/ */
public Optional<List<Event>> checkRequestsAndApply(Request... requests) { public Optional<List<Event>> checkRequestsAndApply(ArrayList<Request> requests) {
if(manager.processRequests(requests, true)) { if(manager.processRequests(requests, true)) {
ArrayList<Event> result = manager.apply(); ArrayList<Event> result = manager.apply();
ArrayList<Event> result2 = manager.checkPostPhase(); result.addAll(manager.checkPostPhase());
manager.applyEvents(result2);
result.addAll(result2);
result.add(GameLogic.buildGameStateEvent(_state));
return Optional.of(result); return Optional.of(result);
} }
@ -58,44 +53,10 @@ public class GameInstance {
* @param requests The requests to check * @param requests The requests to check
* @return Whether or not the given set of requests is valid * @return Whether or not the given set of requests is valid
*/ */
public boolean checkRequests(Request... requests) { public boolean checkRequests(ArrayList<Request> requests) {
return manager.processRequests(requests, false); return manager.processRequests(requests, false);
} }
/**
* Initializes and starts the game. Selected characters are given as a list of indices from the {@link CharacterConfig#characters} array.
* @param selectedCharacters1 The characters selected by player 1
* @param selectedCharacters2 The characters selected by player 2
* @return The list of resulting {@link Event}s
*/
public ArrayList<Event> startGame(ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) {
ArrayList<Event> result = manager.initGame(selectedCharacters1, selectedCharacters2);
manager.applyEvents(result);
ArrayList<Event> result2 = manager.startGame();
manager.applyEvents(result2);
result.addAll(result2); //do not merge this, result needs to be applied before startGame is called
return result;
}
/**
* Produces a {@link EventType#GamestateEvent} for the current {@link GameState}.
* @return The resulting event
*/
public Event getGameStateEvent() {
return GameLogic.buildGameStateEvent(_state);
}
/**
* Restores the current {@link GameState} based on the given one.
* @param state The state to restore from
*/
public void setGameState(GameState state) {
_state.restore(state);
}
/** /**
* Applies an array of {@link Event}s to the game state. * Applies an array of {@link Event}s to the game state.
* @param events The events to apply. * @param events The events to apply.
@ -104,8 +65,30 @@ public class GameInstance {
manager.applyEvents(events); manager.applyEvents(events);
} }
protected GameState getState() { /**
return _state; * Initializes and starts the game. Selected characters are given as a list of indices from the {@link CharacterConfig#characters} array.
* @param selectedCharacters1 The characters selected by player 1
* @param selectedCharacters2 The characters selected by player 2
* @return The list of resulting {@link Event}s
*/
public ArrayList<Event> startGame(ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) {
return manager.startGame(selectedCharacters1, selectedCharacters2);
}
/**
* Produces a {@link EventType#GamestateEvent} for the current {@link GameState}.
* @return The resulting event
*/
public Event getGameStateEvent() {
return manager.getGameStateEvent();
}
/**
* Restores the current {@link GameState} based on the given {@link EventType#GamestateEvent}.
* @param gameStateEvent The event to restore from
*/
public void applyGameStateEvent(Event gameStateEvent) {
manager.applyEvent(gameStateEvent);
} }
@Override @Override

View File

@ -19,199 +19,16 @@ import java.util.HashSet;
import java.util.Random; import java.util.Random;
/** Contains game logic handling. */ /** Contains game logic handling. */
class GameLogic { public class GameLogic {
private static final Random rand = new Random(); private static final Random rand = new Random();
/**
* Produces resulting {@link Event}s from a given {@link Request}.
* @param state The game state to execute on
* @param request The request to execute
* @return The list of resulting events
*/
public static ArrayList<Event> executeRequest(GameState state, Request request) {
ArrayList<Event> result = new ArrayList<>();
switch(request.type) {
case MeleeAttackRequest, RangedAttackRequest -> {
CharacterRequest data = (CharacterRequest)request;
result.add(new EventBuilder(request.type == RequestType.MeleeAttackRequest ? EventType.MeleeAttackEvent : EventType.RangedAttackEvent)
.withOriginEntity(data.originEntity)
.withTargetEntity(data.targetEntity)
.withOriginField(data.originField)
.withTargetField(data.targetField)
.withAmount(data.value)
.buildCharacterEvent());
result.add(new EventBuilder(EventType.ConsumedAPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(1)
.buildEntityEvent());
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withAmount(data.value)
.buildEntityEvent());
}
case MoveRequest -> {
CharacterRequest data = (CharacterRequest)request;
result.add(new EventBuilder(EventType.MoveEvent)
.withOriginEntity(data.originEntity)
.withOriginField(data.originField)
.withTargetField(data.targetField)
.buildCharacterEvent());
result.add(new EventBuilder(EventType.ConsumedMPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.targetField) //when this event gets handled, the character already moved to the target field
.withAmount(1)
.buildEntityEvent());
for(Entity entity: state.entities.findByPosition(data.targetField)) {
if(entity instanceof Character) {
result.add(new EventBuilder(EventType.MoveEvent)
.withOriginEntity(entity.id)
.withOriginField(data.targetField)
.withTargetField(data.originField)
.buildCharacterEvent());
break; //we should only have one entity per field anyways
}else if(entity instanceof InfinityStone) {
result.add(new EventBuilder(EventType.DestroyedEntityEvent)
.withTargetField(data.targetField)
.withTargetEntity(entity.id)
.buildEntityEvent());
break; //we should only have one entity per field anyways
}
}
}
case ExchangeInfinityStoneRequest, UseInfinityStoneRequest -> {
CharacterRequest data = (CharacterRequest)request;
result.add(new EventBuilder(request.type == RequestType.ExchangeInfinityStoneRequest ? EventType.ExchangeInfinityStoneEvent : EventType.UseInfinityStoneEvent)
.withOriginEntity(data.originEntity)
.withOriginField(data.originField)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withStoneType(data.stoneType)
.buildCharacterEvent());
result.add(new EventBuilder(EventType.ConsumedAPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(1)
.buildEntityEvent());
if(request.type == RequestType.UseInfinityStoneRequest) {
switch(((CharacterRequest) request).stoneType) {
case SpaceStone -> {
result.add(new EventBuilder(EventType.MoveEvent)
.withOriginEntity(data.originEntity)
.withOriginField(data.originField)
.withTargetField(data.targetField)
.buildCharacterEvent());
}
case MindStone -> {
EntityType target = data.originEntity.type == EntityType.P1 ? EntityType.P2 : EntityType.P1;
for(IntVector2 pos: rasterize(data.originField, data.targetField, false, true)) {
for(Entity entity: state.entities.findByPosition(pos)) {
if(entity.id.isSameType(target)) {
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(entity.id)
.withTargetField(pos)
.withAmount(data.value)
.buildEntityEvent());
}
}
}
}
case RealityStone -> {
if(data.originEntity == data.targetEntity) { // => place stone
result.add(new EventBuilder(EventType.SpawnEntityEvent)
.withTargetField(data.targetField)
.withEntity(new Rock(new EntityID(EntityType.Rocks, state.entities.findFreeRockSlot()), data.targetField, 100))
.buildEntityEvent());
}else { // => destroy stone
result.add(new EventBuilder(EventType.DestroyedEntityEvent)
.withTargetField(data.targetField)
.withTargetEntity(data.targetEntity)
.buildEntityEvent());
}
}
case PowerStone -> {
Character origin = (Character)state.entities.findEntity(data.originEntity);
int dmg = (int)Math.round(origin.hp.getMax() * 0.1);
if(origin.hp.getValue() != 1 && dmg > 0) {
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(dmg)
.buildEntityEvent());
}
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withAmount(data.value)
.buildEntityEvent());
}
case TimeStone -> {
Character origin = (Character)state.entities.findEntity(data.originEntity);
int ap = origin.ap.getValue() - origin.ap.getMax();
if(ap < 0) {
result.add(new EventBuilder(EventType.ConsumedAPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(ap)
.buildEntityEvent());
}
int mp = origin.mp.getValue() - origin.mp.getMax();
if(mp < 0) {
result.add(new EventBuilder(EventType.ConsumedMPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(mp)
.buildEntityEvent());
}
}
case SoulStone -> {
Character target = (Character)state.entities.findEntity(data.targetEntity);
result.add(new EventBuilder(EventType.HealedEvent)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withAmount(target.hp.getMax())
.buildEntityEvent());
}
}
}
}
case EndRoundRequest -> {
result.addAll(handleTurnEnd(state)); //why is it called end round request when it ends a turn...
}
case Req -> {
result.add(buildGameStateEvent(state));
}
}
return result;
}
/**
* Builds a {@link EventType#GamestateEvent} for the given {@link GameState}.
* @param state The game state to use
* @return The resulting event
*/
public static Event buildGameStateEvent(GameState state) {
return new EventBuilder(EventType.GamestateEvent)
.withEntities(state.entities.export())
.withTurnOrder(state.turnOrder.toArray(new EntityID[0]))
.withMapSize(state.mapSize)
.withActiveCharacter(state.activeCharacter)
.withStoneCooldowns(state.stoneCooldown.export())
.withWinCondition(state.won)
.buildGameStateEvent();
}
/** /**
* Checks a {@link Request} for validity for a {@link GameState}. * Checks a {@link Request} for validity for a {@link GameState}.
* @param state The game state to check on * @param state The game state to check on
* @param request The request to validate * @param request The request to validate
* @return Whether or not the request is valid * @return Whether or not the request is valid
*/ */
public static boolean checkRequest(GameState state, Request request) { protected static boolean checkRequest(GameState state, Request request) {
try { try {
switch(request.type) { switch(request.type) {
case MeleeAttackRequest, RangedAttackRequest -> { case MeleeAttackRequest, RangedAttackRequest -> {
@ -478,74 +295,181 @@ class GameLogic {
} }
} }
/** /**
* Checks if a line of sight exists between the two positions * Produces resulting {@link Event}s from a given {@link Request}.
* @param state The game state to work on * @param state The game state to execute on
* @param start The first position * @param request The request to execute
* @param end The second position * @return The list of resulting events
* @return Whether or not the light of sight exists
*/ */
private static boolean checkLineOfSight(GameState state, IntVector2 start, IntVector2 end) { protected static ArrayList<Event> executeRequest(GameState state, Request request) {
for(IntVector2 pos: rasterize(start, end, false, false)) { ArrayList<Event> result = new ArrayList<>();
if(state.entities.blocksVision(pos)) {
return false; switch(request.type) {
case MeleeAttackRequest, RangedAttackRequest -> {
CharacterRequest data = (CharacterRequest)request;
result.add(new EventBuilder(request.type == RequestType.MeleeAttackRequest ? EventType.MeleeAttackEvent : EventType.RangedAttackEvent)
.withOriginEntity(data.originEntity)
.withTargetEntity(data.targetEntity)
.withOriginField(data.originField)
.withTargetField(data.targetField)
.withAmount(data.value)
.buildCharacterEvent());
result.add(new EventBuilder(EventType.ConsumedAPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(1)
.buildEntityEvent());
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withAmount(data.value)
.buildEntityEvent());
} }
} case MoveRequest -> {
return true; CharacterRequest data = (CharacterRequest)request;
} result.add(new EventBuilder(EventType.MoveEvent)
.withOriginEntity(data.originEntity)
/** .withOriginField(data.originField)
* Finds free neighbour options from a starting field. .withTargetField(data.targetField)
* @param state The game state to work on .buildCharacterEvent());
* @param start The starting position result.add(new EventBuilder(EventType.ConsumedMPEvent)
* @return A list of free neighbour field options .withTargetEntity(data.originEntity)
*/ .withTargetField(data.targetField) //when this event gets handled, the character already moved to the target field
private static ArrayList<IntVector2> getFreeNeighbour(GameState state, IntVector2 start) { .withAmount(1)
ArrayList<IntVector2> options = new ArrayList<>(); .buildEntityEvent());
for(Entity entity: state.entities.findByPosition(data.targetField)) {
if(start.getX() < 0 || start.getX() >= state.mapSize.getX() || start.getY() < 0 || start.getY() >= state.mapSize.getY()) { if(entity instanceof Character) {
return options; result.add(new EventBuilder(EventType.MoveEvent)
} .withOriginEntity(entity.id)
.withOriginField(data.targetField)
for(IntVector2 dir: IntVector2.CardinalDirections) { .withTargetField(data.originField)
if(state.entities.findByPosition(start.add(dir)).size() == 0) { .buildCharacterEvent());
options.add(start.add(dir)); break; //we should only have one entity per field anyways
} }else if(entity instanceof InfinityStone) {
} result.add(new EventBuilder(EventType.DestroyedEntityEvent)
.withTargetField(data.targetField)
if(options.size() == 0) { .withTargetEntity(entity.id)
return getFreeNeighbour(state, start.add(IntVector2.CardinalDirections[rand.nextInt(IntVector2.CardinalDirections.length)])); .buildEntityEvent());
}else { break; //we should only have one entity per field anyways
return options; }
}
}
/**
* Finds free field options.
* @param state The game state to work on
* @return A list of free field options
*/
private static ArrayList<IntVector2> getFreeFields(GameState state) {
ArrayList<IntVector2> options = new ArrayList<>();
for(int x = 0; x < state.mapSize.getX(); x++) {
for(int y = 0; y < state.mapSize.getY(); y++) {
IntVector2 pos = new IntVector2(x, y);
if(state.entities.findByPosition(pos).size() == 0) {
options.add(pos);
} }
} }
case ExchangeInfinityStoneRequest, UseInfinityStoneRequest -> {
CharacterRequest data = (CharacterRequest)request;
result.add(new EventBuilder(request.type == RequestType.ExchangeInfinityStoneRequest ? EventType.ExchangeInfinityStoneEvent : EventType.UseInfinityStoneEvent)
.withOriginEntity(data.originEntity)
.withOriginField(data.originField)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withStoneType(data.stoneType)
.buildCharacterEvent());
result.add(new EventBuilder(EventType.ConsumedAPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(1)
.buildEntityEvent());
if(request.type == RequestType.UseInfinityStoneRequest) {
switch(((CharacterRequest) request).stoneType) {
case SpaceStone -> {
result.add(new EventBuilder(EventType.MoveEvent)
.withOriginEntity(data.originEntity)
.withOriginField(data.originField)
.withTargetField(data.targetField)
.buildCharacterEvent());
}
case MindStone -> {
EntityType target = data.originEntity.type == EntityType.P1 ? EntityType.P2 : EntityType.P1;
for(IntVector2 pos: rasterize(data.originField, data.targetField, false, true)) {
for(Entity entity: state.entities.findByPosition(pos)) {
if(entity.id.isSameType(target)) {
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(entity.id)
.withTargetField(pos)
.withAmount(data.value)
.buildEntityEvent());
}
}
}
}
case RealityStone -> {
if(data.originEntity == data.targetEntity) { // => place stone
result.add(new EventBuilder(EventType.SpawnEntityEvent)
.withTargetField(data.targetField)
.withEntity(new Rock(new EntityID(EntityType.Rocks, state.entities.findFreeRockSlot()), data.targetField, 100))
.buildEntityEvent());
}else { // => destroy stone
result.add(new EventBuilder(EventType.DestroyedEntityEvent)
.withTargetField(data.targetField)
.withTargetEntity(data.targetEntity)
.buildEntityEvent());
}
}
case PowerStone -> {
Character origin = (Character)state.entities.findEntity(data.originEntity);
int dmg = (int)Math.round(origin.hp.getMax() * 0.1);
if(origin.hp.getValue() != 1 && dmg > 0) {
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(dmg)
.buildEntityEvent());
}
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withAmount(data.value)
.buildEntityEvent());
}
case TimeStone -> {
Character origin = (Character)state.entities.findEntity(data.originEntity);
int ap = origin.ap.getValue() - origin.ap.getMax();
if(ap < 0) {
result.add(new EventBuilder(EventType.ConsumedAPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(ap)
.buildEntityEvent());
}
int mp = origin.mp.getValue() - origin.mp.getMax();
if(mp < 0) {
result.add(new EventBuilder(EventType.ConsumedMPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(mp)
.buildEntityEvent());
}
}
case SoulStone -> {
Character target = (Character)state.entities.findEntity(data.targetEntity);
result.add(new EventBuilder(EventType.HealedEvent)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withAmount(target.hp.getMax())
.buildEntityEvent());
}
}
}
}
case EndRoundRequest -> {
result.addAll(handleTurnEnd(state)); //why is it called end round request when it ends a turn...
}
case Req -> {
result.add(buildGameStateEvent(state));
}
} }
return options; return result;
} }
/** /**
* Applies an {@link Event} to a {@link GameState}. * Applies an {@link Event} to a {@link GameState}.
* @param state The game state to apply to * @param state The game state to apply to
* @param event The event to apply * @param event The event to apply
*/ */
public static void applyEvent(GameState state, Event event) { protected static void applyEvent(GameState state, Event event) {
switch(event.type) { switch(event.type) {
case DestroyedEntityEvent -> { case DestroyedEntityEvent -> {
state.entities.removeEntity(((EntityEvent)event).targetEntity); state.entities.removeEntity(((EntityEvent)event).targetEntity);
@ -596,6 +520,87 @@ class GameLogic {
} }
} }
/**
* Builds a {@link EventType#GamestateEvent} for the given {@link GameState}.
* @param state The game state to use
* @return The resulting event
*/
public static Event buildGameStateEvent(GameState state) {
return new EventBuilder(EventType.GamestateEvent)
.withEntities(state.entities.export())
.withTurnOrder(state.turnOrder.toArray(new EntityID[0]))
.withMapSize(state.mapSize)
.withActiveCharacter(state.activeCharacter)
.withStoneCooldowns(state.stoneCooldown.export())
.withWinCondition(state.won)
.buildGameStateEvent();
}
/**
* Checks if a line of sight exists between the two positions
* @param state The game state to work on
* @param start The first position
* @param end The second position
* @return Whether or not the light of sight exists
*/
public static boolean checkLineOfSight(GameState state, IntVector2 start, IntVector2 end) {
for(IntVector2 pos: rasterize(start, end, false, false)) {
if(state.entities.blocksVision(pos)) {
return false;
}
}
return true;
}
/**
* Finds free neighbour options from a starting field.
* @param state The game state to work on
* @param start The starting position
* @return A list of free neighbour field options
*/
public static ArrayList<IntVector2> getFreeNeighbour(GameState state, IntVector2 start) {
ArrayList<IntVector2> options = new ArrayList<>();
if(start.getX() < 0 || start.getX() >= state.mapSize.getX() || start.getY() < 0 || start.getY() >= state.mapSize.getY()) {
return options;
}
for(IntVector2 dir: IntVector2.CardinalDirections) {
if(state.entities.findByPosition(start.add(dir)).size() == 0) {
options.add(start.add(dir));
}
}
if(options.size() == 0) {
return getFreeNeighbour(state, start.add(IntVector2.CardinalDirections[rand.nextInt(IntVector2.CardinalDirections.length)]));
}else {
return options;
}
}
/**
* Finds free field options.
* @param state The game state to work on
* @return A list of free field options
*/
public static ArrayList<IntVector2> getFreeFields(GameState state) {
ArrayList<IntVector2> options = new ArrayList<>();
for(int x = 0; x < state.mapSize.getX(); x++) {
for(int y = 0; y < state.mapSize.getY(); y++) {
IntVector2 pos = new IntVector2(x, y);
if(state.entities.findByPosition(pos).size() == 0) {
options.add(pos);
}
}
}
return options;
}
/** /**
* Starts the game and initializes all entities. * Starts the game and initializes all entities.
* @param state The game state to work on * @param state The game state to work on
@ -603,7 +608,7 @@ class GameLogic {
* @param selectedCharacters2 The characters selected by player 2 * @param selectedCharacters2 The characters selected by player 2
* @return The list of resulting {@link Event}s * @return The list of resulting {@link Event}s
*/ */
public static ArrayList<Event> startGame(GameState state, ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) { protected static ArrayList<Event> startGame(GameState state, ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) {
ArrayList<Event> result = new ArrayList<>(); ArrayList<Event> result = new ArrayList<>();
ArrayList<IntVector2> free = new ArrayList<>(); ArrayList<IntVector2> free = new ArrayList<>();
@ -652,12 +657,21 @@ class GameLogic {
return result; return result;
} }
/**
* Forcefully starts the next round.
* @param state The game state to work on
* @return The list of resulting {@link Event}s
*/
protected static ArrayList<Event> startRound(GameState state) {
return handleRoundStart(state);
}
/** /**
* Starts end of round handling if necessary. * Starts end of round handling if necessary.
* @param state The game state to work on * @param state The game state to work on
* @return The list of resulting {@link Event}s * @return The list of resulting {@link Event}s
*/ */
public static ArrayList<Event> checkTurnEnd(GameState state) { protected static ArrayList<Event> checkTurnEnd(GameState state) {
if( if(
((Character) state.entities.findEntity(state.activeCharacter)).ap.getValue() <= 0 && ((Character) state.entities.findEntity(state.activeCharacter)).ap.getValue() <= 0 &&
((Character) state.entities.findEntity(state.activeCharacter)).mp.getValue() <= 0 ((Character) state.entities.findEntity(state.activeCharacter)).mp.getValue() <= 0
@ -668,12 +682,22 @@ class GameLogic {
return new ArrayList<>(); return new ArrayList<>();
} }
/**
* Forcefully ends the current turn.
* @param state The game state to work on
* @return The list of resulting {@link Event}s
*/
protected static ArrayList<Event> endTurn(GameState state) {
return handleTurnEnd(state);
}
/** /**
* Handles everything that happens at the end of a turn, including new rounds. * Handles everything that happens at the end of a turn, including new rounds.
* @param state The game state to work on * @param state The game state to work on
* @return The list of resulting {@link Event}s * @return The list of resulting {@link Event}s
*/ */
public static ArrayList<Event> handleTurnEnd(GameState state) { private static ArrayList<Event> handleTurnEnd(GameState state) {
ArrayList<Event> result = new ArrayList<>(); ArrayList<Event> result = new ArrayList<>();
ArrayList<EntityID> alive = new ArrayList<>(); ArrayList<EntityID> alive = new ArrayList<>();
@ -721,7 +745,7 @@ class GameLogic {
* @param state The game state to work on * @param state The game state to work on
* @return The list of resulting {@link Event}s * @return The list of resulting {@link Event}s
*/ */
public static ArrayList<Event> handleRoundStart(GameState state) { private static ArrayList<Event> handleRoundStart(GameState state) {
ArrayList<Event> result = new ArrayList<>(); ArrayList<Event> result = new ArrayList<>();
state.roundNumber++; state.roundNumber++;
@ -764,7 +788,7 @@ class GameLogic {
* @param state The game state to work on * @param state The game state to work on
* @return The list of resulting {@link Event}s * @return The list of resulting {@link Event}s
*/ */
public static ArrayList<Event> handleGoose(GameState state) { private static ArrayList<Event> handleGoose(GameState state) {
ArrayList<Event> result = new ArrayList<>(); ArrayList<Event> result = new ArrayList<>();
ArrayList<StoneType> inventory = new ArrayList<>(state.unvomitedStones); ArrayList<StoneType> inventory = new ArrayList<>(state.unvomitedStones);
@ -804,7 +828,7 @@ class GameLogic {
* @param state The game state to work on * @param state The game state to work on
* @return The list of resulting {@link Event}s * @return The list of resulting {@link Event}s
*/ */
public static ArrayList<Event> handleStan(GameState state, HashSet<EntityID> revived) { private static ArrayList<Event> handleStan(GameState state, HashSet<EntityID> revived) {
ArrayList<Event> result = new ArrayList<>(); ArrayList<Event> result = new ArrayList<>();
ArrayList<Character> characters = new ArrayList<>(); ArrayList<Character> characters = new ArrayList<>();
@ -864,7 +888,7 @@ class GameLogic {
* @param state The game state to work on * @param state The game state to work on
* @return The list of resulting {@link Event}s * @return The list of resulting {@link Event}s
*/ */
public static ArrayList<Event> spawnThanos(GameState state) { private static ArrayList<Event> spawnThanos(GameState state) {
ArrayList<Event> result = new ArrayList<>(); ArrayList<Event> result = new ArrayList<>();
ArrayList<IntVector2> free = getFreeFields(state); ArrayList<IntVector2> free = getFreeFields(state);
@ -894,7 +918,7 @@ class GameLogic {
* @param state The game state to work on * @param state The game state to work on
* @return The list of resulting {@link Event}s * @return The list of resulting {@link Event}s
*/ */
public static ArrayList<Event> handleThanos(GameState state, NPC thanos) { private static ArrayList<Event> handleThanos(GameState state, NPC thanos) {
ArrayList<Event> result = new ArrayList<>(); ArrayList<Event> result = new ArrayList<>();
if(thanos.inventory.getFreeSlots() > 0) { if(thanos.inventory.getFreeSlots() > 0) {
@ -1009,7 +1033,7 @@ class GameLogic {
* @param state The game state to work on * @param state The game state to work on
* @return The list of resulting {@link Event}s * @return The list of resulting {@link Event}s
*/ */
public static ArrayList<Event> handleTurnStart(GameState state) { private static ArrayList<Event> handleTurnStart(GameState state) {
ArrayList<Event> result = new ArrayList<>(); ArrayList<Event> result = new ArrayList<>();
state.turnNumber++; state.turnNumber++;
@ -1068,7 +1092,7 @@ class GameLogic {
* @param winner The winning character * @param winner The winning character
* @return The list of resulting {@link Event}s * @return The list of resulting {@link Event}s
*/ */
public static ArrayList<Event> handlePlayerWin(GameState state, EntityType winner) { private static ArrayList<Event> handlePlayerWin(GameState state, EntityType winner) {
ArrayList<Event> result = new ArrayList<>(); ArrayList<Event> result = new ArrayList<>();
state.won = true; state.won = true;
@ -1080,6 +1104,7 @@ class GameLogic {
return result; return result;
} }
/** /**
* Computes all fields which intersect the line between the center points of the given two fields including the two defining points. * Computes all fields which intersect the line between the center points of the given two fields including the two defining points.
* @return The list of intersecting positions * @return The list of intersecting positions

View File

@ -1,9 +1,9 @@
package uulm.teamname.marvelous.gamelibrary.gamelogic; package uulm.teamname.marvelous.gamelibrary.gamelogic;
import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.events.Event;
import uulm.teamname.marvelous.gamelibrary.events.EventType;
import uulm.teamname.marvelous.gamelibrary.requests.Request; import uulm.teamname.marvelous.gamelibrary.requests.Request;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
/** Represents manager for a game state. */ /** Represents manager for a game state. */
@ -12,7 +12,7 @@ class GameStateManager {
private final GameState state; private final GameState state;
/** The queue of {@link Event}s to be applied during {@link Request} processing */ /** The queue of {@link Event}s to be applied during {@link Request} processing */
private final ArrayDeque<Event> queue = new ArrayDeque<>(); private final ArrayList<Event> queue = new ArrayList<>();
/** /**
* Constructs a new {@link GameStateManager}. * Constructs a new {@link GameStateManager}.
@ -26,13 +26,17 @@ class GameStateManager {
* Checks a list of {@link Request}s for validity and optionally produces resulting {@link Event}s. * Checks a list of {@link Request}s for validity and optionally produces resulting {@link Event}s.
* @param requests The requests to check * @param requests The requests to check
* @param apply True if resulting events should be stored for later application * @param apply True if resulting events should be stored for later application
* @return Whether the requests are valid
*/ */
public boolean processRequests(Request[] requests, boolean apply) { public boolean processRequests(ArrayList<Request> requests, boolean apply) {
GameState snapshot = state.snapshot(); GameState snapshot = state.snapshot();
for(Request request: requests) { for(Request request: requests) {
if (GameLogic.checkRequest(snapshot, request)) { if (GameLogic.checkRequest(snapshot, request)) {
ArrayList<Event> result = GameLogic.executeRequest(snapshot, request); ArrayList<Event> result = GameLogic.executeRequest(snapshot, request);
for(Event event: result) {
GameLogic.applyEvent(snapshot, event);
}
if(apply) { if(apply) {
queue.addAll(result); queue.addAll(result);
} }
@ -45,32 +49,6 @@ class GameStateManager {
return true; return true;
} }
/**
* Handles events that happen after a turn phase.
* @return The optionally resulting {@link Event}s
*/
public ArrayList<Event> checkPostPhase() {
return GameLogic.checkTurnEnd(state);
}
/**
* Initializes the game.
* @param selectedCharacters1 The characters selected by player 1
* @param selectedCharacters2 The characters selected by player 2
* @return The resulting {@link Event}s
*/
public ArrayList<Event> initGame(ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) {
return GameLogic.startGame(state, selectedCharacters1, selectedCharacters2);
}
/**
* Starts the game.
* @return The resulting {@link Event}s
*/
public ArrayList<Event> startGame() {
return GameLogic.handleRoundStart(state);
}
/** /**
* Applies an array of {@link Event}s to the game state. * Applies an array of {@link Event}s to the game state.
* @param events The events to apply * @param events The events to apply
@ -81,17 +59,64 @@ class GameStateManager {
} }
} }
/**
* Applies an {@link Event} to the game state.
* @param event The event to apply
*/
public void applyEvent(Event event) {
GameLogic.applyEvent(state, event);
}
/** /**
* Applies the result of the last processRequests call. * Applies the result of the last processRequests call.
* @return A list of applied events * @return A list of applied events
*/ */
public ArrayList<Event> apply() { public ArrayList<Event> apply() {
ArrayList<Event> toReturn = new ArrayList<>(queue.size()); for(Event event: queue) {
Event current; GameLogic.applyEvent(state, event);
while ((current = queue.poll()) != null) {
GameLogic.applyEvent(state, current);
toReturn.add(current);
} }
return toReturn;
// i feel so smart for remembering this trick
try {
return new ArrayList<>(queue);
}finally {
queue.clear();
}
}
/**
* Handles events that happen after a turn phase.
* @return The optionally resulting {@link Event}s
*/
public ArrayList<Event> checkPostPhase() {
ArrayList<Event> result = GameLogic.checkTurnEnd(state);
applyEvents(result);
return result;
}
/**
* Initializes and starts the game.
* @param selectedCharacters1 The characters selected by player 1
* @param selectedCharacters2 The characters selected by player 2
* @return The resulting {@link Event}s
*/
public ArrayList<Event> startGame(ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) {
ArrayList<Event> result = GameLogic.startGame(state, selectedCharacters1, selectedCharacters2);
applyEvents(result);
ArrayList<Event> result2 = GameLogic.startRound(state);
applyEvents(result2);
result.addAll(result2);
return result;
}
/**
* Produces a {@link EventType#GamestateEvent} for the current {@link GameState}.
* @return The resulting event
*/
public Event getGameStateEvent() {
return GameLogic.buildGameStateEvent(state);
} }
} }

View File

@ -7,8 +7,6 @@ import uulm.teamname.marvelous.gamelibrary.IntVector2;
import uulm.teamname.marvelous.gamelibrary.config.*; import uulm.teamname.marvelous.gamelibrary.config.*;
import uulm.teamname.marvelous.gamelibrary.entities.*; import uulm.teamname.marvelous.gamelibrary.entities.*;
import uulm.teamname.marvelous.gamelibrary.entities.Character; import uulm.teamname.marvelous.gamelibrary.entities.Character;
import uulm.teamname.marvelous.gamelibrary.events.Event;
import uulm.teamname.marvelous.gamelibrary.events.EventType;
import uulm.teamname.marvelous.gamelibrary.requests.*; import uulm.teamname.marvelous.gamelibrary.requests.*;
import java.util.*; import java.util.*;
@ -79,12 +77,12 @@ class GameLogicTest {
props.name = generateName(10); props.name = generateName(10);
props.HP = Math.abs(randomIntegers.next() % 20); props.HP = Math.abs(randomIntegers.next() % 15 + 5);
props.MP = Math.abs(randomIntegers.next() % 7); props.MP = Math.abs(randomIntegers.next() % 5 + 2);
props.AP = Math.abs(randomIntegers.next() % 7); props.AP = Math.abs(randomIntegers.next() % 5 + 2);
props.meleeDamage = Math.abs(randomIntegers.next() % 7); props.meleeDamage = Math.abs(randomIntegers.next() % 5 + 2);
props.rangedDamage = Math.abs(randomIntegers.next() % 7); props.rangedDamage = Math.abs(randomIntegers.next() % 5 + 2);
props.attackRange = Math.abs(randomIntegers.next() % 7); props.attackRange = Math.abs(randomIntegers.next() % 5 + 2);
return props; return props;
} }
@ -161,35 +159,11 @@ class GameLogicTest {
@Test @Test
void testGame() { void testGame() {
GameInstance game = new GameInstance(partyConfig, characterConfig, scenarioConfig); GameInstance server = new GameInstance(partyConfig, characterConfig, scenarioConfig);
ArrayList<Event> result = game.startGame(player1Selection, player2Selection); server.startGame(player1Selection, player2Selection);
assertTrue(result.size() > 0, "Start game should return at least one event"); System.out.println(server.toString());
Event actual = result.get(0);
assertEquals(EventType.GamestateEvent, actual.type, "First event should be a GameStateEvent");
System.out.println(game.toString());
game.applyEvents(GameLogic.spawnThanos(game.getState()));
System.out.println(game.toString());
NPC thanos = (NPC)game.state.getEntities().findEntity(new EntityID(EntityType.NPC, 2));
game.applyEvents(GameLogic.handleThanos(game.getState(), thanos));
System.out.println(game.toString());
game.applyEvents(GameLogic.handleThanos(game.getState(), thanos));
System.out.println(game.toString());
game.applyEvents(GameLogic.handleThanos(game.getState(), thanos));
System.out.println(game.toString());
} }