Last active
April 14, 2023 14:51
-
-
Save Hasstrup/726d9940fc0032863a826be4512d585d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Implement the sections of code marked TODO above. | |
# Rules for bidding: | |
# Agents always bid in sequence, e.g. agent 1 bids first, then agent 2, etc. | |
# The bidding starts with the first agent deciding on their initial bid (which will be returned by `get_bid_increase`). The amount can also be 0. | |
# The next agent (e.g. agent 2) must then increase their bid so that their bid is as much as any other agent's bid (which so far is only agent 1), or be forced to withdraw from the bidding completely. (if the agent has withdrawn, they will be skipped from here onwards). They may also decide to bid more, in which case the next agent has to match that higher bid (or withdraw). | |
# The bidding will come around to agent 1 again if their current bid does not match the highest bid. The agent must increase their bid (by returning the value they want to increase it by from `getBidIncrease`) so that their bid will match the highest bid that any other agent has put in so far, or withdraw from the bidding. They can also have the option to decide to bid more, same as discussed above. | |
# Keep in mind that the bidding can circle the group of agents more than once, depending on if agents up their bids. Each agent who wishes to proceed is required to have committed a bid that is equal to the highest bid made by any other agent, counted over the duration of the bidding process. NB: The bidding will end immediately once this condition is met - an agent is not allowed to up their bid once everyone else has matched them. | |
# There can be multiple agents left at the end of bidding process, all with the same bid. That will be handled by a second bidding process that's outside the scope of this exercise. | |
# Hints: | |
# - Be sure you understand the above bidding rules clearly. | |
# - Do not implement a literal solution to the problem, i.e. don't read each line on its own and write code just for that part of the requirements. Rather, think about it holistically so you can come up with a solution that meets all the requirements. | |
# - No consideration should be paid to performance - clear, readable code is more imporant in this exercise. | |
class Agent | |
# This will already be implemented and is not part of this exercise | |
def initialize; end | |
# This will return the amount (integer) by which the agent wants to increase its bid | |
# (i.e. how much they want to add onto their bid as it stands so far) | |
# This will already be implemented and is not part of this exercise | |
def get_bid_increase; end | |
end | |
class Bidding | |
INITIAL_BID = 0 | |
POSSIBLE_AGENT_DECISIONS = %i[match top forfeit].freeze | |
POSSIBLE_BID_INCREASES = [50.0, 100.0, 150.0, 200.0].freeze # assuming that the increases are fixed | |
Session = Struct.new(*%i[rounds winners participants], keyword_init: true) | |
RoundParticipant = Struct.new(*%i[agent active last_placed_bid id last_round_decision], keyword_init: true) | |
def self.run(agent_count: 10) | |
agents = [*(1..agent_count)].map { Agent.new } | |
new(agents).run | |
end | |
def initialize(agents) | |
@agents = agents | |
@session = Session.new(participants: build_participants_from_agents(agents), rounds: 1) | |
end | |
def run | |
while session_ongoing? | |
run_bidding_session! | |
session.rounds = session.rounds + 1 | |
end | |
session.winners = determine_winners_for_session | |
pp(session) # for debugging purposes | |
end | |
private | |
attr_reader(:agents, :session) | |
def session_ongoing? | |
active_participants.any? && bidding_participants.map(&:last_placed_bid).uniq.length != 1 | |
end | |
def bidding_participants | |
active_participants.select do |participant| | |
participant.last_placed_bid > 0 | |
end | |
end | |
def determine_winners_for_session | |
bidding_participants.select do |participant| | |
participant.last_placed_bid == highest_available_bid | |
end | |
end | |
def active_participants | |
session.participants.values.select(&:active) | |
end | |
def run_bidding_session! | |
active_participants.each.with_index do |participant, index| | |
if update_initial_bid?( | |
participant, index | |
) | |
next update_participant_bid!(participant, participant.agent.get_bid_increase || POSSIBLE_BID_INCREASES.sample, | |
:top) | |
end | |
# make decision for agent at random | |
update_participant_bid_decision(participant, POSSIBLE_AGENT_DECISIONS.sample) | |
end | |
end | |
def update_initial_bid?(participant, index) | |
index.zero? && participant.active && participant.last_placed_bid.zero? | |
end | |
def highest_available_bid | |
active_participants.map(&:last_placed_bid).max | |
end | |
def update_participant_bid_decision(participant, decision) | |
case decision | |
when :forfeit | |
forfeit_round!(participant) | |
when :top | |
# increases are selected at random | |
update_participant_bid!(participant, | |
highest_available_bid + (participant.agent.get_bid_increase || POSSIBLE_BID_INCREASES.sample), decision) | |
when :match | |
update_participant_bid!(participant, | |
participant.last_placed_bid + (highest_available_bid - participant.last_placed_bid), decision) | |
end | |
end | |
def forfeit_round!(participant) | |
participant.active = false | |
participant.last_round_decision = { decision: :forfeit, round: session.rounds, | |
forfeited_at: highest_available_bid } | |
session.participants[participant.id] = participant | |
end | |
def update_participant_bid!(participant, new_bid, decision) | |
participant.last_placed_bid = new_bid | |
participant.last_round_decision = { decision: decision, round: session.rounds } | |
session.participants[participant.id] = participant | |
end | |
def build_participants_from_agents(agents) | |
agents.each_with_index.with_object({}) do |(agent, index), hash| | |
participant_id = "participant-#{index + 1}" | |
hash[participant_id] = | |
RoundParticipant.new(agent: agent, active: true, last_placed_bid: 0.0, id: participant_id) | |
end | |
end | |
end | |
# NB: To test the class above, uncomment the method and feel free to modify the no of agents | |
# by passing the number desired as an arg to the function | |
Bidding.run(agent_count: 15) | |
# execute this script in your terminal by running `ruby __filename__`` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment