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 }