-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGameComponent.java
More file actions
264 lines (226 loc) · 9.88 KB
/
Copy pathGameComponent.java
File metadata and controls
264 lines (226 loc) · 9.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
package a6;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Random;
import javax.swing.JPanel;
import javax.swing.Timer;
/** GUI component for Click-a-Dot game board. Maintains and visualizes game state and responds to
* mouse inputs. Allows programmatic control of game settings and property access to game state. */
public class GameComponent extends JPanel implements MouseListener {
/** Number of targets to show during one game. */
private int maxTargets= 10;
/** Duration each target is shown [ms]. */
private int targetTimeMillis= 1500;
/** Encapsulates state and controls for current target. */
private Target target= new Target();
/** Timer to trigger changing targets. */
private Timer timer;
/** Whether a game is currently being played. */
private boolean isActive= false;
/** Number of targets that have been shown during current game (including the current target).
* If a game is not currently being played, is the number of targets shown during the previous
* game (0 if no games have been played). */
private int targetCount= 0;
/** Number of targets successfully hit by player during the current game. If a game is not
* currently being played, is the number of targets hit during the previous game (0 if no games
* have been played). */
private int score= 0;
/** Construct a new GameComponent with default settings. */
public GameComponent() {
// Set up timer with initial delay of 0 (so target is shown as soon as
// game starts). Coalescing queued actions ensures targets are not
// skipped due to stalls or leftover events after a game is restarted.
timer= new Timer(targetTimeMillis, (ActionEvent e) -> timeout());
timer.setInitialDelay(0);
timer.setCoalesce(true);
// This component reacts to mouse events.
addMouseListener(this);
// Set a recommended size for the game board (prevents it from
// disappearing when frame is packed).
setPreferredSize(new Dimension(480, 360));
}
/** Start a new game using current settings. Progress from any previous or ongoing game is
* reset. Request a repaint. */
public void startGame() {
targetCount= 0;
setScore(0);
isActive= true;
timer.restart();
repaint();
}
/** Stop current game. Takes effect immediately. Requests a repaint. All background tasks are
* cancelled when game is stopped. */
public void stopGame() {
timer.stop();
isActive= false;
repaint();
}
/** Update the game when the timer "goes off". If the game is inactive, does nothing. If the
* game is active, and if the maximum number of targets has already been shown, stops the game.
* Otherwise, respawns the target within the current component area, increments the target
* count, and requests a repaint. */
private void timeout() {
// TODO 5: Implement this method according to its specification.
if (isActive) {
if (targetCount == maxTargets) {
stopGame();
} else {
target.respawn(getWidth(), getHeight());
targetCount+= 1;
repaint();
}
}
}
/** Query the current score. */
public int getScore() {
return score;
}
/** Change the current score to `newScore` and notify observers. */
private void setScore(int newScore) {
int oldScore= score;
score= newScore;
firePropertyChange("GameScore", oldScore, newScore);
}
/** Query the radius of the target dot [px]. */
public int getTargetRadius() {
return target.radius;
}
/** Change the radius of the target dot to `r` px. Takes effect immediately (triggers a repaint
* request).
* <p>
* Precondition: `r > 0` */
public void setTargetRadius(int r) {
target.radius= r;
repaint();
}
/** Query the duration each target is shown [ms]. */
public int getTargetTimeMillis() {
return targetTimeMillis;
}
/** Change the duration each target is shown to `t` ms. Does not affect the currently-shown
* target (if any).
* <p>
* Precondition: `t >= 0` */
public void setTargetTimeMillis(int t) {
targetTimeMillis= t;
timer.setDelay(targetTimeMillis);
}
/** Visualize the current game state by painting on `g`. If the game is inactive, fills the
* component area with black. If the game is active, draws the target on top of the default
* JPanel background. */
@Override
public void paintComponent(Graphics g) {
// Paint the default background. Keep this as the first line of the method.
super.paintComponent(g);
// TODO 4: Implement this method according to its specification.
// Use these classes and methods: Graphics.setColor, Graphics.fillRect [1]; Color [2]
// [1]:
// https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/Graphics.html
// [2]: https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/Color.html
if (!isActive) {
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
} else {
// super.paintComponent(g);
target.paintDot(g);
}
}
/** Handle a mouse button being pressed. If the game is inactive, does nothing. If the game is
* active, and if the press was a new target hit, increments score by 1, notifies observers, and
* requests a repaint. */
@Override
public void mousePressed(MouseEvent e) {
// TODO 8: Implement this method according to its specifications.
// The X and Y coordinates of the mouse press can be found using `MouseEvent.getX` and
// `.getY`. [1]
// Do not modify the `score` field directly; use `setScore`.
// [1]:
// https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/event/MouseEvent.html
if (isActive) {
if (!target.isHit && target.checkHit(e.getX(), e.getY())) {
setScore(getScore() + 1);
repaint();
}
}
}
// The remaining MouseListener event handlers are required to be present because of the
// `implements`
// declaration of this class. But they are unused by this game.
@Override
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseClicked(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
/** Represents a dot to be clicked. Maintains current position and size and whether it has
* already been "hit" in its current location. Able to check clicks for hits, move to a new
* location, and paint itself. */
private static class Target {
/** x-coordinate of current center position [px]. */
int x;
/** y-coordinate of current center position [px]. */
int y;
/** Radius of circular dot representing the target [px]. */
int radius= 15;
/** Whether this has been "hit" in its current location. */
private boolean isHit;
/** Generate random numbers to use when choosing new location. */
private Random rng= new Random();
/** Paint dot on provided Graphics `g`. Dot is a circle with our current radius, centered at
* our current position. Circle is filled red if we have been hit, otherwise blue. */
void paintDot(Graphics g) {
// TODO 6: Implement this method according to its specifications.
// Use these classes and methods: Graphics.setColor, Graphics.fillOval [1]; Color [2].
// [1]:
// https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/Graphics.html
// [2]:
// https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/Color.html
if (isHit) {
g.setColor(Color.red);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
} else {
g.setColor(Color.blue);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
}
/** Clip `coord` to be within `[radius, max - radius]`, if possible. Otherwise, return
* `coord` unchanged. */
int clip(int coord, int max) {
if (2 * radius > max) return coord;
if (coord < radius) {
return radius;
} else if (coord > max - radius) {
return max - radius;
} else {
return coord;
}
}
/** Move target to a random location and reset "hit" state. xMax and yMax are the
* (inclusive) upper bounds of the x- and y-coordinates of the bounding box of the new
* position [px]; lower bound is 0 (inclusive). */
void respawn(int xMax, int yMax) {
x= clip(rng.nextInt(xMax + 1), xMax);
y= clip(rng.nextInt(yMax + 1), yMax);
isHit= false;
}
/** Return true and set state to "hit" if coordinates (cx,cy) are within this target's
* circular area and it was not already "hit"; return false otherwise. */
boolean checkHit(int cx, int cy) {
// TODO 7: Implement this method according to its specifications. Delete the
// `throw` statement and replace it with your own implementation.
// No Swing methods are needed, just high-school geometry.
if (cx <= x + radius && cx >= x - radius &&
cy <= y + radius && cy >= y - radius) {
isHit= true;
return true;
}
return false;
}
}
}