Here is my attempt. I checked the code for 2, 3 and 4 round tournaments. The conclusions for a tournament with 2 rounds and 3 rounds are shown here:
I used the same model that you provided to define Match
. I added the Tournament
class to generate test data.
Match.cs - a class containing models
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace tournament { public class Match { public int id { get; set; } public int teamid1 { get; set; } public int teamid2 { get; set; } public int roundnumber { get; set; } public int winner { get; set; } public Match(int id, int teamid1, int teamid2, int roundnumber, int winner) { this.id = id; this.teamid1 = teamid1; this.teamid2 = teamid2; this.roundnumber = roundnumber; this.winner = winner; } } public class Tournament { public SortedList<int, SortedList<int, Match>> TournamentRoundMatches { get; private set; } public Match ThirdPlaceMatch { get; private set; } public Tournament(int rounds) { this.TournamentRoundMatches = new SortedList<int, SortedList<int, Match>>(); this.GenerateTournamentResults(rounds); if (rounds > 1) { this.GenerateThirdPlaceResult(rounds); } } public void AddMatch(Match m) { if (this.TournamentRoundMatches.ContainsKey(m.roundnumber)) { if (!this.TournamentRoundMatches[m.roundnumber].ContainsKey(m.id)) { this.TournamentRoundMatches[m.roundnumber].Add(m.id, m); } } else { this.TournamentRoundMatches.Add(m.roundnumber, new SortedList<int, Match>()); this.TournamentRoundMatches[m.roundnumber].Add(m.id, m); } } private void GenerateTournamentResults(int rounds) { Random WinnerRandomizer = new Random(); for (int round = 1, match_id = 1; round <= rounds; round++) { int matches_in_round = 1 << (rounds - round); for (int round_match = 1; round_match <= matches_in_round; round_match++, match_id++) { int team1_id; int team2_id; int winner; if (round == 1) { team1_id = (match_id * 2) - 1; team2_id = (match_id * 2); } else { int match1 = (match_id - (matches_in_round * 2) + (round_match - 1)); int match2 = match1 + 1; team1_id = this.TournamentRoundMatches[round - 1][match1].winner; team2_id = this.TournamentRoundMatches[round - 1][match2].winner; } winner = (WinnerRandomizer.Next(1, 3) == 1) ? team1_id : team2_id; this.AddMatch(new Match(match_id, team1_id, team2_id, round, winner)); } } } private void GenerateThirdPlaceResult(int rounds) { Random WinnerRandomizer = new Random(); int semifinal_matchid1 = this.TournamentRoundMatches[rounds - 1].Keys.ElementAt(0); int semifinal_matchid2 = this.TournamentRoundMatches[rounds - 1].Keys.ElementAt(1); Match semifinal_1 = this.TournamentRoundMatches[rounds - 1][semifinal_matchid1]; Match semifinal_2 = this.TournamentRoundMatches[rounds - 1][semifinal_matchid2]; int semifinal_loser1 = (semifinal_1.winner == semifinal_1.teamid1) ? semifinal_1.teamid2 : semifinal_1.teamid1; int semifinal_loser2 = (semifinal_2.winner == semifinal_2.teamid1) ? semifinal_2.teamid2 : semifinal_2.teamid1; int third_place_winner = (WinnerRandomizer.Next(1, 3) == 1) ? semifinal_loser1 : semifinal_loser2; this.ThirdPlaceMatch = new Match((1 << rounds) + 1, semifinal_loser1, semifinal_loser2, 1, third_place_winner); } } }
I generated raw HTML dynamically using the static GenerateHTMLResultsTable
method. This was done using only <table>
without the need for <div>
blocks.
Program.cs - a static class of a program that initializes test data and generates HTML
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace tournament { class Program { static string GenerateHTMLResultsTable(Tournament tournament) { int match_white_span; int match_span; int position_in_match_span; int column_stagger_offset; int effective_row; int col_match_num; int cumulative_matches; int effective_match_id; int rounds = tournament.TournamentRoundMatches.Count; int teams = 1 << rounds; int max_rows = teams << 1; StringBuilder HTMLTable = new StringBuilder(); HTMLTable.AppendLine("<style type=\"text/css\">"); HTMLTable.AppendLine(" .thd {background: rgb(220,220,220); font: bold 10pt Arial; text-align: center;}"); HTMLTable.AppendLine(" .team {color: white; background: rgb(100,100,100); font: bold 10pt Arial; border-right: solid 2px black;}"); HTMLTable.AppendLine(" .winner {color: white; background: rgb(60,60,60); font: bold 10pt Arial;}"); HTMLTable.AppendLine(" .vs {font: bold 7pt Arial; border-right: solid 2px black;}"); HTMLTable.AppendLine(" td, th {padding: 3px 15px; border-right: dotted 2px rgb(200,200,200); text-align: right;}"); HTMLTable.AppendLine(" h1 {font: bold 14pt Arial; margin-top: 24pt;}"); HTMLTable.AppendLine("</style>"); HTMLTable.AppendLine("<h1>Tournament Results</h1>"); HTMLTable.AppendLine("<table border=\"0\" cellspacing=\"0\">"); for (int row = 0; row <= max_rows; row++) { cumulative_matches = 0; HTMLTable.AppendLine(" <tr>"); for (int col = 1; col <= rounds + 1; col++) { match_span = 1 << (col + 1); match_white_span = (1 << col) - 1; column_stagger_offset = match_white_span >> 1; if (row == 0) { if (col <= rounds) { HTMLTable.AppendLine(" <th class=\"thd\">Round " + col + "</th>"); } else { HTMLTable.AppendLine(" <th class=\"thd\">Winner</th>"); } } else if (row == 1) { HTMLTable.AppendLine(" <td class=\"white_span\" rowspan=\"" + (match_white_span - column_stagger_offset) + "\"> </td>"); } else { effective_row = row + column_stagger_offset; if (col <= rounds) { position_in_match_span = effective_row % match_span; position_in_match_span = (position_in_match_span == 0) ? match_span : position_in_match_span; col_match_num = (effective_row / match_span) + ((position_in_match_span < match_span) ? 1 : 0); effective_match_id = cumulative_matches + col_match_num; if ((position_in_match_span == 1) && (effective_row % match_span == position_in_match_span)) { HTMLTable.AppendLine(" <td class=\"white_span\" rowspan=\"" + match_white_span + "\"> </td>"); } else if ((position_in_match_span == (match_span >> 1)) && (effective_row % match_span == position_in_match_span)) { HTMLTable.AppendLine(" <td class=\"team\">Team " + tournament.TournamentRoundMatches[col][effective_match_id].teamid1 + "</td>"); } else if ((position_in_match_span == ((match_span >> 1) + 1)) && (effective_row % match_span == position_in_match_span)) { HTMLTable.AppendLine(" <td class=\"vs\" rowspan=\"" + match_white_span + "\">VS</td>"); } else if ((position_in_match_span == match_span) && (effective_row % match_span == 0)) { HTMLTable.AppendLine(" <td class=\"team\">Team " + tournament.TournamentRoundMatches[col][effective_match_id].teamid2 + "</td>"); } } else { if (row == column_stagger_offset + 2) { HTMLTable.AppendLine(" <td class=\"winner\">Team " + tournament.TournamentRoundMatches[rounds][cumulative_matches].winner + "</td>"); } else if (row == column_stagger_offset + 3) { HTMLTable.AppendLine(" <td class=\"white_span\" rowspan=\"" + (match_white_span - column_stagger_offset) + "\"> </td>"); } } } if (col <= rounds) { cumulative_matches += tournament.TournamentRoundMatches[col].Count; } } HTMLTable.AppendLine(" </tr>"); } HTMLTable.AppendLine("</table>"); HTMLTable.AppendLine("<h1>Third Place Results</h1>"); HTMLTable.AppendLine("<table border=\"0\" cellspacing=\"0\">"); HTMLTable.AppendLine(" <tr>"); HTMLTable.AppendLine(" <th class=\"thd\">Round 1</th>"); HTMLTable.AppendLine(" <th class=\"thd\">Third Place</th>"); HTMLTable.AppendLine(" </tr>"); HTMLTable.AppendLine(" <tr>"); HTMLTable.AppendLine(" <td class=\"white_span\"> </td>"); HTMLTable.AppendLine(" <td class=\"white_span\" rowspan=\"2\"> </td>"); HTMLTable.AppendLine(" </tr>"); HTMLTable.AppendLine(" <tr>"); HTMLTable.AppendLine(" <td class=\"team\">Team " + tournament.ThirdPlaceMatch.teamid1 + "</td>"); HTMLTable.AppendLine(" </tr>"); HTMLTable.AppendLine(" <tr>"); HTMLTable.AppendLine(" <td class=\"vs\">VS</td>"); HTMLTable.AppendLine(" <td class=\"winner\">Team " + tournament.ThirdPlaceMatch.winner + "</td>"); HTMLTable.AppendLine(" </tr>"); HTMLTable.AppendLine(" <tr>"); HTMLTable.AppendLine(" <td class=\"team\">Team " + tournament.ThirdPlaceMatch.teamid2 + "</td>"); HTMLTable.AppendLine(" <td class=\"white_span\"> </td>"); HTMLTable.AppendLine(" </tr>"); HTMLTable.AppendLine("</table>"); return HTMLTable.ToString(); } static void Main(string[] args) { Tournament Test3RoundTournament = new Tournament(3); Tournament Test2RoundTournament = new Tournament(2); File.WriteAllText(@"C:\Tournament\results.html", GenerateHTMLResultsTable(Test2RoundTournament)); File.WriteAllText(@"C:\Tournament\results.html", GenerateHTMLResultsTable(Test3RoundTournament)); Console.ReadLine(); } } }
UPDATE
Explanation of the options used to create the HTML table
As you can see, column_stagger_offset
is the sum by which each column is shifted to align them the way they should. effective_row
essentially where a particular table cell would have been if there hadn't been a vertical shift. Knowing effective_row
and position_in_match_span
helps determine what should be shown in this particular cell (empty, team1, team2 or vs.).
As you noticed, I repeat the columns one row at a time. This seems the most natural, given that HTML tables are also constructed in this way, i.e. Create a string, add cells ... create a string, add cels ... etc.