Created
January 26, 2016 08:42
-
-
Save Rapptz/a2074e62ef53d6672373 to your computer and use it in GitHub Desktop.
The random map generator for Splatoon tournaments.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment