def update_resource_level(experiment, group, round_data, regrowth_rate, max_resource_level=None): if max_resource_level is None: max_resource_level = get_max_resource_level( round_data.round_configuration) current_resource_level_dv = get_resource_level_dv( group, round_data, shared_resource_enabled=False) current_resource_level = current_resource_level_dv.int_value group_harvest_dv = get_group_harvest_dv(group, round_data) regrowth_dv = get_regrowth_dv(group, round_data) # FIXME: would be nicer to extend Group behavior and have group.get_total_harvest() instead of # get_total_group_harvest(group, ...), see if we can enable this # dynamically total_harvest = get_total_group_harvest(group, round_data) logger.debug("Harvest: total group harvest for playable round: %s", total_harvest) if current_resource_level > 0: if total_harvest > current_resource_level: adjusted_harvest = adjust_harvest_decisions( current_resource_level, group, round_data, total_harvest) total_harvest = adjusted_harvest group.log("Harvest: removing %s from current resource level %s" % (total_harvest, current_resource_level)) group_harvest_dv.update_int(total_harvest) current_resource_level = current_resource_level - total_harvest resource_regrowth = calculate_regrowth(current_resource_level, regrowth_rate, max_resource_level) group.log("Regrowth: adding %s to current resource level %s" % (resource_regrowth, current_resource_level)) regrowth_dv.update_int(resource_regrowth) # clamp resource current_resource_level_dv.update_int( min(current_resource_level + resource_regrowth, max_resource_level)) else: group.log("current resource level is 0, no one can harvest") group_harvest_dv.update_int(0) ParticipantRoundDataValue.objects.for_group( group, parameter=get_harvest_decision_parameter(), round_data=round_data).update(is_active=False) for pgr in group.participant_group_relationship_set.all(): # Create adjusted data values ParticipantRoundDataValue.objects.create( participant_group_relationship=pgr, round_data=round_data, parameter=get_harvest_decision_parameter(), int_value=0) ''' XXX: transfer resource levels across chat and quiz rounds if they exist ''' if experiment.has_next_round: ''' set group round data resource_level for each group + regrowth ''' group.log("Transferring resource level %s to next round" % current_resource_level_dv.int_value) group.copy_to_next_round(current_resource_level_dv, group_harvest_dv, regrowth_dv)
def _zero_harvest_decisions(participant_group_relationship_ids, round_data): # FIXME: possible performance issue, generates two queries per participant parameters = ( get_harvest_decision_parameter(), get_participant_ready_parameter()) data_values = ParticipantRoundDataValue.objects.filter(round_data=round_data, participant_group_relationship__pk__in=participant_group_relationship_ids, parameter__in=parameters) for dv in data_values: if dv.parameter == get_harvest_decision_parameter(): dv.update_int(0, submitted=True) elif dv.parameter == get_participant_ready_parameter(): dv.update_boolean(True)
def adjust_harvest_decisions(current_resource_level, group, round_data, total_harvest, group_size=0): if group_size == 0: group_size = group.size # pass in the group size to handle group cluster case average_harvest = current_resource_level / group_size group.log( "GROUP HARVEST ADJUSTMENT - original total harvest: %s, resource level: %s, average harvest: %s" % (total_harvest, current_resource_level, average_harvest)) hds = ParticipantRoundDataValue.objects.for_group( group=group, parameter=get_harvest_decision_parameter(), round_data=round_data, int_value__gt=0).order_by('int_value') total_adjusted_harvest = 0 # FIXME: should be the same as group.size total_number_of_decisions = hds.count() logger.debug("total number of decisions: %s - group size: %s", total_number_of_decisions, group_size) decisions_allocated = 0 for hd in hds: if hd.int_value <= average_harvest: group.log("preserving %s < average harvest" % hd) total_adjusted_harvest += hd.int_value else: # now to assign the overs, find out how much resource level is # remaining remaining_resource_level = current_resource_level - \ total_adjusted_harvest remaining_decisions = total_number_of_decisions - \ decisions_allocated average_harvest = remaining_resource_level / remaining_decisions hd.is_active = False hd.save() logger.debug("Assigning %s to hd %s", average_harvest, hd) ParticipantRoundDataValue.objects.create( participant_group_relationship=hd. participant_group_relationship, parameter=get_harvest_decision_parameter(), round_data=round_data, int_value=average_harvest, submitted=True) total_adjusted_harvest += average_harvest decisions_allocated += 1 logger.debug("harvested total %s", total_adjusted_harvest) return total_adjusted_harvest
def get_total_harvest(participant_group_relationship, session_id): q = ParticipantRoundDataValue.objects.for_participant( participant_group_relationship, parameter=get_harvest_decision_parameter(), participant_group_relationship__group__session_id=session_id ).aggregate(total_harvest=models.Sum('int_value')) return _zero_if_none(q['total_harvest'])
def get_player_data(group, previous_round_data, current_round_data, self_pgr): """ Returns a tuple ([list of player data dictionaries], { dictionary of this player's data }) FIXME: refactor this into its own class as opposed to an arcane data structure """ prdvs = ParticipantRoundDataValue.objects.for_group( group=group, round_data__in=[previous_round_data, current_round_data], parameter__in=(get_player_status_parameter(), get_storage_parameter(), get_harvest_decision_parameter())) # nested dict mapping participant group relationship -> dict(parameter -> # participant round data value) player_dict = defaultdict(lambda: defaultdict(lambda: None)) player_status_parameter = get_player_status_parameter() for prdv in prdvs: player_dict[prdv.participant_group_relationship][prdv.parameter] = prdv player_data = [] for pgr, pgrdv_dict in player_dict.iteritems(): # FIXME: figure out a way to handle default values elegantly in this case since we aren't using the accessor # methods for int_parameter in (get_harvest_decision_parameter(), get_storage_parameter()): if pgrdv_dict[int_parameter] is None: pgrdv_dict[int_parameter] = DefaultValue(0) if pgrdv_dict[player_status_parameter] is None: pgrdv_dict[player_status_parameter] = DefaultValue(True) player_data.append({ 'id': pgr.pk, 'number': pgr.participant_number, 'lastHarvestDecision': pgrdv_dict[get_harvest_decision_parameter()].int_value, 'alive': pgrdv_dict[get_player_status_parameter()].boolean_value, 'storage': pgrdv_dict[get_storage_parameter()].int_value }) own_player = player_dict[self_pgr] return (player_data, { 'lastHarvestDecision': own_player[get_harvest_decision_parameter()].int_value, 'alive': own_player[get_player_status_parameter()].boolean_value, 'storage': own_player[get_storage_parameter()].int_value })
def _zero_harvest_decisions(participant_group_relationship_ids, round_data): data_values = ParticipantRoundDataValue.objects.with_parameter( get_harvest_decision_parameter(), round_data=round_data, participant_group_relationship__pk__in=participant_group_relationship_ids) data_values.update(int_value=0, submitted=True) data_values = ParticipantRoundDataValue.objects.with_parameter( get_participant_ready_parameter(), round_data=round_data, participant_group_relationship__pk__in=participant_group_relationship_ids) data_values.update(boolean_value=True) '''
def _zero_harvest_decisions(participant_group_relationship_ids, round_data): data_values = ParticipantRoundDataValue.objects.with_parameter( get_harvest_decision_parameter(), round_data=round_data, participant_group_relationship__pk__in=participant_group_relationship_ids) data_values.update(int_value=0, submitted=True) data_values = ParticipantRoundDataValue.objects.with_parameter( get_participant_ready_parameter(), round_data=round_data, participant_group_relationship__pk__in=participant_group_relationship_ids) data_values.update(boolean_value=True) '''
def round_ended_handler(sender, experiment=None, **kwargs): ''' calculates new resource levels for practice or regular rounds based on the group harvest and resultant regrowth. also responsible for transferring those parameters to the next round as needed. ''' round_configuration = experiment.current_round round_data = experiment.get_round_data(round_configuration) logger.debug("ending boundary effects round: %s", round_configuration) try: if round_configuration.is_playable_round: regrowth_rate = get_regrowth_rate(round_configuration) harvest_decision_parameter = get_harvest_decision_parameter() for pgr in experiment.participant_group_relationships: # FIXME: not thread-safe but this *should* only be invoked once per experiment. If we start getting # spurious data values, revisit this section prdvs = ParticipantRoundDataValue.objects.filter( round_data=round_data, participant_group_relationship=pgr, parameter=harvest_decision_parameter, is_active=True) number_of_harvest_decisions = prdvs.count() if number_of_harvest_decisions == 0: # create zero harvest decisions for any unsubmitted harvest decisions ParticipantRoundDataValue.objects.create( round_data=round_data, participant_group_relationship=pgr, parameter=harvest_decision_parameter, is_active=True, int_value=0) logger.debug( "autozero harvest decision for participant %s", pgr) elif number_of_harvest_decisions > 1: # deactivate all prior harvest decisions logger.debug( "multiple harvest decisions found for %s, deactivating all but the latest", pgr) final_harvest_decision = prdvs.latest('date_created') prdvs.exclude(pk=final_harvest_decision.pk).update( is_active=False) # FIXME: generify and merge update_shared_resource_level and # update_resource_level to operate on "group-like" objects if # possible if is_shared_resource_enabled(round_configuration): for group_cluster in experiment.active_group_clusters: update_shared_resource_level(experiment, group_cluster, round_data, regrowth_rate) else: for group in experiment.groups: update_resource_level(experiment, group, round_data, regrowth_rate) update_participants(experiment, round_data, round_configuration) except: logger.exception('Failed to end round cleanly')
def adjust_harvest_decisions(current_resource_level, group, round_data, total_harvest, group_size=0): if group_size == 0: group_size = group.size # pass in the group size to handle group cluster case average_harvest = current_resource_level / group_size group.log("GROUP HARVEST ADJUSTMENT - original total harvest: %s, resource level: %s, average harvest: %s" % (total_harvest, current_resource_level, average_harvest)) hds = ParticipantRoundDataValue.objects.for_group(group=group, parameter=get_harvest_decision_parameter(), round_data=round_data, int_value__gt=0).order_by('int_value') total_adjusted_harvest = 0 # FIXME: should be the same as group.size total_number_of_decisions = hds.count() logger.debug("total number of decisions: %s - group size: %s", total_number_of_decisions, group_size) decisions_allocated = 0 for hd in hds: if hd.int_value <= average_harvest: group.log("preserving %s < average harvest" % hd) total_adjusted_harvest += hd.int_value else: # now to assign the overs, find out how much resource level is # remaining remaining_resource_level = current_resource_level - \ total_adjusted_harvest remaining_decisions = total_number_of_decisions - \ decisions_allocated average_harvest = remaining_resource_level / remaining_decisions hd.is_active = False hd.save() logger.debug("Assigning %s to hd %s", average_harvest, hd) ParticipantRoundDataValue.objects.create(participant_group_relationship=hd.participant_group_relationship, parameter=get_harvest_decision_parameter(), round_data=round_data, int_value=average_harvest, submitted=True) total_adjusted_harvest += average_harvest decisions_allocated += 1 logger.debug("harvested total %s", total_adjusted_harvest) return total_adjusted_harvest
def get_player_data(group, previous_round_data, current_round_data, self_pgr): """ Returns a tuple ([list of player data dictionaries], { dictionary of this player's data }) FIXME: refactor this into its own class as opposed to an arcane data structure """ prdvs = ParticipantRoundDataValue.objects.for_group(group=group, round_data__in=[ previous_round_data, current_round_data], parameter__in=(get_player_status_parameter(), get_storage_parameter(), get_harvest_decision_parameter())) # nested dict mapping participant group relationship -> dict(parameter -> # participant round data value) player_dict = defaultdict(lambda: defaultdict(lambda: None)) player_status_parameter = get_player_status_parameter() for prdv in prdvs: player_dict[prdv.participant_group_relationship][prdv.parameter] = prdv player_data = [] for pgr, pgrdv_dict in player_dict.iteritems(): # FIXME: figure out a way to handle default values elegantly in this case since we aren't using the accessor # methods for int_parameter in (get_harvest_decision_parameter(), get_storage_parameter()): if pgrdv_dict[int_parameter] is None: pgrdv_dict[int_parameter] = DefaultValue(0) if pgrdv_dict[player_status_parameter] is None: pgrdv_dict[player_status_parameter] = DefaultValue(True) player_data.append({ 'id': pgr.pk, 'number': pgr.participant_number, 'lastHarvestDecision': pgrdv_dict[get_harvest_decision_parameter()].int_value, 'alive': pgrdv_dict[get_player_status_parameter()].boolean_value, 'storage': pgrdv_dict[get_storage_parameter()].int_value }) own_player = player_dict[self_pgr] return (player_data, { 'lastHarvestDecision': own_player[get_harvest_decision_parameter()].int_value, 'alive': own_player[get_player_status_parameter()].boolean_value, 'storage': own_player[get_storage_parameter()].int_value })
def round_ended_handler(sender, experiment=None, **kwargs): ''' calculates new resource levels for practice or regular rounds based on the group harvest and resultant regrowth. also responsible for transferring those parameters to the next round as needed. ''' round_configuration = experiment.current_round round_data = experiment.get_round_data(round_configuration) logger.debug("ending boundary effects round: %s", round_configuration) try: if round_configuration.is_playable_round: regrowth_rate = get_regrowth_rate(round_configuration) harvest_decision_parameter = get_harvest_decision_parameter() for pgr in experiment.participant_group_relationships: # FIXME: not thread-safe but this *should* only be invoked once per experiment. If we start getting # spurious data values, revisit this section prdvs = ParticipantRoundDataValue.objects.filter( round_data=round_data, participant_group_relationship=pgr, parameter=harvest_decision_parameter, is_active=True) # create zero harvest decisions for any unsubmitted harvest # decisions if prdvs.count() == 0: prdv = ParticipantRoundDataValue.objects.create(round_data=round_data, participant_group_relationship=pgr, parameter=harvest_decision_parameter, is_active=True, int_value=0) logger.debug( "autozero harvest decision for participant %s", pgr) elif prdvs.count() > 1: # another degenerate data condition, deactivate all prior logger.debug( "multiple harvest decisions found for %s, deactivating all but the latest", pgr) prdv = prdvs.latest('id') prdvs.exclude(pk=prdv.pk).update(is_active=False) # FIXME: generify and merge update_shared_resource_level and # update_resource_level to operate on "group-like" objects if # possible if is_shared_resource_enabled(round_configuration): for group_cluster in experiment.active_group_clusters: update_shared_resource_level( experiment, group_cluster, round_data, regrowth_rate) else: for group in experiment.groups: update_resource_level( experiment, group, round_data, regrowth_rate) update_participants(experiment, round_data, round_configuration) except: logger.exception('Failed to end round cleanly')
def update_resource_level(experiment, group, round_data, regrowth_rate, max_resource_level=None): if max_resource_level is None: max_resource_level = get_max_resource_level( round_data.round_configuration) current_resource_level_dv = get_resource_level_dv( group, round_data, shared_resource_enabled=False) current_resource_level = current_resource_level_dv.int_value group_harvest_dv = get_group_harvest_dv(group, round_data) regrowth_dv = get_regrowth_dv(group, round_data) # FIXME: would be nicer to extend Group behavior and have group.get_total_harvest() instead of # get_total_group_harvest(group, ...), see if we can enable this # dynamically total_harvest = get_total_group_harvest(group, round_data) logger.debug( "Harvest: total group harvest for playable round: %s", total_harvest) if current_resource_level > 0: if total_harvest > current_resource_level: adjusted_harvest = adjust_harvest_decisions( current_resource_level, group, round_data, total_harvest) total_harvest = adjusted_harvest group.log("Harvest: removing %s from current resource level %s" % (total_harvest, current_resource_level)) group_harvest_dv.update_int(total_harvest) current_resource_level = current_resource_level - total_harvest resource_regrowth = calculate_regrowth( current_resource_level, regrowth_rate, max_resource_level) group.log("Regrowth: adding %s to current resource level %s" % (resource_regrowth, current_resource_level)) regrowth_dv.update_int(resource_regrowth) # clamp resource current_resource_level_dv.update_int( min(current_resource_level + resource_regrowth, max_resource_level)) else: group.log("current resource level is 0, no one can harvest") group_harvest_dv.update_int(0) ParticipantRoundDataValue.objects.for_group(group, parameter=get_harvest_decision_parameter(), round_data=round_data).update(is_active=False) for pgr in group.participant_group_relationship_set.all(): # Create adjusted data values ParticipantRoundDataValue.objects.create(participant_group_relationship=pgr, round_data=round_data, parameter=get_harvest_decision_parameter( ), int_value=0) ''' XXX: transfer resource levels across chat and quiz rounds if they exist ''' if experiment.has_next_round: ''' set group round data resource_level for each group + regrowth ''' group.log("Transferring resource level %s to next round" % current_resource_level_dv.int_value) group.copy_to_next_round( current_resource_level_dv, group_harvest_dv, regrowth_dv)
def round_started_handler(sender, experiment=None, **kwargs): if experiment is None: logger.error( "Received round started signal with no experiment: %s", sender) raise ValueError("Received round started signal with no experiment") round_configuration = experiment.current_round logger.debug("setting up round %s", round_configuration) # initialize group and participant data values if round_configuration.is_playable_round: experiment.initialize_data_values( group_cluster_parameters=(get_group_cluster_bonus_parameter(),), group_parameters=(get_group_local_bonus_parameter(),), participant_parameters=(get_harvest_decision_parameter(), get_conservation_decision_parameter(), get_participant_link_parameter( ), get_participant_payoff_parameter(), get_chat_between_group_parameter( ), get_chat_within_group_parameter(), ) )
def get_total_harvest(participant_group_relationship, session_id): q = ParticipantRoundDataValue.objects.for_participant(participant_group_relationship, parameter=get_harvest_decision_parameter(), participant_group_relationship__group__session_id=session_id).aggregate( total_harvest=models.Sum('int_value')) return _zero_if_none(q['total_harvest'])
def get_total_group_harvest(group, round_data): q = ParticipantRoundDataValue.objects.for_group(group=group, parameter=get_harvest_decision_parameter(), round_data=round_data).aggregate(total_harvest=models.Sum('int_value')) return _zero_if_none(q['total_harvest'])
def get_total_group_harvest(group, round_data): q = ParticipantRoundDataValue.objects.for_group( group=group, parameter=get_harvest_decision_parameter(), round_data=round_data).aggregate(total_harvest=models.Sum('int_value')) return _zero_if_none(q['total_harvest'])