<reponame>talksportsdata/tennisscorer<filename>tennisscorer/core.py
# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/00_core.ipynb (unless otherwise specified).

__all__ = ['Scorer', 'TiebreakScorer', 'MatchTiebreakScorer', 'GamePointScorer', 'SetTracker', 'MatchTracker']

# Cell
import random
import math


class Scorer:
    def __init__(self, home_score=0, away_score=0, score_diff=1):
        # init with score or start with 0-0
        self.home_score = home_score
        self.away_score = away_score
        self.score_diff = score_diff

    def increase(self, is_home, score_diff=None):
        if not self.finished:
            score_diff = score_diff if score_diff else self.score_diff
            if is_home:
                self.home_score += score_diff
            else:
                self.away_score += score_diff

    def decrease(self, is_home, score_diff=None):
        if not self.finished:
            score_diff = score_diff if score_diff else self.score_diff
            if is_home:
                self.home_score -= score_diff
            else:
                self.away_score -= score_diff

    def update(self, home_score, away_score):
        self.home_score = home_score
        self.away_score = away_score

    def __repr__(self):
        return f"{self.__class__.__name__}({self.home_score}, {self.away_score})"

    @property
    def finished(self):
        raise NotImplementedError('You need to implement this function in your Scorer')

    @property
    def is_winner_home(self):
        """returns winner:
           - still playing -> None
           - True: home won
           - False: away won
        """
        if self.finished:
            return self.home_score > self.away_score
        return None

    def get_score(self):
        return f"{self.home_score}:{self.away_score}"



class TiebreakScorer(Scorer):
    final_point = 7
    min_distance = 2

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    @property
    def finished(self):
        # someone reaches seven points and is at least two points ahead
        if ((self.home_score >= self.final_point or self.away_score >= self.final_point)
           and abs(self.home_score - self.away_score) >= self.min_distance):
            return True

        return False

class MatchTiebreakScorer(Scorer):
    final_point = 10
    min_distance = 2

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    @property
    def finished(self):
        # someone reaches seven points and is at least two points ahead
        if ((self.home_score >= self.final_point or self.away_score >= self.final_point)
           and abs(self.home_score - self.away_score) >= self.min_distance):
            return True
        return False


# Need: Track winner, track stats

class GamePointScorer(Scorer):
    internal_to_game_mapping = {0: '0', 1:'15', 2:'30', 3:'40', 4:'Ad', 5:'F'}
    game_to_internal_mapping = {'0':0, '15':1, '30':2, '40':3, 'Ad':4, 'F':5}
    final_point = 4

    def __init__(self, home_score='0', away_score='0', score_diff=1, deciding_point=False):
        # init with score or start with 0-0
        int_home_score = self.game_to_internal_mapping[home_score]
        int_away_score = self.game_to_internal_mapping[away_score]
        self.deciding_point = deciding_point
        super().__init__(int_home_score, int_away_score, score_diff)

    def increase(self, is_home, score_diff=None):
        super().increase(is_home, score_diff)
        if self.home_score == 4 and self.away_score == 4:
            self.home_score, self.away_score = 3, 3

    def update(self, home_score, away_score):
        try:
            self.home_score = self.game_to_internal_mapping[home_score]
            self.away_score = self.game_to_internal_mapping[away_score]
        except:
            raise ValueError('No valid score ({list(self.internal_to_game_mapping.values())})')

    @property
    def finished(self):
        # someone reaches Ad point and is at least two points ahead
        max_score = max(self.home_score, self.away_score)
        min_score = min(self.home_score, self.away_score)
        diff_score = max_score - min_score

        if max_score >= 4:
            if diff_score>=2:
                return True
        if self.deciding_point:
            if max_score == 4 and min_score == 3:
                return True

        return False

    def __repr__(self):
        home_score = self.internal_to_game_mapping[self.home_score]
        away_score = self.internal_to_game_mapping[self.away_score]
        return f"{self.__class__.__name__}({home_score}, {away_score})"

    def get_score(self):
        home_score = self.internal_to_game_mapping[self.home_score]
        away_score = self.internal_to_game_mapping[self.away_score]
        return f"{home_score}:{away_score}"


# Need to track winner of each GamePoint to decide when it is finished (return True/False should be enough)
class SetTracker(Scorer):
    min_distance = 2
    tiebreak = True # 6-6 -> Tiebreak

    def __init__(self, home_score=0, away_score=0, score_diff=1, final_point=6, deciding_point=False):
        super().__init__(home_score,away_score,score_diff)
        self.games = [GamePointScorer(deciding_point=deciding_point) for i in range(final_point*2)]
        self.games = self.games + [TiebreakScorer()]
        self.current_gamepoint = home_score + away_score
        self.final_point = final_point
        self.deciding_point = deciding_point
        #self.score_diff = score_diff
        #self.home_score = 0
        #self.away_score = 0
        # points[12] of PointTracker(deuce=True)
        # tiebreak of TiebreakTracker

    @property
    def is_winner_home(self):
        """returns winner:
           - still playing -> None
           - True: home won
           - False: away won
        """
        if self.finished:
            return self.home_score > self.away_score
        return None

    @property
    def current_game(self):
        return self.games[self.current_gamepoint]

    @property
    def finished(self):
        # someone reaches seven points and is at least two points ahead
        max_score = max(self.home_score, self.away_score)
        min_score = min(self.home_score, self.away_score)
        diff_score = max_score - min_score
        if max_score == self.final_point + 1:
            return True
        elif ((self.home_score == self.final_point or self.away_score == self.final_point)
            and diff_score>=2):
            return True

        return False

    def increase(self, is_home, score_diff=None):
        if not self.finished:
            # score_diff = score_diff if score_diff else self.score_diff
            self.current_game.increase(is_home, score_diff)
            if self.current_game.finished:
                if self.current_game.is_winner_home is not None:
                    if self.current_game.is_winner_home:
                        self.home_score+=1
                    else:
                        self.away_score+=1
                if self.current_gamepoint < len(self.games)-1:
                    self.current_gamepoint += 1


    def update(self, home_score, away_score):
        self.home_score = home_score
        self.away_score = away_score
        self.current_gamepoint = home_score + away_score

    def __repr__(self):
        return f"{self.__class__.__name__}({self.home_score}, {self.away_score}), {self.current_game}"

    def get_score(self):
        return f"{self.home_score}:{self.away_score}"


class MatchTracker(Scorer):

    def __init__(self, home_score=0, away_score=0, score_diff=1, best_of=3, match_tiebreak=True, deciding_point=False):
        super().__init__(home_score,away_score,score_diff)
        self.best_of = best_of
        self.match_tiebreak = match_tiebreak
        self.sets = [SetTracker(deciding_point=deciding_point) for i in range(best_of-1)]
        if self.match_tiebreak:
            self.sets = self.sets + [MatchTiebreakScorer()]# might add MatchTiebreak here
        else:
            self.sets = self.sets + [SetTracker(deciding_point=deciding_point)]
        self.current_set_id = home_score + away_score

    @property
    def is_winner_home(self):
        """returns winner:
           - still playing -> None
           - True: home won
           - False: away won
        """
        if self.finished:
            return self.home_score > self.away_score
        return None

    @property
    def current_set(self):
        return self.sets[self.current_set_id]

    @property
    def finished(self):
        # someone reaches seven points and is at least two points ahead
        max_score = max(self.home_score, self.away_score)
        min_score = min(self.home_score, self.away_score)
        if max_score == math.ceil(self.best_of/2):
            return True
        return False

    def increase(self, is_home, score_diff=None):
        if not self.finished:
            # score_diff = score_diff if score_diff else self.score_diff
            self.current_set.increase(is_home, score_diff)
            if self.current_set.finished:
                if self.current_set.is_winner_home is not None:
                    if self.current_set.is_winner_home:
                        self.home_score+=1
                    else:
                        self.away_score+=1
                if (self.current_set_id < len(self.sets)-1) and not self.finished:
                    self.current_set_id += 1


    def update(self, home_score, away_score):
        self.home_score = home_score
        self.away_score = away_score
        self.current_set_id = home_score + away_score

    def __repr__(self):
        return f"{self.__class__.__name__}({self.home_score}, {self.away_score}), {self.current_set}"

    def get_score(self):
        finished_sets = [set_.get_score() for i, set_ in enumerate(self.sets) if i <= self.current_set_id]
        return ";".join(finished_sets) + '-' + self.current_set.current_game.get_score()

    @classmethod
    def WIMBLEDON(cls):
        BEST_OF=5
        MATCH_TIEBREAK=False
        FINAL_POINT=12

        instance = cls(
            match_tiebreak=MATCH_TIEBREAK,
            best_of=BEST_OF)

        instance.sets = [SetTracker() for i in range(BEST_OF-1)]
        instance.sets = instance.sets + [SetTracker(final_point=FINAL_POINT)]

        return instance


# class MatchTrackerWimbledon(MatchTracker):
#     BEST_OF=5
#     MATCH_TIEBREAK=False
#     FINAL_POINT=12

#     def __init__(self, **kwargs):
#         super().__init__(
#             match_tiebreak=self.MATCH_TIEBREAK,
#             best_of=self.BEST_OF,
#             **kwargs)

#         self.sets = [SetTracker() for i in range(self.BEST_OF-1)]
#         self.sets = self.sets + [SetTracker(final_point=self.FINAL_POINT)]
# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/00_core.ipynb (unless otherwise specified).

__all__ = ['Scorer', 'TiebreakScorer', 'MatchTiebreakScorer', 'GamePointScorer', 'SetTracker', 'MatchTracker']

# Cell
import random
import math


class Scorer:
    def __init__(self, home_score=0, away_score=0, score_diff=1):
        # init with score or start with 0-0
        self.home_score = home_score
        self.away_score = away_score
        self.score_diff = score_diff

    def increase(self, is_home, score_diff=None):
        if not self.finished:
            score_diff = score_diff if score_diff else self.score_diff
            if is_home:
                self.home_score += score_diff
            else:
                self.away_score += score_diff

    def decrease(self, is_home, score_diff=None):
        if not self.finished:
            score_diff = score_diff if score_diff else self.score_diff
            if is_home:
                self.home_score -= score_diff
            else:
                self.away_score -= score_diff

    def update(self, home_score, away_score):
        self.home_score = home_score
        self.away_score = away_score

    def __repr__(self):
        return f"{self.__class__.__name__}({self.home_score}, {self.away_score})"

    @property
    def finished(self):
        raise NotImplementedError('You need to implement this function in your Scorer')

    @property
    def is_winner_home(self):
        """returns winner:
           - still playing -> None
           - True: home won
           - False: away won
        """
        if self.finished:
            return self.home_score > self.away_score
        return None

    def get_score(self):
        return f"{self.home_score}:{self.away_score}"



class TiebreakScorer(Scorer):
    final_point = 7
    min_distance = 2

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    @property
    def finished(self):
        # someone reaches seven points and is at least two points ahead
        if ((self.home_score >= self.final_point or self.away_score >= self.final_point)
           and abs(self.home_score - self.away_score) >= self.min_distance):
            return True

        return False

class MatchTiebreakScorer(Scorer):
    final_point = 10
    min_distance = 2

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    @property
    def finished(self):
        # someone reaches seven points and is at least two points ahead
        if ((self.home_score >= self.final_point or self.away_score >= self.final_point)
           and abs(self.home_score - self.away_score) >= self.min_distance):
            return True
        return False


# Need: Track winner, track stats

class GamePointScorer(Scorer):
    internal_to_game_mapping = {0: '0', 1:'15', 2:'30', 3:'40', 4:'Ad', 5:'F'}
    game_to_internal_mapping = {'0':0, '15':1, '30':2, '40':3, 'Ad':4, 'F':5}
    final_point = 4

    def __init__(self, home_score='0', away_score='0', score_diff=1, deciding_point=False):
        # init with score or start with 0-0
        int_home_score = self.game_to_internal_mapping[home_score]
        int_away_score = self.game_to_internal_mapping[away_score]
        self.deciding_point = deciding_point
        super().__init__(int_home_score, int_away_score, score_diff)

    def increase(self, is_home, score_diff=None):
        super().increase(is_home, score_diff)
        if self.home_score == 4 and self.away_score == 4:
            self.home_score, self.away_score = 3, 3

    def update(self, home_score, away_score):
        try:
            self.home_score = self.game_to_internal_mapping[home_score]
            self.away_score = self.game_to_internal_mapping[away_score]
        except:
            raise ValueError('No valid score ({list(self.internal_to_game_mapping.values())})')

    @property
    def finished(self):
        # someone reaches Ad point and is at least two points ahead
        max_score = max(self.home_score, self.away_score)
        min_score = min(self.home_score, self.away_score)
        diff_score = max_score - min_score

        if max_score >= 4:
            if diff_score>=2:
                return True
        if self.deciding_point:
            if max_score == 4 and min_score == 3:
                return True

        return False

    def __repr__(self):
        home_score = self.internal_to_game_mapping[self.home_score]
        away_score = self.internal_to_game_mapping[self.away_score]
        return f"{self.__class__.__name__}({home_score}, {away_score})"

    def get_score(self):
        home_score = self.internal_to_game_mapping[self.home_score]
        away_score = self.internal_to_game_mapping[self.away_score]
        return f"{home_score}:{away_score}"


# Need to track winner of each GamePoint to decide when it is finished (return True/False should be enough)
class SetTracker(Scorer):
    min_distance = 2
    tiebreak = True # 6-6 -> Tiebreak

    def __init__(self, home_score=0, away_score=0, score_diff=1, final_point=6, deciding_point=False):
        super().__init__(home_score,away_score,score_diff)
        self.games = [GamePointScorer(deciding_point=deciding_point) for i in range(final_point*2)]
        self.games = self.games + [TiebreakScorer()]
        self.current_gamepoint = home_score + away_score
        self.final_point = final_point
        self.deciding_point = deciding_point
        #self.score_diff = score_diff
        #self.home_score = 0
        #self.away_score = 0
        # points[12] of PointTracker(deuce=True)
        # tiebreak of TiebreakTracker

    @property
    def is_winner_home(self):
        """returns winner:
           - still playing -> None
           - True: home won
           - False: away won
        """
        if self.finished:
            return self.home_score > self.away_score
        return None

    @property
    def current_game(self):
        return self.games[self.current_gamepoint]

    @property
    def finished(self):
        # someone reaches seven points and is at least two points ahead
        max_score = max(self.home_score, self.away_score)
        min_score = min(self.home_score, self.away_score)
        diff_score = max_score - min_score
        if max_score == self.final_point + 1:
            return True
        elif ((self.home_score == self.final_point or self.away_score == self.final_point)
            and diff_score>=2):
            return True

        return False

    def increase(self, is_home, score_diff=None):
        if not self.finished:
            # score_diff = score_diff if score_diff else self.score_diff
            self.current_game.increase(is_home, score_diff)
            if self.current_game.finished:
                if self.current_game.is_winner_home is not None:
                    if self.current_game.is_winner_home:
                        self.home_score+=1
                    else:
                        self.away_score+=1
                if self.current_gamepoint < len(self.games)-1:
                    self.current_gamepoint += 1


    def update(self, home_score, away_score):
        self.home_score = home_score
        self.away_score = away_score
        self.current_gamepoint = home_score + away_score

    def __repr__(self):
        return f"{self.__class__.__name__}({self.home_score}, {self.away_score}), {self.current_game}"

    def get_score(self):
        return f"{self.home_score}:{self.away_score}"


class MatchTracker(Scorer):

    def __init__(self, home_score=0, away_score=0, score_diff=1, best_of=3, match_tiebreak=True, deciding_point=False):
        super().__init__(home_score,away_score,score_diff)
        self.best_of = best_of
        self.match_tiebreak = match_tiebreak
        self.sets = [SetTracker(deciding_point=deciding_point) for i in range(best_of-1)]
        if self.match_tiebreak:
            self.sets = self.sets + [MatchTiebreakScorer()]# might add MatchTiebreak here
        else:
            self.sets = self.sets + [SetTracker(deciding_point=deciding_point)]
        self.current_set_id = home_score + away_score

    @property
    def is_winner_home(self):
        """returns winner:
           - still playing -> None
           - True: home won
           - False: away won
        """
        if self.finished:
            return self.home_score > self.away_score
        return None

    @property
    def current_set(self):
        return self.sets[self.current_set_id]

    @property
    def finished(self):
        # someone reaches seven points and is at least two points ahead
        max_score = max(self.home_score, self.away_score)
        min_score = min(self.home_score, self.away_score)
        if max_score == math.ceil(self.best_of/2):
            return True
        return False

    def increase(self, is_home, score_diff=None):
        if not self.finished:
            # score_diff = score_diff if score_diff else self.score_diff
            self.current_set.increase(is_home, score_diff)
            if self.current_set.finished:
                if self.current_set.is_winner_home is not None:
                    if self.current_set.is_winner_home:
                        self.home_score+=1
                    else:
                        self.away_score+=1
                if (self.current_set_id < len(self.sets)-1) and not self.finished:
                    self.current_set_id += 1


    def update(self, home_score, away_score):
        self.home_score = home_score
        self.away_score = away_score
        self.current_set_id = home_score + away_score

    def __repr__(self):
        return f"{self.__class__.__name__}({self.home_score}, {self.away_score}), {self.current_set}"

    def get_score(self):
        finished_sets = [set_.get_score() for i, set_ in enumerate(self.sets) if i <= self.current_set_id]
        return ";".join(finished_sets) + '-' + self.current_set.current_game.get_score()

    @classmethod
    def WIMBLEDON(cls):
        BEST_OF=5
        MATCH_TIEBREAK=False
        FINAL_POINT=12

        instance = cls(
            match_tiebreak=MATCH_TIEBREAK,
            best_of=BEST_OF)

        instance.sets = [SetTracker() for i in range(BEST_OF-1)]
        instance.sets = instance.sets + [SetTracker(final_point=FINAL_POINT)]

        return instance


# class MatchTrackerWimbledon(MatchTracker):
#     BEST_OF=5
#     MATCH_TIEBREAK=False
#     FINAL_POINT=12

#     def __init__(self, **kwargs):
#         super().__init__(
#             match_tiebreak=self.MATCH_TIEBREAK,
#             best_of=self.BEST_OF,
#             **kwargs)

#         self.sets = [SetTracker() for i in range(self.BEST_OF-1)]
#         self.sets = self.sets + [SetTracker(final_point=self.FINAL_POINT)]
# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/00_core.ipynb (unless otherwise specified). # Cell # init with score or start with 0-0 returns winner:            - still playing -> None            - True: home won            - False: away won # someone reaches seven points and is at least two points ahead # someone reaches seven points and is at least two points ahead # Need: Track winner, track stats # init with score or start with 0-0 # someone reaches Ad point and is at least two points ahead # Need to track winner of each GamePoint to decide when it is finished (return True/False should be enough) # 6-6 -> Tiebreak #self.score_diff = score_diff #self.home_score = 0 #self.away_score = 0 # points[12] of PointTracker(deuce=True) # tiebreak of TiebreakTracker returns winner:            - still playing -> None            - True: home won            - False: away won # someone reaches seven points and is at least two points ahead # score_diff = score_diff if score_diff else self.score_diff # might add MatchTiebreak here returns winner:            - still playing -> None            - True: home won            - False: away won # someone reaches seven points and is at least two points ahead # score_diff = score_diff if score_diff else self.score_diff # class MatchTrackerWimbledon(MatchTracker): #     BEST_OF=5 #     MATCH_TIEBREAK=False #     FINAL_POINT=12 #     def __init__(self, **kwargs): #         super().__init__( #             match_tiebreak=self.MATCH_TIEBREAK, #             best_of=self.BEST_OF, #             **kwargs) #         self.sets = [SetTracker() for i in range(self.BEST_OF-1)] #         self.sets = self.sets + [SetTracker(final_point=self.FINAL_POINT)]