Я сделал крестики-нолики на 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 ответ
Именование переменных идеально, и вы следуете соглашению Python — и это действительно хорошо, потому что это упрощает чтение кода.
Я выполнил код и заметил, что когда вы вставляете недопустимый ввод, могут произойти две вещи.
- Вход «1А»; Вывод: «Неверный формат. Это место уже занято».
- Введите «X»; Выход: сбой приложения
Так что это ошибка, которую нам нужно исправить.
Самые большие проблемы в коде:
- Использование глобальной области видимости (то есть размещение переменных вне методов)
- Дублирование кода
Что касается первого, нам нужно поместить все в метод. Это лучше, потому что вы ограничите область действия переменных, упростите чтение, а цель каждой строки будет более ясной. Кроме того, всегда лучше определять все методы в начале, а затем код, который необходимо выполнить (он должен быть в 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()
Как работает победитель чека? Разве мне еще не нужно записывать все логические значения?
— Пользователь
@user Да и нет. Вы должны проверить каждую ячейку, чтобы быть уверенным, выиграл кто-то или нет, но вам не нужно записывать каждую ячейку. Представьте, что вы играете в Connect Four на гигантской доске, как бы вы это сделали? Крестики-нолики проще, потому что вы можете просто проверить, что вся строка / столбец / диагональ равна символу.
— fabiofili2pi