UNPKG

csc-tools

Version:

Tools for Cincinnati Smash Classic

635 lines (518 loc) 22.3 kB
# csc-tools A small set of tools for optimizing competitive tournament pools, originally created to help aid a Super Smash Bros. Melee tournament series by the name of Cincinnati Smash Classic. Nothing specific to SSBM is included in any of the logic, so if you're running any kind of competitive tournament in which competitors are divided up into an initial pool stage, you might find it useful. ## The Problem Most decently-sized SSBM tournaments consist of at least two stages: a pool stage, and a bracket stage. The bracket stage is a double-elimination tournament that most people are familiar with. In the pool stage, however, players are divided up into smaller groups that compete with one another. Usually, a set number of players from each pool will advance into the bracket stage, with seeding positions based on their performance in the pool stages. This structure helps greatly with tournament logistics and, if implemented properly, is widely considered to produce more fair results than a straight double-elimination bracket. It does, however, come with its own set of problems. Like a straight double-elimination bracket, pools cannot simply be created at random without seriously risking the accuracy of the results. Imagine a tournament in which the 3 best players are all shoved into the same pool in which only the top 2 advance, and you can easily see the issue. In that case, one of the most likely top 3 competitors wouldn't even get to play in bracket, no matter what. ### A solution? This skill-balancing problem is easily addressed by arranging competitors into a *seed order* based on their expected performance. For example, let's say I've sorted the following list of players based on known skill level from power rankings and previous events: 1. Fox 2. Falco 3. Marth 4. Sheik 5. Jigglypuff 6. Peach 7. Ice Climbers 8. Captain Falcon 9. Pikachu 10. Samus 11. Dr. Mario 12. Yoshi 13. Luigi 14. Ganondorf 15. Mario 16. Young Link I would then arrange players into 4 pools using a *snake-style* seeding pattern, which is widely regarded as the most fair pattern and is used by [smash.gg](http://smash.gg) and other popular tournament hosting services. This pattern looks like this: ``` 1, 2, 3, 4, 4, 3, 2, 1, repeat ``` This produces the following pools: | Pool 1 | Pool 2 | Pool 3 | Pool 4 | | -------------- | ------------ | ------------ | ------------ | | Fox | Falco | Marth | Sheik | | Captain Falcon | Ice Climbers | Peach | Jigglypuff | | Pikachu | Samus | Dr. Mario | Yoshi | | Young Link | Mario | Ganondorf | Luigi | Looks great, right? Well... As it turns out there's one more problem... ### Regional Collisions For most players, an important part of the tournament experience is the opportunity to play against new people. Nobody likes to drive 6+ hours only to get stuck in a pool with their carpool mates from back home. Very large tourmaments don't usually take steps to prevent this, since it isn't very likely to occur, but at small to midsize events, organizers are expected to reduce such collisions as much as possible to keep everybody happy. ### Ok, now what? Let's go back and reimagine our seeding process a little bit and try to take this new problem into account. Obviously, we can't have a set-in-stone seeding order like we did above. If we did, we wouldn't be able to rearrange players to reduce regional collisions. Instead of a hard seed order, let's arrange players into *ranks*. Players of a better rank are always seeded earlier than players of a worse rank, but players within a rank can be rearranged as much as needed. | Player | Rank | Region | | -------------- | :--: | ---------------- | | Fox | 1 | Brockway | | Falco | 2 | Ogdenville | | Marth | 2 | Brockway | | Sheik | 2 | Brockway | | Jigglypuff | 3 | Ogdenville | | Peach | 3 | North Haverbrook | | Ice Climbers | 4 | Ogdenville | | Captain Falcon | 4 | Brockway | | Pikachu | 5 | North Haverbrook | | Samus | 5 | Ogdenville | | Dr. Mario | 6 | North Haverbrook | | Yoshi | 6 | Brockway | | Luigi | 6 | Brockway | | Ganondorf | 7 | Ogdenville | | Mario | 7 | North Haverbrook | | Young Link | 7 | Ogdenville | Now, if we assume the same seeding order as above and check out the regions, we'll notice some problems. There are only 4 North Haverbrook players, and two of them are in Pool 3. Meanwhile, Pool 2 has 3 Ogdenville players, and Pool 4 has 3 Brockway players! Based on the total number of players from each of those regions, there's no need to have more than 2 players from either in a given pool. So, let's try to fix this manually. One of the North Haverbrook players in Pool 3 needs to move into Pool 4. Those players are Peach and Dr. Mario, and their ranks are 3 and 6. In pool 4, there is one rank 3 player (Jigglypuff) and one rank 6 player (Yoshi). Jigglypuff is from Ogdenville, however, and moving her into Pool 3 would create a new collision where there wasn't one before. Therefore, we must swap Dr. Mario and Yoshi. Now we've got these pools: | Pool 1 | Pool 2 | Pool 3 | Pool 4 | | -------------- | ------------ | ------------ | ------------ | | Fox | Falco | Marth | Sheik | | Captain Falcon | Ice Climbers | Peach | Jigglypuff | | Pikachu | Samus | Yoshi | Dr. Mario | | Young Link | Mario | Ganondorf | Luigi | This had the added benefit of reducing the number of Brockway players in Pool 4. But now, we still have Pool 2, with no Brockway players at all, while every other pool has two! So, we need to move one of those 6 Brockway players into Pool 2. Let's evaluate each Brockway player in sequence: - Fox can't move because he's the only rank 1 player. - Marth could potentially switch with Falco. - Sheik could also switch with Falco. - Captain Falcon could switch with the Ice Climbers. - Yoshi can't move into Pool 2 because nobody in pool 2 shares his rank. - Luigi can't move into Pool 2 for the same reason as Yoshi. Both Falco and the Ice Climbers are from Ogdenville, and we *do* need to move an Ogdenville player out of Pool 2. Thus, any one of the three moves above will work. To eliminate bias, we should choose randomly. Let's say I rolled a die just now, 1-2 for Marth, 3-4 for Sheik, 5-6 for Captain Falcon. I rolled a 3, so I swap Sheik and Falco: | Pool 1 | Pool 2 | Pool 3 | Pool 4 | | -------------- | ------------ | ------------ | ------------ | | Fox | Sheik | Marth | Falco | | Captain Falcon | Ice Climbers | Peach | Jigglypuff | | Pikachu | Samus | Yoshi | Dr. Mario | | Young Link | Mario | Ganondorf | Luigi | At this point, we've reduced regional collisions in this tournament to their absolute minimum. Fortunately, in this case, we were able to do it in only two moves. Now imagine, for a moment, doing this for a tournament with one hundred or more players, ten or more ranks, and six or seven different regions. Hopefully you can see why this is a huge undertaking. For a real tournament it takes hours, it's very easy to mess up, and when pre-registered players drop out, it sometimes has to be done all over again. This is the reason why most organizers don't bother going this far with keeping everyone happy. ### Enter csc-tools Computer programs can solve these kinds of problems much more rapidly than a human ever could. `csc-tools` is one such program, and it can perform this regional collision minimization in a matter of seconds. It does this by way of a genetic algorithm. If you're curious about how it works, feel free to look over the [source code](https://github.com/sripberger/csc-tools). Otherwise, keep reading to learn how to use it! ## Getting Started `csc-tools` is a [Node.js](https://nodejs.org/) script. I'm assuming that most people reading this will not be well-versed in node or similar environments. In the future, I may create a web app for these folks so they don't have to go through the hassle of setting up node on their system, but until then I'll include some basics here. Feel free to skip this if you already know what you're doing. ### Installing node and npm Node can be installed in several different ways. Ideally, you should find your system and follow the instructions [here](https://nodejs.org/en/download/package-manager/). If you are using Windows, however, you're probably best off downloading and runnning the msi installer package from [here](https://nodejs.org/en/download/). All of these methods will install both the node `node` and `npm` commands to your system. `node` can be used to run programs, while `npm` is a package manager for easily downloading public node programs (like `csc-tools`). ### Installing csc-tools Open a terminal. Mac or Linux users can use the built in terminal app. Windows users can either use Command Prompt or Windows Powershell. Type the following and press enter: ``` npm install -g csc-tools ``` This will download `csc-tools` and place it somewhere in your system path, allowing you to easily use it from anywhere on your system. To make sure it worked, try running this command next: ``` csc-tools --version ``` This should display the version of `csc-tools` that you have installed. ## Using csc-tools To use csc tools, you'll want to start by having a [csv](https://en.wikipedia.org/wiki/Comma-separated_values) file somewhere containing your list of players, in seed order. I chose csv for this purpose because its easily downloaded from Google Sheets, which I use for most of my organizational work. Other formats might be supported in the future, but for now, use csv. The first row should contain column names. You can include any columns you like, though the three required ones are `tag`, `rank`, and `region`. These can be in any order, but the **names are case sensitive**. If you call your rank column `Rank` instead of `rank`, for example, `csc-tools` will not work. The sample player list from above would look like this: ``` tag,rank,region Fox,1,Brockway Falco,2,Ogdenville Marth,2,Brockway Sheik,2,Brockway Jigglypuff,3,Ogdenville Peach,3,North Haverbrook Ice Climbers,4,Ogdenville Captain Falcon,4,Brockway Pikachu,5,North Haverbrook Samus,5,Ogdenville Dr. Mario,6,North Haverbrook Yoshi,6,Brockway Luigi,6,Brockway Ganondorf,7,Ogdenville Mario,7,North Haverbrook Young Link,7,Ogdenville ``` Note that the rank column must contain numbers, but these numbers need not be integers. If you wanted to make sure Falco was always seeded above Marth and Sheik but below Fox, for example, you can change his rank to 1.5. Also note that the region names here are case sensitive. If we changed Marth's region to `brockway`, he would actually be treated as if he were in a completely different region than Sheik. Once you have your csv file, use your terminal to change directories (`cd`) to wherever that file is. If I'm using Windows 10 and put it on my desktop, for example, I would do the following. If you do the same, you'll want to replace `Steve` with your own Windows username. ``` cd C:\Users\Steve\Desktop ``` Now that we're in the same directory as our csv file, we can run `csc-tools` to check on the status and perform the minimization. ### The Analyze Tool First things first, let's check out the seeding order we already have. We want to know how many collisions it currently has, and how many collisions we'd ideally like it to have. We can do this by way of the `analyze` tool, which we can run like so: ``` csc-tools analyze 4 players.csv ``` In case you're wondering what all that means, `csc-tools` is the name of the program I'm running, and `analyze` is an argument telling it to use the analyze tool. `4` is the number of pools in the tournament (you'll want to change this for larger tournaments), and `players.csv` is the name of my file. This command will cause the following to be logged into the terminal: ``` collisionScore: 8 minCollisionScore: 4 minPoolCollisionScore: 0 ``` - The `collisionScore` is based on the number of regional collisions. It isn't exactly linear, meaning this result doesn't mean there are exactly eight collisions. In fact, there are only four collisions occurring right now, but since two of them involve three players instead of just two, they're considered more severe. The key thing to take away here is that lower is better, and higher is worse. - The `minCollisionScore` is based on the total number of players in each region. It represents the absolute smallest `collisionScore` that can possibly be reached by shifting players around. Ideally, we'd like our `collisionScore` and our `minCollisionScore` to be the same. This will not *necessarily* be possible without moving players outside of their ranks, but `csc-tools` will get as close as it possibly can when we run the `solve` tool later. - The `minPoolCollisionScore` is also based on the total number of players in each region. It represents the smallest number that an individual pool can possibly contribute to the total `collisionScore` in the case of a solution. Individual pools might have lower scores, but not without other pools having higher scores as a result. In this case, the `minPoolCollisionScore` is zero, meaning that an optimized result might have at least one pool with no collisions at all. This isn't all that useful, but it can make it easier to identify problem pools at a glance. This short analysis seen above is all well and good, but sometimes you might want some more detail. #### Showing Region Counts To include region counts in analysis, include the -r flag, like so: ``` csc-tools analyze -r 4 players.csv ``` Which will output the following: ``` collisionScore: 8 minCollisionScore: 4 minPoolCollisionScore: 0 Region Counts Brockway: 6 Ogdenville: 6 North Haverbrook: 4 ``` This is nice because it helps us verify the `minCollisionScore`. We know that 4 is correct, because four collisions are necessary. Ogdenville and Brockway each have two more players than there are pools, and each of these 4 overflowing players will cause at least one collision, no matter what we do. #### Showing Pools To actually display the pools in analysis, include the -p flag, like so: ``` csc-tools analyze -p 4 players.csv ``` Which will output the following: ``` collisionScore: 8 minCollisionScore: 4 minPoolCollisionScore: 0 Pool 1 (collisionScore: 1) tag rank region Fox 1 Brockway Captain Falcon 4 Brockway Pikachu 5 North Haverbrook Young Link 7 Ogdenville Pool 2 (collisionScore: 3) tag rank region Falco 2 Ogdenville Ice Climbers 4 Ogdenville Samus 5 Ogdenville Mario 7 North Haverbrook Pool 3 (collisionScore: 1) tag rank region Marth 2 Brockway Peach 3 North Haverbrook Dr. Mario 6 North Haverbrook Ganondorf 7 Ogdenville Pool 4 (collisionScore: 3) tag rank region Sheik 2 Brockway Jigglypuff 3 Ogdenville Yoshi 6 Brockway Luigi 6 Brockway ``` We can use this to preview the pools, as well as trace the sources of the total collision score. As we can see, pools 2 and 4 our our main problem pools. Pool 3 could use some adjustment as well, seeing as North Haverbrook only as four players and thus should not have any collisions at all. #### Showing Everything If you wish, you can combine the r and p flags, like so: ``` csc-tools analyze -rp players.csv ``` Which will output everything: ``` collisionScore: 8 minCollisionScore: 4 minPoolCollisionScore: 0 Region Counts Brockway: 6 Ogdenville: 6 North Haverbrook: 4 Pool 1 (collisionScore: 1) tag rank region Fox 1 Brockway Captain Falcon 4 Brockway Pikachu 5 North Haverbrook Young Link 7 Ogdenville Pool 2 (collisionScore: 3) tag rank region Falco 2 Ogdenville Ice Climbers 4 Ogdenville Samus 5 Ogdenville Mario 7 North Haverbrook Pool 3 (collisionScore: 1) tag rank region Marth 2 Brockway Peach 3 North Haverbrook Dr. Mario 6 North Haverbrook Ganondorf 7 Ogdenville Pool 4 (collisionScore: 3) tag rank region Sheik 2 Brockway Jigglypuff 3 Ogdenville Yoshi 6 Brockway Luigi 6 Brockway ``` For a large tournament this can be a lot of information cluttering your console, which is why only the basics are shown by default. ### The Solve Tool Now that we've analyzed our player list, let's see if we can cut down that collision score using the `solve` tool, like so: ``` csc-tools solve 4 players.csv > players-optimized.csv ``` Again, `csc-tools` is the program we're running, `solve` is the tool we're using, `4` is the pool count, and `players.csv` is the file. Normally, the solve tool will output the optimized csv into the console. The `>` allows you to instead redirect it into a file, in this case one named `players-optimized.csv`. Now, let's try analyzing this new file: ``` csc-tools analyze -p 4 players-optimized.csv ``` ``` collisionScore: 4 minCollisionScore: 4 minPoolCollisionScore: 0 Pool 1 (collisionScore: 1) tag rank region Fox 1 Brockway Ice Climbers 4 Ogdenville Samus 5 Ogdenville Mario 7 North Haverbrook Pool 2 (collisionScore: 1) tag rank region Falco 2 Ogdenville Captain Falcon 4 Brockway Pikachu 5 North Haverbrook Young Link 7 Ogdenville Pool 3 (collisionScore: 1) tag rank region Sheik 2 Brockway Peach 3 North Haverbrook Yoshi 6 Brockway Ganondorf 7 Ogdenville Pool 4 (collisionScore: 1) tag rank region Marth 2 Brockway Jigglypuff 3 Ogdenville Luigi 6 Brockway Dr. Mario 6 North Haverbrook ``` As you can see, the collisions have now been minimized. Now, we're free to take `players-optimized.csv` and upload it into Google Sheets or copy it into smash.gg or some other service. If you find that the solve tool takes a long time and can't seem to hit that minimum collision score, this probably means that your rankings are too strict and that truly minimizing collisions is not possible without moving some players outside of their ranks. You can remedy this by merging some of your ranks together and running the solve tool again. This should be all you need to know for basic use. The rest of this README is stuff for nerds that may or may not interest you. ## Usage ``` csc-tools [options] [command] Options: -V, --version output the version number -h, --help output usage information Commands: analyze|a [options] <poolCount> [path] analyze a player list solve|s <poolCount> [path] minimize regional collisions in a player list ``` ``` csc-tools analyze|a [options] <poolCount> [path] analyze a player list Options: -r, --show-region-counts Show region counts -p, --show-pools Show pools -h, --help output usage information ``` ``` csc-tools solve|s [options] <poolCount> [path] minimize regional collisions in a player list Options: -h, --help output usage information ``` For either command, if `path` is omitted, input is instead read from stdin. ## API `csc-tools` can also be used programatically in node, like so: ``` npm install csc-tools --save ``` ```js const cscTools = require('csc-tools'); let players = [ { tag: 'Fox', rank: 1, region: 'Brockway' }, { tag: 'Falco', rank: 2, region: 'Ogdenville' }, { tag: 'Marth', rank: 2, region: 'Brockway' }, { tag: 'Sheik', rank: 2, region: 'Brockway' }, { tag: 'Jigglypuff', rank: 3, region: 'Ogdenville' }, { tag: 'Peach', rank: 3, region: 'North Haverbrook' }, { tag: 'Ice Climbers', rank: 4, region: 'Ogdenville' }, { tag: 'Captain Falcon', rank: 4, region: 'Brockway' }, { tag: 'Pikachu', rank: 5, region: 'North Haverbrook' }, { tag: 'Samus', rank: 5, region: 'Ogdenville' }, { tag: 'Dr. Mario', rank: 6, region: 'North Haverbrook' }, { tag: 'Yoshi', rank: 6, region: 'Brockway' }, { tag: 'Luigi', rank: 6, region: 'Brockway' }, { tag: 'Ganondorf', rank: 7, region: 'Ogdenville' }, { tag: 'Mario', rank: 7, region: 'North Haverbrook' }, { tag: 'Young Link', rank: 7, region: 'Ogdenville' } ]; let optimizedPlayers = cscTools.solve(players, 4); let analysis = cscTools.analyze(optimizedPlayers, 4); // analysis example: // { // collisionScore: 4, // minCollisionScore: 4, // regionCounts: { // 'Brockway': 6, // 'Ogdenville': 6, // 'North Haverbrook': 4 // }, // pools: [ // { // collisionScore: 1, // players: [ // { tag: 'Fox', rank: 1, region: 'Brockway' }, // { tag: 'Ice Climbers', rank: 4, region: 'Ogdenville' }, // { tag: 'Samus', rank: 5, region: 'Ogdenville' }, // { tag: 'Mario', rank: 7, region: 'North Haverbrook' } // ] // }, // { // collisionScore: 1, // players: [ // { tag: 'Marth', rank: 2, region: 'Brockway' }, // { tag: 'Captain Falcon', rank: 4, region: 'Brockway' }, // { tag: 'Pikachu', rank: 5, region: 'North Haverbrook' }, // { tag: 'Ganondorf', rank: 7, region: 'Ogdenville' } // ] // }, // { // collisionScore: 1, // players: [ // { tag: 'Sheik', rank: 2, region: 'Brockway' }, // { tag: 'Jigglypuff', rank: 3, region: 'Ogdenville' }, // { tag: 'Dr. Mario', rank: 6, region: 'North Haverbrook' }, // { tag: 'Young Link', rank: 7, region: 'Ogdenville' } // ] // }, // { // collisionScore: 1, // players: [ // { tag: 'Falco', rank: 2, region: 'Ogdenville' }, // { tag: 'Peach', rank: 3, region: 'North Haverbrook' }, // { tag: 'Yoshi', rank: 6, region: 'Brockway' }, // { tag: 'Luigi', rank: 6, region: 'Brockway' } // ] // } // ] // } ```