Hatena::ブログ(Diary)

UEI shi3zの日記 RSSフィード

2014-04-13

プログラマーの道具

 アスキー遠藤諭さんが、「たまには飯でも食おう」というので地図を頼りに飯田橋まで出かけて行くと、ほぼ廃墟みたいな見た目の中華料理店にぶちあたった。


 「これは昭和・・・というよりも満州か・・・」


 恐る恐る古めかしいドアを押すと、明るく活気ある店内に入り込んだ。


 「エンドーさん、で予約してると思うのですが」


 「エンドーさん?誰それ?」


 女将らしい女性が中国訛りの日本語でそっけなく答えた。


 「ええと、遠藤諭さん・・・」


 「わかんない。そのへんに適当に座ってて」


 と促されて、本当に適当に座る。


 ほどなくして現れた遠藤さん。

 女将はそれを見るなり


 「アイヤー、エンドーさんね」


 と笑った。


 遠藤さんは紹興酒をオーダーすると、「これやるよ」と一台の電卓を取り出した。


https://scontent-a.xx.fbcdn.net/hphotos-frc3/t1.0-9/1531535_10153051579650752_278219723229954701_n.jpg

 

 TIことテキサス・インスツルメンツの、その名もプログラマーと名付けられた電卓。


 「これ、プログラマーの憧れだったんだよ」


 と笑う。

http://www.datamath.org/Sci/MAJESTIC/IMAGES/Programmer.jpg

 1977年に発売が開始されたベストセラー電卓。

 16進モードと8進モードを備え、ビット演算も可能。


 確かにこれは便利そうだ。


 コンピュータを使ってると、ちょっとした計算をしたくなることは良くある。

 そのような場合、僕はOpenOfficeの表計算を立ち上げるか、MacOSに搭載された電卓か、ダッシュボードの電卓を使うことが多い。


 しかし、PCのデスクトップから電卓を呼び出して使うというのがどうも、なにか居心地が悪い。画面の中にある電卓の形をしたソフトのボタンにトラックパッドで狙いを定めてクリックするのがなにか滑稽だし、それはiPhoneで動いてる電卓で多少はマシになるけれども、本質的には変わらない。そこにあるのは電卓のイミテーションであって、電卓ではない。


 時たま、物理的な電卓が恋しくなることがある。クリック感が欲しい。


 ちょっと凝った計算をするなら表計算しか選択肢がないけれども、シンプルな計算を繰り返すなら物理的な電卓があったほうがいい。


 ただ、16進数の計算やビット演算をするとなると専用の電卓が必要になる。


 そういうものはたいがい高価なので、さすがにそこまで用意するという発想は僕にはなかったけれども、このTI Programmerは僕のそういうニーズに答えてくれる。


 16進数の計算なんていつ必要になるのか、たいがいの人には想像もつかないかもしれないけど、本格的なプログラミングをしていると今でもたまに必要になることがあるのだ。


 たとえばenchantMOON S-IIでは、指で適当に囲んだ図形が、自動的に長方形や楕円や三角形として認識される。


 このアルゴリズムの元になるコードは僕が書いたんだけど、そのとき、様々な仮説をビット演算で表現してる。

 例えば長方形かどうか判定しているのは以下のような部分だ

	//長方形かな?
	if(inside(x,y,left,top,acceptable*0.5,acceptable*0.5))box|=1;
	if(inside(x,y,right-acceptable*0.5,top,acceptable*0.5,acceptable*0.5))box|=2;
	if(inside(x,y,left,bottom-acceptable*0.5,acceptable*0.5,acceptable*0.5))box|=4;
	if(inside(x,y,right-acceptable*0.5,bottom-acceptable*0.5,acceptable*0.5,acceptable*0.5))box|=8;

	 //それ以外の変な形なのかな? (♡など)
	if(inside(x,y, center_x-width*0.2,center_y-height*0.2,
			width*0.4,height*0.4)){
			box|=16;
	}

 ここではboxという変数に論理演算で仮説の妥当性を検証している。

 四隅にあるバウンディングボックスを点が通過するかどうかで、それぞれの四隅に1ビットずつ割り当てている。

 ただし、真ん中付近を通る場合は、それは長方形ではなく、ハート形などのヘンな形である、と判定する。それが16の位(5ビット目)だ。

 boxの値が15なら、その形は長方形の可能性が高いと判定するわけだ。


 長方形はまだ簡単だけれども、三角形の判定は少し複雑だ。

 原理は同じだが

	//直角三角形かな?
	if(inside(x,y,left,top,acceptable,acceptable))triangle|=1;
	if(inside(x,y,right-acceptable,top,acceptable,acceptable))triangle|=2;
	if(inside(x,y,left,bottom-acceptable,acceptable,acceptable))triangle|=4;
	if(inside(x,y,right-acceptable,bottom-acceptable,acceptable,acceptable))triangle|=8;

	//二等辺三角形かな?
	if(inside(x,y,left+width/2-acceptable*0.5,top,acceptable,acceptable*0.5))isoscelesTri|=32;
	if(inside(x,y,right-acceptable*0.5,top+height/2-acceptable*0.5,acceptable*0.5,acceptable))isoscelesTri|=64;
	if(inside(x,y,left,top+height/2-acceptable*0.5,acceptable*0.5,acceptable))isoscelesTri|=128;
	if(inside(x,y,left+width/2-acceptable*0.5,bottom-acceptable*0.5,acceptable,acceptable*0.5))isoscelesTri|=256;

 

 triangleとisoscelesTriの二つの変数でビットが重ならないようになっている。

 これを判定している部分は以下のようになる


	//三角形、または二等辺三角形、直角三角形を認識する
	var composite = isoscelesTri|triangle;
	if( (triangle & 16)!=0 ){
		//二等辺三角形ならそれを作る
		if( composite == (1|2|16|32|256)){
			vertices = [	{x:left,y:top},
							{x:right,y:top},	
							{x:left+width/2,y:bottom}];
			
		}else
		if( composite == (1|4|16|64|128)){
			vertices = [	{x:left,y:top},
							{x:left,y:bottom},	
							{x:right,y:top+height/2}];
		}else
		if( composite == (2|8|16|64|128)){
			vertices = [	{x:right,y:top},
							{x:right,y:bottom},	
							{x:left,y:top+height/2}];
		}else
		if( composite == (4|8|16|32|256)){
			vertices = [	{x:left,y:bottom},
							{x:right,y:bottom},	
							{x:left+width/2,y:top}];
		}
		else{
			//直角三角形以外なら自由図形
			if(!((triangle == (1|2|4|16))||
				(triangle == (1|2|8|16))||
				(triangle == (1|4|8|16))||
				(triangle == (2|4|8|16))))vertices=[];
		}
	}

 与えられた図形が二等辺三角形である可能性は、4パターンある。

 それぞれ上下左右方向に向いた形になっている。


 このアルゴリズムをデバッグするとき、triangleとisoscelesTriの値だけを表示させても、それがどのように判定されてるのか瞬時にはわからない。

 

 このようなとき、ビット演算ができる電卓があると便利だ。なくても困らないが、あると頼もしい。


 このビット演算によって仮説を管理する、というテクニックは、プログラミングをしているとごく当たり前のように出て来るが、ビット演算を使わないと大変面倒くさいことになる。


 まあもうちょっと読み易くするには、数字を直接書くのではなく、TOPLEFT=1,TOPRIGHT=2のようにしてTOPLEFT|TOPRIGHTのような書き方をするべきだと思うが、この時は時間がなかったのでハードコーディングしてしまった。実際にこのアルゴリズムを作ったのはサンフランシスコから戻る飛行機の中のわずかな時間しかなかったのだ。


 

 「この電卓、いくらくらいするんですか?」


 「まあそう高価なもんじゃないよ。eBayで調べてみようか・・・2000円くらいだね」


 「なるほど」


 「オークションで落札したはいいんだけど、僕はヒューレット・パッカード派でね。そっちのほうを愛用してるからTIのはいらないのよ。だからあげるよ。プログラミングの本も書いた記念にね」


 「ありがとうございます」


 ちなみに今回のアルゴリズムで僕が苦労したのは、認識した図形にモーフィング(滑らかなアニメーション)する機能だ。


 最初、このモーフィング機能はなく、パッと図形が切り替わるようになっていたんだけど、それだとどうしても違和感があってモーフィング機能をできれば入れたい、と思うようになった。しかしこの時もやはり時間がなくて、慌てて書いたんだけど、


	//二直線の交点を求める
	crossLine = function(x1, y1, x2, y2, x3, y3, x4, y4){
		if(x1==x2){
		}
		if(x3==x4){
			var y = (y2-y1)/(x2-x1)*(x3-x1)+y1
			return {x:x3,y:y};
		}
		var a1 = (y2-y1)/(x2-x1)
		var a3 = (y4-y3)/(x4-x3)
		var x = (a1*x1-y1-a3*x3+y3)/(a1-a3)
		var y = (y2-y1)/(x2-x1)*(x-x1)+y1
		return {x:x, y:y};
	}
	var m=0.1;
	for(j=0;j<clip.length-3*2;j+=3){
		var x = clip[j];
		var y = clip[j+1];
			
		var x1=dstClip[0],y1=dstClip[1];
		var distance=1000;
		var goal={x:x,y:y};
		for(var i=3;i<dstClip.length;i+=3){
			var x2=dstClip[i];
			var y2=dstClip[i+1];
							
			//交点を求める	
			var cross = crossLine(x,y,gx,gy,x1,y1,x2,y2);
				
			//交点との距離を求める
			var d = getDistance(x,y,cross.x,cross.y);
			if(d<distance){
				//交点との距離が最も近い点を求める
				goal = {x:cross.x,y:cross.y};
				distance=d;
			}
			x1=x2;y1=y2;
		}
			
		var px = (goal.x-x)*m+x;
		var py = (goal.y-y)*m+y;
		clipAnimVel.push((goal.x-x)*m);
		clipAnimVel.push((goal.y-y)*m);
		clipAnimVel.push(0);
	}

 これは全ての点に対して、最寄りの直線との交点を求め、その点が10フレームでぴったり交点に達するようなスピード(clipAnimVel)を求める。

 パッと見ると計算量は多いが、dstClipは長方形などのシンプルな図形であり、見た目ほどはループ回数は多くない。この計算は一度行っておけばあとは毎フレームごとにclipAnimVelを足し込んで行けば自動的にアニメーションが開始され完了する。

https://fbcdn-sphotos-f-a.akamaihd.net/hphotos-ak-prn1/t1.0-9/10264556_10153061035495752_6063236333560146293_n.jpg

 こういうアルゴリズムを考えるときは、どうしても紙とペンのようなものが必要になる。

 今回はenchantMOONをその代わりに使うことにした。数式を書いたり、図を書いたりして検証するには、悪くない端末だ。その目的に関して言えば、enchantMOONは僕にとって完全に紙とペンの代替となった。いわば電卓ともう一つ、僕が考えをまとめるときに必要な機能を持った機械というわけだ。ないと絶対に困るわけではないが、あると頼もしい。


 プログラムを書くとき、なにか新しいことを考えるとき、そういうときほど、こういう道具が必要になる。MOONにする利点は、暗闇でも使えることと、Evernoteに入れておくとあとで読み返すのがラクということだ。


 人間は複雑な思考をまとめるとき、情報をどこかに集約しつつ推敲するというプロセスが必要になる。

 電卓も、MOONも、そういう思考を補助する道具であろうとする。


 まあ究極のところは、そういう複雑なアルゴリズムもMOONBlockだけで記述できるようになることなんだけど、まだなかなかそこまでは行かない。そのためにはMOONBlock自体の研究が必要だと思うし、MOONBlockはまだ生まれたばかりの言語だ。

 

 あるプログラミング言語が産声を上げてから、何もかもそれでできるようになるまでには相当な時間を要する。MOONBlock自体も研究が必要だし、そのためにはもっと時間がかかるだろうと思う。