import java.io.*;
import java.util.*;
import java.lang.reflect.*;

public class GameEngine {

	private Dungeon dungeon;
	private AbstractCharacter rogue;
	private AbstractCharacter monster;
	private Position roguePosition;
	private Position monsterPosition;
	private Set<Position> potionPositions;
	private char monsterName;
	private int rogueSpeed;
	private int monsterSpeed;

	public GameEngine(String filename, String rogue, String monster) {
		this.potionPositions = new HashSet<Position>();
		readDungeon(filename);
		if ((roguePosition == null) || (monsterPosition == null)) {
			throw new RuntimeException("No rogue and/or monster start position in dungeon");
		}
		this.rogue = loadAbstractCharacter(rogue, roguePosition);
		this.monster = loadAbstractCharacter(monster, monsterPosition);
		this.rogueSpeed = 1;
		this.monsterSpeed = 1;
	}

	private AbstractCharacter loadAbstractCharacter(String name, Position p) {
		try {
			Class<?> c = Class.forName(name);
			Class<? extends AbstractCharacter> ac = c.asSubclass(AbstractCharacter.class);
			Constructor<? extends AbstractCharacter> con = ac.getConstructor(Dungeon.class, Position.class);
			return con.newInstance(dungeon, p);
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("Error loading AbstractCharacter");
		}
	}

	private void readDungeon(String filename) {
		try {
			Scanner sc = new Scanner(new File(filename));
			int N = sc.nextInt();
			sc.nextLine();
			char[][] board = new char[N][N];
			for (int y = 0; y < N; y++) {
				String row = sc.nextLine();
				for (int x = 0; x < N; x++) {
					char c = row.charAt(2*x);
					if(c == '@') {
						roguePosition = new Position(x, y);
						c = '.';
					} else if (c >= 'A' && c <= 'Z') {
						monsterPosition = new Position(x, y);
						monsterName = c;
						c = '.';
					} else if (c == 's') {
						potionPositions.add(new Position(x, y));
						c = '.';
					}
					board[x][y] = c;
				}
			}
			sc.close();
			dungeon = new Dungeon(board, this);
		} catch(Exception e) {
			e.printStackTrace();
			throw new RuntimeException("Illigal dungeon");
		}
	}

	public Position getRoguePosition() {
		return roguePosition.clone();
	}

	public Position getMonsterPosition() {
		return monsterPosition.clone();
	}
	
	public Set<Position> getPotionPositions() {
		Set<Position> potions = new HashSet<Position>();
		for (Position p : potionPositions) {
			potions.add(p.clone());
		}
		return potions;
	}

	public String toString() {
		String s = "";
		for (int y = 0; y < dungeon.size(); y++) {
			for (int x = 0; x < dungeon.size(); x++) {
				Position p = new Position(x, y);
				if (roguePosition.equals(monsterPosition) && (roguePosition.equals(p))) {
					s += "* ";
				} else if (roguePosition.equals(p)) {
					s += "@ ";
				} else if (monsterPosition.equals(p)) {
					s += monsterName + " ";
				} else if (potionPositions.contains(p)) {
					s += "s ";
				} else if (dungeon.isRoom(p)) {
					s += ". ";
				} else if (dungeon.isCorridor(p)) {
					s += "+ ";
				} else {
					s += "  ";
				}
			}
			if (y < dungeon.size() - 1) {
				s += "\n";
			}
		}
		return s;
	}

	public void play() {
		int move = 0;
		Position next;
		int numMoves;

		while (move < 100) {
			move += 1;
			System.out.println(this);
			System.out.println("Move: " + move);

			if(roguePosition.equals(monsterPosition)) {
				break;
			}
			numMoves = this.monsterSpeed;
			while (numMoves > 0) {
				numMoves--;
				next = monster.move(numMoves);
				if(!dungeon.isLegalMove(monsterPosition, next)) {
					throw new RuntimeException("Monster caught cheating");
				}
				if (potionPositions.contains(next)) {
					potionPositions.remove(next);
					this.monsterSpeed++;
					System.out.println("Monster consumes Potion of Speed and can make "+this.monsterSpeed+" moves per round!");
				}
				monsterPosition = next;
			}

			if(roguePosition.equals(monsterPosition)) {
				break;
			}
			numMoves = this.rogueSpeed;
			while (numMoves > 0) {
				numMoves--;
				next = rogue.move(numMoves);
				if(!dungeon.isLegalMove(roguePosition, next)) {
					throw new RuntimeException("Rogue caught cheating");
				}
				if (potionPositions.contains(next)) {
					potionPositions.remove(next);
					this.rogueSpeed++;
					System.out.println("Rogue consumes Potion of Speed and can make "+this.rogueSpeed+" moves per round!");
				}
				roguePosition = next;
			}
		}

		System.out.println( "Rogue caught by monster!");
	}

	public static void main(String[] args) {
		if (args.length != 3) {
			throw new RuntimeException("Wrong arguments - need <dungeon> <rogue> <monster>");
		}
		GameEngine game = new GameEngine(args[0], args[1], args[2]);
		game.play();
	}

}