反弹没有给出正确的结果

时间:2015-12-06 23:45:08

标签: java javafx

嘿,我这样做只是为了好玩,但我希望每次圆圈完美地落在角落里时(就像我们以前常常看到屏幕保护程序上的DVD符号弹出角落而死了关闭,但没有。我想肯定地知道)。我的计数器似乎在计算一次时计数两次。为什么要这样做以及如何解决它。

% heroku accounts:set personal
 !    `accounts:set` is not a heroku command.
 !    See `heroku help` for a list of available commands.

% heroku accounts:add personal
 !    `accounts:add` is not a heroku command.
 !    See `heroku help` for a list of available commands.

这是我使用的第二个文件。两者都是必需的。

 import javafx.animation.KeyFrame;
    import javafx.animation.Timeline;
    import javafx.beans.property.DoubleProperty;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Ellipse;
    import javafx.util.Duration;
    import javafx.scene.control.Button;

    public class EllipsePane extends Pane {
      public final double radius = 20;
      private double x = radius, y = radius;
      private double dx = 1, dy = 1;
      private Ellipse ellipse = new Ellipse();
      private Timeline animation;
      private Button btCountRead = new Button("How many times has it landed perfectly in the corner?");
      private int i = 0;

      public EllipsePane() {
        ellipse.setFill(Color.GREEN); // Set ball color
        ellipse.setRadiusX(radius);
        ellipse.setRadiusY(radius);
        getChildren().add(ellipse); // Place a ball into this pane
        getChildren().add(btCountRead);

        // Create an animation for moving the ball
        animation = new Timeline(
          new KeyFrame(Duration.millis(50), e -> moveBall()));
        animation.setCycleCount(Timeline.INDEFINITE);
        animation.play(); // Start animation
      }

      public void play() {
        animation.play();
      }

      public void pause() {
        animation.pause();
      }

      public void increaseSpeed() {
        animation.setRate(animation.getRate() + 100);
      }

      public void decreaseSpeed() {
        animation.setRate(
          animation.getRate() > 0 ? animation.getRate() - 100 : 0);
      }

      public DoubleProperty rateProperty() {
        return animation.rateProperty();
      }

      protected void moveBall() {
        // Check boundaries
        if (x < radius || x > getWidth() - radius) {
          dx *= -1; // Change ball move direction
        }
        if (y < radius || y > getHeight() - radius) {
          dy *= -1; // Change ball move direction
        }

        // Adjust ball position
        x += dx;
        y += dy;
        ellipse.setCenterX(x);
        ellipse.setCenterY(y);

        if ((ellipse.getCenterX() - radius == 0 && ellipse.getCenterY() - radius == 0) ||
          (ellipse.getCenterX() - radius == 0 && ellipse.getCenterY() + radius == getHeight()) ||
          (ellipse.getCenterX() + radius == getWidth() && ellipse.getCenterY() + radius == getHeight()) ||
          (ellipse.getCenterX() - radius == getWidth() && ellipse.getCenterY() - radius == 0)){
          i++;
          /*btCountRead.setOnAction( e ->*/ System.out.println(i);
        }
      }
    }

2 个答案:

答案 0 :(得分:0)

你增加计数的条件非常脆弱。我认为发生的事情是在一个框架中#34;椭圆正好在拐角处,它会增加计数,但不会改变任何一个方向(因为你在测试中使用严格的不等式来改变方向)。在下一帧中,椭圆将(dx, dy)移出屏幕,您可以更改两个方向,在下一帧中,您将完全回到角落。

我会做一个增加计数器的测试#34;我们是否在同一帧&#39;?&#34;中改变水平和垂直方向。即:

protected void moveBall() {
    // Check boundaries

    boolean changedHorizontal = false ;

    if (x < radius || x > getWidth() - radius) {
        dx *= -1; // Change ball move direction
        changedHorizontal = true ;
    }
    if (y < radius || y > getHeight() - radius) {
        dy *= -1; // Change ball move direction
        if (changedHorizontal) {
            i++ ;
            System.out.println(i);
        }
    }

    // Adjust ball position
    x += dx;
    y += dy;
    ellipse.setCenterX(x);
    ellipse.setCenterY(y);

}

答案 1 :(得分:0)

fwiw,我同意@ James_D的答案......你应该选择那个,因为它更直接和最优。但只是为了好玩,让我们为您的输出添加一些调试信息,看看发生了什么。

“双增量”的答案是逻辑在输入一个角时增加“i”,然后在退出同一个角时立即增加。 @James_D打了一个简单的方法来大大简化“我们得到了一个完美的角球?”逻辑。

但首先,更有趣的部分是如何查找,所以我修改了一些调试信息以添加到程序中,希望你会发现它有用。

这里我将介绍一些我想在调试输出中跟踪的内容,其中大部分内容是通过向EllipsePane.java添加一些私有实例变量来完成的(请参阅修改后的代码,如下所示)。

"moveBallCnt" is new, it counts each time your moveBall() method
is called, whether or not "i" changed.

"i" is just your original i, note that we (currently) only print
out a stats line (e.g. call debugStats() whenever "i" is incremented).

"x" is the original's value of "x".

"lastx" is the value of "x" during the *previous* call to
moveBall(); I thought it would be helpful to see what 'x' has
been doing over time.

Same thing for "y" and "lasty" and so on.

"hit" indicates which part of your original "if-else" logic
was hit, I broke that out to just be "A" for the first one,
"B" for the 2nd one and so on.

因此。以下输出是由我添加到EllipsPane.java的debugHeader()和debugStats()方法生成的(参见下面的代码)。

你可以在movBall()方法的末尾看到lastx,lasty等的更新(参见下面的代码)。 现在设置了所有这些,让我们看看我们得到的输出。 (注意我在ubuntu上的virtualbox中运行它,因此出现“libGL错误”。)

$ java BounceBallControl 
pci id for fd 6: 80ee:beef, driver (null)
libGL error: core dri or dri2 extension not found
libGL error: failed to load driver: vboxvideo
 moveBallCnt. hit:      i |      x |  lastX |      y |  lastY |     dx | lastdx |     dy | lastdy |
        5933.   B:      1 |  20.00 |  21.00 | 130.00 | 129.00 |  -1.00 |  -1.00 |   1.00 |   1.00 |
        5935.   B:      2 |  20.00 |  19.00 | 130.00 | 131.00 |   1.00 |  -1.00 |  -1.00 |   1.00 |
       11869.   A:      3 |  20.00 |  21.00 |  20.00 |  21.00 |  -1.00 |  -1.00 |  -1.00 |  -1.00 |
       11871.   A:      4 |  20.00 |  19.00 |  20.00 |  19.00 |   1.00 |  -1.00 |   1.00 |  -1.00 |
       17805.   B:      5 |  20.00 |  21.00 | 130.00 | 129.00 |  -1.00 |  -1.00 |   1.00 |   1.00 |
       17807.   B:      6 |  20.00 |  19.00 | 130.00 | 131.00 |   1.00 |  -1.00 |  -1.00 |   1.00 |
       23741.   A:      7 |  20.00 |  21.00 |  20.00 |  21.00 |  -1.00 |  -1.00 |  -1.00 |  -1.00 |
       23743.   A:      8 |  20.00 |  19.00 |  20.00 |  19.00 |   1.00 |  -1.00 |   1.00 |  -1.00 |
       29677.   B:      9 |  20.00 |  21.00 | 130.00 | 129.00 |  -1.00 |  -1.00 |   1.00 |   1.00 |
       29679.   B:     10 |  20.00 |  19.00 | 130.00 | 131.00 |   1.00 |  -1.00 |  -1.00 |   1.00 |
       35613.   A:     11 |  20.00 |  21.00 |  20.00 |  21.00 |  -1.00 |  -1.00 |  -1.00 |  -1.00 |
       35615.   A:     12 |  20.00 |  19.00 |  20.00 |  19.00 |   1.00 |  -1.00 |   1.00 |  -1.00 |
       41549.   B:     13 |  20.00 |  21.00 | 130.00 | 129.00 |  -1.00 |  -1.00 |   1.00 |   1.00 |
       41551.   B:     14 |  20.00 |  19.00 | 130.00 | 131.00 |   1.00 |  -1.00 |  -1.00 |   1.00 |
       47485.   A:     15 |  20.00 |  21.00 |  20.00 |  21.00 |  -1.00 |  -1.00 |  -1.00 |  -1.00 |
       47487.   A:     16 |  20.00 |  19.00 |  20.00 |  19.00 |   1.00 |  -1.00 |   1.00 |  -1.00 |
       53421.   B:     17 |  20.00 |  21.00 | 130.00 | 129.00 |  -1.00 |  -1.00 |   1.00 |   1.00 |
       53423.   B:     18 |  20.00 |  19.00 | 130.00 | 131.00 |   1.00 |  -1.00 |  -1.00 |   1.00 |
       59357.   A:     19 |  20.00 |  21.00 |  20.00 |  21.00 |  -1.00 |  -1.00 |  -1.00 |  -1.00 |
 moveBallCnt. hit:      i |      x |  lastX |      y |  lastY |     dx | lastdx |     dy | lastdy |
       59359.   A:     20 |  20.00 |  19.00 |  20.00 |  19.00 |   1.00 |  -1.00 |   1.00 |  -1.00 |
       65293.   B:     21 |  20.00 |  21.00 | 130.00 | 129.00 |  -1.00 |  -1.00 |   1.00 |   1.00 |
       65295.   B:     22 |  20.00 |  19.00 | 130.00 | 131.00 |   1.00 |  -1.00 |  -1.00 |   1.00 |
       71229.   A:     23 |  20.00 |  21.00 |  20.00 |  21.00 |  -1.00 |  -1.00 |  -1.00 |  -1.00 |
       71231.   A:     24 |  20.00 |  19.00 |  20.00 |  19.00 |   1.00 |  -1.00 |   1.00 |  -1.00 | ^C
$ 

那么,这告诉我们什么? 每次“i”发生变化时,都会发生在相邻的moveBall()调用之间。 例如,第一组“邻居”是moveBall = 5,933和moveBall = 5,935。看起来像你的“角落?”逻辑绊倒两次,一次进入角落,然后再离开角落一次。

接下来,考虑“dx vs. lastdx”和“dy vs lastdy”的变化。 例如,在moveBallCnt = 5,933 dx与lastdx相同。 退出它,在moveBallCnt = 5,935 dx已从-1更改为1.

请注意,使用debugStats()运行它只在“i”更改适度的输出量时打印,很容易在显示窗口中看到。

下一次更改将产生大量输出,但有趣的是:取消注释

//DEBUG: change += "dx";
//DEBUG: change += "dy"
行以进行dx / dy操作将生成一个debugStats()调用几乎每次调用moveBall()。 如果您这样做,您可能希望运行如下所示:

$java BounceBallContorl > run.dat

让它持续一段时间,足够长,以获得一些角落命中。 然后点击^ C并在文本编辑器中打开run.dat,查找有“B:”或“A:”和“dxyd:”的命中,以研究dx&amp;的模式。 dy改变它。

如果你这样做,你会发现一个有趣的优化。 如果dx和dy在给定的moveBox()调用中都发生了变化,那么看起来你可以声明一个“完美的角落命中”。 这将大大简化你的逻辑。 请注意,如果dx和dy都发生变化,您可以通过多种方式查看。 我正在使用temp-var

String change="";
只是为了使debugStats()打印输出更具可显示性。

你可以使用change var as-is并在change.equals(“dxdy”)时声明“perfect corner”。

你也可以为“dx_change”和“dy_change”设置布尔值,并在两者都为真时声明“完美角落”。

或者在调用moveBall()期间为每个增量变化保留一个计数器,并在“deltaChangeCnt == 2”时声明“完美角落”。

祝你好运,编程愉快。

===开始修改EllipsPane.java ===

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.util.Duration;
import javafx.scene.control.Button;

public class EllipsePane extends Pane {
   public final double radius = 20;
   private double x = radius, y = radius;
   private double dx = 1, dy = 1;
   private Ellipse ellipse = new Ellipse();
   private Timeline animation;
   //private Button btCountRead = new Button("How many times has it landed perfectly in the corner?");
   private Button btCountRead = new Button("corner cnt:");
   private int i = 0;
   private double lasti = 0, lastx, lasty, lastdx, lastdy; // added for debugging.
   private long moveBallCnt = 0;

   public EllipsePane() {
      ellipse.setFill(Color.GREEN); // Set ball color
      ellipse.setRadiusX(radius);
      ellipse.setRadiusY(radius);
      getChildren().add(ellipse); // Place a ball into this pane
      getChildren().add(btCountRead);

      // Create an animation for moving the ball

      animation = new Timeline(
         new KeyFrame(Duration.millis(50), e -> moveBall()));
      animation.setCycleCount(Timeline.INDEFINITE);
      animation.play(); // Start animation
      debugHeaders();
   }

   public void play() {
      animation.play();
   }

   public void pause() {
      animation.pause();
   }

   public void increaseSpeed() {
      animation.setRate(animation.getRate() + 100);
   }

   public void decreaseSpeed() {
      animation.setRate(
         animation.getRate() > 0 ? animation.getRate() - 100 : 0);
   }

   public DoubleProperty rateProperty() {
      return animation.rateProperty();
   }

   protected void moveBall() {
      // Check boundaries
      String change = "";
      if (x < radius || x > getWidth() - radius) {
         dx *= -1; // Change ball move direction
         //DEBUG: change += "dx";
      }
      if (y < radius || y > getHeight() - radius) {
         dy *= -1; // Change ball move direction
         //DEBUG: change += "dy";
      }
      if( !change.isEmpty() ) {
         debugStats( change );
      }

      // Adjust ball position
      x += dx;
      y += dy;
      ellipse.setCenterX(x);
      ellipse.setCenterY(y);

      String hitType = null;
      // Let's start by splitting up the if-logic so we can see what is actually firing.
      // We'll add temp-var "hitType" to see which if expression fires.
      // (Also... just for fun, since the "center" of the elipse is x & y, you could
      // rewrite the following in terms of "x" and "y"):
      if( x - radius == 0 && y - radius == 0 ) {
         hitType = "A";
      } else if( x - radius == 0 && y + radius == getHeight())  {
         hitType = "B";
      } else if( x + radius == getWidth() && y + radius == getHeight() )  {
         hitType = "C";
      } else if( x - radius == getWidth() && y - radius == 0 ) {
         hitType = "D";
      }
      //original: if ((ellipse.getCenterX() - radius == 0 && ellipse.getCenterY() - radius == 0) ||
      //original:   hitType = "A";
      //original: else if( ellipse.getCenterX() - radius == 0 && ellipse.getCenterY() + radius == getHeight())  {
      //original:   hitType = "B";
      //original: } else if( ellipse.getCenterX() + radius == getWidth() && ellipse.getCenterY() + radius == getHeight() )  {
      //original:   hitType = "C";
      //original: } else if( ellipse.getCenterX() - radius == getWidth() && ellipse.getCenterY() - radius == 0 ) {
      //original:   hitType = "D";
      //original: }

      if( hitType != null ) {
         // something fired, let's print out some background info...
         i++;
         if( 0 == (i % 20 ) ) {
         debugHeaders();  // do this so they don't scroll offscreen.
         }
         debugStats( hitType );
      }
      // update our "last" values each time through the moveBall() method whether or not anything fired.
      lasti = i;
      lastx = x; lastdx = dx;
      lasty = y; lastdy = dy;
      moveBallCnt++; // count each time moveBall() is called.
      //{
      //  /*btCountRead.setOnAction( e ->*/
      //}
   }

   public void debugHeaders() {
      System.out.printf("%12s.%4s: %6s | %6s | %6s | %6s | %6s | %6s | %6s | %6s | %6s |\n"
         ,"moveBallCnt"
         ,"hit"
         ,"i"
         ,"x",  "lastX"
         ,"y",  "lastY"
         ,"dx", "lastdx"
         ,"dy", "lastdy" );
   }

   public void debugStats( String hitType ) {
      System.out.printf("%12d.%4s: %6d | %6.2f | %6.2f | %6.2f | %6.2f | %6.2f | %6.2f | %6.2f | %6.2f |\n"
         , moveBallCnt
         , hitType
         , i
         , x,  lastx
         , y,  lasty
         , dx, lastdx
         , dy, lastdy );
   }
}

===结束修改EllipsePane.java ===