Учим Python, делая крутые игры 2018. Invent your owncomputer gameswith python
Скачать 6.56 Mb.
|
Рис. 15.3. Ход игрока белыми привел к тому, что одна из фишек игрока черными перевернулась Игра «Реверси» 265 Например, когда игрок белыми помещает новую белую фишку в позицию (5, 6), как показано на рис. 15.2, черная фишка в позиции (5, 5) оказывается между двумя белыми, поэтому она переворачивается и тоже становится бе- лой, как показано на рис. 15.3. Цель игры состоит в том, чтобы фишек вашего цвета было больше, чем фишек противника. Следом игрок черными мог сделать аналогичный ход, поместив свою фишку в позицию (4, 6) и этим перевернув белую фишку в позиции (4, 5). Результатом стала бы ситуация, изображенная на рис. 15.4. 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 Рис. 15.4. Игрок черными выставил новую фишку, перевернув одну из фишек игрока белыми Фишки во всех направлениях остаются перевернутыми, пока они нахо- дятся между новой фишкой игрока и существующей фишкой такого же цве- та. На рис. 15.5 игрок белыми помещает фишку в позицию (3, 6) и перевора- чивает черные фишки в двух направлениях (обозначены линиями). Результат показан на рис. 15.6. 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 Рис. 15.5. Второй ход игрока белыми в позицию (3, 6) перевернет две фишки игрока черными 266 Глава 15 За один или два хода каждый игрок может быстро перевернуть много фишек. Игроки всегда должны делать ход, который переворачивает хотя бы одну фишку. Игра заканчивается либо когда игрок не может сделать ход, либо когда поле заполнено фишками. Побеждает тот игрок, у которого осталось больше фишек своего цвета. 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 Рис. 15.6. Поле после второго хода игрока белыми Искусственный интеллект, который мы создадим для этой игры, будет искать на поле любые угловые ходы, которые сможет сделать. Если доступных угловых ходов нет, компьютер выберет ход, который перевернет больше всего фишек. Пример запуска игры «Реверси» Вот что видит пользователь, запустив программу «Реверси». Текст, кото- рый вводит игрок, выделен полужирным шрифтом. Приветствуем в игре "Реверси"! Вы играете за Х или О? х Человек ходит первым. 12345678 +--------+ 1| |1 2| |2 3| |3 4| ХО |4 5| ОХ |5 6| |6 7| |7 8| |8 +--------+ 12345678 Игра «Реверси» 267 Ваш счет: 2. Счет компьютера: 2. Укажите ход, текст "выход" для завершения игры или "подсказка" для вывода подсказки. 53 12345678 +--------+ 1| |1 2| |2 3| Х |3 4| ХХ |4 5| ОХ |5 6| |6 7| |7 8| |8 +--------+ 12345678 Ваш счет: 4. Счет компьютера: 1. Нажмите клавишу Enter для просмотра хода компьютера. -- пропуск-- 12345678 +--------+ 1|ХХХХОООО|1 2|ХХХХХООО|2 3|ХХХХХХОО|3 4|ХХХХХХХО|4 5|ХОХХОХХХ|5 6|ХОХОХОХХ|6 7|ХХОООХХХ|7 8|ХХХХХХХХ|8 +--------+ 12345678 X набрал 46 очков. O набрал 18 очков. Вы победили компьютер, обогнав его на 28 очков! Поздравления! Хотите сыграть еще раз? (да или нет) нет 268 Глава 15 Как видно из примера, автор довольно уверенно победил компьютер, 46 очков против 18. Чтобы помочь начинающему игроку, мы добавим в игру подсказки. Игрок может ввести слово подсказка во время своего хода, вклю- чив или выключив режим подсказок. Когда режим подсказок включен, все допустимые ходы игрока отобразятся на игровом поле в виде точек (.), на- пример, так: 12345678 +--------+ 1| . |1 2| Х О. |2 3| ХО. |3 4| ХХО. |4 5| ОО. |5 6| ..О |6 7| . |7 8| |8 +--------+ 12345678 Как видите, игрок может поместить фишку в позиции (6, 1), (6, 2), (4, 6), (7, 7) и несколько других, основываясь на выданных ему подсказках. Исходный код игры «Реверси» «Реверси» — гигантская программа по сравне- нию с нашими предыдущими играми, она содержит почти 300 строк кода! Но не беспокойтесь: многие из них — это комментарии или пустые строки, оставленные, чтобы разделить код и сделать его бо- лее удобочитаемым. Как и в других наших программах, сначала мы создадим несколько функций, которые будут вы- зываться в основном коде игры. Около 250 первых строк кода предназначены для этих вспомогатель- ных функций, а последние 30 строк реализуют саму игру «Реверси». В редакторе файлов создайте новый файл, выбрав команду меню File ⇒ New File (Файл ⇒ Новый файл). В открывшемся окне введите приве- денный ниже исходный код и сохраните файл под именем reversegam.py. На- жмите клавишу F5 и запустите программу. Если при выполнении программы Make sure you’re using Python 3, not Python 2! УБЕ ДИТЕСЬ, ЧТО ИСПО ЛЬЗУЕТЕ PY THON 3, А НЕ PY THON 2! Игра «Реверси» 269 возникают ошибки, сравните код, который вы набрали, с оригинальным ко- дом с помощью онлайн-инструмента на сайте inventwithpython.com/diff/. reversegam.py 1. # "Реверси": клон "Отелло". 2. import random 3. import sys 4. WIDTH = 8 # Игровое поле содержит 8 клеток по ширине. 5. HEIGHT = 8 # Игровое поле содержит 8 клеток по высоте. 6. def drawBoard(board): 7. # Вывести игровое поле, переданное этой функции. Ничего не возвращать. 8. print(' 12345678') 9. print(' +--------+') 10. for y in range(HEIGHT): 11. print('%s|' % (y+1), end='') 12. for x in range(WIDTH): 13. print(board[x][y], end='') 14. print('|%s' % (y+1)) 15. print(' +--------+') 16. print(' 12345678') 17. 18. def getNewBoard(): 19. # Создать структуру данных нового чистого игрового поля. 20. board = [] 21. for i in range(WIDTH): 22. board.append([' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']) 23. return board 24. 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 = [] 37. for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]: 270 Глава 15 38. x, y = xstart, ystart 39. x += xdirection # Первый шаг в направлении x 40. y += ydirection # Первый шаг в направлении y 41. while isOnBoard(x, y) and board[x][y] == otherTile: 42. # Продолжать двигаться в этом направлении x и y. 43. x += xdirection 44. y += ydirection 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]) 53. 54. if len(tilesToFlip) == 0: # Если ни одна из фишек не перевернулась, это недопустимый ход. 55. return False 56. return tilesToFlip 57. 58. def isOnBoard(x, y): 59. # Вернуть True, если координаты есть на игровом поле. 60. return x >= 0 and x <= WIDTH - 1 and y >= 0 and y <= HEIGHT — 1 61. 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 69. 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 78. Игра «Реверси» 271 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} 90. 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 ['О', 'Х'] 104. 105. def whoGoesFirst(): 106. # Случайно выбрать, кто ходит первым. 107. if random.randint(0, 1) == 0: 108. return 'Компьютер' 109. else: 110. return 'Человек' 111. 112. def makeMove(board, tile, xstart, ystart): 113. # Поместить фишку на игровое поле в позицию xstart, ystart и перевернуть какую-либо фишку противника. 114. # Вернуть False, если это недопустимый ход; вернуть True, если допустимый. 115. tilesToFlip = isValidMove(board, tile, xstart, ystart) 116. 117. if tilesToFlip == False: 118. return False 119. 120. board[xstart][ystart] = tile 272 Глава 15 121. for x, y in tilesToFlip: 122. board[x][y] = tile 123. return True 124. 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 134. 135. def isOnCorner(x, y): 136. # Вернуть True, если указанная позиция находится в одном из четырех углов. 137. return (x == 0 or x == WIDTH - 1) and (y == 0 or y == HEIGHT - 1) 138. 139. def getPlayerMove(board, playerTile): 140. # Позволить игроку ввести свой ход. 141. # Вернуть ход в виде [x, y] (или вернуть строки 'подсказка' или 'выход'). 142. DIGITS1TO8 = '1 2 3 4 5 6 7 8'.split() 143. while True: 144. print('Укажите ход, текст "выход" для завершения игры или "подсказка" для вывода подсказки.') 145. move = input().lower() 146. if move == 'выход' or move == 'подсказка': 147. return move 148. 149. if len(move) == 2 and move[0] in DIGITS1TO8 and move[1] in DIGITS1TO8: 150. x = int(move[0]) - 1 151. y = int(move[1]) - 1 152. if isValidMove(board, playerTile, x, y) == False: 153. continue 154. else: 155. break 156. else: 157. print('Это недопустимый ход. Введите номер столбца (1-8) и номер ряда (1-8).') 158. print('К примеру, значение 81 перемещает в верхний правый угол.') 159. 160. return [x, y] 161. 162. def getComputerMove(board, computerTile): Игра «Реверси» 273 163. # Учитывая данное игровое поле и данную фишку компьютера, определить, 164. # куда сделать ход, и вернуть этот ход в виде списка [x, y]. 165. possibleMoves = getValidMoves(board, computerTile) 166. random.shuffle(possibleMoves) # Сделать случайным порядок ходов 167. 168. # Всегда делать ход в угол, если это возможно. 169. for x, y in possibleMoves: 170. if isOnCorner(x, y): 171. return [x, y] 172. 173. # Найти ход с наибольшим возможным количеством очков. 174. bestScore = -1 175. for x, y in possibleMoves: 176. boardCopy = getBoardCopy(board) 177. makeMove(boardCopy, computerTile, x, y) 178. score = getScoreOfBoard(boardCopy)[computerTile] 179. if score > bestScore: 180. bestMove = [x, y] 181. bestScore = score 182. return bestMove 183. 184. def printScore(board, playerTile, computerTile): 185. scores = getScoreOfBoard(board) 186. print('Ваш счет: %s. Счет компьютера: %s.' % (scores[playerTile], scores[computerTile])) 187. 188. def playGame(playerTile, computerTile): 189. showHints = False 190. turn = whoGoesFirst() 191. print(turn + ' ходит первым.') 192. 193. # Очистить игровое поле и выставить стартовые фишки. 194. board = getNewBoard() 195. board[3][3] = 'Х' 196. board[3][4] = 'О' 197. board[4][3] = 'О' 198. board[4][4] = 'Х' 199. 200. while True: 201. playerValidMoves = getValidMoves(board, playerTile) 202. computerValidMoves = getValidMoves(board, computerTile) 203. 204. if playerValidMoves == [] and computerValidMoves == []: 274 Глава 15 205. return board # Ходов нет ни у кого, так что окончить игру. 206. 207. elif turn == 'Человек': # Ход человека 208. if playerValidMoves != []: 209. if showHints: 210. validMovesBoard = getBoardWithValidMoves(board, playerTile) 211. drawBoard(validMovesBoard) 212. else: 213. drawBoard(board) 214. printScore(board, playerTile, computerTile) 215. 216. move = getPlayerMove(board, playerTile) 217. if move == 'выход': 218. print('Благодарим за игру!') 219. sys.exit() # Завершить работу программы. 220. elif move == 'подсказка': 221. showHints = not showHints 222. continue 223. else: 224. makeMove(board, playerTile, move[0], move[1]) 225. turn = 'Компьютер' 226. 227. elif turn == 'Компьютер': # Ход компьютера 228. if computerValidMoves != []: 229. drawBoard(board) 230. printScore(board, playerTile, computerTile) 231. 232. input('Нажмите клавишу Enter для просмотра хода компьютера.') 233. move = getComputerMove(board, computerTile) 234. makeMove(board, computerTile, move[0], move[1]) 235. turn = 'Человек' 236. 237. 238. 239. print('Приветствуем в игре "Реверси"!') 240. 241. playerTile, computerTile = enterPlayerTile() 242. 243. while True: 244. finalBoard = playGame(playerTile, computerTile) 245. 246. # Отобразить итоговый счет. Игра «Реверси» 275 247. drawBoard(finalBoard) 248. scores = getScoreOfBoard(finalBoard) 249. print('X набрал %s очков. O набрал %s очков.' % (scores['Х'], scores['О'])) 250. if scores[playerTile] > scores[computerTile]: 251. print('Вы победили компьютер, обогнав его на %s очков! Поздравления!' % (scores[playerTile] - scores[computerTile])) 252. elif scores[playerTile] < scores[computerTile]: 253. print('Вы проиграли. Компьютер победил вас, обогнав на %s очков.' % (scores[computerTile] - scores[playerTile])) 254. else: 255. print('Ничья!') 256. 257. print('Хотите сыграть еще раз? (да или нет)') 258. if not input().lower().startswith('д'): 259. break Импорт модулей и создание констант Как мы уже научились, начинаем программу с импорта модулей. 1. # "Реверси": клон "Отелло". 2. import random 3. import sys 4. WIDTH = 8 # Игровое поле содержит 8 клеток по ширине. 5. HEIGHT = 8 # Игровое поле содержит 8 клеток по высоте. Код в строке 2 импортирует модуль random для получения доступа к его функциям randint() и choice(). Код в строке 3 импортирует модуль sys для получения доступа к его функции exit(). Код в строках 4 и 5 задают две константы, WIDTH и HEIGHT, которые исполь- зуются для настройки игрового поля. Структура данных игрового поля Давайте разберемся со структурой данных игрового поля. Она представ- ляет собой список списков, точно так же, как в игре «Охотник за сокровища- ми» из главы 13. Список списков создается за тем, чтобы с помощью board[x] [y] можно было представлять символ, находящийся в клетке, расположенной в позиции x по оси x (перемещение влево/вправо) и в позиции y по оси y (вверх/вниз). 276 Глава 15 Этим символом может быть либо ' ' (пробел, представляющий пустую клетку), '.' (точка, указывающая на возможный ход в режиме подсказок) или 'X' или 'O' (буквы, представляющие фишки). Параметр board обозначает эту структуру данных вида список списков. Важно отметить, что если значения координат x и y игрового поля будут варьироваться от 1 до 8, то индексы структуры данных списка — от 0 до 7. Нам придется скорректировать код для учета этого. Отображение на экране структуры данных игрового поля Структура данных игрового поля — это всего лишь список в Python, но нам нужен более удобный способ представить ее на экране. Функция drawBoard() принимает структуру данных поля и отображает ее на экране, чтобы игрок знал, где находятся фишки. 6. def drawBoard(board): 7. # Вывести игровое поле, переданное этой функции. Ничего не возвращать. 8. print(' 12345678') 9. print(' +--------+') 10. for y in range(HEIGHT): 11. print('%s|' % (y+1), end='') 12. for x in range(WIDTH): 13. print(board[x][y], end='') 14. print('|%s' % (y+1)) 15. print(' +--------+') 16. print(' 12345678') Функция drawBoard() выводит игровое поле в текущем состоянии, осно- вываясь на структуре данных в параметре board. Код в строке 8 — первый из вызовов функции print(), выполняемый для каждого игрового поля; он выводит метки для оси x в верхней части поля. Код в строке 9 выводит верхнюю горизонтальную строку поля. Цикл for в строке кода 10 будет выполняться восемь раз, по одному разу для каждого ряда. Код в строке 11 выводит метку для оси y в левой части поля и содержит аргумент — ключевое слово end='', чтобы вместо новой строки поля не выводить ничего. Это связано с тем, что другой цикл в строке 12 (который также выпол- няется восемь раз, по одному для каждого столбца в ряде) выводит каждую клетку вместе с символом X, O, . или пробелом в зависимости от того, что хранится в board[x][y]. Вызов функции print() в строке кода 13 внутри это- го цикла также содержит аргумент — ключевое слово end='', чтобы символ Игра «Реверси» 277 новой строчки поля не выводился. Это создаст на экране одну строку вида '1|XXXXXXXX|1' (если каждое из значений board[x][y] было 'X'). После выполнения внутреннего цикла в строках 15 и 16 вызывается функ- ция print(), которая выводит нижнюю горизонтальную строчку и метки оси x. Цикл for в строке 13 формирует игровое поле. 12345678 +--------+ 1|XXXXXXXX|1 2|XXXXXXXX|2 3|XXXXXXXX|3 4|XXXXXXXX|4 5|XXXXXXXX|5 6|XXXXXXXX|6 7|XXXXXXXX|7 8|XXXXXXXX|8 +--------+ 12345678 Конечно, вместо X некоторые клетки на поле будут отмечены знаком дру- гого игрока (O), точкой (.), если включен режим подсказок, или пробелом для пустых клеток. |