あみだくじを作ってみよう7
プログラムを書いている時は、頭が「熱く」なるものです。
一晩おいて、あらためて書いたコードを見なおしてみると
新たな気づきが生まれたりします。
毎度、管理人イガジーです。
そんな訳で、昨日の「あみだをたどる」コードを見なおしてみると
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() メソッドがスレッドとして動きます。
スレッドは並行動作なので、他の処理(例えば、あみだくじの生成など)と
衝突しないように排他制御をする必要があります。
具体的なコード例は、また明日。
この記事へのコメントはこちら