Package detail

duel

clux2.4kMIT3.2.0

Duel elimination tournament

duel, elimination, tournament

readme

Duel elimination tournaments

npm status build status dependency status coverage status

Overview

Duel elimination tournaments consist of two players / teams per match. after each match the winner is advanced to the right in the bracket, and if loser bracket is in use, the loser is put in the loser bracket.

Duel tournaments can be of any size although perfect powers of 2 are the nicest because they are perfectly balanced. That said, the module will fill out the gaps with walkover markers (-1) and will run an unbalanced tournament in the fairest possible way. Unbalanced tournaments typically benefits the highest seeds as these are the players receiving walk-overs in the first round.

A nice property of this duel tournament implementation is that if the seeding is perfect (i.e. if player a is seeded higher than player b, then player a wins over player b) then the top X in the results are also the top X seeded players. As an example, seed 1 can only meet seed 2 in the final in single elimination.

In this doc

For readibility and convenience:

  • WB := Duel.WB := 1
  • LB := Duel.LB := 2

Construction

Specify the number of players an optional options object.

// 5 players - single elimination (8 player model)
var duel3 = new Duel(5);

// 4 players - double elimination
var duel1 = new Duel(4, { last: LB });

The Duel.invalid(numPlayers, opts) will tell you whether the constructor arguments produce a valid tournament. Read its entry in the tournament commonalities doc for info on this.

The only option supported at the moment is a boolean short:

Short Variants

The default implementation of an elimination tournament includes the usual (but sometimes controversial) extra match in each case:

  • bronze final in single elimination
  • double grand final in double elimination

Passing a short:true flag in the options object to the Duel constructor will override the default behaviour and use the short variants.

// no bronze final in this
var duelSingle = new Duel(16, { short: true });

// winner of LB can win the grand final in one match
var duelDouble = new Duel(16, { last: LB, short: true });

NB: Short double elimination tournaments are strongly discouraged because they breaks several fairness properties. As a worst case example, if player 1 and 4 met early in the tournament and 1 won, 4 could come back from the losers bracket and win the grand final in one game despite the two players being 1-1 in games overall in the tournament.

Match Ids

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

{
  s: Number, // the bracket - either WB or LB
  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 helper methods exist on a Duel instance. Some notable examples follow:

var wb = duel.findMatches({ s: WB });
var lb = duel.findMatches({ s: LB });
var wbr3 = duel.findMatches({ s: WB, r: 3 });
var upcomingForSeed1 = duel.upcoming(1);
var matchesForSeed1 = duel.matchesFor(1);

Scoring Matches

Call duel.score(id, [player0Score, player1Score]) as for every match played. The duel.unscorable(id, scoreArray) will tell you whether the score is valid. Read the entry in the tournament commonalities doc.

NB: Restrictions

Duel tournaments does not allow ties at any stage. It's meant to eliminate, so you have to do your own best of 3 / overtime methods etc to determine winners in case of draws.

Special Methods

Progression Trackers

A players progress is in a tournament is entirely determined by a sequence of booleans for wins/losses. If you have a match id and want to know where a player will gets sent, either to the right in the bracket or downwards to the losers bracket, pass the id to the following helpers methods:

duel.right(id) :: [rightId, index]

duel.down(id) :: [downId, index]

The index returned is the index in the player array the winner (if right) or loser (if down) will end up in. This is mostly beneficial internally. If you think about using this, consider looking at duel.upcoming(playerId).

NB: These raw helpers do not consider the special case of the sometimes unplayed grand final game two in double elimination (but upcoming does).

Caveats

End progression

Towards the end of a duel tournament, players may move in seemingly strange ways. These are:

  • In single elimination, winner of the each semi final goes to { s: WB, r: duel.p, m: 1 }, whereas the loser goes to { s: LB, r: 1, m: 1 }.

The duel.p is the power of the tournament (defined as smallest integer p such that 2^(p-1) is the number of matches in WBR1). The WB final always occurs in this round.

The loser gets sent to the bronze final which is situated in "LBR1", perhaps a little oddly so, but it the bronze final does contain two losers, and it's the first round where losers get to play.

  • In double elimination: the winner of the winner bracket final go to the grand final in { s: LB, r: 2*duel.p - 1, m: 1 } to meet the winner of the losers bracket.

This is a special case match of double eliminations. The match is neither in the winners bracket nor the losers bracket because it got players from both, but by our convention it is located in the losers bracket. It also makes the following convention more sensible:

  • In double elimination: if the grand final in { s: LB, r: 2*duel.p - 1, m: 1 } is won by the player coming from the losers bracket, then a second grand final is required, and is located in { s: LB, r: 2*duel.p, m: 1 }.

This makes more sense, because we can sensibly say that both players are in the losers bracket (both having lost one match). The winner of this second grand final wins the tournament. Note that if the winner of the winner bracket wins the first grand final, the second grand final (the last match in duel.matches) never gets its players filled in, however duel.isDone() will return true. Double elimination tournaments are the only tournaments where isDone() can return true while a match is not played.

Bracket movement

In double elimination, the losers bracket moves upwards to meet the winner bracket close to the final. This is a design decision that unfortunately has to be made, because it affects the match ids score will drop the next player at. An issue is open for this, but it is currently not prioritized.

License

MIT-Licensed. See LICENSE file for details.

changelog

3.2.0 / 2015-12-06

  • Bump tournament@3.1.0 for configurable logs
  • Expose Duel.Id class for consistency

3.1.1 / 2015-10-23

  • Disabled downMix option in single elimination

3.1.0 / 2015-10-23

  • Implement optional downMix progression mode for double elimination tournaments #11 and #10

3.0.1 / 2015-01-03

  • Fix bug in _safe implementation which made unscorable too strict when not having full access (#6 via @a5sk4s)

3.0.0 / 2014-10-11

  • Bump tournament to 3.0.0 for better serialization via ::state and .restore

2.1.0 / 2014-10-02

  • Implement _safe for better unscorable check for safe history rewrites
  • Bump tournament to 2.2.0 for _safe

2.0.2 / 2014-10-01

  • Fixed an issue where the double elimination grand final game two could be left in a bad state when rescoring gfg1. This caused upcoming to return an unplayable match.

2.0.1 / 2014-09-30

  • Bump tournament to 2.0.1
  • Bump interlude to 1.1.0

2.0.0 / 2014-09-14

  • BREAKING Bump tournament to 2.0.0 for more sensible Tournament::upcoming

1.0.0 / 2014-09-01

  • Duel::rep removed (was a bad undocumented feature)
  • Duel.idString removed (match ids have .toString())
  • Duel.defaults and Duel() no longer modifies options arguments
  • Major bump for satisfaction
  • Bump tournament to 1.0.0

0.5.2 / 2014-08-02

  • Documentation and coverage release

0.5.1 / 2014-08-02

  • roundName remove and is now a mixin via the duel-names module
  • duel-names can be mixed in via Duel.attachNames

0.5.0 / 2013-12-24

  • Updated tournament to 0.21.0 so that Duel is an EventEmitter

0.4.4 / 2013-11-13

  • Interface with tournament@0.20.2 for default results[i].against

0.4.3 / 2013-11-06

  • Interface with tournament@0.20.0 for cleaner results implementation

0.4.1 / 2013-11-02

  • Interface with tournament@0.19.0 for multi stage support

0.4.0 / 2013-10-31

  • last now passed in as an optional argument (single elimination default)
  • Use tournament@0.18.0 interface

0.3.0 / 2013-10-25

  • Use tournament@0.17.0 interface
  • Rename maps to for and add against as well to count both map wins and map losses
  • Huge code readability improvements

0.2.0 / 2013-10-22

  • Use tournament@0.16.0 interface

0.1.1 / 2013-10-16

  • refactor score to use Base implementation

0.1.0 / 2013-10-15

  • first release - factored out of tournament 0.14.0