What's the most efficient way of generating all possible combinations of skyrim (PC Game) potions?

后端 未结 3 1195
一个人的身影
一个人的身影 2021-01-12 01:38

So each ingredient has 4 effects http://www.uesp.net/wiki/Skyrim:Ingredients

If I combine two ingredients. The potions will have the bonus effects of where the two s

3条回答
  •  有刺的猬
    2021-01-12 02:18

    So I had the thought, "What's the most cost-efficient way to gain all ingredient knowledge?" i.e. I want all the ingredients' effects to be known in game, but I don't want to spend twelve Daedra Hearts to do it.

    If you use a traditional search solution (A*, etc.) the branching factor is horrific (there are 22000ish possible effective potions). I tried an annealing approach but wasn't getting good results. I eventually went with an informed search; it's subobptimal but it'll get the job done.

    Here's the import-and-combinatorize code: puts "Importing ingredients..."

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

    The input file is copy-pasta'd from one of the wikis, so if you're using a mod or something you can drop right in. From here you have all the data imported and the effective potions generated, so do what you want!

    For my original purpose (efficient "learning"), I used the following code. Basically it starts with the most expensive remaining ingredient, exhausts its effects as cheaply as possible, then moves on down. Some rarer ingredients are cheap (forex. human flesh), so I "goosed" my data file to artificially inflate their value. All told, this program runs in about 45 minutes on my laptop, but it is an interpreted language...

    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
    

提交回复
热议问题