import util;
////////////////////////////////////////////////////////////////////////////////
class Pos
{
public immutable int y, x;
mixin DeriveCreate;
mixin DeriveCompare;
mixin DeriveShow;
Pos clone() const { return new Pos(y, x); }
@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 new Water(base, pace); }
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 Map
{
mixin DeriveShow;
static Map load(string[] raw_data, string[string] params)
{
// TODO: choose optimal representation.
return new Map(raw_data, params);
}
char[][] data;
Pos robot;
Pos lift;
int waterproof;
Map clone() const { return new Map(this); }
this(const(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(string[] raw_data, string[string] params)
{
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);
}
this.waterproof = params.get("Waterproof", "5").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(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, Pos p)
{
this[p.y, p.x] = c;
}
Pos[] lambdas() {
Pos[] ans;
for(int y=1; y<=H; ++y)
for(int x=1; x<=W; ++x)
if(this[y,x] == '\\')
ans ~= new Pos(y,x);
return ans;
}
bool cleared()
{
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)
{
if(c=='R') return move( 0, +1);
if(c=='L') return move( 0, -1);
if(c=='U') return move(+1, 0);
if(c=='D') return move(-1, 0);
if(c=='W') return move( 0, 0);
assert(false);
}
Tuple!(int, bool) move(int dy, int dx)
{
int y = robot.y;
int x = robot.x;
assert( this[robot] == 'R' );
int lambda = 0;
bool dead = false;
if( '\\' == this[y+dy,x+dx] )
lambda++;
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);
}
if( update() )
dead = true;
return tuple(lambda,dead);
}
bool update()
{
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';
}
}
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.
for(string line; !(line=input.readln()).empty; ) {
string[] ss = line.split();
if( ss.length == 2 )
params[ss[0]] = ss[1];
}
return load(raw_data, params);
}
static Game load(string[] raw_data, string[string] params)
{
return new Game(raw_data, params);
}
this(string[] raw_data, string[string] params)
{
this.map = Map.load(raw_data, params);
this.water = Water.load(params);
}
Game clone() const { return new Game(this); }
this(const(Game) g) {
map = g.map.clone();
water = g.water.clone();
turn = g.turn;
dead = g.dead;
lambda = g.lambda;
exit_bonus = g.exit_bonus;
under_water = g.under_water;
}
void command(char c)
{
if(dead || cleared)
return;
if(c == 'A')
{
exit_bonus = 1;
return;
}
// TODO: clarify the event order
Tuple!(int,bool) ld = map.command(c);
if( map.cleared() ) {
exit_bonus = 2;
}
else {
lambda += ld[0];
if( ld[1] ) {
dead = true;
}
}
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 exit_bonus = 0;
int under_water = 0;
// TODO: when adding members, take care of clone().
// TODO: fix this poor design.
@property const {
long score() { return lambda*25L*(1+exit_bonus) - turn; }
int water_level() { return water.level(turn); }
int water_until_rise() { return water.until_rise(turn); }
bool cleared() { return exit_bonus>0; }
int hp() { return map.waterproof - under_water; }
long score_if_abort_now() { return lambda*25*(1+max(1,exit_bonus)) - turn; }
}
}
unittest
{
Game.load(["###","...","#RL"], ["xxx":"yyy"]);
}