/// <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); }
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); }
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); }
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); }