Я учил себя Python и решил сделать игру в крестики-нолики, чтобы немного попрактиковаться. Любая критика или указатели приветствуются!
class TicTacToe:
"""A class for playing tic tac toe"""
def __init__(self):
self.values = ["-", "-", "-",
"-", "-", "-",
"-", "-", "-"]
self.player = "x"
def show_board(self):
print(f"{self.values[0]} | {self.values[1]} | {self.values[2]}")
print(f"{self.values[3]} | {self.values[4]} | {self.values[5]}")
print(f"{self.values[6]} | {self.values[7]} | {self.values[8]}")
def play_game(self):
# display initial board
self.show_board()
# play for 9 turns max
for i in range(9):
self.handle_turn(self.values)
if self.check_tie() or self.winner():
break
def handle_turn(self, values):
# get next move from a player
try:
turn = int(input(f"Player {self.player} pick a square (1-9) from left to right: "))
except ValueError:
self.handle_turn(self.values)
return
# change values to show the move made (check square empty)
if self.values[turn-1] == "x" or self.values[turn-1] == "o":
print("That square has been played already!")
self.handle_turn(self.values)
else:
self.values[turn-1] = self.player
self.show_board()
# switch turns
self.flip_player()
def winner(self):
# check all possible win methods
self.row_winner = self.check_rows()
self.column_winner = self.check_columns()
self.diagonal_winner = self.check_diagonals()
#declare a winner
if self.row_winner:
winner = self.check_rows()
print(f"{winner} has won the game!")
return True
elif self.column_winner:
winner = self.check_columns()
print(f"{winner} has won the game!")
return True
elif self.diagonal_winner:
winner = self.check_diagonals()
print(f"{winner} has won the game!")
return True
def check_rows(self):
# Check for a win in the rows
row1 = self.values[0] == self.values[1] == self.values[2] != "-"
row2 = self.values[3] == self.values[4] == self.values[5] != "-"
row3 = self.values[6] == self.values[7] == self.values[8] != "-"
# Return the player that has won
if row1:
return self.values[0]
elif row2:
return self.values[3]
elif row3:
return self.values[6]
def check_columns(self):
# Check for a win in the columns
col1 = self.values[0] == self.values[3] == self.values[6] != "-"
col2 = self.values[1] == self.values[4] == self.values[7] != "-"
col3 = self.values[2] == self.values[5] == self.values[8] != "-"
# Return the winning player
if col1:
return self.values[0]
elif col2:
return self.values[1]
elif col3:
return self.values[2]
def check_diagonals(self):
# Check for win in the diagonals
dia1 = self.values[0] == self.values[4] == self.values[8] != "-"
dia2 = self.values[2] == self.values[4] == self.values[6] != "-"
# Return the player that has won
if dia1:
return self.values[0]
elif dia2:
return self.values[2]
def check_tie(self):
if "-" not in self.values:
print("Game is a tie!")
return True
else:
return False
def flip_player(self):
if self.player == "x":
self.player = "o"
else:
self.player = "x"
if __name__ == "__main__":
new_board = TicTacToe()
new_board.play_game()
1 ответ
Визуальные эффекты
Можешь попробовать
self.values = [["-" for _ in range(3)] for _ in range(3)]
Это позволит вам создать сетку 3×3 без необходимости рисовать целиком.
Теперь вы также можете сделать
def show_board(self):
board_str = "n".join([ " | ".join(row) for row in grid ])
print(board_str)
Я бы также предложил полностью отказаться от функции show_board и вместо этого использовать (для печати)
def __str__(self):
return "n".join([ " | ".join(row) for row in grid ])
и / или (для печати в интерактивной консоли)
def __repr__(self):
return "n".join([ " | ".join(row) for row in grid ])
Тогда вы можете напрямую сделать
xoxo = TicTacToe
print(xoxo)
и внутри класса
print(self)
Представление и ввод данных
Я повторяюсь здесь, но не имеет смысла иметь значения в виде одномерного массива. Это доска для крестиков-ноликов … это должна быть сетка или доска 2D 3×3. Также вам, вероятно, следует переименовать переменную values
к grid
или же board
.
Это будет означать принятие 2 значений в качестве ввода от пользователя в handle_turn()
. Я предлагаю не класть внутрь большую строку input()
как это трудно читать. Попробуйте вместо этого
print("Enter space separated co-ordinates to pick a square")
print(f"Player {self.player} pick a square: "))
x = int(input())
y = int(input())
Хорошо, я признаю, что это немного раздражает игроков … может быть, вы могли бы написать координаты в пустых ячейках, например
X | 1,2 | O
2,1 | X | 1,3
3,1 | 3,2 | O
(попробуйте сделать это, не записывая всю стартовую сетку вручную, это должно быть возможно с использованием «,». join (), str () и понимания списка)
Также должна быть возможность изменять функции строк / столбцов / диагоналей с помощью 2D-индексов, например grid[0][0]
, grid[1][1]
, grid[2][2]
и т. д., чтобы сделать его более читаемым. Из-за способа self.grid
хранится, функция строки может быть упрощена … но она будет сильно отличаться от столбца / диагонали, и поэтому, вероятно, этого не стоит.
Большое спасибо за ваш ответ, это очень полезно!
— Дж. Л. Бирн
Это полезный обзор, и для новичков реализация крестиков-ноликов с 2-мерной сеткой интуитивно понятна, так что ваш совет здравый. Тем не менее, я видел, как кандидаты на вакансии реализуют решения в крестики-нолики эффективными, удобочитаемыми способами, используя 1d-список (например, диагональные проверки проще на равнине; то же самое можно сказать и о сборе данных, вводимых пользователем). В любом случае, это не имеет большого значения, но может быть разумным решить проблемы сетки с 1-мерными данными за кулисами.
— FMc
@Hannah W. При использовании
self.values = [ ["-"] * 3 ] * 3
похоже, что копия исходного списка делается трижды, так как все три строки будут изменены при выполнении одного хода, возможно, я реализовал это неправильно. Может быть лучше использовать что-то вродеself.values = [["-"] * 3 for _ in range(3)]
вместо?— Дж. Л. Бирн
@JLByrne, да, я пропустил это. На самом деле, было бы лучше не смешивать синтаксис и просто придерживаться понимания 2 вложенных списков imo.
— Ханна В.