You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

102 lines
3.3 KiB

# Builds the rotating banner "badges" that sit alongside the ranked leaderboard.
# The banner picks one at a time or cycles through the whole list.
#
# Three kinds, matching the client's brief:
# #1 encouragement "Ketch-up <Country>!" bottom 10 featured countries
# #2 new_players "Yay! New players in <Country>" countries with recent sign-ups
# "<N> new players in <Country>!" (benchmark variant)
# #3 participation "<Country>, every tomato counts!" everyone not covered above
#
# Tunables — the client's example numbers, adjust freely:
class LeaderboardBadges
NEW_PLAYER_WINDOW = 24.hours # #2: "new players in the last X hours"
BENCHMARKS = [100, 50, 10] # #2: highest crossed threshold wins
ENCOURAGEMENT_COUNT = 10 # #1: rotate across the bottom N
# leaderboard: the array returned by LeaderboardScore.call (already ranked).
def self.call(leaderboard)
new(leaderboard).call
end
def initialize(leaderboard)
@leaderboard = leaderboard
end
# At most three badges — one random pick of each type. The pick changes on
# each cache refresh, so the banner naturally rotates through countries.
def call
[
new_player_badges.sample,
encouragement_badges.sample,
participation_badges.sample
].compact
end
private
# #2 — countries that gained new players (started sessions) within the window.
def new_player_badges
new_player_counts.map do |code, count|
benchmark = BENCHMARKS.find { |b| count >= b }
message = if benchmark
"#{benchmark} new players in #{country_name(code)}!"
else
"Yay! New players in #{country_name(code)}"
end
badge(:new_players, code, message)
end
end
# #1 — encouragement for the bottom N countries that are actually playing.
def encouragement_badges
bottom_featured.map do |code|
badge(:encouragement, code, "Ketch-up #{country_name(code)}!")
end
end
# #3 — everyone not featured on the leaderboard and not covered by #1 or #2.
def participation_badges
covered = (new_player_counts.keys + bottom_featured).to_set
unfeatured_codes.reject { |code| covered.include?(code) }.map do |code|
badge(:participation, code, "#{country_name(code)}, every tomato counts!")
end
end
# Countries with at least one tomato — the ones "on" the leaderboard.
def featured
@featured ||= @leaderboard.select { |row| row[:tomatoes].to_i.positive? }
end
# @leaderboard is ranked high→low, so the last N featured are the bottom N.
def bottom_featured
@bottom_featured ||= featured.last(ENCOURAGEMENT_COUNT).map { |row| code_of(row) }
end
def unfeatured_codes
featured_set = featured.map { |row| code_of(row) }.to_set
@leaderboard.map { |row| code_of(row) }.reject { |code| featured_set.include?(code) }
end
def new_player_counts
@new_player_counts ||= Player
.where(country: known_codes)
.where(created_at: NEW_PLAYER_WINDOW.ago..)
.group(:country).count
end
def known_codes
@known_codes ||= I18n.t("countries").keys.map(&:to_s)
end
def code_of(row)
row[:country_code].to_s.downcase
end
def country_name(code)
I18n.t("countries.#{code}", default: code.to_s.upcase)
end
def badge(type, code, message)
{ type: type.to_s, country_code: code.to_s.upcase, message: message }
end
end