0
\$\begingroup\$

Used classes to make this simple tic tac toe game.

Any improvements?

from itertools import chain


class Field:
    def __init__(self, sequence):
        self.sequence = sequence
        self.rows = [[sequence[i] for i in range(j, j + 3)] for j in range(0, 9, 3)]
        self.columns = [[row[i] for row in self.rows] for i in range(3)]
        self.first_diagonal = self.rows[0][0] + self.rows[1][1] + self.rows[2][2]
        self.second_diagonal = self.rows[0][2] + self.rows[1][1] + self.rows[2][0]
        self.diagonals = [self.first_diagonal, self.second_diagonal]

    def __str__(self):
        return '''\
---------
| {} {} {} |
| {} {} {} |
| {} {} {} |
---------\
'''.format(*self.sequence)

    def get_value(self, x, y):
        return self.rows[x][y]

    def set_value(self, x, y, value):
        self.rows[x][y] = value
        sequence = ''.join(list(chain.from_iterable(self.rows)))

        self.__init__(sequence)  # Reinitializing attributes. 

    def get_state(self):

        num_x = self.sequence.lower().count('x')
        num_o = self.sequence.lower().count('o')

        if abs(num_x - num_o) not in {0, 1}:
            return 'Impossible'

        winners = []
        for sequence in chain(self.rows, self.columns, self.diagonals):
            if len(set(sequence)) == 1:
                if sequence[0] not in winners and sequence[0].lower() in ['x', 'o']:
                    winners.append(sequence[0])

        if len(winners) > 1:
            return 'Impossible'
        elif winners:

            return f'{winners[0]} wins'

        # No winner
        else:
            if '_' in self.sequence or ' ' in self.sequence:
                return 'Game not finished'
            else:
                return 'Draw'


def convert_index(x, y):
    x, y = abs(y - 3), x - 1

    return x, y


def make_move(field, move, value):
    try:
        x, y = move.split()
    except ValueError:
        print('You should enter numbers!')
        return False

    if x.isnumeric() and y.isnumeric():
        x, y = int(x), int(y)
        if x in {1, 2, 3} and y in {1, 2, 3}:
            x, y = convert_index(x, y)
            if field.get_value(x, y).lower() not in ['x', 'o']:
                field.set_value(x, y, value)
                return True
            else:
                print('This cell is occupied! Choose another one!')
        else:
            print('Coordinates should be from 1 to 3!')
    else:
        print('You should enter numbers!')

    return False


field = Field('_' * 9)  # Empty field.

i = 1

while True:
    print(field)

    player = 'X' if i % 2 == 1 else 'O'

    ordinates = input()

    if make_move(field, ordinates, player):  # Only change player if a move was successful 
        i += 1

    if field.get_state() not in ['Game not finished', 'Impossible']:
        print(field)
        print(field.get_state())
        break

```
\$\endgroup\$
0
\$\begingroup\$

Input

One thing I would suggest is provide a hint to the user about the format of their coordinates input. It's not immediately obvious that the format should be x y. I had to look at the code to figure that out. Intuitively, one might expect to type in x and y on separate lines or maybe separate them with a comma.

One way to deal with this is to add a string in the input function, so rather than:

ordinates = input()

you could do something like:

ordinates = input("Please enter your coordinates (example: 1 2): ")

Better yet, it would be easier to deal with only individual x and y coordinates throughout the program, rather than mixing them with strings combining the two. For this, you would simply ask for the two coordinates with separate input() statements (e.g. input("Enter your x: ", input("Enter your y: ")). Then you don't have to worry about splitting strings or unpacking tuples or anything or the sort.

Coordinates

Another point is that the vertical coordinates seem to be backwards, in that the 1st row is at the bottom and the 3rd row at the top. Again it would make the game more user friendly to either specify this to the user or rearrange the grid array so that [1, 1] is top left and [3, 3] is bottom right.

| improve this answer | |
\$\endgroup\$

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.