生成天际(PC游戏)药水的所有可能组合的最有效方法是什么?

时间:2011-12-09 16:25:10

标签: dataset combinations

所以每种成分都有4种效果 http://www.uesp.net/wiki/Skyrim:Ingredients

如果我合并两种成分。魔药将具有两组相交的奖励效果。我不能两次使用相同的成分。为了生成所有2种成分的可能性,我只列出了成分对的成分列表。我取了列表的头部,并将其与列表中每个元素的其余部分进行比较,每次迭代都会删除头部。这避免了欺骗。

但是我被困了。我不知道如何在没有欺骗的情况下生成3种成分组合。有什么建议吗?

3 个答案:

答案 0 :(得分:14)

听起来像是每个人最喜欢的编程语言的工作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文件。

/编辑:解释我在做什么:XML包中包含readHTMLTable函数,该函数将网站中的所有html表作为data.frames提取并将其保存为列表。此列表中的第一个表是我们想要的表。 combn函数查找所有药剂名称的2路,3路和n路combinations,并将结果作为矩阵返回。我使用t函数来转置此矩阵,因此每个组合都是一行,然后将其转换为数据帧。这很容易扩展到n种成分的组合。

/编辑2:我写了一个函数将n路表保存到用户指定的csv文件中。我也对它进行了一些重新设计,因为转置巨大的matricies在计算上是昂贵的。这个版本应该允许你计算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)

答案 1 :(得分:4)

这是一些c#。

它通过潜在效果的名称查找成分。然后它使用该查找来确定哪些成分可以匹配当前配方。最后,它生成配方并丢弃重复项,因为它使用哈希集生成它们。

完整代码(不完整的成分列表)

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

答案 2 :(得分:1)

所以我想到了“获得所有成分知识的最具成本效益的方法是什么?”即我希望所有成分的效果在游戏中都是众所周知的,但我不想花十二个Daedra Hearts来做它。

如果你使用传统的搜索解决方案(A *等),分支因子是可怕的(有22000种可能的有效药水)。我试过退火方法,但没有取得好成绩。我最终选择了一个明智的搜索;它是次要的,但它会完成工作。

这是import-and-combinatorize代码:     把“进口成分......”

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..."

输入文件是从其中一个维基上复制意大利面,所以如果你正在使用mod或其他东西,你可以直接进入。从这里你可以导入所有数据并生成有效的药水,所以你想要什么!

出于我原来的目的(有效的“学习”),我使用了以下代码。基本上它从最昂贵的剩余成分开始,尽可能便宜地耗尽其效果,然后向下移动。一些罕见的成分很便宜(外汇。人类肉体),所以我“go”“我的数据文件,以人为地夸大其价值。总而言之,这个程序在我的笔记本电脑上运行大约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