Пример #1
0
        /// <summary>
        /// Pick a random item to press.
        /// </summary>
        /// <param name="model">The model to act against.</param>
        /// <param name="random">The source of randomness.</param>
        /// <returns>A valid but random action to take.</returns>
        /// <remarks>Current implementations assumes child suggestions lists are finite.</remarks>
        public static ApplicationRobotAction GetRandom(ApplicationModel model, Random random)
        {
            ApplicationRobotAction action;

            var headCount        = model.HeadItems.Count;
            var tailCount        = model.TailItems.Count;
            var suggestionsCount = 0;

            foreach (var subList in model.SuggestionLists)
            {
                var subListCount = subList.Count();

                // TODO: CommandItem should not be excluded from randomness!
                if (subList.First() is CommandItem)
                {
                    suggestionsCount -= subListCount;
                }
                else
                {
                    suggestionsCount += subListCount;
                }
            }

            var index = random.Next(0, suggestionsCount);

            if (index < headCount)
            {
                action = ApplicationRobotAction.CreateHead(index);
            }
            else if (index < headCount + tailCount)
            {
                action = ApplicationRobotAction.CreateTail(index - headCount);
            }
            else
            {
                var subIndex = index - headCount - tailCount;
                index = 0;
                while (model.SuggestionLists[index].First() is CommandItem)
                {
                    index++;
                }
                while (model.SuggestionLists[index].Count() <= subIndex)
                {
                    subIndex -= model.SuggestionLists[index].Count();
                    index++;
                }

                using (var enumerator = model.SuggestionLists[index].GetEnumerator())
                {
                    for (var position = 0; enumerator.MoveNext() && position < index; position++)
                    {
                    }

                    action = ApplicationRobotAction.CreateSuggestion(index, subIndex);
                    AssertGoodAction(model, action);
                }
            }

            return(action);
        }
Пример #2
0
        private static ApplicationRobotAction CreateExtendedSuggestedWordAction(ApplicationModel model,
                                                                                bool complete,
                                                                                TileSequence words,
                                                                                int wordsMatchLim,
                                                                                int index,
                                                                                int subIndex,
                                                                                CultureInfo culture)
        {
            ApplicationRobotAction action;

            var list = model.SuggestionLists[index];

            using (var enumerator = list.GetEnumerator())
            {
                for (var i = 0; i < subIndex; i++)
                {
                    var moved = enumerator.MoveNext();
                    Debug.Assert(moved);
                }

                // See what words match exactly.
                var subLim = 0;
                while (enumerator.MoveNext() &&
                       wordsMatchLim + subLim < words.Count &&
                       !(enumerator.Current is ExtendedSuggestedWordItem) &&
                       IsItemMatchExact <SuggestedWordItem>(enumerator.Current, words[wordsMatchLim + subLim], culture))
                {
                    subLim++;
                }

                if (complete &&
                    wordsMatchLim + subLim == words.Count &&
                    enumerator.Current is TailStopItem)
                {
                    // We can complete the action.
                    action = ApplicationRobotAction.CreateSuggestionAndComplete(index, subIndex + subLim);
                }
                else
                {
                    if (wordsMatchLim + subLim < words.Count &&
                        !(enumerator.Current is ExtendedSuggestedWordItem) &&
                        IsItemMatch <SuggestedWordItem>(enumerator.Current, words[wordsMatchLim + subLim], culture))
                    {
                        subLim++;
                    }

                    // We can advance a word or more.
                    action = ApplicationRobotAction.CreateSuggestion(index, subIndex + subLim);
                    AssertGoodAction(model, action);
                }
            }

            return(action);
        }
Пример #3
0
        private static ApplicationRobotAction GetModeEscape(ApplicationModel model)
        {
            Debug.Assert(model.HeadItems[0] is HeadStartItem);

            var escapeLimit = 1;

            while (escapeLimit < model.HeadItems.Count &&
                   model.HeadItems[escapeLimit] is HeadWordItem)
            {
                escapeLimit++;
            }

            var action = ApplicationRobotAction.CreateHead(escapeLimit - 1);

            return(action);
        }
Пример #4
0
        /// <summary>
        /// Get the next action to achieve the given goal.
        /// </summary>
        /// <param name="model">The model to act against.</param>
        /// <param name="complete">Finish by presssing a Stop button and returning IsComplete true in the action, otherwise don't press Stop button and return null.</param>
        /// <param name="words">The words to be spoken.</param>
        /// <returns>The next action to take.</returns>
        private static ApplicationRobotAction FindNextAction(ApplicationModel model, bool complete, TileSequence words)
        {
            ApplicationRobotAction action;

            Debug.Assert(model.HeadItems[0] is HeadStartItem);

            var culture = model.HeadItems[0].Culture;

            // Find number of words already correctly entered.
            var wordsMatchLim = 0;

            while (wordsMatchLim < words.Count &&
                   wordsMatchLim + 1 < model.HeadItems.Count &&
                   IsItemMatchExact <HeadWordItem>(model.HeadItems[wordsMatchLim + 1], words[wordsMatchLim], culture))
            {
                wordsMatchLim++;
            }

            if (wordsMatchLim < words.Count &&
                wordsMatchLim + 1 < model.HeadItems.Count &&
                IsItemMatch <HeadWordItem>(model.HeadItems[wordsMatchLim + 1], words[wordsMatchLim], culture))
            {
                if (wordsMatchLim + 2 < model.HeadItems.Count && model.HeadItems[wordsMatchLim + 2] is HeadWordItem)
                {
                    // Make the miscased item the last current item.
                    action = ApplicationRobotAction.CreateHead(wordsMatchLim + 1);
                }
                else
                {
                    var headWordItem = (HeadWordItem)model.HeadItems[wordsMatchLim + 1];
                    action = GetCaseWordAction(model, headWordItem.Tile, words[wordsMatchLim], culture);
                }
            }
            else if (wordsMatchLim + 1 < model.HeadItems.Count &&
                     model.HeadItems[wordsMatchLim + 1] is HeadWordItem)
            {
                // We have a word we don't want, so truncate.
                action = ApplicationRobotAction.CreateHead(wordsMatchLim);
            }
            else if (wordsMatchLim == words.Count)
            {
                // We have all the words in the head we want.
                if (!complete)
                {
                    // We're done.
                    action = null;
                }
                else if (wordsMatchLim + 1 < model.HeadItems.Count &&
                         model.HeadItems[wordsMatchLim + 1] is GhostStopItem)
                {
                    // Click item in head to complete selection.
                    action = ApplicationRobotAction.CreateHeadAndComplete(wordsMatchLim + 1);
                }
                else
                {
                    // Click in tail to complete selection.

                    Debug.Assert(model.HeadItems.Count <= wordsMatchLim + 1 ||
                                 model.HeadItems[wordsMatchLim + 1] is GhostWordItem);
                    Debug.Assert(model.TailItems.Count == 1);
                    Debug.Assert(model.TailItems[0] is TailStopItem);

                    action = ApplicationRobotAction.CreateTailAndComplete(0);
                }
            }
            else
            {
                // See how many ghost items match.
                var ghostMatchLim = wordsMatchLim;
                while (ghostMatchLim < words.Count &&
                       ghostMatchLim + 1 < model.HeadItems.Count &&
                       IsItemMatchExact <GhostWordItem>(model.HeadItems[ghostMatchLim + 1], words[ghostMatchLim], culture))
                {
                    ghostMatchLim++;
                }

                // Have we got enough ghost words to complete?
                if (complete &&
                    ghostMatchLim == words.Count &&
                    ghostMatchLim + 1 < model.HeadItems.Count &&
                    model.HeadItems[ghostMatchLim + 1] is GhostStopItem)
                {
                    // Complete by clicking ghost tail item.
                    action = ApplicationRobotAction.CreateHeadAndComplete(ghostMatchLim + 1);
                }
                else
                {
                    // Click something in the suggestions to advance.
                    action = GetSuggestionsAction(model, complete, words, wordsMatchLim, culture);

                    if (!action.IsComplete && wordsMatchLim < ghostMatchLim)
                    {
                        if (action.Target != ApplicationRobotActionTarget.Suggestion ||
                            action.SubIndex <= ghostMatchLim - wordsMatchLim)
                        {
                            // See if we can snaffle one more ghost.
                            if (ghostMatchLim < words.Count &&
                                ghostMatchLim + 1 < model.HeadItems.Count &&
                                IsItemMatch <GhostWordItem>(model.HeadItems[ghostMatchLim + 1], words[ghostMatchLim], culture))
                            {
                                ghostMatchLim++;
                            }

                            // We would have been no better off than clicking in the ghost words, so do that.
                            action = ApplicationRobotAction.CreateHead(ghostMatchLim);
                        }
                    }
                }
            }

            return(action);
        }
Пример #5
0
        private static ApplicationRobotAction GetCaseWordAction(ApplicationModel model, TileData startWord, TileData targetWord, CultureInfo culture)
        {
            ApplicationRobotAction action;

            var firstOfFirst = model.SuggestionLists[0].First();

            if (IsItem <CommandItem>(firstOfFirst, out var commandItem))
            {
                switch (commandItem.Command)
                {
                case TileCommand.Typing:
                    action = ApplicationRobotAction.CreateSuggestion(0, 0);
                    break;

                default:
                    action = ApplicationRobotAction.CreateInterstitial(0);
                    break;
                }
            }
            else if (!(firstOfFirst is ReplacementItem))
            {
                Debug.Assert(model.SuggestionInterstitials[0] is InterstitialGapItem);
                action = ApplicationRobotAction.CreateInterstitial(0);
            }
            else
            {
                var startTile  = startWord;
                var targetTile = targetWord;

                var startMap  = WordCaseMap.Create(startTile.Content);
                var targetMap = WordCaseMap.Create(targetTile.Content);
                Debug.Assert(startMap.LetterCount == targetMap.LetterCount);

                var startDistance = targetMap.GetDistanceTo(startMap);
                if (targetTile.IsPrefix != startTile.IsPrefix)
                {
                    startDistance++;
                }
                if (targetTile.IsSuffix != startTile.IsSuffix)
                {
                    startDistance++;
                }

                var bestReplacementIndex    = -1;
                var bestReplacementDistance = startDistance;

                var index = 0;
                foreach (var list in model.SuggestionLists)
                {
                    var replacement   = (ReplacementItem)list.First();
                    var suggestedTile = TileData.FromTokenString(replacement.Content);
                    var suggestedMap  = WordCaseMap.Create(suggestedTile.Content);
                    Debug.Assert(suggestedMap.LetterCount == targetMap.LetterCount);

                    var suggestionDistance = targetMap.GetDistanceTo(suggestedMap);
                    if (targetTile.IsPrefix != suggestedTile.IsPrefix)
                    {
                        suggestionDistance++;
                    }
                    if (targetTile.IsSuffix != suggestedTile.IsSuffix)
                    {
                        suggestionDistance++;
                    }

                    if (suggestionDistance < bestReplacementDistance)
                    {
                        bestReplacementDistance = suggestionDistance;
                        bestReplacementIndex    = index;
                    }

                    index++;
                }

                if (bestReplacementIndex != -1)
                {
                    action = ApplicationRobotAction.CreateSuggestion(bestReplacementIndex, 0);
                }
                else
                {
                    Debug.Assert(model.SuggestionInterstitials[model.SuggestionInterstitials.Count - 1] is InterstitialGapItem);
                    action = ApplicationRobotAction.CreateInterstitial(model.SuggestionInterstitials.Count - 1);
                }
            }

            return(action);
        }
Пример #6
0
        private static ApplicationRobotAction GetSuggestionsAction(ApplicationModel model,
                                                                   bool complete,
                                                                   TileSequence words,
                                                                   int wordsMatchLim,
                                                                   CultureInfo culture)
        {
            ApplicationRobotAction action;

            var targetWord = words[wordsMatchLim];

            action = null;
            for (var index = 0; action == null && index < model.SuggestionLists.Count; index++)
            {
                var list      = model.SuggestionLists[index];
                var firstItem = list.First();

                if (IsItem <SuggestedWordItem>(firstItem, out var suggestedWordItem))
                {
                    var suggestedWord = suggestedWordItem.Tile.Content;

                    if (StringEquals(suggestedWord, targetWord.Content, culture))
                    {
                        // Found our word.
                        action = CreateSuggestedWordAction(model, complete, words, wordsMatchLim, index, culture);
                    }
                    else if (StringCompare(targetWord.Content, suggestedWord, culture) < 0)
                    {
                        // Need to step back.
                        action = ApplicationRobotAction.CreateInterstitial(index);
                    }
                    else
                    {
                        var subIndex = 1;
                        while (subIndex < list.Count &&
                               list[subIndex] is ExtendedSuggestedWordItem extensionWord &&
                               StringCompare(extensionWord.Content, targetWord.Content, culture) < 0)
                        {
                            subIndex++;
                        }

                        if (subIndex < list.Count &&
                            list[subIndex] is ExtendedSuggestedWordItem extensionWordFound &&
                            StringCompare(extensionWordFound.Content, targetWord.Content, culture) == 0)
                        {
                            action = CreateExtendedSuggestedWordAction(model, complete, words, wordsMatchLim, index, subIndex, culture);
                        }
                    }
                }
                else if (firstItem is SuggestedSpellingItem)
                {
                    var partial = firstItem.Content;

                    if (StartsWith(targetWord.Content, partial, culture))
                    {
                        var subIndex = 0;
                        using (var enumerator = list.GetEnumerator())
                        {
                            var more = enumerator.MoveNext();
                            Debug.Assert(more);
                            Debug.Assert(enumerator.Current is SuggestedSpellingItem);

                            more = enumerator.MoveNext();
                            while (more &&
                                   enumerator.Current is SuggestedSpellingSequenceItem &&
                                   StartsWith(targetWord.Content, enumerator.Current.Content, culture))
                            {
                                subIndex++;
                                more = enumerator.MoveNext();
                            }

                            if (complete &&
                                more &&
                                enumerator.Current is SuggestedWordItem &&
                                StringEquals(enumerator.Current.Content, targetWord.Content, culture))
                            {
                                action = ApplicationRobotAction.CreateSuggestion(index, subIndex + 1);
                                AssertGoodAction(model, action);
                            }
                            else
                            {
                                action = ApplicationRobotAction.CreateSuggestion(index, subIndex);
                            }
                        }
                    }
                    else if (!StartsWith(targetWord.Content, partial.Substring(0, partial.Length - 1), culture))
                    {
                        // Need to remove incorrectly spelled items.
                        if (targetWord.Content[0] != partial[0])
                        {
                            action = GetModeEscape(model);
                        }
                        else if (index != 0 && model.SuggestionLists[0].First() is SuggestedSpellingBackspaceItem)
                        {
                            action = ApplicationRobotAction.CreateSuggestion(0, 0);
                            AssertGoodAction(model, action);
                        }
                        else
                        {
                            // Need to erase unwanted letters, which means winding to top of spellings.
                            Debug.Assert(model.SuggestionLists[0].First() is InterstitialGapItem);
                            action = ApplicationRobotAction.CreateInterstitial(0);
                        }
                    }
                    else if (StringCompare(partial.Substring(partial.Length - 1, 1), targetWord.Content.Substring(partial.Length - 1, 1), culture) < 0)
                    {
                        // Look at next item.
                    }
                    else
                    {
                        action = ApplicationRobotAction.CreateInterstitial(index);
                    }
                }
                else if (firstItem is SuggestedSpellingWordItem)
                {
                    if (StringEquals(firstItem.Content, targetWord.Content, culture))
                    {
                        action = ApplicationRobotAction.CreateSuggestion(index, 0);
                        AssertGoodAction(model, action);
                    }
                }
                else if (firstItem is SuggestedSpellingBackspaceItem)
                {
                    if (!StartsWith(targetWord.Content, firstItem.Content, culture))
                    {
                        action = ApplicationRobotAction.CreateSuggestion(index, 0);
                    }
                }
                else if (firstItem is CommandItem)
                {
                    // TODO: We don't deal with these yet!
                }
                else if (firstItem is TailStopItem)
                {
                    // TODO: We don't deal with these, could use it or press the static stop!
                }
                else
                {
                    Debug.Assert(firstItem is SuggestedUnicodeItem);

                    var partial = firstItem.Content;

                    Debug.Assert(((SuggestedUnicodeItem)firstItem).Symbol.Length == 1);
                    Debug.Assert(((SuggestedUnicodeItem)firstItem).Symbol[0] == partial[partial.Length - 1]);

                    if (StartsWith(targetWord.Content, partial, culture))
                    {
                        action = ApplicationRobotAction.CreateSuggestion(index, 0);
                    }
                    else if (!StartsWith(targetWord.Content, partial.Substring(0, partial.Length - 1), culture))
                    {
                        action = GetModeEscape(model);
                    }
                    else if (partial[partial.Length - 1] < targetWord.Content[partial.Length - 1])
                    {
                        // Can just move along.
                    }
                    else
                    {
                        // Need to step back.
                        Debug.Assert(model.SuggestionInterstitials[index] is InterstitialGapItem);
                        action = ApplicationRobotAction.CreateInterstitial(index);
                    }
                }
            }

            if (action == null)
            {
                // Need to step forward.
                action = ApplicationRobotAction.CreateInterstitial(model.SuggestionLists.Count);
            }

            return(action);
        }
Пример #7
0
 private static void AssertGoodAction(ApplicationModel model, ApplicationRobotAction action)
 {
     Debug.Assert(action.Target != ApplicationRobotActionTarget.Suggestion || action.SubIndex != 0 ||
                  !(model.SuggestionLists[action.Index].First() is CommandItem));
 }