Java Tower of Hanoi 动画绘制

来源:互联网 发布:excel数据有效性 编辑:程序博客网 时间:2024/06/05 16:41

本文使用Java语言,利用状态空间法,采用面向对象编程,实现Hanoi动画绘制。

程序实现目标: 用户界面交互,单步执行,后退执行,过程动态演示。


首先对于盘片:笔者采用面向对象式的表示,盘子有盘号,长度,宽度,颜色等信息.

import java.awt.Color;
public class Plate {

  //  盘号,长度,宽度,颜色信息
  private int num;
  private int length;
  private int width;
  private Color color;
  // 通过成员方法实现盘片信息的设置与获取
   public Plate(int n) 
    {
                this.num = n;
    }

            public int getLength() {

return length;

  }

  public void setLength(int length) {

this.length = length;

  }

  public int getWidth() {

return width;

  }

  public void setWidth(int width) {

this.width = width;

  }

public Color getColor() {

return color;

}

public void setColor(int r,int g,int b,int a) {

this.color = new Color(r,g,b,a);

}  

}

实现用户界面:

                                                                              

Hanoi动画效果实现代码:

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.LinkedList;
import java.util.Queue;

import javax.swing.*;

public class AI
{
public static void main(String args[])
{
ManagerFrame managerFrame = new ManagerFrame();
managerFrame.show();
}
}


class ManagerFrame extends JFrame implements ActionListener
{
private JLabel Title = new JLabel("Tower of HAnoi",SwingConstants.CENTER);
private JLabel Label1 = new JLabel("盘数:",SwingConstants.RIGHT);
private JTextField Text1 = new JTextField(10);
private JButton Confirm = new JButton("确定");

private JButton SingleStep = new JButton("单步");
private JButton Retreat = new JButton("后退");
private JButton Reset = new JButton("复位");
private JButton Result = new JButton("结果");

private JLabel sum = new JLabel("sum");
private JTextField sum1 = new JTextField(10);
private JTextField sum2 = new JTextField(10);
private JTextField sum3 = new JTextField(10);
private JTextField sum4 = new JTextField(10);

private JLabel resultlabel = new JLabel("显示执行结果",SwingConstants.CENTER); 
private JTextArea textarea = new JTextArea(50,80);

private int n;
private int value;
private int statecount;
private int statevalue;
private int nextstatevalue;
private int stept;
private Queue<Integer> queue = new LinkedList<Integer>();
private int[] temp;
private int[] state;
private int[] orderstate;
// 采用泛型编程,利用List承载每根柱子上,某一状态下的盘子(盘子作为对象包含着其自己的信息)
public List<Plate> cloum0 = new ArrayList<Plate>();
public List<Plate> cloum1 = new ArrayList<Plate>();
public List<Plate> cloum2 = new ArrayList<Plate>();
public List<Plate> cloum = new ArrayList<Plate>();  // 盛装创建的盘子信息(与n有关)以便在修改中获取原始信息
public int[] nowtemp; // 该数组用于装载当前状态下三进制数序列
private DrawCanvas draw = new DrawCanvas();  // 自定义画布对象,利用其更新实现动画效果
private int clickcount; // 记录单步执行的次数,用于实现单步效果和后退效果显示。

public ManagerFrame()
{
setTitle("Tower of HAnoi");
setSize(460,630);
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
}); 
// 为各个按钮设置事件监听
Confirm.addActionListener(this);
Result.addActionListener(this);
SingleStep.addActionListener(this);
Retreat.addActionListener(this);
Reset.addActionListener(this);
CreateView();
}
// 绘制用户界面
public void CreateView()
{
Container layout = getContentPane();
layout.setLayout(null);

textarea.setEditable(false);
textarea.setLineWrap(false);

layout.add(Title);
Title.setBounds(160,5,110,25);

layout.add(Label1);
Label1.setBounds(100,40,40,25);

layout.add(Text1);
Text1.setBounds(160,40,60,25);
layout.add(Confirm);
Confirm.setBounds(240,40,60,25);

layout.add(SingleStep);
SingleStep.setBounds(70,80,60,25);
layout.add(Retreat);
Retreat.setBounds(145,80,60,25);
layout.add(Reset);
Reset.setBounds(220,80,60,25);
layout.add(Result);
Result.setBounds(295,80,60,25);

layout.add(resultlabel);
resultlabel.setBounds(190,400,80,25);

JScrollPane scrollpane = new JScrollPane(textarea);
layout.add(scrollpane);
scrollpane.setBounds(70,430,300,150);

layout.add(draw);  // 定义画布区间,动画效果就是不断更新当前区间实现的
draw.setBounds(new Rectangle(0,160,420,230));
}
     // 设置按钮点击,执行的函数
public void actionPerformed(ActionEvent e)
{ // 选择某种执行方式,应当将相应List清空,以免上次执行结果对此产生干扰
if(e.getSource()==Confirm)
{  // 单纯的如此设置事件监听:会执行两次(一起一落)??NO
// 什么也没输入,返回""对象而不是null??
cloum0.clear();
cloum1.clear();
cloum2.clear();
cloum.clear();  // 这里要添加这一个,不然修改n之后,会出错(创造盘子总是从前往后塞的)
  // 如果原来有盘子,从头建的会在其后面添加,导致号码出错,重复
if(Text1.getText().trim()!=null)
{
n = Integer.parseInt(Text1.getText().trim());
textarea.setText("盘子数为:"+String.valueOf(n));
CreatePlate(n);   // 根据n值来创建盘子,盘子的信息与盘号有关
        InitGet();  // 初始化各成员变量
GetState(); // 获得所需的状态值序列,保存在相应数组中
draw.repaint();
}
}else if(e.getSource()==Result)
{
if(stept!=0)
{   
for(int j=stept-1;j>=0;--j)
{
cloum0.clear();
   cloum1.clear();
   cloum2.clear();
int m = orderstate[j];
for(int i=n-1;i>=0;--i)
{
nowtemp[i] = m%3;
m /= 3;
}
// 先确定:当前状态值下底盘所在的位置,逐步向上确定盘子的位置.
for(int i=n-1;i>=0;--i)
{  // 在nowtemp数组中最底盘的下标为0 (逐步从大盘到小盘确定各自的位置)
if(nowtemp[i]==0)
{
cloum0.add(cloum.get(n-1-i));
}else if(nowtemp[i]==1)
{
cloum1.add(cloum.get(n-1-i));
}else
{
cloum2.add(cloum.get(n-1-i));
}
}
try {
//draw.repaint();

// 这里要注意,在循环中要强制实现重绘,调用update()方法,否则无法实现每次都调用paint()

draw.update(draw.getGraphics());
Thread.sleep(1500);
} catch (InterruptedException e1) {
System.out.println(e1.toString());
}

}


}
}else if(e.getSource()==SingleStep)
{
if(clickcount<=stept)
{  // 这里不能是<,否则当clickcount=stept时,执行单步会陷入不断向List中添加盘子的状态
clickcount++;
cloum0.clear();
   cloum1.clear();
   cloum2.clear();
}
if(clickcount<=stept) 
{
int m = orderstate[stept-clickcount];
// System.out.println("*******m = "+m);
for(int i=n-1;i>=0;--i)
{
nowtemp[i] = m%3;
m /= 3;
}
for(int i=n-1;i>=0;--i)
{  // 在nowtemp数组中最底盘的下标为0 (逐步从大盘到小盘确定各自的位置)
if(nowtemp[i]==0) // 这里nowtemp数组访问,是从小盘到大盘,cloum也是从小盘到大盘
{                 // 结果存储在List中也是从小到大的,不过绘图时是从后往前取
cloum0.add(cloum.get(n-1-i));
}else if(nowtemp[i]==1)
{
cloum1.add(cloum.get(n-1-i));
}else
{
cloum2.add(cloum.get(n-1-i));
}
}
draw.repaint();
}

}else if(e.getSource()==Retreat)
{
if(clickcount>0) //  这里不能>1否则会导致=1事,陷入不断重复绘制的境地(重复向List中添加)
{ clickcount--;
 cloum0.clear();
     cloum1.clear();
     cloum2.clear();
}
if(clickcount>0)
{
int m = orderstate[stept-clickcount];
for(int i=n-1;i>=0;--i)
{
nowtemp[i] = m%3;
m /= 3;
}
for(int i=n-1;i>=0;--i)
{  
if(nowtemp[i]==0) 
{                 
cloum0.add(cloum.get(n-1-i));
}else if(nowtemp[i]==1)
{
cloum1.add(cloum.get(n-1-i));
}else
{
cloum2.add(cloum.get(n-1-i));
}
}
draw.repaint();
}
}else if(e.getSource()==Reset)
{
 cloum0.clear();
     cloum1.clear();
     cloum2.clear();
     cloum.clear();
     clickcount=0;
     draw.repaint();  
}
}
     // 根据n值来创建盘子序列
private void CreatePlate(int n) {
for(int i=0;i<n;++i)
{  // 盘子从小盘排列到大盘,cloum中下标越大,代表盘越大
Plate plate = new Plate(i);
plate.setColor(255-12*i,20*i+15,22+18*i,220);
plate.setLength(7);
plate.setWidth(8*i+7);
cloum0.add(plate);
cloum.add(plate);
}
}

   // 初始化话各个状态值
private void InitGet() {
statecount = 1;
for(int i=0;i<n;++i)
    statecount *= 3;

temp = new int[n];
state = new int[statecount];
//System.out.println(state.length);
orderstate = new int[statecount];

nowtemp = new int[n];

statevalue=0;
clickcount=0;

for(int i=0;i<statecount;++i)
state[i]=-1;

queue.add(statevalue);
state[statevalue]=-2;
GetState();
}
   // 获得状态空间法执行后的状态值序列,状态值保存在orderstate数组中
private void GetState() {
int add;
while(queue.peek()!=null)
{
value=queue.remove();
statevalue=value;
for(int i=n-1;i>=0;--i)
{
temp[i]=value%3;
value /= 3;
}
if(temp[n-1]==0)
{
nextstatevalue = statevalue+1;
IsExist(nextstatevalue,statevalue);
nextstatevalue = statevalue+2; 
IsExist(nextstatevalue,statevalue);
add=3;
for(int i=n-2;i>=0;--i)
{
if(temp[i]==1)
{
nextstatevalue = statevalue + add;
IsExist(nextstatevalue, statevalue);
break;
}
else if (temp[i]==2)
{
nextstatevalue = statevalue - add;
IsExist(nextstatevalue, statevalue);
break;
}
add *= 3;
}

}else if(temp[n-1]==1)
{
   nextstatevalue = statevalue-1; 
IsExist(nextstatevalue,statevalue);
nextstatevalue = statevalue+1; 
IsExist(nextstatevalue,statevalue);
add=3;
for(int i=n-2;i>=0;--i)
{
if(temp[i]==0)
{
nextstatevalue = statevalue + add*2;
IsExist(nextstatevalue, statevalue);
break;
}
else if (temp[i]==2)
{
nextstatevalue = statevalue - add*2;
IsExist(nextstatevalue, statevalue);
break;
}
add *= 3;
}
 }
else
{
   nextstatevalue = statevalue-2; 
IsExist(nextstatevalue,statevalue);
nextstatevalue = statevalue-1; 
IsExist(nextstatevalue,statevalue);
add=3;
for(int i=n-2;i>=0;--i)
{
if(temp[i]==0)
{
nextstatevalue = statevalue + add;
IsExist(nextstatevalue, statevalue);
break;
}
else if (temp[i]==1)
{
nextstatevalue = statevalue - add;
IsExist(nextstatevalue, statevalue);
break;
}
add *= 3;
}
}
}
statevalue = statecount-1;
int k = 0;   // 这里利用了状态值前后的关系
while(state[statevalue]!=-2)
{
orderstate[k]=statevalue;
statevalue = state[statevalue];
++k;
}
stept = k+1;
orderstate[k]=statevalue;
}
    // 当前入队的状态值是否如果队,如果没有就修改标志
private void IsExist(int next0, int state0) {
if(state[next0]==-1)

state[next0]=state0;
queue.add(next0);
}

}
   // 自定义画布类,实现重绘的函数回调
public class DrawCanvas extends Canvas {
   // 这里每个状态值对应List值都在变化,所以根据List内容和状态值先后顺序实现动态效果
public void paint(Graphics g)
{
g.drawLine(80, 20, 80, 200);
g.drawLine(210, 20, 210, 200);
g.drawLine(340, 20, 340, 200);
g.drawLine(10, 200, 420, 200);
// 绘制柱子上的盘片时:都是依据大盘对应下标大的原则进行的。
// 也即List中盘子要依次增大
if(!cloum0.isEmpty())
{
for(int i=cloum0.size()-1,k=1;i>=0;--i,++k)
{
Plate plate = cloum0.get(i);
g.setColor(plate.getColor());
g.fillRect(80-plate.getWidth()/2, 200-plate.getLength()*k, plate.getWidth(), plate.getLength());
}

}
if(!cloum1.isEmpty())
{
for(int i=cloum1.size()-1,k=1;i>=0;--i,++k)
{
Plate plate = cloum1.get(i);
g.setColor(plate.getColor());
g.fillRect(210-plate.getWidth()/2, 200-plate.getLength()*k, plate.getWidth(), plate.getLength());
}
}
if(!cloum2.isEmpty())

for(int i=cloum2.size()-1,k=1;i>=0;--i,++k)
{
Plate plate = cloum2.get(i);
g.setColor(plate.getColor());
g.fillRect(340-plate.getWidth()/2, 200-plate.getLength()*k, plate.getWidth(), plate.getLength());
}
}
}
}
}

程序大体思路:找到需要的状态值序列根据序列来重绘(每一个值,绘制一次),同时在单步和回退时要避免前后影响,以及数组下标"越界"

不太清楚的可以参见我另外一篇文章,对于状态算法的注解。


0 0
原创粉丝点击