happy5214/competitions-cup

View on GitHub
competitions/cup/default/single_elimination.py

Summary

Maintainability
A
45 mins
Test Coverage
# -*- coding: utf-8  -*-
"""Single-elimination cups."""

# Copyright (C) 2015, 2016, 2017 Alexander Jones
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import print_function, unicode_literals

from competitions.cup import StandardCup, init_nested_list


class SingleEliminationCup(StandardCup):

    """Single-elimination cup."""

    def __init__(self, match_class, rounds=0, teams=[]):
        """Constructor.

        @param match_class: The match simulator class
        @type match_class: Match
        @param rounds: The number of rounds
        @type rounds: int
        @param teams: An optional list of teams
        @type teams: list
        """
        super(SingleEliminationCup, self).__init__(match_class=match_class,
                                                   teams=teams,
                                                   team_count=2 ** rounds,
                                                   rounds=rounds)

    def update_teams(self, teams):
        """Update the list of teams and the first-round matches.

        @param teams: The new list of teams
        @type teams: list
        """
        self.teams = teams
        first_round = self.matches[0]
        for x in range(len(first_round)):
            first_round[x].team1 = self.teams[x * 2]
            first_round[x].team2 = self.teams[x * 2 + 1]

    def _build_bracket(self):
        """Build the nested list representing the bracket."""
        Match = self.MatchClass
        match_count = self.team_count // 2
        self.matches.append([Match(self.teams[i * 2], self.teams[i * 2 + 1])
                             for i in range(match_count)])
        match_num = 1
        match_count //= 2
        match_num = self._build_second_round(match_count, match_num)
        for __ in range(2, self.round_count):
            match_count //= 2
            round = []
            for ___ in range(match_count):
                round.append(Match('Match {} Winner'.format(match_num),
                                   'Match {} Winner'.format(match_num + 1)))
                match_num += 2
            self.matches.append(round)

    def _build_second_round(self, match_count, match_num):
        """Build the bracket's second round."""
        raise NotImplementedError

    def _assign_winner(self, winner):
        """Assign winner to their next match."""
        next_match = self.matches[self.index[0] + 1][self.index[1] // 2]
        if self.index[1] % 2 == 0:
            next_match.team1 = winner
        else:
            next_match.team2 = winner

    def _generate_layout(self):
        """Generate the bracket layout for display."""
        line_count = self.team_count * 2 - 1
        layout = init_nested_list(line_count)
        first_team = True
        match_gen = self._match_for_layout
        for round in range(self.round_count):
            match_num = 0
            div = 2 * 2 ** round
            mod = div // 2 - 1
            for i in range(line_count):
                (conf, layout_entry) = match_gen((i % div == mod),
                                                 round, match_num,
                                                 first_team)
                layout[i].append(layout_entry)
                (match_num, first_team) = conf
        return layout


class StandardSingleEliminationCup(SingleEliminationCup):

    """Standard single-elimination cup."""

    def _build_second_round(self, match_count, match_num):
        """Build the bracket's second round."""
        Match = self.MatchClass
        round = []
        for ___ in range(match_count):
            teams = []
            for i in range(2):
                previous_match = self.matches[0][match_num + i - 1]
                if not previous_match.is_walkover:
                    teams.append('Match {} Winner'.format(match_num + i))
                else:
                    teams.append(previous_match.team1 or previous_match.team2)
            round.append(Match(teams[0], teams[1]))
            match_num += 2
        self.matches.append(round)
        return match_num

    def _set_current_match(self):
        """Set the current match."""
        super(StandardSingleEliminationCup, self)._set_current_match()
        while self.current_match.is_walkover:
            super(StandardSingleEliminationCup, self)._set_current_match()


class PowerOfTwoSingleEliminationCup(SingleEliminationCup):

    """Standard single-elimination cup for powers of two (4, 8, 16, etc.)."""

    def _build_second_round(self, match_count, match_num):
        """Build the bracket's second round."""
        Match = self.MatchClass
        round = []
        for ___ in range(match_count):
            round.append(Match('Match {} Winner'.format(match_num),
                               'Match {} Winner'.format(match_num + 1)))
            match_num += 2
        self.matches.append(round)
        return match_num