Merge branch 'first_vs_second_with_offset' into 'master'
First vs second with offset See merge request turniere/turniere-backend!29
This commit is contained in:
commit
b1cc300d98
1
Gemfile
1
Gemfile
|
|
@ -43,7 +43,6 @@ gem 'active_model_serializers'
|
|||
gem 'mailgun-ruby'
|
||||
|
||||
group :test, optional: true do
|
||||
gem 'coveralls', require: false
|
||||
gem 'factory_bot_rails'
|
||||
gem 'faker'
|
||||
gem 'rspec-rails'
|
||||
|
|
|
|||
18
Gemfile.lock
18
Gemfile.lock
|
|
@ -91,12 +91,6 @@ GEM
|
|||
case_transform (0.2)
|
||||
activesupport
|
||||
concurrent-ruby (1.2.3)
|
||||
coveralls (0.8.23)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov (~> 0.16.1)
|
||||
term-ansicolor (~> 1.3)
|
||||
thor (>= 0.19.4, < 2.0)
|
||||
tins (~> 1.6)
|
||||
crass (1.0.6)
|
||||
date (3.3.4)
|
||||
devise (4.9.3)
|
||||
|
|
@ -106,7 +100,6 @@ GEM
|
|||
responders
|
||||
warden (~> 1.2.3)
|
||||
diff-lcs (1.5.1)
|
||||
docile (1.4.0)
|
||||
domain_name (0.6.20240107)
|
||||
e2mmap (0.1.0)
|
||||
erubi (1.12.0)
|
||||
|
|
@ -277,11 +270,6 @@ GEM
|
|||
ruby-progressbar (1.13.0)
|
||||
shoulda-matchers (6.2.0)
|
||||
activesupport (>= 5.2.0)
|
||||
simplecov (0.16.1)
|
||||
docile (~> 1.1)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
solargraph (0.50.0)
|
||||
backport (~> 1.2)
|
||||
benchmark
|
||||
|
|
@ -305,14 +293,9 @@ GEM
|
|||
sqlite3 (1.7.3-aarch64-linux)
|
||||
sqlite3 (1.7.3-arm64-darwin)
|
||||
sqlite3 (1.7.3-x86_64-linux)
|
||||
sync (0.5.0)
|
||||
term-ansicolor (1.7.2)
|
||||
tins (~> 1.0)
|
||||
thor (1.3.1)
|
||||
tilt (2.3.0)
|
||||
timeout (0.4.1)
|
||||
tins (1.32.1)
|
||||
sync
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
|
|
@ -334,7 +317,6 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
active_model_serializers
|
||||
bootsnap
|
||||
coveralls
|
||||
devise
|
||||
devise_token_auth!
|
||||
factory_bot_rails
|
||||
|
|
|
|||
|
|
@ -72,3 +72,4 @@ $ rails diagram:all_with_engines
|
|||
- WICHTIG UND EZ: gruppenphase in der gleichen gruppe sollten erst finale gegeneinander spielen (dazu nicht aus der nächsten gruppe sondern einmal advancing teams von vorne und einmal von hinten, oder offset von hälfte der weiterkommenden teams)
|
||||
- beim eintragen einer runde direkt den nächsten tisch anzeigen
|
||||
- spiel um platz 3
|
||||
- edgecase wenn mehr als die hälfte der teams weiterkommen bedenken bzw zumindest abfangen
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class TournamentsController < ApplicationController
|
|||
end
|
||||
|
||||
def organize_teams_in_groups(teams)
|
||||
# each team gets put into a array of teams depending on the group specified in team[:group]
|
||||
# each team gets put into an array of teams depending on the group specified in team[:group]
|
||||
teams.group_by { |team| team['group'] }.values.map do |group|
|
||||
group.map do |team|
|
||||
find_or_create_team(team)
|
||||
|
|
|
|||
|
|
@ -102,39 +102,69 @@ class GroupStageService
|
|||
# @param group_stage GroupStage the group stage to get all advancing teams from
|
||||
# @return [Array] the teams advancing from that group stage
|
||||
def get_advancing_teams(group_stage)
|
||||
advancing_teams = []
|
||||
teams_per_group_ranked = group_stage.groups.map(&method(:teams_sorted_by_group_scores))
|
||||
# teams_per_group_ranked is a 2D array
|
||||
# [
|
||||
# [ group_a_first, group_a_second, ... ],
|
||||
# [ group_b_first, group_b_second, ... ],
|
||||
# ...
|
||||
# ]
|
||||
advancing_teams_amount = group_stage.tournament.instant_finalists_amount +
|
||||
group_stage.tournament.intermediate_round_participants_amount
|
||||
advancing_teams_amount = calculate_advancing_teams_amount(group_stage)
|
||||
tournament_teams_amount = group_stage.tournament.teams.size
|
||||
|
||||
# special case for po2 teams in tournament and half of them advancing:
|
||||
# we want to match first of first group with second of second group and so on
|
||||
if Utils.po2?(tournament_teams_amount) and advancing_teams_amount * 2 == tournament_teams_amount
|
||||
teams_per_group_ranked.each_with_index do |_group_teams, i|
|
||||
first = teams_per_group_ranked[i % teams_per_group_ranked.size][0]
|
||||
second = teams_per_group_ranked[(i + 1) % teams_per_group_ranked.size][1]
|
||||
advancing_teams << first
|
||||
advancing_teams << second
|
||||
end
|
||||
# default case
|
||||
if special_case_for_po2?(tournament_teams_amount, advancing_teams_amount)
|
||||
handle_special_case(teams_per_group_ranked)
|
||||
else
|
||||
advancing_teams_amount.times do |i|
|
||||
# we want to take the first team of the first group, then the first team of the second group, ...
|
||||
advancing_teams << teams_per_group_ranked[i % group_stage.groups.size].shift
|
||||
handle_default_case(teams_per_group_ranked, advancing_teams_amount, group_stage.groups.size)
|
||||
end
|
||||
end
|
||||
advancing_teams
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Calculates the total number of teams advancing to the playoff stage
|
||||
#
|
||||
# @param group_stage GroupStage the group stage to get the advancing teams amount from
|
||||
# @return [Integer] the number of teams advancing from that group stage
|
||||
def calculate_advancing_teams_amount(group_stage)
|
||||
group_stage.tournament.instant_finalists_amount +
|
||||
group_stage.tournament.intermediate_round_participants_amount
|
||||
end
|
||||
|
||||
# Checks if the special case for po2 teams in the tournament applies
|
||||
#
|
||||
# @param tournament_teams_amount [Integer] the total number of teams in the tournament
|
||||
# @param advancing_teams_amount [Integer] the number of teams advancing to the playoff stage
|
||||
# @return [Boolean] true if the special case applies, false otherwise
|
||||
def special_case_for_po2?(tournament_teams_amount, advancing_teams_amount)
|
||||
Utils.po2?(tournament_teams_amount) && advancing_teams_amount * 2 == tournament_teams_amount
|
||||
end
|
||||
|
||||
# Handles the special case for po2 teams in the tournament
|
||||
#
|
||||
# @param teams_per_group_ranked [Array] a 2D array of teams ranked by group scores
|
||||
# @return [Array] the teams advancing from the group stage
|
||||
def handle_special_case(teams_per_group_ranked)
|
||||
# transpose the array to group first and second places together
|
||||
# e.g. [[1, 2, 3], [4, 5, 6]] to [[1, 4], [2, 5], [3, 6]]
|
||||
teams_per_group_ranked_transposed = teams_per_group_ranked.transpose
|
||||
first_places = teams_per_group_ranked_transposed[0]
|
||||
second_places = teams_per_group_ranked_transposed[1]
|
||||
|
||||
second_places_new_order = Utils.split_and_rotate(second_places)
|
||||
|
||||
# zip the first and second places together
|
||||
# e.g. [1, 2, 3], [a, b, c] to [1, a, 2, b, 3, c]
|
||||
first_places.zip(second_places_new_order).flatten
|
||||
end
|
||||
|
||||
# Handles the default case for advancing teams
|
||||
#
|
||||
# @param teams_per_group_ranked [Array] a 2D array of teams ranked by group scores
|
||||
# @param advancing_teams_amount [Integer] the number of teams advancing to the playoff stage
|
||||
# @param groups_size [Integer] the number of groups in the group stage
|
||||
# @return [Array] the teams advancing from the group stage
|
||||
def handle_default_case(teams_per_group_ranked, advancing_teams_amount, groups_size)
|
||||
advancing_teams = []
|
||||
advancing_teams_amount.times do |i|
|
||||
advancing_teams << teams_per_group_ranked[i % groups_size].shift
|
||||
end
|
||||
advancing_teams
|
||||
end
|
||||
|
||||
def recalculate_position_of_group_scores!(group_scores)
|
||||
group_scores = group_scores.sort
|
||||
|
||||
|
|
|
|||
|
|
@ -29,4 +29,17 @@ class Utils
|
|||
def self.po2?(number)
|
||||
number.to_s(2).count('1') == 1
|
||||
end
|
||||
|
||||
# split the array in half and place the second half at the beginning
|
||||
# e.g. [1, 2, 3, 4, 5, 6] to [4, 5, 6, 1, 2, 3]
|
||||
def self.split_and_rotate(array)
|
||||
# handle the case where the array has an odd number of elements
|
||||
middle_element = []
|
||||
if array.length.odd?
|
||||
# pop the last element and place it in the middle
|
||||
middle_element = [array.pop]
|
||||
end
|
||||
mid = array.length / 2
|
||||
array[mid..] + middle_element + array[0..(mid - 1)]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,13 +7,23 @@ FactoryBot.define do
|
|||
user
|
||||
transient do
|
||||
teams_count { 8 }
|
||||
teams { nil }
|
||||
playoff_teams_amount { 4 }
|
||||
instant_finalists_amount { 4 }
|
||||
intermediate_round_participants_amount { 0 }
|
||||
end
|
||||
after(:create) do |tournament, evaluator|
|
||||
if evaluator.teams.present?
|
||||
tournament.teams = evaluator.teams
|
||||
else
|
||||
tournament.teams = create_list(:team, evaluator.teams_count, tournament: tournament)
|
||||
tournament.playoff_teams_amount = (tournament.teams.size / 2)
|
||||
tournament.instant_finalists_amount = (tournament.playoff_teams_amount / 2)
|
||||
tournament.intermediate_round_participants_amount = ((tournament.playoff_teams_amount -
|
||||
tournament.instant_finalists_amount) * 2)
|
||||
end
|
||||
tournament.playoff_teams_amount = evaluator.playoff_teams_amount
|
||||
tournament.instant_finalists_amount = evaluator.instant_finalists_amount
|
||||
tournament.intermediate_round_participants_amount = evaluator.intermediate_round_participants_amount
|
||||
if tournament.playoff_teams_amount != tournament.instant_finalists_amount + tournament.intermediate_round_participants_amount / 2
|
||||
raise 'playoff_teams_amount must be equal to instant_finalists_amount + intermediate_round_participants_amount / 2'
|
||||
end
|
||||
tournament.save!
|
||||
end
|
||||
|
||||
|
|
@ -52,6 +62,15 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
factory :prepared_group_stage_tournament do
|
||||
transient do
|
||||
group_stage { create(:group_stage) }
|
||||
end
|
||||
after(:create) do |tournament, evaluator|
|
||||
tournament.stages << evaluator.group_stage
|
||||
end
|
||||
end
|
||||
|
||||
factory :dummy_stage_tournament do
|
||||
transient do
|
||||
stage_count { 3 }
|
||||
|
|
|
|||
|
|
@ -190,4 +190,86 @@ RSpec.describe GroupStageService do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_advancing_teams' do
|
||||
context 'when special case for po2 applies' do
|
||||
before do
|
||||
teams = create_list(:team, 32)
|
||||
|
||||
# put the teams in groups of four
|
||||
groups = teams.each_slice(4).to_a
|
||||
|
||||
# iterate over all groups and number the teams in their name
|
||||
groups.each_with_index do |group, group_index|
|
||||
group.each_with_index do |team, team_index|
|
||||
team.name = "#{team.name} #{group_index} #{team_index}"
|
||||
team.save!
|
||||
end
|
||||
end
|
||||
|
||||
# Generate the group stage
|
||||
@group_stage = GroupStageService.generate_group_stage(groups)
|
||||
|
||||
@tournament = create(:prepared_group_stage_tournament,
|
||||
group_stage: @group_stage,
|
||||
teams: teams,
|
||||
playoff_teams_amount: 16,
|
||||
instant_finalists_amount: 16,
|
||||
intermediate_round_participants_amount: 0)
|
||||
# iterate over all groups and update the matches within to all be decided
|
||||
@group_stage.groups.each do |group|
|
||||
group.matches.each do |match|
|
||||
match.match_scores.each do |ms|
|
||||
# give the team 10 points minus the number in their name
|
||||
# this results in the team 0 always winning and getting to place 1 in the group etc.
|
||||
ms.points = 10 - ms.team.name.split(' ').last.to_i
|
||||
ms.save!
|
||||
end
|
||||
match.state = 'finished'
|
||||
match.save!
|
||||
end
|
||||
group_scores = GroupStageService.update_group_scores(group)
|
||||
group_scores.each(&:save!)
|
||||
|
||||
group.group_scores.each(&:reload)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the correct amount of teams' do
|
||||
advancing_teams = GroupStageService.get_advancing_teams(@group_stage)
|
||||
expect(advancing_teams.size).to be(16)
|
||||
end
|
||||
|
||||
it 'returns the correct teams in the correct order' do
|
||||
advancing_teams = GroupStageService.get_advancing_teams(@group_stage)
|
||||
advancing_teams.each_with_index do |team, i|
|
||||
# if index is even, the team should be of a first place; end in a 0
|
||||
# if index is odd, the team should be of a second place; end in a 1
|
||||
team_quality = team.name.split(' ').last.to_i
|
||||
expect(team_quality % 2).to be(i % 2)
|
||||
end
|
||||
end
|
||||
|
||||
it 'spaces groups apart, so you meet your group only in finale' do
|
||||
group_first_matchups_expected = {
|
||||
0 => 4,
|
||||
1 => 5,
|
||||
2 => 6,
|
||||
3 => 7,
|
||||
4 => 0,
|
||||
5 => 1,
|
||||
6 => 2,
|
||||
7 => 3
|
||||
}
|
||||
advancing_teams = GroupStageService.get_advancing_teams(@group_stage)
|
||||
advancing_teams.each_slice(2).to_a.each do |matchup|
|
||||
# this is the team that landed a first place in the group
|
||||
first_place_team = matchup[0].name.split(' ')[1].to_i
|
||||
# this is the team that landed a second place in the group
|
||||
second_place_team = matchup[1].name.split(' ')[1].to_i
|
||||
expect(group_first_matchups_expected[first_place_team]).to eq(second_place_team)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,4 +47,20 @@ RSpec.describe Utils do
|
|||
expect(Utils.po2?(parameters[:test])).to eq(parameters[:result])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#split_and_rotate' do
|
||||
[
|
||||
{ test: [1, 2, 3, 4, 5, 6], result: [4, 5, 6, 1, 2, 3] },
|
||||
{ test: [1, 2, 3, 4, 5], result: [3, 4, 5, 1, 2] },
|
||||
{ test: [1, 2, 3, 4], result: [3, 4, 1, 2] },
|
||||
{ test: [1, 2, 3], result: [2, 3, 1] },
|
||||
{ test: [1, 2], result: [2, 1] },
|
||||
{ test: [1], result: [1] },
|
||||
{ test: [], result: [] }
|
||||
].each do |parameters|
|
||||
it "splits and rotates #{parameters[:test]} to #{parameters[:result]}" do
|
||||
expect(Utils.split_and_rotate(parameters[:test])).to eq(parameters[:result])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'coveralls'
|
||||
Coveralls.wear!('rails')
|
||||
|
||||
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
|
||||
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
||||
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
||||
|
|
|
|||
Loading…
Reference in New Issue