Index: src/game.d ================================================================== --- src/game.d +++ src/game.d @@ -1,20 +1,19 @@ import util; -import output; //////////////////////////////////////////////////////////////////////////////// class Pos { public immutable int y, x; mixin DeriveCreate; mixin DeriveCompare; mixin DeriveShow; - Pos clone() { return this; } + Pos clone() const { return new Pos(y, x); } @property: - Pos wait() { return this; } + 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; @@ -44,26 +43,26 @@ { public immutable int base, pace; mixin DeriveCreate; mixin DeriveCompare; mixin DeriveShow; - Water clone() { return this; } + 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) + int level(int number_of_update) const { return pace ? base+(number_of_update/pace) : base; } - int until_rise(int number_of_update) + int until_rise(int number_of_update) const { return pace ? pace-number_of_update%pace : int.max; } } @@ -102,12 +101,12 @@ char[][] data; Pos robot; Pos lift; int waterproof; - Map clone() { return new Map(this); } - this(Map m) { + 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; @@ -138,22 +137,24 @@ 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]; - } + 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]; + 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). @@ -300,36 +301,27 @@ 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) { + Game clone() const { return new Game(this); } + this(const(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; @@ -355,20 +347,19 @@ 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 { + @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; } Index: src/gui.d ================================================================== --- src/gui.d +++ src/gui.d @@ -1,38 +1,49 @@ import dfl.all; import util; import game; import output; +import driver; //import solver; +pragma(lib, "dfl.lib"); -class GUI : Form +class GUI : Form, GameObserver { + bool on_game_changed(char c, const(Game) g, bool finished) { + draw(gr, g); + invalidate(); + return false; + } + private { - Game g; int cell; int turn = 0; Font font; Color[char] colors; string[char] render; + void delegate(char c) fn; } - this(Game g) + this(const(Game) g) { noMessageFilter(); this.setStyle(ControlStyles.OPAQUE, true); - this.g = g; + this.fn = fn; 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(); + + const scrH = this.clientSize.height; + const scrW = this.clientSize.width; + this.gr = new MemoryGraphics(scrW, scrH); // Resources this.font = new Font("MS Gothic", cell-2, GraphicsUnit.PIXEL); this.backColor = Color(255,255,255); this.colors['#'] = @@ -51,23 +62,31 @@ this.render['\\'] = "λ"; this.render['R'] = "☃"; this.render['D'] = "☠"; this.render['L'] = "☒"; this.render['O'] = "☐"; + draw(gr, g); + } + + void set_fn(F)(F f) { this.fn = f; } + + void run() { + Application.run(this); } private: + Graphics gr; + 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(); - } + gr.copyTo(ev.graphics, Rect(0,0,this.clientSize.width,this.clientSize.height)); + } + void draw(Graphics gr, const(Game) g) + { + int scrW = this.clientSize.width; + int scrH = this.clientSize.height; // Fill bg. gr.fillRectangle(this.backColor, Rect(0,0,scrW,scrH)); // Fill water. int w = g.water_level(); @@ -82,38 +101,36 @@ if( c == 'R' && g.dead ) c = 'D'; gr.drawText(this.render[c], font, this.colors[c], r); } } + + set_text(g); } 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; + case Keys.DOWN: fn('D'); break; + case Keys.UP: fn('U'); break; + case Keys.LEFT: fn('L'); break; + case Keys.RIGHT: fn('R'); break; + case Keys.W: fn('W'); break; + case Keys.A: fn('A'); break; default: break; } - if(g.cleared) - Application.exit(); - invalidate(); - set_text(); } - void set_text() { + void set_text(const(Game) g) { 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); + auto d = new Driver(File(args[1])); + d.addObserver!(GuardedOutput)(); + GUI g = d.addObserver!(GUI)(); + g.set_fn(&d.command); + g.run(); } Index: src/output.d ================================================================== --- src/output.d +++ src/output.d @@ -1,80 +1,79 @@ import util; import game; -import core.stdc.signal; +import driver; import std.c.stdlib; +import core.stdc.signal; -abstract class Output +class NilOutput : GameObserver { - void command(char c); - void flush(); + this(const(Game) g) {} + override bool on_game_changed(char c, const(Game) g, bool finished) {return false;} } -class NilOutput : Output +class StdOutput : GameObserver { - override void command(char c) {} - override void flush() {} -} - -class StdOutput : Output -{ - override void command(char c) + this(const(Game) g) {} + override bool on_game_changed(char c, const(Game) g, bool finished) { - write(c); + stdout.write(c); stdout.flush(); + return false; } - override void flush() {} } -// TODO: clean it up. -__gshared Output g_output; - -class GuardedOutput : StdOutput +class GuardedOutput : GameObserver { - // Handle SIGINT: force abort and exit. - static this() + this(const(Game) g) + { + setup_sigint_handling(); + ideal_log ~= g.score_if_abort_now; + } + + override bool on_game_changed(char c, const(Game) g, bool finished) { - signal(SIGINT, &sigint); + log ~= c; + score_log ~= g.score; + ideal_log ~= g.score_if_abort_now; + if(finished) + flush(); + return false; } - 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; } - +private: 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() + 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(); + + for(int i=0; i