001 package fang;
002
003 import java.awt.*;
004 import javax.swing.*;
005
006
007 import java.net.URL;
008 import java.util.LinkedList;
009 import java.util.TreeMap;
010
011 /**
012 * used to advance the frame in an animation using the AnimationCanvas. There
013 * are two basic parametes to the FrameAdvancer: the model frame rate and the
014 * screen frame rate. The model frame rate describes the number of frames per
015 * second which must be computed to have a consistent model. This in effect
016 * determines the maximum time for computations between displays. The screen
017 * frame rate is how often the screen is refreshed with the current Sprites. The
018 * screen rate is never higher than the model rate.
019 *
020 * @author Jam Jenkins
021 */
022
023 public abstract class FrameAdvancer extends GameWindow implements
024 AlarmScheduler {
025 /** keeps track of the screen refresh rate */
026 private LinkedList<Double> refreshTimes;
027
028 /** the canvas which displays the Sprites */
029 protected AnimationCanvas canvas;
030
031 /** the current time, used in updateInterval method */
032 private double currentTime;
033
034 /** offset used for restarting the game */
035 private double timeOffset = 0;
036
037 /** time since the last call to updateInterval */
038 public double timeInterval;
039
040 /** the highest allowable time between calls to update */
041 private double maxModelTimeInterval = 1 / 10.0;
042
043 /** all pending alarms */
044 private TreeMap<Double, LinkedList<Alarm>> alarms = new TreeMap<Double, LinkedList<Alarm>>();
045
046 /** initializes the canvas to empty and no alarms set */
047 public FrameAdvancer() {
048 this(AnimationCanvas.DEFAULT_SIZE, AnimationCanvas.DEFAULT_BACKGROUND);
049 }
050
051 /** initializes the canvas to size and no alarms set */
052 public FrameAdvancer(Dimension gameDimensions) {
053 this(gameDimensions, AnimationCanvas.DEFAULT_BACKGROUND);
054 }
055
056 public FrameAdvancer(Dimension gameDimensions, Color backgroundColor) {
057 super();
058 canvas = new AnimationCanvas(gameDimensions, backgroundColor);
059 canvas.removeAllSprites();
060 alarms.clear();
061 refreshTimes = new LinkedList<Double>();
062 for (int i = 0; i < 100; i++)
063 refreshTimes.add(0.0);
064
065 }
066
067 /**
068 * Set the bacground color of this game. This sets the background color of
069 * both the FrameAdvancer and the contained AnimationCanvas.
070 *
071 * @param backgroundColor
072 * color to use for the background of the game field
073 */
074 public void setBackground(Color backgroundColor) {
075 if (canvas != null)
076 canvas.setBackground(backgroundColor);
077 super.setBackground(backgroundColor);
078 }
079
080 /**
081 * set canvas to a new canvas
082 *
083 * @param canvas
084 */
085 public void setCanvas(AnimationCanvas canvas) {
086 this.canvas = canvas;
087 }
088
089 /**
090 * returns the canvas
091 *
092 * @return canvas
093 */
094 public AnimationCanvas getCanvas() {
095 return canvas;
096 }
097
098 /** called repeatedly between every model update */
099 public void advanceFrame(double timePassed) {
100 }
101
102 /**
103 * returns the current time in seconds since the beginning of this game. When
104 * the game starts over, so does the time.
105 *
106 * @return the current time in seconds
107 */
108 public double getTime() {
109 return currentTime - timeOffset;
110 }
111
112 /**
113 * returns the screen refresh rate
114 *
115 * @return ScreenRefreshRate
116 */
117 public double getScreenRefreshRate() {
118 return (refreshTimes.size() - 1)
119 / (refreshTimes.getLast() - refreshTimes.getFirst());
120 }
121
122 /**
123 * sets the minimum number of frames which must be computed per second. This
124 * is separate from the screen frame rate in that more frames can be computed
125 * than displayed, but it can be affected by (and affect) the screen rate. Too
126 * high of a model frame rate can cause computation to slow down the display
127 * rate, and too low of a model frame rate can cause important events to be
128 * missed (typically the intersection of Sprites). Too high a model frame rate
129 * can slow down the advancing of time to slower than physical time. Frame
130 * rates higher than 200 Hz are not allowed.
131 *
132 * @param framesPerSecond
133 * the number of frames computed per second
134 */
135 public void setMinimumModelFrameRate(int framesPerSecond) {
136 if (framesPerSecond > 0) {
137 maxModelTimeInterval = Math.max(1.0 / 200, 1.0 / framesPerSecond);
138 }
139 }
140
141 /**
142 * sets and alarm to go off relative to the current time. For example, this
143 * method can be used to set off an alarm in 5 seconds from the current time.
144 *
145 * @param alarm
146 * the class to call the alarm method on
147 * @param relative
148 * the time from now in seconds to call the alarm method
149 */
150 public void scheduleRelative(Alarm alarm, double relative) {
151 scheduleAbsolute(alarm, relative + currentTime - timeOffset);
152 // double absolute=currentTime + relative;
153 // LinkedList<Alarm> existing =
154 // alarms.get(absolute);
155 // if(existing==null)
156 // existing=new LinkedList<Alarm>();
157 // existing.add(alarm);
158 // alarms.put(absolute, existing);
159 }
160
161 public void schedule(TimedAction action, double delay) {
162 scheduleRelative(action, delay);
163 }
164
165 /**
166 * sets and alarm to go off at a time relative to the beginning of time
167 * (zero). For example, this method can be used to set off an alarm 30 seconds
168 * from the beginning time.
169 *
170 * @param alarm
171 * the class to call the alarm method on
172 * @param absolute
173 * the time in seconds to call the alarm method
174 */
175 public void scheduleAbsolute(Alarm alarm, double absolute) {
176 LinkedList<Alarm> existing = alarms.get(absolute + timeOffset);
177 if (existing == null)
178 existing = new LinkedList<Alarm>();
179 existing.add(alarm);
180 alarms.put(absolute + timeOffset, existing);
181 }
182
183 /**
184 * removes all pending alarms on this object. If there are no alarms with this
185 * object as the target, the method call is ignored.
186 *
187 * @param alarm
188 * the object that is the target of a pending alarm
189 */
190 public void cancelAlarm(Alarm alarm) {
191 for (LinkedList<Alarm> list : alarms.values()) {
192 if (list.contains(alarm)) {
193 list.remove(alarm);
194 }
195 }
196 }
197
198 public void cancel(TimedAction action) {
199 cancelAlarm(action);
200 }
201
202 /**
203 * removes all pending alarms. If there are no pending alarms, the method call
204 * is ignored.
205 */
206 public void cancelAllAlarms() {
207 alarms.clear();
208 }
209
210 public void cancelAllTimedActions() {
211 cancelAllAlarms();
212 }
213
214 public TimedAction[] getAllTimedActions() {
215 LinkedList<TimedAction> all = new LinkedList<TimedAction>();
216 for (LinkedList<Alarm> list : alarms.values())
217 for (Alarm single : list)
218 if (single instanceof TimedAction)
219 all.add((TimedAction) single);
220 return all.toArray(new TimedAction[0]);
221 }
222
223 /**
224 * gets the list of alarms scheduled to go off in the future.
225 *
226 * @return the array of alarms in the order which they would go off
227 */
228 public Alarm[] getAlarms() {
229 LinkedList<Alarm> all = new LinkedList<Alarm>();
230 for (LinkedList<Alarm> list : alarms.values())
231 all.addAll(list);
232 return all.toArray(new Alarm[0]);
233 }
234
235 /**
236 * updates the model between displays. This method can split the time passed
237 * if more time has passed since the last display than the maximum time
238 * interval allowable for the model frame rate.
239 *
240 * @param time
241 * the current absolute time
242 */
243 public void updateModel(double time) {
244 double timeInterval = time - currentTime;
245 while (timeInterval > 0) {
246 double timeToNextAlarm = Double.MAX_VALUE;
247 if (!alarms.isEmpty()) {
248 timeToNextAlarm = alarms.firstKey() - currentTime;
249 }
250 double advanced = Math.min(timeInterval, maxModelTimeInterval);
251 advanced = Math.min(advanced, timeToNextAlarm);
252 if (alarms.size() > 0 && currentTime - alarms.firstKey() == advanced) {
253 LinkedList<Alarm> alarmsToSetOff = alarms.remove(alarms.firstKey());
254 for (Alarm alarm : alarmsToSetOff) {
255 alarm.alarm();
256 }
257 }
258 canvas.updateSprites(advanced);
259 currentTime += advanced;
260 timeInterval -= advanced;
261 try {
262 // sometimes the game loses focus
263 // and stops responding to user input
264 // the following 2 lines fix this - Jam
265 if (!canvas.hasFocus())
266 canvas.requestFocusInWindow();
267 advanceFrame(advanced);
268 postAdvanceFrame(advanced);
269 } catch (Throwable e) {
270 Container container = getContentPane();
271 JPanel panel = new JPanel(new GridLayout(2, 1));
272 panel.add(container);
273 JTextArea errorMessage = new JTextArea();
274 errorMessage.setText("Cannot advanceFrame!\n");
275 errorMessage.append("Caused by Exception: " + e.toString() + "\n");
276 for (StackTraceElement ste : e.getStackTrace())
277 errorMessage.append("\t" + ste + "\n");
278 panel.add(new JScrollPane(errorMessage));
279 setContentPane(panel);
280 System.err.println("Error!");
281 e.printStackTrace();
282 ErrorConsole.addError("none", "none", e);
283 // this.pauseToggle();
284 }
285
286 }
287 }
288
289 /**
290 * sets the cursor for the game engine
291 *
292 * @param filename
293 * the image to display as the cursor
294 */
295 public void setCursor(URL url) {
296 canvas.setCursor(url);
297 }
298
299 /** removes the cursor from the game canvas */
300 public void removeCursor() {
301 canvas.removeCursor();
302 }
303
304 /** adds the default cursor back to the screen */
305 public void restoreCursor() {
306 canvas.restoreCursor();
307 }
308
309 /**
310 * called after advanceFrame
311 *
312 * @param timePassed
313 * the duration since the last frame
314 */
315 public abstract void postAdvanceFrame(double timePassed);
316
317 /**
318 * updates the AnimationCanvas, should only be called from the AWTEvent
319 * Thread.
320 */
321 public void refreshScreen() {
322 refreshTimes.removeFirst();
323 refreshTimes.addLast(currentTime);
324 canvas.paintImmediately();
325 // controlPanel.repaint();
326 }
327
328 /**
329 * makes the current time zero
330 */
331 public void resetTime() {
332 timeOffset = currentTime;
333 }
334 }