Учим Python, делая крутые игры 2018. Invent your owncomputer gameswith python
Скачать 6.56 Mb.
|
195 39. return False 40. 41. return True 42. 43. 44. print('Я загадаю %s-х значное число, которое вы должны отгадать.' % (NUM_DIGITS)) 45. print('Я дам несколько подсказок...') 46. print('Когда я говорю: Это означает:') 47. print(' Холодно Ни одна цифра не отгадана.') 48. print(' Тепло Одна цифра отгадана, но не отгадана ее позиция.') 49. print(' Горячо Одна цифра и ее позиция отгаданы.') 50. 51. while True: 52. secretNum = getSecretNum() 53. print('Итак, я загадал число. У вас есть %s попыток, чтобы отгадать его.' % (MAX_GUESS)) 54. 55. guessesTaken = 1 56. while guessesTaken <= MAX_GUESS: 57. guess = '' 58. while len(guess) != NUM_DIGITS or not isOnlyDigits(guess): 59. print('Попытка №%s: ' % (guessesTaken)) 60. guess = input() 61. 62. print(getClues(guess, secretNum)) 63. guessesTaken += 1 64. 65. if guess == secretNum: 66. break 67. if guessesTaken > MAX_GUESS: 68. print('Попыток больше не осталось. Я загадал число %s.' % (secretNum)) 69. 70. print('Хотите сыграть еще раз? (да или нет)') 71. if not input().lower().startswith('д'): 72. break Блок-схема игры «Холодно-горячо» Блок-схема, показанная на рис. 11.1, описывает происходящее в этой игре и порядок, в котором может быть сделан каждый шаг. Блок-схема игры «Холодно-горячо» довольно проста. Компьютер генери- рует секретное число, игрок пытается угадать это число, компьютер выдает 196 Глава 11 игроку подсказки, основанные на его предположениях. Это происходит сно- ва и снова, пока игрок не победит или не проиграет. После окончания игры компьютер предложит сыграть еще раз. Generate secret number START Get player’s guess See if player has run out of guesses Tell player clues Increment guess count Ask to play again Player has lost Player has won END Старт Конец Генерируем се- кретное число Получаем предположе- ние игрока Показываем игроку под- сказки Увеличиваем счетчик по- пыток Игрок выиграл Проверяем, не закончились ли у игрока попытки Игрок проиграл Предлагаем сыграть снова Рис. 11.1. Блок-схема игры «Холодно-горячо» Импорт модуля random и определение функции getSecretNum() В начале программы мы импортируем модуль random и настроим не- которые глобальные переменные. Затем мы определим функцию с именем getSecretNum() 1. import random 2. 3. NUM_DIGITS = 3 4. MAX_GUESS = 10 5. 6. def getSecretNum(): 7. # Возвращает строку уникальных случайных цифр, длина которой составляет NUM_DIGITS. Вместо того чтобы использовать целое число 3 для количества цифр в от- вете, мы используем константу NUM_DIGITS. То же самое касается количества попыток, которые получает игрок; мы используем константу MAX_GUESS вместо Дедуктивная игра «Холодно-горячо» 197 целого числа 10. Теперь будет легко изменить количество попыток или цифр секретного числа. Просто измените значения в строке 3 и/или 4, остальная часть программы по-прежнему будет работать без каких-либо изменений. Функция getSecretNum() генерирует секретное число, которое содержит неповторяющиеся цифры. Игра «Холодно-горячо» станет намного веселее, если цифры секретного числа не будут повторяться, как например '244' или '333' . Мы будем использовать некоторые новые функции Python, чтобы осу- ществить это в функции getSecretNum(). Перетасовка уникального набора цифр Первые две строки функции getSecretNum() перетасовывают набор непо- вторяющихся чисел: 8. numbers = list(range(10)) 9. random.shuffle(numbers) Функция list(range(10)) в строке 8 принимает значение [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] , поэтому переменная numbers содержит список из всех 10 цифр. Изменение порядка элементов списка с помощью функции random.shuffl e() Функция random.shuffle() случайным образом изменяет порядок элемен- тов списка (в данном случае — списка цифр). Эта функция не возвращает зна- чение, а, скорее, изменяет список, который вы ей передаете, прямо на месте. Похоже на то, как функция makeMove() в игре «Крестики-нолики» из главы 10 изменяла передаваемый ей список на месте, вместо того чтобы возвращать новый список с изменением. Вот почему вы не используете код типа numbers = random.shuffle(numbers) Поэкспериментируйте с функцией shuffle(), введя следующий код в ин- терактивной оболочке: >>> import random >>> spam = list(range(10)) >>> print(spam) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> random.shuffle(spam) >>> print(spam) 198 Глава 11 [3, 0, 5, 9, 6, 8, 2, 4, 1, 7] >>> random.shuffle(spam) >>> print(spam) [9, 8, 3, 5, 4, 7, 1, 2, 0, 6] Каждый раз, когда функция random_shuffle() вызывается в spam, элемен- ты в списке spam перетасовываются. Далее вы увидите, как можно применить функцию shuffle() для генерации секретного числа. Получение секретного числа из перетасованных цифр Секретное число будет представлять собой строку из первых цифр NUM_ DIGITS перетасованного списка с целыми числами. 10. secretNum = '' 11. for i in range(NUM_DIGITS): 12. secretNum += str(numbers[i]) 13. return secretNum Переменная secretNum вначале принимает вид пустой строки. Цикл for в строке 11 выполняется NUM_DIGITS количество раз. При каждой итерации цикла целое число с индексом i извлекается из перетасованного списка, пре- образуется в строку и конкатенируется в конец secretNum. Например, если numbers относится к списку [9, 8, 3, 5, 4, 7, 1, 2, 0, 6], то при первой итерации numbers[0] (то есть 9) будет передан str(); это возвра- щает значение '9', конкатенируемое в конец secretNum. При второй итерации то же самое происходит с numbers[1] (то есть 8), а при третьей итерации то же самое происходит с numbers[2] (то есть 3). Конечное возвращаемое значение secretNum равно '983'. Обратите внимание, что secretNum в этой функции содержит строку, а не целое число. Это может показаться странным, но помните, что нельзя кон- катенировать целые числа. Выражение 9 + 8 + 3 принимает значение 20, в то время как нам нужно '9' + '8' + '3', принимающее значение '983'. Расширенные операторы присваивания Оператор += в строке 12 является одним из расширенных операторов при- сваивания. Обычно если вы хотите добавить или конкатенировать значение с переменной, то используете код, который выглядит следующим образом: Дедуктивная игра «Холодно-горячо» 199 >>> spam = 42 >>> spam = spam + 10 >>> spam 52 >>> eggs = 'Привет, ' >>> eggs = eggs + 'мир!' >>> eggs 'Привет, мир!' Расширенные операторы присваивания — это короткий путь, освобож- дающий вас от перепечатывания имени переменной. Следующий код делает то же, что и предыдущий. >>> spam = 42 >>> spam += 10 # То же, что и spam = spam + 10 >>> spam 52 >>> eggs = 'Привет, ' >>> eggs += 'мир!' # То же, что и eggs = eggs + 'мир!' >>> eggs 'Привет, мир!' Есть и другие расширенные операторы присваивания. Введите следую- щий код в интерактивной оболочке: >>> spam = 42 >>> spam -= 2 >>> spam 40 Инструкция spam -= 2 аналогична инструкции spam = spam — 2, поэтому выражение принимает значение spam = 42 — 2, а затем spam = 40. Существуют также расширенные операторы присваивания для умноже- ния и деления . >>> spam *= 3 >>> spam 120 >>> spam /= 10 200 Глава 11 >>> spam 12.0 Инструкция spam *= 3 аналогична инструкции spam = spam * 3. Итак, по- скольку переменной spam ранее было присвоено значение 40, полностью вы- ражение будет иметь вид spam = 40 * 3, что равно 120. Выражение spam /= 10 аналогично spam = spam / 10, а результат вычисления выражения spam = 120/10 равен 12.0. Обратите внимание, что после деления значение переменной spam становится числом с плавающей запятой. Подсчет выдаваемых подсказок Функция getClues() вернет строку с подсказками «горячо», «тепло» и «хо- лодно» в зависимости от параметров guess и secretNum. 15. def getClues(guess, secretNum): 16. # Возвращает строку с подсказками пользователю "Тепло", "Горячо" и "Холодно". 17. if guess == secretNum: 18. return 'Вы угадали!' 19. 20. clues = [] 21. for i in range(len(guess)): 22. if guess[i] == secretNum[i]: 23. clues.append('Горячо') 24. elif guess[i] in secretNum: 25. clues.append('Тепло') Наиболее очевидный шаг — проверить, совпадает ли предположение с се- кретным числом, что мы и делаем в строке 17. В данном случае код в строке 18 возвращает значение 'Вы угадали!'. Если предположение не совпадает с секретным числом, программа долж- на определить, какие игроку дать подсказки. Список в переменной clues сна- чала будет пустым, а по мере необходимости в него будут добавлены строки 'Горячо' и 'Тепло'. Программа делает это, перебирая каждый возможный индекс в guess и secretNum. Строки в обеих переменных будут одинаковой длины, так что код в строке 21 работал бы одинаково, независимо от того, использовались бы там len(guess) или len(secretNum). Поскольку значение переменной i из- меняется в диапазоне от 0 до 1 до 2 и так далее, код в строке 22 проверяет, Дедуктивная игра «Холодно-горячо» 201 совпадает ли первый, второй, третий и так далее символ guess с символом со- ответствующего индекса secretNum. Если совпадает, код в строке 23 добавляет строку 'Горячо' к значению переменной clues. В противном случае код в строке 24 проверяет, содержится ли в secretNum цифра с i-й позиции в guess. Если содержится, это значит, что цифра есть в секретном числе, но не в той же позиции. В данном случае код в строке 25 добавляет 'Тепло' к значению переменной clues. Если список clues пуст после выполнения цикла, тогда ясно, что в guess вообще нет правильных цифр. 26. if len(clues) == 0: 27. return 'Холодно' В данном случае код в строке 27 возвращает строку 'Холодно' как един- ственную подсказку. Метод списка sort() Для списков доступен метод sort() , который размещает элементы списка в алфавитном или нумерованном порядке. Когда вызывается метод sort(), он не возвращает отсортированный список, а сортирует список на месте. Это похоже на работу метода shuffle(). Нельзя использовать return spam.sort(), потому что это вернуло бы значе- ние None. Вместо этого вам нужна отдельная строка, spam.sort(), и тогда стро- ка вернет spam. В интерактивной оболочке введите следующий код: >>> spam = ['кот', 'пес', 'мышь', 'аргонавт'] >>> spam.sort() >>> spam ['аргонавт', 'кот', 'мышь', 'пес'] >>> spam = [9, 8, 3, 5.5, 5, 7, 1, 2.1, 0, 6] >>> spam.sort() >>> spam [0, 1, 2.1, 3, 5, 5.5, 6, 7, 8, 9] Когда мы сортируем список строк, строки возвращаются в алфавитном порядке, но при сортировке списка чисел числа возвращаются в порядке воз- растания. 202 Глава 11 В строке 29 мы используем функцию sort() в списке clues. 29. clues.sort() Список clue нужно сортировать в алфавитном порядке, чтобы изба- виться от дополнительной информации, которая помогла бы игроку от- гадать секретное число с меньшими усилиями. Если бы содержимое clues выглядело так: ['Тепло', 'Горячо', 'Тепло'], это указывало бы игроку, что средняя цифра в его предположении находится на правильном месте. По- скольку две других подсказки — 'Тепло', игрок поймет, что для отгадыва- ния секретного числа ему нужно всего лишь переставить местами первую и третью цифры. Если же подсказки сортируются в алфавитном порядке, игрок не может быть уверен, к какому числу относится подсказка Горячо. Это делает игру сложнее и веселее. Строковый метод join() Строковый метод join() возвращает список строк в виде единой строки, соединенной вместе. 30. return ' '.join(clues) Строка, в которой вызывается этот метод (в строке 30 это одинарный пробел, ' '), появляется между каждой строкой в списке. Чтобы увидеть при- мер, введите в интерактивной оболочке следующее: >>> ' '.join(['Меня', 'зовут', 'Вася', 'Пупкин']) 'Меня зовут Вася Пупкин' >>> ', '.join(['Жизнь', 'Вселенная', 'Бесконечность']) 'Жизнь, Вселенная, Бесконечность' Таким образом, строка, возвращаемая в строке кода 30, представляет со- бой каждую строку в clue плюс одинарные пробелы между этими строками. Строковый метод join() — это своего рода противоположность строковому методу split(). Если split() возвращает список из разделенной строки, то join() возвращает строку из объединенного списка. Дедуктивная игра «Холодно-горячо» 203 Проверка на содержание в строке только чисел Функция isOnlyDigits() помогает определить, ввел ли игрок предположе- ние в допустимом формате. 32. def isOnlyDigits(num): 33. # Возвращает значение True, если num - строка, со- стоящая только из цифр. В противном случае возвращает False. 34. if num == '': 35. return False Сначала код в строке 34 проверяет, является ли num пустой строкой, и если это так, то возвращает значение False. Затем цикл for выполняет итерацию по каждому символу в строке num. 37. for i in num: 38. if i not in '0 1 2 3 4 5 6 7 8 9'.split(): 39. return False 40. 41. return True Значение переменной i будет содержать один символ при каждой итера- ции. Внутри блока for код проверяет, есть ли i в списке, возвращенном '0 1 2 3 4 5 6 7 8 9'.split() . (Возвращаемое значение из split() эквивалентно ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] .) Если i отсутствует в этом списке, понятно, что в значении переменной num есть символ, отличный от цифры. В таком случае код в строке 39 возвращает значение False. Но если выполнение продолжается после цикла for, тогда каждый символ в num — это цифра. В этом случае код в строке 41 возвращает значение True. Начало игры После того как были определены все функции, код в строке 44 фактиче- ски запускает программу. 44. print('Я загадаю %s-х значное число, которое вы должны отгадать.' % (NUM_DIGITS)) 45. print('Я дам несколько подсказок...') 46. print('Когда я говорю: Это означает:') 204 Глава 11 47. print(' Холодно Ни одна цифра не отгадана.') 48. print(' Тепло Одна цифра отгадана, но не отгадана ее позиция.') 49. print(' Горячо Одна цифра и ее позиция отгаданы.') Вызовы функции print() сообщают игроку правила игры, а также зна- чения подсказок «холодно», «тепло» и «горячо». В строке 44 в код функции print() добавлены переменные %(NUM_DIGITS) в конце строки и %s внутри нее. Этот прием известен как интерполяция строк. Интерполяция строк Интерполяция строк , также известная как форматирование строк, — это способ экономии времени при наборе кода. Обычно, если вы хотите исполь- зовать значения одной строки внутри переменных в другой строке, вам нуж- но использовать оператор конкатенации, +. >>> name = 'Алиса' >>> event = 'ночной клуб' >>> location = 'центре города' >>> day = 'субботу' >>> time = '23:00' >>> print('Привет, ' + name + '. Ты пойдешь в ' + event + ' в ' + location + ' в эту ' + day + ' в ' + time + ' часа' + '?') Привет, Алиса. Ты пойдешь в ночной клуб в центре города в эту субботу в 23:00 часа? Как видите, может потребоваться много времени, чтобы набрать строку, которая объединяет несколько строк. Вместо этого вы можете использовать интерполяцию строк, которая позволяет помещать в строку заполнители, вроде %s. Эти заполнители называются спецификаторами преобразования. Как только вы ввели спецификаторы преобразования, можно поместить все имена переменных в конец строки. Каждый спецификатор %s заменяется пе- ременной в конце строки в том порядке, в котором вы ввели эти переменные. Например, следующий код делает то же самое, что и предыдущий: >>> name = 'Алиса' >>> event = 'ночной клуб' >>> location = 'центре города' >>> day = 'субботу' >>> time = '23:00' Дедуктивная игра «Холодно-горячо» 205 >>> print('Привет, %s. Ты пойдешь в %s в %s в эту %s в %s часа?' % (name, event, location, day, time)) Привет, Алиса. Ты пойдешь в ночной клуб в центре города в эту субботу в 23:00 часа? Обратите внимание, что имя первой переменной используется для первого спецификатора %s, имя второй — для второго и так далее. У вас должно быть одинаковое количество спецификаторов преобразования %s и пере менных. Еще одно преимущество использования интерполяции строк вместо конкатенации заключается в том, что интерполяция работает с любым типом данных, а не только со строками. Все значения автоматически преобразуются в строчный тип данных. Если вы соедините целое число со строкой, то по- лучите следующую ошибку. >>> spam = 42 >>> print('Спам == ' + spam) Traceback (most recent call last): File " TypeError: Can't convert 'int' object to str implicitly Конкатенация строк предназначена только для объединения двух строк, но значение переменной spam — целое число. Вам придется помнить о необ- ходимости использовать str(spam) вместо spam. Теперь введите следующий код в интерактивной оболочке: >>> spam = 42 >>> print('Спам — это %s' % (spam)) Спам — это 42 При использовании интерполяции строк преобразование в строки вы- полняется автоматически. Игровой цикл Код в строке 51 представляет собой бесконечный цикл while с условием True , который будет повторяться до тех пор, пока не будет выполнена ин- струкция break. 51. while True: 52. secretNum = getSecretNum() 53. print('Итак, я загадал число. У вас есть %s попыток, чтобы отгадать его.' % (MAX_GUESS)) |