import datetime import random import time from collections import OrderedDict from datetime import datetime # To handle time properly from django import forms from django.contrib.auth.decorators import login_required from django.http import JsonResponse from otree.api import * from otree.db.models import Model, ForeignKey from . import models # Contains my data structure and functions author = 'Filipp Chapkovskii, UZH, chapkovski@gmail.com' doc = """ Amazon-like auction. Bidding time is extended after each bid. """ class Constants(BaseConstants): instructions_template = 'AuctionAmazon08All/Instructions.html' name_in_url = 'AuctionAmazon08All' players_per_group = None num_rounds = 1 # starting_time = 30 # extra_time = 20 timeout_seconds = 60 endowment = 100 prize = 100 bid_success_probability = 0.8 class Subsession(BaseSubsession): pass class Group(BaseGroup): first_price = models.IntegerField(initial=0) second_price = models.IntegerField(initial=0) auctionstartdate = models.FloatField() # auctionenddate = models.FloatField() curwinner = models.IntegerField() curstage = models.IntegerField(initial=1) # def time_left(self): # now = time.time() # time_left = self.auctionenddate - now # time_left = round(time_left) if time_left > 0 else 0 # return time_left class Player(BasePlayer): # max_bid = models.IntegerField(initial=0) # bid_submitted_time = models.FloatField() private_value = models.IntegerField() proceed = models.IntegerField(initial=0) preliminary_payoff = models.IntegerField(initial=0) second_stage_bid = models.BooleanField() second_stage_bid_amount = models.IntegerField(blank=True) class Bid(Model): mode = models.IntegerField(initial=2) amount = models.IntegerField(initial=0) at_time = models.FloatField(initial=0) in_stage = models.IntegerField(initial=0) success = models.IntegerField(initial=1) in_round = models.IntegerField(initial=0) p_value = models.IntegerField(initial=0) player = ForeignKey( Player ) # creates 1:m relation -> this bid was submitted by a certain player def __str__(self): bidstr = "Round " + str(self.player.subsession.round_number) + '\n' bidstr += "Group " + str(self.player.subsession.round_number) + '\n' bidstr += "Player " + str(self.player.id_in_group) + '\n' bidstr += "Value " + str(self.player.private_value) + '\n' bidstr += "Stage " + str(self.in_stage) + '\n' bidstr += "Time " + str(self.at_time) + '\n' bidstr += "Success " + str(self.success) + '\n' bidstr += "Amount " + str(self.amount) + '\n' return bidstr # FUNCTIONS def before_session_starts(subsession: Subsession): # Group randomly with fixed IDs subsession.group_randomly(fixed_id_in_group=True) # There will be a single paying round if subsession.round_number == 1: paying_round = random.randint(1, Constants.num_rounds) subsession.session.vars['paying_round'] = 1 # paying_round #this is set for all apps! # Set start time / Not necessary, only for logs for g in subsession.get_groups(): g.first_price = 0 g.second_price = 0 g.auctionstartdate = time.time() # Add one bid to each player, otherwise error on first screen for player in subsession.get_players(): _ = player.bid_set.create() _.save() def vars_for_admin_report(subsession: Subsession): # Randomly select a group for each round # Then look up the players in that group, # and return their IDs, Codenames and Payoffs selected_groups = [] for r in range(0, Constants.num_rounds): selected_groups.append(random.choice(subsession.get_group_matrix())) selected_players = [] for round in range(0, Constants.num_rounds): players_and_payoffs = [] for player in selected_groups[round]: # selected_groups[round] is a list of players players_and_payoffs.append(player.in_round(round + 1).participant.label) players_and_payoffs.append(player.in_round(round + 1).preliminary_payoff) selected_players.append(players_and_payoffs) return {'selected_players': selected_players} def set_payoffs(group: Group): for p in group.get_players(): if str(group.curwinner) == str(p.id_in_group): p.preliminary_payoff = p.private_value - group.second_price else: p.preliminary_payoff = 0 if group.round_number == group.session.vars['paying_round']: p.payoff = p.preliminary_payoff else: p.payoff = 0 def write_results_to_file(group: Group): players = '' for p in group.get_players(): players += str(p.participant.id_in_session) players += 'vs' players = players[:-2] # Cuts off last 'vs' with open( 'Amazon_All_started_' + str(time.strftime('%m_%d_%H_%M_%S', time.localtime(group.auctionstartdate))) + '_played_by_' + players + 'round_' + str(group.round_number) + '.txt', 'w', ) as f: # First write down the winner f.write("The participants payoffs are:" + '\n') for p in group.get_players(): f.write( "Participant_" + str(p.participant.id_in_session) + ': ' + str(p.preliminary_payoff) + '\n' + '\n' + '\n' ) # Now write down all bids f.write("The participants bids are:" + '\n') for p in group.get_players(): bid_qs = Bid.objects.filter(player__exact=p) for bid in bid_qs: f.write(str(bid) + '\n') f.closed def last_bid(player: Player): sorted_bids = sorted(player.bid_set.all(), key=lambda bid: bid.at_time, reverse=True) sorted_successful_bids = [bid for bid in sorted_bids if bid.success == 1] return sorted_successful_bids[0] def highest_bid(player: Player): sorted_bids = sorted(player.bid_set.all(), key=lambda bid: bid.amount, reverse=True) sorted_successful_bids = [bid for bid in sorted_bids if bid.success == 1] return sorted_successful_bids[0] # PAGES # from pip._vendor.requests.sessions import session # from .utils import get_field_names_for_csv class WPstart(WaitPage): @staticmethod def after_all_players_arrive(group: Group): for p in group.get_players(): p.private_value = int( group.session.config['min_private_value'] * (100 / group.session.config['point_worth_ct']) + random.random() * ( ( group.session.config['max_private_value'] - group.session.config['min_private_value'] ) * (100 / group.session.config['point_worth_ct']) ) ) class WP(WaitPage): @staticmethod def after_all_players_arrive(group: Group): now = time.time() group.auctionstartdate = now # self.group.auctionenddate = now + Constants.starting_time class ResultsWP(WaitPage): @staticmethod def after_all_players_arrive(group: Group): set_payoffs(group) write_results_to_file(group) class InbetweenWP(WaitPage): @staticmethod def is_displayed(player: Player): if all(p.second_stage_bid == False for p in player.group.get_players()): return False else: return True @staticmethod def after_all_players_arrive(group: Group): if not all(p.second_stage_bid == False for p in group.get_players()): for pl in group.get_players(): pl.second_stage_bid = True for pl in group.get_players(): pl.second_stage_bid_amount = None class InstructionsPage(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player: Player): return { 'num_others': len(player.group.get_players()) - 1, 'probability': Constants.bid_success_probability, 'low': int( player.session.config['min_private_value'] * (100 / player.session.config['point_worth_ct']) ), 'high': int( player.session.config['max_private_value'] * (100 / player.session.config['point_worth_ct']) ), } class Stage1(Page): timeout_seconds = Constants.timeout_seconds @staticmethod def is_displayed(player: Player): if all(p.second_stage_bid == False for p in player.group.get_players()): return False else: return True @staticmethod def vars_for_template(player: Player): return { 'num_others': len(player.group.get_players()) - 1, 'max_bid': highest_bid(player).amount, 'probability': Constants.bid_success_probability, 'low': int( player.session.config['min_private_value'] * (100 / player.session.config['point_worth_ct']) ), 'high': int( player.session.config['max_private_value'] * (100 / player.session.config['point_worth_ct']) ), } # def before_next_page(self): # self.player.second_stage_bid_amount = highest_bid(self.player).amount class Stage2(Page): @staticmethod def is_displayed(player: Player): if all(p.second_stage_bid == False for p in player.group.get_players()): return False else: return True form_model = models.Player form_fields = ['second_stage_bid', 'second_stage_bid_amount'] @staticmethod def second_stage_bid_amount_max(player: Player): return Constants.endowment @staticmethod def second_stage_bid_amount_min(player: Player): return max(highest_bid(player).amount, player.group.second_price) + 1 @staticmethod def vars_for_template(player: Player): return { 'num_others': len(player.group.get_players()) - 1, 'first_price': player.group.first_price, 'second_price': player.group.second_price, 'winner': player.group.curwinner, 'max_bid': highest_bid(player).amount, 'probability': Constants.bid_success_probability, 'low': int( player.session.config['min_private_value'] * (100 / player.session.config['point_worth_ct']) ), 'high': int( player.session.config['max_private_value'] * (100 / player.session.config['point_worth_ct']) ), } @staticmethod def before_next_page(player: Player, timeout_happened): if player.second_stage_bid == True: if not player.second_stage_bid_amount == None: # "Illegal" bids are prevented by formfield validation new_bid = player.bid_set.create() new_bid.amount = player.second_stage_bid_amount new_bid.at_time = time.time() - player.group.auctionstartdate new_bid.in_stage = 2 if random.random() <= Constants.bid_success_probability: new_bid.success = 1 else: new_bid.success = 0 player.second_stage_bid = False new_bid.in_round = player.subsession.round_number new_bid.p_value = player.private_value new_bid.save() if new_bid.amount <= player.group.second_price: message_for_player = 'Your new bid must exceed the current selling price.' else: print('Bid is relevant') # Sort players by their respective maximum bid and then the time they submitted it (in case of duplicates) sorted_players = sorted( player.group.get_players(), key=lambda player: ( highest_bid(player).amount, -highest_bid(player).at_time, ), reverse=True, ) # Replace the prices player.group.first_price = sorted_players[0].highest_bid().amount player.group.second_price = sorted_players[1].highest_bid().amount # Set winner player.group.curwinner = sorted_players[0].id_in_group # print('winner', mygroup.curwinner) message_for_player = 'Your bid was submitted successfully.' player.group.save() else: print('Player submitted last bid empty') class Results(Page): pass class ResultsSummary(Page): @staticmethod def is_displayed(player: Player): return player.round_number == Constants.num_rounds @staticmethod def vars_for_template(player: Player): return { 'total_payoff': sum([p.payoff for p in player.in_all_rounds()]), 'paying_round': player.session.vars['paying_round'], } page_sequence = [ WPstart, InstructionsPage, WP, Stage1, Stage2, InbetweenWP, Stage1, Stage2, InbetweenWP, Stage1, Stage2, InbetweenWP, Stage1, Stage2, InbetweenWP, Stage1, Stage2, InbetweenWP, Stage1, Stage2, InbetweenWP, ResultsWP, Results, # ResultsSummary ]