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; import java.util.Objects; class Board { private final Piece[][] data; public final EntityType origin; public final EntityID turn; public final boolean ended; private Board(Piece[][] data, EntityType origin, EntityID turn) { this.data = data; this.origin = origin; this.turn = turn; this.ended = false; } private Board(Piece[][] data, EntityType origin, EntityID turn, boolean ended) { this.data = data; this.origin = origin; this.turn = turn; this.ended = ended; } 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++) { IntVector2 pos = new IntVector2(x, y); ArrayList entities = state.getEntities().findByPosition(pos); if(entities.isEmpty()) { data[y][x] = new Piece(PieceType.Empty); }else { Entity entity = entities.get(0); switch(entity.id.type) { case NPC -> { if(entity.id.id == NPCType.Thanos.getID()) { data[y][x] = new Piece(PieceType.Thanos); }else { data[y][x] = new Piece(PieceType.NPC); } } case P1, P2 -> { Character character = (Character)entity; data[y][x] = new Piece( PieceType.Character, entity.id, character.hp.getValue(), character.hp.getMax(), character.mp.getValue(), character.mp.getMax(), character.ap.getValue(), character.ap.getMax(), character.inventory.getStonesAsArray() ); } case Rocks -> { Rock rock = (Rock)entity; data[y][x] = new Piece(PieceType.Rock, entity.id, rock.getHp(), rock.maxHP); } case Portals -> { data[y][x] = new Piece(PieceType.Portal, entity.id); } case InfinityStones -> { InfinityStone stone = (InfinityStone)entity; data[y][x] = new Piece(PieceType.InfinityStone, entity.id, stone.type); } case None -> { data[y][x] = new Piece(PieceType.Empty); } } } } } return new Board(data, turn.type, turn); } protected ArrayList generateActions(GameStateView state) { ArrayList result = new ArrayList<>(); 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 || this.data[target.getY()][target.getX()].type == PieceType.Character ) { 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(state.getStoneCooldown(stone) > 0) { continue; } 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)); } } } } case EndTurn -> { result.add(new Action(ActionType.EndTurn)); } } } return result; } 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++) { for(int y = 0; y < this.data.length; y++) { clone[y][x] = this.data[y][x].clone(); } } 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); } } } boolean ended = false; //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); //todo: drop stones........ } case RangedAttack -> { clone[action.target.getY()][action.target.getX()].hp.decreaseValue(character.rangedDamage); current.ap.decreaseValue(1); } case UseStone -> { switch(action.stone) { case SpaceStone -> { } case MindStone -> { } case RealityStone -> { } case PowerStone -> { } case TimeStone -> { } case SoulStone -> { } } } case GiveStone -> { } case EndTurn -> { ended = true; } } if(ended || (current.mp.getValue() == 0 && current.ap.getValue() == 0)) { ArrayList alive = new ArrayList<>(); for(EntityID next: state.getTurnOrder()) { if(next.type == EntityType.NPC && next.id == NPCType.Thanos.getID()) { alive.add(next); }else if(next.type == EntityType.P1 || next.type == EntityType.P2) { if(((Character)state.getEntities().findEntity(next)).isAlive()) { alive.add(next); } } } int index = alive.indexOf(turn); if(index == alive.size() - 1) { return new Board(clone, origin, turn, true); }else { return new Board(clone, origin, alive.get(index + 1)); } }else { return new Board(clone, origin, turn); } } 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; } }