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