Крестики-нолики в Python 3.x

Я сделал крестики-нолики на Python. Стандартный обзор кода, подскажите, пожалуйста, способы улучшения. Я новичок в Python, поэтому, пожалуйста, не критикуйте окольные пути. Спасибо.

board = []
for x in range(3):
    board.append([' '] * 3)
def print_board(board):
    x = 1
    print('    A   B   C')
    print()
    dashes = 0
    for row in board:
        print(x, ' ', ' | '.join(row))
        if dashes <= 1:
            print(' ', ' -'*6)
            dashes += 1
        x += 1
def split(word):
    return [char for char in word]
print('This is Tic Tac Toe.')
print('Player 1 will be X and Player 2 will be O.')
win = False
while win != True:
    print()
    print_board(board)
    print()
    while True:
        p1 = input('Player 1, where would you like to place a X? ').lower()  
        try:
            p1_col_let = split(p1)[0]
            p1_row = int(split(p1)[1])
            p1_col = int(ord(p1_col_let) - 96)
        except ValueError:
            print('Invalid format.')
        if board[p1_row - 1][p1_col - 1] != ' ':
            print('That spot is already taken.')
        else:
            break
    while True:
        p2 = input('Player 2, where would you like to place an O? ').lower()
        try:
            p2_col_let = split(p2)[0]
            p2_row = int(split(p2)[1])
            p2_col = int(ord(p2_col_let) - 96)
        except ValueError:
            print('Invalid format.')
        if board[p2_row - 1][p2_col - 1] != ' ' or p2_row == p1_row and p2_col == p1_col:
            print('That spot is already taken.')
        else:
            break
    board[p1_row - 1][p1_col - 1] = 'X'
    board[p2_row - 1][p2_col - 1] = 'O'
    a1 = board[0][0]
    a2 = board[1][0]
    a3 = board[2][0]
    b1 = board[0][1]
    b2 = board[1][1]
    b3 = board[2][1]
    c1 = board[0][2]
    c2 = board[1][2]
    c3 = board[2][2]
    if a1 == 'X' and a2 == 'X' and a3 == 'X' or b1 == 'X' and b2 == 'X' and b3 == 'X' or c1 == 'X' and c2 == 'X' and c3 == 'X' or a1 == 'X' and b1 == 'X' and c1 == 'X' or a2 == 'X' and b2 == 'X' and c2 == 'X' or a3 == 'X' or b3 == 'X' or c3 == 'X' or a1 == 'X' and b2 == 'X' and c3 == 'X' or c1 == 'X' and b2 == 'X' and a3 == 'X':
        print()
        print_board(board)
        print()
        print('Player 1 wins!')
        win = True
    if a1 == 'O' and  a2 == 'O' and a3 == 'O' or b1 == 'O' and b2 == 'O' and b3 == 'O' or c1 == 'O' and c2 == 'O' and c3 == 'O' or a1 == 'O' and b1 == 'O' and c1 == 'O' or a2 == 'O' and b2 == 'O' and c2 == 'O' or a3 == 'O' or b3 == 'O' or c3 == 'O' or a1 == 'O' and b2 == 'O' and c3 == 'O' or c1 == 'O' and b2 == 'O' and a3 == 'O':
        print()
        print_board(board)
        print()
        print('Player 2 wins!')
        win = True
    if a1 != ' ' and a2 != ' ' and a3 != ' ' and b1 != ' ' and b2 != ' ' and b3 != ' ' and c1 != ' ' and c2 != ' ' and c3 != ' ':
        print('It's a tie!')
        break

1 ответ
1

Именование переменных идеально, и вы следуете соглашению Python — и это действительно хорошо, потому что это упрощает чтение кода.

Я выполнил код и заметил, что когда вы вставляете недопустимый ввод, могут произойти две вещи.

  1. Вход «1А»; Вывод: «Неверный формат. Это место уже занято».
  2. Введите «X»; Выход: сбой приложения

Так что это ошибка, которую нам нужно исправить.

Самые большие проблемы в коде:

  1. Использование глобальной области видимости (то есть размещение переменных вне методов)
  2. Дублирование кода

Что касается первого, нам нужно поместить все в метод. Это лучше, потому что вы ограничите область действия переменных, упростите чтение, а цель каждой строки будет более ясной. Кроме того, всегда лучше определять все методы в начале, а затем код, который необходимо выполнить (он должен быть в main метод, обычно).

Если бы я сделал это, я бы создал Board class но нет особой причины использовать здесь объектно-ориентированное программирование, поэтому я постараюсь следовать вашему пути рассуждений.

Сначала мы инициализируем плату, поэтому

def create_board():
    board = []
    for _ in range(3):
        board.append([' '] * 3)
    return board

На самом деле вы не использовали x переменная в цикле, поэтому я переименовал ее в _, что означает соглашение о том, что вас на самом деле не интересует эта переменная. Иметь магические числа, такие как 3 в данном случае, плохо, потому что когда-нибудь вы можете спросить: «Почему 3?». Теперь это довольно очевидно, но может стать более сложным. Простой способ исправить это

def create_board(size=3):
    board = []
    for _ in range(size):
        board.append([' '] * size)
    return board

Поведение такое же, но теперь все будут знать, что такое 3. Совершенно бесполезно иметь доску параметрического размера, потому что вы можете играть в крестики-нолики только с досками 3×3, но я использую это как способ показать вам как быть готовым к дальнейшим улучшениям (Connect-Four? :)).

Теперь распечатаем плату. Ваш код на самом деле был довольно хорош, я хотел добавить доску переменного размера, потому что в вашем коде вы используете тот факт, что плата 3×3 многими скрытыми способами. (Например if dashes <= 1:).

def print_board(board):
    board_size = len(board)
    cols = [chr(i) for i in range(ord('A'), ord('A') + board_size)]
    print()
    print(f"    {'   '.join(cols)}")
    print()
    row_label = 1
    for row in board:
        print(row_label, ' ', ' | '.join(row))
        if row_label < board_size:
            print(' ', ' -' * (2 * board_size))
        row_label += 1
    print()

x стал row_label потому что это то, что есть. Я добавил print() до и после, потому что они, по сути, являются частью печати доски.

В split метод бесполезен, символы в string уже доступны по индексу. Так что мы можем избавиться от него, и игра будет работать как раньше.

Как я уже говорил, в коде есть несколько дубликатов, и они находятся в игровом цикле, давайте попробуем реорганизовать эту часть, начиная с ввода пользователя:

def ask_value(player_name, player_symbol, board):
    board_size = len(board)
    while True:
        player_input = input(f"{player_name}, where would you like to place a {player_symbol}").lower()
        try:
            col_let = player_input[0]
            row = int(player_input[1]) - 1  # [REVIEW COMMENT] we use 0 based indexes, the user doesn't
            col = int(ord(col_let) - ord('a'))
            if (not (0 <= col < board_size)) or (not (0 <= row < board_size)):
                raise ValueError("Index out of board")
            if board[row][col] != ' ':
                print('That spot is already taken.')
                raise ValueError("Spot already taken")
            return row, col
        except (ValueError, IndexError):
            print('Invalid format.')

Этот метод заменит дублирование кода для ввода. Я добавил проверку диапазона, чтобы не выходить за пределы платы, и теперь ошибка проверки ввода исправлена. Избегайте использования индекса, отсчитываемого от нуля; это не нормальный подход, и вы избавитесь от всех этих - 1 по всему коду.

Часть, где вы проверяете, кто победил, немного громоздка и требует больше времени разработчика, чем время процессора (сколько времени потребовалось, чтобы проверить все логические условия? :)), также она не масштабируется с размером платы (я понимаю, что это не было проблемой для вас).

Я не собираюсь писать, как проверить, кто выиграл, но я предложу вам способ структурировать ваш код:

def check_winner(board, symbol):
   if check_rows(board, symbol):
       return symbol
   if check_cols(board, symbol):
       return symbol
   if check_diagonals(board, symbol):
       return symbol
   if check_tie():
       return "https://codereview.stackexchange.com/questions/260084/#"
   return None

Таким образом, основной цикл станет:

def main():
    board = create_board()
    print('This is Tic Tac Toe.')
    print('Player 1 will be X and Player 2 will be O.')
    players = [{"name": "Player 1", "symbol": "X"}, {"name": "Player 2", "symbol": "O"}]
    end = False
    while not end:  # [REVIEW COMMENT] this is more pythonish than "win != True"
        print_board(board)
        for player in players:
            symbol = player["symbol"]
            row, col = ask_value(player["name"], symbol, board)
            board[row][col] = symbol
            winning_symbol = check_winner(board, symbol)
            if winning_symbol == "https://codereview.stackexchange.com/questions/260084/#":
                print("It is a tie!")
                end = True
                break
            elif winning_symbol:
                print(f"{player['name']} has won!!")
                end = True
                break

if __name__ == '__main__':
    main()


  • Как работает победитель чека? Разве мне еще не нужно записывать все логические значения?

    — Пользователь

  • 1

    @user Да и нет. Вы должны проверить каждую ячейку, чтобы быть уверенным, выиграл кто-то или нет, но вам не нужно записывать каждую ячейку. Представьте, что вы играете в Connect Four на гигантской доске, как бы вы это сделали? Крестики-нолики проще, потому что вы можете просто проверить, что вся строка / столбец / диагональ равна символу.

    — fabiofili2pi


Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *