読者です 読者をやめる 読者になる 読者になる

Code 2014 #codejp にて、コードゴルフの出題兼解答をした

Connpassや関係者各位に「お仕事で無理!」って宣言した後に、
参加できる事になりました。

今回は(も?)コードゴルフに焦点当てていきます。

イベント Code 2014 - ホーム [CodeJP]

出題まで

お題提供のお願いされたのが、8/1(金)。 言語問わずで、お願いされました。 ぱぱっと出てきたのが「ライフゲーム」でのコードゴルフ
しかし、お題が有名なのでぐぐったら出てきちゃいそう。

環境Webブラウザがあれば作業出来るという点で去年と同じJavaScriptを使用を決め、
時間もないので去年のお題ベースの別バージョンを用意しようと決めました。 ということを土日考えて、Marqueeみたいなことを表示しようと考えてコード書きました。
(余談:Ticker表示だと後に知る)

#codejp 2014 出題時のソース

$ wc -cm main.js
2375 2435 main.js
  • main.js 2375chars 2435bytes

かなり冗長気味に書いたので、初めてチャレンジする人にも
500byte切れるぐらいの達成感は味わえるかなっておもいました。

解答

去年の問題とは違い、流れる文字「CODEJP2014」は固定なので、パターンを全部連結しちゃいます。

var s=[
'_***___***__****__*****_____*_****__*****_*****_____*_*___*_',
'*___*_*___*_*___*_*_________*_*___*_____*_*___*_____*_*___*_',
'*_____*___*_*___*_*****_*___*_****__*****_*___*_____*_*****_',
'*___*_*___*_*___*_*_____*___*_*_____*_____*___*_____*_____*_',
'_***___***__****__*****__***__*_____*****_*****_____*_____*_'];

「_→0,*→1」で置換すると以下のようになります。

var s=[
'011100011100111100111110000010111100111110111110000010100010',
'100010100010100010100000000010100010000010100010000010100010',
'100000100010100010111110100010111100111110100010000010111110',
'100010100010100010100000100010100000100000100010000010000010',
'011100011100111100111110011100100000111110111110000010000010'];

これを利用して、Ticker表示の部分を削ると、以下のようになりました。

s=[
'011100011100111100111110000010111100111110111110000010100010',
'100010100010100010100000000010100010000010100010000010100010',
'100000100010100010111110100010111100111110100010000010111110',
'100010100010100010100000100010100000100000100010000010000010',
'011100011100111100111110011100100000111110111110000010000010'];

t=0;
a=function(){
  for(j=0,o="";j<5;++j){
    for(i=0;i<30;i++){
      o+='_*'[s[j][(t+i)%60]];
    }
    document.body.innerHTML=o+="<br>";
  }t++;
  setTimeout(a,300);
};a()
$ wc -cm main.js
504 504 main.js
  • main.js 504chars 504bytes

日本語コメント消したので、文字数とバイト数が一致しました。
01に置き換えるよりそのまま「_*」表示したほうが短くなりますが、
今後var sの文字パターンを圧縮するのに必要なので先にやりました。

それでは圧縮方法ですが、Unicode文字化します。
15文字ずつ区切って、それぞれをユニコードのエスケープ表現でUnicode文字化します。
ただ、ビットの取得方法がまだ確定ではなかったので、
試行錯誤し易いようにPythonで圧縮データを作成

# encoding: utf8
s=[
'011100011100111100111110000010111100111110111110000010100010',
'100010100010100010100000000010100010000010100010000010100010',
'100000100010100010111110100010111100111110100010000010111110',
'100010100010100010100000100010100000100000100010000010000010',
'011100011100111100111110011100100000111110111110000010000010'];

t=[]
# 逆順からforを回すため
for ss in s[::-1]:
    # 1行単位でも逆順にする
    t.append(ss[::-1])

p = ''
for v in t:
    q = ''
    # 15文字ずつ切り出して2進数→10進数(数値)→16進数(文字列)で変換
    for i in range(0, len(v), 15):
        q="\u%04x" % int(v[i:i+15], 2) + q
    p += q + '","'
print 's=["%s"]' % p[:-3]

# $python encode.py
# s=["\u738e\u1cf9\u77c1\u2083","\u1451\u220a\u1041\u2082","\u1441\u22fa\u17cf\u3e82","\u1451\u200a\u1411\u2282","\u738e\u20f9\u77cf\u2283"]

これをChromeJavaScriptコンソールに通すと

> s=["\u738e\u1cf9\u77c1\u2083","\u1451\u220a\u1041\u2082","\u1441\u22fa\u17cf\u3e82","\u1451\u200a\u1411\u2282","\u738e\u20f9\u77cf\u2283"]
["玎᳹矁₃", "ᑑ∊၁₂", "ᑁ⋺៏㺂", "ᑑ ᐑ⊂", "玎⃹矏⊃"]

と出る。
これに各行にcharCodeAtを使って1bitずつ抜き取るコードを書くとこんな感じ。

for(j=5,o="";j--;){
  for(i=30;i--;){
    "_*"[s[j].charCodeAt((t-i)/15%4)>>((t-i)%15)&1];
  }
  document.body.innerHTML=o+="<br>";
}t++;
  • s[j]: j行目
  • s[j].charCodeAt(n): j行目n番目のUnicode文字(0-14,15-29,30-44,45-59)
  • n=(t-i)/15%4: t秒後の左からi番目の位置を15で割ってUnicodeの何文字目かを算出
  • m=(t-i)%15: t秒後の左からi番目の位置の右から何ビット目の数値か算出
  • s[j].charCodeAt(n)>>m&1: n文字目、mビット目のビットのみ抜き取り

をまとめると、

s=["玎᳹矁₃","ᑑ∊၁₂","ᑁ⋺៏㺂","ᑑ ᐑ⊂","玎⃹矏⊃"]
t=629;
a=function(){
  for(j=5,o="";j--;){
    for(i=30;i--;){
      o+="_*"[s[j].charCodeAt((t-i)/15%4)>>((t-i)%15)&1];
    }
    document.body.innerHTML=o+="<br>";
  }t++;
  setTimeout(a,300);
};a()
$ wc -cm main.js
240 280 main.js
  • main.js 240chars 280bytes

これを1ライナーにしたら、こうなります

t=629;eval(a='for(j=5,o="";j--;document.body.innerHTML=o+="<br>")for(i=30;i--;)o+="_*"[["玎᳹矁₃","ᑑ∊၁₂","ᑁ⋺៏㺂","ᑑ ᐑ⊂","玎⃹矏⊃"] [j].charCodeAt((t-i)/15%4)>>((t-i)%15)&1];t++;setTimeout(a,300)');
$ wc -cm main.js
191 231 main.js
  • main.js 191chars 231bytes

これがコードゴルフ大会時に提出したソースコードです。
JSFiddle Demo

大会後に色々な方に助言をいただき以下のように短くなっていきました。

末尾セミコロン除去

t=629;eval(a='for(j=5,o="";j--;document.body.innerHTML=o+="<br>")for(i=30;i--;)o+="_*"[["玎᳹矁₃","ᑑ∊၁₂","ᑁ⋺៏㺂","ᑑ ᐑ⊂","玎⃹矏⊃"] [j].charCodeAt((t-i)/15%4)>>((t-i)%15)&1];t++;setTimeout(a,300)')
// $ wc -cm main.js
// 190 230 main.js

t=629 -> t=29

t=29;eval(a='for(j=5,o="";j--;document.body.innerHTML=o+="<br>")for(i=30;i--;)o+="_*"[["玎᳹矁₃","ᑑ∊၁₂","ᑁ⋺៏㺂","ᑑ ᐑ⊂","玎⃹矏⊃"] [j].charCodeAt((t-i)/15%4)>>((t-i)%15)&1];t++;setTimeout(a,300)')
// $ wc -cm main.js
// 189 229 main.js

eval,setTimeout -> setInterval化

t=29;setInterval('for(j=5,o="";j--;document.body.innerHTML=o+="<br>")for(i=30;i--;)o+="_*"[["玎᳹矁₃","ᑑ∊၁₂","ᑁ⋺៏㺂","ᑑ ᐑ⊂","玎⃹矏⊃"][j].charCodeAt((t-i)/15%4)>>((t-i)%15)&1];t++',300)
// $ wc -cm main.js
// 179 219 main.js

※厳密にいうと初回0.3s真っ白だけど…(あとで気づいた)

t-iを別変数に入れて、後ろで使ってるt-i=>uに置き換え括弧削除

t=29;setInterval('for(j=5,o="";j--;document.body.innerHTML=o+="<br>")for(i=30;i--;)o+="_*"[["玎᳹矁₃","ᑑ∊၁₂","ᑁ⋺៏㺂","ᑑ ᐑ⊂","玎⃹矏⊃"][j].charCodeAt((u=t-i)/15%4)>>u%15&1];t++',300)
// $ wc -cm main.js
// 175 215 main.js

データ圧縮のリストを1つの文字列として置き換え

t=29;setInterval('for(j=5,o="";j--;document.body.innerHTML=o+="<br>")for(i=30;i--;)o+="_*"["玎᳹矁₃ᑑ∊၁₂ᑁ⋺៏㺂ᑑ ᐑ⊂玎⃹矏⊃".charCodeAt(j*4+(u=t-i)/15%4)>>u%15&1];t++',300)
// $ wc -cm main.js
// 162 202 main.js

ここまでがCODE 2014開催中に修正出来たものです。
そして2日後に、200bytes切ることが出来ました。

for文の条件書き換えによる、tの初期化とuの変数削除

t=0;setInterval('for(j=5,o="";j--;document.body.innerHTML=o+="<br>")for(i=t;i<t+30;)o+="_*"["玎᳹矁₃ᑑ∊၁₂ᑁ⋺៏㺂ᑑ ᐑ⊂玎⃹矏⊃".charCodeAt(j*4+i/15%4)>>i++%15&1];t++',300)
// $ wc -cm main.js
// 159 199 main.js

JSFiddle Demo

おまけ

setInterval化だと厳密に違うのでその部分を元に戻した場合

t=0;eval(a='for(j=5,o="";j--;document.body.innerHTML=o+="<br>")for(i=t;i<t+30;)o+="_*"["玎᳹矁₃ᑑ∊၁₂ᑁ⋺៏㺂ᑑ ᐑ⊂玎⃹矏⊃".charCodeAt(j*4+i/15%4)>>i++%15&1];t++;setTimeout(a,300)')
// $ wc -cm main.js
// 168 208 main.js

JSFiddle Demo

3歩進んで4歩下がった気分ですね。

終わりに

なんていうか「見て分からん」っていうのを体感してもらったので、とても満足しています。