3
\$\begingroup\$

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 <= level <= 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 <= choice_player <= 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()

\$\endgroup\$
2
  • \$\begingroup\$ When I executed your code it didn't allow me to intoduce any input, only the level of the CPU \$\endgroup\$ – Miguel Avila Jun 9 '20 at 19:10
  • \$\begingroup\$ Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$ – Mast Jun 10 '20 at 6:04
3
\$\begingroup\$

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.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.