ADDED   src/cui_auto_main.d
Index: src/cui_auto_main.d
==================================================================
--- src/cui_auto_main.d
+++ src/cui_auto_main.d
@@ -0,0 +1,13 @@
+import util;
+import game;
+import output;
+import driver;
+import solver;
+
+void main(string[] args)
+{
+	Driver d = new Driver(stdin);
+	d.addObserver!(GuardedOutput)();
+	Solver s = d.addObserver!(Solver)();
+	s.run(&d.command);
+}

Index: src/game.d
==================================================================
--- src/game.d
+++ src/game.d
@@ -167,20 +167,20 @@
 	void opIndexAssign(char c, Pos p)
 	{
 		this[p.y, p.x] = c;
 	}
 
-	Pos[] lambdas() {
+	Pos[] lambdas() const {
 		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()
+	bool cleared() const
 	{
 		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;

Index: src/gui.d
==================================================================
--- src/gui.d
+++ src/gui.d
@@ -1,112 +1,113 @@
 import dfl.all;
 import util;
 import game;
-import output;
 import driver;
-//import solver;
-pragma(lib, "dfl.lib");
 
-class GUI : Form, GameObserver
+class GUI(Solver) : Form, GameObserver
 {
-	bool on_game_changed(char c, const(Game) g, bool finished) {
-		draw(gr, g);
-		invalidate();
-		return false;
+	this(const(Game) g)
+	{
+		this.solver = new Solver(g);
+		setup_size(g.map.W, g.map.H);
+		setup_resources();
+		setup_keyhandling();
+		draw(g);
+	}
+
+	private void delegate(char c) fn;
+	void set_fn(F)(F f) { this.fn = f; }
+
+	void run()
+	{
+		Application.run(this);
 	}
 
-	private {
-		int cell;
-		int turn = 0;
-
-		Font font;
-		Color[char] colors;
-		string[char] render;
-		void delegate(char c) fn;
+	override void on_game_changed(char c, const(Game) g, bool finished)
+	{
+		draw(g);
 	}
 
-	this(const(Game) g)
-	{
-		noMessageFilter();
-		this.setStyle(ControlStyles.OPAQUE, true);
-		this.fn = fn;
+private:
+	int cell;
 
-		this.paint ~= &my_paint;
-		this.keyDown ~= &my_keydown;
-
+	void setup_size(int W, int H)
+	{
 		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);
+		this.cell = min(1024/W, 640/H);
+		this.clientSize = Size(W*cell, H*cell);
+	}
 
-		const scrH = this.clientSize.height;
-		const scrW = this.clientSize.width;
-		this.gr = new MemoryGraphics(scrW, scrH);
+	Font         font;
+	Color[char]  colors;
+	string[char] render;
+	Graphics     graphicContext;
 
-		// Resources
+	void setup_resources()
+	{
+		this.graphicContext = new MemoryGraphics(this.clientSize.width, this.clientSize.height);
+		this.setStyle(ControlStyles.OPAQUE, true);
 		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['D'] = Color(255,0,0);
 		this.colors['\\'] =
 		this.colors['L'] =
 		this.colors['O'] = Color(127,255,127);
-		this.colors['W'] = Color(204,229,255); // water
-
+		this.colors['W'] = Color(204,229,255);
 		this.render['#'] = "■";
 		this.render['*'] = "✹";
 		this.render['.'] = "♒";
 		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);
+		this.paint ~= (Control c, PaintEventArgs ev) {
+			graphicContext.copyTo(ev.graphics, Rect(0,0,this.clientSize.width,this.clientSize.height));
+		};
 	}
 
-private:
-	Graphics gr;
-
-	void my_paint(Control, PaintEventArgs ev)
-	{
-		gr.copyTo(ev.graphics, Rect(0,0,this.clientSize.width,this.clientSize.height));
-	}
-
-	void draw(Graphics gr, const(Game) g)
+	void draw(const(Game) g)
 	{
 		int scrW = this.clientSize.width;
 		int scrH = this.clientSize.height;
+
 		// Fill bg.
-		gr.fillRectangle(this.backColor, Rect(0,0,scrW,scrH));
+		graphicContext.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));
+		graphicContext.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);
+				graphicContext.drawText(this.render[c], font, this.colors[c], r);
 			}
 		}
 
-		set_text(g);
+		// Update textual info.
+		this.text = .text("Score: ", g.score, " Air: ", g.hp, " Tide: ", g.water_until_rise);
+		invalidate();
+	}
+
+private:
+	void setup_keyhandling()
+	{
+		noMessageFilter();
+		this.keyDown ~= &my_keydown;
 	}
 
 	void my_keydown(Control c, KeyEventArgs ev)
 	{
 		switch(ev.keyCode)
@@ -115,22 +116,12 @@
 		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;
+		case Keys.G:     fn(solver.single_step()); break;
 		default:         break;
 		}
 	}
 
-	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 d = new Driver(File(args[1]));
-	d.addObserver!(GuardedOutput)();
-	GUI g = d.addObserver!(GUI)();
-	g.set_fn(&d.command);
-	g.run();
+	Solver solver;
 }

ADDED   src/gui_main.d
Index: src/gui_main.d
==================================================================
--- src/gui_main.d
+++ src/gui_main.d
@@ -0,0 +1,15 @@
+import gui;
+import output;
+import driver;
+import solver;
+import std.stdio;
+pragma(lib, "dfl.lib");
+
+void main(string[] args)
+{
+	Driver d = new Driver(stdin);
+	d.addObserver!(GuardedOutput)();
+	auto g = d.addObserver!(GUI!Solver_1)();
+	g.set_fn(&d.command);
+	g.run();
+}

Index: src/output.d
==================================================================
--- src/output.d
+++ src/output.d
@@ -1,25 +1,23 @@
 import util;
 import game;
 import driver;
-import std.c.stdlib;
 import core.stdc.signal;
 
 class NilOutput : GameObserver
 {
 	this(const(Game) g) {}
-	override bool on_game_changed(char c, const(Game) g, bool finished) {return false;}
+	override void on_game_changed(char c, const(Game) g, bool finished) {}
 }
 
 class StdOutput : GameObserver
 {
 	this(const(Game) g) {}
-	override bool on_game_changed(char c, const(Game) g, bool finished)
+	override void on_game_changed(char c, const(Game) g, bool finished)
 	{
 		stdout.write(c);
 		stdout.flush();
-		return false;
 	}
 }
 
 class GuardedOutput : GameObserver
 {
@@ -27,18 +25,17 @@
 	{
 		setup_sigint_handling();
 		ideal_log ~= g.score_if_abort_now;
 	}
 
-	override bool on_game_changed(char c, const(Game) g, bool finished)
+	override void on_game_changed(char c, const(Game) g, bool finished)
 	{
 		log ~= c;
 		score_log ~= g.score;
 		ideal_log ~= g.score_if_abort_now;
 		if(finished)
 			flush();
-		return false;
 	}
 
 private:
 	string log;
 	long[] score_log;

Index: src/solver.d
==================================================================
--- src/solver.d
+++ src/solver.d
@@ -1,104 +1,125 @@
 import util;
 import game;
-import output;
+import driver;
 
-int g_wc = 0;
-
-void act(Game g)
+/*
+interface Solver
 {
-	Pos   ro = g.map.robot;
-	Pos[] la = g.map.lambdas();
-	Pos   li = g.map.lift;
+	this(const(Game) g);
+	char single_step();
+}
+*/
 
-	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);
+class Solver_0
+{
+	this(const(Game) g) {}
+	char single_step() { return 'W'; }
 }
 
-Tuple!(char,int) search(Game g, Pos s, Pos o)
+class Solver_1
 {
-	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;
-				}
-			}
+	int g_wc = 0;
+
+	Game g;
+	this(const(Game) g)
+	{
+		this.g = g.clone();
+	}
+
+	char single_step()
+	{
+		char c = act(g);
+		g.command(c);
+		return c;
+	}
+
+	char act(const(Game) g)
+	{
+		const Pos   ro = g.map.robot;
+		const Pos[] la = g.map.lambdas();
+		const 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';
 		}
-		q = q2;
+		else
+			g_wc = 0;
+		return c;
 	}
-	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;
+
+	Tuple!(char,int) search(in Game g, in Pos s, in Pos o)
+	{
+		const(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;
 		}
-		q = q2;
+		return tuple('W', int.max);
 	}
-	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);
 }