Sim's blog

電子工作はじめてみました

ICFPプログラミングコンテスト2009 その後

2009-07-01 22:27:49 | ICFPプログラミングコンテスト
ICFPプログラミングコンテスト2009の続きになります。

課題1しか解けてないのはあいかわらずです。手作業で解いていたので、シナリオ番号だけ入力すれば自動で解いてくれるようなプログラムを書きました。
それはそれで、おもしろかったのですが、変なことに気づきました。たぶんバグだと思うのですが、残り燃料が少ない方が点数が高いんです。残り燃料を10000から引いてるんでしょうね。なので、適当なタイミングで加速、即逆加速をして、燃料をほとんど0にすると点数が高くなります。加速すると当たり判定のカウントがリセットされるので、終了間際はやってはいけないです。他の課題は点数が出るところまで行っていないので、同じバグがいるかどうか分かりません。他の課題にも同じバグがいるなら、そもそもランキングの意味ないじゃん、とか思いました。一応、主催者にはメールしておきました。

課題2は、時間待ちする以外に、中間軌道に一旦移ることで時間調整ができる可能性があることに気づきました。

ICFPプログラミングコンテスト2009

2009-06-27 15:05:09 | ICFPプログラミングコンテスト
ICFPプログラミングコンテストが始まっています。

今年は、宇宙船を操縦して4つのミッションをクリアする課題です。去年は惑星探査車の操縦でした。
去年は環境(live CD)が配布されて、その環境で動かしていました。今年は仮想マシン上で動かすので、仮想マシンの作成から始めないといけません。
仮想マシンを作るところから始めるのは2006年, 2007年もそうでした。2007年のように仮想マシンを作ること自体が難しくて入り口にすらたどり着けないということはなく、今年の仮想マシンはかなりシンプルです。
仮想マシンのレジスタはPCと比較結果を入れるstatusの2つだけです。アキュムレータもないので、演算結果は全てメモリに格納します。また、PCを操作する命令もないので、ストレートに実行されるだけになります。0番地から開始して最終番地まで実行して、おしまいです。ハーバードアーキテクチャなので、プログラムとデータは別の空間です(さらにはI/O空間も別です)。つまり自己書き換えコードも存在しません。メモリ空間は14ビットなので16kバイトです。

宇宙船の操作は、以下を繰り返します。
(1) プログラムとメモリの初期値を読み込む
(2) 入力ポートにデータを設定する
(3) 仮想マシンを動かす
(4) 出力を読み取る
(5) (2)に戻る

仮想マシンの仕様やミッションについては開催ホームページからpdfをダウンロードできます。宇宙船(仮想マシン上のプログラム自体)は、メールアドレスを登録するとTeam Pageからダウンロードできるようになります。今、見てみたら仕様書がV1.6になっています。

参加者のすることは、宇宙船を制御するプログラムを書くことです。うまく制御すると点数が高く(低く?)なるようで、点数を競います。
主催者に提出するのは、制御プログラムそのものではなく、どのタイミングでどんな入力を与えたかを示すバイナリファイルです。これなら、どんな言語を使っても平気です。去年の実行ファイルの提出よりはスマートです。なんか2007年と2008年のいいところを合わせた感じになっています。宇宙船の仕様というか動作を知るために仮想マシン上のコードを読まないといけないのも2007年と似ています。

というわけで、概要はなんとなく分かったので、仮想マシンのコードでも書いてみようと思います。逆アセンブラも必要そうです。


6/29 追記 結局、コンパイラを書いちゃいました。って、肝心の問題の方はほとんどやっていません。物理というか数学というか、むずかしいです。最初っから、式で考えるのは諦めて、数字を変えながら、グルグル回すプログラムを作ってたりします。課題1の4つのコースはなんとかできました。そもそもブレーキを踏むと、なんで楕円な軌道になるのかさっぱりです。
上で宇宙船と言っているのは、全て衛星(satellite)の間違いです。英語もさっぱりだあ。
一応、全員のランキングが見えるんですが、下の方に載ってます。同じ数字がいっぱい並んでいるのは、たぶん式で解いた人達なんでしょう。これから課題2を考えてみようと思います。課題1は軌道に乗せればおしまいだったけど、課題2は別な衛星の1k以内に近づかないといけません。さてさて、どうすればいいのやら。今日というか、明日の夜中の3時までになんとかなるんでしょうか。

6/30 追記
今日の夜中の3時に無事終了したみたいです。結局、寝ちゃって課題2はしませんでした。今公開されているランキングは終了4時間前のものみたいです。その段階では249位でした。最終結果はICFPという国際会議で発表されます。

今更ながら、wikiのHohmann transfer orbitの記事を見ました。計算式が載っていて、試しに代入してみるとぴったりではありませんが、それっぽい数値が出てきます。うーん、計算式なしでやってたなんて、かなりの時間の無駄でした。出てくるのは最初の加速と2番目の加速と、あとは2番目に加速するまでの時間の3つです。この3つを力任せで探索してました。そのせいか計算式で計算するより、少しだけ点数がよくなっています。これは当たり判定が1kmあるので、当たり判定のあるあたりだけコースに入っていて、その先は知らないみたいなコース選びだったということだと思います。
物理関連だということで、おもいっきりやる気をなくしてましたが、wikiをちゃんと読めば普通に計算できたんですね。しくしく。

上でコンパイラと書きましたが、正確にはCへのトランスレータです。代入文くらいしかないので、ひたすら対応するCのコードに変換しているだけです。

課題2は既に別の軌道にいる人工衛星とランデブーする問題です。同じ軌道に入るのは課題1と同じです。回転速度は半径だけの関数なので、もう一つの人工衛星も同じ速度で回っています。なので、軌道に入っただけだと、速度が同じ同士でいつまでたっても追いつくことはありません。ちゃんとは解いていませんが、最初に時間あわせのための待ちを設けて、必要なだけ待ってから軌道移動すればぴったりの座標にいけるはずだと思います。課題3は相手が円でなく楕円の軌道になっています。これも時間合わせと、さらには2番目の加速で同じ楕円軌道になるようにしてやらないといけないはずです。たぶん楕円の一番底でランデブーします。課題2と違うのは、ランデブーするタイミングも合わせてやらないといけないことだと思います。ってか、そこまで分かっていて、なぜ手が動かないのか不思議です。課題4は問題すら見てません。

課題1の軌道変更の様子です。

グラフはエクセルで書きました。プログラムはx, y座標をprintfするので、ファイルにリダイレクトして、エクセルシートにコピペしています。xとyの間をTABにすると直接別の列になってくれるので、即時にグラフも更新されます。って、原始的ですね。

主催者から、各データがダウンロードできるようになっていますが、パスが間違っていて、ダウンロードできません。URLからbinaries/を削ってやるとダウンロードできるようになります。

とりあえず色々と楽しませていただきました。主催者のみなさまありがとうございます。参加者の皆様ごくろうさまでした。

ICFP 2008の結果が出ています

2008-09-28 14:51:31 | ICFPプログラミングコンテスト
ICFP 2008の顛末の続きです。

ICFP Programming Contest 2008結果が出ています。1位はTeam Smartassで2006年から3連覇です。googleのチームということです。2位は日本人の方です(shinhさんのブログ)。おめでとうございます。
スコアは5回試走したうちの上位3つの合計です。コースは11個あります。各コースに足きりスコアが設定されていて、足きりされると次のコースに進めなくなります。最後の11コースに進んだのは14チームです(11コースの結果)。私のは1コースで足きりされています。

1コースから8コースまではマップが公開されています(9~11の3つはまだ)。どんなコースだったか見てみました。

コース1


コース2


コース3


コース4


コース5


コース6


コース7


コース8


まだ公開されていませんが最後の2つのコースが迷路みたいなコースだったんじゃないかと思います。

ICFP 2008の顛末

2008-07-16 22:16:05 | ICFPプログラミングコンテスト
結局、ちょっとだけやって投げ出しちゃいました。

やったこと
- vmwareのubuntuを使おうとした
- _が入力できないことに気づきました → ぐぐって修正方法(xorg.confの修正)を見つけて修正
- shift + spaceで漢字変換モードにならないようにしました。
- gccでコンパイルしようとするとstdio.hが見つからないと怒られました。ぐぐって、追加のライブラリ(?)をインストール
- ぐぐって見つけたsocketの例題を入力してみました。宣言が見つからないと怒られまくりです。どのヘッダファイルにはいっているのか分からないのでgrepを使って片端からファイル検索しました。TCP_NODELAYがなかなか見つかりませんでした。とりあえず、サーバーにつなぐところだけできました。
- サーバーでグラフィック表示をする方法がFAQに載っていたので動かしてみました。しばらくすると、ubuntuがホストOSのXP毎おなくなりになります。3回くらい落ちたところで、こわくなってやめてしまいました。

というわけで、linuxをちょこっといじっておしまいという感じでした。何バイトくるか分からないのにrecvでバイト数を指定するのはどうしてか?とか謎はいっぱい残りました。

ニコニコ動画に今回の課題の動画が載っていました。よけながら、中央のゴールに行く制御プログラムを書くという課題でした。

ICFP Programming Contest 2008 やっつけ回避


グラフィックで見れるのがおもしろかったです。たぶん、ネットワークプログラミングやlinuxが壁になっていない人達にとっては時間があまったのではないかと思います。当初作ろうと思っていたのは、できる操作が少ししかないので、将棋のように何手か先を先読みするプログラムを作ろうと思っていました(思っただけ)。

結果は9月のICFP(学会の方)で発表されるみたいです。審査がどんなコースで計測するか発表されていません。

ICFP 2008プログラミングコンテストがはじまっています

2008-07-12 14:13:05 | ICFPプログラミングコンテスト
たぶん夜中の4時にはじまっているみたいです。昼頃起きだしてきたので既に8時間は経過しています。

ICFP programming contest 2008のページ
課題のページ
FAQのページ
テスト用サーバーのダウンロードページ

ささっと眺めてみました。英語読解力があやしいので色々勘違いしているかもしれません。

今年の課題は、火星の無人探査車を遠隔制御して基地に帰り着くための制御プログラムを書くことです。速く帰りつけたプログラムが優勝です。
遠隔制御なので通信に遅れがあります。センサーからの情報が定期的に送られてきて、状況を判断して無人探査車にコマンドを送信します(ここでも遅れがある?)。基地は原点にあって、自分のx, y座標がセンサー情報から分かります。まっすぐ向かえばいいわけではなくて、色々な障害物を避けながら進まなければいけません。障害物は岩、クレーター、火星人(!)があります。フィールドは長方形ですが、端を越えようとするとバウンドして向きが変わるみたいです。岩もバウンドするみたいです。クレーターは落ちると爆発、火星人もつかまると死亡です。岩とクレーターは動きませんが火星人は動き回っています。無人探査車も含めて全ての物体は円形です。岩は視界をさえぎるので、岩の向こうに何があるか分からないみたいです(まわりこめば分かる?)。

課題のページに載っていたフィールドの様子です。

左上に無人探査車がいて、茶色(c)がクレーター、灰色(b)が岩、赤が火星人です。中央の緑がゴールです。

フィールドの大きさやセンサーの性能や初期位置は、一番最初に送られてきて、1秒後にスタートです。こちらから送れるコマンドはアクセル、ブレーキ、ハンドル左右等です。
プロトコルの詳細は課題のページに載っています。

センサーで見える範囲は楕円です。無人探査車は楕円の焦点にいます。


プログラムはlinuxで動くようにします(windows不可)。通信はTCP/IPです。受信や送信はsocketプログラミングをする必要があります。プログラムの動作環境がLIVE CDという形式で配布されています。KNOPPIXというlinuxのCDで、CDブートできるようになっています。この環境で動作するプログラムでなければいけません。テスト用のサーバーの配布も行われています。
コンテストへの応募はプログラムを含む実行環境のディレクトリをtgzに固めて送ると開催者側で何回か実行して実行時間を測定するみたいです。

私はというと、linuxって何?ネットワークプログラミングって何?みたいな状況なので、まだ何もしてません(笑)。とりあえずLIVE CDはダウンロードしてみました。マイクロマウスや知ロボをやっている方が得意そうな課題だと思いました。

ICFP 2008があるみたいです

2008-06-30 00:19:37 | ICFPプログラミングコンテスト
ICFP 2008コンテストが開催されるようです(リンク)。現地時間の7/11正午から7/14です。今年はどんな問題なんでしょうか。去年はコンテストが終わってから遊ばせてもらいました。問題が分かるだけで3日なんてすぐ過ぎそうです。今年は挑戦してみたいかな。

7/3 追記 日本時間だと7/12の午前4時開始みたいです。

ICFP2007 (28) 主催者の種明かし

2007-10-10 23:10:41 | ICFPプログラミングコンテスト
ICFP2007 (27) 画像修復(5) 完成の続きです

主催者の種明かしレポートが公表されているそうです(ここ)。

このテクニカルレポートに書かれている謎はほとんど解明できたと思います。優勝チームはgoogleチームで、なんと二連覇だそうです。

まだ残っているのは以下です。
- sunの正しい修復方法。私はダンプリストと逆アセンブルリストを見ながら手で直しました。
- bioMul_adaptationの修復。草の乱数の初期値はbioMul_adaptationのあたりを修復すればいいという方針はほとんど合っていたみたいですが、動かすところまではできていません。
- ドキュメントには、audio hintという話が書いてありますが見つけていません。

ドキュメントには3903バイトという信じられないような短いprefixが載っています。私の現在のprefixは6312バイトなので半分くらいです。

現在のprefixは、compressorのパクリで、自分の後ろにバックアップのコピーがいて、その後に次の書き換え場所までのオフセットとデータ長さ、データの三つ組みがひたすら並んでいるというコードになっています。(?P)(?P)(?P)とすると自分の後ろにある数字を3つ取り出せます。まだ実装できていませんが、考えているのはquoteを使うことでデータや数値を圧縮することと、MSBが必ず1なのは冗長なのでMSB抜きにする(浮動小数点と同じ考え方)ことの2つくらいです。あとはパッチを当てる修正箇所の最適化くらいでしょうか。

別のアイデアは登場するデータはICFPの4通りしかないので、例えばIだけについて圧縮して次にCについて書き換え箇所を圧縮してと4回繰り返す方法です。データを載せなくていいぶん縮みそうな気もします。数値のエンコード方法を工夫すればなんとかなるかもしれません。4種類の文字があるので4進にするのもあるかもしれません。

DNA中の部分列をコピーすることで縮むかと思いましたが、patchは結構細切れなのであまりうまくいっていません。

主催者が使ったツールを作るのに使われたプログラミング言語が書かれていたりしてなかなか楽しめるドキュメントでした。

ICFP2007 (27) 画像修復(5) 完成

2007-10-07 02:01:40 | ICFPプログラミングコンテスト
ICFP2007 (26) 画像修復(4)の続きです

ついに完成しました。最後に残っていたのはふきだしの中の文字μでした。文字の形はcharInfo_Tempus-Bold-Huge_Mに格納されていますが、このデータは暗号化されていました。
暗号化の鍵はstickyの画像に書かれているno1@Ax3でした。

試しにprefixを作ってみました。長さは9565バイトです。うまく作ればもっと短くなりそうですが、とりあえずはこんな感じです。大きそうなのがballoonの座標データが2kくらい、cloudの修復も2kくらいです。prefixは1つの代入文で、全てパッチを当てています。サブルーチンコールとかはscenarioの中に埋め込まれているので、これもパッチで作っています。
cow-tailへのパッチは暗号化されたまま行っています。RC4は乱数をxorするだけなので、原文と乱数が分かっていれば暗号化されたままでも修正できます。
cloudを反転するコードは難しそうなので、差分だけをパッチあてしています。

ふーっ、長かったー。でも、かなり楽しませてもらいました。

完成した画像です。prefix 9565 bytes risk 9565


ICFP2007 (26) 画像修復(4)

2007-10-02 00:17:52 | ICFPプログラミングコンテスト
ICFP2007 (25) Steganographyの続きです。

草の乱数の初期値が分かりました。
地面の草を描くdrawGrassPatchは第一パラメータの59文字の文字列を乱数seedとして受け取ります。drawGrassPatchの一番最初の処理は文字配列seedとint24配列biomorphPerturbを要素毎に加えています。biomorphPerturbの初期値は0です。biomorphPerturbを書き換えると草が生える場所が変わります。通常のコードでbiomorphPerturbを変更するコードはpayloadBioMorphという関数にしかありません。payloadBioMorphは2つのパラメータindexとvalueをとって(名前は適当に決めました)、biomorphPerturb[index] = valueという処理を行います。
payloadBioMorphはpayloadBioMorph_adaptationから呼ばれています。そしてpayloadBiomorphはmainで一番最初に呼ばれているadaptationであるbiomorph_adaptationで呼ばれています。
さて、このbiomorph_adaptationですが壊れています。少なくともbiomorph_adaptationを修復する必要があります。関連して中で呼ばれているbioMul_adaptationも修復する必要があります。このあたりの話はhelp-initial-condに書かれています。似た記法でbioMulを書いてみると次のようになるはずです。
bioMul Zero     y = Zero
bioMul (Succ x) y = bioAdd (bioMul x y) y

C風に書くと次のような再帰関数になると思います。
int bioMul(int x, int y)
{
    if(x == 0) return 0;
    else bioMul(x - 1, y) + y;
}

今のところ、biomorph_adaptationの完全な修復はできていません。数値の形式が何種類かあるみたいで、どのadaptationにどの形式を渡せばいいのかがよく分かっていません。少なくとも、mkほにゃの形式、bioほにゃの形式、intBoxの形式があります。
biomorph_adaptationの中で一番最初に呼ばれているのはenableBioMorph_adaptationです。中身はfalseになっていますが、trueに変える必要があります。このadaptationはtrueの時にパラメータを逐次実行する機能があるようです。トレースすると実行の様子が変わります。初期値はfalseになっています。enableBioMorph_adaptationのパラメータが上にでてきたpayloadBioMorph_adaptationです。3回呼ばれています。雰囲気的に以下のようなパラメータで呼び出されているっぽいコードになっています。
payloadBioMorph_adaptation 1 * 3  -4 * 4
payloadBioMorph_adaptation 3 * 3   1 * 7
payloadBioMorph_adaptation 7 * 3   2 * 2

このパラメータがpayloadBioMorphに渡されるのだとすると、biomorphPerturbの3番目に-16、9番目に7、21番目に4が書かれるはずです。試しに書いてみたところビンゴで、草が正しい位置に表示されました。
どうせdrawGrassPatchのseedに足されるのでseedを足された値に書き換えてやってもうまくいきます。書き換えた初期値です。

El gasto ziempre se vi mas verde del otro lado de la cerca.

もしかするとbioMul_adaptationの修復だけで十分なのかもしれません。問題の切り分けができていないというか、biomorph_adaptationの他の部分も壊れているのかどうか、よく分かりません。

残っているballoon(ふきだし)ですが、2つのことが分かりました。

1つ目です。グラデーションを描くdrawGradientCornerNWの位置とサイズが分かりました。balloonを取り囲む最小の長方形にすれば、うまくいきました。
左上の座標が(198, 324)でパラメータが(101, 169)です。実際は102x170なのですが、drawGradientCornerNWに渡すときは1ずつ小さい値にしないといけないようです。

2つ目です。分かったというか、無理やり作りました。ターゲット画像からballoonの枠のドットの座標を全部拾い出して、線画で描くときの移動量をプログラムで探索してpolygonのパラメータを作りました。探索プログラムはgreedyなので、最適とは限りませんが、全ての開始地点について探索を行った中で最も線分の数が少なくなるものです。
探索アルゴリズムは基準点から別の点まで線を引いたときに途中の点を全て通る線の中で一番遠くまで引けたものを採用して、基準点を更新していきます。とりあえず一番遠くまで引けなくても、先まで考えると遠くまで引けるかもしれないといった探索は一切行っていません(というのがgreedyな所以ですね)。どうせ塗りつぶすので、途中の内側に点を作ると、もしかすると短縮できるかもしれませんが、考慮していません。
balloonのpolygonデータ
72,
55, 59,
 -7, -1,  -6, 2,   -4,  5,  -2, 2,    0,12,   5, 8,   2, 6,   5, 6,  -3, 5,  -2,  1,
 -4,  7,  -2, 1,  -16, 17,  -4, 3,  -14,12,   0, 5,   1, 2,   2, 5,   5, 5,   5,  1,
  2,  1,   4, 0,    0,  1,  -2, 1,   -1, 3,  -3,-2,  -4,-1,  -4,-3,  -7,-7,  -1, -3,
  0, -9,   7,-6,   36,-36,   1,-3,   -5,-8,  -4,-5,  -1,-3,   0,-2,  -3,-3,  -1, -3,
  0,-10,   7,-8,    1,  0,   5,-5,    1, 0,   1,-1,  -2,-5,  -2,-6,  -1,-9,   2,-11,
  3, -6,   3,-4,    5, -5,   8,-3,    8,-2,   8, 3,   2, 0,   5, 3,   6, 4,   5,  4,
  0,  1,   4, 5,    2,  6,  -2,13,   -4, 5,  -1, 3,  -4, 4,  -6, 3,  -4, 3,  -11  2,
-14,  0,
0, 0,

というわけで最新の修復画像です。残りはμだけです。ターゲット画像とは548ピクセル異なっています。


μですが、たぶんfontTable_Tempus-Bold-HugeというフォントのMです。ただ、この文字を描こうとするとDNAが途中で止まってしまいます。壊れているみたいです。charInfo_Tempus-Bold-Huge_Mがμの描画情報みたいです。
最後の最後まで楽しませてくれます。

ICFP2007 (25) Steganography

2007-09-30 02:13:19 | ICFPプログラミングコンテスト
ICFP2007 (24) 画像修復(3)の続きです。

修復ガイドのpage 3にはsteganographyのことが書かれていました。steganographyは、画像に秘密を埋め込む技術ということのようです。暗号と違うのは鍵に相当するデータがないので隠し方自体が鍵になっていることです。


二つの絵のうち左側が右側に隠されていると書かれています。左側の絵は右側の絵のLSBとして埋め込まれていました。

各RGB値についてLSBが1のとき0xff、0のとき0x00に変換した画像です。


なんか目がチカチカしますが、埋め込まれているのが分かると思います。

他にもないか探してみました。修復ガイドのpage 112です。


同じようにLSBだけ取り出してみました。


ETのいた辺りに何か文字があるのが見えます。拡大してみます。


cow-tailの暗号鍵の9546がこんなところに隠されていました。
ページ番号の112ですが、海外では緊急通報用の電話番号だそうです。日本の110番みたいなものでしょうか。

LSBではありませんが、黄色の文字を読んでいくとprefixになっているものもあります。過去10年間のICFPの紹介です。

[contest-1998] IIP


[contset-1999] IFFCPICPF


[contest-2000] FIICPIIC


[contest-2001] IPPPI


[contest-2002] CIIC


つなげるとhillsEnabled(0x03346a)をFからPに変えるprefixになっています。
IIPIFFCPICPFFIICPIICIPPPICIIC
(?IFPFCC)F; #0P;

prefixが埋め込まれているのは2003までですが、この紹介自体は2007年まであります。ついでなので載せておきます。
[contest-2003]


[contest-2004]


[contest-2005]


[contest-2006]


[contest-2007]


最多優勝はOCaml(1999, 2000, 2002)とHaskell(2001, 2004, 2005)の3回です。さすがにFunctional Programmingの学会ということでしょうか。

ICFP2007 (24) 画像修復(3)

2007-09-27 00:21:34 | ICFPプログラミングコンテスト
ICFP2007 (23) 画像修復(2)の続きです。

最新の画像です。


差分です。3594ピクセル異なっています。


(17) balloon ふきだし

- 修復方法が不明なのでとりあえずskip

(18) 文字列

- 0x4d6606にgoto 0x4d60fbを書く
- 0x4d6316から"s morphed!xff"を書く

文字列を書くのに微妙にメモリが足りません。直前にドイツ風の"Endo hat gemorpht"を表示するコードがあるので、色をセットし終わったら、そちらに飛ばしてやります。

ここまでで、scenarioに沿った修正はおわりです。足りない部分を追加します。どこにどう追加するとprefixが短くなるのかはよく分かっていません

コードを追加するのに使える隙間は以下です。
- sky-day-bodies
- grassの後からflowerbedまで
- chick (ここまでがwindmillより前)
- lambda-id (ここまでがwhaleより前)
- crater
- 文字列からreturnまで

追加するものは以下です。
- sun(太陽)
- clouds(雲) windmillより前
- chick(ひよこ) windmillより後
- 鯨の噴水 whaleより前
- 鯨のcup whaleより後
- balloon(ふきだし)

(19) sunの修復

- [0x206dce + 855] = I
- [0x207192 + 15] = I
- [0x207192 + 47] = C
- [0x207269 + 87] = I
- [0x207269 + 95] = C
- [0x207269 + 176] = C
- [0x207336 + 7] = I
- [0x207336 + 81] = I
- [0x207336 + 104] = C
- [0x2073cc + 49] = C
- 太陽の座標を(480, 20)にする
- 太陽の色はcolorSoftYellow()
- [0x641878 + 48] = 530 (spirograph5 x)
- [0x6418c4 + 48] = 70 (spirograph5 y)
- [0x641a0a + 48] = 1 (spirograph5 magnify)
- [0x641f81 + 48] = 530 (spirograph6 x)
- [0x641fcd + 48] = 70 (spirograph6 y)
- [0x642113 + 48] = 1 (spirograph6 magnify)
- [0x642694 + 48] = 530 (spirograph7 x)
- [0x6426e0 + 48, 24, 70] (spirograph7 y)
- [0x642826 + 48, 24, 1] (spirograph6 magnify)
- spirograph描画後、適当なreturnへjumpする

前半はsunの修復です。これはダンプリストを見ながら手動でおかしな所を直してみました。修正しては逆アセンブルしなおしを繰り返しました。10箇所修正したところでcheckIntegrityもpassになりました。
後半はspirographのパラメータ修正です。これは修復ガイドのpage 180878の中央のspirographです。太陽の中央にはこのspirographを縮小したものが描かれています。page 180878はmainの一部に埋め込まれています。
太陽の色はcolorSoftYellow()です。この色はflowerbedで使われている色と一緒です。sun()ではYellowなので色を修正する必要があります。sunの直前はimpDoc1なので壊しても構わないので、ここが利用できます。

sun自体はsky-day-bodiesの中で呼ばれています。sky-day-bodiesはおおまかにこんな感じになっています。
void sky-day-bodies()
{
    if(weather == 2){
        if(weather == 2) lightningBolt();
        clouds();
    }
    if(weather == 3) sun();
}

一番最初のifを3と比較することにすればcloudsとsunが呼ばれることになります。weatherの初期値は0なのでweather = 3にするとよいことになります。

(20) clouds

- duolcの逆順をcloudにコピーする
- cloud先頭のcloudy = PをFにする
- [0x5c9164] = 25 (雲1 y座標)
- [0x5c963d] = 10 (雲2 サイズ)
- [0x5c94fa] = 55 (雲2 y座標)
- [0x5c99dd] = 20 (雲2 サイズ)
- [0x5c989a] = 30 (雲3 y座標)

cloudとduolcを連結すると回文になっています。cloudは壊れているのですが、duolcから復元できます。回文に関するヒントはpage 1230321です(修復したpage 1230321)。ページ番号も回文になっています。
cloudのパラメータは雲の大きさです。デフォルトは15になっています。

(21) chick (ひよこ)

- 座標を(171, 410)にする

windmillより後に描画しないといけません。パラメータはcolorDuckYellowとcolorDuckOrangeです。scenarioの中ではchick呼び出しの前後に隙間はないのですが、サブルーチンコールでスタックに戻り番地を積む所を飛ばしたい番地に修正することで呼び出し順序を変えることができます。どのように直すのが最適かはまだ分かっていません。

(22) 噴水

- 座標を(430, 200)にする
- compressorのtableの7番目をturnClockwiseにする([0x29bdda + 26] = FFFFF)
- compressorのtableの9番目をturnCounterClockwiseにする([0x29be1a + 26] = CCCCC)
- 描画しおわったら適当なreturnへjumpする

噴水はprintGeneTableのpage 15以降に隠されていました(右下)。このままでは上下さかさまなのでcompressorのturnClockwiseとturnCounterClockwiseのRNAを入れ替えてやります。
whaleより前に描画する必要があります。

(23) cup (暫定)

- waterの座標を(420, 265)にする
- water(), addbmpのRNA, 完全にopaqueなufo-cup(), clip, 通常のufo-cup()の順に呼び出す
- ufo-cupのpolygonはx座標とy座標を両方とも符号を反転させる
- fillのためのmoveToも座標の符号を反転させる
- ufo-cupの座標は(477, 315)

ターゲットの鯨はcupの中の水に浮いている絵になっています。waterをufo-cupの逆さまでclipした後、半透明なufo-cupを上書きすることで絵が完成します。ufo-cupはx方向にもy方向にも反転しています。一応x座標とy座標を両方とも符号反転することで描画しています。時計回りに2回方向を変えることでも実現できるはずですが、まだ試していません。DNAの中では、現在の描画位置をいくつRNAを出力したかを元に追跡しています。DNAの知らない所で向きを変えたり座標を変えたりすると、追跡している座標と実際の座標が矛盾して、それ以降の描画がずれてしまいます。これはとても危険なので安全策として座標反転をしています。後でダミーのmoveをすることで座標のつじつまを合わせることも可能なはずなので、RNAを直接挿入した方がprefixが短くなるはずです。
ufoの中にはufo-cupと同じpolygonが2つも含まれています。それもwaterを呼び出した直後です。ufoの中身をいじくってやることで今回の絵がうまくつくれそうです。
waterは通常は描画されませんがweaterが1または2のときにだけ描画されるようになっています。waterの存在はweatherをいじくって遊んでいるときに気づきました。というかufoの中に隠されていたということになります。

weather = 2にした画像です。雨や雷、ufo-cupに波模様が見えます。


(24) balloon (ふきだし)

balloonの修正方法は分かっていません。少なくとも単一の白ではなくグラデーションがかかっています。グラデーションはdrawGradientCornerNWを使って描かれています。drawGradientCornerNW自体は四角を書くだけなのでふきだしの形でclipしてやる必要があります。グラデーションのパラメータwとhと描画位置のx, yが未知です。またclip用のふきだしの形も分かりません。力技でpolygonデータを作るしかないのかもしれません。もしかするとどこかにヒントになる図形が隠されているかもしれません。
μの描画方法も分かりません。λはフォントを変えてLを書いています。他の文字に変えると表示されるのかもしれません。
もう残っているのはballoonだけです。

以上で修正箇所のまとめをおわります。宿題として残っているのは以下です。
- 草を描く乱数の修正値biomorphPerturbを決めるbiomorph_adaptationの修正
- 座標を符号反転させないufo-cupの描画方法とufoの修正方法
- balloonのグラデーションパラメータと形状
- 追加描画部分はどこに置くのがよいか考える
- prefixの作成と圧縮(というかアセンブラ作らなきゃ)
- ヒントの探索。例えばcow-tailの暗号化鍵9546

その他に思いつくことはサブルーチンコールの戻り番地の修正でfar jumpを作ることができるので、これを利用できないか考えることです。

ずいぶん長いこと、しつこくやっていましたが、ようやっと終わりが見えてきたという感じです。10月になるとICFPの学会が開催されて上位入賞者の発表があります。もしかするとネタバレもあるかもしれません。どうせなら、その前に片付けておきたいところです。

ICFP2007 (23) 画像修復(2)

2007-09-25 03:15:15 | ICFPプログラミングコンテスト
ICFP2007 (22) 画像修復(1)の続きです。

(13) river 左下の川

- 0x71c897をmkGoldfishR_adaptationにする (a)
- 0x71c9ffをmkGoldfishL_adaptationにする (b)
- 0x71cb67をmkGoldfishR_adaptationにする (c)
- 0x71cc3fを18にする (d)
- 0x71cd17をmkGoldfishL_adaptationにする (e)
- 0x71cec7をmkGoldfishL_adaptationにする (f)
- 0x71cf0fをmkGoldfishR_adaptationにする (g)

川を修正する必要はありませんが川の中の魚を修正します。魚はdrawGoldenFish_adaptationで描かれています。この中のgoldenFish_adaptationを修正することになります。adaptationについては修復ガイドのpage 9に書かれています。各adaptationについてはFuunDocに書かれています。

goldenFish_adaptationを逆アセンブルした結果です。段付けは手動です。パラメータで一段深くしています。最初の方はmkBeforeAbove(mkEmp(0x14, 0x14), mkBeforeAbove(...))のように解釈できます。

goldenFish_adaptation 71c66f 0008d0

71c66f 0008a0
    71c687 activateAdaptationTree
    71c69f mkBeforeAbove_adaptation
    71c6b7 0000c0
        71c6cf activateAdaptationTree
        71c6e7 mkEmp_adaptation
        71c6ff 000030
            71c717 intBox
            71c72f 000014
        71c747 000030
            71c75f intBox
            71c777 000014
    71c78f 000780
        71c7a7 activateAdaptationTree
        71c7bf mkBeforeAbove_adaptation
        71c7d7 000390
            71c7ef activateAdaptationTree
            71c807 mkAbove_adaptation
            71c81f 0001e0
                71c837 activateAdaptationTree
                71c84f mkBeforeAbove_adaptation
                71c867 000030
                    71c87f activateAdaptationTree
                    71c897 mkGoldfishL_adaptation ← (a) mkGoldfishR_adaptation
                71c8af 000150
                    71c8c7 activateAdaptationTree
                    71c8df mkBeforeAbove_adaptation
                    71c8f7 0000c0
                        71c90f activateAdaptationTree
                        71c927 mkEmp_adaptation
                        71c93f 000030
                            71c957 intBox
                            71c96f 7ffffe
                        71c987 000030
                            71c99f intBox
                            71c9b7 7fffff
                    71c9cf 000030
                        71c9e7 activateAdaptationTree
                        71c9ff mkGoldfishR_adaptation ← (b) mkGoldfishL_adaptation
            71ca17 000150
                71ca2f activateAdaptationTree
                71ca47 mkBeforeAbove_adaptation
                71ca5f 0000c0
                    71ca77 activateAdaptationTree
                    71ca8f mkEmp_adaptation
                    71caa7 000030
                        71cabf intBox
                        71cad7 00002d
                    71caef 000030
                        71cb07 intBox
                        71cb1f 000018
                71cb37 000030
                    71cb4f activateAdaptationTree
                    71cb67 mkGoldfishL_adaptation ← (c) mkGoldfishR_adaptation
        71cb7f 000390
            71cb97 activateAdaptationTree
            71cbaf mkBeforeAbove_adaptation
            71cbc7 0000c0
                71cbdf activateAdaptationTree
                71cbf7 mkEmp_adaptation
                71cc0f 000030
                    71cc27 intBox
                    71cc3f 000000 ← (d) 0x12
                71cc57 000030
                    71cc6f intBox
                    71cc87 000018
            71cc9f 000270
                71ccb7 activateAdaptationTree
                71cccf mkAbove_adaptation
                71cce7 000030
                    71ccff activateAdaptationTree
                    71cd17 mkGoldfishR_adaptation ← (e) mkGoldfishL_adaptation
                71cd2f 0001e0
                    71cd47 activateAdaptationTree
                    71cd5f mkBeforeAbove_adaptation
                    71cd77 0000c0
                        71cd8f activateAdaptationTree
                        71cda7 mkEmp_adaptation
                        71cdbf 000030
                            71cdd7 intBox
                            71cdef 000019
                        71ce07 000030
                            71ce1f intBox
                            71ce37 00001d
                    71ce4f 0000c0
                        71ce67 activateAdaptationTree
                        71ce7f threeFish_adaptation
                        71ce97 000030
                            71ceaf activateAdaptationTree
                            71cec7 mkGoldfishR_adaptation ← (f) mkGoldfishL_adaptation
                        71cedf 000030
                            71cef7 activateAdaptationTree
                            71cf0f emptyBox_adaptation ← (g) mkGoldfishR_adaptation
71cf27 00c662

(14) whale 鯨

- if文の条件を反転する (0x266585にIPPを書く)
- 座標を(410, 200)にする

噴水はwhaleより前に描く必要があります。またcupはwhaleより後に描く必要があります。噴水とcupについては後で記します。
whaleのパラメータは2つのboolです。1番目が表情でPで泣き顔、Fで笑顔です。2番目は使用されていません。if文の条件を反転することで笑顔にしています。ifを書き換えるかわりに呼び出し時の第1パラメータを書き換える手も考えられます。

(15) crater

- 不要なのでskipする

(16) bmu endoさん

- enableBiomorphをPにする (enableBiomorphはGZ+0x033963)
- weather判定と変なRNAをskip (0x0dafadにgoto 0x0db0fbを書く)
- 変なrnaとendocowをskip (0x0df476にgoto 0x0df78cを書く)
- cow-spot-middleの誤り訂正 (correctErrors(0x0d985b, 0x000b34, 0x0da3a7))
- cow-tailの復号 (crypt("9546", 0x4aa77d, 0x00145c))
- cow-tailの色修正 addOpaqueを7個、addTransparentを3個追加
- cow-tailのintegrity checkをskip

enableBiomorphはPにしなくても、ifをskipするだけでもいいです。次でweather判定をskipしているので統合した方がprefixが短くなります(0x0daee5にgoto 0x0db0fbを書く)。
cow-tailは復号しただけだと不透明です。0.3だけ透明にします。cow-tailの最初でbucketをemptyにするRNAコードがあるので、これをつぶしてやります。そうするとintegrity checkが通らなくなるので、integrity checkもskipします。今はintegrity checkの所に透明化用のRNA出力を上書きしています。integrity checkはそのままでcow-tailの途中に飛び込む手もありますが、透明化コードを置く場所に困ります。既存のコードで再利用できそうな部分を探すとよいかもしれません。このあたりはもうちょっと考えた方がよさそうです。

暗号化については修復ガイドのpage 4405829に書かれています(このページの復号結果はここです)。cow-tailの暗号鍵9546は力技で発見しました。どこかにヒントがありそうですが見つかっていません。完全数というわけでもなく謎です。もしかすると特徴のない最小の数かもしれません(笑)。
誤り訂正符号については修復ガイドのpage 84に書かれています(このページは暗号化されていて鍵は42でした)。

bmuはおおまかに以下のような流れになっています。
void bmu(void)
{
    if(enableBioMorph == P){
        if(weather == 1 || weather == 2){
            cowを描く
            endocow();
        } else if(ducksShown == P){
            superDuck();
        } else if(ocamlrules == P){
            ocaml();
        } else {
            mlephant();
        }
    } else {
        endo();
    }
}

各変数の初期値は
enableBioMorph(0x033963) = F
weather(0x03394b) = 0
ducksShown(0x033964) = F
ocamlrules(0x0c9228) = P
です。ducksShownはscenarioの中でmotherDuckWithChicksを呼び出す直前でPが代入されています。値を変えると色々な画像が見れます。

endocowにしたときの画像です。


superDuckです。


ocamlです。


mlephantです。


endoです。


長くなってきたので続きます。

ICFP2007 (22) 画像修復(1)

2007-09-24 16:13:44 | ICFPプログラミングコンテスト
ずいぶん間があいてしまいましたが、ICFP2007 (21) 暗号解読(3)の続きです。

ICFP 2007のコンテストの課題は、与えられた画像を修復するパッチ(prefix)を開発することでした。最低でもDNAを解釈してRNAを作るプログラムと、RNAから画像に変換するプログラムを作る必要があります。色々なヒントがDNAの中に隠されていました。

まだ残されているとは思いますが、見つけるのがつらくなってきたので、画像修復をはじめました。今回は現状報告です。

現状の画像です。ターゲットの画像との差分は5799pixelです。prefixは作っていないので長さはわかりません。


ターゲット画像との差分です。


prefixを作るのに主に以下のようなDNAを使います。
- 代入の変更 (!offset)!skip; #0 value;というコードはoffset番地の内容をvalueに書き換える代入文です。skipはvalueのサイズです。このvalueを書き換えると代入する値を書き換えることになります。
- short jump (!skip); ;というコードはプログラムカウンタをskipバイト進めます。
- far jump !gz(!offset(!size))というDNAは他の関数へのjumpになります。gzはGreen Zoneまでのバイト数、offsetは飛び先のGZからのオフセット、sizeは飛び先のサイズ(Red ZoneにロードされたときのGreen Zoneまでのバイト数)です。

mainの中で呼ばれているscenarioという関数が画像表示を行うメイン処理になります。このscenarioの流れに沿って修復箇所を挙げていきます。

(1) sky 空

- night-or-dayをFにする (night_or_dayはGZ+0x50f)。

Pだと夜空になります。night-or-dayをPにするprefixが修復ガイドのpage 1(help_intro)に載っていました。

(9/25 追記 skyから呼び出されるsky-day-bodiesではweatherの値によってcloudsやsunを呼び出すようです。うまくするとprefixの量を削減できそうです。)

(2) surfaceTransform 山

- hillsEnabledをPにする (hillsEnabledはGZ+0x3346a)。
- surfaceTransform()先頭にあるvirusコードをskipします(short jumpを書き込む)。
- 山1のy座標 242 → 218
- 山1のparabolaの第1パラメータ -28 → -21
- 山1のparabolaの第2パラメータ 3348 → 1848
- 山2のy座標 232 → 235
- 山2のparabolaの第1パラメータ -21 → -28
- 山2のparabolaの第2パラメータ 1848 → 3348
- 山3のsineの第2パラメータ 65 → 60

山パラメータの探索には苦労しました。山自体はdrawFunctionBaseという関数で描画しています。drawFunctionBaseのパラメータは関数へのコールバックです。関数は一つ前のy座標との差分を返します。つまり微分されています。山1と山2のコールバック関数はf(x) = a0*x+b0 + sine(a*x+b)*cです。山3はf(x) = sine(a*x+b)*cだけです。実際に描かれるのはf(x)を積分した関数です。関数の仕様はImpDocに書かれています。結果の値は固定小数点になっていて下位12bitが小数部になっています。
山3のパラメータは試行錯誤だけですく見つかりました。山1と山2のパラメータは全然見つかりませんでした。色々試行錯誤しましたが、結局画像を拡大して関数の値を手動で取り出して、結果が一致するようなパラメータを探索するプログラムを書きました。結果としては山1と山2のパラメータが入れ替わっただけでした。探索自体は山1についてしか行っていません。山1のパラメータが分かった時点で山2のパラメータと入れ替わっていることに気づいたからです。描画位置(y座標)だけは自分で調整しました。
virusはDNA全体をscanしてRNAのemptyを書き換えるものです。virusについては修復ガイドのpage 100に書かれています。というか、これ自体は読めませんがvirusの実例が3つもはいっています。

(3) drawGrassPatch 草(川辺を除く)

草は謎が解明しきれていないので力技で描いています。
川沿いの草は別の場所で書いています。drawGrassPatchでは地面の草を描いています。
草単体の描画はgrass1~grass4の4つのルーチンがありますが、drawGrassPatchではgrass1~grass3の3種類のうちのどれかしか描かれません(バグ?)。
草の位置と種類は乱数で決まります。乱数はcryptでも使っているRC4です(initFastRandomとfastRandom)。草の種類を決めるときは乱数の下位2bitをとっているので0~3の値をとりますが、drawGrassPatchの中では1ならgrass1、2ならgrass2、3ならgrass3、4ならgrass4を呼んでいます。乱数が0になったときは何も描かれません。従ってgrass4が呼ばれることはありません。
乱数シードは"El pasto siempre se ve mas verde del otro lado de la cerca."という文字列になっています。さらにdrawGrassPatchの先頭でbioMorphPerturbという配列の内容が要素ごとに加えられてinitFastRandomに渡されています。bioMorphPerturbの中身は最初は0になっています。草の位置と種類の修正には乱数シード(鍵)を見つけるか、bioMorphPerturbに正しい値をセットしてやる必要があります。
怪しいと思っているがbiomorph_adaptationです。これはmainでscenarioが呼ばれる前に呼び出されているadaptationです。adaptationについては修復ガイドのpage 9 (help-adaptive-genes)に、各adaptationの仕様はFuunDocに書かれています。また、help-initial-condにもadaptationのことが書かれていますが、たぶんこれがbiomorph_adaptationのことです。一番最初に呼び出されているadaptationなのでinitial conditionという話に合致しています。調べてみるとbioMul_adaptationは中身がなく乗算するかわりに0を返すだけになっていたりします。このあたりを解明して修復できれば上のbiomorph_perturbという配列の初期化を行ってくれるのではないかと想像していますが、まだできていません。

画像エディタで拡大して一つずつ場所を合わせた草の座標一覧です。何かの影になっていて一部しか見えていないものについては草の種類が間違っているかもしれません。
	grass(3, 444, 332); // 草の種類、x座標、y座標
	grass(1, 496, 402);
	grass(1, 490, 419);
	grass(1, 510, 541);
	grass(1, 470, 554);
	grass(3, 476, 532);
	grass(1, 460, 484);
	grass(1, 426, 373);
	grass(2, 416, 468);
	grass(2, 416, 527);
	grass(1, 386, 365);
	grass(2, 362, 378);
	grass(1, 356, 398);
	grass(2, 346, 355);
	grass(3, 318, 345);
	grass(1, 364, 450);
	grass(2, 338, 546);
	grass(1, 348, 567);
	grass(3, 304, 491);
	grass(2, 286, 531);
	grass(3, 304, 521);
	grass(2, 210, 464);
	grass(2, 222, 479);
	grass(3, 230, 492);
	grass(1, 242, 520);
	grass(1, 118, 381);
	grass(1, 138, 422);
	grass(3, 150, 466);
	grass(3, 222, 396);
	grass(3, 246, 429);
	grass(1,  58, 376);
	grass(2, 134, 323);
	grass(2, 138, 374);
	grass(2, 162, 506);
	grass(1, 114, 563);
	grass(3, 123, 548);
	grass(3,  96, 430);

(4) flowerbed 右下の花

- 花1 (0, 0) <---> 花2 (34, 0)
- 花3 (17, 24) <---> 花4 (58, 12)

座標を入れ替えています。
色はflowerのパラメータなのでパラメータを入れ替えてもいいかもしれません。
このflowerbedの直前にはdead space(実行されないコード)があります。

(5) cargobox 右下の箱

- 8番目のmagentaをyellowに変更 (0x21f12b + 30番地をCFにする)
- 12番目のgreenをblueに変更 (0x21f1ab + 31番地をPにする)

色を修正します。cargoboxはcompressorを使って描画しているのでRNA tableを書き換えます。
色のRNAコードはどれも似ているので書き換える箇所は少ないです。
compressorについては修復ガイドのpage 123456 (help-compression-rna)とpage 1024に書かれています。

(6) motherDuckWithChicks 親子連れのあひる

- 直前のif文でスキップしないようにする (0x4cf3e8番地にIPPを書く)
- 親あひるの描画パラメータ修正 (0x2c932e番地にCを書く)

親あひるを描くmotherDuckという関数はgene tableのintegrity checkにひっかかる(壊れている)関数です。RNAの表示を逐次実行しながらターゲットと比較していくと親あひるの右足がずれているのが分かりました。足はdrawPolygonを使って描画しています。drawPolygonは移動量のx, yをセットにした配列をパラメータとして受け取ります。このパラメータを修正しました。一番最初のx移動量が0になっているのを2にしています。
polygonデータのデータ構造については修復ガイドのpage 8 (help-encodings)に書かれています。polygonデータの最後の2要素は移動量の総和になっているので壊れているデータが分かります。
ちなみに0を2に書き換えるとcheck integrityも通るようになります。

(7) appletree 右のりんごの木

- appletreeからpeartreeへfar jumpする

もしくはappletreeの呼び出しをpeartreeへの呼び出しに書き換えてもいいかもしれません。呼び出しは4箇所あります。appletreeとpeartreeの違いはappleとpearだけなので、appleからpearへfar jumpしてもいいです。どれにするかはprefixのサイズが小さくなるものを選ぶことになるはずです。

(8) chick 右側のひよこ

- skipする
windmill(風車)より後に描かないといけないのでここのコードはskipします。

(9) weeds 左側の枯れた木

- seedを8128にする。

weeds自体はlsystem-weedという関数で描画されています。lsystem-weedでは乱数を使って成長する方向を決めています。seedはrandomInt関数で発生する乱数のシードです。L-Systemについては修復ガイドのpage 5 (help-lsystems)とpage 180878に書かれています。
一番左の木だけを描くプログラムを作って、seedを変えながらターゲットとの差分が最小になるパラメータを探しました。
この8128という数字は4番目の完全数です。修復ガイドのpage 8128 (help-beautiful-numbers)には完全数のことが色々書いてありました。6, 28, 496の最初の3つの完全数も載っていました。実はページ番号自身も4番目の完全数でした。page 8128はlazinessという鍵で暗号化されていました。このページはgoodVibrationsに123を書いても表示されます。
暗号化されているので何か重要な情報が含まれているのではないかと思っていましたが、実は隠していたのはページ番号だったようです。結局、暗号化されているという事実とページ番号だけ分かれば実際に暗号解読する必要はなかったということです。

(10) vmu 中央のcaravan

- vmuCodeを0x1fにする
- vmuRegCodeを"Out_of_Band_II"にする
- 座標を(267, 210)にする

vmuについてはhelp-vmuに書かれています。vmuRegCodeはvmu-codeに書かれていました。vmu-codeは暗号化されていてgiveMeAPresentに暗号鍵OPEを書くことで表示できました。このprefixはalien-lifeformsに描かれています。のりたんさんの情報によるとこのOPEというのは博士の異常な愛情という映画に出てきた暗号キーだそうです。
vmuCodeはvmuを解析して見つけました。0x1f(= 31)だとcaravan、0x33(= 51)だとufo-with-smoke、それ以外だとhelp-vmuが描かれるよいになっています。どこかにヒントがあったのかもしれませんが分かっていません。

(11) windmill 風車

- drawPolylinePolarとmoveToPolarのパラメータ中の角度を全て+5する(いっぱいある)

9/25 追記 ↑のかわりに、polarAngleIncr(0x0c91e0)に5を入れておくだけでいいです。もしくはsetGlobalPolarRotationをパラメータを5にして呼び出してもいいです。setGlobalPolarRotationはpolarAngleIncrにパラメータをセットするだけの関数です。
moveToPolarでは、角度に必ずpolarAngleIncrを加えてから処理をするので、5をセットしておくだけで全ての角度が+5されることになります。polarAngleIncrの初期値は0なので3文字書き込むだけでwindmillが完成することになります。
night-or-dayとpolarAngleIncrの書き換えだけで差分は98904になります。次の76文字のprefixはこの二つを書き換えます。
IIPIPICCCCCIIICICCCPIICICIIPIPIIIICICCIICCIIICIICCPIICCCCIICIPPPPIPPCPFCFIIC
(!3a3e)P(!c8cd0)III; #0F#1CIC;

riskは989116(= 98904*10+76)なのでScoreboardでは25位相当になります。(ここまで追記)

windmillは、羽根の部分の角度だけが違っています。羽根の部分drawPolylinePolarを使って描いています。drawPolylinePolarが受け取るpolygonはdrawPolylineと違って、最初が要素数、2番目以降が(角度、移動量)が並んでいます。角度は相対ではなく絶対なので全ての要素を書き換えてやる必要があります。drawPolylineは5回呼ばれています。順に左上の羽根、右上の羽根、右下の羽根、左下の羽根、軸です。またfillする関係でmoveToPolarで塗りつぶし開始位置に移動していますが、このパラメータの角度も+5してやります。
windmillはcloudyがPのときに変な色になります。cloudyはcloudを呼ぶと(未登場)Pになるのでcloud中の該当箇所をつぶしておく必要があります。他に影響を受けるのは右下の花と、下の文字です。黄色はcolorGermanyYellowという関数の色ですが、この配色はドイツの国旗からきているようです。

cloudy = Pにした画像


windmillより前に描画しなければいけないのは、clouds(雲)です。逆にwindmillより後に描画しなければいけなのはchick(ひよこ)です。

(12) lambda-id 左側の文字

- 不要なのでskipする

長くなってきたので、続きます。

ICFP2007 (21) 暗号解読(3)

2007-09-13 00:43:58 | ICFPプログラミングコンテスト
ICFP2007 (20) 暗号解読(2)の続きです。

caravanの復号方法が分かりました。これはvmuという関数を読んでいて分かったのですが、caravanはvmuRegCodeを鍵として暗号化されていました。
以前張った画像ですが、このOut_of_Band_IIが鍵でした。最後が読みづらかったのですが、何通りか試してみたところI(アイ)でした。(書きながら思ったのが、vmu-codeは復号できるのだからソースを読めばよかったんですね。)



caravanは修復する画像の真ん中あたりにある車(?)です。画像修復には、鍵による復号は必須ではなくて、各部品は暗号化されていないので自分で並べてやってもいいです。ただし、その分prefixが長くなるので鍵で復号した方がいいです。vmuという関数はvmuMode=1fのときにvmuRegCodeを使ってcaravanを復号して表示するようになっています。



help-activating-genesですが、cryptを使って暗号化しているわけではなく、シーザー暗号でした。ICFPを各々FPICと2文字ずらすと復号できました。page 23になります。



サブルーチンコールの仕組みとかが書かれています。最後に書かれているのはadaptationの話です。dnaの中にはインタプリタみたいなのがあって、コード化されたプログラムを逐次実行するみたいなことができます。具体的にはint24がひたすら並んでいて、各々がコマンドだったりパラメータだったりします。FuunDocに書かれているのがadaptationの各コマンドの概要です。正確に言うとインタプリタとも違うような気もしますが、直接実行されるわけではないところがインタプリタ風です。FuunDoc自体はHaskell風に書いてあります。

最後はcow-tailです。鍵は"9546"でした。javaのプログラムは遅かったので結局Cで書き直してなんとか見つかりました。解読の実行時間は3964.95秒(1時間6分)でした。



プログラムは長くなるので貼りませんが、おおまかに次のような流れです。

- 先頭を2バイト分復号してみる
- 正しく復号できていたら次に進む。だめなら次の鍵にする
- 最後の方まで復号していって、最後の固定パターンが正しく復号できているか調べる。
- 正しく復号されていたら出力しておわり。だめなら次の鍵にして最初に戻る

プログラムの先頭の10文字とプログラムの最後の75文字の平文が既知です。1文字が2ビットなので先頭は2バイトと2ビットしか分かっていないことになります。20ビットくらいだと、偽鍵が大量に発生します。つまり、正しくない鍵でも20ビットくらいならたまたま復号できてしまうこともあります。最初は偽鍵が嫌だったので最後の固定パターンを使って鍵探索していましたが、最後まで復号するのは時間がかかります。しかたないので最初のパターンでふるいにかけたものについてだけ最後まで復号するというように方針を変更しました。

これ以上鍵が長いとさすがにPCでの探索もつらいものがあります。アルゴリズムもそんなに複雑ではないのでFPGAを使って並列探索とかするとよいかもしれません。ブロックRAMを使うとすると24並列とか平気でできそうです。これはおもしろそうなので今後の課題です。