I have posted my first version of RockPaperScissors a while back. It was on an old version of python with improper use of some functions. This version was updated to a more recent version of python and has a little extra for users who run on terminal. Is there anything I should add or change? Are my classes used correctly? Are there any bugs? Any overall improvements?# Programmer: DeliriousSyntax# Date: October 30, 2015# File: RockPaperScissorsV2-py3.py# This is a classic game of rock paper scissors.import randomimport shelveimport timeimport coloramaimport sysclass Scorecard(dict):    Stores all items related to Score\    def __init__(self, **kwargs):        Makes all values set to 0        super().__init__(**kwargs)        for key in ('rounds', 'losses', 'wins', 'draws', 'games_won',                    'games_lost', 'games_played', total_rounds,                    'rounds_won', 'rounds_lost', 'rounds_tied', 'game_draws'):            self[key] = 0    def reset(self):        Resets the scores for next game        for key in ('rounds', 'losses', 'wins', 'draws'):            self[key] = 0    def round_update(self, result):        Updates the info for each round        :param result:                self['rounds'] += 1        if result == 'win':            self['wins'] += 1        elif result == 'loss':            self['losses'] += 1        else:            self['draws'] += 1    def add2score(self):        Adds the scores from the game to total scores        self['games_played'] += 1        self['rounds_lost'] += self['losses']        self['rounds_won'] += self['wins']        self['rounds_tied'] += self['draws']        self['total_rounds'] += self['rounds']        if self['losses'] > self['wins']:            self['games_lost'] += 1        elif self['losses'] < self['wins']:            self['games_won'] += 1        else:            self['game_draws'] += 1    def stats_board(self):        Returns the stat board        return ('\n\n++++++++++++++++++++-=Stats=-++++++++++++++++++++++\n'                '===================================================\n'                '|--{:^11}--|--{:^12}--|--{:^12}--|\n'.format(X   , Rounds, Games) +                '|--{:^11}--|--{:^12}--|--{:^12}--|\n'.format(Wins  , self['rounds_won'], self['games_won']) +                '|--{:^11}--|--{:^12}--|--{:^12}--|\n'.format(Losses, self['rounds_lost'], self['games_lost']) +                '|--{:^11}--|--{:^12}--|--{:^12}--|\n'.format(Draws , self['rounds_tied'], self['game_draws']) +                '|--{:^11}--|--{:^12}--|--{:^12}--|\n'.format(Played, self['total_rounds'], self['games_played']) +                '\n')    def final_score(self):        Returns the games final board        return ('\n\n+++++++++++Final Score++++++++++++\n'                '==================================\n'                '|--{:^11}--|--{:^12}--|\n'.format('Wins  ', self['wins']) +                '|--{:^11}--|--{:^12}--|\n'.format('Losses', self['losses']) +                '|--{:^11}--|--{:^12}--|\n'.format('Draws  ', self['draws']) +                '|--{:^11}--|--{:^12}--|\n'.format('Rounds', self['rounds']) +                '\n')class Game:    Main Game Class    MOVES = ('rock', 'paper', 'scissors')    ROUND_OPTIONS = ('1', '3', '5', '7')    MENU = ('stats', 'play', 'quit')    WIN_CONDITIONS = {        rock: scissors,        scissors: paper,        paper: rock,    }    green = colorama.Fore.GREEN    yellow = colorama.Fore.YELLOW    cyan = colorama.Fore.CYAN    blue = colorama.Fore.BLUE    magenta = colorama.Fore.MAGENTA    COLORS = (green, yellow, cyan, blue, magenta)    def delirioussyntax(self):        Creator        colorama.init()        idle = idlelib in sys.modules        time.sleep(.3)        if idle is False:            print(colorama.Back.BLACK + colorama.Style.BRIGHT, end='')        for letter in Delirious:            if idle is False:                print(random.choice(Game.COLORS), end='')            print(letter, end='')            sys.stdout.flush()            time.sleep(random.uniform(.1, .4))        if idle is False:            print(colorama.Fore.RED, end='')        for letter in Syntax:            print(letter, end='')            sys.stdout.flush()            time.sleep(random.uniform(.1, .4))        print(colorama.Style.RESET_ALL, \n\n)    def intro(self):        Intro statements        self.Score = Scorecard()        try:            with shelve.open(RPSv2.dat) as f:                self.Score = f[statistics]        except KeyError:            with shelve.open(RPSv2.dat) as f:                f[statistics] = self.Score        print(Brought to you by: , end='')        sys.stdout.flush()        self.delirioussyntax()        time.sleep(1)        print(Welcome to the greatest, mind blowing challenge of all time.)        print(     - Rock, Paper, Scissors!\n)        print(Many have tried and many have FAILED...)        print(This will be a test between the human mind and my AI.)        input(Press \enter\ when you believe your ready for this challenge.\n)        print(\nGood Luck... Human.\n\n)    def custom_input(self, question, choices):        A custom loop that checks to see if choices are valid        :param choices:        :param question:                response = None        while True:            while response not in choices:                response = input(question).lower()                if response in choices:                    return response                print(Correct inputs:\n  >, end='')                print(\n  >.join(choices), \n)    def start_game(self):        Main menu of game basically        self.intro()        while True:            choice = self.custom_input(Would you like to play, look at stats, or quit?:\n   ->, self.MENU)            if choice == 'play':                self.play_game()            elif choice == 'stats':                stats = self.Score.stats_board()                print(stats)            else:                break    def computer_choice_gen(self):        Generates computer choice        return random.choice(Game.MOVES)    def play_game(self):        Player vs Computer game        rounds = int(self.custom_input(\nHow many rounds would you like to play?\n   ->, self.ROUND_OPTIONS))        while self.Score['rounds'] != rounds:            self.user_choice = self.custom_input(\nWhat is your choice, human?\n   ->, self.MOVES)            self.computer_choice = self.computer_choice_gen()            print(\nComputer choice is {}.format(self.computer_choice))            result = self.evaluate()            self.Score.round_update(result)            if result == win:                print({} beats {}! The human wins this round.\n\n.format(self.user_choice, self.computer_choice))            elif result == loss:                print({} beats {}! Hahaha! You lost this round!\n\n.format(self.computer_choice, self.user_choice))            else:                print(I knew you were going to pick {}!\n\n.format(self.user_choice))        print(self.Score.final_score())        self.Score.add2score()        self.Score.reset()        with shelve.open(RPSv2.dat) as f:            f[statistics] = self.Score    def evaluate(self):        returns if outcome of round        if Game.WIN_CONDITIONS[self.user_choice] == self.computer_choice:            return win        elif self.user_choice == self.computer_choice:            return draw        else:            return lossif __name__ == __main__:    Game = Game()    Game.start_game()
RockPaperScissorsV2 follow-up Advanced
python;python 3.x;rock paper scissors
Great to see the advice from the first question helped! I just have some small notes for this one.Good to have docstrings everywhere, but you can use ''' for docstrings, which is easier to read than having to escape a double quote. '''Stores all items related to Score'''Speaking of docstingsdef delirioussyntax(self):    CreatorThat's really not clear what it does. If it was intended as an easter egg then it is fun to be confused, call the function and see it run. But since you're calling it then it's not really hidden. Give it a clearer name or a docstring that actually outlines what it does.def print_creator_name(self):In custom_input you've got two while loops, but really you just need the while True one. You have a return statement, so as soon as there's a valid input there, your function returns and the loop ends. There's no other time the loop should end, so you can otherwise loop indefinitely:def custom_input(self, question, choices):    A custom loop that checks to see if choices are valid    :param choices:    :param question:        while True:        response = input(question).lower()        if response in choices:            return response        print(Correct inputs:\n  >, end='')        print(\n  >.join(choices), \n)Even though you're only incrementing by one, I'd personally say it's better to use less than to test in play_game. It makes more logical sense to the brain that you want to run while you have played fewer rounds than the full set. But it's marginally more confusing to think that you're playing rounds while the amount played isn't equal to the chosen amount. It's also a way to avoid potential bugs, in case the round amount is ever incremented by more than 1, you might forget to update this and accidentally create an infinite loop.    while self.Score['rounds'] < rounds: