T0nyX1ang/42-Points-Game

View on GitHub
ftptsgame/__init__.py

Summary

Maintainability
A
0 mins
Test Coverage
"""Main module of this project."""

import datetime
from .expr_utils import Node, build_node
from .problem_utils import Problem


class FTPtsGame(object):
    """
    The main game.

    Available methods (+ means playing, - means not playing):
    __init__(): initialization. (Entry point)
    is_playing(): show the status of current game. (+-)
    generate_problem(): generate a problem manually. (-)
    get_elapsed_time(): get the time elapsed during the game. (+)
    get_current_problem(): get current problem (tuple). (+)
    get_current_solutions(): get current solutions (list). (+)
    get_current_solution_number(): print current solution number. (+)
    get_total_solution_number(): print total solution number. (+)
    start(): start the game. (-)
    stop(): stop the game. (+)
    solve(): put forward a solution and show solution intervals. (+)
    """

    def __init__(self):
        """Start the game session, serving as an initialization."""
        self.__valid = []  # this list stores readable answers
        self.__formula = []  # this list stores converted formulas
        self.__players = []  # this list stores player statistics
        self.__playing = False  # this stores playing status

    def __status_check(self, required_status: bool = True):
        """A status checker."""
        if required_status != self.is_playing():
            raise PermissionError('Required status: %s' % required_status)

    def is_playing(self) -> bool:
        """Indicate the game is started or not."""
        return self.__playing

    def get_elapsed_time(self) -> datetime.timedelta:
        """Get elapsed time between solutions. Effective when playing."""
        self.__status_check(required_status=True)
        elapsed = datetime.datetime.now() - self.__timer
        return elapsed

    def generate_problem(self, problem, target=42):
        """Generate a problem manually."""
        self.__status_check(required_status=False)
        self.__target = target
        self.__problem = tuple(sorted(problem))
        self.__problem_class = Problem(list(self.__problem))
        self.__problem_class.generate_answers(self.__target)
        if len(self.__problem_class.distinct_answer_table) == 0:
            raise ValueError('No solution found.')

    def get_current_target(self) -> int:
        """Get current target. Effective when playing."""
        self.__status_check(required_status=True)
        return self.__target

    def get_current_problem(self) -> tuple:
        """Get current problem. Effective when playing."""
        self.__status_check(required_status=True)
        return self.__problem

    def get_current_solutions(self) -> list:
        """Get current valid solutions. Effective when playing."""
        self.__status_check(required_status=True)
        return self.__valid

    def get_current_solution_number(self) -> int:
        """Get the number of current solutions. Effective when playing."""
        self.__status_check(required_status=True)
        return len(self.__valid)

    def get_total_solution_number(self) -> int:
        """Get the number of total solutions. Effective when playing."""
        self.__status_check(required_status=True)
        return len(self.__problem_class.distinct_answer_table)

    def get_remaining_solutions(self) -> list:
        """Get remaining solutions. Effective when playing."""
        self.__status_check(required_status=True)
        current_solution_set = set()
        for expr_str in self.__valid:
            node = build_node(expr_str)
            current_solution_set.add(self.__problem_class.equivalence_dict[node.unique_id()])
        return_list = []
        for expr in self.__problem_class.distinct_answer_table:
            if self.__problem_class.equivalence_dict[expr.unique_id()] not in current_solution_set:
                return_list.append(str(expr))
        return return_list

    def get_current_player_statistics(self) -> list:
        """Get current player statistics. Effective when playing."""
        self.__status_check(required_status=True)
        return self.__players

    def __validate_repeated(self, node: Node):
        """Validate distinguishing expressions. Private method."""
        class_id = self.__problem_class.equivalence_dict[node.unique_id()]
        for ind in range(0, len(self.__formula)):
            cmp_node = self.__formula[ind]
            cmp_class_id = self.__problem_class.equivalence_dict[cmp_node.unique_id()]
            if cmp_class_id == class_id:
                raise LookupError(self.__valid[ind])

    def solve(self, math_expr: str, player_id: int = -1) -> datetime.timedelta:
        """Put forward a solution and show solution intervals if correct."""
        self.__status_check(required_status=True)

        replace_table = [
            ('×', '*'),
            ('x', '*'),
            ('÷', '/'),
            (' ', ''),
            ('\n', ''),
            ('\r', ''),
            ('(', '('),
            (')', ')'),
        ]
        for src, dest in replace_table:
            math_expr = math_expr.replace(src, dest)

        if len(math_expr) >= 30:
            raise OverflowError('Maximum parsing length exceeded.')

        node = build_node(math_expr)

        user_input_numbers = node.extract()
        if tuple(sorted(user_input_numbers)) != self.__problem:
            raise ValueError('Unmatched input numbers.')

        math_expr_value = node.evaluate()
        if math_expr_value != self.__target:
            raise ArithmeticError(str(math_expr_value))

        self.__validate_repeated(node)
        self.__formula.append(node)
        self.__valid.append(math_expr)
        elapsed = self.get_elapsed_time()
        interval = elapsed - self.__last
        self.__last = elapsed
        self.__players.append((player_id, interval))
        return interval

    def start(self):
        """Start the game. Effective when not playing."""
        self.__status_check(required_status=False)
        self.__valid = []
        self.__formula = []
        self.__players = []
        self.__timer = datetime.datetime.now()
        self.__last = datetime.timedelta(seconds=0)  # A tag for each solution.
        self.__playing = True

    def stop(self) -> datetime.timedelta:
        """Stop the game. Effective when playing."""
        self.__status_check(required_status=True)
        elapsed = self.get_elapsed_time()
        self.__playing = False
        return elapsed