Esempio n. 1
0
def generate(tourney, settings, div_rounds):
    (ready, excuse) = check_ready(tourney, div_rounds)
    if not ready:
        raise countdowntourney.FixtureGeneratorException(excuse);

    generated_groups = fixgen.GeneratedGroups()

    for div_index in sorted(div_rounds):
        players = [x for x in tourney.get_active_players() if x.get_division() == div_index];
        group_size = int(settings.get("d%d_groupsize" % (div_index)))

        groups = [];
        games = tourney.get_games(game_type='P');
        round_no = div_rounds[div_index]
        if games:
            # This is not the first round.
            standings = tourney.get_standings(div_index);
            ordered_players = []
            prunes = []
            for s in standings:
                p = lookup_player(players, s.name)
                if p:
                    if p.rating == 0:
                        prunes.append(p)
                    else:
                        ordered_players.append(p)
        else:
            # This is the first round. Put the top rated players on the top
            # table, and so on.
            ordered_players = sorted(players, key=lambda x : x.rating, reverse=True)
            prunes = [x for x in ordered_players if x.rating == 0]
            ordered_players = [x for x in ordered_players if x.rating != 0]

        if group_size == -5:
            group_sizes = countdowntourney.get_5_3_table_sizes(len(players))
        else:
            group_sizes = [ group_size for i in range(len(players) // group_size) ]

        groups = [ [] for i in group_sizes ]

        # Put prunes on the lowest groups
        for i in range(len(prunes)):
            groups[len(groups) - 1 - (i % len(groups))].append(prunes[i])

        # Put the other players in the groups with the top players on
        # higher tables.
        standings_index = 0
        current_group = 0
        for p in ordered_players:
            if len(groups[current_group]) >= group_sizes[current_group]:
                current_group += 1
            if current_group >= len(groups):
                break
            groups[current_group].append(p)

        for g in groups:
            generated_groups.add_group(round_no, div_index, g)
        generated_groups.set_repeat_threes(round_no, div_index, group_size == -5)

    return generated_groups
Esempio n. 2
0
def get_table_sizes(num_players, table_size):
    if table_size == -5:
        sizes = []
        if num_players < 8:
            raise countdowntourney.FixtureGeneratorException(
                "Number of players (%d) not compatible with selected table configuration (5&3)."
                % (num_players))
        while num_players > 0 and num_players % 5 != 0:
            sizes.append(3)
            num_players -= 3
        sizes += [5 for x in range(num_players // 5)]
    else:
        if num_players % table_size != 0:
            raise countdowntourney.FixtureGeneratorException(
                "Number of players (%d) not compatible with selected table configuration (%d)."
                % (num_players, table_size))
        sizes = [table_size for x in range(num_players // table_size)]
    return sizes
Esempio n. 3
0
def random_without_rematches_aux(tables, players, player_indices_remaining,
                                 table_sizes, potential_opponents, id_to_index,
                                 start_time, time_limit_ms):
    if len(player_indices_remaining) == 0:
        return tables

    if time.time() > start_time + time_limit_ms / 1000.0:
        raise countdowntourney.FixtureGeneratorException(
            "Timed out before finding a set of fixtures with no rematches.")

    # Find the first table without its full complement of players
    table_index = 0
    while table_index < len(tables):
        if len(tables[table_index]) < table_sizes[table_index]:
            break
        else:
            table_index += 1

    if table_index >= len(tables):
        return tables

    # Put the remaining players in a random order
    player_indices_remaining_shuffled = player_indices_remaining[:]
    random.shuffle(player_indices_remaining_shuffled)

    # Starting with the first player in the shuffled list, try to place that
    # player on this table. If we can't, try to place the next player here, and
    # so on, until we find something that works or we run out of options.
    for player_index in player_indices_remaining_shuffled:
        tables_copy = []
        for t in tables:
            tables_copy.append(t[:])

        for opponent in tables[table_index]:
            opp_index = id_to_index.get(opponent.get_id())
            if opp_index is not None:
                if opp_index not in potential_opponents[player_index]:
                    break
        else:
            # This player hasn't played anyone on this table. Place them
            # here, and recurse to place the remaining players.
            tables_copy[table_index].append(players[player_index])
            new_player_indices_remaining = player_indices_remaining[:]
            new_player_indices_remaining.remove(player_index)

            finished_tables = random_without_rematches_aux(
                tables_copy, players, new_player_indices_remaining,
                table_sizes, potential_opponents, id_to_index, start_time,
                time_limit_ms)
            if finished_tables:
                # We have a complete solution
                return finished_tables

    # We can't place any player on this table without either having them play
    # someone they've played before, or forcing a rematch later in the
    # recursive process, so the task is impossible.
    return None
def generate(tourney, settings, div_rounds):
    (ready, excuse) = check_ready(tourney, div_rounds)
    if not ready:
        raise countdowntourney.FixtureGeneratorException(excuse)

    generated_groups = fixgen.GeneratedGroups()
    for div_index in div_rounds:
        round_no = div_rounds[div_index]
        players = [ x for x in tourney.get_active_players() if x.get_division() == div_index ]

        table_size = int(settings.get("d%d_groupsize" % (div_index)))
        
        # Put the players in order of rating, from highest to lowest. If two
        # players have the same rating, sort them by player ID (lowest first).
        players = sorted(players, key=lambda x : (x.get_rating(), -x.get_id()), reverse=True)

        if len(players) % table_size != 0:
            raise countdowntourney.FixtureGeneratorException("Well, this is awkward... Division \"%s\" has %d active players, which isn't a multiple of %d." % (tourney.get_division_name(div_index), len(players), table_size))

        pots = [ players[(pot_num * len(players) // table_size):((pot_num+1) * len(players) // table_size)] for pot_num in range(table_size) ]

        tables = []

        # Now randomly shuffle the order of each pot, with the proviso that any
        # prunes go at the end of the list, so they end up on the
        # highest-numbered tables.
        for pot_num in range(table_size):
            prunes = [ p for p in pots[pot_num] if p.get_rating() == 0 ]
            non_prunes = [ p for p in pots[pot_num] if p.get_rating() != 0 ]
            random.shuffle(non_prunes)
            pots[pot_num] = non_prunes + prunes

        # Now distribute the players across the tables
        num_tables = len(players) // table_size

        for table_index in range(num_tables):
            # Each table contains one player from each pot
            tables.append([ pots[pot_num][table_index] for pot_num in range(table_size) ])

        for tab in tables:
            generated_groups.add_group(round_no, div_index, tab)

    return generated_groups
Esempio n. 5
0
def generate(tourney, settings):
    # Use num_players and seed1 ... seedN to generate the knockout series
    # and return the new rounds and fixtures.

    (ready, excuse) = check_ready(tourney)
    if not ready:
        raise countdowntourney.FixtureGeneratorException(excuse)

    players = tourney.get_active_players()

    num_players = settings.get("num_players")
    if not num_players:
        raise countdowntourney.FixtureGeneratorException(
            "Number of players not specified")
    try:
        num_players = int(num_players)
        if num_players < 2 or num_players > len(players):
            raise countdowntourney.FixtureGeneratorException(
                "Number of players must be between 2 and %d" % len(players))
    except ValueError:
        raise countdowntourney.FixtureGeneratorException(
            "Number of players is not valid")

    seeds = []
    for seed in range(1, num_players + 1):
        player_name = settings.get("seed%d" % seed)
        if player_name:
            for p in players:
                if p.name == player_name:
                    if p in seeds:
                        raise countdowntourney.FixtureGeneratorException(
                            "Player \"%s\" appears more than once in the seed list"
                            % player_name)
                    seeds.append(p)
                    break
            else:
                raise countdowntourney.FixtureGeneratorException(
                    "Seed %d \"%s\" is not a known player name" %
                    (seed, player_name))
        else:
            raise countdowntourney.FixtureGeneratorException(
                "Seed %d is not specified" % seed)

    (rounds, fixtures) = generate_knockout(tourney, seeds)

    d = dict()
    d["fixtures"] = fixtures
    d["rounds"] = rounds

    return d
Esempio n. 6
0
def generate(tourney, settings, div_rounds):
    generated_groups = fixgen.GeneratedGroups()

    # Check that these rounds don't already have games in them, and that each
    # division we're generating a final for has at least two players in it,
    # and that there isn't a tie for joint second.
    (ready, excuse) = check_ready(tourney, div_rounds)
    if not ready:
        raise countdowntourney.FixtureGeneratorException(excuse)

    for div_index in div_rounds:
        round_no = div_rounds[div_index]
        standings = tourney.get_standings(division=div_index,
                                          calculate_qualification=False)
        final_players = [
            tourney.get_player_from_name(s.name) for s in standings[0:2]
        ]
        generated_groups.add_group(round_no, div_index, final_players)
        generated_groups.set_round_name(round_no, "Final")
        generated_groups.set_game_type(round_no, div_index, "F")

    return generated_groups
Esempio n. 7
0
def generate(tourney, settings, div_rounds):
    (ready, excuse) = check_ready(tourney, div_rounds)
    if not ready:
        raise countdowntourney.FixtureGeneratorException(excuse)

    num_divisions = tourney.get_num_divisions()
    players = tourney.get_active_players()

    generated_groups = fixgen.GeneratedGroups()

    for div in div_rounds:
        div_players = sorted([x for x in players if x.get_division() == div],
                             key=lambda x: x.get_rating(),
                             reverse=True)

        # If there are an odd number of players, then add an imaginary dummy
        # player. If a player is drawn to play the dummy then no fixture is
        # generated for the player for that round, and they sit out that
        # round.
        if len(div_players) % 2 != 0:
            div_players.append(None)

        num_rounds = len(div_players) - 1

        if num_rounds <= 0:
            # Erm...
            continue

        start_round_no = div_rounds[div]

        # Generate len(div_players) - 1 rounds.
        # Classical round robin algorithm: write the player in two lines
        # like this:
        #
        # 0 1 2 3
        # 7 6 5 4
        #
        # That's the first round: 0v7, 1v6, etc.
        # Then keep player 0 fixed and rotate all the others clockwise:
        #
        # 0 7 1 2
        # 6 5 4 3
        # That's the second round.
        #
        # 0 6 7 1
        # 5 4 3 2
        # That's the third round. And so on.

        top_line = list(range(len(div_players) // 2))
        bottom_line = list(
            range(len(div_players) - 1,
                  len(div_players) // 2 - 1, -1))
        for round_offset in range(num_rounds):
            # Check there aren't already games in this round for this division
            existing_games = tourney.get_games(round_no=(start_round_no +
                                                         round_offset),
                                               division=div)
            if existing_games:
                raise countdowntourney.FixtureGeneratorException(
                    "%s: can't generate fixtures for round %d because there are already %d fixtures for this division in this round."
                    % (tourney.get_division_name(div),
                       start_round_no + round_offset, len(existing_games)))

            tables = []
            for i in range(len(top_line)):
                # The player on the top line goes first, and the player on
                # the bottom line second, with the exception of the first
                # column, which alternates each round otherwise player 0
                # would go first in every game.
                if i > 0 or round_offset % 2 == 0:
                    tables.append((top_line[i], bottom_line[i]))
                else:
                    tables.append((bottom_line[i], top_line[i]))
            groups = []
            for (i1, i2) in tables:
                if div_players[i1] is not None and div_players[i2] is not None:
                    generated_groups.add_group(
                        start_round_no + round_offset, div,
                        (div_players[i1], div_players[i2]))

            #if start_round_no + round_offset not in round_numbers_generated:
            #    round_numbers_generated.append(start_round_no + round_offset)

            #fixtures += tourney.make_fixtures_from_groups(groups, fixtures,
            #        start_round_no + round_offset, False, division=div)

            # Take the last element from top_line and put it on the end of
            # bottom_line, and take the first element of bottom_line and put
            # it after the first element of top_line
            bottom_line.append(top_line[-1])
            top_line = [top_line[0]] + [bottom_line[0]] + top_line[1:-1]
            bottom_line = bottom_line[1:]

    return generated_groups
Esempio n. 8
0
def generate(tourney, settings, div_rounds):
    rank_method = tourney.get_rank_method();

    (ready, excuse) = check_ready(tourney, div_rounds);
    if not ready:
        raise countdowntourney.FixtureGeneratorException(excuse);
    
    max_time = settings.get("maxtime", 30);
    try:
        limit_ms = int(max_time) * 1000;
    except ValueError:
        limit_ms = 30000;

    ignore_rematches_before = settings.get("ignorerematchesbefore", None)
    if ignore_rematches_before:
        try:
            ignore_rematches_before = int(ignore_rematches_before)
        except ValueError:
            ignore_rematches_before = None
    else:
        ignore_rematches_before = None

    default_group_size = settings.get("groupsize", None)
    if default_group_size is not None:
        try:
            default_group_size = int(default_group_size)
        except ValueError:
            default_group_size = None

    num_divisions = tourney.get_num_divisions()
    generated_groups = fixgen.GeneratedGroups()
    for div_index in sorted(div_rounds):
        players = tourney.get_active_players()
        players = [x for x in players if x.division == div_index]

        div_prefix = "d%d_" % (div_index)

        group_size = settings.get(div_prefix + "groupsize", default_group_size)
        try:
            group_size = int(group_size)
        except ValueError:
            group_size = default_group_size
        if group_size == 0:
            group_size = default_group_size

        init_max_rematches = settings.get(div_prefix + "initmaxrematches", 0)
        try:
            init_max_rematches = int(init_max_rematches)
        except ValueError:
            init_max_rematches = 0

        init_max_win_diff = settings.get(div_prefix + "initmaxwindiff", 0)
        try:
            init_max_win_diff = int(init_max_win_diff)
        except ValueError:
            init_max_win_diff = 0

        if group_size < 2 and group_size != -5:
            raise countdowntourney.FixtureGeneratorException("%s: The table size is less than 2 (%d)" % (tourney.get_division_name(div_index), group_size))

        if group_size > 0 and len(players) % group_size != 0:
            raise countdowntourney.FixtureGeneratorException("%s: Number of players (%d) is not a multiple of the table size (%d)" % (tourney.get_division_name(div_index), len(players), group_size));

        if group_size == -5 and len(players) < 8:
            raise countdowntourney.FixtureGeneratorException("%s: Number of players (%d) is not valid for the 5&3 fixture generator - you need at least 8 players" % (tourney.get_division_name(div_index), len(players)))

        # Set a sensible cap of five minutes, in case the user has entered a
        # huge number to be clever
        if limit_ms > 300000:
            limit_ms = 300000;

        rank_by_wins = (rank_method == countdowntourney.RANK_WINS_POINTS or rank_method == countdowntourney.RANK_WINS_SPREAD);

        round_no = div_rounds[div_index]
        games = tourney.get_games(game_type="P");
        if len(games) == 0:
            (weight, groups) = swissN.swissN_first_round(players, group_size);
        else:
            (weight, groups) = swissN.swissN(games, players,
                    tourney.get_standings(div_index), group_size,
                    rank_by_wins=rank_by_wins, limit_ms=limit_ms,
                    init_max_rematches=init_max_rematches,
                    init_max_win_diff=init_max_win_diff,
                    ignore_rematches_before=ignore_rematches_before);

        if groups is None:
            raise countdowntourney.FixtureGeneratorException("%s: Unable to generate any permissible groupings in the given time limit." % (tourney.get_division_name(div_index)))

        for g in groups:
            generated_groups.add_group(round_no, div_index, g)
        generated_groups.set_repeat_threes(round_no, div_index, group_size == -5)
    
    return generated_groups
Esempio n. 9
0
def generate(tourney, settings, div_rounds, check_ready_fn=None):
    num_divisions = tourney.get_num_divisions()
    div_table_sizes = dict()

    players = tourney.get_active_players()
    for div_index in div_rounds:
        table_size = int_or_none(settings.get("d%d_groupsize" % (div_index)))
        div_players = [x for x in players if x.get_division() == div_index]

        # Reject if table size is not specified
        if table_size is None:
            raise countdowntourney.FixtureGeneratorException(
                "%s: No table size specified" %
                tourney.get_division_name(div_index))
        else:
            try:
                table_size = int(table_size)
            except ValueError:
                raise countdowntourney.FixtureGeneratorException(
                    "%s: Invalid table size %s" %
                    (tourney.get_division_name(div_index), table_size))

        # Reject if table size is nonsense
        if table_size not in (2, 3, 4, 5, -5):
            raise countdowntourney.FixtureGeneratorException(
                "%s: Invalid table size: %d" %
                (tourney.get_division_name(div_index), table_size))

        # Work out the number of player slots we're generating for this
        # division. This may be implied by the number of groups, or if that's
        # not given, it's the number of active players in this division.
        num_groups = int_or_none(settings.get("d%d_num_groups" % (div_index)))
        if num_groups is None:
            # No number of groups specified, so the number of players is all
            # the active players in this division.
            num_slots = len(div_players)
        else:
            if table_size <= 1:
                raise countdowntourney.FixtureGeneratorException(
                    "%s: invalid number of players per table for fully-manual setup."
                    % (tourney.get_division_name(div_ikndeX)))
            num_slots = num_groups * table_size

        if table_size > 0:
            if num_slots % table_size != 0:
                raise countdowntourney.FixtureGeneratorException(
                    "%s: Number of player slots (%d) is not a multiple of the table size (%d)"
                    % (tourney.get_division_name(div_index), num_slots,
                       table_size))
            table_sizes = [
                table_size for i in range(0, num_slots // table_size)
            ]
        else:
            if num_slots < 8:
                raise countdowntourney.FixtureGeneratorException(
                    "%s: Can't use a 5&3 configuration if there are fewer than 8 player slots, and there are %d"
                    % (tourney.get_division_name(div_index), num_slots))
            table_sizes = countdowntourney.get_5_3_table_sizes(num_slots)
        div_table_sizes[div_index] = table_sizes

    if check_ready_fn:
        (ready, excuse) = check_ready_fn(tourney, div_rounds)
    else:
        (ready, excuse) = check_ready(tourney, div_rounds)
    if not ready:
        raise countdowntourney.FixtureGeneratorException(excuse)

    #latest_round_no = tourney.get_latest_round_no('P')
    #if latest_round_no is None:
    #    round_no = 1
    #else:
    #    round_no = latest_round_no + 1

    generated_groups = fixgen.GeneratedGroups()
    for div_index in div_rounds:
        round_no = div_rounds[div_index]
        groups = []
        table_sizes = div_table_sizes[div_index]
        div_players = [x for x in players if x.get_division() == div_index]
        game_type = settings.get("d%d_game_type" % (div_index))
        if not game_type:
            if settings.get("d%d_heats" % (div_index)):
                game_type = "P"
            else:
                game_type = "N"

        num_slots = 0
        for size in table_sizes:
            num_slots += size

        # Player names should have been specified by a series of drop-down
        # boxes.
        player_names = []
        for i in range(0, num_slots):
            name = settings.get("d%d_player%d" % (div_index, i))
            if name:
                player_names.append(name)
            else:
                raise countdowntourney.FixtureGeneratorException(
                    "%s: Player %d not specified. This is probably a bug, as the form should have made you fill in all the boxes."
                    % (tourney.get_division_name(div_index), i))

        selected_players = [
            lookup_player(div_players, x) for x in player_names
        ]

        player_index = 0
        groups = []
        for size in table_sizes:
            group = []
            for i in range(size):
                group.append(selected_players[player_index])
                player_index += 1
            groups.append(group)

        for g in groups:
            generated_groups.add_group(round_no, div_index, g)
        generated_groups.set_repeat_threes(round_no, div_index,
                                           table_size == -5)

        round_name = settings.get("d%d_round_name" % (div_index))
        if round_name:
            generated_groups.set_round_name(round_no, round_name)
        generated_groups.set_game_type(round_no, div_index, game_type)

    return generated_groups
Esempio n. 10
0
def get_user_form(tourney, settings, div_rounds):
    num_divisions = tourney.get_num_divisions()
    div_table_sizes = dict()
    players = sorted(tourney.get_active_players(), key=lambda x: x.get_name())

    latest_round_no = tourney.get_latest_round_no()
    if latest_round_no is None:
        latest_round_no = 0

    prev_settings = settings.get_previous_settings()
    for key in prev_settings:
        if key not in settings and re.match("^d[0-9]*_groupsize$", key):
            settings[key] = prev_settings[key]
    if settings.get("submitrestore", None):
        for key in prev_settings:
            if key not in ["submit", "submitrestore", "submitplayers"]:
                settings[key] = prev_settings[key]

    elements = []
    elements.append(htmlform.HTMLFormHiddenInput("tablesizesubmit", "1"))
    elements.append(
        htmlform.HTMLFormHiddenInput("roundno", str(latest_round_no + 1)))

    # If there's a previously-saved form for this round, offer to load it
    prev_settings = settings.get_previous_settings()
    round_no = int_or_none(prev_settings.get("roundno", None))
    if round_no is not None and round_no == latest_round_no + 1:
        elements.append(
            htmlform.HTMLFragment("<div class=\"infoboxcontainer\">"))
        elements.append(htmlform.HTMLFragment("<div class=\"infoboximage\">"))
        elements.append(
            htmlform.HTMLFragment(
                "<img src=\"/images/info.png\" alt=\"Info\" />"))
        elements.append(htmlform.HTMLFragment("</div>"))
        elements.append(
            htmlform.HTMLFragment("<div class=\"infoboxmessagecontainer\">"))
        elements.append(
            htmlform.HTMLFragment("<div class=\"infoboxmessage\">"))
        elements.append(htmlform.HTMLFragment("<p>"))
        elements.append(
            htmlform.HTMLFragment(
                "There is an incomplete fixtures form saved. Do you want to carry on from where you left off?"
            ))
        elements.append(htmlform.HTMLFragment("</p>"))
        elements.append(htmlform.HTMLFragment("<p>"))
        elements.append(
            htmlform.HTMLFormSubmitButton("submitrestore",
                                          "Restore previously-saved form"))
        elements.append(htmlform.HTMLFragment("</p>"))
        elements.append(htmlform.HTMLFragment("</div></div></div>"))

    for div_index in div_rounds:
        div_players = [x for x in players if x.get_division() == div_index]
        table_size = None
        table_size_name = "d%d_groupsize" % (div_index)
        if settings.get(table_size_name, None) is not None:
            try:
                div_table_sizes[div_index] = int(settings.get(table_size_name))
            except ValueError:
                div_table_sizes[div_index] = None
        else:
            div_table_sizes[div_index] = None
        choices = []

        # Number of groups may be specified by fully-manual generator.
        # If it isn't, then use all the players.
        num_groups = int_or_none(settings.get("d%d_num_groups" % (div_index)))
        if num_groups:
            if div_table_sizes[
                    div_index] is not None and div_table_sizes[div_index] <= 0:
                raise countdowntourney.FixtureGeneratorException(
                    "%s: invalid table size for fully-manual setup." %
                    (tourney.get_division_name(div_index)))
        else:
            for size in (2, 3, 4, 5):
                if num_groups or len(div_players) % size == 0:
                    choices.append(
                        htmlform.HTMLFormChoice(
                            str(size), str(size),
                            size == div_table_sizes[div_index]))
            if len(div_players) >= 8:
                choices.append(
                    htmlform.HTMLFormChoice("-5", "5&3",
                                            div_table_sizes[div_index] == -5))

            if not choices:
                raise countdowntourney.FixtureGeneratorException(
                    "%s: number of players (%d) is not compatible with any supported table size."
                    % (tourney.get_division_name(div_index), len(div_players)))

        if num_divisions > 1:
            elements.append(
                htmlform.HTMLFragment(
                    "<h2>%s</h2>" %
                    (cgicommon.escape(tourney.get_division_name(div_index)))))

        elements.append(htmlform.HTMLFragment("<p>"))
        elements.append(
            htmlform.HTMLFormRadioButton(table_size_name, "Players per table",
                                         choices))
        elements.append(htmlform.HTMLFragment("</p>"))

    all_table_sizes_given = True
    for div in div_table_sizes:
        if div_table_sizes.get(div) is None:
            all_table_sizes_given = False

    if not all_table_sizes_given or not (settings.get("tablesizesubmit", "")):
        elements.append(htmlform.HTMLFragment("<p>"))
        elements.append(
            htmlform.HTMLFormSubmitButton(
                "submit", "Submit table sizes and select players"))
        elements.append(htmlform.HTMLFragment("</p>"))
        return htmlform.HTMLForm(
            "POST", "/cgi-bin/fixturegen.py?tourney=%s" %
            (urllib.parse.quote_plus(tourney.name)), elements)

    show_already_assigned_players = bool(settings.get("showallplayers"))

    div_num_slots = dict()
    for div_index in div_rounds:
        num_groups = int_or_none(settings.get("d%d_num_groups" % (div_index)))
        div_players = [x for x in players if x.get_division() == div_index]
        table_size = div_table_sizes[div_index]

        if num_groups:
            # If num_groups is specified, then we can't use the 5&3 setup.
            # If it's any other table setup then the number of player slots is
            # the number of players per table times num_groups.
            if table_size <= 0:
                raise countdowntourney.FixtureGeneratorException(
                    "%s: invalid table size for fully-manual setup" %
                    (tourney.get_division_name(div_index)))
            else:
                num_slots = table_size * num_groups
        else:
            # If num_groups is not specified, then the number if slots is
            # simply the number of active players in this division.
            num_slots = len(div_players)

        div_num_slots[div_index] = num_slots

        if table_size > 0 and num_slots % table_size != 0:
            raise countdowntourney.FixtureGeneratorException(
                "%s: table size of %d is not allowed, as the number of player slots (%d) is not a multiple of it."
                %
                (tourney.get_division_name(div_index), table_size, num_slots))

        if table_size == -5 and num_slots < 8:
            raise countdowntourney.FixtureGeneratorException(
                "%s: can't use table sizes of five and three - you need at least 8 players and you have %d"
                % (tourney.get_division_name(div_index), num_slots))

        if table_size not in (2, 3, 4, 5, -5):
            raise countdowntourney.FixtureGeneratorException(
                "%s: invalid table size: %d" %
                (tourney.get_division_name(div_index), table_size))

    div_set_players = dict()
    div_duplicate_slots = dict()
    div_empty_slots = dict()
    div_invalid_slots = dict()
    div_count_in_standings = dict()
    div_set_text = dict()
    div_game_type = dict()
    all_filled = True
    for div_index in div_rounds:
        div_players = [x for x in players if x.get_division() == div_index]
        num_groups = int_or_none(settings.get("d%d_num_groups" % (div_index)))

        num_slots = div_num_slots[div_index]

        set_players = [None for i in range(0, num_slots)]
        set_text = ["" for i in range(0, num_slots)]

        game_type = settings.get("d%d_game_type" % (div_index))

        if not game_type:
            if not settings.get("submitplayers"):
                count_in_standings = True
            else:
                count_in_standings = settings.get("d%d_heats" % (div_index))
                if count_in_standings is None:
                    count_in_standings = False
                else:
                    count_in_standings = True
        else:
            count_in_standings = (game_type == "P")

        # Slot numbers which contain text that doesn't match any player name
        invalid_slots = []

        allow_player_repetition = int_or_none(
            settings.get("d%d_allow_player_repetition" % (div_index)))
        if allow_player_repetition is None:
            allow_player_repetition = False
        else:
            allow_player_repetition = bool(allow_player_repetition)

        # Ask the user to fill in N little drop-down boxes, where N is the
        # number of players, to decide who's going on what table.
        for player_index in range(0, num_slots):
            name = settings.get("d%d_player%d" % (div_index, player_index))
            if name is None:
                name = ""
            set_text[player_index] = name
            if name:
                set_players[player_index] = lookup_player(div_players, name)
                if set_players[player_index] is None:
                    invalid_slots.append(player_index)
            else:
                set_players[player_index] = None

        # Slot numbers which contain a player already contained in another slot
        duplicate_slots = []

        # Slot numbers which don't contain a player
        empty_slots = []

        player_index = 0
        for p in set_players:
            if player_index in invalid_slots:
                all_filled = False
            elif p is None:
                empty_slots.append(player_index)
                all_filled = False
            else:
                if not allow_player_repetition:
                    count = 0
                    for q in set_players:
                        if q is not None and q.get_name() == p.get_name():
                            count += 1
                    if count > 1:
                        duplicate_slots.append(player_index)
                        all_filled = False
            player_index += 1

        div_set_players[div_index] = set_players
        div_duplicate_slots[div_index] = duplicate_slots
        div_empty_slots[div_index] = empty_slots
        div_invalid_slots[div_index] = invalid_slots
        div_count_in_standings[div_index] = count_in_standings
        div_set_text[div_index] = set_text
        div_game_type[div_index] = game_type

    interface_type = int_or_none(
        settings.get("interfacetype", INTERFACE_AUTOCOMPLETE))

    if all_filled and settings.get("submitplayers"):
        # All slots filled, don't need to ask the user anything more
        return None

    elements = []
    elements.append(
        htmlform.HTMLFormHiddenInput("roundno", str(latest_round_no + 1)))
    elements.append(
        htmlform.HTMLFragment("""<style type=\"text/css\">
table.seltable {
    margin-top: 20px;
}
.seltable td {
    padding: 2px;
    border: 2px solid white;
}
td.tablenumber {
    font-family: "Cabin";
    background-color: blue;
    color: white;
    text-align: center;
    min-width: 1.5em;
}
.duplicateplayer {
    background-color: violet;
}
.emptyslot {
    /*background-color: #ffaa00;*/
}
.invalidslot {
    background-color: red;
}
.validslot {
    background-color: #00cc00;
}
</style>
"""))
    elements.append(
        htmlform.HTMLFragment("""<script>
function set_unsaved_data_warning() {
    if (window.onbeforeunload == null) {
        window.onbeforeunload = function() {
            return "You have modified entries on this page and not submitted them. If you navigate away from the page, these changes will be lost.";
        };
    }
}

function unset_unsaved_data_warning() {
    window.onbeforeunload = null;
}
</script>
"""))

    autocomplete_script = "<script>\n"
    autocomplete_script += "var divPlayerNames = "

    div_player_names = {}
    for div_index in div_rounds:
        name_list = [
            x.get_name() for x in players if x.get_division() == div_index
        ]
        div_player_names[div_index] = name_list
    autocomplete_script += json.dumps(div_player_names, indent=4) + ";\n"

    autocomplete_script += """
function setLastEditedBox(controlId) {
    var lastEdited = document.getElementById("lasteditedinput");
    if (lastEdited != null) {
        lastEdited.value = controlId;
    }
}

function editBoxEdit(divIndex, controlId) {
    var control = document.getElementById(controlId);
    if (control == null)
        return;

    setLastEditedBox(controlId);

    var value = control.value;
    //console.log("editBoxEdit() called, value " + value);
    var previousValue = control.getAttribute("previousvalue");

    /* If the change has made the value longer, then proceed. Otherwise don't
       do any autocompletion because that would interfere with the user's
       attempt to backspace out the text. */
    //console.log("editBoxEdit() called, value " + value + ", previousValue " + previousValue);

    control.setAttribute("previousvalue", value);

    if (previousValue != null && value.length <= previousValue.length) {
        return;
    }

    /* Take the portion of the control's value from the start of the string
       to the start of the selected part. If that string is the start of
       exactly one player's name, then:
       1. Set the control's value to the player's full name
       2. Highlight the added portion
       3. Leave the cursor where it was before.
    */
    var validNames = divPlayerNames[divIndex];
    if (validNames) {
        var lastMatch = null;
        var numMatches = 0;
        var selStart = control.selectionStart;

        // head is the part the user typed in, i.e. the bit not highlighted
        var head = value.toLowerCase().substring(0, selStart);
        for (var i = 0; i < validNames.length; ++i) {
            if (validNames[i].toLowerCase().startsWith(head)) {
                numMatches++;
                lastMatch = validNames[i];
            }
        }

        if (numMatches == 1) {
            control.focus();
            control.value = lastMatch;
            control.setSelectionRange(head.length, lastMatch.length);
        }
    }
}
"""

    autocomplete_script += "</script>\n"
    elements.append(htmlform.HTMLFragment(autocomplete_script))

    elements.append(
        htmlform.HTMLFragment(
            "<p>Enter player names below. Each horizontal row is one group, or table.</p>"
        ))

    choice_data = [("Auto-completing text boxes", INTERFACE_AUTOCOMPLETE),
                   ("Drop-down boxes", INTERFACE_DROP_DOWN),
                   ("Combo boxes (not supported on all browsers)",
                    INTERFACE_DATALIST)]
    choices = [
        htmlform.HTMLFormChoice(str(x[1]), x[0], interface_type == x[1])
        for x in choice_data
    ]
    interface_menu = htmlform.HTMLFormRadioButton(
        "interfacetype", "Player name selection interface", choices)
    elements.append(interface_menu)

    if interface_type == INTERFACE_DROP_DOWN:
        elements.append(htmlform.HTMLFragment("<div class=\"fixgenoption\">"))
        elements.append(
            htmlform.HTMLFormCheckBox(
                "showallplayers",
                "Show all players in drop-down boxes, even those already assigned a table",
                show_already_assigned_players))
        elements.append(htmlform.HTMLFragment("</div>"))

    (acc_tables, acc_default) = tourney.get_accessible_tables()

    table_no = 1
    for div_index in div_rounds:
        div_players = [x for x in players if x.get_division() == div_index]
        player_index = 0
        table_size = div_table_sizes[div_index]
        duplicate_slots = div_duplicate_slots[div_index]
        invalid_slots = div_invalid_slots[div_index]
        empty_slots = div_empty_slots[div_index]
        set_players = div_set_players[div_index]
        set_text = div_set_text[div_index]
        num_slots = div_num_slots[div_index]
        game_type = div_game_type[div_index]

        if num_divisions > 1:
            elements.append(
                htmlform.HTMLFragment(
                    "<h2>%s</h2>" %
                    (cgicommon.escape(tourney.get_division_name(div_index)))))

        if not game_type:
            # Ask the user if they want these games to count towards the
            # standings table (this is pretty much universally yes)
            elements.append(
                htmlform.HTMLFragment("<div class=\"fixgenoption\">"))
            elements.append(
                htmlform.HTMLFormCheckBox(
                    "d%d_heats" % (div_index),
                    "Count the results of these matches in the standings table",
                    div_count_in_standings[div_index]))
            elements.append(htmlform.HTMLFragment("</div>"))

        # Show the table of groups for the user to fill in
        elements.append(htmlform.HTMLFragment("<table class=\"seltable\">\n"))
        prev_table_no = None
        unselected_names = [x.get_name() for x in div_players]

        if table_size > 0:
            table_sizes = [
                table_size for i in range(0, num_slots // table_size)
            ]
        else:
            table_sizes = countdowntourney.get_5_3_table_sizes(num_slots)

        for p in set_players:
            if p and p.get_name() in unselected_names:
                unselected_names.remove(p.get_name())

        for table_size in table_sizes:
            elements.append(htmlform.HTMLFragment("<tr>\n"))
            elements.append(
                htmlform.HTMLFragment(
                    "<td>%s</td><td class=\"tablenumber\">%d</td>\n" %
                    (" &#9855;" if
                     (table_no in acc_tables) != acc_default else "",
                     table_no)))
            if game_type is not None:
                elements.append(
                    htmlform.HTMLFragment(
                        "<td class=\"fixturegametype\">%s</td>" %
                        (cgicommon.escape(game_type, True))))
            for i in range(table_size):
                p = set_players[player_index]
                td_style = ""
                value_is_valid = False
                if player_index in duplicate_slots:
                    td_style = "class=\"duplicateplayer\""
                elif player_index in empty_slots:
                    td_style = "class=\"emptyslot\""
                elif player_index in invalid_slots:
                    td_style = "class=\"invalidslot\""
                else:
                    td_style = "class=\"validslot\""
                    value_is_valid = True
                elements.append(htmlform.HTMLFragment("<td %s>" % td_style))

                # Make a drop down list with every unassigned player in it
                player_option_list = []

                if interface_type == INTERFACE_DROP_DOWN:
                    # Drop-down list needs an initial "nothing selected" option
                    player_option_list.append(
                        htmlform.HTMLFormDropDownOption("", " -- select --"))
                selected_name = ""

                if show_already_assigned_players:
                    name_list = [x.get_name() for x in div_players]
                else:
                    if p:
                        name_list = sorted(unselected_names + [p.get_name()])
                    else:
                        name_list = unselected_names

                for q in name_list:
                    if p is not None and q == p.get_name():
                        selected_name = p.get_name()
                    if interface_type == INTERFACE_DROP_DOWN:
                        player_option_list.append(
                            htmlform.HTMLFormDropDownOption(q, q))
                    else:
                        player_option_list.append(q)
                if interface_type != INTERFACE_DROP_DOWN and not selected_name:
                    selected_name = set_text[player_index]

                # Select the appropriate player
                control_name = "d%d_player%d" % (div_index, player_index)
                if interface_type == INTERFACE_DATALIST:
                    sel = htmlform.HTMLFormComboBox(
                        control_name,
                        player_option_list,
                        other_attrs={
                            "onchange": "set_unsaved_data_warning();"
                        })
                elif interface_type == INTERFACE_DROP_DOWN:
                    sel = htmlform.HTMLFormDropDownBox(
                        control_name,
                        player_option_list,
                        other_attrs={
                            "onchange": "set_unsaved_data_warning();"
                        })
                elif interface_type == INTERFACE_AUTOCOMPLETE:
                    sel = htmlform.HTMLFormTextInput(
                        "",
                        control_name,
                        selected_name,
                        other_attrs={
                            "oninput":
                            "editBoxEdit(%d, \"%s\");" %
                            (div_index, control_name),
                            "onclick":
                            "if (this.selectionStart == this.selectionEnd) { this.select(); }",
                            "id":
                            control_name,
                            "validvalue":
                            "1" if value_is_valid else "0",
                            "previousvalue":
                            selected_name,
                            "class":
                            "playerslot"
                        })
                else:
                    sel = None

                sel.set_value(selected_name)

                elements.append(sel)
                elements.append(htmlform.HTMLFragment("</td>"))
                player_index += 1
            table_no += 1
            elements.append(htmlform.HTMLFragment("</tr>\n"))

        elements.append(htmlform.HTMLFragment("</table>\n"))

        if len(acc_tables) > 0:
            # Warn the user that the table numbers displayed above might not
            # end up being the final table numbers.
            elements.append(
                htmlform.HTMLFragment(
                    "<p style=\"font-size: 10pt\">Note: You have designated accessible tables, so the table numbers above may be automatically reassigned to fulfil accessibility requirements.</p>"
                ))

        # Add the submit button
        elements.append(htmlform.HTMLFragment("<p>\n"))
        elements.append(htmlform.HTMLFormHiddenInput("submitplayers", "1"))
        elements.append(
            htmlform.HTMLFormHiddenInput("lasteditedinput",
                                         "",
                                         other_attrs={"id":
                                                      "lasteditedinput"}))
        elements.append(
            htmlform.HTMLFormSubmitButton("submit",
                                          "Submit",
                                          other_attrs={
                                              "onclick":
                                              "unset_unsaved_data_warning();",
                                              "class": "bigbutton"
                                          }))
        elements.append(htmlform.HTMLFragment("</p>\n"))

        if invalid_slots:
            elements.append(
                htmlform.HTMLFragment(
                    "<p>You have slots with unrecognised player names; these are highlighted in <span style=\"color: red; font-weight: bold;\">red</span>.</p>"
                ))
        if duplicate_slots:
            elements.append(
                htmlform.HTMLFragment(
                    "<p>You have players in multiple slots; these are highlighted in <span style=\"color: violet; font-weight: bold;\">violet</span>.</p>"
                ))

        if unselected_names:
            elements.append(
                htmlform.HTMLFragment(
                    "<p>Players still to be given a table:\n"))
            for i in range(len(unselected_names)):
                name = unselected_names[i]
                elements.append(
                    htmlform.HTMLFragment(
                        "%s%s" %
                        (cgicommon.escape(name, True),
                         "" if i == len(unselected_names) - 1 else ", ")))
            elements.append(htmlform.HTMLFragment("</p>\n"))

        elements.append(
            htmlform.HTMLFormHiddenInput("d%d_groupsize" % (div_index),
                                         str(div_table_sizes[div_index])))

        show_standings = int_or_none(
            settings.get("d%d_show_standings" % (div_index)))
        if show_standings:
            elements.append(
                htmlform.HTMLFormStandingsTable("d%d_standings" % (div_index),
                                                tourney, div_index))

    last_edited_input_name = settings.get("lasteditedinput", "")
    set_element_focus_script = """
<script>
var lastEditedElementName = %s;

var playerBoxes = document.getElementsByClassName("playerslot");
var playerBoxesBefore = [];
var playerBoxesAfter = [];
var foundElement = false;

for (var i = 0; i < playerBoxes.length; ++i) {
    if (playerBoxes[i].name == lastEditedElementName) {
        foundElement = true;
    }
    if (foundElement) {
        playerBoxesAfter.push(playerBoxes[i]);
    }
    else {
        playerBoxesBefore.push(playerBoxes[i]);
    }
}
//console.log("playerBoxesAfter " + playerBoxesAfter.length.toString() + ", playerBoxesBefore " + playerBoxesBefore.length.toString());

/* Give focus to the first text box equal to or after this one which
   does not have a valid value in it. If there are no such text boxes,
   search from the beginning of the document onwards. */
var playerBoxOrder = playerBoxesAfter.concat(playerBoxesBefore);

for (var i = 0; i < playerBoxOrder.length; ++i) {
    var box = playerBoxOrder[i];
    var validValue = box.getAttribute("validvalue");
    if (validValue == null || validValue == "0") {
        box.focus();
        box.select();
        break;
    }
}
</script>
""" % (json.dumps(last_edited_input_name))
    elements.append(htmlform.HTMLFragment(set_element_focus_script))

    form = htmlform.HTMLForm(
        "POST", "/cgi-bin/fixturegen.py?tourney=%s" %
        (urllib.parse.quote_plus(tourney.name)), elements)
    return form
Esempio n. 11
0
def generate(tourney, settings, div_rounds):
    (ready, excuse) = check_ready(tourney, div_rounds)
    if not ready:
        raise countdowntourney.FixtureGeneratorException(excuse)

    avoid_rematches = bool(settings.get("avoidrematches", False))

    generated_groups = fixgen.GeneratedGroups()

    for div_index in div_rounds:
        round_no = div_rounds[div_index]
        players = [
            x for x in tourney.get_active_players()
            if x.get_division() == div_index
        ]
        prunes = [p for p in players if p.rating == 0]
        non_prunes = [p for p in players if p.rating != 0]

        tables = []

        table_size = int(settings.get("d%d_groupsize" % (div_index)))

        table_sizes = gencommon.get_table_sizes(len(players), table_size)

        if avoid_rematches:
            tables = random_without_rematches(players, table_sizes,
                                              tourney.get_games(game_type="P"),
                                              10000)

            if not tables:
                raise countdowntourney.FixtureGeneratorException(
                    "Failed to find a set of fixtures with no rematches.")

            # Put any Pruney tables at the end
            tables.reverse()
        else:
            # Randomly shuffle the player list, but always put any prunes at the
            # end of the list. This will ensure they all go on separate tables
            # if possible.
            random.shuffle(non_prunes)
            players = non_prunes + prunes

            if table_size > 0:
                # Distribute the players across the tables
                num_tables = len(players) // table_size
                tables = [[] for i in range(num_tables)]
                table_no = 0
                for p in players:
                    tables[table_no].append(p)
                    table_no = (table_no + 1) % num_tables
            elif table_size == -5:
                # Have as many tables of 3 as required to take the number of
                # players remaining to a multiple of 5, then put the remaining
                # players on tables of 5.
                table_sizes = []
                players_left = len(players)
                while players_left % 5 != 0:
                    table_sizes.append(3)
                    players_left -= 3
                for i in range(players_left // 5):
                    table_sizes.append(5)
                tables = [[] for x in table_sizes]

                # Reverse the list so we use the prunes first, and they can go
                # on the 3-tables
                players.reverse()

                table_pos = 0
                for p in players:
                    iterations = 0
                    while len(tables[table_pos]) >= table_sizes[table_pos]:
                        table_pos = (table_pos + 1) % len(tables)
                        iterations += 1
                        assert (iterations <= len(tables))
                    tables[table_pos].append(p)
                    table_pos = (table_pos + 1) % len(tables)

                # Reverse the table list so the 5-tables are first
                tables.reverse()

        for tab in tables:
            generated_groups.add_group(round_no, div_index, tab)

        generated_groups.set_repeat_threes(round_no, div_index,
                                           table_size == -5)

    return generated_groups
Esempio n. 12
0
                next_free_round_number = tourney.get_next_free_round_number_for_division(
                    div)
                fixgen_settings["_div%d" % (div)] = "1"
                fixgen_settings["_div%dround" %
                                (div)] = str(next_free_round_number)

        div_rounds = dict()
        for div in range(num_divisions):
            if int_or_none(fixgen_settings.get("_div%d" % (div), "0")):
                start_round = int_or_none(
                    fixgen_settings.get("_div%dround" % (div), None))
                if start_round is not None and start_round > 0:
                    div_rounds[div] = start_round

        if len(div_rounds) == 0:
            raise countdowntourney.FixtureGeneratorException(
                "No divisions selected, so can't generate fixtures.")

        (ready, excuse) = fixturegen.check_ready(tourney, div_rounds)

        if ready:
            settings_form = fixturegen.get_user_form(tourney, fixgen_settings,
                                                     div_rounds)
            if settings_form is None and "accept" not in form:
                # We don't require any more information from the user, so
                # generate the fixtures.
                generated_groups = fixturegen.generate(tourney,
                                                       fixgen_settings,
                                                       div_rounds)

                # Persist the settings used to generate these fixtures,
                # in case the fixture generator wants to refer to them