getDoubleParam
last analyzed

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 2
c 0
b 0
f 0
1
package org.gannacademy.cdf.turtlelogo;
2
3
import java.awt.*;
4
import java.awt.geom.Line2D;
5
import java.awt.geom.Point2D;
6
import java.util.Queue;
7
import java.util.concurrent.ConcurrentLinkedQueue;
8
9
/**
10
 * Animated turtles are turtles that move more slowly, so that we can observe them following their instructions. Their
11
 * speed can be adjusted either when they are instantiated or as a separate instruction ({@link #speed(long)})
12
 *
13
 * @author <a href="https://github.com/gann-cdf/turtlelogo/issues">Seth Battis</a>
14
 */
15
public class AnimatedTurtle extends Turtle implements Runnable {
16
17
  /**
18
   * 10 milliseconds
19
   */
20
  public static final long DEFAULT_FRAME_DELAY = 10; // milliseconds
21
22
  /**
23
   * <p>25 milliseconds</p>
24
   * <p>This is an internal measure, sued to determine how and when to animate turtle turns (frame delays longer than
25
   * this cutoff will have the turns animated in larger steps</p>
26
   */
27
  public static final long TURN_SPEED_CUTOFF = 25; // milliseconds
28
29
  private enum Verb {
30
    MOVE, TURN, HEAD,
31
    PEN_UP, PEN_DOWN, PEN_COLOR, PEN_WIDTH,
32
    HIDE, SHOW,
33
    MOVE_TO, TELEPORT, HOME,
34
    SPEED
35
  }
36
37
  private class Instruction {
38
    private Verb verb;
39
    private Object parameter;
40
41
    public Instruction(Verb verb) {
42
      this.verb = verb;
43
    }
44
45
    public Instruction(Verb verb, double value) {
46
      this.verb = verb;
47
      this.parameter = value;
48
    }
49
50
    public Instruction(Verb verb, long value) {
51
      this.verb = verb;
52
      this.parameter = value;
53
    }
54
55
    public Instruction(Verb verb, double x, double y) {
56
      this.verb = verb;
57
      this.parameter = new Point2D.Double(x, y);
58
    }
59
60
    public Instruction(Verb verb, Color color) {
61
      this.verb = verb;
62
      this.parameter = color;
63
    }
64
65
    public Verb getVerb() {
66
      return verb;
67
    }
68
69
    public double getDoubleParam() {
70
      return (double) parameter;
71
    }
72
73
    public long getLongParam() {
74
      return (long) parameter;
75
    }
76
77
    public Point2D getPointParam() {
78
      return (Point2D) parameter;
79
    }
80
81
    public Color getColorParam() {
82
      return (Color) parameter;
83
    }
84
85
    public void convertTo(Verb verb) {
86
      this.verb = verb;
87
    }
88
  }
89
90
  private Queue<Instruction> instructions;
91
  private Instruction activeInstruction;
92
  private double MOVE_steps, MOVE_targetSteps, TURN_degrees, TURN_targetDegrees, MOVE_TO_tempHeadingInRadians;
93
  private long frameDelay, tick;
94
  private boolean threadStarted = false;
95
96
  /**
97
   * Construct an animated turtle with {@link #DEFAULT_FRAME_DELAY}
98
   */
99
  public AnimatedTurtle() {
100
    this(DEFAULT_FRAME_DELAY);
101
  }
102
103
  /**
104
   * Construct an animated turtle with a custom frame delay
105
   *
106
   * @param frameDelay between frames of animation, measured in milliseconds
107
   */
108
  public AnimatedTurtle(long frameDelay) {
109
    this(frameDelay, Terrarium.getInstance());
110
  }
111
112
  /**
113
   * Construct an animated turtle with {@link #DEFAULT_FRAME_DELAY} in a specific terrarium
114
   *
115
   * @param terrarium to house the turtle
116
   */
117
  public AnimatedTurtle(Terrarium terrarium) {
118
    this(DEFAULT_FRAME_DELAY, terrarium);
119
  }
120
121
  /**
122
   * Construct an animated turtle with a custom frame delay in a specific terrarium
123
   *
124
   * @param frameDelay between frames of animation, measured in milliseconds
125
   * @param terrarium  to house the turtle
126
   */
127
  public AnimatedTurtle(long frameDelay, Terrarium terrarium) {
128
    super(terrarium);
129
    this.frameDelay = frameDelay;
130
    tick = System.currentTimeMillis();
131
    instructions = new ConcurrentLinkedQueue<>(); // thread-safe
132
    new Thread(this).start();
133
  }
134
135
  @Override
136
  public void move(double steps) {
137
    instructions.add(new Instruction(Verb.MOVE, steps));
138
  }
139
140
  @Override
141
  public void turn(double angle) {
142
    instructions.add(new Instruction(Verb.TURN, angle));
143
  }
144
145
  @Override
146
  public void head(double heading) {
147
    instructions.add(new Instruction(Verb.HEAD, heading));
148
  }
149
150
  @Override
151
  public void penUp() {
152
    instructions.add(new Instruction(Verb.PEN_UP));
153
  }
154
155
  @Override
156
  public void penDown() {
157
    instructions.add(new Instruction(Verb.PEN_DOWN));
158
  }
159
160
  @Override
161
  public void penColor(Color color) {
162
    instructions.add(new Instruction(Verb.PEN_COLOR, color));
163
  }
164
165
  @Override
166
  public void penWidth(double width) {
167
    instructions.add(new Instruction(Verb.PEN_WIDTH, width));
168
  }
169
170
  @Override
171
  public void hide() {
172
    instructions.add(new Instruction(Verb.HIDE));
173
  }
174
175
  @Override
176
  public void show() {
177
    instructions.add(new Instruction(Verb.SHOW));
178
  }
179
180
  @Override
181
  public void teleport(double x, double y) {
182
    instructions.add(new Instruction(Verb.TELEPORT, x, y));
183
  }
184
185
  @Override
186
  public void moveTo(double x, double y) {
187
    instructions.add(new Instruction(Verb.MOVE_TO, x, y));
188
  }
189
190
  @Override
191
  public void home() {
192
    instructions.add(new Instruction(Verb.HOME));
193
  }
194
195
  /**
196
   * Alias for {@link #speed(long)}
197
   *
198
   * @param frameDelay in milliseconds
199
   */
200
  public void sp(long frameDelay) {
201
    speed(frameDelay);
202
  }
203
204
  /**
205
   * <p>Set the speed of the turtle's animation</p>
206
   * <p>The frame delay is the time between individual frames of animation. Shorter frame delays
207
   * (below {@link #TURN_SPEED_CUTOFF}) will cause turn animations to be animated in more detail to allow them to remain
208
   * visible to the naked eye</p>
209
   *
210
   * @param frameDelay in milliseconds
211
   */
212
  public void speed(long frameDelay) {
213
    instructions.add(new Instruction(Verb.SPEED, frameDelay));
214
  }
215
216
  /**
217
   * <p>Thread execution</p>
218
   * <p>This method is the control loop that allows animation to be updated in a separate control loop for each turtle.
219
   * This method should not be called by students (although calling it manually should do nothing).</p>
220
   */
221
  @Override
222
  public void run() {
223
    if (!threadStarted) {
224
      threadStarted = true;
225
      while (true) {
0 ignored issues
show
introduced by
Add an end condition to this loop.
Loading history...
226
        if (activeInstruction == null) {
227
          if (!instructions.isEmpty()) {
228
            activeInstruction = instructions.remove();
229
            switch (activeInstruction.getVerb()) {
230
              case MOVE:
231
                MOVE_targetSteps = activeInstruction.getDoubleParam();
232
                MOVE_steps = 0;
233
                break;
234
              case MOVE_TO:
235
                double dx = activeInstruction.getPointParam().getX() - getX(),
236
                        dy = activeInstruction.getPointParam().getY() - getY();
237
                MOVE_targetSteps = Math.hypot(dx, dy);
238
                MOVE_steps = 0;
239
                MOVE_TO_tempHeadingInRadians = Math.atan2(dy, dx);
240
                break;
241
              case TURN:
242
                TURN_targetDegrees = activeInstruction.getDoubleParam();
243
                TURN_degrees = 0;
244
                break;
245
              case HEAD:
246
                activeInstruction.convertTo(Verb.TURN);
247
                if (Math.abs(getHeadingInDegrees() - activeInstruction.getDoubleParam()) > 180.0) {
248
                  TURN_targetDegrees = (360 - Math.abs(getHeadingInDegrees() - activeInstruction.getDoubleParam())) * (getHeadingInDegrees() > activeInstruction.getDoubleParam() ? 1 : -1);
249
                } else {
250
                  TURN_targetDegrees = getHeadingInDegrees() - activeInstruction.getDoubleParam();
251
                }
252
                TURN_degrees = 0;
253
                break;
254
              case PEN_UP:
255
                super.penUp();
256
                activeInstruction = null;
257
                break;
258
              case PEN_DOWN:
259
                super.penDown();
260
                activeInstruction = null;
261
                break;
262
              case PEN_COLOR:
263
                super.penColor(activeInstruction.getColorParam());
264
                activeInstruction = null;
265
                break;
266
              case PEN_WIDTH:
267
                super.penWidth(activeInstruction.getDoubleParam());
268
                activeInstruction = null;
269
                break;
270
              case HIDE:
271
                super.hide();
272
                activeInstruction = null;
273
                break;
274
              case SHOW:
275
                super.show();
276
                activeInstruction = null;
277
                break;
278
              case TELEPORT:
279
                super.teleport(activeInstruction.getPointParam().getX(), activeInstruction.getPointParam().getY());
280
                activeInstruction = null;
281
                break;
282
              case HOME:
283
                super.home();
284
                activeInstruction = null;
285
                break;
286
              case SPEED:
287
                frameDelay = activeInstruction.getLongParam();
288
                activeInstruction = null;
289
                break;
290
            }
291
          }
292
        } else if (System.currentTimeMillis() > tick + frameDelay) {
293
          tick = System.currentTimeMillis();
294
          switch (activeInstruction.getVerb()) {
0 ignored issues
show
Code Smell introduced by
Complete cases by adding the missing enum constants or add a default case to this switch.

Default branches should deal with the unexpected. At a minimum, they should log the error and if applicable, return a default value (null, empty collection). If you really do not expect the default branch to ever be use, throw a RuntimeException when it is.

Loading history...
295
            case MOVE:
296
            case MOVE_TO:
297
              if (Math.abs(MOVE_steps) >= Math.abs(MOVE_targetSteps)) {
298
                if (activeInstruction.getVerb() == Verb.MOVE) {
299
                  super.move(MOVE_targetSteps);
300
                } else {
301
                  super.moveTo(activeInstruction.getPointParam().getX(), activeInstruction.getPointParam().getY());
302
                }
303
                activeInstruction = null;
304
              } else {
305
                MOVE_steps += (MOVE_targetSteps > 0.0 ? 1 : -1);
306
              }
307
              break;
308
            case TURN:
309
              if (Math.abs(TURN_degrees) >= Math.abs(TURN_targetDegrees)) {
310
                super.turn(TURN_targetDegrees);
311
                activeInstruction = null;
312
              } else {
313
                TURN_degrees += (TURN_targetDegrees > 0.0 ? 1 : -1) * (frameDelay >= TURN_SPEED_CUTOFF ? 1 : TURN_SPEED_CUTOFF - frameDelay);
314
              }
315
              break;
316
          }
317
          getTerrarium().repaint();
318
        }
319
      }
320
    }
321
  }
322
323
  public void draw(Graphics2D context, Terrarium.UnderTheSurface key) {
324
    key.hashCode();
325
    if (activeInstruction == null) {
326
      super.draw(context, key);
327
    } else {
328
      context.setPaint(getPenColor());
329
      context.setStroke(getPenStroke());
330
      switch (activeInstruction.getVerb()) {
0 ignored issues
show
Code Smell introduced by
Complete cases by adding the missing enum constants or add a default case to this switch.

Default branches should deal with the unexpected. At a minimum, they should log the error and if applicable, return a default value (null, empty collection). If you really do not expect the default branch to ever be use, throw a RuntimeException when it is.

Loading history...
331
        case MOVE:
332
        case MOVE_TO:
333
          double moveHeadingInRadians = MOVE_TO_tempHeadingInRadians;
334
          if (activeInstruction.getVerb() == Verb.MOVE) {
335
            moveHeadingInRadians = getHeadingInRadians();
336
          }
337
          double tempX = getX() + Math.cos(moveHeadingInRadians) * MOVE_steps;
338
          double tempY = getY() + Math.sin(moveHeadingInRadians) * MOVE_steps;
339
          if (isPenDown()) {
340
            context.draw(new Line2D.Double(getX(), getY(), tempX, tempY));
341
          }
342
          if (!isHidden()) {
343
            drawIcon(tempX, tempY, getHeadingInRadians(), context);
344
          }
345
          break;
346
        case TURN:
347
          if (!isHidden()) {
348
            drawIcon(getX(), getY(), Math.toRadians(getHeadingInDegrees() + TURN_degrees), context);
349
          }
350
          break;
351
      }
352
    }
353
  }
354
}
355