あみだくじを作ってみよう7

   2012/08/05

プログラムを書いている時は、頭が「熱く」なるものです。
一晩おいて、あらためて書いたコードを見なおしてみると
新たな気づきが生まれたりします。

毎度、管理人イガジーです。

そんな訳で、昨日の「あみだをたどる」コードを見なおしてみると
4通りの条件分岐なのですが、処理は2パターンになっているので
工夫の余地がありそうに思えます。即ち4通りを表にしてみると

(i&1)==0
偶数A,C,..
(i&1)==1
奇数B,D,..
(j&1)==0
上段
右をチェック 左をチェック
(j&1)==1
下段
左をチェック 右をチェック

というパターンになっています。

2つの値(ビット値)が、共に0、あるいは共に1か、
片方が0でもう一方が1であるか、をチェックするには XOR を使い

if (((i^j)&1)==0) { // 偶数上段 or 奇数下段
...
}else{ // 偶数下段 or 奇数上段
...
}

と2通りの分岐にまとめることができます。(XOR の演算子は、^ です。)
全体のコード例を以下に示します。

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Label;
import java.awt.Panel;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Random;

public class Amida4 {
	int nn=6;  // 縦棒の数
	int vn=9;   // 横棒の最大本数
	int vd=vn-2; // 横棒の密度
	int x0=40; // 左余白
	int y0=50; // 上余白
	int xlen=210/(nn-1);   // 横棒の長さ
	int ylen=260;  // 縦棒の長さ
	int y1=y0+ylen;  // 縦棒の下端座標
	int yh=(ylen-20)/vn; // 横棒の間隔
	int yh2=yh/2; // 隣の横棒をずらす量

	Frame f;
	MyCanvas cv;
	Graphics g;
	Random rnd=new Random();
	boolean [][] hb=new boolean[10][10];
	boolean gened=false;
	String []gs=new String[10];

	Button gen,drw;
	TextField an=new TextField("6",2);  // 棒数の初期値(6本)

	public Amida4() {
		f=new Frame("Amida");
		f.addWindowListener(new WindowAdapter(){
			public void windowClosing(WindowEvent ev) {
				System.exit(0);
			}
		});
		cv=new MyCanvas(400,500);
		g=cv.b.getGraphics();
		f.add(cv,BorderLayout.CENTER);

		Panel p0=new Panel();
		p0.setLayout(new FlowLayout(FlowLayout.LEFT));
		gen=new Button("生成");
		gen.addActionListener(new genAmida());
		an.addActionListener(new genAmida());

		drw=new Button("描画");
		drw.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (gened) {
					drawAmida(nn-1);
					cv.repaint();
				}
			}
		});
		p0.add(new Label("クジ数:"));
		p0.add(an);
		p0.add(gen);
		p0.add(drw);
		f.add(p0,BorderLayout.NORTH);

		f.setSize(300,420);
		f.setVisible(true);
	} // constructor

	void drawAmida(int n) { // n=あみだ(縦棒)の数
		g.setColor(Color.BLACK);
		g.fillRect(0, 0, cv.b.getWidth(), cv.b.getHeight());
		g.setColor(Color.WHITE);
		for (int i=0;i<nn;++i) { // 縦棒を描画
			int x=xlen*i+x0;
			g.drawLine(x, y0, x, y1);
			g.drawString(Character.toString((char) ('A'+i)), x-3, y0-6); // 棒の上のラベル
			g.drawString(gs[i], x-3, y1+16); // 棒の下のラベル
		}

		for (int i=0;i<n;++i) {
			for (int v=0;v<vn;++v) {
				if (hb[i][v]) {
					int x=xlen*i+x0;
					int y2=v*yh+y0+yh2+(i&1)*yh2;
					g.drawLine(x, y2, x+xlen, y2);
				}
			}
		}
	}

	void trace(int k,boolean up) {
		int y2,x,yt,j,ye;
		int i=k;
		if (i>=nn) i=nn-1;
		y2=x=0;
		g.setColor(Color.CYAN);
		yt=y0;ye=y1;
		if (up) {
			yt=y1;ye=y0;
			g.setColor(Color.YELLOW);
		}
		for (int j2=0;j2<vn*2;++j2) {
			j=j2;
			if (up) {
				j=vn*2-j2-1;
			}
			y2=j*yh2+y0+yh2;
			x=i*xlen+x0;
			if (up) {
				g.fillRect(x-1, y2, 3, yt-y2);
			}else{
				g.fillRect(x-1, yt, 3, yh2);
			}
			yt=y2;
			if (((i^j)&1)==0) { // 偶数上段 or 奇数下段
				// 右を見る
				if (i<(nn-1)){
					if (hb[i][j/2]) { // 右に横棒あり?
						g.fillRect(x, yt-1, xlen+2, 3);
						++i;
					}
				}
			}else{ // 偶数下段 or 奇数上段
				// 左を見る
				if (i>0) { // 左端でなければ左を見る
					if (hb[i-1][j/2]) { // 左に棒あり?
						g.fillRect(x-xlen, yt-1, xlen+2, 3);
						--i;
					}
				}
			}
			cv.repaint();
		}// for j
		x=i*xlen+x0;
		if (up) g.fillRect(x-1,ye,3,yh2);
		else g.fillRect(x-1,yt,3,ye-yt);
	}

	class genAmida implements ActionListener{
		@Override
		public void actionPerformed(ActionEvent e) {
			try {
				nn=Integer.parseInt(an.getText());
			} catch (NumberFormatException ex) {
				nn=6;
			}
			if (nn<2) nn=2;
			if (nn>10) nn=10;
			an.setText(Integer.toString(nn));
			xlen=210/(nn-1);   // 横棒の長さ

			// ゴールの数値セット
			for (int i=0;i<nn;++i) gs[i]=Integer.toString(i);
			for (int i=0;i<nn;++i) { // シャッフル
				int j=rnd.nextInt(nn);
				int k=rnd.nextInt(nn);
				String t=gs[j];
				gs[j]=gs[k]; gs[k]=t;
			}

			for (int rx=0;rx<nn;++rx) { // 横棒の有無をクリア
				Arrays.fill(hb[rx], false);
			}
			for (int i=0;i<(nn-1);++i) {  // 乱数で横棒をセット
				for (int j=0;j<vd;++j) {
					int r=rnd.nextInt(vn);
					hb[i][r]=true;
				}
			}
			// 縦棒のみ描画
			g.setColor(Color.BLACK);
			g.fillRect(0, 0, cv.b.getWidth(), cv.b.getHeight());
			g.setColor(Color.WHITE);
			for (int i=0;i<nn;++i) { // 縦棒を描画
				int x=xlen*i+x0;
				g.drawLine(x, y0, x, y1);
				g.drawString(Character.toString((char) ('A'+i)), x-3, y0-6); // 棒の上のラベル
				g.drawString(gs[i], x-3, y1+16); // 棒の下のラベル
			}
			cv.repaint();
			gened=true;
		}
	}

	public static void main(String[] args) {
		new Amida4();
	}

	public class MyCanvas extends Canvas{
		private static final long serialVersionUID = 1L;
		public BufferedImage b;

		public MyCanvas(int ww,int hh) {
			b=new BufferedImage(ww,hh,BufferedImage.TYPE_INT_RGB);
			this.addMouseListener(new ClickHand());
		}
		public void paint(Graphics g0) {
			g0.drawImage(b,0,0,null);
		}
		public void update(Graphics g0) {
			paint(g0);
		}
	}
	class ClickHand extends MouseAdapter {
		@Override
		public void mouseClicked(MouseEvent me) {
			if (!gened) return;
			int cky=-1;
			int ckx=-1;

			int y=me.getY();
			if ((y>(y0-20))&&(y<y0)) cky=0; // 棒の上(アルファベット)
			else if ((y>y1)&&(y<(y1+20))) cky=1; // 棒の下(数字)

			if (cky>=0) {
				int x=me.getX();
				int x2=(x-x0+8)/xlen;
				int x3=x2*xlen+8+x0;
				if ((x>(x0-8))&&(x<x3)) ckx=x2;
				if (ckx>=0) {
					drawAmida(nn-1);
					trace(ckx,(cky==1));
				}
			}
		}
	}
}

さて、このあみだをたどる部分をアニメーション的にしたいのですが

void repaintwait() {
	cv.repaint();
	try {
		Thread.sleep(80);
	} catch (InterruptedException e) {
		System.out.println("sleep Excepton!");
	}
}

というメソッドを作って、g.fillRect();のあとに追加しても
思うように途中経過が描画されません。

これをうまく処理させるには、trace()メソッドをスレッド化します。
スレッドとは、並行動作する別のプロセスです(という説明では理解困難かもしれませんが)。
具体的には、元クラスに implements Runnable 宣言を追加して
trace()メソッドを run() というメソッド名にします。
そして
new Thread(元クラス).start();とすれば、run() メソッドがスレッドとして動きます。
スレッドは並行動作なので、他の処理(例えば、あみだくじの生成など)と
衝突しないように排他制御をする必要があります。

具体的なコード例は、また明日。

この記事へのコメントはこちら

メールアドレスは公開されませんのでご安心ください。
また、* が付いている欄は必須項目となりますので、必ずご記入をお願いします。

内容に問題なければ、下記の「コメント送信」ボタンを押してください。