ozfortress / tournament-system

A ruby gem that implements various tournament systems.
MIT License
18 stars 9 forks source link

Documentation question: workflow after generating #18

Open emersonthis opened 3 weeks ago

emersonthis commented 3 weeks ago

The docs explain how to generate a round of matches for a tournament. This seems straightforward for creating the first round of matches in a new tournament. For most formats (other than round robin) the subsequent rounds depend on who wins in the previous. I'm trying to understand how the additional rounds are meant to be created. Is it expected for the application to run generate repeatedly after each match is completed?

emersonthis commented 3 weeks ago

I also want to understand how the implementor is meant to associate the matches with one another and/or the overall tournament. For example, if the application has a page that shows the tournament bracket, how would the implementor know which match is in which round?

emersonthis commented 3 weeks ago

For concreteness, here is the rspec test I'm writing while figuring out this library...

RSpec.describe PcslDriver, type: :model do
  let(:season) { create(:season) }
  let!(:team1) { Team.create!(name: 'Team 1', season:) }
  let!(:team2) { Team.create!(name: 'Team 2', season:) }
  let!(:team3) { Team.create!(name: 'Team 3', season:) }
  let!(:team4) { Team.create!(name: 'Team 4', season:) }

  fit 'creates matches' do
    driver = described_class.new season

    rounds_count = TournamentSystem::RoundRobin.total_rounds(driver)
    rounds_count.times do |i|
      TournamentSystem::RoundRobin.generate driver, { round: i }
      puts "Round #{i + 1}"
      season.team_matches.each do |match|
        puts "#{match.side_a.name} vs #{match.side_b.name}"
      end
    end

    expect(season.team_matches.count).to eq(5)
  end
end

This is the output I'm seeing:

Round 1
Team 1 vs Team 4
Team 2 vs Team 3
Round 2
Team 1 vs Team 4
Team 2 vs Team 3
Team 3 vs Team 1
Team 2 vs Team 4
Round 3
Team 1 vs Team 4
Team 2 vs Team 3
Team 3 vs Team 1
Team 2 vs Team 4
Team 1 vs Team 2
Team 3 vs Team 4

Is this close to how the generate class is intended to be used?

BenjaminSchaaf commented 3 weeks ago

Yes that's the intended use. generate generates 1 round and it's up to you to keep track of any extra data (like which matches were generated in which round). The way I use it the list of matches is always empty ahead of calling generate.

emersonthis commented 3 weeks ago

The way I use it the list of matches is always empty ahead of calling generate.

Can you elaborate on this a bit? If I want to generate the second round of a single elimination tournament, do I not need existing matches from the first round?

BenjaminSchaaf commented 3 weeks ago

You only need them for the matches method on the driver, you don't necessarily need to hook that up with the build_match method. See for instance the TestDriver in spec/support/test_driver.rb, which has @matches and @created_matches (though do note that the driver isn't reused). But you don't need to follow that usage pattern; what you're already doing will work fine.

emersonthis commented 2 weeks ago

Thanks! I'll submit a PR with updates to the docs for this (and future) questions. Does that sound good? If so, I'll leave this open for now as a reminder and close it with the corresponding PR.

emersonthis commented 1 week ago

One more follow-up question about this...

I am trying to plan how to associate matches with a specific round of a tournament. If I run TournamentSystem::RoundRobin.generate myDriver it creates the matches and returns nil. I don't see anywhere that the round option value is exposed to the drive. I'm envisioning a model structure in which Tournament has_many Round(s) and Round has_many Match(s). Is this architecture compatible with this library?

BenjaminSchaaf commented 1 week ago

You'll need to put that into your driver yourself. You can use TournamentSystem::RoundRobin.guess_round myDriver to determine the round from the existing matches.

emersonthis commented 1 week ago

Thanks! I'm studying guess_round now... Are you suggesting something like this in the driver?

def build_match(home_team, away_team)
  current_round = TournamentSystem::RoundRobin.guess_round self
  @tournament.team_matches.create!(side_a: home_team, side_b: away_team, round: current_round)
end

(This ☝️ doesn't quite match the structure I described previously with a Round model. I simplified it for discussion purposes)

BenjaminSchaaf commented 1 week ago

Definitely not. If you wanted to have it guess the round I'd do:

my_driver.round = TournamentSystem::RoundRobin.guess_round my_driver
TournamentSystem::RoundRobin.guess_round my_driver

It looks like you're using rails? Are you aware of citadel? You can find its driver here.

emersonthis commented 1 week ago

Thanks. I think I understand now. Citadel is initializing the driver with the round info in advance. So no need for guess_round.

So if I follow this pattern, I think mine might look something like this...

my_driver = MyDriver.new
total_rounds_count = TournamentSystem::RoundRobin.total_rounds(my_driver)

total_rounds_count.times do |i|
  round = Round.create!(number: i, ...)
  my_driver.set_round_id round.id # build_match method will use this when creating a match
  TournamentSystem::RoundRobin.generate my_driver, { round: i }
end

Is this making sense?

BenjaminSchaaf commented 1 week ago

Yep, that looks good.