如何为河内塔制作动画?
How to animate Tower of Hanoi?
所以我正在尝试为河内的常见拼图制作动画。我已经在控制台中编写了执行此操作的算法,但我想制作一个弹出的 JApplet,并在我询问磁盘数量后为正在解决的难题制作动画。如果有帮助,这是我的算法代码。只是寻找一些指令,不需要写出整个代码。谢谢
这是我的算法代码。
public class TowerofHanoi extends JFrame{
static int count= 0;
public void move(int n, String start, String auxiliary, String end) {
if (n == 1) {
count++;
System.out.println(start + " -> " + end);
} else {
count++;
move(n - 1, start, end, auxiliary);
System.out.println(start + " -> " + end);
move(n - 1, auxiliary, start, end);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
TowerofHanoi towersOfHanoi = new TowerofHanoi();
System.out.print("Enter number of discs: ");
Scanner scanner = new Scanner(System.in);
int discs = scanner.nextInt();
towersOfHanoi.move(discs, "A", "B", "C");
System.out.println("This puzzle took "+count+" moves.");
}
public void paint(Graphics g) {
g.drawRect (10, 10, 200, 200);
}
public TowerofHanoi(){
setPreferredSize(new Dimension(WIDTH, HEIGHT));
}
}
这是我的 JApplet 代码。
public class Graphics_TOH {
public static void main(String[] args) {
// TODO Auto-generated method stub
JFrame frame = new JFrame ("Draw Person");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
TowerofHanoi panel = new TowerofHanoi ();
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
}
这个问题实际上对我来说有点有趣,因为它与我的一个小毛病有关——大多数编程语言依赖调用堆栈的方式使得重用那个漂亮的小东西太难了move()
你的函数。
要制作这种动画,您需要:
- 设置一个定时器,使面板每秒刷新几次(比如 20 次);
- 记住动画开始的当前时间;和
- 每次面板刷新时,获取当前时间并绘制当时的样子。
当然,对您来说棘手的部分是第 3 步。
假设您想每秒画一个动作。刷新发生了,你得到了当前时间,发现从动画开始到现在已经过了 4.234 秒。您计算出您进入第 4 步的时间为 0.234 秒,因此您想要绘制第 4 步 23.4% 的样子。
为此,您需要知道:在移动 4 期间哪些圆盘在哪些桩上静止,以及哪个圆盘在移动,其源桩和目标桩。
修正你的递归移动函数来跟踪所有这些是很容易的,但是因为它会一次生成所有的移动,所以没有办法让它具体告诉你移动 4。
要解决这个问题,您基本上有 3 个选择:
a) 你可以在开始时调用你的移动函数,让它记录所有的移动。然后当你必须画画时,你可以通过记录的动作前进到正确的动作。这很容易,而且可能很实用。当然,记录一个大拼图的所有动作需要大量内存(20 张光盘需要 100 万个条目),但制作动画也需要很多时间(1 周一次 move/sec),所以你无论如何都不会制作大谜题的动画。
b) 您可以在单独的线程中执行递归移动函数,该线程在每次重绘时与渲染线程交换信息。在多人游戏中,这实际上是一个很常见的等待,在这种游戏中,无论如何都必须在 "real time" 中跟踪游戏状态。对你来说,麻烦得不偿失
c) 您可以以非递归方式重写您的 hanoi 解决方案。然后你可以实现一个迭代器。这与解决方案 (a) 非常相似,但您需要推进迭代器以在必要时生成新的动作,而不是遍历预先记录的动作。好处是你不需要提前生成和存储所有的动作。
如果是我,我会做解决方案 (c),因为我很乐意将递归解决方案转换为具有单独堆栈的迭代解决方案。如果您对这类事情不满意,那么您可能想要做 (a) 并将所有移动填充到 ArrayList 中。
作为准备措施,创建 "Disk" 类型的对象,将信息保存到它们是什么编号的磁盘,然后它们的 "x" 和 "y" 将根据他们在哪座塔上,在几号楼上。
要使整个过程生动起来,您需要将所有动作放在一起,并在递归调用之间到达系统输出时继续推动它。
堆叠完成后,稍后将其提取出来,弹出并读取哪个磁盘何时移动到哪个塔。在磁盘中,您将有一个更改它的塔号的功能。
磁盘阵列将自行显示,其中每个磁盘都包含一个 show() 函数,其中您在指定的塔乘以常数和特定高度绘制矩形,这里为了省去复杂性,省去改动的高度。
这就是您所需要的。从字面上看,只需将上面的行转换为代码即可。如果你想要合适的高度,你必须创建 3 个堆栈来容纳磁盘并弹出和推动,同时从存储所有移动的主堆栈中提取信息。 Show 函数现在将包含有关塔及其堆叠位置的参数,这就是您绘制矩形的位置。
受您的问题启发,我编写了易于理解的 JavaScript 代码(p5.js 框架上的 运行),它会完全教给您你如何在一个简单的河内塔程序中处理磁盘。
访问这个link:
My Sketch
var amount =22;
Stack = [];
disks = [];
Sos= [];
A = [];
B = [];
C = [];
var frR =5;
var slider;
function Disk(n){
this.n=n;
this.tower=1;
this.x=10+this.tower*200;
this.y=100+n*12;
this.show = function(i,j){
rectMode(CENTER);
fill(map(n,0,amount,1,350),260,260);
rect(-100+(j+1)*350,300-i*12,10 +n*20,10);
}
}
function setup() {
createCanvas(1200, 600);
slider=createSlider(1,80,frR);
slider.position(20,400);
Hanoi(amount,1,2,3);
frameRate(frR);
colorMode(HSB);
Sos.push(A);
Sos.push(B);
Sos.push(C);
for(var i =0 ; i < amount; i++){
disks.push(new Disk(i));
}
for(var i =amount-1 ; i >=0; i--){
Sos[0].push(disks[i]);
}
}
function draw(){
if(frameCount < Stack.length)
drawIt();
}
function drawIt() {
background(0);
frR=slider.value();
frameRate(frR);
push();
fill(255);
noStroke();
text("Select Speed :",20,380);
textSize(20);
text("Steps taken : " + frameCount+ " / " + Stack.length + " ." ,450,450);
text("[ " + amount + " disks ]", 520,490);
rect(500,308,1800,2);
for(var j =0 ; j< 3; j++)
rect(-100+(j+1)*350, 188,2,240);
pop();
for(var j =0 ; j< 3; j++){
for(var i =0 ; i < Sos[j].length; i++){
Sos[j][i].show(i,j);
}
}
var current = Stack[frameCount-1];
Sos[current[1]-1].pop();
Sos[current[2]-1].push(disks[current[0]]);
}
function Hanoi(n, from, to , via)
{
if (n==0) return;
Hanoi(n-1, from, via , to);
//createP(n + " from" + from + " to " + to);
Stack.push([n,from,to]);
Hanoi(n-1, via, to , from);
}
所以我正在尝试为河内的常见拼图制作动画。我已经在控制台中编写了执行此操作的算法,但我想制作一个弹出的 JApplet,并在我询问磁盘数量后为正在解决的难题制作动画。如果有帮助,这是我的算法代码。只是寻找一些指令,不需要写出整个代码。谢谢
这是我的算法代码。
public class TowerofHanoi extends JFrame{
static int count= 0;
public void move(int n, String start, String auxiliary, String end) {
if (n == 1) {
count++;
System.out.println(start + " -> " + end);
} else {
count++;
move(n - 1, start, end, auxiliary);
System.out.println(start + " -> " + end);
move(n - 1, auxiliary, start, end);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
TowerofHanoi towersOfHanoi = new TowerofHanoi();
System.out.print("Enter number of discs: ");
Scanner scanner = new Scanner(System.in);
int discs = scanner.nextInt();
towersOfHanoi.move(discs, "A", "B", "C");
System.out.println("This puzzle took "+count+" moves.");
}
public void paint(Graphics g) {
g.drawRect (10, 10, 200, 200);
}
public TowerofHanoi(){
setPreferredSize(new Dimension(WIDTH, HEIGHT));
}
}
这是我的 JApplet 代码。
public class Graphics_TOH {
public static void main(String[] args) {
// TODO Auto-generated method stub
JFrame frame = new JFrame ("Draw Person");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
TowerofHanoi panel = new TowerofHanoi ();
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
}
这个问题实际上对我来说有点有趣,因为它与我的一个小毛病有关——大多数编程语言依赖调用堆栈的方式使得重用那个漂亮的小东西太难了move()
你的函数。
要制作这种动画,您需要:
- 设置一个定时器,使面板每秒刷新几次(比如 20 次);
- 记住动画开始的当前时间;和
- 每次面板刷新时,获取当前时间并绘制当时的样子。
当然,对您来说棘手的部分是第 3 步。
假设您想每秒画一个动作。刷新发生了,你得到了当前时间,发现从动画开始到现在已经过了 4.234 秒。您计算出您进入第 4 步的时间为 0.234 秒,因此您想要绘制第 4 步 23.4% 的样子。
为此,您需要知道:在移动 4 期间哪些圆盘在哪些桩上静止,以及哪个圆盘在移动,其源桩和目标桩。
修正你的递归移动函数来跟踪所有这些是很容易的,但是因为它会一次生成所有的移动,所以没有办法让它具体告诉你移动 4。
要解决这个问题,您基本上有 3 个选择:
a) 你可以在开始时调用你的移动函数,让它记录所有的移动。然后当你必须画画时,你可以通过记录的动作前进到正确的动作。这很容易,而且可能很实用。当然,记录一个大拼图的所有动作需要大量内存(20 张光盘需要 100 万个条目),但制作动画也需要很多时间(1 周一次 move/sec),所以你无论如何都不会制作大谜题的动画。
b) 您可以在单独的线程中执行递归移动函数,该线程在每次重绘时与渲染线程交换信息。在多人游戏中,这实际上是一个很常见的等待,在这种游戏中,无论如何都必须在 "real time" 中跟踪游戏状态。对你来说,麻烦得不偿失
c) 您可以以非递归方式重写您的 hanoi 解决方案。然后你可以实现一个迭代器。这与解决方案 (a) 非常相似,但您需要推进迭代器以在必要时生成新的动作,而不是遍历预先记录的动作。好处是你不需要提前生成和存储所有的动作。
如果是我,我会做解决方案 (c),因为我很乐意将递归解决方案转换为具有单独堆栈的迭代解决方案。如果您对这类事情不满意,那么您可能想要做 (a) 并将所有移动填充到 ArrayList 中。
作为准备措施,创建 "Disk" 类型的对象,将信息保存到它们是什么编号的磁盘,然后它们的 "x" 和 "y" 将根据他们在哪座塔上,在几号楼上。
要使整个过程生动起来,您需要将所有动作放在一起,并在递归调用之间到达系统输出时继续推动它。
堆叠完成后,稍后将其提取出来,弹出并读取哪个磁盘何时移动到哪个塔。在磁盘中,您将有一个更改它的塔号的功能。
磁盘阵列将自行显示,其中每个磁盘都包含一个 show() 函数,其中您在指定的塔乘以常数和特定高度绘制矩形,这里为了省去复杂性,省去改动的高度。
这就是您所需要的。从字面上看,只需将上面的行转换为代码即可。如果你想要合适的高度,你必须创建 3 个堆栈来容纳磁盘并弹出和推动,同时从存储所有移动的主堆栈中提取信息。 Show 函数现在将包含有关塔及其堆叠位置的参数,这就是您绘制矩形的位置。
受您的问题启发,我编写了易于理解的 JavaScript 代码(p5.js 框架上的 运行),它会完全教给您你如何在一个简单的河内塔程序中处理磁盘。
访问这个link: My Sketch
var amount =22;
Stack = [];
disks = [];
Sos= [];
A = [];
B = [];
C = [];
var frR =5;
var slider;
function Disk(n){
this.n=n;
this.tower=1;
this.x=10+this.tower*200;
this.y=100+n*12;
this.show = function(i,j){
rectMode(CENTER);
fill(map(n,0,amount,1,350),260,260);
rect(-100+(j+1)*350,300-i*12,10 +n*20,10);
}
}
function setup() {
createCanvas(1200, 600);
slider=createSlider(1,80,frR);
slider.position(20,400);
Hanoi(amount,1,2,3);
frameRate(frR);
colorMode(HSB);
Sos.push(A);
Sos.push(B);
Sos.push(C);
for(var i =0 ; i < amount; i++){
disks.push(new Disk(i));
}
for(var i =amount-1 ; i >=0; i--){
Sos[0].push(disks[i]);
}
}
function draw(){
if(frameCount < Stack.length)
drawIt();
}
function drawIt() {
background(0);
frR=slider.value();
frameRate(frR);
push();
fill(255);
noStroke();
text("Select Speed :",20,380);
textSize(20);
text("Steps taken : " + frameCount+ " / " + Stack.length + " ." ,450,450);
text("[ " + amount + " disks ]", 520,490);
rect(500,308,1800,2);
for(var j =0 ; j< 3; j++)
rect(-100+(j+1)*350, 188,2,240);
pop();
for(var j =0 ; j< 3; j++){
for(var i =0 ; i < Sos[j].length; i++){
Sos[j][i].show(i,j);
}
}
var current = Stack[frameCount-1];
Sos[current[1]-1].pop();
Sos[current[2]-1].push(disks[current[0]]);
}
function Hanoi(n, from, to , via)
{
if (n==0) return;
Hanoi(n-1, from, via , to);
//createP(n + " from" + from + " to " + to);
Stack.push([n,from,to]);
Hanoi(n-1, via, to , from);
}