diff --git a/.gitignore b/.gitignore index 9be45762..3ceda31f 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,7 @@ test.sh backend/dev_settings_sensitive.py infrastructure/worker/app/gcloud-key.json + +# Resumes +backend/resumes/files/* +backend/resumes/*.csv diff --git a/RELEASE_JAVA.md b/RELEASE_JAVA.md new file mode 100644 index 00000000..210a25b1 --- /dev/null +++ b/RELEASE_JAVA.md @@ -0,0 +1,43 @@ +# HOW TO RELEASE A JAVA GAME + +## Prereqs + +a bash-like shell (windows command prompt won't work for this). Also, zsh (and perhaps other shell environments) don't work to run the frontend deploy script. bash seems to work. + +npm + +a git key: Obtain a git key that has "publish packages" permissions. This key is a string. Keep it around somewhere + +## Get updates + +Make sure you have all the most recent updates to the repo! (Ideally they're pushed to git. Then do git checkout master, git fetch, git pull.) + +## Update some version numbers + +`client/visualizer/src/config` -- find ``gameVersion`, and update that. + +`gradle.properties` -- update `release_version`. + +Make sure these updates are pushed to master! + +## Update specs and javadoc + +In our game spec (specs folder), make sure changes are up to date. + +Pay attention to the version number at the top of specs.md.html, and to the changelog at the bottom. + +push to master btw! + +## Release packages + +Set BC21_GITUSERNAME: `export BC21_GITUSERNAME=n8kim1`, etc + +Set BC21_GITKEY similarly. This git key is the string discussed above. + +./gradlew publish + +Now set version.txt in gcloud (also set cache policy to no-store) + +## Deploy frontend + +Run the deploy.sh script! For arguments, follow the instructions in the file. For example: `bash ./deploy.sh deploy 2021.3.0.2` diff --git a/RELEASE.md b/RELEASE_PYTHON.md similarity index 82% rename from RELEASE.md rename to RELEASE_PYTHON.md index dd01013a..697f3de3 100644 --- a/RELEASE.md +++ b/RELEASE_PYTHON.md @@ -1,4 +1,6 @@ -# HOW TO RELEASE +# HOW TO RELEASE A PYTHON GAME + +In general, this guide and script may be out of date. Make any changes as necessary. ### Preliminaries - Install the frontend using `npm install`. diff --git a/backend/README.md b/backend/README.md index 92b2eb83..2eddaa7a 100644 --- a/backend/README.md +++ b/backend/README.md @@ -2,6 +2,8 @@ Written in Django Rest Framework. Based on `battlecode19/api`. +NOTE: If you are ever working with teams' eligility (for example, to pull teams for the newbie tournament), note that the columns in the database are poorly named. Please see backend/docs/ELIGIBILITY.md before you do anything! + ## Local Development The best way to run the backend locally is to run `docker-compose up --build backend` from the repo's root directory. diff --git a/backend/api/models.py b/backend/api/models.py index a6f02524..93be9524 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -117,6 +117,8 @@ class Team(models.Model): draws = models.IntegerField(default=0) #eligibility + # NOTE -- these columns are unfortunately poorly named. + # If you want to work with them, see backend/docs/ELIGIBILITY.md! student = models.BooleanField(default=False) mit = models.BooleanField(default=False) high_school = models.BooleanField(default=False) diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 5dffbee4..34d9df8f 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -95,6 +95,7 @@ def update(self, instance, validated_data): """ Update and return an existing user object, given the validated data. """ + instance.email = validated_data.get('email', instance.email) instance.first_name = validated_data.get('first_name', instance.first_name) instance.last_name = validated_data.get('last_name', instance.last_name) instance.date_of_birth = validated_data.get('date_of_birth', instance.date_of_birth) diff --git a/backend/api/views.py b/backend/api/views.py index 08d7d62f..ac4e9021 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -118,18 +118,18 @@ def create_scrimmage_helper(red_team_id, blue_team_id, ranked, requested_by, is_ # If applicable, immediately accept scrimmage, rather than wait for the other team to accept. if accept: - result = accept_scrimmage_helper(scrimmage.id) + result = queue_scrimmage_helper(scrimmage.id) else: scrimmage.status = 'pending' scrimmage.save() result = Response({'message': scrimmage.id}, status.HTTP_200_OK) return result -def accept_scrimmage_helper(scrimmage_id): +def queue_scrimmage_helper(scrimmage_id): scrimmage = Scrimmage.objects.get(pk=scrimmage_id) # put onto pubsub # TODO if called through create_scrimmage_helper, then a lot of these queries are performed twice in succession, once in each method. Could use optimization. - # for example, pass data from create_scrimmage_helper into accept_scrimmage_helper, as an argument, and get your values from there. + # for example, pass data from create_scrimmage_helper into queue_scrimmage_helper, as an argument, and get your values from there. red_team_id = scrimmage.red_team.id blue_team_id = scrimmage.blue_team.id red_submission_id = scrimmage.red_submission_id @@ -171,7 +171,7 @@ def scrimmage_pub_sub_call(red_submission_id, blue_submission_id, red_team_name, 'name2': str(blue_team_name), 'maps': str(map_ids), 'replay': scrimmage_replay, - 'tourmode': str(tourmode) + 'tourmode': tourmode } data_bytestring = json.dumps(scrimmage_server_data).encode('utf-8') # In testing, it's helpful to comment out the actual pubsub call, and print what would be added instead, so you can see it. @@ -271,8 +271,11 @@ class UserViewSet(viewsets.GenericViewSet, serializer_class = FullUserSerializer permission_classes = (IsAuthenticatedAsRequestedUser,) - @action(detail=True, methods=['get']) + @action(detail=True, methods=['post']) def resume_upload(self, request, pk=None): + # Note that post requests always include Origin headers + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin + # https://stackoverflow.com/questions/42239643/when-do-browsers-send-the-origin-header-when-do-browsers-set-the-origin-to-null origin = request.headers['Origin'] upload_url = GCloudUploadDownload.signed_upload_url(RESUME_FILENAME(pk), GCLOUD_RES_BUCKET, origin) user = self.queryset.get(pk=pk) @@ -1006,7 +1009,7 @@ def accept(self, request, league_id, team, pk=None): if scrimmage.status != 'pending': return Response({'message': 'Scrimmage is not pending.'}, status.HTTP_400_BAD_REQUEST) - result = accept_scrimmage_helper(scrimmage.id) + result = queue_scrimmage_helper(scrimmage.id) return result except Scrimmage.DoesNotExist: return Response({'message': 'Scrimmage does not exist.'}, status.HTTP_404_NOT_FOUND) @@ -1045,6 +1048,23 @@ def cancel(self, request, league_id, team, pk=None): except Scrimmage.DoesNotExist: return Response({'message': 'Scrimmage does not exist.'}, status.HTTP_404_NOT_FOUND) + @action(methods=['post'], detail=True) + def requeue(self, request, league_id, team, pk=None): + is_admin = User.objects.all().get(username=request.user).is_superuser + if is_admin: + try: + scrimmage = Scrimmage.objects.all().get(pk=pk) + except: + return Response({'message': 'Scrimmage does not exist.'}, status.HTTP_404_NOT_FOUND) + + if scrimmage.status in ('redwon', 'bluewon'): + return Response({'message': 'Success response already received for this scrimmage'}, status.HTTP_400_BAD_REQUEST) + + response = queue_scrimmage_helper(scrimmage.id) + return response + else: + return Response({'message': 'make this request from server account'}, status.HTTP_401_UNAUTHORIZED) + @action(methods=['patch'], detail=True) def set_outcome(self, request, league_id, team, pk=None): is_admin = User.objects.all().get(username=request.user).is_superuser diff --git a/backend/docs/ELIGIBILITY.md b/backend/docs/ELIGIBILITY.md new file mode 100644 index 00000000..bbbe56a0 --- /dev/null +++ b/backend/docs/ELIGIBILITY.md @@ -0,0 +1,13 @@ +# Database and Eligibility columns + +In our team table of the database are four columns: `high_school`, `international`, `mit`, and `student`. Unfortunately, these names don't actually mean what they may seem at first glance... + +`high_school=True` means that the team is all high school students. (This should be a strict subset of `student` -- that is, `student` can not be false while `high_school` is true, unless someone filled something out wrong.) + +`international=True` means that the team is **not [all (US students)]**. (i.e. at least one non-student and/or one int'l person.) The value of `international` is the boolean opposite of the "US students" checkbox in the frontend. **A team participates in the Intl Tournament if and only if `international=True` and `student=True`.** + +`mit=True` means that the team is all **newbies**. + +`student=True` means that the team is all full-time students. + +(Changing the column names requires either server downtime or messy workarounds. Perhaps when the competition isn't active, it'd be a good idea to rethink what information we hold about a team, or what the columns are named.) \ No newline at end of file diff --git a/backend/resumes/download.py b/backend/resumes/download.py new file mode 100644 index 00000000..1aabc53a --- /dev/null +++ b/backend/resumes/download.py @@ -0,0 +1,125 @@ +import os, csv +from google.oauth2 import service_account +from google.cloud import storage +FILE_PATH = os.path.dirname(__file__) +# cd up, to be able to import GOOGLE_APPLICATION_CREDENTIALS +# (we still preserve the original path to use later) +os.sys.path.append(os.path.join(FILE_PATH, '..')) +from dev_settings_sensitive import GOOGLE_APPLICATION_CREDENTIALS + +# constants, please configure +# NOTE - Make sure to update GCLOUD_BUCKET_RESUMES! +GCLOUD_BUCKET_RESUMES = 'bc21-resumes' +USERS_ALL_PATH = os.path.join(FILE_PATH, 'users_all.csv') +USERS_TEAMS_PATH = os.path.join(FILE_PATH, 'users_teams.csv') +NUM_RETRIES = 5 + +# load up the sql query results, as list of dictionaries +users_all = [] +users_all_header = [] +with open(USERS_ALL_PATH, 'r') as csvfile: + reader = csv.reader(csvfile) + users_all_header = next(reader) + for row in reader: + row_dict = dict() + for i in range (0, len(row)): + key = users_all_header[i] + row_dict[key] = row[i] + users_all.append(row_dict) + +users_teams = [] +users_teams_header = [] +with open(USERS_TEAMS_PATH, 'r') as csvfile: + reader = csv.reader(csvfile) + users_teams_header = next(reader) + for row in reader: + row_dict = dict() + for i in range (0, len(row)): + key = users_teams_header[i] + if key in ["high_school", "international", "student"]: + # we want booleans for these + row_dict[key] = bool(row[i]) + else: + row_dict[key] = row[i] + users_teams.append(row_dict) + +# initialize google bucket things +with open('gcloud-key.json', 'w') as outfile: + outfile.write(GOOGLE_APPLICATION_CREDENTIALS) + outfile.close() +os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = str(os.path.join(FILE_PATH, 'gcloud-key.json')) +client = storage.client.Client() +os.remove('gcloud-key.json') # important!!! +bucket = client.get_bucket(GCLOUD_BUCKET_RESUMES) + +# initialize file paths for downloads +def safe_makedirs(directory): + if not os.path.exists(directory): + os.makedirs(directory) +files_dir = os.path.join(FILE_PATH, 'files') +safe_makedirs(files_dir) +hs_us_dir = os.path.join(files_dir, 'hs-us') +safe_makedirs(hs_us_dir) +hs_intl_dir = os.path.join(files_dir, 'hs-intl') +safe_makedirs(hs_intl_dir) +col_us_dir = os.path.join(files_dir, 'col-us') +safe_makedirs(col_us_dir) +col_intl_dir = os.path.join(files_dir, 'col-intl') +safe_makedirs(col_intl_dir) +other_dir = os.path.join(files_dir, 'other') +safe_makedirs(other_dir) +os.chmod(files_dir, 0o777) + +# download helper! +def download(user_id, file_name, bucket, files_dir): + for i in range (NUM_RETRIES): + try: + blob = bucket.get_blob(os.path.join(str(user_id), 'resume.pdf')) + with open(os.path.join(files_dir, file_name), 'wb+') as file_obj: + blob.download_to_file(file_obj) + file_obj.close() + break + except PermissionError: + print("Could not obtain permissions to save; try running as sudo") + except Exception as e: + print("Could not retrieve source file from bucket, user id", user_id) + print("Exception:", e) + +# actually download resumes, first from users_teams +def download_user(user, bucket, files_dir): + if 'student' in user: # user comes from users_teams + if user['student']: + if user['high_school']: + if user['international']: + subfolder = 'hs-intl' + else: #domestic + subfolder = 'hs-us' + else: # college + if user['international']: + subfolder = 'col-intl' + else: + subfolder = 'col-us' + else: + subfolder = 'other' + else: # user comes from users_all + subfolder = 'other' + user_id = user["id"] + # file name: "0ELO-FirstLast" (elo left padded, min 0) + if "student" in user: + elo_int = int(float(user['score'])) + elo_str_padded = str(max(0, elo_int)).zfill(4) + short_file_name = elo_str_padded + "-" + user["first_name"] + user["last_name"] + else: + short_file_name = user["first_name"] + user["last_name"] + full_file_name = subfolder + '/' + short_file_name +'.pdf' + + download(user_id, full_file_name, bucket, files_dir) + +ids_users_downloaded = set() +for user in users_teams: + download_user(user, bucket, files_dir) + ids_users_downloaded.add(user["id"]) +for user in users_all: + if user["id"] not in ids_users_downloaded: + download_user(user, bucket, files_dir) + ids_users_downloaded.add(user["id"]) diff --git a/backend/resumes/notes.txt b/backend/resumes/notes.txt new file mode 100644 index 00000000..efd5f4cb --- /dev/null +++ b/backend/resumes/notes.txt @@ -0,0 +1,13 @@ +First, we need info about all of the users. See `sql.txt` for two scripts, that produce two files (users_all.csv, users_teams.csv). Run the scripts and save the csvs, according to the instructions in sql.txt. + +Next, run the `download.py` script. **Make sure to update GCLOUD_BUCKET_RESUMES!** +For posterity, here's an outline of what it does: +pull all resumes (for all verified ones), preserve user ids +for each group of users (hs us, hs intl, college us, college intl, others that aren't devs): + in ascending scrim rank, find associated resume + rename to "#elo FirstLastResume" +Also for users in users_all not in users_teams find resume as "FirstLastResume" + +Then, the resumes are in the `files` folder! Make sure to go through all of them, to remove any pdfs that seem corrupt, throwaway resumes (eg blank files), etc. + +Then publish them (we usually just upload all of them to a gdrive folder) and share them with sponsors! diff --git a/backend/resumes/sql.txt b/backend/resumes/sql.txt new file mode 100644 index 00000000..db661eee --- /dev/null +++ b/backend/resumes/sql.txt @@ -0,0 +1,20 @@ +Downloading all users with resumes: run the following query. Export (including headers) to a csv and save as `users_all.csv` in this directory. (This export can be done through GUIs, or through the `COPY` or `\copy` commands, here: https://dataschool.com/learn-sql/export-to-csv-from-psql/) + +``` +SELECT api_user.id, api_user.first_name, api_user.last_name FROM api_user +WHERE api_user.verified=True +ORDER BY api_user.id +``` + +----- + +Downloading all competitive resume users _on teams_ and team info: run the following query. Export (including headers) to a csv, save as `users_teams.csv` in this directory. + +``` +SELECT api_user.id, api_user.first_name, api_user.last_name, api_team_users.team_id, api_team.score, api_team.high_school, api_team.international, api_team.student +FROM api_user +LEFT JOIN api_team_users on api_user.id=api_team_users.user_id +LEFT JOIN api_team on api_team_users.team_id=api_team.id +WHERE api_user.verified=True and api_team.staff_team=False +ORDER BY api_user.id +``` diff --git a/backend/settings.py b/backend/settings.py index dc3ba92e..da924dca 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -43,6 +43,7 @@ "Branches", "Chevron", "Corridor", + "Cow", "CrossStitch", "CrownJewels", "ExesAndOhs", @@ -52,7 +53,63 @@ "NotAPuzzle", "Rainbow", "SlowMusic", - "Snowflake" + "Snowflake", + "BadSnowflake", + "CringyAsF", + "FindYourWay", + "GetShrekt", + "Goldfish", + "HexesAndOhms", + "Licc", + "MainCampus", + "Punctuation", + "Radial", + "SeaFloor", + "Sediment", + "Smile", + "SpaceInvaders", + "Surprised", + "VideoGames", + "AmidstWe", + "BattleCode", + "BattleCodeToo", + "BlobWithLegs", + "ButtonsAndBows", + "CowTwister", + "Extensions", + "Hourglass", + "Maze", + "NextHouse", + "Superposition", + "TicTacTie", + "UnbrandedWordGame", + "Z", + "Zodiac", + "Flawars", + "FrogOrBath", + "HappyBoba", + "Networking", + "NoInternet", + "PaperWindmill", + "Randomized", + "Star", + "Tiger", + "WhatISeeInMyDreams", + "Yoda", + "Blotches", + "CToE", + "Circles", + "EggCarton", + "InaccurateBritishFlag", + "JerryIsEvil", + "Legends", + "Mario", + "Misdirection", + "OneCallAway", + "Saturn", + "Stonks", + "TheClientMapEditorIsSuperiorToGoogleSheetsEom", + "TheSnackThatSmilesBack", ] # this is the constant used in the ELO calculation diff --git a/backend/tournaments/README.md b/backend/tournaments/README.md new file mode 100644 index 00000000..7e5da704 --- /dev/null +++ b/backend/tournaments/README.md @@ -0,0 +1,39 @@ +# Tournament Presentation + +Basically, everything that isn't directly running the matches themselves. (setup before matches; Challonge input after matches) + +## Beforehand + +TODO: +freeze submissions (allow for grace period) +Allow teams to submit extra submissions, through Discord +To handle these: (insert what Quinn and I just did -- notes in Slack) + +Once all these submissions are processed: +Run some SQL, etc. (find notes in slack, and in tournament.sql) + +## Match Running + +Infrastructure likely knows how to do this! + +## Afterward + +### Installation + +Install pychal, link here -- https://pypi.org/project/pychal/. Also, grab the Challonge API key, link here: https://challonge.com/settings/developer. Set this as `CHALLONGE_API_KEY` in `dev_settings_sensitive.py`. + +On Challonge website, create a new tour. Pay special attention to the "tournament format" section; make sure that is as it should be. Also, to add participants, click on "add participants in bulk". + +Parse results: can do this manually. if you want to do programatically -- +In the initial json, would be nice to have round # (altho i think this can be caluclated), scrimmage id (can be worked around), and score (can be worked around too). + +Get all matches, in json -- they're be in some order. maybe have extra matches too. +Get all matches, from challonge -- this is good canonical order. + +For a match number in challonge: + Use API to query: (this match number wil be 'suggested_play_order') + Turn challonge player_id's into battlecode player IDs + Get any matches involving those two teams, in that order OR create a way of matching challonge rounds to JSON rounds + +TBH we should just run a tour w challonge integration already built in, instead. + diff --git a/backend/tournaments/dev_settings_sensitive.py b/backend/tournaments/dev_settings_sensitive.py new file mode 120000 index 00000000..b227cdc1 --- /dev/null +++ b/backend/tournaments/dev_settings_sensitive.py @@ -0,0 +1 @@ +../dev_settings_sensitive.py \ No newline at end of file diff --git a/backend/tournament.sql b/backend/tournaments/tournament.sql similarity index 100% rename from backend/tournament.sql rename to backend/tournaments/tournament.sql diff --git a/backend/tournaments/update_challonge.py b/backend/tournaments/update_challonge.py new file mode 100644 index 00000000..6418c17b --- /dev/null +++ b/backend/tournaments/update_challonge.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +""" +This script updates challonge. +Run `./updatechallonge.py --help` for more info. +THIS SCRIPT ASSUMES THAT THERE ARE NEVER TWO TEAMS THAT ARE MATCHED TO EACH OTHER TWICE. This is +a faulty assumption. Use this script as inspiration for the future, but don't actually run it. +""" + +import challonge +import time +import click +import dev_settings_sensitive + +@click.group() +def cli(): + pass + + +GAMES_PER_MATCH = 5 + +#Configure your settings +challonge.set_credentials("mitbattlecode", dev_settings_sensitive.CHALLONGE_API_KEY) + + +@cli.command() +def list_tournaments(): + # uncommment this to get a new value for s + tournaments = challonge.tournaments.index() + for tournament in tournaments: + print(tournament['name'], tournament['id']) + + + + +def load_tournament(s, tournament_str): + + inp = tournament_str + "replaysraw.txt" + + dd = {} + + ddw = {} + + checkforwinner = {} + + boN = GAMES_PER_MATCH + + # go through every line and parse it and download the replays + # name the replays something informative + redwins = 0 + counter = 0 + with open(inp, 'r') as f: + for l in f.readlines(): + if counter % boN == 0: + redwins = 0 + # create a filename + counter += 1 + teams = l.split('|')[0].strip() + mp = l.split('|')[1].strip().split(' ')[0].strip() + idd = l.split('|')[1].strip().split(' ')[-1].strip() + downloadlink = "https://2020.battlecode.org/replays/" + idd + ".bc20" + fn = "sprint/" + "{:03d}".format(counter) + "______" + teams + "____" + mp + "____" + idd + ".bc20" + displink = "https://2020.battlecode.org/visualizer.html?" + downloadlink + ts = [x.strip() for x in teams.split('-vs-')] + tt = "___".join(sorted([x.strip() for x in teams.split('-vs-')])) + if tt not in dd: + dd[tt] = [displink] + else: + dd[tt].append(displink) + + if counter % boN == 0 or (counter % boN) % 2 != 0: + if 'redwon' in l: + redwins += 1 + else: + if 'redwon' not in l: + redwins += 1 + + if counter % boN == 0 and redwins >= boN//2+1: + ddw[tt] = ts[0] + else: + ddw[tt] = ts[1] + + return dd, ddw + + + + + +def lookupname(s, ps): + return challonge.participants.show(str(s), ps)['name'] + +def findreplays(dd, t1, t2): + tt = "___".join(sorted([t1.strip(), t2.strip()])) + return dd[tt] + + + + +@cli.command() +@click.argument("tournament_id") +@click.argument("tournament_str") +def add_replays(tournament_id, tournament_str): + dd, ddw = load_tournament(tournament_id, tournament_str) + s = tournament_id + r = challonge.matches.index(str(s)) + while True: + r = challonge.matches.index(str(s)) + for d in r: + if (d['state'] =='complete' and d['attachment_count'] is None): + r = findreplays(dd, lookupname(s, d['player1_id']),lookupname(s, d['player2_id'])) + for rr in r: + myparams = {'url': rr} + challonge.match_attachments.create(str(s), d['id'], params=myparams) + print('updated ') + print(d) + time.sleep(10) + +@cli.command() +@click.argument("tournament_id") +@click.argument("tournament_str") +@click.option("--game-limit", help="The index of the first game not to release scores for.") +def update_results(tournament_id, tournament_str, game_limit): + dd, ddw = load_tournament(tournament_id, tournament_str) + s = tournament_id + r = challonge.matches.index(str(s)) + for d in r: + if (d['state'] == 'open' and d['suggested_play_order'] < game_limit): + ts = [lookupname(s, d['player1_id']), lookupname(s, d['player2_id'])] + tt = "___".join(sorted([ts[0].strip(), ts[1].strip()])) + winner_id = ts.index(ddw[tt]) + ss = 'player' + str(winner_id+1) + '_id' + fds = '1-0' + if winner_id == 1: + fds = '0-1' + challonge.matches.update(str(s), d['id'], params={'winner_id': d[ss], 'scores_csv': fds}) + print(d) + + +if __name__ == '__main__': + cli() \ No newline at end of file diff --git a/client/playback/src/gameworld.ts b/client/playback/src/gameworld.ts index 2e106d5a..84e63950 100644 --- a/client/playback/src/gameworld.ts +++ b/client/playback/src/gameworld.ts @@ -34,7 +34,8 @@ export type BodiesSchema = { bytecodesUsed: Int32Array, // TODO: is this needed? ability: Int8Array, bid: Int32Array, - parent: Int32Array + parent: Int32Array, + income: Int32Array }; // NOTE: consider changing MapStats to schema to use SOA for better performance, if it has large data @@ -58,7 +59,10 @@ export type TeamStats = { votes: number, influence: [number, number, number, number, number], conviction: [number, number, number, number, number], - numBuffs: number + numBuffs: number, + bidderID: number, + bid: number, + income: number }; export type IndicatorDotsSchema = { @@ -219,7 +223,8 @@ export default class GameWorld { bytecodesUsed: new Int32Array(0), ability: new Int8Array(0), bid: new Int32Array(0), - parent: new Int32Array(0) + parent: new Int32Array(0), + income: new Int32Array(0) }, 'id'); @@ -238,7 +243,10 @@ export default class GameWorld { votes: 0, influence: [0, 0, 0, 0, 0], conviction: [0, 0, 0, 0, 0], - numBuffs: 0 + numBuffs: 0, + bidderID: -1, + bid: 0, + income: 0 }); } @@ -369,11 +377,13 @@ export default class GameWorld { // Process team info changes for (var i = 0; i < delta.teamIDsLength(); i++) { - var teamID = delta.teamIDs(i); - var statObj = this.teamStats.get(teamID); + let teamID = delta.teamIDs(i); + let statObj = this.teamStats.get(teamID); statObj.votes += delta.teamVotes(i); statObj.numBuffs = delta.teamNumBuffs(i); + statObj.bidderID = delta.teamBidderIDs(i); + statObj.bid = 0; this.teamStats.set(teamID, statObj); } @@ -411,6 +421,8 @@ export default class GameWorld { const action = delta.actions(i); const robotID = delta.actionIDs(i); const target = delta.actionTargets(i); + const body = this.bodies.lookup(robotID); + const teamStatsObj: TeamStats = this.teamStats.get(body.team); switch (action) { // TODO: validate actions? // Actions list from battlecode.fbs enum Action @@ -419,10 +431,7 @@ export default class GameWorld { /// Target: none case schema.Action.EMPOWER: //this.bodies.alter({ id: robotID, ability: 1}); - var x = this.bodies.lookup(robotID).x; - var y = this.bodies.lookup(robotID).y; - var team = this.bodies.lookup(robotID).team; - this.empowered.insert({'id': robotID, 'x': x, 'y': y, 'team': team, 'radius': target}); + this.empowered.insert({'id': robotID, 'x': body.x, 'y': body.y, 'team': body.team, 'radius': target}); this.abilityRobots.push(robotID); break; /// Slanderers passively generate influence for the @@ -435,25 +444,22 @@ export default class GameWorld { /// Slanderers turn into Politicians. /// Target: none case schema.Action.CAMOUFLAGE: - var team = this.bodies.lookup(robotID).team; - var type = this.bodies.lookup(robotID).type; - if (type !== schema.BodyType.SLANDERER) { + + if (body.type !== schema.BodyType.SLANDERER) { throw new Error("non-slanderer camouflaged"); } this.bodies.alter({ id: robotID, type: schema.BodyType.POLITICIAN}); - this.bodies.alter({ id: robotID, ability: (team == 1 ? 4 : 5)}); + this.bodies.alter({ id: robotID, ability: (body.team == 1 ? 4 : 5)}); this.abilityRobots.push(robotID); - this.teamStats.get(team).robots[schema.BodyType.SLANDERER]--; - this.teamStats.get(team).robots[schema.BodyType.POLITICIAN]++; + teamStatsObj.robots[schema.BodyType.SLANDERER]--; + teamStatsObj.robots[schema.BodyType.POLITICIAN]++; - var conviction = this.bodies.lookup(robotID).conviction; - this.teamStats.get(team).conviction[schema.BodyType.SLANDERER] -= conviction; - this.teamStats.get(team).conviction[schema.BodyType.POLITICIAN] += conviction; + teamStatsObj.conviction[schema.BodyType.SLANDERER] -= body.conviction; + teamStatsObj.conviction[schema.BodyType.POLITICIAN] += body.conviction; - var influence = this.bodies.lookup(robotID).influence; - this.teamStats.get(team).influence[schema.BodyType.SLANDERER] -= influence; - this.teamStats.get(team).influence[schema.BodyType.POLITICIAN] += influence; + teamStatsObj.influence[schema.BodyType.SLANDERER] -= body.influence; + teamStatsObj.influence[schema.BodyType.POLITICIAN] += body.influence; break; /// Muckrakers can expose a scandal. /// Target: an enemy body. @@ -476,6 +482,8 @@ export default class GameWorld { case schema.Action.PLACE_BID: this.bodies.alter({id: robotID, bid: target}); this.bidRobots.push(robotID); + + if (robotID === teamStatsObj.bidderID) teamStatsObj.bid = target; break; /// A robot can change team after being empowered /// Target: teamID @@ -485,28 +493,20 @@ export default class GameWorld { /// A robot's influence changes. /// Target: delta value case schema.Action.CHANGE_INFLUENCE: - var old_influence = this.bodies.lookup(robotID).influence; - var type = this.bodies.lookup(robotID).type; - this.bodies.alter({ id: robotID, influence: old_influence + target}); - - var team = this.bodies.lookup(robotID).team; - var statObj = this.teamStats.get(team); - if(!statObj) {continue;} // In case this is a neutral bot - statObj.influence[type] += target; - this.teamStats.set(team, statObj); + this.bodies.alter({ id: robotID, influence: body.influence + target}); + + if(!teamStatsObj) {continue;} // In case this is a neutral bot + teamStatsObj.influence[body.type] += target; + this.teamStats.set(body.team, teamStatsObj); break; /// A robot's conviction changes. /// Target: delta value, i.e. red 5 -> blue 3 is -2 case schema.Action.CHANGE_CONVICTION: - var old_conviction = this.bodies.lookup(robotID).conviction; - this.bodies.alter({ id: robotID, conviction: old_conviction + target}); - - var team = this.bodies.lookup(robotID).team; - var type = this.bodies.lookup(robotID).type; - var statObj = this.teamStats.get(team); - if(!statObj) {continue;} // In case this is a neutral bot - statObj.conviction[type] += target; - this.teamStats.set(team, statObj); + this.bodies.alter({ id: robotID, conviction: body.conviction + target}); + + if(!teamStatsObj) {continue;} // In case this is a neutral bot + teamStatsObj.conviction[body.type] += target; + this.teamStats.set(body.team, teamStatsObj); break; case schema.Action.DIE_EXCEPTION: @@ -520,6 +520,35 @@ export default class GameWorld { } } + for (let team in this.meta.teams) { + let teamID = this.meta.teams[team].teamID; + let teamStats = this.teamStats.get(teamID) as TeamStats; + teamStats.income = 0; + } + + // income + this.bodies.arrays.type.forEach((type, i) => { + let robotID = this.bodies.arrays.id[i]; + let team = this.bodies.arrays.team[i]; + let ability = this.bodies.arrays.ability[i]; + let influence = this.bodies.arrays.influence[i]; + let income = this.bodies.arrays.income[i]; + let parent = this.bodies.arrays.parent[i]; + var teamStatsObj = this.teamStats.get(team); + if (ability === 3) { + let delta = Math.floor((1/50 + 0.03 * Math.exp(-0.001 * influence)) * influence); + teamStatsObj.income += delta; + this.bodies.alter({id: parent, income: delta}); + } else if (type === schema.BodyType.ENLIGHTENMENT_CENTER && teamStatsObj) { + let delta = Math.ceil(0.2 * Math.sqrt(this.turn)); + teamStatsObj.income += delta; + this.bodies.alter({id: robotID, income: delta}); + } else if (income !== 0) { + this.bodies.alter({id: robotID, income: 0}); + } + this.teamStats.set(team, teamStatsObj); + }) + // Died bodies if (delta.diedIDsLength() > 0) { // Update team stats @@ -528,7 +557,7 @@ export default class GameWorld { let index = indices[i]; let team = this.bodies.arrays.team[index]; let type = this.bodies.arrays.type[index]; - var statObj = this.teamStats.get(team); + let statObj = this.teamStats.get(team); if(!statObj) {continue;} // In case this is a neutral bot statObj.robots[type] -= 1; let influence = this.bodies.arrays.influence[index]; diff --git a/client/visualizer/index.html b/client/visualizer/index.html index e00404c3..394f1370 100644 --- a/client/visualizer/index.html +++ b/client/visualizer/index.html @@ -18,13 +18,13 @@ match_url = ""; } } + + var config = {websocketURL: "ws://localhost:6175"}; + if (match_url) config.matchFileURL = match_url; + if (tournamentMode) config.tournamentMode = tournamentMode; window.battleClient = window.battlecode.mount( document.getElementById('client'), - { - websocketURL: "ws://localhost:6175", - matchFileURL: match_url, - tournamentMode: tournamentMode - } + config ); diff --git a/client/visualizer/package-lock.json b/client/visualizer/package-lock.json index c9ad8d9f..f5f89e84 100644 --- a/client/visualizer/package-lock.json +++ b/client/visualizer/package-lock.json @@ -88,6 +88,14 @@ "@types/tape": "*" } }, + "@types/chart.js": { + "version": "2.9.30", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.30.tgz", + "integrity": "sha512-EgjxUUZFvf6ls3kW2CwyrnSJhgyKxgwrlp/W5G9wqyPEO9iFatO63zAA7L24YqgMxiDjQ+tG7ODU+2yWH91lPg==", + "requires": { + "moment": "^2.10.2" + } + }, "@types/debug": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", @@ -2072,6 +2080,32 @@ } } }, + "chart.js": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^1.9.3" + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "requires": { + "color-name": "^1.0.0" + } + }, "chokidar": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", @@ -2199,7 +2233,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -2207,8 +2240,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colorette": { "version": "1.2.1", @@ -5035,6 +5067,11 @@ "minimist": "^1.2.5" } }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/client/visualizer/package.json b/client/visualizer/package.json index 737cbce7..401b8dbb 100644 --- a/client/visualizer/package.json +++ b/client/visualizer/package.json @@ -25,7 +25,9 @@ }, "homepage": "https://github.com/battlecode/battlecode21#readme", "dependencies": { + "@types/chart.js": "^2.9.30", "battlecode-playback": "file:../playback", + "chart.js": "^2.9.4", "speedscope": "1.5.2", "victor": "^1.1.0" }, diff --git a/client/visualizer/src/app.ts b/client/visualizer/src/app.ts index 329310ba..7e95fff3 100644 --- a/client/visualizer/src/app.ts +++ b/client/visualizer/src/app.ts @@ -81,7 +81,7 @@ export default class Client { this.root.appendChild(this.loadSidebar()); this.root.appendChild(this.loadGameArea()); this.loadScaffold(); - this.runner.ready(this.controls, this.stats, this.gamearea, this.console, this.matchqueue, this.profiler); + this.runner.ready(this.controls, this.stats, this.gamearea, this.console, this.matchqueue, this.sidebar, this.profiler); }); } diff --git a/client/visualizer/src/config.ts b/client/visualizer/src/config.ts index 4258322e..29c88572 100644 --- a/client/visualizer/src/config.ts +++ b/client/visualizer/src/config.ts @@ -119,6 +119,16 @@ export interface Config { * Whether to do profiling on profiled match files, assuming the profiler is loaded. */ doProfiling: boolean; + + /** + * Whether to rotate tall maps. + */ + doRotate: boolean; + + /** + * Whether the map is currently rotated. TODO: don't make this a global variable. + */ + doingRotate: boolean; } /** @@ -139,7 +149,7 @@ export enum Mode { */ export function defaults(supplied?: any): Config { let conf: Config = { - gameVersion: "2021.2.4.1", //TODO: Change this on each release! + gameVersion: "2021.3.0.5", //TODO: Change this on each release! fullscreen: false, width: 600, height: 600, @@ -162,7 +172,9 @@ export function defaults(supplied?: any): Config { shorterLogHeader: false, processLogs: true, useProfiler: true, - doProfiling: true + doProfiling: true, + doRotate: false, + doingRotate: false }; return Object.assign(conf, supplied); } diff --git a/client/visualizer/src/constants.ts b/client/visualizer/src/constants.ts index c5f0e521..90ce47f3 100644 --- a/client/visualizer/src/constants.ts +++ b/client/visualizer/src/constants.ts @@ -35,7 +35,7 @@ export const passiveInfluenceRate = (round: number): number => { } export const buffFactor = (numBuffs: number): number => { - return Math.pow(1.001, numBuffs); + return 1 + 0.001 * numBuffs; } export const ACTION_RADIUS_COLOR = "#46ff00"; @@ -73,13 +73,21 @@ export enum MapType { DEFAULT, SPRINT_1, SPRINT_2, - INTL_QUALIFYING, - US_QUALIFYING, - HS, - NEWBIE, + QUALIFYING, + HS_NEWBIE, FINAL, CUSTOM }; + +// Map types to filter in runner +export const mapTypes: MapType[] = [MapType.DEFAULT, + MapType.SPRINT_1, + MapType.SPRINT_2, + MapType.QUALIFYING, + MapType.HS_NEWBIE, + MapType.FINAL, + MapType.CUSTOM]; + export const SERVER_MAPS: Map = new Map([ ["maptestsmall", MapType.DEFAULT], ["circle", MapType.DEFAULT], @@ -100,7 +108,63 @@ export const SERVER_MAPS: Map = new Map([ ["NotAPuzzle", MapType.SPRINT_1], ["Rainbow", MapType.SPRINT_1], ["SlowMusic", MapType.SPRINT_1], - ["Snowflake", MapType.SPRINT_1] + ["Snowflake", MapType.SPRINT_1], + ["BadSnowflake", MapType.SPRINT_2], + ["CringyAsF", MapType.SPRINT_2], + ["FindYourWay", MapType.SPRINT_2], + ["GetShrekt", MapType.SPRINT_2], + ["Goldfish", MapType.SPRINT_2], + ["HexesAndOhms", MapType.SPRINT_2], + ["Licc", MapType.SPRINT_2], + ["MainCampus", MapType.SPRINT_2], + ["Punctuation", MapType.SPRINT_2], + ["Radial", MapType.SPRINT_2], + ["SeaFloor", MapType.SPRINT_2], + ["Sediment", MapType.SPRINT_2], + ["Smile", MapType.SPRINT_2], + ["SpaceInvaders", MapType.SPRINT_2], + ["Surprised", MapType.SPRINT_2], + ["VideoGames", MapType.SPRINT_2], + ["AmidstWe", MapType.QUALIFYING], + ["BattleCode", MapType.QUALIFYING], + ["BattleCodeToo", MapType.QUALIFYING], + ["BlobWithLegs", MapType.QUALIFYING], + ["ButtonsAndBows", MapType.QUALIFYING], + ["CowTwister", MapType.QUALIFYING], + ["Extensions", MapType.QUALIFYING], + ["Hourglass", MapType.QUALIFYING], + ["Maze", MapType.QUALIFYING], + ["NextHouse", MapType.QUALIFYING], + ["Superposition", MapType.QUALIFYING], + ["TicTacTie", MapType.QUALIFYING], + ["UnbrandedWordGame", MapType.QUALIFYING], + ["Z", MapType.QUALIFYING], + ["Zodiac", MapType.QUALIFYING], + ["Flawars", MapType.HS_NEWBIE], + ["FrogOrBath", MapType.HS_NEWBIE], + ["HappyBoba", MapType.HS_NEWBIE], + ["Networking", MapType.HS_NEWBIE], + ["NoInternet", MapType.HS_NEWBIE], + ["PaperWindmill", MapType.HS_NEWBIE], + ["Randomized", MapType.HS_NEWBIE], + ["Star", MapType.HS_NEWBIE], + ["Tiger", MapType.HS_NEWBIE], + ["WhatISeeInMyDreams", MapType.HS_NEWBIE], + ["Yoda", MapType.HS_NEWBIE], + ["Blotches", MapType.FINAL], + ["CToE", MapType.FINAL], + ["Circles", MapType.FINAL], + ["EggCarton", MapType.FINAL], + ["InaccurateBritishFlag", MapType.FINAL], + ["JerryIsEvil", MapType.FINAL], + ["Legends", MapType.FINAL], + ["Mario", MapType.FINAL], + ["Misdirection", MapType.FINAL], + ["OneCallAway", MapType.FINAL], + ["Saturn", MapType.FINAL], + ["Stonks", MapType.FINAL], + ["TheClientMapEditorIsSuperiorToGoogleSheetsEom", MapType.FINAL], + ["TheSnackThatSmilesBack", MapType.FINAL] ]); export function bodyTypeToString(bodyType: schema.BodyType) { diff --git a/client/visualizer/src/gamearea/gamearea.ts b/client/visualizer/src/gamearea/gamearea.ts index e9e7d600..2f4a149d 100644 --- a/client/visualizer/src/gamearea/gamearea.ts +++ b/client/visualizer/src/gamearea/gamearea.ts @@ -53,8 +53,14 @@ export default class GameArea { const width = world.minCorner.absDistanceX(world.maxCorner); const height = world.minCorner.absDistanceY(world.maxCorner); const scale = this.conf.upscale / Math.sqrt(width * height); - this.canvas.width = width * scale; - this.canvas.height = height * scale; + if (!this.conf.doingRotate) { + this.canvas.width = width * scale; + this.canvas.height = height * scale; + } + else { + this.canvas.width = height * scale; + this.canvas.height = width * scale; + } } /** @@ -63,15 +69,21 @@ export default class GameArea { loadSplashDiv() { let splashTitle = document.createElement("h1"); - splashTitle.id = "splashTitle"; - splashTitle.appendChild(document.createTextNode("Battlecode 2021 Client")); - this.splashDiv.appendChild(splashTitle); - let splashSubtitle = document.createElement("h3"); + splashTitle.id = "splashTitle"; splashSubtitle.id = "splashSubtitle"; - splashSubtitle.appendChild(document.createTextNode("v" + this.conf.gameVersion)); - this.splashDiv.appendChild(splashSubtitle); + + if (!this.conf.tournamentMode) { + splashTitle.appendChild(document.createTextNode("Battlecode 2021 Client")); + splashSubtitle.appendChild(document.createTextNode("v" + this.conf.gameVersion)); + } + else { + splashTitle.appendChild(document.createTextNode("Loading...")); + } + this.splashDiv.appendChild(splashTitle); + this.splashDiv.appendChild(splashSubtitle); + if (process.env.ELECTRON) { (async function (splashDiv, version) { diff --git a/client/visualizer/src/gamearea/renderer.ts b/client/visualizer/src/gamearea/renderer.ts index e2d717f1..75814f78 100644 --- a/client/visualizer/src/gamearea/renderer.ts +++ b/client/visualizer/src/gamearea/renderer.ts @@ -48,11 +48,12 @@ export default class Renderer { // setup correct rendering const viewWidth = viewMax.x - viewMin.x const viewHeight = viewMax.y - viewMin.y - const scale = this.canvas.width / viewWidth; + const scale = this.canvas.width / (!this.conf.doingRotate ? viewWidth : viewHeight); this.ctx.save(); this.ctx.scale(scale, scale); - this.ctx.translate(-viewMin.x, -viewMin.y); + if (!this.conf.doingRotate) this.ctx.translate(-viewMin.x, -viewMin.y); + else this.ctx.translate(-viewMin.y, -viewMin.x); this.renderBackground(world); @@ -77,17 +78,18 @@ export default class Renderer { this.ctx.fillStyle = "white"; this.ctx.globalAlpha = 1; - const minX = world.minCorner.x; - const minY = world.minCorner.y; - const width = world.maxCorner.x - world.minCorner.x; - const height = world.maxCorner.y - world.minCorner.y; + let minX = world.minCorner.x; + let minY = world.minCorner.y; + let width = world.maxCorner.x - world.minCorner.x; + let height = world.maxCorner.y - world.minCorner.y; const scale = 20; this.ctx.scale(1/scale, 1/scale); // scale the background pattern - this.ctx.fillRect(minX*scale, minY*scale, width*scale, height*scale); + if (!this.conf.doingRotate) this.ctx.fillRect(minX*scale, minY*scale, width*scale, height*scale); + else this.ctx.fillRect(minY*scale, minX*scale, height*scale, width*scale); const map = world.mapStats; @@ -102,13 +104,15 @@ export default class Renderer { // Fetch and draw tile image const swampLevel = cst.getLevel(map.passability[idxVal]); const tileImg = this.imgs.tiles[swampLevel]; - this.ctx.drawImage(tileImg, cx, cy, scale, scale); + if (!this.conf.doingRotate) this.ctx.drawImage(tileImg, cx, cy, scale, scale); + else this.ctx.drawImage(tileImg, cy, cx, scale, scale); // Draw grid if (this.conf.showGrid) { this.ctx.strokeStyle = 'gray'; this.ctx.globalAlpha = 1; - this.ctx.strokeRect(cx, cy, scale, scale); + if (!this.conf.doingRotate) this.ctx.strokeRect(cx, cy, scale, scale); + else this.ctx.strokeRect(cy, cx, scale, scale); } } @@ -116,9 +120,11 @@ export default class Renderer { if (this.hoverPos != null) { const {x, y} = this.hoverPos; const cx = (minX+x)*scale, cy = (minY+(height-y-1))*scale; - this.ctx.strokeStyle = 'red'; + this.ctx.strokeStyle = 'purple'; + this.ctx.lineWidth *= 2; this.ctx.globalAlpha = 1; - this.ctx.strokeRect(cx, cy, scale, scale); + if (!this.conf.doingRotate) this.ctx.strokeRect(cx, cy, scale, scale); + else this.ctx.strokeRect(cy, cx, scale, scale); } this.ctx.restore(); @@ -129,6 +135,7 @@ export default class Renderer { const length = bodies.length; const types = bodies.arrays.type; const teams = bodies.arrays.team; + const convictions = bodies.arrays.conviction; const ids = bodies.arrays.id; const xs = bodies.arrays.x; const ys = bodies.arrays.y; @@ -167,12 +174,12 @@ export default class Renderer { const effectImgs: HTMLImageElement[] = this.imgs.effects[effect]; const whichImg = (Math.floor(curTime / cst.EFFECT_STEP) % effectImgs.length); const effectImg = effectImgs[whichImg]; - this.drawBot(effectImg, x, y); + this.drawBot(effectImg, x, y, 0); } const renderBot = (i: number) => { const img: HTMLImageElement = this.imgs.robots[cst.bodyTypeToString(types[i])][teams[i]]; - this.drawBot(img, realXs[i], realYs[i]); + this.drawBot(img, realXs[i], realYs[i], convictions[i]); this.drawSightRadii(realXs[i], realYs[i], types[i], ids[i] === this.lastSelectedID); // draw effect @@ -221,6 +228,7 @@ export default class Renderer { * Draws a cirlce centered at (x,y) with given squared radius and color. */ private drawBotRadius(x: number, y: number, radiusSquared: number, color: string) { + if (this.conf.doingRotate) [x,y] = [y,x]; this.ctx.beginPath(); this.ctx.arc(x+0.5, y+0.5, Math.sqrt(radiusSquared), 0, 2 * Math.PI); this.ctx.strokeStyle = color; @@ -250,16 +258,23 @@ export default class Renderer { * Draws an image centered at (x, y) with the given radius */ private drawImage(img: HTMLImageElement, x: number, y: number, radius: number) { + if (this.conf.doingRotate) [x,y] = [y,x]; this.ctx.drawImage(img, x-radius, y-radius, radius*2, radius*2); } /** * Draws an image centered at (x, y), such that an image with default size covers a 1x1 cell */ - private drawBot(img: HTMLImageElement, x: number, y: number) { + private drawBot(img: HTMLImageElement, x: number, y: number, c: number) { + if (this.conf.doingRotate) [x,y] = [y,x]; let realWidth = img.naturalWidth/cst.IMAGE_SIZE; let realHeight = img.naturalHeight/cst.IMAGE_SIZE; - this.ctx.drawImage(img, x+(1-realWidth)/2, y+(1-realHeight)/2, realWidth, realHeight); + const sigmoid = (x) => { + return 1 / (1 + Math.exp(-x)) + } + //this.ctx.filter = `brightness(${sigmoid(c - 100) * 30 + 90}%)`; + let size = sigmoid(c / 100) * 1 + 0.3; + this.ctx.drawImage(img, x+(1-realWidth * size)/2, y+(1-realHeight * size)/2, realWidth * size, realHeight * size); } private setInfoStringEvent(world: GameWorld, @@ -327,10 +342,18 @@ export default class Renderer { const height = world.maxCorner.y - world.minCorner.y; const minY = world.minCorner.y; const maxY = world.maxCorner.y - 1; - const x = width * event.offsetX / this.canvas.offsetWidth + world.minCorner.x; - const _y = height * event.offsetY / this.canvas.offsetHeight + world.minCorner.y; - const y = this.flip(_y, minY, maxY) - return {x: Math.floor(x), y: Math.floor(y+1)}; + var _x: number; + var _y: number; + if (!this.conf.doingRotate) { + _x = width * event.offsetX / this.canvas.offsetWidth + world.minCorner.x; + _y = height * event.offsetY / this.canvas.offsetHeight + world.minCorner.y; + _y = this.flip(_y, minY, maxY) + } + else { + _y = (world.maxCorner.y - world.minCorner.y - 1) - height * event.offsetX / this.canvas.offsetWidth + world.minCorner.y; + _x = width * event.offsetY / this.canvas.offsetHeight + world.minCorner.x; + } + return {x: Math.floor(_x), y: Math.floor(_y+1)}; } private renderIndicatorDotsLines(world: GameWorld) { @@ -380,7 +403,7 @@ export default class Renderer { this.ctx.lineWidth = cst.INDICATOR_LINE_WIDTH; for (let i = 0; i < lines.length; i++) { - if (linesID[i] === this.lastSelectedID) { + if (linesID[i] === this.lastSelectedID || this.conf.allIndicators) { const red = linesRed[i]; const green = linesGreen[i]; const blue = linesBlue[i]; diff --git a/client/visualizer/src/main/controls.ts b/client/visualizer/src/main/controls.ts index 38d2dc7a..85360c3b 100644 --- a/client/visualizer/src/main/controls.ts +++ b/client/visualizer/src/main/controls.ts @@ -19,12 +19,12 @@ export default class Controls { div: HTMLDivElement; wrapper: HTMLDivElement; - readonly timeReadout: Text; + readonly timeReadout: HTMLSpanElement; readonly speedReadout: HTMLSpanElement; - readonly tileInfo: Text; + readonly tileInfo: HTMLSpanElement; readonly infoString: HTMLTableDataCellElement; - winnerDiv: HTMLDivElement; + //winnerDiv: HTMLDivElement; /** * Callbacks initialized from outside Controls @@ -57,11 +57,12 @@ export default class Controls { constructor(conf: Config, images: imageloader.AllImages, runner: Runner) { this.div = this.baseDiv(); - this.timeReadout = document.createTextNode('No match loaded'); - this.tileInfo = document.createTextNode('X | Y | Passability'); + this.timeReadout = document.createElement('span'); + this.tileInfo = document.createElement('span'); this.speedReadout = document.createElement('span'); this.speedReadout.style.cssFloat = 'right'; - this.speedReadout.textContent = 'UPS: 0 FPS: 0'; + + this.setDefaultText(); // initialize the images this.conf = conf; @@ -86,23 +87,14 @@ export default class Controls { let timeline = document.createElement("td"); timeline.className = "timeline"; timeline.vAlign = "top"; - if (this.conf.tournamentMode) { - timeline.style.width = '300px'; - } + timeline.appendChild(this.timeline()); - if (this.conf.tournamentMode) { - this.winnerDiv = document.createElement("div"); - timeline.append(this.winnerDiv); - } timeline.appendChild(document.createElement("br")); timeline.appendChild(this.timeReadout); timeline.appendChild(this.speedReadout); - this.curUPS = 16; - if (this.conf.tournamentMode) { - this.curUPS = 8; // for tournament!!! - } - + this.setDefaultUPS(); + // create the button controls let buttons = document.createElement("td"); buttons.vAlign = "top"; @@ -157,9 +149,10 @@ export default class Controls { function changeTime(dragEvent: MouseEvent) { // jump to a frame when clicking the controls timeline if (runner.looper) { + const loadedTime = !conf.tournamentMode ? runner.looper.match['_farthest'].turn : 1500; let width: number = (this).width; - let turn: number = dragEvent.offsetX / width * runner.looper.match['_farthest'].turn; - turn = Math.round(Math.min(runner.looper.match['_farthest'].turn, turn)); + let turn: number = dragEvent.offsetX / width * loadedTime; + turn = Math.round(Math.min(loadedTime, turn)); runner.looper.onSeek(turn); } @@ -220,11 +213,17 @@ export default class Controls { this.ctx.fillStyle = "white"; this.canvas = canvas; if (this.conf.tournamentMode) { - canvas.style.display = 'none'; // we don't wanna reveal how many rounds there are! + //canvas.style.display = 'none'; // we don't wanna reveal how many rounds there are! } return canvas; } + setDefaultText() { + this.timeReadout.innerHTML = 'No match loaded'; + this.tileInfo.innerHTML = 'X | Y | Passability'; + this.speedReadout.textContent = 'UPS: FPS: '; + } + /** * Returns the UPS determined by the slider */ @@ -232,6 +231,13 @@ export default class Controls { return this.curUPS; } + setDefaultUPS() { + this.curUPS = 16; + if (this.conf.tournamentMode) { + this.curUPS = 8; // for tournament!!! + } + } + /** * Displays the correct controls depending on whether we are in game mode * or map editor mode @@ -347,38 +353,38 @@ export default class Controls { */ onFinish(match: Match, meta: Metadata) { if (this.runner.looper && !this.runner.looper.isPaused()) this.pause(); - if (this.conf.tournamentMode) { - // also update the winner text - this.setWinner(match, meta); - } + // if (this.conf.tournamentMode) { + // // also update the winner text + // this.setWinner(match, meta); + // } } - setWinner(match: Match, meta: Metadata) { - console.log('winner: ' + match.winner); - const matchWinner = this.winnerTeam(meta.teams, match.winner); - while (this.winnerDiv.firstChild) { - this.winnerDiv.removeChild(this.winnerDiv.firstChild); - } - this.winnerDiv.appendChild(matchWinner); - } + // setWinner(match: Match, meta: Metadata) { + // console.log('winner: ' + match.winner); + // const matchWinner = this.winnerTeam(meta.teams, match.winner); + // while (this.winnerDiv.firstChild) { + // this.winnerDiv.removeChild(this.winnerDiv.firstChild); + // } + // this.winnerDiv.appendChild(matchWinner); + // } - private winnerTeam(teams, winnerID: number | null): HTMLSpanElement { - const span = document.createElement("span"); - if (winnerID === null) { - return span; - } else { - // Find the winner - let teamNumber = 1; - for (let team in teams) { - if (teams[team].teamID === winnerID) { - span.className += team === "1" ? " red" : " blue"; - span.innerHTML = teams[team].name + " wins!"; - break; - } - } - } - return span; - } + // private winnerTeam(teams, winnerID: number | null): HTMLSpanElement { + // const span = document.createElement("span"); + // if (winnerID === null) { + // return span; + // } else { + // // Find the winner + // let teamNumber = 1; + // for (let team in teams) { + // if (teams[team].teamID === winnerID) { + // span.className += team === "1" ? " red" : " blue"; + // span.innerHTML = teams[team].name + " wins!"; + // break; + // } + // } + // } + // return span; + // } /** * Redraws the timeline and sets the current round displayed in the controls. @@ -386,10 +392,12 @@ export default class Controls { // TODO scale should be constant; should not depend on loadedTime setTime(time: number, loadedTime: number, upsUnpaused: number, paused: Boolean, fps: number, lagging: Boolean) { - if (!this.conf.tournamentMode) { + if (this.conf.tournamentMode) loadedTime = 1500; + + // if (!this.conf.tournamentMode) { // Redraw the timeline + const scale = this.canvas.width / loadedTime; - // const scale = this.canvas.width / cst.MAX_ROUND_NUM; this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.fillStyle = "rgb(39, 39, 39)"; @@ -400,12 +408,12 @@ export default class Controls { this.ctx.fillStyle = 'rgb(255,0,0)'; this.ctx.fillRect(time * scale, 0, 2, this.canvas.height); - } + // } let speedText = (lagging ? '(Lagging) ' : '') + `UPS: ${upsUnpaused | 0}` + (paused ? ' (Paused)' : '') + ` FPS: ${fps | 0}`; speedText = speedText.padStart(32); this.speedReadout.textContent = speedText; - this.timeReadout.textContent = (this.conf.tournamentMode ? `Round: ${time}` : `Round: ${time}/${loadedTime}`); + this.timeReadout.innerHTML = (this.conf.tournamentMode ? `Round: ${time}` : `Round: ${time}/${loadedTime}`); } @@ -414,11 +422,11 @@ export default class Controls { */ setTileInfo(x: number, y: number, xrel: number, yrel: number, passability: number): void { let content: string = ""; - content += 'X: ' + `${xrel}`.padStart(3) + ` (${x})`.padStart(3); - content += ' | Y: ' + `${yrel}`.padStart(3) + ` (${y})`.padStart(3); - content += ' | Passability: ' + `${passability.toFixed(3)}`; + content += 'X: ' + `${xrel}`.padStart(3) + ` (${x})`.padStart(3); + content += ' | Y: ' + `${yrel}`.padStart(3) + ` (${y})`.padStart(3); + content += ' | Passability: ' + `${passability.toFixed(3)}`; - this.tileInfo.textContent = content; + this.tileInfo.innerHTML = content; } /** diff --git a/client/visualizer/src/main/looper.ts b/client/visualizer/src/main/looper.ts index dce2537e..ed665929 100644 --- a/client/visualizer/src/main/looper.ts +++ b/client/visualizer/src/main/looper.ts @@ -40,8 +40,10 @@ export default class Looper { private conf: config.Config, private imgs: imageloader.AllImages, private controls: Controls, private stats: Stats, private gamearea: GameArea, cconsole: Console, - private matchqueue: MatchQueue, private profiler?: Profiler) { - + private matchqueue: MatchQueue, private profiler?: Profiler, + private mapinfo: string = "", + showTourneyUpload: boolean = true) { + this.console = cconsole; this.conf.mode = config.Mode.GAME; @@ -51,6 +53,10 @@ export default class Looper { // Cancel previous games if they're running this.clearScreen(); + // rotate tall maps + if (this.conf.doRotate) this.conf.doingRotate = (match.current.maxCorner.y - match.current.minCorner.y) > (match.current.maxCorner.x - match.current.minCorner.x); + else this.conf.doingRotate = false; + // Reset the canvas this.gamearea.setCanvasDimensions(match.current); @@ -62,6 +68,9 @@ export default class Looper { teamIDs.push(meta.teams[team].teamID); } this.stats.initializeGame(teamNames, teamIDs); + const extraInfo = (this.mapinfo ? this.mapinfo + "\n" : "") + (this.conf.doingRotate ? " (Map rotated and flipped! Disable for new matches with 'Z'.)" : ""); + this.stats.setExtraInfo(extraInfo); + if (!showTourneyUpload) this.stats.hideTourneyUpload(); // keep around to avoid reallocating this.nextStep = new NextStep(); @@ -84,11 +93,11 @@ export default class Looper { this.conf, meta as Metadata, onRobotSelected, onMouseover); // How fast the simulation should progress - // this.goalUPS = this.controls.getUPS(); - // if (this.conf.tournamentMode) { + // this.goalUPS = this.controls.getUPS(); + // if (this.conf.tournamentMode) { // Always pause on load. Mitigates funky behaviour like 100 rounds playing before any rendering occurs. this.goalUPS = 0; - // } + // } // A variety of stuff to track how fast the simulation is going this.rendersPerSecond = new TickCounter(.5, 100); @@ -103,9 +112,9 @@ export default class Looper { this.externalSeek = false; this.controls.updatePlayPauseButton(this.isPaused()); - + if (this.profiler) - this.profiler.reset(); + this.profiler.reset(); this.loadedProfiler = false; @@ -177,10 +186,12 @@ export default class Looper { this.goalUPS = 0; this.controls.pause(); this.controls.removeInfoString(); + this.controls.setDefaultText(); + this.controls.setDefaultUPS(); } private loop(curTime) { - + let delta = 0; if (this.lastTime === null) { // first simulation step @@ -220,8 +231,7 @@ export default class Looper { // run simulation // this may look innocuous, but it's a large chunk of the run time this.match.compute(30 /* ms */); // An ideal FPS is around 30 = 1000/30, so when compute takes its full time - // FPS is lowered significantly. But I think it's a worthwhile tradeoff. - + // FPS is lowered significantly. But I think it's a worthwhile tradeoff. // update the info string in controls if (this.lastSelectedID !== undefined) { let bodies = this.match.current.bodies.arrays; @@ -241,8 +251,8 @@ export default class Looper { let parent = bodies.parent[index]; let bid = bodies.bid[index]; - this.controls.setInfoString(id, x, y, influence, conviction, cst.bodyTypeToString(type), bytecodes, flag, - bid !== 0 ? bid : undefined, parent !== 0 ? parent : undefined); + this.controls.setInfoString(id, x, y, influence, conviction, cst.bodyTypeToString(type), bytecodes, flag, + bid !== 0 ? bid : undefined, parent !== 0 ? parent : undefined); } } @@ -250,10 +260,14 @@ export default class Looper { this.controls.removeInfoString(); } - this.console.setLogsRef(this.match.current.logs, this.match.current.logsShift); - this.console.seekRound(this.match.current.turn); this.lastTime = curTime; - this.lastTurn = this.match.current.turn; + + if (this.match.current.turn != this.lastTurn) { + this.console.setLogsRef(this.match.current.logs, this.match.current.logsShift); + this.console.seekRound(this.match.current.turn); + this.lastTurn = this.match.current.turn; + this.updateStats(this.match.current, this.meta); + } // @ts-ignore // renderer.render(this.match.current, this.match.current.minCorner, this.match.current.maxCorner); @@ -284,7 +298,7 @@ export default class Looper { this.loadedProfiler = true; } - this.updateStats(this.match.current, this.meta); + //this.updateStats(this.match.current, this.meta); this.loopID = window.requestAnimationFrame((curTime) => this.loop.call(this, curTime)); } @@ -293,12 +307,28 @@ export default class Looper { * team in the current game world. */ private updateStats(world: GameWorld, meta: Metadata) { - var totalInfluence = 0; + let totalInfluence = 0; + let totalConviction = 0; + let teamIDs: number[] = []; + let teamNames: string[] = []; + + this.stats.resetECs(); + for (let i = 0; i < world.bodies.length; i++) { + const type = world.bodies.arrays.type[i]; + if (type === schema.BodyType.ENLIGHTENMENT_CENTER) { + this.stats.addEC(world.bodies.arrays.team[i]); + } + } + for (let team in meta.teams) { let teamID = meta.teams[team].teamID; let teamStats = world.teamStats.get(teamID) as TeamStats; totalInfluence += teamStats.influence.reduce((a, b) => a + b); + totalConviction += teamStats.conviction.reduce((a, b) => a + b); + teamIDs.push(teamID); + teamNames.push(meta.teams[team].name); } + for (let team in meta.teams) { let teamID = meta.teams[team].teamID; let teamStats = world.teamStats.get(teamID) as TeamStats; @@ -306,15 +336,21 @@ export default class Looper { // Update each robot count this.stats.robots.forEach((type: schema.BodyType) => { this.stats.setRobotCount(teamID, type, teamStats.robots[type]); - this.stats.setRobotConviction(teamID, type, teamStats.conviction[type]); + this.stats.setRobotConviction(teamID, type, teamStats.conviction[type], totalConviction); this.stats.setRobotInfluence(teamID, type, teamStats.influence[type]); }); // Set votes this.stats.setVotes(teamID, teamStats.votes); - this.stats.setTeamInfluence(teamID, teamStats.influence.reduce((a, b) => a + b), + this.stats.setTeamInfluence(teamID, teamStats.influence.reduce((a, b) => a + b), totalInfluence); this.stats.setBuffs(teamID, teamStats.numBuffs); + this.stats.setBid(teamID, teamStats.bid); + this.stats.setIncome(teamID, teamStats.income, world.turn); + } + + if (this.match.winner && this.match.current.turn == this.match.lastTurn) { + this.stats.setWinner(this.match.winner, teamNames, teamIDs); } } diff --git a/client/visualizer/src/main/sidebar.ts b/client/visualizer/src/main/sidebar.ts index d96ce15c..3b85d93e 100644 --- a/client/visualizer/src/main/sidebar.ts +++ b/client/visualizer/src/main/sidebar.ts @@ -40,6 +40,9 @@ export default class Sidebar { // Update texts private updateText: HTMLDivElement; + // Mode panel + private modePanel: HTMLTableElement; + // Callback to update the game area when changing modes cb: () => void; @@ -71,16 +74,16 @@ export default class Sidebar { if (conf.useProfiler) this.profiler = new Profiler(conf); this.matchqueue = new MatchQueue(conf, images, runner); this.stats = new Stats(conf, images, runner); - this.help = this.initializeHelp(); this.conf = conf; + this.help = this.initializeHelp(); // Initialize div structure this.loadStyles(); this.div.appendChild(this.screamForUpdate()); this.div.appendChild(this.battlecodeLogo()); - const modePanel = document.createElement('table'); - modePanel.className = 'modepanel'; + this.modePanel = document.createElement('table'); + this.modePanel.className = 'modepanel'; const modePanelRow1 = document.createElement('tr'); const modePanelRow2 = document.createElement('tr'); @@ -94,10 +97,10 @@ export default class Sidebar { modePanelRow2.appendChild(this.modeButton(Mode.MAPEDITOR, "Map Editor")); modePanelRow2.appendChild(this.modeButton(Mode.HELP, "Help")); - modePanel.appendChild(modePanelRow1); - modePanel.appendChild(modePanelRow2); + this.modePanel.appendChild(modePanelRow1); + this.modePanel.appendChild(modePanelRow2); - this.div.appendChild(modePanel); + this.div.appendChild(this.modePanel); this.div.appendChild(this.innerDiv); @@ -119,7 +122,7 @@ export default class Sidebar { * Initializes the help div */ private initializeHelp(): HTMLDivElement { - const innerHTML: string = + var innerHTML: string = ` Beware of too much logging!
@@ -150,6 +153,8 @@ export default class Sidebar { B - Toggle Interpolation
L - Toggle whether to process logs.
Q - Toggle whether to profile matches.
+ Z - Toggle whether to rotate tall maps.
+ [ - Hide/unhide sidebar navigation.

Keyboard Shortcuts (Map Editor)
@@ -217,6 +222,14 @@ export default class Sidebar {
Exported file name must be the same as the map name chosen above. For instance, DefaultMap.bc21.`; + if (this.conf.tournamentMode) { + innerHTML += + `

+ Tournament Mode Keyboard Shortcuts
+ D - Next match
+ A - Previous match` + } + const div = document.createElement("div"); div.id = "helpDiv"; @@ -356,4 +369,8 @@ export default class Sidebar { this.cb(); } } + + hidePanel() { + this.modePanel.style.display = (this.modePanel.style.display === "" ? "none" : ""); + } } diff --git a/client/visualizer/src/main/splash.ts b/client/visualizer/src/main/splash.ts index c238a0ae..4f9f0931 100644 --- a/client/visualizer/src/main/splash.ts +++ b/client/visualizer/src/main/splash.ts @@ -1,5 +1,5 @@ import {Config} from '../config'; -import {Tournament, TournamentGame, TournamentMatch} from './tournament'; +//import {Tournament, TournamentMatch} from './tournament_new'; /** * The splash screen for tournaments. Appears between every match @@ -14,9 +14,9 @@ export default class Splash { // Containers private static header: HTMLDivElement = document.createElement("div"); private static subHeader: HTMLDivElement = document.createElement("div"); - private static columnLeft: HTMLDivElement = document.createElement("div"); - private static columnRight: HTMLDivElement = document.createElement("div"); - private static columnCenter: HTMLDivElement = document.createElement("div"); + private static team1Div: HTMLDivElement = document.createElement("div"); + private static team2Div: HTMLDivElement = document.createElement("div"); + private static versusDiv: HTMLDivElement = document.createElement("div"); // Team elements to modify every time we change the screen private static avatarA: HTMLImageElement = document.createElement("img"); @@ -47,30 +47,24 @@ export default class Splash { Splash.subHeader.className = "tournament-subheader"; // Team A information (red) - Splash.columnLeft.className = "column-left"; - Splash.columnLeft.appendChild(Splash.avatarA); - Splash.avatarA.className = "avatar"; - Splash.columnLeft.appendChild(document.createElement("br")); - Splash.columnLeft.appendChild(Splash.nameAndIDA); + Splash.team1Div.id = "team1"; + Splash.team1Div.appendChild(Splash.nameAndIDA); // Center column (vs.) - Splash.columnCenter.className = "column-center"; - Splash.columnCenter.appendChild(document.createTextNode("vs.")); + Splash.versusDiv.id = "versus"; + Splash.versusDiv.appendChild(document.createTextNode("versus")); // Team B information (blue) - Splash.columnRight.className = "column-right"; - Splash.columnRight.appendChild(Splash.avatarB); - Splash.avatarB.className = "avatar"; - Splash.columnRight.appendChild(document.createElement("br")); - Splash.columnRight.appendChild(Splash.nameAndIDB); + Splash.team2Div.id = "team2"; + Splash.team2Div.appendChild(Splash.nameAndIDB); // Put everything together Splash.container.appendChild(Splash.header); Splash.container.appendChild(Splash.subHeader); Splash.container.appendChild(document.createElement("br")); - Splash.container.appendChild(Splash.columnLeft); - Splash.container.appendChild(Splash.columnCenter); - Splash.container.appendChild(Splash.columnRight); + Splash.container.appendChild(Splash.team1Div); + Splash.container.appendChild(Splash.versusDiv); + Splash.container.appendChild(Splash.team2Div); Splash.screen.appendChild(Splash.container); Splash.loaded = true; @@ -86,39 +80,40 @@ export default class Splash { Splash.winnerScreen.className = "blackout"; Splash.winnerContainer.className = "blackout-container"; - // Put everything together + // Put everything together'\ + Splash.winnerHeader.id = "winner"; Splash.winnerContainer.appendChild(Splash.winnerHeader); - Splash.winnerContainer.appendChild(document.createElement("br")); - Splash.winnerContainer.appendChild(Splash.winnerAvatar); - Splash.winnerContainer.appendChild(document.createElement('br')); - Splash.winnerContainer.appendChild(Splash.winnerExtra); + // Splash.winnerContainer.appendChild(document.createElement("br")); + // Splash.winnerContainer.appendChild(Splash.winnerAvatar); + // Splash.winnerContainer.appendChild(document.createElement('br')); + // Splash.winnerContainer.appendChild(Splash.winnerExtra); Splash.winnerScreen.appendChild(Splash.winnerContainer); Splash.winnerLoaded = true; } } - static addScreen(conf: Config, root: HTMLElement, game: TournamentGame, match: TournamentMatch, tournament: Tournament): void { + static addScreen(conf: Config, root: HTMLElement, team1: string, team2: string): void { this.loadScreen(); - this.header.innerText = match.description;//this.getBracketString(tournament); + // this.header.innerText = 'Round 1';//this.getBracketString(tournament); // this.subHeader.innerText = `Game ${tournament.gameIndex+1} of ${tournament.roundLengths[tournament.roundIndex]}`; - this.avatarA.src = tournament.getAvatar(match.team1_name); - this.nameAndIDA.innerText = `${match.team1_name} (#${match.team1_id})`; - this.avatarB.src = tournament.getAvatar(match.team2_name); - this.nameAndIDB.innerText = `${match.team2_name} (#${match.team2_id})`; + // this.avatarA.src = tournament.getAvatar(match.team1_name); + this.nameAndIDA.innerText = team1; + // this.avatarB.src = tournament.getAvatar(match.team2_name); + this.nameAndIDB.innerText = team2; root.appendChild(this.screen) } - static addWinnerScreen(conf: Config, root: HTMLElement, tournament: Tournament, match: TournamentMatch) { + static addWinnerScreen(conf: Config, root: HTMLElement, text: string) { this.loadWinnerScreen(); - this.winnerHeader.innerText = `${match.winner_name} (#${match.winner_id}) wins!`; + this.winnerHeader.innerText = text; this.winnerHeader.className = "tournament-header"; - this.winnerAvatar.className = "big-avatar"; - this.winnerAvatar.src = tournament.getAvatar(match.winner_name); + // this.winnerAvatar.className = "big-avatar"; + // this.winnerAvatar.src = tournament.getAvatar(match.winner_name); root.appendChild(this.winnerScreen); } diff --git a/client/visualizer/src/main/tournament.ts b/client/visualizer/src/main/tournament.ts index aeed8c1d..6a134262 100644 --- a/client/visualizer/src/main/tournament.ts +++ b/client/visualizer/src/main/tournament.ts @@ -1,6 +1,6 @@ import {path, fs} from './electron-modules'; -export function readTournament(jsonFile: File, cb: (err: Error | null, t: Tournament | null) => void) { +export function readTournament(jsonFile: File, cbTournament: (t: Tournament) => void, cbError: (err: Error) => void) { /*if (!process.env.ELECTRON) { cb(new Error("Can't read tournaments outside of electron"), null); return; @@ -11,18 +11,18 @@ export function readTournament(jsonFile: File, cb: (err: Error | null, t: Tourna console.log('reader RESULT'); console.log(reader.result); if (reader.error) { - cb(reader.error, null); + cbError(reader.error); return; } - var tournament; + var tournament: Tournament; try { tournament = new Tournament(JSON.parse(reader.result)); } catch (e) { - cb(e, null); + cbError(e); return; } - cb(null, tournament); + cbTournament(tournament); }; reader.readAsText(jsonFile); } diff --git a/client/visualizer/src/main/tournament_new.ts b/client/visualizer/src/main/tournament_new.ts new file mode 100644 index 00000000..31d0a536 --- /dev/null +++ b/client/visualizer/src/main/tournament_new.ts @@ -0,0 +1,158 @@ +import { path, fs } from './electron-modules'; + +// matches required to win a tourney game +const goal = 3; + +export function readTournament(jsonFile: File, cbTournament: (t: Tournament) => void, cbError: (err: Error) => void) { + const reader = new FileReader(); + reader.onload = () => { + console.log('reader RESULT'); + console.log(reader.result); + if (reader.error) { + cbError(reader.error); + return; + } + + try { + const data: any[][] = JSON.parse(reader.result); + const parseMatch: (arr: [string, string, string, number, number]) => TournamentMatch = ((arr) => ({ + team1: arr[0], + team2: arr[1], + map: arr[2], + winner: arr[3], + url: "https://2021.battlecode.org/replays/" + arr[4] + ".bc21" + })); + const desc: TournamentMatch[][] = data.filter(game => game != null).map((game) => (game.map(parseMatch))); + const tournament = new Tournament(desc); + cbTournament(tournament); + } catch (e) { + cbError(e); + return; + } + }; + reader.readAsText(jsonFile); +} + +// Use like a "cursor" into a tournament. +// It's a bit awkward. +export class Tournament { + readonly games: TournamentMatch[][]; + // cursors to games + // this is the index within a game + matchI: number; + // cursor to game index + gameI: number; + + constructor(games: TournamentMatch[][]) { + this.matchI = 0; + this.gameI = 0; + this.games = games; + } + + seek(gameIndex: number, matchIndex: number) { + if (gameIndex < this.games.length && matchIndex < this.games[gameIndex].length) { + this.matchI = matchIndex; + this.gameI = gameIndex; + } else { + throw new Error("Out of bounds: " + matchIndex + "," + gameIndex); + } + } + + hasNext(): boolean { + return this.gameI < this.games.length - 1 || this.matchI < this.games[this.gameI].length - 1; + } + + next() { + if (!this.hasNext()) { + throw new Error("No more matches!"); + } + if (this.isLastMatchInGame()) { + this.matchI = 0; + this.gameI++; + } + else this.matchI++; + console.log(`game index: ${this.gameI}\n match index: ${this.matchI}`); + } + + // getAvatar(name: string) { + // // convert the name into an ID + // // TODO: speed this up + // for (var i = 0; i < this.desc.teams.length; i++) { + // if (this.desc.teams[i].name === name) { + // return 'file://' + path.join(this.dir, this.desc.teams[i].avatarPath); + // } + // } + // return "not found :(("; + // } + + isLastMatchInGame(): boolean { + return this.matchI === this.games[this.gameI].length - 1 || (Math.max(this.wins()[this.current().team1], this.wins()[this.current().team2]) >= goal); + } + + isFirstMatchInGame(): boolean { + return this.matchI === 0; + } + + hasPrev(): boolean { + return this.matchI > 0 || this.gameI > 0; + } + + prev() { + if (!this.hasPrev()) { + throw new Error("No previous matches!"); + } + this.matchI--; + if (this.matchI < 0) { + this.gameI--; + this.matchI = 0; + while (!this.isLastMatchInGame()) this.matchI++; + } + console.log(`game index: ${this.gameI}\n match index: ${this.matchI}`); + } + + current(): TournamentMatch { + if (this.gameI >= this.games.length || this.matchI >= this.games[this.gameI].length) { + throw new Error(`BAD COMBO: match ${this.matchI}, ${this.gameI}`); + } + if (this.games[this.gameI][this.matchI] == undefined) { + throw new Error("Undefined game?? " + this.matchI); + } + return this.games[this.gameI][this.matchI]; + } + + currentGame(): TournamentMatch[] { + if (this.gameI > this.games.length) { + throw new Error(`game out of bounds: ${this.gameI}`); + } + if (this.games[this.gameI] == undefined) { + throw new Error("Undefined game?? " + this.matchI); + } + return this.games[this.gameI]; + } + + wins(uptoMatchI?: number) { + if (uptoMatchI === undefined) uptoMatchI = this.matchI; + const team1 = this.current().team1; + const team2 = this.current().team2; + const wins = {}; + wins[team1] = 0; + wins[team2] = 0; + for (let matchI = 0; matchI <= uptoMatchI; matchI++) { + const match = this.games[this.gameI][matchI]; + wins[match.winner == 1 ? match.team1 : match.team2]++; + } + return wins; + } + + totalWins() { + return this.wins(this.games[this.gameI].length - 1); + } +} + +export interface TournamentMatch { + team1: string, + team2: string, + map: string, + winner: number, + url: string +} \ No newline at end of file diff --git a/client/visualizer/src/mapeditor/action/generator.ts b/client/visualizer/src/mapeditor/action/generator.ts index 7ddf3674..e42badef 100644 --- a/client/visualizer/src/mapeditor/action/generator.ts +++ b/client/visualizer/src/mapeditor/action/generator.ts @@ -1,12 +1,10 @@ import * as cst from '../../constants'; -import {schema, flatbuffers} from 'battlecode-playback'; -import Victor = require('victor'); - -import {MapUnit, GameMap} from '../index'; +import { schema, flatbuffers } from 'battlecode-playback'; +import { MapUnit, GameMap } from '../index'; // Bodies information -export type BodiesSchema = { +type BodiesSchema = { robotIDs: number[], teamIDs: number[], types: schema.BodyType[], @@ -15,6 +13,14 @@ export type BodiesSchema = { influences: number[] }; +export type UploadedMap = { + name: string, + width: number, + height: number, + passability: number[], + bodies: MapUnit[] +} + /** * Generates a .map21 file from a GameMap. Assumes the given GameMap represents * a valid game map. @@ -68,15 +74,15 @@ export default class MapGenerator { /** * Adds multiple bodies to the internal array with the given teamID. */ - private static addBodies(bodies: Map, minCorner: Victor) { + private static addBodies(bodies: Map, minCornerX, minCornerY) { bodies.forEach((unit: MapUnit, id: number) => { this.addBody( id, unit.teamID || 0, // Must be set if not a neutral tree unit.type, - unit.loc.x, - unit.loc.y, + unit.x, + unit.y, unit.influence ); }); @@ -100,13 +106,14 @@ export default class MapGenerator { // Get header information from form let name: string = map.name; - let minCorner: Victor = new Victor(Math.random()*20000 + 10000, Math.random()*20000 + 10000); - let maxCorner: Victor = minCorner.clone(); - maxCorner.add(new Victor(map.width, map.height)); - let randomSeed: number = Math.round(Math.random()*1000); + const minCornerX = Math.random() * 20000 + 10000; + const minCornerY = Math.random() * 20000 + 10000; + let maxCornerX = minCornerX + map.width; + let maxCornerY = minCornerY + map.height; + let randomSeed: number = Math.round(Math.random() * 1000); // Get body information from form and convert to arrays - this.addBodies(this.combineBodies(map.originalBodies, map.symmetricBodies), minCorner); + this.addBodies(this.combineBodies(map.originalBodies, map.symmetricBodies), minCornerX, minCornerY); // Create the spawned bodies table let robotIDsVectorB = schema.SpawnedBodyTable.createRobotIDsVector(builder, this.bodiesArray.robotIDs); @@ -128,8 +135,8 @@ export default class MapGenerator { let nameP = builder.createString(name); schema.GameMap.startGameMap(builder); schema.GameMap.addName(builder, nameP); - schema.GameMap.addMinCorner(builder, schema.Vec.createVec(builder, minCorner.x, minCorner.y)); - schema.GameMap.addMaxCorner(builder, schema.Vec.createVec(builder, maxCorner.x, maxCorner.y)); + schema.GameMap.addMinCorner(builder, schema.Vec.createVec(builder, minCornerX, minCornerY)); + schema.GameMap.addMaxCorner(builder, schema.Vec.createVec(builder, maxCornerX, maxCornerY)); schema.GameMap.addBodies(builder, bodies); schema.GameMap.addPassability(builder, passability); schema.GameMap.addRandomSeed(builder, randomSeed); @@ -159,9 +166,47 @@ export default class MapGenerator { link.click(); link.remove(); - setTimeout(function() { + setTimeout(function () { return window.URL.revokeObjectURL(url); }, 30000); } } + + /** + * Reads a .map21 file. + */ + static readMap(file: ArrayBuffer): UploadedMap { + const data = new Uint8Array(file); + const map = schema.GameMap.getRootAsGameMap( + new flatbuffers.ByteBuffer(data) + ); + const minCorner = map.minCorner()!; + const maxCorner = map.maxCorner()!; + + const bodies = map.bodies()!; + const influences = bodies.influencesArray()!; + const types = bodies.typesArray()!; + const teamIDs = bodies.teamIDsArray()!; + const xs = bodies.locs()!.xsArray()!; + const ys = bodies.locs()!.ysArray()!; + + const mapUnits: MapUnit[] = []; + for (let i = 0; i < bodies.robotIDsLength(); i++) { + mapUnits.push({ + x: xs[i], + y: ys[i], + type: types[i], + teamID: teamIDs[i], + influence: influences[i], + radius: 0.5 + }); + } + return { + name: map.name()!, + width: maxCorner.x() - minCorner.x(), + height: maxCorner.y() - minCorner.y(), + passability: Array.from(map.passabilityArray()!), + bodies: mapUnits + }; + } } \ No newline at end of file diff --git a/client/visualizer/src/mapeditor/action/renderer.ts b/client/visualizer/src/mapeditor/action/renderer.ts index 5a314473..62690b10 100644 --- a/client/visualizer/src/mapeditor/action/renderer.ts +++ b/client/visualizer/src/mapeditor/action/renderer.ts @@ -3,7 +3,6 @@ import * as cst from '../../constants'; import {GameWorld, schema} from 'battlecode-playback'; import {AllImages} from '../../imageloader'; -import Victor = require('victor'); import {GameMap, MapUnit} from '../index'; @@ -21,9 +20,9 @@ export default class MapRenderer { // Callbacks for clicking robots and trees on the canvas readonly onclickUnit: (id: number) => void; - readonly onclickBlank: (loc: Victor) => void; + readonly onclickBlank: (x, y) => void; readonly onMouseover: (x: number, y: number, passability: number) => void - readonly onDrag: (loc: Victor) => void + readonly onDrag: (x, y) => void // Other useful values readonly bgPattern: CanvasPattern; @@ -33,9 +32,9 @@ export default class MapRenderer { private map: GameMap; //the current map constructor(canvas: HTMLCanvasElement, imgs: AllImages, conf: config.Config, - onclickUnit: (id: number) => void, onclickBlank: (loc: Victor) => void, + onclickUnit: (id: number) => void, onclickBlank: (x: number, y: number) => void, onMouseover: (x: number, y: number, passability: number) => void, - onDrag: (loc: Victor) => void) { + onDrag: (x: number, y: number) => void) { this.canvas = canvas; this.conf = conf; this.imgs = imgs; @@ -59,6 +58,7 @@ export default class MapRenderer { * Renders the game map. */ render(map: GameMap): void { + console.log("map:", map); const scale = this.canvas.width / map.width; this.width = map.width; this.height = map.height; @@ -128,8 +128,8 @@ export default class MapRenderer { } private renderBody(body: MapUnit) { - const x = body.loc.x; - const y = this.flip(body.loc.y, this.map.height); + const x = body.x; + const y = this.flip(body.y, this.map.height); const radius = body.radius; let img: HTMLImageElement; @@ -149,25 +149,22 @@ export default class MapRenderer { const whilemousedown = () => { if (hoverPos !== null) { const {x,y} = hoverPos; - let loc : Victor = new Victor(x, y); - this.onDrag(loc); + this.onDrag(x, y); } }; var interval: number; this.canvas.onmousedown = (event: MouseEvent) => { const {x,y} = this.getIntegerLocation(event, this.map); - let loc : Victor = new Victor(x, y); - // Get the ID of the selected unit let selectedID; this.map.originalBodies.forEach(function(body: MapUnit, id: number) { - if (loc.isEqualTo(body.loc)) { + if (x == body.x && y == body.y) { selectedID = id; } }); this.map.symmetricBodies.forEach(function(body: MapUnit, id: number) { - if (loc.isEqualTo(body.loc)) { + if (x == body.x && y == body.y) { selectedID = id; } }); @@ -175,7 +172,7 @@ export default class MapRenderer { if (selectedID) { this.onclickUnit(selectedID); } else { - this.onclickBlank(loc); + this.onclickBlank(x, y); } interval = window.setInterval(whilemousedown, 50); diff --git a/client/visualizer/src/mapeditor/action/validator.ts b/client/visualizer/src/mapeditor/action/validator.ts index fa45baf2..91c35d8f 100644 --- a/client/visualizer/src/mapeditor/action/validator.ts +++ b/client/visualizer/src/mapeditor/action/validator.ts @@ -35,8 +35,8 @@ export default class MapValidator { // Invariant: bodies in originalBodies don't overlap with each other, and // bodies in symmetricBodies don't overlap with each other map.originalBodies.forEach((unit: MapUnit, id: number) => { - let x = unit.loc.x; - let y = unit.loc.y; + let x = unit.x; + let y = unit.y; if (x < 0 || y < 0 || x > map.width || y > map.height) { errors.push(`ID ${id} is off the map.`); } @@ -45,7 +45,7 @@ export default class MapValidator { // Bodies must not overlap map.originalBodies.forEach((unitA: MapUnit, idA: number) => { map.symmetricBodies.forEach((unitB: MapUnit, idB: number) => { - if (unitA.loc.distanceSq(unitB.loc) == 0) { + if (unitA.x === unitB.x && unitA.y === unitB.y) { errors.push (`IDs ${idA} and ${idB} are overlapping.`); } }); @@ -74,8 +74,8 @@ export default class MapValidator { // Remove bodies that are off the map map.originalBodies.forEach((unit: MapUnit, id: number) => { - let x = unit.loc.x; - let y = unit.loc.y; + let x = unit.x; + let y = unit.y; let distanceToWall = Math.min(x, y, map.width - x, map.height - y); if (unit.radius > distanceToWall || x < 0 || y < 0 || x > map.width || y > map.height) { map.originalBodies.delete(id); @@ -88,7 +88,9 @@ export default class MapValidator { // bodies in symmetricBodies don't overlap with each other map.originalBodies.forEach((unitA: MapUnit, idA: number) => { map.symmetricBodies.forEach((unitB: MapUnit, idB: number) => { - if (unitA.loc.distance(unitB.loc) <= unitA.radius + unitB.radius) { + // no radii this year + // if (unitA.loc.distance(unitB.loc) <= unitA.radius + unitB.radius) { + if (unitA.x === unitB.x && unitA.y === unitB.y) { map.originalBodies.delete(idA); map.originalBodies.delete(idB); actions.push (`Removed IDs ${idA} and ${idB}. (overlapping)`); diff --git a/client/visualizer/src/mapeditor/form.ts b/client/visualizer/src/mapeditor/form.ts index 88ca7d53..867a81a0 100644 --- a/client/visualizer/src/mapeditor/form.ts +++ b/client/visualizer/src/mapeditor/form.ts @@ -4,13 +4,13 @@ import {AllImages} from '../imageloader'; import {cow_border as cow} from '../cow'; import {schema, flatbuffers} from 'battlecode-playback'; -import Victor = require('victor'); -import {MapRenderer, HeaderForm, SymmetryForm, RobotForm, TileForm} from './index'; +import {MapRenderer, HeaderForm, SymmetryForm, RobotForm, TileForm, UploadedMap} from './index'; import { SSL_OP_NO_QUERY_MTU } from 'constants'; export type MapUnit = { - loc: Victor, + x: number, + y: number, type: schema.BodyType, radius: 0.5, teamID?: number, @@ -144,12 +144,12 @@ export default class MapEditorForm { // Set the corresponding form appropriately let body: MapUnit = this.originalBodies.get(id)!; this.robotsRadio.click(); - this.robots.setForm(body.loc, body, id); + this.robots.setForm(body.x, body.y, body, id); } }; - const onclickBlank = (loc: Victor) => { - this.getActiveForm().setForm(loc); + const onclickBlank = (x, y) => { + this.getActiveForm().setForm(x, y); }; const onMouseover = (x: number, y: number, passability: number) => { @@ -160,7 +160,7 @@ export default class MapEditorForm { this.tileInfo.textContent = content; }; - const onDrag = (loc: Victor) => { + const onDrag = (x, y) => { if (this.getActiveForm() === this.tiles && this.tiles.isValid()) { let r: number = this.tiles.getBrush(); let inBrush: (dx, dy) => boolean = () => true; @@ -174,7 +174,7 @@ export default class MapEditorForm { case "Cow": inBrush = (dx,dy) => (Math.abs(dx) < r && Math.abs(dy) < r && cow[Math.floor(20*(1+dx/r))][Math.floor(20*(1-dy/r))]); } - this.setAreaPassability(loc.x, loc.y, this.tiles.getPass(), inBrush); + this.setAreaPassability(x, y, this.tiles.getPass(), inBrush); this.render(); } } @@ -369,26 +369,25 @@ export default class MapEditorForm { * If an id is given, does not consider the body with the corresponding id to * overlap with the given coordinates. */ - private maxRadius(x: number, y: number, ignoreID?: number): number { - // Min distance to wall - let maxRadius = Math.min(x, y, this.header.getWidth()-x, this.header.getHeight()-y); - const loc = new Victor(x, y); - - // Min distance to tree or body - ignoreID = ignoreID || -1; - this.originalBodies.forEach((body: MapUnit, id: number) => { - if (id != ignoreID) { - maxRadius = Math.min(maxRadius, loc.distance(body.loc) - body.radius); - } - }); - this.symmetricBodies.forEach((body: MapUnit, id: number) => { - if (id != ignoreID) { - maxRadius = Math.min(maxRadius, loc.distance(body.loc) - body.radius); - } - }); - - return Math.max(0, maxRadius - cst.DELTA); - } + // private maxRadius(x: number, y: number, ignoreID?: number): number { + // // Min distance to wall + // let maxRadius = Math.min(x, y, this.header.getWidth()-x, this.header.getHeight()-y); + + // // Min distance to tree or body + // ignoreID = ignoreID || -1; + // this.originalBodies.forEach((body: MapUnit, id: number) => { + // if (id != ignoreID) { + // maxRadius = Math.min(maxRadius, loc.distance(body.loc) - body.radius); + // } + // }); + // this.symmetricBodies.forEach((body: MapUnit, id: number) => { + // if (id != ignoreID) { + // maxRadius = Math.min(maxRadius, loc.distance(body.loc) - body.radius); + // } + // }); + + // return Math.max(0, maxRadius - cst.DELTA); + // } /** * If a unit with the given ID already exists, updates the existing unit. @@ -428,8 +427,8 @@ export default class MapEditorForm { private setPassability(x: number, y: number, pass: number) { if (this.randomMode) pass = this.randomLow + (this.randomHigh - this.randomLow) * Math.random(); - const translated: Victor = this.symmetry.transformLoc(new Victor(x, y), this.header.getWidth(), this.header.getHeight()); - this.passability[y*this.header.getWidth() + x] = this.passability[translated.y*this.header.getWidth() + translated.x] = pass; + const {x: translated_x, y: translated_y} = this.symmetry.transformLoc(x, y, this.header.getWidth(), this.header.getHeight()); + this.passability[y*this.header.getWidth() + x] = this.passability[translated_y*this.header.getWidth() + translated_x] = pass; } private setAreaPassability(x0: number, y0: number, pass: number, inBrush: (dx, dy) => boolean) { @@ -491,42 +490,63 @@ export default class MapEditorForm { }; } - getMapJSON(): string { - // from https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map/56150320 - const map = this.getMap(); - function replacer(key, value) { - const originalObject = this[key]; - if(originalObject instanceof Map) { - return { - dataType: 'Map', - value: Array.from(originalObject.entries()), // or with spread: value: [...originalObject] - }; - } else { - return value; - } - } - return JSON.stringify(map, replacer); - } - - setMap(mapJSON) { - // from https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map/56150320 - function reviver(key, value) { - if(typeof value === 'object' && value !== null) { - if (value.dataType === 'Map') { - return new Map(value.value); - } - } - return value; - } - const map = JSON.parse(mapJSON, reviver); + // getMapJSON(): string { + // // from https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map/56150320 + // const map = this.getMap(); + // function replacer(key, value) { + // const originalObject = this[key]; + // if(originalObject instanceof Map) { + // return { + // dataType: 'Map', + // value: Array.from(originalObject.entries()), // or with spread: value: [...originalObject] + // }; + // } else { + // return value; + // } + // } + // return JSON.stringify(map, replacer); + // } + + // setMap(mapJSON) { + // // from https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map/56150320 + // function reviver(key, value) { + // if(typeof value === 'object' && value !== null) { + // if (value.dataType === 'Map') { + // return new Map(value.value); + // } + // } + // return value; + // } + // const map = JSON.parse(mapJSON, reviver); + // this.header.setName(map.name); + // this.header.setWidth(map.width); + // this.header.setHeight(map.height); + + // this.originalBodies = map.originalBodies; + // this.symmetricBodies = map.symmetricBodies; + // this.symmetry.setSymmetry(map.symmetry); + // this.passability = map.passability; + // this.render(); + // } + + // TODO: types + setUploadedMap(map: UploadedMap) { + + const symmetryAndBodies = this.symmetry.discoverSymmetryAndBodies(map.bodies, map.passability, map.width, map.height); + console.log(symmetryAndBodies); + if (symmetryAndBodies === null) return; + + this.reset(); this.header.setName(map.name); this.header.setWidth(map.width); this.header.setHeight(map.height); + this.symmetry.setSymmetry(symmetryAndBodies.symmetry); + this.originalBodies = symmetryAndBodies.originalBodies; + this.lastID = this.originalBodies.size + 1; + this.symmetricBodies = this.symmetry.getSymmetricBodies(this.originalBodies, map.width, map.height); - this.originalBodies = map.originalBodies; - this.symmetricBodies = map.symmetricBodies; - this.symmetry.setSymmetry(map.symmetry); this.passability = map.passability; + this.render(); } diff --git a/client/visualizer/src/mapeditor/forms/robots.ts b/client/visualizer/src/mapeditor/forms/robots.ts index 1a09f59d..bc76c71f 100644 --- a/client/visualizer/src/mapeditor/forms/robots.ts +++ b/client/visualizer/src/mapeditor/forms/robots.ts @@ -1,7 +1,6 @@ import * as cst from '../../constants'; import {schema} from 'battlecode-playback'; -import Victor = require('victor'); import {MapUnit} from '../index'; @@ -193,9 +192,9 @@ export default class RobotForm { this.y.value = ""; } - setForm(loc: Victor, body?: MapUnit, id?: number): void { - this.x.value = String(loc.x); - this.y.value = String(loc.y); + setForm(x, y, body?: MapUnit, id?: number): void { + this.x.value = String(x); + this.y.value = String(y); this.id.textContent = id === undefined ? "" : String(id); if (body && id) { this.type.value = String(body.type); @@ -217,7 +216,8 @@ export default class RobotForm { return undefined; } return { - loc: new Victor(this.getX(), this.getY()), + x: this.getX(), + y: this.getY(), radius: 0.5, type: this.getType(), teamID: this.getTeam(), diff --git a/client/visualizer/src/mapeditor/forms/symmetry.ts b/client/visualizer/src/mapeditor/forms/symmetry.ts index f253378d..0041d137 100644 --- a/client/visualizer/src/mapeditor/forms/symmetry.ts +++ b/client/visualizer/src/mapeditor/forms/symmetry.ts @@ -1,7 +1,5 @@ import * as cst from '../../constants'; -import Victor = require('victor'); - import {MapUnit} from '../index'; export enum Symmetry { @@ -9,7 +7,6 @@ export enum Symmetry { HORIZONTAL, VERTICAL }; - export default class SymmetryForm { // The public div @@ -21,7 +18,7 @@ export default class SymmetryForm { // Callback on input change to redraw the canvas private cb: () => void; - // Constants + // Constants. TODO: make these and assosciated methods static private readonly SYMMETRY_OPTIONS: Symmetry[] = [ Symmetry.ROTATIONAL, Symmetry.HORIZONTAL, Symmetry.VERTICAL ]; @@ -82,20 +79,20 @@ export default class SymmetryForm { } setSymmetry(symmetry) { - this.select.options[this.select.selectedIndex].value = symmetry; + this.select.value = symmetry; } // Whether or not loc lies on the point or line of symmetry - private onSymmetricLine(loc: Victor, width: number, height: number): boolean { + private onSymmetricLine(x, y, width: number, height: number): boolean { const midX = width / 2 - 0.5; const midY = height / 2 - 0.5; switch(this.getSymmetry()) { case(Symmetry.ROTATIONAL): - return loc.x === midX && loc.y === midY; + return x === midX && y === midY; case(Symmetry.HORIZONTAL): - return loc.y === midY; + return y === midY; case(Symmetry.VERTICAL): - return loc.x === midX; + return x === midX; } }; @@ -104,7 +101,12 @@ export default class SymmetryForm { } // Returns the symmetric location on the canvas - transformLoc (loc: Victor, width: number, height: number): Victor { + transformLoc (x, y, width: number, height: number) { + return this.transformLocStatic(x, y, width, height, this.getSymmetry()); + }; + + // TODO: make this actually static! + transformLocStatic(x, y, width, height, symmetry: Symmetry) { function reflect(x: number, mid: number): number { if (x > mid) { return mid - Math.abs(x - mid); @@ -115,15 +117,15 @@ export default class SymmetryForm { const midX = width / 2 - 0.5; const midY = height / 2 - 0.5; - switch(this.getSymmetry()) { + switch(symmetry) { case(Symmetry.ROTATIONAL): - return new Victor(reflect(loc.x, midX), reflect(loc.y, midY)); + return {x: reflect(x, midX), y: reflect(y, midY)}; case(Symmetry.HORIZONTAL): - return new Victor(loc.x, reflect(loc.y, midY)); + return {x: x, y: reflect(y, midY)}; case(Symmetry.VERTICAL): - return new Victor(reflect(loc.x, midX), loc.y); + return {x: reflect(x, midX), y: y}; } - }; + } /** * Uses the bodies stored internally to create a mapping of original body @@ -136,11 +138,13 @@ export default class SymmetryForm { const symmetricBodies: Map = new Map(); bodies.forEach((body: MapUnit, id: number) => { - if (!this.onSymmetricLine(body.loc, width, height)) { + if (!this.onSymmetricLine(body.x, body.y, width, height)) { const type = body.type; const teamID = body.teamID === undefined? 0 : body.teamID; + const newLoc = this.transformLoc(body.x, body.y, width, height); symmetricBodies.set(id, { - loc: this.transformLoc(body.loc, width, height), + x: newLoc.x, + y: newLoc.y, radius: body.radius, type: type, teamID: this.flipTeamID(teamID), @@ -151,4 +155,40 @@ export default class SymmetryForm { return symmetricBodies; } + + /** + * Given a list of units and passability, finds a compatible symmetry. + */ + discoverSymmetryAndBodies(mapUnits: MapUnit[], passability: number[], width: number, height: number): {symmetry: Symmetry, originalBodies: Map} | null { + for (const symmetry of this.SYMMETRY_OPTIONS) { + + var possible: boolean = true; + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + const newLoc = this.transformLocStatic(x, y, width, height, symmetry); + if (passability[y * width + x] !== passability[newLoc.y * width + newLoc.x]) { + possible = false; + } + } + } + + const originalBodies = new Map(); + const matched = new Array(originalBodies.size); + var id = 1; + for (let i = 0; i < mapUnits.length; i++) { + if (matched[i]) continue; + const unit1 = mapUnits[i]; + const newLoc = this.transformLocStatic(unit1.x, unit1.y, width, height, symmetry); + for (let j = i; j < mapUnits.length; j++) { + const unit2 = mapUnits[j]; + if (unit2.x == newLoc.x && unit2.y == newLoc.y) { + originalBodies.set(id++, unit1); + matched[i] = matched[j] = true; + } + } + } + if (possible) return {symmetry: symmetry, originalBodies: originalBodies} + } + return null; + } } diff --git a/client/visualizer/src/mapeditor/index.ts b/client/visualizer/src/mapeditor/index.ts index e262f644..17f8090c 100644 --- a/client/visualizer/src/mapeditor/index.ts +++ b/client/visualizer/src/mapeditor/index.ts @@ -1,5 +1,6 @@ import {MapUnit} from './form'; import MapGenerator from './action/generator'; +import {UploadedMap}from './action/generator'; import MapRenderer from './action/renderer'; import MapValidator from './action/validator'; @@ -12,6 +13,6 @@ import {GameMap} from './form'; import MapEditorForm from './form'; import MapEditor from './mapeditor'; -export {MapGenerator, MapUnit, MapRenderer, MapValidator} +export {MapGenerator, MapUnit, MapRenderer, MapValidator, UploadedMap} export {HeaderForm, RobotForm, Symmetry, SymmetryForm, TileForm} export {GameMap, MapEditorForm, MapEditor}; diff --git a/client/visualizer/src/mapeditor/mapeditor.ts b/client/visualizer/src/mapeditor/mapeditor.ts index 38ac2433..30c858a6 100644 --- a/client/visualizer/src/mapeditor/mapeditor.ts +++ b/client/visualizer/src/mapeditor/mapeditor.ts @@ -7,7 +7,7 @@ import {electron} from '../main/electron-modules'; import {schema, flatbuffers} from 'battlecode-playback'; import Victor = require('victor'); -import {MapUnit, MapValidator, MapGenerator, MapEditorForm, GameMap} from './index'; +import {MapUnit, MapValidator, MapGenerator, MapEditorForm, GameMap, UploadedMap} from './index'; import { env } from 'process'; /** @@ -52,9 +52,10 @@ export default class MapEditor { // TODO // div.appendChild(this.removeInvalidButton()); div.appendChild(this.resetButton()); - div.appendChild(document.createElement("br")); - div.appendChild(this.getMapJSONButton()); - div.appendChild(this.pasteMapJSONButton()); + //div.appendChild(document.createElement("br")); + // div.appendChild(this.getMapJSONButton()); + // div.appendChild(this.pasteMapJSONButton()); + div.appendChild(this.importMapButton()); div.appendChild(document.createElement("br")); div.appendChild(this.exportButton()); @@ -167,6 +168,7 @@ export default class MapEditor { const button = document.createElement("button"); button.type = "button"; button.className = 'form-button custom-button'; + button.style.backgroundColor = "mediumslateblue"; button.appendChild(document.createTextNode("Reset Map")); button.onclick = () => { this.form.reset(); @@ -174,38 +176,67 @@ export default class MapEditor { return button; } - private getMapJSONButton(): HTMLButtonElement { - const button = document.createElement("button"); - button.type = "button"; - button.className = 'form-button custom-button'; - button.appendChild(document.createTextNode(process.env.ELECTRON ? "Copy Map JSON to Clipboard" : "Get Map JSON")); - button.onclick = () => { - // from https://stackoverflow.com/questions/17591559/how-to-copy-text-of-alert-box - if (!process.env.ELECTRON) { - const newWin = window.open(); - if (newWin) { - newWin.document.write(this.form.getMapJSON()); - newWin.document.close(); - } - } - else { - // prompt("Copy to clipboard: Ctrl+C, Enter", this.form.getMapJSON()); - electron.clipboard.writeText(this.form.getMapJSON()); + // private getMapJSONButton(): HTMLButtonElement { + // const button = document.createElement("button"); + // button.type = "button"; + // button.className = 'form-button custom-button'; + // button.appendChild(document.createTextNode(process.env.ELECTRON ? "Copy Map JSON to Clipboard" : "Get Map JSON")); + // button.onclick = () => { + // // from https://stackoverflow.com/questions/17591559/how-to-copy-text-of-alert-box + // if (!process.env.ELECTRON) { + // const newWin = window.open(); + // if (newWin) { + // newWin.document.write(this.form.getMapJSON()); + // newWin.document.close(); + // } + // } + // else { + // // prompt("Copy to clipboard: Ctrl+C, Enter", this.form.getMapJSON()); + // electron.clipboard.writeText(this.form.getMapJSON()); + // } + // }; + // return button; + // } + + // private pasteMapJSONButton(): HTMLButtonElement { + // const button = document.createElement("button"); + // button.type = "button"; + // button.className = 'form-button custom-button'; + // button.appendChild(document.createTextNode(process.env.ELECTRON ? "Input Map JSON from Clipboard" : "Input Map JSON")); + // button.onclick = () => { + // if (!process.env.ELECTRON) this.form.setMap(prompt("Paste Map JSON: Ctrl+V, Enter")); + // else this.form.setMap(electron.clipboard.readText()); + // }; + // return button; + // } + + private importMapButton() { + let uploadLabel = document.createElement("label"); + uploadLabel.setAttribute("for", "file-upload"); + uploadLabel.setAttribute("class", "custom-button"); + uploadLabel.innerText = 'Upload a .map21 file'; + uploadLabel.style.backgroundColor = "mediumslateblue"; // TODO: move to CSS + // create the functional button + let upload = document.createElement('input'); + upload.textContent = 'upload'; + upload.id = "file-upload"; + upload.setAttribute('type', 'file'); + upload.accept = '.map21'; + upload.onchange = () => { + if (upload.files) { + const reader = new FileReader(); + reader.onload = () => { + const map: UploadedMap = MapGenerator.readMap(reader.result); + console.log(map); + this.form.setUploadedMap(map); + }; + reader.readAsArrayBuffer(upload.files[0]); } - }; - return button; - } + } + upload.onclick = () => upload.value = ""; + uploadLabel.appendChild(upload); - private pasteMapJSONButton(): HTMLButtonElement { - const button = document.createElement("button"); - button.type = "button"; - button.className = 'form-button custom-button'; - button.appendChild(document.createTextNode(process.env.ELECTRON ? "Input Map JSON from Clipboard" : "Input Map JSON")); - button.onclick = () => { - if (!process.env.ELECTRON) this.form.setMap(prompt("Paste Map JSON: Ctrl+V, Enter")); - else this.form.setMap(electron.clipboard.readText()); - }; - return button; + return uploadLabel; } private exportButton(): HTMLButtonElement { diff --git a/client/visualizer/src/runner.ts b/client/visualizer/src/runner.ts index 5dc761ca..76349474 100644 --- a/client/visualizer/src/runner.ts +++ b/client/visualizer/src/runner.ts @@ -14,8 +14,9 @@ import WebSocketListener from './main/websocket'; // import { electron } from './main/electron-modules'; import { TeamStats } from 'battlecode-playback/out/gameworld'; -import { Tournament, readTournament } from './main/tournament'; +import { Tournament, readTournament } from './main/tournament_new'; import Looper from './main/looper'; +import Sidebar from './main/sidebar'; /** @@ -27,9 +28,11 @@ export default class Runner { private matchqueue: MatchQueue; private controls: Controls; private stats: Stats; - private gamearea: GameArea; + private gamearea: GameArea; private console: Console; private profiler?: Profiler; + private asyncRequests: XMLHttpRequest[] = []; + private sidebar: Sidebar; looper: Looper | null; // Match logic @@ -39,6 +42,7 @@ export default class Runner { tournament?: Tournament; tournamentState: TournamentState; + showTourneyUpload: boolean = true; currentGame: number | null; currentMatch: number | null; @@ -58,8 +62,9 @@ export default class Runner { /** * Marks the client as fully loaded. */ - ready(controls: Controls, stats: Stats, gamearea: GameArea, - cconsole: Console, matchqueue: MatchQueue, profiler?: Profiler) { + ready(controls: Controls, stats: Stats, gamearea: GameArea, + cconsole: Console, matchqueue: MatchQueue, sidebar: Sidebar, + profiler?: Profiler) { this.controls = controls; this.stats = stats; @@ -67,80 +72,19 @@ export default class Runner { this.console = cconsole; this.matchqueue = matchqueue; this.profiler = profiler; + this.sidebar = sidebar; this.gamearea.setCanvas(); - let toMain = (msg) => { - console.log(msg); - alert('Error occurred. Check console on your browser'); - // window.location.assign('/visualizer.html'); - } - if (this.conf.tournamentMode) { this.conf.processLogs = false; // in tournament mode, don't process logs by default } this.console.setNotLoggingDiv(); if (this.conf.matchFileURL) { - // Load a match file console.log(`Loading provided match file: ${this.conf.matchFileURL}`); - - const req = new XMLHttpRequest(); - req.open('GET', this.conf.matchFileURL, true); - req.responseType = 'arraybuffer'; - req.onerror = (error) => { - toMain(`Can't load provided match file: ${error}`); - }; - - req.onload = (event) => { - const resp = req.response; - if (!resp || req.status !== 200) { - toMain(`Can't load file from URL: invalid URL(${req.status})`); - } - else { - let lastGame = this.games.length - this.games[lastGame] = new Game(this.conf); - - try { - this.games[lastGame].loadFullGameRaw(resp); - } catch (error) { - toMain(`Can't get file from URL: ${error}`); - return; - } - - console.log('Successfully loaded provided match file'); - this.startGame(); - } - }; - - req.send(); + this.loadGameFromURL(this.conf.matchFileURL); } - // not loading default match file - // else { - // console.log('Starting with a default match file (/client/default.bc21)'); - // if(_fs.readFile){ - // _fs.readFile('../default.bc21', (err, data: ArrayBuffer) => { - // if(err){ - // console.log('Error while loading default local file!'); - // console.log(err); - // console.log('Starting without any match files. Please upload via upload button in queue tab of sidebar'); - // return; - // } - - // let lastGame = this.games.length - // this.games[lastGame] = new Game(); - // // lastGame should be 0? - // try { - // this.games[lastGame].loadFullGameRaw(data); - // } catch (error) { - // console.log(`Error occurred! ${error}`); - // } - - // console.log('Running game!'); - // startGame(); - // }); - // } - // } if (this.listener != null) { this.listener.start( @@ -230,12 +174,38 @@ export default class Runner { } } - onGameLoaded(data: ArrayBuffer) { - let lastGame = this.games.length - this.games[lastGame] = new Game(this.conf); - this.games[lastGame].loadFullGameRaw(data); + loadGameFromURL(url: string) { + // Load a match file + const req = new XMLHttpRequest(); + req.open('GET', url, true); + req.responseType = 'arraybuffer'; + req.onerror = (error) => { + throw new Error(`Can't load provided match file: ${error}`); + }; + + req.onload = (event) => { + const resp = req.response; + if (!resp || req.status !== 200) { + throw new Error(`Can't load file from URL: invalid URL(${req.status})`); + } + else { + this.onGameLoaded(resp); + } + }; - this.startGame(); + req.send(); + this.asyncRequests.push(req); + } + + onGameLoaded(data: ArrayBuffer) { + try { + const newGame = new Game(this.conf); + newGame.loadFullGameRaw(data); + this.games.push(newGame); + this.startGame(); + } catch { + throw new Error("game load failed."); + } }; startGame() { @@ -246,53 +216,43 @@ export default class Runner { this.matchqueue.refreshGameList(this.games, this.currentGame ? this.currentGame : 0, this.currentMatch ? this.currentMatch : 0); } - onTournamentLoaded(jsonFile: File) { - if (!process.env.ELECTRON) { - console.error("Can't load tournament outside of electron!"); - return; - } - readTournament(jsonFile, (err, tournament) => { - if (err) { - console.error(`Can't load tournament: ${err}`); - return; - } - if (tournament) { - this.tournament = tournament; - const t = this; - document.onkeydown = function (event) { - // TODO: figure out what this is??? - if (document.activeElement == null) { - throw new Error('idk?????? i dont know what im doing document.actievElement is null??'); - } - let input = document.activeElement.nodeName == "INPUT"; - if (!input) { - // TODO after touching viewoption buttons, the input (at least arrow keys) does not work - console.log(event.keyCode); - switch (event.keyCode) { - case 65: // "a" - previous tournament Match - t.previousTournamentThing(); - t.updateTournamentState(); - break; - case 68: // 'd' - next tournament match - console.log('next tournament d!'); - t.nextTournamentThing(); - t.updateTournamentState(); - break; - } - } + removeGame(game: number) { - }; - // CHOOSE STARTING ROUND? - tournament.seek(0, 0); - this.tournamentState = TournamentState.START_SPLASH; - this.updateTournamentState(); + if (game > (this.currentGame as number)) { + this.games.splice(game, 1); + } else if (this.currentGame == game) { + if (game == 0) { + // if games.length > 1, remove game, set game to 0, set match to 0 + if (this.games.length > 1) { + this.setGame(0); + this.games.splice(game, 1); + } else { + this.resetAllGames(); + } + } else { + this.setGame(game - 1); + this.games.splice(game, 1); } - }); + } else { + // remove game, set game to game - 1 + this.games.splice(game, 1); + this.currentGame = game - 1; + } + + this.showBlankCanvas(); + + this.matchqueue.refreshGameList(this.games, this.currentGame ? this.currentGame : 0, this.currentMatch ? this.currentMatch : 0); }; - goNextMatch() { - console.log("NEXT MATCH"); + showBlankCanvas() { + if (this.games.length == 0) { + this.conf.splash = true; + this.gamearea.setCanvas(); + } + } + + goNextMatch() { if (this.currentGame as number < 0) { return; // Special case when deleting games } @@ -310,8 +270,6 @@ export default class Runner { } goPreviousMatch() { - console.log("PREV MATCH"); - if (this.currentMatch as number > 0) { this.setMatch(this.currentMatch as number - 1); } else { @@ -324,64 +282,48 @@ export default class Runner { }; - seekTournament (num: number) { + onTournamentLoaded(jsonFile: File) { + readTournament(jsonFile, (tournament) => { + this.tournament = tournament; + + // Choose starting round + tournament.seek(0, 0); + this.tournamentState = TournamentState.START_SPLASH; + this.processTournamentState(); + }, (err) => { + console.error(`Can't load tournament: ${err}`); + return; + }); + }; + + seekTournament(num: number) { console.log('seek tournament'); this.tournament?.seek(num, 0); - this.updateTournamentState(); + this.processTournamentState(); }; - removeGame(game: number) { - - if (game > (this.currentGame as number)) { - this.games.splice(game, 1); - } else if (this.currentGame == game) { - if (game == 0) { - // if games.length > 1, remove game, set game to 0, set match to 0 - if (this.games.length > 1) { - this.setGame(0); - this.games.splice(game, 1); - } else { - this.games.splice(game, 1); - if (this.looper) { - this.looper.die(); - this.looper = null; - } - this.currentGame = -1; - this.currentMatch = 0; - } - } else { - this.setGame(game - 1); - this.games.splice(game, 1); - } - } else { - // remove game, set game to game - 1 - this.games.splice(game, 1); - this.currentGame = game - 1; - } - - if (this.games.length == 0) { - this.conf.splash = true; - this.gamearea.setCanvas(); - } - + resetAllGames() { + if (this.looper) this.looper.die(); + this.games = []; + this.currentGame = -1; + this.currentMatch = 0; this.matchqueue.refreshGameList(this.games, this.currentGame ? this.currentGame : 0, this.currentMatch ? this.currentMatch : 0); - }; + this.asyncRequests.forEach((req) => req.abort()); + } - private nextTournamentThing() { + /** + * Transitions tournament state from start splash, to mid game, + * to end splash (only if last match in game), to next game. + */ + private nextTournamentState() { console.log('actually next tournament thing!'); - // either displays a splash screen with who won, or displays the next game - // activated by some sort of hotkey, ideally - // so, we can be in a couple of different states - // either a splash screen is showing, in which case we should display the next game, - // or a game is showing, in which case we should display either a next game or a splash screen - if (this.tournament) { if (this.tournamentState === TournamentState.START_SPLASH) { // transition to mid game this.tournamentState = TournamentState.MID_GAME; } else if (this.tournamentState === TournamentState.MID_GAME) { // go to the next game - if (this.tournament.hasNext() && !this.tournament.isLastGameInMatch()) { + if (this.tournament.hasNext() && !this.tournament.isLastMatchInGame()) { this.tournament.next(); } else { // go to end splash @@ -400,13 +342,7 @@ export default class Runner { } } - private previousTournamentThing() { - // either displays a splash screen with who won, or displays the next game - // activated by some sort of hotkey, ideally - // so, we can be in a couple of different states - // either a splash screen is showing, in which case we should display the next game, - // or a game is showing, in which case we should display either a next game or a splash screen - + private previousTournamentState() { if (this.tournament) { if (this.tournamentState === TournamentState.START_SPLASH) { // transition to mid game @@ -418,7 +354,7 @@ export default class Runner { } } else if (this.tournamentState === TournamentState.MID_GAME) { // go to the previous game - if (this.tournament.hasPrev() && !this.tournament.isFirstGameInMatch()) { + if (this.tournament.hasPrev() && !this.tournament.isFirstMatchInGame()) { this.tournament.prev(); } else { // go to start splash @@ -432,31 +368,33 @@ export default class Runner { } } - private updateTournamentState() { + private processTournamentState() { console.log('update tour state!'); if (this.tournament) { console.log('real update tour state!'); // clear things Splash.removeScreen(); - if (this.looper) this.looper.die(); // simply updates according to the current tournament state + this.resetAllGames(); + this.showBlankCanvas(); if (this.tournamentState === TournamentState.START_SPLASH) { console.log('go from splash real update tour state!'); - Splash.addScreen(this.conf, this.root, this.tournament.current(), this.tournament.currentMatch(), this.tournament); + Splash.addScreen(this.conf, this.root, this.tournament.current().team1, this.tournament.current().team2); } else if (this.tournamentState === TournamentState.END_SPLASH) { - Splash.addWinnerScreen(this.conf, this.root, this.tournament, this.tournament.currentMatch()); + const wins = this.tournament.wins(); + const totalWins = this.tournament.totalWins(); + let result: string = ""; + const team1 = this.tournament.current().team1; + const team2 = this.tournament.current().team2; + if (wins[team1] > wins[team2]) result = `${team1} wins ${wins[team1]}-${wins[team2]}!`; + else result = `${team2} wins ${wins[team2]}-${wins[team1]}!`; + if (totalWins[team1] != wins[team1] || totalWins[team2] != wins[team2]) { + if (totalWins[team1] > totalWins[team2]) result += ` (Final score ${totalWins[team1]}-${totalWins[team2]})`; + else result += ` (Final score ${totalWins[team2]}-${totalWins[team1]})`; + } + Splash.addWinnerScreen(this.conf, this.root, result); } else if (this.tournamentState === TournamentState.MID_GAME) { - this.tournament.readCurrent((err, data) => { - if (err) throw err; - if (!data) throw new Error("No match loaded from tournament?"); - - // reset all games so as to save memory - // because things can be rough otherwise - this.games.pop(); - this.games = [new Game(this.conf)]; - this.games[0].loadFullGameRaw(data); - this.setGame(0); - }); + this.loadGameFromURL(this.tournament.current().url); } } } @@ -475,16 +413,26 @@ export default class Runner { } const game = this.games[this.currentGame as number] as Game; - const match = game.getMatch(this.currentMatch as number) as Match; + const match = game.getMatch(this.currentMatch as number) as Match; const meta = game.meta as Metadata; if (this.looper) this.looper.die(); + var infoString = ""; + + if (this.tournament) { + const wins = this.tournament.wins(this.tournament.matchI - 1); + infoString += `Map: ${this.tournament?.current().map}`; + infoString += `
`; + infoString += `Score: ${wins[this.tournament.current().team1]} - ${wins[this.tournament.current().team2]}`; + } + this.looper = new Looper(match, meta, this.conf, this.imgs, - this.controls, this.stats, this.gamearea, this.console, this.matchqueue, this.profiler); - - // if (this.profiler) - // this.profiler.load(match); + this.controls, this.stats, this.gamearea, this.console, this.matchqueue, this.profiler, infoString, + this.showTourneyUpload); + + // if (this.profiler) + // this.profiler.load(match); } readonly onkeydown = (event: KeyboardEvent) => { @@ -536,9 +484,9 @@ export default class Runner { break; case 77: // "m" - Toggle sensor radius this.conf.seeSensorRadius = !this.conf.seeSensorRadius; - break; + break; case 188: // "," - Toggle detection radius - this.conf.seeDetectionRadius = !this.conf.seeDetectionRadius; + this.conf.seeDetectionRadius = !this.conf.seeDetectionRadius; break; case 71: // "g" - Toogle grid view this.conf.showGrid = !this.conf.showGrid; @@ -548,24 +496,30 @@ export default class Runner { this.console.updateLogHeader(); break; case 65: // "a" - previous tournament Match - this.previousTournamentThing(); - this.updateTournamentState(); + this.previousTournamentState(); + this.processTournamentState(); break; case 68: // 'd' - next tournament match - this.nextTournamentThing(); - this.updateTournamentState(); + this.nextTournamentState(); + this.processTournamentState(); + break; + case 219: // '[' - hide sidebar + this.sidebar.hidePanel(); + this.stats.hideTourneyUpload(); + this.showTourneyUpload = !this.showTourneyUpload; break; case 76: // 'l' - Toggle process logs this.conf.processLogs = !this.conf.processLogs; this.console.setNotLoggingDiv(); break; case 81: // 'q' - Toggle profiler - console.log(this.profiler); if (this.profiler) { this.conf.doProfiling = !this.conf.doProfiling; this.profiler.setNotProfilingDiv(); } break; + case 90: // 'z' - Toggle rotate + this.conf.doRotate = !this.conf.doRotate; } } diff --git a/client/visualizer/src/sidebar/mapfilter.ts b/client/visualizer/src/sidebar/mapfilter.ts index f351b1ef..1d31feb8 100644 --- a/client/visualizer/src/sidebar/mapfilter.ts +++ b/client/visualizer/src/sidebar/mapfilter.ts @@ -20,9 +20,6 @@ export default class MapFilter { private readonly filterName: HTMLInputElement; private readonly filterType: Map; - // Map types available (NOTE: Update after each tournament) - private readonly types: MapType[] = [MapType.DEFAULT, MapType.CUSTOM, MapType.SPRINT_1]; - // All the maps displayed on the client private maps: Array; @@ -111,7 +108,7 @@ export default class MapFilter { this.filterName.onchange = () => { this.applyFilter() }; // Filter for map type - for (let type of this.types) { + for (let type of cst.mapTypes) { const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.value = String(type); @@ -148,10 +145,8 @@ export default class MapFilter { case MapType.DEFAULT: return "Default"; case MapType.SPRINT_1: return "Sprint 1"; case MapType.SPRINT_2: return "Sprint 2"; - case MapType.INTL_QUALIFYING: return "Intl Quals"; - case MapType.US_QUALIFYING: return "US Quals"; - case MapType.HS: return "HS"; - case MapType.NEWBIE: return "Newbie"; + case MapType.QUALIFYING: return "Quals"; + case MapType.HS_NEWBIE: return "HS and Newbie"; case MapType.FINAL: return "Final"; default: return "Custom"; } diff --git a/client/visualizer/src/sidebar/stats.ts b/client/visualizer/src/sidebar/stats.ts index 18fe2c51..3932d611 100644 --- a/client/visualizer/src/sidebar/stats.ts +++ b/client/visualizer/src/sidebar/stats.ts @@ -3,6 +3,7 @@ import * as cst from '../constants'; import { AllImages } from '../imageloader'; import { schema } from 'battlecode-playback'; import Runner from '../runner'; +import Chart = require('chart.js'); const hex: Object = { 1: "#db3627", @@ -11,7 +12,8 @@ const hex: Object = { type VoteBar = { bar: HTMLDivElement, - label: HTMLSpanElement + vote: HTMLSpanElement, + bid: HTMLSpanElement }; type BuffDisplay = { @@ -19,6 +21,10 @@ type BuffDisplay = { buff: HTMLSpanElement } +type IncomeDisplay = { + income: HTMLSpanElement +} + /** * Loads game stats: team name, votes, robot count * We make the distinction between: @@ -34,22 +40,37 @@ export default class Stats { private readonly tourIndexJump: HTMLInputElement; + private teamNameNodes: HTMLSpanElement[] = []; + // Key is the team ID + private robotImages: Map> = new Map(); // the robot image elements in the unit statistics display private robotTds: Map>> = new Map(); private voteBars: VoteBar[]; private maxVotes: number; - private relativeBars: HTMLDivElement[]; + private incomeDisplays: IncomeDisplay[]; + private relativeBars: HTMLDivElement[]; + private buffDisplays: BuffDisplay[]; - + + private extraInfo: HTMLDivElement; + private robotConsole: HTMLDivElement; - + private runner: Runner; //needed for file uploading in tournament mode - + private conf: Config; + private tourneyUpload: HTMLDivElement; + + private incomeChart: Chart; + + private ECs: HTMLDivElement; + + private teamMapToTurnsIncomeSet: Map>; + // Note: robot types and number of teams are currently fixed regardless of // match info. Keep in mind if we ever change these, or implement this less // statically. @@ -59,6 +80,12 @@ export default class Stats { constructor(conf: Config, images: AllImages, runner: Runner) { this.conf = conf; this.images = images; + + for (const robot in this.images.robots) { + let robotImages: Array = this.images.robots[robot]; + this.robotImages[robot] = robotImages.map((image) => image.cloneNode() as HTMLImageElement); + } + this.div = document.createElement("div"); this.tourIndexJump = document.createElement("input"); this.runner = runner; @@ -75,9 +102,11 @@ export default class Stats { let teamHeader: HTMLDivElement = document.createElement("div"); teamHeader.className += ' teamHeader'; - let teamNameNode = document.createTextNode(teamName); + let teamNameNode = document.createElement('span'); + teamNameNode.innerHTML = teamName; teamHeader.style.backgroundColor = hex[inGameID]; teamHeader.appendChild(teamNameNode); + this.teamNameNodes[inGameID] = teamNameNode; return teamHeader; } @@ -108,10 +137,12 @@ export default class Stats { let robotName: string = cst.bodyTypeToString(robot); let tdRobot: HTMLTableCellElement = document.createElement("td"); tdRobot.className = "robotSpriteStats"; + tdRobot.style.height = "100px"; + tdRobot.style.width = "100px"; - const img = this.images.robots[robotName][inGameID]; - img.style.width = "64px"; - img.style.height = "64px"; + const img: HTMLImageElement = this.robotImages[robotName][inGameID]; + img.style.width = "60%"; + img.style.height = "60%"; tdRobot.appendChild(img); robotImages.appendChild(tdRobot); @@ -119,6 +150,10 @@ export default class Stats { for (let value in this.robotTds[teamID]) { let tdCount: HTMLTableCellElement = this.robotTds[teamID][value][robot]; robotCounts[value].appendChild(tdCount); + if (robot === schema.BodyType.ENLIGHTENMENT_CENTER && value === "count") { + tdCount.style.fontWeight = "bold"; + tdCount.style.fontSize = "18px"; + } } } table.appendChild(robotImages); @@ -136,46 +171,68 @@ export default class Stats { votes.className = "stat-bar"; votes.style.backgroundColor = hex[teamID]; let votesSpan = document.createElement("span"); + let bidSpan = document.createElement("span"); votesSpan.innerHTML = "0"; + bidSpan.innerHTML = "0"; // Store the stat bars voteBars[teamID] = { bar: votes, - label: votesSpan + vote: votesSpan, + bid: bidSpan }; }); return voteBars; } - private getVoteBarElement(teamIDs: Array): HTMLTableElement { - const table = document.createElement("table"); - const bars = document.createElement("tr"); - const counts = document.createElement("tr"); - table.id = "stats-table"; - bars.id = "stats-bars"; - table.setAttribute("align", "center"); + private getVoteBarElement(teamIDs: Array): HTMLElement { + const votesDiv = document.createElement('div'); - const title = document.createElement('td'); - title.colSpan = 2; - const label = document.createElement('h3'); - label.innerText = 'Votes'; + const box = document.createElement('div'); + box.className = "votes-box"; + + const title = document.createElement('div'); + title.className = "stats-header"; + + const bars = document.createElement('div'); + bars.id = "vote-bars"; + bars.appendChild(document.createElement('div')); + + const votes = document.createElement('div'); + votes.className = "votes-info"; + const bids = document.createElement('div'); + bids.className = "votes-info"; + + title.innerHTML = "Voting"; + + const votesTitle = document.createElement('div'); + votesTitle.innerHTML = "Votes"; + votes.appendChild(votesTitle); + + const bidsTitle = document.createElement('div'); + bidsTitle.innerHTML = "Bid"; + bids.appendChild(bidsTitle); + + // build table teamIDs.forEach((id: number) => { - const bar = document.createElement("td"); - bar.height = "120"; - bar.vAlign = "bottom"; - bar.appendChild(this.voteBars[id].bar); - bars.appendChild(bar); - - const count = document.createElement("td"); - count.appendChild(this.voteBars[id].label); - counts.appendChild(count); + + const vote = document.createElement('div'); + vote.appendChild(this.voteBars[id].vote); + votes.appendChild(vote); + + const bid = document.createElement('div'); + bid.appendChild(this.voteBars[id].bid); + bids.appendChild(bid); + + bars.appendChild(this.voteBars[id].bar); }); - title.appendChild(label); - table.appendChild(title); - table.appendChild(bars); - table.appendChild(counts); - return table; + votesDiv.appendChild(title); + box.appendChild(bids); + box.appendChild(votes); + box.appendChild(bars); + votesDiv.appendChild(box); + return votesDiv; } private initRelativeBars(teamIDs: Array) { @@ -195,12 +252,14 @@ export default class Stats { private getRelativeBarsElement(teamIDs: Array): HTMLElement { const div = document.createElement("div"); div.setAttribute("align", "center"); + div.id = "relative-bars"; - const label = document.createElement('h3'); + const label = document.createElement('div'); + label.className = "stats-header"; label.innerText = 'Total Influence'; const frame = document.createElement("div"); - frame.style.width = "250px"; + frame.style.width = "90%"; teamIDs.forEach((id: number) => { frame.appendChild(this.relativeBars[id]); @@ -220,30 +279,64 @@ export default class Stats { buff.style.color = hex[id]; buff.style.fontWeight = "bold"; numBuffs.textContent = "0"; - buff.textContent = "1.00"; + buff.textContent = "1.000"; buffDisplays[id] = {numBuffs: numBuffs, buff: buff}; }); return buffDisplays; } + private initIncomeDisplays(teamIDs: Array) { + const incomeDisplays: IncomeDisplay[] = []; + teamIDs.forEach((id: number) => { + const income = document.createElement("span"); + income.style.color = hex[id]; + income.style.fontWeight = "bold"; + income.textContent = "1"; + incomeDisplays[id] = {income: income}; + }); + return incomeDisplays; + } private getBuffDisplaysElement(teamIDs: Array): HTMLElement { + const div = document.createElement("div"); + div.id = "buffs"; + + const label = document.createElement('div'); + label.className = "stats-header"; + label.innerText = 'Buffs'; + div.appendChild(label); + + teamIDs.forEach((id: number) => { + const buffDiv = document.createElement("div"); + buffDiv.className = "buff-div"; + // cell.appendChild(document.createTextNode("1.001")); + // cell.appendChild(this.buffDisplays[id].numBuffs); + // cell.appendChild(document.createTextNode(" = ")); + buffDiv.appendChild(this.buffDisplays[id].buff); + div.appendChild(buffDiv); + }); + + return div; + } + + private getIncomeDisplaysElement(teamIDs: Array): HTMLElement { const table = document.createElement("table"); - table.id = "buffs-table"; + table.id = "income-table"; table.style.width = "100%"; const title = document.createElement('td'); title.colSpan = 2; - const label = document.createElement('h3'); - label.innerText = 'Buffs'; + const label = document.createElement('div'); + label.className = "stats-header"; + label.innerText = 'Total Income Per Turn'; const row = document.createElement("tr"); teamIDs.forEach((id: number) => { const cell = document.createElement("td"); - cell.appendChild(document.createTextNode("1.001")); - cell.appendChild(this.buffDisplays[id].numBuffs); - cell.appendChild(document.createTextNode(" = ")); - cell.appendChild(this.buffDisplays[id].buff); + // cell.appendChild(document.createTextNode("1.001")); + // cell.appendChild(this.buffDisplays[id].numBuffs); + // cell.appendChild(document.createTextNode(" = ")); + cell.appendChild(this.incomeDisplays[id].income); row.appendChild(cell); }); @@ -254,6 +347,22 @@ export default class Stats { return table; } + private getIncomeDominationGraph() { + const canvas = document.createElement("canvas"); + canvas.id = "myChart"; + return canvas; + } + + private getECDivElement() { + const div = document.createElement('div'); + const label = document.createElement('div'); + label.className = "stats-header"; + label.innerText = 'EC Control'; + div.appendChild(label); + div.appendChild(this.ECs); + return div; + } + // private drawBuffsGraph(ctx: CanvasRenderingContext2D, upto: number) { // ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // // draw axes @@ -311,23 +420,33 @@ export default class Stats { } this.voteBars = []; this.relativeBars = []; - this.maxVotes = 1500; + this.maxVotes = 750; + this.teamMapToTurnsIncomeSet = new Map(); + this.div.appendChild(document.createElement("br")); if (this.conf.tournamentMode) { // FOR TOURNAMENT + this.tourneyUpload = document.createElement('div'); + let uploadButton = this.runner.getUploadButton(); let tempdiv = document.createElement("div"); tempdiv.className = "upload-button-div"; tempdiv.appendChild(uploadButton); - this.div.appendChild(tempdiv); + this.tourneyUpload.appendChild(tempdiv); // add text input field this.tourIndexJump.type = "text"; this.tourIndexJump.onkeyup = (e) => { this.tourIndexJumpFun(e) }; this.tourIndexJump.onchange = (e) => { this.tourIndexJumpFun(e) }; - this.div.appendChild(this.tourIndexJump); + this.tourneyUpload.appendChild(this.tourIndexJump); + + this.div.appendChild(this.tourneyUpload); } + this.extraInfo = document.createElement('div'); + this.extraInfo.className = "extra-info"; + this.div.appendChild(this.extraInfo); + // Populate with new info // Add a section to the stats bar for each team in the match for (var index = 0; index < teamIDs.length; index++) { @@ -354,17 +473,12 @@ export default class Stats { // Add the team name banner and the robot count table teamDiv.appendChild(this.teamHeaderNode(teamName, inGameID)); teamDiv.appendChild(this.robotTable(teamID, inGameID)); - teamDiv.appendChild(document.createElement("br")); this.div.appendChild(teamDiv); } this.div.appendChild(document.createElement("hr")); - // TODO relative bar - // this.div.appendChild(this.relativeBarElement); - // console.log(this.relativeBarElement) - // Add stats table this.voteBars = this.initVoteBars(teamIDs); const voteBarsElement = this.getVoteBarElement(teamIDs); @@ -377,6 +491,60 @@ export default class Stats { this.buffDisplays = this.initBuffDisplays(teamIDs); const buffDivsElement = this.getBuffDisplaysElement(teamIDs); this.div.appendChild(buffDivsElement); + + this.incomeDisplays = this.initIncomeDisplays(teamIDs); + const incomeElement = this.getIncomeDisplaysElement(teamIDs); + this.div.appendChild(incomeElement); + + const canvasElement = this.getIncomeDominationGraph(); + this.div.appendChild(canvasElement); + this.incomeChart = new Chart(canvasElement, { + type: 'line', + data: { + datasets: [{ + label: 'Red', + data: [], + backgroundColor: 'rgba(255, 99, 132, 0)', + borderColor: 'rgb(219, 54, 39)', + pointRadius: 0, + }, + { + label: 'Blue', + data: [], + backgroundColor: 'rgba(54, 162, 235, 0)', + borderColor: 'rgb(79, 126, 230)', + pointRadius: 0, + }] + }, + options: { + aspectRatio: 1.5, + scales: { + xAxes: [{ + type: 'linear', + ticks: { + beginAtZero: true + }, + scaleLabel: { + display: true, + labelString: "Turn" + } + }], + yAxes: [{ + type: 'linear', + ticks: { + beginAtZero: true + } + }] + } + } + }); + + this.ECs = document.createElement("div"); + this.ECs.style.height = "100px"; + this.ECs.style.display = "flex"; + this.div.appendChild(this.getECDivElement()); + + this.div.appendChild(document.createElement("br")); } tourIndexJumpFun(e) { @@ -397,9 +565,16 @@ export default class Stats { /** * Change the robot conviction on the stats bar */ - setRobotConviction(teamID: number, robotType: schema.BodyType, conviction: number) { + setRobotConviction(teamID: number, robotType: schema.BodyType, conviction: number, totalConviction: number) { let td: HTMLTableCellElement = this.robotTds[teamID]["conviction"][robotType]; td.innerHTML = String(conviction); + + const robotName: string = cst.bodyTypeToString(robotType); + let img = this.robotImages[robotName][teamID]; + + const size = (55 + 45 * conviction / totalConviction); + img.style.width = size + "%"; + img.style.height = size + "%"; } /** @@ -416,9 +591,9 @@ export default class Stats { setVotes(teamID: number, count: number) { // TODO: figure out if statbars.get(id) can actually be null?? const statBar: VoteBar = this.voteBars[teamID]; - statBar.label.innerText = String(count); + statBar.vote.innerText = String(count); this.maxVotes = Math.max(this.maxVotes, count); - statBar.bar.style.height = `${Math.min(100 * count / this.maxVotes, 100)}%`; + statBar.bar.style.width = `${Math.min(100 * count / this.maxVotes, 100)}%`; // TODO add reactions to relative bars // TODO get total votes to get ratio @@ -438,7 +613,71 @@ export default class Stats { } setBuffs(teamID: number, numBuffs: number) { - this.buffDisplays[teamID].numBuffs.textContent = String(numBuffs); - this.buffDisplays[teamID].buff.textContent = String(cst.buffFactor(numBuffs).toFixed(2)); + //this.buffDisplays[teamID].numBuffs.textContent = String(numBuffs); + this.buffDisplays[teamID].buff.textContent = String(cst.buffFactor(numBuffs).toFixed(3)); + this.buffDisplays[teamID].buff.style.fontSize = 14 * Math.sqrt(Math.min(9, cst.buffFactor(numBuffs))) + "px"; + } + + setIncome(teamID: number, income: number, turn: number) { + this.incomeDisplays[teamID].income.textContent = String(income); + if (!this.teamMapToTurnsIncomeSet.has(teamID)) { + this.teamMapToTurnsIncomeSet.set(teamID, new Set()); + } + let teamTurnsIncomeSet = this.teamMapToTurnsIncomeSet.get(teamID); + + if (!teamTurnsIncomeSet!.has(turn)) { + //@ts-ignore + this.incomeChart.data.datasets![teamID - 1].data?.push({y:income, x: turn}); + this.incomeChart.data.datasets?.forEach((d) => { + d.data?.sort((a, b) => a.x - b.x); + }); + teamTurnsIncomeSet?.add(turn); + this.incomeChart.update(); + } + } + + setWinner(teamID: number, teamNames: Array, teamIDs: Array) { + const name = teamNames[teamIDs.indexOf(teamID)]; + this.teamNameNodes[teamID].innerHTML = "" + name + " " + `🌟`; + } + + setBid(teamID: number, bid: number) { + // TODO: figure out if statbars.get(id) can actually be null?? + const statBar: VoteBar = this.voteBars[teamID]; + statBar.bid.innerText = String(bid); + // TODO add reactions to relative bars + // TODO get total votes to get ratio + // this.relBars[teamID].width; + + // TODO winner gets star? + // if (this.images.star.parentNode === statBar.bar) { + // this.images.star.remove(); + // } + } + + setExtraInfo(info: string) { + this.extraInfo.innerHTML = info; + } + + hideTourneyUpload() { + console.log(this.tourneyUpload); + this.tourneyUpload.style.display = this.tourneyUpload.style.display === "none" ? "" : "none"; + } + + resetECs() { + // while (this.ECs.lastChild) this.ECs.removeChild(this.ECs.lastChild); + // console.log(this.ECs); + this.ECs.innerHTML = ""; + } + + addEC(teamID: number) { + const div = document.createElement("div"); + div.style.width = "35px"; + div.style.height = "35px"; + const img = this.images.robots["enlightenmentCenter"][teamID].cloneNode() as HTMLImageElement; + img.style.width = "64px"; + img.style.height = "64px"; // update dynamically later + div.appendChild(img); + this.ECs.appendChild(div); } } diff --git a/client/visualizer/src/static/css/style.css b/client/visualizer/src/static/css/style.css index 1b6d3789..9cd53dec 100644 --- a/client/visualizer/src/static/css/style.css +++ b/client/visualizer/src/static/css/style.css @@ -174,12 +174,17 @@ input[type='file'] { width: 100%; } +/* TODO: rename this class */ .not-logging-div { color: red; margin-top: 0.5em; font-size: 14px; } +.extra-info { + font-size: 17px; +} + .custom-button:active, button:target { background-color: #8c8c8c; } @@ -187,13 +192,6 @@ input[type='file'] { outline: none; } -.influence-bar { - height: 30px; - line-height: 30px; - color: white; - min-width: fit-content; -} - .info { font-size: 11px; line-height: 20px; @@ -220,11 +218,11 @@ input[type='file'] { } #gamearea { - width: calc(100vw - 330px); + width: calc(100vw - 405px); height: calc(100vh - 75px); position: fixed; top: 75px; - left: 330px; + left: 405px; background: rgb(39, 39, 39); } @@ -292,7 +290,7 @@ input, select { #baseDiv { width: 100%; height: 65px; - margin-left: 335px; + margin-left: 410px; position: fixed; z-index: 0.5; top: 0; @@ -310,6 +308,17 @@ input, select { display: inline-block; } +.stats-header { + margin: 8px auto 8px auto; + width: fit-content; + font-size: 1.17em; + font-weight: bold; + padding: 5px; + border-style: solid; + border-radius: 5px; + border-width: 1px; +} + h4 { margin: 10px; } @@ -325,7 +334,7 @@ h4 { #sidebar { /* Positioning */ height: 100%; - width: 325px; + width: 400px; position: fixed; z-index: 1; top: 0; @@ -554,25 +563,54 @@ h4 { overflow: hidden; } -#stats-table { +.votes-box { + display: flex; + padding-right: 5px; + padding-left: 5px; +} + +.votes-box > div { + margin-left: 5px; + margin-right: 5px; +} + +.votes-box > div > div { + height: 25px; + line-height: 25px; +} + +#vote-bars { width: 100%; } -#stats-table td { - width: 25%; - padding: .2em; +#relative-bars { + width: 50%; + float: left; +} + +.influence-bar { + height: 30px; + line-height: 30px; + color: white; + min-width: fit-content; } #buffs-table { border-collapse: collapse; } -.stat-bar { - height: 100%; - max-width: 50%; - margin: auto; +#buffs { + width: 50%; + float: left; +} + +.buff-div { + height: 30px; + line-height: 30px } + + #star { margin-top: 1em; width: 40px; diff --git a/client/visualizer/src/static/css/tournament.css b/client/visualizer/src/static/css/tournament.css index c4845796..fb950086 100644 --- a/client/visualizer/src/static/css/tournament.css +++ b/client/visualizer/src/static/css/tournament.css @@ -5,7 +5,8 @@ top: 0px; left: 0px; - background-color: black; + background-color: rebeccapurple; + opacity: 100%; color: white; font-size: 50px; text-align: center; @@ -14,8 +15,7 @@ .blackout-container { margin-top: 10vh; - margin-left: 16vw; - margin-right: 16vw; + height: 100% } .tournament-header { @@ -61,3 +61,46 @@ font-size: 40px; margin-bottom: 32px; } + +#team1, #team2, #versus, #winner { + position: absolute; + width: fit-content; + text-align: center; + font-size: 80px; + font-family: "Luminari"; + transform: translateX(-50%); +} + +#team1 { + top: 36%; + left: 35%; + font-weight: bold; + transform: translateX(-1000px); + animation: slide-in 0.6s forwards; + -webkit-animation: slide-in 0.6s forwards; +} + +#team2 { + top: 54%; + left: 65%; + font-weight: bold; + transform: translateX(1000px); + animation: slide-in 0.6s forwards; + -webkit-animation: slide-in 0.6s forwards; +} + +#versus { + top: 45%; + left: 50%; + color: orange; +} + +#winner { + top: 45%; + left: 50%; + font-weight: bold; +} + +@keyframes slide-in { + 100% { transform: translateX(-50%); } +} \ No newline at end of file diff --git a/engine/src/main/battlecode/common/GameConstants.java b/engine/src/main/battlecode/common/GameConstants.java index 552b420f..a9d18d7e 100644 --- a/engine/src/main/battlecode/common/GameConstants.java +++ b/engine/src/main/battlecode/common/GameConstants.java @@ -53,7 +53,7 @@ public class GameConstants { public static final int EMPOWER_TAX = 10; /** The buff factor from exposing Slanderers. */ - public static final double EXPOSE_BUFF_FACTOR = 1.001; + public static final double EXPOSE_BUFF_FACTOR = 0.001; /** The number of rounds a buff is applied. */ public static final int EXPOSE_BUFF_NUM_ROUNDS = 50; diff --git a/engine/src/main/battlecode/world/GameWorld.java b/engine/src/main/battlecode/world/GameWorld.java index 372e73cf..b4ff29ae 100644 --- a/engine/src/main/battlecode/world/GameWorld.java +++ b/engine/src/main/battlecode/world/GameWorld.java @@ -410,6 +410,7 @@ public void processEndOfRound() { // Didn't win. If didn't place bid, halfBid == 0 int halfBid = (highestBids[i] + 1) / 2; highestBidders[i].addInfluenceAndConviction(-halfBid); + teamBidderIDs[i] = highestBidders[i].getID(); } } diff --git a/engine/src/main/battlecode/world/InternalRobot.java b/engine/src/main/battlecode/world/InternalRobot.java index c4f811a5..772e94ae 100644 --- a/engine/src/main/battlecode/world/InternalRobot.java +++ b/engine/src/main/battlecode/world/InternalRobot.java @@ -1,6 +1,5 @@ package battlecode.world; -import java.util.Arrays; import java.util.ArrayList; import battlecode.common.*; import battlecode.schema.Action; @@ -365,27 +364,36 @@ public void empower(int radiusSquared) { int numBots = robots.length - 1; // excluding self if (numBots == 0) return; - - long convictionToGive = (long) (((long) this.conviction) * this.gameWorld.getTeamInfo().getBuff(this.team)); - convictionToGive -= GameConstants.EMPOWER_TAX; + + double convictionToGive = this.conviction - GameConstants.EMPOWER_TAX; if (convictionToGive <= 0) return; - - long convictionPerBot = convictionToGive / numBots; - long numBotsWithExtraConviction = convictionToGive % numBots; - Arrays.sort(robots); + final double convictionPerBot = convictionToGive / numBots; + final double buff = this.gameWorld.getTeamInfo().getBuff(this.team); + for (InternalRobot bot : robots) { // check if this robot if (bot.equals(this)) continue; - long conv = convictionPerBot; - if (numBotsWithExtraConviction > 0) { - conv++; - numBotsWithExtraConviction--; + + double conv = convictionPerBot; + if (bot.type == RobotType.ENLIGHTENMENT_CENTER && bot.team == this.team) { + // conviction doesn't get buffed, do nothing + } else if (bot.type == RobotType.ENLIGHTENMENT_CENTER) { + // complicated stuff + double convNeededToConvert = bot.conviction / buff; + if (conv <= convNeededToConvert) { + // all of conviction is buffed + conv *= buff; + } else { + // conviction buffed until conversion + conv = bot.conviction + (conv - convNeededToConvert); + } + } else { + // buff applied, cast down + conv *= buff; } - // HACK[jerry]: this is the maximum amount the unit can be affected by - conv = Math.min(conv, GameConstants.ROBOT_INFLUENCE_LIMIT * 2); bot.empowered(this, (int) conv, this.team); } @@ -412,10 +420,10 @@ public void empower(int radiusSquared) { * Called when a Politician Empowers. * If conviction becomes negative, the robot switches teams or is destroyed. * - * @param amount the amount this robot is empowered by, must be positive + * @param amount the amount this robot's conviction changes by (including all buffs) * @param newTeam the team of the robot that empowered */ - public void empowered(InternalRobot caller, int amount, Team newTeam) { + private void empowered(InternalRobot caller, int amount, Team newTeam) { if (this.team != newTeam) amount = -amount; diff --git a/engine/src/main/battlecode/world/TeamInfo.java b/engine/src/main/battlecode/world/TeamInfo.java index 1de17c40..045536a9 100644 --- a/engine/src/main/battlecode/world/TeamInfo.java +++ b/engine/src/main/battlecode/world/TeamInfo.java @@ -35,13 +35,13 @@ public int getVotes(Team t) { // returns current buff public double getBuff(Team t) { - return Math.pow(GameConstants.EXPOSE_BUFF_FACTOR, this.numBuffs[t.ordinal()]); + return 1 + GameConstants.EXPOSE_BUFF_FACTOR * this.numBuffs[t.ordinal()]; } // returns the buff at specified round public double getBuff(Team t, int roundNumber) { int buffs = getNumBuffs(t, roundNumber); - return Math.pow(GameConstants.EXPOSE_BUFF_FACTOR, buffs); + return 1 + GameConstants.EXPOSE_BUFF_FACTOR * buffs; } // returns the number of buffs at specified round diff --git a/engine/src/main/battlecode/world/resources/AmidstWe.map21 b/engine/src/main/battlecode/world/resources/AmidstWe.map21 new file mode 100644 index 00000000..39397b34 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/AmidstWe.map21 differ diff --git a/engine/src/main/battlecode/world/resources/BadSnowflake.map21 b/engine/src/main/battlecode/world/resources/BadSnowflake.map21 new file mode 100644 index 00000000..60e9b68c Binary files /dev/null and b/engine/src/main/battlecode/world/resources/BadSnowflake.map21 differ diff --git a/engine/src/main/battlecode/world/resources/BattleCode.map21 b/engine/src/main/battlecode/world/resources/BattleCode.map21 new file mode 100644 index 00000000..3fb82d09 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/BattleCode.map21 differ diff --git a/engine/src/main/battlecode/world/resources/BattleCodeToo.map21 b/engine/src/main/battlecode/world/resources/BattleCodeToo.map21 new file mode 100644 index 00000000..919a5324 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/BattleCodeToo.map21 differ diff --git a/engine/src/main/battlecode/world/resources/BlobWithLegs.map21 b/engine/src/main/battlecode/world/resources/BlobWithLegs.map21 new file mode 100644 index 00000000..961cc076 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/BlobWithLegs.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Blotches.map21 b/engine/src/main/battlecode/world/resources/Blotches.map21 new file mode 100644 index 00000000..5e1f052f Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Blotches.map21 differ diff --git a/engine/src/main/battlecode/world/resources/ButtonsAndBows.map21 b/engine/src/main/battlecode/world/resources/ButtonsAndBows.map21 new file mode 100644 index 00000000..46d00d56 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/ButtonsAndBows.map21 differ diff --git a/engine/src/main/battlecode/world/resources/CToE.map21 b/engine/src/main/battlecode/world/resources/CToE.map21 new file mode 100644 index 00000000..93ad6959 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/CToE.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Circles.map21 b/engine/src/main/battlecode/world/resources/Circles.map21 new file mode 100644 index 00000000..75c416f2 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Circles.map21 differ diff --git a/engine/src/main/battlecode/world/resources/CowTwister.map21 b/engine/src/main/battlecode/world/resources/CowTwister.map21 new file mode 100644 index 00000000..71bd2d5a Binary files /dev/null and b/engine/src/main/battlecode/world/resources/CowTwister.map21 differ diff --git a/engine/src/main/battlecode/world/resources/CringyAsF.map21 b/engine/src/main/battlecode/world/resources/CringyAsF.map21 new file mode 100644 index 00000000..d29fa78b Binary files /dev/null and b/engine/src/main/battlecode/world/resources/CringyAsF.map21 differ diff --git a/engine/src/main/battlecode/world/resources/EggCarton.map21 b/engine/src/main/battlecode/world/resources/EggCarton.map21 new file mode 100644 index 00000000..ff30301b Binary files /dev/null and b/engine/src/main/battlecode/world/resources/EggCarton.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Extensions.map21 b/engine/src/main/battlecode/world/resources/Extensions.map21 new file mode 100644 index 00000000..d303b7cf Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Extensions.map21 differ diff --git a/engine/src/main/battlecode/world/resources/FindYourWay.map21 b/engine/src/main/battlecode/world/resources/FindYourWay.map21 new file mode 100644 index 00000000..5f57d0b6 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/FindYourWay.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Flawars.map21 b/engine/src/main/battlecode/world/resources/Flawars.map21 new file mode 100644 index 00000000..b3363dc4 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Flawars.map21 differ diff --git a/engine/src/main/battlecode/world/resources/FrogOrBath.map21 b/engine/src/main/battlecode/world/resources/FrogOrBath.map21 new file mode 100644 index 00000000..83d70e55 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/FrogOrBath.map21 differ diff --git a/engine/src/main/battlecode/world/resources/GetShrekt.map21 b/engine/src/main/battlecode/world/resources/GetShrekt.map21 new file mode 100644 index 00000000..f6677ff2 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/GetShrekt.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Goldfish.map21 b/engine/src/main/battlecode/world/resources/Goldfish.map21 new file mode 100644 index 00000000..116a291d Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Goldfish.map21 differ diff --git a/engine/src/main/battlecode/world/resources/HappyBoba.map21 b/engine/src/main/battlecode/world/resources/HappyBoba.map21 new file mode 100644 index 00000000..9ebb2dd4 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/HappyBoba.map21 differ diff --git a/engine/src/main/battlecode/world/resources/HexesAndOhms.map21 b/engine/src/main/battlecode/world/resources/HexesAndOhms.map21 new file mode 100644 index 00000000..6616991f Binary files /dev/null and b/engine/src/main/battlecode/world/resources/HexesAndOhms.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Hourglass.map21 b/engine/src/main/battlecode/world/resources/Hourglass.map21 new file mode 100644 index 00000000..ec2d0cf8 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Hourglass.map21 differ diff --git a/engine/src/main/battlecode/world/resources/InaccurateBritishFlag.map21 b/engine/src/main/battlecode/world/resources/InaccurateBritishFlag.map21 new file mode 100644 index 00000000..5471ebac Binary files /dev/null and b/engine/src/main/battlecode/world/resources/InaccurateBritishFlag.map21 differ diff --git a/engine/src/main/battlecode/world/resources/JerryIsEvil.map21 b/engine/src/main/battlecode/world/resources/JerryIsEvil.map21 new file mode 100644 index 00000000..6c51af46 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/JerryIsEvil.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Legends.map21 b/engine/src/main/battlecode/world/resources/Legends.map21 new file mode 100644 index 00000000..d83ae96b Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Legends.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Licc.map21 b/engine/src/main/battlecode/world/resources/Licc.map21 new file mode 100644 index 00000000..9c4e687b Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Licc.map21 differ diff --git a/engine/src/main/battlecode/world/resources/MainCampus.map21 b/engine/src/main/battlecode/world/resources/MainCampus.map21 new file mode 100644 index 00000000..83cc6917 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/MainCampus.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Mario.map21 b/engine/src/main/battlecode/world/resources/Mario.map21 new file mode 100644 index 00000000..ed70fbfb Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Mario.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Maze.map21 b/engine/src/main/battlecode/world/resources/Maze.map21 new file mode 100644 index 00000000..98abcdfa Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Maze.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Misdirection.map21 b/engine/src/main/battlecode/world/resources/Misdirection.map21 new file mode 100644 index 00000000..a42e9982 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Misdirection.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Networking.map21 b/engine/src/main/battlecode/world/resources/Networking.map21 new file mode 100644 index 00000000..56b6b022 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Networking.map21 differ diff --git a/engine/src/main/battlecode/world/resources/NextHouse.map21 b/engine/src/main/battlecode/world/resources/NextHouse.map21 new file mode 100644 index 00000000..8ba4153b Binary files /dev/null and b/engine/src/main/battlecode/world/resources/NextHouse.map21 differ diff --git a/engine/src/main/battlecode/world/resources/NoInternet.map21 b/engine/src/main/battlecode/world/resources/NoInternet.map21 new file mode 100644 index 00000000..8ac6d8dc Binary files /dev/null and b/engine/src/main/battlecode/world/resources/NoInternet.map21 differ diff --git a/engine/src/main/battlecode/world/resources/OneCallAway.map21 b/engine/src/main/battlecode/world/resources/OneCallAway.map21 new file mode 100644 index 00000000..3af0bf05 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/OneCallAway.map21 differ diff --git a/engine/src/main/battlecode/world/resources/PaperWindmill.map21 b/engine/src/main/battlecode/world/resources/PaperWindmill.map21 new file mode 100644 index 00000000..2893370c Binary files /dev/null and b/engine/src/main/battlecode/world/resources/PaperWindmill.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Punctuation.map21 b/engine/src/main/battlecode/world/resources/Punctuation.map21 new file mode 100644 index 00000000..8c88dcc0 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Punctuation.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Radial.map21 b/engine/src/main/battlecode/world/resources/Radial.map21 new file mode 100644 index 00000000..277f066d Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Radial.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Randomized.map21 b/engine/src/main/battlecode/world/resources/Randomized.map21 new file mode 100644 index 00000000..85fff6ea Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Randomized.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Saturn.map21 b/engine/src/main/battlecode/world/resources/Saturn.map21 new file mode 100644 index 00000000..b7e2a4b2 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Saturn.map21 differ diff --git a/engine/src/main/battlecode/world/resources/SeaFloor.map21 b/engine/src/main/battlecode/world/resources/SeaFloor.map21 new file mode 100644 index 00000000..520c6f40 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/SeaFloor.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Sediment.map21 b/engine/src/main/battlecode/world/resources/Sediment.map21 new file mode 100644 index 00000000..8dbef3a6 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Sediment.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Smile.map21 b/engine/src/main/battlecode/world/resources/Smile.map21 new file mode 100644 index 00000000..9edc9464 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Smile.map21 differ diff --git a/engine/src/main/battlecode/world/resources/SpaceInvaders.map21 b/engine/src/main/battlecode/world/resources/SpaceInvaders.map21 new file mode 100644 index 00000000..55fe333e Binary files /dev/null and b/engine/src/main/battlecode/world/resources/SpaceInvaders.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Star.map21 b/engine/src/main/battlecode/world/resources/Star.map21 new file mode 100644 index 00000000..6a5bdb15 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Star.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Stonks.map21 b/engine/src/main/battlecode/world/resources/Stonks.map21 new file mode 100644 index 00000000..57a895b7 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Stonks.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Superposition.map21 b/engine/src/main/battlecode/world/resources/Superposition.map21 new file mode 100644 index 00000000..27db7d44 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Superposition.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Surprised.map21 b/engine/src/main/battlecode/world/resources/Surprised.map21 new file mode 100644 index 00000000..53afb5d5 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Surprised.map21 differ diff --git a/engine/src/main/battlecode/world/resources/TheClientMapEditorIsSuperiorToGoogleSheetsEom.map21 b/engine/src/main/battlecode/world/resources/TheClientMapEditorIsSuperiorToGoogleSheetsEom.map21 new file mode 100644 index 00000000..0edd00a2 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/TheClientMapEditorIsSuperiorToGoogleSheetsEom.map21 differ diff --git a/engine/src/main/battlecode/world/resources/TheSnackThatSmilesBack.map21 b/engine/src/main/battlecode/world/resources/TheSnackThatSmilesBack.map21 new file mode 100644 index 00000000..871eeae4 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/TheSnackThatSmilesBack.map21 differ diff --git a/engine/src/main/battlecode/world/resources/TicTacTie.map21 b/engine/src/main/battlecode/world/resources/TicTacTie.map21 new file mode 100644 index 00000000..73c51f30 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/TicTacTie.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Tiger.map21 b/engine/src/main/battlecode/world/resources/Tiger.map21 new file mode 100644 index 00000000..940fe8dd Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Tiger.map21 differ diff --git a/engine/src/main/battlecode/world/resources/UnbrandedWordGame.map21 b/engine/src/main/battlecode/world/resources/UnbrandedWordGame.map21 new file mode 100644 index 00000000..2cee0cc6 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/UnbrandedWordGame.map21 differ diff --git a/engine/src/main/battlecode/world/resources/VideoGames.map21 b/engine/src/main/battlecode/world/resources/VideoGames.map21 new file mode 100644 index 00000000..dc687338 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/VideoGames.map21 differ diff --git a/engine/src/main/battlecode/world/resources/WhatISeeInMyDreams.map21 b/engine/src/main/battlecode/world/resources/WhatISeeInMyDreams.map21 new file mode 100644 index 00000000..d4ebb3a3 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/WhatISeeInMyDreams.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Yoda.map21 b/engine/src/main/battlecode/world/resources/Yoda.map21 new file mode 100644 index 00000000..a095fd59 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Yoda.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Z.map21 b/engine/src/main/battlecode/world/resources/Z.map21 new file mode 100644 index 00000000..a50ad91c Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Z.map21 differ diff --git a/engine/src/main/battlecode/world/resources/Zodiac.map21 b/engine/src/main/battlecode/world/resources/Zodiac.map21 new file mode 100644 index 00000000..791db140 Binary files /dev/null and b/engine/src/main/battlecode/world/resources/Zodiac.map21 differ diff --git a/frontend/deploy.sh b/frontend/deploy.sh index d887baa5..a7914791 100755 --- a/frontend/deploy.sh +++ b/frontend/deploy.sh @@ -1,3 +1,5 @@ +# TODO check presence of 2nd argument (version number) + if [ "$1" == "deploy" ] then echo "WARNING: Do you really want to deploy with the game? This SHOULD NEVER BE DONE before the game is released to the world, since this means that the game specs and the visualizer become public." @@ -15,11 +17,45 @@ then # esac # done echo "Proceding with deploy!" + + # TODO check git status + # (on master, up-to-date) + + rm -r public/specs mkdir public/specs - cp ../specs public -r - npm install + cp -r ../specs public + + rm -r public/javadoc + cd .. + # Assumes version as second arg to deploy script + ./gradlew release_docs_zip -Prelease_version=$2 + mv battlecode-javadoc-$2.zip javadoc.zip + unzip -d javadoc javadoc.zip + rm javadoc.zip + mkdir frontend/public/javadoc + mv javadoc frontend/public + cd frontend + # Gets generated somewhere in the javadoc process; is better not to have, to ensure that a different bucket (the battleaccess bucket) always holds this. + rm public/version.txt + + rm -r public/out + cd ../client + cd playback + npm install + npm run build + cd ../visualizer + npm install + npm run prod + mkdir ../../frontend/public/out + cp -r out ../../frontend/public + cd ../../frontend + + npm install npm run build - rm public/specs -r + + rm -r public/specs + rm -r public/javadoc + rm -r public/out cd build gsutil -m rm gs://battlecode21-frontend/** gsutil -m cp -r * gs://battlecode21-frontend diff --git a/frontend/public/visualizer.html b/frontend/public/visualizer.html index 6a44cc17..b2a70b63 100644 --- a/frontend/public/visualizer.html +++ b/frontend/public/visualizer.html @@ -23,12 +23,12 @@ match_url = ""; } } + var config = {websocketURL: "ws://localhost:6175"}; + if (match_url) config.matchFileURL = match_url; + if (tournamentMode) config.tournamentMode = tournamentMode; window.battleClient = window.battlecode.mount( document.getElementById('client'), - { - matchFileURL: match_url, - tournamentMode: tournamentMode - } + config ); diff --git a/frontend/src/api.js b/frontend/src/api.js index 4bc2ac27..efef8064 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -431,7 +431,7 @@ class Api { } static resumeUpload(resume_file, callback) { - $.get(`${Cookies.get('user_url')}resume_upload/`, (data, succcess) => { + $.post(`${Cookies.get('user_url')}resume_upload/`, (data, succcess) => { $.ajax({ url: data['upload_url'], method: "PUT", @@ -592,9 +592,9 @@ class Api { static getNextTournament(callback) { // TODO: actually use real API for this callback({ - "est_date_str": '7 PM ET on January 18, 2021', - "seconds_until": (Date.parse(new Date('January 18, 2021 19:00:00-5:00')) - Date.parse(new Date())) / 1000, - "tournament_name": "Sprint Tournament 2" + "est_date_str": '7 PM ET on January 28, 2021', + "seconds_until": (Date.parse(new Date('January 28, 2021 19:00:00-5:00')) - Date.parse(new Date())) / 1000, + "tournament_name": "Final Tournament" }); // callback({ // "est_date_str": '8 PM EDT on April 22, 2020', diff --git a/frontend/src/components/rankingTeamList.js b/frontend/src/components/rankingTeamList.js index ed3f5023..1ff800b9 100644 --- a/frontend/src/components/rankingTeamList.js +++ b/frontend/src/components/rankingTeamList.js @@ -51,7 +51,7 @@ class RankingTeamList extends TeamList { { team.name } { team.users.join(", ") } { team.bio } - { team.student ? "✅" : "🛑"}{(team.student && team.mit) ? "🐥" : ""} + { team.student ? "✅" : "🛑"}{(team.student && team.international) ? "🌍" : "🇺🇸"}{(team.student && team.mit) ? "🐥" : ""}{(team.student && team.high_school) ? "HS" : ""} { team.auto_accept_unranked ? "Yes" : "No"} {props.canRequest && ( diff --git a/frontend/src/views/account.js b/frontend/src/views/account.js index a21152e2..f17523ba 100644 --- a/frontend/src/views/account.js +++ b/frontend/src/views/account.js @@ -114,11 +114,14 @@ class Account extends Component {

Edit Profile

+
+ Make sure to press the "Update Info" button, and wait for confirmation! +
- +
diff --git a/frontend/src/views/submissions.js b/frontend/src/views/submissions.js index 38f5489e..42780d5f 100755 --- a/frontend/src/views/submissions.js +++ b/frontend/src/views/submissions.js @@ -158,10 +158,10 @@ class Submissions extends Component { } else { switch (key) { case 'tour_sprint': - add_data = ['Sprint', data] + add_data = ['Sprint 1', data] break case 'tour_seed': - add_data = ['Seeding', data] + add_data = ['Sprint 2', data] break case 'tour_qual': add_data = ['Qualifying', data] @@ -170,7 +170,7 @@ class Submissions extends Component { add_data = ['Final', data] break case 'tour_hs': - add_data = ['High School', data] + add_data = ['US High School', data] break case 'tour_intl_qual': add_data = ['International Qualifying', data] @@ -242,7 +242,11 @@ class Submissions extends Component {
{/* TODO could this paragraph be dynamically filled? that'd be amazing */}

- The submission deadline for Sprint Tour 2 is 7 pm ET on Monday 1/18. Submit your code using the button below. For peace of mind, submit 15 minutes before and make sure it compiles and shows up under "Latest Submissions." + The submission deadline for the Final Tournament is 7 pm ET on Thursday 1/28. Make sure to have indicated your eligibility on your Team Profile page. Also make sure to have all members upload a resume, at your personal profile page. + **See the Eligibility Rules in the Tournaments page for more info** +

+

+ Submit your code using the button below. For peace of mind, submit 15 minutes before and make sure it compiles and shows up under "Latest Submissions." We will have a 5-minute grace period; if you're having trouble submitting, send us your code on Discord before 7:05. If the code you submit to us on Discord has only minor differences to the code submitted on time through the website (e.g., 1 or 2 lines), we will accept it. We will not accept anything submitted after 7:05 pm.

diff --git a/frontend/src/views/team.js b/frontend/src/views/team.js index 77d57720..1710d82b 100755 --- a/frontend/src/views/team.js +++ b/frontend/src/views/team.js @@ -102,7 +102,7 @@ class YesTeam extends Component {

{/* */}

We need to know a little about your team in order to determine which prizes your team is eligible for. - Check all boxes that apply to your team. + Check all boxes that apply to all members your team.

@@ -295,6 +295,7 @@ class ResumeStatus extends Component { } // pass change handler in props.change and team in props.team +// NOTE: If you are ever working with teams' eligility (for example, to pull teams for the newbie tournament), please see backend/docs/ELIGIBILITY.md before you do anything! The variable names here are poorly named (because columns in the database are poorly named). class EligibiltyOptions extends Component { render() { return ( @@ -309,7 +310,7 @@ class EligibiltyOptions extends Component {
- {/*
+
@@ -317,25 +318,25 @@ class EligibiltyOptions extends Component { -
*/} +
- + -

Look it up! (If you don't know, you probably aren't one...)

} showCloseButton={true}> +

Teams consisting entirely of MIT students who have never competed in Battlecode before are eligible for the Newbie Tournament.

} showCloseButton={true}>
- {/*
+
-

Teams of only high school (and earlier) students are eligible for the High School Tournament.

} showCloseButton={true}> +

Teams of only high school (and earlier) students are eligible for the US High School Tournament. (Note that you must also be all US students to be eligible -- if you're all US students, don't forget to check that box, too!)

} showCloseButton={true}> -
*/} + diff --git a/frontend/src/views/tournaments.js b/frontend/src/views/tournaments.js index c68935f8..9764dfcd 100644 --- a/frontend/src/views/tournaments.js +++ b/frontend/src/views/tournaments.js @@ -67,7 +67,7 @@ class Tournaments extends Component { Newbie Tournament: 1/28. The top newbie teams compete for a smaller prize pool. The final match between the top 2 teams will be run at the Final Tournament.
  • - High School Tournament: 1/28. The top high school teams compete for a smaller prize pool. Like the Newbie Tournament, the final match will be run at the Final Tournament. + US High School Tournament: 1/28. The top US high school teams compete for a smaller prize pool. Like the Newbie Tournament, the final match will be run at the Final Tournament.
  • Final Tournament: 1/30. The top 16 teams, as determined by the qualifying tournaments, compete for glory, fame and a big prize pool. The tournament will take place live, and will be streamed online for 2021. There will not be a component on MIT campus this year. @@ -85,12 +85,41 @@ class Tournaments extends Component {

    Tournament Results

    -

    Congratulations to the winners!

    +

    View tournament brackets here:

    +

    Congratulations to our prizewinning teams!

    +
    +{`                                         1st      $5000  babyducks
    +                                         2nd      $3000  Producing Perfection
    +                                         3rd      $2500  Chicken
    +                                         4th      $1500  Malott Fat Cats
    +                                         5-6th    $1250  Kryptonite
    +                                         5-6th    $1250  monky
    +                                         7-8th    $1000  Nikola
    +                                         7-8th    $1000  wololo
    +                                         9-12th    $750  3 Musketeers
    +                                         9-12th    $750  Chop Suey
    +                                         9-12th    $750  confused
    +                                         9-12th    $750  smite
    +                                         13-16th   $500  BattlePath
    +                                         13-16th   $500  Bytecode Mafia
    +                                         13-16th   $500  GoreTeks
    +                                         13-16th   $500  waffle
    +Most adaptive strategy (sponsored by Five Rings)  $1500  Chop Suey
    +                      Sprint Tournament 1 Winner   $500  Super Cow Powers
    +                      Sprint Tournament 2 Winner   $500  Super Cow Powers
    +                                 High School 1st   $500  idrc
    +                                 High School 2nd   $200  java :ghosthug:
    +                                      Newbie 1st   $500  Dis Team
    +                                      Newbie 2nd   $200  $nowball`}
    @@ -125,7 +154,7 @@ class Tournaments extends Component { Thanks to our gold sponsor, Five Rings!
    • 1st Place prize: to whosoever has the highest rating at the end (hacks not allowed). Smaller prizes for subsequent placers.
    • -
    • Smaller prizes for top placers in other non-final (newbie, high school, sprint) tournaments.
    • +
    • Smaller prizes for top placers in other non-final (newbie, US high school, sprint) tournaments.
    • More prizes??? TBA, maybe 👀
      • Historically, we have given out prizes for creative strategies, major bugs found, and other game-specific topics. Have fun with your strategies, write-ups, and overall participation in Battlecode!
      • @@ -144,7 +173,40 @@ class Tournaments extends Component {

        - Anyone is welcome to participate in Battlecode! Anyone can write a bot, create a team and participate in the tournament. More eligibility details can be found here. + Anyone is welcome to participate in Battlecode! Anyone can write a bot, create a team, and participate in matches and the Sprint Tournaments. +

        +

        Your team must meet all three conditions to be eligible for the Qualifying and Final tournaments by the submission deadline: +

          +
        1. + Have uploaded a bot +
        2. +
        3. + Have indicated your eligibility on your Team Profile page +
        4. +
        5. + Have all members upload a resume, at your personal profile page. +
        6. +
        +

        +

        As a reminder, the tournament divisions are: +

          +
        • + Full-time US teams, consisting entirely of US students studying full-time, or in a transition phase. We may ask for some documentation to verify your student status if you advance to the finals. The top 12 teams in this division will earn a place out of 16 final tournament spots; eligibility is conditioned on attendance to our virtual finalists celebration on the evening of Friday 1/29. +
        • +
        • + Full-time international teams, consisting entirely of students studying full-time, or in a transition phase, where at least one team member is not a US student. We may ask for some documentation to verify your student status if you advance to the finals. The top 4 teams in this division will earn a place out of 16 final tournament spots. +
        • +
        • + US High-school teams, consisting entirely of high school students in the US. The top 2 teams will have the final match played during the final tournament. +
        • +
        • + Newbie teams, consisting entirely of MIT students who have never competed in Battlecode before. The top 2 teams will have their final match played during the final tournament. +
        • +
        +

        + +

        + More eligibility details can be found here.

        diff --git a/gradle.properties b/gradle.properties index a9e59d71..1565098d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,4 +5,4 @@ maps=maptestsmall profilerEnabled=false source=src mapLocation=maps -release_version=2021.2.4.1 +release_version=2021.3.0.5 diff --git a/infrastructure/matcher/maps.json b/infrastructure/matcher/maps.json index 555d3545..7ccb7c7e 100644 --- a/infrastructure/matcher/maps.json +++ b/infrastructure/matcher/maps.json @@ -1,22 +1,20 @@ { "Round 1 (Winners)": [ - "maptestsmall", - "quadrants" + "circle","maptestsmall","quadrants" ], "Round 1 (Losers)": [ - "maptestsmall", - "quadrants" + "maptestsmall","quadrants","circle" ], "Round 2 (Winners)": [ - "circle" + "quadrants","circle","maptestsmall" ], "Round 2 (Losers A)": [ - "circle" + "circle","maptestsmall","quadrants" ], "Round 3": [ - "circle" + "maptestsmall","quadrants","circle" ], "Round 4 (if needed)": [ - "circle" + "quadrants","circle","maptestsmall" ] -} \ No newline at end of file +} diff --git a/infrastructure/matcher/replay_dump.json b/infrastructure/matcher/replay_dump.json new file mode 100644 index 00000000..9a0740e6 --- /dev/null +++ b/infrastructure/matcher/replay_dump.json @@ -0,0 +1,117 @@ +[ + [ + [ + "testteam the redux", + "Teh Dev", + "circle", + 2, + "ed18b8d6df89457a9a4f3316590a04" + ], + [ + "Teh Dev", + "testteam the redux", + "maptestsmall", + 1, + "e1f9d9799f0c92c69739f414db3806" + ], + [ + "testteam the redux", + "Teh Dev", + "quadrants", + 2, + "5e04919b30c410882412355e229ec9" + ] + ], + [ + [ + "asdfsddtjggyftawehjjhsrsfghgsdf", + "Teh Dev", + "quadrants", + 2, + "94cfdd55bad5bdc12361e30590cab5" + ], + [ + "Teh Dev", + "asdfsddtjggyftawehjjhsrsfghgsdf", + "circle", + 1, + "9f9fa4b3c968ad5d6b0ca9a9d50206" + ], + [ + "asdfsddtjggyftawehjjhsrsfghgsdf", + "Teh Dev", + "maptestsmall", + 2, + "e74caebeae5c2a8fcdda321b3e7674" + ] + ], + [ + [ + "asdfsddtjggyftawehjjhsrsfghgsdf", + "testteam the redux", + "circle", + 1, + "69a7d58c6deb7702424a90dcd3f6f4" + ], + [ + "testteam the redux", + "asdfsddtjggyftawehjjhsrsfghgsdf", + "maptestsmall", + 2, + "8f791ed3a1886383344cc6c3935674" + ], + [ + "asdfsddtjggyftawehjjhsrsfghgsdf", + "testteam the redux", + "quadrants", + 1, + "98dece93751195cd749ec873af5b76" + ] + ], + [ + [ + "Teh Dev", + "asdfsddtjggyftawehjjhsrsfghgsdf", + "maptestsmall", + 1, + "3c1bf7ea8946dd41be747ae9d50d61" + ], + [ + "asdfsddtjggyftawehjjhsrsfghgsdf", + "Teh Dev", + "quadrants", + 2, + "66e34f96783bff4766c586b3712613" + ], + [ + "Teh Dev", + "asdfsddtjggyftawehjjhsrsfghgsdf", + "circle", + 1, + "7f3ce4410ad81f6148ced409a39249" + ] + ], + [ + [ + "Teh Dev", + "asdfsddtjggyftawehjjhsrsfghgsdf", + "quadrants", + 1, + "efecbea6cc0c417e1a705c67f6194d" + ], + [ + "asdfsddtjggyftawehjjhsrsfghgsdf", + "Teh Dev", + "circle", + 2, + "943863b22850b31aef653e8b95bade" + ], + [ + "Teh Dev", + "asdfsddtjggyftawehjjhsrsfghgsdf", + "maptestsmall", + 1, + "5325b4a444057b3bb0955b78d988cd" + ] + ] +] diff --git a/infrastructure/matcher/scrimmage.py b/infrastructure/matcher/scrimmage.py index 7c7ccd37..581eb2d3 100755 --- a/infrastructure/matcher/scrimmage.py +++ b/infrastructure/matcher/scrimmage.py @@ -27,7 +27,7 @@ def worker(): if result == None: scrim_queue.put(scrim) -@sched.scheduled_job('cron', hour='*/6') +@sched.scheduled_job('cron', hour='*/3') def matchmake(): try: logging.info('Obtaining scrimmage list') diff --git a/infrastructure/passed_students.sql b/infrastructure/passed_students.sql index a0121951..a123582b 100644 --- a/infrastructure/passed_students.sql +++ b/infrastructure/passed_students.sql @@ -22,14 +22,14 @@ SELECT api_user.last_name, mit_students.email, collated_results.max_wins_out_of_10, - (collated_results.max_wins_out_of_10 >= 8) AS passed + (collated_results.max_wins_out_of_10 >= 7) AS passed FROM api_user INNER JOIN api_team_users ON api_user.id = api_team_users.user_id -INNER JOIN ( +LEFT JOIN ( SELECT team_id, MAX(wins_out_of_10) AS max_wins_out_of_10 @@ -63,7 +63,7 @@ INNER JOIN ( FROM api_scrimmage CROSS JOIN ( - VALUES (919) + VALUES (1790) ) AS consts(ref_team_id) WHERE red_team_id = consts.ref_team_id OR blue_team_id = consts.ref_team_id @@ -78,10 +78,10 @@ INNER JOIN ( ) ) AS last_10 CROSS JOIN ( - VALUES (CAST ('2020-01-17 16:57:35.785685+00' AS TIMESTAMP)) + VALUES (CAST ('2021-01-25 08:54:31.889726+00' AS TIMESTAMP)) ) AS consts(ref_timestamp) WHERE - oldest_scrim >= consts.ref_timestamp + oldest_scrim >= consts.ref_timestamp OR oldest_scrim IS NULL ) AS result_dump GROUP BY team_id diff --git a/infrastructure/tournament-util/.gitignore b/infrastructure/tournament-util/.gitignore index 512d5e24..8f09d666 100644 --- a/infrastructure/tournament-util/.gitignore +++ b/infrastructure/tournament-util/.gitignore @@ -1,2 +1,3 @@ /data/* !/data/0-example +!/data/1-sprint1 diff --git a/infrastructure/tournament-util/challonge_pubber.py b/infrastructure/tournament-util/challonge_pubber.py new file mode 100644 index 00000000..a5e82a69 --- /dev/null +++ b/infrastructure/tournament-util/challonge_pubber.py @@ -0,0 +1,108 @@ +# Requires achallonge, and _not_ pychal. Make sure to `pip uninstall pychal`, `pip install achallonge` etc before using. + +# IMPORTANT -- BEFORE RUNNING THIS: +# Get the Challonge API Key. Set it to an env, CHALLONGE_API_KEY. DON'T PUSH NOR SCREENSHARE/STREAM IT! +# Get the tournament url, it's the alphanumeric string at the end of the tournament website's url. (e.g. http://challonge.com/thispart). Set it to the tour_url variable. +# Now with all those set, run the script as specified directly below: + +# Usage: +# python challonge_pubber.py path/to/json.json start end +# path/to/json: a json produced by running the tour. Get this from someone who ran the tournament. +# start: the challonge match number (as shown on the bracket page) of the first Challonge match (or, if argv[3] not specified, the only challonge match) whose result is to be published +# end: optional; the challonge match number of the last Challonge match to be published, _exclusive_. +# e.g. python challonge_pubber.py path/to/replay_dump.json 1 4 +# will publish the results of the Challonge bracket's matches 1, 2, and 3. + +import sys, json, challonge, asyncio, os + +tour_url = 'example' + +async def run(): + print("Setting up...\n") + try: + api_key = os.getenv('CHALLONGE_API_KEY') + user = await challonge.get_user('mitbattlecode',api_key) + tournament = await user.get_tournament(url = tour_url) + except: + print("Make sure you have properly configured CHALLONGE_API_KEY and tour_url.") + print("See the comments at the top of this file for instructions.") + raise Exception + + # To ensure tournament is started and attachments are allowed. + # Only needs to be run once per tournament. + # But, we run it every time the script is run: this can be run unlimited times, and is pretty quick. + # Also makes the script much simpler to use. + await tournament.start() + await tournament.allow_attachments(True) + + # We map matches' suggested play order to their match objects, so that we can easily access the matches. + # This is because we (battlecode) play matches in their suggested play order, and so suggested play order is the index that we use. + # There's no way to directly access the match objects by their suggested play order, so we need some preprocessing. + match_play_order_dict = dict() + tournament_matches = await tournament.get_matches() + for m in tournament_matches: + suggested_play_order = m.suggested_play_order + match_play_order_dict[suggested_play_order] = m + + replay_file_name = sys.argv[1] + match_no_start = int(sys.argv[2]) + try: + match_no_end = int(sys.argv[3]) + except: + match_no_end = match_no_start+1 + + with open(replay_file_name, 'r') as replay_file: + replays = json.load(replay_file) + + for match_no in range(match_no_start, match_no_end): + print(f'Reporting match {match_no}') + match = replays[match_no - 1] # note -1, for proper indexing: challonge is 1-indexed while the json is 0 + api_match = match_play_order_dict[match_no] + if api_match.state == 'pending': + print('Match is not ready to have results reported.') + print('Check that necessary previous matches have been reported, so that participants are set.') + raise Exception + + api_player1 = await tournament.get_participant( api_match.player1_id ) + api_player2 = await tournament.get_participant( api_match.player2_id ) + + + player1 = match[0][0] + player2 = match[0][1] + + if (api_player1.display_name != player1) or (api_player2.display_name != player2): + print("Match's player names on json do not match those on Challonge.") + print("Check proper entry/order of participants on Challonge, and correct match ordering in json.") + raise Exception + # For a sanity check, to ensure you're publishing the match that you want to publish. + print(f'{player1} vs {player2}') + + player1_score = 0 + player2_score = 0 + + for game in match: + p_red = game[0] + p_blue = game[1] + map = game[2] + winner = p_red if game[3] == 1 else p_blue + replay = game[4] + + if winner == player1: + player1_score += 1 + else: + player2_score += 1 + + replayurl = f'http://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/{replay}.bc21' + await api_match.attach_url(replayurl) + + if player1_score > player2_score: + await api_match.report_winner(api_player1, f'{player1_score}-{player2_score}') + else: + await api_match.report_winner(api_player2, f'{player1_score}-{player2_score}') + + print(f'Match {match_no} reported!\n') + + +loop = asyncio.get_event_loop() +loop.run_until_complete(run()) +loop.close() diff --git a/infrastructure/tournament-util/csv_to_files.py b/infrastructure/tournament-util/csv_to_files.py index 1d44dadf..946faa31 100755 --- a/infrastructure/tournament-util/csv_to_files.py +++ b/infrastructure/tournament-util/csv_to_files.py @@ -11,6 +11,5 @@ f.readline() # Skip the title row for line in f.readlines(): team_id, team_name, team_score = line.split(',') - team_name = team_name[1:-1] # Remove quotation marks g.write(team_id+'\n') h.write(team_name+'\n') diff --git a/infrastructure/tournament-util/data/0-example/parsed.txt b/infrastructure/tournament-util/data/0-example/parsed.txt new file mode 100644 index 00000000..2d8786ed --- /dev/null +++ b/infrastructure/tournament-util/data/0-example/parsed.txt @@ -0,0 +1,6 @@ + testteam the redux -vs- Teh Dev | maptestsmall bluewon replay fc87d381b294fb59e34fdd6f3e6370 + Teh Dev -vs- testteam the redux | quadrants bluewon replay 94962a605adad55b5e9d79cc3cc713 + asdfsddtjggyftawehjjhsrsfghgsdf -vs- Teh Dev | circle bluewon replay 9c1d36955c80048c781a29ad80d522 + asdfsddtjggyftawehjjhsrsfghgsdf -vs- testteam the redux | circle bluewon replay 06097511c49d99846fef94190dab4e + Teh Dev -vs- testteam the redux | circle bluewon replay 051411ba85be3f8798aff26993a7e3 + testteam the redux -vs- Teh Dev | circle bluewon replay 41a8169ac95d36565c10c684445622 diff --git a/infrastructure/tournament-util/data/0-example/results.json b/infrastructure/tournament-util/data/0-example/results.json new file mode 100644 index 00000000..029992f5 --- /dev/null +++ b/infrastructure/tournament-util/data/0-example/results.json @@ -0,0 +1,54 @@ +[ + [ + [ + "testteam the redux", + "Teh Dev", + "maptestsmall", + 2, + "fc87d381b294fb59e34fdd6f3e6370" + ], + [ + "Teh Dev", + "testteam the redux", + "quadrants", + 2, + "94962a605adad55b5e9d79cc3cc713" + ] + ], + [ + [ + "asdfsddtjggyftawehjjhsrsfghgsdf", + "Teh Dev", + "circle", + 2, + "9c1d36955c80048c781a29ad80d522" + ] + ], + [ + [ + "asdfsddtjggyftawehjjhsrsfghgsdf", + "testteam the redux", + "circle", + 2, + "06097511c49d99846fef94190dab4e" + ] + ], + [ + [ + "Teh Dev", + "testteam the redux", + "circle", + 2, + "051411ba85be3f8798aff26993a7e3" + ] + ], + [ + [ + "testteam the redux", + "Teh Dev", + "circle", + 2, + "41a8169ac95d36565c10c684445622" + ] + ] +] \ No newline at end of file diff --git a/infrastructure/tournament-util/data/1-sprint1/replay_dump.json b/infrastructure/tournament-util/data/1-sprint1/replay_dump.json new file mode 100644 index 00000000..cf6a9685 --- /dev/null +++ b/infrastructure/tournament-util/data/1-sprint1/replay_dump.json @@ -0,0 +1 @@ +[[["Amoosed", "NeoHazard", "SlowMusic", 2, "4e68fe7ab6b6e496a6fc4a214b855d"], ["NeoHazard", "Amoosed", "Corridor", 2, "b3352046408750bb97adc2281b836a"], ["Amoosed", "NeoHazard", "Gridlock", 2, "efb06babb96565db2cad1fceb977ba"]], [["BeachBANDITS", "Pi over squared", "SlowMusic", 1, "ac9f36bc55ff8edfddd0539da127b1"], ["Pi over squared", "BeachBANDITS", "Corridor", 2, "2b064301799d2b60a443f12b51bbc4"], ["BeachBANDITS", "Pi over squared", "Gridlock", 1, "7f7ad5049ce3b594e5fbc75c208a19"]], [["Egg Clan", "polyteam version 2", "SlowMusic", 2, "7ce4e95730a958acb1ab21a05807cd"], ["polyteam version 2", "Egg Clan", "Corridor", 1, "d40f17a47ad353bba401a9d21c7eb6"], ["Egg Clan", "polyteam version 2", "Gridlock", 2, "8d5593694ba9884d47fc96325125c1"]], [["boib", "The Al Gore Rhythm", "SlowMusic", 2, "97220f5513b6996942db248eaccfbb"], ["The Al Gore Rhythm", "boib", "Corridor", 1, "94bea2ecb76d83a76fc9b21fc0d836"], ["boib", "The Al Gore Rhythm", "Gridlock", 2, "39679aa7625afa87cb7cdaee3b9342"]], [["Alpha Centauri", "ButterBois", "SlowMusic", 1, "72b421c1f4dbd2af05348d4595a615"], ["ButterBois", "Alpha Centauri", "Corridor", 2, "acae997799723509313823bb5f9dea"], ["Alpha Centauri", "ButterBois", "Gridlock", 1, "84f48d7045e50d9deead749215305c"]], [["Serpentine - M.A.R.S.", "The Lurkers in the Wire", "SlowMusic", 1, "15700ec0d11733be18687bdc5cd4c5"], ["The Lurkers in the Wire", "Serpentine - M.A.R.S.", "Corridor", 1, "07bf6b158f1ce2c7896f147908315a"], ["Serpentine - M.A.R.S.", "The Lurkers in the Wire", "Gridlock", 1, "cb490bf363d82307bc43dcdbbff779"]], [["Principia", "The Eager Sloths", "SlowMusic", 1, "aef6a3bb5f02ccaf29e2bc7d7e38fe"], ["The Eager Sloths", "Principia", "Corridor", 2, "8f8f1614bdd95426a2ec8867be7e75"], ["Principia", "The Eager Sloths", "Gridlock", 1, "614d6a2fbb6b4a6b08d0c391b9dbb2"]], [["DaMa", "Balloon Platoon", "SlowMusic", 1, "0a359a03d59f88b675e6f32b88ad68"], ["Balloon Platoon", "DaMa", "Corridor", 2, "1d5fe15e696f5f2acddbf316ec0383"], ["DaMa", "Balloon Platoon", "Gridlock", 1, "4320fdba3df68d2420e14e18ca07ae"]], [["helloMars", "java :ghosthug:", "SlowMusic", 2, "e605bab770f6965cb042cca18a7f26"], ["java :ghosthug:", "helloMars", "Corridor", 1, "043f4b3a668e5c4a8bf657903a92b2"], ["helloMars", "java :ghosthug:", "Gridlock", 2, "d080e9a958568493bcca25e34605b0"]], [["Team Confused", "JT5", "SlowMusic", 1, "06d31819a6d736da3417e13aa54d56"], ["JT5", "Team Confused", "Corridor", 2, "249400fcd2453e46b9e4329514946a"], ["Team Confused", "JT5", "Gridlock", 1, "f36a8e1042091208d57c0196a1d7eb"]], [["CHAD", "BearFish", "SlowMusic", 1, "f76ad509fd829c08b2bdd255d1151b"], ["BearFish", "CHAD", "Corridor", 2, "e65bc4afc7c99765f5287228e71d77"], ["CHAD", "BearFish", "Gridlock", 1, "5be9eb1959e242c9084527ae388c63"]], [["Python Waifu", "Intrepid losers", "SlowMusic", 2, "15eba1b76526770d3ece42bc74d22c"], ["Intrepid losers", "Python Waifu", "Corridor", 2, "1f7dd4865c7b5b4fdec48ec554b049"], ["Python Waifu", "Intrepid losers", "Gridlock", 1, "d5894d13d35df8d3da5f64dd939ff4"]], [["JavaScrapped", "holy choir", "SlowMusic", 1, "133644580e37276775d257e58ffe1b"], ["holy choir", "JavaScrapped", "Corridor", 2, "344c620f7be4783a9a48ebbb2c4343"], ["JavaScrapped", "holy choir", "Gridlock", 1, "e66f2dfd081b59b888863697d5ae4c"]], [["Goreteks", "Children of Talos", "SlowMusic", 1, "a7f9f2b96ae6b4f143bf1baf2ec35f"], ["Children of Talos", "Goreteks", "Corridor", 2, "a71b18b7d2734207e2982b62c3ae63"], ["Goreteks", "Children of Talos", "Gridlock", 1, "fee62bb3cfc804500076024d36b9ab"]], [["InfiniteLoop", "Team_of_One", "SlowMusic", 2, "cf5121bd2444f8371b896bc357fe98"], ["Team_of_One", "InfiniteLoop", "Corridor", 1, "0e4c73fb6e87f88e516dc5f3312632"], ["InfiniteLoop", "Team_of_One", "Gridlock", 2, "54ed6b350d3216022c188a65110d10"]], [["cuttlefish", "SeizeMeansOfSoftwareProduction", "SlowMusic", 1, "ec22af09313b4f9d86fbb7dc8e8949"], ["SeizeMeansOfSoftwareProduction", "cuttlefish", "Corridor", 2, "3cd6426e440f3c1e30fa5175965823"], ["cuttlefish", "SeizeMeansOfSoftwareProduction", "Gridlock", 1, "a7440d90ea252fbf63152d4df7cf32"]], [["Cavalier", "Rael Tarmo T\u00e4hvend", "SlowMusic", 1, "c42feb44629966d63ffab5bb4b5bf8"], ["Rael Tarmo T\u00e4hvend", "Cavalier", "Corridor", 2, "4ad3edb6e651537863fa37ff76ff5a"], ["Cavalier", "Rael Tarmo T\u00e4hvend", "Gridlock", 1, "694ff508861d7ee4cbdafcb9150800"]], [["PenguinBattler", "Quantum", "SlowMusic", 1, "6a3fdcef0ebbf93831daf7c76881ce"], ["Quantum", "PenguinBattler", "Corridor", 2, "b076bafb5b511b13e1ab3fe3795b77"], ["PenguinBattler", "Quantum", "Gridlock", 1, "c7d946efa55102f11bf03c93fcbd35"]], [["ginger cow", "\u300e100910\u300f", "SlowMusic", 1, "f4184dc6dccd266dc0128512c01f6e"], ["\u300e100910\u300f", "ginger cow", "Corridor", 2, "8bce17b0d8fbb1132d150b998d079b"], ["ginger cow", "\u300e100910\u300f", "Gridlock", 1, "9be0ec55206196d2d8eb38cf1b3d79"]], [["The Matarrhites", "Endless Downpour", "SlowMusic", 1, "73802e67600827838cff5f2e96a751"], ["Endless Downpour", "The Matarrhites", "Corridor", 2, "791f20a65c96cb9ecfd1898b66a5be"], ["The Matarrhites", "Endless Downpour", "Gridlock", 1, "c3b62e73bd50f559fa609ad5460d04"]], [["idrc", "CHINA", "SlowMusic", 1, "334acf16bd8a6f8725a6cfed812aea"], ["CHINA", "idrc", "Corridor", 2, "f568c1a1e4524e2cfc7155f3e7810e"], ["idrc", "CHINA", "Gridlock", 1, "8ecc9df8db6848621501306f37d37e"]], [["elongatedmuskrat", "JT6", "SlowMusic", 1, "a5cc30664790d38710bdaab895e70f"], ["JT6", "elongatedmuskrat", "Corridor", 2, "6916a4906d5973a86524627436893b"], ["elongatedmuskrat", "JT6", "Gridlock", 1, "8b965df23c4a7e51c5203e751b8437"]], [["Joe", "Handshakers x3", "SlowMusic", 1, "3e9259fdf6463b650d055418b28dd8"], ["Handshakers x3", "Joe", "Corridor", 2, "4c621236ce56ccdf99604b6fafa82c"], ["Joe", "Handshakers x3", "Gridlock", 1, "38a187c6afe133c453e3551df8aba6"]], [["Big Oh", "Oni Phantom Pog", "SlowMusic", 1, "4679f5e7a918bbb397d3e88be62490"], ["Oni Phantom Pog", "Big Oh", "Corridor", 2, "33865665ecd0b261cfde6cad6f1dde"], ["Big Oh", "Oni Phantom Pog", "Gridlock", 1, "78a19c953507551e7e8e6cc85d1342"]], [["0x5f3759df", "null", "SlowMusic", 1, "431f6b3fa895e567c6079f16693c63"], ["null", "0x5f3759df", "Corridor", 2, "1f26e1a8e08baa786f04ab685d1fb9"], ["0x5f3759df", "null", "Gridlock", 1, "2cf63a96de6979175758a32b7ae04b"]], [["pigeons", "JT3", "SlowMusic", 1, "830d9e603f42fd6255c6c7260dc860"], ["JT3", "pigeons", "Corridor", 2, "591d726b832e11e8ab37f1ccb339ab"], ["pigeons", "JT3", "Gridlock", 1, "e33bbb95cc5c8631b7f07b4ff6dd1e"]], [["Serpentine - Viper", "Snakes and Ladders", "SlowMusic", 1, "08c2c84b2860a34ebe8ff4f111d6da"], ["Snakes and Ladders", "Serpentine - Viper", "Corridor", 2, "02d006452462913bb63e4cf31774c1"], ["Serpentine - Viper", "Snakes and Ladders", "Gridlock", 1, "58a309702b816a5422456628005b45"]], [["lit", "Beginner", "SlowMusic", 1, "5c3d81e576a883bb02df5488c3d020"], ["Beginner", "lit", "Corridor", 2, "3106f347ac9d399b3aa65504cb8041"], ["lit", "Beginner", "Gridlock", 1, "3536bb2a0468e1af171d3cc839386e"]], [["Scopes Monkey Trial 1925", "JT2", "SlowMusic", 2, "7c068cbc9c1903cd96ebce2709e0f4"], ["JT2", "Scopes Monkey Trial 1925", "Corridor", 1, "df149a4cdfb976839344039727da52"], ["Scopes Monkey Trial 1925", "JT2", "Gridlock", 2, "8aa1590b33bb38bce1cb372cba1ba9"]], [["Hippocrene", "JT1", "SlowMusic", 1, "e72244918702746e653f1b06ed27a0"], ["JT1", "Hippocrene", "Corridor", 2, "45403de77328e216607176aef41a15"], ["Hippocrene", "JT1", "Gridlock", 1, "9fda89cbd8349609a71c4bb6a1c892"]], [["flexqueue", "GCI Gophers", "SlowMusic", 2, "4bddf643ea1491f7e709c75d5e2f86"], ["GCI Gophers", "flexqueue", "Corridor", 1, "24376c7d3fd75cd91b1ba8a7d34d8c"], ["flexqueue", "GCI Gophers", "Gridlock", 1, "a8e6982b3a7d2145f24ac3042b9b8b"]], [["CamelMan", "21 Codestreet", "SlowMusic", 2, "f23e642c6807c6eb31cbc8efe29229"], ["21 Codestreet", "CamelMan", "Corridor", 2, "d925716dbb1c3a10b71baa673fafdd"], ["CamelMan", "21 Codestreet", "Gridlock", 2, "a06a6850f8c6e7da7e77acf6ded9c6"]], [["The Method.", "Borscht Bot", "SlowMusic", 1, "c468e13928e759ecf47fb2727f5b82"], ["Borscht Bot", "The Method.", "Corridor", 2, "49f5f0dbad77aeb7a749f489376a81"], ["The Method.", "Borscht Bot", "Gridlock", 1, "1a1328ddfc69f099658df5c4527031"]], [["monky", "Solexa", "SlowMusic", 1, "6536360d3e26ecd5acf7ecb119618e"], ["Solexa", "monky", "Corridor", 2, "0e5a6c9b311b93fe532ae71daad6ea"], ["monky", "Solexa", "Gridlock", 1, "f909531f95f4f060b791e74f396554"]], [["MossyBot", "Homeless Coders", "SlowMusic", 1, "776c9f15ce34c27011a9bd88e9bf7a"], ["Homeless Coders", "MossyBot", "Corridor", 2, "35a6709cbc8f5d9a0bb08d2966a08f"], ["MossyBot", "Homeless Coders", "Gridlock", 1, "b2cc581f91fa00bdf6d4c4a355ab21"]], [["GHS Guardians", "CMU-MIT combo plater", "SlowMusic", 1, "ba2f3c3d0ee79cc72db0169311c7b9"], ["CMU-MIT combo plater", "GHS Guardians", "Corridor", 2, "db4902e027040ee3520f8ebf308a23"], ["GHS Guardians", "CMU-MIT combo plater", "Gridlock", 1, "5d917f2099103efb94da89edeae26e"]], [["Yeet Pull", "doesthisevenwork", "SlowMusic", 1, "fb8a724947a86601a2dba2697f42a5"], ["doesthisevenwork", "Yeet Pull", "Corridor", 2, "a6cbc58472043aa82a773246f55ba6"], ["Yeet Pull", "doesthisevenwork", "Gridlock", 1, "163660b764ec3bc91a924df92f71e8"]], [["Touhutippa", "BattlePath", "SlowMusic", 1, "0a0704bd5a2476b5c3e78fe5da22f1"], ["BattlePath", "Touhutippa", "Corridor", 2, "959a2950a12a2bc243c93919640e1f"], ["Touhutippa", "BattlePath", "Gridlock", 1, "b749a600531d854251ea1552190fd5"]], [["Hertzhaft", "HedgeTech", "SlowMusic", 1, "4a00a2888b3dda04f32d940654b0fd"], ["HedgeTech", "Hertzhaft", "Corridor", 2, "0116a4270defcebb399597456e4444"], ["Hertzhaft", "HedgeTech", "Gridlock", 1, "4fe3b0327a87217d6549b7c8d4e886"]], [["JT4", "69 Tons More Data", "SlowMusic", 1, "d2a57c8b3d1123de9dcaaed995d375"], ["69 Tons More Data", "JT4", "Corridor", 2, "2f51b173701ada29079b593639f21a"], ["JT4", "69 Tons More Data", "Gridlock", 1, "20f48d123ba9ff16e39de6e2fb611e"]], [["Veto", "Toot Toot Train", "SlowMusic", 1, "6cc35103615949b13d295d6538d51c"], ["Toot Toot Train", "Veto", "Corridor", 2, "de158e02e7970afd222ef48c0c9ef8"], ["Veto", "Toot Toot Train", "Gridlock", 1, "1c800d87b731633e7333a5dd13af60"]], [["YA BOI", "Burnerteam; passwordisqweasdqweasd", "SlowMusic", 1, "9570fdc575037869917206a9f8901c"], ["Burnerteam; passwordisqweasdqweasd", "YA BOI", "Corridor", 2, "05dbabc18b1e1343a7e6fbe20c5290"], ["YA BOI", "Burnerteam; passwordisqweasdqweasd", "Gridlock", 1, "01d683747a5d3bdb4399d04c93eece"]], [["blair blezers", "Sagittarius A* Algorithm", "SlowMusic", 1, "ccd7ecdbd6385e19e0d4ec7970f681"], ["Sagittarius A* Algorithm", "blair blezers", "Corridor", 2, "7b089f1f5a1feb3dc83ae83a256993"], ["blair blezers", "Sagittarius A* Algorithm", "Gridlock", 1, "90e07498297484ec879db5daa0a784"]], [["ACM @ UNCC", "MinneCal", "SlowMusic", 1, "0b64bef7fad0c69144131d22b4d037"], ["MinneCal", "ACM @ UNCC", "Corridor", 2, "fb2fbfff1900933152be2d4f6e8db2"], ["ACM @ UNCC", "MinneCal", "Gridlock", 1, "636680747e7aefb01c1cdd387d817e"]], [["ThotBot", "sinksanksunk", "SlowMusic", 1, "e3cc823d9c70ff143465ee035bd671"], ["sinksanksunk", "ThotBot", "Corridor", 2, "214dafd6a0768c208ed94e87d6b9aa"], ["ThotBot", "sinksanksunk", "Gridlock", 1, "50fb88aa53172148db35927d617178"]], [["$nowball", "CodeReapers", "Gridlock", 2, "c3e0cec57197f8aefaa213a9d85a11"], ["CodeReapers", "$nowball", "Arena", 1, "2f438063ed778e880db6b107ff5cc1"], ["$nowball", "CodeReapers", "ExesAndOhs", 2, "deee4137b52a975df508c86d36b403"]], [["The 501st", "The other team", "Gridlock", 2, "42e442e1448cf4aa5e53f7db865afd"], ["The other team", "The 501st", "Arena", 1, "ebdb09be67446b0bb7eea9157c9bb2"], ["The 501st", "The other team", "ExesAndOhs", 2, "386621ef51c53942f6e10cb6a076a5"]], [["01001000", "Team Awesome", "Gridlock", 2, "fe2c45b5e219d9d81d395468a33fcd"], ["Team Awesome", "01001000", "Arena", 1, "b418fbad77f19aaa098b25df071405"], ["01001000", "Team Awesome", "ExesAndOhs", 2, "6fd80b71700f9c9cf6b60cf7822962"]], [["Mycroft", "BossTweed", "Gridlock", 2, "b81e38dd03f67093d90de819f1903d"], ["BossTweed", "Mycroft", "Arena", 1, "4f906b049de725d40775b66a2da48a"], ["Mycroft", "BossTweed", "ExesAndOhs", 2, "79d986e165852df617f78686668dc3"]], [["Ctrl Alt Elite", "fishy yum", "Gridlock", 1, "bd32cd7262ccb19ae41159b677a05e"], ["fishy yum", "Ctrl Alt Elite", "Arena", 2, "96940008a63a9dad0e42a24b4601e0"], ["Ctrl Alt Elite", "fishy yum", "ExesAndOhs", 2, "c4964c24ba9ca0bd16c8990550e568"]], [["Broccoli Bros !", "ERO2", "Gridlock", 1, "5c19756616ac46ff09571eae12ebcd"], ["ERO2", "Broccoli Bros !", "Arena", 2, "6fb58bc654adba091fbb789a096cce"], ["Broccoli Bros !", "ERO2", "ExesAndOhs", 1, "4b7b40b66629535ca0f91a19ffa24f"]], [["Kansas City Asians", "Stepstool", "Gridlock", 1, "8136c42139f66736f42e99fbb84906"], ["Stepstool", "Kansas City Asians", "Arena", 2, "dbcae8fe2e552cc67caaba2d6bf600"], ["Kansas City Asians", "Stepstool", "ExesAndOhs", 1, "0f9cbd5012d8c3ad24d94dbbc93569"]], [["No Battlecode for Old Men", "Influencer Force", "Gridlock", 1, "645881a0279c6fe1c69243e36ffe8b"], ["Influencer Force", "No Battlecode for Old Men", "Arena", 1, "c3a7b60de622c863949b90e2db9cb6"], ["No Battlecode for Old Men", "Influencer Force", "ExesAndOhs", 1, "9166acd56b0730c7bb8ff6ba536503"]], [["armed pythons", "A214", "Gridlock", 2, "fbfaef7f645e0acd7515948046cdb4"], ["A214", "armed pythons", "Arena", 1, "f5abcc164560e96a5adf17a4cce895"], ["armed pythons", "A214", "ExesAndOhs", 2, "e6186fc321bb6f32c5953e0702fcbb"]], [["confused", "Soju", "Gridlock", 2, "8c00a45b5a7d96147bba18bee8f762"], ["Soju", "confused", "Arena", 2, "03563d74e4037fee34ba4e98bed714"], ["confused", "Soju", "ExesAndOhs", 1, "4b21deedf40c86b69354d5b26eeb93"]], [["(+[](){})();", "\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd", "Gridlock", 2, "2068a93dc656b294485c4c22abb70f"], ["\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd", "(+[](){})();", "Arena", 2, "a2ada8d11ba14e87d385b4e8cecdda"], ["(+[](){})();", "\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd\ufdfd", "ExesAndOhs", 1, "f4e14926c590f06402d25f13ec0a24"]], [["Ctrl Alt Defeat", "The Unladen Swallows", "Gridlock", 2, "6449dca66194609d09217128fcd074"], ["The Unladen Swallows", "Ctrl Alt Defeat", "Arena", 1, "dc3f6abdf172cc4d4c657e354efaa8"], ["Ctrl Alt Defeat", "The Unladen Swallows", "ExesAndOhs", 2, "a98e4933f5b9e8583389dfa1fe0745"]], [["bombocombo", "free boba", "Gridlock", 2, "3f2cee7472eba4cb293af956659f41"], ["free boba", "bombocombo", "Arena", 1, "8c892fda4686017888677e581e52df"], ["bombocombo", "free boba", "ExesAndOhs", 2, "bc6b33d25053ef96391eacda8e61ff"]], [["random", "What are you doing stepBot?", "Gridlock", 2, "074b64ee6dfeb63e9e096c1f006920"], ["What are you doing stepBot?", "random", "Arena", 1, "e38dff6fce4d940ca5feac669af2fd"], ["random", "What are you doing stepBot?", "ExesAndOhs", 1, "5ee44cfad76fa4a85a9e58644b678b"]], [["Team Nit(h)ya", "Coconut9", "Gridlock", 2, "a49f951d50985f22c4940e579d302d"], ["Coconut9", "Team Nit(h)ya", "Arena", 1, "9a0e23feebb68f8e6feb015059db4d"], ["Team Nit(h)ya", "Coconut9", "ExesAndOhs", 2, "14105d0d074b2f6f08f7b608fedbc0"]], [["Daedalus", "2b1y", "Gridlock", 1, "c6740d19fa6ecef91bd8eb6aeb67c6"], ["2b1y", "Daedalus", "Arena", 2, "1b6ac77b2d25756f82273913af3186"], ["Daedalus", "2b1y", "ExesAndOhs", 1, "c2c11e41508ee7f222167783048a15"]], [["Double J", "0K", "Gridlock", 2, "116cc9dba03e45c6de79eb8641d650"], ["0K", "Double J", "Arena", 1, "63959603d6f1fb0f12ff2c3fa5f0bc"], ["Double J", "0K", "ExesAndOhs", 2, "517e450bb135509636b531a5cdb93a"]], [["The Night's Watch", "Engineer Gaming", "Gridlock", 2, "f4aec9a65586bcd3617adbb47bbc0d"], ["Engineer Gaming", "The Night's Watch", "Arena", 1, "78592de32d3a4d2d1f2f0a9e4a46ca"], ["The Night's Watch", "Engineer Gaming", "ExesAndOhs", 2, "614d175ae3cb07f46794036cc1f212"]], [["Propaganda Machine", "Malott Fat Cats", "Gridlock", 1, "6d27815dda691ff55633baabd06af9"], ["Malott Fat Cats", "Propaganda Machine", "Arena", 1, "7434a4f26519f39c1011fdcef5aca9"], ["Propaganda Machine", "Malott Fat Cats", "ExesAndOhs", 2, "271b8ff119b494b87d04208766fb1f"]], [["babyducks", "NeoHazard", "Gridlock", 1, "0f33d7d04d019d4d218b2da285cb29"], ["NeoHazard", "babyducks", "Arena", 2, "27da4012e3d9a8aaf545b065b5991b"], ["babyducks", "NeoHazard", "ExesAndOhs", 1, "7788274440de7d5ccbf18a29c15ef3"]], [["Yeicor", "BeachBANDITS", "Gridlock", 1, "32d8a111e8043d82094c9cc3eedc8a"], ["BeachBANDITS", "Yeicor", "Arena", 2, "75cb625710f02b250af5149dc9754a"], ["Yeicor", "BeachBANDITS", "ExesAndOhs", 1, "7b4e212e28e72e4a3c032e202b7c49"]], [["Coast", "polyteam version 2", "Gridlock", 1, "9ecd0aa1ac8329009979c84912d968"], ["polyteam version 2", "Coast", "Arena", 2, "ee80b1f43ba18c35358fcfdb9a1cda"], ["Coast", "polyteam version 2", "ExesAndOhs", 1, "83eabb10b388cbab0ca998665df8e6"]], [["Mars Analytica", "The Al Gore Rhythm", "Gridlock", 2, "e61e9579178cd429ede4352b29c36c"], ["The Al Gore Rhythm", "Mars Analytica", "Arena", 2, "16a3660d18ab528e7e8eea3f59d697"], ["Mars Analytica", "The Al Gore Rhythm", "ExesAndOhs", 1, "5a496dfc61a2163f2d74b03352d718"]], [["Bytecode Mafia", "Alpha Centauri", "Gridlock", 1, "307734dbd0b6e06ecefe3dc395e45e"], ["Alpha Centauri", "Bytecode Mafia", "Arena", 2, "f84067d9a74f575ff2c7bf2d5346da"], ["Bytecode Mafia", "Alpha Centauri", "ExesAndOhs", 1, "96526c1e63b5122590c09590f34fa4"]], [["Chicken", "Serpentine - M.A.R.S.", "Gridlock", 1, "c29b11d5cbbba7d8d8fd75a93fdd0f"], ["Serpentine - M.A.R.S.", "Chicken", "Arena", 2, "1d209ea5b84f486459b0101a9073d1"], ["Chicken", "Serpentine - M.A.R.S.", "ExesAndOhs", 1, "ffe26bf366ad1dba4f2ccfb2fe6107"]], [["I am the Senate", "Principia", "Gridlock", 1, "2e6f97882619fd4a86cdf98283d8f6"], ["Principia", "I am the Senate", "Arena", 2, "71f0bf69faad0b732ea98e221deb77"], ["I am the Senate", "Principia", "ExesAndOhs", 1, "24be687d32b2cca6fbbe0d271f9504"]], [["Harvard sucks lol", "DaMa", "Gridlock", 2, "416ee25ba138294f3e39102675d8fd"], ["DaMa", "Harvard sucks lol", "Arena", 1, "777a676fde65f7e889af02b38d4031"], ["Harvard sucks lol", "DaMa", "ExesAndOhs", 2, "1de16129f02884d8053391c5280bc3"]], [["Chocolate Banana Cake", "java :ghosthug:", "Gridlock", 1, "29f0e79bc2d94a2360b3c63a016f2c"], ["java :ghosthug:", "Chocolate Banana Cake", "Arena", 2, "35ad8626b7d07b977bf292f36e5164"], ["Chocolate Banana Cake", "java :ghosthug:", "ExesAndOhs", 1, "4ad35e68b0aa5f83d428d9a6339535"]], [["Blue Dragon", "Team Confused", "Gridlock", 1, "03b597f6c23e84503e71518887f245"], ["Team Confused", "Blue Dragon", "Arena", 2, "039015c190e0805eeef1c5d1b76ee5"], ["Blue Dragon", "Team Confused", "ExesAndOhs", 1, "57cc46830b05c07d2cbdfa62c55734"]], [["Team Barcode", "CHAD", "Gridlock", 1, "0f32bc09300ecaaf7f4d0d41370a5f"], ["CHAD", "Team Barcode", "Arena", 2, "8237bf343be8177662558a65c5cb80"], ["Team Barcode", "CHAD", "ExesAndOhs", 1, "42416a16f0e87df945c817a5523c71"]], [["waffle", "Python Waifu", "Gridlock", 1, "d087d00b68feb538967ba11870740b"], ["Python Waifu", "waffle", "Arena", 2, "967a7f18fe2ad29104ea6c8636851a"], ["waffle", "Python Waifu", "ExesAndOhs", 1, "ce522597a83fea6636b0b92d3cc958"]], [["AntiVaxxKids", "JavaScrapped", "Gridlock", 1, "4b00bf03ee56786fe6a6e3e64e7983"], ["JavaScrapped", "AntiVaxxKids", "Arena", 2, "d3e7111b3185d1cb8b1a26652bb085"], ["AntiVaxxKids", "JavaScrapped", "ExesAndOhs", 1, "449e83150fc9afdfb2ae2ec99a188a"]], [["Galaga", "Goreteks", "Gridlock", 1, "e247987e2e5d1ab8b09291ed9f0ef7"], ["Goreteks", "Galaga", "Arena", 2, "a4b524da39905cd6ed78134b9021e3"], ["Galaga", "Goreteks", "ExesAndOhs", 1, "0525e63be7728b445b3d8dc7429d30"]], [["wololo", "Team_of_One", "Gridlock", 1, "0458af6b8544607c4241374d63a5c8"], ["Team_of_One", "wololo", "Arena", 2, "9f6757f94a8dbff9c44364f4368cf6"], ["wololo", "Team_of_One", "ExesAndOhs", 1, "256d1d68b9d9e58adfd9bcbc783493"]], [["Large Green Ogres", "cuttlefish", "Gridlock", 2, "c7849a5a1de309b86fd28869309cbb"], ["cuttlefish", "Large Green Ogres", "Arena", 1, "490e5deb12c5f591db3e90cd6d398e"], ["Large Green Ogres", "cuttlefish", "ExesAndOhs", 1, "6494b0d331125a576dd09584f17111"]], [["Lee's Morons", "Cavalier", "Gridlock", 1, "22ee94b1373bd6ce14d843c846e1c2"], ["Cavalier", "Lee's Morons", "Arena", 2, "7e2c254080290c49db26463fef4095"], ["Lee's Morons", "Cavalier", "ExesAndOhs", 1, "2329795beeddf1b43cbbff3fa1e8a7"]], [["Hard Coders", "PenguinBattler", "Gridlock", 1, "ea15f528a468e8357a04cc619e819a"], ["PenguinBattler", "Hard Coders", "Arena", 2, "06a61ea1902ffbec2705e5056bc8b9"], ["Hard Coders", "PenguinBattler", "ExesAndOhs", 1, "0695ae5196c012b3035642daa4ae44"]], [["Kryptonite", "ginger cow", "Gridlock", 1, "dbb525a56ae45baaa788e6577cc67d"], ["ginger cow", "Kryptonite", "Arena", 2, "2ebf109f32a9e9eeaba10a6d292ed6"], ["Kryptonite", "ginger cow", "ExesAndOhs", 1, "7c914ffce99c482dd1480b37bedf39"]], [["Pain", "The Matarrhites", "Gridlock", 1, "3ef4fb69b2d51b4c9ce77258c3efad"], ["The Matarrhites", "Pain", "Arena", 2, "46a88213bf3aa44d0f4258c06feb8b"], ["Pain", "The Matarrhites", "ExesAndOhs", 1, "92a1aaf461b5f2bbe2f2cc7af953a2"]], [["camel_case", "idrc", "Gridlock", 1, "73db97de6e73a7fc61087f5ec61cbb"], ["idrc", "camel_case", "Arena", 2, "43883745d469ff486d2f575fd3c96f"], ["camel_case", "idrc", "ExesAndOhs", 1, "26f2bcdde696a794d3964b9d2b3611"]], [["tooOldForThis", "elongatedmuskrat", "Gridlock", 1, "b05cd83540165b8bdcde80f97390f4"], ["elongatedmuskrat", "tooOldForThis", "Arena", 2, "5cf653a898809519c4f0a30a881c6b"], ["tooOldForThis", "elongatedmuskrat", "ExesAndOhs", 1, "39b7df9bf5585f1318154efc995683"]], [["Oculus", "Joe", "Gridlock", 1, "1d97848bb64bfbe2ad25822a76c9dd"], ["Joe", "Oculus", "Arena", 2, "80e7ea29149becc56c955d09db9ac4"], ["Oculus", "Joe", "ExesAndOhs", 1, "466f843583fa8a50ba8a23e72355ea"]], [["Super Cow Powers", "Big Oh", "Gridlock", 1, "c0762c9c1c2d5dadbb05ba8d00ebe6"], ["Big Oh", "Super Cow Powers", "Arena", 2, "7923e022799ff2c5c31c9ff923ff7b"], ["Super Cow Powers", "Big Oh", "ExesAndOhs", 1, "fac2fc9af5becebfb87d3b78a1e110"]], [["Play C. Holder", "0x5f3759df", "Gridlock", 1, "f1255f766948fb1654ed70777e85f4"], ["0x5f3759df", "Play C. Holder", "Arena", 2, "1c171b36a23508fdeb4aba824bbe28"], ["Play C. Holder", "0x5f3759df", "ExesAndOhs", 1, "c9f258086316d119400fb575ef49c8"]], [["Kruskal's Kompadres", "pigeons", "Gridlock", 1, "f5dc451face0b7ac40e56d42d5f0ac"], ["pigeons", "Kruskal's Kompadres", "Arena", 1, "a296a9498e24d6186de67f3a27330c"], ["Kruskal's Kompadres", "pigeons", "ExesAndOhs", 2, "6eaae0428a6c2d93fb632bc2ad6bcb"]], [["Rua!!", "Serpentine - Viper", "Gridlock", 1, "ec538ce61a99d271cdf87b69b002ed"], ["Serpentine - Viper", "Rua!!", "Arena", 2, "f7caaf59d1db8840df5d821e26ac05"], ["Rua!!", "Serpentine - Viper", "ExesAndOhs", 1, "294ebfa740bade85b2ecd0cc012afd"]], [["Huge L Club", "lit", "Gridlock", 1, "adc6225d057979c27110e27a0dfb35"], ["lit", "Huge L Club", "Arena", 2, "dbd9e6c163408e24e94ba7585a57a9"], ["Huge L Club", "lit", "ExesAndOhs", 1, "33317515569db77be0afca4a755bd7"]], [["3 Musketeers", "JT2", "Gridlock", 1, "9a971f49c324d0f48b2f9b544c7314"], ["JT2", "3 Musketeers", "Arena", 2, "5e566698da6a1c7fae477f120d8d26"], ["3 Musketeers", "JT2", "ExesAndOhs", 1, "8573aa43017bab5255ab6fc4836b27"]], [["StepZero", "Hippocrene", "Gridlock", 1, "4a3fba97253353bb8172a3e51f5848"], ["Hippocrene", "StepZero", "Arena", 2, "9c67c84c33d290313554928e073987"], ["StepZero", "Hippocrene", "ExesAndOhs", 1, "95d3cf81a53b49877536ec57769fea"]], [["bumbum", "GCI Gophers", "Gridlock", 1, "45ab1b7f413abab8474ed8327729c9"], ["GCI Gophers", "bumbum", "Arena", 2, "a29dc8b30f2d2f61ee0167087bfada"], ["bumbum", "GCI Gophers", "ExesAndOhs", 1, "b425d32f62f730fbdff96a77923f74"]], [["Code Not Found", "21 Codestreet", "Gridlock", 1, "991c193fb0e17995ab811e00be92a9"], ["21 Codestreet", "Code Not Found", "Arena", 2, "2205ff2a7b0d3a679a0db87992cbb8"], ["Code Not Found", "21 Codestreet", "ExesAndOhs", 1, "5edaf58bddde142a3e7689eb86e6ed"]], [["Atom", "The Method.", "Gridlock", 1, "bc56b9f63ee01b82937ef958008686"], ["The Method.", "Atom", "Arena", 1, "3442b12fa3a2f548112aa9784e2624"], ["Atom", "The Method.", "ExesAndOhs", 2, "487495076dcdee35da24aaa8950933"]], [["Blue Steel", "monky", "Gridlock", 1, "6c0244b72f8ae78003b5cf2e28b9e3"], ["monky", "Blue Steel", "Arena", 2, "a2791f067d4ca42f33d98dbbca66dc"], ["Blue Steel", "monky", "ExesAndOhs", 1, "f1ac64d530bfe797982fa0f54c24ca"]], [["Nikola", "MossyBot", "Gridlock", 1, "87b2025df45d424cdb59f6cd8f6019"], ["MossyBot", "Nikola", "Arena", 2, "4f75fcb298bfafd0e376125ce89d8a"], ["Nikola", "MossyBot", "ExesAndOhs", 1, "52baf77b8628c9338922685e8807b3"]], [["Download More RAM", "GHS Guardians", "Gridlock", 1, "968b71a31e906f1253410857af8a17"], ["GHS Guardians", "Download More RAM", "Arena", 2, "e35ce37458d31fb33c345394f445bd"], ["Download More RAM", "GHS Guardians", "ExesAndOhs", 1, "1cbc93906bcf3bea8b22ff47646f8e"]], [["The Patriots", "Yeet Pull", "Gridlock", 1, "9e0e2d5c724ab07ebe5852f87b807e"], ["Yeet Pull", "The Patriots", "Arena", 2, "eaa1b1d3256f818d7437ce4459a9d0"], ["The Patriots", "Yeet Pull", "ExesAndOhs", 1, "110b65efa6ec49eac5ce929e342271"]], [["Dis Team", "Touhutippa", "Gridlock", 1, "ff8c45ebc302890f63d99f85a6e98b"], ["Touhutippa", "Dis Team", "Arena", 2, "a9d9e4b72421042190f53422150934"], ["Dis Team", "Touhutippa", "ExesAndOhs", 1, "1067430dca5c80c7d18565b2f6c744"]], [["Ripples", "Hertzhaft", "Gridlock", 1, "489deb8b5f6d0980a2d1491f0aed7f"], ["Hertzhaft", "Ripples", "Arena", 2, "8ba1d5eb3a4b389b4165ed1a752834"], ["Ripples", "Hertzhaft", "ExesAndOhs", 1, "a613330f989aff5770f61485ce2a42"]], [["Producing Perfection", "JT4", "Gridlock", 1, "cbb9bead7441fd8e700dce34e97b4c"], ["JT4", "Producing Perfection", "Arena", 2, "fff02b8884d659ae8cf9b664a35185"], ["Producing Perfection", "JT4", "ExesAndOhs", 1, "f0442566aafc066369d18221d56d04"]], [["sucks2BU", "Veto", "Gridlock", 2, "a588c70e14513a8890444efa5d1adf"], ["Veto", "sucks2BU", "Arena", 1, "dd2ae34510601d95692eb034731457"], ["sucks2BU", "Veto", "ExesAndOhs", 2, "67746ea0b9c65a561fecc3319f7aaf"]], [["Up For A While", "YA BOI", "Gridlock", 2, "8646ed68984912d6b16f3933386193"], ["YA BOI", "Up For A While", "Arena", 2, "59c6639e65dba079d01a3ac4dd32dc"], ["Up For A While", "YA BOI", "ExesAndOhs", 1, "ea6c8b83abb9410b173ebf5b6253b2"]], [["Chop Suey", "blair blezers", "Gridlock", 2, "8b331b688ffbc4d1d12e8217cdf9f9"], ["blair blezers", "Chop Suey", "Arena", 1, "92d71188f9f4d220b46306fe0b7111"], ["Chop Suey", "blair blezers", "ExesAndOhs", 2, "387d678f95db6a64fcca62c21ed018"]], [["remotED", "ACM @ UNCC", "Gridlock", 1, "94232d3c837dc3bbd92f61a3a7db9d"], ["ACM @ UNCC", "remotED", "Arena", 1, "1aead85d9ffdcf0fc149d9881e9a18"], ["remotED", "ACM @ UNCC", "ExesAndOhs", 1, "df9069f32a7676188be8de2700aa15"]], [["BASHA ESPORTS", "ThotBot", "Gridlock", 2, "7339e5ee4c83a94f0b3befa176b972"], ["ThotBot", "BASHA ESPORTS", "Arena", 1, "3341c64ee7f85d9c24427cb9a720d4"], ["BASHA ESPORTS", "ThotBot", "ExesAndOhs", 2, "b0026f8ab7c9b0032cf4673ec6a45c"]], [["babyducks", "CodeReapers", "ExesAndOhs", 1, "0c909ec529025cf56129afc5a97704"], ["CodeReapers", "babyducks", "Bog", 2, "af579089839d857ed5bd9812798d7e"], ["babyducks", "CodeReapers", "Chevron", 1, "ea0b6aa0bea72fbb50aed527ac939d"]], [["Yeicor", "Coast", "ExesAndOhs", 2, "579aed9c6405b48779f9724812ca79"], ["Coast", "Yeicor", "Bog", 1, "5b0550d88083c0bd7562ad6b51eb4d"], ["Yeicor", "Coast", "Chevron", 2, "edc04146ce6bfaf3312facc53b1530"]], [["Mars Analytica", "The other team", "ExesAndOhs", 1, "8d22eaeea375d863bb5a1aa8acfe58"], ["The other team", "Mars Analytica", "Bog", 2, "c25e7db3e99fddebbce3ccdb2597ea"], ["Mars Analytica", "The other team", "Chevron", 1, "f348ac48ab27db746dc4c0d02c2c0d"]], [["Bytecode Mafia", "Team Awesome", "ExesAndOhs", 1, "b5e42bf2871773d03f3f35d7e00962"], ["Team Awesome", "Bytecode Mafia", "Bog", 2, "6ad869ce21450484d9246ed65f1c33"], ["Bytecode Mafia", "Team Awesome", "Chevron", 1, "511c8b30e8191e0f4a48f349ad710c"]], [["Chicken", "BossTweed", "ExesAndOhs", 1, "d61b8c70cf01547635e4ae4421ff91"], ["BossTweed", "Chicken", "Bog", 2, "99671457e2a4147005fd0c7029020a"], ["Chicken", "BossTweed", "Chevron", 1, "aaf6abcb3cc1026aec0b125b0696fd"]], [["I am the Senate", "DaMa", "ExesAndOhs", 2, "46811ca18ad261dbd3765ebc172e74"], ["DaMa", "I am the Senate", "Bog", 1, "eb39f0a492302f7889f1ce60dfa9a7"], ["I am the Senate", "DaMa", "Chevron", 1, "e2f7a56e0f74a9152a6b0a0e67b95a"]], [["Chocolate Banana Cake", "Ctrl Alt Elite", "ExesAndOhs", 1, "d3ad7c1e2732ea109185f7c93937f5"], ["Ctrl Alt Elite", "Chocolate Banana Cake", "Bog", 2, "8577d724ad1e3c71e1d96005f71193"], ["Chocolate Banana Cake", "Ctrl Alt Elite", "Chevron", 1, "589b679732c3e9780bc7d9dcc21809"]], [["Blue Dragon", "Team Barcode", "ExesAndOhs", 2, "0e6dc32a1d83a9a9a57d6199c8dd22"], ["Team Barcode", "Blue Dragon", "Bog", 2, "6f2a60bdfdecda7e0559614b72a17a"], ["Blue Dragon", "Team Barcode", "Chevron", 2, "a9d3308e2f907614b2ad20a58fb710"]], [["waffle", "Broccoli Bros !", "ExesAndOhs", 1, "dd079c9ede562d37ee90a6bf408cb2"], ["Broccoli Bros !", "waffle", "Bog", 2, "bf7cae120ca34d62f2860eb6306145"], ["waffle", "Broccoli Bros !", "Chevron", 1, "b0d66f44b26909e3a584a18d479430"]], [["AntiVaxxKids", "Galaga", "ExesAndOhs", 1, "5497eee5b0a7c3089f4193b60da310"], ["Galaga", "AntiVaxxKids", "Bog", 2, "555fe97ef0876403d5d392b410fdea"], ["AntiVaxxKids", "Galaga", "Chevron", 1, "10f93ad5bf84f6a6b303bbae563b71"]], [["wololo", "Kansas City Asians", "ExesAndOhs", 1, "41ab25579262c9525af9f943ec4430"], ["Kansas City Asians", "wololo", "Bog", 1, "886ae0d7b32a8adf5da8c7fec8f0b0"], ["wololo", "Kansas City Asians", "Chevron", 2, "10a04f7b8112679b0fc8036c3c39d9"]], [["cuttlefish", "Lee's Morons", "ExesAndOhs", 2, "7eee15b21046352842095471270797"], ["Lee's Morons", "cuttlefish", "Bog", 1, "02508f42909f8dc6aa7720c5161e74"], ["cuttlefish", "Lee's Morons", "Chevron", 2, "62341af9fc6c945ceb19b31a7c4aea"]], [["Hard Coders", "No Battlecode for Old Men", "ExesAndOhs", 1, "bc587c7387d0ba2e9396f4c8a61edb"], ["No Battlecode for Old Men", "Hard Coders", "Bog", 2, "cb344935b2827c904f699db665560b"], ["Hard Coders", "No Battlecode for Old Men", "Chevron", 1, "03d0933e37b1fe40ebc64090c3337e"]], [["Kryptonite", "Pain", "ExesAndOhs", 1, "d1f030ae4709db43c69169c08aed82"], ["Pain", "Kryptonite", "Bog", 2, "71e26937cfeefeac42206419247135"], ["Kryptonite", "Pain", "Chevron", 1, "cce94659d1c846222ed252782535ba"]], [["camel_case", "A214", "ExesAndOhs", 1, "501bcd34ba81394dcc2f31db3d75fb"], ["A214", "camel_case", "Bog", 2, "fac9ae25801e9eb5cc15b9bb7515b8"], ["camel_case", "A214", "Chevron", 1, "c3a3b715ebc5fb7e1ccbb589a8a657"]], [["tooOldForThis", "Oculus", "ExesAndOhs", 1, "7a61675ed375b21ff64153815412dd"], ["Oculus", "tooOldForThis", "Bog", 2, "db1fc171a8ac2fb3cd2a3bc3d14452"], ["tooOldForThis", "Oculus", "Chevron", 1, "b43fcbfda963e8d58b6986c78924b9"]], [["Super Cow Powers", "confused", "ExesAndOhs", 1, "f4dfbe7b70f265de7f0101c03b1990"], ["confused", "Super Cow Powers", "Bog", 2, "a1dee719fc137c6fa196a403850756"], ["Super Cow Powers", "confused", "Chevron", 1, "36f632b0c830d601c5a4d34b6dee2d"]], [["Play C. Holder", "pigeons", "ExesAndOhs", 2, "05348b0c9a064578dbab2eee9ef025"], ["pigeons", "Play C. Holder", "Bog", 1, "9d45571b95855e172323427c43be6a"], ["Play C. Holder", "pigeons", "Chevron", 1, "1e286a11d4b1e8e951a08f76387c8f"]], [["Rua!!", "(+[](){})();", "ExesAndOhs", 2, "2a60a3d21b5a6e9ffd64cf0d7721e4"], ["(+[](){})();", "Rua!!", "Bog", 2, "bfb75930648f831d4cbd497bb841e6"], ["Rua!!", "(+[](){})();", "Chevron", 1, "67af049be381aaf177f3842d4287bc"]], [["Huge L Club", "The Unladen Swallows", "ExesAndOhs", 1, "0c21755bfbcfb2f14ca81d726ee5bc"], ["The Unladen Swallows", "Huge L Club", "Bog", 2, "d56636c1416648f6f2a74361cf8b7e"], ["Huge L Club", "The Unladen Swallows", "Chevron", 1, "08a5a2dc5def0b568a4311c8d1dab6"]], [["3 Musketeers", "free boba", "ExesAndOhs", 1, "5265ad079c79e087c1ccefce01f804"], ["free boba", "3 Musketeers", "Bog", 2, "5be1f2d113369694f59ce6a4ebf7fd"], ["3 Musketeers", "free boba", "Chevron", 1, "6990108583403fc6e2be9f6e588b93"]], [["StepZero", "bumbum", "ExesAndOhs", 1, "022241fceaf7ca9a000ee93145513e"], ["bumbum", "StepZero", "Bog", 2, "17fcbe03de01b9cfb52768aa79b11e"], ["StepZero", "bumbum", "Chevron", 1, "8419ea381dfb3a6dda24c5a1e4e07f"]], [["Code Not Found", "What are you doing stepBot?", "ExesAndOhs", 2, "21385bdaf9eccd695baf0052f583cc"], ["What are you doing stepBot?", "Code Not Found", "Bog", 2, "33fa77d9b3247c641026ad99f8877f"], ["Code Not Found", "What are you doing stepBot?", "Chevron", 1, "6434ed05329392870fb2a67790b2c1"]], [["The Method.", "Blue Steel", "ExesAndOhs", 2, "10bde96addde86524368cb7a59d9f5"], ["Blue Steel", "The Method.", "Bog", 1, "60b03467942cf1b398542710891225"], ["The Method.", "Blue Steel", "Chevron", 2, "8b83daa01ee17d0deabeddd71190c2"]], [["Nikola", "Coconut9", "ExesAndOhs", 1, "851a0314b23b17be31d6003fad8444"], ["Coconut9", "Nikola", "Bog", 2, "57cadbea3910197b65569ca7675409"], ["Nikola", "Coconut9", "Chevron", 1, "58d8561109d039081848af3340d4b3"]], [["Download More RAM", "The Patriots", "ExesAndOhs", 1, "ed02197927b849ebfa6dec7063ec6b"], ["The Patriots", "Download More RAM", "Bog", 2, "88a9f8a4710f72f0fa0fd48ffdd7df"], ["Download More RAM", "The Patriots", "Chevron", 1, "c681104115288afea779d1e44695d6"]], [["Dis Team", "Daedalus", "ExesAndOhs", 1, "f71658ce0e3c25536a8330cbfbf4a5"], ["Daedalus", "Dis Team", "Bog", 2, "f2fe71704b50aaf1786f3caae19674"], ["Dis Team", "Daedalus", "Chevron", 1, "d62e1dfa42f9e7922cd585f5986132"]], [["Ripples", "0K", "ExesAndOhs", 1, "4c458b0a626dc9c1633428bb9a2634"], ["0K", "Ripples", "Bog", 2, "7e6c284c74b9edae73c9f27991dd99"], ["Ripples", "0K", "Chevron", 2, "dd3a4710bed854669523d711c824ba"]], [["Producing Perfection", "Engineer Gaming", "ExesAndOhs", 1, "fc11a02893546ec7a021d34e18a938"], ["Engineer Gaming", "Producing Perfection", "Bog", 2, "f8a6690c747de6fd8eda1a066e2a70"], ["Producing Perfection", "Engineer Gaming", "Chevron", 1, "ee40ca00a7e99f909ae18e0cb8f3da"]], [["Veto", "Up For A While", "ExesAndOhs", 1, "6960009ded2047c134d2c7f6eb3c0e"], ["Up For A While", "Veto", "Bog", 2, "0ba21993f235e0072dff7de1cb3bc4"], ["Veto", "Up For A While", "Chevron", 2, "6b0705f3eaa807500961cb8fe75441"]], [["blair blezers", "Malott Fat Cats", "ExesAndOhs", 1, "d7bbbd92f93fc6c81daf64dabc4e3c"], ["Malott Fat Cats", "blair blezers", "Bog", 1, "b6c25530c6e33da10af6cbf6abec99"], ["blair blezers", "Malott Fat Cats", "Chevron", 2, "7aaaeae16853b8c4de9620dea818ba"]], [["remotED", "ThotBot", "ExesAndOhs", 1, "f2125fd661fb7edc148899baaa7959"], ["ThotBot", "remotED", "Bog", 1, "caa249dbecdd4fb472dc425d7a6b6a"], ["remotED", "ThotBot", "Chevron", 1, "fdc3ccc949ea6b387c4112808f39fd"]], [["babyducks", "Coast", "Chevron", 1, "ba31fb94bb56253f3c815f8b1410ce"], ["Coast", "babyducks", "CrossStitch", 1, "eebe3f1c6a4a81ab60076aeca90ecb"], ["babyducks", "Coast", "CrownJewels", 1, "2b57056e04e2387133b1d5ab0b0991"]], [["Mars Analytica", "Bytecode Mafia", "Chevron", 1, "5f7ec0f068ac73d9d98de4006572da"], ["Bytecode Mafia", "Mars Analytica", "CrossStitch", 2, "c2de2b6a24976536db0cb841a290ad"], ["Mars Analytica", "Bytecode Mafia", "CrownJewels", 2, "1655404cc8df0b56def13a122c75e7"]], [["Chicken", "DaMa", "Chevron", 1, "a65d534870c5229ca6423b68c13ace"], ["DaMa", "Chicken", "CrossStitch", 2, "9edc7a4174040a012618d532618552"], ["Chicken", "DaMa", "CrownJewels", 1, "49da8c34c2f6e2b396359615eb54da"]], [["Chocolate Banana Cake", "Team Barcode", "Chevron", 2, "5eff0c192a84cb5324a84f11a828a4"], ["Team Barcode", "Chocolate Banana Cake", "CrossStitch", 1, "f942d1b9cff2687961d35406211c90"], ["Chocolate Banana Cake", "Team Barcode", "CrownJewels", 1, "af6385645df4b8cb59dec456b74b2e"]], [["waffle", "AntiVaxxKids", "Chevron", 1, "8c2bcef17704f12b5f7af2ec49cfdc"], ["AntiVaxxKids", "waffle", "CrossStitch", 2, "b8dcdd11799979cca176e47c776dd3"], ["waffle", "AntiVaxxKids", "CrownJewels", 1, "0568f56e3ad3be5c4bca31fd131011"]], [["Kansas City Asians", "Lee's Morons", "Chevron", 2, "a862d702b27986f18231ca8ea95863"], ["Lee's Morons", "Kansas City Asians", "CrossStitch", 1, "ae0e28a2fb15a33468251851cd2c87"], ["Kansas City Asians", "Lee's Morons", "CrownJewels", 2, "2f9f0ad470db3cfc81448c3d4f99be"]], [["Hard Coders", "Kryptonite", "Chevron", 2, "8ee187176d60359acd7dad513a74a6"], ["Kryptonite", "Hard Coders", "CrossStitch", 2, "c9da9ff91e49938df8ca76ca3bfb52"], ["Hard Coders", "Kryptonite", "CrownJewels", 2, "c70146607a429ddff4dc580ac36299"]], [["camel_case", "tooOldForThis", "Chevron", 1, "41f40a352dc394d6b40a96948681a5"], ["tooOldForThis", "camel_case", "CrossStitch", 1, "00836188f372dbd03fcf25f3afd7ee"], ["camel_case", "tooOldForThis", "CrownJewels", 1, "423fdb754ce3de38705328c8d187ce"]], [["Super Cow Powers", "pigeons", "Chevron", 1, "4913473aedc877d3745d444e53267a"], ["pigeons", "Super Cow Powers", "CrossStitch", 2, "41162ea1e474bfc59f832fcc190191"], ["Super Cow Powers", "pigeons", "CrownJewels", 1, "449445dec8a0c29060c6affb47ba27"]], [["Rua!!", "Huge L Club", "Chevron", 1, "c9b7fcb86af087ee187b95dd6aa3ef"], ["Huge L Club", "Rua!!", "CrossStitch", 2, "660c6a0ab8420e9aebca44830ff0de"], ["Rua!!", "Huge L Club", "CrownJewels", 2, "17f9401653865c6d25ab2000d80d8a"]], [["3 Musketeers", "StepZero", "Chevron", 1, "09636d2c31d464873706a5340bc67c"], ["StepZero", "3 Musketeers", "CrossStitch", 2, "82141563bb94ac70bafa34b5f25ffc"], ["3 Musketeers", "StepZero", "CrownJewels", 1, "74abb881f0286f8e676dbed31056a7"]], [["Code Not Found", "Blue Steel", "Chevron", 1, "bd4a65357952ec08525124763a8c26"], ["Blue Steel", "Code Not Found", "CrossStitch", 1, "9e476e2e03d8d99835e68e16ee2a92"], ["Code Not Found", "Blue Steel", "CrownJewels", 2, "860e0cba96928b2d67eefe731a4840"]], [["Nikola", "Download More RAM", "Chevron", 2, "8ca9e4c7750f9ab3a8b699ed95147c"], ["Download More RAM", "Nikola", "CrossStitch", 2, "de75d7f5f873ce5b77ab4bc7e72553"], ["Nikola", "Download More RAM", "CrownJewels", 2, "046413c1233738e9e2fd13ce29cf6c"]], [["Dis Team", "Ripples", "Chevron", 1, "d5028d1e70367330574ce71525cd84"], ["Ripples", "Dis Team", "CrossStitch", 2, "f94bcdf2f33300bc857b1be073d889"], ["Dis Team", "Ripples", "CrownJewels", 2, "18af2775d6a936e3d37bbd74f71100"]], [["Producing Perfection", "Veto", "Chevron", 1, "9eb4182fe7d871f5803b779fd236a9"], ["Veto", "Producing Perfection", "CrossStitch", 2, "9de7cf111587d4f0e2a3748f6c05e4"], ["Producing Perfection", "Veto", "CrownJewels", 1, "c1f24fdea939307c11faa7978a6f9b"]], [["Malott Fat Cats", "remotED", "Chevron", 2, "a7ecb60bf5ee5d5d0f4a3d475ce805"], ["remotED", "Malott Fat Cats", "CrossStitch", 1, "3bbc75d27becb6dfc064d28dd34634"], ["Malott Fat Cats", "remotED", "CrownJewels", 2, "3c56ddfee960cfdb495323d6a17fd9"]], [["babyducks", "Mars Analytica", "CrownJewels", 1, "c8d1b602f9a67d35d038991f9fc4a3"], ["Mars Analytica", "babyducks", "Cow", 2, "9b7bce1ad0efa869848c0c2fdb5169"], ["babyducks", "Mars Analytica", "Branches", 1, "960184dd05cd5145a345303e469eaf"]], [["Chicken", "Team Barcode", "CrownJewels", 1, "7f2b3699cd3aab7c5658bb14813851"], ["Team Barcode", "Chicken", "Cow", 2, "e452686e275b1b21eaf7f81e33ff88"], ["Chicken", "Team Barcode", "Branches", 1, "df9812b835a5010ad11dcd90b4df50"]], [["waffle", "Lee's Morons", "CrownJewels", 1, "77eafcdd8b4bbba77a6dfed879f119"], ["Lee's Morons", "waffle", "Cow", 2, "e91ab9ef81423261f933111de4d2c9"], ["waffle", "Lee's Morons", "Branches", 1, "65bcbc78f68924d5770d9a2f99aa81"]], [["Kryptonite", "camel_case", "CrownJewels", 1, "7a6e5f11c019772555a0c371b6dd81"], ["camel_case", "Kryptonite", "Cow", 2, "4b470c1a445a8e57bff7f248aa4338"], ["Kryptonite", "camel_case", "Branches", 1, "6f7bd0ffc463b34e72cbc550930f1b"]], [["Super Cow Powers", "Rua!!", "CrownJewels", 1, "65bf80e805200eab4c15841c87df6f"], ["Rua!!", "Super Cow Powers", "Cow", 2, "8a6ea307a56a569dfa83a8989b27c7"], ["Super Cow Powers", "Rua!!", "Branches", 1, "394c65e85bbe7b3ff1059d6812d581"]], [["3 Musketeers", "Blue Steel", "CrownJewels", 1, "f830bc3fc373c161baf1e777fbcbec"], ["Blue Steel", "3 Musketeers", "Cow", 2, "f8e9809360a2dcecb7e74e688bcaee"], ["3 Musketeers", "Blue Steel", "Branches", 1, "343c11636e213103ce8b68d01084ce"]], [["Download More RAM", "Dis Team", "CrownJewels", 2, "22be024c0b3cc4c454bd0d08137a23"], ["Dis Team", "Download More RAM", "Cow", 1, "c74b88fd034d72d0b8ba969cc952e6"], ["Download More RAM", "Dis Team", "Branches", 1, "74ae39d307074b177fdce96b69078a"]], [["Producing Perfection", "remotED", "CrownJewels", 1, "6363c8db4c8a72ce65da9912f41a77"], ["remotED", "Producing Perfection", "Cow", 2, "b2da277f365f7465ecdbda0bf31799"], ["Producing Perfection", "remotED", "Branches", 1, "ef1936d20e319c6524703435968d76"]], [["babyducks", "Chicken", "Branches", 1, "e932b72f70a484455a879bbd0164f1"], ["Chicken", "babyducks", "Andromeda", 2, "1142f78b7e58e8f4116ec4649f6a11"], ["babyducks", "Chicken", "FiveOfHearts", 1, "380019a7e14f76541c6027d3faa2d8"]], [["waffle", "Kryptonite", "Branches", 1, "87a698bbe39c11f9b67d87124535b6"], ["Kryptonite", "waffle", "Andromeda", 2, "9b853a833b78e6afdeb219308f4674"], ["waffle", "Kryptonite", "FiveOfHearts", 1, "872f3ecb929b5e32ff376fb984a5bc"]], [["Super Cow Powers", "3 Musketeers", "Branches", 1, "c308e3e2917e470ba8a1211890a92b"], ["3 Musketeers", "Super Cow Powers", "Andromeda", 2, "dc97fb822d01d1ffd7f358d4c6f4ab"], ["Super Cow Powers", "3 Musketeers", "FiveOfHearts", 1, "f42acba5680ebba1ac0cb8562c9326"]], [["Dis Team", "Producing Perfection", "Branches", 2, "ff4bf1c12dbb1c8125e59714728950"], ["Producing Perfection", "Dis Team", "Andromeda", 1, "bd8a324f514d8c69cf5f234ff1ca5e"], ["Dis Team", "Producing Perfection", "FiveOfHearts", 2, "ed1923f7ba85f2481c7ea0d995a75b"]], [["babyducks", "waffle", "FiveOfHearts", 1, "5eaf0f18abb49eebbee465894eea9b"], ["waffle", "babyducks", "Rainbow", 2, "e3aafe5c91ecc8520b4e8a52fd7ef2"], ["babyducks", "waffle", "Illusion", 1, "f83bee62b7a70e6323bc96a620894e"]], [["Super Cow Powers", "Producing Perfection", "FiveOfHearts", 1, "f1c847903038ac8796e09378d6c16f"], ["Producing Perfection", "Super Cow Powers", "Rainbow", 2, "e630c7f313ac4661cdad77ed9b29d4"], ["Super Cow Powers", "Producing Perfection", "Illusion", 1, "07c3e65dd56902bc0428ceca897d5d"]], [["babyducks", "Super Cow Powers", "Illusion", 2, "2d862833a6366076aa64839c2d843f"], ["Super Cow Powers", "babyducks", "Snowflake", 1, "ae2ec48c78a79112e3abe6ec8baee4"], ["babyducks", "Super Cow Powers", "NotAPuzzle", 1, "f8ce4525b058cc10a5011961cab46d"]]] \ No newline at end of file diff --git a/infrastructure/tournament-util/data/1-sprint1/replay_dump_parsed.txt b/infrastructure/tournament-util/data/1-sprint1/replay_dump_parsed.txt new file mode 100644 index 00000000..01d68977 --- /dev/null +++ b/infrastructure/tournament-util/data/1-sprint1/replay_dump_parsed.txt @@ -0,0 +1,2408 @@ +Amoosed -vs- NeoHazard +winner: NeoHazard +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4e68fe7ab6b6e496a6fc4a214b855d.bc21 + +NeoHazard -vs- Amoosed +winner: Amoosed +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b3352046408750bb97adc2281b836a.bc21 + +Amoosed -vs- NeoHazard +winner: NeoHazard +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/efb06babb96565db2cad1fceb977ba.bc21 + + + +BeachBANDITS -vs- Pi over squared +winner: BeachBANDITS +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ac9f36bc55ff8edfddd0539da127b1.bc21 + +Pi over squared -vs- BeachBANDITS +winner: BeachBANDITS +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2b064301799d2b60a443f12b51bbc4.bc21 + +BeachBANDITS -vs- Pi over squared +winner: BeachBANDITS +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7f7ad5049ce3b594e5fbc75c208a19.bc21 + + + +Egg Clan -vs- polyteam version 2 +winner: polyteam version 2 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7ce4e95730a958acb1ab21a05807cd.bc21 + +polyteam version 2 -vs- Egg Clan +winner: polyteam version 2 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d40f17a47ad353bba401a9d21c7eb6.bc21 + +Egg Clan -vs- polyteam version 2 +winner: polyteam version 2 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8d5593694ba9884d47fc96325125c1.bc21 + + + +boib -vs- The Al Gore Rhythm +winner: The Al Gore Rhythm +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/97220f5513b6996942db248eaccfbb.bc21 + +The Al Gore Rhythm -vs- boib +winner: The Al Gore Rhythm +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/94bea2ecb76d83a76fc9b21fc0d836.bc21 + +boib -vs- The Al Gore Rhythm +winner: The Al Gore Rhythm +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/39679aa7625afa87cb7cdaee3b9342.bc21 + + + +Alpha Centauri -vs- ButterBois +winner: Alpha Centauri +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/72b421c1f4dbd2af05348d4595a615.bc21 + +ButterBois -vs- Alpha Centauri +winner: Alpha Centauri +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/acae997799723509313823bb5f9dea.bc21 + +Alpha Centauri -vs- ButterBois +winner: Alpha Centauri +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/84f48d7045e50d9deead749215305c.bc21 + + + +Serpentine - M.A.R.S. -vs- The Lurkers in the Wire +winner: Serpentine - M.A.R.S. +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/15700ec0d11733be18687bdc5cd4c5.bc21 + +The Lurkers in the Wire -vs- Serpentine - M.A.R.S. +winner: The Lurkers in the Wire +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/07bf6b158f1ce2c7896f147908315a.bc21 + +Serpentine - M.A.R.S. -vs- The Lurkers in the Wire +winner: Serpentine - M.A.R.S. +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/cb490bf363d82307bc43dcdbbff779.bc21 + + + +Principia -vs- The Eager Sloths +winner: Principia +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/aef6a3bb5f02ccaf29e2bc7d7e38fe.bc21 + +The Eager Sloths -vs- Principia +winner: Principia +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8f8f1614bdd95426a2ec8867be7e75.bc21 + +Principia -vs- The Eager Sloths +winner: Principia +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/614d6a2fbb6b4a6b08d0c391b9dbb2.bc21 + + + +DaMa -vs- Balloon Platoon +winner: DaMa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0a359a03d59f88b675e6f32b88ad68.bc21 + +Balloon Platoon -vs- DaMa +winner: DaMa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1d5fe15e696f5f2acddbf316ec0383.bc21 + +DaMa -vs- Balloon Platoon +winner: DaMa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4320fdba3df68d2420e14e18ca07ae.bc21 + + + +helloMars -vs- java :ghosthug: +winner: java :ghosthug: +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e605bab770f6965cb042cca18a7f26.bc21 + +java :ghosthug: -vs- helloMars +winner: java :ghosthug: +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/043f4b3a668e5c4a8bf657903a92b2.bc21 + +helloMars -vs- java :ghosthug: +winner: java :ghosthug: +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d080e9a958568493bcca25e34605b0.bc21 + + + +Team Confused -vs- JT5 +winner: Team Confused +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/06d31819a6d736da3417e13aa54d56.bc21 + +JT5 -vs- Team Confused +winner: Team Confused +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/249400fcd2453e46b9e4329514946a.bc21 + +Team Confused -vs- JT5 +winner: Team Confused +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f36a8e1042091208d57c0196a1d7eb.bc21 + + + +CHAD -vs- BearFish +winner: CHAD +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f76ad509fd829c08b2bdd255d1151b.bc21 + +BearFish -vs- CHAD +winner: CHAD +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e65bc4afc7c99765f5287228e71d77.bc21 + +CHAD -vs- BearFish +winner: CHAD +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5be9eb1959e242c9084527ae388c63.bc21 + + + +Python Waifu -vs- Intrepid losers +winner: Intrepid losers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/15eba1b76526770d3ece42bc74d22c.bc21 + +Intrepid losers -vs- Python Waifu +winner: Python Waifu +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1f7dd4865c7b5b4fdec48ec554b049.bc21 + +Python Waifu -vs- Intrepid losers +winner: Python Waifu +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d5894d13d35df8d3da5f64dd939ff4.bc21 + + + +JavaScrapped -vs- holy choir +winner: JavaScrapped +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/133644580e37276775d257e58ffe1b.bc21 + +holy choir -vs- JavaScrapped +winner: JavaScrapped +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/344c620f7be4783a9a48ebbb2c4343.bc21 + +JavaScrapped -vs- holy choir +winner: JavaScrapped +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e66f2dfd081b59b888863697d5ae4c.bc21 + + + +Goreteks -vs- Children of Talos +winner: Goreteks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a7f9f2b96ae6b4f143bf1baf2ec35f.bc21 + +Children of Talos -vs- Goreteks +winner: Goreteks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a71b18b7d2734207e2982b62c3ae63.bc21 + +Goreteks -vs- Children of Talos +winner: Goreteks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/fee62bb3cfc804500076024d36b9ab.bc21 + + + +InfiniteLoop -vs- Team_of_One +winner: Team_of_One +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/cf5121bd2444f8371b896bc357fe98.bc21 + +Team_of_One -vs- InfiniteLoop +winner: Team_of_One +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0e4c73fb6e87f88e516dc5f3312632.bc21 + +InfiniteLoop -vs- Team_of_One +winner: Team_of_One +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/54ed6b350d3216022c188a65110d10.bc21 + + + +cuttlefish -vs- SeizeMeansOfSoftwareProduction +winner: cuttlefish +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ec22af09313b4f9d86fbb7dc8e8949.bc21 + +SeizeMeansOfSoftwareProduction -vs- cuttlefish +winner: cuttlefish +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/3cd6426e440f3c1e30fa5175965823.bc21 + +cuttlefish -vs- SeizeMeansOfSoftwareProduction +winner: cuttlefish +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a7440d90ea252fbf63152d4df7cf32.bc21 + + + +Cavalier -vs- Rael Tarmo Tähvend +winner: Cavalier +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c42feb44629966d63ffab5bb4b5bf8.bc21 + +Rael Tarmo Tähvend -vs- Cavalier +winner: Cavalier +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4ad3edb6e651537863fa37ff76ff5a.bc21 + +Cavalier -vs- Rael Tarmo Tähvend +winner: Cavalier +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/694ff508861d7ee4cbdafcb9150800.bc21 + + + +PenguinBattler -vs- Quantum +winner: PenguinBattler +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6a3fdcef0ebbf93831daf7c76881ce.bc21 + +Quantum -vs- PenguinBattler +winner: PenguinBattler +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b076bafb5b511b13e1ab3fe3795b77.bc21 + +PenguinBattler -vs- Quantum +winner: PenguinBattler +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c7d946efa55102f11bf03c93fcbd35.bc21 + + + +ginger cow -vs- 『100910』 +winner: ginger cow +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f4184dc6dccd266dc0128512c01f6e.bc21 + +『100910』 -vs- ginger cow +winner: ginger cow +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8bce17b0d8fbb1132d150b998d079b.bc21 + +ginger cow -vs- 『100910』 +winner: ginger cow +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9be0ec55206196d2d8eb38cf1b3d79.bc21 + + + +The Matarrhites -vs- Endless Downpour +winner: The Matarrhites +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/73802e67600827838cff5f2e96a751.bc21 + +Endless Downpour -vs- The Matarrhites +winner: The Matarrhites +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/791f20a65c96cb9ecfd1898b66a5be.bc21 + +The Matarrhites -vs- Endless Downpour +winner: The Matarrhites +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c3b62e73bd50f559fa609ad5460d04.bc21 + + + +idrc -vs- CHINA +winner: idrc +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/334acf16bd8a6f8725a6cfed812aea.bc21 + +CHINA -vs- idrc +winner: idrc +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f568c1a1e4524e2cfc7155f3e7810e.bc21 + +idrc -vs- CHINA +winner: idrc +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8ecc9df8db6848621501306f37d37e.bc21 + + + +elongatedmuskrat -vs- JT6 +winner: elongatedmuskrat +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a5cc30664790d38710bdaab895e70f.bc21 + +JT6 -vs- elongatedmuskrat +winner: elongatedmuskrat +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6916a4906d5973a86524627436893b.bc21 + +elongatedmuskrat -vs- JT6 +winner: elongatedmuskrat +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8b965df23c4a7e51c5203e751b8437.bc21 + + + +Joe -vs- Handshakers x3 +winner: Joe +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/3e9259fdf6463b650d055418b28dd8.bc21 + +Handshakers x3 -vs- Joe +winner: Joe +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4c621236ce56ccdf99604b6fafa82c.bc21 + +Joe -vs- Handshakers x3 +winner: Joe +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/38a187c6afe133c453e3551df8aba6.bc21 + + + +Big Oh -vs- Oni Phantom Pog +winner: Big Oh +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4679f5e7a918bbb397d3e88be62490.bc21 + +Oni Phantom Pog -vs- Big Oh +winner: Big Oh +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/33865665ecd0b261cfde6cad6f1dde.bc21 + +Big Oh -vs- Oni Phantom Pog +winner: Big Oh +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/78a19c953507551e7e8e6cc85d1342.bc21 + + + +0x5f3759df -vs- null +winner: 0x5f3759df +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/431f6b3fa895e567c6079f16693c63.bc21 + +null -vs- 0x5f3759df +winner: 0x5f3759df +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1f26e1a8e08baa786f04ab685d1fb9.bc21 + +0x5f3759df -vs- null +winner: 0x5f3759df +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2cf63a96de6979175758a32b7ae04b.bc21 + + + +pigeons -vs- JT3 +winner: pigeons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/830d9e603f42fd6255c6c7260dc860.bc21 + +JT3 -vs- pigeons +winner: pigeons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/591d726b832e11e8ab37f1ccb339ab.bc21 + +pigeons -vs- JT3 +winner: pigeons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e33bbb95cc5c8631b7f07b4ff6dd1e.bc21 + + + +Serpentine - Viper -vs- Snakes and Ladders +winner: Serpentine - Viper +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/08c2c84b2860a34ebe8ff4f111d6da.bc21 + +Snakes and Ladders -vs- Serpentine - Viper +winner: Serpentine - Viper +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/02d006452462913bb63e4cf31774c1.bc21 + +Serpentine - Viper -vs- Snakes and Ladders +winner: Serpentine - Viper +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/58a309702b816a5422456628005b45.bc21 + + + +lit -vs- Beginner +winner: lit +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5c3d81e576a883bb02df5488c3d020.bc21 + +Beginner -vs- lit +winner: lit +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/3106f347ac9d399b3aa65504cb8041.bc21 + +lit -vs- Beginner +winner: lit +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/3536bb2a0468e1af171d3cc839386e.bc21 + + + +Scopes Monkey Trial 1925 -vs- JT2 +winner: JT2 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7c068cbc9c1903cd96ebce2709e0f4.bc21 + +JT2 -vs- Scopes Monkey Trial 1925 +winner: JT2 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/df149a4cdfb976839344039727da52.bc21 + +Scopes Monkey Trial 1925 -vs- JT2 +winner: JT2 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8aa1590b33bb38bce1cb372cba1ba9.bc21 + + + +Hippocrene -vs- JT1 +winner: Hippocrene +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e72244918702746e653f1b06ed27a0.bc21 + +JT1 -vs- Hippocrene +winner: Hippocrene +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/45403de77328e216607176aef41a15.bc21 + +Hippocrene -vs- JT1 +winner: Hippocrene +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9fda89cbd8349609a71c4bb6a1c892.bc21 + + + +flexqueue -vs- GCI Gophers +winner: GCI Gophers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4bddf643ea1491f7e709c75d5e2f86.bc21 + +GCI Gophers -vs- flexqueue +winner: GCI Gophers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/24376c7d3fd75cd91b1ba8a7d34d8c.bc21 + +flexqueue -vs- GCI Gophers +winner: flexqueue +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a8e6982b3a7d2145f24ac3042b9b8b.bc21 + + + +CamelMan -vs- 21 Codestreet +winner: 21 Codestreet +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f23e642c6807c6eb31cbc8efe29229.bc21 + +21 Codestreet -vs- CamelMan +winner: CamelMan +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d925716dbb1c3a10b71baa673fafdd.bc21 + +CamelMan -vs- 21 Codestreet +winner: 21 Codestreet +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a06a6850f8c6e7da7e77acf6ded9c6.bc21 + + + +The Method. -vs- Borscht Bot +winner: The Method. +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c468e13928e759ecf47fb2727f5b82.bc21 + +Borscht Bot -vs- The Method. +winner: The Method. +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/49f5f0dbad77aeb7a749f489376a81.bc21 + +The Method. -vs- Borscht Bot +winner: The Method. +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1a1328ddfc69f099658df5c4527031.bc21 + + + +monky -vs- Solexa +winner: monky +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6536360d3e26ecd5acf7ecb119618e.bc21 + +Solexa -vs- monky +winner: monky +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0e5a6c9b311b93fe532ae71daad6ea.bc21 + +monky -vs- Solexa +winner: monky +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f909531f95f4f060b791e74f396554.bc21 + + + +MossyBot -vs- Homeless Coders +winner: MossyBot +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/776c9f15ce34c27011a9bd88e9bf7a.bc21 + +Homeless Coders -vs- MossyBot +winner: MossyBot +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/35a6709cbc8f5d9a0bb08d2966a08f.bc21 + +MossyBot -vs- Homeless Coders +winner: MossyBot +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b2cc581f91fa00bdf6d4c4a355ab21.bc21 + + + +GHS Guardians -vs- CMU-MIT combo plater +winner: GHS Guardians +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ba2f3c3d0ee79cc72db0169311c7b9.bc21 + +CMU-MIT combo plater -vs- GHS Guardians +winner: GHS Guardians +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/db4902e027040ee3520f8ebf308a23.bc21 + +GHS Guardians -vs- CMU-MIT combo plater +winner: GHS Guardians +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5d917f2099103efb94da89edeae26e.bc21 + + + +Yeet Pull -vs- doesthisevenwork +winner: Yeet Pull +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/fb8a724947a86601a2dba2697f42a5.bc21 + +doesthisevenwork -vs- Yeet Pull +winner: Yeet Pull +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a6cbc58472043aa82a773246f55ba6.bc21 + +Yeet Pull -vs- doesthisevenwork +winner: Yeet Pull +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/163660b764ec3bc91a924df92f71e8.bc21 + + + +Touhutippa -vs- BattlePath +winner: Touhutippa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0a0704bd5a2476b5c3e78fe5da22f1.bc21 + +BattlePath -vs- Touhutippa +winner: Touhutippa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/959a2950a12a2bc243c93919640e1f.bc21 + +Touhutippa -vs- BattlePath +winner: Touhutippa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b749a600531d854251ea1552190fd5.bc21 + + + +Hertzhaft -vs- HedgeTech +winner: Hertzhaft +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4a00a2888b3dda04f32d940654b0fd.bc21 + +HedgeTech -vs- Hertzhaft +winner: Hertzhaft +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0116a4270defcebb399597456e4444.bc21 + +Hertzhaft -vs- HedgeTech +winner: Hertzhaft +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4fe3b0327a87217d6549b7c8d4e886.bc21 + + + +JT4 -vs- 69 Tons More Data +winner: JT4 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d2a57c8b3d1123de9dcaaed995d375.bc21 + +69 Tons More Data -vs- JT4 +winner: JT4 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2f51b173701ada29079b593639f21a.bc21 + +JT4 -vs- 69 Tons More Data +winner: JT4 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/20f48d123ba9ff16e39de6e2fb611e.bc21 + + + +Veto -vs- Toot Toot Train +winner: Veto +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6cc35103615949b13d295d6538d51c.bc21 + +Toot Toot Train -vs- Veto +winner: Veto +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/de158e02e7970afd222ef48c0c9ef8.bc21 + +Veto -vs- Toot Toot Train +winner: Veto +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1c800d87b731633e7333a5dd13af60.bc21 + + + +YA BOI -vs- Burnerteam; passwordisqweasdqweasd +winner: YA BOI +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9570fdc575037869917206a9f8901c.bc21 + +Burnerteam; passwordisqweasdqweasd -vs- YA BOI +winner: YA BOI +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/05dbabc18b1e1343a7e6fbe20c5290.bc21 + +YA BOI -vs- Burnerteam; passwordisqweasdqweasd +winner: YA BOI +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/01d683747a5d3bdb4399d04c93eece.bc21 + + + +blair blezers -vs- Sagittarius A* Algorithm +winner: blair blezers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ccd7ecdbd6385e19e0d4ec7970f681.bc21 + +Sagittarius A* Algorithm -vs- blair blezers +winner: blair blezers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7b089f1f5a1feb3dc83ae83a256993.bc21 + +blair blezers -vs- Sagittarius A* Algorithm +winner: blair blezers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/90e07498297484ec879db5daa0a784.bc21 + + + +ACM @ UNCC -vs- MinneCal +winner: ACM @ UNCC +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0b64bef7fad0c69144131d22b4d037.bc21 + +MinneCal -vs- ACM @ UNCC +winner: ACM @ UNCC +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/fb2fbfff1900933152be2d4f6e8db2.bc21 + +ACM @ UNCC -vs- MinneCal +winner: ACM @ UNCC +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/636680747e7aefb01c1cdd387d817e.bc21 + + + +ThotBot -vs- sinksanksunk +winner: ThotBot +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e3cc823d9c70ff143465ee035bd671.bc21 + +sinksanksunk -vs- ThotBot +winner: ThotBot +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/214dafd6a0768c208ed94e87d6b9aa.bc21 + +ThotBot -vs- sinksanksunk +winner: ThotBot +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/50fb88aa53172148db35927d617178.bc21 + + + +$nowball -vs- CodeReapers +winner: CodeReapers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c3e0cec57197f8aefaa213a9d85a11.bc21 + +CodeReapers -vs- $nowball +winner: CodeReapers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2f438063ed778e880db6b107ff5cc1.bc21 + +$nowball -vs- CodeReapers +winner: CodeReapers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/deee4137b52a975df508c86d36b403.bc21 + + + +The 501st -vs- The other team +winner: The other team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/42e442e1448cf4aa5e53f7db865afd.bc21 + +The other team -vs- The 501st +winner: The other team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ebdb09be67446b0bb7eea9157c9bb2.bc21 + +The 501st -vs- The other team +winner: The other team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/386621ef51c53942f6e10cb6a076a5.bc21 + + + +01001000 -vs- Team Awesome +winner: Team Awesome +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/fe2c45b5e219d9d81d395468a33fcd.bc21 + +Team Awesome -vs- 01001000 +winner: Team Awesome +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b418fbad77f19aaa098b25df071405.bc21 + +01001000 -vs- Team Awesome +winner: Team Awesome +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6fd80b71700f9c9cf6b60cf7822962.bc21 + + + +Mycroft -vs- BossTweed +winner: BossTweed +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b81e38dd03f67093d90de819f1903d.bc21 + +BossTweed -vs- Mycroft +winner: BossTweed +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4f906b049de725d40775b66a2da48a.bc21 + +Mycroft -vs- BossTweed +winner: BossTweed +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/79d986e165852df617f78686668dc3.bc21 + + + +Ctrl Alt Elite -vs- fishy yum +winner: Ctrl Alt Elite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/bd32cd7262ccb19ae41159b677a05e.bc21 + +fishy yum -vs- Ctrl Alt Elite +winner: Ctrl Alt Elite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/96940008a63a9dad0e42a24b4601e0.bc21 + +Ctrl Alt Elite -vs- fishy yum +winner: fishy yum +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c4964c24ba9ca0bd16c8990550e568.bc21 + + + +Broccoli Bros ! -vs- ERO2 +winner: Broccoli Bros ! +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5c19756616ac46ff09571eae12ebcd.bc21 + +ERO2 -vs- Broccoli Bros ! +winner: Broccoli Bros ! +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6fb58bc654adba091fbb789a096cce.bc21 + +Broccoli Bros ! -vs- ERO2 +winner: Broccoli Bros ! +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4b7b40b66629535ca0f91a19ffa24f.bc21 + + + +Kansas City Asians -vs- Stepstool +winner: Kansas City Asians +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8136c42139f66736f42e99fbb84906.bc21 + +Stepstool -vs- Kansas City Asians +winner: Kansas City Asians +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/dbcae8fe2e552cc67caaba2d6bf600.bc21 + +Kansas City Asians -vs- Stepstool +winner: Kansas City Asians +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0f9cbd5012d8c3ad24d94dbbc93569.bc21 + + + +No Battlecode for Old Men -vs- Influencer Force +winner: No Battlecode for Old Men +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/645881a0279c6fe1c69243e36ffe8b.bc21 + +Influencer Force -vs- No Battlecode for Old Men +winner: Influencer Force +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c3a7b60de622c863949b90e2db9cb6.bc21 + +No Battlecode for Old Men -vs- Influencer Force +winner: No Battlecode for Old Men +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9166acd56b0730c7bb8ff6ba536503.bc21 + + + +armed pythons -vs- A214 +winner: A214 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/fbfaef7f645e0acd7515948046cdb4.bc21 + +A214 -vs- armed pythons +winner: A214 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f5abcc164560e96a5adf17a4cce895.bc21 + +armed pythons -vs- A214 +winner: A214 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e6186fc321bb6f32c5953e0702fcbb.bc21 + + + +confused -vs- Soju +winner: Soju +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8c00a45b5a7d96147bba18bee8f762.bc21 + +Soju -vs- confused +winner: confused +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/03563d74e4037fee34ba4e98bed714.bc21 + +confused -vs- Soju +winner: confused +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4b21deedf40c86b69354d5b26eeb93.bc21 + + + +(+[](){})(); -vs- ﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽ +winner: ﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽ +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2068a93dc656b294485c4c22abb70f.bc21 + +﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽ -vs- (+[](){})(); +winner: (+[](){})(); +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a2ada8d11ba14e87d385b4e8cecdda.bc21 + +(+[](){})(); -vs- ﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽ +winner: (+[](){})(); +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f4e14926c590f06402d25f13ec0a24.bc21 + + + +Ctrl Alt Defeat -vs- The Unladen Swallows +winner: The Unladen Swallows +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6449dca66194609d09217128fcd074.bc21 + +The Unladen Swallows -vs- Ctrl Alt Defeat +winner: The Unladen Swallows +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/dc3f6abdf172cc4d4c657e354efaa8.bc21 + +Ctrl Alt Defeat -vs- The Unladen Swallows +winner: The Unladen Swallows +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a98e4933f5b9e8583389dfa1fe0745.bc21 + + + +bombocombo -vs- free boba +winner: free boba +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/3f2cee7472eba4cb293af956659f41.bc21 + +free boba -vs- bombocombo +winner: free boba +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8c892fda4686017888677e581e52df.bc21 + +bombocombo -vs- free boba +winner: free boba +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/bc6b33d25053ef96391eacda8e61ff.bc21 + + + +random -vs- What are you doing stepBot? +winner: What are you doing stepBot? +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/074b64ee6dfeb63e9e096c1f006920.bc21 + +What are you doing stepBot? -vs- random +winner: What are you doing stepBot? +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e38dff6fce4d940ca5feac669af2fd.bc21 + +random -vs- What are you doing stepBot? +winner: random +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5ee44cfad76fa4a85a9e58644b678b.bc21 + + + +Team Nit(h)ya -vs- Coconut9 +winner: Coconut9 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a49f951d50985f22c4940e579d302d.bc21 + +Coconut9 -vs- Team Nit(h)ya +winner: Coconut9 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9a0e23feebb68f8e6feb015059db4d.bc21 + +Team Nit(h)ya -vs- Coconut9 +winner: Coconut9 +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/14105d0d074b2f6f08f7b608fedbc0.bc21 + + + +Daedalus -vs- 2b1y +winner: Daedalus +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c6740d19fa6ecef91bd8eb6aeb67c6.bc21 + +2b1y -vs- Daedalus +winner: Daedalus +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1b6ac77b2d25756f82273913af3186.bc21 + +Daedalus -vs- 2b1y +winner: Daedalus +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c2c11e41508ee7f222167783048a15.bc21 + + + +Double J -vs- 0K +winner: 0K +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/116cc9dba03e45c6de79eb8641d650.bc21 + +0K -vs- Double J +winner: 0K +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/63959603d6f1fb0f12ff2c3fa5f0bc.bc21 + +Double J -vs- 0K +winner: 0K +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/517e450bb135509636b531a5cdb93a.bc21 + + + +The Night's Watch -vs- Engineer Gaming +winner: Engineer Gaming +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f4aec9a65586bcd3617adbb47bbc0d.bc21 + +Engineer Gaming -vs- The Night's Watch +winner: Engineer Gaming +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/78592de32d3a4d2d1f2f0a9e4a46ca.bc21 + +The Night's Watch -vs- Engineer Gaming +winner: Engineer Gaming +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/614d175ae3cb07f46794036cc1f212.bc21 + + + +Propaganda Machine -vs- Malott Fat Cats +winner: Propaganda Machine +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6d27815dda691ff55633baabd06af9.bc21 + +Malott Fat Cats -vs- Propaganda Machine +winner: Malott Fat Cats +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7434a4f26519f39c1011fdcef5aca9.bc21 + +Propaganda Machine -vs- Malott Fat Cats +winner: Malott Fat Cats +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/271b8ff119b494b87d04208766fb1f.bc21 + + + +babyducks -vs- NeoHazard +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0f33d7d04d019d4d218b2da285cb29.bc21 + +NeoHazard -vs- babyducks +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/27da4012e3d9a8aaf545b065b5991b.bc21 + +babyducks -vs- NeoHazard +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7788274440de7d5ccbf18a29c15ef3.bc21 + + + +Yeicor -vs- BeachBANDITS +winner: Yeicor +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/32d8a111e8043d82094c9cc3eedc8a.bc21 + +BeachBANDITS -vs- Yeicor +winner: Yeicor +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/75cb625710f02b250af5149dc9754a.bc21 + +Yeicor -vs- BeachBANDITS +winner: Yeicor +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7b4e212e28e72e4a3c032e202b7c49.bc21 + + + +Coast -vs- polyteam version 2 +winner: Coast +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9ecd0aa1ac8329009979c84912d968.bc21 + +polyteam version 2 -vs- Coast +winner: Coast +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ee80b1f43ba18c35358fcfdb9a1cda.bc21 + +Coast -vs- polyteam version 2 +winner: Coast +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/83eabb10b388cbab0ca998665df8e6.bc21 + + + +Mars Analytica -vs- The Al Gore Rhythm +winner: The Al Gore Rhythm +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e61e9579178cd429ede4352b29c36c.bc21 + +The Al Gore Rhythm -vs- Mars Analytica +winner: Mars Analytica +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/16a3660d18ab528e7e8eea3f59d697.bc21 + +Mars Analytica -vs- The Al Gore Rhythm +winner: Mars Analytica +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5a496dfc61a2163f2d74b03352d718.bc21 + + + +Bytecode Mafia -vs- Alpha Centauri +winner: Bytecode Mafia +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/307734dbd0b6e06ecefe3dc395e45e.bc21 + +Alpha Centauri -vs- Bytecode Mafia +winner: Bytecode Mafia +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f84067d9a74f575ff2c7bf2d5346da.bc21 + +Bytecode Mafia -vs- Alpha Centauri +winner: Bytecode Mafia +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/96526c1e63b5122590c09590f34fa4.bc21 + + + +Chicken -vs- Serpentine - M.A.R.S. +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c29b11d5cbbba7d8d8fd75a93fdd0f.bc21 + +Serpentine - M.A.R.S. -vs- Chicken +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1d209ea5b84f486459b0101a9073d1.bc21 + +Chicken -vs- Serpentine - M.A.R.S. +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ffe26bf366ad1dba4f2ccfb2fe6107.bc21 + + + +I am the Senate -vs- Principia +winner: I am the Senate +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2e6f97882619fd4a86cdf98283d8f6.bc21 + +Principia -vs- I am the Senate +winner: I am the Senate +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/71f0bf69faad0b732ea98e221deb77.bc21 + +I am the Senate -vs- Principia +winner: I am the Senate +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/24be687d32b2cca6fbbe0d271f9504.bc21 + + + +Harvard sucks lol -vs- DaMa +winner: DaMa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/416ee25ba138294f3e39102675d8fd.bc21 + +DaMa -vs- Harvard sucks lol +winner: DaMa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/777a676fde65f7e889af02b38d4031.bc21 + +Harvard sucks lol -vs- DaMa +winner: DaMa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1de16129f02884d8053391c5280bc3.bc21 + + + +Chocolate Banana Cake -vs- java :ghosthug: +winner: Chocolate Banana Cake +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/29f0e79bc2d94a2360b3c63a016f2c.bc21 + +java :ghosthug: -vs- Chocolate Banana Cake +winner: Chocolate Banana Cake +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/35ad8626b7d07b977bf292f36e5164.bc21 + +Chocolate Banana Cake -vs- java :ghosthug: +winner: Chocolate Banana Cake +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4ad35e68b0aa5f83d428d9a6339535.bc21 + + + +Blue Dragon -vs- Team Confused +winner: Blue Dragon +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/03b597f6c23e84503e71518887f245.bc21 + +Team Confused -vs- Blue Dragon +winner: Blue Dragon +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/039015c190e0805eeef1c5d1b76ee5.bc21 + +Blue Dragon -vs- Team Confused +winner: Blue Dragon +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/57cc46830b05c07d2cbdfa62c55734.bc21 + + + +Team Barcode -vs- CHAD +winner: Team Barcode +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0f32bc09300ecaaf7f4d0d41370a5f.bc21 + +CHAD -vs- Team Barcode +winner: Team Barcode +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8237bf343be8177662558a65c5cb80.bc21 + +Team Barcode -vs- CHAD +winner: Team Barcode +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/42416a16f0e87df945c817a5523c71.bc21 + + + +waffle -vs- Python Waifu +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d087d00b68feb538967ba11870740b.bc21 + +Python Waifu -vs- waffle +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/967a7f18fe2ad29104ea6c8636851a.bc21 + +waffle -vs- Python Waifu +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ce522597a83fea6636b0b92d3cc958.bc21 + + + +AntiVaxxKids -vs- JavaScrapped +winner: AntiVaxxKids +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4b00bf03ee56786fe6a6e3e64e7983.bc21 + +JavaScrapped -vs- AntiVaxxKids +winner: AntiVaxxKids +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d3e7111b3185d1cb8b1a26652bb085.bc21 + +AntiVaxxKids -vs- JavaScrapped +winner: AntiVaxxKids +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/449e83150fc9afdfb2ae2ec99a188a.bc21 + + + +Galaga -vs- Goreteks +winner: Galaga +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e247987e2e5d1ab8b09291ed9f0ef7.bc21 + +Goreteks -vs- Galaga +winner: Galaga +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a4b524da39905cd6ed78134b9021e3.bc21 + +Galaga -vs- Goreteks +winner: Galaga +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0525e63be7728b445b3d8dc7429d30.bc21 + + + +wololo -vs- Team_of_One +winner: wololo +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0458af6b8544607c4241374d63a5c8.bc21 + +Team_of_One -vs- wololo +winner: wololo +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9f6757f94a8dbff9c44364f4368cf6.bc21 + +wololo -vs- Team_of_One +winner: wololo +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/256d1d68b9d9e58adfd9bcbc783493.bc21 + + + +Large Green Ogres -vs- cuttlefish +winner: cuttlefish +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c7849a5a1de309b86fd28869309cbb.bc21 + +cuttlefish -vs- Large Green Ogres +winner: cuttlefish +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/490e5deb12c5f591db3e90cd6d398e.bc21 + +Large Green Ogres -vs- cuttlefish +winner: Large Green Ogres +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6494b0d331125a576dd09584f17111.bc21 + + + +Lee's Morons -vs- Cavalier +winner: Lee's Morons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/22ee94b1373bd6ce14d843c846e1c2.bc21 + +Cavalier -vs- Lee's Morons +winner: Lee's Morons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7e2c254080290c49db26463fef4095.bc21 + +Lee's Morons -vs- Cavalier +winner: Lee's Morons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2329795beeddf1b43cbbff3fa1e8a7.bc21 + + + +Hard Coders -vs- PenguinBattler +winner: Hard Coders +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ea15f528a468e8357a04cc619e819a.bc21 + +PenguinBattler -vs- Hard Coders +winner: Hard Coders +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/06a61ea1902ffbec2705e5056bc8b9.bc21 + +Hard Coders -vs- PenguinBattler +winner: Hard Coders +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0695ae5196c012b3035642daa4ae44.bc21 + + + +Kryptonite -vs- ginger cow +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/dbb525a56ae45baaa788e6577cc67d.bc21 + +ginger cow -vs- Kryptonite +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2ebf109f32a9e9eeaba10a6d292ed6.bc21 + +Kryptonite -vs- ginger cow +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7c914ffce99c482dd1480b37bedf39.bc21 + + + +Pain -vs- The Matarrhites +winner: Pain +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/3ef4fb69b2d51b4c9ce77258c3efad.bc21 + +The Matarrhites -vs- Pain +winner: Pain +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/46a88213bf3aa44d0f4258c06feb8b.bc21 + +Pain -vs- The Matarrhites +winner: Pain +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/92a1aaf461b5f2bbe2f2cc7af953a2.bc21 + + + +camel_case -vs- idrc +winner: camel_case +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/73db97de6e73a7fc61087f5ec61cbb.bc21 + +idrc -vs- camel_case +winner: camel_case +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/43883745d469ff486d2f575fd3c96f.bc21 + +camel_case -vs- idrc +winner: camel_case +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/26f2bcdde696a794d3964b9d2b3611.bc21 + + + +tooOldForThis -vs- elongatedmuskrat +winner: tooOldForThis +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b05cd83540165b8bdcde80f97390f4.bc21 + +elongatedmuskrat -vs- tooOldForThis +winner: tooOldForThis +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5cf653a898809519c4f0a30a881c6b.bc21 + +tooOldForThis -vs- elongatedmuskrat +winner: tooOldForThis +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/39b7df9bf5585f1318154efc995683.bc21 + + + +Oculus -vs- Joe +winner: Oculus +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1d97848bb64bfbe2ad25822a76c9dd.bc21 + +Joe -vs- Oculus +winner: Oculus +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/80e7ea29149becc56c955d09db9ac4.bc21 + +Oculus -vs- Joe +winner: Oculus +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/466f843583fa8a50ba8a23e72355ea.bc21 + + + +Super Cow Powers -vs- Big Oh +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c0762c9c1c2d5dadbb05ba8d00ebe6.bc21 + +Big Oh -vs- Super Cow Powers +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7923e022799ff2c5c31c9ff923ff7b.bc21 + +Super Cow Powers -vs- Big Oh +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/fac2fc9af5becebfb87d3b78a1e110.bc21 + + + +Play C. Holder -vs- 0x5f3759df +winner: Play C. Holder +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f1255f766948fb1654ed70777e85f4.bc21 + +0x5f3759df -vs- Play C. Holder +winner: Play C. Holder +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1c171b36a23508fdeb4aba824bbe28.bc21 + +Play C. Holder -vs- 0x5f3759df +winner: Play C. Holder +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c9f258086316d119400fb575ef49c8.bc21 + + + +Kruskal's Kompadres -vs- pigeons +winner: Kruskal's Kompadres +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f5dc451face0b7ac40e56d42d5f0ac.bc21 + +pigeons -vs- Kruskal's Kompadres +winner: pigeons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a296a9498e24d6186de67f3a27330c.bc21 + +Kruskal's Kompadres -vs- pigeons +winner: pigeons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6eaae0428a6c2d93fb632bc2ad6bcb.bc21 + + + +Rua!! -vs- Serpentine - Viper +winner: Rua!! +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ec538ce61a99d271cdf87b69b002ed.bc21 + +Serpentine - Viper -vs- Rua!! +winner: Rua!! +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f7caaf59d1db8840df5d821e26ac05.bc21 + +Rua!! -vs- Serpentine - Viper +winner: Rua!! +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/294ebfa740bade85b2ecd0cc012afd.bc21 + + + +Huge L Club -vs- lit +winner: Huge L Club +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/adc6225d057979c27110e27a0dfb35.bc21 + +lit -vs- Huge L Club +winner: Huge L Club +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/dbd9e6c163408e24e94ba7585a57a9.bc21 + +Huge L Club -vs- lit +winner: Huge L Club +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/33317515569db77be0afca4a755bd7.bc21 + + + +3 Musketeers -vs- JT2 +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9a971f49c324d0f48b2f9b544c7314.bc21 + +JT2 -vs- 3 Musketeers +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5e566698da6a1c7fae477f120d8d26.bc21 + +3 Musketeers -vs- JT2 +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8573aa43017bab5255ab6fc4836b27.bc21 + + + +StepZero -vs- Hippocrene +winner: StepZero +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4a3fba97253353bb8172a3e51f5848.bc21 + +Hippocrene -vs- StepZero +winner: StepZero +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9c67c84c33d290313554928e073987.bc21 + +StepZero -vs- Hippocrene +winner: StepZero +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/95d3cf81a53b49877536ec57769fea.bc21 + + + +bumbum -vs- GCI Gophers +winner: bumbum +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/45ab1b7f413abab8474ed8327729c9.bc21 + +GCI Gophers -vs- bumbum +winner: bumbum +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a29dc8b30f2d2f61ee0167087bfada.bc21 + +bumbum -vs- GCI Gophers +winner: bumbum +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b425d32f62f730fbdff96a77923f74.bc21 + + + +Code Not Found -vs- 21 Codestreet +winner: Code Not Found +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/991c193fb0e17995ab811e00be92a9.bc21 + +21 Codestreet -vs- Code Not Found +winner: Code Not Found +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2205ff2a7b0d3a679a0db87992cbb8.bc21 + +Code Not Found -vs- 21 Codestreet +winner: Code Not Found +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5edaf58bddde142a3e7689eb86e6ed.bc21 + + + +Atom -vs- The Method. +winner: Atom +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/bc56b9f63ee01b82937ef958008686.bc21 + +The Method. -vs- Atom +winner: The Method. +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/3442b12fa3a2f548112aa9784e2624.bc21 + +Atom -vs- The Method. +winner: The Method. +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/487495076dcdee35da24aaa8950933.bc21 + + + +Blue Steel -vs- monky +winner: Blue Steel +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6c0244b72f8ae78003b5cf2e28b9e3.bc21 + +monky -vs- Blue Steel +winner: Blue Steel +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a2791f067d4ca42f33d98dbbca66dc.bc21 + +Blue Steel -vs- monky +winner: Blue Steel +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f1ac64d530bfe797982fa0f54c24ca.bc21 + + + +Nikola -vs- MossyBot +winner: Nikola +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/87b2025df45d424cdb59f6cd8f6019.bc21 + +MossyBot -vs- Nikola +winner: Nikola +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4f75fcb298bfafd0e376125ce89d8a.bc21 + +Nikola -vs- MossyBot +winner: Nikola +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/52baf77b8628c9338922685e8807b3.bc21 + + + +Download More RAM -vs- GHS Guardians +winner: Download More RAM +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/968b71a31e906f1253410857af8a17.bc21 + +GHS Guardians -vs- Download More RAM +winner: Download More RAM +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e35ce37458d31fb33c345394f445bd.bc21 + +Download More RAM -vs- GHS Guardians +winner: Download More RAM +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1cbc93906bcf3bea8b22ff47646f8e.bc21 + + + +The Patriots -vs- Yeet Pull +winner: The Patriots +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9e0e2d5c724ab07ebe5852f87b807e.bc21 + +Yeet Pull -vs- The Patriots +winner: The Patriots +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/eaa1b1d3256f818d7437ce4459a9d0.bc21 + +The Patriots -vs- Yeet Pull +winner: The Patriots +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/110b65efa6ec49eac5ce929e342271.bc21 + + + +Dis Team -vs- Touhutippa +winner: Dis Team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ff8c45ebc302890f63d99f85a6e98b.bc21 + +Touhutippa -vs- Dis Team +winner: Dis Team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a9d9e4b72421042190f53422150934.bc21 + +Dis Team -vs- Touhutippa +winner: Dis Team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1067430dca5c80c7d18565b2f6c744.bc21 + + + +Ripples -vs- Hertzhaft +winner: Ripples +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/489deb8b5f6d0980a2d1491f0aed7f.bc21 + +Hertzhaft -vs- Ripples +winner: Ripples +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8ba1d5eb3a4b389b4165ed1a752834.bc21 + +Ripples -vs- Hertzhaft +winner: Ripples +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a613330f989aff5770f61485ce2a42.bc21 + + + +Producing Perfection -vs- JT4 +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/cbb9bead7441fd8e700dce34e97b4c.bc21 + +JT4 -vs- Producing Perfection +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/fff02b8884d659ae8cf9b664a35185.bc21 + +Producing Perfection -vs- JT4 +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f0442566aafc066369d18221d56d04.bc21 + + + +sucks2BU -vs- Veto +winner: Veto +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a588c70e14513a8890444efa5d1adf.bc21 + +Veto -vs- sucks2BU +winner: Veto +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/dd2ae34510601d95692eb034731457.bc21 + +sucks2BU -vs- Veto +winner: Veto +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/67746ea0b9c65a561fecc3319f7aaf.bc21 + + + +Up For A While -vs- YA BOI +winner: YA BOI +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8646ed68984912d6b16f3933386193.bc21 + +YA BOI -vs- Up For A While +winner: Up For A While +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/59c6639e65dba079d01a3ac4dd32dc.bc21 + +Up For A While -vs- YA BOI +winner: Up For A While +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ea6c8b83abb9410b173ebf5b6253b2.bc21 + + + +Chop Suey -vs- blair blezers +winner: blair blezers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8b331b688ffbc4d1d12e8217cdf9f9.bc21 + +blair blezers -vs- Chop Suey +winner: blair blezers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/92d71188f9f4d220b46306fe0b7111.bc21 + +Chop Suey -vs- blair blezers +winner: blair blezers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/387d678f95db6a64fcca62c21ed018.bc21 + + + +remotED -vs- ACM @ UNCC +winner: remotED +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/94232d3c837dc3bbd92f61a3a7db9d.bc21 + +ACM @ UNCC -vs- remotED +winner: ACM @ UNCC +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1aead85d9ffdcf0fc149d9881e9a18.bc21 + +remotED -vs- ACM @ UNCC +winner: remotED +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/df9069f32a7676188be8de2700aa15.bc21 + + + +BASHA ESPORTS -vs- ThotBot +winner: ThotBot +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7339e5ee4c83a94f0b3befa176b972.bc21 + +ThotBot -vs- BASHA ESPORTS +winner: ThotBot +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/3341c64ee7f85d9c24427cb9a720d4.bc21 + +BASHA ESPORTS -vs- ThotBot +winner: ThotBot +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b0026f8ab7c9b0032cf4673ec6a45c.bc21 + + + +babyducks -vs- CodeReapers +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0c909ec529025cf56129afc5a97704.bc21 + +CodeReapers -vs- babyducks +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/af579089839d857ed5bd9812798d7e.bc21 + +babyducks -vs- CodeReapers +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ea0b6aa0bea72fbb50aed527ac939d.bc21 + + + +Yeicor -vs- Coast +winner: Coast +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/579aed9c6405b48779f9724812ca79.bc21 + +Coast -vs- Yeicor +winner: Coast +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5b0550d88083c0bd7562ad6b51eb4d.bc21 + +Yeicor -vs- Coast +winner: Coast +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/edc04146ce6bfaf3312facc53b1530.bc21 + + + +Mars Analytica -vs- The other team +winner: Mars Analytica +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8d22eaeea375d863bb5a1aa8acfe58.bc21 + +The other team -vs- Mars Analytica +winner: Mars Analytica +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c25e7db3e99fddebbce3ccdb2597ea.bc21 + +Mars Analytica -vs- The other team +winner: Mars Analytica +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f348ac48ab27db746dc4c0d02c2c0d.bc21 + + + +Bytecode Mafia -vs- Team Awesome +winner: Bytecode Mafia +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b5e42bf2871773d03f3f35d7e00962.bc21 + +Team Awesome -vs- Bytecode Mafia +winner: Bytecode Mafia +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6ad869ce21450484d9246ed65f1c33.bc21 + +Bytecode Mafia -vs- Team Awesome +winner: Bytecode Mafia +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/511c8b30e8191e0f4a48f349ad710c.bc21 + + + +Chicken -vs- BossTweed +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d61b8c70cf01547635e4ae4421ff91.bc21 + +BossTweed -vs- Chicken +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/99671457e2a4147005fd0c7029020a.bc21 + +Chicken -vs- BossTweed +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/aaf6abcb3cc1026aec0b125b0696fd.bc21 + + + +I am the Senate -vs- DaMa +winner: DaMa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/46811ca18ad261dbd3765ebc172e74.bc21 + +DaMa -vs- I am the Senate +winner: DaMa +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/eb39f0a492302f7889f1ce60dfa9a7.bc21 + +I am the Senate -vs- DaMa +winner: I am the Senate +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e2f7a56e0f74a9152a6b0a0e67b95a.bc21 + + + +Chocolate Banana Cake -vs- Ctrl Alt Elite +winner: Chocolate Banana Cake +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d3ad7c1e2732ea109185f7c93937f5.bc21 + +Ctrl Alt Elite -vs- Chocolate Banana Cake +winner: Chocolate Banana Cake +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8577d724ad1e3c71e1d96005f71193.bc21 + +Chocolate Banana Cake -vs- Ctrl Alt Elite +winner: Chocolate Banana Cake +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/589b679732c3e9780bc7d9dcc21809.bc21 + + + +Blue Dragon -vs- Team Barcode +winner: Team Barcode +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0e6dc32a1d83a9a9a57d6199c8dd22.bc21 + +Team Barcode -vs- Blue Dragon +winner: Blue Dragon +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6f2a60bdfdecda7e0559614b72a17a.bc21 + +Blue Dragon -vs- Team Barcode +winner: Team Barcode +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a9d3308e2f907614b2ad20a58fb710.bc21 + + + +waffle -vs- Broccoli Bros ! +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/dd079c9ede562d37ee90a6bf408cb2.bc21 + +Broccoli Bros ! -vs- waffle +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/bf7cae120ca34d62f2860eb6306145.bc21 + +waffle -vs- Broccoli Bros ! +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b0d66f44b26909e3a584a18d479430.bc21 + + + +AntiVaxxKids -vs- Galaga +winner: AntiVaxxKids +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5497eee5b0a7c3089f4193b60da310.bc21 + +Galaga -vs- AntiVaxxKids +winner: AntiVaxxKids +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/555fe97ef0876403d5d392b410fdea.bc21 + +AntiVaxxKids -vs- Galaga +winner: AntiVaxxKids +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/10f93ad5bf84f6a6b303bbae563b71.bc21 + + + +wololo -vs- Kansas City Asians +winner: wololo +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/41ab25579262c9525af9f943ec4430.bc21 + +Kansas City Asians -vs- wololo +winner: Kansas City Asians +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/886ae0d7b32a8adf5da8c7fec8f0b0.bc21 + +wololo -vs- Kansas City Asians +winner: Kansas City Asians +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/10a04f7b8112679b0fc8036c3c39d9.bc21 + + + +cuttlefish -vs- Lee's Morons +winner: Lee's Morons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7eee15b21046352842095471270797.bc21 + +Lee's Morons -vs- cuttlefish +winner: Lee's Morons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/02508f42909f8dc6aa7720c5161e74.bc21 + +cuttlefish -vs- Lee's Morons +winner: Lee's Morons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/62341af9fc6c945ceb19b31a7c4aea.bc21 + + + +Hard Coders -vs- No Battlecode for Old Men +winner: Hard Coders +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/bc587c7387d0ba2e9396f4c8a61edb.bc21 + +No Battlecode for Old Men -vs- Hard Coders +winner: Hard Coders +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/cb344935b2827c904f699db665560b.bc21 + +Hard Coders -vs- No Battlecode for Old Men +winner: Hard Coders +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/03d0933e37b1fe40ebc64090c3337e.bc21 + + + +Kryptonite -vs- Pain +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d1f030ae4709db43c69169c08aed82.bc21 + +Pain -vs- Kryptonite +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/71e26937cfeefeac42206419247135.bc21 + +Kryptonite -vs- Pain +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/cce94659d1c846222ed252782535ba.bc21 + + + +camel_case -vs- A214 +winner: camel_case +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/501bcd34ba81394dcc2f31db3d75fb.bc21 + +A214 -vs- camel_case +winner: camel_case +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/fac9ae25801e9eb5cc15b9bb7515b8.bc21 + +camel_case -vs- A214 +winner: camel_case +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c3a3b715ebc5fb7e1ccbb589a8a657.bc21 + + + +tooOldForThis -vs- Oculus +winner: tooOldForThis +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7a61675ed375b21ff64153815412dd.bc21 + +Oculus -vs- tooOldForThis +winner: tooOldForThis +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/db1fc171a8ac2fb3cd2a3bc3d14452.bc21 + +tooOldForThis -vs- Oculus +winner: tooOldForThis +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b43fcbfda963e8d58b6986c78924b9.bc21 + + + +Super Cow Powers -vs- confused +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f4dfbe7b70f265de7f0101c03b1990.bc21 + +confused -vs- Super Cow Powers +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a1dee719fc137c6fa196a403850756.bc21 + +Super Cow Powers -vs- confused +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/36f632b0c830d601c5a4d34b6dee2d.bc21 + + + +Play C. Holder -vs- pigeons +winner: pigeons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/05348b0c9a064578dbab2eee9ef025.bc21 + +pigeons -vs- Play C. Holder +winner: pigeons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9d45571b95855e172323427c43be6a.bc21 + +Play C. Holder -vs- pigeons +winner: Play C. Holder +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1e286a11d4b1e8e951a08f76387c8f.bc21 + + + +Rua!! -vs- (+[](){})(); +winner: (+[](){})(); +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2a60a3d21b5a6e9ffd64cf0d7721e4.bc21 + +(+[](){})(); -vs- Rua!! +winner: Rua!! +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/bfb75930648f831d4cbd497bb841e6.bc21 + +Rua!! -vs- (+[](){})(); +winner: Rua!! +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/67af049be381aaf177f3842d4287bc.bc21 + + + +Huge L Club -vs- The Unladen Swallows +winner: Huge L Club +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0c21755bfbcfb2f14ca81d726ee5bc.bc21 + +The Unladen Swallows -vs- Huge L Club +winner: Huge L Club +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d56636c1416648f6f2a74361cf8b7e.bc21 + +Huge L Club -vs- The Unladen Swallows +winner: Huge L Club +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/08a5a2dc5def0b568a4311c8d1dab6.bc21 + + + +3 Musketeers -vs- free boba +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5265ad079c79e087c1ccefce01f804.bc21 + +free boba -vs- 3 Musketeers +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5be1f2d113369694f59ce6a4ebf7fd.bc21 + +3 Musketeers -vs- free boba +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6990108583403fc6e2be9f6e588b93.bc21 + + + +StepZero -vs- bumbum +winner: StepZero +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/022241fceaf7ca9a000ee93145513e.bc21 + +bumbum -vs- StepZero +winner: StepZero +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/17fcbe03de01b9cfb52768aa79b11e.bc21 + +StepZero -vs- bumbum +winner: StepZero +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8419ea381dfb3a6dda24c5a1e4e07f.bc21 + + + +Code Not Found -vs- What are you doing stepBot? +winner: What are you doing stepBot? +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/21385bdaf9eccd695baf0052f583cc.bc21 + +What are you doing stepBot? -vs- Code Not Found +winner: Code Not Found +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/33fa77d9b3247c641026ad99f8877f.bc21 + +Code Not Found -vs- What are you doing stepBot? +winner: Code Not Found +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6434ed05329392870fb2a67790b2c1.bc21 + + + +The Method. -vs- Blue Steel +winner: Blue Steel +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/10bde96addde86524368cb7a59d9f5.bc21 + +Blue Steel -vs- The Method. +winner: Blue Steel +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/60b03467942cf1b398542710891225.bc21 + +The Method. -vs- Blue Steel +winner: Blue Steel +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8b83daa01ee17d0deabeddd71190c2.bc21 + + + +Nikola -vs- Coconut9 +winner: Nikola +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/851a0314b23b17be31d6003fad8444.bc21 + +Coconut9 -vs- Nikola +winner: Nikola +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/57cadbea3910197b65569ca7675409.bc21 + +Nikola -vs- Coconut9 +winner: Nikola +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/58d8561109d039081848af3340d4b3.bc21 + + + +Download More RAM -vs- The Patriots +winner: Download More RAM +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ed02197927b849ebfa6dec7063ec6b.bc21 + +The Patriots -vs- Download More RAM +winner: Download More RAM +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/88a9f8a4710f72f0fa0fd48ffdd7df.bc21 + +Download More RAM -vs- The Patriots +winner: Download More RAM +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c681104115288afea779d1e44695d6.bc21 + + + +Dis Team -vs- Daedalus +winner: Dis Team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f71658ce0e3c25536a8330cbfbf4a5.bc21 + +Daedalus -vs- Dis Team +winner: Dis Team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f2fe71704b50aaf1786f3caae19674.bc21 + +Dis Team -vs- Daedalus +winner: Dis Team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d62e1dfa42f9e7922cd585f5986132.bc21 + + + +Ripples -vs- 0K +winner: Ripples +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4c458b0a626dc9c1633428bb9a2634.bc21 + +0K -vs- Ripples +winner: Ripples +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7e6c284c74b9edae73c9f27991dd99.bc21 + +Ripples -vs- 0K +winner: 0K +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/dd3a4710bed854669523d711c824ba.bc21 + + + +Producing Perfection -vs- Engineer Gaming +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/fc11a02893546ec7a021d34e18a938.bc21 + +Engineer Gaming -vs- Producing Perfection +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f8a6690c747de6fd8eda1a066e2a70.bc21 + +Producing Perfection -vs- Engineer Gaming +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ee40ca00a7e99f909ae18e0cb8f3da.bc21 + + + +Veto -vs- Up For A While +winner: Veto +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6960009ded2047c134d2c7f6eb3c0e.bc21 + +Up For A While -vs- Veto +winner: Veto +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0ba21993f235e0072dff7de1cb3bc4.bc21 + +Veto -vs- Up For A While +winner: Up For A While +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6b0705f3eaa807500961cb8fe75441.bc21 + + + +blair blezers -vs- Malott Fat Cats +winner: blair blezers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d7bbbd92f93fc6c81daf64dabc4e3c.bc21 + +Malott Fat Cats -vs- blair blezers +winner: Malott Fat Cats +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b6c25530c6e33da10af6cbf6abec99.bc21 + +blair blezers -vs- Malott Fat Cats +winner: Malott Fat Cats +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7aaaeae16853b8c4de9620dea818ba.bc21 + + + +remotED -vs- ThotBot +winner: remotED +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f2125fd661fb7edc148899baaa7959.bc21 + +ThotBot -vs- remotED +winner: ThotBot +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/caa249dbecdd4fb472dc425d7a6b6a.bc21 + +remotED -vs- ThotBot +winner: remotED +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/fdc3ccc949ea6b387c4112808f39fd.bc21 + + + +babyducks -vs- Coast +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ba31fb94bb56253f3c815f8b1410ce.bc21 + +Coast -vs- babyducks +winner: Coast +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/eebe3f1c6a4a81ab60076aeca90ecb.bc21 + +babyducks -vs- Coast +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2b57056e04e2387133b1d5ab0b0991.bc21 + + + +Mars Analytica -vs- Bytecode Mafia +winner: Mars Analytica +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5f7ec0f068ac73d9d98de4006572da.bc21 + +Bytecode Mafia -vs- Mars Analytica +winner: Mars Analytica +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c2de2b6a24976536db0cb841a290ad.bc21 + +Mars Analytica -vs- Bytecode Mafia +winner: Bytecode Mafia +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1655404cc8df0b56def13a122c75e7.bc21 + + + +Chicken -vs- DaMa +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a65d534870c5229ca6423b68c13ace.bc21 + +DaMa -vs- Chicken +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9edc7a4174040a012618d532618552.bc21 + +Chicken -vs- DaMa +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/49da8c34c2f6e2b396359615eb54da.bc21 + + + +Chocolate Banana Cake -vs- Team Barcode +winner: Team Barcode +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5eff0c192a84cb5324a84f11a828a4.bc21 + +Team Barcode -vs- Chocolate Banana Cake +winner: Team Barcode +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f942d1b9cff2687961d35406211c90.bc21 + +Chocolate Banana Cake -vs- Team Barcode +winner: Chocolate Banana Cake +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/af6385645df4b8cb59dec456b74b2e.bc21 + + + +waffle -vs- AntiVaxxKids +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8c2bcef17704f12b5f7af2ec49cfdc.bc21 + +AntiVaxxKids -vs- waffle +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b8dcdd11799979cca176e47c776dd3.bc21 + +waffle -vs- AntiVaxxKids +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/0568f56e3ad3be5c4bca31fd131011.bc21 + + + +Kansas City Asians -vs- Lee's Morons +winner: Lee's Morons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a862d702b27986f18231ca8ea95863.bc21 + +Lee's Morons -vs- Kansas City Asians +winner: Lee's Morons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ae0e28a2fb15a33468251851cd2c87.bc21 + +Kansas City Asians -vs- Lee's Morons +winner: Lee's Morons +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2f9f0ad470db3cfc81448c3d4f99be.bc21 + + + +Hard Coders -vs- Kryptonite +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8ee187176d60359acd7dad513a74a6.bc21 + +Kryptonite -vs- Hard Coders +winner: Hard Coders +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c9da9ff91e49938df8ca76ca3bfb52.bc21 + +Hard Coders -vs- Kryptonite +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c70146607a429ddff4dc580ac36299.bc21 + + + +camel_case -vs- tooOldForThis +winner: camel_case +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/41f40a352dc394d6b40a96948681a5.bc21 + +tooOldForThis -vs- camel_case +winner: tooOldForThis +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/00836188f372dbd03fcf25f3afd7ee.bc21 + +camel_case -vs- tooOldForThis +winner: camel_case +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/423fdb754ce3de38705328c8d187ce.bc21 + + + +Super Cow Powers -vs- pigeons +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4913473aedc877d3745d444e53267a.bc21 + +pigeons -vs- Super Cow Powers +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/41162ea1e474bfc59f832fcc190191.bc21 + +Super Cow Powers -vs- pigeons +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/449445dec8a0c29060c6affb47ba27.bc21 + + + +Rua!! -vs- Huge L Club +winner: Rua!! +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c9b7fcb86af087ee187b95dd6aa3ef.bc21 + +Huge L Club -vs- Rua!! +winner: Rua!! +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/660c6a0ab8420e9aebca44830ff0de.bc21 + +Rua!! -vs- Huge L Club +winner: Huge L Club +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/17f9401653865c6d25ab2000d80d8a.bc21 + + + +3 Musketeers -vs- StepZero +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/09636d2c31d464873706a5340bc67c.bc21 + +StepZero -vs- 3 Musketeers +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/82141563bb94ac70bafa34b5f25ffc.bc21 + +3 Musketeers -vs- StepZero +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/74abb881f0286f8e676dbed31056a7.bc21 + + + +Code Not Found -vs- Blue Steel +winner: Code Not Found +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/bd4a65357952ec08525124763a8c26.bc21 + +Blue Steel -vs- Code Not Found +winner: Blue Steel +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9e476e2e03d8d99835e68e16ee2a92.bc21 + +Code Not Found -vs- Blue Steel +winner: Blue Steel +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/860e0cba96928b2d67eefe731a4840.bc21 + + + +Nikola -vs- Download More RAM +winner: Download More RAM +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8ca9e4c7750f9ab3a8b699ed95147c.bc21 + +Download More RAM -vs- Nikola +winner: Nikola +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/de75d7f5f873ce5b77ab4bc7e72553.bc21 + +Nikola -vs- Download More RAM +winner: Download More RAM +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/046413c1233738e9e2fd13ce29cf6c.bc21 + + + +Dis Team -vs- Ripples +winner: Dis Team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/d5028d1e70367330574ce71525cd84.bc21 + +Ripples -vs- Dis Team +winner: Dis Team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f94bcdf2f33300bc857b1be073d889.bc21 + +Dis Team -vs- Ripples +winner: Ripples +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/18af2775d6a936e3d37bbd74f71100.bc21 + + + +Producing Perfection -vs- Veto +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9eb4182fe7d871f5803b779fd236a9.bc21 + +Veto -vs- Producing Perfection +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9de7cf111587d4f0e2a3748f6c05e4.bc21 + +Producing Perfection -vs- Veto +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c1f24fdea939307c11faa7978a6f9b.bc21 + + + +Malott Fat Cats -vs- remotED +winner: remotED +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/a7ecb60bf5ee5d5d0f4a3d475ce805.bc21 + +remotED -vs- Malott Fat Cats +winner: remotED +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/3bbc75d27becb6dfc064d28dd34634.bc21 + +Malott Fat Cats -vs- remotED +winner: remotED +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/3c56ddfee960cfdb495323d6a17fd9.bc21 + + + +babyducks -vs- Mars Analytica +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c8d1b602f9a67d35d038991f9fc4a3.bc21 + +Mars Analytica -vs- babyducks +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9b7bce1ad0efa869848c0c2fdb5169.bc21 + +babyducks -vs- Mars Analytica +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/960184dd05cd5145a345303e469eaf.bc21 + + + +Chicken -vs- Team Barcode +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7f2b3699cd3aab7c5658bb14813851.bc21 + +Team Barcode -vs- Chicken +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e452686e275b1b21eaf7f81e33ff88.bc21 + +Chicken -vs- Team Barcode +winner: Chicken +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/df9812b835a5010ad11dcd90b4df50.bc21 + + + +waffle -vs- Lee's Morons +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/77eafcdd8b4bbba77a6dfed879f119.bc21 + +Lee's Morons -vs- waffle +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e91ab9ef81423261f933111de4d2c9.bc21 + +waffle -vs- Lee's Morons +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/65bcbc78f68924d5770d9a2f99aa81.bc21 + + + +Kryptonite -vs- camel_case +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/7a6e5f11c019772555a0c371b6dd81.bc21 + +camel_case -vs- Kryptonite +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/4b470c1a445a8e57bff7f248aa4338.bc21 + +Kryptonite -vs- camel_case +winner: Kryptonite +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6f7bd0ffc463b34e72cbc550930f1b.bc21 + + + +Super Cow Powers -vs- Rua!! +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/65bf80e805200eab4c15841c87df6f.bc21 + +Rua!! -vs- Super Cow Powers +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/8a6ea307a56a569dfa83a8989b27c7.bc21 + +Super Cow Powers -vs- Rua!! +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/394c65e85bbe7b3ff1059d6812d581.bc21 + + + +3 Musketeers -vs- Blue Steel +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f830bc3fc373c161baf1e777fbcbec.bc21 + +Blue Steel -vs- 3 Musketeers +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f8e9809360a2dcecb7e74e688bcaee.bc21 + +3 Musketeers -vs- Blue Steel +winner: 3 Musketeers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/343c11636e213103ce8b68d01084ce.bc21 + + + +Download More RAM -vs- Dis Team +winner: Dis Team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/22be024c0b3cc4c454bd0d08137a23.bc21 + +Dis Team -vs- Download More RAM +winner: Dis Team +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c74b88fd034d72d0b8ba969cc952e6.bc21 + +Download More RAM -vs- Dis Team +winner: Download More RAM +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/74ae39d307074b177fdce96b69078a.bc21 + + + +Producing Perfection -vs- remotED +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/6363c8db4c8a72ce65da9912f41a77.bc21 + +remotED -vs- Producing Perfection +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/b2da277f365f7465ecdbda0bf31799.bc21 + +Producing Perfection -vs- remotED +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ef1936d20e319c6524703435968d76.bc21 + + + +babyducks -vs- Chicken +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e932b72f70a484455a879bbd0164f1.bc21 + +Chicken -vs- babyducks +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/1142f78b7e58e8f4116ec4649f6a11.bc21 + +babyducks -vs- Chicken +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/380019a7e14f76541c6027d3faa2d8.bc21 + + + +waffle -vs- Kryptonite +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/87a698bbe39c11f9b67d87124535b6.bc21 + +Kryptonite -vs- waffle +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/9b853a833b78e6afdeb219308f4674.bc21 + +waffle -vs- Kryptonite +winner: waffle +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/872f3ecb929b5e32ff376fb984a5bc.bc21 + + + +Super Cow Powers -vs- 3 Musketeers +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/c308e3e2917e470ba8a1211890a92b.bc21 + +3 Musketeers -vs- Super Cow Powers +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/dc97fb822d01d1ffd7f358d4c6f4ab.bc21 + +Super Cow Powers -vs- 3 Musketeers +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f42acba5680ebba1ac0cb8562c9326.bc21 + + + +Dis Team -vs- Producing Perfection +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ff4bf1c12dbb1c8125e59714728950.bc21 + +Producing Perfection -vs- Dis Team +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/bd8a324f514d8c69cf5f234ff1ca5e.bc21 + +Dis Team -vs- Producing Perfection +winner: Producing Perfection +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ed1923f7ba85f2481c7ea0d995a75b.bc21 + + + +babyducks -vs- waffle +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/5eaf0f18abb49eebbee465894eea9b.bc21 + +waffle -vs- babyducks +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e3aafe5c91ecc8520b4e8a52fd7ef2.bc21 + +babyducks -vs- waffle +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f83bee62b7a70e6323bc96a620894e.bc21 + + + +Super Cow Powers -vs- Producing Perfection +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f1c847903038ac8796e09378d6c16f.bc21 + +Producing Perfection -vs- Super Cow Powers +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/e630c7f313ac4661cdad77ed9b29d4.bc21 + +Super Cow Powers -vs- Producing Perfection +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/07c3e65dd56902bc0428ceca897d5d.bc21 + + + +babyducks -vs- Super Cow Powers +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/2d862833a6366076aa64839c2d843f.bc21 + +Super Cow Powers -vs- babyducks +winner: Super Cow Powers +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/ae2ec48c78a79112e3abe6ec8baee4.bc21 + +babyducks -vs- Super Cow Powers +winner: babyducks +https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/f8ce4525b058cc10a5011961cab46d.bc21 + + + diff --git a/infrastructure/tournament-util/data/1-sprint1/replay_links_only.txt b/infrastructure/tournament-util/data/1-sprint1/replay_links_only.txt new file mode 100644 index 00000000..8f543d22 --- /dev/null +++ b/infrastructure/tournament-util/data/1-sprint1/replay_links_only.txt @@ -0,0 +1,688 @@ +4e68fe7ab6b6e496a6fc4a214b855d +b3352046408750bb97adc2281b836a +efb06babb96565db2cad1fceb977ba + +ac9f36bc55ff8edfddd0539da127b1 +2b064301799d2b60a443f12b51bbc4 +7f7ad5049ce3b594e5fbc75c208a19 + +7ce4e95730a958acb1ab21a05807cd +d40f17a47ad353bba401a9d21c7eb6 +8d5593694ba9884d47fc96325125c1 + +97220f5513b6996942db248eaccfbb +94bea2ecb76d83a76fc9b21fc0d836 +39679aa7625afa87cb7cdaee3b9342 + +72b421c1f4dbd2af05348d4595a615 +acae997799723509313823bb5f9dea +84f48d7045e50d9deead749215305c + +15700ec0d11733be18687bdc5cd4c5 +07bf6b158f1ce2c7896f147908315a +cb490bf363d82307bc43dcdbbff779 + +aef6a3bb5f02ccaf29e2bc7d7e38fe +8f8f1614bdd95426a2ec8867be7e75 +614d6a2fbb6b4a6b08d0c391b9dbb2 + +0a359a03d59f88b675e6f32b88ad68 +1d5fe15e696f5f2acddbf316ec0383 +4320fdba3df68d2420e14e18ca07ae + +e605bab770f6965cb042cca18a7f26 +043f4b3a668e5c4a8bf657903a92b2 +d080e9a958568493bcca25e34605b0 + +06d31819a6d736da3417e13aa54d56 +249400fcd2453e46b9e4329514946a +f36a8e1042091208d57c0196a1d7eb + +f76ad509fd829c08b2bdd255d1151b +e65bc4afc7c99765f5287228e71d77 +5be9eb1959e242c9084527ae388c63 + +15eba1b76526770d3ece42bc74d22c +1f7dd4865c7b5b4fdec48ec554b049 +d5894d13d35df8d3da5f64dd939ff4 + +133644580e37276775d257e58ffe1b +344c620f7be4783a9a48ebbb2c4343 +e66f2dfd081b59b888863697d5ae4c + +a7f9f2b96ae6b4f143bf1baf2ec35f +a71b18b7d2734207e2982b62c3ae63 +fee62bb3cfc804500076024d36b9ab + +cf5121bd2444f8371b896bc357fe98 +0e4c73fb6e87f88e516dc5f3312632 +54ed6b350d3216022c188a65110d10 + +ec22af09313b4f9d86fbb7dc8e8949 +3cd6426e440f3c1e30fa5175965823 +a7440d90ea252fbf63152d4df7cf32 + +c42feb44629966d63ffab5bb4b5bf8 +4ad3edb6e651537863fa37ff76ff5a +694ff508861d7ee4cbdafcb9150800 + +6a3fdcef0ebbf93831daf7c76881ce +b076bafb5b511b13e1ab3fe3795b77 +c7d946efa55102f11bf03c93fcbd35 + +f4184dc6dccd266dc0128512c01f6e +8bce17b0d8fbb1132d150b998d079b +9be0ec55206196d2d8eb38cf1b3d79 + +73802e67600827838cff5f2e96a751 +791f20a65c96cb9ecfd1898b66a5be +c3b62e73bd50f559fa609ad5460d04 + +334acf16bd8a6f8725a6cfed812aea +f568c1a1e4524e2cfc7155f3e7810e +8ecc9df8db6848621501306f37d37e + +a5cc30664790d38710bdaab895e70f +6916a4906d5973a86524627436893b +8b965df23c4a7e51c5203e751b8437 + +3e9259fdf6463b650d055418b28dd8 +4c621236ce56ccdf99604b6fafa82c +38a187c6afe133c453e3551df8aba6 + +4679f5e7a918bbb397d3e88be62490 +33865665ecd0b261cfde6cad6f1dde +78a19c953507551e7e8e6cc85d1342 + +431f6b3fa895e567c6079f16693c63 +1f26e1a8e08baa786f04ab685d1fb9 +2cf63a96de6979175758a32b7ae04b + +830d9e603f42fd6255c6c7260dc860 +591d726b832e11e8ab37f1ccb339ab +e33bbb95cc5c8631b7f07b4ff6dd1e + +08c2c84b2860a34ebe8ff4f111d6da +02d006452462913bb63e4cf31774c1 +58a309702b816a5422456628005b45 + +5c3d81e576a883bb02df5488c3d020 +3106f347ac9d399b3aa65504cb8041 +3536bb2a0468e1af171d3cc839386e + +7c068cbc9c1903cd96ebce2709e0f4 +df149a4cdfb976839344039727da52 +8aa1590b33bb38bce1cb372cba1ba9 + +e72244918702746e653f1b06ed27a0 +45403de77328e216607176aef41a15 +9fda89cbd8349609a71c4bb6a1c892 + +4bddf643ea1491f7e709c75d5e2f86 +24376c7d3fd75cd91b1ba8a7d34d8c +a8e6982b3a7d2145f24ac3042b9b8b + +f23e642c6807c6eb31cbc8efe29229 +d925716dbb1c3a10b71baa673fafdd +a06a6850f8c6e7da7e77acf6ded9c6 + +c468e13928e759ecf47fb2727f5b82 +49f5f0dbad77aeb7a749f489376a81 +1a1328ddfc69f099658df5c4527031 + +6536360d3e26ecd5acf7ecb119618e +0e5a6c9b311b93fe532ae71daad6ea +f909531f95f4f060b791e74f396554 + +776c9f15ce34c27011a9bd88e9bf7a +35a6709cbc8f5d9a0bb08d2966a08f +b2cc581f91fa00bdf6d4c4a355ab21 + +ba2f3c3d0ee79cc72db0169311c7b9 +db4902e027040ee3520f8ebf308a23 +5d917f2099103efb94da89edeae26e + +fb8a724947a86601a2dba2697f42a5 +a6cbc58472043aa82a773246f55ba6 +163660b764ec3bc91a924df92f71e8 + +0a0704bd5a2476b5c3e78fe5da22f1 +959a2950a12a2bc243c93919640e1f +b749a600531d854251ea1552190fd5 + +4a00a2888b3dda04f32d940654b0fd +0116a4270defcebb399597456e4444 +4fe3b0327a87217d6549b7c8d4e886 + +d2a57c8b3d1123de9dcaaed995d375 +2f51b173701ada29079b593639f21a +20f48d123ba9ff16e39de6e2fb611e + +6cc35103615949b13d295d6538d51c +de158e02e7970afd222ef48c0c9ef8 +1c800d87b731633e7333a5dd13af60 + +9570fdc575037869917206a9f8901c +05dbabc18b1e1343a7e6fbe20c5290 +01d683747a5d3bdb4399d04c93eece + +ccd7ecdbd6385e19e0d4ec7970f681 +7b089f1f5a1feb3dc83ae83a256993 +90e07498297484ec879db5daa0a784 + +0b64bef7fad0c69144131d22b4d037 +fb2fbfff1900933152be2d4f6e8db2 +636680747e7aefb01c1cdd387d817e + +e3cc823d9c70ff143465ee035bd671 +214dafd6a0768c208ed94e87d6b9aa +50fb88aa53172148db35927d617178 + +c3e0cec57197f8aefaa213a9d85a11 +2f438063ed778e880db6b107ff5cc1 +deee4137b52a975df508c86d36b403 + +42e442e1448cf4aa5e53f7db865afd +ebdb09be67446b0bb7eea9157c9bb2 +386621ef51c53942f6e10cb6a076a5 + +fe2c45b5e219d9d81d395468a33fcd +b418fbad77f19aaa098b25df071405 +6fd80b71700f9c9cf6b60cf7822962 + +b81e38dd03f67093d90de819f1903d +4f906b049de725d40775b66a2da48a +79d986e165852df617f78686668dc3 + +bd32cd7262ccb19ae41159b677a05e +96940008a63a9dad0e42a24b4601e0 +c4964c24ba9ca0bd16c8990550e568 + +5c19756616ac46ff09571eae12ebcd +6fb58bc654adba091fbb789a096cce +4b7b40b66629535ca0f91a19ffa24f + +8136c42139f66736f42e99fbb84906 +dbcae8fe2e552cc67caaba2d6bf600 +0f9cbd5012d8c3ad24d94dbbc93569 + +645881a0279c6fe1c69243e36ffe8b +c3a7b60de622c863949b90e2db9cb6 +9166acd56b0730c7bb8ff6ba536503 + +fbfaef7f645e0acd7515948046cdb4 +f5abcc164560e96a5adf17a4cce895 +e6186fc321bb6f32c5953e0702fcbb + +8c00a45b5a7d96147bba18bee8f762 +03563d74e4037fee34ba4e98bed714 +4b21deedf40c86b69354d5b26eeb93 + +2068a93dc656b294485c4c22abb70f +a2ada8d11ba14e87d385b4e8cecdda +f4e14926c590f06402d25f13ec0a24 + +6449dca66194609d09217128fcd074 +dc3f6abdf172cc4d4c657e354efaa8 +a98e4933f5b9e8583389dfa1fe0745 + +3f2cee7472eba4cb293af956659f41 +8c892fda4686017888677e581e52df +bc6b33d25053ef96391eacda8e61ff + +074b64ee6dfeb63e9e096c1f006920 +e38dff6fce4d940ca5feac669af2fd +5ee44cfad76fa4a85a9e58644b678b + +a49f951d50985f22c4940e579d302d +9a0e23feebb68f8e6feb015059db4d +14105d0d074b2f6f08f7b608fedbc0 + +c6740d19fa6ecef91bd8eb6aeb67c6 +1b6ac77b2d25756f82273913af3186 +c2c11e41508ee7f222167783048a15 + +116cc9dba03e45c6de79eb8641d650 +63959603d6f1fb0f12ff2c3fa5f0bc +517e450bb135509636b531a5cdb93a + +f4aec9a65586bcd3617adbb47bbc0d +78592de32d3a4d2d1f2f0a9e4a46ca +614d175ae3cb07f46794036cc1f212 + +6d27815dda691ff55633baabd06af9 +7434a4f26519f39c1011fdcef5aca9 +271b8ff119b494b87d04208766fb1f + +0f33d7d04d019d4d218b2da285cb29 +27da4012e3d9a8aaf545b065b5991b +7788274440de7d5ccbf18a29c15ef3 + +32d8a111e8043d82094c9cc3eedc8a +75cb625710f02b250af5149dc9754a +7b4e212e28e72e4a3c032e202b7c49 + +9ecd0aa1ac8329009979c84912d968 +ee80b1f43ba18c35358fcfdb9a1cda +83eabb10b388cbab0ca998665df8e6 + +e61e9579178cd429ede4352b29c36c +16a3660d18ab528e7e8eea3f59d697 +5a496dfc61a2163f2d74b03352d718 + +307734dbd0b6e06ecefe3dc395e45e +f84067d9a74f575ff2c7bf2d5346da +96526c1e63b5122590c09590f34fa4 + +c29b11d5cbbba7d8d8fd75a93fdd0f +1d209ea5b84f486459b0101a9073d1 +ffe26bf366ad1dba4f2ccfb2fe6107 + +2e6f97882619fd4a86cdf98283d8f6 +71f0bf69faad0b732ea98e221deb77 +24be687d32b2cca6fbbe0d271f9504 + +416ee25ba138294f3e39102675d8fd +777a676fde65f7e889af02b38d4031 +1de16129f02884d8053391c5280bc3 + +29f0e79bc2d94a2360b3c63a016f2c +35ad8626b7d07b977bf292f36e5164 +4ad35e68b0aa5f83d428d9a6339535 + +03b597f6c23e84503e71518887f245 +039015c190e0805eeef1c5d1b76ee5 +57cc46830b05c07d2cbdfa62c55734 + +0f32bc09300ecaaf7f4d0d41370a5f +8237bf343be8177662558a65c5cb80 +42416a16f0e87df945c817a5523c71 + +d087d00b68feb538967ba11870740b +967a7f18fe2ad29104ea6c8636851a +ce522597a83fea6636b0b92d3cc958 + +4b00bf03ee56786fe6a6e3e64e7983 +d3e7111b3185d1cb8b1a26652bb085 +449e83150fc9afdfb2ae2ec99a188a + +e247987e2e5d1ab8b09291ed9f0ef7 +a4b524da39905cd6ed78134b9021e3 +0525e63be7728b445b3d8dc7429d30 + +0458af6b8544607c4241374d63a5c8 +9f6757f94a8dbff9c44364f4368cf6 +256d1d68b9d9e58adfd9bcbc783493 + +c7849a5a1de309b86fd28869309cbb +490e5deb12c5f591db3e90cd6d398e +6494b0d331125a576dd09584f17111 + +22ee94b1373bd6ce14d843c846e1c2 +7e2c254080290c49db26463fef4095 +2329795beeddf1b43cbbff3fa1e8a7 + +ea15f528a468e8357a04cc619e819a +06a61ea1902ffbec2705e5056bc8b9 +0695ae5196c012b3035642daa4ae44 + +dbb525a56ae45baaa788e6577cc67d +2ebf109f32a9e9eeaba10a6d292ed6 +7c914ffce99c482dd1480b37bedf39 + +3ef4fb69b2d51b4c9ce77258c3efad +46a88213bf3aa44d0f4258c06feb8b +92a1aaf461b5f2bbe2f2cc7af953a2 + +73db97de6e73a7fc61087f5ec61cbb +43883745d469ff486d2f575fd3c96f +26f2bcdde696a794d3964b9d2b3611 + +b05cd83540165b8bdcde80f97390f4 +5cf653a898809519c4f0a30a881c6b +39b7df9bf5585f1318154efc995683 + +1d97848bb64bfbe2ad25822a76c9dd +80e7ea29149becc56c955d09db9ac4 +466f843583fa8a50ba8a23e72355ea + +c0762c9c1c2d5dadbb05ba8d00ebe6 +7923e022799ff2c5c31c9ff923ff7b +fac2fc9af5becebfb87d3b78a1e110 + +f1255f766948fb1654ed70777e85f4 +1c171b36a23508fdeb4aba824bbe28 +c9f258086316d119400fb575ef49c8 + +f5dc451face0b7ac40e56d42d5f0ac +a296a9498e24d6186de67f3a27330c +6eaae0428a6c2d93fb632bc2ad6bcb + +ec538ce61a99d271cdf87b69b002ed +f7caaf59d1db8840df5d821e26ac05 +294ebfa740bade85b2ecd0cc012afd + +adc6225d057979c27110e27a0dfb35 +dbd9e6c163408e24e94ba7585a57a9 +33317515569db77be0afca4a755bd7 + +9a971f49c324d0f48b2f9b544c7314 +5e566698da6a1c7fae477f120d8d26 +8573aa43017bab5255ab6fc4836b27 + +4a3fba97253353bb8172a3e51f5848 +9c67c84c33d290313554928e073987 +95d3cf81a53b49877536ec57769fea + +45ab1b7f413abab8474ed8327729c9 +a29dc8b30f2d2f61ee0167087bfada +b425d32f62f730fbdff96a77923f74 + +991c193fb0e17995ab811e00be92a9 +2205ff2a7b0d3a679a0db87992cbb8 +5edaf58bddde142a3e7689eb86e6ed + +bc56b9f63ee01b82937ef958008686 +3442b12fa3a2f548112aa9784e2624 +487495076dcdee35da24aaa8950933 + +6c0244b72f8ae78003b5cf2e28b9e3 +a2791f067d4ca42f33d98dbbca66dc +f1ac64d530bfe797982fa0f54c24ca + +87b2025df45d424cdb59f6cd8f6019 +4f75fcb298bfafd0e376125ce89d8a +52baf77b8628c9338922685e8807b3 + +968b71a31e906f1253410857af8a17 +e35ce37458d31fb33c345394f445bd +1cbc93906bcf3bea8b22ff47646f8e + +9e0e2d5c724ab07ebe5852f87b807e +eaa1b1d3256f818d7437ce4459a9d0 +110b65efa6ec49eac5ce929e342271 + +ff8c45ebc302890f63d99f85a6e98b +a9d9e4b72421042190f53422150934 +1067430dca5c80c7d18565b2f6c744 + +489deb8b5f6d0980a2d1491f0aed7f +8ba1d5eb3a4b389b4165ed1a752834 +a613330f989aff5770f61485ce2a42 + +cbb9bead7441fd8e700dce34e97b4c +fff02b8884d659ae8cf9b664a35185 +f0442566aafc066369d18221d56d04 + +a588c70e14513a8890444efa5d1adf +dd2ae34510601d95692eb034731457 +67746ea0b9c65a561fecc3319f7aaf + +8646ed68984912d6b16f3933386193 +59c6639e65dba079d01a3ac4dd32dc +ea6c8b83abb9410b173ebf5b6253b2 + +8b331b688ffbc4d1d12e8217cdf9f9 +92d71188f9f4d220b46306fe0b7111 +387d678f95db6a64fcca62c21ed018 + +94232d3c837dc3bbd92f61a3a7db9d +1aead85d9ffdcf0fc149d9881e9a18 +df9069f32a7676188be8de2700aa15 + +7339e5ee4c83a94f0b3befa176b972 +3341c64ee7f85d9c24427cb9a720d4 +b0026f8ab7c9b0032cf4673ec6a45c + +0c909ec529025cf56129afc5a97704 +af579089839d857ed5bd9812798d7e +ea0b6aa0bea72fbb50aed527ac939d + +579aed9c6405b48779f9724812ca79 +5b0550d88083c0bd7562ad6b51eb4d +edc04146ce6bfaf3312facc53b1530 + +8d22eaeea375d863bb5a1aa8acfe58 +c25e7db3e99fddebbce3ccdb2597ea +f348ac48ab27db746dc4c0d02c2c0d + +b5e42bf2871773d03f3f35d7e00962 +6ad869ce21450484d9246ed65f1c33 +511c8b30e8191e0f4a48f349ad710c + +d61b8c70cf01547635e4ae4421ff91 +99671457e2a4147005fd0c7029020a +aaf6abcb3cc1026aec0b125b0696fd + +46811ca18ad261dbd3765ebc172e74 +eb39f0a492302f7889f1ce60dfa9a7 +e2f7a56e0f74a9152a6b0a0e67b95a + +d3ad7c1e2732ea109185f7c93937f5 +8577d724ad1e3c71e1d96005f71193 +589b679732c3e9780bc7d9dcc21809 + +0e6dc32a1d83a9a9a57d6199c8dd22 +6f2a60bdfdecda7e0559614b72a17a +a9d3308e2f907614b2ad20a58fb710 + +dd079c9ede562d37ee90a6bf408cb2 +bf7cae120ca34d62f2860eb6306145 +b0d66f44b26909e3a584a18d479430 + +5497eee5b0a7c3089f4193b60da310 +555fe97ef0876403d5d392b410fdea +10f93ad5bf84f6a6b303bbae563b71 + +41ab25579262c9525af9f943ec4430 +886ae0d7b32a8adf5da8c7fec8f0b0 +10a04f7b8112679b0fc8036c3c39d9 + +7eee15b21046352842095471270797 +02508f42909f8dc6aa7720c5161e74 +62341af9fc6c945ceb19b31a7c4aea + +bc587c7387d0ba2e9396f4c8a61edb +cb344935b2827c904f699db665560b +03d0933e37b1fe40ebc64090c3337e + +d1f030ae4709db43c69169c08aed82 +71e26937cfeefeac42206419247135 +cce94659d1c846222ed252782535ba + +501bcd34ba81394dcc2f31db3d75fb +fac9ae25801e9eb5cc15b9bb7515b8 +c3a3b715ebc5fb7e1ccbb589a8a657 + +7a61675ed375b21ff64153815412dd +db1fc171a8ac2fb3cd2a3bc3d14452 +b43fcbfda963e8d58b6986c78924b9 + +f4dfbe7b70f265de7f0101c03b1990 +a1dee719fc137c6fa196a403850756 +36f632b0c830d601c5a4d34b6dee2d + +05348b0c9a064578dbab2eee9ef025 +9d45571b95855e172323427c43be6a +1e286a11d4b1e8e951a08f76387c8f + +2a60a3d21b5a6e9ffd64cf0d7721e4 +bfb75930648f831d4cbd497bb841e6 +67af049be381aaf177f3842d4287bc + +0c21755bfbcfb2f14ca81d726ee5bc +d56636c1416648f6f2a74361cf8b7e +08a5a2dc5def0b568a4311c8d1dab6 + +5265ad079c79e087c1ccefce01f804 +5be1f2d113369694f59ce6a4ebf7fd +6990108583403fc6e2be9f6e588b93 + +022241fceaf7ca9a000ee93145513e +17fcbe03de01b9cfb52768aa79b11e +8419ea381dfb3a6dda24c5a1e4e07f + +21385bdaf9eccd695baf0052f583cc +33fa77d9b3247c641026ad99f8877f +6434ed05329392870fb2a67790b2c1 + +10bde96addde86524368cb7a59d9f5 +60b03467942cf1b398542710891225 +8b83daa01ee17d0deabeddd71190c2 + +851a0314b23b17be31d6003fad8444 +57cadbea3910197b65569ca7675409 +58d8561109d039081848af3340d4b3 + +ed02197927b849ebfa6dec7063ec6b +88a9f8a4710f72f0fa0fd48ffdd7df +c681104115288afea779d1e44695d6 + +f71658ce0e3c25536a8330cbfbf4a5 +f2fe71704b50aaf1786f3caae19674 +d62e1dfa42f9e7922cd585f5986132 + +4c458b0a626dc9c1633428bb9a2634 +7e6c284c74b9edae73c9f27991dd99 +dd3a4710bed854669523d711c824ba + +fc11a02893546ec7a021d34e18a938 +f8a6690c747de6fd8eda1a066e2a70 +ee40ca00a7e99f909ae18e0cb8f3da + +6960009ded2047c134d2c7f6eb3c0e +0ba21993f235e0072dff7de1cb3bc4 +6b0705f3eaa807500961cb8fe75441 + +d7bbbd92f93fc6c81daf64dabc4e3c +b6c25530c6e33da10af6cbf6abec99 +7aaaeae16853b8c4de9620dea818ba + +f2125fd661fb7edc148899baaa7959 +caa249dbecdd4fb472dc425d7a6b6a +fdc3ccc949ea6b387c4112808f39fd + +ba31fb94bb56253f3c815f8b1410ce +eebe3f1c6a4a81ab60076aeca90ecb +2b57056e04e2387133b1d5ab0b0991 + +5f7ec0f068ac73d9d98de4006572da +c2de2b6a24976536db0cb841a290ad +1655404cc8df0b56def13a122c75e7 + +a65d534870c5229ca6423b68c13ace +9edc7a4174040a012618d532618552 +49da8c34c2f6e2b396359615eb54da + +5eff0c192a84cb5324a84f11a828a4 +f942d1b9cff2687961d35406211c90 +af6385645df4b8cb59dec456b74b2e + +8c2bcef17704f12b5f7af2ec49cfdc +b8dcdd11799979cca176e47c776dd3 +0568f56e3ad3be5c4bca31fd131011 + +a862d702b27986f18231ca8ea95863 +ae0e28a2fb15a33468251851cd2c87 +2f9f0ad470db3cfc81448c3d4f99be + +8ee187176d60359acd7dad513a74a6 +c9da9ff91e49938df8ca76ca3bfb52 +c70146607a429ddff4dc580ac36299 + +41f40a352dc394d6b40a96948681a5 +00836188f372dbd03fcf25f3afd7ee +423fdb754ce3de38705328c8d187ce + +4913473aedc877d3745d444e53267a +41162ea1e474bfc59f832fcc190191 +449445dec8a0c29060c6affb47ba27 + +c9b7fcb86af087ee187b95dd6aa3ef +660c6a0ab8420e9aebca44830ff0de +17f9401653865c6d25ab2000d80d8a + +09636d2c31d464873706a5340bc67c +82141563bb94ac70bafa34b5f25ffc +74abb881f0286f8e676dbed31056a7 + +bd4a65357952ec08525124763a8c26 +9e476e2e03d8d99835e68e16ee2a92 +860e0cba96928b2d67eefe731a4840 + +8ca9e4c7750f9ab3a8b699ed95147c +de75d7f5f873ce5b77ab4bc7e72553 +046413c1233738e9e2fd13ce29cf6c + +d5028d1e70367330574ce71525cd84 +f94bcdf2f33300bc857b1be073d889 +18af2775d6a936e3d37bbd74f71100 + +9eb4182fe7d871f5803b779fd236a9 +9de7cf111587d4f0e2a3748f6c05e4 +c1f24fdea939307c11faa7978a6f9b + +a7ecb60bf5ee5d5d0f4a3d475ce805 +3bbc75d27becb6dfc064d28dd34634 +3c56ddfee960cfdb495323d6a17fd9 + +c8d1b602f9a67d35d038991f9fc4a3 +9b7bce1ad0efa869848c0c2fdb5169 +960184dd05cd5145a345303e469eaf + +7f2b3699cd3aab7c5658bb14813851 +e452686e275b1b21eaf7f81e33ff88 +df9812b835a5010ad11dcd90b4df50 + +77eafcdd8b4bbba77a6dfed879f119 +e91ab9ef81423261f933111de4d2c9 +65bcbc78f68924d5770d9a2f99aa81 + +7a6e5f11c019772555a0c371b6dd81 +4b470c1a445a8e57bff7f248aa4338 +6f7bd0ffc463b34e72cbc550930f1b + +65bf80e805200eab4c15841c87df6f +8a6ea307a56a569dfa83a8989b27c7 +394c65e85bbe7b3ff1059d6812d581 + +f830bc3fc373c161baf1e777fbcbec +f8e9809360a2dcecb7e74e688bcaee +343c11636e213103ce8b68d01084ce + +22be024c0b3cc4c454bd0d08137a23 +c74b88fd034d72d0b8ba969cc952e6 +74ae39d307074b177fdce96b69078a + +6363c8db4c8a72ce65da9912f41a77 +b2da277f365f7465ecdbda0bf31799 +ef1936d20e319c6524703435968d76 + +e932b72f70a484455a879bbd0164f1 +1142f78b7e58e8f4116ec4649f6a11 +380019a7e14f76541c6027d3faa2d8 + +87a698bbe39c11f9b67d87124535b6 +9b853a833b78e6afdeb219308f4674 +872f3ecb929b5e32ff376fb984a5bc + +c308e3e2917e470ba8a1211890a92b +dc97fb822d01d1ffd7f358d4c6f4ab +f42acba5680ebba1ac0cb8562c9326 + +ff4bf1c12dbb1c8125e59714728950 +bd8a324f514d8c69cf5f234ff1ca5e +ed1923f7ba85f2481c7ea0d995a75b + +5eaf0f18abb49eebbee465894eea9b +e3aafe5c91ecc8520b4e8a52fd7ef2 +f83bee62b7a70e6323bc96a620894e + +f1c847903038ac8796e09378d6c16f +e630c7f313ac4661cdad77ed9b29d4 +07c3e65dd56902bc0428ceca897d5d + +2d862833a6366076aa64839c2d843f +ae2ec48c78a79112e3abe6ec8baee4 +f8ce4525b058cc10a5011961cab46d + diff --git a/infrastructure/tournament-util/match_list.py b/infrastructure/tournament-util/match_list.py index f4c09ec4..9dad10ea 100755 --- a/infrastructure/tournament-util/match_list.py +++ b/infrastructure/tournament-util/match_list.py @@ -9,14 +9,24 @@ if match is not None: for game in match: if game[3] == 1: - winner = 'redwon' + # winner = 'redwon' + winner = game[0] elif game[3] == 2: - winner = 'bluewon' + # winner = 'bluewon' + winner = game[1] else: raise ValueError('Invalid winner: {}'.format(game[3])) - print ('{} -vs- {} | {} {} replay {}'.format( - game[0].rjust(40), # Red team - game[1].ljust(40), # Blue team - game[2].ljust(30), # Map name - winner.ljust(8), # Winner status - game[4])) # Replay id + # print ('{} -vs- {}'.format( + # game[0], # Red team + # game[1])) # Blue team + # print('winner: {}'.format( + # winner)) + # replay_link = 'https://2021.battlecode.org/visualizer.html?tournamentMode&https://2021.battlecode.org/replays/' + game[4] + '.bc21' + replay_link = game[4] + print(replay_link) + # blank line to separate games + # print() + # more blank lines to separate matches + # print() + print() + diff --git a/infrastructure/tournament-util/prep_tournament.sql b/infrastructure/tournament-util/prep_tournament.sql new file mode 100644 index 00000000..ffd8b94d --- /dev/null +++ b/infrastructure/tournament-util/prep_tournament.sql @@ -0,0 +1,22 @@ +-- MAKE A BACKUP ON GCLOUD BEFORE RUNNING THIS +-- Also run this in steps not as a file + +-- 1: Set submissions_enabled to False in api_league +update api_league set submissions_enabled=FALSE; + +-- 2: Change `tour_seed_id` to the current tournament +update api_teamsubmission set tour_seed_id = last_1_id; + +-- 3: Add the tournament to the tournaments table +insert into api_tournament (id, "name", style, date_time, divisions, stream_link, hidden, league_id) +values (2, 'Seeding', 'doubleelim', CURRENTDATE, '{college}', STREAMLINK, True, 0); + + + +-- Get the emails of winning teams +SELECT email from api_user left join api_team_users on api_team_users.user_id = api_user.id +left join api_team on api_team_users.team_id = api_team.id +WHERE api_team."name" in ('wining', 'team', 'names'); + + -- Lock in submissions for more advanced tournaments +UPDATE api_teamsubmission SET tour_intl_qual_id=last_1_id FROM api_team WHERE api_team.id=api_teamsubmission.team_id AND api_team.international AND api_team.student; \ No newline at end of file diff --git a/infrastructure/worker/app/compile_server.py b/infrastructure/worker/app/compile_server.py index 15ec5c7e..f82ed2f6 100755 --- a/infrastructure/worker/app/compile_server.py +++ b/infrastructure/worker/app/compile_server.py @@ -135,4 +135,4 @@ def compile_worker(submissionid): if __name__ == '__main__': - subscription.subscribe(GCLOUD_SUB_COMPILE_NAME, compile_worker, give_up=True) + subscription.subscribe(GCLOUD_SUB_COMPILE_NAME, compile_worker, give_up=False) diff --git a/infrastructure/worker/app/game_server.py b/infrastructure/worker/app/game_server.py index f57a19e7..11d3443f 100755 --- a/infrastructure/worker/app/game_server.py +++ b/infrastructure/worker/app/game_server.py @@ -52,7 +52,8 @@ def game_worker(gameinfo): name2: string, team name of the blue player maps: string, comma separated list of maps replay: string, a unique identifier for the name of the replay - tourmode: string, "True" to use an experimental tournament mode which alternates team sides for each match, and saves replays as a comma-separated list of replay ids for each game + tourmode: string, "True" to use an expiremental tournament mode + Filesystem structure: /box/ @@ -82,9 +83,9 @@ def game_worker(gameinfo): maps = gameinfo['maps'] replay = gameinfo['replay'] tourmode = False + if 'tourmode' in gameinfo and gameinfo['tourmode'] == 'True': tourmode = True - print("Tour mode:", tourmode) # For reverse-compatibility if 'name1' in gameinfo: @@ -165,33 +166,20 @@ def game_worker(gameinfo): else: maps = [maps] - # Initialize win count, to count for each game - wins_overall = [0, 0] - - # Initialize a list of all the replay ids, in case we have to use multiple replay links for the same match - replay_ids = [] - # For tour mode, game_number represents which game (of a match) we're in; # in regular mode, game_number only takes on a value of 0 and doesn't really mean much # (since all the maps get played in the the same engine run) for game_number in range (0, len(maps)): - # Prep game arguments, making sure to switch teams each game. - # If game_number is even, then team A in the engine is player1, etc. - teamA_is_player1 = (game_number%2==0) - player1_info = (teamname1, os.path.join(classdir, 'player1'), package1) - player2_info = (teamname2, os.path.join(classdir, 'player2'), package2) - (teamA_arg, classLocationA_arg, packageNameA_arg) = player1_info if teamA_is_player1 else player2_info - (teamB_arg, classLocationB_arg, packageNameB_arg) = player2_info if teamA_is_player1 else player1_info maps_arg = maps[game_number] # Execute game result = util.monitor_command( ['./gradlew', 'run', - '-PteamA={}'.format(teamA_arg), - '-PteamB={}'.format(teamB_arg), - '-PclassLocationA={}'.format(classLocationA_arg), - '-PclassLocationB={}'.format(classLocationB_arg), - '-PpackageNameA={}'.format(packageNameA_arg), - '-PpackageNameB={}'.format(packageNameB_arg), + '-PteamA={}'.format(teamname1), + '-PteamB={}'.format(teamname2), + '-PclassLocationA={}'.format(os.path.join(classdir, 'player1')), + '-PclassLocationB={}'.format(os.path.join(classdir, 'player2')), + '-PpackageNameA={}'.format(package1), + '-PpackageNameB={}'.format(package2), '-Pmaps={}'.format(maps_arg), '-Preplay=replay.bc21' ], @@ -206,7 +194,6 @@ def game_worker(gameinfo): replay_id = replay if tourmode: replay_id += '-' + str(game_number) - replay_ids.append(replay_id) bucket = client.get_bucket(GCLOUD_BUCKET_REPLAY) try: with open(os.path.join(rootdir, 'replay.bc21'), 'rb') as file_obj: @@ -214,6 +201,7 @@ def game_worker(gameinfo): except: game_log_error(gametype, gameid, 'Could not send replay file to bucket') + # Interpret game result server_output = result[1].split('\n') @@ -234,25 +222,14 @@ def game_worker(gameinfo): except: game_log_error(gametype, gameid, 'Could not determine winner') else: - # Tally up these wins - # wins_overall is in order [player1, player2] - # wins is in order [teamA, teamB] - if teamA_is_player1: - wins_overall[0] += wins[0] - wins_overall[1] += wins[1] + + if wins[0] > wins[1]: + game_report_result(gametype, gameid, GAME_REDWON, wins[0], wins[1]) + elif wins[1] > wins[0]: + game_report_result(gametype, gameid, GAME_BLUEWON, wins[1], wins[0]) else: - wins_overall[0] += wins[1] - wins_overall[1] += wins[0] - - # Find the overall winner - logging.info('Match ended. Result {}:{}'.format(wins_overall[0], wins_overall[1])) - replay_ids_string = ','.join(replay_ids) - if wins_overall[0] > wins_overall[1]: - game_report_result(gametype, gameid, GAME_REDWON, wins_overall[0], wins_overall[1], replay_ids_string) - elif wins_overall[1] > wins_overall[0]: - game_report_result(gametype, gameid, GAME_BLUEWON, wins_overall[1], wins_overall[0], replay_ids_string) - else: - game_log_error(gametype, gameid, 'Ended in draw, which should not happen') + game_log_error(gametype, gameid, 'Ended in draw, which should not happen') + finally: # Clean up working directory @@ -265,4 +242,4 @@ def game_worker(gameinfo): if __name__ == '__main__': - subscription.subscribe(GCLOUD_SUB_GAME_NAME, game_worker, give_up=True) + subscription.subscribe(GCLOUD_SUB_GAME_NAME, game_worker, give_up=False) diff --git a/release.py b/release_python.py similarity index 76% rename from release.py rename to release_python.py index d6e2faf7..15ef7b24 100755 --- a/release.py +++ b/release_python.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 """ +In general, this script is out of date. Make any changes as necessary. +(e.g. repo names, year numbers, new frontend deploy process, different specs) Here's what this script does: * Generates a comparison link to review the changes * Adds version number and changelog to specs/specs.md @@ -18,24 +20,20 @@ import datetime def main(version): - # generate_comparison_link() + generate_comparison_link() - # TODO change to use right file name. also, make that file name a easy-to-set variable - # specs(version) + specs(version) # TODO should be adapted now that we use markdeep instead - # fancy_specs() - client() + fancy_specs() deploy_frontend() - # These steps are used for a Python-based engine, eg Battlehack 2020. - # When running a Python game, these commands should be maintained and used, instead. - # update_setup_py_version(version) + update_setup_py_version(version) - # publish_pypi() + publish_pypi() - # commit_tag_push(version) + commit_tag_push(version) def generate_comparison_link(): @@ -131,27 +129,13 @@ def commit_tag_push(version): subprocess.call('git push', shell=True) subprocess.call('git push --tags', shell=True) -def client(): - """ - Build client for web. - """ - # TODO this should be in the npm script too, to get this to run during local development. - os.chdir("client/visualizer") - # TODO apparently need to run npm install first. - # This is okay in the deploy script. (we run npm install for the frontend folder too) - # However, for development, devs should run npm install in client dir. Can just drop a note about this in readme. (also note that if client ever looks out of date, do npm run install-all, npm run build again.) - # Easiest to do each of these builds thru npm run install-all, npm run build. in top client folder. - subprocess.call("npm run prod", shell=True) - subprocess.call("cp -r out ../../frontend/public", shell=True) - os.chdir("../../frontend") - if __name__ == '__main__': parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter ) - parser.add_argument('version', help='Version number, e.g. 2021.0.1.1') + parser.add_argument('version', help='Version number, e.g. 0.1.1') args = parser.parse_args() diff --git a/specs/specs.md.html b/specs/specs.md.html index 4ecf47f0..8fe2ed50 100644 --- a/specs/specs.md.html +++ b/specs/specs.md.html @@ -16,7 +16,7 @@ **Battlecode: Campaign** *The formal specification of the Battlecode 2021 game.* - Current version: 2021.2.4.1 + Current version: 2021.3.0.5 Welcome to Battlecode 2021: Campaign. This is a high-level overview of this year's game. @@ -227,11 +227,10 @@ if the politician has less than 10 conviction, the speech will not affect other robots at all. - If there are $n$ nearby robots, then the remaining conviction will be divided into $n$ equal parts. - If it cannot be equally divided, - the extra conviction will be distributed with priority given first to later creation, - with ties broken by smaller robot ID. - Each friendly unit will gain conviction, capped at the unit's initial conviction. + Any buffs from Muckrakers **will** be applied here. - Each friendly building will gain conviction. + Friendly buildings **do not receive** buffs from Muckrakers. - Each non-friendly (enemy or neutral) robot will lose conviction. If its conviction becomes negative, then: - Politicians will be **converted** to your team, @@ -239,7 +238,7 @@ capped at the robot's initial conviction. - Slanderers and muckrakers will be destroyed. - Buildings will be **converted** to your team, - with conviction equal to the absolute value of the difference. + although the excess conviction **does not receive** any buffs from Muckrakers. - Unused conviction (i.e. conviction lost due to conviction caps) will be lost forever, with echoes of the speech carried away by the Martian wind. @@ -275,9 +274,9 @@ - **Expose (active ability)**: Targets an enemy slanderer, exposing its lies and destroying it. For the next 50 turns, all speeches made by the muckraker's team will have a multiplicative factor of - $1.001^\text{slanderer's influence}$ applied to the total conviction of the speech, - before the 10 units of conviction are deducted. - If multiple slanderers are exposed, these factors are combined multiplicatively. + $1+0.001\cdot(\text{slanderer's influence})$ applied to the total conviction of the speech, + after the 10 units of conviction are deducted. + If multiple slanderers are exposed, the total slanderer influence accumulates. ### Enlightenment Centers @@ -474,6 +473,38 @@ # Changelog +- Version 2021.3.0.5 (2/2/21) + - Release all maps + +- Version 2021.3.0.4 (1/28/21) + - Update client stats display + +- Version 2021.3.0.3 (1/28/21) + - Client (thanks to a PR from Team California Roll) + - Display team income + - Represent conviction with unit size + +- Version 2021.3.0.2 (1/23/21) + - Fix client buff display + +- Version 2021.3.0.1 (1/22/21) + - No changes: redeploy was needed for technical reasons + +- Version 2021.3.0.0 (1/22/21) + - Muckraker buff changed from exponential to linear (1st order Taylor expansion) + - Buff has no effect on friendly enlightnment centers + - Empowering now gets taxed before buff is applied + +- Version 2021.2.4.3 (1/20/21) + - Make new maps visible in Client + - Fix bid tracking + +- Version 2021.2.4.2 (1/20/21) + - Release Sprint 2 maps + - Client + - Allow upload of .map21 files in map editor + - Track max bids (losing bids are only tracked for new replays) + - Version 2021.2.4.1 (1/16/21) - Client - Track more info (buffs will only work for new replays)