Exemple #1
0
    private void OnWordBoardFinished(WordBoard wordBoard)
    {
        if (!isCreatingBoards)
        {
            return;
        }

        totalFilesCreated++;

        if (wordBoard != null)
        {
            Utilities.SaveWordBoard(wordBoard, Utilities.BoardFilesDirectory);
            AssetDatabase.Refresh();
        }

        categoryLevelIndex++;

        if (categoryLevelIndex >= gameManagerReference.CategoryInfos[categoryIndex].levelInfos.Count)
        {
            categoryIndex++;
            categoryLevelIndex = 0;

            if (categoryIndex >= gameManagerReference.CategoryInfos.Count)
            {
                StopCreatingBoards();

                Debug.Log("Successfully created all boards");

                return;
            }
        }

        CreateBoardFile();
    }
    /// <summary>
    /// Starts a new thread that will process a board in the background. The OnBoardCreated callback will be called when the board is complete
    /// </summary>
    private bool StartCreatingBoard(string id, string[] words, OnBoardFinished callback, int randomNumberSeed, long restartTime)
    {
        if (IsCreatingBoard(id))
        {
            UnityEngine.Debug.LogErrorFormat("Could not start board creation because the id \"{0}\" already exist.", id);

            return(false);
        }

        // Create a new Board object
        WordBoard board = new WordBoard();

        board.id          = id;
        board.words       = words;
        board.randSeed    = randomNumberSeed;
        board.rand        = new System.Random(randomNumberSeed);
        board.boardState  = WordBoard.BoardState.Processing;
        board.stopwatch   = new System.Diagnostics.Stopwatch();
        board.restartTime = restartTime;

        // Create a new ActiveThread object that will hold information about the thread/board
        ActiveThread activeThread = new ActiveThread();

        activeThread.id       = id;
        activeThread.board    = board;
        activeThread.callback = callback;

        // Create the new Thread to start processing the Board
        activeThread.thread = new Thread(new ThreadStart(() => ProcessBoard(board, words)));
        activeThread.thread.Start();

        activeThreads.Add(activeThread);

        return(true);
    }
Exemple #3
0
    public void StartLevel(string category, int levelIndex)
    {
        ActiveCategory   = category;
        ActiveLevelIndex = levelIndex;

        // Get the board id for the level and load the WordBoard from Resources
        string    boardId   = Utilities.FormatBoardId(ActiveCategory, ActiveLevelIndex);
        WordBoard wordBoard = Utilities.LoadWordBoard(boardId);

        if (wordBoard == null)
        {
            Debug.LogError("Could not load WordBoard with the boardId: " + boardId);
            return;
        }

        // If a saved BoardState does not already exist then create one
        if (!SavedBoardStates.ContainsKey(boardId))
        {
            SavedBoardStates.Add(boardId, CreateNewBoardState(wordBoard));
        }

        // Try and get a saved board state if one exists
        ActiveBoardState = SavedBoardStates[boardId];

        // Save the game
        Save();

        // Setup the display using the assigned activeBoardState
        SetupActiveBoard();
    }
Exemple #4
0
    public static WordBoard LoadWordBoard(string boardId)
    {
        TextAsset textAsset = Resources.Load <TextAsset>(Utilities.BoardFilesDirectory + "/" + boardId);

        if (textAsset != null)
        {
            string[] text = textAsset.text.Split(',');

            WordBoard wordBoard = new WordBoard();
            wordBoard.id    = text[0];
            wordBoard.size  = System.Convert.ToInt32(text[1]);
            wordBoard.words = text[2].Split('_');

            wordBoard.wordTiles = new WordBoard.WordTile[text[3].Length / 3];

            for (int i = 0; i < wordBoard.wordTiles.Length; i++)
            {
                WordBoard.WordTile wordTile = new WordBoard.WordTile();

                wordTile.used      = text[3][0] == '1';
                wordTile.hasLetter = text[3][1] == '1';
                wordTile.letter    = text[3][2];

                wordBoard.wordTiles[i] = wordTile;

                // Remove the first 3 characters of text[3]
                text[3] = text[3].Substring(3, text[3].Length - 3);
            }

            return(wordBoard);
        }

        return(null);
    }
Exemple #5
0
 private void OnWordBoardFinished(WordBoard wordBoard)
 {
     if (wordBoard != null)
     {
         Utilities.SaveWordBoard(wordBoard, Utilities.BoardFilesDirectory);
         AssetDatabase.Refresh();
     }
 }
 /// <summary>
 /// Resets the regions back to 0 and the regionLocked back to false.
 /// </summary>
 private void ResetRegions(WordBoard board)
 {
     for (int i = 0; i < board.wordTiles.Length; i++)
     {
         board.wordTiles[i].region       = 0;
         board.wordTiles[i].regionLocked = false;
     }
 }
Exemple #7
0
    public static void SaveWordBoard(WordBoard wordBoard, string directoryInResources)
    {
                #if !UNITY_EDITOR
        Debug.LogError("Can only save WordBoards in the Unity Editor.");
                #else
        /* Important things we need to save in order to create a game board: id, size, words, and wordTiles */

        // Get the words as one long string seperated by _ to save space
        string wordsStr = "";
        for (int i = 0; i < wordBoard.words.Length; i++)
        {
            if (i != 0)
            {
                wordsStr += "_";
            }

            wordsStr += wordBoard.words[i];
        }

        // Get the word tile states as one long string to save space
        string wordTilesStr = "";
        for (int i = 0; i < wordBoard.wordTiles.Length; i++)
        {
            string usedStr      = wordBoard.wordTiles[i].used ? "1" : "0";
            string hasLetterStr = wordBoard.wordTiles[i].hasLetter ? "1" : "0";

            wordTilesStr += string.Format("{0}{1}{2}", usedStr, hasLetterStr, (hasLetterStr == "1") ? wordBoard.wordTiles[i].letter : '-');
        }

        // Create the text that defines a WordBoard
        string text = "";
        text += string.Format("{0},", wordBoard.id);
        text += string.Format("{0},", wordBoard.size);
        text += string.Format("{0},", wordsStr);
        text += string.Format("{0}", wordTilesStr);

        // Get the full path
        string directoyPath = Application.dataPath + "/WordGame/Resources/" + BoardFilesDirectory;
        string ioPath       = directoyPath + "/" + wordBoard.id + ".csv";

        if (!System.IO.Directory.Exists(directoyPath))
        {
            System.IO.Directory.CreateDirectory(directoyPath);
        }

        // If there is already a board, then delete it to create a new one
        if (System.IO.File.Exists(ioPath))
        {
            System.IO.File.Delete(ioPath);
        }

        // Open a StreamWriter and write the text to the file
        System.IO.StreamWriter stream = System.IO.File.CreateText(ioPath);
        stream.WriteLine(text);
        stream.Close();
                #endif
    }
 /// <summary>
 /// This will convert one regoin to another region. It will not attempt to merge any crossroad regions and will also
 /// lock the tile to the converted region.
 /// </summary>
 private void ConvertRegionForCrossroad(WordBoard board, int fromRegion, int toRegion)
 {
     for (int i = 0; i < board.wordTiles.Length; i++)
     {
         if (board.wordTiles[i].region == fromRegion)
         {
             board.wordTiles[i].region       = toRegion;
             board.wordTiles[i].regionLocked = true;
         }
     }
 }
    /// <summary>
    /// Returns all indexes of board.wordTiles that we can place a letter at from the index specified by fromTile. This will
    /// only return indexes that are in the validIndexes list.
    /// </summary>
    private List <int> GetPossibleLetterPositions(WordBoard board, List <int> validIndexes, int fromTile)
    {
        List <int> possibleIndexes = new List <int>();

        // If fromTile is -1 then we return all indexes from validIndexes that is used and doesn't already have a letter
        if (fromTile == -1)
        {
            for (int i = 0; i < validIndexes.Count; i++)
            {
                if (board.wordTiles[validIndexes[i]].used && !board.wordTiles[validIndexes[i]].hasLetter)
                {
                    possibleIndexes.Add(validIndexes[i]);
                }
            }
        }
        else
        {
            // Now we need to check the indexes that "surround" the index at fromTile so for instance we have a board:
            // _ _ _ _ _
            // _ i i i _   'f' is the tile represented by fromTile and we want to return all 'i' indexes that are
            // _ i f i _    used, don't have a letter, and exist in validIndexes.
            // _ i i i _
            // _ _ _ _ _

            int iStart = Mathf.FloorToInt((float)fromTile / (float)board.size) - 1;
            int jStart = (fromTile % board.size) - 1;
            int iEnd   = iStart + 3;
            int jEnd   = jStart + 3;

            // Clamp the indexes so we don't go off the board
            iStart = (iStart < 0) ? 0 : iStart;
            jStart = (jStart < 0) ? 0 : jStart;
            iEnd   = (iEnd > board.size) ? board.size : iEnd;
            jEnd   = (jEnd > board.size) ? board.size : jEnd;

            for (int i = iStart; i < iEnd; i++)
            {
                for (int j = jStart; j < jEnd; j++)
                {
                    int tileIndex = i * board.size + j;

                    if (tileIndex != fromTile &&                                                // Don't want to return the fromTile index
                        board.wordTiles[tileIndex].used &&                                      // The tile needs to be used
                        !board.wordTiles[tileIndex].hasLetter &&                                // The tile cannot have a letter already on it
                        validIndexes.Contains(tileIndex))                                       // The index must exist in validIndexes
                    {
                        possibleIndexes.Add(tileIndex);
                    }
                }
            }
        }

        return(possibleIndexes);
    }
    /// <summary>
    /// Merges the region at index i.
    /// </summary>
    private void MergeRegion(WordBoard board, int i)
    {
        int thisRegion = board.wordTiles[i].region;

        if (thisRegion == 0 || board.wordTiles[i].regionLocked)
        {
            return;
        }

        int thisI = Mathf.FloorToInt((float)i / (float)board.size);
        int thisJ = (i % board.size);

        List <int> cornerIndexes = GetCornerRegionIndexes(board, i);

        // A region can be merged with another if the region has only 1 corner region or it has 2 corner regions and it is
        // a single region (only one tile in the region)
        if (cornerIndexes.Count == 2)
        {
            // Get the left, right, top, and bottom regions
            int left   = thisI * board.size + (thisJ - 1);
            int right  = thisI * board.size + (thisJ + 1);
            int top    = (thisI + 1) * board.size + thisJ;
            int bottom = (thisI - 1) * board.size + thisJ;

            // If none of those regions equal this region then it is a single region
            if (!((thisRegion == board.wordTiles[left].region) ||
                  (thisRegion == board.wordTiles[right].region) ||
                  (thisRegion == board.wordTiles[top].region) ||
                  (thisRegion == board.wordTiles[bottom].region)))
            {
                // Need to check if the index for the region we are about to merge with is a crossroads becuase if it is
                // then we cannot merge with it. Also check if the region is locked (ie. it cannot be changed)
                if (!IsCrossroad(board, cornerIndexes[0]) && !board.wordTiles[cornerIndexes[0]].regionLocked)
                {
                    // Convert the first corner region to this region
                    ConvertRegion(board, board.wordTiles[cornerIndexes[0]].region, thisRegion);
                }

                // Need to check if the index for the region we are about to merge with is a crossroads becuase if it is
                // then we cannot merge with it. Also check if the region is locked (ie. it cannot be changed
                if (!IsCrossroad(board, cornerIndexes[1]) && !board.wordTiles[cornerIndexes[1]].regionLocked)
                {
                    // Convert the second corner region to this region
                    ConvertRegion(board, board.wordTiles[cornerIndexes[1]].region, thisRegion);
                }
            }
        }
        else if (cornerIndexes.Count == 1 && !IsCrossroad(board, cornerIndexes[0]) && !board.wordTiles[cornerIndexes[0]].regionLocked)
        {
            ConvertRegion(board, board.wordTiles[cornerIndexes[0]].region, thisRegion);
        }
    }
    /// <summary>
    /// Attempts to place all letters from the first word in "words" on the board. The list "validIndexes" contains all the
    /// indexes of board.wordTiles that a letter can be placed on. The int "letterIndex" is the character in the word at words[0]
    /// that is next to be placed. The int "lastPlacedIndex" is the index in board.wordTiles of the last letter that was placed
    /// on the board (-1 if letterIndex is 0, ie. we are placing the first letter).
    ///
    /// This part of the algorithm works by getting all possible places for the letter at words[0][letterIndex] can go then
    /// trying each on. If it runs out of possible places then it returns false indicating to the letter before it that it needs
    /// to pick another place. Once the full word is place on the board it calls FillRestOfWords method to continue to the next
    /// part of the algorithm.
    /// </summary>
    private bool PlaceLetters(WordBoard board, List <int> validIndexes, string[] words, int letterIndex, int lastPlacedIndex)
    {
        // If letterIndex is greater than the number of letters in the word then we are done placing this word on the board
        if (letterIndex >= words[0].Length)
        {
            // If this was the last word to place then we completed the board
            if (words.Length == 1)
            {
                return(true);
            }

            // Now we try to fill in the rest of with words using the board
            return(FillRestOfWords(board, validIndexes, words));
        }

        // Check if the amount of time that has elapsed is grater than board.restartTimer
        if (board.stopwatch.ElapsedMilliseconds >= board.restartTime)
        {
            // Signals the main thread that the thread that is processing this board needs to abort and restart
            board.boardState = WordBoard.BoardState.Restart;
        }

        // Get the list of possible board indexes where we can place the next letter of the word
        List <int> possibleIndexes = GetPossibleLetterPositions(board, validIndexes, lastPlacedIndex);
        int        numberOfIndexes = possibleIndexes.Count;

        // Loop through each index, trying each one
        for (int i = 0; i < numberOfIndexes; i++)
        {
            // Get a random index to try from the list of possible indexes
            int randIndex  = board.rand.Next(possibleIndexes.Count);
            int indexToTry = possibleIndexes[board.rand.Next(possibleIndexes.Count)];

            // Remove that index from the list of possibilities so we don't pick it again if it fails
            possibleIndexes.RemoveAt(randIndex);

            // Set the bool on the board to say we are going to use this tile
            board.wordTiles[indexToTry].hasLetter = true;
            board.wordTiles[indexToTry].letter    = words[0][letterIndex];

            // Try and place the remaining letters on the board
            if (PlaceLetters(board, validIndexes, words, letterIndex + 1, indexToTry))
            {
                return(true);
            }

            // We did not find a completed board with this board piece so set it back to false so we can pick another one
            board.wordTiles[indexToTry].hasLetter = false;
        }

        return(false);
    }
    /// <summary>
    /// Assignes all tiles that are used and have no letters to a region. A region is just an integer assigned to the WordTile
    /// region field. A region is "filled" by finding all tiles that connect to each other either vertically or horizontally but
    /// not diagonally.
    /// </summary>
    public void FillRegions(WordBoard board, List <int> validIndexes)
    {
        int regionCount = 1;

        for (int i = 0; i < board.wordTiles.Length; i++)
        {
            if (validIndexes.Contains(i) && IsUnassignedRegion(board, i))
            {
                FillRegion(board, i, regionCount);
                regionCount++;
            }
        }
    }
    /// <summary>
    /// Merges all regions that can be merged.
    /// </summary>
    private void MergeRegions(WordBoard board)
    {
        for (int i = board.size + 1; i < board.wordTiles.Length - board.size; i++)
        {
            if (i % board.size == board.size - 1)
            {
                i += 1;

                continue;
            }

            MergeRegion(board, i);
        }
    }
    /// <summary>
    /// Determines if the tile at index is a crossroad tile.
    /// </summary>
    private bool IsCrossroad(WordBoard board, int index)
    {
        // It cannot be a crossroad tile if it is on one of the edges of the board
        if ((index < board.size) ||                                             // Top edge
            (index >= board.wordTiles.Length - board.size) ||                   // Bottom edge
            (index % board.size == 0) ||                                        // Left edge
            (index % board.size == board.size - 1))                             // Right edge
        {
            return(false);
        }

        // Get the region number
        int thisRegion = board.wordTiles[index].region;

        // If its 0 then this is not a region and therefore cannot be a crossroad
        if (thisRegion == 0)
        {
            return(false);
        }

        int thisI = Mathf.FloorToInt((float)index / (float)board.size);
        int thisJ = (index % board.size);

        List <int> cornerRegions = GetCornerRegionIndexes(board, index);

        // If there are 3 or 4 cornering regions then this is definitly a crossroad region
        if (cornerRegions.Count > 2)
        {
            return(true);
        }

        // If there are only 2 corner regions then we need to check if thisRegion is a single region (only 1 tile in the region)
        if (cornerRegions.Count == 2)
        {
            int left   = thisI * board.size + (thisJ - 1);
            int right  = thisI * board.size + (thisJ + 1);
            int top    = (thisI + 1) * board.size + thisJ;
            int bottom = (thisI - 1) * board.size + thisJ;

            if ((thisRegion == board.wordTiles[left].region) ||
                (thisRegion == board.wordTiles[right].region) ||
                (thisRegion == board.wordTiles[top].region) ||
                (thisRegion == board.wordTiles[bottom].region))
            {
                return(true);
            }
        }

        return(false);
    }
    /// <summary>
    /// Helper method for FillRegions
    /// </summary>
    private void FillRegion(WordBoard board, int index, int region)
    {
        board.wordTiles[index].region = region;

        int i = Mathf.FloorToInt((float)index / (float)board.size);
        int j = (index % board.size);

        if (i != 0)
        {
            int topIndex = (i - 1) * board.size + j;

            if (IsUnassignedRegion(board, topIndex))
            {
                FillRegion(board, topIndex, region);
            }
        }

        if (i != board.size - 1)
        {
            int bottomIndex = (i + 1) * board.size + j;

            if (IsUnassignedRegion(board, bottomIndex))
            {
                FillRegion(board, bottomIndex, region);
            }
        }

        if (j != 0)
        {
            int leftIndex = i * board.size + (j - 1);

            if (IsUnassignedRegion(board, leftIndex))
            {
                FillRegion(board, leftIndex, region);
            }
        }

        if (j != board.size - 1)
        {
            int rightIndex = i * board.size + (j + 1);

            if (IsUnassignedRegion(board, rightIndex))
            {
                FillRegion(board, rightIndex, region);
            }
        }
    }
    /// <summary>
    /// Creates a new Board and initializes it using the array of words, then calls the methods that starts the board creation algorithm.
    /// </summary>
    private void ProcessBoard(WordBoard board, string[] words)
    {
        int letterCount = 0;

        // Get the total number of letters from all words
        for (int i = 0; i < words.Length; i++)
        {
            letterCount += words[i].Length;
        }

        // Get the core board size and actual board size.
        int coreBoardSize = Mathf.FloorToInt(Mathf.Sqrt(letterCount));
        int boardSize     = Mathf.CeilToInt(Mathf.Sqrt(letterCount));

        // Create a new Board, set its size, and intialize a new array of WordTiles
        board.size      = boardSize;
        board.wordTiles = new WordBoard.WordTile[boardSize * boardSize];

        // Instantiate all the WordTiles
        for (int i = 0; i < boardSize * boardSize; i++)
        {
            board.wordTiles[i] = new WordBoard.WordTile();
        }

        // Set all the core board indexes to used
        for (int i = 0; i < coreBoardSize; i++)
        {
            for (int j = 0; j < coreBoardSize; j++)
            {
                board.wordTiles[i * boardSize + j].used = true;
            }
        }

        board.stopwatch.Start();

        // Start the algorithm
        if (words.Length == 0 || CompleteBoard(board, letterCount - coreBoardSize * coreBoardSize, words))
        {
            board.boardState = WordBoard.BoardState.DoneSuccess;
        }
        else
        {
            board.boardState = WordBoard.BoardState.DoneFailed;
        }

        board.stopwatch.Stop();
    }
    /// <summary>
    /// Get a List of all the unique regions that corner the region at the specified index.
    /// </summary>
    private List <int> GetCornerRegionIndexes(WordBoard board, int index)
    {
        int thisI = Mathf.FloorToInt((float)index / (float)board.size);
        int thisJ = (index % board.size);

        int corner1Index = (thisI - 1) * board.size + (thisJ - 1);
        int corner2Index = (thisI - 1) * board.size + (thisJ + 1);
        int corner3Index = (thisI + 1) * board.size + (thisJ - 1);
        int corner4Index = (thisI + 1) * board.size + (thisJ + 1);

        int corner1Region = board.wordTiles[corner1Index].region;
        int corner2Region = board.wordTiles[corner2Index].region;
        int corner3Region = board.wordTiles[corner3Index].region;
        int corner4Region = board.wordTiles[corner4Index].region;

        int        thisRegion    = board.wordTiles[index].region;
        List <int> cornerRegions = new List <int>();
        List <int> cornerIndexes = new List <int>();

        if (corner1Region != 0 && !board.wordTiles[corner1Index].regionLocked && thisRegion != corner1Region)
        {
            cornerIndexes.Add(corner1Index);
            cornerRegions.Add(corner1Region);
        }

        if (corner2Region != 0 && !board.wordTiles[corner2Index].regionLocked && thisRegion != corner2Region && !cornerRegions.Contains(corner2Region))
        {
            cornerIndexes.Add(corner2Index);
            cornerRegions.Add(corner2Region);
        }

        if (corner3Region != 0 && !board.wordTiles[corner3Index].regionLocked && thisRegion != corner3Region && !cornerRegions.Contains(corner3Region))
        {
            cornerIndexes.Add(corner3Index);
            cornerRegions.Add(corner3Region);
        }

        if (corner4Region != 0 && !board.wordTiles[corner4Index].regionLocked && thisRegion != corner4Region && !cornerRegions.Contains(corner4Region))
        {
            cornerIndexes.Add(corner4Index);
            cornerRegions.Add(corner4Region);
        }

        return(cornerIndexes);
    }
    /// <summary>
    /// Returns the index of the first crossroad region we come across, -1 if there are no corssroads
    /// </summary>
    private int GetAnyCrossroad(WordBoard board)
    {
        for (int i = board.size + 1; i < board.wordTiles.Length - board.size; i++)
        {
            if (i % board.size == board.size - 1)
            {
                i += 1;

                continue;
            }

            if (IsCrossroad(board, i))
            {
                return(i);
            }
        }

        return(-1);
    }
    /// <summary>
    /// Returns a Dictionary of List<int> where the key is a region and the list is all the indexes of tiles that belong
    /// to that region.
    /// </summary>
    private Dictionary <int, List <int> > GetRegionIndexes(WordBoard board)
    {
        Dictionary <int, List <int> > regionIndexes = new Dictionary <int, List <int> >();

        for (int i = 0; i < board.wordTiles.Length; i++)
        {
            int region = board.wordTiles[i].region;

            if (board.wordTiles[i].region != 0)
            {
                if (!regionIndexes.ContainsKey(region))
                {
                    regionIndexes.Add(region, new List <int>());
                }

                regionIndexes[region].Add(i);
            }
        }

        return(regionIndexes);
    }
Exemple #20
0
    /// <summary>
    /// Creates a new BoardState object using the values defined in the given WordBoard
    /// </summary>
    private BoardState CreateNewBoardState(WordBoard wordBoard)
    {
        BoardState boardState = new BoardState();

        boardState.wordBoardId   = wordBoard.id;
        boardState.wordBoardSize = wordBoard.size;
        boardState.words         = wordBoard.words;
        boardState.nextHintIndex = 0;

        boardState.foundWords       = new bool[wordBoard.words.Length];
        boardState.tileLetters      = new char[wordBoard.wordTiles.Length];
        boardState.tileStates       = new BoardState.TileState[wordBoard.wordTiles.Length];
        boardState.hintLettersShown = new List <int[]>();

        for (int i = 0; i < boardState.tileStates.Length; i++)
        {
            boardState.tileLetters[i] = wordBoard.wordTiles[i].hasLetter ? wordBoard.wordTiles[i].letter : (char)0;
            boardState.tileStates[i]  = wordBoard.wordTiles[i].hasLetter ? BoardState.TileState.UsedButNotFound : BoardState.TileState.NotUsed;
        }

        return(boardState);
    }
Exemple #21
0
    public long restartTime;                    // The amount of time in milliseconds thay must past for board creation to restart

    #endregion

    #region Public Methods

    /// <summary>
    /// Copies the board.
    /// </summary>
    public WordBoard Copy()
    {
        WordBoard newBoard = new WordBoard();

        newBoard.id        = id;
        newBoard.size      = size;
        newBoard.wordTiles = new WordBoard.WordTile[newBoard.size * newBoard.size];

        for (int i = 0; i < newBoard.wordTiles.Length; i++)
        {
            WordBoard.WordTile wordTile    = wordTiles[i];
            WordBoard.WordTile newWordTile = new WordBoard.WordTile();

            newWordTile.hasLetter    = wordTile.hasLetter;
            newWordTile.letter       = wordTile.letter;
            newWordTile.region       = wordTile.region;
            newWordTile.regionLocked = wordTile.regionLocked;
            newWordTile.used         = wordTile.used;

            newBoard.wordTiles[i] = newWordTile;
        }

        return(newBoard);
    }
    /// <summary>
    /// Converts one region to another region
    /// </summary>
    private void ConvertRegion(WordBoard board, int fromRegion, int toRegion)
    {
        List <int> covertedCrossroads = new List <int>();

        for (int i = 0; i < board.wordTiles.Length; i++)
        {
            if (board.wordTiles[i].region == fromRegion)
            {
                // If the region we are about to convert is a crossroad region then we need to remember it because
                // when we are done converting it we need to attempt to merge it since it might have become mergeable
                if (IsCrossroad(board, i))
                {
                    covertedCrossroads.Add(i);
                }

                board.wordTiles[i].region = toRegion;
            }
        }

        for (int i = 0; i < covertedCrossroads.Count; i++)
        {
            MergeRegion(board, covertedCrossroads[i]);
        }
    }
    public void Update()
    {
        // Checks for completed boards
        for (int i = activeThreads.Count - 1; i >= 0; i--)
        {
            switch (activeThreads[i].board.boardState)
            {
            case WordBoard.BoardState.DoneSuccess:
            case WordBoard.BoardState.DoneFailed:
            {
                WordBoard       wordBoard = activeThreads[i].board;
                OnBoardFinished callback  = activeThreads[i].callback;

                activeThreads.RemoveAt(i);

                callback(wordBoard);

                break;
            }

            case WordBoard.BoardState.Restart:
            {
                string          id           = activeThreads[i].id;
                string[]        words        = activeThreads[i].board.words;
                long            restartTimer = activeThreads[i].board.restartTime;
                OnBoardFinished callback     = activeThreads[i].callback;

                AbortBoardCreation(id);

                StartCreatingBoard(id, words, callback, restartTimer);

                break;
            }
            }
        }
    }
    /// <summary>
    /// Assigns any extra tiles that need to be assigned to "used" on the board then tries to placed the words on the board.
    /// This is part of a backtracking algorithm that picks extraTilesToPick number of un-used tiles and makes sets them as used,
    /// it then calls PlaceLetters in an attempt to place all the words on the board. If PlaceLetters returns false then the
    /// assignment of "used" tiles is invalid (ie. cannot place all words on the board) so it picks a different arrangement of
    /// "used" tiles.
    /// </summary>
    private bool CompleteBoard(WordBoard board, int extraTilesToPick, string[] words)
    {
        // If there are no more tiles we need to assigned as used then start placing words on the board
        if (extraTilesToPick == 0)
        {
            List <int> validIndexes = new List <int>();

            for (int i = 0; i < board.wordTiles.Length; i++)
            {
                validIndexes.Add(i);
            }

            return(PlaceLetters(board, validIndexes, words, 0, -1));
        }

        List <int> unusedBottomIndexes = new List <int>();
        List <int> unusedRightIndexes  = new List <int>();

        // Gather all the indexes on the bottom most row and right most column that are not being used. We need to place a tile in one of those positions.
        for (int i = 0; i < board.size - 1; i++)
        {
            int bottomIndex = board.size * (board.size - 1) + i;
            int rightIndex  = i * board.size + board.size - 1;

            if (!board.wordTiles[bottomIndex].used)
            {
                unusedBottomIndexes.Add(bottomIndex);
            }

            if (!board.wordTiles[rightIndex].used)
            {
                unusedRightIndexes.Add(rightIndex);
            }
        }

        int numberOfIndexes = unusedBottomIndexes.Count + unusedRightIndexes.Count;

        for (int i = 0; i < numberOfIndexes; i++)
        {
            // Get a random index to try from the list of possible indexes
            int randIndex  = 0;
            int indexToTry = 0;

            if (unusedBottomIndexes.Count > 0)
            {
                randIndex  = board.rand.Next(unusedBottomIndexes.Count);
                indexToTry = unusedBottomIndexes[randIndex];

                unusedBottomIndexes.RemoveAt(randIndex);
            }
            else
            {
                randIndex  = board.rand.Next(unusedRightIndexes.Count);
                indexToTry = unusedRightIndexes[randIndex];

                unusedRightIndexes.RemoveAt(randIndex);
            }

            // Set the bool on the board to say we are going to use this tile
            board.wordTiles[indexToTry].used = true;

            // If CompleteBoard returns true then we found a completed board
            if (CompleteBoard(board, extraTilesToPick - 1, words))
            {
                return(true);
            }

            // We did not find a completed board with this board piece so set it back to false so we can pick another one
            board.wordTiles[indexToTry].used = false;
        }

        // Non of the pissible indexes lead to a completed board
        return(false);
    }
 /// <summary>
 /// Determines if the wordTile at index 'i' is used, has no letter, and has no region (ie region == 0)
 /// </summary>
 private bool IsUnassignedRegion(WordBoard board, int i)
 {
     return(board.wordTiles[i].used && !board.wordTiles[i].hasLetter && board.wordTiles[i].region == 0);
 }
    /// <summary>
    /// This part of the algorithm is called after a full word is placed on the board by PlaceLetters. It sets in motion the
    /// "sub-algorithm" that splits the board into "regions" where words can be placed then attempts to place the remaining
    /// words in those regions. If it fails to do so then that means the word that was just placed in PlaceLetters created a
    /// board in which its impossible to place the remain letters so it returns false to indicate to PlaceLetters that it needs
    /// to find a new arrangment of letters for the word it just placed.
    /// </summary>
    private bool FillRestOfWords(WordBoard board, List <int> validIndexes, string[] words)
    {
        // Make sure we set all regions to 0 and regionLocked to false on the board
        ResetRegions(board);

        // Fill the regions with numbers
        FillRegions(board, validIndexes);

        List <WordBoard> possibleBoards = GetAllBoardRegionCombinations(board);

        for (int i = 0; i < possibleBoards.Count; i++)
        {
            Dictionary <int, List <int> > regionIndexes = GetRegionIndexes(board);

            if (regionIndexes.Count > board.words.Length - 1)
            {
                continue;
            }

            Dictionary <int, List <int> > wordRegionAssignments = new Dictionary <int, List <int> >();

            List <int> regionSizes = new List <int>();
            List <int> wordSizes   = new List <int>();
            List <int> regions     = new List <int>();
            List <int> wordIndexes = new List <int>();

            foreach (KeyValuePair <int, List <int> > pair in regionIndexes)
            {
                wordRegionAssignments.Add(pair.Key, new List <int>());
                regions.Add(pair.Key);
                regionSizes.Add(pair.Value.Count);
            }

            for (int j = 1; j < words.Length; j++)
            {
                wordIndexes.Add(j);
                wordSizes.Add(words[j].Length);
            }

            if (TryFitWordsIntoRegions(regionSizes, wordSizes, regions, wordIndexes, wordRegionAssignments))
            {
                bool allWordsPlaced = true;

                WordBoard.WordTile[] savedWordTiles = board.wordTiles;
                board.wordTiles = possibleBoards[i].wordTiles;

                foreach (KeyValuePair <int, List <int> > pair in wordRegionAssignments)
                {
                    string[] regionWords = new string[pair.Value.Count];

                    for (int j = 0; j < pair.Value.Count; j++)
                    {
                        regionWords[j] = words[pair.Value[j]];
                    }

                    if (!PlaceLetters(board, regionIndexes[pair.Key], regionWords, 0, -1))
                    {
                        allWordsPlaced = false;
                        break;
                    }
                }

                if (allWordsPlaced)
                {
                    return(true);
                }

                board.wordTiles = savedWordTiles;
            }
        }

        return(false);
    }
    /// <summary>
    /// Takes a single Board that has a number of regions defined in the wordTiles then returns a List of Boards whose
    /// regions have been orgninsed in such a way that they define all the ways words could be laid out.
    /// </summary>
    private List <WordBoard> GetAllBoardRegionCombinations(WordBoard board)
    {
        List <WordBoard> possibleBoards = new List <WordBoard>();

        // Merge any regions that can be merged
        MergeRegions(board);

        // Get any crossroad index. A crossroad is an index where a region could split off into 2 - 4 other regions
        int crossroadIndex = GetAnyCrossroad(board);

        // If its -1 then no crossroads exist and this Board is complete and can be returned
        if (crossroadIndex == -1)
        {
            possibleBoards.Add(board);
        }
        else
        {
            // If there is a crossroad we need to split the board
            List <int> cornerIndexes = GetCornerRegionIndexes(board, crossroadIndex);

            // If there is only 2 distinct crossroad regions
            if (cornerIndexes.Count == 2)
            {
                int crossroadRegion = board.wordTiles[crossroadIndex].region;
                int corner1Region   = board.wordTiles[cornerIndexes[0]].region;
                int corner2Region   = board.wordTiles[cornerIndexes[1]].region;

                WordBoard newBoard1 = board.Copy();
                WordBoard newBoard2 = board.Copy();
                WordBoard newBoard3 = board.Copy();

                // Setup new board 1
                newBoard1.wordTiles[crossroadIndex].region       = corner1Region;
                newBoard1.wordTiles[crossroadIndex].regionLocked = true;
                ConvertRegionForCrossroad(newBoard1, corner1Region, corner1Region);                             // Makes region un-mergable
                ConvertRegionForCrossroad(newBoard1, corner2Region, corner1Region);

                // Setup new board 2
                ConvertRegionForCrossroad(newBoard2, crossroadRegion, crossroadRegion);                 // Makes region un-mergable
                ConvertRegionForCrossroad(newBoard2, corner1Region, crossroadRegion);

                // Setup new board 3
                ConvertRegionForCrossroad(newBoard3, crossroadRegion, crossroadRegion);                 // Makes region un-mergable
                ConvertRegionForCrossroad(newBoard3, corner2Region, crossroadRegion);

                // Get all possible boards that can be made from the 3 new boards
                possibleBoards.AddRange(GetAllBoardRegionCombinations(newBoard1));
                possibleBoards.AddRange(GetAllBoardRegionCombinations(newBoard2));
                possibleBoards.AddRange(GetAllBoardRegionCombinations(newBoard3));
            }
            else
            {
                for (int i = 0; i < cornerIndexes.Count; i++)
                {
                    int corner1Region = board.wordTiles[cornerIndexes[i]].region;

                    for (int j = i + 1; j < cornerIndexes.Count; j++)
                    {
                        int corner2Region = board.wordTiles[cornerIndexes[j]].region;

                        WordBoard newBoard = board.Copy();

                        newBoard.wordTiles[crossroadIndex].region       = corner1Region;
                        newBoard.wordTiles[crossroadIndex].regionLocked = true;

                        ConvertRegionForCrossroad(newBoard, corner1Region, corner1Region);                              // Makes region un-mergable
                        ConvertRegionForCrossroad(newBoard, corner2Region, corner1Region);

                        possibleBoards.AddRange(GetAllBoardRegionCombinations(newBoard));
                    }
                }
            }
        }

        return(possibleBoards);
    }