package com.m00ware.vindinium;
import java.util.*;
import vindinium.*;
import vindinium.Board.HeroTile;
import vindinium.Board.Mine;
import vindinium.Board.Position;
import vindinium.Board.Tile;
import vindinium.Hero.HeroState;
public class PathFinder {
public static final boolean LOGGING = false;
public static final int GOLD_LOSS_TAVERN = 2;
public static final int HP_LOSS_PER_TURN = 1;
public static final int HP_WIN_TAVERN = 50;
public static final int HP_LOSS_MINE_FIGHT = 20;
public static final int HP_LOSS_HERO_FIGHT = 20;
private final boolean safe;
private final Hero hero;
private final Board board;
private final int maxDist;
public PathFinder(boolean safe, Hero hero, Board board) {
this(safe, hero, board, board.getSize() * board.getSize());
}
public PathFinder(boolean safe, Hero hero, Board board, int maxDist) {
this.safe = safe;
this.hero = hero;
this.board = board;
this.maxDist = maxDist;
}
public Direction findNearest(Predicate<State> t) {
BoardMarks marks = new BoardMarks(board);
Queue<State> queue = new LinkedList<>();
queue.offer(new State(hero.state));
marks.mark(queue.peek().hero.position);
while (!queue.isEmpty()) {
State state = queue.poll();
State move = move(state, Direction.NORTH);
if (processCandidateState(move, queue, t, marks)) {
return findFirstMove(move);
}
move = move(state, Direction.SOUTH);
if (processCandidateState(move, queue, t, marks)) {
return findFirstMove(move);
}
move = move(state, Direction.WEST);
if (processCandidateState(move, queue, t, marks)) {
return findFirstMove(move);
}
move = move(state, Direction.EAST);
if (processCandidateState(move, queue, t, marks)) {
return findFirstMove(move);
}
}
return null;
}
public static void log(String s) {
if (LOGGING) {
System.out.println(s);
}
}
private Direction findFirstMove(State s) {
State c = s;
while (c.prev.deltaToPrev != Direction.STAY) {
c = c.prev;
}
return c.deltaToPrev;
}
private boolean processCandidateState(State s, Queue<State> queue, Predicate<State> t, BoardMarks marks) {
if (!s.hero.position.isValid(board)) {
log(" Trying " + s + " Impossible: not on board");
return false;
}
if (marks.isMarked(s.hero.position)) {
log(" Trying " + s + " ...Already explored");
return false;
}
marks.mark(s.hero.position);
if (!isPossibleState(s)) {
return false;
}
if (t.apply(s)) {
log(" Trying " + s + " Success!");
return true;
}
if (safe && !s.isSafe) {
log(" Trying " + s + " Impossible: not safe");
return false;
}
if (!isFinalState(s)) {
log(" Trying " + s + " Continuing...");
queue.offer(s);
} else {
log(" Trying " + s + " Dead end");
}
return false;
}
private static class BoardMarks {
private final boolean marks[];
private final int size;
public BoardMarks(Board b) {
this.size = b.getSize();
this.marks = new boolean[size * size];
}
public boolean isMarked(Position p) {
return marks[p.x + p.y * size];
}
public void mark(Position p) {
marks[p.x + p.y * size] = true;
}
}
// TODO imple respawn rules
public boolean isPossibleState(State state) {
if (state.hero.life <= 0) {
log(" Trying " + state + " Impossible: no HP");
return false;
}
if (state.hero.gold < 0) {
log(" Trying " + state + " Impossible: no GOLD");
return false;
}
if (state.dist > maxDist) {
log(" Trying " + state + " Impossible: too far");
return false;
}
Tile tile = board.tileAt(state.hero.position);
if (tile == Tile.WALL) {
log(" Trying " + state + " Impossible: it's a Wall");
return false;
}
return true;
}
public boolean isFinalState(State state) {
Tile tile = board.tileAt(state.hero.position);
if (tile == Tile.AIR) {
return false;
}
return true;
}
public static class State {
@Override
public String toString() {
return "State [hero=" + hero + ", isSafe= " + isSafe + "]";
}
final int dist;
final State prev;
final Direction deltaToPrev;
final HeroState hero;
final boolean isSafe;
public State(HeroState hero) {
this.hero = hero;
this.dist = 0;
prev = null;
isSafe = true;
deltaToPrev = Direction.STAY;
}
public State(State prev, Direction deltaToPrev, HeroState hero) {
this.prev = prev;
this.deltaToPrev = deltaToPrev;
this.hero = hero;
this.isSafe = prev != null && prev.hero.life - this.hero.life == HP_LOSS_PER_TURN;
this.dist = prev.dist + 1;
}
}
private State move(State s, Direction d) {
Position newPos = move(s.hero.position, d);
return new State(s, d, new HeroState(s.hero.life + deltaHp(newPos, s.hero.life), deltaGold(newPos), deltaMines(newPos), newPos));
}
private static Position move(Position pos, Direction d) {
return new Position(pos.x + d.dx, pos.y + d.dy);
}
private int deltaHp(Position p, int currentHealth) {
if (!p.isValid(board)) {
return 0;
}
int delta = 0;
Tile tile = board.tileAt(p);
if (tile instanceof Mine) {
Mine mine = (Mine) tile;
delta += mine.isOwnedBy(hero) ? 0 : -HP_LOSS_MINE_FIGHT;
} else if (tile == Tile.TAVERN) {
delta += HP_WIN_TAVERN;
}
delta += deltaHpFrom(move(p, Direction.NORTH));
delta += deltaHpFrom(move(p, Direction.SOUTH));
delta += deltaHpFrom(move(p, Direction.EAST));
delta += deltaHpFrom(move(p, Direction.WEST));
if (currentHealth + delta > 1) {
delta += -HP_LOSS_PER_TURN;
}
return delta;
}
private int deltaGold(Position p) {
if (!p.isValid(board)) {
return 0;
}
int delta = board.minesForHero(hero);
Tile tile = board.tileAt(p);
if (tile == Tile.TAVERN) {
delta += -GOLD_LOSS_TAVERN;
}
return delta;
}
private int deltaMines(Position p) {
if (!p.isValid(board)) {
return 0;
}
Tile tile = board.tileAt(p);
if (tile instanceof Mine) {
return 1;
}
return 0;
}
// TODO this is inaccurate since we could be the attacker. Here we just assume they will attack us
private int deltaHpFrom(Position p) {
if (!p.isValid(board) || !safe) {
return 0;
}
Tile tile = board.tileAt(p);
if (tile instanceof HeroTile) {
HeroTile heroTile = (HeroTile) tile;
return heroTile.is(hero) ? 0 : -HP_LOSS_HERO_FIGHT;
}
return 0;
}
}