Exemple #1
0
        // Helper, returns the number of words that do not intersect with any other word
        public int WordsNotConnectedCount()
        {
            List <WordPosition> tempList = new List <WordPosition>(m_WordPositionList);

            int blocksCount = 0;

            for (; ;)
            {
                if (tempList.Count == 0)
                {
                    break;
                }
                // Start a new block of connected WordPosition
                blocksCount++;

                WordPosition wordPosition = tempList[0];
                tempList.RemoveAt(0);
                foreach (WordPosition w in GetConnectedWordPositions(wordPosition, tempList))
                {
                    if (tempList.Contains(w))
                    {
                        tempList.Remove(w);
                    }
                }
            }

            return(blocksCount);
        }
Exemple #2
0
        private void RemoveSquares(WordPosition wordPosition)
        {
            Debug.Assert(wordPosition != null);

            int row    = wordPosition.StartRow;
            int column = wordPosition.StartColumn;

            for (int i = 0; i < wordPosition.Word.Length; i++)
            {
                Square sq = GetSquare(row, column);
                Debug.Assert(sq != null);
                if (sq.ShareCount == 1)
                {
                    m_Squares.Remove(Index(row, column));
                }
                else
                {
                    sq.ShareCount--;
                }

                if (wordPosition.IsVertical)
                {
                    row++;
                }
                else
                {
                    column++;
                }
            }
        }
Exemple #3
0
        // Private helper
        private void AddSquares(WordPosition wordPosition)
        {
            Debug.Assert(wordPosition != null);

            int row    = wordPosition.StartRow;
            int column = wordPosition.StartColumn;

            foreach (char c in wordPosition.Word)
            {
                if (GetSquare(row, column) == null)
                {
                    Square sq = new Square(row, column, c, false, 1);
                    m_Squares.Add(Index(row, column), sq);
                }
                else
                {
                    Square sq = GetSquare(row, column);
                    Debug.Assert(sq.Letter == c);
                    sq.ShareCount++;
                }
                if (wordPosition.IsVertical)
                {
                    row++;
                }
                else
                {
                    column++;
                }
            }
        }
Exemple #4
0
        /// <summary>Core placement function, adds a list of words to current layout</summary>
        /// <param name="wordsToAddList">List of words to place</param>
        /// <param name="withLayoutBackup">If true, current layout is backed up, and restored if placement of all words failed</param>
        /// <returns>Returns a list of WordPosition for placed words in case of success, or false if placement failed, current layout is preserved in this case</returns>
        public IEnumerable <WordPosition> PlaceWordsList(IEnumerable <string> wordsToAddList, bool withLayoutBackup)
        {
            if (wordsToAddList == null)
            {
                throw new ArgumentNullException(nameof(wordsToAddList));
            }

            // Keep a copy of current layout to restore if placement fails at some point
            WordPositionLayout backupLayout = null;

            if (withLayoutBackup)
            {
                backupLayout = new WordPositionLayout(Layout);
            }

            string checkMessage = CheckWordsList(wordsToAddList);

            if (!string.IsNullOrEmpty(checkMessage))
            {
                throw new BonzaException(checkMessage);
            }

            List <string>       shuffledList           = new List <string>(wordsToAddList).Shuffle();
            List <WordPosition> placedWordPositionList = new List <WordPosition>();

            while (shuffledList.Count > 0)
            {
                List <string> placedWords = new List <string>();
                foreach (string word in shuffledList)
                {
                    WordPosition wordPosition = PlaceWord(word);
                    if (wordPosition != null)
                    {
                        placedWords.Add(word);
                        placedWordPositionList.Add(wordPosition);
                        //Debug.WriteLine($"Placed {placedWordPositionList.Count}/{wordsToAddList.Count}: {wordPosition.Word}");
                    }
                }
                // If at the end of this loop no canonizedWord has been placed, we have a problem...
                if (placedWords.Count == 0)
                {
                    if (withLayoutBackup)
                    {
                        Layout = backupLayout;                        // Restore initial layout
                    }
                    return(null);
                }
                // On the other hand, if pass was successful, remove all placed words and go for a new pass
                foreach (string placedWord in placedWords)
                {
                    shuffledList.Remove(placedWord);
                }
            }

            return(placedWordPositionList);
        }
Exemple #5
0
        // Try to place a word in current layout, following rules of puzzle layout
        // If withTooClose if false, a too close condition returns Invalid
        // Part of public interface
        public PlaceWordStatus CanPlaceWord(WordPosition wordPosition, bool withTooClose)
        {
            if (wordPosition == null)
            {
                throw new ArgumentNullException(nameof(wordPosition));
            }

            return(wordPosition.IsVertical ? CanPlaceVerticalWord(wordPosition, withTooClose)
                                           : CanPlaceHorizontalWord(wordPosition, withTooClose));
        }
Exemple #6
0
        // Low-level removal function, public
        public void RemoveWordPosition(WordPosition wordPosition)
        {
            if (wordPosition == null)
            {
                throw new ArgumentNullException(nameof(wordPosition));
            }
            if (!m_WordPositionList.Contains(wordPosition))
            {
                throw new ArgumentException("WordPosition not in the layout");
            }

            RemoveSquares(wordPosition);
            m_WordPositionList.Remove(wordPosition);
        }
Exemple #7
0
        // Core function, adds a canonizedWord to current layout and place it
        // Returns WordPosition of placed word, or null if the canonizedWord couldn't be placed
        private WordPosition PlaceWord(string originalWord)
        {
            var l = FindWordPossiblePlacements(originalWord, PlaceWordOptimization.High);

            if (l == null || l.Count == 0)
            {
                return(null);
            }

            WordPosition wordPosition = l.TakeRandom();

            Layout.AddWordPositionNoCheck(wordPosition);
            return(wordPosition);
        }
Exemple #8
0
        // Safe version, public
        public PlaceWordStatus AddWordPosition(WordPosition wordPosition)
        {
            if (wordPosition == null)
            {
                throw new ArgumentNullException(nameof(wordPosition));
            }
            if (m_WordPositionList.Contains(wordPosition))
            {
                throw new ArgumentException("WordPosition already in the layout");
            }

            var res = CanPlaceWord(wordPosition, true);

            if (res != PlaceWordStatus.Invalid)
            {
                AddWordPositionNoCheck(wordPosition);
            }
            return(res);
        }
Exemple #9
0
 // Return layout bounds extended with a WordPosition added
 // Don't use Position version of BoundingRectangle constructor, too slow
 internal static BoundingRectangle ExtendBounds(BoundingRectangle r, WordPosition wordPosition)
 {
     if (wordPosition.IsVertical)
     {
         return(new BoundingRectangle(
                    Math.Min(r.Min.Row, wordPosition.StartRow),
                    Math.Max(r.Max.Row, wordPosition.StartRow + wordPosition.Word.Length - 1),
                    Math.Min(r.Min.Column, wordPosition.StartColumn),
                    Math.Max(r.Max.Column, wordPosition.StartColumn)));
     }
     else
     {
         return(new BoundingRectangle(
                    Math.Min(r.Min.Row, wordPosition.StartRow),
                    Math.Max(r.Max.Row, wordPosition.StartRow),
                    Math.Min(r.Min.Column, wordPosition.StartColumn),
                    Math.Max(r.Max.Column, wordPosition.StartColumn + wordPosition.Word.Length - 1)));
     }
 }
Exemple #10
0
        // Private version, returns a list of WordPosition that wordPosition is connected to from wordPositionList
        private static List <WordPosition> GetConnectedWordPositions(WordPosition wordPosition, List <WordPosition> wordPositionList)
        {
            List <WordPosition>  tempList  = new List <WordPosition>(wordPositionList);
            Stack <WordPosition> toExamine = new Stack <WordPosition>();
            List <WordPosition>  connected = new List <WordPosition>();

            toExamine.Push(wordPosition);
            if (tempList.Contains(wordPosition))
            {
                tempList.Remove(wordPosition);
            }

            while (toExamine.Count > 0)
            {
                WordPosition w1 = toExamine.Pop();
                if (!connected.Contains(w1))
                {
                    if (wordPosition != w1)
                    {
                        connected.Add(w1);
                    }
                    if (tempList.Contains(w1))
                    {
                        tempList.Remove(w1);
                    }
                    foreach (var w2 in tempList)
                    {
                        if (DoWordsIntersect(w1, w2) && !connected.Contains(w2))
                        {
                            toExamine.Push(w2);
                        }
                    }
                }
            }

            return(connected);
        }
Exemple #11
0
 // Low-level function to add a WordPosition to Layout, do not check that placement is correct
 public void AddWordPositionNoCheck(WordPosition wordPosition)
 {
     m_WordPositionList.Add(wordPosition);
     AddSquares(wordPosition);
 }
Exemple #12
0
        // Returns true if the two WordPosition intersect
        private static bool DoWordsIntersect(WordPosition word1, WordPosition word2)
        {
            if (word1.IsVertical && word2.IsVertical)
            {
                // Both vertical, different column: no problem
                if (word1.StartColumn != word2.StartColumn)
                {
                    return(false);
                }

                // On the same column, check that one row ends before the other starts
                // ReSharper disable once ConvertIfStatementToReturnStatement
                if (word1.StartRow + word1.Word.Length - 1 < word2.StartRow ||
                    word2.StartRow + word2.Word.Length - 1 < word1.StartRow)
                {
                    return(false);
                }

                // They overlap, it's not really an intersection but still count as one
                return(true);
            }

            if (!word1.IsVertical && !word2.IsVertical)
            {
                // Both horizontal, different row, no problem
                if (word1.StartRow != word2.StartRow)
                {
                    return(false);
                }

                // On the same row, check that one column ends before the other starts
                // ReSharper disable once ConvertIfStatementToReturnStatement
                if (word1.StartColumn + word1.Word.Length - 1 < word2.StartColumn ||
                    word2.StartColumn + word2.Word.Length - 1 < word1.StartColumn)
                {
                    return(false);
                }

                // Overlap of two horizontal words
                return(true);
            }

            if (!word1.IsVertical && word2.IsVertical)
            {
                // word1 horizontal, word2 vertical
                // if word2 column does not overlap with word1 columns, no problem
                if (word2.StartColumn < word1.StartColumn || word2.StartColumn > word1.StartColumn + word1.Word.Length - 1)
                {
                    return(false);
                }

                // If word2 rows do now overlap with word1 row, no problem
                // ReSharper disable once ConvertIfStatementToReturnStatement
                if (word1.StartRow < word2.StartRow || word1.StartRow > word2.StartRow + word2.Word.Length - 1)
                {
                    return(false);
                }

                // Otherwise we have an intersection
                return(true);
            }

            {
                // word1 vertical, word2 horizontal
                // if word1 column does not overlap with word2 columns, no problem
                if (word1.StartColumn < word2.StartColumn || word1.StartColumn > word2.StartColumn + word2.Word.Length - 1)
                {
                    return(false);
                }

                // If word1 rows do now overlap with word2 row, no problem
                // ReSharper disable once ConvertIfStatementToReturnStatement
                if (word2.StartRow < word1.StartRow || word2.StartRow > word1.StartRow + word1.Word.Length - 1)
                {
                    return(false);
                }

                // Otherwise we have an intersection
                return(true);
            }
        }
Exemple #13
0
 // Public version, returns words connected to wordPosition (not including wordPosition) from current layout
 public IEnumerable <WordPosition> GetConnectedWordPositions(WordPosition wordPosition)
 {
     return(GetConnectedWordPositions(wordPosition, m_WordPositionList));
 }
Exemple #14
0
        // Helper to reduce cyclomatic complexity
        private PlaceWordStatus CanPlaceHorizontalWord(WordPosition wordPosition, bool withTooClose)
        {
            PlaceWordStatus result = PlaceWordStatus.Valid;
            int             row    = wordPosition.StartRow;
            int             column = wordPosition.StartColumn;

            // Free cell left
            if (IsOccupiedSquare(row, column - 1))
            {
                if (withTooClose)
                {
                    result = PlaceWordStatus.TooClose;
                }
                else
                {
                    return(PlaceWordStatus.Invalid);
                }
            }
            // Free cell right
            if (IsOccupiedSquare(row, column + wordPosition.Word.Length))
            {
                if (withTooClose)
                {
                    result = PlaceWordStatus.TooClose;
                }
                else
                {
                    return(PlaceWordStatus.Invalid);
                }
            }

            for (int i = 0; i < wordPosition.Word.Length; i++)
            {
                char l = GetLetter(row, column + i);
                if (l == wordPosition.Word[i])
                {
                    // It's OK to already have a matching letter only if it only belongs to a crossing word (of opposite direction)
                    foreach (WordPosition loop in GetWordPositionsFromSquare(row, column + i))
                    {
                        if (loop.IsVertical == wordPosition.IsVertical)
                        {
                            return(PlaceWordStatus.Invalid);
                        }
                    }
                }
                else
                {
                    // We need an empty cell for this letter
                    if (l != '\0')
                    {
                        return(PlaceWordStatus.Invalid);
                    }

                    // We need a free cell above and below, or else we're too close
                    if (IsOccupiedSquare(row - 1, column + i) || IsOccupiedSquare(row + 1, column + i))
                    {
                        if (withTooClose)
                        {
                            result = PlaceWordStatus.TooClose;
                        }
                        else
                        {
                            return(PlaceWordStatus.Invalid);
                        }
                    }
                }
            }

            return(result);
        }
Exemple #15
0
        /// <summary>Find all the ways to add WordToPlace to placedWord, iterator returning an enumeration of matching WordPosition</summary>
        /// <param name="canonizedWordToPlace">Canonized form of Word to place (ex: NON·SEQUITUR)</param>
        /// <param name="originalWordToPlace">Original form of Word to place (ex: Non Sequitur)</param>
        /// <param name="placedWord">WordPosition to connect to</param>
        private IEnumerable <WordPosition> TryPlace(string canonizedWordToPlace, string originalWordToPlace, WordPosition placedWord)
        {
            // Build a dictionary of (letter, count) for each canonizedWord
            List <char> wordToPlaceLetters = BreakLetters(canonizedWordToPlace).Shuffle();
            List <char> placedWordLetters  = BreakLetters(placedWord.Word);

            // Internal helper function, returns a dictionary of (letter, count) for canonizedWord w
            List <char> BreakLetters(string w)
            {
                var set = new HashSet <char>();

                foreach (char letter in w)
                {
                    if (!set.Contains(letter))
                    {
                        set.Add(letter);
                    }
                }
                return(set.ToList());
            }

            // For each letter of canonizedWordToPlace, look if placedWord contains this letter at least once
            foreach (char letter in wordToPlaceLetters)
            {
                if (placedWordLetters.Contains(letter))
                {
                    // Matching letter!

                    // Helper, returns a list of positions of letter (external variable) in canonizedWord
                    List <int> FindPositions(string word)
                    {
                        var list = new List <int>(3);
                        int p    = 0;

                        for (; ;)
                        {
                            int q = word.IndexOf(letter, p);
                            if (q < 0)
                            {
                                break;
                            }
                            list.Add(q);
                            p = q + 1;
                        }
                        return(list);
                    }

                    // Look for all possible combinations of letter in both words if it's possible to place canonizedWordToPlace.
                    foreach (int positionInWordToPlace in FindPositions(canonizedWordToPlace))              // positionsInWordToPlace)
                    {
                        foreach (int positionInPlacedWord in FindPositions(placedWord.Word))                // positionsInPlacedWord)
                        {
                            WordPosition test = new WordPosition(canonizedWordToPlace, originalWordToPlace,
                                                                 new PositionOrientation(
                                                                     placedWord.IsVertical
                                        ? placedWord.StartRow + positionInPlacedWord
                                        : placedWord.StartRow - positionInWordToPlace,
                                                                     placedWord.IsVertical
                                        ? placedWord.StartColumn - positionInWordToPlace
                                        : placedWord.StartColumn + positionInPlacedWord,
                                                                     !placedWord.IsVertical));
                            if (Layout.CanPlaceWord(test, false) == PlaceWordStatus.Valid)
                            {
                                yield return(test);
                            }
                        }
                    }
                }
            }
            yield break;
        }
Exemple #16
0
        private IList <WordPosition> FindWordPossiblePlacements(string originalWord, PlaceWordOptimization optimization)
        {
            string canonizedWord = CanonizeWord(originalWord);

            // If it's the first canonizedWord of the layout, chose random orientation and place it at position (0, 0)
            if (Layout.WordPositionList.Count == 0)
            {
                WordPosition wordPosition = new WordPosition(canonizedWord, originalWord, new PositionOrientation(0, 0, rnd.NextDouble() > 0.5));
                return(new List <WordPosition> {
                    wordPosition
                });
            }

            // Get current layout since we'll prefer placements that minimize layout extension to keep words grouped
            BoundingRectangle r = Layout.Bounds;
            int surface         = ComputeAdjustedSurface(r.Max.Column - r.Min.Column + 1, r.Max.Row - r.Min.Row + 1);

            // Find first all positions where the canonizedWord can be added to current layout;
            List <WordPosition> possibleWordPositions = new List <WordPosition>();
            List <WordPosition> possibleWordPositionsBelowThreshold = new List <WordPosition>();
            int minSurface = int.MaxValue;

            foreach (WordPosition wordPosition in Layout.WordPositionList)
            {
                foreach (WordPosition placedWordPosition in TryPlace(canonizedWord, originalWord, wordPosition))
                {
                    BoundingRectangle newR = WordPositionLayout.ExtendBounds(r, placedWordPosition);
                    if (newR.Equals(r))
                    {
                        if (optimization == PlaceWordOptimization.Aggressive)
                        {
                            return new List <WordPosition> {
                                       placedWordPosition
                            }
                        }
                        ;
                        if (optimization == PlaceWordOptimization.High)
                        {
                            possibleWordPositionsBelowThreshold.Add(placedWordPosition);
                        }
                    }
                    int newSurface = ComputeAdjustedSurface(newR.Max.Column - newR.Min.Column + 1, newR.Max.Row - newR.Min.Row + 1);
                    possibleWordPositions.Add(placedWordPosition);
                    switch (optimization)
                    {
                    case PlaceWordOptimization.Aggressive:
                    case PlaceWordOptimization.High:
                        if (possibleWordPositions.Count > 0 && minSurface > newSurface)
                        {
                            possibleWordPositions.RemoveAt(0);
                            minSurface = newSurface;
                        }
                        if (possibleWordPositions.Count == 0)
                        {
                            possibleWordPositions.Add(placedWordPosition);
                        }
                        break;

                    case PlaceWordOptimization.Standard:
                        if (newSurface < surface * 1.15)
                        {
                            possibleWordPositionsBelowThreshold.Add(placedWordPosition);
                        }
                        possibleWordPositions.Add(placedWordPosition);
                        break;

                    default:
                        possibleWordPositions.Add(placedWordPosition);
                        break;
                    }
                }
            }

            if (possibleWordPositionsBelowThreshold.Count > 0)
            {
                return(possibleWordPositionsBelowThreshold);
            }
            return(possibleWordPositions);
        }