From 01ccabad141fb138dc0a3cd16732838f722ec9a8 Mon Sep 17 00:00:00 2001 From: punchready Date: Thu, 27 May 2021 15:57:06 +0200 Subject: [PATCH 1/7] refactor: massively improve turn and round handling code --- .../gamelibrary/gamelogic/GameLogic.java | 135 +++++++++++++----- 1 file changed, 102 insertions(+), 33 deletions(-) diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java index 9f72431..a00b9f1 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java @@ -13,6 +13,8 @@ import uulm.teamname.marvelous.gamelibrary.requests.RequestType; import java.awt.*; import java.awt.geom.Line2D; import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; /** Contains game logic handling. */ class GameLogic { @@ -173,6 +175,9 @@ class GameLogic { } } } + case EndRoundRequest -> { + result.addAll(handleTurnEnd(state)); //why is it called end round request when it ends a turn... + } case Req -> { result.add(buildGameStateEvent(state)); } @@ -493,13 +498,9 @@ class GameLogic { public static ArrayList handleTurnEnd(GameState state) { ArrayList result = new ArrayList<>(); - ArrayList order = state.turnOrder; ArrayList alive = new ArrayList<>(); - state.turnNumber++; - boolean newRound = false; - - for (EntityID id: order) { + for (EntityID id: state.turnOrder) { Character character = ((Character)state.entities.findEntity(id)); if(character.hp.getValue() > 0){ @@ -507,56 +508,124 @@ class GameLogic { } if(character.inventory.getFreeSlots() == 0) { // no slots => has all infinity stones - state.won = true; - result.add(new EventBuilder(EventType.WinEvent) - .withPlayerWon(character.id.id) - .buildGameEvent()); + result.addAll(handlePlayerWin(state, character)); return result; } } if(alive.isEmpty()) { + result.addAll(handleThanosWin(state)); + return result; + } - //thanos win handling - + int index = alive.indexOf(state.activeCharacter); + AtomicInteger turnOrderSize = new AtomicInteger(alive.size()); + if(index == alive.size() - 1) { + result.addAll(handleRoundStart(state, turnOrderSize)); }else { + state.activeCharacter = alive.get(index + 1); + } - int activeIndex = state.activeCharacter != null ? alive.indexOf(state.activeCharacter) : -1; - if(activeIndex == -1 || activeIndex == alive.size() - 1) { - state.activeCharacter = alive.get(0); - //reached end of turn order, new round - state.roundNumber++; - newRound = true; - }else { - state.activeCharacter = alive.get(activeIndex + 1); + result.addAll(handleTurnStart(state, turnOrderSize.get())); + + return result; + } + + /** + * Handles everything that happens at the beginning of new rounds. + * @param state The game state to work on + * @return The list of resulting {@link Event}s + */ + public static ArrayList handleRoundStart(GameState state, AtomicInteger turnOrderSize) { + ArrayList result = new ArrayList<>(); + + state.roundNumber++; + + Collections.shuffle(state.turnOrder); + + ArrayList alive = new ArrayList<>(); + for (EntityID id: state.turnOrder) { + Character character = ((Character)state.entities.findEntity(id)); + if(character.hp.getValue() > 0){ + alive.add(id); } - Character activeCharacter = (Character)state.entities.findEntity(state.activeCharacter); + } + turnOrderSize.set(alive.size()); + + state.activeCharacter = alive.get(0); + + result.add(new EventBuilder(EventType.RoundSetupEvent) + .withRoundCount(state.roundNumber) + .withCharacterOrder(alive.toArray(new EntityID[0])) + .buildGameEvent()); + + return result; + } + + /** + * Handles everything that happens at the beginning of a turn. + * @param state The game state to work on + * @return The list of resulting {@link Event}s + */ + public static ArrayList handleTurnStart(GameState state, int turnOrderSize) { + ArrayList result = new ArrayList<>(); + + state.turnNumber++; + + Character activeCharacter = (Character)state.entities.findEntity(state.activeCharacter); + + if(activeCharacter.ap.getValue() != activeCharacter.ap.max) { result.add(new EventBuilder(EventType.ConsumedAPEvent) .withTargetEntity(state.activeCharacter) .withTargetField(activeCharacter.getPosition()) .withAmount(activeCharacter.ap.getValue() - activeCharacter.ap.max) .buildGameEvent()); + } + if(activeCharacter.mp.getValue() != activeCharacter.mp.max) { result.add(new EventBuilder(EventType.ConsumedMPEvent) .withTargetEntity(state.activeCharacter) .withTargetField(activeCharacter.getPosition()) .withAmount(activeCharacter.mp.getValue() - activeCharacter.mp.max) .buildGameEvent()); - result.add(new EventBuilder(EventType.TurnEvent) - .withTurnCount(alive.size()) - .withNextCharacter(state.activeCharacter) - .buildGameEvent()); - } + result.add(new EventBuilder(EventType.TurnEvent) + .withTurnCount(turnOrderSize) + .withNextCharacter(state.activeCharacter) + .buildGameEvent()); - if(newRound) { - //special round handling - //shuffle turn order - /* - result.add(new EventBuilder(EventType.RoundSetupEvent) - .buildGameEvent()); - */ - } + return result; + } + + /** + * Handles the victory of a player through one character. + * @param state The game state to work on + * @param winner The winning character + * @return The list of resulting {@link Event}s + */ + public static ArrayList handlePlayerWin(GameState state, Character winner) { + ArrayList result = new ArrayList<>(); + + state.won = true; + + result.add(new EventBuilder(EventType.WinEvent) + .withPlayerWon(winner.id.id) + .buildGameEvent()); + + return result; + } + + /** + * Handles the victory of thanos after all characters are knocked out. + * @param state The game state to work on + * @return The list of resulting {@link Event}s + */ + public static ArrayList handleThanosWin(GameState state) { + ArrayList result = new ArrayList<>(); + + state.won = true; + + //TODO: add thanos victory event return result; } From a416698adfa6ca663630f3c29267d16b4bdf3d82 Mon Sep 17 00:00:00 2001 From: punchready Date: Thu, 27 May 2021 17:08:08 +0200 Subject: [PATCH 2/7] feat: add handling for stan and goose --- .../marvelous/gamelibrary/IntVector2.java | 11 ++ .../gamelibrary/gamelogic/GameLogic.java | 182 ++++++++++++++++-- .../gamelibrary/gamelogic/GameState.java | 5 + 3 files changed, 178 insertions(+), 20 deletions(-) diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java index 1872d3a..5ef49c9 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java @@ -16,6 +16,17 @@ public class IntVector2 implements Serializable { public final static IntVector2 Y = new IntVector2(0, 1); public final static IntVector2 Zero = new IntVector2(0, 0); + public final static IntVector2[] CardinalDirections = { + new IntVector2(1, 0), + new IntVector2(0, 1), + new IntVector2(1, 1), + new IntVector2(-1, 0), + new IntVector2(0, -1), + new IntVector2(-1, -1), + new IntVector2(1, -1), + new IntVector2(-1, 1), + }; + private final static float nearZero = 0.000000001f; public IntVector2(int x, int y) { diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java index a00b9f1..9179b37 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java @@ -12,12 +12,13 @@ import uulm.teamname.marvelous.gamelibrary.requests.RequestType; import java.awt.*; import java.awt.geom.Line2D; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** Contains game logic handling. */ 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 @@ -423,20 +424,60 @@ class GameLogic { * Verifies that there is a line of sight between two positions. */ private static void requireLineOfSight(GameState state, IntVector2 start, IntVector2 end) throws InvalidRequestException { + if(!checkLineOfSight(state, start, end)) { + throw new InvalidRequestException(); + } + } + + /** + * 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 + */ + private static boolean checkLineOfSight(GameState state, IntVector2 start, IntVector2 end) { //naive code for the win!!! \o/ //at least its early exit and probably only O(ln(n*m)) - //TODO: implement proper line rasterization algorithm in GameLogic.requireLineOfSight + //TODO: implement proper line rasterization algorithm in GameLogic.checkLineOfSight Line2D line = new Line2D.Float(start.getX(), start.getY(), end.getX(), end.getY()); for(int i = start.getX(); i <= end.getX(); i++) { for(int j = start.getY(); j <= end.getY(); j++) { var cell = new Rectangle.Float(i - 0.5f, j - 0.5f, 1, 1); if(line.intersects(cell)) { if(state.entities.blocksVision(new IntVector2(i, j))) { - throw new InvalidRequestException(); + 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 + */ + private static ArrayList getFreeNeighbour(GameState state, IntVector2 start) { + ArrayList 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; + } } /** @@ -519,14 +560,13 @@ class GameLogic { } int index = alive.indexOf(state.activeCharacter); - AtomicInteger turnOrderSize = new AtomicInteger(alive.size()); if(index == alive.size() - 1) { - result.addAll(handleRoundStart(state, turnOrderSize)); + result.addAll(handleRoundStart(state)); }else { state.activeCharacter = alive.get(index + 1); } - result.addAll(handleTurnStart(state, turnOrderSize.get())); + result.addAll(handleTurnStart(state)); return result; } @@ -536,39 +576,141 @@ class GameLogic { * @param state The game state to work on * @return The list of resulting {@link Event}s */ - public static ArrayList handleRoundStart(GameState state, AtomicInteger turnOrderSize) { + public static ArrayList handleRoundStart(GameState state) { ArrayList result = new ArrayList<>(); state.roundNumber++; + if(state.roundNumber >= 1 && state.roundNumber <= 6) { + result.addAll(handleGoose(state)); + } + + HashSet revived = new HashSet<>(); + if(state.roundNumber == 7) { + result.addAll(handleStan(state, revived)); + } + Collections.shuffle(state.turnOrder); - ArrayList alive = new ArrayList<>(); for (EntityID id: state.turnOrder) { - Character character = ((Character)state.entities.findEntity(id)); - if(character.hp.getValue() > 0){ - alive.add(id); + if(revived.contains(id) || ((Character)state.entities.findEntity(id)).hp.getValue() > 0){ + state.activeCharacter = id; + break; } } - turnOrderSize.set(alive.size()); - - state.activeCharacter = alive.get(0); - result.add(new EventBuilder(EventType.RoundSetupEvent) .withRoundCount(state.roundNumber) - .withCharacterOrder(alive.toArray(new EntityID[0])) + .withCharacterOrder(state.turnOrder.toArray(new EntityID[0])) .buildGameEvent()); return result; } + /** + * Handles the actions of Goose at rounds 1-6. + * @param state The game state to work on + * @return The list of resulting {@link Event}s + */ + public static ArrayList handleGoose(GameState state) { + ArrayList result = new ArrayList<>(); + + StoneType[] available = state.unvomitedStones.toArray(new StoneType[0]); + StoneType stone = available[rand.nextInt(available.length)]; + state.unvomitedStones.remove(stone); + + ArrayList free = 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) { + free.add(pos); + } + } + } + IntVector2 position = free.get(rand.nextInt(free.size())); + + EntityID goose = new EntityID(EntityType.NPC, 0); + result.add(new EventBuilder(EventType.SpawnEntityEvent) + .withEntity(new NPC(goose, position)) + .buildEntityEvent()); + result.add(new EventBuilder(EventType.SpawnEntityEvent) + .withEntity(new InfinityStone(new EntityID(EntityType.InfinityStones, stone.getID()), position, stone)) + .buildEntityEvent()); + result.add(new EventBuilder(EventType.DestroyedEntityEvent) + .withTargetEntity(goose) + .withTargetField(position) + .buildEntityEvent()); + + return result; + } + + /** + * Handles the actions of Stan at round 7. + * @param state The game state to work on + * @return The list of resulting {@link Event}s + */ + public static ArrayList handleStan(GameState state, HashSet revived) { + ArrayList result = new ArrayList<>(); + + ArrayList characters = new ArrayList<>(); + + ArrayList targetOptions = new ArrayList<>(); + int lowest = -1; + for(EntityID id: state.turnOrder) { + Character character = (Character)state.entities.findEntity(id); + characters.add(character); + + if(lowest == -1 || character.hp.getValue() < lowest) { + lowest = character.hp.getValue(); + targetOptions.clear(); + } + if(lowest == character.hp.getValue()) { + targetOptions.add(character.getPosition()); + } + } + IntVector2 targetPosition = targetOptions.get(rand.nextInt(targetOptions.size())); + + ArrayList spawnOptions = getFreeNeighbour(state, targetPosition); + if(spawnOptions.size() == 0) { + return result; + } + IntVector2 spawnPosition = spawnOptions.get(rand.nextInt(spawnOptions.size())); + + EntityID stan = new EntityID(EntityType.NPC, 1); + result.add(new EventBuilder(EventType.SpawnEntityEvent) + .withEntity(new NPC(stan, spawnPosition)) + .buildEntityEvent()); + + for(Character character: characters) { + if(checkLineOfSight(state, spawnPosition, character.getPosition())) { + if(character.hp.getValue() == 0) { + revived.add(character.id); + } + if(character.hp.getValue() != character.hp.max) { + result.add(new EventBuilder(EventType.HealedEvent) + .withTargetEntity(character.id) + .withTargetField(character.getPosition()) + .withAmount(character.hp.max - character.hp.getValue()) + .buildEntityEvent()); + } + } + } + + result.add(new EventBuilder(EventType.DestroyedEntityEvent) + .withTargetEntity(stan) + .withTargetField(spawnPosition) + .buildEntityEvent()); + + return result; + } + /** * Handles everything that happens at the beginning of a turn. * @param state The game state to work on * @return The list of resulting {@link Event}s */ - public static ArrayList handleTurnStart(GameState state, int turnOrderSize) { + public static ArrayList handleTurnStart(GameState state) { ArrayList result = new ArrayList<>(); state.turnNumber++; @@ -590,7 +732,7 @@ class GameLogic { .buildGameEvent()); } result.add(new EventBuilder(EventType.TurnEvent) - .withTurnCount(turnOrderSize) + .withTurnCount(state.turnOrder.size()) .withNextCharacter(state.activeCharacter) .buildGameEvent()); @@ -616,7 +758,7 @@ class GameLogic { } /** - * Handles the victory of thanos after all characters are knocked out. + * Handles the special victory conditions after all characters are knocked out. * @param state The game state to work on * @return The list of resulting {@link Event}s */ diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java index 533ba93..ede2d08 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java @@ -5,7 +5,9 @@ import uulm.teamname.marvelous.gamelibrary.entities.*; import uulm.teamname.marvelous.gamelibrary.Tuple; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; /** Represents the state of a game instance. */ class GameState { @@ -15,6 +17,9 @@ class GameState { /** The list of {@link Entity}s inside the game */ public final EntityManager entities = new EntityManager(); + /** The set of stones that are yet to be placed on the map */ + public HashSet unvomitedStones = new HashSet<>(Arrays.asList(StoneType.values())); + /** The total amount of full turn cycles that occurred */ public int roundNumber = 0; From 0f50efa121103f3d8fa9deab9760280cf3c732db Mon Sep 17 00:00:00 2001 From: punchready Date: Thu, 27 May 2021 17:18:36 +0200 Subject: [PATCH 3/7] feat: add handling for overtime victories --- .../gamelibrary/entities/EntityType.java | 4 +- .../gamelibrary/gamelogic/GameLogic.java | 42 ++++++------------- .../gamelibrary/gamelogic/GameState.java | 6 +-- .../gamelibrary/gamelogic/GameStateView.java | 3 +- 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/EntityType.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/EntityType.java index 6ad9c95..acbc9f4 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/EntityType.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/EntityType.java @@ -11,5 +11,7 @@ public enum EntityType { /** Represents a Rock entity */ Rocks, /** Represents an InfinityStone entity */ - InfinityStones + InfinityStones, + /** Represents nothing */ + None } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java index 9179b37..e8aee84 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java @@ -13,7 +13,6 @@ import uulm.teamname.marvelous.gamelibrary.requests.RequestType; import java.awt.*; import java.awt.geom.Line2D; import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; /** Contains game logic handling. */ class GameLogic { @@ -549,13 +548,13 @@ class GameLogic { } if(character.inventory.getFreeSlots() == 0) { // no slots => has all infinity stones - result.addAll(handlePlayerWin(state, character)); + result.addAll(handlePlayerWin(state, character.id.type)); return result; } } if(alive.isEmpty()) { - result.addAll(handleThanosWin(state)); + result.addAll(handlePlayerWin(state, checkWinConditions(state))); return result; } @@ -745,48 +744,33 @@ class GameLogic { * @param winner The winning character * @return The list of resulting {@link Event}s */ - public static ArrayList handlePlayerWin(GameState state, Character winner) { + public static ArrayList handlePlayerWin(GameState state, EntityType winner) { ArrayList result = new ArrayList<>(); state.won = true; result.add(new EventBuilder(EventType.WinEvent) - .withPlayerWon(winner.id.id) + .withPlayerWon(winner == EntityType.P1 ? 1 : 2) .buildGameEvent()); return result; } - /** - * Handles the special victory conditions after all characters are knocked out. - * @param state The game state to work on - * @return The list of resulting {@link Event}s - */ - public static ArrayList handleThanosWin(GameState state) { - ArrayList result = new ArrayList<>(); - - state.won = true; - - //TODO: add thanos victory event - - return result; - } - /** * Checks a {@link GameState} for the current overtime win condition. * @param state The game state to check - * @return The {@link ParticipantType} that is currently winning the game according to overtime ruling + * @return The {@link EntityType} that is currently winning the game according to overtime ruling */ - public static ParticipantType checkWinConditions(GameState state) { + public static EntityType checkWinConditions(GameState state) { //TODO: GameLogic.checkWinConditions is kind of ugly - Tuple player1; - Tuple player2; + Tuple player1; + Tuple player2; int value1; int value2; for(WinCondition condition: WinCondition.values()) { - player1 = new Tuple(ParticipantType.Player1, condition); - player2 = new Tuple(ParticipantType.Player2, condition); + player1 = new Tuple(EntityType.P1, condition); + player2 = new Tuple(EntityType.P2, condition); value1 = 0; value2 = 0; @@ -798,13 +782,13 @@ class GameLogic { } if(value1 > value2) { - return ParticipantType.Player1; + return EntityType.P1; } if(value2 > value1) { - return ParticipantType.Player2; + return EntityType.P2; } } - return ParticipantType.None; + return EntityType.None; } } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java index ede2d08..9e75b2d 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java @@ -39,7 +39,7 @@ class GameState { public final StoneCooldownManager stoneCooldown = new StoneCooldownManager(); /** The store of the {@link WinCondition} data for every win condition for each player */ - public final HashMap, Integer> winConditions = new HashMap<>(); + public final HashMap, Integer> winConditions = new HashMap<>(); /** * Constructs a new {@link GameState}. @@ -73,7 +73,7 @@ class GameState { clone.stoneCooldown.cloneFrom(stoneCooldown); - for(Tuple condition: winConditions.keySet()) { + for(Tuple condition: winConditions.keySet()) { clone.winConditions.put(condition, winConditions.get(condition)); } @@ -104,7 +104,7 @@ class GameState { stoneCooldown.cloneFrom(state.stoneCooldown); - for(Tuple condition: state.winConditions.keySet()) { + for(Tuple condition: state.winConditions.keySet()) { winConditions.put(condition, state.winConditions.get(condition)); } } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameStateView.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameStateView.java index b5a0de9..95ac621 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameStateView.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameStateView.java @@ -3,6 +3,7 @@ package uulm.teamname.marvelous.gamelibrary.gamelogic; import uulm.teamname.marvelous.gamelibrary.IntVector2; import uulm.teamname.marvelous.gamelibrary.entities.Entity; import uulm.teamname.marvelous.gamelibrary.entities.EntityID; +import uulm.teamname.marvelous.gamelibrary.entities.EntityType; import uulm.teamname.marvelous.gamelibrary.entities.StoneType; import java.util.ArrayList; @@ -53,7 +54,7 @@ public class GameStateView { return state.stoneCooldown.getCooldown(stone); } - public ParticipantType getWinnerAccordingToWinConditions() { + public EntityType getWinnerAccordingToWinConditions() { return GameLogic.checkWinConditions(state); } } From 3522cee9a41d72e60927f87a00d9114024564272 Mon Sep 17 00:00:00 2001 From: punchready Date: Thu, 27 May 2021 17:26:46 +0200 Subject: [PATCH 4/7] feat: add stone cooldown handling --- .../marvelous/gamelibrary/gamelogic/GameInstance.java | 1 + .../marvelous/gamelibrary/gamelogic/GameLogic.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameInstance.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameInstance.java index 5c06777..02b1736 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameInstance.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameInstance.java @@ -101,6 +101,7 @@ public class GameInstance { * @param events The events to emit */ private void emit(Event... events) { + manager.applyEvents(events); emitter.update(events); } } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java index e8aee84..b1f8404 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java @@ -288,6 +288,10 @@ class GameLogic { requireAP(origin, 1); requireInfinityStone(origin, data.stoneType); + if(state.stoneCooldown.onCooldown(data.stoneType)) { + throw new InvalidRequestException(); + } + switch(((CharacterRequest) request).stoneType) { case SpaceStone -> { verifyCoordinates(state, data.targetField); @@ -507,6 +511,9 @@ class GameLogic { case MoveEvent -> { (state.entities.findEntity(((CharacterEvent)event).originEntity)).setPosition(((CharacterEvent)event).targetField); } + case UseInfinityStoneEvent -> { + state.stoneCooldown.setCooldown(((CharacterEvent)event).stoneType, 10); //TODO: use stone cooldown from config + } case ExchangeInfinityStoneEvent -> { ((Character)state.entities.findEntity(((CharacterEvent)event).originEntity)).inventory.removeStone(((CharacterEvent)event).stoneType); ((Character)state.entities.findEntity(((CharacterEvent)event).targetEntity)).inventory.addStone(((CharacterEvent)event).stoneType); @@ -598,6 +605,8 @@ class GameLogic { } } + state.stoneCooldown.update(); + result.add(new EventBuilder(EventType.RoundSetupEvent) .withRoundCount(state.roundNumber) .withCharacterOrder(state.turnOrder.toArray(new EntityID[0])) From 7f7459e7a7739456ac18b9fd791a17066ae1c06d Mon Sep 17 00:00:00 2001 From: punchready Date: Thu, 27 May 2021 17:30:36 +0200 Subject: [PATCH 5/7] fix: make movement events pick up infinity stones --- .../marvelous/gamelibrary/gamelogic/GameLogic.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java index b1f8404..f9c81fb 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java @@ -509,7 +509,13 @@ class GameLogic { ((Character)state.entities.findEntity(((CharacterEvent)event).targetEntity)).hp.increaseValue(((CharacterEvent)event).amount); } case MoveEvent -> { - (state.entities.findEntity(((CharacterEvent)event).originEntity)).setPosition(((CharacterEvent)event).targetField); + Character target = (Character)state.entities.findEntity(((CharacterEvent)event).originEntity); + for(Entity entity: state.entities.findByPosition(((CharacterEvent)event).targetField)) { + if(entity instanceof InfinityStone) { + target.inventory.addStone(((InfinityStone)entity).type); + } + } + target.setPosition(((CharacterEvent)event).targetField); } case UseInfinityStoneEvent -> { state.stoneCooldown.setCooldown(((CharacterEvent)event).stoneType, 10); //TODO: use stone cooldown from config From 830ef22997de4ced7eb16b38c65f21aedd9e22d6 Mon Sep 17 00:00:00 2001 From: punchready Date: Thu, 27 May 2021 17:58:44 +0200 Subject: [PATCH 6/7] feat/refactor: actually track win conditions --- .../gamelibrary/entities/Inventory.java | 5 ++ .../gamelibrary/gamelogic/GameInstance.java | 1 - .../gamelibrary/gamelogic/GameLogic.java | 61 +++++---------- .../gamelibrary/gamelogic/GameState.java | 14 +--- .../gamelibrary/gamelogic/GameStateView.java | 6 +- .../gamelogic/WinConditionManager.java | 76 +++++++++++++++++++ 6 files changed, 108 insertions(+), 55 deletions(-) create mode 100644 src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/WinConditionManager.java diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/Inventory.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/Inventory.java index 420cbc4..1b6a2ce 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/Inventory.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/Inventory.java @@ -40,6 +40,11 @@ public class Inventory implements Iterable { return size - content.size(); } + /** Returns the number stones the inventory has. */ + public int getSize() { + return content.size(); + } + /** * Checks if the inventory contains the given stone. * @param stone The {@link StoneType} to check for diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameInstance.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameInstance.java index 02b1736..8cfb2f6 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameInstance.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameInstance.java @@ -5,7 +5,6 @@ import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.events.EventType; import uulm.teamname.marvelous.gamelibrary.requests.Request; -import java.util.ArrayList; import java.util.Observer; /** Represents a game instance. */ diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java index f9c81fb..692b2b0 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java @@ -1,7 +1,6 @@ package uulm.teamname.marvelous.gamelibrary.gamelogic; import uulm.teamname.marvelous.gamelibrary.IntVector2; -import uulm.teamname.marvelous.gamelibrary.Tuple; import uulm.teamname.marvelous.gamelibrary.entities.*; import uulm.teamname.marvelous.gamelibrary.entities.Character; import uulm.teamname.marvelous.gamelibrary.events.*; @@ -494,7 +493,14 @@ class GameLogic { state.entities.removeEntity(((EntityEvent)event).targetEntity); } case TakenDamageEvent -> { - ((Character)state.entities.findEntity(((CharacterEvent)event).targetEntity)).hp.decreaseValue(((CharacterEvent)event).amount); + Character target = (Character)state.entities.findEntity(((CharacterEvent)event).targetEntity); + target.hp.decreaseValue(((CharacterEvent)event).amount); + + EntityType opposing = target.id.type == EntityType.P1 ? EntityType.P2 : EntityType.P1; + state.winConditions.increaseValue(opposing, WinCondition.TotalDamage, ((CharacterEvent)event).amount); + if(target.hp.getValue() == 0) { + state.winConditions.increaseValue(opposing, WinCondition.TotalKnockouts, 1); + } } case ConsumedAPEvent -> { ((Character)state.entities.findEntity(((CharacterEvent)event).targetEntity)).ap.decreaseValue(((CharacterEvent)event).amount); @@ -513,6 +519,8 @@ class GameLogic { for(Entity entity: state.entities.findByPosition(((CharacterEvent)event).targetField)) { if(entity instanceof InfinityStone) { target.inventory.addStone(((InfinityStone)entity).type); + + state.winConditions.updateValue(target.id.type, WinCondition.MaxStones, target.inventory.getSize()); } } target.setPosition(((CharacterEvent)event).targetField); @@ -522,7 +530,10 @@ class GameLogic { } case ExchangeInfinityStoneEvent -> { ((Character)state.entities.findEntity(((CharacterEvent)event).originEntity)).inventory.removeStone(((CharacterEvent)event).stoneType); - ((Character)state.entities.findEntity(((CharacterEvent)event).targetEntity)).inventory.addStone(((CharacterEvent)event).stoneType); + Character target = (Character)state.entities.findEntity(((CharacterEvent)event).targetEntity); + target.inventory.addStone(((CharacterEvent)event).stoneType); + + state.winConditions.updateValue(target.id.type, WinCondition.MaxStones, target.inventory.getSize()); } } } @@ -567,7 +578,11 @@ class GameLogic { } if(alive.isEmpty()) { - result.addAll(handlePlayerWin(state, checkWinConditions(state))); + EntityType winner = state.winConditions.getWinner(); + if(winner == EntityType.None) { + winner = rand.nextBoolean() ? EntityType.P1 : EntityType.P2; + } + result.addAll(handlePlayerWin(state, winner)); return result; } @@ -602,6 +617,8 @@ class GameLogic { result.addAll(handleStan(state, revived)); } + //TODO: add handling for thanos + Collections.shuffle(state.turnOrder); for (EntityID id: state.turnOrder) { @@ -770,40 +787,4 @@ class GameLogic { return result; } - - /** - * Checks a {@link GameState} for the current overtime win condition. - * @param state The game state to check - * @return The {@link EntityType} that is currently winning the game according to overtime ruling - */ - public static EntityType checkWinConditions(GameState state) { - //TODO: GameLogic.checkWinConditions is kind of ugly - - Tuple player1; - Tuple player2; - int value1; - int value2; - for(WinCondition condition: WinCondition.values()) { - player1 = new Tuple(EntityType.P1, condition); - player2 = new Tuple(EntityType.P2, condition); - value1 = 0; - value2 = 0; - - if(state.winConditions.containsKey(player1)) { - value1 = state.winConditions.get(player1); - } - if(state.winConditions.containsKey(player2)) { - value2 = state.winConditions.get(player2); - } - - if(value1 > value2) { - return EntityType.P1; - } - if(value2 > value1) { - return EntityType.P2; - } - } - - return EntityType.None; - } } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java index 9e75b2d..ee36b63 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameState.java @@ -2,11 +2,9 @@ package uulm.teamname.marvelous.gamelibrary.gamelogic; import uulm.teamname.marvelous.gamelibrary.IntVector2; import uulm.teamname.marvelous.gamelibrary.entities.*; -import uulm.teamname.marvelous.gamelibrary.Tuple; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; /** Represents the state of a game instance. */ @@ -38,8 +36,8 @@ class GameState { /** The global cooldown of every infinity stone */ public final StoneCooldownManager stoneCooldown = new StoneCooldownManager(); - /** The store of the {@link WinCondition} data for every win condition for each player */ - public final HashMap, Integer> winConditions = new HashMap<>(); + /** The data for every win condition for each player */ + public final WinConditionManager winConditions = new WinConditionManager(); /** * Constructs a new {@link GameState}. @@ -73,9 +71,7 @@ class GameState { clone.stoneCooldown.cloneFrom(stoneCooldown); - for(Tuple condition: winConditions.keySet()) { - clone.winConditions.put(condition, winConditions.get(condition)); - } + clone.winConditions.cloneFrom(winConditions); return clone; } @@ -104,8 +100,6 @@ class GameState { stoneCooldown.cloneFrom(state.stoneCooldown); - for(Tuple condition: state.winConditions.keySet()) { - winConditions.put(condition, state.winConditions.get(condition)); - } + winConditions.cloneFrom(state.winConditions); } } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameStateView.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameStateView.java index 95ac621..d3c618a 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameStateView.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameStateView.java @@ -1,13 +1,11 @@ package uulm.teamname.marvelous.gamelibrary.gamelogic; import uulm.teamname.marvelous.gamelibrary.IntVector2; -import uulm.teamname.marvelous.gamelibrary.entities.Entity; import uulm.teamname.marvelous.gamelibrary.entities.EntityID; import uulm.teamname.marvelous.gamelibrary.entities.EntityType; import uulm.teamname.marvelous.gamelibrary.entities.StoneType; import java.util.ArrayList; -import java.util.Iterator; /** Represents a game state view containing getters for all the properties of a {@link GameState}. */ public class GameStateView { @@ -54,7 +52,7 @@ public class GameStateView { return state.stoneCooldown.getCooldown(stone); } - public EntityType getWinnerAccordingToWinConditions() { - return GameLogic.checkWinConditions(state); + public EntityType getCurrentOvertimeWinner() { + return state.winConditions.getWinner(); } } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/WinConditionManager.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/WinConditionManager.java new file mode 100644 index 0000000..2a8814e --- /dev/null +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/WinConditionManager.java @@ -0,0 +1,76 @@ +package uulm.teamname.marvelous.gamelibrary.gamelogic; + +import uulm.teamname.marvelous.gamelibrary.Tuple; +import uulm.teamname.marvelous.gamelibrary.entities.EntityType; + +import java.util.HashMap; + +/** Represents a manager for win conditions. */ +public class WinConditionManager { + /** The store of the {@link WinCondition} data for every win condition for each player */ + private final HashMap, Integer> winConditions = new HashMap<>(); + + /** + * Returns the current winner + * @return The {@link EntityType} that is currently winning the game according to overtime ruling + */ + public EntityType getWinner() { + int value1; + int value2; + for(WinCondition condition: WinCondition.values()) { + value1 = getValue(EntityType.P1, condition); + value2 = getValue(EntityType.P2, condition); + + if(value1 > value2) { + return EntityType.P1; + } + if(value2 > value1) { + return EntityType.P2; + } + } + + return EntityType.None; + } + + /** + * Updates the value for a win condition for a specific player if the new value is greater than the old one. + * @param player The player to use + * @param condition The {@link WinCondition} to use + * @param value The new value + */ + public void updateValue(EntityType player, WinCondition condition, Integer value) { + Integer old = getValue(player, condition); + if(old < value) { + winConditions.put(new Tuple<>(player, condition), value); + } + } + + /** + * Increases the value for a win condition for a specific player. + * @param player The player to use + * @param condition The {@link WinCondition} to use + * @param value The new value + */ + public void increaseValue(EntityType player, WinCondition condition, Integer value) { + winConditions.put(new Tuple<>(player, condition), getValue(player, condition) + value); + } + + /** + * Gets the value for a specific player and win condition. + * @param player The player to use + * @param condition The {@link WinCondition} to use + * @return The value of the condition or 0 if it doesn't exist + */ + private Integer getValue(EntityType player, WinCondition condition) { + return winConditions.getOrDefault(new Tuple<>(player, condition), 0); + } + + /** + * Takes over all the win conditions from a different {@link WinConditionManager}. + * @param other The win condition manager to take the data from + */ + public void cloneFrom(WinConditionManager other) { + winConditions.clear(); + winConditions.putAll(other.winConditions); + } +} From 06961766091e0f8de031c54b402470aa9002241f Mon Sep 17 00:00:00 2001 From: punchready Date: Thu, 27 May 2021 18:02:30 +0200 Subject: [PATCH 7/7] fix: allow EndRoundRequest event --- .../teamname/marvelous/gamelibrary/gamelogic/GameLogic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java index 692b2b0..6fd9a36 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java @@ -346,7 +346,7 @@ class GameLogic { return true; } - case Req -> { + case EndRoundRequest, Req -> { return true; } }