WGGの活動log

都内でゲーム開発だったりVRだったりをしてるかもしれないエンジニアです. WGGは「ワグ」と読みます

#つぶやきProcessing で限界までコードを圧縮する方法 (p5.js 編)

先日 (5/16), Processing Community Hangout Japan #01 というオンラインイベントがありました
その中で,#つぶやきProcessing についての話をしている方が居て,みんな1ツイートに収めるための文字数制限に苦労しているように感じたので,自分がやっているコード圧縮の手法をまとめて説明してみようと思います

www.youtube.com

#つぶやきProcessing について

以前書いた記事でも紹介したのですが, Twitter の1ツイートに収まる Processing のプログラムを書いて,実行結果と一緒にツイートする企画です
自分が以前作った作品を少し載せておきます


Processingでも, p5.js でも, Processing Python Mode でもどれでも大丈夫で (有利不利差はありそう), 最近は派生して #つぶやきGLSL なんてものもできてるらしいです


以前自分が書いた,つぶやきProcessing用の環境構築はこちら ↓

wgg.hatenablog.jp

プログラムを圧縮するために

ここから,圧縮技術について説明していきます.
今回,題材として 以前作ってみた以下の作品を圧縮する工程を紹介してみます

こちらのプログラムは 222文字で収まっています
Twitterでは, 半角文字だけで書けば280文字, #つぶやきProcessing のタグを付ければ 260文字ぐらい入ります
実際に書いてみればわかるのですが,文字数制限は非常に厳しく,中々満足に作ることができないです
その中で少しでも自分の表現を増やすために,あらゆる手段を使って文字数を減らしていきます

t=0,draw=e=>{t++||(createCanvas(w=900,w),w/=2,blendMode(DIFFERENCE),m=translate),clear(),m(w,w),f=e=>{if(e--<1)return;let r=3;for(;r-- >0;)push(),rotate(TAU/3*r+t/800),m(4e4/t+t*t/2e3,0),circle(0,0,t+200),f(e),pop()},f(3)}

圧縮前のソースコードは以下のようになります (507文字)
(ただし,以下のコードは元々書いたものを圧縮して↑のコードにしたわけではなく,この記事を書くために圧縮していない想定のコードを書き直しています)


function recursiveDraw(recursiveCount) {
  recursiveCount--
  if(recursiveCount<=-1){
    return
  }

  for(let i=0;i<3;i++){
    push()
    rotate(PI*2/3*i+frameCount/800)
    translate(40000/frameCount+frameCount*frameCount/2000,0)
    ellipse(0,0,frameCount+200,frameCount+200)
    recursiveDraw(recursiveCount)
    pop()
  }
}

function setup() {
  createCanvas(900,900)
}

function draw() {
  blendMode(BLEND)
  background(0)
  blendMode(DIFFERENCE)
  translate(width/2,height/2)

  recursiveDraw(3)
}

minify してみる

まずはソースコードを短くするには minifyツールを使って短縮してみるのが一番楽だと思います
以下のようなサイトやツールを利用して,ソースコードを意味をそのままで文字数を減らすことができます

javascript-minifier.com
www.npmjs.com

自分は以前書いた記事で紹介しているように,プログラムを保存するたびに minify を実行して圧縮後の文字数を表示するようにしています

function recursiveDraw(e){if(!(--e<=-1))for(let r=0;r<3;r++)push(),rotate(2*PI/3*r+frameCo
unt/800),translate(4e4/frameCount+frameCount*frameCount/2e3,0),ellipse(0,0,frameCount+200,fram
eCount+200),recursiveDraw(e),pop()}function setup(){createCanvas(900,900)}function draw(){blen
dMode(BLEND),background(0),blendMode(DIFFERENCE),translate(width/2,height/2),recursiveDraw(3)}

結構短くなったはずですが,まだ372文字あり,1ツイートに収めるには遠いです
しかしながら,このminify で圧縮してくれる要素は意識せずに書くことができるので,活用していきます

minify で圧縮された要素

minifier がいい感じにやってくれた内容を,一応確認してみます

不要なスペースの削除

多くの場合プログラムを書くときにインデントを付けて構造を見やすくするのですが,minifier ではそれを全部消してくれます
(自分でインデント崩した状態でコーディングするのは辛いので,これを自動で任せられるのは助かる)

変数名の簡略化

recursiveCounte に省略されてたりと,基本的に全ての変数を1文字で書いてくれます
これを利用すれば,自分でコーディングする際には意味がわからない命名をせずに書き,最後に minify で1文字に圧縮する事ができます

数値の表記変更

元のコードには 400002000 といった数値を使用していましたが minify 後は 4e4, 2e3 と指数表記にしてくれます
他にも,今回のコードでは使用していませんが,絶対値1未満の少数を書く場合, 0.54 などの 0 を省略して .54 のように書くことができますが,これも自動で行ってくれます

minifier でやってくれない圧縮を自力でする

ここまでは minifier が自動でやってくれるので,自分は意識して書く必要がないです
ここからは, minifier に頼らず 多少コードの意味が変わったとしても文字数を減らす工夫を行っていきます

function の省略
function setup() {
  createCanvas(900,900)
}

これは,↓のように書き直せます

setup=_=>{
  createCanvas(900,900)
}

引数がある場合は,

function recursiveDraw(recursiveCount){
f=i=>{

のように書きます

複数回使用した関数名の省略

元のコードでは translate を2回使用していますが,同じ関数を2回以上使用する場合は,短縮できる可能性があります

translate(40000/frameCount+frameCount*frameCount/2000,0)
translate(width/2,height/2)

m=translate
m(40000/frameCount+frameCount*frameCount/2000,0)
m(width/2,height/2)

のようにすると,少し文字数を減らせます
名前の長い関数を2回以上使う場合に特に有効です

p5 組み込み変数の省略と, setup の排除

processing (p5.js) には,mouseX, frameCount, width, height など,予め使える変数がいくつかあります
この中で,自分はframeCount, width, height をいい感じに省略できる方法を用意しました

t=0
draw=_=>{
  if(!t++){
    createCanvas(w=900,w)
  }
}


この書き方をすると, width, height は全て w で表わすことができ,
frameCount は tで表せます
更に, function setup(){} が不要になるのでかなりの短縮になります

ellipseを使わない, circle を使う

p5.js では円を描く際には ellipse() を使用することが多いですが,最新の p5.js 1.0 からは楕円ではなく真円を描く場合に circle() 関数が使えるようになりました.
これは, ellipse() に引数を3つ指定した場合と同じ挙動なので1文字の短縮になります

同様に, rect() も square() という正方形に限定して描画する関数がありますが,こちらは逆に名前が長くなるので使ってないです

translate の活用

絵を画面の中心に描きたい場合は結構多いと思います
その際には,各描画命令で座標を画面の真ん中にするのではなく, 初めに translate(w/2,w/2) とすると,その後の描画命令の引数が簡潔になる場合があります.
width を頻繁に使用する場合は, setup() 内で w/=2 を実行しておくのも良さそうです

ループ処理を圧縮する
for(i=0;i<3;i++){
}

a
for 文はこの用に書くのが一般的だとは思いますが,以下のように書くと少し短縮できます(ただしカウンタがずれるので他の箇所で処理が変わってくる可能性があります)

for(i=0;i--<3;){
}
background(0) ではなく clear() を使う

動画ものの作品を作る場合に, draw の中で描画内容を初期化するために background(0)などを使うことは多いと思います
しかし, 背景色をcss で設定しておくと,clear() で代用することができます
(つぶやきProcessing として若干レギュレーション違反な気がしなくもないですが)

ただし,background(0,5) などを使った残像表現をする場合は clear() では完全に消えてしまうのでおそらくできないです

PI*2 を TAU にする

角度を使う場合, PI*2 で360度を表すことは多いですが, TAU という定数が用意されているのでそちらを使うと1文字減ります

終わりに

こんな感じに,自分が使ってるテクニックを色々上げてみました
他にもまだまだ工夫できると思うので,もっと情報共有してつぶやきProcessingの表現力を増やしていきたいです