Учим Python, делая крутые игры 2018. Invent your owncomputer gameswith python
Скачать 6.56 Mb.
|
Рис. 10.1. Клетки игрового поля нумеруются так же, как цифровая клавиатура 160 Глава 10 В ЭТОЙ ГЛАВЕ РАССМАТРИВАЮТСЯ СЛЕДУЮЩИЕ ТЕМЫ: • Искусственный интеллект • Ссылки на список • Вычисление по короткой схеме • Значение None Пример запуска игры «Крестики-нолики» Вот что видит пользователь при запуске программы «Крестики-нолики» . Текст, который вводит игрок, выделен полужирным шрифтом. Игра "Крестики-нолики" Вы выбираете Х или О? о Человек ходит первым. | | -+-+- | | -+-+- | | Ваш следующий ход? (1-9) 7 О| |Х -+-+- | | -+-+- | | Ваш следующий ход? (1-9) 4 О| |Х -+-+- О| | -+-+- Х| | Ваш следующий ход? (1-9) 5 О| |Х -+-+- О|О| Игра «Крестики-нолики» 161 -+-+- Х| |Х Ваш следующий ход? (1-9) 6 О| |Х -+-+- О|О|О -+-+- Х| |Х Ура! Вы выиграли! Сыграем еще раз? (да или нет) нет Исходный код игры «Крестики-нолики» В редакторе файлов создайте новый файл, вы- брав команду меню File ⇒ New File (Файл ⇒ Но- вый файл). В открывшемся окне введите приве- денный ниже исходный код и сохраните файл под именем tictactoe.py. Затем запустите программу, на- жав клавишу F5. Если при выполнении программы возникают ошибки, сравните код, который вы на- брали, с оригинальным кодом с помощью онлайн- инструмента на сайте inventwithpython.com/diff/. tictactoe.py 1. # Крестики-нолики 2. 3. import random 4. 5. def drawBoard(board): 6. # Эта функция выводит на экран игровое поле, клетки которого будут заполняться. 7. 8. # "board" — это список из 10 строк, для прорисовки игрового поля (индекс 0 игнорируется). 9. print(board[7] + '|' + board[8] + '|' + board[9]) 10. print('-+-+-') 11. print(board[4] + '|' + board[5] + '|' + board[6]) 12. print('-+-+-') 13. print(board[1] + '|' + board[2] + '|' + board[3]) 14. Make sure you’re using Python 3, not Python 2! УБЕ ДИТЕСЬ, ЧТО ИСПО ЛЬЗУЕТЕ PY THON 3, А НЕ PY THON 2! 162 Глава 10 15. def inputPlayerLetter(): 16. # Разрешение игроку ввести букву, которую он выбирает. 17. # Возвращает список, в котором буква игрока — первый элемент, а буква компьютера — второй. 18. letter = '' 19. while not (letter == 'Х' or letter == 'О'): 20. print('Вы выбираете Х или О?') 21. letter = input().upper() 22. 23. # Первым элементом списка является буква игрока, вторым — буква компьютера. 24. if letter == 'Х': 25. return ['Х', 'О'] 26. else: 27. return ['О', 'Х'] 28. 29. def whoGoesFirst(): 30. # Случайный выбор игрока, который ходит первым. 31. if random.randint(0, 1) == 0: 32. return 'Компьютер' 33. else: 34. return 'Человек' 35. 36. def makeMove(board, letter, move): 37. board[move] = letter 38. 39. def isWinner(bo, le): 40. # Учитывая заполнение игрового поля и буквы игрока, эта функция возвращает True, если игрок выиграл. 41. # Мы используем "bo" вместо "board" и "le" вместо "letter", поэтому нам не нужно много печатать. 42. return ((bo[7] == le and bo[8] == le and bo[9] == le) or # across the top 43. (bo[4] == le and bo[5] == le and bo[6] == le) or # через центр 44. (bo[1] == le and bo[2] == le and bo[3] == le) or # через низ 45. (bo[7] == le and bo[4] == le and bo[1] == le) or # вниз по левой стороне 46. (bo[8] == le and bo[5] == le and bo[2] == le) or # вниз по центру 47. (bo[9] == le and bo[6] == le and bo[3] == le) or # вниз по правой стороне 48. (bo[7] == le and bo[5] == le and bo[3] == le) or # по диагонали 49. (bo[9] == le and bo[5] == le and bo[1] == le)) # по диагонали 50. 51. def getBoardCopy(board): 52. # Создает копию игрового поля и возвращает его. 53. boardCopy = [] 54. for i in board: 55. boardCopy.append(i) Игра «Крестики-нолики» 163 56. return boardCopy 57. 58. def isSpaceFree(board, move): 59. # Возвращает True, если сделан ход в свободную клетку. 60. return board[move] == ' ' 61. 62. def getPlayerMove(board): 63. # Разрешение игроку сделать ход. 64. move = ' ' 65. while move not in '1 2 3 4 5 6 7 8 9'.split() or not isSpaceFree(board, int(move)): 66. print('Ваш следующий ход? (1-9)') 67. move = input() 68. return int(move) 69. 70. def chooseRandomMoveFromList(board, movesList): 71. # Возвращает допустимый ход, учитывая список сделанных ходов и список заполненных клеток. 72. # Возвращает значение None, если больше нет допустимых ходов. 73. possibleMoves = [] 74. for i in movesList: 75. if isSpaceFree(board, i): 76. possibleMoves.append(i) 77. 78. if len(possibleMoves) != 0: 79. return random.choice(possibleMoves) 80. else: 81. return None 82. 83. def getComputerMove(board, computerLetter): 84. # Учитывая заполнение игрового поля и букву компьютера, определяет допустимый ход и возвращает его. 85. if computerLetter == 'Х': 86. playerLetter = 'О' 87. else: 88. playerLetter = 'Х' 89. 90. # Это алгоритм для ИИ "Крестиков-Ноликов": 91. # Сначала проверяем — победим ли мы, сделав следующий ход. 92. for i in range(1, 10): 93. boardCopy = getBoardCopy(board) 94. if isSpaceFree(boardCopy, i): 95. makeMove(boardCopy, computerLetter, i) 96. if isWinner(boardCopy, computerLetter): 164 Глава 10 97. return i 98. 99. # Проверяем — победит ли игрок, сделав следующий ход, и блокируем его. 100. for i in range(1, 10): 101. boardCopy = getBoardCopy(board) 102. if isSpaceFree(boardCopy, i): 103. makeMove(boardCopy, playerLetter, i) 104. if isWinner(boardCopy, playerLetter): 105. return i 106. 107. # Пробуем занять один из углов, если есть свободные. 108. move = chooseRandomMoveFromList(board, [1, 3, 7, 9]) 109. if move != None: 110. return move 111. 112. # Пробуем занять центр, если он свободен. 113. if isSpaceFree(board, 5): 114. return 5 115. 116. # Делаем ход по одной стороне. 117. return chooseRandomMoveFromList(board, [2, 4, 6, 8]) 118. 119. def isBoardFull(board): 120. # Возвращает True, если клетка на игровом поле занята. В противном случае, возвращает False. 121. for i in range(1, 10): 122. if isSpaceFree(board, i): 123. return False 124. return True 125. 126. 127. print('Игра "Крестики-нолики"') 128. 129. while True: 130. # Перезагрузка игрового поля 131. theBoard = [' '] * 10 132. playerLetter, computerLetter = inputPlayerLetter() 133. turn = whoGoesFirst() 134. print('' + turn + ' ходит первым.') 135. gameIsPlaying = True 136. Игра «Крестики-нолики» 165 137. while gameIsPlaying: 138. if turn == 'Человек': 139. # Ход игрока. 140. drawBoard(theBoard) 141. move = getPlayerMove(theBoard) 142. makeMove(theBoard, playerLetter, move) 143. 144. if isWinner(theBoard, playerLetter): 145. drawBoard(theBoard) 146. print('Ура! Вы выиграли!') 147. gameIsPlaying = False 148. else: 149. if isBoardFull(theBoard): 150. drawBoard(theBoard) 151. print('Ничья!') 152. break 153. else: 154. turn = 'Компьютер' 155. 156. else: 157. # Ход компьютера. 158. move = getComputerMove(theBoard, computerLetter) 159. makeMove(theBoard, computerLetter, move) 160. 161. if isWinner(theBoard, computerLetter): 162. drawBoard(theBoard) 163. print('Компьютер победил! Вы проиграли.') 164. gameIsPlaying = False 165. else: 166. if isBoardFull(theBoard): 167. drawBoard(theBoard) 168. print('Ничья!') 169. break 170. else: 171. turn = 'Человек' 172. 173. print('Сыграем еще раз? (да или нет)') 174. if not input().lower().startswith('д'): 175. break 166 Глава 10 Проектирование программы На рис. 10.2 показана блок-схема игры «Крестики-нолики». Программа начинается с предложения игроку выбрать букву «Х» или «О». Кому принад- лежит первый ход, выбирается случайно. Затем игрок и компьютер по очере- ди совершают свои ходы. Player’s Turn Computer’s Turn START Show the board Get player’s move Check for tie Check for tie END Ask for player’s letter Decide who goes first Get computer’s move Check if computer won Check if player won Ask player to play again Старт Конец Показать игровое поле Проверить ничью Проверить ничью Определить ход игрока Проверить, выиграл ли игрок Спросить игрока, бу- дет ли играть заново Проверить, выиграл ли компьютер Определить ход компьютера Ход компьютера Ход игрока Запросить у игрока вы- брать букву Определить, кто ходит первым Рис. 10.2. Блок-схема программы «Крестики-нолики» Клетки в левой части блок-схемы показывают, что происходит, когда игрок совершает ход. Клетки в правой части показывают, что происходит, когда ход совершает компьютер. После того как игрок или компьютер делают ход, программа проверяет, не выиграл ли кто-нибудь или не получилась ли ничья, далее возможны варианты. По завершении игры программа предлага- ет игроку сыграть заново. Данные для прорисовки игрового поля Сначала надо определить, как представить игровое поле данными, хра- нящимися в переменных. На бумаге поле для игры в «Крестики-нолики» вычерчивается парой горизонтальных и парой вертикальных линий, а де- Игра «Крестики-нолики» 167 вять полученных клеток заполняются буквами «Х» и «О». В программе «Крестики-нолики» игровое поле представлено списком строк, содержащих ASCII-символы, так же, как в игре «Виселица». В каждой строке представле- на одна клетка игрового поля. Есть строки для буквы игрока «Х», для буквы игрока «О», есть строка с единичным пробелом ' ' для пустых клеток. Напомню, что размещение клеток игрового поля аналогично размеще- нию клавиш на цифровой клавиатуре компьютера. Таким образом, если спи- сок из 10 строк хранится в переменной с именем board, то board[7] — это клет- ка в левом верхнем углу игрового поля, board[8] — верхняя средняя клетка, board[9] — верхняя правая клетка и так далее. Программа игнорирует индекс 0 списка. Чтобы сообщить программе, в какую клетку игрок хочет сделать ход, он должен ввести цифру от 1 до 9. Стратегия игры ИИ ИИ должен быть способен оценить состояние игрового поля и принять решение, в какую клетку сделать ход. Для удобства мы отметим три типа кле- ток: угловые, боковые и центр. Карта на рис. 10.3 показывает, где расположе- на каждая клетка. Side Side Center Corner Corner Side Corner Corner Side Сторона Центр Сторона Сторона Сторона Угол Угол Угол Угол Рис. 10.3. Расположение сторон, углов и центра клеток на поле Стратегия ИИ игры в «Крестики-нолики» подчинена простому алгорит- му — ограниченному числу инструкций, рассчитывающих результат. Одна программа может использовать несколько алгоритмов. Алгоритм можно представить блок-схемой. Алгоритм ИИ для игры в «Крестики-нолики» бу- дет рассчитывать наилучший ход, как показано на рис. 10.4. Алгоритм ИИ состоит из следующих шагов: 1. Проверить существует ли ход, сделав который, компьютер мог бы выиграть. Если существует, сделать его. В противном случае, перейти к шагу 2. 2. Проверить, существует ли ход, сделав который, игрок мог бы выи- грать. Если существует, сделать ход для блокировки. В противном случае перейти к шагу 3. 168 Глава 10 3. Проверить, свободны ли какие-либо угловые клетки (под номером 1, 3, 7 или 9). Если да, то сделать ход в угол. Если нет свободных углов, перейти к шагу 4. 4. Проверить, свободен ли центр. Если да, то сделать ход в центр. Если нет, перейти к шагу 5. 5. Сделать ход в свободную боковую клетку (под номером 2, 4, 6 или 8). Если процесс выполнения алгоритма завершил 5-й шаг, возможных ходов больше не существует, так как все боковые клетки заняты. 1. Make winning move. 2. Block player’s winning move. 3. Move on corner. 4. Move on center. 5. Move on side. 1. Сделать потенциально выигрышный ход. 2. Заблокировать выигрышный ход человека. 3. Перейти в угол. 4. Перейти в центр. 5. Перейти в сторону. Рис. 10.4. Клетки представляют пять шагов алгоритма «Ход компьютера». Стрелки, указывающие налево, соответствуют указанию перейти к клетке «Проверить, выиграл ли компьютер» блок-схемы Все это происходит в элементе «Определить ход компьютера» блок-схемы на рис. 10.2. Вы можете добавить клетки с этой информацией, изображенные на рис. 10.4, в блок-схему. Этот алгоритм реализован в функции getComputerMove() и других, вызы- ваемых ей, функциях. Импорт модуля random Первая пара строк содержит комментарий и инструкцию импорта моду- ля random. Таким образом, становится возможным вызов функции randint(). 1. # Крестики-нолики 2. 3. import random С этими двумя понятиями вы уже встречались ранее, поэтому перейдем к следующей части программы. Игра «Крестики-нолики» 169 Вывод игрового поля на экран В следующей части кода определим функцию для прорисовки игрового поля. 5. def drawBoard(board): 6. # Эта функция выводит на экран игровое поле, клетки которого будут заполняться. 7. 8. # "board" — это список из 10 строк, для прорисовки игрового поля (индекс 0 игнорируется). 9. print(board[7] + '|' + board[8] + '|' + board[9]) 10. print('-+-+-') 11. print(board[4] + '|' + board[5] + '|' + board[6]) 12. print('-+-+-') 13. print(board[1] + '|' + board[2] + '|' + board[3]) Функция drawBoard() выводит игровое поле, представленное переменной board . Напомню, что в переменной board хранится список из 10 строк, в кото- ром строка с индексом 1 соответствует клетке 1 игрового поля «Крестики- нолики» и так далее. Строка с индексом 0 игнорируется. Многие функции игры работают, пе- редавая этот список как переменную board. Не забудьте указать правильные расстояния в строках, иначе игровое поле будет выглядеть очень забавно при выводе на экран. Ниже продемонстри- ровано несколько примеров вызова функции drawBoard() с переменной board в качестве аргумента и несколько примеров того, что выводится на экран. >>> drawBoard([' ', ' ', ' ', ' ', 'X', 'O', ' ', 'X', ' ', 'O']) X| | -+-+- X|O| -+-+- | | >>> drawBoard([' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']) | | -+-+- | | -+-+- | | Каждую строку программа размещает на игровом поле в порядке, соот- ветствующем порядку цифровой клавиатуры компьютера, как показано на 170 Глава 10 рис. 10.1. Таким образом, первые три строки — это нижний ряд, следующие три строки — середина, и последние три строки — верхний ряд. Предоставление игроку выбора между «Х» или «О» Далее определим функцию назначения игроку буквы «Х» или «О». 15. def inputPlayerLetter(): 16. # Разрешение игроку ввести букву, которую он выбирает. 17. # Возвращает список, в котором буква игрока — первый элемент, а буква компьютера — второй. 18. letter = '' 19. while not (letter == 'Х' or letter == 'О'): 20. print('Вы выбираете Х или О?') 21. letter = input().upper() По завершении игры программа предлагает игроку сыграть заново. Усло- вие цикла while содержит круглые скобки, это значит, что выражение в скоб- ках вычисляется первым. Если переменной letter присваивается значение 'X' , выражение вычисляется следующим образом: not ( letter == 'X' or letter == 'O') not ( 'X' == 'X' or 'X' == 'O' ) not (True or False) not (True) not True False Если переменной letter присваивается значение 'X' или 'O', то условие цикла становится ложным, что позволяет продолжить выполнение програм- мы после блока while. Если условие истинно, программа продолжит пред- лагать игроку выбрать букву до тех пор, пока игрок не нажмет клавишу Х или О. Код в строке 21 автоматически преобразует возвращаемое строковое значение переменной в прописные буквы. Делается это через вызов функции input() с помощью метода upper(). Игра «Крестики-нолики» 171 Следующая функция возвращает список из двух элементов. 23. # Первым элементом списка является буква игрока, вторым — буква компьютера. 24. if letter == 'Х': 25. return ['Х', 'О'] 26. else: 27. return ['О', 'Х'] Первый элемент (строковая переменная с индексом 0) — это буква игро- ка, второй элемент (строка с индексом 1) — буква компьютера. Инструкции if и else выбирают соответствующий список. Выбор — кто будет ходить первым Далее создаем функцию, которая использует randnt(), для выбора того, кто будет ходить первым. 29. def whoGoesFirst(): 30. # Случайный выбор игрока, который ходит первым. 31. if random.randint(0, 1) == 0: 32. return 'Компьютер' 33. else: 34. return 'Человек' Функция whoGoesFirst() — виртуальный аналог подброшенной монетки, для определения того, кто будет ходить первым. Вместо подбрасываний мо- нетки вызываем функцию random.randint(0, 1). Шансы 50 на 50, что функция вернет 0 или 1. Если эта функция возвращает 0, функция whoGoesFirst() воз- вращает строку 'Компьютер'. В противном случае функция вернет строку 'Че- ловек' . Код, вызвавший эту функцию, использует возвращенное значение для определения того, кто сделает первый ход. Размещение меток на игровом поле Функция makeMove() очень проста. 36. def makeMove(board, letter, move): 37. board[move] = letter 172 Глава 10 Параметрами являются переменные board, letter и move. Переменная board — это список из 10 строк, представляющих состояние игрового поля. Переменная letter — это буква игрока («Х» или «О»). Переменная move — это клетка, на которую игрок пожелал сделать ход (целочисленная переменная в диапазоне от 1 до 9). Стоп! Но ведь, кажется, в строке 37 этот код заменяет значение одного из элементов списка board значением переменной letter. Так как этот код при- надлежит функции, то, может быть, изменяемый параметр будет утерян при выходе из функции. Стоит ли вносить изменения, которые будут отменены? В действительности это не так, потому что списки, которые передаются в функцию как аргументы, — особенные. Передаются не сами списки, а ссыл- ки на них. Рассмотрим разницу между списками и ссылками на списки. |