Hi,

I have a tendency to over-engineer things, so I intentionally tried to
keep this simple.  Given the quiz requirements, it seemed simplest to
represent the match-tree as a collection of nested arrays.  The
presentation code is slightly clever in that it dynamically mixes a
particular renderer module into the tournament to be rendered.

* Sample run

$ ruby tournament.rb 1 2 3 4 5
R1  R2  R3  
============
1              
---            
   |---        
---    |       
bye    |       
       |---    
4      |   |   
---    |   |   
   |---    |   
---        |   
5          |   
           |---
2          |   
---        |   
   |---    |   
---    |   |   
bye    |   |   
       |---    
3      |       
---    |       
   |---        
---            
bye            


* Code

#! /usr/bin/env ruby

require 'facet/symbol/to_proc'

class << Math
  def log2(n); log(n) / log(2); end
end

class Array
  def half_size; size >> 1; end
  def top_half; self[0, half_size]; end
  def bottom_half; self[half_size, half_size]; end
end

class Tournament
  def initialize(players)
    raise "Tournament requires 2 or more players" if players.size < 2
    
    @players = players
    @matches = (0...nrounds).inject(seed) do |(memo,)|
      memo.top_half.zip(memo.bottom_half.reverse)
    end
  end

  attr_reader :players
  attr_reader :matches
  
  def render(renderer = AsciiRenderer)
    extend renderer
    render_tournament
  end

  protected
  def seed; @seed ||= players + Array.new(nbyes, :bye); end
  def nplayers; players.size; end
  def nrounds; Math.log2(nplayers).ceil; end
  def nbyes; (1 << nrounds) - nplayers; end
end

module Tournament::AsciiRenderer
  protected
  def render_tournament
    render_header.concat(render_rounds).join("\n")
  end

  private
  def render_header
    [ (1..nrounds).collect { |r| "R#{r}".ljust(width + 1) }.join,
      ('=' * (nrounds * (width + 1))) ]
  end

  def render_rounds
    render_match(matches.first)
  end
  
  def render_match(match)
    unless match.kind_of? Array
      draw = [ render_player(match), slot1 ]
      (@flip = !@flip) ? draw : draw.reverse
    else
      draw = match.collect do |match_|
        render_match(match_)
      end.inject do |memo, draw_|
        (memo << (' ' * memo.first.size)).concat(draw_)
      end

      fh = (draw.size - 3) / 4
      sh = [ (draw.size + 1) / 4, 2 ].max
      draw_ = [ [space]*sh, [flow]*fh, slot, [flow]*fh, [space]*sh ]
      draw.zip(draw_.flatten).collect(&:join)
    end
  end

  def render_player(player)
    player.to_s.ljust(width)
  end
  
  def slot;  '|' << ('-' * width); end
  def slot1;        ('-' * width); end
  def flow;  '|' << (' ' * width); end
  def space; ' ' << (' ' * width); end

  def width
    @width ||= seed.collect { |x| x.to_s.size }.max;
  end
end

if __FILE__ == $0
  puts Tournament.new(ARGV).render
end


-Marshall