import util;

////////////////////////////////////////////////////////////////////////////////

class Pos
{
	public immutable int y, x;
	mixin DeriveCreate;
	mixin DeriveCompare;
	mixin DeriveShow;
	Pos clone() const { return cast(Pos) this; }

@property:
	Pos wait()  { return this.clone(); }
	Pos up()    { return new Pos(y+1, x); }
	Pos down()  { return new Pos(y-1, x); }
	Pos left()  { return new Pos(y, x-1); }
	Pos right() { return new Pos(y, x+1); }
	alias wait  W,w;
	alias up    U,u;
	alias down  D,d;
	alias left  L,l;
	alias right R,r;
}

unittest
{
	assert( (new Pos(2,1)).U == new Pos(3,1) );
	assert( (new Pos(0,1)).D == new Pos(-1,1) );
	assert( (new Pos(2,1)).L == new Pos(2,0) );
	assert( (new Pos(2,1)).R == new Pos(2,2) );
	int[Pos] aa;
	aa[new Pos(1,2)] = 1;
	aa[new Pos(1,2)] = 2;
	aa[new Pos(2,1)] = 3;
	assert( aa.length==2 );
	assert( aa[new Pos(1,2)]==2 );
}

////////////////////////////////////////////////////////////////////////////////

class Water
{
	public immutable int base, pace;
	mixin DeriveCreate;
	mixin DeriveCompare;
	mixin DeriveShow;
	Water clone() const { return cast(Water)this; }

	static load(string[string] params)
	{
		return new Water(
			params.get("Water",    "0").to!int(),
			params.get("Flooding", "0").to!int()
		);
	}

	int level(int number_of_update) const
	{
		return pace ? base+(number_of_update/pace) : base;
	}

	int until_rise(int number_of_update) const
	{
		return pace ? pace-number_of_update%pace : int.max;
	}
}

unittest
{
	Water w = new Water(1, 3);
	assert( 1 == w.level(0) );
	assert( 1 == w.level(1) );
	assert( 1 == w.level(2) );
	assert( 2 == w.level(3) );
	assert( 2 == w.level(4) );
	assert( 2 == w.level(5) );
	assert( 3 == w.level(6) );

	w = new Water(1, 0);
	assert( 1 == w.level(0) );
	assert( 1 == w.level(1) );
	assert( 1 == w.level(2) );
	assert( 1 == w.level(3) );
	assert( 1 == w.level(4) );
	assert( 1 == w.level(5) );
}

////////////////////////////////////////////////////////////////////////////////

class Hige
{
	public immutable int pace;
	mixin DeriveCreate;
	mixin DeriveCompare;
	mixin DeriveShow;
	Hige clone() const { return cast(Hige)this; }

	static load(string[string] params)
	{
		return new Hige(params.get("Growth", "25").to!int());
	}

	bool is_growing_turn(int turn) const
	{
		return pace ? turn%pace == pace-1 : false;
	}

	int until_rise(int turn) const
	{
		return pace ? pace-turn%pace : int.max;
	}
}

////////////////////////////////////////////////////////////////////////////////

class Map
{
	mixin DeriveShow;

	static Map load(string[] raw_data, string[string] params, char[char] trampo)
	{
		// TODO: choose optimal representation.
		return new Map(raw_data, params, trampo);
	}

	char[][] data;
	Pos robot;
	Pos lift;
	int waterproof;
	// TODO: immutable
	Pos[char] tr_target;
	Pos[][char] tr_source;
	const(Hige) hige;
	int razor;

	Map clone() const { return new Map(this); }
	this(in Map m) {
		foreach(s; m.data)
			this.data ~= s.dup;
		this.robot = m.robot.clone();
		this.lift = m.lift.clone();
		this.waterproof = m.waterproof;
		this.tr_target = cast(Pos[char])m.tr_target;
		this.tr_source = cast(Pos[][char])m.tr_source;
		this.hige = m.hige.clone();
		this.razor = m.razor;
	}

	this(string[] raw_data, string[string] params, char[char] trampo)
	{
		int width = 0;
		foreach(r; raw_data)
			width = max(width, r.length);
		foreach(r; raw_data) {
			this.data ~= r.dup;
			this.data[$-1].length = width;
			this.data[$-1][r.length..$] = ' ';
		}

		for(int y=1; y<=H; ++y)
		for(int x=1; x<=W; ++x) {
			if(this[y,x] == 'R')
				this.robot = new Pos(y,x);
			if(this[y,x] == 'L' || this[y,x] == 'O')
				this.lift = new Pos(y,x);
		}

		Pos[char] tr_pos;
		for(int y=1; y<=H; ++y)
		for(int x=1; x<=W; ++x) {
			char c = this[y,x];
			if('1'<=c && c<='9' || 'A'<=c&&c<='I')
				tr_pos[c] = new Pos(y,x);
		}

		this.waterproof = params.get("Waterproof", "5").to!int();
		foreach(fr,to; trampo) {
			tr_target[fr] = tr_pos[to];
			if(to !in tr_source) tr_source[to] = [];
			tr_source[to] ~= tr_pos[fr];
		}

		this.hige = Hige.load(params);
		this.razor = params.get("Razors", "0").to!int();
	}

	const @property {
		int H() { return data.length; }
		int W() { return data[0].length; }
	}

	const {
		char opIndex(int y, int x)
		{
			// Adjust coordinate to the spec. bottom-left is (1,1).
			--y, --x;
			if(y<0||H<=y||x<0||W<=x)
				return '#';
			return data[H-1-y][x];
		}

		char opIndex(in Pos p)
		{
			return this[p.y, p.x];
		}
	}

	void opIndexAssign(char c, int y, int x)
	{
		// Adjust coordinate to the spec. bottom-left is (1,1).
		--y, --x;
		if(y<0||H<=y||x<0||W<=x)
			return;
		data[H-1-y][x] = c;
	}

	void opIndexAssign(char c, in Pos p)
	{
		this[p.y, p.x] = c;
	}

	Pos[] lambdas() const {
		Pos[] ans;
		for(int y=1; y<=H; ++y)
		for(int x=1; x<=W; ++x)
			if(this[y,x] == '\\')
				ans ~= new Pos(y,x);
		return ans;
	}

	bool cleared() const
	{
		for(int y=1; y<=H; ++y)
		for(int x=1; x<=W; ++x)
			if(this[y,x] == 'L' || this[y,x] == 'O')
				return false;
		return true;
	}
	
	Tuple!(int,bool) command(char c, int turn)
	{
		assert( this[robot] == 'R' );
		if(c=='R') return move( 0, +1, turn);
		if(c=='L') return move( 0, -1, turn);
		if(c=='U') return move(+1,  0, turn);
		if(c=='D') return move(-1,  0, turn);
		if(c=='W') return move( 0,  0, turn);
		if(c=='S') return use_razor(turn);
		assert(false);
	}

	Tuple!(int, bool) use_razor(int turn)
	{
		if(razor) {
			razor--;
			for(int dy=-1; dy<=+1; ++dy)
			for(int dx=-1; dx<=+1; ++dx)
				if(this[robot.y+dy,robot.x+dx] == 'W')
					this[robot.y+dy,robot.x+dx] = ' ';
		}

		bool dead = update(turn);
		return tuple(0,dead);
	}

	Tuple!(int, bool) move(int dy, int dx, int turn)
	{
		int y = robot.y;
		int x = robot.x;
		int lambda = 0;
		if( '\\' == this[y+dy,x+dx] )
			lambda++;
		if( '!' == this[y+dy,x+dx] )
			razor++;
		if( " \\!.O".count(this[y+dy,x+dx])==1 ) {
			this[y,x]=' ';
			this[y+dy,x+dx]='R';
			robot = new Pos(y+dy,x+dx);
		} else if(dy==0 && '*'==this[y+dy,x+dx] && ' '==this[y+dy*2,x+dx*2]) {
			this[y,x]=' ';
			this[y+dy,x+dx]='R';
			this[y+dy*2,x+dx*2]='*';
			robot = new Pos(y+dy,x+dx);
		} else if('A'<=this[y+dy,x+dx] && this[y+dy,x+dx]<='I') {
			this[y,x]=' ';
			Pos tp = tr_target[this[y+dy,x+dx]];
			foreach(p; tr_source[this[tp]])
				this[p] = ' ';
			this[tp] = 'R';
			robot = tp;
		}
		bool dead = update(turn);
		return tuple(lambda,dead);
	}

	bool update(int turn)
	{
		bool dead = false;

		char[][] next;
		foreach(y,s; data)
			next ~= s.dup;

		ref char access(Pos p) { return next[H-p.y][p.x-1]; }

		bool lambda = false;
		for(int y=1; y<=H; ++y)
		for(int x=1; x<=W; ++x)
			lambda |= (this[y,x] == '\\');

		for(int y=1; y<=H; ++y)
		for(int x=1; x<=W; ++x) {
			Pos p = new Pos(y,x);
			if(this[p]=='*') {
				if(this[p.D]==' ') {
					access(p)  =' ';
					access(p.D)='*';
					if(robot == p.D.D)
						dead=true;
				}
				else if((this[p.D]=='*' || this[p.D]=='\\') && this[p.R]==' ' && this[p.R.D]==' ') {
					access(p)=' ';
					access(p.R.D)='*';
					if(robot == p.R.D.D)
						dead=true;
				}
				else if(this[p.D]=='*' && this[p.L]==' ' && this[p.L.D]==' ') {
					access(p)=' ';
					access(p.L.D)='*';
					if(robot == p.L.D.D)
						dead=true;
				}
			}
			else if(this[p]=='L') {
				if(!lambda)
					access(p) = 'O';
			}
			else if(this[p]=='W') {
				if( hige.is_growing_turn(turn) )
					for(int dy=-1; dy<=+1; ++dy)
					for(int dx=-1; dx<=+1; ++dx)
						if(this[p.y+dy,p.x+dx] == ' ')
							access(new Pos(p.y+dy,p.x+dx)) = 'W';
			}
		}
		data = next;
		return dead;
	}
}

////////////////////////////////////////////////////////////////////////////////

class Game
{
	mixin DeriveShow;

	static Game load(File input)
	{
		string[]       raw_data;
		string[string] params;

		// Raw map data; read until empty line.
		for(string line; !(line=input.readln().chomp()).empty; )
			raw_data ~= line;

		// Additional commands; read until EOF.
		char[char] trampo;
		for(string line; !(line=input.readln()).empty; ) {
			string[] ss = line.split();
			if( ss.length == 2 )
				params[ss[0]] = ss[1];
			if( ss.length == 4 && ss[0]=="Trampoline" && ss[2]=="targets" )
				trampo[ss[1][0]] = ss[3][0];
		}

		return load(raw_data, params, trampo);
	}

	static Game load(string[] raw_data, string[string] params, char[char] trampo = null)
	{
		return new Game(raw_data, params, trampo);
	}

	this(string[] raw_data, string[string] params, char[char] trampo)
	{
		this.map = Map.load(raw_data, params, trampo);
		this.water = Water.load(params);
	}

	Game clone() const { return new Game(this); }
	this(in Game g) {
		map = g.map.clone();
		water = g.water.clone();
		turn = g.turn;
		dead = g.dead;
		lambda = g.lambda;
		exit_bonus = g.exit_bonus;
		under_water = g.under_water;
	}

	void command(char c)
	{
		if(dead || cleared)
			return;

		if(c == 'A')
		{
			exit_bonus = 1;
			return;
		}

		// TODO: clarify the event order
		Tuple!(int,bool) ld = map.command(c, turn);
		if( map.cleared() ) {
			exit_bonus = 2;
		}
		else {
			lambda += ld[0];
			if( ld[1] ) {
				dead = true;
			}
		}
		if( map.robot.y <= water_level )
			++under_water;
		else
			under_water = 0;
		if( under_water > map.waterproof )
			dead = true;
		turn += 1;
	}

	Map map;
	Water water;
	int  turn = 0;
	bool dead = false;
	int  lambda = 0;
	int  exit_bonus = 0;
	int  under_water = 0;
	// TODO: when adding members, take care of clone().
	// TODO: fix this poor design.

	@property const {
		long score() { return lambda*25L*(1+exit_bonus) - turn; }
		int water_level() { return water.level(turn); }
		int water_until_rise() { return water.until_rise(turn); }
		int hige_until_rise() { return map.hige.until_rise(turn); }
		bool cleared() { return exit_bonus>0; }
		int hp() { return map.waterproof - under_water; }
		long score_if_abort_now() { return lambda*25*(1+max(1,exit_bonus)) - turn; }
	}
}
