breaking: remove observable pattern, make methods return events directly

This commit is contained in:
punchready 2021-06-01 15:43:04 +02:00
parent 4802b0113d
commit 842db2439a
6 changed files with 92 additions and 92 deletions

View File

@ -64,4 +64,13 @@ public class NPC extends Entity {
} }
return clone; return clone;
} }
@Override
public String toString() {
return "NPC{" +
"mp=" + mp +
", inventory=" + inventory +
", position=" + position +
'}';
}
} }

View File

@ -167,6 +167,6 @@ public class EntityManager {
* @return An array containing every {@link Entity} * @return An array containing every {@link Entity}
*/ */
Entity[] export() { Entity[] export() {
return (Entity[])entities.toArray(); return entities.toArray(new Entity[0]);
} }
} }

View File

@ -8,7 +8,6 @@ import uulm.teamname.marvelous.gamelibrary.json.config.ScenarioConfig;
import uulm.teamname.marvelous.gamelibrary.requests.Request; import uulm.teamname.marvelous.gamelibrary.requests.Request;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Observer;
/** Represents a game instance. */ /** Represents a game instance. */
public class GameInstance { public class GameInstance {
@ -21,9 +20,6 @@ public class GameInstance {
/** The {@link GameStateManager} managing the {@link GameState} of the instance */ /** The {@link GameStateManager} managing the {@link GameState} of the instance */
private final GameStateManager manager; private final GameStateManager manager;
/** The {@link EventEmitter} for {@link Event}s resulting from {@link Request}s */
private final EventEmitter emitter = new EventEmitter();
/** 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);
@ -34,20 +30,16 @@ public class GameInstance {
/** /**
* Checks an array of {@link Request}s for validity and automatically applies them if valid. * Checks an array of {@link Request}s for validity and automatically applies them if valid.
* @param requests The requests to check * @param requests The requests to check
* @return Whether or not the given set of requests was valid * @return The list of resulting {@link Event}s or `null` if the check failed
*/ */
public boolean checkRequestsAndApply(Request... requests) { public ArrayList<Event> checkRequestsAndApply(Request... requests) {
if(manager.processRequests(requests, true)) { if(manager.processRequests(requests, true)) {
emit(manager.apply()); ArrayList<Event> result = manager.apply();
result.addAll(manager.checkPostPhase());
Event[] result = manager.checkPostPhase(); return result;
if(result.length > 0) {
emit(result);
}
return true;
} }
return false;
return null;
} }
/** /**
@ -55,7 +47,7 @@ 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 checkRequestsSilent(Request... requests) { public boolean checkRequests(Request... requests) {
return manager.processRequests(requests, false); return manager.processRequests(requests, false);
} }
@ -63,10 +55,14 @@ public class GameInstance {
* Initializes and starts the game. Selected characters are given as a list of indices from the {@link CharacterConfig#characters} array. * 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 selectedCharacters1 The characters selected by player 1
* @param selectedCharacters2 The characters selected by player 2 * @param selectedCharacters2 The characters selected by player 2
* @return The list of resulting {@link Event}s
*/ */
public void startGame(ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) { public ArrayList<Event> startGame(ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) {
emit(manager.initGame(selectedCharacters1, selectedCharacters2)); ArrayList<Event> result = manager.initGame(selectedCharacters1, selectedCharacters2);
emit(manager.startGame()); manager.applyEvents(result);
result.addAll(manager.startGame());
return result;
} }
/** /**
@ -89,24 +85,7 @@ public class GameInstance {
* 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.
*/ */
public void applyEvents(Event... events) { public void applyEvents(ArrayList<Event> events) {
manager.applyEvents(events); manager.applyEvents(events);
} }
/**
* Adds an {@link Observer} for events.
* @param observer The observer to add
*/
public void addObserver(Observer observer) {
emitter.addObserver(observer);
}
/**
* Instructs the emitter to emit an array of {@link Event}s.
* @param events The events to emit
*/
private void emit(Event... events) {
manager.applyEvents(events);
emitter.update(events);
}
} }

View File

@ -188,7 +188,7 @@ class GameLogic {
public static Event buildGameStateEvent(GameState state) { public static Event buildGameStateEvent(GameState state) {
return new EventBuilder(EventType.GamestateEvent) return new EventBuilder(EventType.GamestateEvent)
.withEntities(state.entities.export()) .withEntities(state.entities.export())
.withTurnOrder((EntityID[])state.turnOrder.toArray()) .withTurnOrder(state.turnOrder.toArray(new EntityID[0]))
.withMapSize(state.mapSize) .withMapSize(state.mapSize)
.withActiveCharacter(state.activeCharacter) .withActiveCharacter(state.activeCharacter)
.withStoneCooldowns(state.stoneCooldown.export()) .withStoneCooldowns(state.stoneCooldown.export())
@ -562,9 +562,7 @@ class GameLogic {
for(int x = 0; x < state.mapSize.getX(); x++) { for(int x = 0; x < state.mapSize.getX(); x++) {
for(int y = 0; y < state.mapSize.getY(); y++) { for(int y = 0; y < state.mapSize.getY(); y++) {
if(state.scenarioConfig.scenario[y][x] == FieldType.ROCK) { if(state.scenarioConfig.scenario[y][x] == FieldType.ROCK) {
result.add(new EventBuilder(EventType.SpawnEntityEvent) state.entities.addEntity(new Rock(new EntityID(EntityType.Rocks, rockIndex++), new IntVector2(x, y), 100));
.withEntity(new Rock(new EntityID(EntityType.Rocks, rockIndex++), new IntVector2(x, y), 100))
.buildEntityEvent());
}else { }else {
free.add(new IntVector2(x, y)); free.add(new IntVector2(x, y));
} }
@ -585,22 +583,22 @@ class GameLogic {
int selected = characters.get(i); int selected = characters.get(i);
EntityID id = new EntityID(i < p1 ? EntityType.P1 : EntityType.P2, i % p1); EntityID id = new EntityID(i < p1 ? EntityType.P1 : EntityType.P2, i % p1);
result.add(new EventBuilder(EventType.SpawnEntityEvent) state.entities.addEntity(new Character(
.withEntity(new Character( id, position,
id, position, state.characterConfig.characters[selected].name,
state.characterConfig.characters[selected].name, state.characterConfig.characters[selected].HP,
state.characterConfig.characters[selected].HP, state.characterConfig.characters[selected].MP,
state.characterConfig.characters[selected].MP, state.characterConfig.characters[selected].AP,
state.characterConfig.characters[selected].AP, state.characterConfig.characters[selected].attackRange,
state.characterConfig.characters[selected].attackRange, state.characterConfig.characters[selected].rangedDamage,
state.characterConfig.characters[selected].rangedDamage, state.characterConfig.characters[selected].meleeDamage
state.characterConfig.characters[selected].meleeDamage ));
))
.buildEntityEvent());
state.turnOrder.add(id); state.turnOrder.add(id);
} }
result.add(buildGameStateEvent(state));
return result; return result;
} }
@ -849,7 +847,28 @@ class GameLogic {
public static ArrayList<Event> handleThanos(GameState state, NPC thanos) { public static ArrayList<Event> handleThanos(GameState state, NPC thanos) {
ArrayList<Event> result = new ArrayList<>(); ArrayList<Event> result = new ArrayList<>();
if(thanos.inventory.getFreeSlots() > 0) {
IntVector2 picked = null;
float lowestDistance = Integer.MAX_VALUE;
for(int x = 0; x < state.mapSize.getX(); x++) {
for(int y = 0; y < state.mapSize.getY(); y++) {
IntVector2 pos = new IntVector2(x, y);
for(Entity e: state.entities.findByPosition(pos)) {
if(e instanceof InfinityStone || (e instanceof Character && ((Character)e).inventory.getSize() > 0)) {
float distance = thanos.getPosition().distanceChebyshev(pos);
if(distance < lowestDistance) {
picked = pos;
lowestDistance = distance;
break;
}
}
}
}
}
//
}
return result; return result;
} }

View File

@ -49,8 +49,8 @@ class GameStateManager {
* Handles events that happen after a turn phase. * Handles events that happen after a turn phase.
* @return The optionally resulting {@link Event}s * @return The optionally resulting {@link Event}s
*/ */
public Event[] checkPostPhase() { public ArrayList<Event> checkPostPhase() {
return (Event[])GameLogic.checkTurnEnd(state).toArray(); return GameLogic.checkTurnEnd(state);
} }
/** /**
@ -59,23 +59,23 @@ class GameStateManager {
* @param selectedCharacters2 The characters selected by player 2 * @param selectedCharacters2 The characters selected by player 2
* @return The resulting {@link Event}s * @return The resulting {@link Event}s
*/ */
public Event[] initGame(ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) { public ArrayList<Event> initGame(ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) {
return GameLogic.startGame(state, selectedCharacters1, selectedCharacters2).toArray(new Event[0]); return GameLogic.startGame(state, selectedCharacters1, selectedCharacters2);
} }
/** /**
* Starts the game. * Starts the game.
* @return The resulting {@link Event}s * @return The resulting {@link Event}s
*/ */
public Event[] startGame() { public ArrayList<Event> startGame() {
return GameLogic.handleRoundStart(state).toArray(new Event[0]); 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
*/ */
public void applyEvents(Event... events) { public void applyEvents(ArrayList<Event> events) {
for(Event event: events) { for(Event event: events) {
GameLogic.applyEvent(state, event); GameLogic.applyEvent(state, event);
} }
@ -85,13 +85,12 @@ class GameStateManager {
* 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 Event[] apply() { public ArrayList<Event> apply() {
Event[] toReturn = new Event[queue.size()]; ArrayList<Event> toReturn = new ArrayList<Event>(queue.size());
Event current; Event current;
int i = 0;
while ((current = queue.poll()) != null) { while ((current = queue.poll()) != null) {
GameLogic.applyEvent(state, current); GameLogic.applyEvent(state, current);
toReturn[i++] = current; toReturn.add(current);
} }
return toReturn; return toReturn;
} }

View File

@ -7,6 +7,8 @@ import uulm.teamname.marvelous.gamelibrary.IntVector2;
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.Event;
import uulm.teamname.marvelous.gamelibrary.events.EventBuilder;
import uulm.teamname.marvelous.gamelibrary.events.EventType;
import uulm.teamname.marvelous.gamelibrary.json.config.*; import uulm.teamname.marvelous.gamelibrary.json.config.*;
import uulm.teamname.marvelous.gamelibrary.requests.*; import uulm.teamname.marvelous.gamelibrary.requests.*;
@ -16,6 +18,7 @@ import java.util.List;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class GameLogicTest { class GameLogicTest {
private static final Iterator<Integer> randomIntegers = ThreadLocalRandom.current().ints().iterator(); private static final Iterator<Integer> randomIntegers = ThreadLocalRandom.current().ints().iterator();
@ -24,6 +27,9 @@ class GameLogicTest {
private static CharacterConfig characterConfig; private static CharacterConfig characterConfig;
private static ScenarioConfig scenarioConfig; private static ScenarioConfig scenarioConfig;
private static ArrayList<Integer> player1Selection = new ArrayList<>();
private static ArrayList<Integer> player2Selection = new ArrayList<>();
@BeforeAll @BeforeAll
static void setUp() { static void setUp() {
partyConfig = new PartyConfig(); partyConfig = new PartyConfig();
@ -55,6 +61,14 @@ class GameLogicTest {
} }
} }
} }
for(int i = 0; i < 6; i++) {
player1Selection.add(i);
}
for(int i = 6; i < 12; i++) {
player2Selection.add(i);
}
} }
private static String generateName(int length) { private static String generateName(int length) {
@ -87,16 +101,7 @@ class GameLogicTest {
void testGeneration() { void testGeneration() {
GameInstance game = new GameInstance(partyConfig, characterConfig, scenarioConfig); GameInstance game = new GameInstance(partyConfig, characterConfig, scenarioConfig);
ArrayList<Integer> selected1 = new ArrayList<>(); game.startGame(player1Selection, player2Selection);
for(int i = 0; i < 6; i++) {
selected1.add(i);
}
ArrayList<Integer> selected2 = new ArrayList<>();
for(int i = 6; i < 12; i++) {
selected2.add(i);
}
game.startGame(selected1, selected2);
int n = 0; int n = 0;
@ -114,7 +119,7 @@ class GameLogicTest {
n = 0; n = 0;
for(Integer i: selected1) { for(Integer i: player1Selection) {
Entity found = game.state.getEntities().findEntity(new EntityID(EntityType.P1, n)); Entity found = game.state.getEntities().findEntity(new EntityID(EntityType.P1, n));
assertNotEquals(null, found, "Character Entity "+n+" for Player 1 should exist"); assertNotEquals(null, found, "Character Entity "+n+" for Player 1 should exist");
Character c = (Character)found; Character c = (Character)found;
@ -129,7 +134,7 @@ class GameLogicTest {
n = 0; n = 0;
for(Integer i: selected2) { for(Integer i: player2Selection) {
Entity found = game.state.getEntities().findEntity(new EntityID(EntityType.P2, n)); Entity found = game.state.getEntities().findEntity(new EntityID(EntityType.P2, n));
assertNotEquals(null, found, "Character Entity "+n+" for Player 2 should exist"); assertNotEquals(null, found, "Character Entity "+n+" for Player 2 should exist");
Character c = (Character)found; Character c = (Character)found;
@ -165,26 +170,15 @@ class GameLogicTest {
void testGame() { void testGame() {
GameInstance game = new GameInstance(partyConfig, characterConfig, scenarioConfig); GameInstance game = new GameInstance(partyConfig, characterConfig, scenarioConfig);
game.addObserver(new EventObserver() { ArrayList<Event> result = game.startGame(player1Selection, player2Selection);
@Override
protected void handle(Event[] events) {
} assertTrue(result.size() > 0, "Start game should return at least one event");
});
ArrayList<Integer> selected1 = new ArrayList<>(); Event actual = result.get(0);
for(int i = 0; i < 6; i++) {
selected1.add(i);
}
ArrayList<Integer> selected2 = new ArrayList<>();
for(int i = 6; i < 12; i++) {
selected2.add(i);
}
game.startGame(selected1, selected2); assertEquals(EventType.GamestateEvent, actual.type, "First event should be a GameStateEvent");
} }
// @Provide("gamestate") // @Provide("gamestate")
// Arbitrary<GameState> gamestate() { // Arbitrary<GameState> gamestate() {
// var states = Arbitraries.integers() // var states = Arbitraries.integers()