sorare-rewards 0.1.0.beta11 → 1.0.0.beta5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.rb +0 -9
  5. data/lib/sorare/rewards/configuration.rb +7 -5
  6. data/lib/sorare/rewards/game_week.rb +63 -0
  7. data/lib/sorare/rewards/game_week_allocations.rb +32 -0
  8. data/lib/sorare/rewards/game_week_config.rb +38 -0
  9. data/lib/sorare/rewards/interactor.rb +24 -0
  10. data/lib/sorare/rewards/interactors/allocations/compute_for_game_week.rb +0 -1
  11. data/lib/sorare/rewards/interactors/allocations/compute_for_league.rb +0 -1
  12. data/lib/sorare/rewards/interactors/allocations/compute_for_quality.rb +0 -1
  13. data/lib/sorare/rewards/interactors/allocations/compute_for_rarity.rb +0 -1
  14. data/lib/sorare/rewards/interactors/build.rb +12 -10
  15. data/lib/sorare/rewards/interactors/cards/pick_for_division.rb +8 -12
  16. data/lib/sorare/rewards/interactors/cards/pick_for_division_and_rarity.rb +7 -7
  17. data/lib/sorare/rewards/interactors/cards/pick_for_division_rarity_and_quality.rb +15 -10
  18. data/lib/sorare/rewards/interactors/cards/pick_for_game_week.rb +6 -8
  19. data/lib/sorare/rewards/interactors/cards/pick_for_league.rb +22 -22
  20. data/lib/sorare/rewards/interactors/pick.rb +8 -3
  21. data/lib/sorare/rewards/interactors/prize_pools/compute_for_division.rb +32 -0
  22. data/lib/sorare/rewards/interactors/prize_pools/compute_for_game_week.rb +27 -0
  23. data/lib/sorare/rewards/interactors/prize_pools/compute_for_league.rb +30 -0
  24. data/lib/sorare/rewards/interactors/supply/compute_for_game_week.rb +16 -5
  25. data/lib/sorare/rewards/interactors/supply/compute_for_quality.rb +0 -2
  26. data/lib/sorare/rewards/interactors/supply/compute_for_rarity.rb +4 -14
  27. data/lib/sorare/rewards/interactors/tiers/qualify_players.rb +2 -3
  28. data/lib/sorare/rewards/interactors/tiers/qualify_supply.rb +0 -1
  29. data/lib/sorare/rewards/league.rb +55 -0
  30. data/lib/sorare/rewards/picker.rb +41 -0
  31. data/lib/sorare/rewards/player.rb +77 -0
  32. data/lib/sorare/rewards/prize_pool_configuration.yml +9 -0
  33. data/lib/sorare/rewards/version.rb +1 -1
  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: 0a70b22851cb74e28254ed02396bcb2663b0af657ea7b91ab95517d8be764e27
4
+ data.tar.gz: 8bee3c9c69e8e7cbf16ad0f612b9cbbde502582cc657e4b952ded56e6c11fce5
5
5
  SHA512:
6
- metadata.gz: 921aa2e26ae7271027c6c91887e6a8a4bf975f4084bfce759871ca1a25192e8420c8aa25a5a9ed1e3854d4ec99d756fd267f7b66ee8c3bf5f36c0f7c3f33f49e
7
- data.tar.gz: fc32cd8862c4de110f9a6d2f101755230a5aaa9c1b0c89bfbbba48b4d8ce5b9ab1d0a09afb2772d3db9f026d1879bee37c6c5ddd11faebb47e4385242a9baf97
6
+ metadata.gz: 9ccfd7fa498eb3c7795a0c658a6921703bcd25bd536f3742aac1492e7d7e0963bb6828b4ed3836e40aab6d1844542353c137c447e461af3ca88da3eb67969936
7
+ data.tar.gz: 6baec2fd1cec6cbdb0c3493c64169afabe0d07df70920b6f8a8a253a8a330053feed8a1a03ed62e0d4034b6aab601a4fe90dd8a213c514fdf77bf56fddf2d59e
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.beta5)
5
5
  activesupport (~> 6.1)
6
6
  interactor (~> 3.0)
7
7
  json (~> 2)
data/data.patch ADDED
File without changes
@@ -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
@@ -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] || 1
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.beta5'
6
6
  end
7
7
  end
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.beta5
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