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

  • Рис. 15.7. Каждый двухэлементный список представляет одно из восьми направлений 280

  • Определение наличия фишек, которые можно перевернуть Затем мы проверяем, есть ли соседние фишки, которые можно перевер- нуть. Игра «Реверси» 281

  • Получение списка со всеми допустимыми ходами

  • Получение игрового счета

  • Получение сделанного игроком выбора фишки

  • Определение первого игрока

  • Создание копии структуры данных игрового поля

  • Учим Python, делая крутые игры 2018. Invent your owncomputer gameswith python


    Скачать 6.56 Mb.
    НазваниеInvent your owncomputer gameswith python
    Дата10.12.2022
    Размер6.56 Mb.
    Формат файлаpdf
    Имя файлаУчим Python, делая крутые игры 2018.pdf
    ТипДокументы
    #837554
    страница27 из 39
    1   ...   23   24   25   26   27   28   29   30   ...   39
    Создание структуры данных нового игрового поля
    Функция drawBoard() выводит структуру данных поля на экран, но нам также нужно каким-то образом создать эти структуры данных. Функция getNewBoard()
    возвращает список восьми списков, каждый из которых содер- жит восемь строк ' '. Они представляют собой чистое игровое поле.
    18. def getNewBoard():
    19. # Создать структуру данных нового чистого игрового поля.
    20. board = []
    21. for i in range(WIDTH):
    22. board.append([' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '])
    23. return board
    Код в строке 20 создает список, содержащий внутренние списки. Цикл for добавляет внутрь этого списка восемь списков. Эти внутренние списки содержат восемь строк для представления восьми пустых клеток на поле.

    278
    Глава 15
    Вместе этот код создает поле с 64 пустыми клетками — начальный вид поля игры «Реверси».
    Проверка допустимости хода
    Имея структуру данных игрового поля, фишку игрока и координаты x и y его хода, функция isValidMove() должна вернуть значение True, если правила игры «Реверси» позволяют сделать ход по этим координатам, и False, если не позволяют. Чтобы ход был допустимым, он должен быть сделан в пределах игрового поля, а также перевернуть хотя бы одну из фишек противника.
    Эта функция использует несколько координат x и y на поле, так что пере- менные xstart и ystart отслеживают координаты x и y исходного хода.
    25. def isValidMove(board, tile, xstart, ystart):
    26. # Вернуть False, если ход игрока в клетку с координатами xstart, ystart — недопустимый.
    27. # Если это допустимый ход, вернуть список клеток, которые "присвоил" бы игрок, если бы сделал туда ход.
    28. if board[xstart][ystart] != ' ' or not isOnBoard(xstart, ystart):
    29. return False
    30.
    31. if tile == 'Х':
    32. otherTile = 'О'
    33. else:
    34. otherTile = 'Х'
    35.
    36. tilesToFlip = []
    Код в строке 28, используя функцию isOnBoard() (которую мы определим позже), проверяет, принадлежат ли координаты x и y игровому полю и пуста ли эта клетка. Эта функция проверяет, что значения координат x и y нахо- дятся в диапазоне между 0 и значениями WIDTH (ширины) или HEIGHT (высоты) игрового поля минус 1.
    Фишка игрока (человека или компьютера) содержится в параметре tile, но этой функции должна быть также известна фишка противника. Если фиш- ка игрока — X, то, очевидно, фишка противника — O, и наоборот. Для этого в строках 31–34 мы используем инструкцию if-else.
    Наконец, если заданные координаты x и y допустимы, функция isValidMove()
    возвращает список всех фишек противника, которые будут пе- ревернуты этим ходом. Мы создаем новый пустой список, tilesToFlip, кото- рый будем использовать для хранения всех координат фишки.

    Игра «Реверси»
    279
    Проверка каждого из восьми направлений
    Чтобы ход был допустимым, игрок должен перевернуть хотя бы одну из фишек противника, зажав ее между новой фишкой и одной из своих старых фишек. Это значит, что новая фишка должна быть рядом с одной из фишек противника.
    Цикл for в строке 37 выполняет перебор списка списков, содержащий на- правления, в которых программа будет проверять фишку противника.
    37. for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:
    Игровое поле представляет собой декартову систему координат с направ- лениями х и у. Есть восемь направлений, которые нужно проверить: вверх, вниз, влево и вправо, а также четыре диагональных направления. Каждый из восьми двухэлементных списков в списке в строке 37 используется для про- верки одного из этих направлений. Программа проверяет направление, при- бавляя первое значение в двухэлементном списке к координате x, а второе значение — к координате y.
    Поскольку координаты x увеличиваются по мере продвижения вправо, вы можете проверить правое направление, прибавив 1 к значению коорди- наты x. Таким образом, список [1, 0] прибавляет 1 к значению координа- ты x и 0 к значению координаты y. Принцип проверки направления влево противоположен: вычитаете 1 (то есть прибавляете −1) из значения коорди- наты x.
    Но чтобы проверить диагональные направления, нужно прибавлять к значениям обеих координат или вычитать из них. Например, прибавление
    1
    к значению координаты x и прибавление -1 к значению координаты y при- ведет к проверке диагонального направления вправо-вверх.
    На рис. 15.7 показана диаграмма, позволяющая легче запомнить, какое направление представлено каким двухэлементным списком.
    x
    возрастает
    y возрас та ет
    Рис. 15.7.
    Каждый двухэлементный список представляет одно из восьми направлений

    280
    Глава 15
    Цикл for в строке 37 перебирает каждый из двухэлементных списков, чтобы проверить каждое направление. Внутри цикла for в строке 38 пере- менным x и y с помощью множественного присваивания назначены те же значения, что и переменным xstart и ystart соответственно. Переменным xdirection и ydirection присваиваются значения из одного из двухэлементных списков; они изменяют значения переменных x и y в соответствии с прове- ряемым направлением в той итерации цикла for.
    37. for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:
    38. x, y = xstart, ystart
    39. x += xdirection # Первый шаг в направлении x
    40. y += ydirection # Первый шаг в направлении y
    Значения переменных xstart и ystart останутся такими же, чтобы про- грамма могла запомнить, с какой клетки началась игра.
    Помните, для того, чтобы ход был допустимым, он должен быть одно- временно сделан в пределах игрового поля и рядом с одной из фишек другого игрока. (В противном случае не будет доступных для переворота фишек про- тивника, а игрок должен перевернуть, по крайней мере, одну фишку, чтобы быть допустимым.) Код в строке 41 проверяет это условие, и, если оно не ис- тинно, интерпретатор возвращается к инструкции for для выполнения про- верки в следующем направлении.
    41. while isOnBoard(x, y) and board[x][y] == otherTile:
    42. # Продолжать двигаться в этом направлении x и y.
    43. x += xdirection
    44. y += ydirection
    Но если первая проверенная клетка содержит фишку противника, про- грамма должна продолжать проверять фишки противника в этом направле- нии, пока не достигнет одной из фишек игрока или конца поля. Следующая фишка в том же направлении проверяется с помощью повторного использо- вания переменных xdirection и ydirection, чтобы присвоить переменным x и y следующие координаты на проверку. Так что программа изменяет значения переменных x и y в строках 43 и 44.
    Определение наличия фишек, которые можно перевернуть
    Затем мы проверяем, есть ли соседние фишки, которые можно перевер- нуть.

    Игра «Реверси»
    281
    45. if isOnBoard(x, y) and board[x][y] == tile:
    46. # Есть фишки, которые можно перевернуть. Двигаться в обратном направлении до достижения исходной клетки, отмечая все фишки на этом пути.
    47. while True:
    48. x -= xdirection
    49. y -= ydirection
    50. if x == xstart and y == ystart:
    51. break
    52. tilesToFlip.append([x, y])
    Инструкция if в строке 45 проверяет, занята ли координата собственной фишкой игрока. Эта фишка будет служить отметкой конца «бутерброда», об- разованного фишками игрока, которые окружают фишки противника. Нам также необходимо записать координаты всех фишек противника, которые нужно перевернуть.
    В строках 48 и 49 цикл while перемещает x и y в обратном направлении.
    До тех пор пока x и y не вернутся на исходную позицию xstart и ystart, из них вычитаются xdirection и ydirection, а каждая позиция x и y присоединя- ется к списку tilesToFlip. Когда x и y достигли клетки с координатами xstart и ystart, код в строке 51 прерывает выполнение цикла. Поскольку исходная клетка xstart и ystart пуста (мы удостоверились в этом в строках 28 и 29), условие цикла while в строке 41 примет значение False. Программа переходит к строке 37, а цикл for проверяет следующее направление.
    Так цикл for проверяет все восемь направлений. После завершения этого цикла список tilesToFlip будет содержать координаты x и y всех фишек наше- го оппонента, которые перевернулись бы, если бы игрок походил бы в xstart, ystart
    . Помните, функция isValidMove() только проверяет, был ли исходный ход допустимым; она не занимается реальным постоянным изменением структуры данных игрового поля.
    Если ни одна из фишек противника не перевернулась ни в одном из вось- ми направлений, то tilesToFlip будет пустым списком.
    54. if len(tilesToFlip) == 0: # Если ни одна из фишек не перевернулась, это недопустимый ход.
    55. return False
    56. return tilesToFlip
    Это признак того, что такой ход недопустим, и функция isValidMove() должна вернуть значение False. Иначе функция isValidMove() возвращает tilesToFlip

    282
    Глава 15
    Проверка допустимости координат
    Функция isOnBoard() вызывается из функции isValidMove(). Она выполня- ет простую проверку, чтобы определить, находятся ли данные координаты x и y в пределах игрового поля. Например, клетки с координатой x 4 и коорди- натой y 9999 не существует, так как координаты y заканчиваются 7, что равно
    WIDTH − 1
    или HEIGHT − 1.
    58. def isOnBoard(x, y):
    59. # Вернуть True, если координаты есть на игровом поле.
    60. return x >= 0 and x <= WIDTH - 1 and y >= 0 and y <= HEIGHT — 1
    Вызов этой функции служит сокращением логического выражения в строке 72, которое проверяет, находятся ли значения и x, и y между 0 и WIDTH
    - 1
    или 0 и HEIGHT - 1 (то есть в диапазоне от 0 до7).
    Получение списка со всеми допустимыми ходами
    Теперь давайте создадим режим подсказок, который будет отобра- жать поле со всеми возможными ходами, отмеченными на нем. Функция getBoardWithValidMoves()
    возвращает структуру данных игрового поля, кото- рая содержит точки (.) во всех клетках, ходы в которые допустимы.
    62. def getBoardWithValidMoves(board, tile):
    63. # Вернуть новое поле с точками, обозначающими допустимые ходы, которые может сделать игрок.
    64. boardCopy = getBoardCopy(board)
    65.
    66. for x, y in getValidMoves(boardCopy, tile):
    67. boardCopy[x][y] = '.'
    68. return boardCopy
    Эта функция создает копию структуры данных поля board с именем boardCopy
    (возвращаемую функцией getBoardCopy() в строке 64) вместо того, чтобы изменять переданную ей в параметре board. Код в строке 66 вызывает функцию getValidMoves(), чтобы получить список координат x и y со всеми допустимыми ходами, которые игрок может сделать. Копия игрового поля отмечается точками в соответствующих клетках и возвращается.

    Игра «Реверси»
    283
    Функция getValidMoves() возвращает список двухэлементных списков, в которых содержатся координаты x и y всех допустимых ходов фишки tile для структуры данных поля в параметре board.
    70. def getValidMoves(board, tile):
    71. # Вернуть список списков с координатами x и y допустимых ходов для данного игрока на данном игровом поле.
    72. validMoves = []
    73. for x in range(WIDTH):
    74. for y in range(HEIGHT):
    75. if isValidMove(board, tile, x, y) != False:
    76. validMoves.append([x, y])
    77. return validMoves
    Эта функция использует вложенные циклы (в строках 73 и 74) для про- верки каждой координаты x и y (всех 64 координат), вызывая функцию isValidMove()
    в соответствующей клетке и проверяя, возвращает она значение
    False или список возможных ходов (в последнем случае ход является допусти- мым). Каждая допустимая координата x и y добавляется в список validMoves.
    Вызов функции bool()
    Возможно, вы заметили, что в строке 75 программа проверяет, возвра- щает ли функция isValidMove() значение False, несмотря на то, что она воз- вращает список. Чтобы понять, как это работает, вам нужно немного узнать о логических типах и функции bool().
    Функция bool() похожа на функции int() и str(). Она возвращает логиче- ский тип переданного ей значения.
    Большинство типов данных имеют одно значение, которое принимается
    False для этого типа данных. При этом любое другое значение считается True.
    Целое число 0, число с плавающей запятой 0,0, пустая строка, пустой список и пустой словарь принимают значение False, когда используются, например, в качестве условия для инструкции if или цикла. Все остальные значения —
    True
    . В интерактивной оболочке введите следующие команды:
    >>>
    bool(0)
    False
    >>>
    bool(0.0)
    False

    284
    Глава 15
    >>>
    bool('')
    False
    >>>
    bool([])
    False
    >>>
    bool({})
    False
    >>>
    bool(1)
    True
    >>>
    bool('привет')
    True
    >>>
    bool([1, 2, 3, 4, 5])
    True
    >>>
    bool({'спам':'сыр', 'робик':'бобик'})
    True
    Условия автоматически интерпретируются как логические значения. Вот почему условие в строке 75 работает правильно. Вызов функции isValidMove() возвращает или логическое значение False, или непустой список.
    Если вы представите, что все условие помещается внутри вызова функ- ции bool(), тогда условие False в строке 75 становится bool(False) (что, разуме- ется, равно False). А условие непустого списка, переданного функции bool() в качестве параметра, вернет True.
    Получение игрового счета
    Функция getScoreOfBoard() использует вложенные циклы for, чтобы про- верить все 64 позиции на поле и выяснить, какие на них есть фишки игрока
    (если есть).
    79. def getScoreOfBoard(board):
    80. # Определить количество очков, подсчитав фишки. Вернуть словарь с ключами 'Х' и 'О'.
    81. xscore = 0 82. oscore = 0 83. for x in range(WIDTH):
    84. for y in range(HEIGHT):
    85. if board[x][y] == 'Х':
    86. xscore += 1 87. if board[x][y] == 'О':
    88. oscore += 1 89. return {'Х':xscore, 'О':oscore}

    Игра «Реверси»
    285
    В строке 86 код увеличивает значение переменной xscore для каждой фишки X. В строке 88 увеличивается oscore для каждой фишки O. Затем функ- ция возвращает xscore и oscore в словаре.
    Получение сделанного игроком выбора фишки
    Функция enterPlayerTile() спрашивает игрока, какой фишкой он желает играть, X или O.
    91. def enterPlayerTile():
    92. # Позволить игроку ввести выбранную фишку.
    93. # Возвращает список с фишкой игрока в качестве первого элемента и фишкой компьютера в качестве второго.
    94. tile = ''
    95. while not (tile == 'Х' or tile == 'О'):
    96. print('Вы играете за Х или О?')
    97. tile = input().upper()
    98.
    99. # Первый элемент в списке — фишка игрока, второй элемент — фишка компьютера.
    100. if tile == 'Х':
    101. return ['Х', 'О']
    102. else:
    103. return ['О', 'Х']
    Цикл for будет продолжать выполняться, пока игрок не введет пропис- ную или строчную букву X или O. Затем функция enterPlayerTile() возвраща- ет список из двух элементов, в котором выбранная игроком фишка является первым элементом, а фишка компьютера — вторым. Позже код в строке 241 вызывает функцию enterPlayerTile() и использует множественное присваи- вание, чтобы поместить эти два возвращенных элемента в две переменные.
    Определение первого игрока
    Функция whoGoesFirst() случайным образом определяет, кто делает ход первым, и возвращает либо строку 'Компьютер', либо строку 'Человек'.
    105. def whoGoesFirst():
    106. # Случайно выбрать, кто ходит первым.
    107. if random.randint(0, 1) == 0:
    108. return 'Компьютер'
    109. else:
    110. return 'Человек'

    286
    Глава 15
    Помещение фишки на поле
    Функция makeMove() вызывается, когда игрок хочет выставить фишку на поле и перевернуть другие фишки в соответствии с правилами игры.
    112. def makeMove(board, tile, xstart, ystart):
    113. # Поместить фишку на игровое поле в позицию xstart, ystart и перевернуть какую-либо фишку противника.
    114. # Вернуть False, если это недопустимый ход; вернуть True, если допустимый.
    115. tilesToFlip = isValidMove(board, tile, xstart, ystart)
    Эта функция на месте изменяет передаваемую ей структуру данных поля board
    . Изменения, внесенные в переменную board (поскольку это ссылка на список), будут внесены глобально.
    Большая часть работы выполняется функцией isValidMove() в строке 115.
    Эта функция возвращает список координат x и y (в двухэлементном списке) фишек, которые нужно перевернуть. Помните, что если аргументы xstart и ystart укажут на недопустимый ход, функция isValidMove() вернет логиче- ское значение False — это проверяется в строке 117.
    117. if tilesToFlip == False:
    118. return False
    119.
    120. board[xstart][ystart] = tile
    121. for x, y in tilesToFlip:
    122. board[x][y] = tile
    123. return True
    Если значение, возвращаемое функцией isValidMove() (теперь хранящееся в tilesToFlip) — False, то функция makeMove() в строке 118 также вернет False.
    В противном случае функция isValidMove() возвращает список клеток на поле, в которых нужно перевернуть фишки (строка 'X' или 'O' в tile). Код в строке 20 назначает клетку, в которую переместился игрок. Цикл for в стро- ке 121 назначает все фишки, которые находятся в списке tilesToFlip.
    Создание копии структуры данных игрового поля
    Функция getBoardCopy() отличается от getNewBoard(). Функция getNewBoard() создает структуру данных пустого игрового поля, которая содержит лишь пустые клетки и четыре стартовые фишки. Функция getBoardCopy() создает

    Игра «Реверси»
    287
    структуру данных пустого поля, но затем копирует все позиции в параметре board с помощью вложенного цикла. ИИ использует функцию getBoardCopy(), чтобы была возможность вносить изменения в копию игрового поля, не из- меняя исходное игровое поле. Эта методика также использовалась в игре
    «Крестики-нолики» в главе 10.
    125. def getBoardCopy(board):
    126. # Сделать копию списка board и вернуть ее.
    127. boardCopy = getNewBoard()
    128.
    129. for x in range(WIDTH):
    130. for y in range(HEIGHT):
    131. boardCopy[x][y] = board[x][y]
    132.
    133. return boardCopy
    Вызов функции getNewBoard() назначает boardCopy структурой данных но- вого игрового поля. Затем два вложенных цикла for копируют каждую из 64 фишек из board в копию структуры данных поля boardCopy.
    1   ...   23   24   25   26   27   28   29   30   ...   39


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