func evaluateMechanicCount(d deck.Deck) uint { count := 0 if d.Potions() { count++ } if d.CoinTokens() { count++ } if d.VictoryTokens() { count++ } if d.TavernMats() { count++ } if d.TradeRouteMats() { count++ } if d.NativeVillageMats() { count++ } if d.Spoils() { count++ } if d.Ruins() { count++ } if d.MinusOneCardTokens() { count++ } if d.MinusOneCoinTokens() { count++ } if d.JourneyTokens() { count++ } switch count { case 0: return 100 case 1: return 100 case 2: return 50 default: return 0 } }
// TooManyMechanics vetos decks using more than 2 mechanics func TooManyMechanics(p Probability, d deck.Deck) bool { count := 0 if d.Potions() { count++ } if d.CoinTokens() { count++ } if d.VictoryTokens() { count++ } if d.TavernMats() { count++ } if d.TradeRouteMats() { count++ } if d.NativeVillageMats() { count++ } if d.Spoils() { count++ } if d.Ruins() { count++ } if d.MinusOneCardTokens() { count++ } if d.MinusOneCoinTokens() { count++ } if d.JourneyTokens() { count++ } return (count > 2) && rnd.Float64() < p.WhenTooManyMechanics }
func makeDeck(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { var ( sets = availableSets requestedSets deck.Sets vetoProbability veto.Probability weights score.Weights req makeDeckRequest maxSetCount uint maxScore uint d deck.Deck ) decoder := json.NewDecoder(r.Body) err := decoder.Decode(&req) if err != nil { http.Error(w, fmt.Sprintf("Error decoding JSON body: %s", err), http.StatusBadRequest) return } log.Printf("makeDeck(%+v)\n", req) for _, set := range req.Sets { requestedSets.Add(set) } if !requestedSets.Empty() { sets.Intersect(requestedSets) } if sets.Empty() { http.Error(w, "Can't generate a deck from no sets", http.StatusBadRequest) return } maxSetCount = req.MaxSetCount if maxSetCount == 0 { maxSetCount = 2 } if req.VetoProbability == nil || (req.VetoProbability == &veto.Probability{}) { log.Println("Using default veto probs") // Defaults vetoProbability = veto.Probability{ WhenTooExpensive: 0.90, WhenNoTrashing: 0.70, WhenTooManyMechanics: 0.85, WhenTooManyAttacks: 0.85, } } else { vetoProbability = *req.VetoProbability } weights = req.Weights // Defaults if weights.Trashing == 0 { weights.Trashing = 5 } if weights.Random == 0 { weights.Random = 5 } if weights.Chaining == 0 { weights.Chaining = 5 } if weights.CostSpread == 0 { weights.CostSpread = 5 } if weights.SetCount == 0 { weights.SetCount = 5 } if weights.MechanicCount == 0 { weights.MechanicCount = 5 } if weights.Novelty == 0 { weights.Novelty = 5 } decks := make([]deck.Deck, 0, 1000) count := 0 GenerateDeck: candidateDeck := deck.NewRandomDeck(maxSetCount, sets) if veto.TooExpensive(vetoProbability, candidateDeck) { goto GenerateDeck } if veto.NoTrashing(vetoProbability, candidateDeck) { goto GenerateDeck } if veto.TooManyMechanics(vetoProbability, candidateDeck) { goto GenerateDeck } if veto.TooManyAttacks(vetoProbability, candidateDeck) { goto GenerateDeck } count++ decks = append(decks, candidateDeck) if len(decks) < cap(decks) { goto GenerateDeck } var totalCards uint cardCounts := make(map[deck.Card]uint, len(deck.Cards)) for _, deck := range decks { for _, card := range deck.Cards { cardCounts[card]++ totalCards++ } for _, card := range deck.Events { cardCounts[card]++ totalCards++ } } cardProbs := make(map[deck.Card]float64, len(cardCounts)) for card, count := range cardCounts { cardProbs[card] = float64(count) / float64(totalCards) } for _, candidateDeck := range decks { candidateScore := score.Evaluate(weights, candidateDeck, cardProbs) if candidateScore > maxScore { d = candidateDeck maxScore = candidateScore } } resp := deckResponse{ ID: base64.URLEncoding.EncodeToString(d.ID()), Cards: d.Cards, Events: d.Events, ColoniesAndPlatinums: d.ColoniesAndPlatinums, Shelters: d.Shelters, Potions: d.Potions(), Spoils: d.Spoils(), Ruins: d.Ruins(), Hardware: deckHardware{ CoinTokens: d.CoinTokens(), VictoryTokens: d.VictoryTokens(), MinusOneCardTokens: d.MinusOneCardTokens(), MinusOneCoinTokens: d.MinusOneCoinTokens(), JourneyTokens: d.JourneyTokens(), TavernMats: d.TavernMats(), TradeRouteMats: d.TradeRouteMats(), NativeVillageMats: d.NativeVillageMats(), }, } enc := json.NewEncoder(w) _ = enc.Encode(resp) w.Header().Set("Content-Type", "application/json") }
// Determine how well distributed the card costs are based on the entropy of the // cost distribution func evaluateCostSpread(d deck.Deck) uint { // First, count up the total number of cards in the deck with each cost var distribution = map[int]uint{ // coppers, curses 0: 2, // estates 2: 1, // silver 3: 1, // duchys 5: 1, // gold 6: 1, // prov 8: 1, } if d.ColoniesAndPlatinums { // platinum distribution[9]++ // colonies distribution[11]++ } if d.Potions() { distribution[4]++ } for _, card := range d.Cards { distribution[card.CostTreasure]++ } for _, card := range d.Events { distribution[card.CostTreasure]++ } // Now, convert those counts into "probabilities" that a randomly selected // card will have a given cost. For instance, if there are 10 cards in the // deck and exactly 2 of them cost 3 coins, there's a 20% chance of a random // card costing 3 coins // // With this distribution (the probability of each different coin value), // calculate the entropy, which describes how "random" the distribution is // Decks with high entropy have a broad spread of values, decks with low // entropy have a small spread of values. cards := uint(0) for _, n := range distribution { cards += n } entropy := 0.0 maxEntropy := 0.0 for _, n := range distribution { probability := float64(n) / float64(cards) entropy -= probability * math.Log(probability) maxEntropy -= 1 / float64(cards) * math.Log(1/float64(cards)) } // Scale to the range 0-100 based on the fact that the entropy for this set // has an upper bound where each value has the same probability return uint(100 * (entropy / maxEntropy)) }