refactor: restructure code and fix some events not being applied
This commit is contained in:
parent
e0af4cf01c
commit
3bb6d81b27
@ -27,7 +27,7 @@ public class GameInstance {
|
||||
/** Constructs a new {@link GameInstance}. */
|
||||
public GameInstance(PartyConfig partyConfig, CharacterConfig characterConfig, ScenarioConfig scenarioConfig) {
|
||||
_state = new GameState(partyConfig, characterConfig, scenarioConfig);
|
||||
this.state = new GameStateView(_state);
|
||||
state = new GameStateView(_state);
|
||||
manager = new GameStateManager(_state);
|
||||
}
|
||||
|
||||
@ -36,16 +36,11 @@ public class GameInstance {
|
||||
* @param requests The requests to check
|
||||
* @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)) {
|
||||
ArrayList<Event> result = manager.apply();
|
||||
|
||||
ArrayList<Event> result2 = manager.checkPostPhase();
|
||||
manager.applyEvents(result2);
|
||||
|
||||
result.addAll(result2);
|
||||
|
||||
result.add(GameLogic.buildGameStateEvent(_state));
|
||||
result.addAll(manager.checkPostPhase());
|
||||
|
||||
return Optional.of(result);
|
||||
}
|
||||
@ -58,44 +53,10 @@ public class GameInstance {
|
||||
* @param requests The requests to check
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param events The events to apply.
|
||||
@ -104,8 +65,30 @@ public class GameInstance {
|
||||
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
|
||||
|
@ -19,199 +19,16 @@ import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
|
||||
/** Contains game logic handling. */
|
||||
class GameLogic {
|
||||
public class GameLogic {
|
||||
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}.
|
||||
* @param state The game state to check on
|
||||
* @param request The request to validate
|
||||
* @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 {
|
||||
switch(request.type) {
|
||||
case MeleeAttackRequest, RangedAttackRequest -> {
|
||||
@ -478,74 +295,181 @@ class GameLogic {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
*/
|
||||
private static boolean checkLineOfSight(GameState state, IntVector2 start, IntVector2 end) {
|
||||
for(IntVector2 pos: rasterize(start, end, false, false)) {
|
||||
if(state.entities.blocksVision(pos)) {
|
||||
return false;
|
||||
protected 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());
|
||||
}
|
||||
}
|
||||
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
|
||||
*/
|
||||
private 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
|
||||
*/
|
||||
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 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 options;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies an {@link Event} to a {@link GameState}.
|
||||
* @param state The game state to apply to
|
||||
* @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) {
|
||||
case DestroyedEntityEvent -> {
|
||||
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.
|
||||
* @param state The game state to work on
|
||||
@ -603,7 +608,7 @@ class GameLogic {
|
||||
* @param selectedCharacters2 The characters selected by player 2
|
||||
* @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<IntVector2> free = new ArrayList<>();
|
||||
@ -652,12 +657,21 @@ class GameLogic {
|
||||
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.
|
||||
* @param state The game state to work on
|
||||
* @return The list of resulting {@link Event}s
|
||||
*/
|
||||
public static ArrayList<Event> checkTurnEnd(GameState state) {
|
||||
protected static ArrayList<Event> checkTurnEnd(GameState state) {
|
||||
if(
|
||||
((Character) state.entities.findEntity(state.activeCharacter)).ap.getValue() <= 0 &&
|
||||
((Character) state.entities.findEntity(state.activeCharacter)).mp.getValue() <= 0
|
||||
@ -668,12 +682,22 @@ class GameLogic {
|
||||
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.
|
||||
* @param state The game state to work on
|
||||
* @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<EntityID> alive = new ArrayList<>();
|
||||
@ -721,7 +745,7 @@ class GameLogic {
|
||||
* @param state The game state to work on
|
||||
* @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<>();
|
||||
|
||||
state.roundNumber++;
|
||||
@ -764,7 +788,7 @@ class GameLogic {
|
||||
* @param state The game state to work on
|
||||
* @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<StoneType> inventory = new ArrayList<>(state.unvomitedStones);
|
||||
@ -804,7 +828,7 @@ class GameLogic {
|
||||
* @param state The game state to work on
|
||||
* @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<Character> characters = new ArrayList<>();
|
||||
@ -864,7 +888,7 @@ class GameLogic {
|
||||
* @param state The game state to work on
|
||||
* @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<IntVector2> free = getFreeFields(state);
|
||||
@ -894,7 +918,7 @@ class GameLogic {
|
||||
* @param state The game state to work on
|
||||
* @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<>();
|
||||
|
||||
if(thanos.inventory.getFreeSlots() > 0) {
|
||||
@ -1009,7 +1033,7 @@ class GameLogic {
|
||||
* @param state The game state to work on
|
||||
* @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<>();
|
||||
|
||||
state.turnNumber++;
|
||||
@ -1068,7 +1092,7 @@ class GameLogic {
|
||||
* @param winner The winning character
|
||||
* @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<>();
|
||||
|
||||
state.won = true;
|
||||
@ -1080,6 +1104,7 @@ class GameLogic {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -1,9 +1,9 @@
|
||||
package uulm.teamname.marvelous.gamelibrary.gamelogic;
|
||||
|
||||
import uulm.teamname.marvelous.gamelibrary.events.Event;
|
||||
import uulm.teamname.marvelous.gamelibrary.events.EventType;
|
||||
import uulm.teamname.marvelous.gamelibrary.requests.Request;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** Represents manager for a game state. */
|
||||
@ -12,7 +12,7 @@ class GameStateManager {
|
||||
private final GameState state;
|
||||
|
||||
/** 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}.
|
||||
@ -26,13 +26,17 @@ class GameStateManager {
|
||||
* Checks a list of {@link Request}s for validity and optionally produces resulting {@link Event}s.
|
||||
* @param requests The requests to check
|
||||
* @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();
|
||||
|
||||
for(Request request: requests) {
|
||||
if (GameLogic.checkRequest(snapshot, request)) {
|
||||
ArrayList<Event> result = GameLogic.executeRequest(snapshot, request);
|
||||
for(Event event: result) {
|
||||
GameLogic.applyEvent(snapshot, event);
|
||||
}
|
||||
if(apply) {
|
||||
queue.addAll(result);
|
||||
}
|
||||
@ -45,32 +49,6 @@ class GameStateManager {
|
||||
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.
|
||||
* @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.
|
||||
* @return A list of applied events
|
||||
*/
|
||||
public ArrayList<Event> apply() {
|
||||
ArrayList<Event> toReturn = new ArrayList<>(queue.size());
|
||||
Event current;
|
||||
while ((current = queue.poll()) != null) {
|
||||
GameLogic.applyEvent(state, current);
|
||||
toReturn.add(current);
|
||||
for(Event event: queue) {
|
||||
GameLogic.applyEvent(state, event);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import uulm.teamname.marvelous.gamelibrary.IntVector2;
|
||||
import uulm.teamname.marvelous.gamelibrary.config.*;
|
||||
import uulm.teamname.marvelous.gamelibrary.entities.*;
|
||||
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 java.util.*;
|
||||
@ -79,12 +77,12 @@ class GameLogicTest {
|
||||
|
||||
props.name = generateName(10);
|
||||
|
||||
props.HP = Math.abs(randomIntegers.next() % 20);
|
||||
props.MP = Math.abs(randomIntegers.next() % 7);
|
||||
props.AP = Math.abs(randomIntegers.next() % 7);
|
||||
props.meleeDamage = Math.abs(randomIntegers.next() % 7);
|
||||
props.rangedDamage = Math.abs(randomIntegers.next() % 7);
|
||||
props.attackRange = Math.abs(randomIntegers.next() % 7);
|
||||
props.HP = Math.abs(randomIntegers.next() % 15 + 5);
|
||||
props.MP = Math.abs(randomIntegers.next() % 5 + 2);
|
||||
props.AP = Math.abs(randomIntegers.next() % 5 + 2);
|
||||
props.meleeDamage = Math.abs(randomIntegers.next() % 5 + 2);
|
||||
props.rangedDamage = Math.abs(randomIntegers.next() % 5 + 2);
|
||||
props.attackRange = Math.abs(randomIntegers.next() % 5 + 2);
|
||||
|
||||
return props;
|
||||
}
|
||||
@ -161,35 +159,11 @@ class GameLogicTest {
|
||||
|
||||
@Test
|
||||
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");
|
||||
|
||||
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());
|
||||
System.out.println(server.toString());
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user