Monday 26 December 2016

Chapter 15 Exercise 33, Introduction to Java Programming, Tenth Edition Y. Daniel LiangY.

15.33 (Game: bean-machine animation)
Write a program that animates the bean machine introduced in
Programming Exercise 7.21. The animation terminates after ten
balls are dropped, as shown in Figure 15.36b and c.

import javafx.animation.PathTransition;
import javafx.collections.ObservableList;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polyline;
import javafx.util.Duration;

import java.util.ArrayList;

public class TBeanMachinePane extends Pane {

    // Bean machine measurements
    double w = 800; // Pane default width
    double h = 800; // Pane default height
    double gap; // distance gap between slot lines
    double radius; // peg's radius
    double dropX; // Drop ball starting x point
    double dropY; // Drop ball starting y point  //
    int numOfSlots; // numbers of slots

    // Line's sequence drawn matters
    Line baseLine;
    Line leftSLine; // left side line (bottom left)
    Line rightSLine; //  right side line (bottom right)
    Line[] cSlotLines; // center slot lines
    Line midLeftSLine; // middle left line
    Line midRightSLine; // middle right line
    Line topLeftSLine; // top left line
    Line topRightSLine; // top right line


    // Triangle pegs
    Circle[] pegs; // PEEGGSSSSS

    // Dropped balls
    ArrayList<Circle> balls;
    ArrayList<Circle> fineshedBalls;
    ArrayList<Polyline> ballPaths;

    // Animation
    ArrayList<PathTransition> mPathTransitions = new ArrayList<>();

    private TBeanMachinePane() {
        baseLine = new Line();
        baseLine.translateYProperty().bind(translateXProperty());
        leftSLine = new Line();
        rightSLine = new Line();
        midLeftSLine = new Line();
        midRightSLine = new Line();
        topLeftSLine = new Line();
        topRightSLine = new Line();
        balls = new ArrayList<>();
        fineshedBalls = new ArrayList<>();
        ballPaths = new ArrayList<>();
    }

    public TBeanMachinePane(int slots, double width, double height) {
        this();
        initSlotLinesAndPegs(slots); // All layout nodes are initialized after this point

        numOfSlots = slots;
        setMinWidth(w = width);
        setMinHeight(h = height);
        setMaxSize(width, height);

        drawLayout();
        addLayoutShapes();

    }

    public void dropBall() {
        Circle ball = new Circle(dropX, dropY, radius);
        Polyline polyline = generatePath();
        ObservableList<Double> list = polyline.getPoints();
        double x = list.get(list.size() - 2);
        double y = list.get(list.size() - 1);
        fineshedBalls.add(new Circle(x, y, radius));

        ballPaths.add(polyline);
        balls.add(ball);


        PathTransition path = new PathTransition(Duration.seconds(8), polyline, ball);

        mPathTransitions.add(path);
        path.setDelay(Duration.seconds(mPathTransitions.size()));

        getChildren().addAll(ball);
        //getChildren().addAll(polyline);
        path.play();
    }

    private Polyline generatePath() {
        // dropped ball's beginning x and y point
        final int X = 0;
        final int Y = 1;
        double[] p = new double[]{dropX, dropY };

        // the polyline will track the ball's path
        Polyline polyline = new Polyline();
        ObservableList<Double> list = polyline.getPoints();

        // Adding start point to the polyline
        list.addAll(p[X], p[Y]);

        // While the ball hasn't reached the end

        while (!isAtEnd(p[Y]) ) {

            // while the ball hasn't hit a boundary: keep falling...
            if (!hasBouncedOnPeg(p[X], p[Y]) && !hasBouncedOnBall(p[X], p[Y])) {

                p[Y] = moveDown(p[Y]);
                list.addAll(p[X], p[Y]);

            }
            // If the ball didn't bounce on another ball
            else if (hasBouncedOnPeg(p[X], p[Y]) || hasBouncedOnBall(p[X], p[Y])){

                // If both sides are clear than make a random turn
                if (isClearLeft(p[X], p[Y]) && isClearRight(p[X], p[Y])) {
                    p = ((int) (Math.random() * 2) == 1) ? rightArc(list, p[X], p[Y]) : leftArc(list, p[X], p[Y]);
                }
                // else if left side is clear make a left turn
                else if (isClearLeft(p[X], p[Y])) {
                    p = leftArc(list, p[X], p[Y]);
                }
                // else if right side is clear make a right turn
                else if (isClearRight(p[X], p[Y])) {
                    p = rightArc(list, p[X], p[Y]);

                } else {
                    // if statement reaches here its a dead end
                    break;
                }
            }

        }

        return polyline;
    }

    private boolean hasBouncedOnPeg(double x, double y) {

        for (Circle c : pegs) {
            if (c.contains(x, y + radius * 1.59999999)) {
                //getChildren().add(new Circle(x, y + radius * 2, 1));
                return true;
            }
        }
        return false;
    }

    private double[] rightArc(ObservableList<Double> list, double startX, double startY) {
        double angle = 90;
        double x = 0;
        double y = 0;
        double arcRadius = radius * 2;
        for (int i = 0; i < 90; i++) {
            x = startX + arcRadius * Math.cos(Math.toRadians(angle));
            y = (startY + arcRadius) - arcRadius * Math.sin(Math.toRadians(angle));
            list.addAll(x,y);
            angle--;
        }
        return new double[]{x,y};
    }

    private double[] leftArc(ObservableList<Double> list, double startX, double startY) {

        double angle = 90;
        double x = 0;
        double y = 0;
        double arcRadius = radius * 2;

        for (int i = 0; i < 90; i++) {
            x = startX + arcRadius * Math.cos(Math.toRadians(angle));
            y = (startY + arcRadius) - arcRadius * Math.sin(Math.toRadians(angle));
            list.addAll(x,y);
            angle++;
        }

        return new double[]{x,y};
    }

    private boolean isClearLeft(double x, double y) {
        double limitX = x - radius * 2;

        if (limitX < baseLine.getStartX()) return false;
        for (Circle peg : pegs) {

            if (peg.contains(limitX, y) || !isInsideBeanMachine(limitX, y)) {
                return false;
            }
        }
        for (Circle c : balls) {
            if (c.contains(limitX, y) || !isInsideBeanMachine(limitX, y)) {
                return false;
            }
        }


        return true;
    }


    private boolean isClearRight(double x, double y) {
        double limitX = x + radius * 2;
        if (limitX > baseLine.getEndX()) return false;

        for (Circle peg : pegs) {

            if (peg.contains(limitX, y) || !isInsideBeanMachine(limitX, y)) {
                return false;
            }
        }
        for (Circle c : balls) {
            if (c.contains(limitX, y) || !isInsideBeanMachine(limitX, y)) {
                return false;
            }
        }
        return true;
    }

    private boolean isInsideBeanMachine(double x, double y) {

        if (y < pegs[pegs.length - 1].getCenterY()) return true;

        MyPoint p1 = new MyPoint(leftSLine.getEndX(), leftSLine.getEndY());
        MyPoint p2 = new MyPoint(rightSLine.getEndX(), rightSLine.getEndY());
        MyPoint p3 = new MyPoint(
                pegs[pegs.length - 1].getCenterX(),
                pegs[pegs.length - 1].getCenterY());

        return (new Triangle2D(p1, p2, p3).contains(x, y));
    }


    private boolean isAtEnd(double y) {
        return y >= baseLine.getEndY() - radius * 2;
    }

    private boolean hasBouncedOnBall(double x, double y) {
        for (Circle c : fineshedBalls) {

            if (c.contains(x, y + radius * 2)) {
                return true;
            }
        }
        return false;
    }

    private double moveDown(double y){
        return y + radius;
    }

    private void drawLayout() {
        // sequence matters...
        drawBase();
        drawLeftAndRightSide();
        drawSlotLines();
        drawPegs();
        drawMidLeftAndRightSide();
        drawTopLeftAndRightSide();
        dropX = pegs[pegs.length - 1].getCenterX();
        dropY = pegs[pegs.length - 1].getCenterY() - radius * 18;

    }

    private void addLayoutShapes() {
        getChildren().addAll(
                baseLine,
                leftSLine,
                rightSLine,
                midLeftSLine,
                midRightSLine,
                topLeftSLine,
                topRightSLine
                );
        getChildren().addAll(pegs);
        getChildren().addAll(cSlotLines);
    }

    private void drawTopLeftAndRightSide() {
        topLeftSLine.setStartX(midLeftSLine.getEndX());
        topLeftSLine.setStartY(midLeftSLine.getEndY());

        topLeftSLine.setEndX(midLeftSLine.getEndX());
        topLeftSLine.setEndY(midLeftSLine.getEndY() - (radius * 5));

        topRightSLine.setStartX(midRightSLine.getEndX());
        topRightSLine.setStartY(midRightSLine.getEndY());

        topRightSLine.setEndX(midRightSLine.getEndX());
        topRightSLine.setEndY(midRightSLine.getEndY() - (radius * 5));

    }

    private void drawMidLeftAndRightSide() {
        midLeftSLine.setStartX(leftSLine.getEndX());
        midLeftSLine.setStartY(leftSLine.getEndY());
        midLeftSLine.setEndX(pegs[pegs.length - 3].getCenterX() - radius);
        midLeftSLine.setEndY(pegs[pegs.length - 1].getCenterY());

        midRightSLine.setStartX(rightSLine.getEndX());
        midRightSLine.setStartY(rightSLine.getEndY());
        midRightSLine.setEndX(pegs[pegs.length - 2].getCenterX() + radius);
        midRightSLine.setEndY(pegs[pegs.length - 1].getCenterY());

    }

    private void drawPegs() {
        // Draw triangle circles
        int index = 0;
        radius = gap / 4;
        for (int i = 1; i < numOfSlots; i++) {

            double x = leftSLine.getEndX();
            double y = leftSLine.getEndY() - radius * 1.5;
            double distanceX = gap * (i - 1) / 2 + gap;
            double distanceY = gap * (i - 1);

            for (int j = 0; j < numOfSlots - i; j++) {

                pegs[index].setCenterX(x + distanceX);
                pegs[index].setCenterY(y - distanceY);
                pegs[index].setFill(Color.TRANSPARENT);
                pegs[index].setStroke(Color.BLACK);
                pegs[index++].setRadius(radius);
                x += gap;
            }
        }
    }

    private void drawBase() {
        double y = h * 0.9;
        double x = w * 0.2;
        double length = w * 0.6;

        baseLine.setStartX(x);
        baseLine.setStartY(y);
        baseLine.setEndX(x + length);
        baseLine.setEndY(y);
    }

    private void drawLeftAndRightSide() {
        // left side
        double x = baseLine.getStartX();
        double y = baseLine.getStartY();
        double length = h * 0.12;

        leftSLine.setStartX(x);
        leftSLine.setStartY(y);
        leftSLine.setEndX(x);
        leftSLine.setEndY(y - length);

        // Right side
        x = baseLine.getEndX();
        y = baseLine.getEndY();
        rightSLine.setStartX(x);
        rightSLine.setStartY(y);
        rightSLine.setEndX(x);
        rightSLine.setEndY(y - length);
    }

    private void drawSlotLines() {
        // start after the leftSLine and increment
        // and increment until second to last line is reached
        gap = PaneCollection.distance(baseLine) / numOfSlots; // gap between each each slot

        for (int i = 0; i < cSlotLines.length; i++) {
            double distance = gap * (i + 1);
            cSlotLines[i].setStartX(leftSLine.getStartX() + distance);
            cSlotLines[i].setStartY(leftSLine.getStartY());
            cSlotLines[i].setEndX(leftSLine.getEndX() + distance);
            cSlotLines[i].setEndY(leftSLine.getEndY());
        }
    }

    private void initSlotLinesAndPegs(int slots) {
        cSlotLines = new Line[slots - 1];
        for (int i = 0; i < cSlotLines.length; i++) {
            cSlotLines[i] = new Line();
        }
        pegs = new Circle[circleCount(slots)];
        for (int i = 0; i < pegs.length; i++) {
            pegs[i] = new Circle();
        }
    }

    private int circleCount(int slots) {
        int count = 0;
        while (--slots != 0) {
            count += slots;
        }
        return count;
    }

}

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage; 
 
public class Exercise_33 extends Application {

    @Override
    public void start(Stage primaryStage) {

        TBeanMachinePane pane = new TBeanMachinePane(8, 600, 600);

        Text text = new Text(20, 20, "");

        pane.setOnMouseMoved(e -> {
            text.setText("x = " + e.getX() + "\n" + "y = " + e.getY());
        });

        Scene scene = new Scene(new StackPane(pane));

        primaryStage.setScene(scene);
        pane.getChildren().add(text);
        primaryStage.setTitle("Bean Machine");
        primaryStage.show();

        for (int i = 0; i < 10; i++) {
            pane.dropBall();
        }

    }

    public static void main(String[] args) {

        Application.launch(args);

    }
}

No comments :

Post a Comment