killer_queen_scene_scoring 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +7 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/killer_queen_scene_scoring +29 -0
- data/killer_queen_scene_scoring.gemspec +43 -0
- data/lib/killer_queen_scene_scoring.rb +18 -0
- data/lib/killer_queen_scene_scoring/bracket.rb +334 -0
- data/lib/killer_queen_scene_scoring/config.rb +19 -0
- data/lib/killer_queen_scene_scoring/match.rb +25 -0
- data/lib/killer_queen_scene_scoring/player.rb +25 -0
- data/lib/killer_queen_scene_scoring/scene.rb +26 -0
- data/lib/killer_queen_scene_scoring/team.rb +18 -0
- data/lib/killer_queen_scene_scoring/tournament.rb +153 -0
- data/lib/killer_queen_scene_scoring/version.rb +3 -0
- metadata +151 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d33202ac0d276376a6f6a5b044ff7c7780599eba401c8046359390cca7b0677e
|
|
4
|
+
data.tar.gz: e435962c1fd937190dc49cf950b5bfea1dd54d0aeab5f6431892330fe265ab9d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9f0190a641d7e30c18d8c4a7d653d48360df7b45337ccc4266115192889c48509296374ebcb51f938108c6d4fc72f52f1d9bca88017fb93d4311247a1d41aba1
|
|
7
|
+
data.tar.gz: b39af761fbb5a74829b228b7bdcae7d732a0e99fa5a9b8cdfa6aec7bf66e28f6faf71fef3411479f3458143c85d30f208960cfc538391111fa92b381ef8a4951
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
killer_queen_scene_scoring (0.1.0)
|
|
5
|
+
dotenv (~> 2.7)
|
|
6
|
+
json (~> 2.2)
|
|
7
|
+
rest-client (~> 2.0)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
domain_name (0.5.20180417)
|
|
13
|
+
unf (>= 0.0.5, < 1.0.0)
|
|
14
|
+
dotenv (2.7.1)
|
|
15
|
+
http-cookie (1.0.3)
|
|
16
|
+
domain_name (~> 0.5)
|
|
17
|
+
json (2.2.0)
|
|
18
|
+
mime-types (3.2.2)
|
|
19
|
+
mime-types-data (~> 3.2015)
|
|
20
|
+
mime-types-data (3.2018.0812)
|
|
21
|
+
minitest (5.11.3)
|
|
22
|
+
netrc (0.11.0)
|
|
23
|
+
rake (10.5.0)
|
|
24
|
+
rest-client (2.0.2)
|
|
25
|
+
http-cookie (>= 1.0.2, < 2.0)
|
|
26
|
+
mime-types (>= 1.16, < 4.0)
|
|
27
|
+
netrc (~> 0.8)
|
|
28
|
+
unf (0.1.4)
|
|
29
|
+
unf_ext
|
|
30
|
+
unf_ext (0.0.7.5)
|
|
31
|
+
|
|
32
|
+
PLATFORMS
|
|
33
|
+
ruby
|
|
34
|
+
|
|
35
|
+
DEPENDENCIES
|
|
36
|
+
bundler (~> 1.17)
|
|
37
|
+
killer_queen_scene_scoring!
|
|
38
|
+
minitest (~> 5.0)
|
|
39
|
+
rake (~> 10.0)
|
|
40
|
+
|
|
41
|
+
RUBY VERSION
|
|
42
|
+
ruby 2.6.0p0
|
|
43
|
+
|
|
44
|
+
BUNDLED WITH
|
|
45
|
+
1.17.2
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Michael Dunn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# KillerQueenSceneScoring
|
|
2
|
+
|
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/killer_queen_scene_scoring`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
4
|
+
|
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'killer_queen_scene_scoring'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
$ bundle
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
$ gem install killer_queen_scene_scoring
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
TODO: Write usage instructions here
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
30
|
+
|
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/killer_queen_scene_scoring.
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "killer_queen_scene_scoring"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "killer_queen_scene_scoring"
|
|
4
|
+
|
|
5
|
+
id = ARGV[0]
|
|
6
|
+
api_key = ENV["CHALLONGE_API_KEY"] || ARGV[1]
|
|
7
|
+
|
|
8
|
+
unless id && api_key
|
|
9
|
+
puts <<~EOS
|
|
10
|
+
Usage: #{File.basename __FILE__} <bracket_id> <api_key>
|
|
11
|
+
|
|
12
|
+
bracket_id is the slug or numeric ID of the first bracket in the tournament.
|
|
13
|
+
api_key is your Challonge API key.
|
|
14
|
+
|
|
15
|
+
If you don't have a tournament ready, you can try one of these, which have
|
|
16
|
+
already been set up: tvtpeasf clonekqxxv bb3wc
|
|
17
|
+
|
|
18
|
+
For convenience, you can create a .env file in the current directory and
|
|
19
|
+
store your API key in that file. The file should have this line:
|
|
20
|
+
CHALLONGE_API_KEY=Your_API_key_here
|
|
21
|
+
EOS
|
|
22
|
+
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
t = KillerQueenSceneScoring::Tournament.new(id: id, api_key: api_key)
|
|
27
|
+
t.load
|
|
28
|
+
t.calculate_points
|
|
29
|
+
puts t.scene_scores.sort
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
|
|
4
|
+
require "date"
|
|
5
|
+
require "killer_queen_scene_scoring/version"
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = "killer_queen_scene_scoring"
|
|
9
|
+
spec.version = KillerQueenSceneScoring::VERSION
|
|
10
|
+
spec.authors = ["Michael Dunn"]
|
|
11
|
+
spec.email = ["acidhelm@gmail.com"]
|
|
12
|
+
|
|
13
|
+
spec.summary = "Scene-wide scoring for Killer Queen tournaments."
|
|
14
|
+
spec.description = "Classes that implement scene-wide scoring for Killer Queen tournaments."
|
|
15
|
+
spec.homepage = "https://github.com/acidhelm/killer_queen_scene_scoring"
|
|
16
|
+
spec.license = "MIT"
|
|
17
|
+
|
|
18
|
+
if spec.respond_to?(:metadata)
|
|
19
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
21
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
22
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
|
23
|
+
else
|
|
24
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Specify which files should be added to the gem when it is released.
|
|
28
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
29
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(/^(test|spec|features)\//) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
spec.bindir = "exe"
|
|
33
|
+
spec.executables = spec.files.grep(/^exe\//) { |f| File.basename(f) }
|
|
34
|
+
spec.require_paths = ["lib"]
|
|
35
|
+
|
|
36
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
|
37
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
38
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
39
|
+
|
|
40
|
+
spec.add_runtime_dependency "dotenv", "~> 2.7"
|
|
41
|
+
spec.add_runtime_dependency "json", "~> 2.2"
|
|
42
|
+
spec.add_runtime_dependency "rest-client", "~> 2.0"
|
|
43
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "dotenv/load"
|
|
2
|
+
require "json"
|
|
3
|
+
require "rest-client"
|
|
4
|
+
require "killer_queen_scene_scoring/bracket.rb"
|
|
5
|
+
require "killer_queen_scene_scoring/config"
|
|
6
|
+
require "killer_queen_scene_scoring/match"
|
|
7
|
+
require "killer_queen_scene_scoring/player"
|
|
8
|
+
require "killer_queen_scene_scoring/scene"
|
|
9
|
+
require "killer_queen_scene_scoring/team"
|
|
10
|
+
require "killer_queen_scene_scoring/tournament"
|
|
11
|
+
require "killer_queen_scene_scoring/version"
|
|
12
|
+
|
|
13
|
+
module KillerQueenSceneScoring
|
|
14
|
+
# Creates a hash whose values are arrays.
|
|
15
|
+
def self.hash_of_arrays
|
|
16
|
+
Hash.new { |h, k| h[k] = [] }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KillerQueenSceneScoring
|
|
4
|
+
|
|
5
|
+
class Bracket
|
|
6
|
+
attr_reader :players, :config
|
|
7
|
+
|
|
8
|
+
# `id` can be the slug or the challonge ID of the bracket. If you pass a
|
|
9
|
+
# and the bracket is owned by an organization, it must be of the form
|
|
10
|
+
# "<org name>-<slug>". `api_key` is your Challonge API key.
|
|
11
|
+
def initialize(id:, api_key:)
|
|
12
|
+
@id = id
|
|
13
|
+
@api_key = api_key
|
|
14
|
+
@loaded = false
|
|
15
|
+
@state == ""
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def complete?
|
|
19
|
+
@state == "complete"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Reads the Challonge bracket with the ID that was passed to the constructor,
|
|
23
|
+
# and fills in all the data structures that represent that bracket.
|
|
24
|
+
# Returns a boolean indicating whether the bracket was loaded.
|
|
25
|
+
def load
|
|
26
|
+
url = "https://api.challonge.com/v1/tournaments/#{@id}.json"
|
|
27
|
+
params = { include_matches: 1, include_participants: 1 }
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
response = send_get_request(url, params)
|
|
31
|
+
rescue RestClient::NotFound
|
|
32
|
+
# Bail out if we got a 404 error. The bracket doesn't exist on
|
|
33
|
+
# Challonge right now, but it might be created in the future.
|
|
34
|
+
# TODO: Rails.logger.warn "The bracket does not exist."
|
|
35
|
+
return false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Set `@challonge_bracket` to the data structure that represents the
|
|
39
|
+
# bracket, its teams, and its matches. We also keep the bracket's
|
|
40
|
+
# state separately, for convenience.
|
|
41
|
+
@challonge_bracket = OpenStruct.new(response[:tournament])
|
|
42
|
+
@state = @challonge_bracket.state
|
|
43
|
+
|
|
44
|
+
# Bail out if the bracket hasn't started yet. This lets the tournament
|
|
45
|
+
# organizer set the `next_bracket` value to a bracket that has been
|
|
46
|
+
# created on Challonge, but which will be started in the future. For
|
|
47
|
+
# example, the organizer can create a wild card bracket and a finals
|
|
48
|
+
# bracket, and set `next_bracket` in the wild card bracket to the ID
|
|
49
|
+
# of the finals bracket before the wild card bracket has finished.
|
|
50
|
+
if @challonge_bracket.started_at.nil?
|
|
51
|
+
# TODO: Rails.logger.warn "The bracket has not been started yet."
|
|
52
|
+
return false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Read all the things.
|
|
56
|
+
read_config
|
|
57
|
+
read_teams
|
|
58
|
+
read_matches
|
|
59
|
+
read_players
|
|
60
|
+
|
|
61
|
+
@loaded = true
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Calculates how many points each player has earned in this bracket. If the
|
|
66
|
+
# bracket is not yet complete, the point values are the mininum number of
|
|
67
|
+
# points that the player can earn based on their current position in
|
|
68
|
+
# the bracket.
|
|
69
|
+
# On exit, `@players` contains a hash. The keys are the Challonge IDs of
|
|
70
|
+
# the teams in the bracket. The values are arrays of `Player` objects
|
|
71
|
+
# representing the players on the team.
|
|
72
|
+
# The caller must call `load`, and `load` must succeed, before calling this
|
|
73
|
+
# function.
|
|
74
|
+
def calculate_points
|
|
75
|
+
raise_error "The bracket was not loaded" if !@loaded
|
|
76
|
+
|
|
77
|
+
calculate_team_points
|
|
78
|
+
calculate_player_points
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
protected
|
|
82
|
+
|
|
83
|
+
def finalizable?
|
|
84
|
+
@state == "awaiting_review"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def raise_error(msg)
|
|
88
|
+
# TODO: Rails.logger.error "ERROR: #{msg}"
|
|
89
|
+
raise msg
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Reads the config file from the bracket.
|
|
93
|
+
def read_config
|
|
94
|
+
# Find the match that has the config file attached to it. By convention,
|
|
95
|
+
# the file is attached to the first match, although we don't enforce that.
|
|
96
|
+
# We just look for a match with exactly one attachment. We do require
|
|
97
|
+
# that exactly one match have exactly one attachment.
|
|
98
|
+
first_match = @challonge_bracket.matches.select do |match|
|
|
99
|
+
match[:match][:attachment_count] == 1
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
raise_error "No matches with one attachment were found in the bracket" if first_match.empty?
|
|
103
|
+
raise_error "Multiple matches have one attachment" if first_match.size > 1
|
|
104
|
+
|
|
105
|
+
# Read the options from the config file that's attached to that match.
|
|
106
|
+
url = "https://api.challonge.com/v1/tournaments/#{@id}/matches/" \
|
|
107
|
+
"#{first_match[0][:match][:id]}/attachments.json"
|
|
108
|
+
|
|
109
|
+
attachment_list = send_get_request(url)
|
|
110
|
+
asset_url = attachment_list[0][:match_attachment][:asset_url]
|
|
111
|
+
|
|
112
|
+
raise_error "Couldn't find the config file attachment" if asset_url.nil?
|
|
113
|
+
|
|
114
|
+
uri = URI(asset_url)
|
|
115
|
+
|
|
116
|
+
# The attachment URLs that Challonge returns don't have a scheme, and
|
|
117
|
+
# instead start with "//". Default to HTTPS.
|
|
118
|
+
uri.scheme ||= "https"
|
|
119
|
+
|
|
120
|
+
# TODO: Rails.logger.debug "Reading the config file from #{uri}"
|
|
121
|
+
|
|
122
|
+
# Read the config file from the attchment.
|
|
123
|
+
config = send_get_request(uri.to_s)
|
|
124
|
+
|
|
125
|
+
# Ensure that the required values are in the config file.
|
|
126
|
+
%i(base_point_value max_players_to_count match_values).each do |key|
|
|
127
|
+
raise_error "The config file is missing \"#{key}\"" unless config.key?(key)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
@config = Config.new(config)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Reads the teams that are in this bracket, and sets `@teams` to an array
|
|
134
|
+
# of `Team` objects.
|
|
135
|
+
def read_teams
|
|
136
|
+
@teams = []
|
|
137
|
+
|
|
138
|
+
# Make a `Team` for each participant in the bracket.
|
|
139
|
+
@challonge_bracket.participants.each do |team|
|
|
140
|
+
@teams << Team.new(team[:participant])
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# TODO: Rails.logger.info "#{@teams.size} teams are in the bracket: " +
|
|
144
|
+
# @teams.sort_by(&:name).map { |t| %("#{t.name}") }.join(", ")
|
|
145
|
+
|
|
146
|
+
# Check that all of the teams in the bracket are also in the config file.
|
|
147
|
+
# We do case-insensitive name comparisons to allow for different
|
|
148
|
+
# capitalizations of prepositions and articles. This is fine, because
|
|
149
|
+
# two teams' names will never be the same except for case.
|
|
150
|
+
missing_teams = []
|
|
151
|
+
config_team_names = @config.teams.map { |t| t[:name] }
|
|
152
|
+
|
|
153
|
+
@teams.each do |team|
|
|
154
|
+
if config_team_names.none? { |name| name.casecmp?(team.name) }
|
|
155
|
+
missing_teams << team.name
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
if missing_teams.any?
|
|
160
|
+
raise_error "These teams are in the bracket but not the config file: " +
|
|
161
|
+
missing_teams.join(", ")
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Reads the matches that are in this bracket, and sets `@matches` to an array
|
|
166
|
+
# of `Match` objects.
|
|
167
|
+
def read_matches
|
|
168
|
+
# Check that `match_values` in the config file is the right size.
|
|
169
|
+
# The size must normally equal the number of matches. However, if the
|
|
170
|
+
# bracket is complete (finalized, or not finalized but all matches have
|
|
171
|
+
# been played) and it is double-elimination, then the array size is
|
|
172
|
+
# allowed to be one more than the number of matches, to account for a grand
|
|
173
|
+
# final that was only one match long.
|
|
174
|
+
#
|
|
175
|
+
# TODO: Also check that grand_finals_modifier is not set.
|
|
176
|
+
#
|
|
177
|
+
# If this is a two-stage bracket, the matches in the first stage have
|
|
178
|
+
# `suggested_play_order` set to nil, so don't consider those matches.
|
|
179
|
+
# If there is a match for 3rd place, its `suggested_play_order` is nil.
|
|
180
|
+
# We also ignore that match, and instead, we award points to the 3rd-place
|
|
181
|
+
# and 4th-place teams after the bracket has finished.
|
|
182
|
+
@matches = []
|
|
183
|
+
elim_stage_matches =
|
|
184
|
+
@challonge_bracket.matches.select { |m| m[:match][:suggested_play_order] }
|
|
185
|
+
num_matches = elim_stage_matches.size
|
|
186
|
+
config_array_size = @config.match_values.size
|
|
187
|
+
|
|
188
|
+
if num_matches != config_array_size
|
|
189
|
+
if !(complete? || finalizable?) ||
|
|
190
|
+
@challonge_bracket.tournament_type != "double elimination" ||
|
|
191
|
+
config_array_size != num_matches + 1
|
|
192
|
+
raise_error "match_values in the config file is the wrong size." \
|
|
193
|
+
" The size is #{config_array_size}, expected #{num_matches}."
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Make a `Match` for each match in the bracket.
|
|
198
|
+
elim_stage_matches.each do |match|
|
|
199
|
+
@matches << Match.new(match[:match], @config.match_values)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Reads the players that are in this bracket, and sets `@players` to a hash.
|
|
204
|
+
# Each key is a Challonge ID of a team, and each value is an array of
|
|
205
|
+
# `Player` objects for the players on that team.
|
|
206
|
+
def read_players
|
|
207
|
+
@players = KillerQueenSceneScoring::hash_of_arrays
|
|
208
|
+
|
|
209
|
+
# Read the team list from the config file and create structs for each
|
|
210
|
+
# player on each team.
|
|
211
|
+
@config.teams.each do |team|
|
|
212
|
+
# Look up the team in the `@teams` array. This is how we associate a
|
|
213
|
+
# team in the config file with its Challonge ID.
|
|
214
|
+
team_obj = @teams.find { |t| t.name.casecmp?(team[:name]) }
|
|
215
|
+
|
|
216
|
+
# If the `find` call failed, then there is a team in the team list that
|
|
217
|
+
# isn't in the bracket. We allow this so that multiple brackets can
|
|
218
|
+
# use the same master team list during a tournament.
|
|
219
|
+
if team_obj.nil?
|
|
220
|
+
# TODO: Rails.logger.info "Skipping a team that isn't in the bracket: #{team[:name]}"
|
|
221
|
+
next
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
team[:players].each do |player|
|
|
225
|
+
@players[team_obj.id] << Player.new(player)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# TODO: Rails.logger.info "#{team[:name]} (ID #{team_obj.id}) has: " +
|
|
229
|
+
# @players[team_obj.id].map { |p| "#{p.name} (#{p.scene})" }.join(", ")
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Bail out if any team doesn't have exactly 5 players.
|
|
233
|
+
# TODO: Do this in `read_config` instead.
|
|
234
|
+
invalid_teams = @players.select do |_, team|
|
|
235
|
+
team.size != 5
|
|
236
|
+
end.each_key.map do |team_id|
|
|
237
|
+
@teams.find { |t| t.id == team_id }.name
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
if invalid_teams.any?
|
|
241
|
+
raise_error "These teams don't have 5 players: #{invalid_teams.join(', ')}"
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Calculates how many points each team has earned in this bracket. If the
|
|
246
|
+
# bracket is not yet complete, the values are the mininum number of points
|
|
247
|
+
# that the team can receive based on their current position in the bracket.
|
|
248
|
+
# Sets the `points` member of each object in `@teams` to the number of
|
|
249
|
+
# points that each team member has earned.
|
|
250
|
+
def calculate_team_points
|
|
251
|
+
# If the bracket is complete, we can calculate points based on the
|
|
252
|
+
# teams' `final_rank`s.
|
|
253
|
+
if complete?
|
|
254
|
+
calculate_team_points_by_final_rank
|
|
255
|
+
return
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# For each team, look at the matches that it is in, look at the point
|
|
259
|
+
# values of those matches, and take the maximum point value. That's the
|
|
260
|
+
# number of points that the team has earned so far in the bracket.
|
|
261
|
+
base_point_value = @config.base_point_value
|
|
262
|
+
|
|
263
|
+
@teams.each do |team|
|
|
264
|
+
matches_with_team = @matches.select { |match| match.has_team?(team.id) }
|
|
265
|
+
|
|
266
|
+
# TODO: Rails.logger.info "Team #{team.name} was in #{matches_with_team.size} matches"
|
|
267
|
+
|
|
268
|
+
points_earned = matches_with_team.max_by(&:points).points
|
|
269
|
+
|
|
270
|
+
# TODO: Rails.logger.info "The largest point value of those matches is #{points_earned}" \
|
|
271
|
+
# "#{" + #{base_point_value} base" if base_point_value > 0}"
|
|
272
|
+
|
|
273
|
+
team.points = points_earned + base_point_value
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Calculates how many points each player has earned in the tournament, and
|
|
278
|
+
# sets the `points` member of each object in `@players` to that value.
|
|
279
|
+
def calculate_player_points
|
|
280
|
+
# Iterate over the teams in descending order of their scores. This way,
|
|
281
|
+
# the debug output will follow the teams' finishing order, which will be
|
|
282
|
+
# easier to read.
|
|
283
|
+
@teams.sort_by(&:points).reverse_each do |team|
|
|
284
|
+
# TODO: Rails.logger.info "Awarding #{team.points} points to #{team.name}: " +
|
|
285
|
+
# @players[team.id].map(&:to_s).join(", ")
|
|
286
|
+
|
|
287
|
+
@players[team.id].each do |player|
|
|
288
|
+
player.points = team.points
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Calculates how many points each team earned in this bracket.
|
|
294
|
+
# Sets the `points` member of each object in `@teams` to the number of
|
|
295
|
+
# points that each team member has earned.
|
|
296
|
+
def calculate_team_points_by_final_rank
|
|
297
|
+
# Calculate how many points to award to each rank. When multiple teams
|
|
298
|
+
# have the same rank (e.g., two teams tie for 5th place), those teams
|
|
299
|
+
# get the average of the points available to those ranks. For example,
|
|
300
|
+
# in a 6-team bracket, the teams in 1st through 4th place get 6 through 3
|
|
301
|
+
# points respectively. The two teams in 5th get 1.5, the average of 2 and 1.
|
|
302
|
+
sorted_teams = @teams.sort_by(&:final_rank)
|
|
303
|
+
num_teams = sorted_teams.size.to_f
|
|
304
|
+
final_rank_points = KillerQueenSceneScoring::hash_of_arrays
|
|
305
|
+
|
|
306
|
+
sorted_teams.each_with_index do |team, idx|
|
|
307
|
+
final_rank_points[team.final_rank] << num_teams - idx
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
base_point_value = @config.base_point_value
|
|
311
|
+
|
|
312
|
+
sorted_teams.each do |team|
|
|
313
|
+
points_earned = final_rank_points[team.final_rank].sum /
|
|
314
|
+
final_rank_points[team.final_rank].size
|
|
315
|
+
|
|
316
|
+
# TODO: Rails.logger.info "#{team.name} finished in position #{team.final_rank}" \
|
|
317
|
+
# " and gets #{points_earned} points" \
|
|
318
|
+
# "#{" + #{base_point_value} base" if base_point_value > 0}"
|
|
319
|
+
|
|
320
|
+
team.points = points_earned + base_point_value
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Sends a GET request to `url`, treats the returned data as JSON, parses it
|
|
325
|
+
# into an object, and returns that object.
|
|
326
|
+
def send_get_request(url, params = {})
|
|
327
|
+
params[:api_key] = @api_key
|
|
328
|
+
resp = RestClient.get(url, params: params)
|
|
329
|
+
|
|
330
|
+
JSON.parse(resp, symbolize_names: true)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KillerQueenSceneScoring
|
|
4
|
+
|
|
5
|
+
class Config
|
|
6
|
+
attr_reader :base_point_value, :next_bracket, :max_players_to_count,
|
|
7
|
+
:match_values, :teams
|
|
8
|
+
|
|
9
|
+
# `config_obj` is a hash that contains the data from the config file.
|
|
10
|
+
def initialize(config_obj)
|
|
11
|
+
@base_point_value = config_obj[:base_point_value]
|
|
12
|
+
@next_bracket = config_obj[:next_bracket]
|
|
13
|
+
@max_players_to_count = config_obj[:max_players_to_count]
|
|
14
|
+
@match_values = config_obj[:match_values]
|
|
15
|
+
@teams = config_obj[:teams]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KillerQueenSceneScoring
|
|
4
|
+
|
|
5
|
+
class Match
|
|
6
|
+
attr_reader :points
|
|
7
|
+
|
|
8
|
+
# `challonge_obj` is the Challonge data for this match. `match_values` is
|
|
9
|
+
# the array from the config file that holds how many points are awarded to
|
|
10
|
+
# the teams in that match.
|
|
11
|
+
def initialize(challonge_obj, match_values)
|
|
12
|
+
@team1_id = challonge_obj[:player1_id]
|
|
13
|
+
@team2_id = challonge_obj[:player2_id]
|
|
14
|
+
|
|
15
|
+
play_order = challonge_obj[:suggested_play_order]
|
|
16
|
+
@points = match_values[play_order - 1]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns whether the team with the Challonge ID `team_id` is in this match.
|
|
20
|
+
def has_team?(team_id)
|
|
21
|
+
@team1_id == team_id || @team2_id == team_id
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KillerQueenSceneScoring
|
|
4
|
+
|
|
5
|
+
class Player
|
|
6
|
+
attr_reader :name, :scene
|
|
7
|
+
attr_accessor :points
|
|
8
|
+
|
|
9
|
+
# `config_obj` is a hash that contains the player's data from the config file.
|
|
10
|
+
def initialize(config_obj)
|
|
11
|
+
@name = config_obj[:name]
|
|
12
|
+
@scene = config_obj[:scene]
|
|
13
|
+
@points = 0.0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def hash
|
|
17
|
+
to_s.hash
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_s
|
|
21
|
+
"#{@name} (#{@scene})"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KillerQueenSceneScoring
|
|
4
|
+
|
|
5
|
+
class Scene
|
|
6
|
+
attr_accessor :name, :score, :num_players
|
|
7
|
+
|
|
8
|
+
# `name` is the name of the scene. `player_scores` is an array that holds
|
|
9
|
+
# the points that were awarded to each player in the scene.
|
|
10
|
+
def initialize(name, player_scores)
|
|
11
|
+
@name = name
|
|
12
|
+
@score = player_scores.sum
|
|
13
|
+
@num_players = player_scores.size
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
"#{@name}: #{@score} points from #{@num_players} players"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def <=>(rhs)
|
|
21
|
+
# Sort by score in descending order so the largest scores come first.
|
|
22
|
+
@score != rhs.score ? rhs.score <=> @score : @name <=> rhs.name
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KillerQueenSceneScoring
|
|
4
|
+
|
|
5
|
+
class Team
|
|
6
|
+
attr_reader :players, :id, :name, :final_rank
|
|
7
|
+
attr_accessor :points
|
|
8
|
+
|
|
9
|
+
# `challonge_obj` is the Challonge data for this team.
|
|
10
|
+
def initialize(challonge_obj)
|
|
11
|
+
@id = challonge_obj[:id]
|
|
12
|
+
@name = challonge_obj[:name]
|
|
13
|
+
@final_rank = challonge_obj[:final_rank]
|
|
14
|
+
@points = 0.0
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KillerQueenSceneScoring
|
|
4
|
+
|
|
5
|
+
class Tournament
|
|
6
|
+
attr_reader :scene_scores, :complete
|
|
7
|
+
|
|
8
|
+
# `id` can be the slug or the challonge ID of the first bracket in the
|
|
9
|
+
# tournament. If you pass a slug, and the bracket is owned by an organization,
|
|
10
|
+
# it must be of the form "<org name>-<slug>".
|
|
11
|
+
# `api_key` is your Challonge API key.
|
|
12
|
+
def initialize(id:, api_key:)
|
|
13
|
+
@brackets = []
|
|
14
|
+
@scene_scores = []
|
|
15
|
+
@loaded = false
|
|
16
|
+
@complete = false
|
|
17
|
+
@id = id
|
|
18
|
+
@api_key = api_key
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Reads the Challonge bracket with the ID that was passed to the constructor,
|
|
22
|
+
# and fills in all the data structures that represent that bracket and any
|
|
23
|
+
# later brackets in the tournament. `@complete` is also set to indicate
|
|
24
|
+
# whether the entire tournament is complete.
|
|
25
|
+
# Returns true if at least one bracket was loaded, and false otherwise.
|
|
26
|
+
def load
|
|
27
|
+
@loaded = false
|
|
28
|
+
tournament_id = @id
|
|
29
|
+
all_brackets_loaded = true
|
|
30
|
+
|
|
31
|
+
while tournament_id
|
|
32
|
+
# TODO: Rails.logger.debug "Reading the bracket \"#{tournament_id}\""
|
|
33
|
+
|
|
34
|
+
# Load the next bracket in the chain. Bail out if we can't load it.
|
|
35
|
+
bracket = Bracket.new(id: tournament_id, api_key: @api_key)
|
|
36
|
+
|
|
37
|
+
if !bracket.load
|
|
38
|
+
all_brackets_loaded = false
|
|
39
|
+
break
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Store that bracket.
|
|
43
|
+
@brackets << bracket
|
|
44
|
+
|
|
45
|
+
# For debugging purposes, log the players in each scene ->
|
|
46
|
+
scenes = KillerQueenSceneScoring::hash_of_arrays
|
|
47
|
+
|
|
48
|
+
bracket.players.each_value do |team|
|
|
49
|
+
team.each do |player|
|
|
50
|
+
scenes[player.scene] << player
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
scene_list = scenes.map do |scene, players|
|
|
55
|
+
"#{scene} has #{players.size} players: " +
|
|
56
|
+
players.map(&:name).join(", ")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# TODO: Rails.logger.info scene_list.join("\n")
|
|
60
|
+
# <- end debug logging
|
|
61
|
+
|
|
62
|
+
# Go to the next bracket in the chain.
|
|
63
|
+
tournament_id = bracket.config.next_bracket
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
return false if @brackets.empty?
|
|
67
|
+
|
|
68
|
+
# Check that all the config files have the same `max_players_to_count`.
|
|
69
|
+
values = @brackets.map { |b| b.config.max_players_to_count }
|
|
70
|
+
|
|
71
|
+
if values.count(values[0]) != values.size
|
|
72
|
+
msg = "ERROR: All brackets must have the same \"max_players_to_count\"."
|
|
73
|
+
# TODO: Rails.logger.error msg
|
|
74
|
+
raise msg
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# If we loaded all the brackets in the list of brackets, set our
|
|
78
|
+
# `complete` member based on the completed state of the last bracket.
|
|
79
|
+
# We only check the last bracket because previous brackets in the
|
|
80
|
+
# sequence are not guaranteed to be marked as complete on Challonge.
|
|
81
|
+
# For an example, see "bb3wc". The BB3 wild card bracket was not
|
|
82
|
+
# marked as complete because play stopped once it got down to 4 teams.
|
|
83
|
+
@complete = all_brackets_loaded && @brackets.last.complete?
|
|
84
|
+
|
|
85
|
+
@loaded = true
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Calculates the score for each scene in the tournament, and sets
|
|
90
|
+
# `@scene_scores` to an array of `Scene` objects.
|
|
91
|
+
# The caller must call `load`, and `load` must succeed, before calling this
|
|
92
|
+
# function.
|
|
93
|
+
def calculate_points
|
|
94
|
+
raise "The tournament was not loaded" unless @loaded
|
|
95
|
+
|
|
96
|
+
@brackets.each(&:calculate_points)
|
|
97
|
+
calculate_scene_points
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
protected
|
|
101
|
+
|
|
102
|
+
# Calculates how many points each scene has earned in the tournament.
|
|
103
|
+
# Sets `@scene_scores` to an array of `Scene` objects.
|
|
104
|
+
def calculate_scene_points
|
|
105
|
+
# Collect the scores of all players from the same scene. Since a player
|
|
106
|
+
# may be in multiple brackets, we find their greatest score across
|
|
107
|
+
# all brackets.
|
|
108
|
+
# `player_scores` is a hash from a `Player` object's hash to the `Player`
|
|
109
|
+
# object. This is a hash to make lookups easier; the keys aren't used
|
|
110
|
+
# after# this loop.
|
|
111
|
+
player_scores = @brackets.each_with_object({}) do |bracket, scores|
|
|
112
|
+
bracket.players.each_value do |team_players|
|
|
113
|
+
team_players.each do |player|
|
|
114
|
+
key = player.hash
|
|
115
|
+
|
|
116
|
+
if !scores.key?(key) || player.points > scores[key].points
|
|
117
|
+
scores[key] = player
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Assemble the scores from the players in each scene.
|
|
124
|
+
# `scene_players_scores` is a hash from a scene name to an array that
|
|
125
|
+
# holds the scores of all the players in that scene.
|
|
126
|
+
scene_players_scores = KillerQueenSceneScoring::hash_of_arrays
|
|
127
|
+
|
|
128
|
+
player_scores.each_value do |player|
|
|
129
|
+
scene_players_scores[player.scene] << player.points
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
@scene_scores = scene_players_scores.map do |scene, scores|
|
|
133
|
+
# If a scene has more players than the max number of players whose
|
|
134
|
+
# scores can be counted, drop the extra players' scores.
|
|
135
|
+
# Sort the scores for each scene in descending order, so we only
|
|
136
|
+
# keep the highest scores.
|
|
137
|
+
max_players_to_count = @brackets[0].config.max_players_to_count
|
|
138
|
+
scores.sort!.reverse!
|
|
139
|
+
|
|
140
|
+
if scores.size > max_players_to_count
|
|
141
|
+
dropped = scores.slice!(max_players_to_count..-1)
|
|
142
|
+
|
|
143
|
+
# TODO: Rails.logger.info "Dropping the #{dropped.size} lowest scores from #{scene}:" +
|
|
144
|
+
# dropped.join(", ")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Add up the scores for this scene.
|
|
148
|
+
Scene.new(scene, scores)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: killer_queen_scene_scoring
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Michael Dunn
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2019-03-15 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.17'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.17'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: minitest
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '5.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '5.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '10.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '10.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: dotenv
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '2.7'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '2.7'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: json
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '2.2'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2.2'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rest-client
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '2.0'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '2.0'
|
|
97
|
+
description: Classes that implement scene-wide scoring for Killer Queen tournaments.
|
|
98
|
+
email:
|
|
99
|
+
- acidhelm@gmail.com
|
|
100
|
+
executables:
|
|
101
|
+
- killer_queen_scene_scoring
|
|
102
|
+
extensions: []
|
|
103
|
+
extra_rdoc_files: []
|
|
104
|
+
files:
|
|
105
|
+
- ".gitignore"
|
|
106
|
+
- ".travis.yml"
|
|
107
|
+
- Gemfile
|
|
108
|
+
- Gemfile.lock
|
|
109
|
+
- LICENSE.txt
|
|
110
|
+
- README.md
|
|
111
|
+
- Rakefile
|
|
112
|
+
- bin/console
|
|
113
|
+
- bin/setup
|
|
114
|
+
- exe/killer_queen_scene_scoring
|
|
115
|
+
- killer_queen_scene_scoring.gemspec
|
|
116
|
+
- lib/killer_queen_scene_scoring.rb
|
|
117
|
+
- lib/killer_queen_scene_scoring/bracket.rb
|
|
118
|
+
- lib/killer_queen_scene_scoring/config.rb
|
|
119
|
+
- lib/killer_queen_scene_scoring/match.rb
|
|
120
|
+
- lib/killer_queen_scene_scoring/player.rb
|
|
121
|
+
- lib/killer_queen_scene_scoring/scene.rb
|
|
122
|
+
- lib/killer_queen_scene_scoring/team.rb
|
|
123
|
+
- lib/killer_queen_scene_scoring/tournament.rb
|
|
124
|
+
- lib/killer_queen_scene_scoring/version.rb
|
|
125
|
+
homepage: https://github.com/acidhelm/killer_queen_scene_scoring
|
|
126
|
+
licenses:
|
|
127
|
+
- MIT
|
|
128
|
+
metadata:
|
|
129
|
+
allowed_push_host: https://rubygems.org
|
|
130
|
+
homepage_uri: https://github.com/acidhelm/killer_queen_scene_scoring
|
|
131
|
+
source_code_uri: https://github.com/acidhelm/killer_queen_scene_scoring
|
|
132
|
+
post_install_message:
|
|
133
|
+
rdoc_options: []
|
|
134
|
+
require_paths:
|
|
135
|
+
- lib
|
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
137
|
+
requirements:
|
|
138
|
+
- - ">="
|
|
139
|
+
- !ruby/object:Gem::Version
|
|
140
|
+
version: '0'
|
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
requirements: []
|
|
147
|
+
rubygems_version: 3.0.1
|
|
148
|
+
signing_key:
|
|
149
|
+
specification_version: 4
|
|
150
|
+
summary: Scene-wide scoring for Killer Queen tournaments.
|
|
151
|
+
test_files: []
|