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;