Merge pull request #11 from turniere/ticket/TURNIERE-69
Implement Tournament Generation
This commit is contained in:
commit
5b5562cbb6
|
|
@ -25,3 +25,9 @@
|
||||||
|
|
||||||
# IDEA
|
# IDEA
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# database schema
|
||||||
|
db/schema.rb
|
||||||
|
|
||||||
|
#Ignore Coverage
|
||||||
|
coverage/**
|
||||||
|
|
@ -17,7 +17,7 @@ Metrics/LineLength:
|
||||||
Metrics/MethodLength:
|
Metrics/MethodLength:
|
||||||
Exclude:
|
Exclude:
|
||||||
- "db/migrate/*"
|
- "db/migrate/*"
|
||||||
Max: 20
|
Max: 50
|
||||||
|
|
||||||
# The guiding principle of classes is SRP, SRP can't be accurately measured by LoC
|
# The guiding principle of classes is SRP, SRP can't be accurately measured by LoC
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
|
|
@ -29,7 +29,7 @@ Metrics/ModuleLength:
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Exclude:
|
Exclude:
|
||||||
- "db/migrate/*"
|
- "db/migrate/*"
|
||||||
Max: 20
|
Max: 50
|
||||||
|
|
||||||
Metrics/BlockLength:
|
Metrics/BlockLength:
|
||||||
ExcludedMethods:
|
ExcludedMethods:
|
||||||
|
|
|
||||||
4
Gemfile
4
Gemfile
|
|
@ -34,6 +34,10 @@ gem 'devise_token_auth'
|
||||||
|
|
||||||
gem 'rack-cors'
|
gem 'rack-cors'
|
||||||
|
|
||||||
|
# Interactors
|
||||||
|
gem 'interactor'
|
||||||
|
gem 'interactor-rails'
|
||||||
|
|
||||||
gem 'active_model_serializers'
|
gem 'active_model_serializers'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,10 @@ GEM
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
i18n (1.1.1)
|
i18n (1.1.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
|
interactor (3.1.1)
|
||||||
|
interactor-rails (2.2.0)
|
||||||
|
interactor (~> 3.0)
|
||||||
|
rails (>= 4.2, < 5.3)
|
||||||
jaro_winkler (1.5.1)
|
jaro_winkler (1.5.1)
|
||||||
json (2.1.0)
|
json (2.1.0)
|
||||||
jsonapi-renderer (0.2.0)
|
jsonapi-renderer (0.2.0)
|
||||||
|
|
@ -262,6 +266,8 @@ DEPENDENCIES
|
||||||
devise_token_auth
|
devise_token_auth
|
||||||
factory_bot_rails
|
factory_bot_rails
|
||||||
faker
|
faker
|
||||||
|
interactor
|
||||||
|
interactor-rails
|
||||||
listen (>= 3.0.5, < 3.2)
|
listen (>= 3.0.5, < 3.2)
|
||||||
puma (~> 3.11)
|
puma (~> 3.11)
|
||||||
rack-cors
|
rack-cors
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddPlayoffsToTournament
|
||||||
|
include Interactor
|
||||||
|
|
||||||
|
def call
|
||||||
|
tournament = context.tournament
|
||||||
|
context.fail! if tournament.stages.size > 1
|
||||||
|
if (playoff_stages = PlayoffStageService.generate_playoff_stages_from_tournament(tournament))
|
||||||
|
if tournament.stages.empty?
|
||||||
|
tournament.stages = playoff_stages
|
||||||
|
else
|
||||||
|
tournament.stages.concat playoff_stages
|
||||||
|
end
|
||||||
|
context.tournament = tournament
|
||||||
|
else
|
||||||
|
context.fail!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SaveTournamentToDatabase
|
||||||
|
include Interactor
|
||||||
|
|
||||||
|
def call
|
||||||
|
if context.tournament.save
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
context.fail!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Match < ApplicationRecord
|
class Match < ApplicationRecord
|
||||||
|
enum state: %i[single_team not_ready not_started in_progress team1_won team2_won undecided]
|
||||||
|
|
||||||
belongs_to :stage, optional: true
|
belongs_to :stage, optional: true
|
||||||
belongs_to :group, optional: true
|
belongs_to :group, optional: true
|
||||||
has_many :scores, dependent: :destroy
|
has_many :scores, dependent: :destroy
|
||||||
|
|
@ -14,4 +16,18 @@ class Match < ApplicationRecord
|
||||||
def stage_xor_group
|
def stage_xor_group
|
||||||
errors.add(:stage_xor_group, 'Stage and Group missing or both present') unless stage.present? ^ group.present?
|
errors.add(:stage_xor_group, 'Stage and Group missing or both present') unless stage.present? ^ group.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def evaluate_status
|
||||||
|
if score_team1 < score_team2
|
||||||
|
:team2_won
|
||||||
|
elsif score_team2 < score_team1
|
||||||
|
:team1_won
|
||||||
|
else
|
||||||
|
group_match? ? :undecided : :in_progress
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def group_match?
|
||||||
|
group.present?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddPlayoffsToTournamentAndSaveTournamentToDatabase
|
||||||
|
include Interactor::Organizer
|
||||||
|
|
||||||
|
organize AddPlayoffsToTournament, SaveTournamentToDatabase
|
||||||
|
end
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Turniere
|
|
||||||
class GroupStage
|
|
||||||
def self.generate_playoffs(teams, _tournament)
|
|
||||||
stage_count = calculate_required_stage_count(teams.size)
|
|
||||||
generate_matches(teams)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.calculate_required_stage_count(number_of_teams)
|
|
||||||
if number_of_teams.zero? || number_of_teams == 1
|
|
||||||
0
|
|
||||||
else
|
|
||||||
Math.log(Turniere::Utils.previous_power_of_two(number_of_teams)) / Math.log(2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class MatchService
|
||||||
|
def self.generate_matches(teams)
|
||||||
|
if teams.size < 2
|
||||||
|
# should be prevented by controller
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Utils.po2?(teams.size)
|
||||||
|
normal_games = teams.size / 2
|
||||||
|
needed_games = normal_games
|
||||||
|
else
|
||||||
|
normal_games = teams.size - Utils.previous_power_of_two(teams.size)
|
||||||
|
needed_games = Utils.previous_power_of_two(teams.size)
|
||||||
|
end
|
||||||
|
|
||||||
|
matches = []
|
||||||
|
while matches.size < normal_games
|
||||||
|
i = matches.size
|
||||||
|
match = Match.new state: :not_started,
|
||||||
|
position: i,
|
||||||
|
scores: [
|
||||||
|
Score.create(team: teams[2 * i]),
|
||||||
|
Score.create(team: teams[(2 * i) + 1])
|
||||||
|
]
|
||||||
|
matches << match
|
||||||
|
end
|
||||||
|
|
||||||
|
until matches.size >= needed_games
|
||||||
|
i = matches.size
|
||||||
|
match = Match.new state: :single_team, position: i, scores: [Score.create(team: teams[i])]
|
||||||
|
matches << match
|
||||||
|
end
|
||||||
|
matches
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
module Turniere
|
|
||||||
class Matches
|
|
||||||
def self.generate_matches(teams)
|
|
||||||
if teams.size == 1
|
|
||||||
return #TODO error with only one team
|
|
||||||
end
|
|
||||||
needed_games = 0
|
|
||||||
if (Turniere::Utils.po2?(teams.size())
|
|
||||||
needed_games = teams.size() / 2
|
|
||||||
else
|
|
||||||
needed_games = teams.size() - Turniere::Utils.previous_power_of_two(teams.size()) / 2
|
|
||||||
end
|
|
||||||
|
|
||||||
lastPos = 0
|
|
||||||
matches = []
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
while i < needed_games
|
|
||||||
match = Match(teams[2 * i], teams[( 2 * i ) + 1], 0, 0, :not_startet, i, false)
|
|
||||||
matches.insert match
|
|
||||||
i++
|
|
||||||
end
|
|
||||||
|
|
||||||
lastPos = i + 1
|
|
||||||
|
|
||||||
while teams.size() != 0
|
|
||||||
match = Match(teams[2 * i], teams[( 2 * i ) + 1], 0, 0, Match, i, false)
|
|
||||||
matches.insert match
|
|
||||||
end
|
|
||||||
return lastPos
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PlayoffStageService
|
||||||
|
def self.generate_playoff_stages(teams)
|
||||||
|
playoffs = []
|
||||||
|
stage_count = calculate_required_stage_count(teams.size)
|
||||||
|
initial_matches = MatchService.generate_matches(teams)
|
||||||
|
initial_stage = Stage.new level: stage_count - 1, matches: initial_matches
|
||||||
|
playoffs << initial_stage
|
||||||
|
empty_stages = generate_stages_with_empty_matches(stage_count - 1)
|
||||||
|
empty_stages.each do |stage|
|
||||||
|
playoffs << stage
|
||||||
|
end
|
||||||
|
playoffs
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.generate_playoff_stages_from_tournament(tournament)
|
||||||
|
generate_playoff_stages tournament.teams
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.generate_stages_with_empty_matches(stage_count)
|
||||||
|
empty_stages = []
|
||||||
|
stage_count.times do |i|
|
||||||
|
stage = Stage.new level: i, matches: generate_empty_matches(2**i)
|
||||||
|
empty_stages << stage
|
||||||
|
end
|
||||||
|
empty_stages.reverse!
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.generate_empty_matches(amount)
|
||||||
|
matches = []
|
||||||
|
amount.times do |i|
|
||||||
|
match = Match.new state: :not_ready, position: i
|
||||||
|
matches << match
|
||||||
|
end
|
||||||
|
matches
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.calculate_required_stage_count(number_of_teams)
|
||||||
|
if number_of_teams.zero? || number_of_teams == 1
|
||||||
|
0
|
||||||
|
else
|
||||||
|
stage_count = Math.log(Utils.previous_power_of_two(number_of_teams)) / Math.log(2)
|
||||||
|
stage_count.to_int
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Utils
|
||||||
|
def self.previous_power_of_two(number)
|
||||||
|
return 0 if number.zero?
|
||||||
|
|
||||||
|
exponent = Math.log2 number
|
||||||
|
2**exponent.floor
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.next_power_of_two(number)
|
||||||
|
return 1 if number.zero?
|
||||||
|
|
||||||
|
2 * previous_power_of_two(number)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.po2?(number)
|
||||||
|
number.to_s(2).count('1') == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -76,6 +76,7 @@ class CreateSchema < ActiveRecord::Migration[5.2]
|
||||||
|
|
||||||
create_table :matches do |t|
|
create_table :matches do |t|
|
||||||
t.integer :state, default: 0
|
t.integer :state, default: 0
|
||||||
|
t.integer :position
|
||||||
|
|
||||||
t.belongs_to :stage, index: true, foreign_key: { on_delete: :cascade }
|
t.belongs_to :stage, index: true, foreign_key: { on_delete: :cascade }
|
||||||
t.belongs_to :group, index: true, foreign_key: { on_delete: :cascade }
|
t.belongs_to :group, index: true, foreign_key: { on_delete: :cascade }
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ RSpec.describe MatchesController, type: :controller do
|
||||||
it 'should return the correct state' do
|
it 'should return the correct state' do
|
||||||
get :show, params: { id: @match.to_param }
|
get :show, params: { id: @match.to_param }
|
||||||
body = ActiveModelSerializers::Deserialization.jsonapi_parse(JSON.parse(response.body))
|
body = ActiveModelSerializers::Deserialization.jsonapi_parse(JSON.parse(response.body))
|
||||||
expect(body[:state]).to be(@match.state)
|
expect(body[:state]).to eq(@match.state)
|
||||||
expect(body[:score_ids]).to eq(@match.scores.map { |score| score.id.to_s })
|
expect(body[:score_ids]).to eq(@match.scores.map { |score| score.id.to_s })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :stage_match, aliases: [:match], class: Match do
|
factory :playoff_match, aliases: [:match], class: Match do
|
||||||
stage
|
stage
|
||||||
|
factory :running_playoff_match do
|
||||||
|
transient do
|
||||||
|
scores_count { 2 }
|
||||||
|
end
|
||||||
|
after(:create) do |match, evaluator|
|
||||||
|
match.scores = create_list(:score, evaluator.scores_count)
|
||||||
|
end
|
||||||
|
state { 3 }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
factory :group_match, class: Match do
|
factory :group_match, class: Match do
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :score do
|
factory :score do
|
||||||
score { 0 }
|
score { rand(0..10) }
|
||||||
match
|
match
|
||||||
team
|
team
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,19 @@ FactoryBot.define do
|
||||||
name { Faker::Dog.name }
|
name { Faker::Dog.name }
|
||||||
description { Faker::Lorem.sentence }
|
description { Faker::Lorem.sentence }
|
||||||
user
|
user
|
||||||
|
transient do
|
||||||
|
teams_count { 16 }
|
||||||
|
end
|
||||||
|
after(:create) do |tournament, evaluator|
|
||||||
|
tournament.teams = create_list(:team, evaluator.teams_count, tournament: tournament)
|
||||||
|
end
|
||||||
|
factory :stage_tournament do
|
||||||
|
transient do
|
||||||
|
stage_count { 1 }
|
||||||
|
end
|
||||||
|
after(:create) do |tournament, evaluator|
|
||||||
|
tournament.stages = create_list(:stage, evaluator.stage_count)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :user do
|
factory :user do
|
||||||
username { Faker::Internet.username }
|
username { Faker::Internet.unique.username }
|
||||||
email { Faker::Internet.email }
|
email { Faker::Internet.unique.email }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe AddPlayoffsToTournament do
|
||||||
|
let(:group_stage_tournament_context) do
|
||||||
|
AddPlayoffsToTournament.call(tournament: @group_stage_tournament)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:playoff_stage_tournament_context) do
|
||||||
|
AddPlayoffsToTournament.call(tournament: @playoff_stage_tournament)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:full_tournament_context) do
|
||||||
|
AddPlayoffsToTournament.call(tournament: @full_tournament)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
@group_stage_tournament = create(:stage_tournament)
|
||||||
|
@playoff_stage_tournament = create(:tournament)
|
||||||
|
@full_tournament = create(:stage_tournament, stage_count: 5)
|
||||||
|
@stages = create_list(:stage, 5)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'ez lyfe' do
|
||||||
|
before do
|
||||||
|
expect(class_double('PlayoffStageService').as_stubbed_const(transfer_nested_constants: true))
|
||||||
|
.to receive(:generate_playoff_stages_from_tournament)
|
||||||
|
.and_return(@stages)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Playoff only tournament' do
|
||||||
|
it 'succeeds' do
|
||||||
|
expect(playoff_stage_tournament_context).to be_a_success
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds playoffs to the tournament' do
|
||||||
|
test = playoff_stage_tournament_context.tournament.stages
|
||||||
|
expect(test).to match_array(@stages)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'GroupStage tournament' do
|
||||||
|
it 'succeeds' do
|
||||||
|
expect(group_stage_tournament_context).to be_a_success
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds playoffs to the tournament' do
|
||||||
|
test = group_stage_tournament_context.tournament.stages[1..-1]
|
||||||
|
expect(test).to match_array(@stages)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'playoff generation fails' do
|
||||||
|
before do
|
||||||
|
expect(class_double('PlayoffStageService').as_stubbed_const(transfer_nested_constants: true))
|
||||||
|
.to receive(:generate_playoff_stages_from_tournament)
|
||||||
|
.and_return(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails' do
|
||||||
|
test = playoff_stage_tournament_context.failure?
|
||||||
|
expect(test).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Tournament where playoffs are already generated' do
|
||||||
|
it 'does not add playoff stages' do
|
||||||
|
test = full_tournament_context.failure?
|
||||||
|
expect(test).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe SaveTournamentToDatabase do
|
||||||
|
before do
|
||||||
|
@tournament = create(:tournament)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'save succeeds' do
|
||||||
|
let(:context) do
|
||||||
|
SaveTournamentToDatabase.call(tournament: @tournament)
|
||||||
|
end
|
||||||
|
before do
|
||||||
|
expect_any_instance_of(Tournament)
|
||||||
|
.to receive(:save).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'succeeds' do
|
||||||
|
expect(context).to be_a_success
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'provides the tournament' do
|
||||||
|
expect(context.tournament).to eq(@tournament)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'save fails' do
|
||||||
|
let(:context) do
|
||||||
|
SaveTournamentToDatabase.call(tournament: @tournament)
|
||||||
|
end
|
||||||
|
before do
|
||||||
|
expect_any_instance_of(Tournament)
|
||||||
|
.to receive(:save).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails' do
|
||||||
|
test = context.failure?
|
||||||
|
expect(test).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe MatchService do
|
||||||
|
describe '#generate_matches' do
|
||||||
|
[
|
||||||
|
{ team_size: 2 },
|
||||||
|
{ team_size: 4 },
|
||||||
|
{ team_size: 8 },
|
||||||
|
{ team_size: 16 },
|
||||||
|
{ team_size: 32 },
|
||||||
|
{ team_size: 64 }
|
||||||
|
].each do |parameters|
|
||||||
|
result = parameters[:team_size] / 2
|
||||||
|
it "generates #{result} matches from #{parameters[:team_size]} teams" do
|
||||||
|
teams = build_list(:team, parameters[:team_size], tournament: create(:tournament))
|
||||||
|
generated_matches = MatchService.generate_matches teams
|
||||||
|
expect(generated_matches.size).to eq(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
[
|
||||||
|
{ team_size: 3, result: 2 },
|
||||||
|
{ team_size: 5, result: 4 },
|
||||||
|
{ team_size: 6, result: 4 },
|
||||||
|
{ team_size: 7, result: 4 },
|
||||||
|
{ team_size: 12, result: 8 },
|
||||||
|
{ team_size: 17, result: 16 },
|
||||||
|
{ team_size: 18, result: 16 },
|
||||||
|
{ team_size: 19, result: 16 },
|
||||||
|
{ team_size: 22, result: 16 },
|
||||||
|
{ team_size: 45, result: 32 },
|
||||||
|
{ team_size: 87, result: 64 },
|
||||||
|
{ team_size: 102, result: 64 },
|
||||||
|
{ team_size: 111, result: 64 },
|
||||||
|
{ team_size: 124, result: 64 },
|
||||||
|
{ team_size: 132, result: 128 },
|
||||||
|
{ team_size: 255, result: 128 }
|
||||||
|
].each do |parameters|
|
||||||
|
it "generates #{parameters[:result]} matches from #{parameters[:team_size]} teams" do
|
||||||
|
teams = build_list(:team, parameters[:team_size], tournament: create(:tournament))
|
||||||
|
generated_matches = MatchService.generate_matches teams
|
||||||
|
expect(generated_matches.size).to eq(parameters[:result])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
[
|
||||||
|
{ team_size: 2 },
|
||||||
|
{ team_size: 4 },
|
||||||
|
{ team_size: 8 },
|
||||||
|
{ team_size: 16 },
|
||||||
|
{ team_size: 32 },
|
||||||
|
{ team_size: 64 },
|
||||||
|
{ team_size: 128 },
|
||||||
|
{ team_size: 256 }
|
||||||
|
|
||||||
|
].each do |parameters|
|
||||||
|
it "matches the right teams for powers of 2 (#{parameters[:team_size]})" do
|
||||||
|
teams = build_list(:team, parameters[:team_size], tournament: create(:tournament))
|
||||||
|
generated_matches = MatchService.generate_matches teams
|
||||||
|
generated_matches.each_index do |index|
|
||||||
|
match = generated_matches[index]
|
||||||
|
first_team = match.scores.first.team.name
|
||||||
|
second_team = match.scores.second.team.name
|
||||||
|
expect(first_team).to eq(teams[2 * index].name)
|
||||||
|
expect(second_team).to eq(teams[2 * index + 1].name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: matches right teams for !powers of 2
|
||||||
|
|
||||||
|
[
|
||||||
|
{ team_size: 3, single_team_matches: 1 },
|
||||||
|
{ team_size: 5, single_team_matches: 3 },
|
||||||
|
{ team_size: 6, single_team_matches: 2 },
|
||||||
|
{ team_size: 17, single_team_matches: 15 },
|
||||||
|
{ team_size: 34, single_team_matches: 30 },
|
||||||
|
{ team_size: 65, single_team_matches: 63 },
|
||||||
|
{ team_size: 138, single_team_matches: 118 },
|
||||||
|
{ team_size: 276, single_team_matches: 236 }
|
||||||
|
|
||||||
|
].each do |parameters|
|
||||||
|
team_size = parameters[:team_size]
|
||||||
|
single_team_matches = parameters[:single_team_matches]
|
||||||
|
it "generates #{single_team_matches} empty matches for #{team_size} teams" do
|
||||||
|
teams = build_list(:team, team_size, tournament: create(:tournament))
|
||||||
|
generated_matches = MatchService.generate_matches teams
|
||||||
|
filtered_matches = generated_matches.select(&:single_team?)
|
||||||
|
expected_single_team_matches_size = single_team_matches
|
||||||
|
expect(filtered_matches.size).to eq(expected_single_team_matches_size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'generates no matches for 0 teams' do
|
||||||
|
expect(MatchService.generate_matches([])). to eq(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'generates no matches for 1 team' do
|
||||||
|
expect(MatchService.generate_matches(build_list(:team, 1))). to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe PlayoffStageService do
|
||||||
|
describe '#generate_empty_matches' do
|
||||||
|
[
|
||||||
|
{ amount: 1 },
|
||||||
|
{ amount: 3 },
|
||||||
|
{ amount: 4 },
|
||||||
|
{ amount: 7 },
|
||||||
|
{ amount: 23 },
|
||||||
|
{ amount: 33 },
|
||||||
|
{ amount: 82 },
|
||||||
|
{ amount: 359 }
|
||||||
|
].each do |parameters|
|
||||||
|
it "generates #{parameters[:amount]} empty matches" do
|
||||||
|
amount = parameters[:amount]
|
||||||
|
generated_matches = PlayoffStageService.generate_empty_matches amount
|
||||||
|
generated_matches.each_index do |i|
|
||||||
|
expect(generated_matches[i].not_ready?).to eq(true)
|
||||||
|
expect(generated_matches[i].position).to eq(i)
|
||||||
|
end
|
||||||
|
expect(generated_matches.size).to eq(amount)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#generate_stages_with_empty_matches' do
|
||||||
|
[
|
||||||
|
{ stages: 1 },
|
||||||
|
{ stages: 2 },
|
||||||
|
{ stages: 3 },
|
||||||
|
{ stages: 4 },
|
||||||
|
{ stages: 5 },
|
||||||
|
{ stages: 6 },
|
||||||
|
{ stages: 7 },
|
||||||
|
{ stages: 8 },
|
||||||
|
{ stages: 9 },
|
||||||
|
{ stages: 10 }
|
||||||
|
].each do |parameters|
|
||||||
|
it "generates #{parameters[:stages]} stages with matches provided by #generate_empty_matches" do
|
||||||
|
amount_of_empty_stages = parameters[:stages]
|
||||||
|
empty_stages = PlayoffStageService.generate_stages_with_empty_matches(amount_of_empty_stages)
|
||||||
|
expect(empty_stages.size).to eq(amount_of_empty_stages)
|
||||||
|
empty_stages.each_index do |i|
|
||||||
|
empty_stage = empty_stages[i]
|
||||||
|
expected_empty_stages_size = empty_stages.size - 1 - i
|
||||||
|
expect(empty_stage.level).to eq(expected_empty_stages_size)
|
||||||
|
expect(empty_stage.matches.size).to eq(2**expected_empty_stages_size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#generate_playoffs' do
|
||||||
|
[
|
||||||
|
{ team_size: 4, expected_amount_of_playoff_stages: 2 },
|
||||||
|
{ team_size: 8, expected_amount_of_playoff_stages: 3 },
|
||||||
|
{ team_size: 16, expected_amount_of_playoff_stages: 4 },
|
||||||
|
{ team_size: 24, expected_amount_of_playoff_stages: 4 },
|
||||||
|
{ team_size: 32, expected_amount_of_playoff_stages: 5 },
|
||||||
|
{ team_size: 64, expected_amount_of_playoff_stages: 6 },
|
||||||
|
{ team_size: 111, expected_amount_of_playoff_stages: 6 }
|
||||||
|
].each do |parameters|
|
||||||
|
it "generates playoff stages for #{parameters[:team_size]} teams" do
|
||||||
|
amount_of_teams = parameters[:team_size]
|
||||||
|
expected_amount_of_playoff_stages = parameters[:expected_amount_of_playoff_stages]
|
||||||
|
teams = build_list(:team, amount_of_teams)
|
||||||
|
stages = PlayoffStageService.generate_playoff_stages(teams)
|
||||||
|
expect(stages.size).to eq(expected_amount_of_playoff_stages)
|
||||||
|
stages.each_index do |i|
|
||||||
|
stage = stages[i]
|
||||||
|
stage_level = stages.size - i - 1
|
||||||
|
expect(stage.level).to eq stage_level
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Utils do
|
||||||
|
[
|
||||||
|
{ test: 5, result: 4 },
|
||||||
|
{ test: 3, result: 2 },
|
||||||
|
{ test: 13, result: 8 },
|
||||||
|
{ test: 35, result: 32 },
|
||||||
|
{ test: 32, result: 32 },
|
||||||
|
{ test: 0, result: 0 },
|
||||||
|
{ test: 3482, result: 2048 },
|
||||||
|
{ test: 1337, result: 1024 }
|
||||||
|
].each do |parameters|
|
||||||
|
it "calculates #{parameters[:result]} as previous power of two from #{parameters[:test]}" do
|
||||||
|
expect(Utils.previous_power_of_two(parameters[:test])).to eq(parameters[:result])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
[
|
||||||
|
{ test: 5, result: 8 },
|
||||||
|
{ test: 3, result: 4 },
|
||||||
|
{ test: 13, result: 16 },
|
||||||
|
{ test: 35, result: 64 },
|
||||||
|
{ test: 32, result: 64 },
|
||||||
|
{ test: 0, result: 1 },
|
||||||
|
{ test: 3482, result: 4096 },
|
||||||
|
{ test: 1337, result: 2048 }
|
||||||
|
].each do |parameters|
|
||||||
|
it "calculates #{parameters[:result]} as previous power of two from #{parameters[:test]}" do
|
||||||
|
expect(Utils.next_power_of_two(parameters[:test])).to eq(parameters[:result])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
[
|
||||||
|
{ test: 5, result: false },
|
||||||
|
{ test: 3, result: false },
|
||||||
|
{ test: 16, result: true },
|
||||||
|
{ test: 4, result: true },
|
||||||
|
{ test: 32, result: true },
|
||||||
|
{ test: 0, result: false },
|
||||||
|
{ test: 3482, result: false },
|
||||||
|
{ test: 8192, result: true }
|
||||||
|
].each do |parameters|
|
||||||
|
is_isnt = "isn't" unless parameters[:result]
|
||||||
|
is_isnt = 'is' if parameters[:result]
|
||||||
|
it "thinks #{parameters[:test]} #{is_isnt} a power of two" do
|
||||||
|
expect(Utils.po2?(parameters[:test])).to eq(parameters[:result])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -22,6 +22,10 @@ RSpec.configure do |config|
|
||||||
# rspec-expectations config goes here. You can use an alternate
|
# rspec-expectations config goes here. You can use an alternate
|
||||||
# assertion/expectation library such as wrong or the stdlib/minitest
|
# assertion/expectation library such as wrong or the stdlib/minitest
|
||||||
# assertions if you prefer.
|
# assertions if you prefer.
|
||||||
|
|
||||||
|
# only runs tests with " , focus: true "
|
||||||
|
# config.filter_run focus: true
|
||||||
|
|
||||||
config.expect_with :rspec do |expectations|
|
config.expect_with :rspec do |expectations|
|
||||||
# This option will default to `true` in RSpec 4. It makes the `description`
|
# This option will default to `true` in RSpec 4. It makes the `description`
|
||||||
# and `failure_message` of custom matchers include text for helper methods
|
# and `failure_message` of custom matchers include text for helper methods
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue