Значительно изменил бд и генерацию так же добавил роли

This commit is contained in:
2026-03-25 20:44:28 +05:00
parent 0aeffffa56
commit d16af289fe
14 changed files with 485 additions and 67 deletions

View File

@@ -3,33 +3,124 @@
module ClashDeckGenerator2
module Services
class DeckGenerator
DECK_SIZE = 8
DEFAULT_RULES = {
deck_size: 8,
min_spells: 1,
min_troops: 1,
max_buildings: 2,
min_avg_elixir: 2.5,
max_avg_elixir: 5.0
}.freeze
def initialize(cards_repo:)
SPECIAL_COMBINATIONS = [
{ evolutions: 1, specials: 2 },
{ evolutions: 2, specials: 1 }
].freeze
def initialize(cards_repo: ClashDeckGenerator2::Repos::CardsRepo.new, rules: {})
@cards_repo = cards_repo
@rules = DEFAULT_RULES.merge(rules)
end
def call(only_meta: true)
pool = only_meta ? @cards_repo.meta_cards : @cards_repo.all_cards
deck = sample_unique(pool, DECK_SIZE)
def call
cards = unwrap_cards(@cards_repo.all)
{
cards: deck,
avg_elixir: average_elixir(deck)
}
raise "No cards found in DB" if cards.empty?
raise "Not enough cards to build a deck" if cards.size < @rules[:deck_size]
evolution_cards = cards.select { |card| card.rarity == "evolution" }
special_cards = cards.select { |card| %w[hero champion].include?(card.rarity) }
regular_cards = cards.reject { |card| ["evolution", "hero", "champion"].include?(card.rarity) }
def enough_win_conditions?(deck)
deck.count { |card| has_role?(card, "win_condition") } >= 1
end
def enough_anti_air?(deck)
deck.count { |card| has_role?(card, "anti_air") } >= 1
end
def has_role?(card, role)
@roles_repo.has_role?(card.name, role)
end
attempts = 1000
attempts.times do
combo = SPECIAL_COMBINATIONS.sample
next if evolution_cards.size < combo[:evolutions]
next if special_cards.size < combo[:specials]
chosen_evolutions = evolution_cards.sample(combo[:evolutions])
chosen_specials = special_cards.sample(combo[:specials])
used_names = (chosen_evolutions + chosen_specials).map(&:name)
remaining_pool = regular_cards.reject { |card| used_names.include?(card.name) }
remaining_needed = @rules[:deck_size] - chosen_evolutions.size - chosen_specials.size
next if remaining_pool.size < remaining_needed
chosen_regulars = remaining_pool.sample(remaining_needed)
deck = chosen_evolutions + chosen_specials + chosen_regulars
next unless valid_deck?(deck)
return deck.shuffle
end
raise "Failed to generate a valid deck after #{attempts} attempts"
end
private
def sample_unique(pool, n)
raise "Not enough cards in pool (need #{n}, have #{pool.size})" if pool.size < n
pool.sample(n)
def unwrap_cards(result)
if result.is_a?(Array) && result.first == :cards
result.last
else
result
end
end
def average_elixir(deck)
sum = deck.sum { |c| c[:elixir_cost].to_i }
sum.fdiv(deck.size).round(2)
def valid_deck?(deck)
return false unless deck.size == @rules[:deck_size]
return false unless unique_cards?(deck)
return false unless enough_spells?(deck)
return false unless enough_troops?(deck)
return false unless buildings_limit_ok?(deck)
return false unless avg_elixir_ok?(deck)
return false unless special_slots_ok?(deck)
true
end
def unique_cards?(deck)
deck.map(&:name).uniq.size == deck.size
end
def enough_spells?(deck)
deck.count { |card| card.type == "spell" } >= @rules[:min_spells]
end
def enough_troops?(deck)
deck.count { |card| card.type == "troop" } >= @rules[:min_troops]
end
def buildings_limit_ok?(deck)
deck.count { |card| card.type == "building" } <= @rules[:max_buildings]
end
def avg_elixir_ok?(deck)
avg = deck.sum(&:elixir_cost).to_f / deck.size
avg >= @rules[:min_avg_elixir] && avg <= @rules[:max_avg_elixir]
end
def special_slots_ok?(deck)
evolutions_count = deck.count { |card| card.rarity == "evolution" }
special_count = deck.count { |card| %w[hero champion].include?(card.rarity) }
[[1, 2], [2, 1]].include?([evolutions_count, special_count])
end
end
end