def get_user_form(tourney, settings, div_rounds): div_group_size = dict() div_init_max_rematches = dict() div_init_max_win_diff = dict() prev_settings = settings.get_previous_settings() for key in prev_settings: if key not in settings and key != "submit": settings[key] = prev_settings[key] rounds = tourney.get_rounds(); num_divisions = tourney.get_num_divisions() max_time = int_or_none(settings.get("maxtime", None)) ignore_rematches_before_round = int_or_none(settings.get("ignorerematchesbefore", None)) div_ready = [] for div in range(num_divisions): if div in div_rounds: div_ready.append(False) else: div_ready.append(True) default_group_size = int_or_none(settings.get("groupsize", None)) for div_index in sorted(div_rounds): group_size = int_or_none(settings.get("d%d_groupsize" % (div_index), None)) if group_size is None or group_size == 0: group_size = default_group_size init_max_rematches = int_or_none(settings.get("d%d_initmaxrematches" % (div_index), "0")) init_max_win_diff = int_or_none(settings.get("d%d_initmaxwindiff" % (div_index), 0)) games = tourney.get_games(game_type='P', division=div_index); players = [x for x in tourney.get_active_players() if x.division == div_index]; if max_time is not None and max_time > 0 and group_size in valid_group_sizes and (group_size == -5 or len(players) % group_size == 0): div_ready[div_index] = True # else: # if max_time is None or max_time == 0: # max_time = 30; # if group_size is None or group_size not in valid_group_sizes: # if len(players) % 3 == 0: # group_size = 3 # elif len(players) % 2 == 0: # group_size = 2 # elif len(players) % 5 == 0: # group_size = 5 # elif len(players) >= 8: # group_size = -5 # elif len(players) % 4 == 0: # group_size = 4 # else: # group_size = None div_group_size[div_index] = group_size div_init_max_rematches[div_index] = init_max_rematches div_init_max_win_diff[div_index] = init_max_win_diff if False not in div_ready and settings.get("submit") is not None: return None elements = []; javascript = """ <script type="text/javascript"> var click_time = 0; var limit_seconds = 0; var noticed_results_overdue = false; var gerunds = ["Reticulating", "Exaggerating", "Refrigerating", "Bisecting", "Reordering", "Unseeding", "Reconstituting", "Inverting", "Convolving", "Reinventing", "Overpopulating", "Unwedging", "Tenderising", "Refactoring", "Frobnicating", "Normalising", "Factorising", "Transforming", "Relaying", "Decoupling", "Randomising", "Ignoring", "Disposing of", "Translating", "Restarting", "Entertaining", "Checking", "Verifying", "Flushing", "Contextualising", "Deconstructing", "Justifying", "Hacking", "Redrawing", "Reimagining", "Reinterpreting", "Reasoning with", "Impersonating", "Abbreviating", "Underestimating", "Misappropriating", "Constructing", "Preparing", "Redelivering", "Arguing over", "Grilling", "Baking", "Poaching", "Washing", "Stealing", "Emulsifying", "Discombobulating", "Correcting", "Extracting", "Unspooling", "Descaling", "Duplicating", "Overwriting" ]; var nouns = ["seeding list", "rule book", "hypergrid", "network services", "timestamps", "multidimensional array", "decision tree", "player list", "weighting matrix", "instrument panel", "database", "videprinter", "standings table", "preclusion rules", "event handlers", "dynamic modules", "hypertext", "fixture generator", "linked lists", "hash tables", "system clock", "file descriptors", "syntax tree", "binary tree", "dictionary", "homework", "breakfast", "contextualiser", "splines", "supercluster", "record books", "sandwiches", "grouping strategy", "reality", "spatula", "Eyebergine", "scripts", "blockchain", "phone charger", "fixtures", "associative arrays", "browser window", "subfolders" ]; var endings = [ "Bribing officials", "Talking bollocks", "Feeding cat", "Rewinding tape", "Invading privacy", "Falling off cliff", "Kicking tyres", "Tapping barometer", "Serving hot", "Deploying parachute", "Cleaning up mess", "Straightening tie", "Seasoning to taste", "Stealing towels", "Reversing polarity", "Untangling headphones", "Compounding misery" ]; function spam_progress_label() { var progress = ""; var pc = 0; var ms_elapsed = 0; if (limit_seconds != NaN) { current_time = new Date(); ms_elapsed = current_time.getTime() - click_time.getTime(); pc = Math.floor(ms_elapsed * 100 / (limit_seconds * 1000)); if (pc > 100) { pc = 100; } progress = pc.toString() + "%"; } if (ms_elapsed < 500) { document.getElementById('progresslabel').innerHTML = "Generating fixtures..."; } else if (pc < 100) { if (Math.random() < 0.4) { var gerund = ""; var noun = ""; gerund = gerunds[Math.floor(Math.random() * gerunds.length)]; noun = nouns[Math.floor(Math.random() * nouns.length)]; document.getElementById('progresslabel').innerHTML = progress + " " + gerund + " " + noun + "..."; } } else if (ms_elapsed < limit_seconds * 1000 + 3000) { if (!noticed_results_overdue) { var ending = endings[Math.floor(Math.random() * endings.length)]; document.getElementById('progresslabel').innerHTML = "100% " + ending + "..."; noticed_results_overdue = true; } } else { document.getElementById('progresslabel').innerHTML = "We ought to have finished by now."; } } function generate_fixtures_clicked() { click_time = new Date(); noticed_results_overdue = false; limit_seconds = parseInt(document.getElementById('maxtime').value) * parseInt(document.getElementById('numdivisions').value); // document.getElementById('generatefixtures').disabled = true; spam_progress_label(); setInterval(function() { spam_progress_label(); }, 300); } </script>"""; elements.append(htmlform.HTMLFragment(javascript)); elements.append(htmlform.HTMLFragment("<h2>Overall settings</h2>")) div_valid_table_sizes = [] for div_index in sorted(div_rounds): div_players = [x for x in tourney.get_active_players() if x.get_division() == div_index] sizes = get_valid_group_sizes(len(div_players), len(rounds)) div_valid_table_sizes.append(sizes) table_sizes_valid_for_all_divs = [] for size in valid_group_sizes: for div_sizes in div_valid_table_sizes: if size not in div_sizes: break else: table_sizes_valid_for_all_divs.append(size) for size in (3, 2, 5, -5, 4): if size in table_sizes_valid_for_all_divs: default_default_group_size = size break else: default_default_group_size = None if num_divisions > 1 and len(table_sizes_valid_for_all_divs) > 0: elements.append(htmlform.HTMLFragment("<p>")) group_size_choices = [ htmlform.HTMLFormChoice(str(gs), "5&3" if gs == -5 else str(gs), int_or_none(settings.get("groupsize", default_default_group_size)) == gs) for gs in table_sizes_valid_for_all_divs ] elements.append(htmlform.HTMLFormRadioButton("groupsize", "Default players per table", group_size_choices)) elements.append(htmlform.HTMLFragment("</p>")) elements.append(htmlform.HTMLFragment("<p>\n")) elements.append(htmlform.HTMLFormTextInput("Fixture generator time limit %s(seconds)" % ("per division " if num_divisions > 1 else ""), "maxtime", settings.get("maxtime", "30"), other_attrs={"size": "3", "id" : "maxtime"})); elements.append(htmlform.HTMLFragment("</p>\n<p>\n")) elements.append(htmlform.HTMLFormTextInput("For the purpose of avoiding rematches, disregard games before round ", "ignorerematchesbefore", str(ignore_rematches_before_round) if ignore_rematches_before_round is not None else "", other_attrs={"size": "3"})); elements.append(htmlform.HTMLFragment(" (leave blank to count all rematches)")) elements.append(htmlform.HTMLFormHiddenInput("numdivisions", str(len(div_rounds)), other_attrs={"id" : "numdivisions"})) elements.append(htmlform.HTMLFragment("</p>\n")) elements.append(htmlform.HTMLFragment("<hr />\n")) for div_index in sorted(div_rounds): group_size = div_group_size[div_index] init_max_rematches = div_init_max_rematches[div_index] init_max_win_diff = div_init_max_win_diff[div_index] players = [x for x in tourney.get_active_players() if x.division == div_index]; div_prefix = "d%d_" % (div_index) if num_divisions > 1: elements.append(htmlform.HTMLFragment("<h2>%s (%d active players)</h2>" % (cgicommon.escape(tourney.get_division_name(div_index)), len(players)))) else: elements.append(htmlform.HTMLFragment("<h2>Fixture generation (%d active players)</h2>" % (len(players)))) elements.append(htmlform.HTMLFragment("<p>")) div_valid_sizes = get_valid_group_sizes(len(players), len(rounds)) ticked_group_size = int_or_none(settings.get(div_prefix + "groupsize")) if ticked_group_size is None: if len(table_sizes_valid_for_all_divs) > 0 and num_divisions > 1: # There is a "default table size" option ticked_group_size = 0 else: ticked_group_size = get_default_group_size(len(players), len(rounds)) group_size_choices = [ htmlform.HTMLFormChoice(str(gs), "5&3" if gs == -5 else str(gs), gs == ticked_group_size) for gs in div_valid_sizes ] if num_divisions > 1 and len(table_sizes_valid_for_all_divs) > 0: group_size_choices = [ htmlform.HTMLFormChoice("0", "Round default (above)", ticked_group_size == 0) ] + group_size_choices elements.append(htmlform.HTMLFormRadioButton(div_prefix + "groupsize", "Players per table", group_size_choices)) elements.append(htmlform.HTMLFragment("</p>\n")) elements.append(htmlform.HTMLFragment("<p>Increase the following values if the fixture generator has trouble finding a grouping within the time limit.</p>\n")); elements.append(htmlform.HTMLFragment("<blockquote>")) elements.append(htmlform.HTMLFormTextInput("Initial maximum rematches between players", div_prefix + "initmaxrematches", str(init_max_rematches), other_attrs={"size" : "3"})) elements.append(htmlform.HTMLFragment("</blockquote>\n<blockquote>")) elements.append(htmlform.HTMLFormTextInput("Initial maximum win count difference between players", div_prefix + "initmaxwindiff", str(init_max_win_diff), other_attrs={"size" : "3"})) elements.append(htmlform.HTMLFragment("</blockquote>\n")) if num_divisions > 1: elements.append(htmlform.HTMLFragment("<hr />\n")) elements.append(htmlform.HTMLFormSubmitButton("submit", "Generate Fixtures", other_attrs={"onclick": "generate_fixtures_clicked();", "id": "generatefixtures", "class" : "bigbutton"})); elements.append(htmlform.HTMLFragment("<p id=\"progresslabel\">For large numbers of players or unusual formats, fixture generation is not immediate - it can take up to the specified number of seconds, or longer if no permissible configurations are found in that time.</p><hr /><p></p>")); elements.append(htmlform.HTMLFragment("<noscript>Your browser doesn't have Javascript enabled, which means you miss out on progress updates while fixtures are being generated.</noscript>")); form = htmlform.HTMLForm("POST", "/cgi-bin/fixturegen.py", elements); return form;
def get_user_form(tourney, settings, div_rounds): num_divisions = tourney.get_num_divisions() div_num_games = dict() div_game_types = 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("numgamessubmit", "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>")) # When we pass these settings to fixgen_manual, we don't want it asking # awkward questions about the number of players in a group when we're # fixing at it two, so tell it that's already been submitted. settings["tablesizesubmit"] = "1" for div_index in div_rounds: num_games_name = "d%d_num_groups" % (div_index) game_type_name = "d%d_game_type" % (div_index) # For fully-manual, number of players per group is always 2, and # we're allowed to put a player on more than one table settings["d%d_groupsize" % (div_index)] = "2" settings["d%d_allow_player_repetition" % (div_index)] = "1" # Also we want fixgen_manual to show the standings table for each # division. settings["d%d_show_standings" % (div_index)] = "1" if settings.get(num_games_name, None) is not None: try: div_num_games[div_index] = int(settings.get(num_games_name)) if div_num_games[div_index] < 0: div_num_games[div_index] = 0 except ValueError: div_num_games[div_index] = 0 else: div_num_games[div_index] = 0 if settings.get(game_type_name, None) is not None: try: div_game_types[div_index] = settings.get(game_type_name) if div_game_types[div_index] in special_round_names: settings["d%d_round_name" % (div_index)] = special_round_names[ div_game_types[div_index]] except ValueError: div_game_types[div_index] = None if num_divisions > 1: elements.append( htmlform.HTMLFragment( "<h2>%s</h2>" % (cgicommon.escape(tourney.get_division_name(div_index))))) elements.append(htmlform.HTMLFragment("<div class=\"fixgenoption\">")) num_games_element = htmlform.HTMLFormTextInput( "Number of games to create", num_games_name, "") elements.append(num_games_element) elements.append(htmlform.HTMLFragment("</div>")) elements.append(htmlform.HTMLFragment("<div class=\"fixgenoption\">")) elements.append(htmlform.HTMLFragment("Create games of this type: ")) game_type_options = [ htmlform.HTMLFormDropDownOption(x["code"], x["name"] + " (" + x["code"] + ")") for x in countdowntourney.get_game_types() ] type_element = htmlform.HTMLFormDropDownBox( "d%d_game_type" % (div_index), game_type_options) current_setting = settings.get("d%d_game_type" % (div_index)) if current_setting: type_element.set_value(current_setting) elements.append(type_element) elements.append(htmlform.HTMLFragment("</div>")) num_games_total = sum([div_num_games[x] for x in div_num_games]) if num_games_total == 0 or not (settings.get("numgamessubmit", "")): elements.append(htmlform.HTMLFragment("<div class=\"fixgenoption\">")) elements.append(htmlform.HTMLFormSubmitButton("submit", "Continue")) elements.append(htmlform.HTMLFragment("</div>")) return htmlform.HTMLForm( "POST", "/cgi-bin/fixturegen.py?tourney=%s" % (urllib.parse.quote_plus(tourney.name)), elements) else: return fixgen_manual.get_user_form(tourney, settings, div_rounds)
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" % (" ♿" 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
def get_user_form(tourney, settings): elements = [] num_players = settings.get("num_players") if num_players: try: num_players = int(num_players) num_players_in_tourney = len(tourney.get_active_players()) if num_players < 2 or num_players > num_players_in_tourney: elements.append( htmlform.HTMLFragment( "<p><strong>%d is an invalid number of players: must be between 2 and %d.</strong></p>" % (num_players, num_players_in_tourney))) num_players = None except ValueError: elements.append( htmlform.HTMLFragment( "<p><strong>The number of players must be a number.</strong></p>" )) num_players = None player_selection_mode = settings.get("player_sel_mode") if player_selection_mode not in ("topntable", "topnrating", "random", "manual"): player_selection_mode = None if not num_players: # Page 1 elements.append( htmlform.HTMLFormTextInput("How many players?", "num_players", "", other_attrs={"size": "4"})) elements.append(htmlform.HTMLFormSubmitButton("submit", "Submit")) elif not player_selection_mode: # Page 2 elements.append( htmlform.HTMLFragment( "<p>How do you want to pick these %d players?</p>" % num_players)) sel_options = [] sel_options.append( htmlform.HTMLFormDropDownOption( "topntable", "Top %d players by table position" % num_players)) sel_options.append( htmlform.HTMLFormDropDownOption( "topnrating", "Top %d players by rating" % num_players)) sel_options.append( htmlform.HTMLFormDropDownOption( "random", "%d players in a random draw" % num_players)) sel_options.append( htmlform.HTMLFormDropDownOption("manual", "Specify draw manually")) elements.append(htmlform.HTMLFragment("<p>")) elements.append( htmlform.HTMLFormDropDownBox("player_sel_mode", sel_options)) elements.append(htmlform.HTMLFormHiddenInput("page2", "1")) elements.append(htmlform.HTMLFragment("</p><p>")) elements.append(htmlform.HTMLFormSubmitButton("submit", "Pick Players")) elements.append(htmlform.HTMLFragment("</p>")) else: # Page 3 players = tourney.get_active_players() standings = tourney.get_standings() # If we've just come from page 2, decide on initial player names in # "settings" for seed1 ... seedN. if settings.get("page2"): del settings["page2"] if player_selection_mode == "topntable": seed = 1 for standing in standings[0:num_players]: settings["seed%d" % seed] = standing[1] seed += 1 elif player_selection_mode == "topnrating": players_by_rating = sorted(players, key=lambda x: x.rating, reverse=True) seed = 1 for p in players_by_rating[0:num_players]: settings["seed%d" % seed] = p.name seed += 1 elif player_selection_mode == "random": random_player_order = players[:] random.shuffle(random_player_order) seed = 1 for p in random_player_order[0:num_players]: settings["seed%d" % seed] = p.name seed += 1 all_seeds_set = True found_dupes = False seed_players = [None for i in range(num_players)] elements.append( htmlform.HTMLFragment( "<p>Use the following %d players in the knockout series...</p>" % num_players)) # Make N drop-down boxes, each containing the N players for seed_index in range(1, num_players + 1): keyname = "seed%d" % seed_index current_player_name = settings.get(keyname) if current_player_name: for p in players: if current_player_name == p.get_name(): break else: # Don't recognise this player current_player_name = None else: current_player_name = None if not current_player_name: all_seeds_set = False options = [] options.append( htmlform.HTMLFormDropDownOption("", "--- select player ---", current_player_name is None)) for standing in standings: player = tourney.get_player_from_name(standing[1]) player_string = "%d. %s (%d wins, %d draws, %d points)" % ( standing[0], player.get_name(), standing[3], standing[5], standing[4]) options.append( htmlform.HTMLFormDropDownOption( player.get_name(), player_string, (player.get_name() == current_player_name))) if player.get_name() == current_player_name: if player in seed_players: # player is already in seed_players, so we have # a duplicate found_dupes = True all_seeds_set = False seed_players[seed_index - 1] = player elements.append(htmlform.HTMLFragment("#%d " % seed_index)) elements.append( htmlform.HTMLFormDropDownBox("seed%d" % seed_index, options)) elements.append(htmlform.HTMLFragment("<br />")) if found_dupes: elements.append( htmlform.HTMLFragment( "<p><strong>Warning</strong>: one or more players appears more than once above. You need to fix this before generating fixtures.</p>" )) # Are any of the seeds involved in a tie? if player_selection_mode == "topntable": ties_mentioned = [] for p in seed_players: if p: player_standing = None for s in standings: if s[1] == p.name: player_standing = s break for s in standings: if (s[3] * 2 + s[5] == player_standing[3] * 2 + player_standing[5] and s[4] == player_standing[4] and s[1] != player_standing[1] and (s[1], player_standing[1]) not in ties_mentioned and (player_standing[1], s[1]) not in ties_mentioned): elements.append( htmlform.HTMLFragment( "<p><strong>Warning:</strong> %s and %s have the same number of wins and points and have been ordered arbitrarily.</p>" % (player_standing[1], s[1]))) ties_mentioned.append((s[1], player_standing[1])) elements.append(htmlform.HTMLFormSubmitButton("setseeds", "Save Order")) if all_seeds_set: # All seed positions have a player in them and no player appears # more than once. # Work out fixtures and display them. (rounds, fixtures) = generate_knockout(tourney, seed_players) if settings.get("generate"): # The fixtures have already been okayed, so nothing more to do return None html = "<h2>Fixture list</h2>\n" html += "<p>The following rounds will be generated</p>\n" html += "<blockquote>\n" for r in rounds: html += "<li>%s</li>\n" % cgicommon.escape(r["name"]) html += "</blockquote>\n" elements.append(htmlform.HTMLFragment(html)) html = "<p>The following fixtures will be generated</p>\n" prev_round_no = None html += "<table>" for g in fixtures: if g.round_no != prev_round_no: round_name = None for r in rounds: if r["round"] == g.round_no: round_name = r["name"] break if not round_name: round_name = "Round %d" % g.round_no html += "<tr><th colspan=\"4\">%s</td></tr>\n" % round_name prev_round_no = g.round_no html += "<tr>" html += "<td>%d</td>" % g.seq html += "<td>%s</td>" % str(g.p1) html += "<td>v</td>" html += "<td>%s</td>" % str(g.p2) html += "</tr>" html += "</table>" elements.append(htmlform.HTMLFragment(html)) elements.append( htmlform.HTMLFragment( "<p>Click the button below to proceed. On the next screen you can review the fixtures and accept them.</p>" )) elements.append( htmlform.HTMLFormSubmitButton("generate", "Yep, looks good to me")) else: if "generate" in settings: del settings["generate"] return htmlform.HTMLForm( "POST", "/cgi-bin/fixturegen.py?tourney=%s" % urllib.parse.quote_plus(tourney.name), elements)
elements.append(htmlform.HTMLFragment("<table class=\"fixdivselector\">")) elements.append( htmlform.HTMLFragment( "<tr><th>Division</th><th>Round number</th></tr>")) for div in range(num_divisions): elements.append(htmlform.HTMLFragment("<tr><td>")) elements.append( htmlform.HTMLFormCheckBox("_div%d" % (div), tourney.get_division_name(div), True)) next_free_round_number = tourney.get_next_free_round_number_for_division( div) elements.append(htmlform.HTMLFragment("</td><td>")) elements.append( htmlform.HTMLFormTextInput("", "_div%dround" % (div), str(next_free_round_number), other_attrs={"class": "fixdivroundsel"})) elements.append(htmlform.HTMLFragment("</td></tr>")) elements.append(htmlform.HTMLFragment("</table>")) elements.append( htmlform.HTMLFormSubmitButton("_divsubmit", "Next", other_attrs={"class": "bigbutton"})) settings_form = htmlform.HTMLForm( "POST", "/cgi-bin/fixturegen.py?tourney=%s&generator=%s" % (urllib.parse.quote_plus(tourney.get_name()), urllib.parse.quote_plus(generator_name)), elements) cgicommon.writeln(settings_form.html()) # If the user has selected which divisions they want to generate fixtures for,