服部平次絶体絶命の暗号を作成するツール

名探偵コナンの第323~324話「服部平次 絶体絶命」に登場する暗号を自動で作れるサイトを作ってた。期末テストの息抜き程度に作る予定だったのに、いつの間にか春休みも半ばになってた(゜_゜)

hattori.cipher.jp

アニメ内では、服部平次は監禁された状態で提示された暗号を解いた上で、犯人を表す暗号に脳内で作り替えてコナンに送るというすごいことをやってのける。どういう思考回路してるのか全く理解できないんだけど、暗号を作成するプログラムを作ったら何かわかるかもしれないと思ったので、これを作った。

※ 暗号の解き方も書いちゃっているので、思いっきりネタバレです!

暗号の解き方

まず、楠川さん作の暗号を使って解読方法を復習する。

暗号は、以下のように7×7=49個の英数字からできている。前提知識としては、この暗号が7桁の数字を表しているということと、暗号の作成者が博打好きだということがある。

Q52A252
K332Q44
4KQQKJ6
AQJ96J3
355533Q
6246426
2KK53KA

1. アルファベットを数字に直す

暗号内のアルファベットがA, J, Q, Kであることから、これはトランプを表していると考え、それぞれ数字に直す。A→1, J→11, Q→12, K→13。

 12521252
 133321244
413121213116
 1121196113
  35553312
  6246426
2131353131

このようにアルファベットを数字に直す作業を「展開」と呼ぶことにする。また、その逆の操作を「短縮」とする。

展開の結果、暗号は64個の数字となった。

2. 仲間外れの9を除き、その9で残りの個数を割ると、答えの桁数である7になる

1~6の数字は多くあるのに、一つだけ9があることに注目する。仲間外れの9を除くと数字は64-1=63個となり、これを9で割ると答えの桁数である7となる。

125212521
333212444
131212131
161121161
133555331
262464262
131353131

↑9を除いた数字を9個ずつで改行したところ。数字が7行できていることがわかる。

この一行ずつが、答えの一桁ずつを表しているのではないか、と考える。

3. サイコロに当てはめる

1~6の数字があることと、暗号の作成者が博打好きであることから、サイコロを連想する。サイコロは正方形なので、9つの数字の組を一つのサイコロに見立てて、3×3の正方形に並べる。

125
212
521

333
212
444

131
212
131

(略)

4. それぞれの数字をサイコロに置き換える

f:id:YaaMaa:20170315175512p:plain

(4文字目以降は省略)

すると、赤い丸を持つ1だけが浮かび上がるので、3×3の枠全体を一つのサイコロと見ると、3156204(語呂合わせで「サイコロ振れよ」)の数字が浮かび上がる。これが暗号の答え。

※ コナンが作った暗号では、3×3の枠全体をサイコロと見なすのではなく、ただ枠に現れる模様を見る。すると、カタカナで「ソトニサソエ」と浮かび上がり、これが答えとなる。

解き方を知った上で暗号を解くと、

展開 → 9を除いた残りを3×3の枠に流し込む → 1が形成する模様を読み取る

という割とシンプルな方法で解ける。

暗号作成

こういう暗号を作るにあたって、二つの記号を定義しておく。

  • N: 2, 3, 4, 5, 6
  • R: Q, K

暗号では結局1以外の数字は「1でない」ことに意味があり、実際の数字なんて何でもいいので、2~6はNと抽象化しといて後で振り分けた方が楽。同様に、QとKも二桁目が2であるか3であるかの違いしかないので、「1以外は何でもOK」という考えの下ではそれほど違いがないため、まとめてRと置く。

ユーザーからの入力

ユーザーから暗号の答えを入力してもらう。

入力する方式は下の二つを用意しなければならない。

  • 模様: コナン作の暗号のように、3×3のマスの中の模様が表すものがそのまま暗号の答えになる場合

  • 数字: 楠川作の暗号のように、3×3のマス全体を一つのサイコロと見立てて数字を導き出す場合

入力されたら、1になるところをA、1以外になるところをNとしておく。

例えば、数字の入力欄に「810」と入力されたら

AAA ANA AAA  NNN NAN NNN  NNN NNN NNN

となるし(「8」はサイコロにない数字なので、勝手に模様作った)、模様の入力欄に

■□□
■□□
■■□

□■□
□□□
□■□

■□□
■■□
■■□

と入力されたら

ANN ANN AAN  NAN NNN NAN  ANN AAN AAN

となる。

短縮

数字をアルファベットに直す操作。短縮には二種類ある。

  • AA-J短縮: AAは「11」の意味なので、Jと短縮できる。

  • AN-R短縮: ANは「1, 1以外の数字」が並んだものなので、Nを2or3に限定することでRと短縮できる。どうせNは後から2~6のどれかの数字に決まることになるので、ここで限定しちゃってもOK

最低短縮数を設定する

J, Q, Kが揃ってないとトランプだとわかってもらうのは難しいので、それぞれ最低一つずつほしい。なので、

  • AA-J短縮の最低短縮数は1

  • AN-R短縮の最低短縮数は2

とする。これに従えない場合(以下で求める短縮可能数が十分でない場合とか)は妥協する。

短縮可能数を調べる

AA-J短縮とAN-R短縮がそれぞれ何回行えるかを調べる。

  • AA-J短縮: Aが連続して二つあり、且つ一つ目のAがそのAの連続内で奇数個目にあるときに行える

  • AN-R短縮: AとNがこの順で連続していて、且つAがそのAの連続内で奇数個目にあるときに行える

合計短縮数を決める

短縮を一度行うごとに、暗号は一文字ずつ短くなる。

つまり、合計短縮数は、展開後の数字の個数 - 展開前の英数字の個数である。作中に出てきた暗号の場合、82-72=15回。

展開後の数字の個数はユーザーからの入力によって自動で決まるけど、展開前の英数字の個数は特に決まっていない。とはいえ、以下のような制限がある。

  1. n2でなければならない

    展開前の英数字の並びが正方形であるというのには、服部曰く「サイコロを表す」という意味があるので、正方形にできるように自然数の二乗個でなければならない。

  2. n2であるとき、nは奇数でなければならない

    9を中央に配置するというのには、服部曰く「サイコロの1の目を表す」という意味があるので、9を中央に配置できるように一辺の個数は奇数でなければならない。

これらの条件を考慮して、また短縮可能数に収まる範囲内で、(そしてできれば最低短縮数よりは多く、)短縮合計数を決める。

ただし、上記の二つの条件には従えない場合がある。例えば暗号の答えが「0」だったとき、暗号は「NNN NNN NNN」のどこかに9を挿入したものとなる。すると数字が10個となり、短縮可能数も0なので、n2には絶対にできない。こういうときは妥協する。

短縮数を分配する

合計短縮数を、AA-J短縮とAN-R短縮に分配する。楠川さんたちが作った暗号での配分は以下の通り。

J + R J R
楠川作 15 3 12
服部作 15 4 11
コナン作 15 5 10

だいたいJの個数:Rの個数=1:2~4くらいが妥当なのかなと思う。

なので、配分の仕方は以下のようにした。

  • 合計短縮数 < 最低短縮数の合計になってしまうときは、それぞれの最低短縮数に比例するように短縮合計数を分配する

  • 最低短縮数の合計 < 合計短縮数の場合は、最低短縮数を満たせば、あとはなるべく上記の比率に従うようにランダムで分配する

置換/反転

ただ短縮しただけでは、文字の配置はワンパターンになってしまう。例えば、「AAAAAN」という並びは、「JJAN」「AJAR」などいろいろな方法で短縮できるのに、上記で行った短縮だけだと毎回同じパターンになってしまう。それを防ぐために、置換や反転を行う。

  • JNとARの置換: JNは、NをRに限定することでARと置き換えられる。特にRの数が足りないときに積極的に置換する

  • AとJの反転: AとJが隣接しているとき、それらはひっくり返しても大丈夫

具体化

抽象化してあったRとNを具体化する。

Rの具体化

RをQとKに具体化する。楠川さんたちが作った暗号では平等に割り振られているので、それに従う。

R Q K
楠川作 12 6 6
服部作 11 6 5
コナン作 10 5 5

Rが奇数個で余ってしまったら、服部作のに倣ってQに回せばいいかな。

Nの具体化

楠川さんたちの暗号での2~6の出現回数は以下の通り。

  • 楠川作
展開前 展開後
2の出現回数 7 13
3の出現回数 7 13
4の出現回数 5 5
5の出現回数 6 6
6の出現回数 5 5
  • 服部作
展開前 展開後
2の出現回数 5 11
3の出現回数 5 10
4の出現回数 6 6
5の出現回数 4 4
6の出現回数 6 6
  • コナン作
展開前 展開後
2の出現回数 3 8
3の出現回数 3 8
4の出現回数 4 4
5の出現回数 3 3
6の出現回数 4 4

こう見ると、展開後ではなく展開前の時点で大体均一に分配すればいいことがわかる。

これくらいのばらつきを保ちつつ、ほどよく均一に分配する方法を調べてたら、以下の記事がすごい参考になった。

Box-Muller法を用いて正規分布に従う分配を行う - Qiita

この方は、出てきた二数を$readyを使うことで効率的に使ってるけど、めんどくさいし、片方しか使ってない例(書いた方は「大丈夫なのか…」と不安げにしてはるけど、結果のグラフはいい感じに見える)もあるので、まあいっかと思って片方しか使わなかった。

標準偏差はどうするのかということだけど、こういう場合平均が上がるにつれて標準偏差も大きくなっていくのが自然だと思うので、標準偏差を平均で割ってみたらどんな値が出るか見てみた。

平均 標準偏差 標準偏差/平均
楠川さん作の暗号 6 0.89443 0.14907
服部作の暗号 5.2 0.74833 0.14391
コナン作の暗号 3.4 0.4899 0.14409

かなり一定になってる! なので、この平均をとって、標準偏差=平均*0.146にした。

(後から知ったけど、標準偏差/平均は「変動係数」と呼ばれるもので、ちゃんと意味のある数値らしい)

また暗号を解く際に服部が「1から6までは仰山あんのに、9は一個だけしかない」と言っていたことから、9を際立たせるために2~6は複数個、最低2個ずつないといけない。 更に、Nが極端に少ない場合、6を出現させるのを最優先する。「サイコロ」という発想を浮かばせるためには、「6までの数字がある」と認識させるのが大事だから。それに、2や3はRの展開で出てくる可能性もあるから、Nで無理に作る必要性はあんまりない。

なので、2~6までに2個ずつ配分できるほどNが多くない場合、つまりNの個数<(6-2+1)*2のときには、6→5→4→3→2→6→5→…の順で配分していく。

9の挿入

最後に、真ん中に「9」を挿入する。

これで暗号が完成!

できたもの

hattori.cipher.jp

↑のページで公開していますので、よかったら遊んでみてください。結構な頻度で「この暗号には問題があります」って出るので鬱陶しいと思います(*‘▽’)

ミニバードで無料ドメイン見てたら、「cipher.jp」っていうのがあったから、ぴったりな名前やなって思ってすぐ取得した。

サイト内で一番力を入れたのは、暗号解読画面の解説。なんとかアニメ放送された本家の画像をcanvasで再現しようと頑張った。

f:id:YaaMaa:20170315133906p:plain

f:id:YaaMaa:20170315133928p:plain

f:id:YaaMaa:20170315133940p:plain

f:id:YaaMaa:20170315180929p:plain

上が本家、下がcanvasで作ったやつ。よく見るとグラデーションの色合い・角度とか服部のツノ(一枚目の右上から生えてるやつ)のとんがり具合とかいろいろと違う(・_・`)

あと、楠川さんの暗号って3×3の枠に流し込んだときに2~6がすごい対称性のある綺麗な並びになるようにできてんねんかな。これのおかげで解読方法により確信が持てるようになるんだろうけど、私には真似できなかった…。服部とコナンも、(切羽詰まった状況で暗号作ったから当たり前だけど)そんなに対称性は持たせられてないので、まあしゃあないかと思って妥協してしまった。

服部が暗号を組み立てた手順

では、作中で服部がどんな感じであの暗号を組み立てたのか考えてみる。

まず、下3行を血で汚した理由だけど、あれは「3文字分で弁護士のことを表せるから、およそ3文字分に相当する3行分を汚そう」と思ったのか、「血の量的に汚すのは3行が限界だから3行分汚そう」となったのかどっちかはわからない。というか、手を使えない状況でどうやって暗号の紙を血で汚したんだろう。顎から一滴一滴したたらせてたんだったら死ぬほど時間かかるし、楠川さんに手伝ってもらったんかな。めっちゃ大変そう。

いずれにせよ、下3行分を見えなくした時点で、展開前の(7×3=)21文字が隠されたことになる。これは展開後の25文字分に相当する。3×3の枠で3文字を表そうと思ったら27個(=3×3×3)の数字が必要だが、最初の二つ(暗号全体の展開後でいうと後ろから26個目と27個目)を楠川さんのと共通にしとけばOK

そこを共通にするためには、犯人を示唆する文字列の一文字目はANから始まることになる。「Lib」ならその条件を満たしている。

Libを3×3の枠×3で表そうとすると、

(AN)N
ANN
AAN

NAN
NNN
NAN

ANN
AAN
AAN

上のように配置することになる。これを全部繋げると、下の通り。

(AN)NANNAANNANNNNNANANNAANAAN

※ 初めのANは既に固定されているので、()付きで表した。

このうち初めのANを除いた「NANNAANNANNNNNANANNAANAAN(25文字)」を短縮して21文字(7文字×3行)にすればいい。なので、短縮は25-21で4回行う。

血で汚れてないところにもJ, Q, Kはそれぞれトランプを連想するのに十分な回数出てるから、今からの短縮では特に文字の出現回数は考慮しなくて大丈夫。

短縮できるのは、0始まりで数えると、

  • 1-2個目: AN-R

  • 4-5個目: AA-J or 5-6個目: AN-R

  • 8-9個目: AN-R

  • 14-15個目: AN-R

  • 16-17個目: AN-R

  • 19-20個目: AA-J or 20-21個目: AN-R

  • 22-23個目: AA-J or 23-24個目: AN-R

これだけある。このうち、服部は4-5, 8-9, 20-21, 23-24個目を短縮した。

(私の方法でいったら、AANをARにするためにはまずAA-J短縮をしてからJN-AR置換をするけど、あれはプログラム書くときに楽な方を取っただけで、人間だったらAANからそのままAN-R短縮すると思う)

短縮が終わったら、下のようになった。

NANNJNNKNNNNANANNAQAK

最後に、Nに2~6までの適当な数字を分配する。

3A55J64K6243A6A64AQAK

これを上の4行と合わせたら、工藤に送る暗号の完成。

殺されそうな瀬戸際で、殴られまくって頭ガンガンしてるだろうに、血で汚れてない上4行を記憶しながらこんだけ考えられるなんて、服部ってすごい…(゜-゜)


USJでやってるコナンアトラクションの服部がめっちゃかっこいいらしい。何としてでも行かなければ(>_<)