import time from shared.reward_group import RewardGroup from shared import helpers MAX_WAIT_TIME_MIN = 15 # TODO: SET BACK TO 15 MAX_WAIT_TIME = MAX_WAIT_TIME_MIN * 60 ENDOGENEOUS_ROLES = ["A1", "A2", "D1", "D2"] def find_receiver_couple(dictator, receivers): """Find two Receivers which are of opposite reward groups and strange to the Dictator. Receivers can be matched to multiple Dictators.""" receiver1, receiver2 = None, None known_receivers = dictator.participant.previous_third_party_partners for r in receivers: # Receiver must be strange to Dictator if r.receiver_id in known_receivers: continue # Receivers must be of opposite reward groups if not receiver1 and r.reward_group == str(RewardGroup.HIGH): receiver1 = r if not receiver2 and r.reward_group == str(RewardGroup.LOW): receiver2 = r if receiver1 and receiver2: # Match for Allocation Decision print( f"Match Third-party partners: D{dictator.id_in_subsession}, R{receiver1.receiver_id}, R{receiver2.receiver_id}" ) dictator.receiver1_id = receiver1.receiver_id dictator.receiver2_id = receiver2.receiver_id dictator.receivers_comm_account = receiver1.reward + receiver2.reward dictator.participant.previous_third_party_partners.append(receiver1.receiver_id) dictator.participant.previous_third_party_partners.append(receiver2.receiver_id) break if not receiver1 or not receiver2: raise RuntimeError( f"Dictator {dictator.id_in_subsession} could not be matched with two Third-party-receivers." ) def find_account_partner(dictators, receivers): """Find a match of a Dictator and a Receiver where they are of opposite reward groups and strange to each other. Receivers can only be matched once to Dictators as Account partner per round.""" def match(): print(f"Match Account partners: D{d.id_in_subsession}, R{r.receiver_id}") r.dictator = d d.account_partner_id = r.receiver_id d.common_account = r.reward + d.participant.rewards[d.round_number - 1] d.participant.previous_acc_partners.append(r.receiver_id) for d in dictators: # Is already matched if d.has_account_partner(): continue for r in receivers: # Is already matched if r.dictator: continue # Is not of opposite reward group if r.reward_group == str(d.participant.reward_group): continue # Is not strange if r.receiver_id in d.participant.previous_acc_partners: continue match() break def set_communication_partner(p1, p2): # Set the communication partner within the group p1.comm_partner_id = p2.id_in_subsession p2.comm_partner_id = p1.id_in_subsession # Set the channel for the chat to same value channel_code = f"{p1.participant.code}-{p2.participant.code}" p1.channel_code = channel_code p2.channel_code = channel_code # Save selected role in endogeneous round if role := p1.field_maybe_none("endogeneous_role"): p2.selected_role = role if role := p2.field_maybe_none("endogeneous_role"): p1.selected_role = role def find_exogeneous_partner(player, potential_partners): """Try to find a partner for `player` who fulfills conditions, e.g. has target reward group and is strange""" def fulfills_conditions(): print(f"Test Match: D{player.id_in_subsession} and D{partner.id_in_subsession}", end=". ") # Must not be self if partner == player: print("Failure, is self") return False # Must be strange if partner_id in player_previous_partners: print("Failure, is not strange") return False # Must meet target reward group if partner_reward_group != player_target_reward_group: print("Failure, is not target reward group:", partner_reward_group, player_target_reward_group) return False if player_reward_group != partner_target_reward_group: print("Failure, is not target reward group:", player_reward_group, partner_target_reward_group) return False # Must have account partners if not partner.has_account_partner(): print("Failure, has no account partner") return False if not player.has_account_partner(): print("Failure, has no account partner") return False print("Success!") return True def set_previous_partners(): # Set that players are not strange anymore for next matching round partner_previous_partners = partner_pt.previous_comm_partners player_previous_partners.append(partner_id) partner_previous_partners.append(player_id) def set_target_reward_groups(): # Save to player player.comm_partner_reward_group = str(player_target_reward_group) partner.comm_partner_reward_group = str(partner_target_reward_group) # Set opposite target reward groups for next matching round player_pt.reward_group_comm_partner = player_target_reward_group.get_opposite() partner_pt.reward_group_comm_partner = partner_target_reward_group.get_opposite() def match(): print(f"Matching D{player.id_in_subsession} and D{partner.id_in_subsession}.") set_communication_partner(player, partner) set_previous_partners() set_target_reward_groups() helpers.set_info([player, partner]) return partner player_pt = player.participant player_id = player.id_in_subsession player_reward_group = player_pt.reward_group player_target_reward_group = player_pt.reward_group_comm_partner player_previous_partners = player_pt.previous_comm_partners for partner in potential_partners: partner_pt = partner.participant partner_id = partner.id_in_subsession partner_reward_group = partner_pt.reward_group partner_target_reward_group = partner_pt.reward_group_comm_partner if fulfills_conditions(): # Match return match() def find_endogeneous_group(players): def set_roles(): p1.endogeneous_role = ENDOGENEOUS_ROLES[0] p2.endogeneous_role = ENDOGENEOUS_ROLES[1] p3.endogeneous_role = ENDOGENEOUS_ROLES[2] p4.endogeneous_role = ENDOGENEOUS_ROLES[3] def match(): print( f"Matching D{p1.id_in_subsession}, D{p2.id_in_subsession}, D{p3.id_in_subsession} and D{p4.id_in_subsession}." ) helpers.set_info([p1, p2, p3, p4]) set_roles() return [p1, p2, p3, p4] if len(players) < 4: return low_players, high_players = [], [] for p in players: # Don't match players without account partners if not p.has_account_partner(): print(f"Not considering D{p.id_in_subsession}, they have no Account partner.") continue reward_group = p.participant.reward_group high_players.append(p) if reward_group == RewardGroup.HIGH else low_players.append(p) for p1 in high_players: previous_partners = p1.participant.previous_comm_partners.copy() for p2 in high_players: print(f"Test Match: D{p1.id_in_subsession} + D{p2.id_in_subsession}", end="") # Must not be self if p1 == p2: print(". Failure, is self") continue # Must be strange if p2.id_in_subsession in previous_partners: print(". Failure, is not strange") continue previous_partners += p2.participant.previous_comm_partners for p3 in low_players: print(f" + D{p3.id_in_subsession}", end="") # Must be strange if p3.id_in_subsession in previous_partners: print(". Failure, is not strange") continue previous_partners += p3.participant.previous_comm_partners for p4 in low_players: print(f" + D{p4.id_in_subsession}", end="") # Must not be self if p3 == p4: print(". Failure, is self") continue # Must be strange if p4.id_in_subsession in previous_partners: print(". Failure, is not strange") continue print("\n########## Success ##########") return match() def try_match(subsession, waiting_players): round_nr = subsession.round_number if round_nr == 1: for player in waiting_players: if partner := find_exogeneous_partner(player, waiting_players): return [player, partner] elif round_nr == 2: for player in waiting_players: if partner := find_exogeneous_partner(player, waiting_players): return [player, partner] else: if group := find_endogeneous_group(waiting_players): return group def waiting_too_long(waiting_players): for p in waiting_players: wait_time = time.time() - p.participant.wait_page_arrival if wait_time >= MAX_WAIT_TIME: print(f"Dropping D{ p.id_in_subsession }; was waiting too long") return [p]