seed_list 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.md +103 -0
- data/Rakefile +27 -0
- data/lib/seed_list/list.rb +36 -0
- data/lib/seed_list/model.rb +38 -0
- data/lib/seed_list/strategy.rb +59 -0
- data/lib/seed_list/version.rb +3 -0
- data/lib/seed_list.rb +7 -0
- data/lib/tasks/seed_list_tasks.rake +4 -0
- metadata +150 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 Agora Games
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# SeedList
|
2
|
+
*Seed management for tournament brackets*
|
3
|
+
|
4
|
+
## Overview
|
5
|
+
|
6
|
+
SeedList is designed for Rails-powered tournament engines that need to persist
|
7
|
+
a 1-indexed ordered list of players (ranked low-to-high by skill or past performance)
|
8
|
+
and then match them up appropriately in the first round of a bracket.
|
9
|
+
|
10
|
+
Players are then matched up according to a strategy specified on a per-instance basis.
|
11
|
+
|
12
|
+
See also: https://github.com/agoragames/bracket_tree
|
13
|
+
|
14
|
+
## Example
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
|
18
|
+
class Tournament < ActiveRecord::Base
|
19
|
+
has_many :players
|
20
|
+
seed :players
|
21
|
+
end
|
22
|
+
|
23
|
+
class Player < ActiveRecord::Base
|
24
|
+
belongs_to :tournament
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
|
30
|
+
add_column :tournaments, :players_seed_list, :text
|
31
|
+
|
32
|
+
```
|
33
|
+
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
|
37
|
+
t = Tournament.create
|
38
|
+
=> #<Tournament id: 2, players_seed_list: #<SeedList::List:0x00000002aeff68 @list=[]>>
|
39
|
+
|
40
|
+
p = t.players.create
|
41
|
+
=> #<Player id: 16, tournament_id: 2>
|
42
|
+
|
43
|
+
t.reload
|
44
|
+
=> #<Tournament id: 2, players_seed_list: #<SeedList::List:0x00000002d342d8 @list=[16]>>
|
45
|
+
|
46
|
+
# Seed numbers start at 1
|
47
|
+
p.seed
|
48
|
+
=> 1
|
49
|
+
|
50
|
+
```
|
51
|
+
|
52
|
+
When a player is created, the `id` is pushed to the list in the highest (worst-place) seed
|
53
|
+
position. You can then move the seed to another position in the list.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
|
57
|
+
3.times { t.players.create }
|
58
|
+
|
59
|
+
p.seed = 4
|
60
|
+
|
61
|
+
t.reload
|
62
|
+
=> #<Tournament id: 2, players_seed_list: #<SeedList::List:0x00000002d342d8 @list=[17, 18, 19, 16]>>
|
63
|
+
|
64
|
+
```
|
65
|
+
|
66
|
+
## Strategies
|
67
|
+
|
68
|
+
Once your players have been created and moved to the appropriate seed positions, you can use
|
69
|
+
the included seeding strategies to match them up appropriately in the first round of the bracket.
|
70
|
+
|
71
|
+
You can easily implement your own strategies as well. The initializer must accept an array
|
72
|
+
of objects that respond to #seed with an integer, and the #seed method must return an array
|
73
|
+
of those objects sorted appropriately as pairs.
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
|
77
|
+
t.players.map { |p| p.seed }
|
78
|
+
=> [1, 2, 3 , 4]
|
79
|
+
|
80
|
+
# A Knockout tournament matches players at random. The seed position is irrelevant.
|
81
|
+
SeedList::Strategy::Knockout.new(t.players).seed.map { |p| p.seed }
|
82
|
+
=> [2, 3, 4, 1]
|
83
|
+
|
84
|
+
# A Playoff tournament matches the best players against the worst.
|
85
|
+
SeedList::Strategy::Playoff.new(t.players).seed.map { |p| p.seed }
|
86
|
+
=> [1, 4, 2, 3]
|
87
|
+
|
88
|
+
# An Amateur tournament matches by skill similarity straight down
|
89
|
+
SeedList::Strategy::Playoff.new(t.players).seed.map { |p| p.seed }
|
90
|
+
=> [1, 2, 3, 4]
|
91
|
+
|
92
|
+
```
|
93
|
+
## License
|
94
|
+
|
95
|
+
See the [MIT-LICENSE](https://github.com/agoragames/seed_list/blob/master/MIT-LICENSE) file.
|
96
|
+
|
97
|
+
## Contributions
|
98
|
+
|
99
|
+
Contributions are awesome. Feature branch pull requests are the preferred method.
|
100
|
+
|
101
|
+
## Author
|
102
|
+
|
103
|
+
Written by [Logan Koester](https://github.com/logankoester)
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'SeedList'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module SeedList
|
2
|
+
class List
|
3
|
+
def initialize(*seeds)
|
4
|
+
@list = Array.new(seeds)
|
5
|
+
end
|
6
|
+
|
7
|
+
def move(s, i)
|
8
|
+
i -= 1
|
9
|
+
index = @list.index s
|
10
|
+
@list.delete_at index if index < i
|
11
|
+
@list.insert(i, s).uniq!
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def unshift(s)
|
16
|
+
@list.unshift(s).uniq!
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def push(s)
|
21
|
+
@list.push(s).uniq!
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(s)
|
26
|
+
@list.delete_if { |e| e == s }
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def find(s)
|
31
|
+
i = @list.index(s)
|
32
|
+
i.nil? ? nil : i + 1
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SeedList
|
2
|
+
module Model
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def seed(assoc_name)
|
7
|
+
assoc_name = assoc_name.to_s
|
8
|
+
|
9
|
+
eval "serialize :#{assoc_name}_seed_list, SeedList::List"
|
10
|
+
|
11
|
+
assoc_name.classify.constantize.class_eval <<-CODE
|
12
|
+
after_create do |p|
|
13
|
+
p.#{self.name.downcase}.#{assoc_name}_seed_list.push(p.id)
|
14
|
+
p.#{self.name.downcase}.save
|
15
|
+
end
|
16
|
+
|
17
|
+
after_destroy do |p|
|
18
|
+
p.#{self.name.downcase}.#{assoc_name}_seed_list.delete(p.id)
|
19
|
+
p.#{self.name.downcase}.save
|
20
|
+
end
|
21
|
+
|
22
|
+
def seed
|
23
|
+
#{self.name.downcase}.#{assoc_name}_seed_list.find(id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def seed=(n)
|
27
|
+
#{self.name.downcase}.#{assoc_name}_seed_list.move(id, n)
|
28
|
+
#{self.name.downcase}.save
|
29
|
+
end
|
30
|
+
CODE
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
ActiveRecord::Base.send :include, SeedList::Model
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Strategy classes are responsible for matching up players against each other
|
2
|
+
# in the first round of a bracket. A strategy is chosen for each tournament by the host.
|
3
|
+
#
|
4
|
+
# Player objects have a numeric property #seed, which is set at creation
|
5
|
+
# and may be reordered by the host. These positions are ranked by skill from
|
6
|
+
# best (1) to worst (max).
|
7
|
+
#
|
8
|
+
# When the Bracket is generated, the players are sorted in accordance with the
|
9
|
+
# chosen strategy logic.
|
10
|
+
module SeedList
|
11
|
+
module Strategy
|
12
|
+
class Base
|
13
|
+
attr_accessor :players
|
14
|
+
def initialize(players); @players = players; end
|
15
|
+
def seed; raise 'Abstract method called'; end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Players matched against each other at random. A player's seed position is
|
19
|
+
# completely irrelevant.
|
20
|
+
class Knockout < Base
|
21
|
+
def seed
|
22
|
+
@players.shuffle
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Best matched against worst, second best against second worst, and so on.
|
27
|
+
# Optimized for serious players and viewer entertainment.
|
28
|
+
class Playoff < Base
|
29
|
+
def seed
|
30
|
+
input, output = @players.clone, []
|
31
|
+
input.size.times { |i| output << (i.even? ? input.shift : input.pop) }
|
32
|
+
output
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# TODO This strategy is implemented incorrectly, with an associated pending test.
|
37
|
+
# Best matched against worst, second best against second worst, and so on.
|
38
|
+
# Ensures that if the higher seed wins, he will always face someone from the
|
39
|
+
# lower half in the next round.
|
40
|
+
class MLGPlayoff < Base
|
41
|
+
def seed
|
42
|
+
input, sequence, pairs, output = @players.clone, [], [], []
|
43
|
+
input.size.times { |i| sequence << (i.even? ? input.shift : input.pop) }
|
44
|
+
loop { pairs << sequence.shift(2); break if sequence.empty? }
|
45
|
+
pairs.size.times { |i| output << (i.even? ? pairs.shift : pairs.pop) }
|
46
|
+
output.flatten
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Matched by similar skill straight through; best on the top, worst on the bottom.
|
51
|
+
# Optimized for friendly tournaments with diversely skilled players (to prevent casual
|
52
|
+
# players from being knocked out right away).
|
53
|
+
class Amateur < Base
|
54
|
+
def seed
|
55
|
+
@players.sort { |a,b| a.seed <=> b.seed }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/seed_list.rb
ADDED
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: seed_list
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Logan Koester
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: &6890660 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.6
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *6890660
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: sqlite3
|
27
|
+
requirement: &6889020 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *6889020
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec-rails
|
38
|
+
requirement: &6887580 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *6887580
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: capybara
|
49
|
+
requirement: &7428560 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *7428560
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: guard-rspec
|
60
|
+
requirement: &7427160 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *7427160
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-spork
|
71
|
+
requirement: &7426500 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *7426500
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: factory_girl_rails
|
82
|
+
requirement: &7425200 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *7425200
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: pry-rails
|
93
|
+
requirement: &7423820 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *7423820
|
102
|
+
description: SeedList is designed for Rails-powered tournament engines that need to
|
103
|
+
persist a 1-indexed ordered list of players (ranked low-to-high by skill or past
|
104
|
+
performance) and then match them up appropriately in the first round of a bracket.
|
105
|
+
email:
|
106
|
+
- lkoester@majorleaguegaming.com
|
107
|
+
executables: []
|
108
|
+
extensions: []
|
109
|
+
extra_rdoc_files: []
|
110
|
+
files:
|
111
|
+
- lib/seed_list/strategy.rb
|
112
|
+
- lib/seed_list/list.rb
|
113
|
+
- lib/seed_list/version.rb
|
114
|
+
- lib/seed_list/model.rb
|
115
|
+
- lib/tasks/seed_list_tasks.rake
|
116
|
+
- lib/seed_list.rb
|
117
|
+
- MIT-LICENSE
|
118
|
+
- Rakefile
|
119
|
+
- README.md
|
120
|
+
homepage: https://github.com/agoragames/seed_list
|
121
|
+
licenses: []
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
hash: 1687580428413640907
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ! '>='
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
hash: 1687580428413640907
|
144
|
+
requirements: []
|
145
|
+
rubyforge_project:
|
146
|
+
rubygems_version: 1.8.17
|
147
|
+
signing_key:
|
148
|
+
specification_version: 3
|
149
|
+
summary: Seed management for tournament brackets
|
150
|
+
test_files: []
|