# ---------------------------------------------------------------------- # "THE BEER-WARE LICENSE" (Revision 43): # <pablo@propus.com.br> wrote this file and it's provided AS-IS, no # warranties. As long as you retain this notice you can do whatever you # want with this stuff. If we meet some day, and you think this stuff is # worth it, you can buy me a beer in return." # ---------------------------------------------------------------------- RSwiss ------ This is a simple implementation of Swiss Tournament (actually it's more like a "King of The Hill" Tournament) focused on simplicity and no manual interaction. The goal is to generate the simplest thing that could possible work (and not replace FIDE rules - if you're thinking about chess - or any other software dealing with the same issue). Requirements ------------ Sequel: tested with 3.9.0. Probably will work with earlier versions. How it works ------------ We're using a database under Sequel to store our tournaments, players and matches data. The model RSwiss::Tournament is the central piece here. A new instance of that is generated passing the number of players. An array of player ids have to be passed with #inject_players. With that in place, new matches are generated automatically for each round and are checked-out with the #checkout_match method. Since every round depends on the results of previous matches, checked-out matches have to be commited back with the #commit_match method (just saving the object RSwiss::Match also will work). This goes on until RSwiss::Tournament raises an RSwiss::EndOfTournament exception, which marks... well... the end of the tournament (at any given point #ended? can be called if you want to prevent exceptions being raised). The result table can be obtained with #players(:score) or with #players(:criteria). You can set the criteria with #criteria=. The default #criteria can be accessed by criteria, and equals to the following array: [ :score, :buchholz_score, :neustadtl_score, :c_score, :opp_c_score, :wins ] Where: * :score is the conventional score (1.0 for win, 0.5 for draw and 0.0 for defeat; * :buchholz_score is the one calculated by Median Buchholz method (sometimes called Harkness), which is the sum of the players opponents scores, discarding one or two lowest and highest; * :neustadtl_score is the one calculated by adding the scores of every opponent the player beats and half of the score of every opponent the player draws. Also called Sonneborn-Berger; * :c_score is the cumulative score, which sums the running score for each round; * :opp_c_score is the sum of the cumulative score of the opponents; and * :wins is the number of wins. Ok... But how it works under the hood? -------------------------------------- It's quite simple, actually. First round order is decided at random. After that, every round is generated by sorting players by the score and descending the sorted array of players from the highest to the lowest, generating one match for each player in each round against those with similar scores. If we have an odd number of players, one is always left out. This is called "bye", and can only happen to a player once. It sums 1.0 to the score, and counts as a match, but adds no opponent (so it gives in the score, but takes in every other criteria, including number of wins). The "bye" is given each round to the last player that has not received one yet. By default, RSwiss generates just the number of rounds that ensure a fairly evaluated tournament (the binary logarithm of the number of players). You can use #additional_rounds= to set the number of additional rounds to be generated (just in case you need to "overevaluate" your tournament). Use this feature with care... Traditionally, there's a restriction on repeating matches. Unfortunatelly, this can generate deadlocks (where we simply cannot generate a round without repeated matches given the previous results). This usually happens in as low as 0.3% of the tournaments, but, according to the number of players, can happen in up to 2%! When this happens, the only way to break out of it is to throw everything away and start over (not a good thing). Well... there's, of course, less destructive measures, but it requires bending the rules a little. The first way RSwiss uses to try to break the deadlock is to discarding some of the last few unexposed matches, shuffling their players and trying to build different matches. But even that has limitations. Also, when creating a new tournament, you can also use #allow_repeat= as a bloolean flag. When set to "true", it will allow one of the previous matches to be repeated as a way to break the deadlock. This can only happen once for each match. In my experience, when it happens is also usually happens once in the whole tournament, so it's not a big deal. Other solutions have been proposed, such as giving a "bye" to the involved players, or generating a really unbalanced match (with players with too different scores), but I think that the implemented solution is the one with less "side-effects" (distributing "byes" has effects on criterias and unbalanced matches can generate less fair results). Tests ----- This is, mostly, a proof-of-concept code, so there are "tests" (not Unit Tests) that can be run against it. The script "test.rb" accepts the number of players as the first argument and begin recurrently testing tournaments with that number of players, recording the "problem rate" (tournaments that have reached the deadlock); the presence of any other argument will render tournaments with "allow repeat" flag on (in this case, the number of repeated matches will be recorded). XML-RPC ------- The "xml-sswiss.rb" is a simple XML-RPC server that accepts the creation of tournaments and all the interface to exchange matches with a XML-RPC client. You can use it by running "simple_serve.rb" (for the native XMLRPC server that ships with Ruby) or as a Rack app - try running "rack_serve.ru". This also have a test in "xml-test.rb", that actually does the same "test.rb" does, but acting as a XML-RPC client to "xml-sswiss.rb" server. If you're using the Rack version with "xml-test.rb", run it with "-p 9090" (or change the source for "xml-test.rb").