fix: improve rasterization and tests

This commit is contained in:
punchready 2021-06-01 17:15:44 +02:00
parent 0572450ced
commit 5984e40384
4 changed files with 95 additions and 59 deletions

View File

@ -1,17 +0,0 @@
package uulm.teamname.marvelous.gamelibrary.gamelogic;
import uulm.teamname.marvelous.gamelibrary.events.Event;
import java.util.Observable;
/** Represents an event emitter for game events fired by a game instance. */
class EventEmitter extends Observable {
/**
* Emits an array of {@link Event}s. This method is necessary because {@link Observable#setChanged} is protected.
* @param events The events to emit
*/
public void update(Event... events) {
setChanged();
notifyObservers(events);
}
}

View File

@ -1,22 +0,0 @@
package uulm.teamname.marvelous.gamelibrary.gamelogic;
import uulm.teamname.marvelous.gamelibrary.events.Event;
import java.util.Observable;
import java.util.Observer;
/** Represents an event observer for game events emitted by an {@link EventEmitter}. */
public class EventObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
handle((Event[])arg);
}
/**
* Gets called with incoming {@link Event}s. Override this method to handle events.
* @param events The events that got emitted
*/
protected void handle(Event[] events) {
}
}

View File

@ -1,6 +1,7 @@
package uulm.teamname.marvelous.gamelibrary.gamelogic; package uulm.teamname.marvelous.gamelibrary.gamelogic;
import uulm.teamname.marvelous.gamelibrary.IntVector2; import uulm.teamname.marvelous.gamelibrary.IntVector2;
import uulm.teamname.marvelous.gamelibrary.Tuple;
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.*; import uulm.teamname.marvelous.gamelibrary.events.*;
@ -11,7 +12,9 @@ import uulm.teamname.marvelous.gamelibrary.requests.Request;
import uulm.teamname.marvelous.gamelibrary.requests.RequestType; import uulm.teamname.marvelous.gamelibrary.requests.RequestType;
import java.awt.*; import java.awt.*;
import java.awt.geom.Area;
import java.awt.geom.Line2D; import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.*; import java.util.*;
@ -98,7 +101,7 @@ class GameLogic {
} }
case MindStone -> { case MindStone -> {
EntityType target = data.originEntity.type == EntityType.P1 ? EntityType.P2 : EntityType.P1; EntityType target = data.originEntity.type == EntityType.P1 ? EntityType.P2 : EntityType.P1;
for(IntVector2 pos: rasterize(data.originField, data.targetField)) { for(IntVector2 pos: rasterize(data.originField, data.targetField, false, true)) {
for(Entity entity: state.entities.findByPosition(pos)) { for(Entity entity: state.entities.findByPosition(pos)) {
if(entity.id.isSameType(target)) { if(entity.id.isSameType(target)) {
result.add(new EventBuilder(EventType.TakenDamageEvent) result.add(new EventBuilder(EventType.TakenDamageEvent)
@ -436,7 +439,7 @@ class GameLogic {
* @return Whether or not the light of sight exists * @return Whether or not the light of sight exists
*/ */
private static boolean checkLineOfSight(GameState state, IntVector2 start, IntVector2 end) { private static boolean checkLineOfSight(GameState state, IntVector2 start, IntVector2 end) {
for(IntVector2 pos: rasterize(start, end)) { for(IntVector2 pos: rasterize(start, end, false, false)) {
if(state.entities.blocksVision(pos)) { if(state.entities.blocksVision(pos)) {
return false; return false;
} }
@ -949,28 +952,84 @@ class GameLogic {
} }
/** /**
* Computes all fields which intersect the line between the center points of the given two fields. * Computes all fields which intersect the line between the center points of the given two fields including the two defining points.
* @return The list of intersecting positions * @return The list of intersecting positions
*/ */
public static ArrayList<IntVector2> rasterize(IntVector2 a, IntVector2 b) { public static ArrayList<IntVector2> rasterize(IntVector2 a, IntVector2 b) {
ArrayList<IntVector2> result = new ArrayList<>(); return rasterize(a, b, true, true);
}
//TODO: implement proper line rasterization algorithm in GameLogic.rasterize /**
* Computes all fields which intersect the line between the center points of the given two fields.
* @param includeA Whether to include point a in the result
* @param includeB Whether to include point b in the result
* @return The list of intersecting positions
*/
public static ArrayList<IntVector2> rasterize(IntVector2 a, IntVector2 b, boolean includeA, boolean includeB) {
ArrayList<IntVector2> result = new ArrayList<>();
int x1 = Math.min(a.getX(), b.getX()); int x1 = Math.min(a.getX(), b.getX());
int x2 = Math.max(a.getX(), b.getX()); int x2 = Math.max(a.getX(), b.getX());
int y1 = Math.min(a.getY(), b.getY()); int y1 = Math.min(a.getY(), b.getY());
int y2 = Math.max(a.getY(), b.getY()); int y2 = Math.max(a.getY(), b.getY());
Line2D line = new Line2D.Float(x1 + 0.5f, y1 + 0.5f, x2 + 0.5f, y2 + 0.5f); Line2D line = new Line2D.Float(x1 + 0.5f, y1 + 0.5f, x2 + 0.5f, y2 + 0.5f);
for(int i = x1; i <= x2; i++) { for(int i = x1; i <= x2; i++) {
for(int j = y1; j <= y2; j++) { for(int j = y1; j <= y2; j++) {
Rectangle2D cell = new Rectangle.Float(i, j, 1, 1); HashSet<Point2D.Float> intersections = new HashSet<>();
if(line.intersects(cell)) { for(Line2D.Float part: getWireframe(new Rectangle.Float(i, j, 1, 1))) {
if(part.intersectsLine(line)) {
Point2D.Float intersection = calculateInterceptionPoint(line.getP1(), line.getP2(), part.getP1(), part.getP2());
intersections.add(intersection);
if(intersections.size() > 1) {
break;
}
}
}
if(intersections.size() > 1) {
result.add(new IntVector2(i, j)); result.add(new IntVector2(i, j));
} }
} }
} }
if(includeA) {
result.add(a);
}
if(includeB) {
result.add(b);
}
return result;
}
//https://rosettacode.org/wiki/Find_the_intersection_of_two_lines#Java
public static Point2D.Float calculateInterceptionPoint(Point2D s1, Point2D s2, Point2D d1, Point2D d2) {
double a1 = s2.getY() - s1.getY();
double b1 = s1.getX() - s2.getX();
double c1 = a1 * s1.getX() + b1 * s1.getY();
double a2 = d2.getY() - d1.getY();
double b2 = d1.getX() - d2.getX();
double c2 = a2 * d1.getX() + b2 * d1.getY();
double delta = a1 * b2 - a2 * b1;
return new Point2D.Float((float) ((b2 * c1 - b1 * c2) / delta), (float) ((a1 * c2 - a2 * c1) / delta));
}
/** Computes all wireframe lines for the given rectangle. */
public static ArrayList<Line2D.Float> getWireframe(Rectangle.Float rect) {
ArrayList<Line2D.Float> result = new ArrayList<>();
Point2D.Float lt = new Point2D.Float(rect.x, rect.y);
Point2D.Float rt = new Point2D.Float(rect.x + rect.width, rect.y);
Point2D.Float rb = new Point2D.Float(rect.x + rect.width, rect.y + rect.height);
Point2D.Float lb = new Point2D.Float(rect.x, rect.y + rect.height);
result.add(new Line2D.Float(lt, rt));
result.add(new Line2D.Float(rt, rb));
result.add(new Line2D.Float(lb, rb));
result.add(new Line2D.Float(lt, lb));
return result; return result;
} }
} }

View File

@ -7,32 +7,27 @@ 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.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.*;
import java.util.ArrayList; import java.util.*;
import java.util.Iterator;
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();
private static PartyConfig partyConfig; private static final PartyConfig partyConfig = new PartyConfig();
private static CharacterConfig characterConfig; private static final CharacterConfig characterConfig = new CharacterConfig();
private static ScenarioConfig scenarioConfig; private static final ScenarioConfig scenarioConfig = new ScenarioConfig();
private static ArrayList<Integer> player1Selection = new ArrayList<>(); private static final ArrayList<Integer> player1Selection = new ArrayList<>();
private static ArrayList<Integer> player2Selection = new ArrayList<>(); private static final ArrayList<Integer> player2Selection = new ArrayList<>();
@BeforeAll @BeforeAll
static void setUp() { static void setUp() {
partyConfig = new PartyConfig();
partyConfig.maxRounds = 100; partyConfig.maxRounds = 100;
partyConfig.mindStoneCD = 2; partyConfig.mindStoneCD = 2;
partyConfig.powerStoneCD = 3; partyConfig.powerStoneCD = 3;
@ -42,13 +37,11 @@ class GameLogicTest {
partyConfig.timeStoneCD = 7; partyConfig.timeStoneCD = 7;
partyConfig.mindStoneDMG = 3; partyConfig.mindStoneDMG = 3;
characterConfig = new CharacterConfig();
characterConfig.characters = new CharacterProperties[] { characterConfig.characters = new CharacterProperties[] {
generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(),
generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter() generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter()
}; };
scenarioConfig = new ScenarioConfig();
scenarioConfig.name = generateName(20); scenarioConfig.name = generateName(20);
scenarioConfig.author = generateName(20); scenarioConfig.author = generateName(20);
scenarioConfig.scenario = new FieldType[30][30]; scenarioConfig.scenario = new FieldType[30][30];
@ -179,6 +172,29 @@ class GameLogicTest {
assertEquals(EventType.GamestateEvent, actual.type, "First event should be a GameStateEvent"); assertEquals(EventType.GamestateEvent, actual.type, "First event should be a GameStateEvent");
} }
@Test
void testRasterize() {
ArrayList<IntVector2> result = GameLogic.rasterize(new IntVector2(0, 0), new IntVector2(1, 1), false, false);
assertEquals(0, result.size(), "Diagonals are not included");
IntVector2[] result2 = GameLogic.rasterize(new IntVector2(0, 0), new IntVector2(4, 0)).toArray(new IntVector2[0]);
assertEquals(new HashSet<>(Arrays.asList(
new IntVector2(0, 0), new IntVector2(1, 0), new IntVector2(2, 0), new IntVector2(3, 0), new IntVector2(4, 0)
)), new HashSet<>(Arrays.asList(result2)), "Straight lines work correctly");
IntVector2[] result3 = GameLogic.rasterize(new IntVector2(0, 0), new IntVector2(2, 1)).toArray(new IntVector2[0]);
assertEquals(new HashSet<>(Arrays.asList(
new IntVector2(0, 0), new IntVector2(1, 0), new IntVector2(1, 1), new IntVector2(2, 1))
), new HashSet<>(Arrays.asList(result3)), "Tilted lines work correctly");
}
// @Provide("gamestate") // @Provide("gamestate")
// Arbitrary<GameState> gamestate() { // Arbitrary<GameState> gamestate() {
// var states = Arbitraries.integers() // var states = Arbitraries.integers()