329 lines
13 KiB
Java
329 lines
13 KiB
Java
package uulm.teamname.marvelous.gamelibrary.gamelogic;
|
|
|
|
import net.jqwik.api.*;
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
import org.junit.jupiter.api.Test;
|
|
import uulm.teamname.marvelous.gamelibrary.IntVector2;
|
|
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.EventBuilder;
|
|
import uulm.teamname.marvelous.gamelibrary.events.EventType;
|
|
import uulm.teamname.marvelous.gamelibrary.json.config.*;
|
|
import uulm.teamname.marvelous.gamelibrary.requests.*;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
import static org.mockito.Mockito.*;
|
|
|
|
class GameLogicTest {
|
|
private static final Iterator<Integer> randomIntegers = ThreadLocalRandom.current().ints().iterator();
|
|
|
|
private static PartyConfig partyConfig;
|
|
private static CharacterConfig characterConfig;
|
|
private static ScenarioConfig scenarioConfig;
|
|
|
|
private static ArrayList<Integer> player1Selection = new ArrayList<>();
|
|
private static ArrayList<Integer> player2Selection = new ArrayList<>();
|
|
|
|
@BeforeAll
|
|
static void setUp() {
|
|
partyConfig = new PartyConfig();
|
|
partyConfig.maxRounds = 100;
|
|
partyConfig.mindStoneCD = 2;
|
|
partyConfig.powerStoneCD = 3;
|
|
partyConfig.realityStoneCD = 4;
|
|
partyConfig.soulStoneCD = 5;
|
|
partyConfig.spaceStoneCD = 6;
|
|
partyConfig.timeStoneCD = 7;
|
|
partyConfig.mindStoneDMG = 3;
|
|
|
|
characterConfig = new CharacterConfig();
|
|
characterConfig.characters = new CharacterProperties[] {
|
|
generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(),
|
|
generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter()
|
|
};
|
|
|
|
scenarioConfig = new ScenarioConfig();
|
|
scenarioConfig.name = generateName(20);
|
|
scenarioConfig.author = generateName(20);
|
|
scenarioConfig.scenario = new FieldType[30][30];
|
|
for(int x = 0; x < scenarioConfig.scenario[0].length; x++) {
|
|
for(int y = 0; y < scenarioConfig.scenario.length; y++) {
|
|
if(Math.abs(randomIntegers.next() % 100) < 10) {
|
|
scenarioConfig.scenario[y][x] = FieldType.ROCK;
|
|
}else {
|
|
scenarioConfig.scenario[y][x] = FieldType.GRASS;
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
StringBuilder name = new StringBuilder();
|
|
for (int j = 0; j < length; j++) {
|
|
name.append((char) (
|
|
65 + Math.abs(randomIntegers.next() % 26) + 32 * Math.abs(randomIntegers.next() % 2)
|
|
));
|
|
}
|
|
return name.toString();
|
|
}
|
|
|
|
private static CharacterProperties generateCharacter() {
|
|
CharacterProperties props = new CharacterProperties();
|
|
|
|
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);
|
|
|
|
return props;
|
|
}
|
|
|
|
|
|
@Test
|
|
void testGeneration() {
|
|
GameInstance game = new GameInstance(partyConfig, characterConfig, scenarioConfig);
|
|
|
|
game.startGame(player1Selection, player2Selection);
|
|
|
|
int n = 0;
|
|
|
|
for(int x = 0; x < scenarioConfig.scenario[0].length; x++) {
|
|
for(int y = 0; y < scenarioConfig.scenario.length; y++) {
|
|
if(scenarioConfig.scenario[y][x] == FieldType.ROCK) {
|
|
ArrayList<Entity> found = game.state.getEntities().findByPosition(new IntVector2(x, y));
|
|
assertEquals(1, found.size(), "Rock Entity "+n+" should exist");
|
|
assertEquals(EntityType.Rocks, found.get(0).id.type, "Rock Entity "+n+" should be of type Rock");
|
|
assertEquals(100, ((Rock)found.get(0)).getHp(), "Rock Entity "+n+" should have 100 HP");
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
|
|
n = 0;
|
|
|
|
for(Integer i: player1Selection) {
|
|
Entity found = game.state.getEntities().findEntity(new EntityID(EntityType.P1, n));
|
|
assertNotEquals(null, found, "Character Entity "+n+" for Player 1 should exist");
|
|
Character c = (Character)found;
|
|
assertEquals(characterConfig.characters[i].HP, c.hp.getMax(), "Character "+n+" should have the right amount of HP");
|
|
assertEquals(characterConfig.characters[i].MP, c.mp.getMax(), "Character "+n+" should have the right amount of MP");
|
|
assertEquals(characterConfig.characters[i].AP, c.ap.getMax(), "Character "+n+" should have the right amount of AP");
|
|
assertEquals(characterConfig.characters[i].meleeDamage, c.meleeDamage, "Character "+n+" should have the right amount of meleeDamage");
|
|
assertEquals(characterConfig.characters[i].rangedDamage, c.rangedDamage, "Character "+n+" should have the right amount of rangedDamage");
|
|
assertEquals(characterConfig.characters[i].attackRange, c.attackRange, "Character "+n+" should have the right amount of attackRange");
|
|
n++;
|
|
}
|
|
|
|
n = 0;
|
|
|
|
for(Integer i: player2Selection) {
|
|
Entity found = game.state.getEntities().findEntity(new EntityID(EntityType.P2, n));
|
|
assertNotEquals(null, found, "Character Entity "+n+" for Player 2 should exist");
|
|
Character c = (Character)found;
|
|
assertEquals(characterConfig.characters[i].HP, c.hp.getMax(), "Character "+n+" should have the right amount of HP");
|
|
assertEquals(characterConfig.characters[i].MP, c.mp.getMax(), "Character "+n+" should have the right amount of MP");
|
|
assertEquals(characterConfig.characters[i].AP, c.ap.getMax(), "Character "+n+" should have the right amount of AP");
|
|
assertEquals(characterConfig.characters[i].meleeDamage, c.meleeDamage, "Character "+n+" should have the right amount of meleeDamage");
|
|
assertEquals(characterConfig.characters[i].rangedDamage, c.rangedDamage, "Character "+n+" should have the right amount of rangedDamage");
|
|
assertEquals(characterConfig.characters[i].attackRange, c.attackRange, "Character "+n+" should have the right amount of attackRange");
|
|
n++;
|
|
}
|
|
}
|
|
|
|
@Test
|
|
void testSnapshot() {
|
|
GameState state = new GameState(partyConfig, characterConfig, scenarioConfig);
|
|
|
|
state.entities.addEntity(new Rock(new EntityID(EntityType.Rocks, 0), new IntVector2(0, 0), 100));
|
|
|
|
GameState snapshot = state.snapshot();
|
|
|
|
snapshot.turnNumber = 10;
|
|
assertEquals(0, state.turnNumber, "Original's turn number should remain unchanged");
|
|
|
|
assertTrue(snapshot.entities.getEntities().hasNext(), "Snapshot should contain cloned entities");
|
|
|
|
((Rock)snapshot.entities.getEntities().next()).decreaseHp(5);
|
|
assertEquals(100, ((Rock)state.entities.getEntities().next()).getHp(), "Original's rock entity hp should remain unchanged");
|
|
}
|
|
|
|
|
|
@Test
|
|
void testGame() {
|
|
GameInstance game = new GameInstance(partyConfig, characterConfig, scenarioConfig);
|
|
|
|
ArrayList<Event> result = game.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");
|
|
}
|
|
|
|
// @Provide("gamestate")
|
|
// Arbitrary<GameState> gamestate() {
|
|
// var states = Arbitraries.integers()
|
|
// .tuple2()
|
|
// .map(x -> new IntVector2(x.get1(), x.get2()))
|
|
// .map(GameState::new);
|
|
// }
|
|
|
|
// @Provide("entityManager")
|
|
// Arbitrary<EntityManager> entityManager() {
|
|
// var managers = Arbitraries.of(EntityManager.class);
|
|
//
|
|
// }
|
|
|
|
// @Provide("entity")
|
|
// Arbitrary<Entity> entity() {
|
|
// var entities = Arbitraries.of(Entity.class);
|
|
// }
|
|
|
|
// Characters are kind of hard, but most prequisites are here
|
|
|
|
// @Provide("character")
|
|
// Arbitrary<Character> character() {
|
|
// var characters = Combinators.combine(
|
|
// anyStat(),
|
|
// Arbitraries.integers()
|
|
// ).as(x -> new Character());
|
|
// }
|
|
|
|
@Provide("stat")
|
|
Arbitrary<Stat> anyStat() {
|
|
var stats = Combinators.combine(
|
|
Arbitraries.of(StatType.class),
|
|
Arbitraries.integers())
|
|
.as(Stat::new);
|
|
return Combinators.combine(stats, Arbitraries.integers().greaterOrEqual(0))
|
|
.as((stat, decrease) -> {
|
|
stat.decreaseValue(decrease % (stat.getMax() + 1));
|
|
return stat;
|
|
});
|
|
}
|
|
|
|
|
|
@Provide("entityIDs")
|
|
Arbitrary<EntityID> entityIDs() {
|
|
return Combinators.combine(
|
|
Arbitraries.of(EntityType.class),
|
|
Arbitraries.integers()
|
|
).as((type, id) -> {
|
|
EntityID eid;
|
|
switch (type) {
|
|
case NPC -> eid = new EntityID(type, id % 3);
|
|
case P1, P2, InfinityStones -> eid = new EntityID(type, id % 6);
|
|
case Rocks -> eid = new EntityID(type, id);
|
|
default -> throw new IllegalStateException("Unexpected value: " + type);
|
|
}
|
|
return eid;
|
|
});
|
|
}
|
|
|
|
@Provide("positions")
|
|
Arbitrary<IntVector2> positions() {
|
|
return Arbitraries.integers()
|
|
.greaterOrEqual(0)
|
|
.tuple2()
|
|
.map(x -> new IntVector2(x.get1(), x.get2()));
|
|
}
|
|
|
|
@Provide("requests")
|
|
Arbitrary<Request> requests() {
|
|
return Combinators.combine(
|
|
Arbitraries.of(true, false),
|
|
gameRequests(),
|
|
characterRequests())
|
|
.as((which, gameRequest, characterRequest) -> which ? gameRequest : characterRequest);
|
|
}
|
|
|
|
@Provide("gameRequests")
|
|
Arbitrary<GameRequest> gameRequests() {
|
|
final var gameRequestTypes = List.of(
|
|
RequestType.PauseStopRequest,
|
|
RequestType.PauseStartRequest,
|
|
RequestType.EndRoundRequest,
|
|
RequestType.DisconnectRequest,
|
|
RequestType.Req);
|
|
|
|
|
|
return Arbitraries.of(RequestType.class)
|
|
.filter(gameRequestTypes::contains)
|
|
.map(x -> new RequestBuilder(x).buildGameRequest());
|
|
}
|
|
|
|
@Provide("characterRequests")
|
|
Arbitrary<CharacterRequest> characterRequests() {
|
|
|
|
final var characterRequestTypes = List.of(
|
|
RequestType.MeleeAttackRequest,
|
|
RequestType.RangedAttackRequest,
|
|
RequestType.MoveRequest,
|
|
RequestType.ExchangeInfinityStoneRequest,
|
|
RequestType.UseInfinityStoneRequest);
|
|
|
|
var positions = Arbitraries.integers()
|
|
.greaterOrEqual(0)
|
|
.tuple2()
|
|
.map(x -> new IntVector2(x.get1(), x.get2()));
|
|
|
|
|
|
var characterIDs = Combinators.combine(Arbitraries.of(true, false)
|
|
.map(x -> x ? EntityType.P1 : EntityType.P2),
|
|
Arbitraries.integers()
|
|
.between(0, 5))
|
|
.as(EntityID::new);
|
|
|
|
var rockIDs = Arbitraries.integers()
|
|
.greaterOrEqual(0)
|
|
.map(x -> new EntityID(EntityType.Rocks, x));
|
|
|
|
var targetIDs = Combinators.combine(
|
|
Arbitraries.of(true, false),
|
|
characterIDs,
|
|
rockIDs)
|
|
.as((which, characterID, rockID) -> which ? characterID : rockID);
|
|
|
|
var stoneTypes = Arbitraries.of(StoneType.class);
|
|
|
|
var values = Arbitraries.integers();
|
|
|
|
return Combinators.combine(
|
|
Arbitraries.of(RequestType.class).filter(characterRequestTypes::contains), // types
|
|
positions.tuple2(),
|
|
characterIDs,
|
|
targetIDs,
|
|
stoneTypes,
|
|
values
|
|
).as((type, pos, characterID, targetID, stoneType, val) -> {
|
|
return new RequestBuilder(type)
|
|
.withOriginField(pos.get1())
|
|
.withTargetField(pos.get2())
|
|
.withOriginEntity(characterID)
|
|
.withTargetEntity(targetID)
|
|
.withStoneType(stoneType)
|
|
.withValue(val)
|
|
.buildCharacterRequest();
|
|
});
|
|
}
|
|
}
|