Almost done

October 10, 2009

I’ve completed cards #13-16 and my little app is looking good.  There are only a few more things to do so a few more cards:

  • Card #17 – Fix icons for tab buttons
  • Card #18 – Figure out how/where to host source and link here

Here are the latest screenshots:
StopWatch Game Screen OneStopWatch Game Screen Two

You will notice my icons on the bottom are cut off by where the text should be.  I need to make smaller icons (figure out the size) and then fill in the button text.
Notes on the project:

  1. I hooked everything up through the app delegate class, even though I had two views to work with, this isn’t a good pattern to follow for a real app.
  2. The score saving/loading is not very robust, any damage to the score file and the app is toast.
  3. Sandwich logging, I’d say is sufficient for this type of application as it allows you to quickly isolate which method fatal errors occur in and go from there.  A real app will have better logging functionality esp. given that monotouch does not support a visual ide level debugger currently.
  4. WordPress strips the xml out of my posted code.  Need to fix this.  The xml is HighScores on the root, and each sub node is HighScoreOne and HighScoreTwo with the values between.

Here is the source for your perusal.  I could use to do some refactoring but this whole exercise was a spike in basic functionality and general how does it worky for MonoTouch.  So I will probably leave it as is and move on to some more interesting problems and definitely a whole new twist on this project.

	// The name AppDelegate is referenced in the MainWindow.xib file.
	public partial class AppDelegate : UIApplicationDelegate
	{
		bool isRunning = false;
		DateTime start, end = new DateTime();
		TimeSpan ts = new TimeSpan(0,0,0,0,0);
		TimeSpan hs = new TimeSpan(0,0,1);
		TimeSpan hs2 = new TimeSpan(0,0,1);
		private static Selector selector = new Selector("UpdateTimer");
		private static string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
		private static string filePath = Path.Combine(path, "highscore.xml");

		// This method is invoked when the application has loaded its UI and its ready to run
		public override bool FinishedLaunching (UIApplication app, NSDictionary options)
		{
			// If you have defined a view, add it here:
			window.AddSubview (tabBarController.View);

			buttonStartOne.TouchDown += ButtonStartOnePushed;
			buttonStartTwo.TouchDown += ButtonStartTwoPushed;
			buttonEndTwo.TouchDown += ButtonEndTwoPushed;

			InitHighScore();

			// Create timer
			// notes: the timer starts immediately
			//        selector is the delegate we want to run when the timer fires
			NSTimer.CreateScheduledTimer(0.01, this, selector, null, true);

			window.MakeKeyAndVisible ();

			return true;
		}

		public void InitHighScore()
		{
			Console.WriteLine("InitHighScore() Enter");
			if (File.Exists(filePath))
			{
				string hsXML = File.ReadAllText(filePath);
				XmlDocument doc = new XmlDocument();
				doc.LoadXml(hsXML);

				hs = GetScoresFromXML(doc, "/HighScores/HighScoreOne");
				hs2 = GetScoresFromXML(doc, "/HighScores/HighScoreTwo");

				// highScoreOne.Text = hsText;
			}

			highScoreOne.Text = string.Format("{0}:{1}", hs.Seconds, hs.Milliseconds);
			highScoreTwo.Text = string.Format("{0}:{1}", hs2.Seconds, hs2.Milliseconds);
			Console.WriteLine("HighScore: {0}", highScoreOne.Text);
			Console.WriteLine("HighScore2: {0}", highScoreTwo.Text);
			Console.WriteLine("InitHighScore() Exit");
		}

		public TimeSpan GetScoresFromXML(XmlDocument doc, string node)
		{
			Console.WriteLine("GetScoresFromXML() Enter");
			TimeSpan ts;
			string hsText = doc.SelectSingleNode(node).InnerText;
			string[] ret = hsText.Split(':');
			Console.WriteLine("Ref?: {0}, {1}:{2}", hsText, ret[0], ret[1]);
			ts = new TimeSpan(0,0,0,Convert.ToInt32(ret[0]),Convert.ToInt32(ret[1]));
			Console.WriteLine("Got High Score: {0}:{1}", ts.Seconds, ts.Milliseconds);
			return ts;
		}

		[Export ("UpdateTimer")]
		public void UpdateTimer()
		{
			string time;
			DateTime increment;

			if (isRunning)
			{
				increment = DateTime.Now;
				ts = increment.Subtract(start);
				time = string.Format("{0}:{1}", ts.Seconds, ts.Milliseconds);
				displayOne.Text = time;
				displayTwo.Text = time;
			}
		}

		public void UpdateScoreFile()
		{
			Console.WriteLine("UpdateScoreFile() Enter");
			if (!File.Exists(filePath))
			{
				Console.WriteLine("Creating new highscore file");
				StreamWriter sw = File.CreateText(filePath);
				sw.Close();
			}

			string hsXML = string.Format("" +
				"&ltHighScoreOne&gt{0}:{1}&lt/HighScoreOne&gt" +
			    "{2}:{3}" +
				"", hs.Seconds, hs.Milliseconds,
			                     hs2.Seconds, hs2.Milliseconds);
			File.WriteAllText(filePath, hsXML);
			Console.WriteLine(File.ReadAllText(filePath));
			Console.WriteLine("UpdateScoreFile() Exit");
		}

		public void ButtonStartOnePushed(object sender, EventArgs e)
		{
			Console.WriteLine("ButtonStartOnePushed() Enter");
			if (isRunning)
			{
				end = DateTime.Now;
				isRunning = false;
				buttonStartOne.SetTitle("Start", UIControlState.Normal);
				ts = end.Subtract(start);
				displayOne.Text = string.Format("{0}:{1}", ts.Seconds, ts.Milliseconds);
				if (ts < hs) {
					hs = ts;
					highScoreOne.Text = string.Format("{0}:{1}", hs.Seconds, hs.Milliseconds);
					UpdateScoreFile();
					AnnounceWinner();
				}
			}
			else
			{
				start = DateTime.Now;
				isRunning = true;
				buttonStartOne.SetTitle("Stop", UIControlState.Normal);
			}
			Console.WriteLine("ButtonStartOnePushed() Exit");
		}

		public void ButtonStartTwoPushed(object sender, EventArgs e)
		{
			Console.WriteLine("ButtonStartTwoPushed() Enter");
			if (!isRunning)
			{
				start = DateTime.Now;
				isRunning = true;
			}
			Console.WriteLine("ButtonStartTwoPushed() Exit");
		}

		public void AnnounceWinner()
		{
			SystemSound sound = SystemSound.FromFile(new NSUrl("sounds/winner.wav"));
			sound.PlaySystemSound();
		}

		public void ButtonEndTwoPushed(object sender, EventArgs e)
		{
			Console.WriteLine("ButtonEndTwoPushed() Enter");
			if (isRunning)
			{
				end = DateTime.Now;
				isRunning = false;
				ts = end.Subtract(start);
				displayTwo.Text = string.Format("{0}:{1}", ts.Seconds, ts.Milliseconds);
				if (ts < hs2) {
					hs2 = ts;
					highScoreTwo.Text = string.Format("{0}:{1}", hs2.Seconds, hs2.Milliseconds);
					UpdateScoreFile();
					AnnounceWinner();
				}
			}
			Console.WriteLine("ButtonEndTwoPushed() Exit");
		}

		// This method is required in iPhoneOS 3.0
		public override void OnActivated (UIApplication application)
		{
		}
	}

Uh-oh

October 7, 2009

Well I managed to do something bad.  I’m not sure what it was, all I did was add a resource directory and then added 3 image files to it and bam!  My app won’t run at all.  I get no output in the console for tracing and just a black screen.  I tried deleting the files and removing the directory but that did not help.  I’m going to have to see if I can repro this on a consistent basis.  Hopefully I can’t…

This is why we have source control which I did not have installed at the time.  I do now!  I picked up p4 since it is free for two users which is one more than I need currently.  Got it running and checked in my files so this sort of thing should not happen again.  I didn’t lose any source, just the work in Interface Builder which was considerable.  I know what I did there though and will be able to recreate the app quickly enough (tonite is the goal).  More on the progress of this later.

Update: It looks like MonoDevelop or the iPhone simulator doesn’t like me naming the folder I want to put my assets in as ‘Resources’.  If I name it that, I get a black screen every time.  I changed the name to ‘images’ and reset the device settings and all is well.  Yay!  I will post the latest source this weekend.

A high score

October 3, 2009

Persisting the high score turned out to be pretty easy. First I had to set up the variable that stores it at runtime. After that it was a matter of hooking that value up to the label and then updating it each time a better score was reached. I had to do two things next, create a new highscore.xml file to store the score, then write the score to it. Next I made a check to see if the file existed at load time, if so load it and read the contents. Once again the complete source is posted below. Pretty soon I’ll figure out how to upload solutions and then you can try it out for yourself. The source is getting a little long now so I’ll probably start breaking it up and posting snippets instead of most of the main.cs file.

public partial class AppDelegate : UIApplicationDelegate
	{
		bool isRunning = false;
		DateTime start, end = new DateTime();
		TimeSpan ts = new TimeSpan(0,0,0,0,0);
		TimeSpan hs = new TimeSpan(0,0,1);
		private static Selector selector = new Selector("UpdateTimer");
		private static string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
		private static string filePath = Path.Combine(path, "highscore.xml");

		// This method is invoked when the application has loaded its UI and its ready to run
		public override bool FinishedLaunching (UIApplication app, NSDictionary options)
		{
			// If you have defined a view, add it here:
			// window.AddSubview (navigationController.View);

			button.TouchDown += ButtonPushed;

			InitHighScore();

			// Create timer
			// notes: the timer starts immediately
			//        selector is the delegate we want to run when the timer fires
			NSTimer.CreateScheduledTimer(0.01, this, selector, null, true);

			window.MakeKeyAndVisible ();

			return true;
		}

		public void InitHighScore()
		{
			Console.WriteLine("InitHighScore() Enter");
			if (File.Exists(filePath))
			{
				string hsXML = File.ReadAllText(filePath);
				XmlDocument doc = new XmlDocument();
				doc.LoadXml(hsXML);
				string hsText = doc.SelectSingleNode("/HighScore").InnerText;
				highScore.Text = hsText;
			}
			else
			{
				highScore.Text = string.Format("{0}:{1}", hs.Seconds, hs.Milliseconds);
			}
			Console.WriteLine("InitHighScore() Exit");
		}

		[Export ("UpdateTimer")]
		public void UpdateTimer()
		{
			string time;
			DateTime increment;

			if (isRunning)
			{
				increment = DateTime.Now;
				ts = increment.Subtract(start);
				time = string.Format("{0}:{1}", ts.Seconds, ts.Milliseconds);
				display.Text = time;
			}
		}

		public void UpdateScoreFile()
		{
			Console.WriteLine("UpdateScoreFile() Enter");
			if (!File.Exists(filePath))
			{
				Console.WriteLine("Creating new highscore file");
				StreamWriter sw = File.CreateText(filePath);
				sw.Close();
			}

			string hsXML = string.Format("{0}:{1}", hs.Seconds, hs.Milliseconds);
			File.WriteAllText(filePath, hsXML);
			Console.WriteLine(File.ReadAllText(filePath));
			Console.WriteLine("UpdateScoreFile() Exit");
		}

		public void ButtonPushed(object sender, EventArgs e)
		{
			Console.WriteLine("ButtonPushed() Enter");
			if (isRunning)
			{
				end = DateTime.Now;
				isRunning = false;
				button.SetTitle("Start", UIControlState.Normal);
				ts = end.Subtract(start);
				display.Text = string.Format("{0}:{1}", ts.Seconds, ts.Milliseconds);
				if (ts < hs) {
					hs = ts;
					highScore.Text = string.Format("{0}:{1}", hs.Seconds, hs.Milliseconds);
					UpdateScoreFile();
				}
			}
			else
			{
				start = DateTime.Now;
				isRunning = true;
				button.SetTitle("Stop", UIControlState.Normal);
			}
			Console.WriteLine("ButtonPushed() Exit");
		}

		// This method is required in iPhoneOS 3.0
		public override void OnActivated (UIApplication application)
		{
		}
	}
}

And here is the first ginormous screenshot of the StopWatch game:
First screenshot of StopWatch game

I’ll figure out how to scale them better later.

A running stopwatch

October 2, 2009

Nothing is ever as simple as it seems.  I figured I would be able to create a Timer, hook into an event off of that timer and do some work i.e. display the time on the screen.  Boy howdy did that not turn out to be the case.  I had to learn a few things to get it working.

One important bit is about selectors.  From what I can determine, selectors are pointers to delegates.  You create a selector and give it a name of an exported method, in this way it can be passed to things like event loops or timers in our case.  Another tidbit is that if you see references to ‘self’ in samples for objective c code it maps to ‘this’ in c#.  Obvious to some but something that caused me a bit of a lightbulb moment.

Hopefully the code speaks for itself.  I haven’t refactored yet but I haven’t exactly done any TDD during this development cycle so I’m not too worried about it.  That said, if you see anything that needs improvement please let me know!

public partial class AppDelegate : UIApplicationDelegate
{
	bool isRunning = false;
	DateTime start, end = new DateTime();
	TimeSpan ts = new TimeSpan(0,0,0,0,5);
	private static Selector selector = new Selector("UpdateTimer");

	// This method is invoked when the application has loaded its UI and its ready to run
	public override bool FinishedLaunching (UIApplication app, NSDictionary options)
	{
		// If you have defined a view, add it here:
		// window.AddSubview (navigationController.View);

		button.TouchDown += ButtonPushed;
		// Create timer
		// notes: the timer starts immediately
		//        selector is the delegate we want to run when the timer fires
		NSTimer.CreateScheduledTimer(0.01, this, selector, null, true);

		window.MakeKeyAndVisible ();

		return true;
	}

	[Export ("UpdateTimer")]
	public void UpdateTimer()
	{
		string time;
		DateTime increment;

		if (isRunning)
		{
			increment = DateTime.Now;
			ts = increment.Subtract(start);
			time = string.Format("{0}:{1}", ts.Seconds, ts.Milliseconds);
			display.Text = time;
			// Console.WriteLine(time);
		}
	}

	public void ButtonPushed(object sender, EventArgs e)
	{
		if (isRunning)
		{
			end = DateTime.Now;
			isRunning = false;
			button.SetTitle("Start", UIControlState.Normal);
			ts = end.Subtract(start);
			display.Text = string.Format("{0}:{1}", ts.Seconds, ts.Milliseconds);
		}
		else
		{
			start = DateTime.Now;
			isRunning = true;
			button.SetTitle("Stop", UIControlState.Normal);
		}
	}
}

I did demo the running app to my girlfriend so my weekly demo requirement is complete. Onward to the next cards…

I was sitting at lunch today thinking about the kinds of features I would like to add to the stopwatch game.  I’m using this game as a learning / springboard tool for a more complex project so I’d like the features to be similar to ones I may use later.  This won’t be true of every little thing but I can contrive some features to take me through learning something I need to figure out.

The first thing I’d like to do is make it an actual stopwatch that counts when you hit the start button.  Currently you only get to see the end result of your button presses.  Next I want to display the current best score for the session, so you know what you need to beat.  Third I would like to add a new screen (view) that plays a slightly different game, this time you have to see how fast you can press two buttons far apart.  This last feature is most useful to me in figuring out views and becoming more comfortable with the MVC pattern.  Next I’d like to change the buttons to be images.  This will help me figure out resources and how to access and display them.  Finally, a sound effect when you beat the best score would be cool, so I’ll try to put that in too.

Sounds like a few cards to me:

  • Card #12: Add a visual timer to the screen
  • Card #13: Best score to beat
  • Card #14: New game with 2 buttons (views spike)
  • Card #15: Change buttons to be images
  • Card #16: Play a sound

It is important to note that in using agile here I am dynamically creating a backlog.  Because it is just me, I tend to break things down into tasks that are less than 5 points of effort each.  The tasks above I would consider to be 1-3 points each of effort.  This is where the shady part comes in: what is “effort” anyway?  Near as I can tell (or what works for me) is that 1 point of effort is equal to the amount of work you could get done on it in a day.  It gets really gray because two important things to note are that the amount of work you could do in a day is not equal to your peers, everyone is different.  Second, a day may in fact be an 8 hour work day but most of the time it is closer to 4 real working hours or less at night like with this project.  As long as you are comfortable with your definitions for a day and how much work you can do in said day, you will get meaningful estimates using agile.  When a task shows up that is more than 5 points, this is an indication that maybe it needs to be broken down.  Not always, but most of the time this is the case.  Scoring tasks more than 5 points in the backlog is not a bad thing.  Sometimes research needs to be done to figure out how to break down a problem later, this is ok.  It is not ok to put up cards that are more than 5 points.  The goal is to pump cards through the system, watch them move organically and quickly.  Cards should be 1 to 3 point tasks, that way a high card turnover is guaranteed which shows progress to those non technical types who desire it so.  Plus it feels good to knock them off the board. :)

OK another set of cards to work on, whee!

I started going through the Hello World tutorials again after getting thoroughly confused by Interface Builder.  I mean seriously, talk about non-intuitive!  I have to write some code, then go into a GUI to connect lines to dots, then switch back out and somehow through magic the lines made the objects that make up the interface visible in code.  I’m really scratching my head on that one.  Like any new tool there is a learning curve involved and I’ll just have to get used to it or learn how to go without IB which I understand is possible and is a good idea for a spike [investigate building ui w/o IB].

That said, I did finally get something working.  I wanted to build an easy game that I used to play with my Father called the stopwatch game.  Back when I was young I was really keen on digital watches.  Most of the watches I owned had a stopwatch feature.  By pressing a tiny button on the watch you could start and stop the time with a granularity down to the hundredths of a second.  The game was that we would tap that button as fast as we could and see who could get the lowest “score” i.e. time on the watch.

That is our user story for a new project, stopwatch game.  My only card really is to do this spike: Card #10 Try to write the stopwatch game.  The game isn’t complicated… all I need is one button to start and stop the timer, and a label to store the score (time) in.  It is not much different from ‘Hello World’ on the monotouch site so I won’t go over things step by step.

Here are the results:

namespace StopWatch
{
	public class Application
	{
		static void Main (string[] args)
		{
			UIApplication.Main (args);
		}
	}

	// The name AppDelegate is referenced in the MainWindow.xib file.
	public partial class AppDelegate : UIApplicationDelegate
	{
		bool isRunning = false;
		DateTime start, end = new DateTime();
		TimeSpan ts = new TimeSpan();

		// This method is invoked when the application has loaded its UI and its ready to run
		public override bool FinishedLaunching (UIApplication app, NSDictionary options)
		{
			// If you have defined a view, add it here:
			// window.AddSubview (navigationController.View);

			button.TouchDown += UpdateDisplay;

			window.MakeKeyAndVisible ();

			return true;
		}

		public void UpdateDisplay(object sender, EventArgs e)
		{
			if (isRunning)
			{
				end = DateTime.Now;
				isRunning = false;
				button.SetTitle("Start", UIControlState.Normal);
				ts = end.Subtract(start);
				display.Text = string.Format("{0}:{1}", ts.Seconds, ts.Milliseconds);
			}
			else
			{
				start = DateTime.Now;
				isRunning = true;
				button.SetTitle("Stop", UIControlState.Normal);
			}

		}

		// This method is required in iPhoneOS 3.0
		public override void OnActivated (UIApplication application)
		{
		}
	}
}

At some point I will figure out how to package and upload/link the solution here.  It doesn’t run as is, you will need to define a button and a label in Interface Builder and then configure outlets called button and display respectively.  If you hook those up in the app delegate class you will be good to go.

I need to figure out how to do screen shots on this mac lol…  So much to do!

The online documentation for monotouch is pretty thin thus far. There are no books that I know of as well unfortunately.  So until I learn otherwise I’m going to have to do some translation.  That means a new card, #8 Buy Books.  I went out and picked up two books so far (card #8 might stick around a while).  Beginning iPhone 3 Development and Programming the iPhone User Experience.

Card #9: Follow book samples and translate from objective C to C#.

This should be interesting as well as give me an opportunity to add something real to this blog.

A new office

September 27, 2009

I’ve completed cards 4 and 5 and now have a new office!  This is great, a place to sit and work in comfort.  OK enough of the namby pamby stuff.

Card #6 install mac is coming along nicely although still a work in progress.  I started working on the next card (more on that later) and learned that I needed some more software.  I also realized I needed some things to make my new computer a more useful tool for everyday.  I installed the following:

  • MenuMeters – computer performance widget
  • Adium – universal im client
  • Flip4Mac – wmv codec for mac
  • p4 – perforce (a source control system, more on that later)
  • TextWrangler – text pad (use a lot for debugging, log files etc…)
  • Transmission – bittorrent client
  • Perian – more codecs for mac
  • Seashore – free paint/draw tool (will need for project icons, placeholders etc…)

I will try to link these in the future once I figure out how :)

Speaking of figuring out how to do something, I fired up monotouch and decided I needed a new card immediately and that was to go through the tutorial and write Hello World.  Card #7 Hello World.  Which I did and didn’t really learn much.  The monotouch documentation is in a neophyte stage.

Time for some more cards to support a new story: Learning development on the iPhone.

First Cards

September 25, 2009

My new Mac arrived!  Made it from Shanghai China to my doorstep in 2 days.  There you go, card #1 is done.  I’m super excited and it’s a sweet machine.  Can’t wait to put it through it’s paces.

I’m working on Card #2.  Went to the furniture store and got a desk.  Trying to figure out what to do about an office chair.  They can be very expensive.  I’m not sure I want to spend a ton of $ on a chair but you gotta be comfy to work for long hours so I’m torn.  I’m still undecided here.

I’ve procured the paint necessary to eliminate the obnoxious color on the walls in my soon to be office.

I’m blocked on painting the room or building furniture until this weekend when I have time.  Not much I can do about those for now.   This leads us to the final card: install mac.

An interesting card as some turn out to be because it implies many things.  I need to boot it up and get the basics going but I also need to purchase and install the software I’ll need to get going.  Need need need it’s all about me really isn’t it?  I have been working on this part and have downloaded:

  1. The iPhone sdk from apples developer website
  2. MonoTouch
  3. MonoFramework
  4. MonoDevelop

I’m interested in trying the Mono suite out given that it is C# and .NET on the mac for iPhone.  I’m really curious as to how this is going to work.  I’m skeptical but hopeful that I will be able use it as a platform for the game.

For the next day or so I’m just going to play with my new mac and get to know it a little better.  So far I like it.

How it’s coming together…

September 24, 2009

I have had this itch to write and iPhone game ever since the first one came out.  Unfortunately I was unable to do anything about it for quite some time.  Through a few lucky breaks and a dose of serendipity I can now do something about this idea that has been floating in my head for two years.  I’m so excited!

What do I do? How do I start? New projects can be intimidating because you are thinking about many different aspects of the project at one time.  Trying to fit it all in your head is impossible, but we endeavor to try anyway.  Lets look to agile for a good way to start: user stories.

What do I need?  Our first story starts with a question.  In order for me to get going I’m going to need some things.  First and foremost is a new computer, a mac in fact.  Now I haven’t used a mac since high school so this will be a fun learning experience.  I’m not the type that is religious about computer platforms anyway, I’ve developed on just about everything but a mac so may as well add it to my arsenal. I will also need a desk, chair, lamp and I need to paint the walls of the room I will be using as an office.  An office is important to me  because I will need a place free of distraction in order to maximize my time in the ‘zone’.

OK, so this story gives us a few cards to work with.  Here’s how I think the cards should read in order:

  1. Buy MacBook Pro
  2. Buy Office Furniture
  3. Buy Paint
  4. Paint room
  5. Build Furniture
  6. Install Mac

Wow! Six cards from one little story.  Obviously I’m trying to make a point at the simplicity of each of these tasks.  This kind of thinking is how I will go about breaking up the units of work, start with the story then create the backlog of tasks.  One of the most important things about agile I want to illustrate in this blog is how the design of your software will emerge from the stories.  There is no need for huge design docs to design a successful piece of code.  Anyway I’m getting ahead of myself.

We have Six cards, lots of work to do including paint a whole room!  Well I have to you see, it’s superman blue right now and I cannot work in that.  We’ll see how far I get on these cards.

Follow

Get every new post delivered to your Inbox.