Code JP 2015 出し物班やりました(出題編) #codejp

前記事に引き続き、今度は(出題編)です。

これを利用して他の人にオリジナルのQRコード問題を出題しよう。 (きっと嫌がられます)

準備編

Pythonqrcode というライブラリを作って作成します。 画像作成のために、 pillow も入れます。

$ mkdir -p /tmp/work/qrcode2
$ cd /tmp/work/qrcode2
$ virtualenv -p /usr/bin/python2.7 venv
Running virtualenv with interpreter /usr/bin/python2.7
New python executable in venv/bin/python
Installing setuptools, pip...done.

$ . venv/bin/activate
(venv)$ pip install -U pip
(venv)$ pip install qrcode
(venv)$ pip install pillow

q1の作成

リスト内容記法使うと大体3行で出来ます。 確認用にcryptを出力して確認してます。

#!/usr/bin/env python
# coding: utf-8

import qrcode

text = '[CodeJP2015]'

def main():
    crypt = ' '.join([str(ord(v)) for v in text])
    qrcode.make(crypt).save('./01-01.png')

if __name__ == '__main__':
    main()

q2の作成

上級へのベースとなった問題です。 文字数が均一になるように気をつけたり、あとあと zipfile が使いやすいようにストリーム使ったりしました。

#!/usr/bin/env python
# coding: utf-8

import qrcode
import base64
import io

text = 'Mt. Moiwa Ropeway,Sapporo Art Park,[Jozankei Hot Springs],Hoheikyo Dam,Marukoma Hot Spring Hotel'

def main():
    qr_count = 16
    crypt = text.encode('utf-8')
    for i in range(4):
        crypt = base64.b64encode(crypt)

    crypt_len = len(crypt)
    parts_lens = [crypt_len / qr_count for i in range(qr_count)]
    surplus = crypt_len % qr_count
    if surplus != 0:
        for i in range(surplus):
            parts_lens[i] += 1

    n = 0
    for i, m in enumerate(parts_lens):
        part_text = crypt[n:n+m]
        qr = qrcode.QRCode()
        qr.add_data(part_text)
        qr.make(fit=True)
        img = qr.make_image()

        output_bytes = io.BytesIO()
        img.save(output_bytes, 'PNG')
        output_bytes.seek(0)
        with io.open('imgs/02-{0:0>2}.png'.format(i), 'wb') as f:
            f.write(output_bytes.read())
        n = n + m

if __name__ == '__main__':
    main()

q3の作成

「Drewのいつもの3語使った答えにしよう」と決まったのが開催1週間ぐらい前。
「よし、つくるぞ!」ってなったのが、イベント前夜。

悪い意味で「よく、間に合ったもんだ」と思いました。 ただ、やりたいことは決まってて、

でした。更に手作業を減らすために極力プログラム側で実装する!のを目標に作成しました。

まぁ、寝不足で書いていたコードだったので、バグが多く難産でした。
本当は1024枚程度にしようと思ってたのに、QRコードの使用上1枚1万強の文字のみなので、泣く泣く4倍の枚数に。
(因みに当日慌ててたのは、ここの実装作業で、PCが落ちてたりしました)
また、dummyの方も解いて欲しかったので、なるだけ正答と比較して同じ量のbase64の長さにするのを心掛けました。

\某煽り用URLは深夜テンションで思いつきました/

#!/usr/bin/env python
# coding: utf-8

import qrcode
import base64
import os
import io
import zipfile

text = '[Eat.Drink.Sleep.]'
dummy = '[https://goo.gl/xGFW4m]'

def get_count_base64_length(text, min_length):
    b64 = base64.b64encode(text)
    count = 1
    while len(b64) < min_length:
        b64 = base64.b64encode(b64)
        count = count + 1

    return (count, len(b64))

def fizzbuzz_cond(max_n):
    loop_fizzbuzz = [False, False, True,  False, True,
                     True,  False, False, True,  True,
                     False, True,  False, False, True]
    ret_fizzbuzz = []
    n = max_n
    while n >= 0:
        ret_fizzbuzz.extend(loop_fizzbuzz[:])
        n = n - len(loop_fizzbuzz)
    else:
        ret_fizzbuzz = ret_fizzbuzz[:n]

    return ret_fizzbuzz

def qr_encode(text, b64count, qr_count):
    png_images = []
    crypt = text
    for i in range(b64count):
        crypt = base64.b64encode(crypt)

    crypt_len = len(crypt)
    parts_lens = [crypt_len / qr_count for i in range(qr_count)]
    surplus = crypt_len % qr_count
    if surplus != 0:
        for i in range(surplus):
            parts_lens[i] += 1

    n = 0
    for i, m in enumerate(parts_lens):
        part_text = crypt[n:n+m]
        qr = qrcode.QRCode()
        qr.add_data(part_text)
        qr.make(fit=True)
        img = qr.make_image()

        output_bytes = io.BytesIO()
        img.save(output_bytes, 'PNG')
        output_bytes.seek(0)
        png_images.append(output_bytes.read())
        n = n + m

        if (i + 1) % 100 == 0:
            print i + 1 

    return png_images

def shuffle_binimg(imgbin_list1, imgbin_list2, cond_list):
    if len(cond_list) < len(imgbin_list1) + len(imgbin_list2):
        return None

    ret_list = []
    n, m = 0, 0
    for c in cond_list:
        v = None
        if c:
            v = imgbin_list1[n]
            n = n + 1
        else:
            v = imgbin_list2[m]
            m = m + 1
        ret_list.append(v)
    return ret_list

def main():
    zio = io.BytesIO()
    if not os.path.exists('./q3_demo.zip'):
        qr_count = 128
        cond = fizzbuzz_cond(qr_count * 2)
        crypt_count, crypt_len = get_count_base64_length(text, qr_count * 4)

        images = qr_encode(text, crypt_count, cond.count(False))

        dummy_count, dummy_len = get_count_base64_length(dummy, crypt_len)
        dummy_images = qr_encode(dummy, dummy_count, cond.count(True))

        mix_png_images = shuffle_binimg(dummy_images, images, cond)

        zio.seek(0)
        with zipfile.ZipFile(zio, 'w') as _zipfile:
            io_text = io.StringIO()
            io_text.write('! fizzbuzz'.decode('utf-8'))
            _zipfile.writestr('hint.txt', io_text.getvalue().encode('utf-8'))

            for i, image in enumerate(mix_png_images):
                n = i + 1
                _zipfile.writestr('qr/{0:0>4}.png'.format(n), image)

        zio.seek(0)
        with io.open('q3_demo.zip', 'wb') as f:
            f.write(zio.read())
        zio.seek(0)
    else:
        with io.open('q3_demo.zip', 'rb') as f:
            zio.write(f.read())
        zio.seek(0)

    zip_b64 = base64.b64encode(zio.read())
    url_scheme_str = 'application/zip;base64,' + zip_b64


    zip_images = qr_encode(url_scheme_str, 3, 4096)
    for i, image in enumerate(zip_images):
        with io.open('qr/{0:0>4}.png'.format(i + 1), 'wb') as f:
            f.write(image)

if __name__ == '__main__':
    main()

まとめ

反省点としては、「QRコードのreaderライブラリの存在有無調べなかった」点が大きいです。
あのあと自分で他言語で読み込めるか試した所、
Rubyで3個中3個中級のQRコードがところどころ読めないのが出てきました。
C++は準備に手間がかかって断念…。
結果的に言語を限定しちゃったのかなぁと思います。
ライブラリ探しが明暗を分ける問題になったのが如何ともし難い。
もうちょいコードのみに集中出来るものを(次回があるなら)作りたいかなぁ。 (でも、別言語で解けた人がいるならブログ書いて共有して欲しいな!上級解いた人は特にちらっちらっ)

出し物も「夜の宿題」形式ではなく、「きんぎょばち別トラック」形式でもう少し時間をとれたほうが嬉しいのだろうか?
(その場合、今回の上級は作成間に合わなかったけど…)
言語自由でコードに集中できるお題を探しましょう。適度に。

と、ネガティブ方面ばっかだとあれなので良かった点。
出題全般でLevelは結構出し物班で吟味しました。 上級はほぼほぼうちの自由にやりましたが、初級、中級は話し合いで結構詰めたので良かったかな。
今回の事でかなり問題に関係ない所の知識がついた気がします。
某人が「勉強会駆動開発」とかいってましたが、うちは「お題駆動開発」じゃないかなぁと。
投げ銭もそうですし、今回のお題もそうですしなんやかんやで自分の届きそうで届かない部分を頑張るのが好きみたいです。

あとは、byte streamの汎用性の高さに気づけたのが良かった。
今後役に立ちそう。

そんなこんなの「出し物班」の出題編でした。