3

Our final project for school is to create a class that implements a horse racing animation with betting capabilities. We have to make sure it's multi-threaded, and we have to have buttons the user can click to begin the race. The five horses need to all start the race at the same time and make their way to the finish line. We've finished most of the project requirements, but we're having trouble getting all of the horses to move.

Sometimes on pushing the start button, only two horses will move. Sometimes three of the horses will move. Sometimes all the horses will move. Sometimes none of the horses will move. The method we're currently using uses a while loop to change the layoutX of the horse to whatever the current layoutX is plus 10. I added a print out statement to let me know whenever a horse crosses the finish line, and according to that all five horses are always crossing the finish line.

BUT, visually this isn't happening. The images will randomly just not go anywhere, even though the horse the image is representing DOES. I'm sure it has something to do with how we're moving the images, or how we're inserting them into the program, but just in case I'll also show the run method for the threads and how we're calling that too.

This is the method we used to add the images of the five horses.

public ArrayList<ImageView> callAllHorses() {
    //In this method, we have created an array of type image and an array list of type ImageView,
    //Using a for loop, make a new Image, add to the image array,
    //and add that array to the ArrayList. At the same, we set the spacing for the horses evenly
    //using a mathematical expression. The first horse, in index 0, doesn't
    //need any more space vertically beyond the first 50 pixels we leave open for our buttons.
        Image[] allHorses;
        ArrayList<ImageView> allHorseView = new ArrayList<ImageView>();
        allHorses = new Image[5];
        for (int i = 0; i < allHorses.length; i++) {
            allHorses[i] = new Image("file:C:\\User\\Downloads\\finalhorse" + (i + 1) + ".png");
            allHorseView.add(new ImageView(allHorses[i]));
            allHorseView.get(i).setY(50 + (150 * i));
        }
        return allHorseView;
}

This arraylist gets added to a pane, which then gets added to a group.

Here's the inner class and run method for the horses and what we're asking them to do:

public class Horse implements Runnable {
    //A horse inner class was needed in order to consolidate thread creation.
    //AND in order to be able to store the horse's number into a rankings list.
    //This will allow us to record where the horses placed in the race,
    //and then compare the rankings to the user's bet.

    Node horse;
    int horseNumber;

    public Horse(int i) {
        horseNumber = i;
        createHorse(horseNumber);
    }

    public void createHorse(int i) {
        horse = horses.getChildren().get(i);
    }

    public int getHorseNumber() {
        return horseNumber;
    }

    @Override
    public void run() {
        //In this run method, the horses will move 10 pixels to the right and then sleep for a random number
        //between 1-500 milliseconds, then resume their progress. Once their tails have reached the 650 pixel
        //mark they stop. This puts them right at the edge of the window. Change 650 to change when the horse stops.
        //Once the horse has reached the point it needs to, it finishes running by adding the number it has to the rankings arraylist.

        Random horseSlow = new Random();
        // TODO Auto-generated method stub
        while (horse.getLayoutX() < 650) {
            horse.setLayoutX(horse.getLayoutX() + 10);
            try {
                Thread.sleep(horseSlow.nextInt(500));
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            rankings.add(getHorseNumber() + 1);
        }

    }
    // TODO Auto-generated method stub

}

This is our begin race method, where we create the horses using the inner class, and assign them to their corresponding images. All the horses are individual threads, which are all ran through an executor.

public void beginRace(){
    ExecutorService executor = Executors.newFixedThreadPool(5);
    executor.execute(new Horse(0));
    executor.execute(new Horse(1));
    executor.execute(new Horse(2));
    executor.execute(new Horse(3));
    executor.execute(new Horse(4));

}

And this is the action event that occurs whenever the start button is pressed.

startBtn.setOnAction(e -> {
        beginRace();

        System.out.println("This button is active.");
        resetBtn.setDisable(false);
        startBtn.setDisable(true);
    });

I've been trying to solve this problem for three days now, and for the life of me I can't figure it out.

G. Vazquez
  • 31
  • 2
  • The logic behind the movement seems good. Where do you draw and update the screen? Whichever object controls the container (where the animation will be shown) needs to be updated along with all the objects (horses) within it. – Sterls Dec 14 '15 at 00:54
  • Not exactly sure why you are seeing the behavior you describe, but you are trying to update the UI from a background thread, which you are not allowed to do (see. e.g. the "Threading" section in the [`Application` JavaDocs](http://docs.oracle.com/javase/8/javafx/api/javafx/application/Application.html)). I don't really understand why you're not getting an exception from that code, unless you are using an old JDK version. There may be other issues with the threading too: e.g. you are accessing `rankings` (whose definition you haven't shown) from multiple threads... – James_D Dec 14 '15 at 00:56
  • @Sterls If you change the `layout` properties of a node, it will automatically be repainted on the next rendering pulse. Why do you think you need special instructions to update it? – James_D Dec 14 '15 at 01:02
  • @James_D, Is there any workaround that allows me to use a background thread to update the UI then? I've been reading on Tasks, but have been unable to grasp the concept. – G. Vazquez Dec 14 '15 at 03:41
  • You can just use [`Platform.runLater()`](http://docs.oracle.com/javase/8/javafx/api/javafx/application/Platform.html#runLater-java.lang.Runnable-) to wrap the call(s) that change the UI. Task doesn't really work here. For general background info on multithreading in JavaFX, see my answer to http://stackoverflow.com/questions/30249493/using-threads-to-make-database-requests (Note that in real life I would probably not use background threads at all here, and just use an `AnimationTimer`, but it sounds like using threads is a requirement.) – James_D Dec 14 '15 at 03:43

0 Answers0