396 lines
17 KiB
Java
396 lines
17 KiB
Java
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<Entity> 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<Action> generateActions(GameStateView state) {
|
|
ArrayList<Action> 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<EntityID> 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;
|
|
}
|
|
}
|