Git Product home page Git Product logo

tournament's Introduction

Tournament

npm status build status dependency status coverage status

Tournament provides a base class for stateful tournament types of fixed size tournaments.

All tournaments have a huge amount of common logic so the helper methods included on this base class is worth reading about even if you don't use this module directly.

You should read at least one of:

Implementions:

For multi stage tournaments, or if you need to grab functionality from multiple implementations, there is also a module for that:

Example implementation usage

Create a new tournament instance from one of the separate implementations, then interact with helper functions to score and calculate results.

var Duel = require('duel');
var d = new Duel(4); // 4 players - single elimination

d.matches; // in playable order
[ { id: { s: 1, r: 1, m: 1 }, // semi 1
    p: [ 1, 4 ] },
  { id: { s: 1, r: 1, m: 2 }, // semi 2
    p: [ 3, 2 ] },
  { id: { s: 1, r: 2, m: 1 }, // grand final
    p: [ 0, 0 ] },
  { id: { s: 2, r: 1, m: 1 }, // bronze final
    p: [ 0, 0 ] } ]

// let's pretend we scored these individually in a more realistic manner
d.matches.forEach(function (m) {
  d.score(m.id, [1, 0]);
});

// now winners are propagated and map scores are recorded
d.matches;
[ { id: { s: 1, r: 1, m: 1 },
    p: [ 1, 4 ],
    m: [ 1, 0 ] },
  { id: { s: 1, r: 1, m: 2 },
    p: [ 3, 2 ],
    m: [ 1, 0 ] },
  { id: { s: 1, r: 2, m: 1 }, // 1 and 3 won their matches and play the final
    p: [ 1, 3 ],
    m: [ 1, 0 ] },
  { id: { s: 2, r: 1, m: 1 }, // 4 and 2 lost and play the bronze final
    p: [ 4, 2 ],
    m: [ 1, 0 ] } ]

// can view results at every stage of the tournament, here are the final ones
d.results();
[ { seed: 1, wins: 2, for: 2, against: 0, pos: 1 },
  { seed: 3, wins: 1, for: 1, against: 1, pos: 2 },
  { seed: 4, wins: 1, for: 1, against: 1, pos: 3 },
  { seed: 2, wins: 0, for: 0, against: 2, pos: 4 } ]

Installation

For specific tournament usage, install the modules you want:

$ npm install duel ffa groupstage tiebreaker --save

To use these on in the browser, bundle it up with browserify

$ npm dedupe
$ browserify -r duel -r ffa -r groupstage -r tiebreaker > bundle.js

License

MIT-Licensed. See LICENSE file for details.

tournament's People

Contributors

clux avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

tournament's Issues

Undo damage when changing history OR lockdown history

At the moment when scoring matches were winners have already been propagated in the tournament may lead to inconsistencies.

The simplest example is a 4 player single elimination Duel tournament where the semis have been scored. If one rewrites history by scoring the first round (quarter finals) this will cause different players in the already scored finals.

At the moment, the semi finals does not suddenly lose its scores because of this and so the already propagated winner (that gets put in the final) may not suddenly have been eliminated in round 1 so the tournament is inconsistent.

As I see it, there are two options. Either:

  1. If scoring changed the winner, delete all future scores along this branch (tracing this shouldn't be too hard - Duel is the only difficult one, and there we have the internal right and down functions to trace with) so they have to be scored again.
  2. Disallow changing the winner of a such a match (where the new winner propagation is not enough to fix everything), which could be checked reasonably easily in all tournaments.

Since rewriting history is a special request anyway, it's not so much of a priority. But I'd be interested to hear some opinions on this before I do anything.

I'm leaning towards undo damage at the moment because it means the tournament is always in a consistent state, but it might be hard to do if the scores are changed very far back, in which case it's a very silly and likely erroneous operation anyway.

cyclical devDependencies

tournament now has ffa groupstage duel masters as devDependencies, and these in turn obviously depend on tournament.

This means symlink can't link things back up without me manually closing the loop.
Is there something we can do with this? The dependencies on this side are only there for tests, is there a way this can be done without having this loop?

logs should be configurable

While we only log stuff when there are user errors, each tournament can easily just expose a this.log instance that can default to console.log, and people should be able to hook up a smell instance or something similar instead. Makes it easier to aggregate logs from multi-stage.

Will also make test output nicer since we can actually suppress/verify when we are testing error cases.

Cant render tournaments correctly

Hey!

I was wondering if you had or used any specific module to render the tournaments, I've been trying the implementation myself, but I can't fully render a bracket form tournament... having issues with spacing and line drawing between rounds...

Wondering if you used a prefered module for rendering or maybe if you had tips for rendering the tournaments

Thanks!

Multi-Stage Tournaments

I need a good way to plumb different tournament types together.
I want to be able to create a, say, 32 player FFA elimination and forward the top 8 to a single elimination tournament (and auto create tiebreaker mini-groups when needed). Also, I may not necessarily want to finish the FFA elimination (but just stop when a top 8 has been determined if this is a good number to stop on).

trn.results() provides excellent statistics that should be sufficient figure out these things, but I can imagine it would get quite hairy if it's not been modeled properly.

The plumbing should work in a generic way between all the different tournament types that's serializable and deserializable into a wrapper type (or whatever works nicely).

Documentation due for an overhaul

Current docs are not very well split between repos, and are basically only high level implementation details, but very little actual usage.

Should be able to provide some graphical flows of tournaments and how they map to the data structures now.

  • decide on nice markdown compiler -> rolling our own based on the node api one
  • write some basic api docs
    • duel
    • groupstage
    • ffa
    • masters
    • tiebreaker
  • parse and generate to gh-pages
  • overhaul original docs
  • create master repo for all tournament docs
  • make some nice graphical flows the front page for master repo

More info on results

Now, results method return an array of object like the following:

{
"seed" : 1,
"maps" : 4,
"pts" : 12,
"pos" : 1,
"wins" : 4,
"draws" : 0,
"losses" : 0,
"grp" : 1,
"gpos" : 1
}

If would be great if that object could return also goalsfor and goals againts.

I mean, all results have a score, 1-0, 2-1 so you it would be possible to return that numbers in the result method.

Regards and thanks for this great module ;)

Localization and standardization of error messages

At the moment all the ::unscorable and .invalid functions return hardcoded strings for the different error cases. I don't throw for these because it's likely just hard to configure the parameters right so that everything is consistent and therefore just try to provide helpful messages for the app to pass up to the user (if tournament is presented via a web interface for instance).

Most of these strings require no interpolation (and can probably part with this entirely), so for localization purposes it should be possible to just stash them in a config somewhere.

The two things I'm unsure about: should messages be interpolated with expected parameters (such as match.p.length to indicate how long the scores array really needs to be) via util.format and then just stored somewhere as "Need to have at least %d scores" and expect it to be interpolated, or should the error message be so generic that it can be a constant so the app can test against specific reasons like an ENUM?

Secondly, are the error messages actually good now? Can they be standardized more across the tournament types so that they can be reused etc? Some more pairs of eyes on this bit would be helpful :)

Serialization sufficient now?

Tried methods:

  • serializing the entire instance
  • serializing the matches array only
  • serializing operations only via preservative

All methods suffer from problems of API change, some more than others.

The first one basically mean any change to a class fucks up your saved version. The second and third one both suffer from progression bug fixes changes outcomes, but the second one is a cleaner way at least.

Perhaps the only way to guarantee the saved state meaning something is to also save the version we serialized from.. At any rate, I am still not completely happy with this. Feedback is appreciated.

What to do about no-shows?

This is a question that has been brought up to me a few times, and the answer from me was "I dunno, give them a loss?"

I figured it was worth asking, since not all tournaments are made perfect - what do you do if you have a no-show? Score a loss for them (ie, 1/0)? Remove them (although this has other implications, esp. if later in the tournament)?

Is it even worth scoping such a thing into the tournament base?

Serialization still brittle

There are too many moving parts inside a class to sensibly serialize anything and expect to always be able to upgrade versions without sometimes screwing up already serialized versions.

Ideally, what we need is a simple way to just remember all the successful:

  • constructions
  • score calls

By pushing them onto something. That way you also have complete revision history, and everything is immutable. This is a must in the long run.

Refactor GroupStage and TieBreaker results

Both results method use a lot of similar stuff, but TieBreaker is based on the temporary posAry thing we create in there.

tieCompute, and probably a lot more can be shared between them in group_algs or somewhere.

GroupStage winner determination

First off, the pos attribute from results() in GroupStage tournaments are broken at the moment.
This post is a response to the realizations that I had whilst working on that.

Realization 1: Current position cannot be accurately computed before isDone

In the middle of a tournament it's theoretically impossible to provide a current position because each group can be super tight in different ways. One win does nothing to each estimate in large groups because the player can still come last. At current master, this is not done at all. Don't know what I was thinking when I wrote GroupStage::results, but it wasn't properly thought about, that much is clear.

At any rate, we thus have to reduce the effectiveness of this feature for GroupStage, by just setting pos to numPlayers on each result element until ALL matches are played. Regardless, the results list will contain the current number of points, and will be sorted between groups in order of points so at least the information is there should someone try to do something with it.

Realization 2: Ties can happen in 2 difficult ways

  • Between groups
  • Within groups

Between groups is the simple case I was expecting and modelling simplified limit behaviour for in #6. I.e. say if you have 3 groups of 3 (9 player group stage) and want to determine/extract the top 3 you pick the winner of each group. If you want the top 4, then you have to consider all the second placers in the groups as candidates for the 4th place as well. This could be done in different ways based on options. Either pick based on points, then (optionally) by maps, but if these are tied, then tiebreaking has to be done between the top tieing players.

Within groups presents an additional complication to the first case, by having the opportunity that all players in the group places 1st (say) meaning that even taking the top 3 involves tiebreaking within the group first.

Since a group stage should not enforce no tie situations by default, it's therefore possible that for certain annoying parameters, tiebreakers for progression have to be constructed twice! Once for within the group, and then once between the group for the xth placers (that we can now accurately pick because of the first tiebreaking) that we only pick some of.

This gets additionally hairy by the fact that the within tiebreaking only needs to occur if the ties extend to the xth place, where x is cutoff point for inclusion.

Discussion

Adding tiebreak-like functionality would be the solution to #6, but exactly how is a bit unclear. Due to the nature of it messing up history rewrites, it is my gut feeling that it should happen outside the main tournament structure.

My idea so far is to create these as a separate tournament type TieBreak. This would allow:

  • tiebreak 2 players in an individual duel
  • tiebreaking more than 2 players in either individual sub-groupstages or FFA matches
  • allow tiebreaking to not really happen, but allow custom cases to be specified manually by administrators by pretend scoring a pretend match
  • encapsulate all this logic in one place in a way that firmly entrenches history leaving the mess in the hands of the operator rather than (unfixably) in the tournaments hands
  • By using results at the end and manually piping to TieBreak, most tournament cases may simply work as if we didn't specify limit before

/thought stream

dynamic tournaments

At the moment all tournaments are static, in the sense that all matches are pre-created an there is not really any room for adding matches to the match list as the tournament progresses because of assumptions we have based tournament on, and this is fine.

TieBreaker add this kind of dynamic behaviour of having new matches suddenly introduced by setting a freezing point, then forwarding to it.

However, this is actually a lot more common than previously anticipated. Example FFA without the arfiticial scoring limit need tiebreakers at each round: tournament-js/ffa#1

Thus, what we really need is a dynamic tournament, that can have freezing points, but really encapsulate some of this progression to a new stage logic. Initially we can have a dynamic FFA tournament type that features this, creates tiebreakers automatically after each round, then forwards the winners of the tiebreaker to a new ffa round based on advancers / sizes parameters. Essentially, the API would be unchanged, except for the addition of the freezing points.

This would only really necessitate an extra id parameter short for "stage" so that ALL these dynamic matches can be bunched into one "giant" array. Obviously, we also need a module to actually treat these types of tournaments differently by not expecting the matches array to stay static.

Custom limits and crossover matches

The limit option, regarding how we plan to do multi-stage tournaments is a bit problematic.

One one hand, it allows us to disallow certain final match scores by saying that we NEED to determine a top X here (if we allowed X and X+1 to ties then we would be picking top X a bit randomly). So we will simply say that you HAVE to score it this way, either by setting this match condition yourself (somehow) or by having tiebreakers outside the actual match structure.

The second is a bit annoying, but at least we have made it a little better for the people who can apply such constraints to their final matches.

Unfortunately, limit cannot (in current state) be set to any number. For FFA we need to pick a top X where X can only be a multiple of the number of games in the final round so we don't have to choose only some of the people placing 2nd for instance as there is no (good) way of judging whether one 2nd placer is better than another when they are in different matches (match score is not sufficient if the matches are differently skilled). Thus, if there was 2 final round matches, and you want top 5 (say) then this would not work because we'd have to pick one of the 3rd placers.

Now we could generate automatic crossover matches and let the two 3rd placers duel (whether or not this is actually carried out is up to the organizer, but at least the match shell is there). But this is actually a bit problematic with the current philosophy that we can rewrite history.

If we allow history rewrites, it would now change who's in the crossover match (or group if there's more than 2 to fight for the spot), so this seems really difficult to me right now.

Anyone with some bright ideas?

Serialization improvements

It is possible that serialization/deserialization can be significantly improved by just stringifying the entire instance and casting an object back into an instance on deserialization. This can reduce most logic in the badly named .fromJSON static method and reduce book-keeping to one variable/string.

May do this for 1.0.0 (because it will clearly break the current API)

Browserify unable to locate files

Hi, your work looks awesome, but as I use Django as my backend, browserify provides much simpler way for me to use it than using NodeJS directly.

I ran into problems during the browserify step though, and I found it impossible to google a solution, so I'm using this as the final resort - many thanks for your time.

I ran the
$ npm install duel ffa groupstage tiebreaker --save
and
$ npm dedupe
without problems, but

$ browserify -r duel -r ffa -r groupstage -r tiebreaker > bundle.js

raises following error:

Error: Cannot find module './lib-cov/group.js' from '/home/pro/node_modules/groupstage/node_modules/group'
at /usr/lib/node_modules/browserify/node_modules/resolve/lib/async.js:42:25
at load (/usr/lib/node_modules/browserify/node_modules/resolve/lib/async.js:60:43)
at /usr/lib/node_modules/browserify/node_modules/resolve/lib/async.js:66:22
at /usr/lib/node_modules/browserify/node_modules/resolve/lib/async.js:21:47
at Object.oncomplete (fs.js:107:15)

If I try to run browserify for each module separately, each gives a similar error.
If it would help, here is npm ls output
$ npm ls
/home/pro
├─┬ [email protected]
│ ├─┬ [email protected]
│ │ ├── [email protected]
│ │ ├── [email protected]
│ │ └── [email protected]
│ └── [email protected]
├─┬ [email protected]
│ ├── [email protected]
│ ├─┬ [email protected]
│ │ ├── [email protected]
│ │ ├── [email protected]
│ │ └── [email protected]
│ └── [email protected]
├─┬ [email protected]
│ ├── [email protected]
│ ├─┬ [email protected]
│ │ ├── [email protected]
│ │ ├── [email protected]
│ │ └── [email protected]
│ ├── [email protected]
│ └── [email protected]
└─┬ [email protected]
├─┬ [email protected]
│ ├── [email protected]
│ ├── [email protected]
│ └── [email protected]
└── [email protected]

Many thanks for your time, and your help would be greatly appreciated.

Jakub Trancik

idString and rep are dirty hacks

They will be removed in the next version in favour of ids that optionally specify their own toString via their own small class.

Ladder

Should we include a ladder type? I have little knowledge on how to do these well or what the controversies are with this more static tournament type so several questions have to be researched and argued clearly for and against. In particular:

  • Do we need a limit on size?
  • What's a good scoring system?
  • Should there be decay?
  • How to store a 'potential' match?
  • Can a player challenge anyone or just someone within his own range?
  • How does a persistent tournament type mix with my abstractions?
  • Should it even be completely persistent or seasonal?

Bracket movement

Should double (and future triple elimination) brackets move when displayed?

Yes, unfortunately this assumption is inherently nested with the assumptions we make about progression. E.g. in double elimination; dropping losers in WB in the top position in their designated LB matches causes the LB bracket to slowly move upwards to 'meet the WB winner' at the vertical midpoint. Which is nice. Not moving the brackets allow the brackets to be viewed nicely split at the y-axis (so the LB moves leftward from the right to meet the final).

Allowing both types is likely just a (very careful) modification to the internal down and right functions, but if we should allow both types is really the question here. Especially if we want more advanced elimination types later (like triple elimination).

The unmoved brackets may quickly look silly with large participant numbers, and it's hard to track where WB losers are dropped as it's at the other side of the screen. It's also hard to conceive of nicely designed non-moving elimination grids that do not move when the number of brackets is > 2.

Anyone with some thoughts on this? Why one style over another? Are both required? Will both they work in higher numbered elimination types?

isPlayable should check if the game is unscorable

I noticed that isPlayable doesn't check if the game is unscorable... for instance... I'm at round 3 of a Duel with all games scored and I want to edit a match from Round 1, the isPlayable validation returns true even if editing that match would cause a history rewrite and possibly leave the tournament in an invalid state... and if you think about it, a game is playable only if its scorable.

unscorable allowPast is dumb

Just mis-scored the match? Now it's considered past. It won't affect anything to rescore it, but there is no difference between this case and an actually dangerous recore.

Implement a default "virtual" that implementations can override with a check if the current match has something in its progression tree (and so is unsafe to rescore). Duel could simply check if right or down is played, ffa could check if next round has any played matches (although this is not a problem in upcoming ffa-tb), groupstage could just return false or have an option for it.

Handle errors from .restore better

At the moment, one some .score calls will just fail and there will be tons of logs. There should be some guarding and handling of the error cases.

Need tests for double subclasses

Currently can figure out when this breaks by running the tests for masters on the ffasub implementation - which tournament-dev will do, but should have a basic test here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.