sorare-rewards 0.1.0.beta11 → 1.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/data.patch +0 -0
  4. data/lib/sorare/rewards/configuration.rb +7 -5
  5. data/lib/sorare/rewards/game_week.rb +63 -0
  6. data/lib/sorare/rewards/game_week_allocations.rb +32 -0
  7. data/lib/sorare/rewards/game_week_config.rb +38 -0
  8. data/lib/sorare/rewards/interactor.rb +24 -0
  9. data/lib/sorare/rewards/interactors/allocations/compute_for_game_week.rb +0 -1
  10. data/lib/sorare/rewards/interactors/allocations/compute_for_league.rb +0 -1
  11. data/lib/sorare/rewards/interactors/allocations/compute_for_quality.rb +0 -1
  12. data/lib/sorare/rewards/interactors/allocations/compute_for_rarity.rb +0 -1
  13. data/lib/sorare/rewards/interactors/build.rb +12 -10
  14. data/lib/sorare/rewards/interactors/cards/pick_for_division.rb +8 -12
  15. data/lib/sorare/rewards/interactors/cards/pick_for_division_and_rarity.rb +7 -7
  16. data/lib/sorare/rewards/interactors/cards/pick_for_division_rarity_and_quality.rb +15 -10
  17. data/lib/sorare/rewards/interactors/cards/pick_for_game_week.rb +6 -8
  18. data/lib/sorare/rewards/interactors/cards/pick_for_league.rb +22 -22
  19. data/lib/sorare/rewards/interactors/pick.rb +8 -3
  20. data/lib/sorare/rewards/interactors/prize_pools/compute_for_division.rb +32 -0
  21. data/lib/sorare/rewards/interactors/prize_pools/compute_for_game_week.rb +27 -0
  22. data/lib/sorare/rewards/interactors/prize_pools/compute_for_league.rb +30 -0
  23. data/lib/sorare/rewards/interactors/supply/compute_for_game_week.rb +16 -5
  24. data/lib/sorare/rewards/interactors/supply/compute_for_quality.rb +0 -2
  25. data/lib/sorare/rewards/interactors/supply/compute_for_rarity.rb +4 -14
  26. data/lib/sorare/rewards/interactors/tiers/qualify_players.rb +2 -3
  27. data/lib/sorare/rewards/interactors/tiers/qualify_supply.rb +0 -1
  28. data/lib/sorare/rewards/league.rb +55 -0
  29. data/lib/sorare/rewards/picker.rb +41 -0
  30. data/lib/sorare/rewards/player.rb +77 -0
  31. data/lib/sorare/rewards/prize_pool_configuration.yml +9 -0
  32. data/lib/sorare/rewards/version.rb +1 -1
  33. data/lib/sorare/rewards.rb +0 -9
  34. metadata +14 -3
  35. data/lib/sorare/rewards/interactors/supply/compute_for_league.rb +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77c0cb011e78b240a5708daaef48b59be3bd6919c44fc0a84515314c77e2ee10
4
- data.tar.gz: d248dd322adda855d1668f9b6cc3424d98c0c0a33dc95f5d7caf2b9b457b3fd7
3
+ metadata.gz: 8b703ed076cf77397da8c97fcae63c48379b11fda722bcb047c9e9a7e2f47fb9
4
+ data.tar.gz: 6a428a838d574916a4a5ad3dedbc79046845a4f9b6009b28fde70adbc1ac673d
5
5
  SHA512:
6
- metadata.gz: 921aa2e26ae7271027c6c91887e6a8a4bf975f4084bfce759871ca1a25192e8420c8aa25a5a9ed1e3854d4ec99d756fd267f7b66ee8c3bf5f36c0f7c3f33f49e
7
- data.tar.gz: fc32cd8862c4de110f9a6d2f101755230a5aaa9c1b0c89bfbbba48b4d8ce5b9ab1d0a09afb2772d3db9f026d1879bee37c6c5ddd11faebb47e4385242a9baf97
6
+ metadata.gz: cea7dd5e7e61647fa6a30718917059a1dd25f34d864c6214f44959d4a546665db3b0125a00742da5216d1ed56284d28f4f5403cca77fb1c714089759aaf371de
7
+ data.tar.gz: dd66f18067ae9b07202ac98d0e89b2a80cc52042726ab40eb6da73fa947905aca45343b319815ed8917031ba53dd993ca90b6ec2a484f9e05dea75449e9954e4
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sorare-rewards (0.1.0.beta11)
4
+ sorare-rewards (1.0.0.beta4)
5
5
  activesupport (~> 6.1)
6
6
  interactor (~> 3.0)
7
7
  json (~> 2)
data/data.patch ADDED
File without changes
@@ -6,19 +6,21 @@ module Sorare
6
6
  module Rewards
7
7
  # Configuration stores the runtime configuration
8
8
  class Configuration
9
- attr_accessor :tiers, :gateway, :transform_tier, :transform_division, :allocation_configuration
9
+ attr_accessor :tiers, :gateway, :transform_tier, :transform_division,
10
+ :allocation_configuration, :prize_pool_configuration
10
11
 
11
12
  def initialize
12
13
  @tiers = 4
13
14
  @gateway = 'https://gateway.pinata.cloud/ipfs/'
14
15
  @transform_tier = ->(tier) { "tier_#{tier}" }
15
16
  @transform_division = ->(division) { "D#{division}" }
16
- @allocation_configuration = load_configuration
17
+ @allocation_configuration = load_yaml_configuration_file('allocation_configuration.yml')['leagues']
18
+ @prize_pool_configuration = load_yaml_configuration_file('prize_pool_configuration.yml')['rates']
17
19
  end
18
20
 
19
- def load_configuration
20
- YAML.safe_load(File.read("#{File.dirname(__FILE__)}/allocation_configuration.yml"), [], [], true)
21
- .with_indifferent_access['leagues']
21
+ def load_yaml_configuration_file(filename)
22
+ YAML.safe_load(File.read("#{File.dirname(__FILE__)}/#{filename}"), [], [], true)
23
+ .with_indifferent_access
22
24
  end
23
25
  end
24
26
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+
6
+ module Sorare
7
+ module Rewards
8
+ # GameWeek stores the reward data of a game week
9
+ class GameWeek
10
+ attr_reader :data, :config, :salt
11
+
12
+ def initialize(data: nil, hash: nil, salt: nil)
13
+ uri = URI("#{Sorare::Rewards.configuration.gateway}#{hash}")
14
+ @data = data || JSON.parse(::Net::HTTP.get(uri))
15
+ @config = GameWeekConfig.new(@data['config'])
16
+ @salt = salt
17
+ end
18
+
19
+ %w[public_seed version supply playing_players leagues].each do |key|
20
+ define_method(key) do
21
+ data[key]
22
+ end
23
+ end
24
+
25
+ def supply_for(league, rarity: nil, player: nil)
26
+ return (supply.dig(league, rarity, player) || 0) if rarity && player
27
+ return (supply.dig(league, rarity) || {}) if rarity
28
+
29
+ supply[league] || {}
30
+ end
31
+
32
+ def each_league
33
+ (leagues || {}).each_key do |league_name|
34
+ return to_enum(:each_league) unless block_given?
35
+
36
+ yield(league(league_name))
37
+ end
38
+ end
39
+
40
+ def rewards
41
+ raise 'Salt required' unless salt
42
+
43
+ @rewards ||= Sorare::Rewards::Build.call!(game_week: self, salt: salt)
44
+ end
45
+
46
+ def card_rewards
47
+ @card_rewards ||= Sorare::Rewards::Pick.call!(rewards.to_h)
48
+ end
49
+
50
+ def league(name)
51
+ raise 'Unknown league' unless (leagues || {})[name]
52
+
53
+ League.new(
54
+ game_week: self, name: name, config: leagues[name], supply: supply[leagues.dig(name, 'pool_supply')]
55
+ )
56
+ end
57
+
58
+ def player(slug)
59
+ Player.new(slug, self)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sorare
4
+ module Rewards
5
+ # GameWeekAllocations stores the expected allocations for a game week
6
+ class GameWeekAllocations
7
+ attr_reader :allocations
8
+
9
+ def initialize(allocations)
10
+ @allocations = allocations
11
+ end
12
+
13
+ def each_league_allocations
14
+ allocations.each_key do |league_name|
15
+ return to_enum(:each_league_allocations) unless block_given?
16
+
17
+ yield(league_name, allocations[league_name])
18
+ end
19
+ end
20
+
21
+ def each_league_rarity_allocations
22
+ each_league_allocations do |league_name, league_allocations|
23
+ league_allocations.each_key do |rarity|
24
+ return to_enum(:each_league_rarity_allocations) unless block_given?
25
+
26
+ yield(league_name, rarity, league_allocations[rarity])
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module Sorare
6
+ module Rewards
7
+ # GameWeekConfig stores the config used to reward a game week
8
+ class GameWeekConfig
9
+ attr_reader :config
10
+
11
+ def initialize(config)
12
+ @config = config
13
+ end
14
+
15
+ %w[cooldown limits prize_pool_currency].each do |key|
16
+ define_method(key) do
17
+ config[key]
18
+ end
19
+ end
20
+
21
+ def over_limit?(rarity, rewarded)
22
+ limits[rarity] <= rewarded
23
+ end
24
+
25
+ def distribution_limit(rarity)
26
+ limits[rarity]
27
+ end
28
+
29
+ def cooldown_limit(rarity)
30
+ cooldown.dig(rarity, 'quantity')
31
+ end
32
+
33
+ def cooldown_since(rarity)
34
+ cooldown.dig(rarity, 'since')
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'interactor'
5
+
6
+ module Sorare
7
+ module Rewards
8
+ # Interactor extends Interactor to improve on it
9
+ module Interactor
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ include ::Interactor
14
+
15
+ # make sure we fail the current context when a call! to another interactor fails
16
+ around do |interactor|
17
+ interactor.call
18
+ rescue ::Interactor::Failure => e
19
+ context.fail!(error: e.context.error)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
4
  require 'active_support/core_ext/enumerable'
5
- require 'interactor'
6
5
 
7
6
  module Sorare
8
7
  module Rewards
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
4
  require 'active_support/core_ext/enumerable'
5
- require 'interactor'
6
5
 
7
6
  module Sorare
8
7
  module Rewards
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
- require 'interactor'
5
4
 
6
5
  module Sorare
7
6
  module Rewards
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
4
  require 'active_support/core_ext/enumerable'
5
- require 'interactor'
6
5
 
7
6
  module Sorare
8
7
  module Rewards
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/core_ext/enumerable'
4
- require 'active_support/core_ext/hash/indifferent_access'
5
3
  require 'active_support/core_ext/module/delegation'
6
- require 'interactor'
7
4
 
8
5
  module Sorare
9
6
  module Rewards
@@ -11,22 +8,27 @@ module Sorare
11
8
  class Build
12
9
  include Interactor
13
10
 
14
- delegate :data, :salt, to: :context
11
+ delegate :game_week, :salt, to: :context
15
12
 
16
13
  def call
17
- context.game_week_supply = game_week_supply
18
- context.allocations = allocate!
14
+ context.card_allocations = allocate!
15
+ context.prize_pools = prize_pools!
16
+ context.prize_pool_currency = game_week.config.prize_pool_currency
17
+ end
18
+
19
+ def game_week_supply
20
+ @game_week_supply ||= Supply::ComputeForGameWeek.call!(game_week: game_week, salt: salt)
21
+ .game_week_supply
19
22
  end
20
23
 
21
24
  def allocate!
22
25
  Allocations::ComputeForGameWeek.call!(
23
- **data.to_h, supply: game_week_supply, salt: salt
26
+ game_week: game_week, supply: game_week_supply, salt: salt
24
27
  ).game_week_allocations
25
28
  end
26
29
 
27
- def game_week_supply
28
- @game_week_supply ||= Supply::ComputeForGameWeek.call!(**data.to_h, salt: salt)
29
- .game_week_supply
30
+ def prize_pools!
31
+ PrizePools::ComputeForGameWeek.call!(game_week: game_week, salt: salt).prize_pools
30
32
  end
31
33
  end
32
34
  end
@@ -2,23 +2,16 @@
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
4
  require 'active_support/core_ext/enumerable'
5
- require 'interactor'
6
5
  require 'yaml'
7
6
 
8
7
  module Sorare
9
8
  module Rewards
10
9
  module Cards
11
10
  # PickForDivision picks the rewards for a given division
12
- # Receive a list of eligible qualified cards
11
+ # Receive a list of pickers for each tiers and quality
13
12
  # {
14
- # 'rare' => {
15
- # 0 => ['kylian-mbappe-lottin', 'kylian-mbappe-lottin', 'keylor-navas', ...]
16
- # }
17
- # }
18
- # A game week league supply
19
- # {
20
- # 'rare' => { 'kylian-mbappe-lottin' => { 'rank' => 1 }, ...},
21
- # 'super_rare' => { 'kylian-mbappe-lottin' => { 'rank' => 1 }, ...}
13
+ # 'rare' => { 'tier_0' => Picker, 'tier_1' => Picker },
14
+ # 'super_rare' => 'tier_0' => Picker, 'tier_1' => Picker }
22
15
  # }
23
16
  # And a reward allocations
24
17
  # {
@@ -29,12 +22,15 @@ module Sorare
29
22
  class PickForDivision
30
23
  include Interactor
31
24
 
32
- delegate :allocations, :cards, :qualified_players, :supply, to: :context
25
+ delegate :league, :allocations, :pickers, :qualified_players, to: :context
33
26
 
34
27
  def call
35
28
  context.division_picks = allocations.keys.index_with do |rarity|
36
29
  PickForDivisionAndRarity.call!(
37
- **context.to_h, cards: cards[rarity], allocations: allocations[rarity], supply: supply[rarity]
30
+ **context.to_h,
31
+ rarity: rarity,
32
+ pickers: pickers[rarity],
33
+ allocations: allocations[rarity]
38
34
  ).rarity_picks
39
35
  end
40
36
  end
@@ -2,24 +2,22 @@
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
4
  require 'active_support/core_ext/enumerable'
5
- require 'interactor'
6
5
 
7
6
  module Sorare
8
7
  module Rewards
9
8
  module Cards
10
9
  # PickForDivisionAndRarity picks the rewards for a given division and rarity
11
- # Receive a randomizer, a list of eligible qualified cards of the corresponding rarity
10
+ # Receive a list of pickers for each tiers
12
11
  # {
13
- # 'tier_0' => ['kylian-mbappe-lottin', 'kylian-mbappe-lottin', 'keylor-navas', ...]
12
+ # 'tier_0' => Picker, 'tier_1' => Picker
14
13
  # }
15
- # A game week league rarity supply
16
- # { 'kylian-mbappe-lottin' => { 'rank' => 1 }, ...}
14
+ # A game week data
17
15
  # And a reward allocations
18
16
  # { 'tier_0' => 1, 'tier_1' => 2 }
19
17
  class PickForDivisionAndRarity
20
18
  include Interactor
21
19
 
22
- delegate :allocations, :cards, to: :context
20
+ delegate :league, :rarity, :allocations, :pickers, to: :context
23
21
 
24
22
  def call
25
23
  context.rarity_picks = supply!
@@ -27,8 +25,10 @@ module Sorare
27
25
 
28
26
  def supply!
29
27
  allocations.keys.index_with do |tier|
28
+ next [] if pickers&.dig(tier).nil?
29
+
30
30
  PickForDivisionRarityAndQuality.call!(**context.to_h, allocations: allocations[tier],
31
- cards: cards[tier]).picks
31
+ picker: pickers[tier]).picks
32
32
  end
33
33
  end
34
34
  end
@@ -1,16 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
- require 'interactor'
5
4
 
6
5
  module Sorare
7
6
  module Rewards
8
7
  module Cards
9
8
  # PickForDivisionRarityAndQuality picks the rewards for a given division, rarity, and quality
10
- # Receive a list of eligible qualified cards
11
- # ['kylian-mbappe-lottin', 'kylian-mbappe-lottin', 'keylor-navas', ...]
12
- # A game week league rarity supply
13
- # { 'kylian-mbappe-lottin' => { 'rank' => 1 }, ...}
9
+ # Receive a picker
10
+ # A game week data
14
11
  # And a reward allocations
15
12
  # 15
16
13
  class PickForDivisionRarityAndQuality
@@ -18,7 +15,8 @@ module Sorare
18
15
 
19
16
  MISSING_CARDS = 'There are not enough cards to fulfill the requirements'
20
17
 
21
- delegate :cards, :allocations, :supply, :force, :picks, :randomizer, to: :context
18
+ delegate :picker, :allocations, :league, :rarity, :force, :picks, to: :context
19
+ delegate :game_week, to: :league
22
20
 
23
21
  def call
24
22
  context.picks = []
@@ -29,13 +27,20 @@ module Sorare
29
27
  end
30
28
 
31
29
  def pick!
32
- while picks.length != allocations && cards.length.positive?
33
- picks.push(cards.delete_at(randomizer.rand * cards.length))
34
- end
30
+ pick_if_available! while picks.length != allocations && picker.length.positive?
31
+ end
32
+
33
+ def pick_if_available!
34
+ player = picker.pick
35
+ return if picker.player_counter[player] > game_week.player(player).supply(league, rarity).ceil
36
+
37
+ picks.push(player)
35
38
  end
36
39
 
37
40
  def reorder!
38
- picks.sort! { |a, b| supply[a]['rank'].to_i <=> supply[b]['rank'].to_i }
41
+ picks.sort! do |a, b|
42
+ game_week.player(a).rank(league, rarity) <=> game_week.player(b).rank(league, rarity)
43
+ end
39
44
  end
40
45
 
41
46
  def check_length!
@@ -2,15 +2,12 @@
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
4
  require 'active_support/core_ext/enumerable'
5
- require 'interactor'
6
- require 'yaml'
7
5
 
8
6
  module Sorare
9
7
  module Rewards
10
8
  module Cards
11
9
  # PickForGameWeek picks the rewards for a given game week
12
- # Receive a Game Week reward supply data
13
- # { public_seed: 123, playing_players: [...], supply: {...} }
10
+ # Receive a Game Week object
14
11
  # And a reward allocations
15
12
  # {
16
13
  # 'global-all_star' => {
@@ -22,16 +19,17 @@ module Sorare
22
19
  class PickForGameWeek
23
20
  include Interactor
24
21
 
25
- delegate :supply, :allocations, :randomizer, :public_seed, :salt, to: :context
22
+ delegate :game_week, :allocations, :salt, :picks, to: :context
26
23
 
27
24
  def call
28
- context.randomizer = Sorare::Rewards::Random.new(public_seed, salt)
29
25
  context.picks = picks
30
26
  end
31
27
 
32
28
  def picks
33
- allocations.keys.index_with do |league|
34
- PickForLeague.call!(**context.to_h, supply: supply[league], allocations: allocations[league]).league_picks
29
+ allocations.each_league_allocations.each_with_object({}) do |(league_name, league_allocations), picks|
30
+ picks[league_name] = PickForLeague.call!(
31
+ **context.to_h, league: game_week.league(league_name), allocations: league_allocations
32
+ ).league_picks
35
33
  end
36
34
  end
37
35
  end
@@ -2,19 +2,13 @@
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
4
  require 'active_support/core_ext/enumerable'
5
- require 'interactor'
6
- require 'yaml'
7
5
 
8
6
  module Sorare
9
7
  module Rewards
10
8
  module Cards
11
9
  # PickForLeague picks the rewards for a given league
12
- # Receive a game week league supply
13
- # {
14
- # 'rare' => { 'kylian-mbappe-lottin' => { 'rank' => 1 }, ...},
15
- # 'super_rare' => { 'kylian-mbappe-lottin' => { 'rank' => 1 }, ...}
16
- # }
17
- # And a reward allocations
10
+ # Receive a game week object
11
+ # And a league allocations
18
12
  # {
19
13
  # 'D1' => {
20
14
  # 'rare' => { 'tier_0' => 1, 'tier_1' => 2 }
@@ -23,40 +17,46 @@ module Sorare
23
17
  class PickForLeague
24
18
  include Interactor
25
19
 
26
- delegate :supply, :allocations, :cards, to: :context
20
+ delegate :salt, :league, :allocations, :pickers, to: :context
21
+ delegate :game_week, to: :league
27
22
 
28
23
  def call
29
- context.cards = {}
30
- add_cards!
24
+ context.pickers = {}
25
+ add_pickers!
31
26
 
32
27
  context.league_picks = allocations.keys.index_with do |division|
33
28
  PickForDivision.call!(
34
- **context.to_h, cards: cards, allocations: allocations[division]
29
+ **context.to_h, pickers: pickers, allocations: allocations[division]
35
30
  ).division_picks
36
31
  end
37
32
  end
38
33
 
39
- def add_cards!
40
- supply.keys.index_with do |rarity|
41
- cards[rarity] = {}
34
+ def add_pickers!
35
+ league.supply.keys.index_with do |rarity|
36
+ pickers[rarity] = {}
42
37
  qualify_and_add!(rarity)
43
38
  end
44
39
  end
45
40
 
46
41
  def qualify_and_add!(rarity)
47
- qualified_players = Tiers::QualifyPlayers.call!(sorted_supply: supply[rarity]).players
42
+ sorted_supply = league.supply[rarity]
43
+ qualified_players = Tiers::QualifyPlayers.call!(sorted_supply: sorted_supply).players
48
44
  qualified_players.each_with_index.map do |players, tier|
49
45
  transformed_tier = Sorare::Rewards.configuration.transform_tier.call(tier)
50
- cards[rarity][transformed_tier] = []
46
+ initialize_tier_picker(players, rarity, transformed_tier)
47
+ end
48
+ end
49
+
50
+ def initialize_tier_picker(players, rarity, tier)
51
+ pickers[rarity][tier] = Picker.new(game_week.public_seed, salt)
51
52
 
52
- players.each do |player_slug|
53
- add(player_slug, rarity, transformed_tier)
54
- end
53
+ players.each do |slug|
54
+ pickers[rarity][tier].add_player(slug, player_supply(league, rarity, slug))
55
55
  end
56
56
  end
57
57
 
58
- def add(player_slug, rarity, tier)
59
- cards[rarity][tier] += Array.new(supply[rarity][player_slug]['supply'], player_slug)
58
+ def player_supply(league, rarity, slug)
59
+ game_week.player(slug).overall_supply(league, rarity)
60
60
  end
61
61
  end
62
62
  end
@@ -3,7 +3,6 @@
3
3
  require 'active_support/core_ext/enumerable'
4
4
  require 'active_support/core_ext/hash/indifferent_access'
5
5
  require 'active_support/core_ext/module/delegation'
6
- require 'interactor'
7
6
 
8
7
  module Sorare
9
8
  module Rewards
@@ -20,15 +19,21 @@ module Sorare
20
19
  class Pick
21
20
  include Interactor
22
21
 
23
- delegate :allocations, :data, :salt, to: :context
22
+ delegate :card_allocations, :game_week, :salt, to: :context
24
23
 
25
24
  # Depending on the way the allocations interpreter is implemented it could be done by him or we
26
25
  # update the allocations process to match the structure
27
26
  def call
28
27
  context.picks = Sorare::Rewards::Cards::PickForGameWeek.call!(
29
- **data.to_h, salt: salt, allocations: Transposer.transpose_allocations(allocations)
28
+ **context.to_h,
29
+ salt: salt,
30
+ allocations: transposed_allocations
30
31
  ).picks
31
32
  end
33
+
34
+ def transposed_allocations
35
+ Sorare::Rewards::GameWeekAllocations.new(Transposer.transpose_allocations(card_allocations))
36
+ end
32
37
  end
33
38
  end
34
39
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/delegation'
4
+
5
+ module Sorare
6
+ module Rewards
7
+ module PrizePools
8
+ # ComputeForDivision computes the prize pools of a division
9
+ class ComputeForDivision
10
+ include Interactor
11
+
12
+ delegate :playing_rate, :available_prize_pools, to: :context
13
+
14
+ def call
15
+ context.division_prize_pools = available_prize_pools.map do |available_prize_pool|
16
+ available_prize_pool * percentage_rewarded
17
+ end
18
+ end
19
+
20
+ def percentage_rewarded
21
+ @percentage_rewarded ||= (config.find do |rate_config|
22
+ (rate_config.dig('playing_rates', 0)...rate_config.dig('playing_rates', 1)).include?(playing_rate)
23
+ end || config.last)['percentage_rewarded']
24
+ end
25
+
26
+ def config
27
+ Sorare::Rewards.configuration.prize_pool_configuration
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/enumerable'
4
+ require 'active_support/core_ext/module/delegation'
5
+
6
+ module Sorare
7
+ module Rewards
8
+ module PrizePools
9
+ # ComputeForGameWeek computes the prize pools of a game week
10
+ class ComputeForGameWeek
11
+ include Interactor
12
+
13
+ delegate :game_week, to: :context
14
+
15
+ def call
16
+ context.prize_pools = prize_pools!
17
+ end
18
+
19
+ def prize_pools!
20
+ game_week.each_league.each_with_object(Hash.new({})) do |league, game_week_prize_pools|
21
+ game_week_prize_pools[league.name] = ComputeForLeague.call!(league: league).league_prize_pools
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/delegation'
4
+
5
+ module Sorare
6
+ module Rewards
7
+ module PrizePools
8
+ # ComputeForLeague computes the prize pools of a league
9
+ class ComputeForLeague
10
+ include Interactor
11
+
12
+ delegate :league, to: :context
13
+
14
+ def call
15
+ context.league_prize_pools = prize_pools!
16
+ end
17
+
18
+ def prize_pools!
19
+ league.each_division_prize_pools.map do |division, prize_pools|
20
+ [
21
+ Sorare::Rewards.configuration.transform_division.call(division),
22
+ ComputeForDivision.call!(playing_rate: league.playing_rate, available_prize_pools: prize_pools)
23
+ .division_prize_pools
24
+ ]
25
+ end.to_h
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'active_support/core_ext/enumerable'
4
4
  require 'active_support/core_ext/module/delegation'
5
- require 'interactor'
6
5
 
7
6
  module Sorare
8
7
  module Rewards
@@ -11,16 +10,28 @@ module Sorare
11
10
  class ComputeForGameWeek
12
11
  include Interactor
13
12
 
14
- delegate :public_seed, :salt, :supply, :playing_players, :game_week_supply, to: :context
13
+ delegate :salt, :game_week, :game_week_supply, to: :context
15
14
 
16
15
  def call
17
- context.game_week_supply = supply.keys.index_with do |league|
18
- ComputeForLeague.call!(**context.to_h, randomizer: randomizer, supply: supply[league]).league_supply
16
+ context.game_week_supply = game_week_supply!
17
+ end
18
+
19
+ def game_week_supply!
20
+ game_week.each_league.each_with_object(Hash.new({})) do |league, game_week_supply|
21
+ game_week_supply[league.name] = league_supply!(league)
22
+ end
23
+ end
24
+
25
+ def league_supply!(league)
26
+ league.each_rarity_supply.each_with_object({}) do |(rarity, supply), league_supply|
27
+ league_supply[rarity] = ComputeForRarity.call!(
28
+ league: league, rarity: rarity, supply: supply, randomizer: randomizer
29
+ ).rarity_supply
19
30
  end
20
31
  end
21
32
 
22
33
  def randomizer
23
- @randomizer ||= Sorare::Rewards::Random.new(public_seed, salt)
34
+ @randomizer ||= Sorare::Rewards::Random.new(game_week.public_seed, salt)
24
35
  end
25
36
  end
26
37
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/core_ext/enumerable'
4
3
  require 'active_support/core_ext/module/delegation'
5
- require 'interactor'
6
4
 
7
5
  module Sorare
8
6
  module Rewards
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/core_ext/enumerable'
4
3
  require 'active_support/core_ext/module/delegation'
5
- require 'interactor'
6
4
 
7
5
  module Sorare
8
6
  module Rewards
@@ -11,8 +9,9 @@ module Sorare
11
9
  class ComputeForRarity
12
10
  include Interactor
13
11
 
14
- delegate :randomizer, :supply, :playing_players, :rewardable, :rarity_supply,
12
+ delegate :randomizer, :league, :rarity, :supply, :rewardable, :rarity_supply,
15
13
  :minimum_remaining_games, to: :context
14
+ delegate :game_week, to: :league
16
15
 
17
16
  def call
18
17
  context.rarity_supply = []
@@ -22,7 +21,7 @@ module Sorare
22
21
  end
23
22
 
24
23
  def compute_rewardable_supply!
25
- total_supply = playing_players.keys.sum do |player|
24
+ total_supply = supply.keys.sum do |player|
26
25
  player_supply(player)
27
26
  end
28
27
 
@@ -45,22 +44,13 @@ module Sorare
45
44
  end
46
45
 
47
46
  def player_supply(slug)
48
- remaining_games = playing_players[slug] || 0
49
- return 0 unless remaining_games.positive?
50
- return 0 unless supply[slug]
51
-
52
- supply[slug]['supply'].to_f / corrected_remaining_games(remaining_games)
47
+ game_week.player(slug).supply(league, rarity)
53
48
  end
54
49
 
55
50
  def rounded_supply(float_supply)
56
51
  float_supply.floor + remaining_supply(float_supply.modulo(1))
57
52
  end
58
53
 
59
- # To be improved to handle properly seasons overlap
60
- def corrected_remaining_games(remaining_games)
61
- [remaining_games, minimum_remaining_games || 10].max
62
- end
63
-
64
54
  def remaining_supply(reward_probability)
65
55
  probability = randomizer.rand
66
56
  return 0 unless probability < reward_probability
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
- require 'interactor'
5
4
 
6
5
  module Sorare
7
6
  module Rewards
@@ -19,9 +18,9 @@ module Sorare
19
18
 
20
19
  # Use the tier specified within the data
21
20
  def qualified_players_by_tier
22
- tiers = Sorare::Rewards.configuration.tiers.times.map { |_| [] }
21
+ tiers = Sorare::Rewards.configuration.tiers.times.map { |_| {} }
23
22
  sorted_supply.each do |slug, data|
24
- tiers[data['tier']] << slug
23
+ tiers[data['tier']][slug] = data
25
24
  end
26
25
 
27
26
  tiers
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/module/delegation'
4
- require 'interactor'
5
4
 
6
5
  module Sorare
7
6
  module Rewards
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module Sorare
6
+ module Rewards
7
+ # League stores the reward config for a league
8
+ class League
9
+ attr_reader :name, :supply, :config, :game_week
10
+
11
+ delegate :salt, to: :game_week
12
+
13
+ def initialize(name:, config:, supply:, game_week:)
14
+ @name = name
15
+ @config = config
16
+ @supply = supply
17
+ @game_week = game_week
18
+ end
19
+
20
+ %w[playing_rate prize_pools].each do |key|
21
+ define_method(key) do
22
+ config[key]
23
+ end
24
+ end
25
+
26
+ def each_division_prize_pools
27
+ prize_pools.each_with_index do |prize_pool, index|
28
+ return to_enum(:each_division_prize_pools) unless block_given?
29
+
30
+ yield(index + 1, prize_pool)
31
+ end
32
+ end
33
+
34
+ def each_rarity_supply
35
+ supply.each_key do |rarity|
36
+ return to_enum(:each_rarity_supply) unless block_given?
37
+
38
+ yield(rarity, supply[rarity])
39
+ end
40
+ end
41
+
42
+ def each_rarity_tier_supply
43
+ each_rarity_supply do |rarity, rarity_supply|
44
+ qualified_supply = Tiers::QualifyPlayers.call!(sorted_supply: rarity_supply).players
45
+ qualified_supply.each_key.map do |tier|
46
+ return to_enum(:each_rarity_tier_supply) unless block_given?
47
+
48
+ transformed_tier = Sorare::Rewards.configuration.transform_tier.call(tier)
49
+ yield(rarity, transformed_tier, qualified_supply[tier])
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+
6
+ module Sorare
7
+ module Rewards
8
+ # Picker allows to randomly pick a card
9
+ class Picker
10
+ attr_accessor :cards, :player_counter
11
+
12
+ delegate :length, to: :cards
13
+
14
+ def initialize(seed, salt)
15
+ @random = Random.new(seed, salt)
16
+ @cards = []
17
+ @player_counter = Hash.new(0)
18
+ @shuffled = false
19
+ end
20
+
21
+ def add_player(slug, count)
22
+ raise "Can't add a player to a started draw" if @shuffled
23
+
24
+ @cards += Array.new(count, slug)
25
+ end
26
+
27
+ def pick
28
+ shuffle! unless @shuffled
29
+
30
+ @cards.shift.tap do |card|
31
+ @player_counter[card] += 1 if card
32
+ end
33
+ end
34
+
35
+ def shuffle!
36
+ @cards.shuffle!(random: @random)
37
+ @shuffled = true
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'time'
5
+
6
+ module Sorare
7
+ module Rewards
8
+ # Player stores the reward data of a player for a game week
9
+ class Player
10
+ attr_reader :slug
11
+
12
+ def initialize(slug, reward_data)
13
+ @slug = slug
14
+ @supply = Hash.new { |h, k| h[k] = {} }
15
+ @config = reward_data.config
16
+ @game_data = reward_data.playing_players[slug]
17
+ load_supply(reward_data)
18
+ end
19
+
20
+ def rewardable?(league, rarity)
21
+ !@config.cooldown?(rarity, @game_data.dig(league, rarity) || [])
22
+ end
23
+
24
+ def playing?
25
+ @game_data.present?
26
+ end
27
+
28
+ def supply(league, rarity)
29
+ return 0 unless playing? && remaining_games_for_supply.positive?
30
+
31
+ [
32
+ uncapped_supply_for_game_week(league, rarity), # Remaining supply
33
+ available_during_cooldown(league, rarity), # Cooldown limit
34
+ @config.distribution_limit(rarity) # Hard limit
35
+ ].min
36
+ end
37
+
38
+ def rank(league, rarity)
39
+ @supply.dig(league.name, rarity, 'rank')
40
+ end
41
+
42
+ def overall_supply(league, rarity)
43
+ @supply.dig(league.name, rarity, 'supply') || 0
44
+ end
45
+
46
+ def uncapped_supply_for_game_week(league, rarity)
47
+ overall_supply(league, rarity) / remaining_games_for_supply.to_f
48
+ end
49
+
50
+ def available_during_cooldown(league, rarity)
51
+ [@config.cooldown_limit(rarity) - rewarded_during_cooldown(league, rarity), 0].max
52
+ end
53
+
54
+ def rewarded_during_cooldown(league, rarity)
55
+ (rewards&.dig(league) || []).count { |at| (Time.parse(at) + @config.cooldown_since(rarity)) < Time.now }
56
+ end
57
+
58
+ %w[rewards remaining_games_for_supply remaining_games].each do |key|
59
+ define_method(key) do
60
+ @game_data&.dig(key)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def load_supply(reward_data)
67
+ reward_data.each_league do |league|
68
+ league.each_rarity_supply do |rarity, supply|
69
+ next unless supply[slug]
70
+
71
+ @supply[league.name][rarity] = supply[slug]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,9 @@
1
+ rates:
2
+ - playing_rates: [0, 0.1]
3
+ percentage_rewarded: 0.25
4
+ - playing_rates: [0.1, 0.2]
5
+ percentage_rewarded: 0.5
6
+ - playing_rates: [0.2, 0.3]
7
+ percentage_rewarded: 0.75
8
+ - playing_rates: [0.3, 1]
9
+ percentage_rewarded: 1
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sorare
4
4
  module Rewards
5
- VERSION = '0.1.0.beta11'
5
+ VERSION = '1.0.0.beta4'
6
6
  end
7
7
  end
@@ -21,15 +21,6 @@ module Sorare
21
21
  end
22
22
  end
23
23
 
24
- def self.game_week_data(hash)
25
- uri = URI("#{configuration.gateway}#{hash}")
26
- JSON.parse(::Net::HTTP.get(uri))
27
- end
28
-
29
- def self.build_rewards(salt:, hash: nil, data: nil)
30
- Sorare::Rewards::Build.call!(data: data || game_week_data(hash), salt: salt).allocations
31
- end
32
-
33
24
  def self.pick(salt:, hash: nil, data: nil)
34
25
  allocation_ctx = Sorare::Rewards::Build.call!(data: data || game_week_data(hash), salt: salt)
35
26
  Sorare::Rewards::Pick.call!(allocation_ctx.to_h).picks
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sorare-rewards
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.beta11
4
+ version: 1.0.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - pierre
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-06-02 00:00:00.000000000 Z
11
+ date: 2021-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -168,9 +168,14 @@ files:
168
168
  - Rakefile
169
169
  - bin/console
170
170
  - bin/setup
171
+ - data.patch
171
172
  - lib/sorare/rewards.rb
172
173
  - lib/sorare/rewards/allocation_configuration.yml
173
174
  - lib/sorare/rewards/configuration.rb
175
+ - lib/sorare/rewards/game_week.rb
176
+ - lib/sorare/rewards/game_week_allocations.rb
177
+ - lib/sorare/rewards/game_week_config.rb
178
+ - lib/sorare/rewards/interactor.rb
174
179
  - lib/sorare/rewards/interactors/allocations/compute_for_game_week.rb
175
180
  - lib/sorare/rewards/interactors/allocations/compute_for_league.rb
176
181
  - lib/sorare/rewards/interactors/allocations/compute_for_quality.rb
@@ -182,12 +187,18 @@ files:
182
187
  - lib/sorare/rewards/interactors/cards/pick_for_game_week.rb
183
188
  - lib/sorare/rewards/interactors/cards/pick_for_league.rb
184
189
  - lib/sorare/rewards/interactors/pick.rb
190
+ - lib/sorare/rewards/interactors/prize_pools/compute_for_division.rb
191
+ - lib/sorare/rewards/interactors/prize_pools/compute_for_game_week.rb
192
+ - lib/sorare/rewards/interactors/prize_pools/compute_for_league.rb
185
193
  - lib/sorare/rewards/interactors/supply/compute_for_game_week.rb
186
- - lib/sorare/rewards/interactors/supply/compute_for_league.rb
187
194
  - lib/sorare/rewards/interactors/supply/compute_for_quality.rb
188
195
  - lib/sorare/rewards/interactors/supply/compute_for_rarity.rb
189
196
  - lib/sorare/rewards/interactors/tiers/qualify_players.rb
190
197
  - lib/sorare/rewards/interactors/tiers/qualify_supply.rb
198
+ - lib/sorare/rewards/league.rb
199
+ - lib/sorare/rewards/picker.rb
200
+ - lib/sorare/rewards/player.rb
201
+ - lib/sorare/rewards/prize_pool_configuration.yml
191
202
  - lib/sorare/rewards/random.rb
192
203
  - lib/sorare/rewards/transposer.rb
193
204
  - lib/sorare/rewards/version.rb
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/core_ext/enumerable'
4
- require 'active_support/core_ext/module/delegation'
5
- require 'interactor'
6
-
7
- module Sorare
8
- module Rewards
9
- module Supply
10
- # ComputeForLeague computes the rewardable supply of a league
11
- class ComputeForLeague
12
- include Interactor
13
-
14
- delegate :randomizer, :supply, :playing_players, to: :context
15
-
16
- def call
17
- context.league_supply = supply.keys.index_with do |rarity|
18
- ComputeForRarity.call!(**context.to_h, supply: supply[rarity]).rarity_supply
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end