diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/AI.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/AI.java index dc686db..9e25077 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/AI.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/AI.java @@ -47,8 +47,8 @@ class AI { Character character = (Character)state.getEntities().findEntity(turn); - BoardAnalyzer analyzer = new BoardAnalyzer(state, player); - Action action = analyzer.analyze(character.getPosition(), player, partyConfig, characterConfig, scenarioConfig); + BoardAnalyzer analyzer = new BoardAnalyzer(state, turn); + Action action = analyzer.analyze(state, character.getPosition(), turn); switch(action.type) { case None -> { diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/Action.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/Action.java index 6f489ec..e0fb690 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/Action.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/Action.java @@ -38,6 +38,13 @@ class Action { this.stone = stone; } + public Action(ActionType type, IntVector2 target, StoneType stone) { + this.type = type; + this.target = target; + this.targetEntity = null; + this.stone = stone; + } + public Action(ActionType type, IntVector2 target, EntityID targetEntity, StoneType stone) { this.type = type; this.target = target; diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/Board.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/Board.java index 3b2e320..44926af 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/Board.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/Board.java @@ -3,6 +3,7 @@ package uulm.teamname.marvelous.gamelibrary.ai; import uulm.teamname.marvelous.gamelibrary.IntVector2; import uulm.teamname.marvelous.gamelibrary.entities.*; import uulm.teamname.marvelous.gamelibrary.entities.Character; +import uulm.teamname.marvelous.gamelibrary.gamelogic.GameLogic; import uulm.teamname.marvelous.gamelibrary.gamelogic.GameStateView; import java.util.ArrayList; @@ -10,14 +11,16 @@ import java.util.Objects; class Board { private final Piece[][] data; - private final EntityType turn; + private final EntityType origin; + private final EntityID turn; - private Board(Piece[][] data, EntityType turn) { + private Board(Piece[][] data, EntityType origin, EntityID turn) { this.data = data; + this.origin = origin; this.turn = turn; } - public static Board generate(GameStateView state, EntityType player) { + public static Board generate(GameStateView state, EntityID turn) { Piece[][] data = new Piece[state.getMapSize().getY()][state.getMapSize().getX()]; for(int x = 0; x < state.getMapSize().getX(); x++) { for(int y = 0; y < state.getMapSize().getY(); y++) { @@ -64,18 +67,169 @@ class Board { } } } - return new Board(data, player); + return new Board(data, turn.type, turn); } - protected ArrayList generateActions() { + protected ArrayList generateActions(GameStateView state) { ArrayList result = new ArrayList<>(); - //TODO: generate possible actions + EntityType opposite = turn.type == EntityType.P1 ? EntityType.P2 : EntityType.P1; + + Character character = (Character)state.getEntities().findEntity(turn); + + Piece current = null; + IntVector2 position = null; + for(int x = 0; x < this.data[0].length; x++) { + for(int y = 0; y < this.data.length; y++) { + if(this.data[y][x].type == PieceType.Character && this.data[y][x].id.equals(turn)) { + current = this.data[y][x]; + position = new IntVector2(x, y); + } + } + } + + if(current == null) { + return result; + } + + for(ActionType action: ActionType.values()) { + switch(action) { + case Move -> { + if(current.mp.getValue() <= 0) { + continue; + } + for(IntVector2 dir: IntVector2.CardinalDirections) { + IntVector2 target = position.add(dir); + if(target.getX() < 0 || target.getX() >= state.getMapSize().getX() || target.getY() < 0 || target.getY() >= state.getMapSize().getY()) { + continue; + } + if(this.data[target.getY()][target.getX()].type == PieceType.Empty) { + result.add(new Action(ActionType.Move, target)); + } + } + } + case MeleeAttack -> { + if(current.ap.getValue() <= 0) { + continue; + } + for(IntVector2 dir: IntVector2.CardinalDirections) { + IntVector2 target = position.add(dir); + if(target.getX() < 0 || target.getX() >= state.getMapSize().getX() || target.getY() < 0 || target.getY() >= state.getMapSize().getY()) { + continue; + } + Piece targetPiece = this.data[target.getY()][target.getX()]; + if(targetPiece.type == PieceType.Character && targetPiece.id.type == opposite) { + result.add(new Action(ActionType.MeleeAttack, target, targetPiece.id)); + } + } + } + case RangedAttack -> { + if(current.ap.getValue() <= 0) { + continue; + } + for(int x = 0; x < this.data[0].length; x++) { + for(int y = 0; y < this.data.length; y++) { + IntVector2 pos = new IntVector2(x, y); + float dist = pos.distanceChebyshev(position); + if(this.data[y][x].type == PieceType.Character && this.data[y][x].id.type == opposite && dist > 1 && dist <= character.attackRange && lineOfSight(position, pos)) { + result.add(new Action(ActionType.RangedAttack, pos, this.data[y][x].id)); + } + } + } + } + case UseStone -> { + if(current.ap.getValue() <= 0) { + continue; + } + for(StoneType stone: StoneType.values()) { + if(!character.inventory.hasStone(stone)) { + continue; + } + switch(stone) { + case SpaceStone -> { + for(int x = 0; x < this.data[0].length; x++) { + for(int y = 0; y < this.data.length; y++) { + if(this.data[y][x].type == PieceType.Empty) { + result.add(new Action(ActionType.UseStone, new IntVector2(x, y), stone)); + } + } + } + } + case MindStone -> { + for(int x = 0; x < this.data[0].length; x++) { + for(int y = 0; y < this.data.length; y++) { + result.add(new Action(ActionType.UseStone, new IntVector2(x, y), stone)); + } + } + } + case RealityStone -> { + for(IntVector2 dir: IntVector2.CardinalDirections) { + IntVector2 target = position.add(dir); + if(target.getX() < 0 || target.getX() >= state.getMapSize().getX() || target.getY() < 0 || target.getY() >= state.getMapSize().getY()) { + continue; + } + Piece targetPiece = this.data[target.getY()][target.getX()]; + if(targetPiece.type == PieceType.Empty) { + result.add(new Action(ActionType.UseStone, target, turn, stone)); + }else if(targetPiece.type == PieceType.Rock) { + result.add(new Action(ActionType.UseStone, target, targetPiece.id, stone)); + } + } + } + case PowerStone -> { + for(IntVector2 dir: IntVector2.CardinalDirections) { + IntVector2 target = position.add(dir); + if(target.getX() < 0 || target.getX() >= state.getMapSize().getX() || target.getY() < 0 || target.getY() >= state.getMapSize().getY()) { + continue; + } + Piece targetPiece = this.data[target.getY()][target.getX()]; + if(targetPiece.type == PieceType.Character && targetPiece.id.type == opposite) { + result.add(new Action(ActionType.UseStone, target, targetPiece.id, stone)); + } + } + } + case TimeStone -> { + result.add(new Action(ActionType.UseStone, stone)); + } + case SoulStone -> { + for(IntVector2 dir: IntVector2.CardinalDirections) { + IntVector2 target = position.add(dir); + if(target.getX() < 0 || target.getX() >= state.getMapSize().getX() || target.getY() < 0 || target.getY() >= state.getMapSize().getY()) { + continue; + } + Piece targetPiece = this.data[target.getY()][target.getX()]; + if(targetPiece.type == PieceType.Character && targetPiece.id.type == turn.type && targetPiece.hp.getValue() <= 0) { + result.add(new Action(ActionType.UseStone, target, targetPiece.id, stone)); + } + } + } + } + } + } + case GiveStone -> { + if(current.ap.getValue() <= 0) { + continue; + } + for(StoneType stone: current.inventory) { + for(IntVector2 dir: IntVector2.CardinalDirections) { + IntVector2 target = position.add(dir); + if(target.getX() < 0 || target.getX() >= state.getMapSize().getX() || target.getY() < 0 || target.getY() >= state.getMapSize().getY()) { + continue; + } + Piece targetPiece = this.data[target.getY()][target.getX()]; + if(targetPiece.type == PieceType.Character && targetPiece.id.type == turn.type) { + result.add(new Action(ActionType.GiveStone, target, targetPiece.id, stone)); + } + } + } + } + } + } return result; } - protected Board applyAction(Action action) { + protected Board applyAction(GameStateView state, Action action) { Piece[][] clone = new Piece[this.data.length][this.data[0].length]; for(int x = 0; x < this.data[0].length; x++) { @@ -84,17 +238,111 @@ class Board { } } - //TODO: apply action + Character character = (Character)state.getEntities().findEntity(turn); - return new Board(clone, turn == EntityType.P1 ? EntityType.P2 : EntityType.P1); + Piece current = null; + IntVector2 position = null; + for(int x = 0; x < this.data[0].length; x++) { + for(int y = 0; y < this.data.length; y++) { + if(this.data[y][x].type == PieceType.Character && this.data[y][x].id.equals(turn)) { + current = this.data[y][x]; + position = new IntVector2(x, y); + } + } + } + + //TODO: finalize action applying + + switch(action.type) { + case Move -> { + clone[position.getY()][position.getX()] = clone[action.target.getY()][action.target.getX()]; + clone[action.target.getY()][action.target.getX()] = current; + current.mp.decreaseValue(1); + } + case MeleeAttack -> { + clone[action.target.getY()][action.target.getX()].hp.decreaseValue(character.meleeDamage); + current.ap.decreaseValue(1); + } + case RangedAttack -> { + clone[action.target.getY()][action.target.getX()].hp.decreaseValue(character.meleeDamage); + current.ap.decreaseValue(1); + } + case UseStone -> { + switch(action.stone) { + case SpaceStone -> { + + } + case MindStone -> { + + } + case RealityStone -> { + + } + case PowerStone -> { + + } + case TimeStone -> { + + } + case SoulStone -> { + + } + } + } + case GiveStone -> { + + } + } + + //...get next turn in turn order... + //TODO: figure out next turn properly + return new Board(clone, origin, turn); } - protected int calculateScore() { - //TODO: create score calculation - return 0; + protected float calculateScore() { + float score = 0; + + for(int x = 0; x < this.data[0].length; x++) { + for(int y = 0; y < this.data.length; y++) { + IntVector2 current = new IntVector2(x, y); + Piece piece = this.data[y][x]; + switch(piece.type) { + case Empty, Rock, NPC, InfinityStone, Thanos -> { + score += 0; + } + case Character -> { + float nearby = 0; + for(int x2 = 0; x2 < this.data[0].length; x2++) { + for(int y2 = 0; y2 < this.data.length; y2++) { + if(this.data[y2][x2].type == PieceType.InfinityStone) { + nearby += (1f / new IntVector2(x2, y2).distance(current)) * 3f; + } + } + } + float result = nearby + piece.inventory.size() + ((float)piece.hp.getValue() / (float)piece.hp.getMax()) * 2f; + if(piece.id.type == origin) { + score += result; + }else { + score -= result; + } + } + } + } + } + + return score; } protected int calculateHash() { return Objects.hash(super.hashCode(), data); } + + private boolean lineOfSight(IntVector2 a, IntVector2 b) { + for(IntVector2 pos: GameLogic.rasterize(a, b, false, false)) { + if(this.data[pos.getY()][pos.getX()].type != PieceType.Empty) { + return false; + } + } + return true; + } } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/BoardAnalyzer.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/BoardAnalyzer.java index 72e6b02..2f348b0 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/BoardAnalyzer.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/ai/BoardAnalyzer.java @@ -1,9 +1,7 @@ package uulm.teamname.marvelous.gamelibrary.ai; import uulm.teamname.marvelous.gamelibrary.IntVector2; -import uulm.teamname.marvelous.gamelibrary.config.CharacterConfig; -import uulm.teamname.marvelous.gamelibrary.config.PartyConfig; -import uulm.teamname.marvelous.gamelibrary.config.ScenarioConfig; +import uulm.teamname.marvelous.gamelibrary.entities.EntityID; import uulm.teamname.marvelous.gamelibrary.entities.EntityType; import uulm.teamname.marvelous.gamelibrary.gamelogic.GameStateView; @@ -13,15 +11,15 @@ import java.util.HashMap; class BoardAnalyzer { private final Board origin; private final EntityType player; - private final HashMap cache = new HashMap<>(); + private final HashMap cache = new HashMap<>(); - public BoardAnalyzer(GameStateView state, EntityType player) { - this.origin = Board.generate(state, player); - this.player = player; + public BoardAnalyzer(GameStateView state, EntityID turn) { + this.origin = Board.generate(state, turn); + this.player = turn.type; } - public Action analyze(IntVector2 position, EntityType turn, PartyConfig partyConfig, CharacterConfig characterConfig, ScenarioConfig scenarioConfig) { - ArrayList actions = origin.generateActions(); + public Action analyze(GameStateView state, IntVector2 position, EntityID turn) { + ArrayList actions = origin.generateActions(state); //TODO: create minimax tree