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.config.*; import uulm.teamname.marvelous.gamelibrary.entities.*; import uulm.teamname.marvelous.gamelibrary.entities.Character; import uulm.teamname.marvelous.gamelibrary.requests.*; import java.util.*; import java.util.concurrent.ThreadLocalRandom; import static org.junit.jupiter.api.Assertions.*; class GameLogicTest { private static final Iterator randomIntegers = ThreadLocalRandom.current().ints().iterator(); private static final PartyConfig partyConfig = new PartyConfig(); private static final CharacterConfig characterConfig = new CharacterConfig(); private static final ScenarioConfig scenarioConfig = new ScenarioConfig(); private static final ArrayList player1Selection = new ArrayList<>(); private static final ArrayList player2Selection = new ArrayList<>(); @BeforeAll static void setUp() { 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.characters = new CharacterProperties[] { generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter(), generateCharacter() }; scenarioConfig.name = generateName(20); scenarioConfig.author = generateName(20); scenarioConfig.scenario = new FieldType[20][20]; 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() % 15 + 5); props.MP = Math.abs(randomIntegers.next() % 5 + 2); props.AP = Math.abs(randomIntegers.next() % 5 + 2); props.meleeDamage = Math.abs(randomIntegers.next() % 5 + 2); props.rangedDamage = Math.abs(randomIntegers.next() % 5 + 2); props.attackRange = Math.abs(randomIntegers.next() % 5 + 2); 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 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 server = new GameInstance(partyConfig, characterConfig, scenarioConfig); server.startGame(player1Selection, player2Selection); System.out.println(server.toString()); } @Test void testRasterize() { ArrayList 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"); } @Test void testPathfind() { int size = 20; IntVector2 a = new IntVector2(Math.abs(randomIntegers.next() % size), Math.abs(randomIntegers.next() % size)); IntVector2 b = new IntVector2(Math.abs(randomIntegers.next() % size), Math.abs(randomIntegers.next() % size)); ArrayList result = GameLogic.Bresenham4Connected(a, b); StringBuilder sb = new StringBuilder(); for(int y = 0; y < size; y++) { for(int x = 0; x < size; x++) { IntVector2 pos = new IntVector2(x, y); if(pos.equals(a)) { sb.append("A "); }else if(pos.equals(b)) { sb.append("B "); }else if(result.contains(pos)) { sb.append("o "); }else { sb.append(". "); } } sb.append("\n"); } System.out.println(sb.toString()); } // @Provide("gamestate") // Arbitrary gamestate() { // var states = Arbitraries.integers() // .tuple2() // .map(x -> new IntVector2(x.get1(), x.get2())) // .map(GameState::new); // } // @Provide("entityManager") // Arbitrary entityManager() { // var managers = Arbitraries.of(EntityManager.class); // // } // @Provide("entity") // Arbitrary entity() { // var entities = Arbitraries.of(Entity.class); // } // Characters are kind of hard, but most prequisites are here // @Provide("character") // Arbitrary character() { // var characters = Combinators.combine( // anyStat(), // Arbitraries.integers() // ).as(x -> new Character()); // } @Provide("stat") Arbitrary 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 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 positions() { return Arbitraries.integers() .greaterOrEqual(0) .tuple2() .map(x -> new IntVector2(x.get1(), x.get2())); } @Provide("requests") Arbitrary requests() { return Combinators.combine( Arbitraries.of(true, false), gameRequests(), characterRequests()) .as((which, gameRequest, characterRequest) -> which ? gameRequest : characterRequest); } @Provide("gameRequests") Arbitrary 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 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(); }); } }