Artifact Content
Not logged in

Artifact 9428bbab7d2bc9a88df46c31274974227f071684


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;
	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[] objects(char c) const {
		Pos[] ans;
		for(int y=1; y<=H; ++y)
		for(int x=1; x<=W; ++x)
			if(this[y,x] == c)
				ans ~= new Pos(y,x);
		return ans;
	}

	Pos[] razors() const { return objects('!'); }
	Pos[] lambdas() const { return objects('\\'); }

	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;
		cleared = g.cleared;
		under_water = g.under_water;
	}

	void command(char c)
	{
		assert(c != 'A');
		if(dead || cleared)
			return;

		// TODO: clarify the event order
		Tuple!(int,bool) ld = map.command(c, turn);
		if( map.cleared() ) {
			cleared = true;
		}
		else {
			lambda += ld[0];
			if( ld[1] )
				dead = true;
		}
		if(!cleared) {
			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  under_water = 0;
	bool cleared = false;
	// TODO: when adding members, take care of clone().
	// TODO: fix this poor design.

	@property const {
		long score() { return lambda*(dead ? 25L : cleared ? 75L : 50L) - 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); }
		int hp() { return map.waterproof - under_water; }
	}
}
*/

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

class Game
{
public:
	this(File input)
	{
		// Read map data
		string[] map_data_lines;
		for(string line; !(line=input.readln().chomp()).empty; )
			map_data_lines ~= line;

		// H*W
		H_ = map_data_lines.length;
		W_ = 0;
		foreach(mdl; map_data_lines)
			W_ = max(W_, mdl.length);

		// Copy to modifiable buffer and adjust coordinates.
		raw_data_ = new char[][H_+1];
		foreach(i,mdl; map_data_lines) {
			char[] buf = new char[mdl.length+1];
			buf[0] = '#';
			buf[1..$] = mdl[];
			raw_data_[H_-i] = buf;
		}

		// Detect objects
		for(int y=1; y<=H_; ++y)
		for(int x=1; x<raw_data_[y].length; ++x)
		{
			char c = raw_data_[y][x];
			switch(c)
			{
			case '#':
			case '.':
			case ' ':
				break;
			case 'L':
			case 'O':
				lift_pos_ = new Pos(y,x);
				break;
			case 'A': .. case 'I':
			case '1': .. case '9':
				trampoline_pos_[c] = new Pos(y,x);
				break;
			case '!':
				razor_pos_ ~= new Pos(y,x);
				break;
			case '\\':
				lambda_pos_ ~= new Pos(y,x);
				break;

			// Moving objects are erased from raw_data_
			case 'R':
				robot_pos_ = new Pos(y,x);
				raw_data_[y][x] = ' ';
				break;
			case '*':
			case 'W':
				dynamic_objects_[new Pos(y,x)] = c;
				raw_data_[y][x] = ' ';
				if(c=='*')
					may_update_[new Pos(y,x)] = true;
				break;
			default:
				assert(false);
			}
		}

		// Read other parameters
		for(string line; !(line=input.readln()).empty; )
		{
			string[] ss = line.split();
			if( ss.length == 2 )
				switch(ss[0])
				{
				case "Water":      water_base_ = ss[1].to!int(); break;
				case "Flooding":   water_pace_ = ss[1].to!int(); break;
				case "Waterproof": max_air_    = ss[1].to!int(); break;
				case "Growth":     hige_pace_  = ss[1].to!int(); break;
				case "Razors":     num_razor_  = ss[1].to!int(); break;
				default: assert(false);
				}
			if( ss.length == 4 && ss[0]=="Trampoline" && ss[2]=="targets" )
			{
				char fr=ss[1][0], to=ss[3][0];
				trampoline_[fr] = to;
				if(to !in trampoline_rev_) trampoline_rev_[to] = [];
				trampoline_rev_[to] ~= fr;
			}
		}

		air_left_ = max_air_;
	}

	@property const {
		int H() { return H_; }
		int W() { return W_; }
		char trampoline(char c) { return (c in trampoline_ ? trampoline_[c] : 0); }
		int water_level() {
			return water_pace_ ? water_base_ + turn_/water_pace_ : water_base_;
		}
		int water_until_rise() {
			return water_pace_ ? water_pace_ - turn_%water_pace_ : int.max;
		}
		int hige_until_rise() {
			return hige_pace_ ? hige_pace_ - turn_%hige_pace_ : int.max;
		}
		bool is_hige_turn() {
			return hige_pace_ ? turn_%hige_pace_ == hige_pace_-1 : false;
		}
		int hp() { return air_left_; }
		int num_razor() { return num_razor_; }
		bool cleared() { return cleared_; }
		bool dead() { return dead_; }
		long score() { return num_lambda_*(dead_ ? 25L : cleared_ ? 75L : 50L) - turn_; }
	}
	const {
		char opIndex(in Pos p) { return opIndex(p.y, p.x); }
		char opIndex(int y, int x) { return map_get(y, x); }
	}

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

		if(c == 'U') command_move(+1, 0);
		if(c == 'D') command_move(-1, 0);
		if(c == 'L') command_move(0, -1);
		if(c == 'R') command_move(0, +1);
		if(c == 'S') use_razor();
		if(c == 'W') {}

		if(cleared)
			return;

		map_update();
		water_update();
		turn_ ++;
	}

	void command_move(int dy, int dx)
	{
		int y = robot_pos_.y, x = robot_pos_.x;
		char c = this[y+dy, x+dx];
		Pos p = new Pos(y+dy, x+dx);

		switch(c){
		case 'O':
			cleared_ = true;
			move_robot_to(p);
			break;
		case '\\':
			take_lambda_at(p);
			move_robot_to(p);
			break;
		case '!':
			take_razor_at(p);
			move_robot_to(p);
			break;
		case 'A': .. case 'I':
			enter_trampoline_at(p, c);
			break;
		case ' ':
		case '.':
			move_robot_to(p);
			break;
		case '*':
			if(dy!=0 || this[y,x+dx*2]!=' ')
				break;
			move_robot_to(p);
			push_rock(p, new Pos(y,x+dx*2));
			break;
		default:
			break;
		}
	}

	void use_razor()
	{
		if(num_razor_ == 0)
			return;
		num_razor_ --;

		for(int dy=-1; dy<=+1; ++dy)
		for(int dx=-1; dx<=+1; ++dx) if(dy||dx)
		{
			Pos p = new Pos(robot_pos_.y+dy, robot_pos_.x+dx);
			if(auto it = p in dynamic_objects_)
				if(*it == 'W') {
					something_gone(p);
					dynamic_objects_.remove(p);
				}
		}
	}

	void take_lambda_at(Pos p)
	{
		map_set_empty(p);
		num_lambda_ ++;
		lambda_pos_ = lambda_pos_.erase(p);
	}

	void take_razor_at(Pos p)
	{
		map_set_empty(p);
		num_razor_ ++;
		razor_pos_ = razor_pos_.erase(p);
	}

	void enter_trampoline_at(Pos p, char c)
	{
		char d = trampoline(c);
		foreach(cc; trampoline_rev_[d]) {
			Pos pp = trampoline_pos_[cc];
			something_gone(pp);
			map_set_empty(pp);
		}
		move_robot_to(trampoline_pos_[d]);
	}

	void move_robot_to(Pos p)
	{
		something_gone(robot_pos_);
		map_set_empty(p.y, p.x);
		robot_pos_ = p;
	}

	void push_rock(Pos fr, Pos to)
	{
		dynamic_objects_.remove(fr);
		dynamic_objects_[to] = '*';
		may_update_[to] = true;
	}

	void something_gone(Pos p)
	{
		for(int dy=0;  dy<=+1; ++dy)
		for(int dx=-1; dx<=+1; ++dx) if(dy||dx)
			may_update_[new Pos(p.y+dy,p.x+dx)] = true;
	}

	void map_update()
	{
		Pos[] may_update_list;
		foreach(p,_; may_update_)
			if(this[p] == '*')
				may_update_list ~= p;
		may_update_ = null;

		if( is_hige_turn() )
			foreach(p,c; dynamic_objects_)
				if(c == 'W')
					may_update_list ~= p;

		sort(may_update_list);
		char[Pos] to_be_written;
		foreach(p; may_update_list)
			if(is_hige_turn() && this[p]=='W')
			{
				for(int dy=-1; dy<=+1; ++dy)
				for(int dx=-1; dx<=+1; ++dx) {
					Pos q = new Pos(p.y+dy,p.x+dx);
					if( this[q] == ' ' )
						to_be_written[q] = 'W';
				}
			}
			else
			{
				int y = p.y;
				int x = p.x;
				char below = this[y-1,x];
				// *
				// _
				if(below==' ') {
					Pos q = new Pos(y-1,x);
					to_be_written[p] = ' ';
					to_be_written[q] = '*';
					may_update_[q] = true;
				}
				// *_      *_
				// *_  or  \_
				else if((below=='*'||below=='\\')&&this[y-1,x+1]==' '&&this[y,x+1]==' ') {
					Pos q = new Pos(y-1,x+1);
					to_be_written[p] = ' ';
					to_be_written[q] = '*';
					may_update_[q] = true;
				}
				// _*
				// _*
				else if(below=='*'&&this[y-1,x-1]==' '&&this[y,x-1]==' ') {
					Pos q = new Pos(y-1,x-1);
					to_be_written[p] = ' ';
					to_be_written[q] = '*';
					may_update_[q] = true;
				}
			}

		foreach(p,c; to_be_written) {
			dynamic_objects_[p] = c;
			if(c=='*' && p.y==robot_pos_.y+1 && p.x==robot_pos_.x)
				dead_ = true;
		}

		if(lambda_pos_.empty)
			raw_data_[lift_pos_.y][lift_pos_.x] = 'O';
	}

	void water_update()
	{
		if( robot_pos_.y <= water_level() )
			air_left_ --;
		else
			air_left_ = max_air_;
		if( air_left_ < 0 )
			dead_ = true;
	}

private:
	char map_get(int y, int x) const
	{
		if( y<1 || H<y || x<1 || W<x ) return '#';
		Pos p = new Pos(y,x);
		if(p == robot_pos_)
			return 'R';
		if(auto it = (p in dynamic_objects_))
			return *it;
		if( x<0 || raw_data_[y].length<=x ) return ' ';
		return raw_data_[y][x];
	}

	void map_set_empty(in Pos p)
	{
		return map_set_empty(p.y, p.x);
	}

	void map_set_empty(int y, int x)
	{
		if( y<1 || H<y || x<1 || W<x ) return;
		if( x<0 || raw_data_[y].length<=x ) return;
		raw_data_[y][x] = ' ';
	}

private:
	int          H_;
	int          W_;
	char[][]     raw_data_;
	Pos[char]    trampoline_pos_;
	Pos[]        razor_pos_;
	Pos[]        lambda_pos_;
	Pos          lift_pos_;
	Pos          robot_pos_;
	char[Pos]    dynamic_objects_;
	char[char]   trampoline_;
	char[][char] trampoline_rev_;
	int          water_base_ = 0;
	int          water_pace_ = 0;
	int          max_air_    = 10;
	int          hige_pace_  = 25;
	int          num_razor_  = 0;
	int          num_lambda_ = 0;

	int turn_     = 0;
	int air_left_ = 0;
	bool cleared_ = false;
	bool dead_    = false;
	bool[Pos] may_update_;
}