コード例 #1
0
def ready_to_pair(round_to_check):
    """
    Check if we can pair a round using basic heuristics 
    In particular:
        1) We need enough N/2 judges that are *checked in*
        2) Do we have N/2 rooms
        3) We need all rounds to be entered from the previous round
    """
    if CheckIn.objects.filter(round_number=round_to_check).count(
    ) < Team.objects.filter(checked_in=True).count() / 2:
        raise errors.NotEnoughJudgesError()
    elif Room.objects.all().count() < Team.objects.filter(
            checked_in=True).count() / 2:
        raise errors.NotEnoughRoomsError()
    elif round_to_check != 1:
        prev_rounds = Round.objects.filter(round_number=round_to_check - 1)
        for r in prev_rounds:
            if r.victor == Round.NONE:
                raise errors.PrevRoundNotEnteredError()

    return True
コード例 #2
0
def validate_round_data(round_to_check):
    """
    Validate that the current round has all the data we expect before
    pairing the next round
    In particular we require:
        1) N/2 judges that are *checked in*
        2) N/2 rooms available
        3) All rounds must be entered from the previous round
        4) Check that no teams both have a round result and a NoShow or Bye

    If any of these fail we raise a specific error as to the type of error
    """

    # Check that there are enough judges
    if not have_enough_judges(round_to_check)[0]:
        raise errors.NotEnoughJudgesError()

    # Check there are enough rooms
    if not have_enough_rooms(round_to_check)[0]:
        raise errors.NotEnoughRoomsError()

    # If we have results, they should be entered and there should be no
    # byes or noshows for teams that debated
    have_properly_entered_data(round_to_check)
コード例 #3
0
def pair_round():
    """
    Pair the next round of debate.
    This function will do the following:
        1) Check that we can pair the round
        2) Check that we have scratches all judges from
           teams of the same school, and if not add these
           scratches
        3) Record no-show teams
        4) Setup the list of teams by either seed or speaks
        5) Calculate byes
        6) Calculate pull ups based on byes
        7) Pass in evened brackets to the perfect pairing algorithm
        8) Assign judges to pairings
        9) Assign rooms to pairings

    pairings are computed in the following format: [gov,opp,judge,room]
    and then directly put into the database
    FIXME: Allow for good rollback behavior
    """
    current_round = TabSettings.objects.get(key="cur_round").value
    try:
        ready_to_pair(current_round)
    except errors.NotEnoughJudgesError:
        raise errors.NotEnoughJudgesError()
    except errors.NotEnoughRoomsError:
        raise errors.NotEnoughRoomsError()
    except errors.PrevRoundNotEnteredError:
        raise errors.PrevRoundNotEnteredError

    # For testing purposes
    random.seed(0xBEEF)

    # add scratches for teams/judges from the same school
    # NOTE that this only happens if they haven't already been added
    add_scratches_for_school_affil()

    list_of_teams = [None] * current_round
    all_pull_ups = []

    # Record no-shows
    forfeit_teams = list(Team.objects.filter(checked_in=False))
    for t in forfeit_teams:
        n = NoShow(no_show_team=t, round_number=current_round)
        n.save()

    # If it is the first round, pair by *seed*
    if current_round == 1:
        list_of_teams = list(Team.objects.filter(checked_in=True))

        # If there are an odd number of teams, give a random team the bye
        if len(list_of_teams) % 2 == 1:
            b = Bye(bye_team=list_of_teams[random.randint(
                0,
                len(list_of_teams) - 1)],
                    round_number=current_round)
            b.save()
            list_of_teams.remove(b.bye_team)

        # Sort the teams by seed. We must randomize beforehand so that similarly
        # seeded teams are paired randomly.
        random.shuffle(list_of_teams)
        list_of_teams = sorted(list_of_teams,
                               key=lambda team: team.seed,
                               reverse=True)

        #pairings = [None]* Team.objects.count()/2
    # Otherwise, pair by *speaks*
    else:
        bye_teams = [bye.bye_team for bye in Bye.objects.all()]
        # For each time that a team has won by forfeit, add them
        # to the list of bye_teams
        bye_teams = bye_teams + team_wins_by_forfeit()
        # FIXME (jolynch): Why is this random thing here? - (julia) If there are multiple teams that have had the bye/won by forfeit,
        #we want to order that they are inserted into the middle of the bracket to be random.  I need to change the code below so
        #that this is actually true/used - See issue 3
        random.shuffle(bye_teams, random=random.random)

        # Bucket all the teams into brackets
        # NOTE: We do not bucket teams that have only won by
        #       forfeit/bye in every round because they have no speaks
        all_checked_in_teams = Team.objects.filter(checked_in=True)
        team_buckets = [(tot_wins(team), team) for team in all_checked_in_teams
                        if current_round - bye_teams.count(team) != 1]
        list_of_teams = [
            rank_teams_except_record(
                [team for (w, team) in team_buckets if w == i])
            for i in range(current_round)
        ]

        # Take care of teams that only have forfeits/byes
        # FIXME (julia): This should just look at the bye teams. No need to look at all teams, plus looking only at bye teams will
        #insert them in a random order. See issue 3
        if len(bye_teams) != 0:
            for t in list(Team.objects.filter(checked_in=True)):
                # pair into the middle
                if current_round - bye_teams.count(t) == 1:
                    print t
                    list_of_teams[current_round - 1].insert(
                        int(float(len(list_of_teams[tot_wins(t)])) / 2.0), t)
        print "these are the teams before pullups"
        print pprint.pprint(list_of_teams)

        # Correct for brackets with odd numbers of teams
        #  1) If we are in the bottom bracket, give someone a bye
        #  2) If we are in 1-up bracket and there are no all down
        #     teams, give someone a bye
        #  FIXME: Do we need to do special logic for smaller brackets? - (julia) I need to make the logic more general to deal
        # with if there are no teams in the all down or up one bracket. See Issue 4
        #  3) Otherwise, find a pull up from the next bracket
        for bracket in reversed(range(current_round)):
            if len(list_of_teams[bracket]) % 2 != 0:
                #print "need pull-up"
                # If there are no teams all down, give the bye to a one down team.
                if bracket == 0:
                    byeint = len(list_of_teams[bracket]) - 1
                    b = Bye(bye_team=list_of_teams[bracket][byeint],
                            round_number=current_round)
                    b.save()
                    list_of_teams[bracket].remove(
                        list_of_teams[bracket][byeint])
                elif bracket == 1 and len(
                        list_of_teams[0]) == 0:  #in 1 up and no all down teams
                    found_bye = False
                    for byeint in range(len(list_of_teams[1]) - 1, -1, -1):
                        if had_bye(list_of_teams[1][byeint]):
                            pass
                        elif found_bye == False:
                            b = Bye(bye_team=list_of_teams[1][byeint],
                                    round_number=current_round)
                            b.save()
                            list_of_teams[1].remove(list_of_teams[1][byeint])
                            found_bye = True
                    if found_bye == False:
                        raise errors.NotEnoughTeamsError()
                else:
                    pull_up = None
                    # FIXME (jolynch): Try to use descriptive variable names. (julia) - I'll fix this.
                    # instead of commenting

                    # i is the last team in the bracket below
                    i = len(list_of_teams[bracket - 1]) - 1
                    pullup_rounds = Round.objects.exclude(pullup=Round.NONE)
                    teams_been_pulled_up = [
                        r.gov_team for r in pullup_rounds
                        if r.pullup == Round.GOV
                    ]
                    teams_been_pulled_up.extend([
                        r.opp_team for r in pullup_rounds
                        if r.pullup == Round.OPP
                    ])
                    #find the lowest team in bracket below that can be pulled up
                    while pull_up == None:
                        if list_of_teams[bracket -
                                         1][i] not in teams_been_pulled_up:
                            pull_up = list_of_teams[bracket - 1][i]
                            all_pull_ups.append(pull_up)
                            list_of_teams[bracket].append(pull_up)
                            list_of_teams[bracket - 1].remove(pull_up)
                            #after adding pull-up to new bracket and deleting from old, sort again by speaks making sure to leave any first
                            #round bye in the correct spot
                            removed_teams = []
                            for t in list(
                                    Team.objects.filter(checked_in=True)):
                                #They have all wins and they haven't forfeited so they need to get paired in
                                if current_round - bye_teams.count(
                                        t) == 1 and tot_wins(t) == bracket:
                                    removed_teams += [t]
                                    list_of_teams[bracket].remove(t)
                            list_of_teams[bracket] = rank_teams_except_record(
                                list_of_teams[bracket])
                            print "list of teams in " + str(
                                bracket) + " except removed"
                            print list_of_teams[bracket]
                            for t in removed_teams:
                                list_of_teams[bracket].insert(
                                    len(list_of_teams[bracket]) / 2, t)
                        else:
                            i -= 1
    print "these are the teams after pullups"
    print pprint.pprint(list_of_teams)
    if current_round > 1:
        for i in range(len(list_of_teams)):
            print "Bracket %i has %i teams" % (i, len(list_of_teams[i]))

    # Pass in the prepared nodes to the perfect pairing logic
    # to get a pairing for the round
    pairings = []
    for bracket in range(current_round):
        if current_round == 1:
            temp = pairing_alg.perfect_pairing(list_of_teams)
        else:
            temp = pairing_alg.perfect_pairing(list_of_teams[bracket])
            print "Pairing round %i of size %i" % (bracket, len(temp))
        for pair in temp:
            pairings.append([pair[0], pair[1], [None], [None]])

    # FIXME: WHY DO WE RANDOMIZE THIS - we want the order of which fullseeded teams get the best judge to be random.
    # We should possibly also sort on the weakest team first? I.e. a fullseed/halfseed should get a better judge than a
    # fullseed/freeseed, etc. - Julia to fix. Issue 6.
    # should randomize first
    if current_round == 1:
        random.shuffle(pairings, random=random.random)
        pairings = sorted(pairings,
                          key=lambda team: highest_seed(team[0], team[1]),
                          reverse=True)
    # sort with pairing with highest ranked team first
    else:
        sorted_teams = rank_teams()
        print sorted_teams
        print "pairings"
        print pairings
        pairings = sorted(pairings,
                          key=lambda team: min(sorted_teams.index(team[0]),
                                               sorted_teams.index(team[1])))

    # Assign rooms (does this need to be random? maybe bad to have top ranked teams/judges in top rooms?)
    rooms = Room.objects.all()
    rooms = sorted(rooms, key=lambda r: r.rank, reverse=True)

    for i in range(len(pairings)):
        pairings[i][3] = rooms[i]

    # Enter into database
    for p in pairings:
        if isinstance(p[2], Judge):
            r = Round(round_number=current_round,
                      gov_team=p[0],
                      opp_team=p[1],
                      judge=p[2],
                      room=p[3])
        else:
            r = Round(round_number=current_round,
                      gov_team=p[0],
                      opp_team=p[1],
                      room=p[3])
        if p[0] in all_pull_ups:
            r.pullup = Round.GOV
        elif p[1] in all_pull_ups:
            r.pullup = Round.OPP
        r.save()