// ---------------- // UPDATING METHODS // ---------------- public async Task SaveActivitiesAsync(ITurnContext turnContext, IEnumerable <Activity> activities, CancellationToken cancellationToken = default) { BotAssert.ContextNotNull(turnContext); if (activities is null) { throw new ArgumentNullException(nameof(activities)); } var state = await GetStateAsync(turnContext, cancellationToken).ConfigureAwait(false); foreach (var activity in activities) { if (activity.Id != null) { // Remove any activities with a matching ID so that a duplicate isn't saved when updating await UnsaveActivityAsync(turnContext, activity.Id, cancellationToken).ConfigureAwait(false); } if (CardTree.GetIds(activity, TreeNodeType.Activity).Any()) { state.SavedActivities.Add(activity); } } await StateAccessor.SetAsync(turnContext, state, cancellationToken).ConfigureAwait(false); }
private async Task UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken) { var ignoreUpdate = turnContext.TurnState.Get <CardManagerTurnState>()?.MiddlewareIgnoreUpdate; ignoreUpdate?.Add(activity); try { await turnContext.UpdateActivityAsync(activity, cancellationToken).ConfigureAwait(false); } catch { // TODO: Find out what exceptions I need to handle throw; } finally { ignoreUpdate?.Remove(activity); } if (!CardTree.GetIds(activity, TreeNodeType.Activity).Any()) { await RemoveActivityAsync(turnContext, activity, cancellationToken).ConfigureAwait(false); } }
public static void AdaptOutgoingCardActions(this IEnumerable <IMessageActivity> activities, string channelId = null) { if (activities is null) { throw new ArgumentNullException(nameof(activities)); } foreach (var activity in activities) { var activityChannelId = channelId ?? activity.ChannelId; CardTree.Recurse( activity, (CardAction action) => { var text = action.Text; var value = action.Value; void EnsureText() { if (text == null) { action.Text = value.SerializeIfNeeded(); } } void EnsureValue() { if (value == null) { action.Value = text; } } void EnsureStringValue() { if (!(value is string)) { if (value == null && text != null) { action.Value = text; } else { action.Value = value.SerializeIfNeeded(); } } } void EnsureObjectValue() { // Check if value is null or otherwise primitive if (value.ToJObject() is null) { if (value is string stringValue && stringValue.TryParseJObject() is JObject parsedValue) { action.Value = parsedValue; }
public static ISet <DataId> GetIdsFromBatch(this IEnumerable <IMessageActivity> activities) { if (activities is null) { throw new ArgumentNullException(nameof(activities)); } return(CardTree.GetIds(activities, TreeNodeType.Batch)); }
private CardDecision getCardDecision(RpsAgent otherPlayer) { List <Card> otherMoves = otherPlayer.GetMoveSequence(); CardTree cardTree = getCardTree(otherMoves); // Look up the most likely move based on N-gram tree // Tries to do N-gram first, if not enough trials, decreases N and tries again (until N == 1) for (int nGramN = nGramAmount; nGramAmount >= 1; nGramAmount--) { Queue <Card> lastNMoves = new Queue <Card>(); for (int i = Mathf.Max(otherMoves.Count - nGramN, 0); i < otherMoves.Count; i++) { lastNMoves.Enqueue(otherMoves[i]); } CardTree cardTreeNode = cardTree; while (lastNMoves.Count > 0) { cardTreeNode = cardTreeNode.Traverse(lastNMoves.Dequeue()); } if (cardTreeNode.totalCount < minNGramSamplesNeeded) { continue; } Card likelyCard = cardTreeNode.MostLikelyCard(); Dictionary <Card, int> opponentCardCounts = otherPlayer.getCardCounts(); if (likelyCard == Card.Rock) { if (opponentCardCounts[Card.Paper] == 0 && opponentCardCounts[Card.Scissors] == 0) { return(CardDecision.DefiniteRock); } return(CardDecision.ProbableRock); } else if (likelyCard == Card.Paper) { if (opponentCardCounts[Card.Scissors] == 0 && opponentCardCounts[Card.Rock] == 0) { return(CardDecision.DefinitePaper); } return(CardDecision.ProbablePaper); } else if (likelyCard == Card.Scissors) { if (opponentCardCounts[Card.Rock] == 0 && opponentCardCounts[Card.Paper] == 0) { return(CardDecision.DefiniteScissors); } return(CardDecision.ProbableScissors); } } return(CardDecision.Random); }
private static T Set <T>( T entryValue, string behaviorName, object behaviorValue, TreeNodeType entryType) where T : class { if (behaviorName is null) { throw new ArgumentNullException(nameof(behaviorName)); } var jToken = behaviorValue is null ? null : JToken.FromObject(behaviorValue); return(CardTree.SetLibraryData(entryValue, new JObject { { behaviorName, jToken } }, entryType, true)); }
/// <summary> /// This will convert Adaptive Cards to JObject instances to work around this issue: /// https://github.com/microsoft/AdaptiveCards/issues/2148. /// </summary> /// <param name="activities">A batch of activities.</param> public static void ConvertAdaptiveCards(this IEnumerable <IMessageActivity> activities) { if (activities is null) { throw new ArgumentNullException(nameof(activities)); } CardTree.Recurse( activities, (Attachment attachment) => { if (attachment.ContentType == ContentTypes.AdaptiveCard) { attachment.Content = attachment.Content.ToJObject(); } }, TreeNodeType.Batch, TreeNodeType.Attachment); }
public void Add(Card c) { switch (c) { case Card.Rock: if (rockTree == null) { rockTree = new CardTree(1); } else { rockTree.count++; } break; case Card.Paper: if (paperTree == null) { paperTree = new CardTree(1); } else { paperTree.count++; } break; case Card.Scissors: if (scissorsTree == null) { scissorsTree = new CardTree(1); } else { scissorsTree.count++; } break; } }
private CardTree getCardTree(List <Card> otherMoves) { /* Constructs a prefix tree for looking up N-grams, since the number of possible * N-move sequences is 3^N * * The prefix tree actually stores all the information needed for 1 to N grams */ CardTree cardTreeHead = new CardTree(-1); CardTree cardTreeNode; for (int i = 0; i < otherMoves.Count; i++) { cardTreeNode = cardTreeHead; for (int j = i; j < Mathf.Min(otherMoves.Count, i + nGramAmount + 1); j++) { cardTreeNode.Add(otherMoves[j]); cardTreeNode = cardTreeNode.Traverse(otherMoves[j]); } } return(cardTreeHead); }
public static void SetInAdaptiveCard(ref object card, DataIdOptions options = null) => card = CardTree.ApplyIds(card, options, TreeNodeType.AdaptiveCard);
public static void SetInActionData(ref object data, DataIdOptions options = null) => data = CardTree.ApplyIds(data, options, TreeNodeType.ActionData);
public static void SetInCardAction(CardAction action, DataIdOptions options = null) => CardTree.ApplyIds(action, options, TreeNodeType.CardAction);
public static void SetInSubmitAction(ref object action, DataIdOptions options = null) => action = CardTree.ApplyIds(action, options, TreeNodeType.SubmitAction);
public static void SetInVideoCard(VideoCard card, DataIdOptions options = null) => CardTree.ApplyIds(card, options, TreeNodeType.VideoCard);
public static void SetInThumbnailCard(ThumbnailCard card, DataIdOptions options = null) => CardTree.ApplyIds(card, options, TreeNodeType.ThumbnailCard);
public static void SetInSigninCard(SigninCard card, DataIdOptions options = null) => CardTree.ApplyIds(card, options, TreeNodeType.SigninCard);
public static void SetInAttachment(Attachment attachment, DataIdOptions options = null) => CardTree.ApplyIds(attachment, options, TreeNodeType.Attachment);
public static void SetInCarousel(IEnumerable <Attachment> carousel, DataIdOptions options = null) => CardTree.ApplyIds(carousel, options, TreeNodeType.Carousel);
public static void SetInActivity(IMessageActivity activity, DataIdOptions options = null) => CardTree.ApplyIds(activity, options, TreeNodeType.Activity);
public static void SetInBatch(IEnumerable <IMessageActivity> batch, DataIdOptions options = null) => CardTree.ApplyIds(batch, options, TreeNodeType.Batch);
public async Task DeleteActionSourceAsync(ITurnContext turnContext, string dataIdScope, CancellationToken cancellationToken = default) { // TODO: Provide a way to delete elements by specifying an ID that's not in the incoming action data BotAssert.ContextNotNull(turnContext); if (string.IsNullOrEmpty(dataIdScope)) { throw new ArgumentNullException(nameof(dataIdScope)); } var state = await GetStateAsync(turnContext, cancellationToken).ConfigureAwait(false); if (dataIdScope == DataIdScopes.Batch) { if (turnContext.GetIncomingActionData().ToJObject().GetIdFromActionData(DataIdScopes.Batch) is string batchId) { var toDelete = new DataId(DataIdScopes.Batch, batchId); // Iterate over a copy of the set so the original can be modified foreach (var activity in state.SavedActivities.ToList()) { // Delete any activity that contains the specified batch ID (data items are compared by value) if (CardTree.GetIds(activity, TreeNodeType.Activity).Any(toDelete.Equals)) { await DeleteActivityAsync(turnContext, activity, cancellationToken).ConfigureAwait(false); } } } } else { var matchResult = await GetDataMatchAsync(turnContext, cancellationToken).ConfigureAwait(false); var matchedActivity = matchResult.SavedActivity; var matchedAttachment = matchResult.SavedAttachment; var matchedAction = matchResult.SavedAction; var shouldUpdateActivity = false; // TODO: Provide options for how to determine emptiness when cascading deletion // (e.g. when a card has no more actions rather than only when the card is completely empty) if (dataIdScope == DataIdScopes.Action && matchedActivity != null && matchedAttachment != null && matchedAction != null) { if (matchedAttachment.ContentType.EqualsCI(ContentTypes.AdaptiveCard)) { // For Adaptive Cards if (matchedAction is JObject savedSubmitAction) { var adaptiveCard = (JObject)savedSubmitAction.Root; // Remove the submit action from the Adaptive Card. // SafeRemove will work whether the action is in an array // or is the value of a select action property. savedSubmitAction.SafeRemove(); matchedAttachment.Content = matchedAttachment.Content.FromJObject(adaptiveCard); shouldUpdateActivity = true; // Check if the Adaptive Card is now empty if (adaptiveCard.GetValue(AdaptiveProperties.Body).IsNullishOrEmpty() && adaptiveCard.GetValue(AdaptiveProperties.Actions).IsNullishOrEmpty()) { // If the card is now empty, execute the next if block to delete it dataIdScope = DataIdScopes.Card; } } } else { // For Bot Framework rich cards if (matchedAction is CardAction cardAction) { // Remove the card action from the card CardTree.Recurse( matchedAttachment, (IList <CardAction> actions) => { actions.Remove(cardAction); }, TreeNodeType.Attachment, TreeNodeType.CardActionList); shouldUpdateActivity = true; // Check if the card is now empty. // We are assuming that if a developer wants to make a rich card // with only postBack/messageBack buttons then they will use a hero card // and any other card would have more content than just postBack/messageBack buttons // so only a hero card should potentially be empty at this point. // We aren't checking if Buttons is null because it can't be at this point. if (matchedAttachment.Content is HeroCard heroCard && !heroCard.Buttons.Any() && heroCard.Images?.Any() != true && string.IsNullOrWhiteSpace(heroCard.Title) && string.IsNullOrWhiteSpace(heroCard.Subtitle) && string.IsNullOrWhiteSpace(heroCard.Text)) { // If the card is now empty, execute the next if block to delete it dataIdScope = DataIdScopes.Card; } } } } if (dataIdScope == DataIdScopes.Card && matchedActivity != null && matchedAttachment != null) { matchedActivity.Attachments.Remove(matchedAttachment); shouldUpdateActivity = true; // Check if the activity is now empty if (string.IsNullOrWhiteSpace(matchedActivity.Text) && !matchedActivity.Attachments.Any()) { // If the activity is now empty, execute the next if block to delete it dataIdScope = DataIdScopes.Carousel; } } if (dataIdScope == DataIdScopes.Carousel && matchedActivity != null) { await DeleteActivityAsync(turnContext, matchedActivity, cancellationToken).ConfigureAwait(false); } else if (shouldUpdateActivity) { await UpdateActivityAsync(turnContext, matchedActivity, cancellationToken).ConfigureAwait(false); } } await StateAccessor.SetAsync(turnContext, state, cancellationToken).ConfigureAwait(false); }
public static void SetInOAuthCard(OAuthCard card, DataIdOptions options = null) => CardTree.ApplyIds(card, options, TreeNodeType.OAuthCard);
private async Task <DataMatchResult> GetDataMatchAsync( ITurnContext turnContext, CancellationToken cancellationToken = default) { var result = new DataMatchResult(); if (!(turnContext.GetIncomingActionData() is JObject incomingData)) { return(result); } var state = await GetStateAsync(turnContext, cancellationToken).ConfigureAwait(false); var couldBeFromAdaptiveCard = turnContext.Activity.Value.ToJObject() is JObject; // Iterate over all saved activities that contain any of the action data ID's from the incoming action data foreach (var savedActivity in state.SavedActivities .Where(activity => activity?.Attachments?.Any() == true)) { foreach (var savedAttachment in savedActivity.Attachments.WhereNotNull()) { if (savedAttachment.ContentType.EqualsCI(ContentTypes.AdaptiveCard)) { if (couldBeFromAdaptiveCard && savedAttachment.Content.ToJObject() is JObject savedAdaptiveCard) { // For Adaptive Cards we need to check the inputs var inputsMatch = true; var dataWithoutInputValues = incomingData.DeepClone() as JObject; // First, determine what matching submit action data is expected to look like // by taking the incoming data and removing the values associated with // the inputs found in the Adaptive Card foreach (var inputId in AdaptiveCardUtil.GetAdaptiveInputs(savedAdaptiveCard) .Select(AdaptiveCardUtil.GetAdaptiveInputId)) { // If the Adaptive Card is poorly designed, // the same input ID might show up multiple times. // Therefore we're checking if the original incoming data // contained the ID, because the inputs might still // match even if this input was already removed. if (incomingData.ContainsKey(inputId)) { // Removing a property that doesn't exist // will not throw an exception dataWithoutInputValues.Remove(inputId); } else { inputsMatch = false; break; } } // Second, if all the input ID's found in the card were present in the incoming data // then check each submit action in the card to see if its data matches the incoming data if (inputsMatch) { CardTree.Recurse( savedAdaptiveCard, (JObject savedSubmitAction) => { var submitActionData = savedSubmitAction.GetValue( AdaptiveProperties.Data) ?? new JObject(); if (JToken.DeepEquals(submitActionData, dataWithoutInputValues)) { result.Add(savedActivity, savedAttachment, savedSubmitAction); } }, TreeNodeType.AdaptiveCard, TreeNodeType.SubmitAction); } } } else { // For Bot Framework cards that are not Adaptive Cards CardTree.Recurse( savedAttachment, (CardAction savedCardAction) => { var savedData = savedCardAction.Value.ToJObject(true); // This will not throw an exception if the saved action data is null if (JToken.DeepEquals(savedData, incomingData)) { result.Add(savedActivity, savedAttachment, savedCardAction); } }, TreeNodeType.Attachment, TreeNodeType.CardAction); } } } return(result); }
public static void SetInReceiptCard(ReceiptCard card, DataIdOptions options = null) => CardTree.ApplyIds(card, options, TreeNodeType.ReceiptCard);