ICPC2020チーム紹介ドキュメントのメイキング

kotatsugame.hatenablog.com

gist.github.com

https://ideone.com/BrtL8u

今回はQuineのAAコードを作ってみました。

1.AAを用意する

tool-taro.com

前回と同じく、またこのサイトを使ってAAを作ります。元となった画像はガウリールドロップアウトのアニメ公式サイトから拾ってきました。↓のページの4枚目の画像です。著作権は……よくわかりません。

gabdro.com

このままAA変換サイトに投げてもよいのですが、輪郭をちゃんと取り出すために下準備をします。輪郭を認識する……機械学習だな!とはならず、温かみのある手作業です。ペイントで300%くらいに拡大し、マウスを握りしめて縁取りして、内部を白で埋めます。何度か試してみて、つぶれがちだったアホ毛・眉毛・口はちょっと太くしておきました。

f:id:kotatsugame:20210228081646j:plain
微妙に残った元の色が哀愁を誘う

ideoneのフォントでいい感じに見えるように縦横比を調整します(1敗)。ここでいう1敗とは、全ての工程が完了してから縦横比を調整していないことに気づいた、という意味です。

AA変換サイトに投げたものがこれです。サイズは80を指定しました。これはチーム紹介ドキュメントの制約で、幅が半角75文字以内である必要があったからです(1敗)。ここでいう1敗とは、全ての工程が完了してから幅が制限をオーバーしていることに気づいた、という意味です。上の敗北とは別の回です。

f:id:kotatsugame:20210228083245p:plain
第一段階

https://ideone.com/lPnJYs

まず、`を空白に置換し、それ以外すべての文字を#に置換します。また、頭のてっぺんの線を付け足します。そして、幅が半角75文字に収まるように、左右から少しだけ行を削除します。幅を80に指定してAAに変換しましたが、実際は79文字でした。アホ毛を少し縮めて右から-2行、あとは左から-2行しておきます。顔が真ん中に来るようにしました。

さらに後々のため、右目の下2行が9文字・7文字になるようにしておきます。

f:id:kotatsugame:20210228084045p:plain
第二段階

https://ideone.com/2LgmEH

2.ランレングス圧縮する

今回は出力をAAにするにあたって、ランレングス圧縮した数列をもとに構築するアルゴリズムを採用しました。白と黒しかないので、それが交互に出現するようにゼロ埋めしておきます。行の幅をすべて等しくしておけば、改行の出力は75回に1回行うと決め打ってよいです。数列は、各値に65を足したものを文字コードとして見た文字列にして保持します。運よくバックスラッシュが出現しなかったので、特にエスケープなどを考える必要はありません。

次のようなプログラムで文字列を圧縮しました。

ans=[]
while s=gets
  s.chomp!
  a=[]
  t=" #"
  c=0
  75.times{|i|
    t[a.size%2]==s[i] ? c+=1 : (a<<c;c=1)
  }
  a<<c
  a<<0 if a.size%2==1
  ans+=a
end
A=65
puts"WARNING"if ans.include?(92-A)
puts ans.map{|e|(e+A).chr}.join

3.Rubyコードを書く

「あなたの知らない超絶技巧プログラミングの世界」という本を読みます。

https://www.amazon.co.jp/o/ASIN/4774176435/gihyojp-22

4-1-1項(p.137)に、アスキーアートQuineの基本構造が載っています。これを使いました。

eval$s=%w(
s=%(eval$s=%w(#$s))*"";
# 成形して出力
)*""

成形して出力するところさえ考えればよいです。次のようになりました。(一か所(){}が変わっていますが、コードの内容に違いはありません。)

s=%(eval$s=%w{#$s}*"").chars;
a="圧縮された文字列";
t=[32.chr]*897;
l=0;
a.bytes{|c|s,t=t,s;c.downto(66){$><<s.shift;(l+=1)%75==0&&puts}}

どのようなコードを経てこれにたどり着いたかは記録していませんが、最初に書いたコードからかなり変わったことは覚えています。このコードの文字数がちゃんとAAの#の文字数より少なくなるようにコードゴルフしたからです。それでも微妙に収まらなかったので、先ほどのAAから孤立した空白や#をいくつか削除することで、圧縮後の文字列が短くなるようにしました。最終的に、以下のようなAAを使いました。

f:id:kotatsugame:20210228094947p:plain
第三段階

https://ideone.com/KL1HCq

4.AAにする

先ほどのコードをそのまま実行すれば、AAのQuineが手に入ります。つまり、わざわざ手で空白を入れたりして最初の状態を作る必要はないということです(これも「あなたの知らない超絶技巧プログラミングの世界」に書いてあることです)。

ここで、先ほどのコードに少し無意味な変数を足し、順番を入れ替え、圧縮された文字列を2つに分割して、次のようなコードを実行します。

eval$s=%w{
a="圧縮された文字列の先頭";
Aobayama_=t=[32.chr]*897;
l=0;
s=dropout=%(eval$s=%w{#$s}*"").chars;
(a+"圧縮された文字列の末尾").bytes{|c|s,t=t,s;c.downto(66){$><<s.shift;(l+=1)%75==0&&puts}}
}*""

僕が所属するICPCのチーム名はAobayama_dropoutです。これがQuineの中に見えていると、より技巧的に感じられることが期待されます。そこで、Aobayama_dropoutという変数を足しました。最初にAAを作ったとき、右目の下2行が9文字・7文字になるようにしていましたが、そこにちょうどこのAobayama_(9文字)とdropout(7文字)が来れば、全体のだいたい真ん中あたりにチーム名が見えることになります。

試行錯誤の結果、圧縮された文字列の最後3文字だけを分割すれば、ちょうど狙った位置に変数名が来てくれることがわかりました。

5.完成!

f:id:kotatsugame:20210228100549p:plain
実行結果とともに

https://ideone.com/BrtL8u

6.余談

大きな幅で作ってしまったものは、Aobayama_dropoutに加えTOHOKU_UnivもQuineに含めることができました。せっかくなのでそちらも載せておきますが、縦横比を修正する前のため、キャプチャはideoneではなく僕の手元の環境になります。

f:id:kotatsugame:20210228101350p:plain
右目の下半分に注目

https://ideone.com/8bii4H

7.手元に届いてみると……

f:id:kotatsugame:20210305110836j:plain
潰れちゃった

悲しいなあ