Skip to content

Commit 06f6b10

Browse files
committed
Completed Chapter 15
1 parent d23e545 commit 06f6b10

File tree

1 file changed

+226
-0
lines changed

1 file changed

+226
-0
lines changed

ch_15/Exercise15_33.java

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package ch_15;
2+
3+
import javafx.animation.PathTransition;
4+
import javafx.application.Application;
5+
import javafx.collections.ObservableList;
6+
import javafx.geometry.Point2D;
7+
import javafx.scene.Scene;
8+
import javafx.scene.layout.Pane;
9+
import javafx.scene.paint.Color;
10+
import javafx.scene.shape.Circle;
11+
import javafx.scene.shape.Line;
12+
import javafx.scene.shape.Polyline;
13+
import javafx.stage.Stage;
14+
import javafx.util.Duration;
15+
16+
import java.util.ArrayList;
17+
import java.util.List;
18+
19+
/**
20+
* ***15.33 (Game: bean-machine animation) Write a program that animates the bean
21+
* machine introduced in Programming Exercise 7.21. The animation terminates
22+
* after ten balls are dropped, as shown in Figure 15.36b and c.
23+
*/
24+
public class Exercise15_33 extends Application {
25+
26+
@Override
27+
public void start(Stage primaryStage) {
28+
double WIDTH = 400;
29+
double HEIGHT = 400;
30+
BeanMachinePane pane = new BeanMachinePane(WIDTH, HEIGHT, 8);
31+
Scene scene = new Scene(pane, WIDTH, HEIGHT);
32+
primaryStage.setScene(scene);
33+
primaryStage.setResizable(false);
34+
primaryStage.setTitle(getClass().getName());
35+
primaryStage.show();
36+
playAnimation(pane);
37+
}
38+
39+
static void playAnimation(BeanMachinePane pane) {
40+
/* Drop 10 balls */
41+
for (int i = 0; i < 10; i++) {
42+
pane.dropBall();
43+
}
44+
}
45+
46+
static class BeanMachinePane extends Pane {
47+
private final int numberOfPins;
48+
private final int numberOfSlots;
49+
50+
double ballRadius;
51+
52+
Line[] outlineShape;
53+
Line outlineShapeBaseLine;
54+
Line[] slotVerticalLines;
55+
Circle[] pins;
56+
57+
List<Circle> droppedBalls = new ArrayList<>();
58+
59+
double spaceBallDropDelaySeconds = 0.5;
60+
61+
public BeanMachinePane(double width, double height, int numberOfSlots) {
62+
setWidth(width);
63+
setHeight(height);
64+
this.numberOfSlots = numberOfSlots;
65+
this.numberOfPins = getNumberOfPins(numberOfSlots);
66+
buildStaticShapes();
67+
getChildren().add(outlineShapeBaseLine);
68+
getChildren().addAll(outlineShape);
69+
getChildren().addAll(slotVerticalLines);
70+
getChildren().addAll(pins);
71+
}
72+
73+
public void dropBall() {
74+
ballDropAnimation();
75+
}
76+
77+
private void ballDropAnimation() {
78+
/* Draw ball in the center flask shape opening */
79+
double flaskOpeningCenterX = getDropStartPtX();
80+
/* Draw ball just ABOVE flask shape opening */
81+
ballRadius = pins[0].getRadius();
82+
Circle ball = new Circle(flaskOpeningCenterX,
83+
outlineShape[5].getEndY() - ballRadius - 20,
84+
ballRadius,
85+
Color.RED);
86+
Polyline polyline = createRandomPath(ball);
87+
PathTransition path = new PathTransition(Duration.seconds(4), polyline, ball);
88+
path.setDelay(Duration.seconds(spaceBallDropDelaySeconds));
89+
spaceBallDropDelaySeconds += 1.5;
90+
getChildren().addAll(polyline);
91+
getChildren().addAll(ball);
92+
droppedBalls.add(ball);
93+
path.play();
94+
}
95+
96+
private double getDropStartPtX() {
97+
return outlineShapeBaseLine.getStartX()
98+
+ (outlineShapeBaseLine.getEndX()
99+
- outlineShapeBaseLine.getStartX()) / 2;
100+
}
101+
102+
103+
private Line getBaseLineShape(double paneWidth, double paneHeight) {
104+
/* Horizontal line at the bottom of the machine (Y value does not change) */
105+
double baseLineYValue = paneHeight * 0.8;
106+
double baseLineStartXValue = paneWidth * 0.2;
107+
double baseLineEndXValue = paneWidth * 0.8;
108+
return new Line(baseLineStartXValue, baseLineYValue, baseLineEndXValue, baseLineYValue);
109+
110+
}
111+
112+
113+
/**
114+
* @param slots the number of slots in the bean machine
115+
* @return the number of pins in the bean machine
116+
*/
117+
int getNumberOfPins(int slots) {
118+
int numPins = 0;
119+
do {
120+
slots--;
121+
numPins += slots;
122+
} while (slots > 1);
123+
124+
return numPins;
125+
}
126+
127+
private void buildStaticShapes() {
128+
double paneWidth = getWidth();
129+
double paneHeight = getHeight();
130+
outlineShapeBaseLine = getBaseLineShape(paneWidth, paneHeight);
131+
double distance = (outlineShapeBaseLine.getEndX() - outlineShapeBaseLine.getStartX()) / numberOfSlots;
132+
pins = new Circle[numberOfPins];
133+
int index = 0;
134+
for (int i = 1; i < numberOfSlots; i++) {
135+
double x = outlineShapeBaseLine.getStartX() + (i * distance * 0.50) + distance / 2;
136+
double y = outlineShapeBaseLine.getStartY() - (distance * i) - distance / 2;
137+
for (int j = 0; j < numberOfSlots - i; j++) {
138+
pins[index++] = new Circle(x, y, paneWidth * 0.012, Color.BLUE);
139+
x += distance;
140+
}
141+
}
142+
143+
distance = distance + (distance / 2) - pins[0].getRadius();
144+
slotVerticalLines = new Line[numberOfSlots - 1];
145+
for (int i = 0; i < numberOfSlots - 1; i++) {
146+
double x1 = pins[i].getCenterX() + pins[i].getRadius() * Math.sin(Math.PI);
147+
double y1 = pins[i].getCenterY() - pins[i].getRadius() * Math.cos(Math.PI);
148+
slotVerticalLines[i] = new Line(x1, y1, x1, y1 + distance);
149+
150+
}
151+
152+
outlineShape = new Line[6];
153+
outlineShape[0] = new Line(
154+
outlineShapeBaseLine.getEndX(), outlineShapeBaseLine.getEndY(),
155+
outlineShapeBaseLine.getEndX(), outlineShapeBaseLine.getEndY() - distance);
156+
outlineShape[1] = new Line(
157+
outlineShapeBaseLine.getStartX(), outlineShapeBaseLine.getStartY(),
158+
outlineShapeBaseLine.getStartX(), outlineShapeBaseLine.getStartY() - distance);
159+
160+
for (int i = 2; i < 4; i++) {
161+
double x = pins[pins.length - i].getCenterX();
162+
double y = pins[pins.length - i].getCenterY() - distance;
163+
outlineShape[i] =
164+
new Line(x, y, outlineShape[i - 2].getEndX(), outlineShape[i - 2].getEndY());
165+
}
166+
167+
for (int i = 4; i < outlineShape.length; i++) {
168+
outlineShape[i] =
169+
new Line(
170+
outlineShape[i - 2].getStartX(),
171+
outlineShape[i - 2].getStartY(),
172+
outlineShape[i - 2].getStartX(),
173+
outlineShape[i - 2].getStartY() - (distance * 0.6)
174+
);
175+
}
176+
}
177+
178+
private Polyline createRandomPath(Circle ball) {
179+
Polyline polyLine = new Polyline();
180+
polyLine.setFill(Color.TRANSPARENT);
181+
polyLine.setStroke(Color.TRANSPARENT);
182+
ObservableList<Double> pathPoints = polyLine.getPoints();
183+
Point2D startPoint = new Point2D(ball.getCenterX(), ball.getCenterY());
184+
double nextX = startPoint.getX(); // Set current X to ball's center X starting position
185+
double nextY = startPoint.getY(); // Set current Y to ball's center Y starting position
186+
pathPoints.addAll(nextX, nextY);
187+
188+
while (nextY < outlineShapeBaseLine.getStartY() - ball.getRadius()) {
189+
nextY += 1; // Move ball down on Y axis to simulate falling
190+
/* Re-calculate random path until it does not pass through a static shape */
191+
nextX = getRandomX(nextX);
192+
while (collideWithPins(nextX, nextY) || collideWithOutline(nextX, nextY)) {
193+
nextX = getRandomX(nextX);
194+
}
195+
pathPoints.addAll(nextX, nextY);
196+
}
197+
198+
return polyLine;
199+
}
200+
201+
private boolean collideWithOutline(double nextX, double nextY) {
202+
return false;
203+
}
204+
205+
private boolean collideWithPins(double nextX, double nextY) {
206+
for (Circle pin : pins) {
207+
if (pin.contains(nextX, nextY + ballRadius)) {
208+
return true;
209+
}
210+
}
211+
return false;
212+
}
213+
214+
private double getRandomX(double currentX) {
215+
double random = Math.random();
216+
if (random < 0.5) {
217+
currentX -= 1;
218+
} else {
219+
currentX += 1;
220+
}
221+
return currentX;
222+
}
223+
224+
225+
}
226+
}

0 commit comments

Comments
 (0)