Artifact Content
Not logged in

Artifact d9bf3ea129c7cbc53bdfa87b6a290a4f08fd0845


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