Главная страница
Навигация по странице:

  • Вывод результатов

  • Исходный код

  • Написание кода

  • Чистыйкод дляпродолжающи х


    Скачать 7.85 Mb.
    НазваниеЧистыйкод дляпродолжающи х
    Дата13.05.2023
    Размер7.85 Mb.
    Формат файлаpdf
    Имя файлаPython_Chisty_kod_dlya_prodolzhayuschikh_2022_El_Sveygart.pdf
    ТипДокументы
    #1127485
    страница32 из 40
    1   ...   28   29   30   31   32   33   34   35   ...   40
    Игра «Четыре в ряд»
    Это игра для двух игроков: каждый пытается выстроить ряд из четырех своих фишек по горизонтали, по вертикали или по диагонали, помещая свои фишки на самое нижнее свободное место в столбце. В этой игре используется вертикальная доска 7
    × 6. В нашей реализации игры два игрока-человека (обозначаемые X и O) играют друг с другом (режим игры с компьютером не поддерживается).
    Вывод результатов
    При запуске программы «Четыре в ряд» вывод будет выглядеть примерно так:
    Four-in-a-Row, by Al Sweigart al@inventwithpython.com
    Two players take turns dropping tiles into one of seven columns, trying to make four in a row horizontally, vertically, or diagonally.
    1234567
    +-------+
    |.......|
    |.......|
    |.......|
    |.......|
    |.......|
    |.......|
    +-------+
    Player X, enter 1 to 7 or QUIT:
    > 1 1234567
    +-------+
    |.......|
    |.......|
    |.......|
    |.......|
    |.......|
    |X......|
    +-------+
    Player O, enter 1 to 7 or QUIT:
    --snip--

    302
    Глава 14.Проекты для тренировки
    Player O, enter 1 to 7 or QUIT:
    > 4
    1234567
    +-------+
    |.......|
    |.......|
    |...O...|
    |X.OO...|
    |X.XO...|
    |XOXO..X|
    +-------+
    Player O has won!
    Игрок должен найти такую стратегию, которая позволит ему выстроить четыре фишки в ряд, одновременно не позволяя сделать то же самое противнику.
    Исходный код
    Откройте в редакторе или IDE новый файл и введите приведенный ниже код. Со- храните файл под именем fourinarow.py
    """Four-in-a-Row, by Al Sweigart al@inventwithpython.com
    Игра на выстраивание четырех фишек в ряд."""
    import sys
    # Константы, используемые для вывода игрового поля:
    EMPTY_SPACE = "." # Точки проще подсчитать, чем пробелы.
    PLAYER_X = "X"
    PLAYER_O = "O"
    # Примечание: если BOARD_WIDTH изменится, обновите BOARD_TEMPLATE и COLUMN_LABELS.
    BOARD_WIDTH = 7
    BOARD_HEIGHT = 6
    COLUMN_LABELS = ("1", "2", "3", "4", "5", "6", "7")
    assert len(COLUMN_LABELS) == BOARD_WIDTH
    # Шаблонная строка для вывода игрового поля:
    BOARD_TEMPLATE = """
    1234567
    +-------+
    |{}{}{}{}{}{}{}|
    |{}{}{}{}{}{}{}|
    |{}{}{}{}{}{}{}|
    |{}{}{}{}{}{}{}|
    |{}{}{}{}{}{}{}|
    |{}{}{}{}{}{}{}|
    +-------+"""
    def main():
    """Проводит одну игру Четыре в ряд."""

    Игра «Четыре в ряд»
    303
    print(
    """Four-in-a-Row, by Al Sweigart al@inventwithpython.com
    Два игрока по очереди опускают фишки в один из семи столбцов,
    стараясь выстроить четыре фишки по вертикали, горизонтали или диагонали.
    """
    )
    # Подготовка новой игры:
    gameBoard = getNewBoard()
    playerTurn = PLAYER_X
    while True: # Обрабатывает ход игрока.
    # Вывод игрового поля и получение хода игрока:
    displayBoard(gameBoard)
    playerMove = getPlayerMove(playerTurn, gameBoard)
    gameBoard[playerMove] = playerTurn
    # Проверка победы или ничьей:
    if isWinner(playerTurn, gameBoard):
    displayBoard(gameBoard) # В последний раз вывести поле.
    print("Player {} has won!".format(playerTurn))
    sys.exit()
    elif isFull(gameBoard):
    displayBoard(gameBoard) # В последний раз вывести поле.
    print("There is a tie!")
    sys.exit()
    # Ход передается другому игроку:
    if playerTurn == PLAYER_X:
    playerTurn = PLAYER_O
    elif playerTurn == PLAYER_O:
    playerTurn = PLAYER_X
    def getNewBoard():
    """Возвращает словарь, представляющий игровое поле.
    Ключи - кортежи (columnIndex, rowIndex) с двумя целыми числами,
    а значения - одна из строк "X", "O" or "." (пробел)."""
    board = {}
    for rowIndex in range(BOARD_HEIGHT):
    for columnIndex in range(BOARD_WIDTH):
    board[(columnIndex, rowIndex)] = EMPTY_SPACE
    return board def displayBoard(board):
    """Выводит на экран игровое поле и фишки."""
    # Подготовить список, передаваемый строковому методу format() для
    # шаблона игрового поля. Список содержит все фишки игрового поля
    # и пустые ячейки, перечисляемые слева направо, сверху вниз:
    tileChars = []
    for rowIndex in range(BOARD_HEIGHT):
    for columnIndex in range(BOARD_WIDTH):
    tileChars.append(board[(columnIndex, rowIndex)])

    304
    Глава 14.Проекты для тренировки
    # Выводит игровое поле:
    print(BOARD_TEMPLATE.format(*tileChars))
    def getPlayerMove(playerTile, board):
    """Предлагает игроку выбрать столбец для размещения фишки.
    Возвращает кортеж (столбец, строка) итогового положения фишки."""
    while True: # Пока игрок не введет допустимый ход.
    print(f"Player {playerTile}, enter 1 to {BOARD_WIDTH} or QUIT:")
    response = input("> ").upper().strip()
    if response == "QUIT":
    print("Thanks for playing!")
    sys.exit()
    if response not in COLUMN_LABELS:
    print(f"Enter a number from 1 to {BOARD_WIDTH}.")
    continue # Снова запросить ход.
    columnIndex = int(response) - 1 # -1, потому что индексы начинаются с 0.
    # Если столбец заполнен, снова запросить ход:
    if board[(columnIndex, 0)] != EMPTY_SPACE:
    print("That column is full, select another one.")
    continue # Снова запросить ход.
    # Начать снизу, найти первую пустую ячейку.
    for rowIndex in range(BOARD_HEIGHT - 1, -1, -1):
    if board[(columnIndex, rowIndex)] == EMPTY_SPACE:
    return (columnIndex, rowIndex)
    def isFull(board):
    """Возвращает True, если в `board` не осталось пустых ячеек,
    иначе возвращается False."""
    for rowIndex in range(BOARD_HEIGHT):
    for columnIndex in range(BOARD_WIDTH):
    if board[(columnIndex, rowIndex)] == EMPTY_SPACE:
    return False # Пустая ячейка найдена, вернуть False.
    return True # Все ячейки заполнены.
    def isWinner(playerTile, board):
    """Возвращает True, если `playerTile` образует ряд из четырех фишек в `board`, в противном случае возвращается False."""
    # Проверить всю доску в поисках четырех фишек в ряд:
    for columnIndex in range(BOARD_WIDTH - 3):
    for rowIndex in range(BOARD_HEIGHT):
    # Проверить четверку направо:
    tile1 = board[(columnIndex, rowIndex)]
    tile2 = board[(columnIndex + 1, rowIndex)]
    tile3 = board[(columnIndex + 2, rowIndex)]
    tile4 = board[(columnIndex + 3, rowIndex)]
    if tile1 == tile2 == tile3 == tile4 == playerTile:
    return True

    Игра «Четыре в ряд»
    305
    for columnIndex in range(BOARD_WIDTH):
    for rowIndex in range(BOARD_HEIGHT - 3):
    # Проверить четверку вниз:
    tile1 = board[(columnIndex, rowIndex)]
    tile2 = board[(columnIndex, rowIndex + 1)]
    tile3 = board[(columnIndex, rowIndex + 2)]
    tile4 = board[(columnIndex, rowIndex + 3)]
    if tile1 == tile2 == tile3 == tile4 == playerTile:
    return True for columnIndex in range(BOARD_WIDTH - 3):
    for rowIndex in range(BOARD_HEIGHT - 3):
    # Проверить четверку по диагонали направо вниз:
    tile1 = board[(columnIndex, rowIndex)]
    tile2 = board[(columnIndex + 1, rowIndex + 1)]
    tile3 = board[(columnIndex + 2, rowIndex + 2)]
    tile4 = board[(columnIndex + 3, rowIndex + 3)]
    if tile1 == tile2 == tile3 == tile4 == playerTile:
    return True
    # Проверить четверку по диагонали налево вниз:
    tile1 = board[(columnIndex + 3, rowIndex)]
    tile2 = board[(columnIndex + 2, rowIndex + 1)]
    tile3 = board[(columnIndex + 1, rowIndex + 2)]
    tile4 = board[(columnIndex, rowIndex + 3)]
    if tile1 == tile2 == tile3 == tile4 == playerTile:
    return True return False
    # Если программа была запущена (а не импортирована), начать игру:
    if __name__ == "__main__":
    main()
    Запустите программу и сыграйте несколько раз, чтобы получить представление о том, что делает программа, прежде чем читать объяснения работы исходного кода.
    Чтобы проверить возможные опечатки, скопируйте код в сетевую программу diff по адресу https://inventwithpython.com/beyond/diff/.
    Написание кода
    Рассмотрим исходный код программы, как это было сделано в программе «Ханой- ская башня». Как и в предыдущем случае, я отформатировал код с использованием
    Black с ограничением длины строки 75 символов.
    Начало программы:
    """Four-in-a-Row, by Al Sweigart al@inventwithpython.com
    Игра на выстраивание четырех фишек в ряд."""
    import sys

    306
    Глава 14.Проекты для тренировки
    # Константы, используемые для вывода игрового поля:
    EMPTY_SPACE = "." # Точки проще подсчитать, чем пробелы.
    PLAYER_X = "X"
    PLAYER_O = "O"
    Программа начинается с doc-строки, импортирования модулей и присваивания констант, как в программе «Ханойская башня». В программе определяются констан- ты
    PLAYER_X
    и
    PLAYER_O
    , чтобы нам не приходилось использовать строки "X"
    и "O"
    в программе, а ошибки находились проще. Если при вводе констант будет допущена опечатка (например,
    PLAYER_XX
    ), Python выдаст ошибку
    NameError
    , и вы моменталь- но узнаете о проблеме. Но если опечатка будет допущена при вводе символов "X"
    (например,
    "XX"
    или "Z"
    ), возникшая ошибка уже не будет столь очевидной. Как объяснялось в разделе «“Магические” числа», с. 97, используя константы вместо строковых значений, вы не только получаете описание, но и предупреждаете по- явление опечаток в исходном коде.
    Константы не должны изменяться во время выполнения программы. Тем не менее программист может обновить их значения в будущих версиях программы. По этой причине мы оставляем напоминание программисту, что при изменении значения
    BOARD_WIDTH
    он должен обновить константы
    BOARD_TEMPLATE
    и
    COLUMN_LABELS
    , опи- санные ниже:
    # Примечание: если BOARD_WIDTH изменится, обновите BOARD_TEMPLATE и COLUMN_LABELS.
    BOARD_WIDTH = 7
    BOARD_HEIGHT = 6
    COLUMN_LABELS = ("1", "2", "3", "4", "5", "6", "7")
    assert len(COLUMN_LABELS) == BOARD_WIDTH
    Эта константа будет использоваться позднее для проверки правильности столбца, введенного игроком. Если
    BOARD_WIDTH
    будет присвоено значение, отличное от 7, придется добавить или удалить метки из кортежа
    COLUMN_LABELS
    . Этого можно было бы избежать, генерируя значение
    COLUMN_LABELS
    на основании
    BOARD_WIDTH
    кодом вида
    COLUMN_LABELS
    =
    tuple([str(n)
    for n
    in range(1,
    BOARD_WIDTH
    +
    1)])
    . Однако
    COLUMN_LABELS
    вряд ли изменится в будущем, потому что стандартно в «Четыре в ряд» играют на доске 7
    × 6, поэтому я решил записать значение кортежа явно.
    Конечно, подобная жесткая фиксация значений в программе является признаком проблем в коде, о чем я уже рассказывал в разделе «“Магические” числа», с. 97, но такой код читается лучше, чем альтернатива. Кроме того, команда assert преду- предит об изменении
    BOARD_WIDTH
    без обновления
    COLUMN_LABELS
    Как в игре «Ханойская башня», «Четыре в ряд» использует ASCII-графику для рисования игрового поля. В следующих строках содержится одна команда при- сваивания с многострочным текстом:

    Игра «Четыре в ряд»
    307
    # Шаблонная строка для вывода игрового поля:
    BOARD_TEMPLATE = """
    1234567
    +-------+
    |{}{}{}{}{}{}{}|
    |{}{}{}{}{}{}{}|
    |{}{}{}{}{}{}{}|
    |{}{}{}{}{}{}{}|
    |{}{}{}{}{}{}{}|
    |{}{}{}{}{}{}{}|
    +-------+"""
    Строка содержит фигурные скобки
    {}
    , которые метод format()
    заменит содержи- мым игрового поля. (Эту задачу решает функция displayBoard()
    , описанная ниже.)
    Так как игровое поле состоит из семи столбцов и шести строк, мы используем семь пар фигурных скобок
    {}
    в любой из шести строк для представления каждой ячейки.
    Как и в случае с
    COLUMN_LABELS
    , формально мы жестко фиксируем поле с заданным набором столбцов и строк. Если
    BOARD_WIDTH
    и
    BOARD_HEIGHT
    будут заменены новыми целыми значениями, многострочный шаблон в
    BOARD_TEMPLATE
    также потребуется обновить.
    Также можно было написать код генерирования
    BOARD_TEMPLATE
    на основании констант
    BOARD_WIDTH
    и
    BOARD_HEIGHT
    :
    BOARD_EDGE = " +" + ("-" * BOARD_WIDTH) + "+"
    BOARD_ROW = " |" + ("{}" * BOARD_WIDTH) + "|\n"
    BOARD_TEMPLATE = "\n " + "".join(COLUMN_LABELS) + "\n" + BOARD_EDGE + "\n"
    + (BOARD_ROW * BOARD_WIDTH) + BOARD_EDGE
    Но этот код читается намного хуже простого многострочного текста, и размер игрового поля вряд ли будет изменяться, поэтому мы используем простой много- строчный текст.
    Начнем с функции main()
    , которая будет вызывать все остальные функции, на- писанные для игры:
    def main():
    """Проводит одну игру Четыре в ряд."""
    print(
    """Four-in-a-Row, by Al Sweigart al@inventwithpython.com
    Два игрока по очереди опускают фишки в один из семи столбцов,
    стараясь выстроить четыре фишки по вертикали, горизонтали или диагонали.
    """
    )
    # Подготовка новой игры:
    gameBoard = getNewBoard()
    playerTurn = PLAYER_X

    308
    Глава 14.Проекты для тренировки
    Для функции main()
    определяется doc-строка, для просмотра которой можно вос- пользоваться встроенной функцией help()
    . Функция main()
    также готовит игровое поле к новой игре и выбирает первого игрока.
    Внутри функции main()
    выполняется бесконечный цикл:
    while True: # Обрабатывает ход игрока.
    # Вывод игрового поля и получение хода игрока:
    displayBoard(gameBoard)
    playerMove = getPlayerMove(playerTurn, gameBoard)
    gameBoard[playerMove] = playerTurn
    Каждая итерация цикла состоит из одного хода. Сначала игровое поле выводится на экран. Затем игрок выбирает столбец, в который опускает фишку, и, наконец, происходит обновление структуры данных игрового поля.
    Затем определяется результат хода игрока:
    # Проверка победы или ничьей:
    if isWinner(playerTurn, gameBoard):
    displayBoard(gameBoard) # В последний раз вывести поле.
    print("Player {} has won!".format(playerTurn))
    sys.exit()
    elif isFull(gameBoard):
    displayBoard(gameBoard) # В последний раз вывести поле.
    print("There is a tie!")
    sys.exit()
    Если игрок сделал победный ход, isWinner()
    возвращает
    True и партия завершается.
    Если игрок заполнил последнюю ячейку, а победитель не определился, isFull()
    возвращает
    True и игра завершается. Обратите внимание: вместо вызова sys.exit()
    можно воспользоваться простой командой break
    . Это бы прервало цикл while
    , а поскольку функция main()
    не содержит кода после цикла, управление вернется к вызову main()
    в конце программы, что приведет к ее завершению. Но я выбрал sys.exit()
    , чтобы наглядно показать программисту, читающему код, что программа немедленно завершится.
    Если игра не закончена, следующие строки присваивают playerTurn значение, представляющее другого игрока:
    # Ход передается другому игроку:
    if playerTurn == PLAYER_X:
    playerTurn = PLAYER_O
    elif playerTurn == PLAYER_O:
    playerTurn = PLAYER_X
    Команду elif можно преобразовать в простую команду else без условия. Но вспом- ните принцип из «Дзен Python»: явное лучше, чем неявное. Этот код явно сообщает,

    Игра «Четыре в ряд»
    309
    что если сейчас ход игрока O, то следующим ходит игрок X. Также можно было бы использовать другую формулировку: если сейчас не ход игрока X, то X ходит следующим. И хотя команды if и else естественно сочетаются с логическими усло- виями, значения
    PLAYER_X
    и
    PLAYER_O
    не эквивалентны
    True или
    False
    ; а значит, not
    PLAYER_X
    — не то же самое, что
    PLAYER_O
    . Следовательно, лучше проверять значение playerTurn напрямую.
    Те же действия можно было выполнить в одной строке:
    playerTurn = {PLAYER_X: PLAYER_O, PLAYER_O: PLAYER_X}[ playerTurn]
    Здесь используется трюк со словарем, упоминавшийся в подразделе «Использо- вание словарей вместо команды switch», с. 129. Но как и многие однострочные команды, она читается хуже прямолинейной конструкции if и elif
    Затем определяется функция getNewBoard()
    :
    def getNewBoard():
    """Возвращает словарь, представляющий игровое поле.
    Ключи - кортежи (columnIndex, rowIndex) с двумя целыми числами,
    а значения - одна из строк "X", "O" или "." (пробел)."""
    board = {}
    for rowIndex in range(BOARD_HEIGHT):
    for columnIndex in range(BOARD_WIDTH):
    board[(columnIndex, rowIndex)] = EMPTY_SPACE
    return board
    Функция возвращает словарь, представляющий игровое поле для игры «Четыре в ряд». Она содержит кортежи (
    columnIndex
    , rowIndex
    ) для ключей (где columnIndex и rowIndex являются целыми числами), а также символы 'X'
    ,
    'O'
    или '.'
    для фишки в каждой ячейке поля. Эти строки сохраняются в
    PLAYER_X
    ,
    PLAYER_O
    и
    EMPTY_SPACE
    соответственно.
    Наша игра «Четыре в ряд» довольно проста, так что использование словаря для представления игрового поля можно считать приемлемым. Впрочем, также можно воспользоваться решением на базе ООП (об ООП мы поговорим в главах с 15 по 17).
    Функция displayBoard()
    получает структуру данных игрового поля в аргументе board и выводит поле на экран с использованием константы
    BOARD_TEMPLATE
    :
    def displayBoard(board):
    """Выводит на экран игровое поле и фишки."""
    # Подготовить список, передаваемый строковому методу format() для
    # шаблона игрового поля. Список содержит все фишки игрового поля
    # и пустые ячейки, перечисляемые слева направо, сверху вниз:
    tileChars = []

    310
    Глава 14.Проекты для тренировки
    Напомню, что
    BOARD_TEMPLATE
    представляет собой многострочный текст, содер- жащий множество пар фигурных скобок. При вызове метода format()
    для
    BOARD_
    TEMPLATE
    эти фигурные скобки будут заменены аргументами, переданными format()
    Переменная tileChars содержит список таких аргументов. В начале ей присваива- ется пустой список. Первое значение в tileChars заменяет первую пару фигурных скобок в
    BOARD_TEMPLATE
    , второе значение заменяет вторую пару и т. д. По сути, мы создаем список значений из словаря board
    :
    for rowIndex in range(BOARD_HEIGHT):
    for columnIndex in range(BOARD_WIDTH):
    tileChars.append(board[(columnIndex, rowIndex)])
    # Выводит игровое поле:
    print(BOARD_TEMPLATE.format(*tileChars))
    Вложенные циклы for перебирают все возможные комбинации строк и столбцов игрового поля, присоединяя их к списку tileChars
    . После завершения этих циклов значения из tileChars передаются как отдельные аргументы метода format()
    с пре- фиксом
    *
    . В подразделе «Использование * при создании вариадических функций»
    (с. 201) показано, как использовать этот синтаксис для обработки значений в списке как отдельных аргументов функции; код print(*['cat',
    'dog',
    'rat'])
    эквивален- тен print('cat',
    'dog',
    'rat')
    . Звездочка необходима, потому что метод format()
    ожидает получить один аргумент для каждой пары фигурных скобок, а не один аргумент-список.
    Затем записывается функция getPlayerMove()
    :
    def getPlayerMove(playerTile, board):
    """Предлагает игроку выбрать столбец для размещения фишки.
    Возвращает кортеж (столбец, строка) итогового положения фишки."""
    while True: # Пока игрок не введет допустимый ход.
    print(f"Player {playerTile}, enter 1 to {BOARD_WIDTH} or QUIT:")
    response = input("> ").upper().strip()
    if response == "QUIT":
    print("Thanks for playing!")
    sys.exit()
    Функция начинается с бесконечного цикла, который ожидает, пока игрок введет до- пустимый ход. По своей структуре этот код напоминает функцию getPlayerMove()
    из программы «Ханойская башня». Обратите внимание: вызов print()
    в начале цикла while()
    использует f-строку, чтобы нам не пришлось изменять сообщение при обновлении
    BOARD_WIDTH
    Мы проверяем, что ответ игрока представляет столбец; в противном случае команда continue передает управление обратно в начало цикла, чтобы запросить у игрока допустимый ход:

    Игра «Четыре в ряд»
    311
    if response not in COLUMN_LABELS:
    print(f"Enter a number from 1 to {BOARD_WIDTH}.")
    continue # Снова запросить ход.
    Условие проверки ввода также можно было бы записать в виде not response.
    isdecimal()
    or spam
    <
    1
    or spam
    >
    BOARD_WIDTH
    , но проще воспользоваться условием not in
    COLUMN_LABELS
    Затем необходимо определить, в какую строку упадет фишка, опущенная игроком в выбранный столбец:
    columnIndex = int(response) - 1 # -1, потому что индексы начинаются с 0.
    # Если столбец заполнен, снова запросить ход:
    if board[(columnIndex, 0)] != EMPTY_SPACE:
    print("That column is full, select another one.")
    continue # Снова запросить ход.
    На экран выводятся метки столбцов от 1 до 7. Но индексы
    (columnIndex,
    rowIndex)
    начинаются с 0, поэтому их значения лежат в диапазоне от 0 до 6. Чтобы устранить это расхождение, строковые значения от '1'
    до '7'
    преобразуются в целые значе- ния от 0 до 6.
    Индексы строк начинаются с 0 (верх игрового поля) и увеличиваются до 6 (низ игрового поля). Мы проверяем верхнюю ячейку выбранного столбца и смотрим, занята ли она. Если она занята, значит, столбец заполнен, и команда continue пере- дает управление обратно в начало цикла, чтобы запросить у игрока другой ход.
    Если столбец не заполнен, необходимо найти самую нижнюю свободную ячейку, в которую упадет фишка:
    # Начать снизу, найти первую пустую ячейку.
    for rowIndex in range(BOARD_HEIGHT - 1, -1, -1):
    if board[(columnIndex, rowIndex)] == EMPTY_SPACE:
    return (columnIndex, rowIndex)
    Цикл for начинает с индекса нижней строки,
    BOARD_HEIGHT
    -
    1
    (или 6), и двигается вверх, пока не найдет первую свободную ячейку. Затем функция возвращает ин- дексы самой нижней пустой ячейки.
    Если игровое поле заполнено, игра заканчивается вничью:
    def isFull(board):
    """Возвращает True, если в `board` не осталось пустых ячеек,
    иначе возвращается False."""
    for rowIndex in range(BOARD_HEIGHT):
    for columnIndex in range(BOARD_WIDTH):
    if board[(columnIndex, rowIndex)] == EMPTY_SPACE:
    return False # Пустая ячейка найдена, вернуть False.
    return True # Все ячейки заполнены.

    312
    Глава 14.Проекты для тренировки
    Функция isFull()
    использует пару вложенных циклов for для перебора всех ячеек игрового поля. Если функция находит хотя бы одну пустую ячейку, значит, поле еще не заполнено и функция возвращает
    False
    . Если оба цикла будут выполнены до конца, значит, функция не нашла пустой ячейки и она возвращает
    True
    Функция isWinner()
    проверяет, выиграл ли игрок своим ходом:
    def isWinner(playerTile, board):
    """Возвращает True, если `playerTile` образует ряд из четырех фишек в `board`, в противном случае возвращается False."""
    # Проверить всю доску в поисках четырех фишек в ряд:
    for columnIndex in range(BOARD_WIDTH - 3):
    for rowIndex in range(BOARD_HEIGHT):
    # Проверить четверку направо:
    tile1 = board[(columnIndex, rowIndex)]
    tile2 = board[(columnIndex + 1, rowIndex)]
    tile3 = board[(columnIndex + 2, rowIndex)]
    tile4 = board[(columnIndex + 3, rowIndex)]
    if tile1 == tile2 == tile3 == tile4 == playerTile:
    return True
    Функция возвращает
    True
    , если playerTile встречается в четырех ячейках подряд по горизонтали, вертикали или диагонали. Чтобы проверить, выполняется ли это условие, необходимо проверить каждый набор из четырех смежных позиций игро- вого поля. Для этого будет использоваться серия вложенных циклов for
    Кортеж
    (columnIndex,
    rowIndex)
    определяет отправную точку для проверки. Мы проверяем начальную позицию и ячейки в трех позициях справа от нее в поисках строки playerTile
    . Если начальной позицией является
    (columnIndex,
    rowIndex)
    , то позиция справа от нее определяется кортежем
    (columnIndex
    +
    1,
    rowIndex)
    , и т. д.
    Фишки в этих четырех позициях сохраняются в переменных tile1
    , tile2
    , tile3
    и tile4
    . Если все четыре переменные содержат такое же значение, как playerTile
    , значит, четыре фишки в ряд найдены и функция isWinner()
    возвращает
    True
    В разделе «Переменные с числовыми суффиксами», с. 102, я упомянул о том, что имена переменных с последовательными числовыми суффиксами (например, tile1

    tile4
    в этой игре) часто являются признаком проблем в коде и их лучше заменить одним списком. Однако в данном контексте такие имена переменных вполне допустимы. Заменять их списком не нужно, потому что в программе «Четыре в ряд» всегда используются ровно четыре такие переменные. Напомню, что запах кода не всегда указывает на наличие проблемы; он означает лишь то, что к коду стоит присмотреться и убедиться в том, что он написан наиболее понятным и удо- бочитаемым способом. В данном случае использование списка только усложнит код без каких-либо преимуществ, поэтому мы ограничимся использованием имен tile1
    , tile2
    , tile3
    и tile4

    Игра «Четыре в ряд»
    313
    Аналогичный процесс используется для проверки вертикальной последователь- ности из четырех фишек:
    for columnIndex in range(BOARD_WIDTH):
    for rowIndex in range(BOARD_HEIGHT - 3):
    # Проверить четверку вниз:
    tile1 = board[(columnIndex, rowIndex)]
    tile2 = board[(columnIndex, rowIndex + 1)]
    tile3 = board[(columnIndex, rowIndex + 2)]
    tile4 = board[(columnIndex, rowIndex + 3)]
    if tile1 == tile2 == tile3 == tile4 == playerTile:
    return True
    Остается проверить последовательность из четырех фишек в ряд по диагонали, идущую вниз и вправо и вниз и влево:
    for columnIndex in range(BOARD_WIDTH - 3):
    for rowIndex in range(BOARD_HEIGHT - 3):
    # Проверить четверку по диагонали направо вниз:
    tile1 = board[(columnIndex, rowIndex)]
    tile2 = board[(columnIndex + 1, rowIndex + 1)]
    tile3 = board[(columnIndex + 2, rowIndex + 2)]
    tile4 = board[(columnIndex + 3, rowIndex + 3)]
    if tile1 == tile2 == tile3 == tile4 == playerTile:
    return True
    # Проверить четверку по диагонали налево вниз:
    tile1 = board[(columnIndex + 3, rowIndex)]
    tile2 = board[(columnIndex + 2, rowIndex + 1)]
    tile3 = board[(columnIndex + 1, rowIndex + 2)]
    tile4 = board[(columnIndex, rowIndex + 3)]
    if tile1 == tile2 == tile3 == tile4 == playerTile:
    return True return False
    Код аналогичен проверке четырех фишек в ряд по горизонтали, и я не стану повто- рять объяснения. Если ни одна проверка четырех фишек в ряд ничего не находит, функция возвращает
    False
    , показывая, что ход playerTile не принес победы в игре:
    return False
    Остается только вызвать функцию main()
    :
    # Если программа была запущена (а не импортирована), начать игру:
    if __name__ == "__main__":
    main()
    И снова здесь используется стандартная идиома Python: функция main()
    вызыва- ется в том случае, если программа fourinarow.py была запущена напрямую, но не при импортировании fourinarow.py в виде модуля.

    314
    Глава 14.Проекты для тренировки
    Итоги
    Головоломка «Ханойская башня» и игра «Четыре в ряд» — короткие программы, но так как мы следовали принципам, представленным в книге, их код хорошо чи- тается и прост в отладке. В этих программах применяется ряд полезных практик: они были автоматически отформатированы программой Black, для описания мо- дулей и функций использовались doc-строки, а константы мы разместили в начале файла. Переменные, параметры функций и возвращаемые значения функций огра- ничиваются одним типом данных, так что аннотации типов (как полезная форма дополнительной документации) оказываются излишними.
    В «Ханойской башне» три башни представлены словарем с ключами 'A'
    ,
    'B'
    и 'C'
    , значениями которых являются списки целых чисел. Такой подход работает, но если бы программа была сколько-нибудь большой и сложной, для представления этих данных стоило бы воспользоваться классом. Классы и средства ООП в этой главе не использовались, потому что об ООП речь пойдет только в главах 15–17. Про- сто помните, что для таких структур данных абсолютно нормально использовать класс. Башни выводятся на экран в ASCII-графике, а диски изображаются серией текстовых символов.
    Игра «Четыре в ряд» также использует ASCII-графику для вывода представления игрового поля. Изображение поля строится из многострочного текста, хранимого в константе
    BOARD_TEMPLATE
    . Строка включает 42 пары фигурных скобок
    {}
    для каждой ячейки игрового поля 7
    × 6. Строковый метод format()
    заменяет каждую пару фигурных скобок ячейкой, находящейся в соответствующей позиции. При таком подходе более очевидно, как строка
    BOARD_TEMPLATE
    строит игровое поле, выводимое на экран.
    Несмотря на различия в структурах данных, у этих двух программ много общего.
    Обе программы выводят свои структуры данных на экран, запрашивают у игрока входные данные, проверяют ввод, а затем используют его для обновления своих структур данных, прежде чем возвращаться к началу цикла. Однако код для вы- полнения всех этих действий можно написать многими способами. Как сделать свой код удобочитаемым? Удобочитаемость — субъективное ощущение, а не объективная метрика, определяемая степенью соответствия некоторому набору правил. Исходный код, приведенный в этой главе, показывает, что, хотя к любому коду с запахом всегда стоит присмотреться еще раз, признаки проблемы далеко не всегда указывают на существование проблемы, которую необходимо исправить.
    Удобочитаемость кода важнее бездумного следования политике недопустимости запахов кода в ваших программах.

    1   ...   28   29   30   31   32   33   34   35   ...   40


    написать администратору сайта