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

Я учил себя 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 ответ
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 хранится, функция строки может быть упрощена … но она будет сильно отличаться от столбца / диагонали, и поэтому, вероятно, этого не стоит.

  • Большое спасибо за ваш ответ, это очень полезно!

    — Дж. Л. Бирн

  • 1

    Это полезный обзор, и для новичков реализация крестиков-ноликов с 2-мерной сеткой интуитивно понятна, так что ваш совет здравый. Тем не менее, я видел, как кандидаты на вакансии реализуют решения в крестики-нолики эффективными, удобочитаемыми способами, используя 1d-список (например, диагональные проверки проще на равнине; то же самое можно сказать и о сборе данных, вводимых пользователем). В любом случае, это не имеет большого значения, но может быть разумным решить проблемы сетки с 1-мерными данными за кулисами.

    — FMc


  • 1

    @Hannah W. При использовании self.values = [ ["-"] * 3 ] * 3 похоже, что копия исходного списка делается трижды, так как все три строки будут изменены при выполнении одного хода, возможно, я реализовал это неправильно. Может быть лучше использовать что-то вроде self.values = [["-"] * 3 for _ in range(3)] вместо?

    — Дж. Л. Бирн

  • 1

    @JLByrne, да, я пропустил это. На самом деле, было бы лучше не смешивать синтаксис и просто придерживаться понимания 2 вложенных списков imo.

    — Ханна В.

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

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