Rock Paper Scissor with a CPU (console) I tried my hands on Rock-Paper-Scissors. But the twist is that the game has a brain that aims to learns your behaviour.

The brain is a simple counter that updates the player's current choice based on players previous choices. The level determines the number of previous choices cpu will remember. In any event the player tracks its own inputs and makes calculated choices to defeat the CPU, I have added a timed input function i.e. if you don't provide input within 5 secs, you lose.

The game works perfectly, however I am unhappy with the main function. There are too many try-except blocks. My questions are


What are possible improvements ?
How do I improve the print_cpu_data function in the way it shows the information ?
If I wish to make a GUI for the game (e.g tkinter, Pygame, etc), what part of the code should be made into functions and how to do so efficiently ?


As I am not a beginner (somewhat less than intermediate) , I appreciate some advanced ideas and concepts.

"""
Rock-Paper-Scissor with CPU
"""
from itertools import product
import select
import sys

SCORE_TABLE = [[1, 0, 2],
               [2, 1, 0],
               [0, 2, 1]]
SCORE_STR = ['LOSE', 'DRAW', 'WIN']
ROCK = 0
PAPER = 1
SCISSOR = 2
CHOICE_STR = ['ROCK', 'PAPER', 'SCISSOR']
WIN_MOVE = [PAPER, SCISSOR, ROCK]


class GameCpu:
    """Class to create game CPU"""
    def __init__(self, level=1):
        self.level = level
        self.probs = {}
        for t in product([ROCK, PAPER, SCISSOR], repeat=level):
            self.probs[t] = [0, 0, 0]
        self.prev = [0]*level

    def get_cpu_choice(self):
        """Return CPU choice"""
        tmp = self.probs[tuple(self.prev)]
        pred = tmp.index(max(tmp))
        return WIN_MOVE[pred]

    def train_cpu(self, choice_player):
        """Train CPU with current player choice"""
        self.probs[tuple(self.prev)][choice_player] += 1
        self.prev.pop(0)
        self.prev.append(choice_player)

    def print_cpu_data(self):
        """Print CPU data"""
        print('\nPattern', '\t'*self.level, 'Choice count\n')
        for key, val in self.probs.items():
            print(*[f'{CHOICE_STR[k]}' for k in key], sep=', ', end='\t\t')
            print(*[f'{CHOICE_STR[i]}:{v}' for i, v in enumerate(val)])


def input_with_timeout(prompt, timeout):
    """Timed input"""
    sys.stdout.write(prompt)
    sys.stdout.flush()
    ready, _, _ = select.select([sys.stdin], [], [], timeout)
    if ready:
        return sys.stdin.readline().rstrip('\n')
    raise Exception

def main():
    """main function"""
    while True:
        try:
            level = int(input('Enter CPU level (Level 1,2,3): '))
            assert 1 &lt;= level &lt;= 3
            break
        except:
            print('Invalid.')

    print('\nPress Q to quit game\
        \nRemember you have got only 5secs to give input.\
        \nThe choices are ROCK:1  PAPER:2  SCISSOR:3\n')

    cpu = GameCpu(level)
    scores = [0, 0, 0]
    timeout = 5
    while True:
        choice_player = None
        try:
            choice_player = input_with_timeout('Enter choice: ', timeout)
        except:
            print('Times up')

        if not choice_player:
            break
        if choice_player.lower() == 'q':
            break
        try:
            choice_player = int(choice_player)-1
            assert 0 &lt;= choice_player &lt;= 2
        except:
            print('Invalid. ', end='\n')
            continue

        choice_cpu = cpu.get_cpu_choice()
        s = SCORE_TABLE[choice_player][choice_cpu]
        scores[s] += 1
        print(f'{SCORE_STR[s]}')

        cpu.train_cpu(choice_player)

    total = max(1, sum(scores))
    percent = [100*s/total for s in scores]

    print('\nSTATS: \n')
    print(f'Total games played: {total}')
    print(*[f'{SCORE_STR[i]}:{p:.2f}% ' for i, p in enumerate(percent)])
    cpu.print_cpu_data()


if __name__ == '__main__':
    main()
How do I improve the print_cpu_data function in the way it shows the information ?

Well, nowadays the most common architectural pattern (design scheme) for many projects is the MVC its principle is to separate the classes and code constructs in your project into 3 packages (model-view-controller)


In the model goes all the code which constitutes your application logic for example your class GameCpu
In the view goes the code which displays the information that the user is supposed to see in exmaple a class which uses tkinter, Pygame etc.
In the controller goes the code which allows communication between model and view.


You need to be aware that Desktop apps and Web apps have a different approach for the MVC due its nature.

If I wish to make a GUI for the game (e.g tkinter, Pygame, etc), what part of the code should be made into functions and how to do so efficiently ?

As I said, you could use an MVC pattern which is basically 3 packages named model, view, controller and import classes accordingly.

I believe your question is more likely to be answered with modularization and refactoring

Modularization is to split independent code blocs into functions, and refactoring is to simplify the code you have written.

Well, I hope it helped you, maybe you need a couple of youtube videos to understand how MVC works. I know, it seems more work, but when you are debugging medium to semi-large personal projects it is worthy, in great scale projects its indispensable. (not just MVC, to use an architectural pattern with an enterprise architecture [the names makes it sound complex but actually it isn't]).

Note: You may ask why did i not answered your first question, well, the code does not work when I execute it, so I can't make suggestions in the air, plus if I recommend a new form to solve this problem, many mods request answers to be bassed on the poster's code.