Учим Python, делая крутые игры 2018. Invent your owncomputer gameswith python
Скачать 6.56 Mb.
|
232 Глава 13 Выход прост: добавьте пробел перед каждым однозначным числом. Стро- ки кода 34–37 задают переменную extraSpace равной либо пробелу, либо пу- стой строке. Переменная extraSpace выводится всегда, но символ пробела она содержит только при однозначных номерах рядов, в противном случае при- нимая значение пустой строки. Таким образом мы обеспечим правильный вывод рядов на экран. Вывод ряда в океане Параметр board — это структура данных, отвечающая за все океан- ские волны. Строки кода 39–44 считывают переменную board и выводят один ряд. 39. # Создать строку для этого ряда на игровом поле. 40. boardRow = '' 41. for column in range(60): 42. boardRow += board[column][row] 43. 44. print('%s%s %s %s' % (extraSpace, row, boardRow, row)) В строке кода 40 переменная boardRow сначала принимает значение пу- стой строки. Цикл for в строке кода 32 заставляет переменную row выво- дить текущий ряд океанских волн. Внутри этого цикла в строке кода 41 есть еще один цикл for, который перебирает каждый столбец текущего ряда. В этом цикле мы присваиваем переменной boardRow результат конка- тенации board[column][row], что подразумевает объединение board[0][row], board[1][row] , board[2][row] и так далее до board[59][row]. Это связано с тем, что ряд содержит 60 символов, начиная с индекса 0 и заканчивая индек- сом 59. Цикл for в строке кода 41 перебирает целые числа от 0 до 59. При каждой такой итерации, следующий символ в структуре данных игрового поля ко- пируется в конец boardRow. К моменту выхода из цикла переменная boardRow содержит полностью нарисованные с помощью ASCII волны ряда. Затем в строке кода 44 выводится строка в переменной boardRow вместе с номерами рядов. Изображение координат x вдоль нижней части игрового поля Код в строках 46–49 аналогичен строкам кода 26–29. Игра «Охотник за сокровищами» 233 46. # Вывести числа в нижней части поля. 47. print() 48. print(' ' + ('0123456789' * 6)) 49. print(tensDigitsLine) Эти строки выводят ось координат x в нижней части поля. Создание случайных сундуков с сокровищами Код в игре случайным образом размещает скрытые сундуки с сокрови- щами. Сундуки с сокровищами представлены в виде списка списков из двух целых чисел. Эти два числа представляют собой координаты x и y каждого сундука. Например, если структура данных сундука была [[2, 2], [2, 4], [10, 0]] , то это означало бы наличие трех сундуков с сокровищами, одного в позиции с координатами (2, 2), другого — в позиции (2, 4) и третьего — в позиции (10, 0). Функция getRandomChests() создает определенное количество структур данных сундука в случайных координатах. 51. def getRandomChests(numChests): 52. # Создать список структур данных сундука (двухэлементные списки целочисленных координат x и y)). 53. chests = [] 54. while len(chests) < numChests: 55. newChest = [random.randint(0, 59), random.randint(0, 14)] 56. if newChest not in chests: # Убедиться, что сундука здесь еще нет. 57. chests.append(newChest) 58. return chests Параметр numChests сообщает функции о том, сколько сундуков с со- кровищами нужно сгенерировать. Цикл while в строке кода 54 будет по- вторяться до тех пор, пока всем сундукам не будут назначены координаты. Для координат в строке кода 55 выбраны два случайных целых числа. Ко- ордината x может быть любой в диапазоне значений от 0 до 59, а коорди- ната y — в диапазоне от 0 до 14. Выражение [random.randint(0, 59), random. randint(0, 14)] станет списком наподобие [2, 2],[2, 4] или [10, 0]. Если эти координаты еще не содержатся в списке chests, они присоединяются к нему в строке кода 57. 234 Глава 13 Определение допустимости хода Когда игрок вводит координаты x и y позиции, где он желает опустить гидролокатор в воду, нужно убедиться, что введенные числа допустимы. Как упоминалось ранее, существуют два условия для того, чтобы ход был допустимым: значение координаты x должно находиться в диапазоне от 0 до 59, а координаты y — в диапазоне от 0 до 14. В коде функции isOnBoard() используются операторы and, чтобы объеди- нить упомянутые условия в одно выражение и удостовериться, что каждая часть выражения истинна. 60. def isOnBoard(x, y): 61. # Возвращать True, если координаты есть на поле; в противном случае возвращать False. 62. return x >= 0 and x <= 59 and y >= 0 and y <= 14 Поскольку мы используем логический оператор and, все выражение ста- новится ложным, если хотя бы одна из координат недопустима. Отражение хода на игровом поле В игре «Охотник за сокровищами» игровое поле обновляется, чтобы ото- бразить число, указывающее расстояние от каждого гидролокатора до бли- жайшего сундука с сокровищами. Поэтому, когда игрок делает ход, задавая программе координаты x и y, поле изменяется в зависимости от местонахож- дения сундуков с сокровищами. 64. def makeMove(board, chests, x, y): 65. # Изменить структуру данных поля, используя символ гидролокатора. Удалить сундуки 66. # с сокровищами из списка с сундуками, как только их нашли. Вернуть False, если это 67. # недопустимый ход. В противном случае, вернуть строку с результатом этого хода. Функция makeMove() принимает четыре параметра: структуру данных игрового поля, структуру данных сундука с сокровищами, координату x и ко- ординату y. Функция makeMove() возвращает строковое значение, описываю- щее, что произошло в ответ на ход. • Если координаты попадают прямо на сундук с сокровищами, функ- ция makeMove() возвращает 'Вы нашли сундук с сокровищами на затонувшем судне!' Игра «Охотник за сокровищами» 235 • Если координаты находятся от сундука на расстоянии 9 или менее единиц, функция makeMove() возвращает 'Сундук с сокровищами обна- ружен на расстоянии %s от гидролокатора.' (где %s заменяется на целое значение расстояния). • В противном случае функция makeMove() вернет 'Гидролокатор ничего не обнаружил. Все сундуки с сокровищами вне пределов досягае мости.' Имея координаты позиции, в которой игрок хочет опустить в воду ги- дролокатор, и список координат x и y для сундуков с сокровищами, вам по- надобится алгоритм, чтобы узнать, какой сундук с сокровищами находится ближе всего. Поиск ближайшего сундука с сокровищами Строки кода 68–75 содержат алгоритм определения, какой сундук с со- кровищами находится ближе всего к гидролокатору. 68. smallestDistance = 100 # Все сундуки будут расположены ближе, чем на расстоянии в 100 единиц. 69. for cx, cy in chests: 70. distance = math.sqrt((cx - x) * (cx - x) + (cy - y) * (cy - y)) 71. 72. if distance < smallestDistance: # Нам нужен ближайший сундук с сокровищами. 73. smallestDistance = distance Параметры x и y являются целыми числами (например, 3 и 5), и в паре они обозначают позицию на игровом поле, которую игрок указал в своем предположении. Переменная chests получит значение типа [[5, 0], [0, 2], [4, 2]] , представляющее собой расположение трех сундуков с сокровищами. На рис. 13.4 это значение представлено в графическом виде. Чтобы определить расстояние между гидролокатором и сундуком с со- кровищами, нам нужно будет произвести некоторые арифметические расче- ты и найти расстояние между двумя координатами x и двумя y. Предположим, мы поместили гидролокатор в позицию (3, 5) и хотим определить расстояние до сундука с сокровищами, находящегося в позиции (4, 2). Чтобы определить расстояние между двумя наборами координат x и y, мы воспользуемся теоремой Пифагора. Эта теорема применима к прямоугольным треугольникам — таким, у которых один угол равен 90 градусам (такие же углы у прямоугольника). В теореме Пифагора сообщается, что диагональную сторону треугольника можно рассчитать исходя из длин горизонтальной и вертикальной сторон. 236 Глава 13 0 0 1 2 3 4 5 6 7 1 2 3 4 5 6 Рис. 13.4. Сундуки с сокровищами, представленные значением [[5, 0], [0, 2], [4, 2]] На рис. 13.5 показан прямоугольный треугольник, образованный путем соединения позиций гидролокатора (3, 5) и сундука с сокровищами (4, 2). 0 0 1 2 3 4 5 6 7 1 2 3 4 5 6 a b c 0 0 1 2 3 4 5 6 7 1 2 3 4 5 6 a b c Рис. 13.5. Поле с прямоугольным треугольником, соединяющим гидролокатор и сундук с сокровищами Теорема Пифагора выглядит как a 2 + b 2 = c 2 , где a — длина горизонтальной стороны, b — длина вертикальной стороны, c — длина диагональной стороны или гипотенузы. Эти длины возведены в квадрат, то есть числа были умноже- ны сами на себя. Обратная операция называется нахождением квадратного корня числа — это то, что мы делаем, чтобы получить c из c 2 Давайте воспользуемся теоремой Пифагора, чтобы определить расстоя- ние между гидролокатором (3, 5) и сундуком (4, 2). 1. Чтобы вычислить a, вычтите вторую координату x (4) из первой коор- динаты x (3): 3 − 4 = −1. 2. Чтобы определить a 2 , умножьте a на a: −1 × −1 = 1. (Отрицательное чис- ло, умноженное на отрицательное, всегда дает положительное число.) 3. Чтобы вычислить b, вычтите вторую координату y (2) из первой коор- динаты y (5): 5 − 2 = 3. Игра «Охотник за сокровищами» 237 4. Чтобы вычислить b 2 , умножьте b на b: 3 × 3 = 9. 5. Чтобы определить c 2 , сложите a 2 и b 2 : 1 + 9 = 10. 6. Чтобы получить c из c 2 , вам нужно найти квадратный корень c 2 Модуль math, который мы импортировали в строке кода 5, содержит функцию с именем sqrt(), вычисляющую квадратный корень. В интерактив- ной оболочке введите следующие команды: >>> import math >>> math.sqrt(10) 3.1622776601683795 >>> 3.1622776601683795 * 3.1622776601683795 10.000000000000002 Обратите внимание, что результатом умножения квадратного корня чис- ла на самого себя будет это число. (Дополнительная 2 в конце 10 — результат погрешности нахождения корня числа 10.) Передав значение c 2 функции sqrt(), можно утверждать, что сундук с со- кровищами находится на расстоянии 3,16 единицы от гидролокатора. Игра округлит это значение до 3. Давайте снова взглянем на строки кода с 68 по 70. 68. smallestDistance = 100 # Все сундуки будут расположены ближе, чем на расстоянии в 100 единиц. 69. for cx, cy in chests: 70. distance = math.sqrt((cx - x) * (cx - x) + (cy - y) * (cy - y)) Код внутри цикла for в строке кода 69 вычисляет расстояние до каждого сундука. В начале цикла код в строке 68 присваивает переменной smallestDistance невероятно большое расстояние в 100 единиц, так что по крайней мере один из сундуков с сокровищами, что вы найдете, будет помещен в smallestDistance в строке кода 73. Так как cx – x представляет собой горизонтальное расстояние a между сундуком и гидролокатором, то (cx – x) * (cx – x) — это, согласно нашей теореме Пифагора, а 2 Значение а 2 добавляется к (cy – y) * (cy – y), то есть к b 2 . Эта сумма равна c 2 и передается функции sqrt(), чтобы определить расстояние между сунду- ком и гидролокатором. Нам нужно определить расстояние между гидролокатором и ближайшим сундуком, поэтому, если это расстояние меньше, чем минимальное, оно со- храняется как новое минимальное расстояние в строке кода 73. 238 Глава 13 72. if distance < smallestDistance: # Нам нужен ближайший сундук с сокровищами. 73. smallestDistance = distance К моменту завершения цикла for вам известно, что переменная smallestDistance содержит наиболее короткое расстояние между гидролока- тором и всеми существующими в игре сундуками с сокровищами. Удаление значений с помощью метода списка remove() Метод списка remove() удаляет первое вхождение значения, соответству- ющее переданному аргументу. Например, введите следующий код в интерак- тивной оболочке: >>> x = [42, 5, 10, 42, 15, 42] >>> x.remove(10) >>> x [42, 5, 42, 15, 42] Значение 10 было удалено из списка x. Теперь введите в интерактивной оболочке следующее: >>> x = [42, 5, 10, 42, 15, 42] >>> x.remove(42) >>> x [5, 10, 42, 15, 42] Обратите внимание, что было удалено только первое значение 42, а вто- рое и третье остались на месте. Метод remove() удаляет только первое вхож- дение значения, которое вы ему передаете. Если вы попытаетесь удалить значение, которого нет в списке, то получи- те сообщение об ошибке. >>> x = [5, 42] >>> x.remove(10) Traceback (most recent call last): File " ValueError: list.remove(x): x not in list Игра «Охотник за сокровищами» 239 Как и append(), метод remove() вызывается в списке и не возвращает список. Вам необходимо использовать код типа x.remove(42), а не x = x.remove(42). Давайте вернемся к определению расстояний между гидролокатора- ми и сундуками с сокровищами. Единственный случай, когда переменная smallestDistance принимает значение 0, — когда координаты x и y гидролока- тора совпадают с соответствующими координатами сундука. Это значит, что игрок угадал местонахождение сундука с сокровищами. 77. if smallestDistance == 0: 78. # Координаты xy попали прямо в сундук с сокровищами! 79. chests.remove([x, y]) 80. return 'Вы нашли сундук с сокровищами на затонувшем судне!' В этом случае программа с помощью метода списка remove() удаляет из структуры данных chests список из двух чисел, относящийся к этому сунду- ку. Затем функция возвращает строку 'Вы нашли сундук с сокровищами на за- тонувшем судне!' Но если переменная smallestDistance не равна 0, это значит, что игрок не угадал точное местоположение сундука с сокровищами, и запускается блок else со строки 81. 81. else: 82. if smallestDistance < 10: 83. board[x][y] = str(smallestDistance) 84. return 'Сундук с сокровищами обнаружен на расстоянии %s от гидролокатора.' % (smallestDistance) 85. else: 86. board[x][y] = 'X' 87. return 'Гидролокатор ничего не обнаружил. Все сундуки с сокровищами вне пределов досягаемости.' Если расстояние от гидролокатора до сундука с сокровищами меньше 10, код в строке 83 отмечает поле строковой версией переменной smallestDistance. В противном случае на поле выводится символ 'X'. Таким образом, игрок знает, насколько близко каждый гидролокатор рас- положен к сундуку с сокровищами. Если игрок видит X, то понимает, что он очень далеко. Получение хода игрока Функция enterPlayerMove() получает координаты x и y следующего хода игрока. 240 Глава 13 89. def enterPlayerMove(previousMoves): 90. # Позволить игроку сделать ход. Вернуть двухэлементный список с целыми координатами x и y. 91. print('Где следует опустить гидролокатор? (координаты: 0-59 0-14) (или введите "выход")') 92. while True: 93. move = input() 94. if move.lower() == 'выход': 95. print('Спасибо за игру!') 96. sys.exit() Параметр previousMoves представляет собой список списков с двумя це- лыми числами, обозначающими предыдущие позиции, в которых игрок опу- скал гидролокатор. Это нужно для того, чтобы игрок не сделал повторный ход в одной и той же позиции. Цикл while будет продолжать предлагать игро- ку сделать следующий ход, пока он не введет координаты позиции, где еще нет гидролокатора. Игрок может также ввести 'выход', чтобы выйти из игры. В этом случае код в строке 96 вызывает функцию sys.exit(), немедленно за- вершающую работу программы. Предполагая, что игрок не ввел 'выход', программа проверяет, что ввод представляет собой два целых числа, разделенных пробелом. Код в строке 98 вызывает метод split() в списке move как новое значение move. 98. move = move.split() 99. if len(move) == 2 and move[0].isdigit() and move[1].isdigit() and isOnBoard(int(move[0]), int(move[1])): 100. if [int(move[0]), int(move[1])] in previousMoves: 101. print('Здесь вы уже опускали гидролокатор.') 102. continue 103. return [int(move[0]), int(move[1])] 104. 105. print('Введите число от 0 до 59, потом пробел, а затем число от 0 до 14.') Если игрок ввел значение, подобное '1 2 3', тогда список, возвращаемый функцией split(), будет иметь вид ['1', '2', '3']. В этом случае выраже- ние len(move) == 2 станет ложным (список позиции должен содержать лишь два числа, поскольку он представляет собой координаты), и все выражение немедленно станет ложным. Интерпретатор Python не проверяет остальную часть выражения из-за короткого замыкания (описанного в разделе «Вычис- ление по короткой схеме» главы 10). Игра «Охотник за сокровищами» 241 Если длина списка равна 2, то два значения будут иметь индексы move[0] и move[1]. Чтобы проверить, числовые ли это значения (как '2' или '17'), вы можете использовать функцию типа isOnlyDigits(), описанную в разделе «Проверка на содержание в строке только чисел» главы 11. Но в Python уже есть метод, который делает это. Строковый метод isdigit() возвращает True, если строка состоит исклю- чительно из чисел. В противном случае он возвращает значение False. В инте- рактивной оболочке введите следующие команды: >>> '42'.isdigit() True >>> 'сорок'.isdigit() False >>> ''.isdigit() False >>> 'привет'.isdigit() False >>> x = '10' >>> x.isdigit() True И move[0].isdigit(), и move[1].isdigit() должны принимать значение True, чтобы все условие было истинным. Заключительная часть условия в строке кода 99 вызывает функцию isOnBoard(), чтобы проверить, существуют ли ко- ординаты x и y на игровом поле. Если все условие истинно, код в строке 100 проверяет, есть ли данный ход в списке previousMoves. Если есть, тогда инструкция continue в строке 102 воз- вращает интерпретатор к началу цикла while в строке кода 92, а затем снова предлагает игроку сделать ход. Если хода нет в списке, код в строке 103 воз- вращает список с двумя целыми числами — координатами x и y. |