1 package se.nekotronic.satelliterush;
2
3 import java.util.ArrayList;
4 import java.util.Random;
5 import java.util.Timer;
6 import java.util.TimerTask;
7
8 import android.app.Activity;
9 import android.content.Context;
10 import android.graphics.Canvas;
11 import android.graphics.Color;
12 import android.graphics.Paint;
13 import android.graphics.Paint.Style;
14 import android.location.Location;
15 import android.location.LocationListener;
16 import android.location.LocationManager;
17 import android.location.LocationProvider;
18 import android.os.Bundle;
19 import android.text.format.Time;
20 import android.util.AttributeSet;
21 import android.util.DisplayMetrics;
22 import android.util.Log;
23 import android.view.Display;
24 import android.view.View;
25 import android.view.WindowManager;
26
27 public class RushView extends View {
28
29 // Ok, these bricks are round, but you get what I mean. Positions are in
30 // pixels.
31 class Brick {
32 public float x, y;
33 public float radius;
34 public int color;
35 public boolean active = true;
36
37 public Brick(float x, float y, float radius, int color) {
38 this.x = x;
39 this.y = y;
40 this.radius = radius;
41 this.color = color;
42 }
43 } // class Brick
44
45 // A ball is a moving brick! Speeds are measured in pixels per second.
46 class Ball extends Brick {
47 public float dx, dy;
48
49 public Ball(float x, float y, float dx, float dy, float radius,
50 int color) {
51 super(x, y, radius, color);
52 this.dx = dx;
53 this.dy = dy;
54 }
55 } // class Ball
56
57 class Paddle {
58 // The "x_quotient" is the distance from the left corner to the current position,
59 // when the current position is projected on the base line.
60 // Measured in "baseline lengths". Not pixels. Not actual ground meters.
61 public float x_quotient;
62 public float width;
63 public float thickness;
64 public int color;
65
66 public Paddle(float x_quotient, float width, float thickness, int color) {
67 this.x_quotient = x_quotient;
68 this.width = width;
69 this.thickness = thickness;
70 this.color = color;
71 }
72 } // class Ball
73
74 private ArrayList<Brick> bricks = new ArrayList<Brick>();
75 private Ball ball;
76 private Paddle paddle;
77 private Timer ticker;
78 private Random random;
79 private LocationManager location_manager;
80 private LocationListener location_listener;
81 private boolean locations_enabled = true;
82 private RushActivity context;
83
84 private void init(final Context context) {
85 if (context instanceof RushActivity)
86 this.context = (RushActivity) context;
87 random = new Random();
88 location_manager = (LocationManager) context
89 .getSystemService(Context.LOCATION_SERVICE);
90
91 // Define a listener that responds to location updates
92 location_listener = new LocationListener() {
93
94 @Override
95 public void onLocationChanged(Location location) {
96 use_new_location(location);
97 }
98
99 @Override
100 public void onProviderDisabled(String provider) {
101 if (locations_enabled) {
102 locations_enabled = false;
103 if (context != null)
104 RushActivity.showWarning("GPS has been disabled.",
105 context);
106 }
107 }
108
109 @Override
110 public void onProviderEnabled(String provider) {
111 if (locations_enabled == false) {
112 locations_enabled = true;
113 if (context != null)
114 RushActivity.showWarning(
115 "GPS has been enabled again. Good!", context);
116 }
117 }
118
119 @Override
120 public void onStatusChanged(String provider, int status,
121 Bundle extras) {
122 if (status == LocationProvider.OUT_OF_SERVICE) {
123 if (locations_enabled) {
124 locations_enabled = false;
125 if (context != null)
126 RushActivity.showWarning("GPS is out of service.",
127 context);
128 }
129 } else if (status == LocationProvider.TEMPORARILY_UNAVAILABLE) {
130 if (locations_enabled) {
131 locations_enabled = false;
132 if (context != null)
133 RushActivity.showWarning(
134 "GPS is temporarily unavailable.", context);
135 }
136 } else if (status == LocationProvider.AVAILABLE) {
137 if (locations_enabled == false) {
138 locations_enabled = true;
139 if (context != null)
140 RushActivity.showWarning(
141 "GPS is available again. Good!", context);
142 }
143 }
144
145 }
146 };
147 // Log.d("SatelliteRush", "init done");
148 } // init
149
150 private Location latest_location;
151 private Time latest_location_time;
152 private Location left_corner;
153 private Location right_corner;
154 private Time left_corner_time;
155 private Time right_corner_time;
156 private Time game_start_time;
157 private Time game_win_time;
158 private float game_win_seconds;
159 private float base_line_length; // actual ground distance in meters
160 private float base_speed = -1; // screen speed in pixels per second, calculated from an actual ground speed
161
162 // This should be carefully adjusted
163 // A BASE_SPEED_FACTOR of 1 lets the ball move across a screen diagonal in the same time it took the player to walk the baseline
164 public static float BASE_SPEED_FACTOR = 2.0f;
165 // How much the ball speeds up after popping a brick
166 public static float BALL_SPEED_UP_FACTOR = 1.03f;
167
168 private void calculate_base_speed() {
169 if (base_speed == -1) {
170 // The base speed is only calculated once,
171 // and depends on the player's speed when walking the base line
172 // (actually: the time between the setting of the two corners)
173 float walk_seconds = Math.abs(left_corner_time.toMillis(true) - right_corner_time.toMillis(true)) / 1000f;
174 base_speed = (float)Math.sqrt(width_pixels * width_pixels + height_pixels * height_pixels) / walk_seconds * BASE_SPEED_FACTOR;
175 ball.dx = base_speed / 1.4142f;
176 ball.dy = - base_speed / 1.4142f;
177 }
178 }
179
180 private void use_new_location(Location location) {
181 latest_location = location;
182 Time now = new Time();
183 now.setToNow();
184 latest_location_time = now;
185
186 if (status == Status.WAITING_FOR_LEFT_CORNER) {
187 left_corner = location;
188 left_corner_time = now;
189 if (location.getAccuracy() > 10) {
190 corner_wait_status = CornerWaitStatus.NOT_ENOUGH_ACCURACY;
191 }
192 else if (right_corner == null) {
193 status = Status.NOT_READY;
194 }
195 else {
196 base_line_length = left_corner.distanceTo(right_corner);
197 if (base_line_length < 50) {
198 corner_wait_status = CornerWaitStatus.BASE_LINE_TOO_SHORT;
199 }
200 else {
201 status = Status.READY;
202 calculate_base_speed();
203 }
204 }
205 }
206
207 if (status == Status.WAITING_FOR_RIGHT_CORNER) {
208 right_corner = location;
209 right_corner_time = now;
210 if (location.getAccuracy() > 10) {
211 corner_wait_status = CornerWaitStatus.NOT_ENOUGH_ACCURACY;
212 }
213 else if (left_corner == null) {
214 status = Status.NOT_READY;
215 }
216 else {
217 base_line_length = right_corner.distanceTo(left_corner);
218 if (base_line_length < 50) {
219 corner_wait_status = CornerWaitStatus.BASE_LINE_TOO_SHORT;
220 }
221 else {
222 status = Status.READY;
223 calculate_base_speed();
224 }
225 }
226 }
227
228 if (left_corner != null && right_corner != null) {
229 float distance_from_left = left_corner.distanceTo(latest_location);
230 float distance_from_right = right_corner.distanceTo(latest_location);
231
232 // This assumes that we are on (or at least close to) the base line
233 // paddle.x = distance_from_left / (distance_from_left + distance_from_right) * width_pixels;
234
235 // This projects the player's position onto the base line
236 float baseline_meters_from_left =
237 ((distance_from_left * distance_from_left) - (distance_from_right * distance_from_right) + (base_line_length * base_line_length))
238 / (2 * base_line_length);
239 if (baseline_meters_from_left < 0)
240 baseline_meters_from_left = 0;
241 else if (baseline_meters_from_left > base_line_length)
242 baseline_meters_from_left = base_line_length;
243 paddle.x_quotient = baseline_meters_from_left / base_line_length;
244 }
245
246 /*
247 * updateText(); double longitude = location.getLongitude(); double
248 * latitude = location.getLatitude(); double accuracy =
249 * location.getAccuracy(); double altitude= location.getAltitude();
250 * print("Long " + longitude + ", lat " + latitude + ", accuracy " +
251 * accuracy + ", alt " + altitude);
252 */
253
254 // The things to show on the screen may have changed now
255 invalidate_view();
256 } // use_new_location
257
258 public RushView(Context context) {
259 super(context);
260 init(context);
261 }
262
263 public RushView(Context context, AttributeSet attrs) {
264 super(context, attrs);
265 init(context);
266 }
267
268 public RushView(Context context, AttributeSet attrs, int defStyle) {
269 super(context, attrs, defStyle);
270 init(context);
271 }
272
273 private int width_pixels = -1;
274 private int height_pixels = -1;
275 private float screen_density = -1;
276 private boolean bricks_initialized = false;
277
278 // This can be called not just from Android, but from the Graphical Layout
279 // in Eclipse
280 @Override
281 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
282 width_pixels = w;
283 height_pixels = h;
284 // screen_density = 1f;
285 Context context = getContext();
286 if (context != null && context instanceof Activity) {
287 Activity parent = (Activity) getContext();
288 // parent.getWindowManager().getDefaultDisplay().getMetrics(dm);
289 DisplayMetrics dm = new DisplayMetrics();
290 WindowManager window_manager = parent.getWindowManager();
291 if (dm != null && window_manager != null) {
292 // Crashed with NullPointerException when in the Graphical
293 // Layout in Eclipse
294 Display display = window_manager.getDefaultDisplay();
295 display.getMetrics(dm);
296 screen_density = dm.density;
297 }
298 }
299
300 if (!bricks_initialized) {
301 float d = screen_density;
302 if (d == -1)
303 d = 1f;
304 float standard_brick_size = Math.min(w, h) / 10f * d;
305 int nr_columns = (int) (w / standard_brick_size);
306 float column_width = w / nr_columns;
307 int nr_rows = (int) (h / standard_brick_size); // Rows of the entire
308 // screen!
309 float row_height = h / nr_rows;
310 float current_y_center = row_height / 2;
311 for (int y = 0; y < nr_rows / 2; ++y) { // Divide by 2: Only use the
312 // upper half of the screen
313 float current_x_center = column_width / 2;
314 for (int x = 0; x < nr_columns; ++x) {
315 Brick brick = new Brick(current_x_center, current_y_center,
316 standard_brick_size * 0.4f, 0xff00ff00);
317 bricks.add(brick);
318 current_x_center += column_width;
319 }
320 current_y_center += row_height;
321 }
322 // Log.d("SatelliteRush", "h = " + h);
323 ball = new Ball(w * 0.5f, h * 0.60f, 1, 1,
324 standard_brick_size * 0.3f, 0xff0000ff);
325 // paddle = new Paddle(0.5f, width_pixels / 1f, 10 * d, 0xff0000ff);
326 paddle = new Paddle(0.5f, width_pixels / 5f, 10 * d, 0xff0000ff);
327 bricks_initialized = true;
328 }
329 }
330
331 // This can be called not just from Android, but from the Graphical Layout
332 // in Eclipse
333 // We need to synchronize since we read the same data that update_simulation
334 // updates
335 @Override
336 synchronized protected void onDraw(Canvas canvas) {
337 super.onDraw(canvas);
338
339 int h = height_pixels;
340 if (h == -1)
341 h = canvas.getHeight();
342 int w = width_pixels;
343 if (w == -1)
344 w = canvas.getWidth();
345 float d = screen_density;
346 if (d == -1)
347 d = 1f;
348
349 Paint p = new Paint();
350
351 for (Brick b : bricks) {
352 if (b.active) {
353 p.setColor(b.color);
354 canvas.drawCircle(b.x, b.y, b.radius, p);
355 }
356 }
357
358 p.setStyle(Style.STROKE);
359 for (Brick b : bricks) {
360 if (b.active) {
361 p.setColor(Color.WHITE);
362 canvas.drawCircle(b.x, b.y, b.radius, p);
363 }
364 }
365 p.setStyle(Style.FILL);
366
367 if (status != Status.YOU_WIN) {
368 // Draw a line that shows the ball's direction
369 p.setColor(Color.RED);
370 // I don't remember how I found, or invented, this calculation, and I am rather suspicious of it. It seems to work, though.
371 float ball_speed = (float)Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
372 float screen_diagonal = (float)Math.sqrt(width_pixels * width_pixels + height_pixels * height_pixels);
373 float speed_scale = screen_diagonal / ball_speed;
374 canvas.drawLine(ball.x, ball.y, ball.x + ball.dx * speed_scale, ball.y + ball.dy * speed_scale, p);
375 }
376
377 p.setColor(ball.color);
378 canvas.drawCircle(ball.x, ball.y, ball.radius, p);
379 p.setColor(Color.WHITE);
380 p.setStyle(Style.STROKE);
381 canvas.drawCircle(ball.x, ball.y, ball.radius, p);
382 p.setStyle(Style.FILL);
383
384 float usable_base_pixels = width_pixels - paddle.width;
385 float paddle_left = paddle.x_quotient * usable_base_pixels;
386 float paddle_right = paddle.x_quotient * usable_base_pixels + paddle.width;
387
388 p.setColor(paddle.color);
389 canvas.drawRect(paddle_left, h - paddle.thickness, paddle_right, h, p);
390 p.setColor(Color.WHITE);
391 p.setStyle(Style.STROKE);
392 canvas.drawRect(paddle_left, h - paddle.thickness, paddle_right, h, p);
393 p.setStyle(Style.FILL);
394
395 /*
396 p.setColor(Color.WHITE);
397 canvas.drawText("Satellite Rush: status = " + status,
398 20f, h / 2 + 20f, p);
399 canvas.drawText("h = " + h + " (" + height_pixels + ")" +
400 ", w = " + w + " (" + width_pixels + ")" +
401 ", d = " + d + " (" + screen_density + ")",
402 20f, h / 2 + 40f, p);
403 canvas.drawText("Ball: x = " + String.format("%.2f", ball.x) +
404 ", y = " + String.format("%.2f", ball.y) +
405 ", dx = " + String.format("%.2f", ball.dx) +
406 ", dy = " + String.format("%.2f", ball.dy),
407 20f, h / 2 + 60f, p);
408 canvas.drawText("Speed = " + String.format("%.2f", Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy)) +
409 ", base_speed = " + String.format("%.2f", base_speed) +
410 " (" + String.format("%.2f", Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy) / base_speed) + ")",
411 20f, h / 2 + 80f, p);
412
413 final long now_nanos = java.lang.System.nanoTime();
414 if (previous_draw_nanos != -1 && now_nanos > previous_draw_nanos) {
415 long nanos = now_nanos - previous_draw_nanos;
416 float seconds = nanos / 1e9f;
417 float frame_rate = 1 / seconds;
418 if (accumulated_frame_rate == -1)
419 accumulated_frame_rate = frame_rate;
420 else
421 accumulated_frame_rate = (0.99f * accumulated_frame_rate + 0.01f * frame_rate);
422 canvas.drawText("frame rate = " + (int)(accumulated_frame_rate + 0.5),
423 20f, h / 2 + 100f, p);
424 }
425 previous_draw_nanos = now_nanos;
426
427 canvas.drawText("paddle.x_quotient = " + String.format("%.2f", paddle.x_quotient),
428 20f, h / 2 + 120f, p);
429 */
430
431 if (status == Status.YOU_WIN) {
432 p.setColor(Color.GREEN);
433 p.setTextSize(40f * d);
434 canvas.drawText("YOU WIN!", 0, height_pixels * 0.25f, p);
435 p.setTextSize(40f * d);
436 int hours = (int)(game_win_seconds / 3600);
437 int minutes = (int)(game_win_seconds % 3600 / 60);
438 float seconds = game_win_seconds % 60;
439 canvas.drawText("Time: " + hours + ":" + String.format("%02d", minutes) + ":" + String.format("%05.2f", seconds),
440 0, height_pixels * 0.50f, p);
441 }
442
443 if (this.locations_enabled == false) {
444 p.setColor(Color.RED);
445 p.setTextSize(20f * d);
446 canvas.drawText("You need to enable GPS.", 0, height_pixels * 0.75f, p);
447 }
448 else if (status == Status.WAITING_FOR_LEFT_CORNER) {
449 p.setColor(Color.RED);
450 p.setTextSize(20f * d);
451 canvas.drawText("Setting left corner. Wait...", 0, height_pixels * 0.75f, p);
452 if (corner_wait_status == CornerWaitStatus.NOT_ENOUGH_ACCURACY) {
453 canvas.drawText("(Waiting for better accuracy than " + String.format("%.2f", latest_location.getAccuracy()) + " m.)",
454 0, height_pixels * 0.75f + 25f * d, p);
455 }
456 else if (corner_wait_status == CornerWaitStatus.BASE_LINE_TOO_SHORT) {
457 canvas.drawText("Base line too short (" + (int)(base_line_length + 0.5) + " m).", 0, height_pixels * 0.75f + 25f * d, p);
458 canvas.drawText("Keep walking!", 0, height_pixels * 0.75f + 50f * d, p);
459 }
460 }
461 else if (status == Status.WAITING_FOR_RIGHT_CORNER) {
462 p.setColor(Color.RED);
463 p.setTextSize(20f * d);
464 canvas.drawText("Setting right corner. Wait...", 0, height_pixels * 0.75f, p);
465 if (corner_wait_status == CornerWaitStatus.NOT_ENOUGH_ACCURACY) {
466 canvas.drawText("(Waiting for better accuracy than " + String.format("%.2f", latest_location.getAccuracy()) + " m.)",
467 0, height_pixels * 0.75f + 25f * d, p);
468 }
469 else if (corner_wait_status == CornerWaitStatus.BASE_LINE_TOO_SHORT) {
470 canvas.drawText("Base line too short (" + (int)(base_line_length + 0.5) + " m).", 0, height_pixels * 0.75f + 25f * d, p);
471 canvas.drawText("Keep walking!", 0, height_pixels * 0.75f + 50f * d, p);
472 }
473 }
474 else if (left_corner == null && right_corner == null) {
475 p.setColor(Color.RED);
476 p.setTextSize(20f * d);
477 canvas.drawText("Welcome to Satellite Rush!", 0, height_pixels * 0.75f, p);
478 canvas.drawText("First you must set the corners.", 0, height_pixels * 0.75f + 25f * d, p);
479 canvas.drawText("Go there, and use the commands.", 0, height_pixels * 0.75f + 50f * d, p);
480 }
481 else if (left_corner == null) {
482 p.setColor(Color.RED);
483 p.setTextSize(20f * d);
484 canvas.drawText("Now set the left corner.", 0, height_pixels * 0.75f, p);
485 }
486 else if (right_corner == null) {
487 p.setColor(Color.RED);
488 p.setTextSize(20f * d);
489 canvas.drawText("Now set the right corner.", 0, height_pixels * 0.75f, p);
490 }
491 else if (status == status.READY) {
492 p.setColor(Color.GREEN);
493 p.setTextSize(20f * d);
494 canvas.drawText("Ready to start the game!", 0, height_pixels * 0.75f, p);
495 }
496 } // onDraw
497
498 private long previous_draw_nanos = -1;
499 private float accumulated_frame_rate = -1;
500
501 enum Status {
502 NOT_READY, // Not started yet
503 READY, // Will not continue until user commands it
504 PAUSED, // Will continue as soon as visible
505 RUNNING,
506 WAITING_FOR_LEFT_CORNER,
507 WAITING_FOR_RIGHT_CORNER,
508 YOU_WIN
509 }
510
511 private Status status = Status.NOT_READY;
512
513 enum CornerWaitStatus {
514 NO_LOCATION_YET,
515 NOT_ENOUGH_ACCURACY,
516 BASE_LINE_TOO_SHORT
517 }
518
519 private CornerWaitStatus corner_wait_status = CornerWaitStatus.NO_LOCATION_YET;
520
521 // Called when the player wants to begin (or continue) the game
522 public void start_moving() {
523 if (status == Status.READY || status == status.PAUSED) { // Should never be PAUSED
524 if (game_start_time == null) {
525 Time now = new Time();
526 now.setToNow();
527 game_start_time = now;
528 }
529 status = Status.RUNNING;
530 context.disable_corner_commands();
531 start_ticker();
532 }
533 else if (status == Status.YOU_WIN) {
534 RushActivity.showWarning("You already won.", context);
535 }
536 else {
537 RushActivity.showWarning("No no no! Set the corners first.", context);
538 }
539 invalidate_view();
540 }
541
542 // Called when the player wants to pause the game
543 public void stop_moving() {
544 if (status == Status.RUNNING) {
545 status = Status.READY;
546 stop_ticker();
547 }
548 invalidate_view();
549 }
550
551 // Called when the system will show the screen (possibly for the first time)
552 public void resume() {
553 try {
554 // Register the listener with the Location Manager to receive
555 // location updates
556 location_manager.requestLocationUpdates(
557 LocationManager.GPS_PROVIDER, 0, 0, location_listener);
558 }
559 catch (Exception e) {
560 RushActivity.showWarning("Couldn't use the GPS: " + e.getMessage(), context);
561 }
562
563 if (status == Status.PAUSED) {
564 status = Status.RUNNING;
565 start_ticker();
566 }
567
568 invalidate_view();
569 }
570
571 // Called when the system (not the player) will hide the screen or otherwise pause the game
572 public void pause() {
573 stop_ticker();
574 if (status == Status.RUNNING) {
575 status = Status.READY;
576 }
577 location_manager.removeUpdates(location_listener);
578 // invalidate_view(); -- No!
579 }
580
581 private void start_ticker() {
582 if (ticker != null)
583 return;
584 ticker = new Timer();
585 TimerTask task = new TimerTask() {
586 @Override
587 public void run() {
588 update_simulation();
589 invalidate_view();
590 }
591 };
592 ticker.schedule(task, 100, 50); // Needs to wait?
593 // ticker.scheduleAtFixedRate(task, 100, 100);
594 }
595
596 private void invalidate_view() {
597 Runnable r = new Runnable() {
598 @Override
599 public void run() {
600 invalidate();
601 }
602 };
603 if (getHandler() != null)
604 getHandler().post(r);
605 }
606
607 private void stop_ticker() {
608 if (ticker != null) {
609 ticker.cancel();
610 ticker = null;
611 nanos_when_paused = java.lang.System.nanoTime();
612 }
613 }
614
615 // -1 is not guaranteed to never happen, but we ignore that
616 private long previous_nanos = -1;
617 private long nanos_when_paused = -1;
618
619 // We need to synchronize since we update the same data that onDraw reads
620 synchronized private void update_simulation() {
621 long now_nanos = java.lang.System.nanoTime();
622 if (previous_nanos == -1 || now_nanos < previous_nanos) {
623 // First time, or overflow, so don't update the game
624 previous_nanos = now_nanos;
625 return;
626 }
627
628 long nanos = now_nanos - previous_nanos;
629 if (nanos_when_paused != -1) {
630 // We have been paused!
631 nanos = nanos_when_paused - previous_nanos;
632 nanos_when_paused = -1;
633 }
634
635 previous_nanos = now_nanos;
636 double seconds = nanos / 1e9;
637
638 ball.x += ball.dx * seconds;
639 ball.y += ball.dy * seconds;
640
641 // Check for collisions and handle them
642 Brick tragic_victim = null;
643 for (Brick brick : bricks) {
644 // To avoid some floating-point calculations, check a square first.
645 if (brick.active
646 && Math.abs(brick.x - ball.x) <= brick.radius + ball.radius + 1
647 && Math.abs(brick.y - ball.y) <= brick.radius + ball.radius
648 + 1
649 && Math.sqrt((brick.x - ball.x) * (brick.x - ball.x)
650 + (brick.y - ball.y) * (brick.y - ball.y)) < brick.radius
651 + ball.radius) {
652
653 // Log.d("Satellite Rush", "Popped a brick!");
654
655 double current_heading = Math.atan(ball.dy / ball.dx);
656 if (ball.dx < 0)
657 current_heading += Math.PI;
658
659 double normal = Math.atan((ball.y - brick.y) / (ball.x - brick.x));
660 if ((ball.x - brick.x) < 0)
661 normal += Math.PI;
662
663 float ball_speed = (float)Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
664 // Bounce back!
665 double new_heading = 2 * normal - current_heading - Math.PI;
666 ball.dx = ball_speed * (float)Math.cos(new_heading);
667 ball.dy = ball_speed * (float)Math.sin(new_heading);
668
669 // The ball speeds up slightly after each popped brick
670 ball.dx *= BALL_SPEED_UP_FACTOR;
671 ball.dy *= BALL_SPEED_UP_FACTOR;
672
673 // unbore(); -- Done at the end of this simulation step
674
675 /*
676 // If a speed component (x or y) is less than 1% of the screen
677 // size, increase it a bit
678 float dx_limit = width_pixels / 100f;
679 float dy_limit = height_pixels / 100f;
680 if (Math.abs(ball.dx) < dx_limit)
681 ball.dx = (dx_limit + random.nextFloat())
682 * Math.signum(ball.dx);
683 if (Math.abs(ball.dy) < dy_limit)
684 ball.dy = (dy_limit + random.nextFloat())
685 * Math.signum(ball.dy);
686 */
687
688 tragic_victim = brick;
689 break;
690 } // if the ball hit a brick
691 } // for each brick
692
693 if (tragic_victim != null) {
694 tragic_victim.active = false;
695 boolean any_bricks_left = false;
696 for (Brick brick : bricks) {
697 if (brick.active) {
698 any_bricks_left = true;
699 break;
700 }
701 }
702 if (any_bricks_left == false) {
703 Time now = new Time();
704 now.setToNow();
705 game_win_time = now;
706 game_win_seconds = (game_win_time.toMillis(true) - game_start_time.toMillis(true)) / 1000.0f;
707 status = Status.YOU_WIN;
708 stop_ticker();
709 }
710 }
711
712 // Bounce against the walls, with a small random adjustment to the speed
713 if (ball.x - ball.radius < 0) {
714 ball.x = ball.radius + (ball.radius - ball.x);
715 ball.dx = -ball.dx * (99 + 2 * random.nextFloat()) / 100f;
716 }
717 if (ball.x + ball.radius > width_pixels) {
718 // ball.x = width_pixels - (ball.radius + ball.x + ball.radius -
719 // width_pixels);
720 ball.x = 2 * width_pixels - 2 * ball.radius - ball.x;
721 ball.dx = -ball.dx * (99 + 2 * random.nextFloat()) / 100f;
722 }
723 if (ball.y - ball.radius < 0) {
724 ball.y = ball.radius + (ball.radius - ball.y);
725 ball.dy = -ball.dy * (99 + 2 * random.nextFloat()) / 100f;
726 }
727
728 if (ball.dy > 0 && ball.y + ball.radius > height_pixels - paddle.thickness) {
729 float usable_base_pixels = width_pixels - paddle.width;
730 float paddle_left = paddle.x_quotient * usable_base_pixels;
731 float paddle_right = paddle.x_quotient * usable_base_pixels + paddle.width;
732 if (ball.x > paddle_left && ball.x < paddle_right) {
733 ball.y = 2 * height_pixels - 2 * paddle.thickness - 2 * ball.radius - ball.y;
734 ball.dy = -ball.dy * (99 + 2 * random.nextFloat()) / 100f;
735 }
736 }
737 if (ball.y + ball.radius > height_pixels) {
738 ball.y = 2 * height_pixels - 2 * ball.radius - ball.y;
739 ball.dy = -ball.dy * (99 + 2 * random.nextFloat()) / 100f;
740 add_a_ball();
741 ball.dx /= (BALL_SPEED_UP_FACTOR * BALL_SPEED_UP_FACTOR);
742 ball.dy /= (BALL_SPEED_UP_FACTOR * BALL_SPEED_UP_FACTOR);
743 }
744
745 unbore();
746 } // update_simulation
747
748 public float MIN_HORIZONTAL_FRACTION = 0.10f;
749 public float MIN_VERTICAL_FRACTION = 0.20f;
750
751 // If the ball movement is boring (almost entirely vertical or horizontal), then make it less boring
752 void unbore() {
753 if (ball.dx != 0 && Math.abs(ball.dy / ball.dx) < MIN_VERTICAL_FRACTION) {
754 float ball_speed = (float)Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
755 ball.dx = ball_speed * MIN_VERTICAL_FRACTION;
756 ball.dy = (float)Math.sqrt(ball_speed * ball_speed - ball.dx * ball.dx) * Math.signum(ball.dy);
757 }
758 else if (ball.dy != 0 && Math.abs(ball.dx / ball.dy) < MIN_HORIZONTAL_FRACTION) {
759 float ball_speed = (float)Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
760 ball.dy = ball_speed * MIN_HORIZONTAL_FRACTION;
761 ball.dx = (float)Math.sqrt(ball_speed * ball_speed - ball.dy * ball.dy) * Math.signum(ball.dx);
762 }
763 } // unbore
764
765 void add_a_ball() {
766 for (Brick brick : bricks) {
767 if (!brick.active) {
768 brick.color = Color.RED;
769 brick.active = true;
770 break;
771 }
772 }
773 }
774
775 public void mark_left_corner() {
776 if (left_corner != null)
777 RushActivity.showWarning("Setting new left corner.", context);
778 status = Status.WAITING_FOR_LEFT_CORNER;
779 corner_wait_status = CornerWaitStatus.NO_LOCATION_YET;
780 invalidate_view();
781 }
782
783 public void mark_right_corner() {
784 if (right_corner != null)
785 RushActivity.showWarning("Setting new right corner.", context);
786 status = Status.WAITING_FOR_RIGHT_CORNER;
787 corner_wait_status = CornerWaitStatus.NO_LOCATION_YET;
788 invalidate_view();
789 }
790
791 } // class RushView
|