from __future__ import division import random from collections import Counter, namedtuple import bisect import math # for my own purposes. debug = False def weighted_choice(choices): values, weights = zip(*choices) total = 0 cum_weights = [] for w in weights: total += w cum_weights.append(total) x = random.random() * total i = bisect.bisect(cum_weights, x) return values[i] def debug_print(*args, **kwargs): if debug: print(*args, **kwargs) maps = [ 'Urchin Underpass', 'Walleye Warehouse', 'Saltspray Rig', 'Blackbelly Skatepark', 'Arowana Mall', 'Port Mackerel', 'Kelp Dome', 'Bluefin Depot', 'Moray Towers', 'Camp Triggerfish', 'Flounder Heights', 'Hammerhead Bridge', "Museum d'Alfonsino", 'Mahi-Mahi Resort', 'Piranha Pit', 'Ancho-V Games' ] # probability for each is the second parameter # e.g. 40 -> 40% # modes = [('Splat Zones', 45), ('Rainmaker', 10), ('Tower Control', 45)] modes = ['Splat Zones', 'Rainmaker', 'Tower Control'] Game = namedtuple('Game', ['map', 'mode']) # the total number of rounds being played -- including Losers for DE and GF2 rounds = 7 # the number of games per round games_per_round = 9 # the total number of times a game/mode combo can be played overall in the tournament overall_count = math.ceil(math.log(rounds, 2)) # number of times RM can show up maximum_rainmaker = (games_per_round / 2) - 1 # for output purposes, note if it's a double elimination tournament. is_double_elimination = False assert games_per_round % 2 != 0 # the last game in a decisive victory set, e.g. Bo5 -> 3, Bo7 -> 4 best_of_game = math.ceil(games_per_round / 2.0) def can_be_added_to_pool(game, overall_pool, pool, previous_pool): if overall_pool.count(game) == overall_count: debug_print('overall_pool -> False') return False counter = Counter((entry.mode for entry in pool)) has_one_of_each_mode = all(counter[mode] for mode in modes) if len(pool) < best_of_game: if not has_one_of_each_mode and counter[game.mode] > 0: debug_print('every best_of_game needs a unique mode') return False if counter[game.mode] > (maximum_rainmaker - 1) and game.mode == 'Rainmaker': debug_print('too much rainmaker') return False # map/mode from previous round cannot be played if game in previous_pool: debug_print('duplicate from other game') return False # the previous mode cannot be played if len(pool) and game.mode == pool[-1].mode: debug_print('previous mode') return False if counter[game.mode] == best_of_game - 1: debug_print('too much of this mode') return False for entry in pool: # no duplicate maps if entry.map == game.map: debug_print('duplicate map') return False # well considering we're back here, let's add it debug_print('perfectly fine') return True def pool_to_string(pool): result = [] for i, game in enumerate(pool): result.append('Game {0}: {1.mode} on {1.map}'.format(i + 1, game)) return '\n'.join(result) # so we say we need 5 random picks from the permutations above.. pool = [] previous_pool = [] overall_pool = [] for r in range(rounds): # make sure it's a unique choice while len(pool) < games_per_round: stage = random.choice(maps) # mode = weighted_choice(modes) mode = random.choice(modes) game = Game(map=stage, mode=mode) debug_print('Round {}: {} (pool: {})'.format(r + 1, game, pool)) if can_be_added_to_pool(game, overall_pool, pool, previous_pool): pool.append(game) overall_pool.append(game) # print('valid') output = pool_to_string(pool) if is_double_elimination: number = r + 1 # rounds is GF2, and rounds // 2 - 1 is GF1, etc. gf1 = (rounds // 2) - 1 losers_finals = rounds - 1 losers_semi = rounds - 2 winners_finals = gf1 - 1 winners_semis = gf1 - 2 if number < gf1: print("Winner's Round {} has:\n{}\n".format(number, output)) elif number == gf1: print("Winner's Grand Finals has:\n{}\n".format(output)) elif number == winners_finals: print("Winner's Finals has:\n{}\n".format(output)) elif number == winners_semis: print("Winner's Semifinals has:\n{}\n".format(output)) elif number == losers_finals: print("Loser's Finals has:\n{}\n".format(output)) elif number == losers_semi: print("Loser's Semifinals has:\n{}\n".format(output)) elif number == rounds: print("Winner's Grand Finals 2 has:\n{}\n".format(output)) else: print("Loser's Round {} has:\n{}\n".format(number - gf1, output)) else: print('Round {} has:\n{}\n'.format(r + 1, output)) previous_pool = list(pool) pool = [] print('Sanity Check:') overall = Counter(overall_pool) modes = Counter(map(lambda x: x[1], overall_pool)) maps = Counter(map(lambda x: x[0], overall_pool)) print(modes) print(maps)