/// <summary>
        /// Creates an ensemble of searches based on the provided search function.
        /// </summary>
        /// <param name="context">The current search context.</param>
        /// <param name="searchFunction">The function that runs the actual search.</param>
        /// <param name="ensembleSize">The amount of searches to perform in the ensemble.</param>
        public void EnsembleSearch(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, Func <SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction>, SabberStoneAction> searchFunction, int ensembleSize)
        {
            var gameState          = context.Source;
            var rootStartedPlaying = gameState.Game.FirstPlayer.Id == gameState.CurrentPlayer();

            // What we want to do is first check if we know of any cards in the opponent's deck/hand/secret-zone (e.g. quests)
            // Those should not be replaced by random things
            // Create a list of the IDs of those known cards and then obfuscate the state while supplying our list of known cards
            // TODO Before obfuscation: try to use the played cards to determine if anything was revealed (which means we know about it and shouldn't hide it)

            // Determine known cards for root player
            var knownRootCards = new List <string>();

            // Cards currently in hand
            knownRootCards.AddRange(gameState.Game.CurrentPlayer.HandZone.Select(i => i.Card.Id));
            // Cards that have been played
            var playedRootCards = gameState.Game.CurrentPlayer.PlayHistory.Select(i => i.SourceCard.Id).ToList();

            // Determine known cards for opponent
            var knownOpponentCards = new List <string>();

            // If we are the starting player, we know the opponent has a Coin
            if (rootStartedPlaying)
            {
                knownOpponentCards.Add("GAME_005");
            }
            // We can get the play history of our opponent (filter out Coin because it never starts in a deck)
            var playedOpponentCards = gameState.Game.CurrentOpponent.PlayHistory.Where(i => i.SourceCard.Id != "GAME_005").Select(i => i.SourceCard.Id).ToList();

            // Obfuscate the state
            if (EnableStateObfuscation && !EnablePerfectInformation)
            {
                // For root player
                gameState.Obfuscate(gameState.Game.CurrentPlayer.Id, knownRootCards);
                // For opponent
                gameState.Obfuscate(gameState.Game.CurrentOpponent.Id, knownOpponentCards);
            }

            for (var i = 0; i < ensembleSize; i++)
            {
                // Clone the context
                var clonedContext = context.Copy();

                if (!EnablePerfectInformation)
                {
                    // Try to predict / select what deck the opponent is playing
                    var deckDictionary = Decks.AllDecks();
                    var possibleDecks  = new List <List <Card> >();
                    foreach (var item in deckDictionary)
                    {
                        var deckIds = Decks.CardIDs(item.Value);
                        // A deck can match if all of the cards we have seen played are present
                        if (playedOpponentCards.All(j => deckIds.Contains(j)))
                        {
                            possibleDecks.Add(item.Value);
                        }
                    }

                    // If only one deck matches, assume that is the correct deck
                    // If more decks are possible matches, select one of those possible decks at random
                    var selectedDeck = possibleDecks.Count == 1 ? possibleDecks.First() : possibleDecks.RandomElementOrDefault();

                    // Determinise the game state for the root player's cards-in-deck and for the opponent's cards-in-hand and cards-in-deck
                    var determinedRootCards     = new List <string>(knownRootCards).Concat(playedRootCards).ToList();
                    var determinedOpponentCards = new List <string>(knownOpponentCards).Concat(playedOpponentCards).ToList();
                    clonedContext.Source.Determinise(determinedRootCards, determinedOpponentCards, selectedDeck);
                }

                // Call the search function
                var solution = searchFunction(clonedContext);

                solution.BudgetUsed = clonedContext.BudgetSpent;

                // Use domain in SearchContext to save solutions
                context.Domain.Add(solution);
            }
        }