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 }