Получайте удовольствие от пользовательского класса, чтобы играть в Column4 на консоли

После моего последнего туториала Крестики-нолики (ссылка) мне захотелось создать еще одну игру на основе оболочки с Python. Когда я был ребенком, я много играл в Connect4 (puissance 4 по-французски), и эта игра напомнила мне много воспоминаний. Вот почему я хотел создать свою собственную игру с классом Python.
Прежде всего, я должен признать, что я не убийца в математике, поэтому просто думать о том, как узнать, что одна диагональ заполнена тот же маркер заставляет меня иметь несколько плохих ночей. Но вы увидите, я нашел способ, который отлично работает, и на самом деле не так ориентирован на математику…

Я поделюсь с вами всем кодом в конце этой статьи, если вы хотите его пропустить. Если нет, я буду делиться им блок за блоком, чтобы объяснить вам логику всего этого.

Во-первых, вам нужно немного узнать о классах Python, я не буду объяснять здесь всю концепцию (в Интернете есть множество статей, я даже создал одну самостоятельно, если вы хотите посмотреть.

Давайте начнем с создания собственного класса с некоторыми основными параметрами внутри метода __init__:

class Connect4:
def __init__(self):
    self.numberOfColumns = 7
    self.numberOfColumns = 6
    self.board = [ [ '  ' for _ in range(self.numberOfColumns)] for _ in range(self.numberOfLines) ]

По сути, здесь мы определяем наш класс, начиная с метода __init__, что означает, что каждый раз, когда вызывается наш класс, три элемента numberOfColumns, numberOfColumns и board также будут создаваться.
Это создаст список из 6 списков, каждый один содержит 7 элементов (которые будут нашими столбцами). Каждый элемент пуст.

  def displayBoard(self):
    for i, line in enumerate(self.board):
      # Printing the line separators
      print("_" * self.numberOfColumns * 4)
      # Printing the line
      print(*line, sep=' |')
      # Printing numbers
      print('    '.join(str(x) for x in range(self.numberOfColumns)))
  def isAvailable(self, line, column):
    if line[column] == '  ':
      return True
    return False
  def player_choice(self):
    choice = int(input("Please select an empty space between 0 and 6 : "))
    while self.board[0][choice] != '  ':
      choice = int(input("This column is full. Please choose between 0 and 6 : "))
    return choice
  def player_input(self):
    player1 = input("Please pick a marker 'X' or 'O' ")
    while True:
      if player1.upper() == 'X':
        player2='O'
        print("You've choosen " + player1 + ". Player 2 will be " + player2)
        return player1.upper(),player2
      elif player1.upper() == 'O':
        player2='X'
        print("You've choosen " + player1 + ". Player 2 will be " + player2)
        return player1.upper(),player2
      else:
         player1 = input("Please pick a marker 'X' or 'O' ")
  def play(self, playercolumn, marker):
    for item in reversed(self.board):
      if self.isAvailable(item, playercolumn):
        item[playercolumn] = " " + marker
        return True
      return False

Здесь у нас есть четыре основные функции для работы с пользовательским выбором и печатью.

  • displayBoard : отображать доску на основе нашего основного списка под названием self.board.
  • isAvailable : вернуть True или False, если слот доступен для игры
  • player_choice : выберите столбец для игры
  • player_input : выбор маркера для игроков
  • игра : поместите маркер

Итак, сейчас мы начнем с самых простых функций, которые легко понять. Теперь пришло время проверить, выиграл ли кто-нибудь раунд!

  def checkLines(self, marker, board=None):
    if board is None:
      board=self.board
      # Checkin lines
      for line in board:
        for i in range(0,len(line)):
          if i < len(line) - 3:
            if line[i] == line[i+1] == line[i+2] == line[i+3] == " " + marker:
              return True
  def checkDiags(self, marker):
    diagBoard = []
      for i, line in enumerate(self.board):
        for idx, item in enumerate(line):
        # Find of there is some marker
          if item == ' ' + marker:
            diagBoard.append(int(str(i)+str(idx)))
      for item in diagBoard:
        if int(item) + 11 in diagBoard and int(item) + 22 in diagBoard and int(item) + 33 in diagBoard:
          return True
      for item in reversed(diagBoard):
        if int(item) - 9 in diagBoard and int(item) - 18 in diagBoard and int(item) - 27 in diagBoard:
          return True
  def generateReversedBoard(self):
    reversedBoard = []
    for line in self.board:
      for index, item in enumerate(line):
        try:
          reversedBoard[index].append(item)
        except:
          reversedBoard.append([])
          reversedBoard[index].append(item)
    return reversedBoard

Итак, что нам здесь нужно? Функция для проверки заполнения строк. Может быть хорошей идеей использовать эту функцию как для столбцов, так и для строк.
Таким образом, эта функция будет проверять, только если в строке есть 4 маркера подряд. Чтобы также проверить столбцы, нам может понадобиться преобразовать столбцы в строки.

Это достигается функцией generateReversedBoard, которая использует доску по умолчанию (self.board) и возвращает ту же доску, но перевернутую.

Теперь обе платы можно проверить с помощью нашей функции checkLines. По умолчанию он использует self.board в качестве доски по умолчанию. Но вы можете передать плату в качестве параметра, чтобы перегрузить этот параметр. Эта функция проверит, есть ли у вас 4 одинаковых маркера в строке со следующим оператором

if i < len(line) - 3:
  # Avoid out of range exception
  if line[i] == line[i+1] == line[i+2] == line[i+3] == " " + marker:

Затем нам нужно, чтобы кто-то выиграл по диагонали. Эта функция будет применять следующую логику:

Таким образом, мы преобразуем каждый маркер в строку из двух цифр, где обе цифры: цифра горизонтальной линии + цифра вертикальной линии
Это помещается в список, где мы можем применить базовую функцию:

# Check if someone won from left to the right
if int(item) + 11 in diagBoard and int(item) + 22 in diagBoard and int(item) + 33 in diagBoard:
  return True
OR
# Check if someone won from right to the left
if int(item) - 9 in diagBoard and int(item) - 18 in diagBoard and int(item) - 27 in diagBoard:
  return True

Итак, в нашем примере мы ясно видим, что выиграл маркер «О». И это проверяется в нашем списке, который содержит следующие элементы, соответствующие нашему первому оператору if: [00, 11,21, 22, 33].

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

c = Connect4()
# First while lopp init
game = True
while game:
  # Choose your marker
  players = c.player_input()
  # Display the board
  c.displayBoard()
  # Second while loop init
  win = False
  i = 1
  while not win:
    # Start Playing
    if i % 2 == 0:
      currentPlayer = "Player1"
      marker = players[1]
    else:
      currentPlayer = "Player2"
      marker = players[0]
    # Player to choose where to put the mark
    position = c.player_choice()
      if not c.play(position, marker):
        print(f"Column {position} full")
        # Generate the reversed board
        reversedBoard = c.generateReversedBoard()
        # Check if won
        if c.checkLines(marker) or c.checkLines(marker, reversedBoard) or c.checkDiags(marker):
          # update the win to exit the second while loop
          win = True
          c.displayBoard()
          print(f"Game won by {currentPlayer}")
          # Ask for replay.
          # If no, change the first loop game = True to False
          # If yes, reset our class with fresh new datas
          replay = input("Do you want to play again (Y/N) ? ")
          if replay.lower() == 'n':
            game = False
            print("Game ended !")
          else:
            c = Connect4()
          break
        c.displayBoard()
        i += 1

Итак, я надеюсь, что всем понравился этот урок, я знаю, что отображение не идеально, но это не было здесь целью, если кто-то может исправить это, пожалуйста, не стесняйтесь :)

Дайте мне знать, что вы думаете об этой статье, и не стесняйтесь использовать раздел комментариев, чтобы поделиться своими идеями.

Вот полный список доступных:

https://gist.github.com/gmariette/232599715b45e8b5412113ea40f52d92