Учим Python, делая крутые игры 2018. Invent your owncomputer gameswith python
Скачать 6.56 Mb.
|
Определение того, находится ли клетка в углу Функция isOnCorner() возвращает значение True, если координаты соот- ветствуют угловой клетке с координатами (0, 0), (7, 0), (0, 7) или (7, 7). 135. def isOnCorner(x, y): 136. # Вернуть True, если указанная позиция находится в одном из четырех углов. 137. return (x == 0 or x == WIDTH - 1) and (y == 0 or y == HEIGHT - 1) В противном случае функция isOnCorner() возвращает False. Позже мы будем использовать эту функцию для программирования ИИ. Получение хода игрока Функция getPlayerMove() вызывается, чтобы позволить игроку ввести ко- ординаты следующего хода (и проверить, допустим ли он). Игрок также мо- жет ввести слово подсказка, чтобы включить (если выключен) или выключить (если включен) режим подсказок. Наконец, игрок может ввести слово выход для выхода из игры. 288 Глава 15 139. def getPlayerMove(board, playerTile): 140. # Позволить игроку ввести свой ход. 141. # Вернуть ход в виде [x, y] (или вернуть строки 'подсказка' или 'выход'). 142. DIGITS1TO8 = '1 2 3 4 5 6 7 8'.split() Константа DIGITS1TO8 содержит список [1, 2, 3, 4, 5, 6, 7, 8]. Функция getPlayerMove() использует DIGITS1TO8 пару раз, а имя константы проще про- читать, чем полное значение списка. Нельзя использовать метод isdigit(), потому что это позволит вводить 0 и 9 — значения, не являющиеся допусти- мыми координатами на поле размером 8 × 8. Цикл while продолжает выполнение до тех пор, пока игрок не введет до- пустимый ход. 143. while True: 144. print('Укажите ход, текст "выход" для завершения игры или "подсказка" для вывода подсказки.') 145. move = input().lower() 146. if move == 'выход' or move == 'подсказка': 147. return move Код в строке 146 проверяет, хочет ли игрок выйти или переключить ре- жим подсказок, а код в строке 147 возвращает соответственно строку 'выход' или 'подсказка'. Метод lower() вызывается в строке, возвращаемой функци- ей input(), поэтому игрок может ввести ПОДСКАЗКА или Выход, и код все равно «поймет» команду. Код, который вызвал функцию getPlayerMove(), будет решать, что делать, если игрок хочет выйти или переключить режим подсказок. Если игрок вво- дит координаты для хода, инструкция if в строке 149 проверяет, допустим ли этот ход. 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 Игра «Реверси» 289 Игра ожидает, что игрок введет координаты x и y своего хода в виде двух чисел без какого-либо символа или пробела между ними. Сначала код в строке 149 проверяет, чтобы размер строки, которую ввел игрок, был ра- вен 2. После этого он также проверяет, являются ли и move[0] (первый символ в строке), и move[1] (второй символ в строке) строками, доступными в списке DIGITS1TO8 Помните, что структуры данных игрового поля имеют индексы от 0 до 7, а не от 1 до 8. Когда поле отображается в drawBoard(), код выводит от 1 до 8, потому что непрограммисты привыкли, что числа начинаются с 1, а не с 0. Поэтому для преобразования строк из элементов move[0] и move[1] в целые числа, строки 150 и 151 вычитают 1 из значений переменных x и y. Даже если игрок ввел допустимый ход, код должен проверить, разре- шен ли этот ход правилами игры «Реверси». Это осуществляется с помощью функции isValidMove(), которой передается структура данных игрового поля, фишка игрока и координаты x и y хода. Если isValidMove() возвращает значение False, в строке 153 выполняется инструкция continue. Затем интерпретатор возвращается к началу цикла while и снова запрашивает у игрока допустимый ход. В противном случае игрок действительно ввел допустимый ход, и интерпретатор должен выйти из цик- ла while. Если условие при инструкции if в строке 149 было ложным, значит, игрок не ввел допустимый ход. Строки 157 и 158 сообщают игроку, как правильно вводить ходы. 156. else: 157. print('Это недопустимый ход. Введите номер столбца (1-8) и номер ряда (1-8).') 158. print('К примеру, значение 81 перемещает в верхний правый угол.') После этого интерпретатор возвращается к инструкции while в стро- ке 143, поскольку строка 158 — последняя строка не только в блоке else, но и в блоке while. Цикл while будет продолжать выполняться до тех пор, пока игрок не введет допустимый ход. Если игрок вводит координаты x и y, будет выполнен код в строке 160. 160. return [x, y] Наконец, если код в строке 160 выполняется, функция getPlayerMove() возвращает список из двух элементов с координатами x и y допустимого хода игрока. 290 Глава 15 Получение хода компьютера С помощью функции getComputerMove() мы реализуем алгоритм ИИ. 162. def getComputerMove(board, computerTile): 163. # Учитывая данное игровое поле и данную фишку компьютера, определить, 164. # куда сделать ход, и вернуть этот ход в виде списка [x, y]. 165. possibleMoves = getValidMoves(board, computerTile) Обычно вы используете результаты, полученные от функции getValidMoves() , для режима подсказок, который выведет . на поле, чтобы показать игроку все его возможные ходы. Но если функция getValidMoves() вызывается с фишкой ИИ компьютера (параметр computerTile), она также найдет все возможные ходы, которые может сделать компьютер. ИИ выберет лучший ход из этого списка. Сначала функция random.shuffle() сделает порядок ходов в списке possibleMoves случайным. 166. random.shuffle(possibleMoves) # Сделать случайным порядок ходов Нужно перетасовать список possibleMoves, поскольку это сделает ИИ менее предсказуемым; в противном случае игрок мог бы просто запомнить ходы, необходимые для победы, ведь ответы компьютера всегда были бы оди- наковыми. Давайте рассмотрим алгоритм. Разработка стратегии с угловыми ходами Угловые ходы — это неплохая идея для игры «Реверси», поскольку фишка в углу доски не может быть перевернута. Код в строке 169 перебирает каж- дый ход в списке possibleMoves. Если какой-либо из них является угловым, программа вернет эту клетку для хода компьютера. 168. # Всегда делать ход в угол, если это возможно. 169. for x, y in possibleMoves: 170. if isOnCorner(x, y): 171. return [x, y] Так как possibleMoves — это список двухэлементных списков, мы будем ис- пользовать множественное присваивание в цикле for для назначения значе- Игра «Реверси» 291 ний x и y. Если список possibleMoves содержит несколько угловых ходов, всег- да используется первый из них. Но так как в строке 166 содержимое списка possibleMoves было перетасовано, первый угловой ход в списке определяется случайным образом. Получения списка самых результативных ходов Если угловые ходы отсутствуют, программа переберет весь список воз- можных ходов и выяснит, какой из них приводит к получению наибольшего количества очков. Тогда переменной bestMove присваивается ход с наибольшим количеством очков, найденный до сих пор кодом, а переменной bestScore присваивается это количество очков. Так происходит до тех пор, пока не будет найден луч- ший ход. 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 Сначала код в строке 174 присваивает переменной bestScore значение −1, чтобы первый ход, проверяемый кодом, был присвоен bestMove как первый лучший ход. Этим можно гарантировать, что bestMove будет присвоен один из ходов из possibleMoves, когда bestMove будет возвращен. В строке 175 цикл for присваивает x и y каждый ход в possibleMoves. Пе- ред моделированием хода код в строке 176 создает копию структуры данных игрового поля, вызывая функцию getBoardCopy(). Вам понадобится копия, ко- торую вы можете изменять, не влияя на настоящую структуру данных поля, хранящуюся в переменной board. Затем код в строке 177 вызывает функцию makeMove(), передавая копию поля (хранимую в boardCopy) вместо настоящего поля. Это моделирование си- туации на реальном игровом поле, будь сделан такой ход. Функция makeMove() будет заниматься выставлением фишки компьютера и переворачиванием фишек игрока на копии игрового поля. 292 Глава 15 Код в строке 178 вызывает функцию getScoreOfBoard() с копией поля, которая возвращает словарь, в котором ключами являются 'X' и 'O', а зна- чения — это очки. Когда код в цикле находит ход, у которого больше очков, чем содержится в bestScore, код в строках с 179 по 181 сохраняют этот ход и это количество очков в качестве новых значений bestMove и bestScore. По- сле того как цикл полностью перебрал список possibleMoves, возвращается bestMove К примеру, getScoreOfBoard() возвращает словарь {'X': 22, 'O': 8}, а зна- чение computerTile — 'X'. Тогда getScoreOfBoard(boardCopy)[computerTile] примет значение {'X': 22, 'O': 8}['X'] , что затем примет значение 22. Если 22 больше, чем bestScore, тог- да bestScore устанавливается равной 22, а bestMove устанавливается равной те- кущим значениям x и y. К моменту завершения цикла for вы можете быть уверены, что bestScore содержит наивысшее значение очков, которое можно получить ходом, и этот ход хранится в bestMove. Несмотря на то что код всегда выбирает первый из ходов в списке, вы- бор оказывается случайным, поскольку список был перетасован в строке 166. Это гарантирует, что ИИ не будет предсказуемым в случае наличия больше одного лучшего хода. Вывод игрового счета на экран Функция showPoints() вызывает функцию getScoreOfBoard(), а затем выво- дит результаты игры человека и компьютера. 184. def printScore(board, playerTile, computerTile): 185. scores = getScoreOfBoard(board) 186. print('Ваш счет: %s. Счет компьютера: %s.' % (scores[playerTile], scores[computerTile])) Помните, что функция getScoreOfBoard() возвращает словарь с ключами 'X' и 'O' и значениями с очками игроков X и O. Это все функции, которые используются в игре «Реверси». Код внутри функции playGame(), собственно, воплощает игру в жизнь, вызывая эти функ- ции по мере необходимости. Начало игры Функция playGame() использует вызовы предварительно написанных нами функций для запуска игры. Игра «Реверси» 293 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] = 'Х' Функции playGame() передаются строки 'X' или 'O' в виде playerTile и computerTile. Строка 190 определяет, чей первый ход. Переменная turn со- держит строку 'Компьютер' или 'Человек' для отслеживания того, чья очередь ходить. Код в строке 194 создает структуру данных пустого поля, в то время как строки с 195 по 198 устанавливают на поле начальные четыре фишки. Теперь можно начинать играть. Проверка на попадание в тупик Прежде чем получить ход игрока или компьютера, нужно проверить, мо- гут ли они вообще сделать ход. Если нет, то игра заходит в тупик и должна окончиться. (Если допустимых ходов нет только у одной стороны, очередь переходит к другому игроку.) 200. while True: 201. playerValidMoves = getValidMoves(board, playerTile) 202. computerValidMoves = getValidMoves(board, computerTile) 203. 204. if playerValidMoves == [] and computerValidMoves == []: 205. return board # Ходов нет ни у кого, так что окончить игру. Код в строке 200 начинает основной цикл для управления очередностью ходов игрока и компьютера. Пока этот цикл продолжает выполняться, игра будет работать. Но прежде чем начать выполнять эти очереди, строки 201 и 202 получают списки допустимых ходов и проверяют, может ли каждая из сторон сделать ход. Если оба этих списка пустые, значит, ни один из игроков не может сделать ход. Строка 205 выходит из функции playGame(), возвращая итоговое поле, завершая игру. 294 Глава 15 Выполнение хода игроком Если игра не попадает в тупик, программа определяет, принадлежит ли ход игроку, проверяя, принимает ли переменная turn значение строки 'Человек'. 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) В строке 207 начинается блок elif, содержащий код, который выполня- ется, если это очередь игрока. (Блок elif, который начинается в строке 227, содержит код для очереди компьютера.) Весь этот код будет работать только в том случае, если у игрока есть допустимый ход, что определяет строка 208, проверяя, не пуст ли спи- сок playerValidMoves. Мы отображаем поле на экране, вызывая функцию drawBoard() в строке 211 или 213. Если включен режим подсказок (то есть showHints принимает значение True ), тогда структура данных поля должна отобразить . на каждой допу- стимой для хода игрока клетке. Это осуществляется с помощью функции getBoardWithValidMoves() . Ей передается структура данных игрового поля, а возвращает она копию, которая содержит еще и точки (.). Код в строке 211 передает это поле функции drawBoard(). Если режим подсказок выключен, тогда вместо этого строка 213 передает board в качестве параметра drawBoard(). После вывода игрового поля также необходимо вывести текущие очки. Делается это путем вызова функции printScore() в строке 214. Затем игроку необходимо ввести свой ход. Этим занимается функция getPlayerMove() — возвращаемое ей значение представляет собой двухэле- ментный список координат x и y хода игрока. 216. move = getPlayerMove(board, playerTile) К моменту определения функции getPlayerMove() мы уже убедились в до- пустимости хода игрока. Игра «Реверси» 295 Функция getPlayerMove() может вернуть строки 'выход' или 'подсказка' вместо хода. Строки с 217 по 222 работают с такими случаями: 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 = 'Компьютер' Если игрок вместо хода ввел слово выход, функция getPlayerMove() вернет строку 'выход'. В этом случае код в строке 219 вызывает функцию sys.exit() для завершения работы программы. Если игрок ввел слово подсказка, функция getPlayerMove() вернет строку 'подсказка' . В этом случае нужно включить режим подсказок (если он выклю- чен) либо выключить, если он включен). Инструкция присваивания showHints = not showHints в строке 221 рабо- тает с обоими этими случаями, потому что not False принимает значение True , а not True — то же, что False. Затем инструкция continue переносит вы- полнение в начало цикла (переменная turn не изменила значение, так что это по-прежнему будет очередь игрока). В противном случае, если игрок не вы- шел и не переключил режим подсказок, код в строке 224 вызывает функцию makeMove() , чтобы осуществить ход игрока. Наконец, код в строке 225 присваивает переменной turn значение 'Компью- тер' . Поток выполнения пропускает блок else и достигает конца блока while, поэтому интерпретатор возвращается к инструкции while в строке 200. На этот раз, однако, будет очередь компьютера. Выполнение хода компьютером Если переменная turn содержит строку 'Компьютер', тогда будет выпол- няться код, отвечающий за ход компьютера. Он аналогичен коду для очереди игрока, но имеет отличия. 227. elif turn == 'Компьютер': # Ход компьютера 228. if computerValidMoves != []: 296 Глава 15 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]) После отображения игрового поля с помощью функции drawBoard() про- грамма также выводит текущие очки, вызывая функцию showPoints() в строке 230. Код в строке 232 вызывает функцию input(), чтобы приостановить сце- нарий и дать игроку взглянуть на поле. Это очень похоже на то, как функция input() использовалась для приостановки программы-шутника в главе 4. Вме- сто того чтобы вызывать функцию print() для вывода строки на экран перед вызовом функции input(), вы можете сделать то же самое, передав строку для вывода функции input(). После того как игрок посмотрел на поле и нажал клавишу Enter, код в строке 233 вызывает функцию getComputerMove(), чтобы получить координа- ты x и y следующего хода компьютера. Эти координаты сохраняются в пере- менных x и y с помощью множественного присваивания. Наконец, значения переменных x и y вместе со структурой данных игро- вого поля и фишкой компьютера передаются функции makeMove(). Она поме- щает фишку компьютера на игровое поле в board, отражая ход компьютера. Вызов функции getComputerMove() в строке 233 позволяет получить ход ком- пьютера (и сохраняет его в переменных x и y). Функция makeMove() в строке 234 делает ход на игровом поле. Затем код в строке 235 присваивает переменной turn строковое значение 'Человек' 235. turn = 'Человек' После строки 235 в блоке while больше нет кода, поэтому интерпретатор возвращается к инструкции while в строке 200. Игровой цикл Это все функции, которые используются в игре «Реверси». Начиная со строки 239, основная часть программы запускает игру , вызывая функцию playGame() , но она также отображает окончательный результат и спрашивает игрока, желает ли он сыграть снова. |