一.任务:
设计一个汉诺塔演示软件,自动展示汉诺塔搬运的全过程
二.要求:
(1)实现可视化界面,完成与用户的交互,展示汉诺塔搬运过程。
(2)支持盘子数量自定义设置。
(3)提供开始按钮,点击开始按键,开始展示搬运过程。
(4)提供复位按键,在搬运过程中,点击复位键可以重新回到开始搬运前的状态。
三.问题分析:
- 汉诺塔问题求解算法:汉诺塔问题是一个经典的递归问题,涉及递归算法的设计和实现。
- 图形用户界面(GUI)设计:使用适当的GUI库和工具进行界面设计,包括窗口、控件(如按钮、输入框等)的布局和样式设置。
- 用户交互设计:通过事件处理机制实现用户与软件之间的交互,包括按钮点击事件的捕获和处理。
四.代码:
HanoiWindow类:
package hannuota;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JOptionPane;
public class HanoiWindow extends JFrame implements ActionListener {
//继承JFrame并且实现ActionListener接口
Tower tower = null;
int amountOfDisc = 1;
char[] towerName = { 'A', 'B', 'C' };
JButton renew = null;
JButton autoButton = null;
JPanel center = new JPanel(); //中心面板,放置中心部分
JTextField discCountField; //输入盘子数量的文本框
HanoiWindow() {
tower = new Tower(towerName);
tower.setAmountOfDisc(amountOfDisc);//创建tower
tower.setMaxDiscWidth(120);
tower.setMinDiscWidth(50);
tower.setDiscHeight(16);
tower.putDiscOnTower(); //盘子放在初始位置
add(tower, BorderLayout.CENTER); //将 Tower 添加到中心位置
renew = new JButton("复位");
autoButton = new JButton("开始");
renew.addActionListener(this); //添加事件监听器,以便在按钮被点击时触发相应的操作。
autoButton.addActionListener(this);
JPanel north = new JPanel(); // 创建北部面板
JLabel discCountLabel = new JLabel("盘子数量:"); //创建标签
discCountField = new JTextField(3);
discCountField.setText(Integer.toString(amountOfDisc));
north.add(autoButton);
north.add(renew);
north.add(discCountLabel); // 在北部面板中添加盘子数量标签
north.add(discCountField); // 在北部面板中添加文本框
String mess = "将全部盘子从" + towerName[0] + "座搬运到" + towerName[2] + "座";
JLabel hintMess = new JLabel(mess, JLabel.CENTER); // 创建带有指定文本和对齐方式的标签
north.add(hintMess); // 在北部面板中添加提示信息标签
add(north, BorderLayout.NORTH); // 在界面上方添加北部面板
setResizable(false);
setVisible(true);
setBounds(60, 60, 460, 410);
validate(); // 验证此容器及其所有子组件
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗口关闭操作
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == renew) {
try {
amountOfDisc = Integer.parseInt(discCountField.getText()); //输入转化为整数
if (amountOfDisc < 1) {
JOptionPane.showMessageDialog(this, "请输入正数", "错误", JOptionPane.ERROR_MESSAGE);
discCountField.setText("");
return;
}
if (amountOfDisc > 10) {
JOptionPane.showMessageDialog(this, "输入的数太大,请重新输入", "错误", JOptionPane.ERROR_MESSAGE);
discCountField.setText("");
return;
}
tower.setAmountOfDisc(amountOfDisc);
tower.putDiscOnTower();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this, "输入有误,请重新输入", "错误", JOptionPane.ERROR_MESSAGE);
}
} else if (e.getSource() == autoButton) {
try {
amountOfDisc = Integer.parseInt(discCountField.getText());
if (amountOfDisc < 1) {
JOptionPane.showMessageDialog(this, "请输入正数", "错误", JOptionPane.ERROR_MESSAGE);
discCountField.setText("");
return;
}
if (amountOfDisc > 10) {
JOptionPane.showMessageDialog(this, "输入的数太大,请重新输入", "错误", JOptionPane.ERROR_MESSAGE);
discCountField.setText("");
return;
}
tower.setAmountOfDisc(amountOfDisc);
tower.putDiscOnTower();
int x = this.getBounds().x + this.getBounds().width;
int y = this.getBounds().y;
tower.getAutoMoveDisc().setLocation(x, y);
tower.getAutoMoveDisc().setSize(280, this.getBounds().height);
tower.getAutoMoveDisc().setVisible(true);
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this, "输入有误,请重新输入", "错误", JOptionPane.ERROR_MESSAGE);
discCountField.setText("");
}
}
validate();
}
public static void main(String args[]) {
new hannuota.HanoiWindow(); //启动界面
}
}
Tower 类:
package hannuota;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
//用于表示汉诺塔的柱子
// 在Tower类中,通过设置盘子数量、最大盘子宽度、最小盘子宽度和盘子高度等参数来初始化汉诺塔的状态
public class Tower extends JPanel {
int amountOfDisc = 1;
hannuota.Disc[] disc; //存储盘子对象。
int maxDiscWidth, minDiscWidth, discHeight;
char[] towerName;
TowerPoint[] pointA; //声明变量,表示柱子A上的位置
TowerPoint[] pointB;
TowerPoint[] pointC;
AutoMoveDisc autoMoveDisc; //声明移动盘子的变量
Tower(char[] towerName) {
this.towerName = towerName;
setLayout(null); //设置布局管理器为null,即手动控制组件位置
setBackground(new Color(200, 226, 226));//设置背景颜色
}
public void setAmountOfDisc(int number) {
if (number <= 1)
amountOfDisc = 1;
else
amountOfDisc = number;
}
public void setMaxDiscWidth(int m) {
maxDiscWidth = m;
}
public void setMinDiscWidth(int m) {
minDiscWidth = m;
}
public void setDiscHeight(int h) {
discHeight = h;
}
public hannuota.AutoMoveDisc getAutoMoveDisc() {
return autoMoveDisc;
}
public void putDiscOnTower() { //将盘子放在柱子上
removeDisk(); //移除之前放置在柱子上的盘子。
int n = (maxDiscWidth - minDiscWidth) / amountOfDisc; //计算每个盘子的宽度间隔
disc = new hannuota.Disc[amountOfDisc]; //创建一个长度为amountOfDisc的Disc对象数组。
for (int i = 0; i < disc.length; i++) {
disc[i] = new hannuota.Disc();
disc[i].setNumber(i); //设置盘子的编号。
int diskwidth = minDiscWidth + i * n;//根据索引计算当前盘子的宽度,下面的disc更宽
disc[i].setSize(diskwidth, discHeight); //设置盘子的大小
}
pointA = new TowerPoint[amountOfDisc]; //创建一个长度为amountOfDisc的TowerPoint对象数组,表示柱子A上的位置。
pointB = new TowerPoint[amountOfDisc];
pointC = new TowerPoint[amountOfDisc];
int vertialDistance = discHeight; //设置垂直距离为盘子的高度
for (int i = 0; i < pointA.length; i++) { //遍历柱子A上的位置数组
pointA[i] = new TowerPoint(maxDiscWidth, 100 + vertialDistance);
//实例化一个新的TowerPoint对象,表示柱子A上的一个位置,并设置位置的横坐标和纵坐标
vertialDistance = vertialDistance + discHeight;//更新垂直距离。
}
vertialDistance = discHeight;
for (int i = 0; i < pointB.length; i++) {
pointB[i] = new TowerPoint(2 * maxDiscWidth, 100 + vertialDistance);
vertialDistance = vertialDistance + discHeight;
}
vertialDistance = discHeight;
for (int i = 0; i < pointC.length; i++) {
pointC[i] = new TowerPoint(3 * maxDiscWidth, 100 + vertialDistance);
vertialDistance = vertialDistance + discHeight;
}
for (int i = 0; i < pointA.length; i++) {
pointA[i].putDisc(disc[i], this); //遍历柱子A上的位置数组,将盘子放置在柱子A上的一个位置上
}
autoMoveDisc = new hannuota.AutoMoveDisc(this); //实例化一个新的AutoMoveDisc对象,并将当前Tower对象传递给构造函数。
autoMoveDisc.setTowerName(towerName);
autoMoveDisc.setAmountOfDisc(amountOfDisc);
autoMoveDisc.setPointA(pointA);
autoMoveDisc.setPointB(pointB);
autoMoveDisc.setPointC(pointC);
validate();
repaint(); //重绘组件
}
public void removeDisk() { //移除所有柱子上的盘子,通过遍历柱子A、B、C上的位置数组,依次移除每个位置上的盘子
if (pointA != null) { //检查柱子A上的位置数组是否为null
for (int i = 0; i < pointA.length; i++) { //遍历柱子A上的位置数组
pointA[i].removeDisc(pointA[i].getDiscOnPoint(), this); //调用柱子A上位置数组中第i个位置的removeDisc方法,将该位置上的盘子移除
pointB[i].removeDisc(pointB[i].getDiscOnPoint(), this);
pointC[i].removeDisc(pointC[i].getDiscOnPoint(), this);
}
}
}
//重写的paintComponent方法,用于在组件上绘制柱子、盘子和文字等内容
public void paintComponent(Graphics g) { //用于绘制柱子、盘子和文字等内容
super.paintComponent(g);
int x1, y1, x2, y2;
//计算并绘制柱子A上的线段
//获取柱子A底部盘子的x、y坐标并调整位置。
x1 = pointA[0].getX();
y1 = pointA[0].getY() - discHeight / 2;
x2 = pointA[amountOfDisc - 1].getX();
y2 = pointA[amountOfDisc - 1].getY() + discHeight / 2;
g.drawLine(x1, y1, x2, y2);//划线,绘制从柱子A底部到顶部盘子的线段
x1 = pointB[0].getX();
y1 = pointB[0].getY() - discHeight / 2;
x2 = pointB[amountOfDisc - 1].getX();
y2 = pointB[amountOfDisc - 1].getY() + discHeight / 2;
g.drawLine(x1, y1, x2, y2);
x1 = pointC[0].getX();
y1 = pointC[0].getY() - discHeight / 2;
x2 = pointC[amountOfDisc - 1].getX();
y2 = pointC[amountOfDisc - 1].getY() + discHeight / 2;
g.drawLine(x1, y1, x2, y2);
g.setColor(Color.blue);//设置绘图颜色为蓝色
//计算矩形的位置和大小,并用蓝色填充矩形,用来表示每个柱子的底座
x1 = pointA[amountOfDisc - 1].getX() - maxDiscWidth / 2;
y1 = pointA[amountOfDisc - 1].getY() + discHeight / 2;
x2 = pointC[amountOfDisc - 1].getX() + maxDiscWidth / 2;
y2 = pointC[amountOfDisc - 1].getY() + discHeight / 2;
int length = x2 - x1, height = 6;
g.fillRect(x1, y1, length, height);//填充矩形
int size = 5; //初始化一个点的大小为5,并遍历柱子A、B、C上的位置数组,绘制代表盘子的圆点
for (int i = 0; i < pointA.length; i++) {
//缩短 width和height 画点
g.fillOval(pointA[i].getX() - size / 2, pointA[i].getY() - size / 2, size, size);
g.fillOval(pointB[i].getX() - size / 2, pointB[i].getY() - size / 2, size, size);
g.fillOval(pointC[i].getX() - size / 2, pointC[i].getY() - size / 2, size, size);
}
//画字符串,通过g.drawString方法在每个柱子的顶部位置绘制字符串,显示柱子的名称
g.drawString(towerName[0] + "座", pointA[amountOfDisc - 1].getX(), pointA[amountOfDisc - 1].getY() + 50);
g.drawString(towerName[1] + "座", pointB[amountOfDisc - 1].getX(), pointB[amountOfDisc - 1].getY() + 50);
g.drawString(towerName[2] + "座", pointC[amountOfDisc - 1].getX(), pointC[amountOfDisc - 1].getY() + 50);
}
}
TowerPoint类:
package hannuota;
import java.awt.Component;
import java.awt.Container;
//表示汉诺塔游戏中的塔座位置
public class TowerPoint {
int x, y; //表示塔座位置的横纵坐标
boolean haveDisc; //用于表示该位置是否有盘子
Disc disc = null; //表示当前位置上的盘子,默认为null。
public TowerPoint(int x, int y) { //初始化塔座位置的横纵坐标
this.x = x;
this.y = y;
}
public boolean isHaveDisc() { //判断该位置是否有盘子
return haveDisc;
}
public void setHaveDisc(boolean boo) { //设置当前位置是否有盘子
haveDisc = boo; //将传入的布尔值boo赋给haveDisc表示是否有盘子。
}
public int getX() { //获取塔座位置的横坐标。
return x;
}
public int getY() { //获取塔座位置的纵坐标
return y;
}
public boolean equals(TowerPoint p) { //比较两个塔座位置是否相等
if (p.getX() == this.getX() && p.getY() == this.getY()) //判断传入的塔座位置p的横纵坐标是否与当前实例的横纵坐标相等
return true;
else
return false;
}
public void putDisc(Component com, Container con) { //将盘子放置到该塔座位置上。
disc = (Disc) com; //将传入的组件com转换为Disc对象,并赋值给disc表示该位置上的盘子
con.setLayout(null); //设置容器con的布局为null,即自由布局
con.add(disc); //将盘子组件添加到容器中
int w = disc.getBounds().width; //获取盘子组件的宽度和高度。
int h = disc.getBounds().height; //设置盘子组件在容器中的位置
disc.setBounds(x - w / 2, y - h / 2, w, h); //设置盘子组件在容器中的位置。
haveDisc = true; //将该位置的haveDisc属性设置为true表示该位置上有盘子
disc.setPoint(this); //将该位置信息设置到盘子对象中
con.validate(); //对容器进行验证,以确保界面的更新
}
public Disc getDiscOnPoint() { //定义了一个方法,用于获取该塔座位置上的盘子对象
return disc;
}
//refresh
public void removeDisc(Component com, Container con) { //用于移除该位置上的盘子,com盘子,con塔
if (com != null) //检查传入的组件是否为空
con.remove(com);
con.validate();
}
}
Disc类:
package hannuota;
import java.awt.Color;
import javax.swing.JButton;
//定义了一个表示盘子的类Disc,其中包含了盘子的编号、位置信息以及相关的设置和获取方法
public class Disc extends JButton {
int number;
TowerPoint point;
Disc() {
setBackground(Color.cyan);
}
public void setNumber(int n) {
number = n;
}
public int getNumber() {
return number;
}
public void setPoint(TowerPoint p) {
point = p;
}
public TowerPoint getPoint() {
return point;
}
}
AutoMoveDisc:
package hannuota;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;
public class AutoMoveDisc extends JDialog implements ActionListener { //定义了一个名为"AutoMoveDisc"的类,继承自JDialog类,并实现了ActionListener接口
//声明了一些变量,用于存储盘子数量、柱子对象、柱子名称、界面容器、移动步骤等信息
int amountOfDisc = 1;
TowerPoint[] pointA;
TowerPoint[] pointB;
TowerPoint[] pointC;
char[] towerName;
Container con;
StringBuffer moveStep;
JTextArea showStep;
JButton bStart, bStop, bContinue, bClose;
Timer time;
int i = 0, number = 0;
//用于初始化界面和一些变量,并设置界面的布局、按钮的事件监听器
AutoMoveDisc(Container con) {
setModal(true);
setTitle("搬运过程详情");
this.con = con;
moveStep = new StringBuffer();
time = new Timer(100, this);
time.setInitialDelay(10);
showStep = new JTextArea(10, 12);
bStart = new JButton("演示");
bStop = new JButton("暂停");
bContinue = new JButton("继续");
bClose = new JButton("关闭");
bStart.addActionListener(this);
bStop.addActionListener(this);
bContinue.addActionListener(this);
bClose.addActionListener(this);
JPanel south = new JPanel();
south.setLayout(new FlowLayout());
south.add(bStart);
south.add(bStop);
south.add(bContinue);
south.add(bClose);
add(new JScrollPane(showStep), BorderLayout.CENTER);
add(south, BorderLayout.SOUTH);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
towerName = new char[3];
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
time.stop();
setVisible(false);
}
});
}
// 设置柱子对象、柱子名称和盘子数量等属性
public void setPointA(TowerPoint[] pointA) {
this.pointA = pointA;
}
public void setPointB(TowerPoint[] pointB) {
this.pointB = pointB;
}
public void setPointC(TowerPoint[] pointC) {
this.pointC = pointC;
}
public void setTowerName(char name[]) {
if (name[0] == name[1] || name[0] == name[2] || name[1] == name[2]) {
towerName[0] = 'A';
towerName[1] = 'B';
towerName[2] = 'C';
} else
towerName = name;
}
public void setAmountOfDisc(int n) {
amountOfDisc = n;
}
//这是实现ActionListener接口中的方法,用于处理界面上的按钮点击事件。根据不同的按钮,执行相应的操作
public void actionPerformed(ActionEvent e) {
if (e.getSource() == time) {
number++;
char cStart, cEnd;
if (i <= moveStep.length() - 2) {
cStart = moveStep.charAt(i);
cEnd = moveStep.charAt(i + 1);
showStep.append("(" + number + ")从" + cStart + "座搬一个盘子到" + cEnd + "座\n");
autoMoveDisc(cStart, cEnd);
}
i = i + 2;
if (i >= moveStep.length() - 1) {
time.stop();
}
} else if (e.getSource() == bStart) {
if (moveStep.length() == 0) {
if (time.isRunning() == false) {
i = 0;
moveStep = new StringBuffer();
setMoveStep(amountOfDisc, towerName[0], towerName[1], towerName[2]);
number = 0;
time.start();
}
}
} else if (e.getSource() == bStop) {
if (time.isRunning() == true)
time.stop();
} else if (e.getSource() == bContinue) {
if (time.isRunning() == false)
time.restart();
} else if (e.getSource() == bClose) {
time.stop();
setVisible(false);
}
}
//key,递归实现汉诺塔问题的算法。根据盘子数量和柱子的顺序,将移动步骤记录在moveStep变量中
private void setMoveStep(int amountOfDisc, char one, char two, char three) {
if (amountOfDisc == 1) {
moveStep.append(one);
moveStep.append(three);
} else {
setMoveStep(amountOfDisc - 1, one, three, two);
moveStep.append(one);
moveStep.append(three);
setMoveStep(amountOfDisc - 1, two, one, three);
}
}
//根据传入的起点和终点柱子的名称,获取对应的柱子对象和盘子对象,并进行移动操作
private void autoMoveDisc(char cStart, char cEnd) {
Disc disc = null;
if (cStart == towerName[0]) {
for (int i = 0; i < pointA.length; i++) {
if (pointA[i].isHaveDisc() == true) {
disc = pointA[i].getDiscOnPoint();
pointA[i].setHaveDisc(false);
break;
}
}
}
if (cStart == towerName[1]) {
for (int i = 0; i < pointB.length; i++) {
if (pointB[i].isHaveDisc() == true) {
disc = pointB[i].getDiscOnPoint();
pointB[i].setHaveDisc(false);
break;
}
}
}
if (cStart == towerName[2]) {
for (int i = 0; i < pointC.length; i++) {
if (pointC[i].isHaveDisc() == true) {
disc = pointC[i].getDiscOnPoint();
pointC[i].setHaveDisc(false);
break;
}
}
}
TowerPoint endPoint = null;
int i = 0;
if (cEnd == towerName[0]) {
for (i = 0; i < pointA.length; i++) {
if (pointA[i].isHaveDisc() == true) {
if (i > 0) {
endPoint = pointA[i - 1];
break;
} else if (i == 0)
break;
}
}
if (i == pointA.length)
endPoint = pointA[pointA.length - 1];
}
if (cEnd == towerName[1]) {
for (i = 0; i < pointB.length; i++) {
if (pointB[i].isHaveDisc() == true) {
if (i > 0) {
endPoint = pointB[i - 1];
break;
} else if (i == 0)
break;
}
}
if (i == pointB.length)
endPoint = pointB[pointB.length - 1];
}
if (cEnd == towerName[2]) {
for (i = 0; i < pointC.length; i++) {
if (pointC[i].isHaveDisc() == true) {
if (i > 0) {
endPoint = pointC[i - 1];
break;
} else if (i == 0)
break;
}
}
if (i == pointC.length)
endPoint = pointC[pointC.length - 1];
}
if (endPoint != null && disc != null) {
endPoint.putDisc(disc, con);
endPoint.setHaveDisc(true);
}
}
}
五.运行结果截图:
l
六.总结分析:
本次汉诺塔实验要求设计一个可自动化移动的汉诺塔,要求具备可以自定义盘子数量,开始复位等功能,在其基础上我添加了第二个可视化界面,可以更加详细的查看盘子的具体移动过程,代码中可能存在需要优化的部分,希望大佬们改正