Create a gist now

Instantly share code, notes, and snippets.

NCAA Tournament Formatter

The original idea for tweetyourbracket

Linked to from http://lukecod.es/2014/01/25/tweet-your-bracket/

How To Run

  1. Install node
  2. Install npm
  3. git clone git@gist.github.com:2028007.git gist-2028007
  4. cd gist-2028007
  5. npm install
  6. node app.js S18541137214112424W185463721532533E191213113102112111011111MW1854113728432828FFWMWW

Explanation

The argument passed to app.js is a string containing picks for the 63 games in the NCAA Tournament (after the play-in games). The example above can be looked at as divided into five parts:

E18541137214112424 W185463721532533 SW191213113102112111011111 SE1854113728432828 FFWSEW

Identifiers

Each of the first four parts are a region and the picks for that region. The last part are the picks for the Final Four. The Final Four identifier (FF) and picks must come at the end of the string. The alpha characters that start each part are the identifier for that region. The region identifier must be a key in regions of the data file (except for the Final Four which must be FF).

Picks

The numbers for the first four regions correspond to the picks being made for that region. Each number refers to a seed that you think will win their game. The picks must be made in a top-down, left-right order when viewing the games on a bracket. This means for the first round, the games must be picked in the order: 1 v 16, 8 v 9, 5 v 12, 4 v 13, 6 v 11, 3 v 14, 7 v 10, 2 v 15. So if you wanted to pick all the higher seeded teams, your numbers would be 18546372. To complete a region, keep picking winning seeds in this order. To finish our previous example of all the higher seeded teams winning, your numbers would be 185463721432121. This might be easier to visualize if we look at the picks divided into rounds: 18546372 2143 12 1. Pair that with the identifier for the region and you have E185463721432121 which is a valid region.

Final Four

Once you have put your four regions together you can add your Final Four. For the Final Four 'region', you will pick the winners not be their seed but by the identifier of the region that they originally came out of. Make sure you know which regions are playing each other as well. In the example of the 2011 NCAA tournament, the Final Four was E v W and SE v SW. This will be noted by the value of the sameSideAs key in the data file. So appropriate Final Four picks would be WSEW since we are first picking the winners of the two Final Four matchups and the picking the winner of the final game. Pair that with the Final Four identifier to get FFWSEW.

Output

If your picks are valid, the output should display in an object contain all the regions, rounds and games.

If there are any errors, those will be displayed instead.

Why?

I wanted a codified format to fit all my picks for the NCAA Tournament into as little characters as possible. I believe that even if all region identifiers were two characters and you picked the higher seed in every game, you could fit your picks in 132 characters (enough to fit in a tweet). Obviously this could be shortened further by adding additional conventions (such as region order, etc.) but I believe this to be a good mix of brevity and flexibility.

This logic is the basis of TweetYourBracket.com (GitHub repo). The idea is that there is Twitter watcher which will watch for a specific hashtag and then the bracket will be parsed from the tweet and saved.

Tweet Your Bracket Entry

To enter a bracket into TweetYourBracket.com all you need to do is post a tweet that contains both the hashtag #tybrkt and your codified bracket as a hashtag or as the has portion of a URL.

For example both of the following would work:

  • #S18541137214112424W185463721532533E191213113102112111011111MW1854113728432828FFWMWW #tybrkt
  • #tybrkt http://tweetyourbracket.com/#S18541137214112424W185463721532533E191213113102112111011111MW1854113728432828FFWMWW

You can tweet as many times as you want but only your latest tweet will be entered.

Constraints

As I mentioned in other places above, the main constraints are:

  • Picks must be made in a top-down, left-right order for each region.
  • The Final Four must come at the very end of the string.
  • The Final Four picks must be preceded by their identifier.
  • The region picks must be preceded by a valid identifier.
node_modules/
*.log
{
"regions": {
"S": {
"name": "South",
"sameSideAs": "W",
"teams": [
"Kentucky",
"Duke",
"Baylor",
"Indiana",
"Wichita St",
"UNLV",
"N Dame",
"Iowa St",
"UConn",
"Xavier",
"Colorado",
"VCU",
"New Mex St",
"S Dakota St",
"Lehigh",
"MVST/WKY"
]
},
"W": {
"name": "West",
"sameSideAs": "S",
"teams": [
"Mich St",
"Missouri",
"Marquette",
"Louisville",
"New Mexico",
"Murray St",
"Florida",
"Memphis",
"Saint Louis",
"Virginia",
"Colo St",
"L Beach St",
"Davidson",
"BYU/Iona",
"Norfolk St",
"LIU"
]
},
"E": {
"name": "East",
"sameSideAs": "MW",
"teams": [
"Syracuse",
"Ohio St",
"Florida St",
"Wisconsin",
"Vanderbilt",
"Cincy",
"Gonzaga",
"Kansas St",
"So Miss",
"W Virginia",
"Texas",
"Harvard",
"Montana",
"St B'nvntre",
"Loyola MD",
"UNC-Ash"
]
},
"MW": {
"name": "Midwest",
"sameSideAs": "E",
"teams": [
"UNC",
"Kansas",
"G'town",
"Michigan",
"Temple",
"SDSU",
"St Mary's",
"Creighton",
"Alabama",
"Purdue",
"NC State",
"Cal/USF",
"Ohio",
"Belmont",
"Detroit",
"Lam/UVM"
]
}
},
"FF": {
"name": "Final Four"
}
}
_ = require('underscore');
NCAA = require('./2012.json');
helpers = require('./helpers.js');
validator = require('./pickValidator.js');
fs = require('fs');
var tournament = validator.validateTournament(process.argv[2]),
prettyTournament = JSON.stringify(tournament, null, 2);
fs.writeFile("output.json", prettyTournament, function(err) {
if (!err) {
console.log(prettyTournament);
}
});
var helpers;
helpers = {
array: {
subset: function(small, big) {
if (small.length === 0) return true;
return _.all(small, function(n) {
return _.include(big, n);
});
},
equal: function(arr1, arr2) {
return arr1.length === arr2.length && this.subset(arr1, arr2) && this.subset(arr2, arr1);
}
}
};
module.exports = helpers;
{
"regions": [
{
"id": "S",
"name": "South",
"rounds": [
{
"class": "round1",
"title": "Round of 64",
"games": [
1,
16,
8,
9,
5,
12,
4,
13,
6,
11,
3,
14,
7,
10,
2,
15
],
"teams": [
{
"seed": 1,
"name": "Kentucky",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 16,
"name": "MVST/WKY",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 8,
"name": "Iowa St",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 9,
"name": "UConn",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 5,
"name": "Wichita St",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 12,
"name": "VCU",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 4,
"name": "Indiana",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 13,
"name": "New Mex St",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 6,
"name": "UNLV",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 11,
"name": "Colorado",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 3,
"name": "Baylor",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 14,
"name": "S Dakota St",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 7,
"name": "N Dame",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 10,
"name": "Xavier",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 2,
"name": "Duke",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 15,
"name": "Lehigh",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "round2 winners",
"title": "Round of 32",
"games": [
1,
8,
5,
4,
11,
3,
7,
2
],
"teams": [
{
"seed": 1,
"name": "Kentucky",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 8,
"name": "Iowa St",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 5,
"name": "Wichita St",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 4,
"name": "Indiana",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 11,
"name": "Colorado",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 3,
"name": "Baylor",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 7,
"name": "N Dame",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 2,
"name": "Duke",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "sweet-sixteen winners",
"title": "Sweet Sixteen",
"games": [
1,
4,
11,
2
],
"teams": [
{
"seed": 1,
"name": "Kentucky",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 4,
"name": "Indiana",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 11,
"name": "Colorado",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 2,
"name": "Duke",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "elite-eight winners",
"title": "Elite Eight",
"games": [
4,
2
],
"teams": [
{
"seed": 4,
"name": "Indiana",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 2,
"name": "Duke",
"fromRegion": "S",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "region-final-four winners",
"title": "Final Four",
"games": [
4
],
"teams": [
{
"seed": 4,
"name": "Indiana",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": ""
}
]
}
],
"classes": "completed"
},
{
"id": "W",
"name": "West",
"rounds": [
{
"class": "round1",
"title": "Round of 64",
"games": [
1,
16,
8,
9,
5,
12,
4,
13,
6,
11,
3,
14,
7,
10,
2,
15
],
"teams": [
{
"seed": 1,
"name": "Mich St",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 16,
"name": "LIU",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 8,
"name": "Memphis",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 9,
"name": "Saint Louis",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 5,
"name": "New Mexico",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 12,
"name": "L Beach St",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 4,
"name": "Louisville",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 13,
"name": "Davidson",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 6,
"name": "Murray St",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 11,
"name": "Colo St",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 3,
"name": "Marquette",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 14,
"name": "BYU/Iona",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 7,
"name": "Florida",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 10,
"name": "Virginia",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 2,
"name": "Missouri",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 15,
"name": "Norfolk St",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "round2 winners",
"title": "Round of 32",
"games": [
1,
8,
5,
4,
6,
3,
7,
2
],
"teams": [
{
"seed": 1,
"name": "Mich St",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 8,
"name": "Memphis",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 5,
"name": "New Mexico",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 4,
"name": "Louisville",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 6,
"name": "Murray St",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 3,
"name": "Marquette",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 7,
"name": "Florida",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 2,
"name": "Missouri",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "sweet-sixteen winners",
"title": "Sweet Sixteen",
"games": [
1,
5,
3,
2
],
"teams": [
{
"seed": 1,
"name": "Mich St",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 5,
"name": "New Mexico",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 3,
"name": "Marquette",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 2,
"name": "Missouri",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "elite-eight winners",
"title": "Elite Eight",
"games": [
5,
3
],
"teams": [
{
"seed": 5,
"name": "New Mexico",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 3,
"name": "Marquette",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "region-final-four winners",
"title": "Final Four",
"games": [
3
],
"teams": [
{
"seed": 3,
"name": "Marquette",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": ""
}
]
}
],
"classes": "completed"
},
{
"id": "E",
"name": "East",
"rounds": [
{
"class": "round1",
"title": "Round of 64",
"games": [
1,
16,
8,
9,
5,
12,
4,
13,
6,
11,
3,
14,
7,
10,
2,
15
],
"teams": [
{
"seed": 1,
"name": "Syracuse",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 16,
"name": "UNC-Ash",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 8,
"name": "Kansas St",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 9,
"name": "So Miss",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 5,
"name": "Vanderbilt",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 12,
"name": "Harvard",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 4,
"name": "Wisconsin",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 13,
"name": "Montana",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 6,
"name": "Cincy",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 11,
"name": "Texas",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 3,
"name": "Florida St",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 14,
"name": "St B'nvntre",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 7,
"name": "Gonzaga",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 10,
"name": "W Virginia",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 2,
"name": "Ohio St",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 15,
"name": "Loyola MD",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "round2 winners",
"title": "Round of 32",
"games": [
1,
9,
12,
13,
11,
3,
10,
2
],
"teams": [
{
"seed": 1,
"name": "Syracuse",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 9,
"name": "So Miss",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 12,
"name": "Harvard",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 13,
"name": "Montana",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 11,
"name": "Texas",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 3,
"name": "Florida St",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 10,
"name": "W Virginia",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 2,
"name": "Ohio St",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "sweet-sixteen winners",
"title": "Sweet Sixteen",
"games": [
1,
12,
11,
10
],
"teams": [
{
"seed": 1,
"name": "Syracuse",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 12,
"name": "Harvard",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 11,
"name": "Texas",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 10,
"name": "W Virginia",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "elite-eight winners",
"title": "Elite Eight",
"games": [
1,
11
],
"teams": [
{
"seed": 1,
"name": "Syracuse",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 11,
"name": "Texas",
"fromRegion": "E",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "region-final-four winners",
"title": "Final Four",
"games": [
11
],
"teams": [
{
"seed": 11,
"name": "Texas",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": ""
}
]
}
],
"classes": "completed"
},
{
"id": "MW",
"name": "Midwest",
"rounds": [
{
"class": "round1",
"title": "Round of 64",
"games": [
1,
16,
8,
9,
5,
12,
4,
13,
6,
11,
3,
14,
7,
10,
2,
15
],
"teams": [
{
"seed": 1,
"name": "UNC",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 16,
"name": "Lam/UVM",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 8,
"name": "Creighton",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 9,
"name": "Alabama",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 5,
"name": "Temple",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 12,
"name": "Cal/USF",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 4,
"name": "Michigan",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 13,
"name": "Ohio",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 6,
"name": "SDSU",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 11,
"name": "NC State",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 3,
"name": "G'town",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 14,
"name": "Belmont",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 7,
"name": "St Mary's",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 10,
"name": "Purdue",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 2,
"name": "Kansas",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 15,
"name": "Detroit",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "round2 winners",
"title": "Round of 32",
"games": [
1,
8,
5,
4,
11,
3,
7,
2
],
"teams": [
{
"seed": 1,
"name": "UNC",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 8,
"name": "Creighton",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 5,
"name": "Temple",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 4,
"name": "Michigan",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 11,
"name": "NC State",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 3,
"name": "G'town",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 7,
"name": "St Mary's",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 2,
"name": "Kansas",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "sweet-sixteen winners",
"title": "Sweet Sixteen",
"games": [
8,
4,
3,
2
],
"teams": [
{
"seed": 8,
"name": "Creighton",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 4,
"name": "Michigan",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 3,
"name": "G'town",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 2,
"name": "Kansas",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "elite-eight winners",
"title": "Elite Eight",
"games": [
8,
2
],
"teams": [
{
"seed": 8,
"name": "Creighton",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 2,
"name": "Kansas",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "region-final-four winners",
"title": "Final Four",
"games": [
8
],
"teams": [
{
"seed": 8,
"name": "Creighton",
"fromRegion": "MW",
"startMatchup": true,
"endMatchup": false,
"classes": ""
}
]
}
],
"classes": "completed"
},
{
"id": "FF",
"name": "Final Four",
"rounds": [
{
"class": "final-four",
"title": "Final Four",
"games": [
"S",
"W",
"E",
"MW"
],
"teams": [
{
"seed": 4,
"name": "Indiana",
"fromRegion": "S",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 3,
"name": "Marquette",
"fromRegion": "W",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
},
{
"seed": 11,
"name": "Texas",
"fromRegion": "E",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 8,
"name": "Creighton",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "national-championship winners",
"title": "National Championship",
"games": [
"W",
"MW"
],
"teams": [
{
"seed": 3,
"name": "Marquette",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": "top"
},
{
"seed": 8,
"name": "Creighton",
"fromRegion": "MW",
"startMatchup": false,
"endMatchup": true,
"classes": "bottom"
}
]
},
{
"class": "national-champion winners",
"title": "National Champion",
"games": [
"W"
],
"teams": [
{
"seed": 3,
"name": "Marquette",
"fromRegion": "W",
"startMatchup": true,
"endMatchup": false,
"classes": ""
}
]
}
],
"classes": "completed"
}
]
}
{
"name": "Bracket-Validator",
"description": "Bracket Validator",
"version": "0.1.0",
"private": false,
"dependencies": {
"underscore": "1.3.1"
}
}
module.exports = {
errorMessages: [],
logError: function() {
this.errorMessages.push(_.toArray(arguments).join(" "));
},
unpickedGame: 'X',
finalFourRegionName: 'FF',
regions: _.keys(NCAA.regions),
allRegions: _.keys(NCAA.regions).concat('FF'),
pickOrder: [0, 7, 5, 3, 2, 4, 6, 1],
seedOrder: [1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15],
regionClasses: ['round1', 'round2', 'sweet-sixteen', 'elite-eight', 'region-final-four'],
regionNames: ['Round of 64', 'Round of 32', 'Sweet Sixteen', 'Elite Eight', 'Final Four'],
finalFourClasses: ['final-four', 'national-championship', 'national-champion'],
finalFourNames: ['Final Four', 'National Championship', 'National Champion'],
// Takes a string of the picks for a region and validates them
// Return an array of picks if valid or false if invalid
picksToArray: function(picks, finalFour, regionName) {
var regexp,
replacement = '',
regExpStr = '',
seeds = (finalFour) ? this.regions : this.seedOrder,
seedLength = seeds.length,
regExpJoiner = function(arr) {
return '(' + arr.join('|') + '|' + this.unpickedGame + ')';
},
backref = function(i) {
return regExpJoiner.call(this, _.map(_.range(i, i+2), function(n) { return "\\"+n; }));
};
// Create capture groups for the first round of the region
for (var i = 0; i < seedLength; i += 2) {
regExpStr += regExpJoiner.call(this, seeds.slice(i, i+2));
}
// Create capture groups using backreferences for the capture groups above
for (i = 1; i < seedLength - 2; i += 2) {
regExpStr += backref.call(this, i);
}
regexp = new RegExp(regExpStr);
replacement = _.map(_.range(1, seedLength), function(num) { return '$'+num; }).join();
if (regexp.test(picks)) {
return picks.replace(regexp, replacement).split(',');
} else {
this.logError(regionName, 'was unable to parse the picks');
return false;
}
},
// Takes an array of values and removes all invalids
// return an array or arrays where each subarray is one round
getRounds: function(rounds, regionName) {
var length = rounds.length + 1,
games = (regionName === this.finalFourRegionName) ? this.regions : this.seedOrder,
retRounds = [_.extend(this.getRoundInfo(0, regionName), { games: games })],
verify = function(arr, keep) {
// Compacts the array and remove all duplicates that are not "X"
return _.compact(_.uniq(arr, false, function(n){ return (_.indexOf(keep, n) > -1) ? n+Math.random() : n; }));
},
checkVal = function(val) {
var num = parseInt(val, 10);
if (num >= 1 && num <= 16) {
return num;
} else if (val === this.unpickedGame) {
return val;
} else if (_.include(this.regions, val)) {
return val;
} else {
return 0;
}
},
count = retRounds.length;
while (length > 1) {
length = length / 2;
var roundGames = verify(_.map(rounds.splice(0, Math.floor(length)), checkVal, this), [this.unpickedGame]);
retRounds.push(_.extend(this.getRoundInfo(count, regionName), { games: roundGames }));
count++;
}
return retRounds;
},
// Get classes and titles for each round of each region
getRoundInfo: function(index, region) {
var roundClasses = (region === this.finalFourRegionName) ? this.finalFourClasses : this.regionClasses,
roundNames = (region === this.finalFourRegionName) ? this.finalFourNames : this.regionNames;
return {
"class": roundClasses[index] + ((index > 0) ? ' winners' : ''),
"title": roundNames[index]
};
},
// Takes an array of picks and a regionName
// Validates picks to make sure that all the individual picks are valid
// including each round having the correct number of games
// and each pick being a team that has not been eliminated yet
validatePicks: function(picks, regionName) {
var rounds = this.getRounds(picks, regionName),
length = rounds.length,
trueRounds = [],
regionPicks = {};
_.each(rounds, function(round, i) {
var requiredLength = (Math.pow(2, length - 1) / Math.pow(2, i)),
nextRound = rounds[i + 1],
correctLength = (round.games.length === requiredLength),
lastItem = (i === length - 1),
thisRoundPickedGames = _.without(round.games, this.unpickedGame),
nextRoundPickedGames = (nextRound) ? _.without(nextRound.games, this.unpickedGame) : [],
nextRoundIsSubset = (!lastItem && helpers.array.subset(nextRoundPickedGames, thisRoundPickedGames)),
ncaaRegion = NCAA.regions[regionName] || NCAA[regionName];
if (correctLength && (lastItem || nextRoundIsSubset)) {
regionPicks.id = regionName;
regionPicks.name = ncaaRegion.name;
regionPicks.rounds = rounds;
trueRounds.push(i);
if (_.indexOf(_.flatten(_.pluck(rounds, 'games')), this.unpickedGame) === -1) regionPicks.classes = "completed";
} else if (!correctLength) {
this.logError(regionName, 'has the incorrect number of picks in', this.getRoundInfo(i, regionName).title);
} else if (!nextRoundIsSubset) {
this.logError(regionName, 'round', this.getRoundInfo(i+1, regionName).title, 'is not a subset of the previous round');
}
}, this);
return (length === trueRounds.length) ? regionPicks : false;
},
// Takes two arrays of region names (one from the user, one from valid keys)
// And checks if they are the same
validateRegionNames: function(userRegions, bracketRegions) {
if (helpers.array.equal(userRegions, bracketRegions)) {
return userRegions;
} else {
this.logError('The region names are not correct. Must be', bracketRegions, '. Yours are', userRegions);
return false;
}
},
// Make sure the special rules for the final four work
validateFinalFour: function(finalFourPicks) {
if (finalFourPicks === false) return finalFourPicks;
var finalFourWinners = finalFourPicks.rounds[1].games;
if (finalFourWinners[0] === this.unpickedGame || finalFourWinners[1] === this.unpickedGame) {
return finalFourPicks;
}
var playingItself = (finalFourWinners[0] === finalFourWinners[1]),
playingWrongSide = (NCAA.regions[finalFourWinners[0]].sameSideAs === finalFourWinners[1]);
if (!helpers.array.subset(finalFourWinners, this.regions)) {
this.logError('The championship games participants are invalid.');
return false;
} else if ((playingItself || playingWrongSide)) {
this.logError('The championship game participants are from the same side of the bracket.');
return false;
} else {
return finalFourPicks;
}
},
// Take validated tournament and add necessary content so it is ready for Handlebars
addTeamContent: function(validatedPicks, editable) {
var ncaaRegions = NCAA.regions;
_.each(validatedPicks.regions, function(region, regionIndex) {
var regionTeams = (typeof NCAA.regions[region.id] !== 'undefined') ? NCAA.regions[region.id].teams : [];
_.each(region.rounds, function(round, roundIndex) {
_.each(round.games, function(game, gameIndex) {
if (typeof round.teams === 'undefined') round.teams = [];
var team = {
seed: '',
name: ''
},
isTop = (gameIndex % 2 === 0),
lastRound = (roundIndex === region.rounds.length-1),
classes = ['top', 'bottom'];
if (region.id !== this.finalFourRegionName) {
team.seed = parseInt(game, 10);
team.name = regionTeams[team.seed - 1];
team.fromRegion = region.id;
} else {
// These are selected winners in the final four
var fromRegion = _.find(validatedPicks.regions, function(reg) { return reg.id === game; });
if (fromRegion) {
var finalFourTeam = _.first(_.last(fromRegion.rounds).teams);
team.seed = finalFourTeam.seed;
team.name = finalFourTeam.name;
team.fromRegion = game;
}
}
round.teams[gameIndex] = _.extend(team, {
editable: editable,
startMatchup: isTop,
endMatchup: !isTop,
classes: (lastRound) ? '' : classes[~~!isTop]
});
}, this);
}, this);
}, this);
return validatedPicks;
},
// Make sure the tournament is all validated
// by running all the other checks
validateTournament: function(picks, editable) {
var uPicks = picks.toUpperCase(),
uPicksSplit = uPicks.split(this.finalFourRegionName),
regionPicks = uPicksSplit[0],
finalFourPicks = uPicksSplit[1],
uRegionNames = _.compact(regionPicks.split(new RegExp('[0-9'+this.unpickedGame+']+'))),
uRegionPicks = _.compact(regionPicks.split(/[A-WYZ]+/)),
validatedTournament = {
regions: []
},
error = false;
this.errorMessages = [];
if (finalFourPicks && finalFourPicks.length > 0) {
uRegionNames = uRegionNames.concat(this.finalFourRegionName);
}
_.each(uRegionNames, function(regionName, i) {
var regionPicks = uRegionPicks[i] || finalFourPicks,
validatedPicks,
isFinalFour = (regionName === this.finalFourRegionName),
picksArray = this.picksToArray(regionPicks, isFinalFour, regionName);
if (picksArray === false) {
error = true;
} else if (isFinalFour) {
validatedPicks = this.validateFinalFour(this.validatePicks(picksArray, regionName));
} else {
validatedPicks = this.validatePicks(picksArray, regionName);
}
if (validatedPicks !== false) {
validatedTournament.regions.push(validatedPicks);
} else {
error = true;
}
}, this);
if (this.validateRegionNames(uRegionNames, this.allRegions) === false) {
error = true;
}
if (!error) {
return this.addTeamContent(validatedTournament, editable);
} else {
return this.errors;
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment