Artifact Content
Not logged in

Artifact 7c154814932570834ad12ebee3ffb53d8f2e0f44


     1  import util;
     2  import output;
     3  
     4  ////////////////////////////////////////////////////////////////////////////////
     5  
     6  class Pos
     7  {
     8  	public immutable int y, x;
     9  	mixin DeriveCreate;
    10  	mixin DeriveCompare;
    11  	mixin DeriveShow;
    12  	Pos clone() { return this; }
    13  
    14  @property:
    15  	Pos wait()  { return this; }
    16  	Pos up()    { return new Pos(y+1, x); }
    17  	Pos down()  { return new Pos(y-1, x); }
    18  	Pos left()  { return new Pos(y, x-1); }
    19  	Pos right() { return new Pos(y, x+1); }
    20  	alias wait  W,w;
    21  	alias up    U,u;
    22  	alias down  D,d;
    23  	alias left  L,l;
    24  	alias right R,r;
    25  }
    26  
    27  unittest
    28  {
    29  	assert( (new Pos(2,1)).U == new Pos(3,1) );
    30  	assert( (new Pos(0,1)).D == new Pos(-1,1) );
    31  	assert( (new Pos(2,1)).L == new Pos(2,0) );
    32  	assert( (new Pos(2,1)).R == new Pos(2,2) );
    33  	int[Pos] aa;
    34  	aa[new Pos(1,2)] = 1;
    35  	aa[new Pos(1,2)] = 2;
    36  	aa[new Pos(2,1)] = 3;
    37  	assert( aa.length==2 );
    38  	assert( aa[new Pos(1,2)]==2 );
    39  }
    40  
    41  ////////////////////////////////////////////////////////////////////////////////
    42  
    43  class Water
    44  {
    45  	public immutable int base, pace;
    46  	mixin DeriveCreate;
    47  	mixin DeriveCompare;
    48  	mixin DeriveShow;
    49  	Water clone() { return this; }
    50  
    51  	static load(string[string] params)
    52  	{
    53  		return new Water(
    54  			params.get("Water",    "0").to!int(),
    55  			params.get("Flooding", "0").to!int()
    56  		);
    57  	}
    58  
    59  	int level(int number_of_update)
    60  	{
    61  		return pace ? base+(number_of_update/pace) : base;
    62  	}
    63  
    64  	int until_rise(int number_of_update)
    65  	{
    66  		return pace ? pace-number_of_update%pace : int.max;
    67  	}
    68  }
    69  
    70  unittest
    71  {
    72  	Water w = new Water(1, 3);
    73  	assert( 1 == w.level(0) );
    74  	assert( 1 == w.level(1) );
    75  	assert( 1 == w.level(2) );
    76  	assert( 2 == w.level(3) );
    77  	assert( 2 == w.level(4) );
    78  	assert( 2 == w.level(5) );
    79  	assert( 3 == w.level(6) );
    80  
    81  	w = new Water(1, 0);
    82  	assert( 1 == w.level(0) );
    83  	assert( 1 == w.level(1) );
    84  	assert( 1 == w.level(2) );
    85  	assert( 1 == w.level(3) );
    86  	assert( 1 == w.level(4) );
    87  	assert( 1 == w.level(5) );
    88  }
    89  
    90  ////////////////////////////////////////////////////////////////////////////////
    91  
    92  class Map
    93  {
    94  	mixin DeriveShow;
    95  
    96  	static Map load(string[] raw_data, string[string] params)
    97  	{
    98  		// TODO: choose optimal representation.
    99  		return new Map(raw_data, params);
   100  	}
   101  
   102  	char[][] data;
   103  	Pos robot;
   104  	Pos lift;
   105  	int waterproof;
   106  
   107  	Map clone() { return new Map(this); }
   108  	this(Map m) {
   109  		foreach(s; m.data)
   110  			this.data ~= s.dup;
   111  		this.robot = m.robot.clone();
   112  		this.lift = m.lift.clone();
   113  		this.waterproof = m.waterproof;
   114  	}
   115  
   116  	this(string[] raw_data, string[string] params)
   117  	{
   118  		int width = 0;
   119  		foreach(r; raw_data)
   120  			width = max(width, r.length);
   121  		foreach(r; raw_data) {
   122  			this.data ~= r.dup;
   123  			this.data[$-1].length = width;
   124  			this.data[$-1][r.length..$] = ' ';
   125  		}
   126  
   127  		for(int y=1; y<=H; ++y)
   128  		for(int x=1; x<=W; ++x) {
   129  			if(this[y,x] == 'R')
   130  				this.robot = new Pos(y,x);
   131  			if(this[y,x] == 'L' || this[y,x] == 'O')
   132  				this.lift = new Pos(y,x);
   133  		}
   134  
   135  		this.waterproof = params.get("Waterproof", "5").to!int();
   136  	}
   137  
   138  	const @property {
   139  		int H() { return data.length; }
   140  		int W() { return data[0].length; }
   141  	}
   142  
   143  	char opIndex(int y, int x)
   144  	{
   145  		// Adjust coordinate to the spec. bottom-left is (1,1).
   146  		--y, --x;
   147  		if(y<0||H<=y||x<0||W<=x)
   148  			return '#';
   149  		return data[H-1-y][x];
   150  	}
   151  
   152  	char opIndex(Pos p)
   153  	{
   154  		return this[p.y, p.x];
   155  	}
   156  
   157  	void opIndexAssign(char c, int y, int x)
   158  	{
   159  		// Adjust coordinate to the spec. bottom-left is (1,1).
   160  		--y, --x;
   161  		if(y<0||H<=y||x<0||W<=x)
   162  			return;
   163  		data[H-1-y][x] = c;
   164  	}
   165  
   166  	void opIndexAssign(char c, Pos p)
   167  	{
   168  		this[p.y, p.x] = c;
   169  	}
   170  
   171  	Pos[] lambdas() {
   172  		Pos[] ans;
   173  		for(int y=1; y<=H; ++y)
   174  		for(int x=1; x<=W; ++x)
   175  			if(this[y,x] == '\\')
   176  				ans ~= new Pos(y,x);
   177  		return ans;
   178  	}
   179  
   180  	bool cleared()
   181  	{
   182  		for(int y=1; y<=H; ++y)
   183  		for(int x=1; x<=W; ++x)
   184  			if(this[y,x] == 'L' || this[y,x] == 'O')
   185  				return false;
   186  		return true;
   187  	}
   188  	
   189  	Tuple!(int,bool) command(char c)
   190  	{
   191  		if(c=='R') return move( 0, +1);
   192  		if(c=='L') return move( 0, -1);
   193  		if(c=='U') return move(+1,  0);
   194  		if(c=='D') return move(-1,  0);
   195  		if(c=='W') return move( 0,  0);
   196  		assert(false);
   197  	}
   198  
   199  	Tuple!(int, bool) move(int dy, int dx)
   200  	{
   201  		int y = robot.y;
   202  		int x = robot.x;
   203  		assert( this[robot] == 'R' );
   204  		int lambda = 0;
   205  		bool dead = false;
   206  		if( '\\' == this[y+dy,x+dx] )
   207  			lambda++;
   208  		if( " \\.O".count(this[y+dy,x+dx])==1 ) {
   209  			this[y,x]=' ';
   210  			this[y+dy,x+dx]='R';
   211  			robot = new Pos(y+dy,x+dx);
   212  		} else if(dy==0 && '*'==this[y+dy,x+dx] && ' '==this[y+dy*2,x+dx*2]) {
   213  			this[y,x]=' ';
   214  			this[y+dy,x+dx]='R';
   215  			this[y+dy*2,x+dx*2]='*';
   216  			robot = new Pos(y+dy,x+dx);
   217  		}
   218  		if( update() )
   219  			dead = true;
   220  		return tuple(lambda,dead);
   221  	}
   222  
   223  	bool update()
   224  	{
   225  		bool dead = false;
   226  
   227  		char[][] next;
   228  		foreach(y,s; data)
   229  			next ~= s.dup;
   230  
   231  		ref char access(Pos p) { return next[H-p.y][p.x-1]; }
   232  
   233  		bool lambda = false;
   234  		for(int y=1; y<=H; ++y)
   235  		for(int x=1; x<=W; ++x)
   236  			lambda |= (this[y,x] == '\\');
   237  
   238  		for(int y=1; y<=H; ++y)
   239  		for(int x=1; x<=W; ++x) {
   240  			Pos p = new Pos(y,x);
   241  			if(this[p]=='*') {
   242  				if(this[p.D]==' ') {
   243  					access(p)  =' ';
   244  					access(p.D)='*';
   245  					if(robot == p.D.D)
   246  						dead=true;
   247  				}
   248  				else if((this[p.D]=='*' || this[p.D]=='\\') && this[p.R]==' ' && this[p.R.D]==' ') {
   249  					access(p)=' ';
   250  					access(p.R.D)='*';
   251  					if(robot == p.R.D.D)
   252  						dead=true;
   253  				}
   254  				else if(this[p.D]=='*' && this[p.L]==' ' && this[p.L.D]==' ') {
   255  					access(p)=' ';
   256  					access(p.L.D)='*';
   257  					if(robot == p.L.D.D)
   258  						dead=true;
   259  				}
   260  			}
   261  			else if(this[p]=='L') {
   262  				if(!lambda)
   263  					access(p) = 'O';
   264  			}
   265  		}
   266  		data = next;
   267  		return dead;
   268  	}
   269  }
   270  
   271  ////////////////////////////////////////////////////////////////////////////////
   272  
   273  class Game
   274  {
   275  	mixin DeriveShow;
   276  
   277  	static Game load(File input)
   278  	{
   279  		string[]       raw_data;
   280  		string[string] params;
   281  
   282  		// Raw map data; read until empty line.
   283  		for(string line; !(line=input.readln().chomp()).empty; )
   284  			raw_data ~= line;
   285  
   286  		// Additional commands; read until EOF.
   287  		for(string line; !(line=input.readln()).empty; ) {
   288  			string[] ss = line.split();
   289  			if( ss.length == 2 )
   290  				params[ss[0]] = ss[1];
   291  		}
   292  
   293  		return load(raw_data, params);
   294  	}
   295  
   296  	static Game load(string[] raw_data, string[string] params)
   297  	{
   298  		return new Game(raw_data, params);
   299  	}
   300  
   301  	this(string[] raw_data, string[string] params)
   302  	{
   303  		this.map = Map.load(raw_data, params);
   304  		this.water = Water.load(params);
   305  		this.output = new NilOutput;
   306  	}
   307  
   308  	Game clone() { return new Game(this); }
   309  	this(Game g) {
   310  		map = g.map.clone();
   311  		water = g.water.clone();
   312  		output = new NilOutput;
   313  		turn = g.turn;
   314  		dead = g.dead;
   315  		lambda = g.lambda;
   316  		exit_bonus = g.exit_bonus;
   317  		under_water = g.under_water;
   318  	}
   319  
   320  	void set_output(Output o) { this.output = (o is null ? new NilOutput : o); }
   321  
   322  	void command(char c)
   323  	{
   324  		if(dead || cleared)
   325  			return;
   326  		scope(exit) {
   327  			if(dead || cleared)
   328  				output.flush();
   329  		}
   330  		this.output.command(c);
   331  
   332  		if(c == 'A')
   333  		{
   334  			exit_bonus = 1;
   335  			return;
   336  		}
   337  
   338  		// TODO: clarify the event order
   339  		Tuple!(int,bool) ld = map.command(c);
   340  		if( map.cleared() ) {
   341  			exit_bonus = 2;
   342  		}
   343  		else {
   344  			lambda += ld[0];
   345  			if( ld[1] ) {
   346  				dead = true;
   347  			}
   348  		}
   349  		if( map.robot.y <= water_level )
   350  			++under_water;
   351  		else
   352  			under_water = 0;
   353  		if( under_water > map.waterproof )
   354  			dead = true;
   355  		turn += 1;
   356  	}
   357  
   358  	Map map;
   359  	Water water;
   360  	Output output;
   361  	int  turn = 0;
   362  	bool dead = false;
   363  	int  lambda = 0;
   364  	int  exit_bonus = 0;
   365  	int  under_water = 0;
   366  	// TODO: when adding members, take care of clone().
   367  	// TODO: fix this poor design.
   368  
   369  	@property {
   370  		long score() { return lambda*25L*(1+exit_bonus) - turn; }
   371  		int water_level() { return water.level(turn); }
   372  		int water_until_rise() { return water.until_rise(turn); }
   373  		bool cleared() { return exit_bonus>0; }
   374  		int hp() { return map.waterproof - under_water; }
   375  		long score_if_abort_now() { return lambda*25*(1+max(1,exit_bonus)) - turn; }
   376  	}
   377  }
   378  
   379  unittest
   380  {
   381  	Game.load(["###","...","#RL"], ["xxx":"yyy"]);
   382  }