Google Developer Day 2011

11月1日に今年もGDDが開催されるそうで、エントリーした。すると、DevQuizを解く必要がある。今年のボーダーラインは101点らしいんだけれど、オレは100点。不合格っすか、、、。
気を撮り直して、解いた問題について、記録しておく。

Androidの問題

出題者から提供されるサービスappをインストールする。実機を持ってないので、emulatorに。そのサービスappは次に示すようなAIDLで定義されたInterfaceを持っている。

package com.google.android.apps.gddquiz;
interface IQuizService {
  String getCode();
}

AIDLって何?というレベルのオレでも、AIDLでググればヒントは得られる。とにかく、この定義されたサービスに接続して、getCode()を呼べば良いということなようだ。で、書いたコードが、超やっつけでコピペからこなれていないんだけど、コレ。

package com.google.android.apps.gddquiz;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.IBinder;
import com.google.android.apps.gddquiz.IQuizService;
public class MyAppActivity extends Activity {
        private IQuizService quizServiceIf = null;
        private ServiceConnection quizServiceConn = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder servi
ce) {
                        quizServiceIf = IQuizService.Stub.asInterface(service);
                }
                @Override
                public void onServiceDisconnected(ComponentName name) {
                        quizServiceConn = null;
                }
        };

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);

                Intent intent = new Intent(IQuizService.class.getName());
                bindService(intent, quizServiceConn, BIND_AUTO_CREATE);
                final Button button = (Button) findViewById(R.id.button1);
                button.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                                doConnect();
                        }
                });
                unbindService(quizServiceConn);
        }
        @Override
        protected void onDestroy() {
                super.onDestroy();
                unbindService(quizServiceConn);
        }

        private void doConnect() {
                final EditText etext = (EditText) findViewById(R.id.editText1);
                try {
                        etext.setText(quizServiceIf.getCode());
                } catch (Exception e) {
                        etext.setText(e.toString());
                }
        }
}

画面にはButtonとEditTextが置いてあって、Buttonを押すとAIDLで定義されたサービスに接続してgetCode()を実行して得られた文字列をEditTextに表示する。それだけの簡単なプログラム。
この得られた文字列が、DevQuizの答えである。
そうそう奇特な人も居ないと思うけど、https://github.com/fukuit/DevQuiz11Androidにコード一式を置いておいた。

Go

pngファイルを読んで、使われている色の数を答えるプログラムを書く。そのための、CountColor()という関数を作成することになっている。
Goについては、ちょっとだけカジッたけれど、やっぱり普段使わない言語なのでさっぱりわからない。それに、IntelliSenseで使おうとしているclassのmethodやら何やらが表示されるようなIDE込みじゃないと、オブジェクト指向言語はさっぱり使えない程度にゴミみたいな脳みそなので、、、とか、言い訳はこのくらいに。
で、"godoc image/png"とかしてみる。

% godoc image/png
(前略)
func Decode(r io.Reader) (image.Image, os.Error)
Decode reads a PNG image from r and returns it as an image.Image.
The type of Image returned depends on the PNG contents.
(攻略)

どうやら、Decode()するだけで情報は得られそうだ。

func CountColor(_png io.Reader) int {
        img, e := png.Decode(_png)
        if e != nil {
                fmt.Fprintf(os.Stderr, e.String())
                return -1
        }
        var cnt int = 0
        pix := make(map[string]int)
        pt := img.Bounds().Max
        for x := 0; x < pt.X; x++ {
                for y := 0; y < pt.Y; y++ {
                        color := img.At(x, y)
                        r, g, b, a := color.RGBA()
                        str := fmt.Sprintf("%d%d%d%d", r, g, b, a)
                        if pix[str] == 0 {
                                pix[str] = 1
                                cnt++
                        }
                }
        }
        return cnt
}

デフォルトで表示される関数の定義は、"func CountColor(png io.Reader) int"のようになっているんだけれど、これだとこの関数の引数としての"png"なのか、importしてるimage.pngなのかの区別がつかないので、引数は"_png"にしたった。perlで単語のカウンタを作るときには連想配列を使うのだけれど、Goにもmapという形式で連想配列を使えるので、それを採用する。img.At(x,y)でcolor型が取れるので、そのR,G,B,αを文字列に変換(fmt.Sprintf)して、mapの中に突っ込むことにした。

これも、一応、https://github.com/fukuit/DevQuiz11Goに置いてある。

Chrome Extension

神経衰弱をする。「一枚目のカードをめくる」というChrome Extensionの例があるので、ソレを改造することにする。

var element = document.getElementById('card0');
if (element == null) {
  alert('Card element is not found. Check element id.');
} else {
  var myevent = document.createEvent('MouseEvents');
  myevent.initEvent('click', false, true);
  element.dispatchEvent(myevent);
  alert('Card color is "' + element.style.backgroundColor + '".');
}

これは、card0として定義されているelementにclickイベントを起こさせて、カードの色を表示させるalertを出すだけのものだが、全てのcardを集めた配列を作って、全てのcardをclickして色を決めてから、次に同じ色のカードの組み合わせで順番に再度clickすれば良いはずだ、、、と、考えた。classNameがcardになっている全てのelementを集めてelements配列に入れる方法がよく分からなかったので、ひとまず全てのelementsをallElementsに突っ込んでその中からclassNameがcardのものだけをelementsに入れなおした。

var elements = [];
var allElements = document.getElementsByTagName("*");
for (var i=0;i<allElements.length;i++){
    if (allElements[i].className == "card") {
	elements.push(allElements[i]);
    }
}

で、全てのelementsにclickイベントを起こしたあと、そのcardの色をinnerTextに入れておくと、画面上の全ての伏せられたカードに色の情報が記載されることになる。

for(var i=0;i<elements.length;i++){
    var element = elements[i]
    if (element == null) {
	alert('Card element is not found. Check element id.');
    } else {
	var myevent = document.createEvent('MouseEvents');
	myevent.initEvent('click', false, true);
	element.dispatchEvent(myevent);
	alert('Card '+ i +' color is "' + element.style.backgroundColor + '".');
	element.innerText = element.style.backgroundColor;
    }
}

次に、elementsをinnerTextでsortする。そうすると、色順でsortできる。sortの定義は比較関数を書けばいい。

elements.sort(function(a,b){a.innderText - b.innerText})

これで、for(i=0;i

for(var i=0;i<elements.length;i++){
    for(var j=0;j<elements.length;j++){
	if ( i!=j){
	    if ( elements[i].innerText == elements[j].innerText ){
		var myevent = document.createEvent('MouseEvents');
		myevent.initEvent('click', false, true);
		elements[j].dispatchEvent(myevent);
		elements[i].dispatchEvent(myevent);
		break;
	    }
	}
    }
}

これでも正しく動作することは、動作する。で、clickイベントがあるごとにalertが表示されるんだけれど、「今後alertは表示しない」みたいなチェックボックスをチェックして「OK」すると、全てのカードがめくられ、次の問題になって、また全てのカードがめくられ、、、、っていうのが連鎖して、あっという間に全ての問題を解けてしまったので、もうリファクタリング?はぁ?って感じで、見直しもしなかった。一応、最後に全文を見てみたら、こんな感じになってた。

var elements = [];
var allElements = document.getElementsByTagName("*");
for (var i=0;i<allElements.length;i++){
    if (allElements[i].className == "card") {
        elements.push(allElements[i]);
    }
}
elements.sort()
for(var i=0;i<elements.length;i++){
    var element = elements[i]
    if (element == null) {
	alert('Card element is not found. Check element id.');
    } else {
	var myevent = document.createEvent('MouseEvents');
	myevent.initEvent('click', false, true);
	element.dispatchEvent(myevent);
	alert('Card '+ i +' color is "' + element.style.backgroundColor + '".');
	element.innerText = element.style.backgroundColor;
    }
}
elements.sort(function(a,b){a.innderText - b.innerText})
for(var i=0;i<elements.length;i++){
    for(var j=0;j<elements.length;j++){
	if ( i!=j){
	    if ( elements[i].innerText == elements[j].innerText ){
		var myevent = document.createEvent('MouseEvents');
		myevent.initEvent('click', false, true);
		elements[j].dispatchEvent(myevent);
		elements[i].dispatchEvent(myevent);
		break;
	    }
	}
    }
}

なんか、sort()を無駄に呼んでる箇所もあるし、何なんだオレは一体。まあ、Javascriptこの間、ちょっと勉強してみただけだしとか、言い訳にならない言い訳をしてみる。

以上

実は、この3つの問題は2問解けていれば良いので、無駄なことをしたワケで、むしろチャレンジ問題のslidepuzzleの問題をやらないとボーダーラインを超えられなかったはずなんだけれど、slidepuzzleの問題がちっともできなかったんだから、しょうがない。
ああ、今年も行きたかったなぁ、GDD11JP。1点に泣くことになるとは。