Какой самый эффективный способ создания всевозможных комбинаций зелий Skyrim (PC Game)? - PullRequest
9 голосов
/ 09 декабря 2011

Таким образом, каждый ингредиент имеет 4 эффекта http://www.uesp.net/wiki/Skyrim:Ingredients

Если я объединю два ингредиента.У зелий будут бонусные эффекты от места пересечения двух сетов.Я не могу использовать один и тот же ингредиент дважды.Чтобы сгенерировать все 2 варианта ингредиентов, я просто составил список ингредиентов для создания пар.Я беру заголовок списка и сравниваю его с остальной частью списка для каждого элемента в списке, удаляя заголовок каждую итерацию.Это позволяет избежать обманщиков.

Я застрял, хотя.Я не знаю, как создать 3 комбинации ингредиентов без дупликов.Есть предложения?

Ответы [ 3 ]

14 голосов
/ 09 декабря 2011

Звучит как работа для любимого всеми языка программирования, R !

library(XML)
tables <- readHTMLTable('http://www.uesp.net/wiki/Skyrim:Ingredients', 
    stringsAsFactors=FALSE)
potions <- tables[[1]]
twoway <- data.frame(t(combn(potions$Name,2)))
threeway <- data.frame(t(combn(potions$Name,3)))

BAM!

> head(twoway)
               X1                  X2
1 Abecean Longfin          Bear Claws
2 Abecean Longfin                 Bee
3 Abecean Longfin        Beehive Husk
4 Abecean Longfin      Bleeding Crown
5 Abecean Longfin         Blisterwort
6 Abecean Longfin Blue Butterfly Wing
> head(threeway)
               X1         X2                  X3
1 Abecean Longfin Bear Claws                 Bee
2 Abecean Longfin Bear Claws        Beehive Husk
3 Abecean Longfin Bear Claws      Bleeding Crown
4 Abecean Longfin Bear Claws         Blisterwort
5 Abecean Longfin Bear Claws Blue Butterfly Wing
6 Abecean Longfin Bear Claws       Blue Dartwing

Используйте команду write.csv для сохранениятаблицы в виде CSV-файлов.

/ Edit: Чтобы объяснить, что я делаю: Пакет XML содержит функцию readHTMLTable , которая извлекает все html-таблицы изсайт как data.frames и сохраняет их в виде списка.Первая таблица в этом списке - та, которую мы хотим.Функция combn находит все 2-х, 3-х и n-х комбинаций имен зелий и возвращает результат в виде матрицы.Я использую функцию t для транспонирования этой матрицы, поэтому каждая комбинация представляет собой одну строку, а затем преобразую ее в кадр данных.Это легко распространяется на комбинации из n ингредиентов.

/ Правка 2: Я написал функцию для сохранения таблицы n-way в указанный пользователем файл csv.Я также немного переработал его, потому что транспонирование огромных матриц требует вычислительных затрат.Эта версия должна позволять вам рассчитывать таблицу с 4 путями, хотя это занимает много времени, и я не знаю, относится ли она к игре.

nway <- function(n, filepath, data=potions) {
    nway <- combn(data$Name, n, simplify = FALSE)
    nway <- do.call(rbind,nway)
    write.csv(nway,filepath, row.names=FALSE)
}
nway(4,'~/Desktop/4way.csv')

/ Правка 3: Вот код, который нужно найтифактические рабочие зелья.Это не очень эффективно и, вероятно, может быть значительно улучшено:

#Given an ingredient, lookup effects
findEffects <- function(Name) { #Given a name, lookup effects
    potions[potions$Name==Name,3:6]
}

#2-way potions
intersectTwoEffects <- function(x) {
    Effects1 <- findEffects(x[1])
    Effects2 <- findEffects(x[2])
    Effects <- unlist(intersect(Effects1,Effects2))
    Effects <- c(x[1],x[2],Effects)
    length(Effects) <- 6
    names(Effects) <- NULL
    c(Effects,sum(is.na(Effects)))

}
twoway <- lapply(twoway,intersectTwoEffects)
twoway <- do.call(rbind,twoway)
twoway <- twoway[twoway[,7]<4,-7] #remove combos with no effect
write.csv(twoway,'~/Desktop/twoway.csv',row.names=FALSE)

#3-way potions
intersectThreeEffects <- function(x) {
    Effects1 <- findEffects(x[1])
    Effects2 <- findEffects(x[2])
    Effects3 <- findEffects(x[3])
    Effects <- c(intersect(Effects1,Effects2),intersect(Effects1,Effects3),intersect(Effects2,Effects3))
    Effects <- unlist(unique(Effects))
    Effects <- c(x[1],x[2],x[3],Effects)
    length(Effects) <- 8
    names(Effects) <- NULL
    c(Effects,sum(is.na(Effects)))

}
threeway <- lapply(threeway,intersectThreeEffects)
threeway <- do.call(rbind,threeway)
threeway <- threeway[threeway[,9]<5,-9] #remove combos with no effect
write.csv(threeway,'~/Desktop/threeway.csv',row.names=FALSE)
4 голосов
/ 10 декабря 2011

Вот немного с #.

Это делает поиск ингредиента по названию потенциальных эффектов. Затем он использует этот поиск, чтобы определить, какие ингредиенты могут соответствовать текущему рецепту. Наконец, он генерирует рецепты и удаляет дубликаты по мере их создания с использованием хэш-набора.

Полный код (неполный список ингредиентов)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Combinations
{

    public class Ingredient
    {
        public List<string> Effects { get; set; }
        public string Name { get; set; }
        public Ingredient(string name, params string[] effects)
        { Name = name; Effects = new List<string>(effects); }
    }

    public class Recipe
    {
        public List<Ingredient> Ingredients {get;set;}
        public Recipe(IEnumerable<Ingredient> ingredients)
        { Ingredients = ingredients.OrderBy(x => x.Name).ToList(); }
        public override string ToString()
        { return string.Join("|", Ingredients.Select(x => x.Name).ToArray()); }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<Ingredient> source = GetIngredients();

            ILookup<string, Ingredient> byEffect = (
                from i in source
                from e in i.Effects
                select new { i, e }
                ).ToLookup(x => x.e, x => x.i);

            List<Recipe> oneIng = source.Select(x => new Recipe(new Ingredient[] { x })).ToList();
            List<Recipe> twoIng = oneIng.SelectMany(r => GenerateRecipes(r, byEffect)).ToList();
            List<Recipe> threeIng = twoIng.SelectMany(r => GenerateRecipes(r, byEffect)).ToList();

            Console.WriteLine(twoIng.Count);
            foreach(Recipe r in twoIng) { Console.WriteLine(r); }
            Console.WriteLine(threeIng.Count);
            foreach(Recipe r in threeIng) { Console.WriteLine(r); }
            Console.ReadLine();
        }

        static IEnumerable<Recipe> GenerateRecipes(Recipe recipe, ILookup<string, Ingredient> byEffect)
        {
            IEnumerable<string> knownEffects = recipe.Ingredients
                .SelectMany(i => i.Effects)
                .Distinct();

            IEnumerable<Ingredient> matchingIngredients = knownEffects
                .SelectMany(e => byEffect[e])
                .Distinct()
                .Where(i => !recipe.Ingredients.Contains(i));

            foreach(Ingredient i in matchingIngredients)
            {
                List<Ingredient> newRecipeIngredients = recipe.Ingredients.ToList();
                newRecipeIngredients.Add(i);
                Recipe result = new Recipe(newRecipeIngredients);
                string key = result.ToString();
                if (!_observedRecipes.Contains(key))
                {
                    _observedRecipes.Add(key);
                    yield return result;
                }
            }
        }

        static HashSet<string> _observedRecipes = new HashSet<string>();

        static List<Ingredient> GetIngredients()
        {
            List<Ingredient> result = new List<Ingredient>()
            {
                new Ingredient("Abecean Longfin", "Weakness to Frost", "Fortify Sneak", "Weakness to Poison", "Fortify Restoration"),
                new Ingredient("Bear Claws", "Restore Stamina", "Fortify Health", "Fortify One-handed", "Damage Magicka Regen"),
                new Ingredient("Bee", "Restore Stamina", "Ravage Stamina", "Regenerate Stamina", "Weakness to Shock"),
                new Ingredient("Beehive Husk", "Resist Poison", "Fortify Light Armor", "Fortify Sneak", "Fortify Destruction"),
                new Ingredient("Bleeding Crown", "Weakness to Fire", "Fortify Block", "Weakness to Poison", "Resist Magic"),
                new Ingredient("Blisterwort", "Damage Stamina", "Frenzy", "Restore Health", "Fortify Smithing"),
                new Ingredient("Blue Butterfly Wing", "Damage Stamina", "Fortify Conjuration", "Damage Magicka Regen", "Fortify Enchanting"),
                new Ingredient("Blue Dartwing", "Resist Shock", "Fortify Pickpocket", "Restore Health", "Damage Magicka Regen"),
                new Ingredient("Blue Mountain Flower", "Restore Health", "Fortify Conjuration", "Fortify Health", "Damage Magicka Regen"),
                new Ingredient("Bone Meal", "Damage Stamina", "Resist Fire", "Fortify Conjuration", "Ravage Stamina"),
            };

            return result;
        }
    }
}
1 голос
/ 17 января 2012

Итак, у меня возникла мысль: «Какой самый экономически эффективный способ получить все знания об ингредиентах?»то есть я хочу, чтобы все ингредиенты были известны в игре, но я не хочу тратить на это двенадцать сердец даэдра.

Если вы используете традиционное поисковое решение (A * и т. д.),фактор ветвления ужасен (есть 22000 возможных эффективных зелий).Я попробовал метод отжига, но не получил хороших результатов.Я в конечном счете пошел с осознанным поиском;это субоптимально, но оно выполнит свою работу.

Вот код импорта и комбинаторизации: помещает «Импорт ингредиентов ...»

fd = File::open('ingr_weighted.txt', 'r')
dbtext = fd.read
fd.close
ingredients = []
cvg = []
id = 0
dbtext.each_line { |line|
    infos = line.split("\t")
    ingredients << {:id => id, :name => infos[0], :effects => [infos[2],infos[3],infos[4],infos[5]],
                    :eff1 => infos[2], :eff2 => infos[3], :eff3 => infos[4], :eff4 => infos[5],
                    :weight => infos[6], :cost => infos[7].to_i+1}
    id += 1
    cvg << [false, false, false, false]
}


puts "Building potions..."
potions = []
id = 0
for a in 0..ingredients.length-2
    for b in a+1..ingredients.length-1
        # First try two-ingredient potions
        uses = ingredients[a][:effects] & ingredients[b][:effects]
        cost = ingredients[a][:cost] + ingredients[b][:cost]
        if (uses.length > 0)
            coverage = [ingredients[a][:effects].map{|x| uses.include? x},
                        ingredients[b][:effects].map{|x| uses.include? x}]
            potions << {:id => id, :effects => uses, :coverage => coverage, :ingredients => [a, b], :cost => cost}
            id = id + 1
        end
        # Next create three-ingredient potions
        for c in b+1..ingredients.length-1
            uses =  ingredients[a][:effects] & ingredients[b][:effects] |
                    ingredients[a][:effects] & ingredients[c][:effects] |
                    ingredients[b][:effects] & ingredients[c][:effects]
            cost = ingredients[a][:cost] + ingredients[b][:cost] + ingredients[c][:cost]
            if (uses.length > 0)
                coverage = [ingredients[a][:effects].map{|x| uses.include? x},
                            ingredients[b][:effects].map{|x| uses.include? x},
                            ingredients[c][:effects].map{|x| uses.include? x}]
                # Prune potions that contain a superfluous ingredient
                if (coverage.inject(true) { |cum, cvgn|
                                            cum = cum && cvgn.inject { |cum2,ef| cum2 = cum2 || ef}
                                            } )
                    potions << {:id => id, :effects => uses, :coverage => coverage, :ingredients => [a,b,c], :cost => cost}
                    id = id + 1
                end
            end
        end
    end
end
# 22451
puts "#{potions.count} potions generated!"
puts "Searching..."

Входной файл - copy-pastaЕсли вы используете мод или что-то еще, вы можете зайти прямо сюда. Отсюда вы импортируете все данные и сгенерированные эффективные зелья, так что делайте что хотите!

Для моей первоначальной цели (эффективное «обучение») я использовал следующий код.В основном это начинается с самого дорогого оставшегося ингредиента, исчерпывает его эффекты как можно дешевле, а затем движется вниз.Некоторые более редкие ингредиенты дешевы (форекс. Человеческая плоть), поэтому я «нашел» файл данных, чтобы искусственно завышать их стоимость.В общем, эта программа работает на моем ноутбуке за 45 минут, но это интерпретируемый язык ...

puts "Searching..."

valueChain = ingredients.sort {|a,b| a[:cost] <=> b[:cost]};

while (valueChain.count > 0)
    # Grab highest-value ingredient left
    ingr = valueChain.pop;

    # Initialize the coverage and potion sub-set
    pots = potions.each_with_object([]) { |pot, list| list << pot if pot[:ingredients].include? ingr[:id] }
    puts "#{ingr[:name]}:\t#{pots.count} candidates"
    if (cvg[ingr[:id]].all?)
        puts "Already finished"
        next
    end

    # Find the cheapest combination that completes our coverage situation
    sitch = {:coverage => cvg[ingr[:id]].dup, :solution => [], :cost => 0}
    best = nil;
    working = []
    working << sitch
    while (working.count != 0)
        parent = working.shift
        pots.each { |pot|
            node = {:coverage => parent[:coverage].zip(pot[:coverage][pot[:ingredients].index(ingr[:id])]).map {|a,b| a || b},
                    :cost => parent[:cost] + pot[:cost],
                    :solution => parent[:solution].dup << pot[:id]}

            # This node is useful if its cost is less than the current-best
            if node[:coverage] == [true,true,true,true]
                if (!best || best[:cost] > node[:cost])
                    best = node
                end
            elsif node[:solution].count < 4
                if (!best || best[:cost] > node[:cost])
                    working << node
                end
            end
        }
    end

    # Merge our selected solution into global coverage
    best[:solution].each{ |pIndex|
        potions[pIndex][:ingredients].each_with_index { |ingID, index|
            cvg[ingID] = cvg[ingID].zip(potions[pIndex][:coverage][index]).map {|x,y| x || y}
        }
    }

    # Report the actual potions chosen
    best[:solution].each { |pIndex|
        print "\tPotion #{pIndex}"
        potions[pIndex][:ingredients].each { |iIndex|
            print "\t#{ingredients[iIndex][:name]}"
        }
        print "\n"
    }
#   IRB.start_session(Kernel.binding)
end
...