public void TestBinarySearch()
		{
			List<string> animalsUnordered = new List<string>(
				new[] { "Cat", "Dog", "Elephant", "Eagle", "Eel", "Zebra", "Yak", "Monkey", "Meerkat" });

			WordList words;

			while (animalsUnordered.Count > 0)
			{	
				words = new WordList(animalsUnordered);
				
				Assert.AreEqual(-1, words.BinarySearch("Unicorn", false, false), "Wrong index for: Unicorn");

				string[] animalsOrdered = animalsUnordered.OrderBy(a => a).ToArray();

				Assert.AreEqual(-1, words.BinarySearch("Dragon", false, false), "Wrong index for: Dragon");

				for (int expectedIndex = 0; expectedIndex < animalsOrdered.Length; expectedIndex++)
				{
					string word = animalsOrdered[expectedIndex];
					Assert.AreEqual(expectedIndex, words.BinarySearch(word, false, false), "Wrong index for: " + word);
				}

				animalsUnordered.RemoveAt(0);
			}

			words = new WordList(new[] { "Heaven", "Hell", "Hello", "Zebra", "ZOO" });

			Assert.AreEqual(0, words.BinarySearch("H", false, true), "Wrong index for: H");
			Assert.AreEqual(1, words.BinarySearch("HELL", false, true), "Wrong index for: H");
			Assert.AreEqual(3, words.BinarySearch("M", true, false), "Wrong index for: M");
			Assert.AreEqual(-1, words.BinarySearch("M", false, false), "Wrong index for: M");
		}
        private void Work(object sender, DoWorkEventArgs args)
        {
            var worker = (BackgroundWorker)sender;

            ReportStatus(worker, "Service Started");
            try
            {
                bool        gameRunning   = false;
                Process     browser       = null;
                Rectangle[] tileRects     = new Rectangle[] { };
                Bitmap      screenShot    = null;
                Rectangle   browserRect   = new Rectangle();
                Rectangle   gameBoardRect = new Rectangle(0, 0, 0, 0);
                var         tileTexts     = new string[, ] {
                    { "", "", "", "" },
                    { "", "", "", "" },
                    { "", "", "", "" },
                    { "", "", "", "" }
                };
                var words = new WordSequence[] { };
                var timer = new Stopwatch();

                var dictionary = new WordList();
                dictionary.LoadFromFile("word list.txt", false);

                int wordIndex = 0;

                while (!worker.CancellationPending)
                {
                    if (!gameRunning)
                    {
                        // Wait a bit
                        System.Threading.Thread.Sleep(500);

                        ReportStatus(worker, "Looking for Wordament window...");

                        // Find browser process
                        browser = WordamentWindow.FindProcess();

                        if (browser != null)
                        {
                            ReportStatus(worker, "Found Wordament window");

                            if (!WordamentWindow.GetWindowRect(browser.MainWindowHandle, out browserRect))
                            {
                                ReportStatus(worker, "Couldn't get window brounds");
                                continue;
                            }

                            //ReportStatus(worker, "Taking screenshot");
                            //timer.Start();
                            screenShot = new Bitmap(browserRect.Width, browserRect.Height);
                            WordamentWindow.TakeScreenshot(screenShot, browserRect);
                            //ReportStatus(worker, string.Format("Done in {0:#,##0}ms", timer.ElapsedMilliseconds));

                            ReportStatus(worker, "Looking for tiles...");
                            //timer.Restart();
                            tileRects = WordamentWindow.GetTileRects(screenShot).
                                        Where(r => (r.Width == Settings.ValidTileWidth) && (r.Height == Settings.ValidTileHeight)).
                                        OrderBy(r => r.Top).ThenBy(r => r.Left).ToArray();
                            //ReportStatus(worker, string.Format("Done in {0:#,##0}ms", timer.ElapsedMilliseconds));

                            // Do some tests to make sure that this is the game board we're looking at
                            if (tileRects.Length == 16)
                            {
                                var lefts   = new HashSet <int>();
                                var tops    = new HashSet <int>();
                                var widths  = new HashSet <int>();
                                var heights = new HashSet <int>();

                                foreach (var rect in tileRects)
                                {
                                    lefts.Add(rect.Left);
                                    tops.Add(rect.Top);
                                    widths.Add(rect.Width);
                                    heights.Add(rect.Height);
                                }

                                gameRunning =
                                    (lefts.Count == 4) && (tops.Count == 4) &&
                                    (widths.Count == 1) && (heights.Count == 1) &&
                                    widths.Contains(Settings.ValidTileWidth) && heights.Contains(Settings.ValidTileHeight);
                            }

                            if (gameRunning)
                            {
                                ReportStatus(worker, "Found game tiles");

                                // The game appears to have started

                                // First get the size of the game board area
                                gameBoardRect.Location = new Point(
                                    tileRects.Min(r => r.Left),
                                    tileRects.Min(r => r.Top));
                                gameBoardRect.Size = new Size(
                                    tileRects.Max(r => r.Right) - gameBoardRect.Left,
                                    tileRects.Max(r => r.Bottom) - gameBoardRect.Top);

                                // Perform OCR on each tile
                                ReportStatus(worker, "Performing OCR");
                                using (var tileBitmap = new Bitmap(Settings.ValidTileWidth, Settings.ValidTileHeight))
                                {
                                    using (var graphics = Graphics.FromImage(tileBitmap))
                                    {
                                        for (var index = 0; index < tileRects.Length; index++)
                                        {
                                            var tileRect = tileRects[index];

                                            graphics.DrawImage(screenShot, 0, 0, tileRect, GraphicsUnit.Pixel);

                                            Brush tileBrush = new SolidBrush(Settings.NormalTileColor);
                                            graphics.FillRectangle(tileBrush, 0, 0, Settings.TileScoreWidth, Settings.TileScoreHeight);

                                            string tileText = Ocr.ReadWord(tileBitmap);
                                            tileText = tileText.Trim(' ', '-');

                                            int tileX = index % 4;
                                            int tileY = (int)Math.Truncate(index / 4.0f);
                                            tileTexts[tileX, tileY] = tileText;

                                            //ReportStatus(worker, string.Format("Tile[{0}, {1}] = \"{2}\"", tileX, tileY, tileText));
                                        }
                                    }
                                }

                                ReportStatus(worker, "Gameboard:");
                                for (var y = 0; y < 4; y++)
                                {
                                    string line = "";
                                    for (var x = 0; x < 4; x++)
                                    {
                                        line += " " + tileTexts[x, y];
                                    }
                                    ReportStatus(worker, line);
                                }

                                ReportStatus(worker, "Finding Words");
                                var finder = new WordFinder();

                                words = finder.FindWordSequences(dictionary, tileTexts).
                                        Distinct(new DistinctWordComparer()).
                                        OrderByDescending(w => w.Word.Length).ToArray();

                                ReportStatus(worker, string.Format("Found {0} words", words.Length));

                                wordIndex = 0;

                                /*
                                 * string wordMessage = words[0].Word + " = ";
                                 * for (var index = 0; index < words[0].Tiles.Length; index++)
                                 * {
                                 *      if (index > 0)
                                 *              wordMessage += ", ";
                                 *      wordMessage += string.Format("[{0},{1}]", words[0].Tiles[index].X, words[0].Tiles[index].Y);
                                 * }
                                 * ReportStatus(worker, "Word: " + wordMessage);
                                 */
                            }
                        }
                    }

                    if (gameRunning)
                    {
                        int    lastPlayedTileIndex = -1;
                        string lastPlayedWord      = "";

                        if (wordIndex >= words.Length)
                        {
                            //ReportStatus(worker, "Finished playing all found words!");
                            System.Threading.Thread.Sleep(1000);
                        }
                        else
                        {
                            var seq = words[wordIndex];

                            //ReportStatus(worker, "Playing word: " + seq.Word);

                            const int pathSmoothingPointCount = 4;

                            int previousTileCenterX = -1;
                            int previousTileCenterY = -1;

                            for (var tileIndex = 0; tileIndex < seq.Tiles.Length; tileIndex++)
                            {
                                var tileLocation  = seq.Tiles[tileIndex];
                                int gridTileIndex = (int)(tileLocation.X + (tileLocation.Y * 4));
                                var tileRect      = tileRects[gridTileIndex];
                                int tileCenterX   = browserRect.Left + tileRect.Left + (tileRect.Width / 2);
                                int tileCenterY   = browserRect.Top + tileRect.Top + (tileRect.Height / 2);

                                if ((pathSmoothingPointCount > 0) && (tileIndex > 0))
                                {
                                    float stepX = (tileCenterX - previousTileCenterX) / (pathSmoothingPointCount + 1);
                                    float stepY = (tileCenterY - previousTileCenterY) / (pathSmoothingPointCount + 1);

                                    for (var pathSmoothingIndex = 0; pathSmoothingIndex < pathSmoothingPointCount; pathSmoothingIndex++)
                                    {
                                        int x = (int)(previousTileCenterX + (stepX * (pathSmoothingIndex + 1)));
                                        int y = (int)(previousTileCenterY + (stepY * (pathSmoothingIndex + 1)));
                                        MouseControl.SetMousePos(x, y);
                                        System.Threading.Thread.Sleep(5);
                                    }
                                }

                                MouseControl.SetMousePos(tileCenterX, tileCenterY);

                                if (tileIndex == 0)
                                {
                                    MouseControl.LeftButtonDown(tileCenterX, tileCenterY);
                                }
                                else if (tileIndex == seq.Tiles.Length - 1)
                                {
                                    MouseControl.LeftButtonUp(tileCenterX, tileCenterY);
                                }

                                previousTileCenterX = tileCenterX;
                                previousTileCenterY = tileCenterY;

                                // Wait a very few ms after swiping a tile
                                System.Threading.Thread.Sleep(Settings.PostTileSleepTime);
                            }

                            // Wait a few ms after playing a word
                            System.Threading.Thread.Sleep(Settings.PostWordSleepTime);

                            lastPlayedTileIndex =
                                (int)(seq.Tiles[seq.Tiles.Length - 1].X +
                                      (4 * seq.Tiles[seq.Tiles.Length - 1].Y));
                            lastPlayedWord = seq.Word;

                            wordIndex++;
                        }

                        GameState gameState = WordamentWindow.GetGameState(browserRect, tileRects, lastPlayedTileIndex, lastPlayedWord);

                        switch (gameState)
                        {
                        case GameState.ValidWord:
                            if (lastPlayedWord != "")
                            {
                                ReportStatus(worker, "Played valid word: " + lastPlayedWord);
                            }
                            System.Threading.Thread.Sleep(100);
                            if (wordIndex >= words.Length)
                            {
                                ReportStatus(worker, "Finished playing all found words");
                            }
                            break;

                        case GameState.InvalidWord:
                            if (lastPlayedWord != "")
                            {
                                ReportStatus(worker, "Played INVALID word: " + lastPlayedWord);
                            }
                            System.Threading.Thread.Sleep(100);
                            if (wordIndex >= words.Length)
                            {
                                ReportStatus(worker, "Finished playing all found words");
                            }
                            break;

                        case GameState.GuessingWarning:
                            if (lastPlayedWord != "")
                            {
                                ReportStatus(worker, "Played INVALID word: " + lastPlayedWord);
                            }
                            ReportStatus(worker, "Accused of guessing - How rude!");
                            System.Threading.Thread.Sleep(1700);
                            if (wordIndex >= words.Length)
                            {
                                ReportStatus(worker, "Finished playing all found words");
                            }
                            break;

                        case GameState.TimesUp:
                            ReportStatus(worker, "The only winning move is not to play");
                            System.Threading.Thread.Sleep(25000);
                            gameRunning = false;
                            break;

                        case GameState.Unknown:
                            ReportStatus(worker, "Unsure of game state - assuming game over");
                            System.Threading.Thread.Sleep(3000);
                            gameRunning = false;
                            break;

                        case GameState.InGame:
                        default:
                            // Keep playing!
                            System.Threading.Thread.Sleep(100);
                            break;
                        }

                        // Force check to see if we should stop service
                        ReportStatus(worker, "");
                    }
                }
            }
            catch (Exception error)
            {
                ReportStatus(worker, string.Format("Error {0}: {1}", error.GetType().Name, error.Message));
            }

            ReportStatus(worker, "Service Stopped");
        }