初歩からスタートするAndroidアプリ開発
目次
(1)あみだくじとは
(2)あみだくじの作り方
(3)画面設計と処理手順
(4)プログラムの設計とデザイン
(5)あみだくじ描画法
(6)音声出力
(7)実行結果
(8)SourceCode
(9)ファイル一覧
(10)プロジェクト全ファイル
(11)感想・概評
(12)関連情報
本文
以前にC言語であみだくじ作成法の一部を発表しましたが、ANDROID用に作り直しました。くじを作るところから始めます。以前にあみだくじ作成法を公開すると述べたことへの回答でもあります。
あみだくじを初めて体験される方のために、少し解説します。少人数(3~12人ほど)の仲間うちでくじをランダムに作って参加者全員、くじを引き当番人や当選者を決めるやりかたです。くじを作る場合、誰もがくじに手を加えることができ不正が起きないように配慮されます。コンピュータ処理では、乱数などを用いて不正への対策に当てます。
紙やホワイトボードであみだくじを作る方法を吟味します。ここでは便宜上、6人の参加者の場合を述べます。
1)参加者ぶん6本の縦線を引きます。
2)縦線と縦線の間(枠内)に、間隔4~9で4本ほどの横線を引きます。
3)それを全枠内で実行します。
4)隣どおしで横線の縦位置が同じになることを避けます。
5)くじを引く人は0~5の任意の番号を一意的に確保し引当てます。
6)引いたくじの番号を決めるには各々、上から下に線をたどります。
7)三叉路に出会ったら右、左、下に進み、終点まで繰り返します。
8)終点に宝物やミッション指示書があれば、当選者になります。
9)上のくじでは当選者は2番者になります。
上記の処理をアルゴリズムに反映させます。
1)上図に示すように6×36のマトリックスを作ります。10人参加なら10x36になります。
2)マトリックス(node[x][y])の要素をすべてゼロクリアします。x:0~5,y:0~35。
3)node[x][y]のうち、左端のx=0を除きx:1~5で、横線用のポイントを決定します。
4)乱数を順に4箇発生させ、その値を縦位置に設定します。
5)間隔は4~9、左隣と同じ値を避けます。
6)縦位置の値が36以上になったら4回発生させる前に終了します。
7)乱数は時間データを種にして再現性と人為的な関与を排除します。
8)node[x][y]=2,node[x-1][y]=1に設定します。1は横線の始まり、2は終点です。
9)上記のあみだくじでは右図のnodeテーブル設定値になります。
画面設計は参加者の入力とくじの表示並びに当選者の決定処理です。参加者名を入力して締切り後、参加者の人数に問題がなければあみだくじを表示する次のアクティビティに歩を進めます。
■プログラムの設計
前回に使ったインテントを今回も使用します。参加者の入力画面からくじ表示と当選者の決定画面を起動するために使います。
■画面設計
関数getResources()とgetConfiguration()を使って、スマートフォンの向きを検出して自動的に縦型、横型用画面が制御されます。
1)縦型画面
■主なプログラムのモジュール構成
・delay.java ディレー関数 ・FirstActivity.java 参加者入力画面 ・SecondActivity.jav あみだくじ表示画面
あみだくじを描画するには、あみだくじの作り方をプログラムに表現すべく、具体的に以下に示します。
1)参加者ぶん、ここではxが0から順に5まで繰り返します。
2)縦方向に0から35まで36行あり、順に描画対象かを確認します。
3)2次元配列のnodeの値は0が多く、他は1か2です。
4)1つのxに対し上から順に検索し、0以外が現れたら横線を引きます。
5)値が1ならば右に、2ならば左に向かって引きます。
6)横線はCanvasオブジェクトのdrawLinesを使用します。
7)次に、検索した値により探索地点を1加算(減算)して右(左)に移動します。
8)yは加算され終端(35)に達するまで繰り返します。
9)一人のくじを引き終り、宝物や地雷があれば、当選者に対する処理をします。
10)xを加算して次の人のくじを描画します。
11)最後の参加者の処理が終えたら終了です。
処理の切れ目やエラー発生、くじが三叉路を通過するときに音声が出力されます。C言語のBeep関数がなく、あらかじめ音声ファイルを読み込んで音データを用意して置かなければなりません。SoundPoolオブジェクトを利用し、サウンドファイルを読み込み音声を再生します。音声における書式付きprint関数のような機能はまだマスターできてないので、当選者をアナウンスするために10人ぶんの10個のファイルをあらかじめ用意しています。
ソースコードを以下に示します。
1.delay
package example.android.kuji; public class delay { public static void time(int millis) { try { Thread.sleep(millis); } catch(InterruptedException e){System.out.println("delay failed");} } }
2.FirstActivity.java
package example.android.kuji; import android.app.Activity; import android.content.Intent; import android.media.AudioManager; import android.media.SoundPool; import android.os.Bundle; import android.text.Editable; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; class gv{ /** global variable **/ public static SoundPool sound; // SoundPool public static int Si0; // ちょうはんコマ… public static int Si1; // 乱数を発生させ… public static int Si2; // pyon public static int Si3; // bororo public static int Idx[]={0,0,0,0,0,0,0,0,0,0}; // no0~no9 } public class FirstActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState){// onCreateメソッド(画面初期表示イベントハンドラ) super.onCreate(savedInstanceState); // スーパークラスのonCreateメソッド呼び出し setContentView(R.layout.firstlayout); // レイアウト設定ファイルの指定 Button button = (Button) findViewById(R.id.button1);// ボタンオブジェクト取得 button.setOnClickListener(new LotteryListener());// ボタンオブジェクトにクリックリスナー設定 } @Override public void onResume() { super.onResume(); gv.sound = new SoundPool(2, AudioManager.STREAM_MUSIC, 0); gv.Si0 = gv.sound.load(this, R.raw.chohan, 1); gv.Si1 = gv.sound.load(this, R.raw.ransuu, 1); gv.Si2 = gv.sound.load(this, R.raw.pyon, 1); gv.Si3 = gv.sound.load(this, R.raw.bororo, 1); gv.Idx[0] = gv.sound.load(this, R.raw.no0, 1); gv.Idx[1] = gv.sound.load(this, R.raw.no1, 1); gv.Idx[2] = gv.sound.load(this, R.raw.no2, 1); gv.Idx[3] = gv.sound.load(this, R.raw.no3, 1); gv.Idx[4] = gv.sound.load(this, R.raw.no4, 1); gv.Idx[5] = gv.sound.load(this, R.raw.no5, 1); gv.Idx[6] = gv.sound.load(this, R.raw.no6, 1); gv.Idx[7] = gv.sound.load(this, R.raw.no7, 1); gv.Idx[8] = gv.sound.load(this, R.raw.no8, 1); gv.Idx[9] = gv.sound.load(this, R.raw.no9, 1); } // クリックリスナー定義 class LotteryListener implements OnClickListener { // onClickメソッド(ボタンクリック時イベントハンドラ) public void onClick(View vw) { Log.v("FirstActivity ","v.fortune=" + v.fortune+" v.LineCount=" + v.LineCount); if(v.fortune == 99) { // SecondActivityが実行されている。 v.fortune = 0; // reset fortune gv.sound.play(gv.Si3, 1.0f, 1.0f, 0, 0, 1.0f); // S bororo delay.time(1000); System.exit(0); } // テキストボックスオブジェクト取得 EditText input = (EditText) findViewById(R.id.nametext); Editable str = input.getText(); String string = str.toString(); // 入力情報をトースト機能で画面表示 // インテントの生成(呼び出すクラスの指定) Intent intent = new Intent(FirstActivity.this, SecondActivity.class); // 選択された値をインテントに設定 intent.putExtra("PARTY_MEMBER", string); //Log.v("tag_name", "go new screen."); // 次のアクティビティの起動 startActivity(intent); } } public boolean onKeyDown(int keyCode, KeyEvent event){ // Back-key Log.v("FirstActivity-onKeyDown", "fortune="+v.fortune+" KeyCode="+keyCode); if(keyCode == KeyEvent.KEYCODE_BACK) v.fortune = 0; // reset fortune return super.onKeyDown(keyCode, event); } }
3.SecondActivity.java
package example.android.kuji; // Amida.java ---> Kuji.java import java.util.Random; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Bundle; import android.util.Log; import android.view.View; class v{ /** global variable **/ public static int index=2; // side, vertical public static int x=5; // position-x public static int y=450; // position-y public static int LineCount=0; // party_member public static String string; // input data public static int fortune=0; // fortune-number public static int i=0; // menber/-id public static int end=-1; // end-text public static int node[][]= { // random-value, 10*46 {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},// 0 {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},// 1 {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},// 2 {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},// 3 {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},// 4 {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},// 5 {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},// 6 {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},// 7 {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},// 8 {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} // 9 }; public static int co[]={Color.YELLOW , Color.BLUE , Color.CYAN , Color.DKGRAY , Color.GRAY , Color.GREEN , Color.LTGRAY , Color.MAGENTA , Color.RED , Color.BLACK}; } // MainClass public class SecondActivity extends Activity { /** Called when the activity is first created. **/ @Override public void onCreate(Bundle savedInstanceState) { // ?????????? super.onCreate(savedInstanceState); // make instance of ViewClass. MyCircleView view = new MyCircleView(getApplication()); setContentView(view); // set View. Log.v("SecondActivity", "LineCount=" + v.LineCount); // calc party-member. Intent data = getIntent(); // get additinal info of intent; Bundle extras = data.getExtras(); // get party-member from input-data. v.string = extras.getString("PARTY_MEMBER"); Resources resources = getResources(); // screen direction Configuration config = resources.getConfiguration(); v.index=2; v.x = 5; v.y = 450; v.LineCount = 0; v.i = 0; switch(config.orientation) { case Configuration.ORIENTATION_PORTRAIT: v.index = 0; break; case Configuration.ORIENTATION_LANDSCAPE: v.index = 1; v.x = 500; v.y = 20; break; } int end = -1; // line of input. for(int i=0; i int top = end + 1; int wrk = v.string.indexOf('\n', top); if(wrk == -1) break; end = wrk; ++v.LineCount; } v.fortune = 99; // for abnormal-finish Log.v("SecondActivity", "LineCount:" + v.LineCount+" Fortune="+v.fortune); if((v.LineCount < 3) || (v.LineCount > 10)){ gv.sound.play(gv.Si3, 1.0f, 1.0f, 0, 0, 1.0f); // S bororo delay.time(1000); System.exit(1);/*return;finish();*/ } long seed = System.currentTimeMillis(); // current-time, milisecond Random rnd = new Random(seed); v.fortune = rnd.nextInt(v.LineCount); // random:0~(v.LineCount-1) for(int i=0; i for(int j=0; j } int k=0; // yoko-line:4 /* gen */for(int i=1; i int d=0; // d:0~45, 46 for(int j=0; j= 46) {k=1;break;} // finish,yoko-line:3 if(v.node[i-1][d]==2){ // if equal, alert. d += 4; if(d >= 46) {k=1;break;} // finish,yoko-line:3 if(v.node[i-1][d]==2){ ++d; // if equal, alert. if(d >= 46) {k=1;break;} // finish,yoko-line:3 } } //Log.v("SecondActivity-3", "i="+i+"j="+j+"d="+d); v.node[i-1][d]=1; // start v.node[i][d]=2; // end } /* gen */} // ------------------- if(k==0) k = gv.Si1; // ransuu else k = gv.Si0; // chohan gv.sound.play(k, 1.0f, 1.0f, 0, 0, 1.0f); // L biyoon delay.time(2800); } // ?????????? } // display class class MyCircleView extends View { // View public MyCircleView(Context context) { // initialize View super(context); setFocusable(true); } /**/protected void onDraw(Canvas canvas) { // actualy,method of display. Paint paint = new Paint(); // make paint-object super.onDraw(canvas); canvas.drawColor(Color.WHITE); // set back-color. for(int i=0; i int x=i*50+8; // x-position float[] pts1 = {x+7, 25, x+7, 346}; // 2.vertical line canvas.drawLines(pts1, paint); // basic line if(i != (v.LineCount-1)){ // ########## for(int j=0; j if(v.node[i][j]==1){ // amida-yoko float[] pts0 = {x+7, j*7+25, x+57, j*7+25}; // yoko canvas.drawLines(pts0, paint); // yoko line } } } // ########## } // display basic form. paint.setStrokeWidth(4.0f); // line's thickness if(v.i == v.LineCount){ // draw amida-line. for(v.i=0; v.i display(gv.Si2, paint, canvas); // pyon, all } // final-proc int end = -1; // display member-name paint.setColor(Color.BLACK); // black line paint.setTextSize(24); // member name paint.setTypeface(Typeface.DEFAULT_BOLD); // thin for(int i=0; i paint.setColor(v.co[i]); // COLOR int x=i*50+8; // display character int top = end + 1; end = v.string.indexOf('\n', top); String s = v.string.substring(top, end); // MEMBER-NAME canvas.drawText(i + ":" + s, v.x+20, v.y+i*25+25, paint); String str = String.valueOf(i); // 0 1 2 3 4 5 6 if(i == v.fortune){ Log.v("SecondActivity", "i=" + i+" Fortune="+v.fortune); StringBuffer sb = new StringBuffer(str); str = sb.reverse().toString(); } canvas.drawText(str, x, 20, paint); } paint.setColor(v.co[5]); // GREEN canvas.drawText("笘", v.x, v.y+v.end*25+25, paint); // atari paint.setTextSize(20); // make display-object. paint.setColor(Color.MAGENTA); // display party_member canvas.drawText("蜿ょ刈閠・・" + v.LineCount + "莠コ縺ァ縺吶",v.x, v.y, paint); gv.sound.play(gv.Idx[v.end], 1.0f, 1.0f, 0, 0, 1.0f); // s no0 delay.time(250); paint.setTextSize(24); // lucky mark paint.setColor(v.co[5]); // GREEN canvas.drawText("笘", v.fortune*50+2, 395, paint); // atari paint.setColor(Color.BLUE); // announce lucky-man. canvas.drawText("蠖馴∈閠・・"+v.end+"逡ェ縺ァ縺吶", 5, 417, paint); v.fortune = 99; // if you down back-key, v.fortune=0. v.i = 0; return; /*System.exit(0);*/ } else{ // 0~(v.LineCount-1) display(gv.Si2, paint, canvas); // pyon ++v.i; } invalidate(); // repeat /**/} private void display(int si2, Paint paint, Canvas canvas) { // draw a kuji. gv.sound.play(si2, 1.0f, 1.0f, 0, 0, 1.0f); // s pyon delay.time(500); int y=0; int x=v.i; paint.setColor(v.co[v.i]); // COLOR for(int j=0; j yoko if(v.node[x][j]>0){ float[] pts1 = {x*50+15, y*7+25, x*50+15, j*7+25}; // tate canvas.drawLines(pts1, paint); // tate line y=j; int d=x; if(v.node[x][j]==1) ++x; else --x; float[] pts2 = {d*50+15, j*7+25, x*50+15, j*7+25}; // yoko canvas.drawLines(pts2, paint); // yoko line } } float[] pts1 = {x*50+15, y*7+25, x*50+15, 46*7+24}; // tate canvas.drawLines(pts1, paint); // tate line paint.setTextSize(20); // make display-object. String str = String.valueOf(v.i); // 6 5 4 3 2 1 0 canvas.drawText(str, x*50+8, 372, paint); if(x==v.fortune) v.end = v.i; // save forune-number } } // View
ファイル一覧を階層構造形式に示します。音声ファイルがres\rawフォルダに収納されていることに注目ください。
.\AMIDAKUJI │ .classpath │ .project │ amidakuji.apk │ AndroidManifest.xml │ default.properties │ lint.xml │ proguard.cfg │ project.properties │ ├─assets ├─bin │ │ AndroidManifest.xml │ │ classes.dex │ │ example.android.kuji.FirstActivity.apk │ │ resources.ap_ │ │ │ ├─classes │ │ └─example │ │ └─android │ │ └─kuji │ │ BuildConfig.class │ │ delay.class │ │ FirstActivity$LotteryListener.class │ │ FirstActivity.class │ │ gv.class │ │ MyCircleView.class │ │ R$array.class │ │ R$attr.class │ │ R$drawable.class │ │ R$id.class │ │ R$layout.class │ │ R$raw.class │ │ R$string.class │ │ R.class │ │ SecondActivity.class │ │ v.class │ │ │ └─res │ ├─drawable-hdpi │ │ icon.png │ │ │ ├─drawable-ldpi │ │ icon.png │ │ │ ├─drawable-mdpi │ │ icon.png │ │ │ └─drawable-xhdpi │ icon.png │ ├─gen │ └─example │ └─android │ └─kuji │ BuildConfig.java │ R.java │ ├─res │ ├─drawable │ ├─drawable-hdpi │ │ icon.png │ │ │ ├─drawable-ldpi │ │ icon.png │ │ │ ├─drawable-mdpi │ │ icon.png │ │ │ ├─drawable-xhdpi │ │ icon.png │ │ │ ├─layout │ │ firstlayout.xml │ │ main.xml │ │ secondlayout.xml │ │ │ ├─raw │ │ bororo.wav │ │ buin.wav │ │ chohan.wav │ │ no0.wav │ │ no1.wav │ │ no2.wav │ │ no3.wav │ │ no4.wav │ │ no5.wav │ │ no6.wav │ │ no7.wav │ │ no8.wav │ │ no9.wav │ │ pyon.wav │ │ ransuu.wav │ │ │ └─values │ strings.xml │ └─src └─example └─android └─kuji delay.java FirstActivity.java SecondActivity.java
プロジェクト内の全ファイルをZIP形式で一挙、公開です。 AmidaKuji.ZIP
1.あみだくじは遊び心で始まります。ひとりで全員のかばん6個を運ぶなど、仲間うちで負荷の大きい使役などは避けましょう。
2.のやっかいになるようなトトカルチョっぽいものもやめましょう。
3.あみだくじは、ひとりの当たり人を決める場合が多いのですが、冒頭の芋煮会準備くじにあるように多対多の対応づけに応用でき、ホテルの部屋割り、宴会の席順設定にも使えます。
4.あみだくじを記録するには、結果をファイル化してください。
5.あみだくじは、結果よりも途中のワクワク感を楽しむものです。このページを土台にしてビジュアル的に改良して楽しんでください。
6.くふうすれば、プロ野球ドラフト会議で競合者があった場合に、交渉権を決定するくじ引きに採用されるかも知れません。
7.あみだくじの参加者名を入力し終えて、「締め切り!」を押すと「乱数を発生させあみだくじ作成中です」がアナウンスされますが、稀に妙なメッセージが出力されことがありますが、これは開発者の遊び心です。ソースコードを詳しく眺めると出力条件がわかります。
8.javaはCと違ってstatic変数が初期化されず前回に実行されたメモリに残っている状態になり、Cから移植した場合に悩みの種となるようです。
9.今回の公開は「あみだくじ作成法」です。完成品ではありません。ご利用には利用規定をお読みください。
10.画面上の制限からテーブルサイズなど、解説した内容と実際のソースコードは異なっています。
11.くじ参加者名を全角文字で入力する代わりにアルファベットで、N,SA,A,SU,Yなどと入力する方法があります。
12.くじ運営者は最後に残ったものを引き当てた方が公平でしょう。
13.この作成手法を用いたあみだくじアプリを開発してGoogle Playに公開します。
2014-05-14追記
Google Playに以下の3点を出品しています。
①あみだくじ
②抽選機
③福引き管理ソフト
②電子式ふくびき管理ソフト
③スマホ用抽選器取り扱い説明書
④抽選箱から宝もの
⑤ゲーム考
⑥電子式福引き管理ソフト
⑦ブロックサイン式パスワード
⑧電子式福引き管理ソフト封切
⑨電子式福引き管理ソフト改2
⑩阿弥陀
⑪あみだくじ
⑫あみだくじ雑感
⑬あみだくじ参加人数
⑭あみだくじを引く取り扱い説明書
⑮あみだくじを引く
⑯多人数あみだくじ