Учим Python, делая крутые игры 2018. Invent your owncomputer gameswith python
Скачать 6.56 Mb.
|
253 23. if (key >= 1 and key <= MAX_KEY_SIZE): 24. return key 25. 26. def getTranslatedMessage(mode, message, key): 27. if mode[0] == 'р': 28. key = -key 29. translated = '' 30. 31. for symbol in message: 32. symbolIndex = SYMBOLS.find(symbol) 33. if symbolIndex == -1: # Символ не найден в SYMBOLS. 34. # Просто добавить этот символ без изменений. 35. translated += symbol 36. else: 37. # Зашифровать или расшифровать 38. symbolIndex += key 39. 40. if symbolIndex >= len(SYMBOLS): 41. symbolIndex -= len(SYMBOLS) 42. elif symbolIndex < 0: 43. symbolIndex += len(SYMBOLS) 44. 45. translated += SYMBOLS[symbolIndex] 46. return translated 47. 48. mode = getMode() 49. message = getMessage() 50. key = getKey() 51. print('Преобразованный текст:') 52. print(getTranslatedMessage(mode, message, key)) Установление максимальной длины ключа Процессы шифрования и расшифровывания — это операции, обратные по отношению друг к другу. В то же время они содержат много общего кода. Давайте посмотрим, как работает каждая строка кода программы. 1. # Шифр Цезаря 2. SYMBOLS = 'АБВГДЕЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеежзийклмнопрстуфхцчшщъыьэюя' 3. MAX_KEY_SIZE = len(SYMBOLS) 254 Глава 14 MAX_KEY_SIZE — константа, которая хранит длину строки SYMBOLS(66). Эта константа напоминает нам, что в нашей программе значение ключа, исполь- зуемого в шифре, всегда должно быть между 1 и 66. Выбор между шифрованием и расшифровыванием сообщения Функция getMode() позволяет пользователю решить, желает он использо- вать режим шифрования или расшифровывания. 5. def getMode(): 6. while True: 7. print('Вы хотите зашифровать или расшифровать текст?') 8. mode = input().lower() 9. if mode in ['зашифровать', 'з', 'расшифровать', 'р']: 10. return mode 11. else: 12. print('Введите "зашифровать" или "з" для зашифровки или "расшифровать" или "р" для расшифровки.') Код в строке 8 вызывает функцию input(), чтобы пользователь мог вы- брать желаемый режим. Затем метод lower() вызывается в этой строке для возврата строчной версии (нижнего регистра) строки. Значение, возвращае- мое из input().lower(), сохраняется в переменной mode. Условие конструкции if проверяет, существует ли строка, хранящаяся в mode, в списке ['зашифро- вать', 'з', 'расшифровать', 'р'] Эта функция вернет строку в mode, если mode равен 'зашифровать', 'з', 'рас- шифровать', 'р' . Следовательно, функция getMode() вернет строку mode. Если пользователь вводит что-то отличное от 'зашифровать', 'з', 'расшифровать' или 'р' , тогда цикл while вновь запросит пользователя ввести правильное зна- чение. Получение сообщения от игрока Функция getMessage() просто получает от пользователя сообщение (для шифрования или расшифровывания) и возвращает его. 14. def getMessage(): 15. print('Введите текст:') 16. return input() Шифр Цезаря 255 Вызов input() совмещен с return, так что мы используем только одну стро- ку кода вместо двух. Получение ключа от игрока Функция getKey() позволяет игроку ввести ключ, который будет исполь- зоваться для шифрования или расшифровки сообщения. 18. def getKey(): 19. key = 0 20. while True: 21. print('Введите ключ шифрования (1-%s)' % (MAX_KEY_SIZE)) 22. key = int(input()) 23. if (key >= 1 and key <= MAX_KEY_SIZE): 24. return key Цикл while гарантирует, что функция продолжит перебор до тех пор, пока пользователь не введет допустимый ключ. Значение допустимого ключа здесь находится между целочисленными значениями 1 и 66 (помните, что значение переменной MAX_KEY_SIZE равно 66, потому что в переменной SYMBOLS 66 симво- лов). Затем функция getKey() возвращает этот ключ. Код в строке 22 присваи- вает переменной key значение, равное целой части того числа, которое ввел пользователь, поэтому метод getKey() возвращает целое число. Шифрование/расшифровывание сообщения Функция getTranslatedMessage(), собственно, и выполняет шифрование и расшифровывание. 26. def getTranslatedMessage(mode, message, key): 27. if mode[0] == 'р': 28. key = -key 29. translated = '' Эта функция имеет три параметра: • mode . Этот параметр устанавливает функцию в режим шифрования или расшифровывания; • message . Это открытый текст (или шифротекст), который должен быть зашифрован (или расшифрован); • key . Это ключ, который используется в этом шифре. 256 Глава 14 Код в строке 27 проверяет, является ли первая буква в переменной mode строкой 'р'. Если это так, то программа переходит в режим расшифровыва- ния. Единственное различие между режимами расшифровывания и шифро- вания заключается в том, что в первом ключ является отрицательной версией самого себя. Например, если key — целое число 22, режим расшифровывания превращает его в -22. Причина объясняется в разделе «Шифрование/расшиф- ровка каждой буквы» далее в этой главе. Переменная translated будет содержать строку результата: либо шифро- текст (если вы выполняете шифрование), либо открытый текст (если рас- шифровываете). В начале она содержит пустую строку, а затем к ее значению присоединяются зашифрованные или расшифрованные символы. Но пре- жде чем мы сможем начать присоединять символы к значению переменной translated , нам нужно зашифровать или расшифровать текст, чем мы и зай- мемся в оставшейся части функции getTranslatedMessage(). Нахождение переданных строк с помощью строчного метода fi nd() Чтобы сдвигать буквы, выполняя шифрование или расшифровывание, нам сначала нужно преобразовать их в числа. Числом для каждой буквы в строке SYMBOLS будет индекс, который занимает буква. Поскольку буква «А» занимает индекс SYMBOLS[0], число 0 будет представлять прописную букву «А». Если бы мы захотели зашифровать ее с помощью ключа 3, то просто исполь- зовали бы операцию 0 + 3 для получения индекса зашифрованной буквы: SYMBOLS[3] или 'Г'. Мы будем использовать строковый метод find() , который обнаруживает первое вхождение переданной строки в строке, в которой вызывается метод. В интерактивной оболочке введите следующие команды: >>> 'Привет, мир!'.find('П') 0 >>> 'Привет, мир!'.find('и') 2 >>> 'Привет, мир!'.find('вет') 3 'Привет, мир!.find ('П') возвращает 0, потому что 'П' находится под пер- вым индексом в строке 'Привет, мир!. Помните, индексы начинаются с 0, не с 1. Код 'Привет, мир!.find('и') возвращает 2, потому что первое вхождение строчной буквы 'и' находится в середине слова 'Привет'. Метод find() пре- кращает поиск после первого вхождения, поэтому вторая буква 'и' в слове Шифр Цезаря 257 'мир ' уже не имеет значения. Вы также можете искать строки с более чем од- ним символом. Начало строки 'вет' находится под индексом 3. Если переданная строка не может быть найдена, метод find() возвраща- ет -1. >>> 'Привет, мир!'.find('пупс') -1 Вернемся к программе «Шифр Цезаря». Код в строке 31 представляет со- бой цикл for, перебирающий каждый символ в строке message. 31. for symbol in message: 32. symbolIndex = SYMBOLS.find(symbol) 33. if symbolIndex == -1: # Символ не найден в SYMBOLS. 34. # Просто добавить этот символ без изменений. 35. translated += symbol Метод find() в строке 32 используется для получения индекса строки в symbol. Если метод find() возвращает -1, символ в переменной symbol будет просто присоединен к значению переменной translated без каких-либо изме- нений. Это означает, что любые символы, не являющиеся частью алфавита, такие как запятые и точки, не будут изменены. Шифрование/расшифровка каждой буквы Как только значение индекса буквы определено, прибавление ключа к это- му значению осуществит смещение и выдаст индекс зашифрованной буквы. Код в строке 38 производит это сложение для получения зашифрованной (или расшифрованной) буквы. 36. else: 37. # Зашифровать или расшифровать 38. symbolIndex += key Помните, что в строке 28 мы сделали целое число в переменной key отри- цательным — для расшифровывания. Код, прибавляющий значение ключа, теперь будет вычитать его, так как прибавление отрицательного числа анало- гично вычитанию. 258 Глава 14 Однако, если это сложение (или вычитание, если значение key отрицатель- но) заставляет переменную symbolIndex проходить последний индекс строки SYMBOLS , нам нужно вернуть его к 0 в начале списка. Это делается с помощью конструкции if, начинающейся в строке 40. 40. if symbolIndex >= len(SYMBOLS): 41. symbolIndex -= len(SYMBOLS) 42. elif symbolIndex < 0: 43. symbolIndex += len(SYMBOLS) 44. 45. translated += SYMBOLS[symbolIndex] Код в строке 40 проверяет, прошла ли переменная symbolIndex последний индекс, сравнивая его с длиной строки SYMBOLS. Если прошла, то код в строке 41 вычитает длину SYMBOLS из значения переменной symbolIndex. Если значение переменной symbolIndex теперь отрицательное, то расчет индексов должен на- чаться с другого конца строки SYMBOLS. Код в строке 42 проверяет, является ли значение symbolIndex отрицательным после прибавления к нему значения ключа расшифровывания. Если оно отрицательно, код в строке 43 прибавля- ет длину SYMBOLS к значению переменной symbolIndex. Переменная symbolIndex теперь содержит индекс правильно зашифро- ванного или расшифрованного символа. Код SYMBOLS[symbolIndex] укажет на конкретный символ для этого индекса, и этот символ будет добавлен в конец значения переменной translated в строке 45. Интерпретатор возвращается к строке 31, чтобы повторить то же для сле- дующего символа в переменной message. Как только цикл завершается, функ- ция возвращает зашифрованную (или расшифрованную) строку в translated в строке кода 46. 46. return translated Последняя строка кода функции getTranslatedMessage() возвращает стро- ку translated. Запуск программы Запуск программы вызывает каждую из трех определенных ранее функ- ций для получения от пользователя всех необходимых данных — режима, со- общения и ключа (mode, message и key). Шифр Цезаря 259 48. mode = getMode() 49. message = getMessage() 50. key = getKey() 51. print('Преобразованный текст:') 52. print(getTranslatedMessage(mode, message, key)) Эти три значения передаются функции getTranslatedMessage(), возвращае- мое значение которой (строка translated) выводится пользователю. ДОБАВЛЕНИЕ НОВЫХ СИМВОЛОВ Если вы хотите зашифровывать числа, пробелы и знаки препинания, про- сто добавьте их к значению переменной SYMBOLS в строке 2. Например, с помощью программы можно шифровать числа, пробелы и знаки пре- пинания, изменив код в строке 2 на следующий: 2. SYMBOLS = 'АБВГДЕЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеежзийклмнопрстуфхцчшщъыьэюя 1234567890!@#$%^&*()' Обратите внимание, что значение переменной SYMBOLS содержит про- бел после строчной буквы «я». Также можно добавить еще больше символов в этот список. И не нуж- но менять остальную часть вашей программы, так как все строки кода, где необходим список символов, используют константу SYMBOLS. Просто убедитесь, что каждый символ содержится в строке только один раз. Кроме того, вам нужно будет расшифровывать сообщения с помощью той же строки SYMBOLS, с помощью которой они были зашифрованы. Полный перебор Вот и весь шифр Цезаря. Впрочем, несмотря на то, что этот шифр может обмануть кого-то, кто не разбирается в криптографии, он не удержит сооб- щение в секрете от знатоков криптоанализа. В то время как криптография — это наука о создании кодов, криптоанализ изучает их взлом. Весь смысл криптографии в том, что если даже зашифрованное сообще- ние попадет в чужие руки, то никто не сможет понять исходный текст. Давай- те представим себя на месте взломщика кодов, и будто все, чем мы располага- ем, — этот зашифрованный текст: 260 Глава 14 Цямхд ъжомуц игъд кмхрмф щшмлр тцпжйцт. Полный перебор (метод «грубой силы», от англ. brute force) — это метод последовательного перебора всех возможных ключей вплоть до нахождения правильного. Поскольку существует только 66 возможных ключей, криптоа- налитику будет легко написать программу для взлома, расшифровывающую сообщения с использованием всех возможных ключей. Затем он найдет ключ, который расшифровывает в чистый английский. Давайте добавим в програм- му режим полного перебора Добавление режима полного перебора Сначала измените код в строках 7, 9 и 12 в функции getMode() так, чтобы они выглядели следующим образом (изменения выделены жирным шрифтом ): 5. def getMode(): 6. while True: 7. print('Вы хотите зашифровать, расшифровать или взломать текст?') 8. mode = input().lower() 9. if mode in ['зашифровать', 'з', 'расшифровать', 'р' , 'взломать', 'в']: 10. return mode 11. else: 12. print('Введите "зашифровать" или "з" для зашифровки или "расшифровать" или "р" для расшифровки или "взломать" или "в" для взлома.') Этот код позволит пользователю выбрать в качестве режима полный пе- ребор (взлом шифра) . Затем внесите следующие изменения в основной код программы: 48. mode = getMode() 49. message = getMessage() 50. if mode[0] != 'в': 51. key = getKey() 52. print('Преобразованный текст:') 53. if mode[0] != 'в': 54. print(getTranslatedMessage(mode, message, key)) 55. else: 56. for key in range(1, MAX_KEY_SIZE + 1): 57. print(key, getTranslatedMessage('расшифровать', message, key)) Шифр Цезаря 261 Если пользователь не выбрал режим полного перебора, программа спра- шивает у него ключ, вызывает функцию getTranslatedMessage() и выводит «переведенную» строку. Если же пользователь выбрал режим полного пе- ребора, цикл getTranslatedMessage() выполняет перебор от 1 до MAX_KEY_SIZE (то есть до 66). Помните, что функция range() возвращает список целых чи- сел вплоть до второго параметра, но не включая его, вот почему мы добавля- ем + 1. Затем программа выводит каждый возможный «перевод» сообщения (включая значение ключа, используемого при преобразовании). Ниже пока- зан пример работы модифицированной программы. Вы хотите зашифровать, расшифровать или взломать текст? взломать Введите текст: Цямхд ъжомуц игъд кмхрмф щшмлр тцпжйцт. Преобразованный текст: 1 Хюлфг щенлтх звщг йлфплу шчлкп схоеихс. 2 Фэкув шемксф жбшв икуокт чцкйо рфнезфр. 3 Уьйтб чдлйру еачб зйтнйс цхйин пумджуп. 4 Тыиса цгкипт еЯца жисмир хфизм отлгето. 5 СъзрЯ хвйзос дЮхЯ езрлзп фузжл нсквесн. 6 РщжпЮ фбижнр гЭфЮ ежпкжо утжек мрйбдрм. 7 ПшеоЭ уаземп вЬуЭ деойен тсеей лпиагпл. 8 ОченЬ тЯжело бЫтЬ гением среди козЯвок. 9 НцдмЫ сЮедкн аЪсЫ вдмздл рпдгз йнжЮбнй. 10 МхглЪ рЭегйм ЯЩрЪ бглжгк погвж имеЭами. 11 ЛфвкЩ пЬдвил ЮШпЩ авкевй онвбе злеЬЯлз. 12 КубйШ оЫгбзк ЭЧоШ Ябйеби нмбае жкдЫЮкж. -- пропуск-- Просмотрев каждую строку, вы увидите, что восьмое сообщение не бес- смысленность, а понятный русский текст! Криптоаналитик придет к выводу, что исходный ключ для этого зашифрованного текста был 8. Подобный ме- тод было бы проблематично осуществить в дни Юлия Цезаря и Римской им- перии, но сегодня у нас есть компьютеры, которые могут за короткое время перебрать миллионы или даже миллиарды ключей. Заключение Компьютеры хороши в математических расчетах. Когда мы создаем си- стему для преобразования некоторой части информации в числа (как с тек- стом и порядковыми числительными или с геопозициями и системами ко- ординат), компьютерные программы могут обрабатывать эти числа быстро и эффективно. Большая часть разработки программы заключается в том, как представить информацию, которой вы хотите управлять, в виде понятных Python значений. При том, что наша программа «Шифр Цезаря» может шифровать сообще- ния, смысл которых останется тайной для людей, вооруженных карандашом и бумагой, программа не сможет сохранить их в секрете от тех, кто знает, как обрабатывать информацию с помощью компьютеров (это подтверждает режим полного перебора). В главе 15 мы создадим игру «Реверси» (также известную как «Отелло»). Искусственный интеллект в этой игре намного более продвинут, чем в про- грамме «Крестики-нолики» в главе 10. Он будет настолько хорош, что в боль- шинстве случаев вы не сможете его обыграть! Игра «Реверси» 263 15 ИГРА «РЕВЕРСИ» В этой главе мы создадим игру «Ревер- си», также известную под названием «Отелло». В этой настольной игре для двух игроков используется сетка, поэто- му нам пригодится декартова система с координатами x и y. У нашей версии игры будет компьютерный искусственный интеллект , более продвинутый, чем ИИ игры «Крестики-нолики» из гла- вы 10. Вообще-то этот ИИ настолько хорош, что он, ско- рее всего, будет побеждать вас почти в каждой игре (во всяком случае он непременно обыгрывает автора). В ЭТОЙ ГЛАВЕ РАССМАТРИВАЮТСЯ СЛЕДУЮЩИЕ ТЕМЫ: • Как играть в «Реверси» • Функция bool() • Моделирование ходов на игровом поле • Программирование искусственного интеллекта в игре «Реверси» Как играть в «Реверси» В игре «Реверси» используется поле размером 8×8 клеток и фишки — чер- ные с одной стороны и белые с другой (вместо этого мы будем использовать буквы O и X). В начале игры поле выглядит так, как показано на рис. 15.1. Два игрока по очереди выставляют на поле фишки выбранного ими цве- та — черные или белые. Когда игрок помещает фишку на поле, все фишки противника, которые находятся между новой фишкой и остальными фишка- ми игрока, переворачиваются. |