DELETED game.d Index: game.d ================================================================== --- game.d +++ game.d @@ -1,382 +0,0 @@ -import util; -import output; - -//////////////////////////////////////////////////////////////////////////////// - -class Pos -{ - public immutable int y, x; - mixin DeriveCreate; - mixin DeriveCompare; - mixin DeriveShow; - Pos clone() { return this; } - -@property: - Pos wait() { return this; } - 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() { return this; } - - 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) - { - return pace ? base+(number_of_update/pace) : base; - } - - int until_rise(int number_of_update) - { - 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() { return new Map(this); } - this(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; } - } - - 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); - this.output = new NilOutput; - } - - Game clone() { return new Game(this); } - this(Game g) { - map = g.map.clone(); - water = g.water.clone(); - output = new NilOutput; - turn = g.turn; - dead = g.dead; - lambda = g.lambda; - exit_bonus = g.exit_bonus; - under_water = g.under_water; - } - - void set_output(Output o) { this.output = (o is null ? new NilOutput : o); } - - void command(char c) - { - if(dead || cleared) - return; - scope(exit) { - if(dead || cleared) - output.flush(); - } - this.output.command(c); - - 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; - Output output; - 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 { - 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"]); -} DELETED gui.d Index: gui.d ================================================================== --- gui.d +++ gui.d @@ -1,119 +0,0 @@ -import dfl.all; -import util; -import game; -import output; -//import solver; - -class GUI : Form -{ - private { - Game g; - int cell; - int turn = 0; - - Font font; - Color[char] colors; - string[char] render; - } - - this(Game g) - { - noMessageFilter(); - this.setStyle(ControlStyles.OPAQUE, true); - this.g = g; - - this.paint ~= &my_paint; - this.keyDown ~= &my_keydown; - - this.formBorderStyle = FormBorderStyle.FIXED_DIALOG; - this.maximizeBox = false; - this.minimizeBox = false; - this.cell = min(1024/g.map.W, 640/g.map.H); - this.clientSize = Size(g.map.W*cell, g.map.H*cell); - set_text(); - - // Resources - this.font = new Font("MS Gothic", cell-2, GraphicsUnit.PIXEL); - this.backColor = Color(255,255,255); - this.colors['#'] = - this.colors['.'] = Color(255,191,127); - this.colors['*'] = Color(255,127,127); - this.colors['R'] = Color(128,128,0); - this.colors['D'] = Color(255,0,0); // Dead - this.colors['\\'] = - this.colors['L'] = - this.colors['O'] = Color(127,255,127); - this.colors['W'] = Color(204,229,255); // water - - this.render['#'] = "■"; - this.render['*'] = "✹"; - this.render['.'] = "♒"; - this.render['\\'] = "λ"; - this.render['R'] = "☃"; - this.render['D'] = "☠"; - this.render['L'] = "☒"; - this.render['O'] = "☐"; - } - -private: - void my_paint(Control, PaintEventArgs ev) - { - const scrH = this.clientSize.height; - const scrW = this.clientSize.width; - Graphics gr = new MemoryGraphics(scrW, scrH, ev.graphics); - scope(exit) { - gr.copyTo(ev.graphics, Rect(0,0,scrW,scrH)); - gr.dispose(); - } - - // Fill bg. - gr.fillRectangle(this.backColor, Rect(0,0,scrW,scrH)); - - // Fill water. - int w = g.water_level(); - gr.fillRectangle(this.colors['W'], Rect(0, scrH-cell*w-1, scrW, cell*w+1)); - - // Paint map. - for(int y=1; y<=g.map.H; ++y) - for(int x=1; x<=g.map.W; ++x) { - Rect r = Rect(cell*(x-1), scrH-cell*y, cell, cell); - char c = g.map[y,x]; - if( c != ' ' ) { - if( c == 'R' && g.dead ) - c = 'D'; - gr.drawText(this.render[c], font, this.colors[c], r); - } - } - } - - void my_keydown(Control c, KeyEventArgs ev) - { - switch(ev.keyCode) - { - case Keys.DOWN: g.command('D'); break; - case Keys.UP: g.command('U'); break; - case Keys.LEFT: g.command('L'); break; - case Keys.RIGHT: g.command('R'); break; - case Keys.W: g.command('W'); break; - case Keys.A: g.command('A'); break; -// case Keys.G: solver.act(g); break; - default: break; - } - if(g.cleared) - Application.exit(); - invalidate(); - set_text(); - } - - void set_text() { - this.text = .text("Score: ", g.score, " Air: ", g.hp, " Tide: ", g.water_until_rise); - } -} - -void main(string[] args) -{ - auto g = Game.load(File(args[1])); - g.set_output(new GuardedOutput(g)); - auto myForm = new GUI(g); - Application.run(myForm); -} DELETED output.d Index: output.d ================================================================== --- output.d +++ output.d @@ -1,80 +0,0 @@ -import util; -import game; -import core.stdc.signal; -import std.c.stdlib; - -abstract class Output -{ - void command(char c); - void flush(); -} - -class NilOutput : Output -{ - override void command(char c) {} - override void flush() {} -} - -class StdOutput : Output -{ - override void command(char c) - { - write(c); - stdout.flush(); - } - override void flush() {} -} - -// TODO: clean it up. -__gshared Output g_output; - -class GuardedOutput : StdOutput -{ - // Handle SIGINT: force abort and exit. - static this() - { - signal(SIGINT, &sigint); - } - - extern(C) static void sigint(int) - { - if(g_output !is null) - g_output.flush(); - else { - write("A"); - stdout.flush(); - } - exit(0); - } - - Game g; - this(Game ini) { this.g = ini.clone(); ideal_log ~= g.score_if_abort_now; g_output = this; } - - string log; - long[] score_log; - long[] ideal_log; - - override void command(char c) - { - g.command(c); - log ~= c; - score_log ~= g.score; - ideal_log ~= g.score_if_abort_now; - } - override void flush() - { - Tuple!(long, int, int) cand; - cand[0] = long.min; - foreach(int i, long s; score_log) - if(cand[0] < s) - cand = tuple(s,i,0); - foreach(int i, long s; ideal_log) - if(cand[0] < s) - cand = tuple(s,i,1); - if(cand[2]==0) - writeln(log[0..cand[1]+1]); - else - writeln(log[0..cand[1]]~"A"); - stdout.flush(); - } -} DELETED solver.d Index: solver.d ================================================================== --- solver.d +++ solver.d @@ -1,104 +0,0 @@ -import util; -import game; -import output; - -int g_wc = 0; - -void act(Game g) -{ - Pos ro = g.map.robot; - Pos[] la = g.map.lambdas(); - Pos li = g.map.lift; - - char c = 'W'; - if( la.empty ) { - auto r = search(g, ro, li); - c = r[0]; - } else { - Tuple!(char,int)[] cand; - foreach(lam; la) - cand ~= search(g, ro, lam); - sort!((Tuple!(char,int) c1, Tuple!(char,int) c2){ - if(c1[1] != c2[1]) - return c1[1] < c2[1]; - return c1[0] < c2[0]; - })(cand); - c = cand[0][0]; - } - if(c=='W') { - g_wc++; - if(g_wc > 10) - c = 'A'; - } - else - g_wc = 0; - g.command(c); -} - -Tuple!(char,int) search(Game g, Pos s, Pos o) -{ - Pos[] q = [o]; - bool[][] v = new bool[][](g.map.H+2, g.map.W+2); - for(int step=1; q.length; ++step) { - Pos[] q2; - foreach(p; q) { - int[] dy=[-1,+1,0,0]; - int[] dx=[0,0,-1,+1]; - for(int i=0; i<4; ++i) { - int y = p.y+dy[i]; - int x = p.x+dx[i]; - if(v[y][x]) continue; - if(y==s.y && x==s.x) { - if(i==0) return tuple('U',step); - if(i==1) return tuple('D',step); - if(i==2) return tuple('R',step); - if(i==3) return tuple('L',step); - } else if(g.map[y,x]==' '||g.map[y,x]=='\\') { - q2 ~= new Pos(y,x); - v[y][x]=true; - } else if(g.map[y,x]=='.' && g.map[y-1,x]!='*') { - q2 ~= new Pos(y,x); - v[y][x]=true; - } - } - } - q = q2; - } - q = [o]; - v = new bool[][](g.map.H+2, g.map.W+2); - for(int step=1000; q.length; ++step) { - Pos[] q2; - foreach(p; q) { - int[] dy=[-1,+1,0,0]; - int[] dx=[0,0,-1,+1]; - for(int i=0; i<4; ++i) { - int y = p.y+dy[i]; - int x = p.x+dx[i]; - if(v[y][x]) continue; - if(y==s.y && x==s.x) { - if(i==0) return tuple('U',step); - if(i==1) return tuple('D',step); - if(i==2) return tuple('R',step); - if(i==3) return tuple('L',step); - } else if(g.map[y,x]==' '||g.map[y,x]=='\\') { - q2 ~= new Pos(y,x); - v[y][x]=true; - } else if(g.map[y,x]=='.'/* && g[y-1,x]!='*'*/) { - q2 ~= new Pos(y,x); - v[y][x]=true; - } - } - } - q = q2; - } - return tuple('W', int.max); -} - -void main(string[] args) -{ - auto g = Game.load(File(args[1])); - g.set_output(new GuardedOutput(g)); - - while(!g.dead && !g.cleared) - act(g); -} ADDED src/game.d Index: src/game.d ================================================================== --- src/game.d +++ src/game.d @@ -0,0 +1,382 @@ +import util; +import output; + +//////////////////////////////////////////////////////////////////////////////// + +class Pos +{ + public immutable int y, x; + mixin DeriveCreate; + mixin DeriveCompare; + mixin DeriveShow; + Pos clone() { return this; } + +@property: + Pos wait() { return this; } + 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() { return this; } + + 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) + { + return pace ? base+(number_of_update/pace) : base; + } + + int until_rise(int number_of_update) + { + 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() { return new Map(this); } + this(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; } + } + + 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); + this.output = new NilOutput; + } + + Game clone() { return new Game(this); } + this(Game g) { + map = g.map.clone(); + water = g.water.clone(); + output = new NilOutput; + turn = g.turn; + dead = g.dead; + lambda = g.lambda; + exit_bonus = g.exit_bonus; + under_water = g.under_water; + } + + void set_output(Output o) { this.output = (o is null ? new NilOutput : o); } + + void command(char c) + { + if(dead || cleared) + return; + scope(exit) { + if(dead || cleared) + output.flush(); + } + this.output.command(c); + + 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; + Output output; + 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 { + 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"]); +} ADDED src/gui.d Index: src/gui.d ================================================================== --- src/gui.d +++ src/gui.d @@ -0,0 +1,119 @@ +import dfl.all; +import util; +import game; +import output; +//import solver; + +class GUI : Form +{ + private { + Game g; + int cell; + int turn = 0; + + Font font; + Color[char] colors; + string[char] render; + } + + this(Game g) + { + noMessageFilter(); + this.setStyle(ControlStyles.OPAQUE, true); + this.g = g; + + this.paint ~= &my_paint; + this.keyDown ~= &my_keydown; + + this.formBorderStyle = FormBorderStyle.FIXED_DIALOG; + this.maximizeBox = false; + this.minimizeBox = false; + this.cell = min(1024/g.map.W, 640/g.map.H); + this.clientSize = Size(g.map.W*cell, g.map.H*cell); + set_text(); + + // Resources + this.font = new Font("MS Gothic", cell-2, GraphicsUnit.PIXEL); + this.backColor = Color(255,255,255); + this.colors['#'] = + this.colors['.'] = Color(255,191,127); + this.colors['*'] = Color(255,127,127); + this.colors['R'] = Color(128,128,0); + this.colors['D'] = Color(255,0,0); // Dead + this.colors['\\'] = + this.colors['L'] = + this.colors['O'] = Color(127,255,127); + this.colors['W'] = Color(204,229,255); // water + + this.render['#'] = "■"; + this.render['*'] = "✹"; + this.render['.'] = "♒"; + this.render['\\'] = "λ"; + this.render['R'] = "☃"; + this.render['D'] = "☠"; + this.render['L'] = "☒"; + this.render['O'] = "☐"; + } + +private: + void my_paint(Control, PaintEventArgs ev) + { + const scrH = this.clientSize.height; + const scrW = this.clientSize.width; + Graphics gr = new MemoryGraphics(scrW, scrH, ev.graphics); + scope(exit) { + gr.copyTo(ev.graphics, Rect(0,0,scrW,scrH)); + gr.dispose(); + } + + // Fill bg. + gr.fillRectangle(this.backColor, Rect(0,0,scrW,scrH)); + + // Fill water. + int w = g.water_level(); + gr.fillRectangle(this.colors['W'], Rect(0, scrH-cell*w-1, scrW, cell*w+1)); + + // Paint map. + for(int y=1; y<=g.map.H; ++y) + for(int x=1; x<=g.map.W; ++x) { + Rect r = Rect(cell*(x-1), scrH-cell*y, cell, cell); + char c = g.map[y,x]; + if( c != ' ' ) { + if( c == 'R' && g.dead ) + c = 'D'; + gr.drawText(this.render[c], font, this.colors[c], r); + } + } + } + + void my_keydown(Control c, KeyEventArgs ev) + { + switch(ev.keyCode) + { + case Keys.DOWN: g.command('D'); break; + case Keys.UP: g.command('U'); break; + case Keys.LEFT: g.command('L'); break; + case Keys.RIGHT: g.command('R'); break; + case Keys.W: g.command('W'); break; + case Keys.A: g.command('A'); break; +// case Keys.G: solver.act(g); break; + default: break; + } + if(g.cleared) + Application.exit(); + invalidate(); + set_text(); + } + + void set_text() { + this.text = .text("Score: ", g.score, " Air: ", g.hp, " Tide: ", g.water_until_rise); + } +} + +void main(string[] args) +{ + auto g = Game.load(File(args[1])); + g.set_output(new GuardedOutput(g)); + auto myForm = new GUI(g); + Application.run(myForm); +} ADDED src/output.d Index: src/output.d ================================================================== --- src/output.d +++ src/output.d @@ -0,0 +1,80 @@ +import util; +import game; +import core.stdc.signal; +import std.c.stdlib; + +abstract class Output +{ + void command(char c); + void flush(); +} + +class NilOutput : Output +{ + override void command(char c) {} + override void flush() {} +} + +class StdOutput : Output +{ + override void command(char c) + { + write(c); + stdout.flush(); + } + override void flush() {} +} + +// TODO: clean it up. +__gshared Output g_output; + +class GuardedOutput : StdOutput +{ + // Handle SIGINT: force abort and exit. + static this() + { + signal(SIGINT, &sigint); + } + + extern(C) static void sigint(int) + { + if(g_output !is null) + g_output.flush(); + else { + write("A"); + stdout.flush(); + } + exit(0); + } + + Game g; + this(Game ini) { this.g = ini.clone(); ideal_log ~= g.score_if_abort_now; g_output = this; } + + string log; + long[] score_log; + long[] ideal_log; + + override void command(char c) + { + g.command(c); + log ~= c; + score_log ~= g.score; + ideal_log ~= g.score_if_abort_now; + } + override void flush() + { + Tuple!(long, int, int) cand; + cand[0] = long.min; + foreach(int i, long s; score_log) + if(cand[0] < s) + cand = tuple(s,i,0); + foreach(int i, long s; ideal_log) + if(cand[0] < s) + cand = tuple(s,i,1); + if(cand[2]==0) + writeln(log[0..cand[1]+1]); + else + writeln(log[0..cand[1]]~"A"); + stdout.flush(); + } +} ADDED src/solver.d Index: src/solver.d ================================================================== --- src/solver.d +++ src/solver.d @@ -0,0 +1,104 @@ +import util; +import game; +import output; + +int g_wc = 0; + +void act(Game g) +{ + Pos ro = g.map.robot; + Pos[] la = g.map.lambdas(); + Pos li = g.map.lift; + + char c = 'W'; + if( la.empty ) { + auto r = search(g, ro, li); + c = r[0]; + } else { + Tuple!(char,int)[] cand; + foreach(lam; la) + cand ~= search(g, ro, lam); + sort!((Tuple!(char,int) c1, Tuple!(char,int) c2){ + if(c1[1] != c2[1]) + return c1[1] < c2[1]; + return c1[0] < c2[0]; + })(cand); + c = cand[0][0]; + } + if(c=='W') { + g_wc++; + if(g_wc > 10) + c = 'A'; + } + else + g_wc = 0; + g.command(c); +} + +Tuple!(char,int) search(Game g, Pos s, Pos o) +{ + Pos[] q = [o]; + bool[][] v = new bool[][](g.map.H+2, g.map.W+2); + for(int step=1; q.length; ++step) { + Pos[] q2; + foreach(p; q) { + int[] dy=[-1,+1,0,0]; + int[] dx=[0,0,-1,+1]; + for(int i=0; i<4; ++i) { + int y = p.y+dy[i]; + int x = p.x+dx[i]; + if(v[y][x]) continue; + if(y==s.y && x==s.x) { + if(i==0) return tuple('U',step); + if(i==1) return tuple('D',step); + if(i==2) return tuple('R',step); + if(i==3) return tuple('L',step); + } else if(g.map[y,x]==' '||g.map[y,x]=='\\') { + q2 ~= new Pos(y,x); + v[y][x]=true; + } else if(g.map[y,x]=='.' && g.map[y-1,x]!='*') { + q2 ~= new Pos(y,x); + v[y][x]=true; + } + } + } + q = q2; + } + q = [o]; + v = new bool[][](g.map.H+2, g.map.W+2); + for(int step=1000; q.length; ++step) { + Pos[] q2; + foreach(p; q) { + int[] dy=[-1,+1,0,0]; + int[] dx=[0,0,-1,+1]; + for(int i=0; i<4; ++i) { + int y = p.y+dy[i]; + int x = p.x+dx[i]; + if(v[y][x]) continue; + if(y==s.y && x==s.x) { + if(i==0) return tuple('U',step); + if(i==1) return tuple('D',step); + if(i==2) return tuple('R',step); + if(i==3) return tuple('L',step); + } else if(g.map[y,x]==' '||g.map[y,x]=='\\') { + q2 ~= new Pos(y,x); + v[y][x]=true; + } else if(g.map[y,x]=='.'/* && g[y-1,x]!='*'*/) { + q2 ~= new Pos(y,x); + v[y][x]=true; + } + } + } + q = q2; + } + return tuple('W', int.max); +} + +void main(string[] args) +{ + auto g = Game.load(stdin); + g.set_output(new GuardedOutput(g)); + + while(!g.dead && !g.cleared) + act(g); +} ADDED src/test.d Index: src/test.d ================================================================== --- src/test.d +++ src/test.d @@ -0,0 +1,395 @@ +import std.algorithm; +import std.array; +import std.conv; +import std.stdio; +import std.string; +import std.typecons; +import core.stdc.signal; +import core.stdc.stdlib; +import dfl.all; + +class Map +{ + private char[][] data; + bool dead = false; + bool cleared = false; + int water = 0; + int flooding = 0; + int water_proof = 10; + int underwater = 0; + int flooding_counter = 0; + + this(File input) + { + string line; + while( (line=input.readln().chomp()).length ) + data ~= line.dup; + + int width = 0; + foreach(s; data) + width = max(width, s.length); + + // space padding and sentinels + foreach(ref s; data) { + int p = s.length; + s.length = width; + s[p..$] = ' '; + s = '#' ~ s ~ '#'; + } + + // vertical sentinel + char[] sen = new char[width+2]; + sen[] = '#'; + data = sen.dup ~ data ~ sen; + + // flooding + water = H-1; + while( (line=input.readln()).length ) { + string[] ss = line.split(); + if(ss.length==2 && ss[0]=="Water") + water = H-1 - ss[1].to!int(); + else if(ss.length==2 && ss[0]=="Flooding") + flooding = ss[1].to!int(); + else if(ss.length==2 && ss[0]=="Waterproof") + water_proof = ss[1].to!int(); + } + } + + @property const + { + int W() { return data[0].length; } + int H() { return data.length; } + string toString() { + string result; + foreach(i,s; data) { + if(i) result ~= '\n'; + result ~= s.idup; + } + return result; + } + } + + int command_R() { if(dead)return 0; write("R"); return move(0, +1); } + int command_L() { if(dead)return 0; write("L"); return move(0, -1); } + int command_U() { if(dead)return 0; write("U"); return move(-1, 0); } + int command_D() { if(dead)return 0; write("D"); return move(+1, 0); } + int wait() { if(dead)return 0; update(); write("W"); return -1; } + int abort() { if(dead)return 0; cleared=true; write("A"); return gained*25; } + + int move(int dy, int dx) { + foreach(y,s; data) + foreach(x,c; s) + if(c == 'R') + return move(dy, dx, y, x); + assert(false); + } + + int gained = 0; // TODO: atode naosu + int move(int dy, int dx, int y, int x) { + if(dead) + return 0; + int score = 0; + if(data[y+dy][x+dx]=='\\') { + score += 25; + ++gained; + } + if(data[y+dy][x+dx]=='O') { + score += gained*50; + cleared = true; + } + + if(data[y+dy][x+dx]==' ' || data[y+dy][x+dx]=='.' + || data[y+dy][x+dx]=='\\' || data[y+dy][x+dx]=='O') { + data[y][x]=' '; + data[y+dy][x+dx]='R'; + } else if(dy==0 && data[y+dy][x+dx]=='*' && data[y+2*dy][x+2*dx]==' ') { + data[y][x]=' '; + data[y+dy][x+dx]='R'; + data[y+2*dy][x+2*dx]='*'; + } + update(); + return score-1; + } + + void update() { + char[][] next; + foreach(y,s; data) + next ~= s.dup; + + bool lambda = false; + for(int y=1; y+1=1; --y) + for(int x=1; x+1 water_proof) + dead = true; + } + flooding_counter ++; + if(flooding_counter == flooding) { + flooding_counter = 0; + water --; + } + } + } + + int clever() + { + if(dead) + return 0; + int sy,sx; + int[] ly,lx; + int oy,ox; + for(int y=0; y=1; --y) - for(int x=1; x+1 water_proof) - dead = true; - } - flooding_counter ++; - if(flooding_counter == flooding) { - flooding_counter = 0; - water --; - } - } - } - - int clever() - { - if(dead) - return 0; - int sy,sx; - int[] ly,lx; - int oy,ox; - for(int y=0; y