/// <summary> /// Solves the boggle board! /// </summary> /// <param name="board">Board to solve.</param> /// <param name="library">Dictionary of words.</param> public Result Solve(IBoard board, ILibrary library) { var result = new Result(); result.Words = new HashSet<string>(); var start = Environment.TickCount; var tasks = new List<Task>(); for (int i = 0; i < board.Width; i++) { for (int j = 0; j < board.Height; j++) { // Capture i/j values (will fail without since i/j change too fast before the thread runs) var state = new ThreadState(i, j); // note: I'm sure this is a lot of memory spawning a bajillion tasks but it was easy. tasks.Add(Task.Run(() => { var path = new WordPath(board.Grid[state.X][state.Y], state.X, state.Y); var myWords = Worker( state.X, state.Y, path, 1, board, library.Books[board.Grid[state.X][state.Y]], new HashSet<string>()); // Merge results (queue up instead and have the main thread do the work? // This seems like a bottle neck but since tasks are not reused it might not matter. lock (_mergeResultsLock) { result.Words.UnionWith(myWords); } })); } } Task.WaitAll(tasks.ToArray()); var stop = Environment.TickCount; result.ElapsedMS = stop - start; return result; }
/// <summary> /// Worker thread for a single square in the boggle grid. /// </summary> /// <param name="x">X grid position.</param> /// <param name="y">Y grid position.</param> /// <param name="path">Word Path object.</param> /// <param name="depth">Current number of squares deep, maximum is 32.</param> /// <param name="board">The boggle board.</param> /// <param name="parent">The parent node, when first called this is the first letter in the word's book from the dictionary.</param> /// <param name="words">The words this worker has found on the boggle board.</param> /// <returns></returns> private HashSet<string> Worker( int x, int y, WordPath path, int depth, IBoard board, ILetter parent, HashSet<string> words) { var c = board.Grid[x][y]; for (int i = 0; i < 8; i++) { var nX = x + _neighbors[i][0]; var nY = y + _neighbors[i][1]; // check for out of bounds if (nX < 0 || nX >= board.Width) continue; if (nY < 0 || nY >= board.Height) continue; // do not re-use tiles, and do not check your own level if (path.AlreadyVisited(nX, nY, depth - 1)) continue; var nC = board.Grid[nX][nY]; path.Set(nC, nX, nY, depth); if (parent.Children.ContainsKey(nC)) { var word = string.Concat(path.Letters.Take(depth + 1).Select(letter => (char)letter)); //var word = System.Text.Encoding.UTF8.GetString(path.Letters.Take(depth + 1).ToArray()); var child = parent.Children[nC]; if (child.IsWord) { if (!child.IsFound) { words.Add(word); child.IsFound = true; } } // maximum recursion should be maximum word length (28~) Worker(nX, nY, path, depth + 1, board, child, words); } } return words; }