Artifact Content
Not logged in

Artifact e25c75ea96d8050747f4b4ad800450ae9fbc69b5


     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  @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 DeriveCompare;
    47  	mixin DeriveShow;
    48  	Water clone() const { return cast(Water)this; }
    49  
    50  	static load(string[string] params)
    51  	{
    52  		return new Water(params.get("Water",    "0").to!int(),
    53  		                 params.get("Flooding", "0").to!int());
    54  	}
    55  
    56  	int level(int number_of_update) const
    57  	{
    58  		return pace ? base+(number_of_update/pace) : base;
    59  	}
    60  
    61  	int until_rise(int number_of_update) const
    62  	{
    63  		return pace ? pace-number_of_update%pace : int.max;
    64  	}
    65  }
    66  
    67  unittest
    68  {
    69  	Water w = new Water(1, 3);
    70  	assert( 1 == w.level(0) );
    71  	assert( 1 == w.level(1) );
    72  	assert( 1 == w.level(2) );
    73  	assert( 2 == w.level(3) );
    74  	assert( 2 == w.level(4) );
    75  	assert( 2 == w.level(5) );
    76  	assert( 3 == w.level(6) );
    77  
    78  	w = new Water(1, 0);
    79  	assert( 1 == w.level(0) );
    80  	assert( 1 == w.level(1) );
    81  	assert( 1 == w.level(2) );
    82  	assert( 1 == w.level(3) );
    83  	assert( 1 == w.level(4) );
    84  	assert( 1 == w.level(5) );
    85  }
    86  
    87  ////////////////////////////////////////////////////////////////////////////////
    88  
    89  class Hige
    90  {
    91  	public immutable int pace;
    92  	mixin DeriveCreate;
    93  	mixin DeriveCompare;
    94  	mixin DeriveShow;
    95  	Hige clone() const { return cast(Hige)this; }
    96  
    97  	static load(string[string] params)
    98  	{
    99  		return new Hige(params.get("Growth", "25").to!int());
   100  	}
   101  
   102  	bool is_growing_turn(int turn) const
   103  	{
   104  		return pace ? turn%pace == pace-1 : false;
   105  	}
   106  
   107  	int until_rise(int turn) const
   108  	{
   109  		return pace ? pace-turn%pace : int.max;
   110  	}
   111  }
   112  
   113  ////////////////////////////////////////////////////////////////////////////////
   114  
   115  class Map
   116  {
   117  	mixin DeriveShow;
   118  
   119  	static Map load(string[] raw_data, string[string] params, char[char] trampo)
   120  	{
   121  		// TODO: choose optimal representation.
   122  		return new Map(raw_data, params, trampo);
   123  	}
   124  
   125  	char[][] data;
   126  	Pos robot;
   127  	Pos lift;
   128  	int waterproof;
   129  	Pos[char] tr_target;
   130  	Pos[][char] tr_source;
   131  	const(Hige) hige;
   132  	int razor;
   133  
   134  	Map clone() const { return new Map(this); }
   135  	this(in Map m) {
   136  		foreach(s; m.data)
   137  			this.data ~= s.dup;
   138  		this.robot = m.robot.clone();
   139  		this.lift = m.lift.clone();
   140  		this.waterproof = m.waterproof;
   141  		this.tr_target = cast(Pos[char])m.tr_target;
   142  		this.tr_source = cast(Pos[][char])m.tr_source;
   143  		this.hige = m.hige.clone();
   144  		this.razor = m.razor;
   145  	}
   146  
   147  	this(string[] raw_data, string[string] params, char[char] trampo)
   148  	{
   149  		int width = 0;
   150  		foreach(r; raw_data)
   151  			width = max(width, r.length);
   152  		foreach(r; raw_data) {
   153  			this.data ~= r.dup;
   154  			this.data[$-1].length = width;
   155  			this.data[$-1][r.length..$] = ' ';
   156  		}
   157  
   158  		for(int y=1; y<=H; ++y)
   159  		for(int x=1; x<=W; ++x) {
   160  			if(this[y,x] == 'R')
   161  				this.robot = new Pos(y,x);
   162  			if(this[y,x] == 'L' || this[y,x] == 'O')
   163  				this.lift = new Pos(y,x);
   164  		}
   165  
   166  		Pos[char] tr_pos;
   167  		for(int y=1; y<=H; ++y)
   168  		for(int x=1; x<=W; ++x) {
   169  			char c = this[y,x];
   170  			if('1'<=c && c<='9' || 'A'<=c&&c<='I')
   171  				tr_pos[c] = new Pos(y,x);
   172  		}
   173  
   174  		this.waterproof = params.get("Waterproof", "5").to!int();
   175  		foreach(fr,to; trampo) {
   176  			tr_target[fr] = tr_pos[to];
   177  			if(to !in tr_source) tr_source[to] = [];
   178  			tr_source[to] ~= tr_pos[fr];
   179  		}
   180  
   181  		this.hige = Hige.load(params);
   182  		this.razor = params.get("Razors", "0").to!int();
   183  	}
   184  
   185  	const @property {
   186  		int H() { return data.length; }
   187  		int W() { return data[0].length; }
   188  	}
   189  
   190  	const {
   191  		char opIndex(int y, int x)
   192  		{
   193  			// Adjust coordinate to the spec. bottom-left is (1,1).
   194  			--y, --x;
   195  			if(y<0||H<=y||x<0||W<=x)
   196  				return '#';
   197  			return data[H-1-y][x];
   198  		}
   199  
   200  		char opIndex(in Pos p)
   201  		{
   202  			return this[p.y, p.x];
   203  		}
   204  	}
   205  
   206  	void opIndexAssign(char c, int y, int x)
   207  	{
   208  		// Adjust coordinate to the spec. bottom-left is (1,1).
   209  		--y, --x;
   210  		if(y<0||H<=y||x<0||W<=x)
   211  			return;
   212  		data[H-1-y][x] = c;
   213  	}
   214  
   215  	void opIndexAssign(char c, in Pos p)
   216  	{
   217  		this[p.y, p.x] = c;
   218  	}
   219  
   220  	Pos[] objects(char c) const {
   221  		Pos[] ans;
   222  		for(int y=1; y<=H; ++y)
   223  		for(int x=1; x<=W; ++x)
   224  			if(this[y,x] == c)
   225  				ans ~= new Pos(y,x);
   226  		return ans;
   227  	}
   228  
   229  	Pos[] razors() const { return objects('!'); }
   230  	Pos[] lambdas() const { return objects('\\'); }
   231  
   232  	bool cleared() const
   233  	{
   234  		for(int y=1; y<=H; ++y)
   235  		for(int x=1; x<=W; ++x)
   236  			if(this[y,x] == 'L' || this[y,x] == 'O')
   237  				return false;
   238  		return true;
   239  	}
   240  	
   241  	Tuple!(int,bool) command(char c, int turn)
   242  	{
   243  		assert( this[robot] == 'R' );
   244  		if(c=='R') return move( 0, +1, turn);
   245  		if(c=='L') return move( 0, -1, turn);
   246  		if(c=='U') return move(+1,  0, turn);
   247  		if(c=='D') return move(-1,  0, turn);
   248  		if(c=='W') return move( 0,  0, turn);
   249  		if(c=='S') return use_razor(turn);
   250  		assert(false);
   251  	}
   252  
   253  	Tuple!(int, bool) use_razor(int turn)
   254  	{
   255  		if(razor) {
   256  			razor--;
   257  			for(int dy=-1; dy<=+1; ++dy)
   258  			for(int dx=-1; dx<=+1; ++dx)
   259  				if(this[robot.y+dy,robot.x+dx] == 'W')
   260  					this[robot.y+dy,robot.x+dx] = ' ';
   261  		}
   262  
   263  		bool dead = update(turn);
   264  		return tuple(0,dead);
   265  	}
   266  
   267  	Tuple!(int, bool) move(int dy, int dx, int turn)
   268  	{
   269  		int y = robot.y;
   270  		int x = robot.x;
   271  		int lambda = 0;
   272  		if( '\\' == this[y+dy,x+dx] )
   273  			lambda++;
   274  		if( '!' == this[y+dy,x+dx] )
   275  			razor++;
   276  		if( " \\!.O".count(this[y+dy,x+dx])==1 ) {
   277  			this[y,x]=' ';
   278  			this[y+dy,x+dx]='R';
   279  			robot = new Pos(y+dy,x+dx);
   280  		} else if(dy==0 && '*'==this[y+dy,x+dx] && ' '==this[y+dy*2,x+dx*2]) {
   281  			this[y,x]=' ';
   282  			this[y+dy,x+dx]='R';
   283  			this[y+dy*2,x+dx*2]='*';
   284  			robot = new Pos(y+dy,x+dx);
   285  		} else if('A'<=this[y+dy,x+dx] && this[y+dy,x+dx]<='I') {
   286  			this[y,x]=' ';
   287  			Pos tp = tr_target[this[y+dy,x+dx]];
   288  			foreach(p; tr_source[this[tp]])
   289  				this[p] = ' ';
   290  			this[tp] = 'R';
   291  			robot = tp;
   292  		}
   293  		bool dead = update(turn);
   294  		return tuple(lambda,dead);
   295  	}
   296  
   297  	bool update(int turn)
   298  	{
   299  		bool dead = false;
   300  
   301  		char[][] next;
   302  		foreach(y,s; data)
   303  			next ~= s.dup;
   304  
   305  		ref char access(Pos p) { return next[H-p.y][p.x-1]; }
   306  
   307  		bool lambda = false;
   308  		for(int y=1; y<=H; ++y)
   309  		for(int x=1; x<=W; ++x)
   310  			lambda |= (this[y,x] == '\\');
   311  
   312  		for(int y=1; y<=H; ++y)
   313  		for(int x=1; x<=W; ++x) {
   314  			Pos p = new Pos(y,x);
   315  			if(this[p]=='*') {
   316  				if(this[p.D]==' ') {
   317  					access(p)  =' ';
   318  					access(p.D)='*';
   319  					if(robot == p.D.D)
   320  						dead=true;
   321  				}
   322  				else if((this[p.D]=='*' || this[p.D]=='\\') && this[p.R]==' ' && this[p.R.D]==' ') {
   323  					access(p)=' ';
   324  					access(p.R.D)='*';
   325  					if(robot == p.R.D.D)
   326  						dead=true;
   327  				}
   328  				else if(this[p.D]=='*' && this[p.L]==' ' && this[p.L.D]==' ') {
   329  					access(p)=' ';
   330  					access(p.L.D)='*';
   331  					if(robot == p.L.D.D)
   332  						dead=true;
   333  				}
   334  			}
   335  			else if(this[p]=='L') {
   336  				if(!lambda)
   337  					access(p) = 'O';
   338  			}
   339  			else if(this[p]=='W') {
   340  				if( hige.is_growing_turn(turn) )
   341  					for(int dy=-1; dy<=+1; ++dy)
   342  					for(int dx=-1; dx<=+1; ++dx)
   343  						if(this[p.y+dy,p.x+dx] == ' ')
   344  							access(new Pos(p.y+dy,p.x+dx)) = 'W';
   345  			}
   346  		}
   347  		data = next;
   348  		return dead;
   349  	}
   350  }
   351  
   352  ////////////////////////////////////////////////////////////////////////////////
   353  
   354  class Game
   355  {
   356  	mixin DeriveShow;
   357  
   358  	static Game load(File input)
   359  	{
   360  		string[]       raw_data;
   361  		string[string] params;
   362  
   363  		// Raw map data; read until empty line.
   364  		for(string line; !(line=input.readln().chomp()).empty; )
   365  			raw_data ~= line;
   366  
   367  		// Additional commands; read until EOF.
   368  		char[char] trampo;
   369  		for(string line; !(line=input.readln()).empty; ) {
   370  			string[] ss = line.split();
   371  			if( ss.length == 2 )
   372  				params[ss[0]] = ss[1];
   373  			if( ss.length == 4 && ss[0]=="Trampoline" && ss[2]=="targets" )
   374  				trampo[ss[1][0]] = ss[3][0];
   375  		}
   376  
   377  		return load(raw_data, params, trampo);
   378  	}
   379  
   380  	static Game load(string[] raw_data, string[string] params, char[char] trampo = null)
   381  	{
   382  		return new Game(raw_data, params, trampo);
   383  	}
   384  
   385  	this(string[] raw_data, string[string] params, char[char] trampo)
   386  	{
   387  		this.map = Map.load(raw_data, params, trampo);
   388  		this.water = Water.load(params);
   389  	}
   390  
   391  	Game clone() const { return new Game(this); }
   392  	this(in Game g) {
   393  		map = g.map.clone();
   394  		water = g.water.clone();
   395  		turn = g.turn;
   396  		dead = g.dead;
   397  		lambda = g.lambda;
   398  		cleared = g.cleared;
   399  		under_water = g.under_water;
   400  	}
   401  
   402  	void command(char c)
   403  	{
   404  		assert(c != 'A');
   405  		if(dead || cleared)
   406  			return;
   407  
   408  		// TODO: clarify the event order
   409  		Tuple!(int,bool) ld = map.command(c, turn);
   410  		if( map.cleared() ) {
   411  			cleared = true;
   412  		}
   413  		else {
   414  			lambda += ld[0];
   415  			if( ld[1] )
   416  				dead = true;
   417  		}
   418  		if(!cleared) {
   419  			if( map.robot.y <= water_level )
   420  				++under_water;
   421  			else
   422  				under_water = 0;
   423  			if( under_water > map.waterproof )
   424  				dead = true;
   425  		}
   426  		turn += 1;
   427  	}
   428  
   429  	Map map;
   430  	Water water;
   431  	int  turn = 0;
   432  	bool dead = false;
   433  	int  lambda = 0;
   434  	int  under_water = 0;
   435  	bool cleared = false;
   436  	// TODO: when adding members, take care of clone().
   437  	// TODO: fix this poor design.
   438  
   439  	@property const {
   440  		long score() { return lambda*(dead ? 25L : cleared ? 75L : 50L) - turn; }
   441  		int water_level() { return water.level(turn); }
   442  		int water_until_rise() { return water.until_rise(turn); }
   443  		int hige_until_rise() { return map.hige.until_rise(turn); }
   444  		int hp() { return map.waterproof - under_water; }
   445  	}
   446  }