Search code examples
perltemplate-toolkitdancer

Perl - how to generate a tournament bracket HTML table


Ok I'm creating a tournament bracket generator and so far I have the following which generates an array of the competitors in a particular weight bracket sorted by seed.

The problem I have is that I can't get it to output to HTML in a bracket format.

Ok so before the following code I search a database and select the competitors in a particular weight category and store them in the @result array. Then:

#place the competitors into the @sortBySeed array highest seed to lowest.
my @sortBySeed = sort { $b->[5] <=> $a->[5] } @$result;

# this is an array which contains the arrays of the individual bouts 
i.e competitors/2 bouts, each array holding the names of the two competitors.

    my $numberStartingBouts = scalar(@sortBySeed) / 2;

    # if there are an even amount of competitors then a bye isn't needed
    if ( isint $numberStartingBouts) {
        for ( my $i = 0 ; $i < scalar(@sortBySeed) ; $i++ ) 
        {
           #remove the top seed from the array and return, remove the bottom seed and            return
           my $competitor1 = pop(@sortBySeed);
            my $competitor2 = shift(@sortBySeed);
            # place them into the knockout array in a bout
            push(@knockoutArray, $competitor1);
            push(@knockoutArray, $competitor2);


        }

    }
    else 
    {
        # remove the top seed and give them a bye
        my $competitor1 = shift(@sortBySeed);
        my $competitor2 = 'Bye';

        push(@knockoutArray, $competitor1);
        push(@knockoutArray, $competitor2);

        # process the rest as above
         for (my $i = 0 ; $i < scalar(@sortBySeed) ; $i++ ) 
        {
           my  $competitor1 = shift(@sortBySeed);
            my $competitor2 = pop(@sortBySeed);

            push(@knockoutArray, $competitor1);
            push(@knockoutArray, $competitor2);

        }

    }

    # flatten the first part of the array so that it's a scalar and not an array within the array
    map{$_=join(" ",@$_[1..3]) if(ref $_ eq 'ARRAY');} @knockoutArray;

Ok so now I have an array, @knockoutArray, which contains the following (shown using data::dumper):

$VAR1 = 'Seed 1';
$VAR2 = 'Bye';
$VAR3 = 'Seed 2';
$VAR4 = 'Seed 7';
$VAR5 = 'Seed 3';
$VAR6 = 'Seed 6';
$VAR7 = 'Seed 4';
$VAR8 = 'Seed 5';

I have the following static HTML page for the above array and can put the details in it

<!--
table {
  border-collapse: collapse;
  border: none;
  font: small arial, helvetica, sans-serif;
}
td {
  vertical-align: middle;
  width: 10em;
  margin: 0;
  padding: 0;
}
td p {
  border-bottom: solid 1px black;
  margin: 0;
  padding: 5px 5px 2px 5px;
}
-->
</style>
</head>
<body>
<table summary="Tournament Bracket">
 <tr>
  <td><p><% data.0 %></p></td>
  <td rowspan="2"><p></p></td>
  <td rowspan="4"><p></p></td>
  <td rowspan="8"><p></p></td>
 </tr>
 <tr>
  <td><p><%data.1%></p></td>
 </tr>
 <tr>
  <td><p><% data.2 %></p></td>
  <td rowspan="2"><p></p></td>
 </tr>
 <tr>
  <td><p><% data.3 %></p></td>
 </tr>
 <tr>
  <td><p><% data.4 %></p></td>
  <td rowspan="2"><p></p></td>
  <td rowspan="4"><p></p></td>
 </tr>
    <tr>
    <td><p><% data.5 %></p></td>
    </tr>
    <tr>
    <td><p><% data.6 %></p></td>
    <td rowspan="2"><p></p></td>
    </tr>
    <tr>
    <td><p><% data.7 %></p></td>
    </tr>

    </table>
    </body>
    </html>

How can I output this using HTML where the table would grow or shrink dynamically depending on the size of the array I am passing to it? I am using Perl Dancer and Template Toolkit so anything that involves these specifically would be useful!

Any method which also involves storing the array in a tournament bracket in a file to be recalled later would be extremely helpful.

I would like the bracket to look like this:

http://www.jimyi.com/content/brackets/tournament4.html

Thanks!


Solution

  • If the number of competitors changes, then obviously the number of columns will change, and the rowspan for those columns will double for each column moving right, until you reach the number of competitors rounded up. So the <td> cells going across the page must be dynamically declared. HTML tables being what they are, I would suggest that creating a vertical presentation of the tournament would make this logic a lot simpler.

    If I were faced with this problem, I would be starting with HTML::TableBracket, that appears to get you most of the way there. Even though the output isn't exactly what you need (it seems to assume you are tracking results), the logic of the HTML table output is all there, and it should be straightforward enough to tweak the as_html() method for your purposes. I'd be adding a bunch of debug code to it until I understood the logic of the loop sufficiently.

    As a bonus it seems to obviate the need to calculate the bout opponents - the order the entries are passed into new() determines how they get allocated.

    If you really want to roll your own, I can also imagine a scenario where you have an arrayref of arrayrefs. The 0th row has all competitors eg @knockoutArray. The 1st row has half as many, the 2nd half as many again ... until you have an array with 1 item.

    my $data = [
      [ 'Seed 1','Bye','Seed 2','Seed 7','Seed 3','Seed 6','Seed 4','Seed 5' ],
      [ 'Seed 1', '2 vs 7', '3 vs 6', '4 vs 5' ],
      [ '1 vs 2/7', '3/6 vs 4/5' ],
      [ '1/2/7 vs 3/6/4/5' ]
    ];
    

    I have no idea how you plan to represent these bouts the further into the tournament you go, so constructing that arrayref is left as an exercise for the reader.

    Your template code would probably look something like this:

    [%-
        USE Perl;
        SET rows = data.0.size;
        SET cols = data.size;
        SET elem = [];
        FOREACH row IN data;
            elem.push(0); # [ 0,0,0,0 ]
        END;
        '<table>';
        SET row = 0;
        WHILE row < rows;
            '<tr>';
            SET col = 0;
            WHILE col < cols;
                SET rowspan = Perl.pow(2, col);
                IF row % rowspan == 0;
                    '<td rowspan='; rowspan; '>'; data.$col.item(elem.$col); '</td>';
                    SET elem.$col = elem.$col + 1;
                END;
                SET col = col + 1;
            END;
            '</tr>';
            SET row = row + 1;
        END;
        '</table>';
    
    -%]
    

    I've had to use Template::Plugin::Perl for this code because out-of-the-box TT doesn't support a mathematical exponent.

    (By the way, I don't think your 'even number of entries' logic is going to work if you have 6 entrants. If you keep your original code, I think you need to add Bye records until you reach a factor of 2.)