Artifact Content
Not logged in

Artifact 521705f028ec7e747da70aea0da892f78459bb50


     1  import util;
     2  
     3  ////////////////////////////////////////////////////////////////////////////////
     4  
     5  bool is_spacy(char c)
     6  {
     7  	return c==' ' || c=='.' || c=='R' || c=='!' || c=='\\' || c=='O';
     8  }
     9  
    10  bool is_rocky(char c)
    11  {
    12  	return c=='*' || c=='@';
    13  }
    14  
    15  bool is_true_space(char c)
    16  {
    17  	return c==' ';
    18  }
    19  
    20  bool is_trampoline_source(char c)
    21  {
    22  	return 'A'<=c && c<='I';
    23  }
    24  
    25  bool is_rocklambda(char c)
    26  {
    27  	return is_rocky(c) || c=='\\';
    28  }
    29  
    30  ////////////////////////////////////////////////////////////////////////////////
    31  
    32  class Pos
    33  {
    34  	public immutable int y, x;
    35  	mixin DeriveCreate;
    36  	mixin DeriveCompare;
    37  	mixin DeriveShow;
    38  	Pos clone() const { return cast(Pos) this; }
    39  
    40  const @property:
    41  	Pos wait()  { return this.clone(); }
    42  	Pos up()    { return new Pos(y+1, x); }
    43  	Pos down()  { return new Pos(y-1, x); }
    44  	Pos left()  { return new Pos(y, x-1); }
    45  	Pos right() { return new Pos(y, x+1); }
    46  	alias wait  W,w;
    47  	alias up    U,u;
    48  	alias down  D,d;
    49  	alias left  L,l;
    50  	alias right R,r;
    51  }
    52  
    53  unittest
    54  {
    55  	assert( (new Pos(2,1)).U == new Pos(3,1) );
    56  	assert( (new Pos(0,1)).D == new Pos(-1,1) );
    57  	assert( (new Pos(2,1)).L == new Pos(2,0) );
    58  	assert( (new Pos(2,1)).R == new Pos(2,2) );
    59  	int[Pos] aa;
    60  	aa[new Pos(1,2)] = 1;
    61  	aa[new Pos(1,2)] = 2;
    62  	aa[new Pos(2,1)] = 3;
    63  	assert( aa.length==2 );
    64  	assert( aa[new Pos(1,2)]==2 );
    65  }
    66  
    67  ////////////////////////////////////////////////////////////////////////////////
    68  
    69  class Water
    70  {
    71  	mixin DeriveShow;
    72  
    73  private:
    74  	immutable int base, pace;
    75  	mixin DeriveCreate;
    76  	Water clone() const { return cast(Water) this; }
    77  
    78  	static load(string[string] params)
    79  	{
    80  		return new Water(params.get("Water",    "0").to!int(),
    81  		                 params.get("Flooding", "0").to!int());
    82  	}
    83  
    84  	int level(int turn) const
    85  	{
    86  		return pace ? base+(turn/pace) : base;
    87  	}
    88  
    89  	int until_rise(int turn) const
    90  	{
    91  		return pace ? pace-turn%pace : int.max;
    92  	}
    93  }
    94  
    95  unittest
    96  {
    97  	Water w = new Water(1, 3);
    98  	assert( 1 == w.level(0) );
    99  	assert( 1 == w.level(1) );
   100  	assert( 1 == w.level(2) );
   101  	assert( 2 == w.level(3) );
   102  	assert( 2 == w.level(4) );
   103  	assert( 2 == w.level(5) );
   104  	assert( 3 == w.level(6) );
   105  
   106  	w = new Water(1, 0);
   107  	assert( 1 == w.level(0) );
   108  	assert( 1 == w.level(1) );
   109  	assert( 1 == w.level(2) );
   110  	assert( 1 == w.level(3) );
   111  	assert( 1 == w.level(4) );
   112  	assert( 1 == w.level(5) );
   113  }
   114  
   115  ////////////////////////////////////////////////////////////////////////////////
   116  
   117  class Hige
   118  {
   119  	mixin DeriveShow;
   120  
   121  private:
   122  	immutable int pace;
   123  	mixin DeriveCreate;
   124  	Hige clone() const { return cast(Hige)this; }
   125  
   126  	static load(string[string] params)
   127  	{
   128  		return new Hige(params.get("Growth", "25").to!int());
   129  	}
   130  
   131  	bool is_growing_turn(int turn) const
   132  	{
   133  		return pace ? turn%pace == pace-1 : false;
   134  	}
   135  
   136  	int until_rise(int turn) const
   137  	{
   138  		return pace ? pace-turn%pace : int.max;
   139  	}
   140  }
   141  
   142  ////////////////////////////////////////////////////////////////////////////////
   143  
   144  class Trampoline
   145  {
   146  	mixin DeriveShow;
   147  
   148  private:
   149  	immutable char[]   target_of_;
   150  	immutable char[][] source_of_;
   151  	immutable Pos[]    position_of_;
   152  	immutable char[]   source_list_;
   153  	immutable char[]   target_list_;
   154  	Trampoline clone() const { return cast(Trampoline) this; }
   155  
   156  	this(in Map m, char[char] tramparam)
   157  	{
   158  		auto ta = new char['I'+1];
   159  		auto sr = new char[]['9'+1];
   160  		auto po = new Pos[max('I','9')+1];
   161  		char[] sl, tl;
   162  		foreach(fr,to; tramparam) {
   163  			ta[fr]  = to;
   164  			sr[to] ~= fr;
   165  		}
   166  		for(int y=1; y<=m.H; ++y)
   167  		for(int x=1; x<=m.W; ++x) {
   168  			char c = m[y,x];
   169  			if('A'<=c && c<='I') {
   170  				sl ~= c;
   171  				po[c] = new Pos(y,x);
   172  			}
   173  			if('1'<=c && c<='9') {
   174  				tl ~= c;
   175  				po[c] = new Pos(y,x);
   176  			}
   177  		}
   178  		target_of_ = cast(immutable) ta;
   179  		source_of_ = cast(immutable) sr;
   180  		position_of_ = cast(immutable) po;
   181  		source_list_ = cast(immutable) sl;
   182  		target_list_ = cast(immutable) tl;
   183  	}
   184  
   185  public @property const:
   186  	const(char[]) source_list() { return source_list_; }
   187  	const(char[]) target_list() { return target_list_; }
   188  	const(char[]) source_of(char c) { return source_of_[c]; }
   189  	char target_of(char c) { return target_of_[c]; }
   190  	Pos[] source_pos(char c) {
   191  		Pos[] ps;
   192  		foreach(s; source_of(c))
   193  			ps ~= position_of_[s].clone();
   194  		return ps;
   195  	}
   196  	Pos target_pos(char c) { return position_of_[target_of_[c]].clone(); }
   197  }
   198  
   199  ////////////////////////////////////////////////////////////////////////////////
   200  
   201  class Map
   202  {
   203  	mixin DeriveShow;
   204  
   205  	private char[][] data;
   206  	Pos robot;
   207  	Pos lift;
   208  	private int waterproof;
   209  	private int collected_razor;
   210  	int collected_lambda;
   211  	int total_lambda;
   212  	private bool cleared;
   213  	private Pos[] may_update;
   214  
   215  	private Map clone() const { return new Map(this); }
   216  	private this(in Map m) {
   217  		foreach(s; m.data)
   218  			this.data ~= s.dup;
   219  		this.robot      = m.robot.clone();
   220  		this.lift       = m.lift.clone();
   221  		this.waterproof = m.waterproof;
   222  		this.collected_razor  = m.collected_razor;
   223  		this.collected_lambda = m.collected_lambda;
   224  		this.total_lambda     = m.total_lambda;
   225  		this.cleared    = m.cleared;
   226  		this.may_update = (cast(Map)m).may_update.dup;
   227  	}
   228  
   229  	const {
   230  		@property {
   231  			int H() { return data.length; }
   232  			int W() { return data[0].length; }
   233  			int num_razor() { return collected_razor; }
   234  
   235  			Pos[] razors()  { return objects('!'); }
   236  			Pos[] lambdas() { return objects('\\'); }
   237  		}
   238  
   239  		Pos[] objects(char c) {
   240  			Pos[] ans;
   241  			for(int y=1; y<=H; ++y)
   242  			for(int x=1; x<=W; ++x)
   243  				if(this[y,x] == c)
   244  				ans ~= new Pos(y,x);
   245  			return ans;
   246  		}
   247  
   248  		char opIndex(int y, int x) {
   249  			--y, --x;
   250  			if(y<0||H<=y||x<0||W<=x)
   251  				return '#';
   252  			return data[H-1-y][x];
   253  		}
   254  
   255  		char opIndex(in Pos p) {
   256  			return this[p.y, p.x];
   257  		}
   258  	}
   259  
   260  private:
   261  	this(string[] raw_data, string[string] params, char[char] trampo)
   262  	{
   263  		int width = 0;
   264  		foreach(r; raw_data)
   265  			width = max(width, r.length);
   266  		foreach(r; raw_data) {
   267  			this.data ~= r.dup;
   268  			this.data[$-1].length = width;
   269  			this.data[$-1][r.length..$] = ' ';
   270  		}
   271  
   272  		for(int y=1; y<=H; ++y)
   273  		for(int x=1; x<=W; ++x) {
   274  			if(this[y,x] == 'R')
   275  				this.robot = new Pos(y,x);
   276  			if(this[y,x] == 'L' || this[y,x] == 'O')
   277  				this.lift = new Pos(y,x);
   278  			if(this[y,x] == '\\' || this[y,x] == '@')
   279  				total_lambda++;
   280  			if(is_rocky(this[y,x]))
   281  				may_update ~= new Pos(y,x);
   282  		}
   283  
   284  		this.waterproof      = params.get("Waterproof", "5").to!int();
   285  		this.collected_razor = params.get("Razors", "0").to!int();
   286  	}
   287  
   288  	void opIndexAssign(char c, int y, int x)
   289  	{
   290  		--y, --x;
   291  		if(y<0||H<=y||x<0||W<=x)
   292  			return;
   293  		data[H-1-y][x] = c;
   294  	}
   295  
   296  	void opIndexAssign(char c, in Pos p)
   297  	{
   298  		this[p.y, p.x] = c;
   299  	}
   300  
   301  	bool command(char c, int turn, bool hige_day, in Trampoline tr)
   302  	{
   303  		switch(c)
   304  		{
   305  		case 'R': return move( 0, +1, hige_day, tr);
   306  		case 'L': return move( 0, -1, hige_day, tr);
   307  		case 'U': return move(+1,  0, hige_day, tr);
   308  		case 'D': return move(-1,  0, hige_day, tr);
   309  		case 'W': return move( 0,  0, hige_day, tr);
   310  		case 'S': return use_razor(hige_day);
   311  		default:  assert(false);
   312  		}
   313  	}
   314  
   315  	bool use_razor(bool hige_day)
   316  	{
   317  		if(collected_razor > 0)
   318  		{
   319  			collected_razor--;
   320  			for(int dy=-1; dy<=+1; ++dy)
   321  			for(int dx=-1; dx<=+1; ++dx)
   322  				if(this[robot.y+dy,robot.x+dx] == 'W')
   323  					emptify(new Pos(robot.y+dy,robot.x+dx));
   324  		}
   325  		return update(hige_day);
   326  	}
   327  
   328  	// Register a position that may become empty in the last turn.
   329  	void emptify(Pos p)
   330  	{
   331  		this[p] = ' ';
   332  		for(int dy=0;  dy<=+1; ++dy)
   333  		for(int dx=-1; dx<=+1; ++dx)
   334  			may_update ~= new Pos(p.y+dy, p.x+dx);
   335  	}
   336  
   337  	bool move(int dy, int dx, bool hige_day, in Trampoline tr)
   338  	{
   339  		Pos next = new Pos(robot.y+dy, robot.x+dx);
   340  		int y=robot.y, x=robot.x;
   341  
   342  		if( '\\' == this[next] ) collected_lambda++;
   343  		if(  '!' == this[next] ) collected_razor++;
   344  		if( 'O'  == this[next] ) cleared = true;
   345  
   346  		if( is_spacy(this[next]) )
   347  		{
   348  			emptify(robot);
   349  			robot = next;
   350  			this[next] = 'R';
   351  		}
   352  		else if(dy==0 && is_rocky(this[next]) && ' '==this[y+dy*2,x+dx*2])
   353  		{
   354  			char rock  = this[next];
   355  			emptify(robot);
   356  			robot = next;
   357  			this[next]          = 'R';
   358  			this[y+dy*2,x+dx*2] = rock;
   359  			may_update ~= new Pos(y+dy*2,x+dx*2);
   360  		}
   361  		else if(is_trampoline_source(this[next]))
   362  		{
   363  			emptify(robot);
   364  			Pos tp = tr.target_pos(this[next]);
   365  			foreach(p; tr.source_pos(this[tp]))
   366  				emptify(p);
   367  			this[tp] = 'R';
   368  			robot    = tp;
   369  		}
   370  		return update(hige_day);
   371  	}
   372  
   373  	bool update(bool hige_day)
   374  	{
   375  		// Write after all the updates are processed.
   376  		Tuple!(int,int,char)[] write_buffer;
   377  		void write(int y, int x, char c) { write_buffer ~= tuple(y,x,c); }
   378  		void writep(Pos p, char c) { write_buffer ~= tuple(0+p.y,0+p.x,c); }
   379  		scope(exit) {
   380  			may_update.length = 0;
   381  			foreach(wr; write_buffer) {
   382  				this[wr[0],wr[1]] = wr[2];
   383  				if(is_rocky(wr[2]))
   384  					may_update ~= new Pos(wr[0],wr[1]);
   385  				if(wr[2]==' ')
   386  					emptify(new Pos(wr[0], wr[1]));
   387  			}
   388  		}
   389  
   390  		if(collected_lambda == total_lambda)
   391  			if(this[lift]=='L')
   392  				this[lift] = 'O';
   393  
   394  		bool dead = false;
   395  		if( hige_day ) {
   396  			for(int y=1; y<=H; ++y)
   397  			for(int x=1; x<=W; ++x)
   398  				if(this[y,x]=='W')
   399  					may_update ~= new Pos(y,x);
   400  		}
   401  
   402  		sort(may_update);
   403  		foreach(p; may_update) {
   404  			int y = p.y, x = p.x;
   405  			char rock = this[p];
   406  			if(is_rocky(this[p])) {
   407  				if(this[p.D]==' ') {
   408  					writep(p, ' ');
   409  					writep(p.D, (rock=='@'&&this[p.D.D]!=' ' ? '\\' : rock));
   410  					if(robot == p.D.D)
   411  						dead=true;
   412  				}
   413  				else if((is_rocky(this[p.D]) || this[p.D]=='\\') && this[p.R]==' ' && this[p.R.D]==' ') {
   414  					writep(p, ' ');
   415  					writep(p.R.D,(rock=='@'&&this[p.R.D.D]!=' ' ? '\\' : rock));
   416  					if(robot == p.R.D.D)
   417  						dead=true;
   418  				}
   419  				else if(is_rocky(this[p.D]) && this[p.L]==' ' && this[p.L.D]==' ') {
   420  					writep(p, ' ');
   421  					writep(p.L.D, (rock=='@'&&this[p.L.D.D]!=' ' ? '\\' : rock));
   422  					if(robot == p.L.D.D)
   423  						dead=true;
   424  				}
   425  			}
   426  			else if(this[p]=='W') {
   427  				if(hige_day) {
   428  					for(int dy=-1; dy<=+1; ++dy)
   429  					for(int dx=-1; dx<=+1; ++dx)
   430  						if(this[p.y+dy,p.x+dx] == ' ') {
   431  							write(p.y+dy,p.x+dx,'W');
   432  							if(robot.y==p.y+dy-1 && robot.x==p.x+dx)
   433  								dead = false; // guarded by hige!
   434  						}
   435  				}
   436  			}
   437  		}
   438  
   439  		return dead;
   440  	}
   441  }
   442  
   443  ////////////////////////////////////////////////////////////////////////////////
   444  
   445  class Game
   446  {
   447  	mixin DeriveShow;
   448  
   449  	private {
   450  		Map map_;
   451  		Water water_;
   452  		Hige hige_;
   453  		Trampoline tr_;
   454  		int  turn = 0;
   455  		bool dead_ = false;
   456  		int  under_water = 0;
   457  	}
   458  
   459  	Game clone() const { return new Game(this); }
   460  	this(in Game g) {
   461  		map_   = g.map_.clone();
   462  		water_ = g.water_.clone();
   463  		hige_  = g.hige_.clone();
   464  		tr_    = g.tr_.clone();
   465  		turn   = g.turn;
   466  		dead_  = g.dead_;
   467  		under_water = g.under_water;
   468  	}
   469  
   470  	this(File input)
   471  	{
   472  		string[]       raw_data;
   473  		string[string] params;
   474  
   475  		// Raw map data; read until empty line.
   476  		for(string line; !(line=input.readln().chomp()).empty; )
   477  			raw_data ~= line;
   478  
   479  		// Additional commands; read until EOF.
   480  		char[char] trampo;
   481  		for(string line; !(line=input.readln()).empty; ) {
   482  			string[] ss = line.split();
   483  			if( ss.length == 2 )
   484  				params[ss[0]] = ss[1];
   485  			if( ss.length == 4 && ss[0]=="Trampoline" && ss[2]=="targets" )
   486  				trampo[ss[1][0]] = ss[3][0];
   487  		}
   488  
   489  		this.map_   = new Map(raw_data, params, trampo);
   490  		this.water_ = Water.load(params);
   491  		this.hige_  = Hige.load(params);
   492  		this.tr_    = new Trampoline(this.map, trampo);
   493  	}
   494  
   495  	void command(char c)
   496  	{
   497  		assert(c != 'A');
   498  		if(dead || cleared)
   499  			return;
   500  
   501  		// TODO: clarify the event order
   502  		bool dead_now = map_.command(c, turn, hige.is_growing_turn(turn), tr);
   503  		if( dead_now )
   504  			dead_ = true;
   505  		if(!map.cleared) {
   506  			if( map.robot.y <= water_level )
   507  				++under_water;
   508  			else
   509  				under_water = 0;
   510  			if( under_water > map.waterproof )
   511  				dead_ = true;
   512  		}
   513  		turn += 1;
   514  	}
   515  
   516  @property const:
   517  	long score()           { return map.collected_lambda*(dead?25L:cleared?75L:50L)-turn; }
   518  	int water_level()      { return water.level(turn); }
   519  	int water_until_rise() { return water.until_rise(turn); }
   520  	int hige_until_rise()  { return hige.until_rise(turn); }
   521  	int hp()               { return map.waterproof - under_water; }
   522  	bool cleared()         { return map.cleared; }
   523  	bool dead()            { return dead_; }
   524  	const(Map)        map()   { return map_; }
   525  	const(Water)      water() { return water_; }
   526  	const(Hige)       hige()  { return hige_; }
   527  	const(Trampoline) tr()    { return tr_; }
   528  }