日別アーカイブ: 2012-11-20

あみだくじ作成法

初歩からスタートするAndroidアプリ開発

目次

(1)あみだくじとは
(2)あみだくじの作り方
(3)画面設計と処理手順
(4)プログラムの設計とデザイン
(5)あみだくじ描画法
(6)音声出力
(7)実行結果
(8)SourceCode
(9)ファイル一覧
(10)プロジェクト全ファイル
(11)感想・概評
(12)関連情報

本文

以前にC言語であみだくじ作成法の一部を発表しましたが、ANDROID用に作り直しました。くじを作るところから始めます。以前にあみだくじ作成法を公開すると述べたことへの回答でもあります。 

(1)あみだくじとは

あみだくじを初めて体験される方のために、少し解説します。少人数(3~12人ほど)の仲間うちでくじをランダムに作って参加者全員、くじを引き当番人や当選者を決めるやりかたです。くじを作る場合、誰もがくじに手を加えることができ不正が起きないように配慮されます。コンピュータ処理では、乱数などを用いて不正への対策に当てます。

(2)あみだくじの作り方

紙やホワイトボードであみだくじを作る方法を吟味します。ここでは便宜上、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テーブル設定値になります。

(3)画面設計と処理手順

画面設計は参加者の入力とくじの表示並びに当選者の決定処理です。参加者名を入力して締切り後、参加者の人数に問題がなければあみだくじを表示する次のアクティビティに歩を進めます。

(4)プログラムの設計とデザイン

■プログラムの設計

前回に使ったインテントを今回も使用します。参加者の入力画面からくじ表示と当選者の決定画面を起動するために使います。

■画面設計

関数getResources()とgetConfiguration()を使って、スマートフォンの向きを検出して自動的に縦型、横型用画面が制御されます。
1)縦型画面

2)横型画面

■主なプログラムのモジュール構成

・delay.java           ディレー関数
・FirstActivity.java   参加者入力画面
・SecondActivity.jav   あみだくじ表示画面
(5)あみだくじ描画法

あみだくじを描画するには、あみだくじの作り方をプログラムに表現すべく、具体的に以下に示します。

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)最後の参加者の処理が終えたら終了です。

(6)音声出力

処理の切れ目やエラー発生、くじが三叉路を通過するときに音声が出力されます。C言語のBeep関数がなく、あらかじめ音声ファイルを読み込んで音データを用意して置かなければなりません。SoundPoolオブジェクトを利用し、サウンドファイルを読み込み音声を再生します。音声における書式付きprint関数のような機能はまだマスターできてないので、当選者をアナウンスするために10人ぶんの10個のファイルをあらかじめ用意しています。

(7)実行結果

(8)SourceCode

ソースコードを以下に示します。
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
(9)ファイル一覧

ファイル一覧を階層構造形式に示します。音声ファイルが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
(10)プロジェクト

プロジェクト内の全ファイルをZIP形式で一挙、公開です。 AmidaKuji.ZIP

(11)感想・概評

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点を出品しています。
①あみだくじ
②抽選機
③福引き管理ソフト

(12)関連情報

C,Java,JavaScriptによるあみだくじや福引きソフトの作り方をまとめてあります。
※※※※※