npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ffa

v3.2.2

Published

Free-for-all elimination tournaments

Downloads

76

Readme

FFA elimination tournaments

npm status build status dependency status coverage status

Overview

FFA is a very general tournament building block that you can take in many directions (only a few of which are commonly explored). The result always consists of FFA matches; matches that expect multiple players/teams competing at once in a free for all, and these matches are bracketed like an elimination tournament. Beyond that, however, you have free reigns. Match sizes can vary from round to round, you can define an end round that has multiple matches, and you can have every remaining player in one match for some or all rounds. Endless possibilities.

Construction

You must specify precisely the required group size for each round and how many to advance. This is really the hardest part of an FFA elimination. There are essentially endless possibilities, and we will allow very exotic and adventurous ones as long as they are playable, non-trivial, and leave the next round sensible.

// 8 players in 1 match of 8
var ffa = new FFA(8);
ffa.matches;
[ { id: { s: 1, r: 1, m: 1 }, p: [ 1, 2, 3, 4, 5, 6, 7, 8 ] } ]


// 16 players in matches of 4 each round, top 2 advances between each
var ffa = new FFA(16, { sizes: [4, 4, 4], advancers: [2, 2] });
ffa.matches;
[ { id: { s: 1, r: 1, m: 1 }, p: [ 1, 5, 12, 16 ] },
  { id: { s: 1, r: 1, m: 2 }, p: [ 2, 6, 11, 15 ] },
  { id: { s: 1, r: 1, m: 3 }, p: [ 3, 7, 10, 14 ] },
  { id: { s: 1, r: 1, m: 4 }, p: [ 4, 8, 9, 13 ] },
  { id: { s: 1, r: 2, m: 1 }, p: [ 0, 0, 0, 0 ] }, // semi 1
  { id: { s: 1, r: 2, m: 2 }, p: [ 0, 0, 0, 0 ] }, // semi 2
  { id: { s: 1, r: 3, m: 1 }, p: [ 0, 0, 0, 0 ] } ] // final


// 15 players in groups of 5, limited so that the top 2 from each match can be picked
var ffa = new FFA(15, { sizes: [5], limit: 6 });
ffa.matches;
[ { id: { s: 1, r: 1, m: 1 }, p: [ 1, 4, 7, 12, 15 ] },
  { id: { s: 1, r: 1, m: 2 }, p: [ 2, 5, 8, 11, 14 ] },
  { id: { s: 1, r: 1, m: 3 }, p: [ 3, 6, 9, 10, 13 ] } ]


// 32 player groupstage replacement - 4 matches of size 8
var ffa = new FFA(32, { sizes: [8] }); // may have to tiebreak without limits
[ { id: { s: 1, r: 1, m: 1 }, p: [ 1, 5, 9,  13, 20, 24, 28, 32 ] },
  { id: { s: 1, r: 1, m: 2 }, p: [ 2, 6, 10, 14, 19, 23, 27, 31 ] },
  { id: { s: 1, r: 1, m: 3 }, p: [ 3, 7, 11, 15, 18, 22, 26, 30 ] },
  { id: { s: 1, r: 1, m: 4 }, p: [ 4, 8, 12, 16, 17, 21, 25, 29 ] } ]


// knockout style tournament - one match per round - knock out 2 each round
var ffa = new FFA(8, { sizes: [8, 6, 4], advancers: [6, 4] })
[ { id: { s: 1, r: 1, m: 1 }, p: [ 1, 2, 3, 4, 5, 6, 7, 8 ] },
  { id: { s: 1, r: 2, m: 1 }, p: [ 0, 0, 0, 0, 0, 0 ] },
  { id: { s: 1, r: 3, m: 1 }, p: [ 0, 0, 0, 0 ] } ]

Note that the last example is so common it has been created as a subclass in masters. Subclassing is easy, and if you find yourself reusing certain patters, you should create one after reading the tournament implementors guide.

Limits

To pipe the top n players into another tournament set limit to n in the options argument.

This will cause an additional scoring ambiguity limitat on the final round for the scores be disambiguate the top limit players with the top (limit+1)th player. Read about the ambiguity restriction.

The 15 player example above will allow two players at tied 1st, but not allow two players in 2nd or three players in 1st.

Match Ids

Like all tournament types, matches have an id object that contains three values all in {1, 2, ...}:

{
  s: Number, // the bracket - always 1 at the moment - only winners bracket supported
  r: Number, // the round number in the current bracket
  m: Number  // the match number in the current bracket and round
}

Finding matches

All the normal base class methods exist on a FFA instance. Some notable examples follow:

var r1 = ffa.findMatches({ r: 1 });
var firstRounds = ffa.findMatchesRanged({}, { r: 2 });
var upcomingForSeed1 = ffa.upcoming(1);
var matchesForSeed1 = ffa.matchesFor(1);

Scoring Matches

Call ffa.score(id, [player0Score, player1Score, ...]) as for every match played. The ffa.unscorable(id, scoreArray) will tell you whether the score is valid. Read the entry in the tournament commonalities doc. In addition to the normal scoring restrictions there is an additional one for FFA:

Ambiguity restriction

Individual ties are only allowed as long as we can discriminate between the last advancer and the first non-advancers. If these two scores are identical, the ambiguity is disallowed and .score() will return false (equivalently unscorable() will tell you this).

Note that this is artificial, and if you have no way of enforcing this, you will have to manually adjust the match score to account for ties. FFA will not allow you to enter ambiguous scores at the limit point.

Note that if you would like automatic tiebreakers created when ties occur, rather than enforcing the limit with code, try the ffa-tb module instead.

Checking results

Results are updated after every match, and player positions are always the worst projection based on how far they have gotten.

var ffa = new FFA(8, { sizes: [4, 4], advancers: [2] });
ffa.matches.forEach(function (m) {
  ffa.score(m.id, [4,3,2,1]); // score in order of seeds
});
ffa.results();
[ { seed: 1, wins: 2, for: 8, against: 0, pos: 1, gpos: 1 },
  { seed: 2, wins: 1, for: 7, against: 1, pos: 2, gpos: 2 },
  { seed: 3, wins: 1, for: 5, against: 3, pos: 3, gpos: 3 },
  { seed: 4, wins: 1, for: 4, against: 4, pos: 4, gpos: 4 },
  { seed: 5, wins: 0, for: 2, against: 2, pos: 5 },
  { seed: 6, wins: 0, for: 2, against: 2, pos: 5 },
  { seed: 7, wins: 0, for: 1, against: 3, pos: 7 },
  { seed: 8, wins: 0, for: 1, against: 3, pos: 7 } ]

A few peculiarities you may want to know about scores:

  • The 3rd placers and 4th placers in the round one matches are tied between matches (would have happened even if they had different for scores)
  • Because we sat no limit on the last rounds match, there was only one winner of the second match (whereas normally winners are said to be the ones advancing)
  • The against is the sum of the (difference of your score the top score) in each match
  • The gpos is the position in the last match (which is necessary for tiebreaking if there is more than one final round match)

Upcoming

Unlike inst.upcoming(seedNumber), FFA may need extra work:

Because each round is generated by reseeding players based on performance, if the current round has not been fully completed yet, then ffa.upcoming(seed) returns an empty array.

It is, however, possible to receive a partial id, like {r: 4} (missing a game number) by calling inst.limbo(seedNumber) to get an indication of whether or not the player has a match in the next round.

More

For more information, note that FFA is a very standard tournament subclass. Read base class API for usage tips and tricks.

License

MIT-Licensed. See LICENSE file for details.