Commited: 50+ воркшопів з ТОПами тестування: M.Bolton, G.Bahmutov, T.King, R.Desyatnikov, J.Bach. Лише за $40 на рік та економія $15 до 20 квітня
×Закрыть

Вызов на Python-игры (часть 4)

Продолжение. Начало тут.

Внимание! Прежде чем читать дальше, я настоятельно рекомендую всем попытаться справится с задачами самостоятельно. Опыт и полученное вами удовольствие будут несравненно выше.

Уровень 15

Картинка на уровне содержит календарь, но дата на нем «восстановлению не подлежит». Точнее подлежит, но не вся и не полностью. Заголовок вопрошает «кого?». Внимательное изучение подсказок дает нам несколько ключей к решению.

  1. Маленький календарь на февраль в правом нижнем углу большого — видно что год високосный.
  2. В исходнике HTML — «он не самый младший, он второй» и просит «купить цветы на завтра». Видимо у «кого-то» — день рождения 27 числа.
  3. Выделенная дата — 26 января, понедельник.
Дело за малым — найти список годов, в которые 26 января попадает на понедельник, и при этом год високосный. После этого — определить, кто же все-таки родился 27 января в тот год.
Сначала я сделал программку, которая просто выдавала список подходящих годов. (27 января должно было быть вторником)

 

from datetime import date

for year in range(1006, 2000, 10):
dat = date(year, 1, 27)
if dat.weekday() == 1:
print dat
После этого я решил поискать эти даты в Google. С третей же даты мне повезло, 1756-01-27 родился не кто иной как Johannes Chrisostomus Wolfgangus Theophilus Mozart. Его фамилия и служит «ключем» на следующий уровень.

Уровень 16

На этом уровне мы встречаем странную картинку, состоящую из серого шума. Заголовок страницы просит «помочь выровнять их». Выравнивать видимо предполагается розовые черточки, благо их как раз по одной в ряду.
Опять взявшись за PIL, я составил следующую программу.

 

import Image

srcpix = Image.open('mozart.gif').load()

dest = Image.new('L', (640, 480), 0)
destpix = dest.load()

for y in xrange(480):
for x in xrange(640):
# 195 – розовый цвет, ищем столбец в котором начинается черточка
if srcpix[x, y] == 195:
# сдвигаем ряд
for z in range(640):
destpix[z, y] = srcpix[(x+z) % 640, y]

dest.show()
Получается очень симпатичная, хотя и не цветная картинка со словом romance, проводящим нас на следующий уровнь.

Уровень 17

На этом уровне пришлось попотеть и вспомнить почти все, что встречалось уже раньше.
Для начала, картинка отсылает нас на 4 уровень (те же фигурки с пилой), но печеньица рекомендуют обратить внимание на cookies.
Смотрим, и видим, что нам рекомендуют использовать в этот раз вместо nothing параметь bysunothing. Пробуем, и видим, что на каждой странице имеется cookie с параметром info.
Собираем все печеньица, следуя по ссылкам как на 4 уровне, и получаем 118 байтную строку. Для начала, ее необходимо расшифровать функцией unquote_plus модуля urllib, а потом провести декомпрессию используя модуль bz2.
Получится сообщение, в котором нас просят позвонить его отцу и сказать что цветы уже в пути.
Под отцом, видимо, подразумевается Леопольд Моцарт. Чтобы позвонить, надо узнать номер телефона. А телефонную книгу мы уже использовали в уровне 13. Вызываем XMLRPC сервер и узнаем номер телефона 555-VIOLIN. Подставив это слово в адрес, мы получаем ссылку, на которой можем поговорить с Леопольдом. Сделать это очень просто — тем же методом что и раньше — используя cookies.

В итоге у меня получилась следующая программа.

Часть первая, для поиска сообщения аналогична программе с уровня 14.

 

import urllib2, re, bz2

nothing = '12345'

findnothing = re.compile(r"nothing is (d+)").search

out = ""
for i in xrange(400):
url = "<a title="Linkification: http://www.pythonchallenge.com/pc/def/linkedlist.php?busynothing=%s" class="linkification-ext" href="http://www.pythonchallenge.com/pc/def/linkedlist.php?busynothing=%s">http://www.pythonchallenge.com/pc/def/linkedlist.php?busynothing=%s</a>" % nothing
req = urllib2.Request(url)
res = urllib2.urlopen(req)
page = res.read()

# если заголовок содержит cookie, читаем его
if res.headers.has_key('Set-Cookie'):
cookie = res.headers['Set-Cookie']
matches = re.findall('(?si)info=(.*?);', cookie)
out += urllib.unquote_plus(matches[0])
print out

match = findnothing(page)
if match:
nothing = match.group(1)
print page
else:
break

print bz2.decompress(out)
print out
Часть вторая, для разговоров с отцом Моцарта

 

import urllib
message = "the flowers are on their way"
url = "<a title="Linkification: http://www.pythonchallenge.com/pc/stuff/violin.php" class="linkification-ext" href="http://www.pythonchallenge.com/pc/stuff/violin.php">http://www.pythonchallenge.com/pc/stuff/violin.php</a>"
req = urllib2.Request(url, headers={'Cookie': 'info=' + urllib.quote_plus(message)})
print urllib2.urlopen(req).read()
После выполнения второй программы, мы получаем от Моцарта-страршего совет не забыть про воздушные шарики (baloons), что и выводит нас на следующий уровень.

Уровень 18

Перед нами две картинки и вопрос — видим ли мы разницу. Подсказка в исходном коде страницы говорит, что все очевидно. Я попробовал самое очевидное решение — подставить слово яркость (brightness) в URL.
Картинка не изменилась, но поменялся текст подсказки, указывая нам на файл с данными.
Скачав и разархивировав его, я стал думать что же делать дальше.
Заголовок, вопрошающий «можем ли мы указать разницу» натолкнул на идею использовать для сравнения левой и правой половин файла модуль difflib. В этом модуле имеется функция ndiff, сравнивающая два массива строк и возвращающая результат в виде строк, начинающихся с одного из символов: + если строка есть в первом файле, — если она есть во втором и " " если обе строки содержат файл.
Это наталкивало на мысль разделить исходный файл на 3 потока, используя этот признак в качестве ключевого. Так я и сделал, предварительно конвертировав 16-ричные коды в символы.
Программа получилась следующей.

 

from __future__ import with_statement
from difflib import ndiff, restore

# преобразование строки 16-ричных чисел в символьные коды
def dehex(str):
res =''
for char in map(lambda x: chr(int(x, 16)) if x!='' else '', str.split(' ')):
res += char

return res

(lst1, lst2) = ([], [])

# читаем файл и формируем список строк левой и правой половин
with open('delta.txt', 'r') as file:
for line in file:
spl = line.split('   ')
lst1.append(dehex(spl[0]))
lst2.append(dehex(spl[1]))

# выводим разницу
diff_res = ndiff(lst1, lst2)

reslt = [[], [], []]

# делим на 3 списка
for line in diff_res:
if line[0:2] == '  ':
reslt[0].append(line[2:])
elif line[0:2] == '- ':
reslt[1].append(line[2:])
else:
reslt[2].append(line[2:])

# сохраняем
for x in xrange(0, 3):
f = open('res%d.png' % x, 'wb')
f.write(''.join(reslt[x]))
f.close()
В результате получаем 3 файла, один с сылкой на следующий уровень, и два с логином и паролем для попадания туда.

Уровень 19

В исходнике страницы мы находим сохраненное письмо с вложением. Раскодировать его очень просто с помощью функции b64decode модуля base64.
В итоге мы получим файл indian.wav, но кроме шума и слова sorry в нем ничего не слышно.
В письме говорится, что компьютер Леопольда сломался (out of order), и ему некогда разбираться с этим вложением.
Что делать с файлом, не совсем ясно. Но несколько подсказок проливают на это свет.

  1. Карта на странице задания инвертирована.
  2. И страна на карте, и название файла наталкивают на слово «Indian»
  3. Фраза Леопольда «out of order» также может быть переведена как «не в порядке»
  4. Его фразы про «молодежь».
Все это наводит на мысль что используется разный порядок байтов в файле (little-endian и big-endian).

Дело остается за малым — «перевернуть» байты в файле. Благо семпл у нас как раз 16-битный.

 

from base64 import b64decode
import wave

# открываем файл вложения в письмо
instr = open('base64.txt', 'r').read()

# декодируем его
str2 = b64decode(instr)

# открываем файл для чтения и сохраняем его
outf = open('indian.wav', 'wb')
outf.write(str2)
outf.close()

# открываем файлы источника и назначения как wave-семплы
inw = wave.open('indian.wav', 'r')
outw = wave.open('result.wav', 'w')

outw.setparams(inw.getparams())

for x in xrange(0, inw.getnframes()):
lo = inw.readframes(1)
hi = lo[1:2]+lo[0:1]
outw.writeframes(hi)

inw.close()
outw.close()
Слушаем его, и последнее слово, которое очень легко разобрать, дает нам URL следующего уровня.

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

👍НравитсяПонравилось0
В избранноеВ избранном0
Подписаться на автора
LinkedIn

Похожие статьи




Підписуйтесь: iTunes | Google Podcast | YouTube


7 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

рад.: -) заходите еще: -)

У Вас очень интересный сайт! Мне очень понравилось!

Я так и не нашел начало. Где можно найти Уровни 1−15

В самом начале каждой статьи стоят ссылки на предыдущую часть.

Я так и не нашел начало. Где можно найти Уровни 1−15?

Да, довольно таки интересная серия статей.

круто. предыдущего не читал, но этот пост читается как настоящий детектив!

да уж. сайт там все интересней и интересней становится...: -)

Подписаться на комментарии