blob: cdfe6e578a9f053d6bc858152690ba1f6d589e0a [file] [log] [blame]
Dan Sandler4455da92014-09-14 16:28:58 -04001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.egg;
18
19import android.animation.TimeAnimator;
20import android.content.Context;
21import android.content.res.Resources;
Dan Sandler2200f862014-10-06 01:04:47 -040022import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.Matrix;
25import android.graphics.Outline;
26import android.graphics.Paint;
27import android.graphics.Path;
28import android.graphics.PorterDuff;
29import android.graphics.Rect;
Dan Sandler4455da92014-09-14 16:28:58 -040030import android.graphics.drawable.Drawable;
31import android.graphics.drawable.GradientDrawable;
32import android.util.AttributeSet;
33import android.util.Log;
Dan Sandler2200f862014-10-06 01:04:47 -040034import android.view.Gravity;
35import android.view.KeyEvent;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.ViewOutlineProvider;
Dan Sandler4455da92014-09-14 16:28:58 -040039import android.view.animation.DecelerateInterpolator;
40import android.widget.FrameLayout;
41import android.widget.ImageView;
42import android.widget.TextView;
43
Dan Sandler4455da92014-09-14 16:28:58 -040044import com.android.systemui.R;
45
Dan Sandler2200f862014-10-06 01:04:47 -040046import java.util.ArrayList;
47
Dan Sandler4455da92014-09-14 16:28:58 -040048public class LLand extends FrameLayout {
49 public static final String TAG = "LLand";
50
51 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Dan Sandler2200f862014-10-06 01:04:47 -040052 public static final boolean DEBUG_DRAW = false; // DEBUG
Dan Sandler4455da92014-09-14 16:28:58 -040053
54 public static final void L(String s, Object ... objects) {
55 if (DEBUG) {
56 Log.d(TAG, String.format(s, objects));
57 }
58 }
59
60 public static final boolean AUTOSTART = true;
61 public static final boolean HAVE_STARS = true;
62
63 public static final float DEBUG_SPEED_MULTIPLIER = 1f; // 0.1f;
64 public static final boolean DEBUG_IDDQD = false;
65
Dan Sandler2200f862014-10-06 01:04:47 -040066 final static int[] POPS = {
67 // resid // spinny!
68 R.drawable.pop_belt, 0,
69 R.drawable.pop_droid, 0,
70 R.drawable.pop_pizza, 1,
71 R.drawable.pop_stripes, 0,
72 R.drawable.pop_swirl, 1,
73 R.drawable.pop_vortex, 1,
74 R.drawable.pop_vortex2, 1,
75 };
76
Dan Sandler4455da92014-09-14 16:28:58 -040077 private static class Params {
78 public float TRANSLATION_PER_SEC;
79 public int OBSTACLE_SPACING, OBSTACLE_PERIOD;
80 public int BOOST_DV;
81 public int PLAYER_HIT_SIZE;
82 public int PLAYER_SIZE;
Dan Sandler2200f862014-10-06 01:04:47 -040083 public int OBSTACLE_WIDTH, OBSTACLE_STEM_WIDTH;
Dan Sandler4455da92014-09-14 16:28:58 -040084 public int OBSTACLE_GAP;
85 public int OBSTACLE_MIN;
86 public int BUILDING_WIDTH_MIN, BUILDING_WIDTH_MAX;
87 public int BUILDING_HEIGHT_MIN;
88 public int CLOUD_SIZE_MIN, CLOUD_SIZE_MAX;
89 public int STAR_SIZE_MIN, STAR_SIZE_MAX;
90 public int G;
91 public int MAX_V;
92 public float SCENERY_Z, OBSTACLE_Z, PLAYER_Z, PLAYER_Z_BOOST, HUD_Z;
93 public Params(Resources res) {
94 TRANSLATION_PER_SEC = res.getDimension(R.dimen.translation_per_sec);
95 OBSTACLE_SPACING = res.getDimensionPixelSize(R.dimen.obstacle_spacing);
96 OBSTACLE_PERIOD = (int) (OBSTACLE_SPACING / TRANSLATION_PER_SEC);
97 BOOST_DV = res.getDimensionPixelSize(R.dimen.boost_dv);
98 PLAYER_HIT_SIZE = res.getDimensionPixelSize(R.dimen.player_hit_size);
99 PLAYER_SIZE = res.getDimensionPixelSize(R.dimen.player_size);
100 OBSTACLE_WIDTH = res.getDimensionPixelSize(R.dimen.obstacle_width);
Dan Sandler2200f862014-10-06 01:04:47 -0400101 OBSTACLE_STEM_WIDTH = res.getDimensionPixelSize(R.dimen.obstacle_stem_width);
Dan Sandler4455da92014-09-14 16:28:58 -0400102 OBSTACLE_GAP = res.getDimensionPixelSize(R.dimen.obstacle_gap);
103 OBSTACLE_MIN = res.getDimensionPixelSize(R.dimen.obstacle_height_min);
104 BUILDING_HEIGHT_MIN = res.getDimensionPixelSize(R.dimen.building_height_min);
105 BUILDING_WIDTH_MIN = res.getDimensionPixelSize(R.dimen.building_width_min);
106 BUILDING_WIDTH_MAX = res.getDimensionPixelSize(R.dimen.building_width_max);
107 CLOUD_SIZE_MIN = res.getDimensionPixelSize(R.dimen.cloud_size_min);
108 CLOUD_SIZE_MAX = res.getDimensionPixelSize(R.dimen.cloud_size_max);
109 STAR_SIZE_MIN = res.getDimensionPixelSize(R.dimen.star_size_min);
110 STAR_SIZE_MAX = res.getDimensionPixelSize(R.dimen.star_size_max);
111
112 G = res.getDimensionPixelSize(R.dimen.G);
113 MAX_V = res.getDimensionPixelSize(R.dimen.max_v);
114
115 SCENERY_Z = res.getDimensionPixelSize(R.dimen.scenery_z);
116 OBSTACLE_Z = res.getDimensionPixelSize(R.dimen.obstacle_z);
117 PLAYER_Z = res.getDimensionPixelSize(R.dimen.player_z);
118 PLAYER_Z_BOOST = res.getDimensionPixelSize(R.dimen.player_z_boost);
119 HUD_Z = res.getDimensionPixelSize(R.dimen.hud_z);
120 }
121 }
122
123 private TimeAnimator mAnim;
124
125 private TextView mScoreField;
126 private View mSplash;
127
128 private Player mDroid;
129 private ArrayList<Obstacle> mObstaclesInPlay = new ArrayList<Obstacle>();
130
131 private float t, dt;
132
133 private int mScore;
134 private float mLastPipeTime; // in sec
135 private int mWidth, mHeight;
136 private boolean mAnimating, mPlaying;
137 private boolean mFrozen; // after death, a short backoff
Dan Sandler2200f862014-10-06 01:04:47 -0400138 private boolean mFlipped;
Dan Sandler4455da92014-09-14 16:28:58 -0400139
140 private int mTimeOfDay;
141 private static final int DAY = 0, NIGHT = 1, TWILIGHT = 2, SUNSET = 3;
142 private static final int[][] SKIES = {
143 { 0xFFc0c0FF, 0xFFa0a0FF }, // DAY
144 { 0xFF000010, 0xFF000000 }, // NIGHT
145 { 0xFF000040, 0xFF000010 }, // TWILIGHT
Dan Sandler2200f862014-10-06 01:04:47 -0400146 { 0xFFa08020, 0xFF204080 }, // SUNSET
Dan Sandler4455da92014-09-14 16:28:58 -0400147 };
148
149 private static Params PARAMS;
150
151 public LLand(Context context) {
152 this(context, null);
153 }
154
155 public LLand(Context context, AttributeSet attrs) {
156 this(context, attrs, 0);
157 }
158
159 public LLand(Context context, AttributeSet attrs, int defStyle) {
160 super(context, attrs, defStyle);
161 setFocusable(true);
162 PARAMS = new Params(getResources());
163 mTimeOfDay = irand(0, SKIES.length);
164 }
165
166 @Override
167 public boolean willNotDraw() {
168 return !DEBUG;
169 }
170
171 public int getGameWidth() { return mWidth; }
172 public int getGameHeight() { return mHeight; }
173 public float getGameTime() { return t; }
174 public float getLastTimeStep() { return dt; }
175
176 public void setScoreField(TextView tv) {
177 mScoreField = tv;
178 if (tv != null) {
179 tv.setTranslationZ(PARAMS.HUD_Z);
180 if (!(mAnimating && mPlaying)) {
181 tv.setTranslationY(-500);
182 }
183 }
184 }
185
186 public void setSplash(View v) {
187 mSplash = v;
188 }
189
190 @Override
191 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
192 stop();
193 reset();
194 if (AUTOSTART) {
195 start(false);
196 }
197 }
198
199 final float hsv[] = {0, 0, 0};
200
201 private void reset() {
202 L("reset");
203 final Drawable sky = new GradientDrawable(
204 GradientDrawable.Orientation.BOTTOM_TOP,
205 SKIES[mTimeOfDay]
206 );
207 sky.setDither(true);
208 setBackground(sky);
209
Dan Sandler2200f862014-10-06 01:04:47 -0400210 mFlipped = frand() > 0.5f;
211 setScaleX(mFlipped ? -1 : 1);
Dan Sandler4455da92014-09-14 16:28:58 -0400212
213 setScore(0);
214
215 int i = getChildCount();
216 while (i-->0) {
217 final View v = getChildAt(i);
218 if (v instanceof GameView) {
219 removeViewAt(i);
220 }
221 }
222
223 mObstaclesInPlay.clear();
224
225 mWidth = getWidth();
226 mHeight = getHeight();
227
228 boolean showingSun = (mTimeOfDay == DAY || mTimeOfDay == SUNSET) && frand() > 0.25;
229 if (showingSun) {
230 final Star sun = new Star(getContext());
231 sun.setBackgroundResource(R.drawable.sun);
232 final int w = getResources().getDimensionPixelSize(R.dimen.sun_size);
233 sun.setTranslationX(frand(w, mWidth-w));
234 if (mTimeOfDay == DAY) {
235 sun.setTranslationY(frand(w, (mHeight * 0.66f)));
236 sun.getBackground().setTint(0);
237 } else {
238 sun.setTranslationY(frand(mHeight * 0.66f, mHeight - w));
239 sun.getBackground().setTintMode(PorterDuff.Mode.SRC_ATOP);
240 sun.getBackground().setTint(0xC0FF8000);
241
242 }
243 addView(sun, new LayoutParams(w, w));
244 }
245 if (!showingSun) {
246 final boolean dark = mTimeOfDay == NIGHT || mTimeOfDay == TWILIGHT;
247 final float ff = frand();
248 if ((dark && ff < 0.75f) || ff < 0.5f) {
249 final Star moon = new Star(getContext());
250 moon.setBackgroundResource(R.drawable.moon);
251 moon.getBackground().setAlpha(dark ? 255 : 128);
252 moon.setScaleX(frand() > 0.5 ? -1 : 1);
253 moon.setRotation(moon.getScaleX() * frand(5, 30));
254 final int w = getResources().getDimensionPixelSize(R.dimen.sun_size);
255 moon.setTranslationX(frand(w, mWidth - w));
256 moon.setTranslationY(frand(w, mHeight - w));
257 addView(moon, new LayoutParams(w, w));
258 }
259 }
260
261 final int mh = mHeight / 6;
262 final boolean cloudless = frand() < 0.25;
263 final int N = 20;
264 for (i=0; i<N; i++) {
265 final float r1 = frand();
266 final Scenery s;
267 if (HAVE_STARS && r1 < 0.3 && mTimeOfDay != DAY) {
268 s = new Star(getContext());
269 } else if (r1 < 0.6 && !cloudless) {
270 s = new Cloud(getContext());
271 } else {
272 s = new Building(getContext());
273
274 s.z = (float)i/N;
275 s.setTranslationZ(PARAMS.SCENERY_Z * (1+s.z));
276 s.v = 0.85f * s.z; // buildings move proportional to their distance
277 hsv[0] = 175;
278 hsv[1] = 0.25f;
279 hsv[2] = 1 * s.z;
280 s.setBackgroundColor(Color.HSVToColor(hsv));
281 s.h = irand(PARAMS.BUILDING_HEIGHT_MIN, mh);
282 }
283 final LayoutParams lp = new LayoutParams(s.w, s.h);
284 if (s instanceof Building) {
285 lp.gravity = Gravity.BOTTOM;
286 } else {
287 lp.gravity = Gravity.TOP;
288 final float r = frand();
289 if (s instanceof Star) {
290 lp.topMargin = (int) (r * r * mHeight);
291 } else {
292 lp.topMargin = (int) (1 - r*r * mHeight/2) + mHeight/2;
293 }
294 }
295
296 addView(s, lp);
297 s.setTranslationX(frand(-lp.width, mWidth + lp.width));
298 }
299
300 mDroid = new Player(getContext());
301 mDroid.setX(mWidth / 2);
302 mDroid.setY(mHeight / 2);
303 addView(mDroid, new LayoutParams(PARAMS.PLAYER_SIZE, PARAMS.PLAYER_SIZE));
Dan Sandler2200f862014-10-06 01:04:47 -0400304
Dan Sandler4455da92014-09-14 16:28:58 -0400305 mAnim = new TimeAnimator();
306 mAnim.setTimeListener(new TimeAnimator.TimeListener() {
307 @Override
308 public void onTimeUpdate(TimeAnimator timeAnimator, long t, long dt) {
309 step(t, dt);
310 }
311 });
312 }
313
314 private void setScore(int score) {
315 mScore = score;
316 if (mScoreField != null) mScoreField.setText(String.valueOf(score));
317 }
318
319 private void addScore(int incr) {
320 setScore(mScore + incr);
321 }
322
323 private void start(boolean startPlaying) {
324 L("start(startPlaying=%s)", startPlaying?"true":"false");
325 if (startPlaying) {
326 mPlaying = true;
327
328 t = 0;
Dan Sandler2200f862014-10-06 01:04:47 -0400329 // there's a sucker born every OBSTACLE_PERIOD
330 mLastPipeTime = getGameTime() - PARAMS.OBSTACLE_PERIOD;
Dan Sandler4455da92014-09-14 16:28:58 -0400331
332 if (mSplash != null && mSplash.getAlpha() > 0f) {
333 mSplash.setTranslationZ(PARAMS.HUD_Z);
334 mSplash.animate().alpha(0).translationZ(0).setDuration(400);
335
336 mScoreField.animate().translationY(0)
337 .setInterpolator(new DecelerateInterpolator())
338 .setDuration(1500);
339 }
340
341 mScoreField.setTextColor(0xFFAAAAAA);
342 mScoreField.setBackgroundResource(R.drawable.scorecard);
343 mDroid.setVisibility(View.VISIBLE);
344 mDroid.setX(mWidth / 2);
345 mDroid.setY(mHeight / 2);
346 } else {
347 mDroid.setVisibility(View.GONE);
348 }
349 if (!mAnimating) {
350 mAnim.start();
351 mAnimating = true;
352 }
353 }
354
355 private void stop() {
356 if (mAnimating) {
357 mAnim.cancel();
358 mAnim = null;
359 mAnimating = false;
360 mScoreField.setTextColor(0xFFFFFFFF);
361 mScoreField.setBackgroundResource(R.drawable.scorecard_gameover);
362 mTimeOfDay = irand(0, SKIES.length); // for next reset
363 mFrozen = true;
364 postDelayed(new Runnable() {
365 @Override
366 public void run() {
367 mFrozen = false;
368 }
369 }, 250);
370 }
371 }
372
373 public static final float lerp(float x, float a, float b) {
374 return (b - a) * x + a;
375 }
376
377 public static final float rlerp(float v, float a, float b) {
378 return (v - a) / (b - a);
379 }
380
381 public static final float clamp(float f) {
382 return f < 0f ? 0f : f > 1f ? 1f : f;
383 }
384
385 public static final float frand() {
386 return (float) Math.random();
387 }
388
389 public static final float frand(float a, float b) {
390 return lerp(frand(), a, b);
391 }
392
393 public static final int irand(int a, int b) {
394 return (int) lerp(frand(), (float) a, (float) b);
395 }
396
397 private void step(long t_ms, long dt_ms) {
398 t = t_ms / 1000f; // seconds
399 dt = dt_ms / 1000f;
400
401 if (DEBUG) {
402 t *= DEBUG_SPEED_MULTIPLIER;
403 dt *= DEBUG_SPEED_MULTIPLIER;
404 }
405
406 // 1. Move all objects and update bounds
407 final int N = getChildCount();
408 int i = 0;
409 for (; i<N; i++) {
410 final View v = getChildAt(i);
411 if (v instanceof GameView) {
412 ((GameView) v).step(t_ms, dt_ms, t, dt);
413 }
414 }
415
416 // 2. Check for altitude
417 if (mPlaying && mDroid.below(mHeight)) {
418 if (DEBUG_IDDQD) {
419 poke();
420 } else {
421 L("player hit the floor");
422 stop();
423 }
424 }
425
426 // 3. Check for obstacles
427 boolean passedBarrier = false;
428 for (int j = mObstaclesInPlay.size(); j-->0;) {
429 final Obstacle ob = mObstaclesInPlay.get(j);
430 if (mPlaying && ob.intersects(mDroid) && !DEBUG_IDDQD) {
431 L("player hit an obstacle");
432 stop();
433 } else if (ob.cleared(mDroid)) {
Dan Sandler2200f862014-10-06 01:04:47 -0400434 if (ob instanceof Stem) passedBarrier = true;
Dan Sandler4455da92014-09-14 16:28:58 -0400435 mObstaclesInPlay.remove(j);
436 }
437 }
438
439 if (mPlaying && passedBarrier) {
440 addScore(1);
441 }
442
443 // 4. Handle edge of screen
444 // Walk backwards to make sure removal is safe
445 while (i-->0) {
446 final View v = getChildAt(i);
447 if (v instanceof Obstacle) {
448 if (v.getTranslationX() + v.getWidth() < 0) {
449 removeViewAt(i);
450 }
451 } else if (v instanceof Scenery) {
452 final Scenery s = (Scenery) v;
453 if (v.getTranslationX() + s.w < 0) {
454 v.setTranslationX(getWidth());
455 }
456 }
457 }
458
459 // 3. Time for more obstacles!
460 if (mPlaying && (t - mLastPipeTime) > PARAMS.OBSTACLE_PERIOD) {
461 mLastPipeTime = t;
462 final int obstacley = (int) (Math.random()
463 * (mHeight - 2*PARAMS.OBSTACLE_MIN - PARAMS.OBSTACLE_GAP)) + PARAMS.OBSTACLE_MIN;
464
Dan Sandler2200f862014-10-06 01:04:47 -0400465 final int inset = (PARAMS.OBSTACLE_WIDTH - PARAMS.OBSTACLE_STEM_WIDTH) / 2;
466 final int yinset = PARAMS.OBSTACLE_WIDTH/2;
467
468 final int d1 = irand(0,250);
469 final Obstacle s1 = new Stem(getContext(), obstacley - yinset, false);
470 addView(s1, new LayoutParams(
471 PARAMS.OBSTACLE_STEM_WIDTH,
472 (int) s1.h,
473 Gravity.TOP|Gravity.LEFT));
474 s1.setTranslationX(mWidth+inset);
475 s1.setTranslationY(-s1.h-yinset);
476 s1.setTranslationZ(PARAMS.OBSTACLE_Z*0.75f);
477 s1.animate()
478 .translationY(0)
479 .setStartDelay(d1)
480 .setDuration(250);
481 mObstaclesInPlay.add(s1);
482
483 final Obstacle p1 = new Pop(getContext(), PARAMS.OBSTACLE_WIDTH);
Dan Sandler4455da92014-09-14 16:28:58 -0400484 addView(p1, new LayoutParams(
485 PARAMS.OBSTACLE_WIDTH,
Dan Sandler2200f862014-10-06 01:04:47 -0400486 PARAMS.OBSTACLE_WIDTH,
Dan Sandler4455da92014-09-14 16:28:58 -0400487 Gravity.TOP|Gravity.LEFT));
488 p1.setTranslationX(mWidth);
Dan Sandler2200f862014-10-06 01:04:47 -0400489 p1.setTranslationY(-PARAMS.OBSTACLE_WIDTH);
490 p1.setTranslationZ(PARAMS.OBSTACLE_Z);
491 p1.setScaleX(0.25f);
492 p1.setScaleY(0.25f);
Dan Sandler4455da92014-09-14 16:28:58 -0400493 p1.animate()
Dan Sandler2200f862014-10-06 01:04:47 -0400494 .translationY(s1.h-inset)
495 .scaleX(1f)
496 .scaleY(1f)
497 .setStartDelay(d1)
Dan Sandler4455da92014-09-14 16:28:58 -0400498 .setDuration(250);
499 mObstaclesInPlay.add(p1);
500
Dan Sandler2200f862014-10-06 01:04:47 -0400501 final int d2 = irand(0,250);
502 final Obstacle s2 = new Stem(getContext(),
503 mHeight - obstacley - PARAMS.OBSTACLE_GAP - yinset,
504 true);
505 addView(s2, new LayoutParams(
506 PARAMS.OBSTACLE_STEM_WIDTH,
507 (int) s2.h,
508 Gravity.TOP|Gravity.LEFT));
509 s2.setTranslationX(mWidth+inset);
510 s2.setTranslationY(mHeight+yinset);
511 s2.setTranslationZ(PARAMS.OBSTACLE_Z*0.75f);
512 s2.animate()
513 .translationY(mHeight-s2.h)
514 .setStartDelay(d2)
515 .setDuration(400);
516 mObstaclesInPlay.add(s2);
517
518 final Obstacle p2 = new Pop(getContext(), PARAMS.OBSTACLE_WIDTH);
Dan Sandler4455da92014-09-14 16:28:58 -0400519 addView(p2, new LayoutParams(
520 PARAMS.OBSTACLE_WIDTH,
Dan Sandler2200f862014-10-06 01:04:47 -0400521 PARAMS.OBSTACLE_WIDTH,
Dan Sandler4455da92014-09-14 16:28:58 -0400522 Gravity.TOP|Gravity.LEFT));
523 p2.setTranslationX(mWidth);
524 p2.setTranslationY(mHeight);
Dan Sandler2200f862014-10-06 01:04:47 -0400525 p2.setTranslationZ(PARAMS.OBSTACLE_Z);
526 p2.setScaleX(0.25f);
527 p2.setScaleY(0.25f);
Dan Sandler4455da92014-09-14 16:28:58 -0400528 p2.animate()
Dan Sandler2200f862014-10-06 01:04:47 -0400529 .translationY(mHeight-s2.h-yinset)
530 .scaleX(1f)
531 .scaleY(1f)
532 .setStartDelay(d2)
Dan Sandler4455da92014-09-14 16:28:58 -0400533 .setDuration(400);
534 mObstaclesInPlay.add(p2);
535 }
536
Dan Sandler2200f862014-10-06 01:04:47 -0400537 if (DEBUG_DRAW) invalidate();
Dan Sandler4455da92014-09-14 16:28:58 -0400538 }
539
540 @Override
541 public boolean onTouchEvent(MotionEvent ev) {
542 if (DEBUG) L("touch: %s", ev);
Dan Sandler2200f862014-10-06 01:04:47 -0400543 switch (ev.getAction()) {
544 case MotionEvent.ACTION_DOWN:
545 poke();
546 return true;
547 case MotionEvent.ACTION_UP:
548 unpoke();
549 return true;
Dan Sandler4455da92014-09-14 16:28:58 -0400550 }
551 return false;
552 }
553
554 @Override
555 public boolean onTrackballEvent(MotionEvent ev) {
556 if (DEBUG) L("trackball: %s", ev);
Dan Sandler2200f862014-10-06 01:04:47 -0400557 switch (ev.getAction()) {
558 case MotionEvent.ACTION_DOWN:
559 poke();
560 return true;
561 case MotionEvent.ACTION_UP:
562 unpoke();
563 return true;
Dan Sandler4455da92014-09-14 16:28:58 -0400564 }
565 return false;
566 }
567
568 @Override
569 public boolean onKeyDown(int keyCode, KeyEvent ev) {
570 if (DEBUG) L("keyDown: %d", keyCode);
571 switch (keyCode) {
572 case KeyEvent.KEYCODE_DPAD_CENTER:
573 case KeyEvent.KEYCODE_DPAD_UP:
574 case KeyEvent.KEYCODE_SPACE:
575 case KeyEvent.KEYCODE_ENTER:
576 case KeyEvent.KEYCODE_BUTTON_A:
577 poke();
578 return true;
579 }
580 return false;
581 }
582
583 @Override
Dan Sandler2200f862014-10-06 01:04:47 -0400584 public boolean onKeyUp(int keyCode, KeyEvent ev) {
585 if (DEBUG) L("keyDown: %d", keyCode);
586 switch (keyCode) {
587 case KeyEvent.KEYCODE_DPAD_CENTER:
588 case KeyEvent.KEYCODE_DPAD_UP:
589 case KeyEvent.KEYCODE_SPACE:
590 case KeyEvent.KEYCODE_ENTER:
591 case KeyEvent.KEYCODE_BUTTON_A:
592 unpoke();
593 return true;
594 }
595 return false;
596 }
597
598 @Override
Dan Sandler4455da92014-09-14 16:28:58 -0400599 public boolean onGenericMotionEvent (MotionEvent ev) {
600 if (DEBUG) L("generic: %s", ev);
601 return false;
602 }
603
604 private void poke() {
605 L("poke");
606 if (mFrozen) return;
607 if (!mAnimating) {
608 reset();
609 start(true);
610 } else if (!mPlaying) {
611 start(true);
612 }
613 mDroid.boost();
614 if (DEBUG) {
615 mDroid.dv *= DEBUG_SPEED_MULTIPLIER;
616 mDroid.animate().setDuration((long) (200/DEBUG_SPEED_MULTIPLIER));
617 }
618 }
619
Dan Sandler2200f862014-10-06 01:04:47 -0400620 private void unpoke() {
621 L("unboost");
622 if (mFrozen) return;
623 if (!mAnimating) return;
624 mDroid.unboost();
625 }
626
Dan Sandler4455da92014-09-14 16:28:58 -0400627 @Override
628 public void onDraw(Canvas c) {
629 super.onDraw(c);
630
631 if (!DEBUG_DRAW) return;
632
633 final Paint pt = new Paint();
634 pt.setColor(0xFFFFFFFF);
635 final int L = mDroid.corners.length;
636 final int N = L/2;
637 for (int i=0; i<N; i++) {
638 final int x = (int) mDroid.corners[i*2];
639 final int y = (int) mDroid.corners[i*2+1];
640 c.drawCircle(x, y, 4, pt);
641 c.drawLine(x, y,
642 mDroid.corners[(i*2+2)%L],
643 mDroid.corners[(i*2+3)%L],
644 pt);
645 }
646
Dan Sandler2200f862014-10-06 01:04:47 -0400647 pt.setStyle(Paint.Style.STROKE);
648 pt.setStrokeWidth(getResources().getDisplayMetrics().density);
649
Dan Sandler4455da92014-09-14 16:28:58 -0400650 final int M = getChildCount();
Dan Sandler2200f862014-10-06 01:04:47 -0400651 pt.setColor(0x8000FF00);
Dan Sandler4455da92014-09-14 16:28:58 -0400652 for (int i=0; i<M; i++) {
653 final View v = getChildAt(i);
654 if (v == mDroid) continue;
655 if (!(v instanceof GameView)) continue;
Dan Sandler2200f862014-10-06 01:04:47 -0400656 if (v instanceof Pop) {
657 final Pop p = (Pop) v;
658 c.drawCircle(p.cx, p.cy, p.r, pt);
659 } else {
660 final Rect r = new Rect();
661 v.getHitRect(r);
662 c.drawRect(r, pt);
663 }
Dan Sandler4455da92014-09-14 16:28:58 -0400664 }
665
666 pt.setColor(Color.BLACK);
667 final StringBuilder sb = new StringBuilder("obstacles: ");
668 for (Obstacle ob : mObstaclesInPlay) {
669 sb.append(ob.hitRect.toShortString());
670 sb.append(" ");
671 }
672 pt.setTextSize(20f);
673 c.drawText(sb.toString(), 20, 100, pt);
674 }
675
676 static final Rect sTmpRect = new Rect();
677
678 private interface GameView {
679 public void step(long t_ms, long dt_ms, float t, float dt);
680 }
681
682 private class Player extends ImageView implements GameView {
683 public float dv;
684
Dan Sandler2200f862014-10-06 01:04:47 -0400685 private boolean mBoosting;
686
Dan Sandler4455da92014-09-14 16:28:58 -0400687 private final float[] sHull = new float[] {
688 0.3f, 0f, // left antenna
689 0.7f, 0f, // right antenna
690 0.92f, 0.33f, // off the right shoulder of Orion
691 0.92f, 0.75f, // right hand (our right, not his right)
692 0.6f, 1f, // right foot
693 0.4f, 1f, // left foot BLUE!
694 0.08f, 0.75f, // sinistram
695 0.08f, 0.33f, // cold shoulder
696 };
697 public final float[] corners = new float[sHull.length];
698
699 public Player(Context context) {
700 super(context);
701
702 setBackgroundResource(R.drawable.android);
703 getBackground().setTintMode(PorterDuff.Mode.SRC_ATOP);
704 getBackground().setTint(0xFF00FF00);
705 setOutlineProvider(new ViewOutlineProvider() {
706 @Override
707 public void getOutline(View view, Outline outline) {
708 final int w = view.getWidth();
709 final int h = view.getHeight();
710 final int ix = (int) (w * 0.3f);
711 final int iy = (int) (h * 0.2f);
712 outline.setRect(ix, iy, w - ix, h - iy);
713 }
714 });
715 }
716
717 public void prepareCheckIntersections() {
718 final int inset = (PARAMS.PLAYER_SIZE - PARAMS.PLAYER_HIT_SIZE)/2;
719 final int scale = PARAMS.PLAYER_HIT_SIZE;
720 final int N = sHull.length/2;
721 for (int i=0; i<N; i++) {
722 corners[i*2] = scale * sHull[i*2] + inset;
723 corners[i*2+1] = scale * sHull[i*2+1] + inset;
724 }
725 final Matrix m = getMatrix();
726 m.mapPoints(corners);
727 }
728
729 public boolean below(int h) {
730 final int N = corners.length/2;
731 for (int i=0; i<N; i++) {
732 final int y = (int) corners[i*2+1];
733 if (y >= h) return true;
734 }
735 return false;
736 }
737
738 public void step(long t_ms, long dt_ms, float t, float dt) {
739 if (getVisibility() != View.VISIBLE) return; // not playing yet
740
Dan Sandler2200f862014-10-06 01:04:47 -0400741 if (mBoosting) {
742 dv = -PARAMS.BOOST_DV;
743 } else {
744 dv += PARAMS.G;
745 }
Dan Sandler4455da92014-09-14 16:28:58 -0400746 if (dv < -PARAMS.MAX_V) dv = -PARAMS.MAX_V;
747 else if (dv > PARAMS.MAX_V) dv = PARAMS.MAX_V;
748
749 final float y = getTranslationY() + dv * dt;
750 setTranslationY(y < 0 ? 0 : y);
751 setRotation(
752 90 + lerp(clamp(rlerp(dv, PARAMS.MAX_V, -1 * PARAMS.MAX_V)), 90, -90));
753
754 prepareCheckIntersections();
755 }
756
757 public void boost() {
Dan Sandler2200f862014-10-06 01:04:47 -0400758 mBoosting = true;
Dan Sandler4455da92014-09-14 16:28:58 -0400759 dv = -PARAMS.BOOST_DV;
Dan Sandler2200f862014-10-06 01:04:47 -0400760
761 animate().cancel();
762 animate()
763 .scaleX(1.25f)
764 .scaleY(1.25f)
765 .translationZ(PARAMS.PLAYER_Z_BOOST)
766 .setDuration(100);
Dan Sandler4455da92014-09-14 16:28:58 -0400767 setScaleX(1.25f);
768 setScaleY(1.25f);
Dan Sandler2200f862014-10-06 01:04:47 -0400769 }
770
771 public void unboost() {
772 mBoosting = false;
773
774 animate().cancel();
Dan Sandler4455da92014-09-14 16:28:58 -0400775 animate()
Dan Sandler2200f862014-10-06 01:04:47 -0400776 .scaleX(1f)
777 .scaleY(1f)
778 .translationZ(PARAMS.PLAYER_Z)
779 .setDuration(200);
Dan Sandler4455da92014-09-14 16:28:58 -0400780 }
781 }
782
783 private class Obstacle extends View implements GameView {
784 public float h;
785
786 public final Rect hitRect = new Rect();
787
788 public Obstacle(Context context, float h) {
789 super(context);
Dan Sandler2200f862014-10-06 01:04:47 -0400790 setBackgroundColor(0xFFFF0000);
Dan Sandler4455da92014-09-14 16:28:58 -0400791 this.h = h;
792 }
793
794 public boolean intersects(Player p) {
795 final int N = p.corners.length/2;
796 for (int i=0; i<N; i++) {
797 final int x = (int) p.corners[i*2];
798 final int y = (int) p.corners[i*2+1];
799 if (hitRect.contains(x, y)) return true;
800 }
801 return false;
802 }
803
804 public boolean cleared(Player p) {
805 final int N = p.corners.length/2;
806 for (int i=0; i<N; i++) {
807 final int x = (int) p.corners[i*2];
808 if (hitRect.right >= x) return false;
809 }
810 return true;
811 }
812
813 @Override
814 public void step(long t_ms, long dt_ms, float t, float dt) {
815 setTranslationX(getTranslationX()-PARAMS.TRANSLATION_PER_SEC*dt);
816 getHitRect(hitRect);
817 }
818 }
819
Dan Sandler2200f862014-10-06 01:04:47 -0400820 private class Pop extends Obstacle {
821 int mRotate;
822 int cx, cy, r;
823 public Pop(Context context, float h) {
824 super(context, h);
825 int idx = 2*irand(0, POPS.length/2);
826 setBackgroundResource(POPS[idx]);
827 setScaleX(frand() < 0.5f ? -1 : 1);
828 mRotate = POPS[idx+1] == 0 ? 0 : (frand() < 0.5f ? -1 : 1);
829 setOutlineProvider(new ViewOutlineProvider() {
830 @Override
831 public void getOutline(View view, Outline outline) {
832 final int pad = (int) (getWidth() * 0.02f);
833 outline.setOval(pad, pad, getWidth()-pad, getHeight()-pad);
834 }
835 });
836 }
837
838 public boolean intersects(Player p) {
839 final int N = p.corners.length/2;
840 for (int i=0; i<N; i++) {
841 final int x = (int) p.corners[i*2];
842 final int y = (int) p.corners[i*2+1];
843 if (Math.hypot(x-cx, y-cy) <= r) return true;
844 }
845 return false;
846 }
847
848 @Override
849 public void step(long t_ms, long dt_ms, float t, float dt) {
850 super.step(t_ms, dt_ms, t, dt);
851 if (mRotate != 0) {
852 setRotation(getRotation() + dt * 45 * mRotate);
853 }
854
855 cx = (hitRect.left + hitRect.right)/2;
856 cy = (hitRect.top + hitRect.bottom)/2;
857 r = getWidth()/2;
858 }
859 }
860
861 private class Stem extends Obstacle {
862 Paint mPaint = new Paint();
863 Path mShadow = new Path();
864 boolean mDrawShadow;
865
866 public Stem(Context context, float h, boolean drawShadow) {
867 super(context, h);
868 mDrawShadow = drawShadow;
869 mPaint.setColor(0xFFAAAAAA);
870 setBackground(null);
871 }
872
873 @Override
874 public void onAttachedToWindow() {
875 super.onAttachedToWindow();
876 setWillNotDraw(false);
877 setOutlineProvider(new ViewOutlineProvider() {
878 @Override
879 public void getOutline(View view, Outline outline) {
880 outline.setRect(0, 0, getWidth(), getHeight());
881 }
882 });
883 }
884 @Override
885 public void onDraw(Canvas c) {
886 final int w = c.getWidth();
887 final int h = c.getHeight();
888 final GradientDrawable g = new GradientDrawable();
889 g.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT);
890 g.setGradientCenter(w * 0.75f, 0);
891 g.setColors(new int[] { 0xFFFFFFFF, 0xFFAAAAAA });
892 g.setBounds(0, 0, w, h);
893 g.draw(c);
894 if (!mDrawShadow) return;
895 mShadow.reset();
896 mShadow.moveTo(0,0);
897 mShadow.lineTo(w, 0);
898 mShadow.lineTo(w, PARAMS.OBSTACLE_WIDTH/2+w*1.5f);
899 mShadow.lineTo(0, PARAMS.OBSTACLE_WIDTH/2);
900 mShadow.close();
901 c.drawPath(mShadow, mPaint);
902 }
903 }
904
Dan Sandler4455da92014-09-14 16:28:58 -0400905 private class Scenery extends FrameLayout implements GameView {
906 public float z;
907 public float v;
908 public int h, w;
909 public Scenery(Context context) {
910 super(context);
911 }
912
913 @Override
914 public void step(long t_ms, long dt_ms, float t, float dt) {
915 setTranslationX(getTranslationX() - PARAMS.TRANSLATION_PER_SEC * dt * v);
916 }
917 }
918
919 private class Building extends Scenery {
920 public Building(Context context) {
921 super(context);
922
923 w = irand(PARAMS.BUILDING_WIDTH_MIN, PARAMS.BUILDING_WIDTH_MAX);
924 h = 0; // will be setup later, along with z
925
926 setTranslationZ(PARAMS.SCENERY_Z);
927 }
928 }
929
930 private class Cloud extends Scenery {
931 public Cloud(Context context) {
932 super(context);
933 setBackgroundResource(frand() < 0.01f ? R.drawable.cloud_off : R.drawable.cloud);
934 getBackground().setAlpha(0x40);
935 w = h = irand(PARAMS.CLOUD_SIZE_MIN, PARAMS.CLOUD_SIZE_MAX);
936 z = 0;
937 v = frand(0.15f,0.5f);
938 }
939 }
940
941 private class Star extends Scenery {
942 public Star(Context context) {
943 super(context);
944 setBackgroundResource(R.drawable.star);
945 w = h = irand(PARAMS.STAR_SIZE_MIN, PARAMS.STAR_SIZE_MAX);
946 v = z = 0;
947 }
948 }
949}