# Первая реализация крестиков-ноликов для новичков

Я учил себя 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()
``````

# Визуальные эффекты

Можешь попробовать

``````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.

— Ханна В.