Значительно изменил бд и генерацию так же добавил роли
This commit is contained in:
@@ -35,7 +35,7 @@ bundle exec hanami db prepare
|
||||
```bat
|
||||
bundle exec puma -C config/puma.rb config.ru
|
||||
```
|
||||
|
||||
cd
|
||||
Открыть: [http://localhost:2300](http://localhost:2300)
|
||||
|
||||
## Важно про SQLite для Hanami
|
||||
|
||||
71
app/services/card_roles_repository.rb
Normal file
71
app/services/card_roles_repository.rb
Normal file
@@ -0,0 +1,71 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "json"
|
||||
|
||||
module ClashDeckGenerator2
|
||||
module Services
|
||||
class CardRolesRepository
|
||||
ROLES_PATH = File.expand_path("../../config/cards/roles.json", __dir__)
|
||||
|
||||
def all
|
||||
@all ||= load_roles
|
||||
end
|
||||
|
||||
def cards
|
||||
all.fetch("cards", {})
|
||||
end
|
||||
|
||||
def roles_for(card_name)
|
||||
cards.fetch(card_name, [])
|
||||
end
|
||||
|
||||
def has_role?(card_name, role)
|
||||
roles_for(card_name).include?(role)
|
||||
end
|
||||
|
||||
def meta
|
||||
all.fetch("_meta", {})
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_roles
|
||||
raise "roles.json not found: #{ROLES_PATH}" unless File.exist?(ROLES_PATH)
|
||||
|
||||
parsed = JSON.parse(File.read(ROLES_PATH))
|
||||
|
||||
unless parsed.is_a?(Hash)
|
||||
raise "roles.json must contain a JSON object at root"
|
||||
end
|
||||
|
||||
unless parsed.key?("cards")
|
||||
raise 'roles.json must contain "cards" key'
|
||||
end
|
||||
|
||||
cards = parsed["cards"]
|
||||
|
||||
unless cards.is_a?(Hash)
|
||||
raise '"cards" must be a JSON object'
|
||||
end
|
||||
|
||||
cards.each do |card_name, roles|
|
||||
unless card_name.is_a?(String) && !card_name.strip.empty?
|
||||
raise "Invalid card name in roles.json: #{card_name.inspect}"
|
||||
end
|
||||
|
||||
unless roles.is_a?(Array)
|
||||
raise "Roles for #{card_name} must be an array"
|
||||
end
|
||||
|
||||
roles.each do |role|
|
||||
unless role.is_a?(String) && !role.strip.empty?
|
||||
raise "Invalid role for #{card_name}: #{role.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
parsed
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
21
config/cards/roles.json
Normal file
21
config/cards/roles.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"_meta": {
|
||||
"version": 1,
|
||||
"description": "Card roles mapping for deck generation"
|
||||
},
|
||||
"cards": {
|
||||
"Knight": ["tank", "defense"],
|
||||
"Archers": ["support", "anti_air", "cycle"],
|
||||
"Musketeer": ["support", "anti_air"],
|
||||
"Tesla": ["building", "defense", "anti_air"],
|
||||
"Fireball": ["spell", "splash"],
|
||||
"Zap": ["spell", "cycle"],
|
||||
"Hog Rider": ["win_condition"],
|
||||
"Royal Giant": ["win_condition", "tank"],
|
||||
"Goblin Barrel": ["win_condition"],
|
||||
"Balloon": ["win_condition"],
|
||||
"Giant": ["win_condition", "tank"],
|
||||
"P.E.K.K.A": ["tank", "defense"],
|
||||
"Baby Dragon": ["support", "anti_air", "splash"]
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,14 @@ Sequel.migration do
|
||||
column :rarity, String
|
||||
column :type, String
|
||||
|
||||
# Уникальность имени карты
|
||||
add_unique_constraint :name
|
||||
|
||||
# Ограничения
|
||||
constraint(:valid_rarity, Sequel.lit("rarity IN ('common','rare','epic','legendary','champion', 'evo', 'hero')"))
|
||||
constraint(:valid_type, Sequel.lit("type IN ('troop','spell','building')"))
|
||||
constraint(:positive_elixir, Sequel.lit("elixir_cost >= 0"))
|
||||
|
||||
# 0/1 флаг: карточка в актуальной мете или нет.
|
||||
# Можно позже заменить на true/false, если решим хранить boolean.
|
||||
column :is_meta, Integer, default: 0
|
||||
@@ -20,6 +28,7 @@ Sequel.migration do
|
||||
# Технические таймстемпы для аудита и сортировки
|
||||
column :created_at, DateTime, null: false, default: Sequel::CURRENT_TIMESTAMP
|
||||
column :updated_at, DateTime, null: false, default: Sequel::CURRENT_TIMESTAMP
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,55 +3,29 @@
|
||||
repo = ClashDeckGenerator2::Repos::CardsRepo.new
|
||||
|
||||
CARD_TYPES = %w[troop spell building].freeze
|
||||
RARITIES = %w[common rare epic legendary champion].freeze
|
||||
RARITIES = %w[common rare epic legendary champion hero evolution].freeze
|
||||
|
||||
COMMON_CARDS = [
|
||||
# ========================
|
||||
# TROOPS
|
||||
# ========================
|
||||
{ name: "Skeletons", elixir_cost: 1, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Electro Spirit", elixir_cost: 1, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Fire Spirit", elixir_cost: 1, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Ice Spirit", elixir_cost: 1, rarity: "common", type: "troop", is_meta: 0 },
|
||||
# Подключаем файлы с картами
|
||||
require_relative "seeds/common"
|
||||
require_relative "seeds/rare"
|
||||
require_relative "seeds/epic"
|
||||
require_relative "seeds/legendary"
|
||||
require_relative "seeds/champion"
|
||||
require_relative "seeds/hero"
|
||||
require_relative "seeds/evo"
|
||||
|
||||
{ name: "Goblins", elixir_cost: 2, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Spear Goblins", elixir_cost: 2, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Bomber", elixir_cost: 2, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Bats", elixir_cost: 2, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Berserker", elixir_cost: 2, rarity: "common", type: "troop", is_meta: 0 },
|
||||
# Объединяем все карты
|
||||
CARDS = []
|
||||
CARDS.concat(COMMON_CARDS)
|
||||
CARDS.concat(RARE_CARDS)
|
||||
CARDS.concat(EPIC_CARDS)
|
||||
CARDS.concat(LEGENDARY_CARDS)
|
||||
CARDS.concat(CHAMPION_CARDS)
|
||||
CARDS.concat(HERO_CARDS)
|
||||
CARDS.concat(EVOLUTION_CARDS)
|
||||
|
||||
{ name: "Archers", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Knight", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Minions", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Goblin Gang", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Skeleton Barrel", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Firecracker", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Skeleton Dragons", elixir_cost: 4, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Barbarians", elixir_cost: 5, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Minion Horde", elixir_cost: 5, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Rascals", elixir_cost: 5, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Royal Giant", elixir_cost: 6, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Elite Barbarians", elixir_cost: 6, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Royal Recruits", elixir_cost: 7, rarity: "common", type: "troop", is_meta: 0 },
|
||||
|
||||
# ========================
|
||||
# SPELLS
|
||||
# ========================
|
||||
{ name: "Zap", elixir_cost: 2, rarity: "common", type: "spell", is_meta: 1 },
|
||||
{ name: "Giant Snowball", elixir_cost: 2, rarity: "common", type: "spell", is_meta: 0 },
|
||||
{ name: "Arrows", elixir_cost: 3, rarity: "common", type: "spell", is_meta: 0 },
|
||||
{ name: "Royal Delivery", elixir_cost: 3, rarity: "common", type: "spell", is_meta: 0 },
|
||||
|
||||
# ========================
|
||||
# BUILDINGS
|
||||
# ========================
|
||||
{ name: "Cannon", elixir_cost: 3, rarity: "common", type: "building", is_meta: 0 },
|
||||
{ name: "Mortar", elixir_cost: 4, rarity: "common", type: "building", is_meta: 0 },
|
||||
{ name: "Tesla", elixir_cost: 4, rarity: "common", type: "building", is_meta: 0 }
|
||||
].freeze
|
||||
|
||||
CARDS = COMMON_CARDS
|
||||
|
||||
# ВАЛИДАЦИЯ
|
||||
CARDS.each do |c|
|
||||
raise "Missing name" if c[:name].nil? || c[:name].strip.empty?
|
||||
raise "Missing elixir_cost for #{c[:name]}" if c[:elixir_cost].nil?
|
||||
@@ -62,10 +36,13 @@ CARDS.each do |c|
|
||||
raise "Unknown type: #{c[:type]} for #{c[:name]}" unless CARD_TYPES.include?(c[:type])
|
||||
end
|
||||
|
||||
# Проверка на дубликаты
|
||||
names = CARDS.map { |c| c[:name] }
|
||||
duplicates = names.group_by { |name| name }.select { |_k, v| v.size > 1 }.keys
|
||||
raise "Duplicate cards found: #{duplicates.join(', ')}" if duplicates.any?
|
||||
|
||||
|
||||
# ЗАПИСЬ В БАЗУ
|
||||
CARDS.each do |card|
|
||||
repo.create(card)
|
||||
end
|
||||
12
config/db/seeds/champion.rb
Normal file
12
config/db/seeds/champion.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
CHAMPION_CARDS = [
|
||||
{ name: "Little Prince", elixir_cost: 3, rarity: "champion", type: "troop", is_meta: 1 },
|
||||
{ name: "Golden Knight", elixir_cost: 4, rarity: "champion", type: "troop", is_meta: 1 },
|
||||
{ name: "Skeleton King", elixir_cost: 4, rarity: "champion", type: "troop", is_meta: 1 },
|
||||
{ name: "Mighty Miner", elixir_cost: 4, rarity: "champion", type: "troop", is_meta: 1 },
|
||||
{ name: "Archer Queen", elixir_cost: 5, rarity: "champion", type: "troop", is_meta: 1 },
|
||||
{ name: "Goblinstein", elixir_cost: 5, rarity: "champion", type: "troop", is_meta: 0 },
|
||||
{ name: "Monk", elixir_cost: 5, rarity: "champion", type: "troop", is_meta: 1 },
|
||||
{ name: "Boss Bandit", elixir_cost: 6, rarity: "champion", type: "troop", is_meta: 0 }
|
||||
].freeze
|
||||
41
config/db/seeds/common.rb
Normal file
41
config/db/seeds/common.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
COMMON_CARDS = [
|
||||
|
||||
#Юниты
|
||||
{ name: "Skeletons", elixir_cost: 1, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Electro Spirit", elixir_cost: 1, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Fire Spirit", elixir_cost: 1, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Ice Spirit", elixir_cost: 1, rarity: "common", type: "troop", is_meta: 0 },
|
||||
|
||||
{ name: "Goblins", elixir_cost: 2, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Spear Goblins", elixir_cost: 2, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Bomber", elixir_cost: 2, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Bats", elixir_cost: 2, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Berserker", elixir_cost: 2, rarity: "common", type: "troop", is_meta: 0 },
|
||||
|
||||
{ name: "Archers", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Knight", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Minions", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Goblin Gang", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Skeleton Barrel", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Firecracker", elixir_cost: 3, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Skeleton Dragons", elixir_cost: 4, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Barbarians", elixir_cost: 5, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Minion Horde", elixir_cost: 5, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Rascals", elixir_cost: 5, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Royal Giant", elixir_cost: 6, rarity: "common", type: "troop", is_meta: 1 },
|
||||
{ name: "Elite Barbarians", elixir_cost: 6, rarity: "common", type: "troop", is_meta: 0 },
|
||||
{ name: "Royal Recruits", elixir_cost: 7, rarity: "common", type: "troop", is_meta: 0 },
|
||||
|
||||
#Заклинания
|
||||
{ name: "Zap", elixir_cost: 2, rarity: "common", type: "spell", is_meta: 1 },
|
||||
{ name: "Giant Snowball", elixir_cost: 2, rarity: "common", type: "spell", is_meta: 0 },
|
||||
{ name: "Arrows", elixir_cost: 3, rarity: "common", type: "spell", is_meta: 0 },
|
||||
{ name: "Royal Delivery", elixir_cost: 3, rarity: "common", type: "spell", is_meta: 0 },
|
||||
|
||||
#Здания
|
||||
{ name: "Cannon", elixir_cost: 3, rarity: "common", type: "building", is_meta: 0 },
|
||||
{ name: "Mortar", elixir_cost: 4, rarity: "common", type: "building", is_meta: 0 },
|
||||
{ name: "Tesla", elixir_cost: 4, rarity: "common", type: "building", is_meta: 0 }
|
||||
].freeze
|
||||
39
config/db/seeds/epic.rb
Normal file
39
config/db/seeds/epic.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
EPIC_CARDS = [
|
||||
# Юниты
|
||||
{ name: "Wall Breakers", elixir_cost: 2, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
{ name: "Guards", elixir_cost: 3, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
{ name: "Dark Prince", elixir_cost: 4, rarity: "epic", type: "troop", is_meta: 1 },
|
||||
{ name: "Hunter", elixir_cost: 4, rarity: "epic", type: "troop", is_meta: 1 },
|
||||
{ name: "Baby Dragon", elixir_cost: 4, rarity: "epic", type: "troop", is_meta: 1 },
|
||||
{ name: "Goblin Drill", elixir_cost: 4, rarity: "epic", type: "troop", is_meta: 1 },
|
||||
{ name: "Prince", elixir_cost: 5, rarity: "epic", type: "troop", is_meta: 1 },
|
||||
{ name: "Balloon", elixir_cost: 5, rarity: "epic", type: "troop", is_meta: 1 },
|
||||
{ name: "Witch", elixir_cost: 5, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
{ name: "Bowler", elixir_cost: 5, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
{ name: "Cannon Cart", elixir_cost: 5, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
{ name: "Electro Dragon", elixir_cost: 5, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
{ name: "Giant Skeleton", elixir_cost: 6, rarity: "epic", type: "troop", is_meta: 1 },
|
||||
{ name: "Goblin Giant", elixir_cost: 6, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
{ name: "P.E.K.K.A", elixir_cost: 7, rarity: "epic", type: "troop", is_meta: 1 },
|
||||
{ name: "Golem", elixir_cost: 8, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
{ name: "Electro Giant", elixir_cost: 7, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
{ name: "Goblin Giantess", elixir_cost: 6, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
{ name: "Skeleton Army", elixir_cost: 3, rarity: "epic", type: "troop", is_meta: 1 },
|
||||
{ name: "Executioner", elixir_cost: 5, rarity: "epic", type: "troop", is_meta: 0 },
|
||||
|
||||
# Заклинания
|
||||
{ name: "Mirror", elixir_cost: 0, rarity: "epic", type: "spell", is_meta: 0 },
|
||||
{ name: "Rage", elixir_cost: 2, rarity: "epic", type: "spell", is_meta: 0 },
|
||||
{ name: "Clone", elixir_cost: 3, rarity: "epic", type: "spell", is_meta: 0 },
|
||||
{ name: "Freeze", elixir_cost: 4, rarity: "epic", type: "spell", is_meta: 1 },
|
||||
{ name: "Poison", elixir_cost: 4, rarity: "epic", type: "spell", is_meta: 1 },
|
||||
{ name: "Lightning", elixir_cost: 6, rarity: "epic", type: "spell", is_meta: 0 },
|
||||
{ name: "Void", elixir_cost: 3, rarity: "epic", type: "spell", is_meta: 0 },
|
||||
{ name: "Goblin Barrel", elixir_cost: 3, rarity: "epic", type: "spell", is_meta: 1 },
|
||||
{ name: "Tornado", elixir_cost: 3, rarity: "epic", type: "spell", is_meta: 1 },
|
||||
|
||||
# Здания
|
||||
{ name: "X-Bow", elixir_cost: 6, rarity: "epic", type: "building", is_meta: 0 }
|
||||
].freeze
|
||||
43
config/db/seeds/evo.rb
Normal file
43
config/db/seeds/evo.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
EVOLUTION_CARDS = [
|
||||
{ name: "Archers Evolution", elixir_cost: 3, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Baby Dragon Evolution", elixir_cost: 4, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Barbarians Evolution", elixir_cost: 5, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Battle Ram Evolution", elixir_cost: 4, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Bats Evolution", elixir_cost: 2, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Bomber Evolution", elixir_cost: 2, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Cannon Evolution", elixir_cost: 3, rarity: "evolution", type: "building", is_meta: 0 },
|
||||
{ name: "Dart Goblin Evolution", elixir_cost: 3, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Electro Dragon Evolution", elixir_cost: 5, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Executioner Evolution", elixir_cost: 5, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Firecracker Evolution", elixir_cost: 3, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Furnace Evolution", elixir_cost: 4, rarity: "evolution", type: "building", is_meta: 0 },
|
||||
{ name: "Giant Snowball Evolution", elixir_cost: 2, rarity: "evolution", type: "spell", is_meta: 0 },
|
||||
{ name: "Goblin Barrel Evolution", elixir_cost: 3, rarity: "evolution", type: "spell", is_meta: 1 },
|
||||
{ name: "Goblin Cage Evolution", elixir_cost: 4, rarity: "evolution", type: "building", is_meta: 0 },
|
||||
{ name: "Goblin Drill Evolution", elixir_cost: 4, rarity: "evolution", type: "building", is_meta: 1 },
|
||||
{ name: "Goblin Giant Evolution", elixir_cost: 6, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Hunter Evolution", elixir_cost: 4, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Ice Spirit Evolution", elixir_cost: 1, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Inferno Dragon Evolution", elixir_cost: 4, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Knight Evolution", elixir_cost: 3, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Lumberjack Evolution", elixir_cost: 4, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Mega Knight Evolution", elixir_cost: 7, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Mortar Evolution", elixir_cost: 4, rarity: "evolution", type: "building", is_meta: 0 },
|
||||
{ name: "Musketeer Evolution", elixir_cost: 4, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "P.E.K.K.A Evolution", elixir_cost: 7, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Royal Ghost Evolution", elixir_cost: 3, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Royal Giant Evolution", elixir_cost: 6, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Royal Hogs Evolution", elixir_cost: 5, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Royal Recruits Evolution", elixir_cost: 7, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Skeleton Army Evolution", elixir_cost: 3, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Skeleton Barrel Evolution", elixir_cost: 3, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Skeletons Evolution", elixir_cost: 1, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Tesla Evolution", elixir_cost: 4, rarity: "evolution", type: "building", is_meta: 1 },
|
||||
{ name: "Valkyrie Evolution", elixir_cost: 4, rarity: "evolution", type: "troop", is_meta: 1 },
|
||||
{ name: "Wall Breakers Evolution", elixir_cost: 2, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Witch Evolution", elixir_cost: 5, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Wizard Evolution", elixir_cost: 5, rarity: "evolution", type: "troop", is_meta: 0 },
|
||||
{ name: "Zap Evolution", elixir_cost: 2, rarity: "evolution", type: "spell", is_meta: 1 }
|
||||
].freeze
|
||||
14
config/db/seeds/hero.rb
Normal file
14
config/db/seeds/hero.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
HERO_CARDS = [
|
||||
{ name: "Goblins Hero", elixir_cost: 2, rarity: "hero", type: "troop", is_meta: 1 },
|
||||
{ name: "Knight Hero", elixir_cost: 3, rarity: "hero", type: "troop", is_meta: 1 },
|
||||
{ name: "Ice Golem Hero", elixir_cost: 2, rarity: "hero", type: "troop", is_meta: 0 },
|
||||
{ name: "Mega Minion Hero", elixir_cost: 3, rarity: "hero", type: "troop", is_meta: 0 },
|
||||
{ name: "Mini P.E.K.K.A Hero", elixir_cost: 4, rarity: "hero", type: "troop", is_meta: 1 },
|
||||
{ name: "Musketeer Hero", elixir_cost: 4, rarity: "hero", type: "troop", is_meta: 1 },
|
||||
{ name: "Giant Hero", elixir_cost: 5, rarity: "hero", type: "troop", is_meta: 0 },
|
||||
{ name: "Wizard Hero", elixir_cost: 5, rarity: "hero", type: "troop", is_meta: 0 },
|
||||
{ name: "Barbarian Barrel Hero", elixir_cost: 2, rarity: "hero", type: "spell", is_meta: 1 },
|
||||
{ name: "Magic Archer Hero", elixir_cost: 4, rarity: "hero", type: "troop", is_meta: 1 }
|
||||
].freeze
|
||||
28
config/db/seeds/legendary.rb
Normal file
28
config/db/seeds/legendary.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
LEGENDARY_CARDS = [
|
||||
# Юниты
|
||||
{ name: "Miner", elixir_cost: 3, rarity: "legendary", type: "troop", is_meta: 1 },
|
||||
{ name: "Princess", elixir_cost: 3, rarity: "legendary", type: "troop", is_meta: 1 },
|
||||
{ name: "Ice Wizard", elixir_cost: 3, rarity: "legendary", type: "troop", is_meta: 0 },
|
||||
{ name: "Royal Ghost", elixir_cost: 3, rarity: "legendary", type: "troop", is_meta: 1 },
|
||||
{ name: "Bandit", elixir_cost: 3, rarity: "legendary", type: "troop", is_meta: 1 },
|
||||
{ name: "Fisherman", elixir_cost: 3, rarity: "legendary", type: "troop", is_meta: 0 },
|
||||
{ name: "Electro Wizard", elixir_cost: 4, rarity: "legendary", type: "troop", is_meta: 1 },
|
||||
{ name: "Inferno Dragon", elixir_cost: 4, rarity: "legendary", type: "troop", is_meta: 1 },
|
||||
{ name: "Phoenix", elixir_cost: 4, rarity: "legendary", type: "troop", is_meta: 1 },
|
||||
{ name: "Magic Archer", elixir_cost: 4, rarity: "legendary", type: "troop", is_meta: 1 },
|
||||
{ name: "Lumberjack", elixir_cost: 4, rarity: "legendary", type: "troop", is_meta: 1 },
|
||||
{ name: "Night Witch", elixir_cost: 4, rarity: "legendary", type: "troop", is_meta: 0 },
|
||||
{ name: "Mother Witch", elixir_cost: 4, rarity: "legendary", type: "troop", is_meta: 0 },
|
||||
{ name: "Ram Rider", elixir_cost: 5, rarity: "legendary", type: "troop", is_meta: 0 },
|
||||
{ name: "Goblin Machine", elixir_cost: 5, rarity: "legendary", type: "troop", is_meta: 0 },
|
||||
{ name: "Sparky", elixir_cost: 6, rarity: "legendary", type: "troop", is_meta: 0 },
|
||||
{ name: "Spirit Empress", elixir_cost: 6, rarity: "legendary", type: "troop", is_meta: 0 },
|
||||
{ name: "Mega Knight", elixir_cost: 7, rarity: "legendary", type: "troop", is_meta: 1 },
|
||||
{ name: "Lava Hound", elixir_cost: 7, rarity: "legendary", type: "troop", is_meta: 0 },
|
||||
|
||||
# Заклинания
|
||||
{ name: "The Log", elixir_cost: 2, rarity: "legendary", type: "spell", is_meta: 1 },
|
||||
{ name: "Graveyard", elixir_cost: 5, rarity: "legendary", type: "spell", is_meta: 1 }
|
||||
].freeze
|
||||
38
config/db/seeds/rare.rb
Normal file
38
config/db/seeds/rare.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RARE_CARDS = [
|
||||
# Юниты
|
||||
{ name: "Heal Spirit", elixir_cost: 1, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Ice Golem", elixir_cost: 2, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Suspicious Bush", elixir_cost: 2, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Mega Minion", elixir_cost: 3, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Dart Goblin", elixir_cost: 3, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Elixir Golem", elixir_cost: 3, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Mini P.E.K.K.A", elixir_cost: 4, rarity: "rare", type: "troop", is_meta: 1 },
|
||||
{ name: "Musketeer", elixir_cost: 4, rarity: "rare", type: "troop", is_meta: 1 },
|
||||
{ name: "Valkyrie", elixir_cost: 4, rarity: "rare", type: "troop", is_meta: 1 },
|
||||
{ name: "Battle Ram", elixir_cost: 4, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Hog Rider", elixir_cost: 4, rarity: "rare", type: "troop", is_meta: 1 },
|
||||
{ name: "Battle Healer", elixir_cost: 4, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Goblin Demolisher", elixir_cost: 4, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Giant", elixir_cost: 5, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Wizard", elixir_cost: 5, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Royal Hogs", elixir_cost: 5, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Three Musketeers", elixir_cost: 9, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Zappies", elixir_cost: 4, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
{ name: "Furnace", elixir_cost: 4, rarity: "rare", type: "troop", is_meta: 0 },
|
||||
|
||||
# Заклинания
|
||||
{ name: "Earthquake", elixir_cost: 3, rarity: "rare", type: "spell", is_meta: 1 },
|
||||
{ name: "Fireball", elixir_cost: 4, rarity: "rare", type: "spell", is_meta: 1 },
|
||||
{ name: "Rocket", elixir_cost: 6, rarity: "rare", type: "spell", is_meta: 0 },
|
||||
|
||||
# Здания
|
||||
{ name: "Tombstone", elixir_cost: 3, rarity: "rare", type: "building", is_meta: 0 },
|
||||
{ name: "Goblin Cage", elixir_cost: 4, rarity: "rare", type: "building", is_meta: 0 },
|
||||
{ name: "Goblin Hut", elixir_cost: 5, rarity: "rare", type: "building", is_meta: 0 },
|
||||
{ name: "Bomb Tower", elixir_cost: 4, rarity: "rare", type: "building", is_meta: 0 },
|
||||
{ name: "Inferno Tower", elixir_cost: 5, rarity: "rare", type: "building", is_meta: 1 },
|
||||
{ name: "Barbarian Hut", elixir_cost: 6, rarity: "rare", type: "building", is_meta: 0 },
|
||||
{ name: "Elixir Collector", elixir_cost: 6, rarity: "rare", type: "building", is_meta: 0 }
|
||||
].freeze
|
||||
@@ -4,13 +4,41 @@ require_relative "../config/app"
|
||||
|
||||
Hanami.app.prepare
|
||||
|
||||
repo = ClashDeckGenerator2::Repos::CardsRepo.new
|
||||
cards = repo.all.select { |card| card.rarity == "common" }
|
||||
roles_repo = ClashDeckGenerator2::Services::CardRolesRepository.new
|
||||
|
||||
raise "No common cards found in DB." if cards.empty?
|
||||
raise "Not enough common cards to build a deck. Need at least 8, got #{cards.size}." if cards.size < 8
|
||||
puts roles_repo.roles_for("Knight").inspect
|
||||
puts roles_repo.roles_for("Hog Rider").inspect
|
||||
puts roles_repo.has_role?("Hog Rider", "win_condition")
|
||||
puts roles_repo.has_role?("Tesla", "win_condition")
|
||||
puts roles_repo.meta.inspect
|
||||
|
||||
deck = cards.sample(8)
|
||||
cards = repo.all
|
||||
cards = cards.last if cards.is_a?(Array) && cards.first == :cards
|
||||
|
||||
rarity_counts = cards.group_by(&:rarity).transform_values(&:size)
|
||||
|
||||
puts "RARITY COUNTS:"
|
||||
rarity_counts.each do |rarity, count|
|
||||
puts "#{rarity}: #{count}"
|
||||
end
|
||||
|
||||
puts "========================================"
|
||||
puts "POOL DIAGNOSTICS"
|
||||
puts "========================================"
|
||||
puts "Total cards: #{cards.size}"
|
||||
puts "Spells: #{cards.count { |c| c.type == 'spell' }}"
|
||||
puts "Troops: #{cards.count { |c| c.type == 'troop' }}"
|
||||
puts "Buildings: #{cards.count { |c| c.type == 'building' }}"
|
||||
puts "Champions: #{cards.count { |c| c.rarity == 'champion' }}"
|
||||
puts "Heroes: #{cards.count { |c| c.rarity == 'hero' }}"
|
||||
puts "Evolutions: #{cards.count { |c| c.rarity == 'evolution' }}"
|
||||
puts "Avg elixir all cards: #{(cards.sum(&:elixir_cost).to_f / cards.size).round(2)}"
|
||||
puts "========================================"
|
||||
|
||||
generator = ClashDeckGenerator2::Services::DeckGenerator.new(cards_repo: repo)
|
||||
deck = generator.call
|
||||
|
||||
deck = deck.last if deck.is_a?(Array) && deck.first == :cards
|
||||
|
||||
puts "========================================"
|
||||
puts "DECK GENERATED SUCCESSFULLY"
|
||||
@@ -28,4 +56,10 @@ puts "Unique cards count: #{names.uniq.size}"
|
||||
|
||||
avg_elixir = deck.sum(&:elixir_cost).to_f / deck.size
|
||||
puts "Average elixir: #{avg_elixir.round(2)}"
|
||||
puts "Spells count: #{deck.count { |c| c.type == 'spell' }}"
|
||||
puts "Troops count: #{deck.count { |c| c.type == 'troop' }}"
|
||||
puts "Buildings count: #{deck.count { |c| c.type == 'building' }}"
|
||||
puts "Champions count: #{deck.count { |c| c.rarity == 'champion' }}"
|
||||
puts "Heroes count: #{deck.count { |c| c.rarity == 'hero' }}"
|
||||
puts "Evolutions count: #{deck.count { |c| c.rarity == 'evolution' }}"
|
||||
puts "========================================"
|
||||
Reference in New Issue
Block a user