From 0aeffffa56f87ee196241f56cea8dbf96bf6cb0a Mon Sep 17 00:00:00 2001 From: yummy Date: Sat, 21 Mar 2026 00:15:28 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BB=D0=BE=D0=B4=D1=8B=20=D0=B8=20=D1=83=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D1=88=D0=B8=D0=BB=20=D0=B1=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/relations/cards.rb | 4 +- app/repos/cards_repo.rb | 15 +++++- app/services/deck_generator.rb | 36 +++++++++++++++ config/db/seeds.rb | 84 ++++++++++++++++++++++++++++------ script/test_deck.rb | 31 +++++++++++++ 5 files changed, 153 insertions(+), 17 deletions(-) create mode 100644 app/services/deck_generator.rb create mode 100644 script/test_deck.rb diff --git a/app/relations/cards.rb b/app/relations/cards.rb index a5cbe99..dd09d44 100644 --- a/app/relations/cards.rb +++ b/app/relations/cards.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + module ClashDeckGenerator2 module Relations class Cards < DB::Relation schema :cards, infer: true - def meta + def meta_cards_scope where(is_meta: 1) end end diff --git a/app/repos/cards_repo.rb b/app/repos/cards_repo.rb index e72372a..6564edb 100644 --- a/app/repos/cards_repo.rb +++ b/app/repos/cards_repo.rb @@ -1,9 +1,20 @@ +# frozen_string_literal: true + module ClashDeckGenerator2 module Repos class CardsRepo < DB::Repo[:cards] + commands :create + def meta_cards - cards.meta.to_a + cards.meta_cards_scope.to_a + end + + def all_cards + cards.to_a + end + def all + cards.to_a end end end -end \ No newline at end of file +end \ No newline at end of file diff --git a/app/services/deck_generator.rb b/app/services/deck_generator.rb new file mode 100644 index 0000000..cba1c7b --- /dev/null +++ b/app/services/deck_generator.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ClashDeckGenerator2 + module Services + class DeckGenerator + DECK_SIZE = 8 + + def initialize(cards_repo:) + @cards_repo = cards_repo + end + + def call(only_meta: true) + pool = only_meta ? @cards_repo.meta_cards : @cards_repo.all_cards + deck = sample_unique(pool, DECK_SIZE) + + { + cards: deck, + avg_elixir: average_elixir(deck) + } + 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) + end + + def average_elixir(deck) + sum = deck.sum { |c| c[:elixir_cost].to_i } + sum.fdiv(deck.size).round(2) + end + end + end +end \ No newline at end of file diff --git a/config/db/seeds.rb b/config/db/seeds.rb index fd04ba4..91d48a8 100644 --- a/config/db/seeds.rb +++ b/config/db/seeds.rb @@ -1,15 +1,71 @@ -# This seeds file should create the database records required to run the app. -# -# The code should be idempotent so that it can be executed at any time. -# -# To load the seeds, run `hanami db seed`. Seeds are also loaded as part of `hanami db prepare`. +# frozen_string_literal: true -# For example, if you have appropriate repos available: -# -# category_repo = Hanami.app["repos.category_repo"] -# category_repo.create(title: "General") -# -# Alternatively, you can use relations directly: -# -# categories = Hanami.app["relations.categories"] -# categories.insert(title: "General") +repo = ClashDeckGenerator2::Repos::CardsRepo.new + +CARD_TYPES = %w[troop spell building].freeze +RARITIES = %w[common rare epic legendary champion].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 }, + + { 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 }, + + # ======================== + # 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? + raise "Missing rarity for #{c[:name]}" if c[:rarity].nil? + raise "Missing type for #{c[:name]}" if c[:type].nil? + + raise "Unknown rarity: #{c[:rarity]} for #{c[:name]}" unless RARITIES.include?(c[:rarity]) + 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 \ No newline at end of file diff --git a/script/test_deck.rb b/script/test_deck.rb new file mode 100644 index 0000000..1a06316 --- /dev/null +++ b/script/test_deck.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative "../config/app" + +Hanami.app.prepare + +repo = ClashDeckGenerator2::Repos::CardsRepo.new +cards = repo.all.select { |card| card.rarity == "common" } + +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 + +deck = cards.sample(8) + +puts "========================================" +puts "DECK GENERATED SUCCESSFULLY" +puts "========================================" + +deck.each_with_index do |card, index| + puts "#{index + 1}. #{card.name} | #{card.type} | #{card.rarity} | #{card.elixir_cost}" +end + +puts "----------------------------------------" +puts "Cards count: #{deck.size}" + +names = deck.map(&:name) +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 "========================================" \ No newline at end of file