Dasher Dancer
Dasher Dancer Characters
Changed
diff --git a/docs/village.png b/docs/village.png
new file mode 100644
index 000000000..5d011c526
Binary files /dev/null and b/docs/village.png differ
diff --git a/doodles-lib/build.gradle b/doodles-lib/build.gradle
new file mode 100644
index 000000000..93876fcb5
--- /dev/null
+++ b/doodles-lib/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.tools
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ }
+}
+
+dependencies {
+ implementation project(":common")
+
+ implementation rootProject.ext.appCompat
+ implementation rootProject.ext.firebaseConfig
+ implementation rootProject.ext.firebaseCore
+ implementation rootProject.ext.firebaseAppinvite
+
+ api fileTree(dir: 'libs', include: ['*.jar'])
+}
diff --git a/doodles-lib/src/main/AndroidManifest.xml b/doodles-lib/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..aeea6a59d
--- /dev/null
+++ b/doodles-lib/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/BaseDoodleActivity.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/BaseDoodleActivity.java
new file mode 100644
index 000000000..f61405edc
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/BaseDoodleActivity.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles;
+
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.DOODLE_LAUNCHED;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleDebugLogger;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogTimer;
+import com.google.android.apps.santatracker.doodles.shared.views.GameFragment;
+import com.google.android.apps.santatracker.games.OnDemandActivity;
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+/** Base activity for doodle games */
+public abstract class BaseDoodleActivity extends OnDemandActivity {
+
+ private AppInvitesFragment appInvitesFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_view);
+
+ // Setup Analytics
+ FirebaseAnalytics analytics = FirebaseAnalytics.getInstance(this);
+ int stringResource = getAnalyticsStringResource();
+ MeasurementManager.recordScreenView(analytics, getString(stringResource));
+
+ appInvitesFragment = AppInvitesFragment.getInstance(this);
+
+ // Setup Logging
+ DoodleDebugLogger logger = new DoodleDebugLogger();
+ String gameType = getGameType();
+
+ // Setup Fragment
+ Fragment fragment = makeFragment(logger);
+
+ // Log fragment returned
+ logger.logGameLaunchEvent(this, gameType, DOODLE_LAUNCHED);
+ DoodleLogTimer.getInstance().reset();
+
+ FragmentManager fragmentManager = getFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+ fragmentTransaction.add(R.id.activity_wrapper, fragment, "menu");
+ fragmentTransaction.commit();
+ }
+
+ protected abstract String getGameType();
+
+ protected abstract int getAnalyticsStringResource();
+
+ protected abstract Fragment makeFragment(DoodleDebugLogger logger);
+
+ @Override
+ public void onBackPressed() {
+ // Get the current game fragment
+ final Fragment fragment = getFragmentManager().findFragmentById(R.id.activity_wrapper);
+
+ if (fragment instanceof GameFragment) {
+ GameFragment gameFragment = (GameFragment) fragment;
+
+ // Pause the game, or go back to the home screen if the game is paused already
+ if (gameFragment.isGamePaused()
+ || !gameFragment.isFinishedLoading()
+ || gameFragment.isGameOver()) {
+ super.onBackPressed();
+ } else {
+ gameFragment.onBackPressed();
+ }
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ public AppInvitesFragment getAppInvitesFragment() {
+ return appInvitesFragment;
+ }
+}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/Config.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/Config.java
similarity index 81%
rename from doodles/src/main/java/com/google/android/apps/santatracker/doodles/Config.java
rename to doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/Config.java
index 2167fe198..816547b68 100644
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/Config.java
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/Config.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
+ * Copyright 2019. Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -17,12 +17,10 @@
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
-/**
- * Get Firebase Remote config values easily.
- */
+/** Get Firebase Remote config values easily. */
public class Config {
- /** Density of swimming obstacles **/
+ /** Density of swimming obstacles */
public final double SWIMMING_OBSTACLE_DENSITY;
public Config() {
@@ -30,5 +28,4 @@ public Config() {
SWIMMING_OBSTACLE_DENSITY = config.getDouble("SwimmingObstacleDensity");
}
-
}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/AndroidUtils.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/AndroidUtils.java
new file mode 100644
index 000000000..ffa734455
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/AndroidUtils.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.Environment;
+import android.text.Html;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.WindowManager;
+import com.google.android.apps.santatracker.util.SantaLog;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/** Utility functions to make it easier to interact with Android APIs. */
+public final class AndroidUtils {
+ private static final String TAG = AndroidUtils.class.getSimpleName();
+
+ private AndroidUtils() {
+ // Don't instantiate this class.
+ }
+
+ public static Activity getActivityFromContext(Context context) {
+ while (context instanceof ContextWrapper) {
+ if (context instanceof Activity) {
+ return (Activity) context;
+ }
+ context = ((ContextWrapper) context).getBaseContext();
+ }
+ return null;
+ }
+
+ public static void allowScreenToTurnOff(Context context) {
+ getActivityFromContext(context)
+ .getWindow()
+ .clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ public static void forceScreenToStayOn(Context context) {
+ getActivityFromContext(context)
+ .getWindow()
+ .addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ public static Point getScreenSize() {
+ DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
+ return new Point(displayMetrics.widthPixels, displayMetrics.heightPixels);
+ }
+
+ public static float dipToPixels(float dipValue) {
+ DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics);
+ }
+
+ public static float pixelsToDips(float pixelValue) {
+ DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
+ float dip = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, metrics);
+ return pixelValue / dip;
+ }
+
+ public static boolean isExternalStorageWritable() {
+ String state = Environment.getExternalStorageState();
+ return Environment.MEDIA_MOUNTED.equals(state);
+ }
+
+ public static boolean isExternalStorageReadable() {
+ String state = Environment.getExternalStorageState();
+ return Environment.MEDIA_MOUNTED.equals(state)
+ || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
+ }
+
+ /** Handles loading text from our resources, including interpreting and tags. */
+ public static CharSequence getText(Resources res, int id, Object... formatArgs) {
+ try {
+ return Html.fromHtml(res.getString(id, formatArgs));
+ } catch (java.util.MissingFormatArgumentException e) {
+ SantaLog.e(TAG, "unable to format string id: " + id, e);
+ }
+ return "";
+ }
+
+ /**
+ * Re-orients a coordinate system based on default device rotation. Implementation based on:
+ * http://goo.gl/kRajPd
+ *
+ * @param displayRotation Display rotation, from Display.getRotation()
+ * @param eventValues Event values gathered from the raw sensor event.
+ * @return The adjusted event values, with display rotation taken into account.
+ */
+ public static float[] getAdjustedAccelerometerValues(int displayRotation, float[] eventValues) {
+ float[] adjustedValues = new float[3];
+ final int axisSwap[][] = {
+ {1, -1, 0, 1}, // ROTATION_0
+ {-1, -1, 1, 0}, // ROTATION_90
+ {-1, 1, 0, 1}, // ROTATION_180
+ {1, 1, 1, 0} // ROTATION_270
+ };
+
+ final int[] axisFactors = axisSwap[displayRotation];
+ adjustedValues[0] = ((float) axisFactors[0]) * eventValues[axisFactors[2]];
+ adjustedValues[1] = ((float) axisFactors[1]) * eventValues[axisFactors[3]];
+ adjustedValues[2] = eventValues[2];
+
+ return adjustedValues;
+ }
+
+ /**
+ * Reads all bytes from an input stream into a byte array. Does not close the stream.
+ *
+ * @param in the input stream to read from
+ * @return a byte array containing all the bytes from the stream
+ * @throws IOException if an I/O error occurs
+ */
+ public static byte[] toByteArray(InputStream in) throws IOException {
+ // Presize the ByteArrayOutputStream since we know how large it will need
+ // to be, unless that value is less than the default ByteArrayOutputStream
+ // size (32).
+ ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(32, in.available()));
+ copy(in, out);
+ return out.toByteArray();
+ }
+
+ /**
+ * Copies all bytes from the input stream to the output stream. Does not close or flush either
+ * stream.
+ *
+ * @param from the input stream to read from
+ * @param to the output stream to write to
+ * @return the number of bytes copied
+ * @throws IOException if an I/O error occurs
+ */
+ private static long copy(InputStream from, OutputStream to) throws IOException {
+ byte[] buf = new byte[8192];
+ long total = 0;
+ while (true) {
+ int r = from.read(buf);
+ if (r == -1) {
+ break;
+ }
+ to.write(buf, 0, r);
+ total += r;
+ }
+ return total;
+ }
+
+ public static void finishActivity(Context context) {
+ getActivityFromContext(context).finish();
+ }
+
+ public static void finishActivityWithResult(Context context, int resultCode, Bundle extras) {
+ Activity activity = getActivityFromContext(context);
+ Intent intent = activity.getIntent();
+ intent.putExtras(extras);
+ activity.setResult(resultCode, intent);
+ activity.finish();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/BitmapCache.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/BitmapCache.java
new file mode 100644
index 000000000..302eff661
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/BitmapCache.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+import android.graphics.Bitmap;
+import android.util.Pair;
+import java.util.HashMap;
+
+/**
+ * Cache of bitmaps (and sampleSizes), mapped by resource ID and frame number.
+ *
+ *
+ *
+ *
Note: This cache must be manually cleared in order to free up memory. This is under the
+ * assumption that any bitmaps currently used by the app should be in the cache.
+ */
+public class BitmapCache {
+ public static final String TAG = BitmapCache.class.getSimpleName();
+
+ private HashMap> bitmapCache = new HashMap<>();
+
+ private static String bitmapCacheKey(int id, int frameNumber) {
+ return id + ":" + frameNumber;
+ }
+
+ public Pair getBitmapFromCache(int id, int frame) {
+ Pair pair = bitmapCache.get(bitmapCacheKey(id, frame));
+ return pair;
+ }
+
+ public void putBitmapInCache(Bitmap bitmap, int id, int frame, int sampleSize) {
+ bitmapCache.put(bitmapCacheKey(id, frame), new Pair(bitmap, sampleSize));
+ }
+
+ public void clear() {
+ bitmapCache.clear();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/CallbackProcess.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/CallbackProcess.java
new file mode 100644
index 000000000..4178c80f0
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/CallbackProcess.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+/** A process which should be run once and then be finished. */
+public abstract class CallbackProcess extends Process {
+ private boolean didRun = false;
+
+ @Override
+ public void update(float deltaMs) {
+ if (!didRun) {
+ updateLogic(deltaMs);
+ didRun = true;
+ }
+ }
+
+ @Override
+ public boolean isFinished() {
+ return didRun;
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/ColoredRectangleActor.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/ColoredRectangleActor.java
new file mode 100644
index 000000000..0ebeb0cd1
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/ColoredRectangleActor.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Cap;
+import android.graphics.Paint.Style;
+import com.google.android.apps.santatracker.doodles.shared.actor.Actor;
+import com.google.android.apps.santatracker.doodles.shared.physics.Util;
+import java.util.HashMap;
+import java.util.Map;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** An actor class which represents an arbitrarily-colored rectangle. */
+public class ColoredRectangleActor extends Actor implements Touchable {
+ /* Default color is black*/
+ public static final String UNSPECIFIED = "unspecified";
+
+ /* Golf colors */
+ public static final String TEE_GREEN = "tee";
+ public static final String FAIRWAY_GREEN = "fairway";
+
+ /* Swimming colors */
+ public static final String DISTANCE_30M = "30m";
+ public static final String DISTANCE_50M = "50m";
+ public static final String DISTANCE_100M = "100m";
+ public static final String DISTANCE_LEVEL_LENGTH = "level length";
+ public static final String DISTANCE_PR = "pr";
+ public static final String STARTING_BLOCK = "start";
+
+ public static final String DIMENS_X_KEY = "dimens x";
+ public static final String DIMENS_Y_KEY = "dimens y";
+
+ public static final Map TYPE_TO_COLOR_MAP;
+
+ static {
+ TYPE_TO_COLOR_MAP = new HashMap<>();
+ /* Golf */
+ TYPE_TO_COLOR_MAP.put(TEE_GREEN, Constants.LIGHT_GREEN);
+ TYPE_TO_COLOR_MAP.put(FAIRWAY_GREEN, Constants.DARK_GREEN);
+ /* Swimming */
+ TYPE_TO_COLOR_MAP.put(DISTANCE_30M, 0x44cd7f32);
+ TYPE_TO_COLOR_MAP.put(DISTANCE_50M, 0x44c0c0c0);
+ TYPE_TO_COLOR_MAP.put(DISTANCE_100M, 0x44ffd700);
+ TYPE_TO_COLOR_MAP.put(DISTANCE_LEVEL_LENGTH, 0x44ffffff);
+ TYPE_TO_COLOR_MAP.put(DISTANCE_PR, 0x4400cc00);
+ TYPE_TO_COLOR_MAP.put(STARTING_BLOCK, 0xff4993a4);
+ TYPE_TO_COLOR_MAP.put(UNSPECIFIED, 0xff000000);
+ }
+
+ public String type;
+ public Vector2D dimens;
+ private Direction selectedDirection;
+ private Paint paint;
+ private Paint midpointPaint;
+ private Vector2D upMidpoint = Vector2D.get();
+ private Vector2D downMidpoint = Vector2D.get();
+ private Vector2D leftMidpoint = Vector2D.get();
+ private Vector2D rightMidpoint = Vector2D.get();
+
+ public ColoredRectangleActor(Vector2D position, Vector2D dimens) {
+ this(position, dimens, UNSPECIFIED);
+ }
+
+ public ColoredRectangleActor(Vector2D position, Vector2D dimens, String type) {
+ super(position, Vector2D.get());
+
+ this.dimens = dimens;
+ this.type = type;
+ this.zIndex = -1;
+
+ if (type.equals(FAIRWAY_GREEN)) {
+ this.zIndex = -2;
+ }
+
+ selectedDirection = Direction.NONE;
+ paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setColor(TYPE_TO_COLOR_MAP.get(type));
+ paint.setStyle(Style.FILL);
+
+ midpointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ midpointPaint.setColor(Color.WHITE);
+
+ updateExtents();
+ }
+
+ public static ColoredRectangleActor fromJSON(JSONObject json) throws JSONException {
+ String type = json.getString(Actor.TYPE_KEY);
+ Vector2D position =
+ Vector2D.get((float) json.optDouble(X_KEY, 0), (float) json.optDouble(Y_KEY, 0));
+ Vector2D dimens =
+ Vector2D.get(
+ (float) json.optDouble(DIMENS_X_KEY, 0),
+ (float) json.optDouble(DIMENS_Y_KEY, 0));
+ return new ColoredRectangleActor(position, dimens, type);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawRect(
+ position.x, position.y, position.x + dimens.x, position.y + dimens.y, paint);
+ }
+
+ public void setStyle(Style style) {
+ paint.setStyle(style);
+ }
+
+ public void setStrokeWidth(float width) {
+ paint.setStrokeWidth(width);
+ paint.setStrokeCap(Cap.ROUND);
+ }
+
+ public void setColor(int color) {
+ paint.setColor(color);
+ }
+
+ @Override
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public boolean canHandleTouchAt(Vector2D worldCoords, float cameraScale) {
+ Vector2D lowerRight = Vector2D.get(position).add(dimens);
+ boolean retVal =
+ Util.pointIsWithinBounds(position, lowerRight, worldCoords)
+ || worldCoords.distanceTo(upMidpoint) < Constants.SELECTION_RADIUS
+ || worldCoords.distanceTo(downMidpoint) < Constants.SELECTION_RADIUS
+ || worldCoords.distanceTo(leftMidpoint) < Constants.SELECTION_RADIUS
+ || worldCoords.distanceTo(rightMidpoint) < Constants.SELECTION_RADIUS;
+
+ lowerRight.release();
+ return retVal;
+ }
+
+ @Override
+ public void startTouchAt(Vector2D worldCoords, float cameraScale) {
+ if (worldCoords.distanceTo(upMidpoint) < Constants.SELECTION_RADIUS) {
+ selectedDirection = Direction.UP;
+ } else if (worldCoords.distanceTo(downMidpoint) < Constants.SELECTION_RADIUS) {
+ selectedDirection = Direction.DOWN;
+ } else if (worldCoords.distanceTo(leftMidpoint) < Constants.SELECTION_RADIUS) {
+ selectedDirection = Direction.LEFT;
+ } else if (worldCoords.distanceTo(rightMidpoint) < Constants.SELECTION_RADIUS) {
+ selectedDirection = Direction.RIGHT;
+ } else {
+ selectedDirection = Direction.NONE;
+ }
+ }
+
+ @Override
+ public boolean handleMoveEvent(Vector2D delta) {
+ if (selectedDirection == Direction.NONE) {
+ position.subtract(delta);
+ } else if (selectedDirection == Direction.UP) {
+ position.y -= delta.y;
+ dimens.y += delta.y;
+ } else if (selectedDirection == Direction.DOWN) {
+ dimens.y -= delta.y;
+ } else if (selectedDirection == Direction.LEFT) {
+ position.x -= delta.x;
+ dimens.x += delta.x;
+ } else {
+ // Direction.RIGHT
+ dimens.x -= delta.x;
+ }
+ updateExtents();
+ return true;
+ }
+
+ @Override
+ public boolean handleLongPress() {
+ return false;
+ }
+
+ @Override
+ public JSONObject toJSON() throws JSONException {
+ JSONObject json = new JSONObject();
+ json.put(TYPE_KEY, getType());
+ json.put(X_KEY, position.x);
+ json.put(Y_KEY, position.y);
+ json.put(DIMENS_X_KEY, dimens.x);
+ json.put(DIMENS_Y_KEY, dimens.y);
+ return json;
+ }
+
+ private void updateExtents() {
+ upMidpoint.set(position).add(dimens.x / 2, 0);
+ downMidpoint.set(position).add(dimens.x / 2, dimens.y);
+ leftMidpoint.set(position).add(0, dimens.y / 2);
+ rightMidpoint.set(position).add(dimens.x, dimens.y / 2);
+ }
+
+ /** A direction used to pull the boundaries of the colored rectangle. */
+ private enum Direction {
+ NONE,
+ UP,
+ DOWN,
+ LEFT,
+ RIGHT,
+ }
+}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/Constants.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Constants.java
similarity index 76%
rename from doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/Constants.java
rename to doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Constants.java
index 2da86a591..e51115a8a 100644
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/Constants.java
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Constants.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
+ * Copyright 2019. Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,16 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.apps.santatracker.doodles.tilt;
+package com.google.android.apps.santatracker.doodles.shared;
-/**
- * Constants from deleted games that were unsafe to delete.
- */
+/** Constants from deleted games that were unsafe to delete. */
public class Constants {
public static final float SELECTION_RADIUS = 70;
public static final int DARK_GREEN = 0xff9b953d;
public static final int LIGHT_GREEN = 0xffb1af4b;
public static final float GRAVITY = 4000.0f;
-
}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Debug.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Debug.java
new file mode 100644
index 000000000..dbdf061ea
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Debug.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+import android.content.res.Resources;
+import com.google.android.apps.santatracker.doodles.shared.actor.Actor;
+import com.google.android.apps.santatracker.doodles.shared.actor.SpriteActor;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite;
+import com.google.android.apps.santatracker.doodles.shared.animation.Sprites;
+
+/** Debug flags (collected in one place). */
+public class Debug {
+ // Draw positions of things as they move around. Tennis only for now.
+ public static final boolean DRAW_POSITIONS = false;
+
+ // Draw the targets that the players hit towards. Tennis only for now.
+ public static final boolean DRAW_TARGETS = false;
+
+ // Draw the location of each hit. Tennis only for now.
+ public static final boolean MARK_HITS = false;
+
+ // Play without user input. Tennis only for now.
+ public static final boolean AUTO_PLAY = false;
+
+ // Slow down or speed up everything (scales deltaMs).
+ public static final float SPEED_MULTIPLIER = 1f;
+
+ // Skip frames (slows game down without affecting deltaMs).
+ public static final float FRAME_SKIP = 0;
+
+ public static final boolean SHOW_SECONDARY_MENU_ICONS = false;
+
+ public static final boolean DRAW_COLLISION_BOUNDS = false;
+
+ // Return a SpriteActor of an "X" marker, centered over (x, y). For marking positions for
+ // debugging.
+ public static SpriteActor makeDebugMarkerX(Resources resources, float x, float y) {
+ AnimatedSprite sprite = AnimatedSprite.fromFrames(resources, Sprites.debug_marker);
+ return new SpriteActor(
+ sprite,
+ Vector2D.get(x - sprite.frameWidth / 2, y - sprite.frameHeight / 2),
+ Vector2D.get(0, 0));
+ }
+
+ // Return a tiny rectangle actor, centered over (x, y). For marking positions for debugging.
+ public static Actor makeDebugMarkerPoint(float x, float y) {
+ float size = 3;
+ return new ColoredRectangleActor(
+ Vector2D.get(x - size / 2, y - size / 2), Vector2D.get(size, size), "fairway");
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/EventBus.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/EventBus.java
new file mode 100644
index 000000000..4b71d3447
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/EventBus.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** A simple event bus for passing events between objects. */
+public class EventBus {
+ public static final int VIBRATE = 0;
+ public static final int SCORE_CHANGED = 1;
+ public static final int SHAKE_SCREEN = 2;
+ public static final int BRONZE = 3;
+ public static final int SILVER = 4;
+ public static final int GOLD = 5;
+ public static final int SWIMMING_DIVE = 6;
+ public static final int GAME_STATE_CHANGED = 7;
+ public static final int PLAY_SOUND = 8;
+ public static final int PAUSE_SOUND = 9;
+ public static final int MUTE_SOUNDS = 10;
+ public static final int GAME_OVER = 11;
+ public static final int GAME_LOADED = 12;
+ private static EventBus instance;
+ private final Object lock = new Object();
+ // Listeners for specific events.
+ private Map> specificListeners;
+ // Listeners for all events.
+ private Set globalListeners;
+
+ private EventBus() {
+ globalListeners = new HashSet<>();
+ specificListeners = new HashMap<>();
+ }
+
+ public static EventBus getInstance() {
+ if (instance == null) {
+ instance = new EventBus();
+ }
+ return instance;
+ }
+
+ /** Register for a specific event. Listener will only be called for events of that type. */
+ public void register(EventBusListener listener, int type) {
+ synchronized (lock) {
+ if (!specificListeners.containsKey(type)) {
+ specificListeners.put(type, new HashSet());
+ }
+ specificListeners.get(type).add(listener);
+ }
+ }
+
+ /** Register for all events. Listener will be called for events of any type. */
+ public void register(EventBusListener listener) {
+ synchronized (lock) {
+ globalListeners.add(listener);
+ }
+ }
+
+ /** Send an event without data. */
+ public void sendEvent(int type) {
+ sendEvent(type, null);
+ }
+
+ /** Send an event with data. Type of the data is up to the caller. */
+ public void sendEvent(int type, Object data) {
+ synchronized (lock) {
+ try {
+ Set listeners = specificListeners.get(type);
+ if (listeners != null) {
+ for (EventBusListener listener : listeners) {
+ listener.onEventReceived(type, data);
+ }
+ }
+ for (EventBusListener listener : globalListeners) {
+ listener.onEventReceived(type, data);
+ }
+ } catch (ClassCastException e) {
+ // This was happening when 2 games were running at the same time (which shouldn't be
+ // possible, but was happening in monkey testing). Game A's listener would try
+ // casting
+ // the data arg to the expected type for Game A, but this would fail if Game B sent
+ // a data
+ // of a different type.
+ //
+ // Ignore this and continue running.
+ }
+ }
+ }
+
+ /** Removes all the listeners from this EventBus. */
+ public void clearListeners() {
+ synchronized (lock) {
+ specificListeners.clear();
+ globalListeners.clear();
+ }
+ }
+
+ /** Interface for objects which want to listen to the event bus. */
+ public interface EventBusListener {
+ void onEventReceived(int type, Object data);
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/ExternalStoragePermissions.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/ExternalStoragePermissions.java
new file mode 100644
index 000000000..60f885827
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/ExternalStoragePermissions.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+/** Contains data about whether or not external storage is readable or writable. */
+public class ExternalStoragePermissions {
+
+ public boolean isExternalStorageReadable() {
+ return AndroidUtils.isExternalStorageReadable();
+ }
+
+ public boolean isExternalStorageWritable() {
+ return AndroidUtils.isExternalStorageWritable();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameLoop.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameLoop.java
new file mode 100644
index 000000000..b1617d96a
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameLoop.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+/** Interface to handle the updating of game logic. */
+public interface GameLoop {
+
+ /**
+ * @param deltaMs Milliseconds since the last time update was called. Will be capped to avoid
+ * big jumps.
+ */
+ void update(float deltaMs);
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameType.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameType.java
new file mode 100644
index 000000000..0f42ee6ec
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameType.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+/**
+ * Enum for all the different game types we have. Used by each type to get their proper history
+ * content.
+ */
+public enum GameType {
+ SWIMMING,
+ WATER_POLO,
+ PURSUIT,
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/HistoryManager.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/HistoryManager.java
new file mode 100644
index 000000000..27ed4ca80
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/HistoryManager.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import com.google.android.apps.santatracker.util.SantaLog;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Maintains the history and stats of what the user has accomplished.
+ *
+ *
+ *
+ *
Note that this class handles the serializing into JSON instead of each game. This was done to
+ * make it easier to make a game picker that showed your status on each game. Since there are
+ * canonical types it would then know how to read them. We add a setArbitraryData and
+ * getArbitaryData for any game that wants to put other kind of information in.
+ */
+public class HistoryManager {
+ public static final String BEST_PLACE_KEY = "place";
+ public static final String BEST_STAR_COUNT_KEY = "stars";
+ public static final String BEST_TIME_MILLISECONDS_KEY = "time";
+ public static final String BEST_SCORE_KEY = "score";
+ public static final String BEST_DISTANCE_METERS_KEY = "distance";
+ public static final String ARBITRARY_DATA_KEY = "arb";
+ private static final String TAG = HistoryManager.class.getSimpleName();
+ private static final String FILENAME = "history.json";
+
+ private final Context context;
+ private volatile JSONObject history;
+ private HistoryListener listener;
+
+ /** Creates a history manager. HistoryListener can be null. */
+ public HistoryManager(Context context, HistoryListener listener) {
+ this.context = context;
+ this.listener = listener;
+ // While history is loading from disk, we ignore any changes clients might ask for.
+ history = null;
+ load();
+ }
+
+ public void setListener(HistoryListener listener) {
+ this.listener = listener;
+ }
+
+ /** Gets the json object for a particular game type. */
+ private JSONObject getGameObject(GameType gameType) throws JSONException {
+ if (history == null) {
+ throw new JSONException("null history");
+ }
+ JSONObject gameObject = history.optJSONObject(gameType.toString());
+ if (gameObject == null) {
+ gameObject = new JSONObject();
+ }
+ return gameObject;
+ }
+
+ /**
+ * Set the best place (1st, 2nd, 3rd) for a game type. NOTE: It's expected for the client to
+ * figure out if it is the best place.
+ */
+ public void setBestPlace(GameType gameType, int place) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ gameObject.put(BEST_PLACE_KEY, place);
+ history.put(gameType.toString(), gameObject);
+ } catch (JSONException e) {
+ SantaLog.e(TAG, "error setting place", e);
+ }
+ }
+
+ /** ********************** Setters **************************** */
+
+ /**
+ * Set the best star count for a game type. NOTE: It's expected for the client to figure out if
+ * it is the best star count.
+ */
+ public void setBestStarCount(GameType gameType, int count) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ gameObject.put(BEST_STAR_COUNT_KEY, count);
+ history.put(gameType.toString(), gameObject);
+ } catch (JSONException e) {
+ SantaLog.e(TAG, "error setting place", e);
+ }
+ }
+
+ /**
+ * Set the best time for a game type. NOTE: it's expected for the client to figure out if it is
+ * the best time since some will want bigger and some will want smaller numbers.
+ */
+ public void setBestTime(GameType gameType, long timeInMilliseconds) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ gameObject.put(BEST_TIME_MILLISECONDS_KEY, timeInMilliseconds);
+ history.put(gameType.toString(), gameObject);
+ } catch (JSONException e) {
+ SantaLog.e(TAG, "error setting time", e);
+ }
+ }
+
+ /**
+ * Set the best score for a game type. NOTE: it's expected for the client to figure out if it is
+ * the best score since some will want bigger and some will want smaller numbers.
+ */
+ public void setBestScore(GameType gameType, double score) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ gameObject.put(BEST_SCORE_KEY, score);
+ history.put(gameType.toString(), gameObject);
+ } catch (JSONException e) {
+ SantaLog.e(TAG, "error setting score", e);
+ }
+ }
+
+ /**
+ * Set the best distance for a game type. NOTE: it's expected for the client to figure out if it
+ * is the best distance since some will want bigger and some will want smaller numbers.
+ */
+ public void setBestDistance(GameType gameType, double distanceInMeters) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ gameObject.put(BEST_DISTANCE_METERS_KEY, distanceInMeters);
+ history.put(gameType.toString(), gameObject);
+ } catch (JSONException e) {
+ SantaLog.e(TAG, "error setting distance", e);
+ }
+ }
+
+ /** Sets an arbitrary jsonObject a game might want. */
+ public void setArbitraryData(GameType gameType, JSONObject data) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ gameObject.put(ARBITRARY_DATA_KEY, data);
+ history.put(gameType.toString(), gameObject);
+ } catch (JSONException e) {
+ SantaLog.e(TAG, "error setting distance", e);
+ }
+ }
+
+ /** Returns the best place so far. Null if no value has been given yet. */
+ public Integer getBestPlace(GameType gameType) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ return gameObject.getInt(BEST_PLACE_KEY);
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /** ********************** Getters **************************** */
+
+ /** Returns the best star count so far. Null if no value has been given yet. */
+ public Integer getBestStarCount(GameType gameType) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ return gameObject.getInt(BEST_STAR_COUNT_KEY);
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /** Returns the best time so far. Null if no value has been given yet. */
+ public Long getBestTime(GameType gameType) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ return gameObject.getLong(BEST_TIME_MILLISECONDS_KEY);
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /** Returns the best score so far. Null if no value has been given yet. */
+ public Double getBestScore(GameType gameType) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ return gameObject.getDouble(BEST_SCORE_KEY);
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /** Returns the best distance so far. Null if no value has been given yet. */
+ public Double getBestDistance(GameType gameType) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ return gameObject.getDouble(BEST_DISTANCE_METERS_KEY);
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /** Returns arbitrary JSONObject a game might want. Null if no value has been given yet. */
+ public JSONObject getArbitraryData(GameType gameType) {
+ try {
+ JSONObject gameObject = getGameObject(gameType);
+ return gameObject.getJSONObject(ARBITRARY_DATA_KEY);
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /** Saves the file in the background. */
+ public void save() {
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ FileOutputStream outputStream =
+ context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
+ byte[] bytes = history.toString().getBytes();
+ outputStream.write(bytes);
+ outputStream.close();
+ SantaLog.i(TAG, "Saved: " + history);
+ } catch (IOException e) {
+ SantaLog.w(TAG, "Couldn't save JSON at: " + FILENAME);
+ } catch (Exception e) {
+ SantaLog.w(TAG, "Crazy exception happened", e);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (listener != null) {
+ listener.onFinishedSaving();
+ }
+ }
+ }.execute();
+ }
+
+ /** ******************** File Management ************************* */
+
+ /**
+ * Loads the history object from file. Then merges with any changes that might have occured
+ * while we waited for it to load.
+ */
+ private void load() {
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ File file = new File(context.getFilesDir(), FILENAME);
+ int length = (int) file.length();
+ if (length <= 0) {
+ history = new JSONObject();
+ return null;
+ }
+
+ byte[] bytes = new byte[length];
+ FileInputStream inputStream = new FileInputStream(file);
+ inputStream.read(bytes);
+ inputStream.close();
+
+ history = new JSONObject(new String(bytes, "UTF-8"));
+ SantaLog.i(TAG, "Loaded: " + history);
+ } catch (JSONException e) {
+ SantaLog.w(TAG, "Couldn't create JSON for: " + FILENAME);
+ } catch (UnsupportedEncodingException e) {
+ SantaLog.d(TAG, "Couldn't decode: " + FILENAME);
+ } catch (IOException e) {
+ SantaLog.w(TAG, "Couldn't read history: " + FILENAME);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (listener != null) {
+ listener.onFinishedLoading();
+ }
+ }
+ }.execute();
+ }
+
+ /** Listener for when the history is loaded. */
+ public static interface HistoryListener {
+ public void onFinishedLoading();
+
+ public void onFinishedSaving();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/LogicRefreshThread.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/LogicRefreshThread.java
new file mode 100644
index 000000000..f3b3b4a5c
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/LogicRefreshThread.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/** Thread subclass which handles refreshing the game logic. */
+public class LogicRefreshThread extends Thread {
+ private static final int REFRESH_MODEL = 0;
+
+ // Wait at least this long between updates.
+ // Update at 120 FPS so that stutters due to draw-loop synchronization are less noticeable.
+ private static final int MODEL_INTERVAL_MS = 1000 / 60;
+ private final ConditionVariable handlerCreatedCV = new ConditionVariable();
+ private Handler handler;
+ // Toggled in start/stop, and used in handleMessage to conditionally schedule the next refresh.
+ private volatile boolean running;
+
+ private GameLoop gameLoop;
+ private long lastTick;
+ private int framesSkippedSinceLastUpdate = 0;
+
+ public LogicRefreshThread() {
+ setPriority(Thread.MAX_PRIORITY);
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+
+ handler =
+ new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (running && gameLoop != null) {
+ if (msg.what == REFRESH_MODEL) {
+ float deltaMs = System.currentTimeMillis() - lastTick;
+ // Cap deltaMs. Better for game to appear to slow down than have
+ // skips/jumps.
+ deltaMs = Math.min(100, deltaMs);
+ deltaMs *= Debug.SPEED_MULTIPLIER;
+ lastTick = System.currentTimeMillis();
+
+ framesSkippedSinceLastUpdate++;
+ if (framesSkippedSinceLastUpdate >= Debug.FRAME_SKIP) {
+ framesSkippedSinceLastUpdate = 0;
+ if (running && gameLoop != null) {
+ gameLoop.update(deltaMs);
+ }
+ }
+
+ // Wait different amounts of time depending on how much time the
+ // game loop took.
+ // Wait at least 1ms to avoid a mysterious memory leak.
+ long timeToUpdate = System.currentTimeMillis() - lastTick;
+ sendEmptyMessageDelayed(
+ REFRESH_MODEL,
+ Math.max(1, MODEL_INTERVAL_MS - timeToUpdate));
+ }
+ }
+ }
+ };
+ handlerCreatedCV.open();
+
+ Looper.loop();
+ }
+
+ public void startHandler(GameLoop gameLoop) {
+ this.gameLoop = gameLoop;
+ running = true;
+ lastTick = System.currentTimeMillis();
+
+ handlerCreatedCV.block();
+ handler.sendEmptyMessage(REFRESH_MODEL);
+ }
+
+ public void stopHandler() {
+ running = false;
+ gameLoop = null;
+
+ handlerCreatedCV.block();
+ handler.removeMessages(REFRESH_MODEL);
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Process.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Process.java
new file mode 100644
index 000000000..13dc90125
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Process.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+/**
+ * A generic class for running some piece of code. This class is most useful when used inside of a
+ * process chain, which allows you explicitly define portions of code which should be run in serial
+ * (generally inside of the update loop).
+ */
+public abstract class Process {
+
+ public Process() {}
+
+ /**
+ * The outer update function for this process. Note that, when implementing the logic for the
+ * process, updateLogic() should generally be overridden instead of update().
+ *
+ * @param deltaMs
+ */
+ public void update(float deltaMs) {
+ if (!isFinished()) {
+ updateLogic(deltaMs);
+ }
+ }
+
+ public ProcessChain then(Process other) {
+ return new ProcessChain(this).then(other);
+ }
+
+ public ProcessChain then(ProcessChain pc) {
+ return new ProcessChain(this).then(pc);
+ }
+
+ public abstract void updateLogic(float deltaMs);
+
+ public abstract boolean isFinished();
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/ProcessChain.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/ProcessChain.java
new file mode 100644
index 000000000..2c048bd96
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/ProcessChain.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A chain of processes, which are executed in the order in which they are added. When a process is
+ * finished, it is removed from the chain and the next process in line is executed.
+ */
+public class ProcessChain {
+ private final ReentrantLock lock = new ReentrantLock();
+ private Queue processes;
+
+ public ProcessChain(Process p) {
+ processes = new LinkedList<>();
+ processes.add(p);
+ }
+
+ public static void updateChains(List processChains, float deltaMs) {
+ // Remove finished chains.
+ for (int i = processChains.size() - 1; i >= 0; i--) {
+ ProcessChain chain = processChains.get(i);
+ if (chain.isFinished()) {
+ processChains.remove(i);
+ }
+ }
+ // Update still-running chains.
+ for (int i = 0; i < processChains.size(); i++) {
+ processChains.get(i).update(deltaMs);
+ }
+ }
+
+ public ProcessChain then(Process p) {
+ lock.lock();
+ try {
+ processes.add(p);
+ } finally {
+ lock.unlock();
+ }
+ return this;
+ }
+
+ public ProcessChain then(ProcessChain pc) {
+ lock.lock();
+ try {
+ processes.addAll(pc.processes);
+ } finally {
+ lock.unlock();
+ }
+ return this;
+ }
+
+ public void update(float deltaMs) {
+ lock.lock();
+ try {
+ final Process activeProcess = processes.peek();
+ if (activeProcess != null) {
+ activeProcess.update(deltaMs);
+
+ if (activeProcess.isFinished()) {
+ processes.remove();
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean isFinished() {
+ lock.lock();
+ try {
+ return processes.isEmpty();
+ } finally {
+ lock.unlock();
+ }
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Touchable.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Touchable.java
new file mode 100644
index 000000000..26c0c40c6
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Touchable.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+/** An actor which can be touched in the level editor. */
+public interface Touchable {
+ boolean canHandleTouchAt(Vector2D worldCoords, float cameraScale);
+
+ void startTouchAt(Vector2D worldCoords, float cameraScale);
+
+ /**
+ * Handle a move event internally.
+ *
+ * @param delta the movement vector
+ * @return true if the move event has been handled, false otherwise.
+ */
+ boolean handleMoveEvent(Vector2D delta);
+
+ /**
+ * Handle a long press internally.
+ *
+ * @return true if the long press has been handled, false otherwise.
+ */
+ boolean handleLongPress();
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIRefreshHandler.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIRefreshHandler.java
new file mode 100644
index 000000000..8921d0fcd
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIRefreshHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+
+/** Handler subclass which handles refreshing the UI. */
+public class UIRefreshHandler extends Handler {
+ private static final int REFRESH_UI_MESSAGE = 0;
+ // Refresh the UI at a higher rate so that we can keep the drawing pipeline filled.
+ private static final int UI_INTERVAL_MS = 1000 / 120;
+
+ // Toggled in start/stop, and used in handleMessage to conditionally schedule the next refresh.
+ private volatile boolean running;
+
+ private View view;
+
+ public UIRefreshHandler() {}
+
+ public void start(View view) {
+ running = true;
+ this.view = view;
+ sendEmptyMessage(REFRESH_UI_MESSAGE);
+ }
+
+ public void stop() {
+ running = false;
+ view = null;
+ removeMessages(REFRESH_UI_MESSAGE);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (running) {
+ if (msg.what == REFRESH_UI_MESSAGE) {
+ long timeBeforeDraw = System.currentTimeMillis();
+ if (view != null) {
+ // invalidate
+ view.invalidate();
+ }
+ // Wait different amounts of time depending on how much time the draw took.
+ // Wait at least 1ms to avoid a mysterious memory leak.
+ long timeToDraw = System.currentTimeMillis() - timeBeforeDraw;
+ sendEmptyMessageDelayed(
+ REFRESH_UI_MESSAGE, Math.max(1, UI_INTERVAL_MS - timeToDraw));
+ }
+ }
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIUtil.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIUtil.java
new file mode 100644
index 000000000..94be469c9
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIUtil.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.TextView;
+
+/** Utility class for working with Android views. */
+public final class UIUtil {
+
+ private UIUtil() {
+ // Don't instantiate this class.
+ }
+
+ /** Shortcut to create a ValuesAnimator with the given configuration. */
+ public static ValueAnimator animator(
+ long durationMillis,
+ TimeInterpolator interpolator,
+ AnimatorUpdateListener listener,
+ PropertyValuesHolder... propertyValuesHolders) {
+ ValueAnimator tween = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolders);
+ tween.setDuration(durationMillis);
+ tween.setInterpolator(interpolator);
+ tween.addUpdateListener(listener);
+ return tween;
+ }
+
+ /** Shortcut for making a PropertyValuesHolder for floats. */
+ public static PropertyValuesHolder floatValue(String name, float start, float end) {
+ return PropertyValuesHolder.ofFloat(name, start, end);
+ }
+
+ public static void fadeOutAndHide(
+ final View v, long durationMs, float startAlpha, final Runnable onFinishRunnable) {
+
+ if (v.getVisibility() != View.VISIBLE) {
+ return; // Already hidden.
+ }
+ ValueAnimator fadeOut =
+ animator(
+ durationMs,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ v.setAlpha((float) valueAnimator.getAnimatedValue("alpha"));
+ }
+ },
+ floatValue("alpha", startAlpha, 0));
+ fadeOut.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setVisibility(View.INVISIBLE);
+ if (onFinishRunnable != null) {
+ onFinishRunnable.run();
+ }
+ }
+ });
+ fadeOut.start();
+ }
+
+ public static void fadeOutAndHide(final View v, long durationMs, float startAlpha) {
+ fadeOutAndHide(v, durationMs, startAlpha, null);
+ }
+
+ public static void fadeOutAndHide(final View v, long durationMs) {
+ fadeOutAndHide(v, durationMs, 1);
+ }
+
+ public static void showAndFadeIn(final View v, long durationMs, float endAlpha) {
+ if (v.getVisibility() == View.VISIBLE) {
+ return; // Already visible.
+ }
+ v.setAlpha(0);
+ v.setVisibility(View.VISIBLE);
+
+ ValueAnimator fadeIn =
+ animator(
+ durationMs,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ v.setAlpha((float) valueAnimator.getAnimatedValue("alpha"));
+ }
+ },
+ floatValue("alpha", 0, endAlpha));
+ fadeIn.start();
+ }
+
+ public static void showAndFadeIn(final View v, long durationMs) {
+ showAndFadeIn(v, durationMs, 1);
+ }
+
+ public static void fitToBounds(TextView textView, float widthPx, float heightPx) {
+ textView.measure(0, 0);
+ float currentWidthPx = textView.getMeasuredWidth();
+ float currentHeightPx = textView.getMeasuredHeight();
+ float textSize = textView.getTextSize();
+
+ float scale = Math.min(widthPx / currentWidthPx, heightPx / currentHeightPx);
+ textView.setTextSize(textSize * scale);
+ }
+
+ /**
+ * Translates in Y from startPercent to endPercent (expecting 0 for 0%, 1 for 100%). Hides at
+ * the end based on hideOnEnd.
+ */
+ public static void panUpAndHide(
+ final View v,
+ float startPercent,
+ float endPercent,
+ long durationMs,
+ boolean hideOnEnd) {
+ if (v.getVisibility() != View.VISIBLE) {
+ return; // Already hidden.
+ }
+ ValueAnimator panUp =
+ animator(
+ durationMs,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ v.setY(((float) valueAnimator.getAnimatedValue()) * v.getHeight());
+ }
+ },
+ floatValue("translateY", startPercent, endPercent));
+ if (hideOnEnd) {
+ panUp.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setVisibility(View.INVISIBLE);
+ }
+ });
+ }
+ panUp.start();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Vector2D.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Vector2D.java
new file mode 100644
index 000000000..8e585cd73
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/Vector2D.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+import java.util.Stack;
+
+/** A basic 2D vector, with convenience functions to interact with it. */
+public class Vector2D {
+ private static final int MAX_POOL_SIZE = 50;
+ private static final Stack vectorPool = new Stack<>();
+
+ public float x;
+ public float y;
+
+ private Vector2D(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public static Vector2D get() {
+ return get(0, 0);
+ }
+
+ public static synchronized Vector2D get(float x, float y) {
+ if (!vectorPool.isEmpty()) {
+ Vector2D v = vectorPool.pop();
+ v.set(x, y);
+ return v;
+ } else {
+ return new Vector2D(x, y);
+ }
+ }
+
+ public static Vector2D get(Vector2D other) {
+ return get(other.x, other.y);
+ }
+
+ public static float getLength(float x, float y) {
+ return (float) Math.sqrt(x * x + y * y);
+ }
+
+ /**
+ * Release this vector back into the vector pool. Note that, once this has been called, the
+ * vector object may be re-used, and there is no guarantee that the released object will act as
+ * expected.
+ */
+ public void release() {
+ if (vectorPool.size() < MAX_POOL_SIZE) {
+ vectorPool.push(this);
+ }
+ }
+
+ public Vector2D normalize() {
+ float length = getLength();
+ if (length == 0) {
+ set(0, 0);
+ } else {
+ set(x / length, y / length);
+ }
+ return this;
+ }
+
+ public Vector2D toNormal() {
+ return set(y, -x).normalize();
+ }
+
+ public float getLength() {
+ return getLength(x, y);
+ }
+
+ public Vector2D add(Vector2D rhs) {
+ set(this.x + rhs.x, this.y + rhs.y);
+ return this;
+ }
+
+ public Vector2D add(float x, float y) {
+ set(this.x + x, this.y + y);
+ return this;
+ }
+
+ public Vector2D subtract(Vector2D rhs) {
+ set(this.x - rhs.x, this.y - rhs.y);
+ return this;
+ }
+
+ public Vector2D subtract(float x, float y) {
+ set(this.x - x, this.y - y);
+ return this;
+ }
+
+ public Vector2D scale(float factor) {
+ set(this.x * factor, this.y * factor);
+ return this;
+ }
+
+ public float dot(Vector2D rhs) {
+ return x * rhs.x + y * rhs.y;
+ }
+
+ public Vector2D rotate(float radians) {
+ double cos = Math.cos(radians);
+ double sin = Math.sin(radians);
+ set((float) (x * cos - y * sin), (float) (x * sin + y * cos));
+ return this;
+ }
+
+ public Vector2D set(Vector2D other) {
+ x = other.x;
+ y = other.y;
+ return this;
+ }
+
+ public Vector2D set(float x, float y) {
+ this.x = x;
+ this.y = y;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + x + ", " + y + ")";
+ }
+
+ public float distanceTo(Vector2D other) {
+ float dx = x - other.x;
+ float dy = y - other.y;
+ return (float) Math.sqrt(dx * dx + dy * dy);
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/WaitProcess.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/WaitProcess.java
new file mode 100644
index 000000000..fd60a82fb
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/WaitProcess.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared;
+
+/** A process which just waits for the specified amount of time. */
+public class WaitProcess extends Process {
+ private long elapsedMs;
+ private long durationMs;
+
+ public WaitProcess(long durationMs) {
+ this.durationMs = durationMs;
+ this.elapsedMs = 0;
+ }
+
+ @Override
+ public void updateLogic(float deltaMs) {
+ elapsedMs += deltaMs;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return elapsedMs >= durationMs;
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/Actor.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/Actor.java
new file mode 100644
index 000000000..7778fa703
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/Actor.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.actor;
+
+import android.graphics.Canvas;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** Base class for different characters on the screen. */
+public abstract class Actor implements Comparable {
+ public static final float INFINITE_MASS = 0.0f;
+ public static final String TYPE = "Actor";
+ public static final String TYPE_KEY = "type";
+ public static final String X_KEY = "x";
+ public static final String Y_KEY = "y";
+
+ public Vector2D positionBeforeFrame;
+ // Assumes (0, 0) is upper-left corner of screen, with +y down and +x right.
+ public Vector2D position;
+ public Vector2D velocity;
+
+ // Doesn't do anything yet (except for TextActors)
+ public float scale = 1.0f;
+
+ // The rotation of the actor in radians. Positive means clockwise, negative means anticlockwise.
+ public float rotation = 0.0f;
+
+ // Doesn't do anything yet (except for in tennis)
+ public boolean hidden = false;
+
+ // Specify z-index so that actors can be sorted before drawing. Higher is in front, lower in
+ // back.
+ public int zIndex = 0;
+
+ // 0: transparent, 1: opaque.
+ public float alpha = 1;
+
+ // Bounciness.
+ public float restitution = 1.0f;
+ public float inverseMass = INFINITE_MASS;
+
+ public Actor() {
+ this(Vector2D.get(0, 0), Vector2D.get(0, 0));
+ }
+
+ public Actor(Vector2D position, Vector2D velocity) {
+ this.position = position;
+ this.positionBeforeFrame = Vector2D.get(position);
+ this.velocity = velocity;
+ }
+
+ public void update(float deltaMs) {
+ positionBeforeFrame.set(this.position);
+ float deltaSeconds = deltaMs / 1000.0f;
+ this.position.x += velocity.x * deltaSeconds;
+ this.position.y += velocity.y * deltaSeconds;
+ }
+
+ public void draw(Canvas canvas) {
+ // Nothing to do for base class implementation.
+ }
+
+ @Override
+ public int compareTo(Actor another) {
+ int zDiff = zIndex - another.zIndex;
+ if (zDiff != 0) {
+ return zDiff;
+ } else {
+ // As a fallback, compare the y positions. Obstacles with smaller y values (i.e., higher
+ // on
+ // the screen) should come first.
+ float positionDiff = position.y - another.position.y;
+ if (positionDiff > 0) {
+ return 1;
+ } else if (positionDiff < 0) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ public JSONObject toJSON() throws JSONException {
+ return null;
+ }
+
+ public String getType() {
+ return TYPE;
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/ActorHelper.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/ActorHelper.java
new file mode 100644
index 000000000..843cbd19a
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/ActorHelper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.actor;
+
+/** A collection of helper functions for Actor. */
+public class ActorHelper {
+ public static float distanceBetween(Actor a, Actor b) {
+ return distanceBetween(a.position.x, a.position.y, b.position.x, b.position.y);
+ }
+
+ public static float distanceBetween(float x1, float y1, float x2, float y2) {
+ float dx = x1 - x2;
+ float dy = y1 - y2;
+ return (float) Math.sqrt(dx * dx + dy * dy);
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/Camera.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/Camera.java
new file mode 100644
index 000000000..022e26f8b
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/Camera.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.actor;
+
+import static com.google.android.apps.santatracker.doodles.shared.animation.Interpolator.EASE_IN_AND_OUT;
+
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import com.google.android.apps.santatracker.doodles.shared.animation.Tween;
+
+/**
+ * A camera class to contain the scale, translation, and rotation of the world. Note that the camera
+ * is defined to be positioned at the top-left corner of the screen.
+ */
+public class Camera extends Actor {
+
+ public int screenWidth;
+ public int screenHeight;
+
+ public Camera(int screenWidth, int screenHeight) {
+ this.position = Vector2D.get();
+ scale = 1.0f;
+
+ this.screenWidth = screenWidth;
+ this.screenHeight = screenHeight;
+ }
+
+ /**
+ * Get the world coordinates for the screen coordinates specified.
+ *
+ * @param x The x value in screen space.
+ * @param y The y value in screen space.
+ */
+ public Vector2D getWorldCoords(float x, float y) {
+ return Vector2D.get(xToWorld(x), yToWorld(y));
+ }
+
+ public float xToWorld(float x) {
+ return position.x + x / scale;
+ }
+
+ public float yToWorld(float y) {
+ return position.y + y / scale;
+ }
+
+ /**
+ * Converts a length from screen scale to world space.
+ *
+ * @param dimension: the length in screen space.
+ * @return the length in world space.
+ */
+ public float toWorldScale(float dimension) {
+ return dimension / scale;
+ }
+
+ /**
+ * Move the center of the camera's viewport to the specified position.
+ *
+ * @param position The position to center the camera on.
+ */
+ public void focusOn(Vector2D position) {
+ this.position.set(getPositionToFocusOn(position, scale));
+ }
+
+ /**
+ * Get the camera position needed to focus on the specified position at the specified scale.
+ *
+ * @param position The position to center on.
+ * @param scale The scale at which to focus.
+ */
+ private Vector2D getPositionToFocusOn(Vector2D position, float scale) {
+ return Vector2D.get(position)
+ .subtract((screenWidth / 2) / scale, (screenHeight / 2) / scale);
+ }
+
+ /**
+ * Move the camer immediately so that it can see the bounding box specified by the min and max
+ * position vectors.
+ *
+ * @param levelMinPosition The desired minimum visible portion of the level.
+ * @param levelMaxPosition The desired maximum visible portion of the level.
+ */
+ public void moveImmediatelyTo(Vector2D levelMinPosition, Vector2D levelMaxPosition) {
+ Vector2D levelDimens = Vector2D.get(levelMaxPosition).subtract(levelMinPosition);
+
+ float pannedScale = Math.min(screenWidth / levelDimens.x, screenHeight / levelDimens.y);
+ Vector2D screenDimensInWorldCoords =
+ Vector2D.get(screenWidth, screenHeight).scale(1 / pannedScale);
+
+ // pannedPosition = levelMinPosition - (screenDimensInWorldCoords - levelDimens) / 2
+ Vector2D pannedPosition =
+ Vector2D.get(levelMinPosition)
+ .subtract(
+ (screenDimensInWorldCoords.x - levelDimens.x) * 0.5f,
+ (screenDimensInWorldCoords.y - levelDimens.y) * 0.5f);
+
+ position.set(pannedPosition);
+ scale = pannedScale;
+
+ screenDimensInWorldCoords.release();
+ levelDimens.release();
+ pannedPosition.release();
+ }
+
+ /**
+ * Pan to the specified position over the specified duration.
+ *
+ * @param levelMinPosition The desired minimum visible portion of the level.
+ * @param levelMaxPosition The desired maximum visible portion of the level.
+ * @param duration How many seconds the pan should take.
+ * @return The tween to pan the camera.
+ */
+ public Tween panTo(
+ final Vector2D levelMinPosition, final Vector2D levelMaxPosition, float duration) {
+
+ final Vector2D startMin = Vector2D.get(position);
+ final Vector2D startMax = getMaxVisiblePosition();
+
+ Tween panTween =
+ new Tween(duration) {
+ @Override
+ protected void updateValues(float percentDone) {
+
+ float xMin =
+ EASE_IN_AND_OUT.getValue(
+ percentDone, startMin.x, levelMinPosition.x);
+ float xMax =
+ EASE_IN_AND_OUT.getValue(
+ percentDone, startMax.x, levelMaxPosition.x);
+ float yMin =
+ EASE_IN_AND_OUT.getValue(
+ percentDone, startMin.y, levelMinPosition.y);
+ float yMax =
+ EASE_IN_AND_OUT.getValue(
+ percentDone, startMax.y, levelMaxPosition.y);
+
+ Vector2D min = Vector2D.get(xMin, yMin);
+ Vector2D max = Vector2D.get(xMax, yMax);
+ moveImmediatelyTo(min, max);
+ min.release();
+ max.release();
+ }
+
+ @Override
+ protected void onFinish() {
+ startMin.release();
+ startMax.release();
+ }
+ };
+ return panTween;
+ }
+
+ private Vector2D getMaxVisiblePosition() {
+ return Vector2D.get(position.x + screenWidth / scale, position.y + screenHeight / scale);
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/CameraShake.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/CameraShake.java
new file mode 100644
index 000000000..791c61cf2
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/CameraShake.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.actor;
+
+/**
+ * Tracks a vibration which reduces over time, suitable for screen shake effects. (Use position as
+ * the camera's offset when rendering)
+ */
+public class CameraShake extends Actor {
+
+ private float frequency = 0;
+ private float magnitude = 0;
+ private float falloff = 0;
+ private float msTillNextShake = 0;
+
+ public void shake(float frequency, float magnitude, float falloff) {
+ this.frequency = frequency;
+ this.magnitude = magnitude;
+ this.falloff = falloff;
+ msTillNextShake = 1000 / frequency;
+ }
+
+ @Override
+ public void update(float deltaMs) {
+ if (magnitude == 0) {
+ return;
+ }
+
+ msTillNextShake -= deltaMs;
+ if (msTillNextShake < 0) {
+ msTillNextShake = 1000 / frequency;
+ magnitude *= falloff;
+ // Tiny amounts of shake take too long to fall off, and they look bad, so just quickly
+ // kill the shake once it falls below a low threshold.
+ if (this.magnitude < 2) {
+ this.magnitude = 0;
+ }
+ }
+ position.x = (float) ((Math.random() - 0.5) * magnitude);
+ position.y = (float) ((Math.random() - 0.5) * magnitude);
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/FakeButtonActor.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/FakeButtonActor.java
new file mode 100644
index 000000000..4e5c99f9b
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/FakeButtonActor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.actor;
+
+import android.graphics.Canvas;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite;
+
+/**
+ * An actor that looks like a button, but doesn't actually have any logic to detect or respond to
+ * clicks. We use this to provide a UI affordance to the user. Even though all our games allow you
+ * to click anywhere on the screen, having something that looks like a button helps the users to
+ * know how to play the game.
+ */
+public class FakeButtonActor extends Actor {
+
+ public final AnimatedSprite sprite; // Public so you can get to frameWidth/frameHeight.
+ private final int lastFrameIndex;
+
+ /**
+ * The sprite for the button should conform to the following: 1. The last frame of the animation
+ * will be the "idle" state of the button. 2. When the button is pressed, the animation will be
+ * played through, starting from frame 0 and ending back on the last frame. 3. The FPS of the
+ * sprite should be set to give the button press animation the desired duration.
+ */
+ public FakeButtonActor(AnimatedSprite sprite) {
+ super();
+ this.sprite = sprite;
+ sprite.setLoop(false);
+ lastFrameIndex = sprite.getNumFrames() - 1;
+ sprite.setFrameIndex(lastFrameIndex);
+ }
+
+ public void press() {
+ sprite.setFrameIndex(0);
+ }
+
+ public void pressAndHold() {
+ sprite.setFrameIndex(0);
+ sprite.setPaused(true);
+ }
+
+ public void release() {
+ sprite.setPaused(false);
+ }
+
+ @Override
+ public void update(float deltaMs) {
+ super.update(deltaMs);
+ sprite.update(deltaMs);
+ sprite.setPosition(position.x, position.y);
+ sprite.setRotation(rotation);
+ sprite.setHidden(hidden);
+ sprite.setAlpha(alpha);
+ sprite.setScale(scale, scale);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ sprite.draw(canvas);
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/MultiSpriteActor.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/MultiSpriteActor.java
new file mode 100644
index 000000000..a97cfde45
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/MultiSpriteActor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.actor;
+
+import android.content.res.Resources;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite;
+import com.google.android.apps.santatracker.util.SantaLog;
+import java.util.HashMap;
+import java.util.Map;
+
+/** An actor which has multiple sprites which it can switch between. */
+public class MultiSpriteActor extends SpriteActor {
+ private static final String TAG = MultiSpriteActor.class.getSimpleName();
+ public Map sprites;
+
+ public MultiSpriteActor(
+ Map sprites,
+ String selectedSpriteKey,
+ Vector2D position,
+ Vector2D velocity) {
+ super(sprites.get(selectedSpriteKey), position, velocity);
+ this.sprites = sprites;
+ }
+
+ public static MultiSpriteActor create(
+ Data[] data, String selectedSprite, Vector2D position, Resources resources) {
+ Map sprites = new HashMap<>();
+ for (int i = 0; i < data.length; i++) {
+ sprites.put(data[i].key, data[i].getSprite(resources));
+ }
+ return new MultiSpriteActor(sprites, selectedSprite, position, Vector2D.get());
+ }
+
+ public void setSprite(String key) {
+ if (sprites.containsKey(key)) {
+ sprite = sprites.get(key);
+ } else {
+ SantaLog.w(TAG, "Couldn't set sprite, unrecognized key: " + key);
+ }
+ }
+
+ /** A class which makes it easier to re-construct MultiSpriteActors. */
+ public static class Data {
+ public String key;
+ public int[] idList;
+ public int numFrames;
+
+ public Data(String key, int[] idList) {
+ this.key = key;
+ this.idList = idList;
+ this.numFrames = idList.length;
+ }
+
+ public AnimatedSprite getSprite(Resources resources) {
+ if (idList != null) {
+ return AnimatedSprite.fromFrames(resources, idList);
+ }
+ return null;
+ }
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/RectangularInstructionActor.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/RectangularInstructionActor.java
new file mode 100644
index 000000000..345c8eae0
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/RectangularInstructionActor.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.actor;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite.AnimatedSpriteListener;
+import com.google.android.apps.santatracker.doodles.shared.animation.Sprites;
+
+/** An actor that shows instructions for a game. */
+public class RectangularInstructionActor extends Actor {
+
+ private static final int RECTANGLE_CENTER_X = 257;
+ private static final int RECTANGLE_CENTER_Y = 343;
+
+ private static final int FRAME_BUBBLE_APPEARS = 2;
+
+ public AnimatedSprite rectangle;
+ public AnimatedSprite diagram;
+ private float diagramScale = 1;
+ private float diagramAlpha = 1;
+ private boolean animationIsReversed = false;
+
+ /** @param diagram Animated sprite showing instructions in a loop. */
+ public RectangularInstructionActor(Resources resources, AnimatedSprite diagram) {
+ this.rectangle = AnimatedSprite.fromFrames(resources, Sprites.tutoappear_new);
+ this.diagram = diagram;
+
+ // Off-center anchor point lets us match the rectangle's animation with a simple scale.
+ diagram.setAnchor(diagram.frameWidth / 2, diagram.frameHeight / 2);
+
+ rectangle.setLoop(false);
+ rectangle.addListener(
+ new AnimatedSpriteListener() {
+ @Override
+ public void onFrame(int index) {
+ // Scale (and fade) the diagram to match the rectangle.
+ int maxFrame = rectangle.getNumFrames() - 1;
+ index = animationIsReversed ? maxFrame - index : index;
+ float percent = maxFrame == 0 ? 1 : (float) index / maxFrame;
+ if (index < FRAME_BUBBLE_APPEARS) {
+ percent = 0;
+ }
+ diagramScale = percent;
+ diagramAlpha = percent;
+ }
+
+ @Override
+ public void onFinished() {
+ if (animationIsReversed) {
+ hidden = true;
+ }
+ }
+ });
+ }
+
+ public void show() {
+ if (animationIsReversed) {
+ reverseAnimation();
+ }
+ hidden = false;
+ rectangle.setFrameIndex(0);
+ diagramAlpha = 0;
+ diagramScale = 0;
+ update(0);
+ }
+
+ public void hide() {
+ if (!animationIsReversed) {
+ reverseAnimation();
+ }
+ rectangle.setFrameIndex(0);
+ update(0);
+ }
+
+ private void reverseAnimation() {
+ rectangle.reverseFrames();
+ animationIsReversed = !animationIsReversed;
+ }
+
+ public void setDiagram(AnimatedSprite diagram) {
+ diagram.setAnchor(diagram.frameWidth / 2, diagram.frameHeight / 2);
+ this.diagram = diagram;
+ }
+
+ public float getScaledWidth() {
+ return rectangle.frameWidth * scale;
+ }
+
+ public float getScaledHeight() {
+ return rectangle.frameHeight * scale;
+ }
+
+ @Override
+ public void update(float deltaMs) {
+ super.update(deltaMs);
+
+ rectangle.update(deltaMs);
+ rectangle.setPosition(position.x, position.y);
+ rectangle.setRotation(rotation);
+ rectangle.setHidden(hidden);
+ rectangle.setAlpha(alpha);
+ rectangle.setScale(scale, scale);
+
+ diagram.update(deltaMs);
+ // Center diagram in rectangle.
+ diagram.setPosition(
+ position.x + RECTANGLE_CENTER_X * scale, position.y + RECTANGLE_CENTER_Y * scale);
+ diagram.setRotation(rotation);
+ diagram.setHidden(hidden);
+ // Diagram has to take both this.alpha and diagramAlpha into account.
+ diagram.setAlpha(alpha * diagramAlpha);
+ // Same with scale.
+ diagram.setScale(scale * diagramScale * 1.3f, scale * diagramScale * 1.3f);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (!hidden) {
+ rectangle.draw(canvas);
+ diagram.draw(canvas);
+ }
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/SpriteActor.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/SpriteActor.java
new file mode 100644
index 000000000..82a234742
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/SpriteActor.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.actor;
+
+import android.graphics.Canvas;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite;
+
+/** A generic actor class for game objects. */
+public class SpriteActor extends Actor {
+ public static final String TYPE = "Sprite actor";
+ private final String type;
+ public AnimatedSprite sprite;
+ // If true, then this.scale will be ignored. This would allow you to call sprite.setScale(x, y)
+ // without the effect getting overwritten.
+ public boolean ignoreScale = false;
+
+ public SpriteActor(AnimatedSprite sprite, Vector2D position, Vector2D velocity) {
+ this(sprite, position, velocity, TYPE);
+ }
+
+ public SpriteActor(AnimatedSprite sprite, Vector2D position, Vector2D velocity, String type) {
+ super(position, velocity);
+ this.sprite = sprite;
+ this.type = type;
+ }
+
+ @Override
+ public void update(float deltaMs) {
+ super.update(deltaMs);
+ sprite.update(deltaMs);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ draw(canvas, 0, 0, sprite.frameWidth * scale, sprite.frameHeight * scale);
+ }
+
+ public void draw(Canvas canvas, float xOffset, float yOffset, float width, float height) {
+ if (!hidden) {
+ sprite.setRotation(rotation);
+ sprite.setPosition(position.x + xOffset, position.y + yOffset);
+ if (!ignoreScale) {
+ sprite.setScale(width / sprite.frameWidth, height / sprite.frameHeight);
+ }
+ sprite.setAlpha(alpha);
+ sprite.draw(canvas);
+ }
+ }
+
+ @Override
+ public String getType() {
+ return type;
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/TextActor.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/TextActor.java
new file mode 100644
index 000000000..de9f42691
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/actor/TextActor.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.actor;
+
+import android.content.res.AssetManager;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.text.TextPaint;
+
+/** Draws text on the screen. */
+public class TextActor extends Actor {
+ private static final String TAG = TextActor.class.getSimpleName();
+ public boolean hidden;
+ private String text;
+ private Paint paint;
+ private Rect bounds = new Rect();
+ private float previousAlpha;
+ private boolean centeredVertically;
+
+ public TextActor(String text) {
+ this.paint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
+ // Set text size using px instead of dip so that the scale of text needed to match a bitmap
+ // sprite stays the same across devices.
+ paint.setTextSize(12);
+ setColor(Color.BLACK);
+ setText(text);
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ paint.getTextBounds(text, 0, text.length(), bounds);
+ }
+
+ /** @return The scaled width of the text. */
+ public float getWidth() {
+ return bounds.width() * scale;
+ }
+
+ /** @return The scaled height of the text. */
+ public float getHeight() {
+ return bounds.height() * scale;
+ }
+
+ public void scaleToFitScreen(int screenWidth, int screenHeight) {
+ scale = Math.min(screenWidth / bounds.width(), screenHeight / bounds.height());
+ }
+
+ public void setColor(int color) {
+ paint.setColor(color);
+ previousAlpha = paint.getAlpha() / 255f;
+ }
+
+ public void setFont(AssetManager assetManager, String fontPath) {
+ Typeface typeface = Typeface.createFromAsset(assetManager, fontPath);
+ paint.setTypeface(typeface);
+ }
+
+ public void enableBlur(float blurRadius) {
+ paint.setMaskFilter(new BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.NORMAL));
+ }
+
+ public void alignCenter() {
+ paint.setTextAlign(Align.CENTER);
+ }
+
+ public void setBold(boolean bold) {
+ paint.setTypeface(bold ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
+ }
+
+ public void centerVertically(boolean center) {
+ this.centeredVertically = center;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (previousAlpha != alpha) {
+ previousAlpha = alpha;
+ paint.setAlpha(Math.round(alpha * 255));
+ }
+ if (hidden) {
+ return;
+ }
+ float yOffset = centeredVertically ? -getHeight() / 2 : 0;
+ canvas.save();
+ canvas.scale(scale, scale);
+ // 1. drawText has y=0 at the baseline of the text. To make this work like other actors, y=0
+ // should be the top of the bounding box, so add bounds.top to y.
+ // 2. drawText also has rounding error on the (x, y) coordinates, which makes text jitter
+ // around if you are tweening scale, so use canvas.translate() to position instead.
+ canvas.translate(position.x / scale, (position.y + yOffset) / scale - bounds.top);
+ canvas.drawText(text, 0, 0, paint);
+ canvas.restore();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/ActorTween.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/ActorTween.java
new file mode 100644
index 000000000..e40027751
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/ActorTween.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.animation;
+
+import com.google.android.apps.santatracker.doodles.shared.actor.Actor;
+
+/**
+ * Tweens properties of Actors (currently, position & scale).
+ *
+ *
+ *
+ *
If any of the from-values is left unset, the from value will be initialized on the first
+ * update of the tween. This allows us to tween from an unspecified value, and have it do so
+ * smoothly when the tween actually runs, regardless of the actual starting value.
+ */
+public class ActorTween extends Tween {
+ private static final String TAG = ActorTween.class.getSimpleName();
+
+ private final Actor actor;
+ private Interpolator interpolator;
+ // Use Float objects set to null so that we can detect & support unspecified values.
+ private Float xStart = null;
+ private Float yStart = null;
+ private Float xEnd = null;
+ private Float yEnd = null;
+ private Float scaleStart;
+ private Float scaleEnd;
+ private Callback finishedCallback = null;
+ private Float rotationStart;
+ private Float rotationEnd;
+ private Float alphaStart;
+ private Float alphaEnd;
+
+ private boolean firstUpdate = true;
+
+ public ActorTween(Actor actor) {
+ super(0);
+ this.actor = actor;
+ interpolator = Interpolator.LINEAR;
+ }
+
+ public ActorTween from(float x, float y) {
+ fromX(x);
+ fromY(y);
+ return this;
+ }
+
+ public ActorTween fromX(float x) {
+ xStart = x;
+ return this;
+ }
+
+ public ActorTween fromY(float y) {
+ yStart = y;
+ return this;
+ }
+
+ public ActorTween to(float x, float y) {
+ toX(x);
+ toY(y);
+ return this;
+ }
+
+ public ActorTween toX(float x) {
+ xEnd = x;
+ return this;
+ }
+
+ public ActorTween toY(float y) {
+ yEnd = y;
+ return this;
+ }
+
+ public ActorTween scale(float fromScale, float toScale) {
+ this.scaleStart = fromScale;
+ this.scaleEnd = toScale;
+ return this;
+ }
+
+ public ActorTween scale(float toScale) {
+ this.scaleEnd = toScale;
+ return this;
+ }
+
+ public ActorTween withRotation(float fromRadians, float toRadians) {
+ this.rotationStart = fromRadians;
+ this.rotationEnd = toRadians;
+ return this;
+ }
+
+ public ActorTween withRotation(float toRadians) {
+ this.rotationEnd = toRadians;
+ return this;
+ }
+
+ public ActorTween withAlpha(float fromAlpha, float toAlpha) {
+ this.alphaStart = fromAlpha;
+ this.alphaEnd = toAlpha;
+ return this;
+ }
+
+ public ActorTween withAlpha(float toAlpha) {
+ this.alphaEnd = toAlpha;
+ return this;
+ }
+
+ public ActorTween withDuration(float seconds) {
+ this.durationSeconds = seconds;
+ return this;
+ }
+
+ public ActorTween withInterpolator(Interpolator interpolator) {
+ this.interpolator = interpolator;
+ return this;
+ }
+
+ public ActorTween whenFinished(Callback c) {
+ finishedCallback = c;
+ return this;
+ }
+
+ protected void setInitialValues() {
+ xStart = (xStart != null) ? xStart : actor.position.x;
+ yStart = (yStart != null) ? yStart : actor.position.y;
+ scaleStart = (scaleStart != null) ? scaleStart : actor.scale;
+ rotationStart = (rotationStart != null) ? rotationStart : actor.rotation;
+ alphaStart = (alphaStart != null) ? alphaStart : actor.alpha;
+ }
+
+ @Override
+ protected void updateValues(float percentDone) {
+ if (firstUpdate) {
+ firstUpdate = false;
+ setInitialValues();
+ }
+ // Perform null checks here so that we don't interpolate over unspecified fields.
+ if (xEnd != null) {
+ actor.position.x = interpolator.getValue(percentDone, xStart, xEnd);
+ }
+ if (yEnd != null) {
+ actor.position.y = interpolator.getValue(percentDone, yStart, yEnd);
+ }
+ if (scaleEnd != null) {
+ actor.scale = interpolator.getValue(percentDone, scaleStart, scaleEnd);
+ }
+ if (rotationEnd != null) {
+ actor.rotation = interpolator.getValue(percentDone, rotationStart, rotationEnd);
+ }
+ if (alphaEnd != null) {
+ actor.alpha = interpolator.getValue(percentDone, alphaStart, alphaEnd);
+ }
+ }
+
+ @Override
+ protected void onFinish() {
+ if (finishedCallback != null) {
+ finishedCallback.call();
+ }
+ }
+
+ /**
+ * Callback to be called at the end of a tween (can be used to chain tweens together, to hide an
+ * actor when a tween finishes, etc.)
+ */
+ public interface Callback {
+ void call();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/AnimatedSprite.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/AnimatedSprite.java
new file mode 100644
index 000000000..af8bfbfdf
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/AnimatedSprite.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.animation;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.Pair;
+import com.google.android.apps.santatracker.doodles.shared.BitmapCache;
+import com.google.android.apps.santatracker.doodles.shared.CallbackProcess;
+import com.google.android.apps.santatracker.doodles.shared.ProcessChain;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import com.google.android.apps.santatracker.doodles.shared.WaitProcess;
+import com.google.android.apps.santatracker.doodles.shared.physics.Util;
+import com.google.android.apps.santatracker.util.SantaLog;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An animated image. This also handles static, non-animated images: those are just animations with
+ * only 1 frame.
+ */
+public class AnimatedSprite {
+ private static final String TAG = AnimatedSprite.class.getSimpleName();
+ private static final int DEFAULT_FPS = 24;
+ private static final int NUM_TRIES_TO_LOAD_FROM_MEMORY = 3;
+
+ // When loading any sprite, this was the last successful sampleSize. We start loading the next
+ // Sprite with this sampleSize.
+ public static int lastUsedSampleSize = 1;
+
+ private static BitmapCache bitmapCache;
+ public int frameWidth;
+ public int frameHeight;
+ public Vector2D anchor = Vector2D.get();
+ private Bitmap[] frames;
+ private int fps = DEFAULT_FPS;
+ private int numFrames;
+ private boolean loop = true;
+ private boolean paused = false;
+ private boolean flippedX = false;
+ private List listeners;
+ private Vector2D position = Vector2D.get();
+ private boolean hidden;
+ private float scaleX = 1;
+ private float scaleY = 1;
+ private float rotation;
+ private Paint paint;
+ private int sampleSize = 1;
+
+ private float frameIndex;
+
+ // These are fields in order to avoid allocating memory in draw(). Not threadsafe, but why would
+ // draw get called from multiple threads?
+ private Rect srcRect = new Rect();
+ private RectF dstRect = new RectF();
+
+ /** Use fromFrames() to construct an AnimatedSprite. */
+ private AnimatedSprite(Bitmap[] frames, int sampleSize) {
+ this.frames = frames;
+ this.sampleSize = sampleSize;
+ if (lastUsedSampleSize < sampleSize) {
+ lastUsedSampleSize = sampleSize;
+ }
+ numFrames = this.frames.length;
+ if (numFrames == 0) {
+ throw new IllegalArgumentException("Can't have AnimatedSprite with zero frames.");
+ }
+ frameWidth = frames[0].getWidth() * sampleSize;
+ frameHeight = frames[0].getHeight() * sampleSize;
+ listeners = new ArrayList<>();
+ paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setFilterBitmap(true);
+ }
+
+ /** Return AnimatedSprite built from separate images (one image per frame). */
+ public static AnimatedSprite fromFrames(Resources resources, int[] ids) {
+ int sampleSize = lastUsedSampleSize;
+ Bitmap frames[] = new Bitmap[ids.length];
+ for (int i = 0; i < ids.length; i++) {
+ Pair pair = getBitmapFromCache(ids[i], 0);
+ if (pair != null) {
+ frames[i] = pair.first;
+ sampleSize = pair.second;
+ }
+ if (frames[i] == null) {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ for (int tries = 0; tries < NUM_TRIES_TO_LOAD_FROM_MEMORY; tries++) {
+ try {
+ // Decode bitmap with inSampleSize set
+ options.inSampleSize = sampleSize;
+ frames[i] = BitmapFactory.decodeResource(resources, ids[i], options);
+ } catch (OutOfMemoryError oom) {
+ sampleSize *= 2;
+ SantaLog.d(TAG, "loading failed, trying sampleSize: " + sampleSize, oom);
+ }
+ }
+ putBitmapInCache(frames[i], ids[i], 0, sampleSize);
+ }
+ }
+ return new AnimatedSprite(frames, sampleSize);
+ }
+
+ /** Return AnimatedSprite built from the given Bitmap objects. (For testing). */
+ public static AnimatedSprite fromBitmapsForTest(Bitmap frames[]) {
+ return new AnimatedSprite(frames, 1);
+ }
+
+ /**
+ * Return AnimatedSprite built from the same frames as another animated sprite. This isn't a
+ * deep clone, only the frames & FPS of the original sprite are copied.
+ */
+ public static AnimatedSprite fromAnimatedSprite(AnimatedSprite other) {
+ AnimatedSprite sprite = new AnimatedSprite(other.frames, other.sampleSize);
+ sprite.setFPS(other.fps);
+ return sprite;
+ }
+
+ private static Pair getBitmapFromCache(int id, int frame) {
+ if (bitmapCache == null) {
+ bitmapCache = new BitmapCache();
+ }
+ return bitmapCache.getBitmapFromCache(id, frame);
+ }
+
+ private static void putBitmapInCache(Bitmap bitmap, int id, int frame, int sampleSize) {
+ if (bitmapCache == null) {
+ bitmapCache = new BitmapCache();
+ }
+ bitmapCache.putBitmapInCache(bitmap, id, frame, sampleSize);
+ }
+
+ public static void clearCache() {
+ if (AnimatedSprite.bitmapCache != null) {
+ AnimatedSprite.bitmapCache.clear();
+ }
+ }
+
+ /** Set whether to loop the animation or not. */
+ public void setLoop(boolean loop) {
+ this.loop = loop;
+ }
+
+ public boolean isFlippedX() {
+ return flippedX;
+ }
+
+ public void setFlippedX(boolean value) {
+ this.flippedX = value;
+ }
+
+ public void setPaused(boolean value) {
+ this.paused = value;
+ }
+
+ /**
+ * Pause this sprite and return a process chain which can be updated to unpause the sprite after
+ * the specified length of time.
+ *
+ * @param durationMs how many milliseconds to pause the sprite for.
+ * @return a process chain which will unpause the sprite after the duration has completed.
+ */
+ public ProcessChain pauseFor(long durationMs) {
+ setPaused(true);
+ CallbackProcess unpause =
+ new CallbackProcess() {
+ @Override
+ public void updateLogic(float deltaMs) {
+ setPaused(false);
+ }
+ };
+ return new WaitProcess(durationMs).then(unpause);
+ }
+
+ /** Change the speed of the animation. */
+ public void setFPS(int fps) {
+ this.fps = fps;
+ }
+
+ public int getFrameIndex() {
+ return (int) frameIndex;
+ }
+
+ /** Sets the current frame. */
+ public void setFrameIndex(int frame) {
+ frameIndex = frame;
+ }
+
+ public int getNumFrames() {
+ return numFrames;
+ }
+
+ public float getDurationSeconds() {
+ return numFrames / (float) fps;
+ }
+
+ /** Update the animation based on deltaMs having passed. */
+ public void update(float deltaMs) {
+ if (paused) {
+ return;
+ }
+
+ float deltaFrames = (deltaMs / 1000.0f) * fps;
+
+ // In order to make sure that we don't skip any frames when notifying listeners, this
+ // carefully
+ // accumulates deltaFrames instead of just immediately adding it into frameIndex. Be careful
+ // of floating point precision issues below.
+ while (deltaFrames > 0) {
+ // First, try accumulating the remaining deltaFrames and see if we make it to the next
+ // frame.
+ float newFrameIndex = frameIndex + deltaFrames;
+ if ((int) newFrameIndex == (int) frameIndex) {
+ // Didn't make it to the next frame. Done accumulating.
+ frameIndex = newFrameIndex;
+ deltaFrames = 0;
+ } else {
+ // Move forward to next frame, notify listeners, then keep accumlating.
+ float oldFrameIndex = frameIndex;
+ frameIndex = 1 + (int) frameIndex; // ignores numFrames, will handle it below.
+ deltaFrames -= frameIndex - oldFrameIndex;
+
+ if (frameIndex < numFrames) {
+ sendOnFrameNotification((int) frameIndex);
+ } else {
+ if (loop) {
+ frameIndex = 0;
+ sendOnLoopNotification();
+ sendOnFrameNotification((int) frameIndex);
+ } else {
+ frameIndex = numFrames - 1;
+ sendOnFinishNotification();
+ // In this branch, there are no further onFrame notifications.
+ deltaFrames = 0; // No more changes to frameIndex, done accumulating.
+ }
+ }
+ }
+ }
+ }
+
+ void sendOnLoopNotification() {
+ for (int i = 0; i < listeners.size(); i++) { // Avoiding iterators to avoid garbage.
+ // Call the on-loop callbacks.
+ listeners.get(i).onLoop();
+ }
+ }
+
+ void sendOnFinishNotification() {
+ for (int i = 0; i < listeners.size(); i++) { // Avoiding iterators to avoid garbage
+ // Call the on-finished callbacks.
+ listeners.get(i).onFinished();
+ }
+ }
+
+ void sendOnFrameNotification(int frame) {
+ for (int i = 0; i < listeners.size(); i++) { // Avoiding iterators to avoid garbage.
+ listeners.get(i).onFrame(frame);
+ }
+ }
+
+ public void draw(Canvas canvas) {
+ if (!hidden) {
+ // Integer cast should round down, but clamp it just in case the synchronization with
+ // the
+ // update thread isn't perfect.
+ int frameIndexFloor = Util.clamp((int) frameIndex, 0, numFrames - 1);
+ float scaleX = flippedX ? -this.scaleX : this.scaleX;
+
+ canvas.save();
+ srcRect.set(0, 0, frameWidth, frameHeight);
+ dstRect.set(-anchor.x, -anchor.y, -anchor.x + frameWidth, -anchor.y + frameHeight);
+
+ canvas.translate(position.x, position.y);
+ canvas.scale(scaleX, scaleY, 0, 0);
+ canvas.rotate((float) Math.toDegrees(rotation), 0, 0);
+
+ canvas.drawBitmap(frames[frameIndexFloor], srcRect, dstRect, paint);
+ canvas.restore();
+ }
+ }
+
+ // Unlike Actors, AnimatedSprites use setters instead of public fields for position, scale, etc.
+ // This matches how it works on iOS, which uses setters because the actual values must be passed
+ // down into SKNodes.
+ public void setPosition(float x, float y) {
+ position.x = x;
+ position.y = y;
+ }
+
+ /** @param alpha: 0.0 = transparent, 1.0 = opaque. */
+ public void setAlpha(float alpha) {
+ paint.setAlpha((int) (alpha * 255));
+ }
+
+ // You can use this to more closely match the logic of iOS, where there is no draw method so the
+ // only way to hide something is to set this flag. On Android, you can also just not call draw
+ // if you want a sprite hidden.
+ public void setHidden(boolean hidden) {
+ this.hidden = hidden;
+ }
+
+ public void setScale(float scaleX, float scaleY) {
+ this.scaleX = scaleX;
+ this.scaleY = scaleY;
+ }
+
+ public float getScaledWidth() {
+ return scaleX * frameWidth;
+ }
+
+ public float getScaledHeight() {
+ return scaleY * frameHeight;
+ }
+
+ public void setRotation(float rotation) {
+ this.rotation = rotation;
+ }
+
+ // Sets the anchor point which determines where the sprite is drawn relative to its position.
+ // This
+ // is also the point around which sprite rotates & scales. (x, y) are in pixels, relative to
+ // top-left corner. Initially set to the upper-left corner.
+ public void setAnchor(float x, float y) {
+ anchor.x = x;
+ anchor.y = y;
+ }
+
+ public void addListener(AnimatedSpriteListener listener) {
+ listeners.add(listener);
+ }
+
+ public void clearListeners() {
+ listeners.clear();
+ }
+
+ public int getNumListeners() {
+ return listeners.size();
+ }
+
+ // Reverse the frames of the animation. This doesn't update frameIndex, which may or may not
+ // be what you want.
+ public void reverseFrames() {
+ for (int i = 0; i < frames.length / 2; i++) {
+ Bitmap temp = frames[i];
+ frames[i] = frames[frames.length - i - 1];
+ frames[frames.length - i - 1] = temp;
+ }
+ }
+
+ /** A class which can be implemented to provide callbacks for AnimatedSprite events. */
+ public static class AnimatedSpriteListener {
+ public void onFinished() {}
+
+ public void onLoop() {}
+
+ public void onFrame(int index) {}
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/ElasticOutInterpolator.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/ElasticOutInterpolator.java
new file mode 100644
index 000000000..6a0adde61
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/ElasticOutInterpolator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.animation;
+
+import android.view.animation.Interpolator;
+
+/**
+ * An interpolator which has a rubber bound effect around its end point, bouncing back and forth
+ * before settling at its final value.
+ *
+ * Implementation copied from outElastic here: https://goo.gl/SJZllG
+ */
+public class ElasticOutInterpolator implements Interpolator {
+
+ private final float period;
+
+ public ElasticOutInterpolator() {
+ period = 0.4f;
+ }
+
+ public ElasticOutInterpolator(float period) {
+ this.period = period;
+ }
+
+ @Override
+ public float getInterpolation(float value) {
+ return (float)
+ (Math.pow(2, -10 * value) * Math.sin((value - period / 4) * (2 * Math.PI) / period)
+ + 1);
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/EmptyTween.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/EmptyTween.java
new file mode 100644
index 000000000..88508ec12
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/EmptyTween.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.animation;
+
+/**
+ * A tween that does nothing in its updateValues method. Can be used to insert pauses in chains of
+ * tweens. The useful part of this is that it calls finishedCallback after durationSeconds, and it
+ * fits into the existing Tween framework.
+ */
+public class EmptyTween extends Tween {
+
+ public EmptyTween(float durationSeconds) {
+ super(durationSeconds);
+ }
+
+ @Override
+ protected void updateValues(float percentDone) {
+ // Nothing to do, just waiting for the tween to end.
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/Interpolator.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/Interpolator.java
new file mode 100644
index 000000000..4ba175055
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/Interpolator.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.animation;
+
+/** Interpolates values for Tweens. Used to implement easing and repeated movement. */
+public interface Interpolator {
+
+ Interpolator LINEAR =
+ new Interpolator() {
+ @Override
+ public float getValue(float percent, float initialValue, float finalValue) {
+ return initialValue + (finalValue - initialValue) * percent;
+ }
+ };
+ Interpolator EASE_IN =
+ new Interpolator() {
+ @Override
+ public float getValue(float percent, float initialValue, float finalValue) {
+ return initialValue + (finalValue - initialValue) * percent * percent;
+ }
+ };
+ Interpolator EASE_OUT =
+ new Interpolator() {
+ @Override
+ public float getValue(float percent, float initialValue, float finalValue) {
+ return initialValue - ((finalValue - initialValue) * percent * (percent - 2));
+ }
+ };
+ Interpolator EASE_IN_AND_OUT =
+ new Interpolator() {
+ @Override
+ public float getValue(float percent, float initialValue, float finalValue) {
+ // Simple sigmoid function: y = 3 * x^2 - 2 * x^3
+ return LINEAR.getValue(
+ 3 * percent * percent - 2 * percent * percent * percent,
+ initialValue,
+ finalValue);
+ }
+ };
+ Interpolator FAST_IN =
+ new Interpolator() {
+ @Override
+ public float getValue(float percent, float initialValue, float finalValue) {
+ return initialValue + (finalValue - initialValue) * (-percent * (percent - 2));
+ }
+ };
+ Interpolator FAST_IN_AND_HOLD =
+ new Interpolator() {
+ @Override
+ public float getValue(float percent, float initialValue, float finalValue) {
+ percent *= 2;
+ if (percent > 1) {
+ percent = 1;
+ }
+ return initialValue + (finalValue - initialValue) * (-percent * (percent - 2));
+ }
+ };
+
+ Interpolator OVERSHOOT =
+ new Interpolator() {
+ @Override
+ public float getValue(float percent, float initialValue, float finalValue) {
+ percent -= 1;
+ percent = percent * percent * (3 * percent + 2) + 1;
+ return initialValue + (finalValue - initialValue) * percent;
+ }
+ };
+
+ float getValue(float percent, float initialValue, float finalValue);
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/Sprites.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/Sprites.java
new file mode 100644
index 000000000..2d50614ab
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/Sprites.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.animation;
+
+import com.google.android.apps.santatracker.doodles.R;
+
+/** Frame data for all the sprites. */
+public interface Sprites {
+ int[] debug_marker = {
+ R.drawable.debug_marker,
+ };
+
+ int[] tutoappear_new = {
+ R.drawable.tutoappear_new_00,
+ R.drawable.tutoappear_new_01,
+ R.drawable.tutoappear_new_02,
+ R.drawable.tutoappear_new_03,
+ R.drawable.tutoappear_new_04,
+ R.drawable.tutoappear_new_05,
+ };
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/Tween.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/Tween.java
new file mode 100644
index 000000000..747fbc084
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/Tween.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.animation;
+
+import com.google.android.apps.santatracker.doodles.shared.Process;
+
+/**
+ * Base class for tweens. Handles the basic bookkeeping, then delegates to subclasses via
+ * updateValues() for updating specific values.
+ */
+public abstract class Tween {
+ protected float durationSeconds = 0;
+ private float elapsedSeconds = 0;
+ private float percentDone = 0;
+
+ public Tween(float durationSeconds) {
+ this.durationSeconds = durationSeconds;
+ }
+
+ /**
+ * @return true if update should continue to be called, false if tween is finished and should be
+ * removed.
+ */
+ public boolean update(double deltaMs) {
+ boolean wasFinished = isFinished();
+ if (wasFinished) {
+ return false;
+ }
+ elapsedSeconds += deltaMs / 1000f;
+ percentDone = elapsedSeconds / durationSeconds;
+ if (percentDone > 1) {
+ percentDone = 1;
+ }
+ updateValues(percentDone);
+ if (!wasFinished && isFinished()) {
+ onFinish();
+ }
+ return !isFinished();
+ }
+
+ /**
+ * Subclasses should define this method to update their value(s) every frame. Suggested
+ * implementation: currentValue = interpolator.getValue(percentDone, startValue, endValue);
+ */
+ protected abstract void updateValues(float percentDone);
+
+ /** Subclasses can override this to execute code when the Tween finishes. */
+ protected void onFinish() {}
+
+ // Cancels the tween. Doesn't reset values back to their starting value or the final value. Just
+ // leaves state where it is and stops updating. onFinish will be called.
+ public void cancel() {
+ if (!isFinished()) {
+ // Advance elapsedSeconds and percentDone to the end so future update calls won't do
+ // anything.
+ elapsedSeconds = durationSeconds;
+ percentDone = 1;
+ onFinish();
+ }
+ }
+
+ public boolean isFinished() {
+ return percentDone >= 1;
+ }
+
+ public float durationSeconds() {
+ return durationSeconds;
+ }
+
+ public Process asProcess() {
+ return new Process() {
+ @Override
+ public void updateLogic(float deltaMs) {
+ Tween.this.update(deltaMs);
+ }
+
+ @Override
+ public boolean isFinished() {
+ return Tween.this.isFinished();
+ }
+ };
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/TweenManager.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/TweenManager.java
new file mode 100644
index 000000000..fdee59f4e
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/animation/TweenManager.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.animation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages a list of tweens, taking care of removing them when they are done, and adding them (even
+ * in the middle of iterating over the list of tweens).
+ */
+public class TweenManager {
+ private final List tweens = new ArrayList<>();
+ private final List incomingTweens = new ArrayList<>();
+ private boolean shouldRemoveAll = false;
+
+ public void update(float deltaMs) {
+ // First, check whether removeAll was called since the last update.
+ if (shouldRemoveAll) {
+ finishRemovingAll();
+ return;
+ }
+ try {
+ // Move everything from incomingTweens to tweens (before iterating over tweens)
+ for (int i = 0; i < incomingTweens.size(); i++) { // Avoiding iterator to avoid garbage.
+ tweens.add(incomingTweens.get(i));
+ }
+ incomingTweens.clear();
+
+ // Now iterate through tweens.
+ for (int i = tweens.size() - 1; i >= 0; i--) { // Avoiding iterator to avoid garbage.
+ Tween tween = tweens.get(i);
+ boolean finished = tween == null || !tween.update(deltaMs);
+ if (shouldRemoveAll) {
+ finishRemovingAll();
+ return;
+ }
+ if (finished) {
+ tweens.remove(i);
+ }
+ }
+ } catch (Exception e) { // do nothing
+ }
+ }
+
+ public void add(Tween tween) {
+ if (tween.durationSeconds() < 0) {
+ throw new IllegalArgumentException("Tween duration should not be negative");
+ }
+ // Don't add to main list of tweens directly, to avoid ConcurrentModificationException.
+ incomingTweens.add(tween);
+ }
+
+ /**
+ * Removes all the tweens at the next possible opportunity. This isn't synchronous, but will
+ * happen before any more tween.update calls occur.
+ */
+ public void removeAll() {
+ // Remove incoming tweens immediately. No risk of removing while iterating for these, and we
+ // shouldn't clear this later in finishRemovingAll in case more have been added since then,
+ // so clear immediately.
+ incomingTweens.clear();
+
+ // There is a risk of removing while iterating for tweens though, so set a flag instead of
+ // immediately clearing it.
+ shouldRemoveAll = true;
+ }
+
+ private void finishRemovingAll() {
+ // Don't clear incomingTweens here, on the assumption that A) removeAll already cleared it
+ // and B) there's a possibility more have been added since then, and they *shouldn't* get
+ // cleared.
+ tweens.clear();
+ shouldRemoveAll = false;
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleDebugLogger.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleDebugLogger.java
new file mode 100644
index 000000000..7efcc0f17
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleDebugLogger.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.logging;
+
+import com.google.android.apps.santatracker.util.SantaLog;
+
+/**
+ * A stub logger for the purposes of testing output of log statements in the Pineapple 2016 games.
+ */
+public class DoodleDebugLogger extends DoodleLogger {
+ private static final String TAG = DoodleDebugLogger.class.getSimpleName();
+
+ @Override
+ public void logEvent(DoodleLogEvent event) {
+ SantaLog.d(TAG, event.toString());
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleLogEvent.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleLogEvent.java
new file mode 100644
index 000000000..e794103fb
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleLogEvent.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.logging;
+
+import androidx.annotation.Nullable;
+
+/** A class used for constructing log events for the Pineapple 2016 games. */
+public class DoodleLogEvent {
+ public static final String DEFAULT_DOODLE_NAME = "doodle_game";
+
+ public static final String MUTE_CLICKED = "clicked mute";
+ public static final String UNMUTE_CLICKED = "clicked unmute";
+ public static final String PAUSE_CLICKED = "clicked pause";
+ public static final String UNPAUSE_CLICKED = "clicked unpause";
+ public static final String REPLAY_CLICKED = "clicked replay";
+ public static final String SHARE_CLICKED = "clicked share";
+ public static final String HOME_CLICKED = "clicked home";
+ public static final String LOADING_COMPLETE = "loading complete";
+ public static final String DOODLE_LAUNCHED = "doodle launched";
+ public static final String GAME_OVER = "game over";
+
+ public static final String DISTINCT_GAMES_PLAYED = "distinct games";
+ public static final String RUNNING_GAME_TYPE = "running";
+ public static final String PRESENT_TOSS_GAME_TYPE = "tossing";
+ public static final String SWIMMING_GAME_TYPE = "swimming";
+
+ public final String doodleName;
+ public final String eventName;
+ @Nullable public final String eventSubType;
+ @Nullable public final Float eventValue1;
+ @Nullable public final Float eventValue2;
+ @Nullable public final Long latencyMs;
+
+ private DoodleLogEvent(
+ String doodleName,
+ String eventName,
+ @Nullable String eventSubType,
+ @Nullable Float eventValue1,
+ @Nullable Float eventValue2,
+ @Nullable Long latencyMs) {
+ this.doodleName = doodleName;
+ this.eventName = eventName;
+ this.eventSubType = eventSubType;
+ this.eventValue1 = eventValue1;
+ this.eventValue2 = eventValue2;
+ this.latencyMs = latencyMs;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("DoodleLogEvent(" + doodleName);
+ stringBuilder.append(", " + eventName);
+ stringBuilder.append(", " + eventSubType);
+ stringBuilder.append(", " + eventValue1);
+ stringBuilder.append(", " + eventValue2);
+ stringBuilder.append(", " + latencyMs + ")");
+ return stringBuilder.toString();
+ }
+
+ /** A helper class to build PineappleLogEvents. */
+ public static class Builder {
+ private String doodleName;
+ private String eventName;
+ @Nullable private String eventSubType;
+ @Nullable private Float eventValue1;
+ @Nullable private Float eventValue2;
+ @Nullable private Long latencyMs;
+
+ public Builder(String doodleName, String eventName) {
+ this.doodleName = doodleName;
+ this.eventName = eventName;
+ this.eventSubType = null;
+ this.eventValue1 = null;
+ this.eventValue2 = null;
+ this.latencyMs = null;
+ }
+
+ public Builder withEventSubType(String eventSubType) {
+ this.eventSubType = eventSubType;
+ return this;
+ }
+
+ public Builder withEventValue1(float eventValue1) {
+ this.eventValue1 = eventValue1;
+ return this;
+ }
+
+ public Builder withEventValue2(float eventValue2) {
+ this.eventValue2 = eventValue2;
+ return this;
+ }
+
+ public Builder withLatencyMs(long latencyMs) {
+ this.latencyMs = latencyMs;
+ return this;
+ }
+
+ public DoodleLogEvent build() {
+ return new DoodleLogEvent(
+ doodleName, eventName, eventSubType, eventValue1, eventValue2, latencyMs);
+ }
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleLogTimer.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleLogTimer.java
new file mode 100644
index 000000000..c6e78c03c
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleLogTimer.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.logging;
+
+import androidx.annotation.VisibleForTesting;
+
+/** A helper for timing log events in the Pineapple 2016 games. */
+public class DoodleLogTimer {
+ private static DoodleLogTimer instance;
+ private long startTimeMs;
+ private long pauseTimeMs;
+ private boolean isPaused;
+ private LogClock clock;
+
+ private DoodleLogTimer() {
+ this(new LogClock());
+ }
+
+ @VisibleForTesting
+ DoodleLogTimer(LogClock clock) {
+ this.clock = clock;
+ startTimeMs = clock.currentTimeMillis();
+ }
+
+ public static DoodleLogTimer getInstance() {
+ if (instance == null) {
+ instance = new DoodleLogTimer();
+ }
+ return instance;
+ }
+
+ public void reset() {
+ startTimeMs = clock.currentTimeMillis();
+ pauseTimeMs = clock.currentTimeMillis();
+ unpause();
+ }
+
+ public long timeElapsedMs() {
+ if (isPaused) {
+ return pauseTimeMs - startTimeMs;
+ }
+ return clock.currentTimeMillis() - startTimeMs;
+ }
+
+ public void pause() {
+ if (!isPaused) {
+ pauseTimeMs = clock.currentTimeMillis();
+ isPaused = true;
+ }
+ }
+
+ public void unpause() {
+ if (isPaused) {
+ long pauseDurationMs = clock.currentTimeMillis() - pauseTimeMs;
+ startTimeMs += pauseDurationMs;
+ isPaused = false;
+ }
+ }
+
+ /** Wrapper around System.currentTimeMillis so that we can test DoodleLogTimer. */
+ @VisibleForTesting
+ static class LogClock {
+ public long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleLogger.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleLogger.java
new file mode 100644
index 000000000..2de770a01
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleLogger.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.logging;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+
+/** Interface for logging DoodleEvents within the pineapple games. */
+public abstract class DoodleLogger {
+ private static final String PREFS_NAME = "PineappleLoggerPrefs";
+
+ public abstract void logEvent(DoodleLogEvent event);
+
+ public void logGameLaunchEvent(
+ final Context context, final String gameType, final String eventName) {
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... voids) {
+ SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS_NAME, 0);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+
+ int gamePlays = sharedPreferences.getInt(gameType, 0);
+ int distinctGamesPlayed =
+ sharedPreferences.getInt(DoodleLogEvent.DISTINCT_GAMES_PLAYED, 0);
+ if (gamePlays == 0) {
+ // If this is our first time playing this game, increment distinct games played.
+ editor.putInt(DoodleLogEvent.DISTINCT_GAMES_PLAYED, ++distinctGamesPlayed);
+ }
+ editor.putInt(gameType, ++gamePlays);
+ editor.commit();
+
+ logEvent(
+ new DoodleLogEvent.Builder(DoodleLogEvent.DEFAULT_DOODLE_NAME, eventName)
+ .withEventSubType(gameType)
+ .withEventValue1(gamePlays)
+ .withEventValue2(distinctGamesPlayed)
+ .build());
+
+ return null;
+ }
+ }.execute();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleNullLogger.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleNullLogger.java
new file mode 100644
index 000000000..ef6837e7a
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/logging/DoodleNullLogger.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.logging;
+
+/** A stub of a logger which does nothing. */
+public class DoodleNullLogger extends DoodleLogger {
+
+ @Override
+ public void logEvent(DoodleLogEvent event) {
+ // Do nothing.
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Polygon.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Polygon.java
new file mode 100644
index 000000000..12edc7c0e
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Polygon.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.physics;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import com.google.android.apps.santatracker.doodles.shared.Constants;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import java.util.ArrayList;
+import java.util.List;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * A general polygon class (either concave or convex) which can tell whether or not a point is
+ * inside of it.
+ *
+ *
+ *
+ *
NOTE: vertex winding order affects the normals of the line segments, and can affect things
+ * like collisions. A non-inverted (normals pointed out) polygon should have its vertices wound
+ * clockwise.
+ */
+public class Polygon {
+ private static final String TAG = Polygon.class.getSimpleName();
+ private static final float EPSILON = 0.0001f;
+ private static final float VERTEX_RADIUS = 10;
+ public List vertices;
+ public List normals;
+ public Vector2D min;
+ public Vector2D max;
+ private Paint vertexPaint;
+ private Paint midpointPaint;
+ private Paint linePaint;
+ private boolean isInverted;
+
+ public Polygon(List vertices) {
+ this.vertices = vertices;
+ min = Vector2D.get(0, 0);
+ max = Vector2D.get(0, 0);
+
+ vertexPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ vertexPaint.setColor(Color.RED);
+
+ midpointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ midpointPaint.setColor(Color.GREEN);
+ midpointPaint.setAlpha(100);
+
+ linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ linePaint.setColor(Color.WHITE);
+ linePaint.setStrokeWidth(5);
+
+ updateExtents();
+ updateInversionStatus();
+ calculateNormals();
+ }
+
+ public static Polygon fromJSON(JSONArray json) throws JSONException {
+ List vertices = new ArrayList<>();
+ for (int i = 0; i < json.length(); i++) {
+ JSONObject vertexJson = json.getJSONObject(i);
+ Vector2D vertex =
+ Vector2D.get(
+ (float) vertexJson.getDouble("x"), (float) vertexJson.getDouble("y"));
+ vertices.add(vertex);
+ }
+ return new Polygon(vertices);
+ }
+
+ public void updateExtents() {
+ min.set(this.vertices.get(0));
+ max.set(this.vertices.get(0));
+ for (int i = 0; i < vertices.size(); i++) {
+ Vector2D point = vertices.get(i);
+ min.x = Math.min(min.x, point.x);
+ min.y = Math.min(min.y, point.y);
+ max.x = Math.max(max.x, point.x);
+ max.y = Math.max(max.y, point.y);
+ }
+ }
+
+ public void calculateNormals() {
+ normals = new ArrayList<>();
+ for (int i = 0; i < vertices.size(); i++) {
+ Vector2D start = vertices.get(i);
+ Vector2D end = vertices.get((i + 1) % vertices.size());
+ normals.add(Vector2D.get(end).subtract(start).toNormal());
+ }
+ }
+
+ public float getWidth() {
+ return max.x - min.x;
+ }
+
+ public float getHeight() {
+ return max.y - min.y;
+ }
+
+ public void moveTo(float x, float y) {
+ float deltaX = x - min.x;
+ float deltaY = y - min.y;
+ move(deltaX, deltaY);
+ }
+
+ public void move(float x, float y) {
+ for (int i = 0; i < vertices.size(); i++) {
+ Vector2D vertex = vertices.get(i);
+ vertex.x += x;
+ vertex.y += y;
+ }
+ // Rather than update the extents by checking all of the vertices here, we can just update
+ // them
+ // manually (they will move by the same amount as the rest of the vertices).
+ min.x += x;
+ min.y += y;
+ max.x += x;
+ max.y += y;
+ }
+
+ public void moveVertex(int index, Vector2D delta) {
+ Vector2D vertex = vertices.get(index);
+ vertex.x += delta.x;
+ vertex.y += delta.y;
+ updateExtents();
+ updateInversionStatus();
+ }
+
+ public void addVertexAfter(int index) {
+ int nextIndex = index < vertices.size() - 1 ? index + 1 : 0;
+ Vector2D newVertex = Util.getMidpoint(vertices.get(index), vertices.get(nextIndex));
+ vertices.add(nextIndex, newVertex);
+ updateExtents();
+ calculateNormals();
+ }
+
+ public void removeVertexAt(int index) {
+ vertices.remove(index);
+ updateExtents();
+ }
+
+ /**
+ * Return the index of the vertex selected by the given point.
+ *
+ * @param point the point at which to check for a selected vertex.
+ * @param scale the scale of the world, for slackening the selection radius if needed.
+ * @return the index of the selected vertex, or -1 if no vertex was selected.
+ */
+ public int getSelectedIndex(Vector2D point, float scale) {
+ for (int i = 0; i < vertices.size(); i++) {
+ Vector2D vertex = vertices.get(i);
+ if (point.distanceTo(vertex)
+ < Math.max(Constants.SELECTION_RADIUS, Constants.SELECTION_RADIUS / scale)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int getMidpointIndex(Vector2D point, float scale) {
+ for (int i = 0; i < vertices.size(); i++) {
+ Vector2D start = vertices.get(i);
+ Vector2D end = vertices.get(i < vertices.size() - 1 ? i + 1 : 0);
+ if (point.distanceTo(Util.getMidpoint(start, end))
+ < Math.max(Constants.SELECTION_RADIUS, Constants.SELECTION_RADIUS / scale)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Return whether or not this polygon is inverted (i.e., whether or not the polygon has a normal
+ * which points inwards.
+ *
+ * @return true if the polygon is inverted, false otherwise.
+ */
+ public boolean isInverted() {
+ return isInverted;
+ }
+
+ /**
+ * Calculate whether or not this polygon is inverted. This checks to see if the point which is
+ * one unit in the normal direction on the polygon's first segment is within the bounds of the
+ * polygon. If this is the case, then the normal points inwards and the polygon is inverted.
+ * Otherwise, the polygon is not inverted.
+ *
+ *
+ *
+ *
Note: This doesn't deal with polygons which are partially inverted. These sorts of
+ * polygons should be avoided, as they will break this function.
+ */
+ private void updateInversionStatus() {
+ Vector2D start = vertices.get(0);
+ Vector2D end = vertices.get(1);
+ Vector2D midpoint = Util.getMidpoint(start, end);
+ Vector2D normal = Vector2D.get(end).subtract(start).toNormal().scale(0.1f);
+
+ if (contains(midpoint.add(normal))) {
+ isInverted = true;
+ } else {
+ isInverted = false;
+ }
+ normal.release();
+ midpoint.release();
+ }
+
+ /**
+ * Return whether or not this polygon's collision boundaries contain a given point. A polygon
+ * contains a point iff the point is contained within the polygon's collision boundaries,
+ * regardless of the direction of the polygon's normals.
+ *
+ * @param point the point to check
+ * @return true if this polygon contains the point, false otherwise.
+ */
+ public boolean contains(Vector2D point) {
+ // If the bounding box doesn't contain the point, we don't need to do any more calculations.
+ if (!Util.pointIsWithinBounds(min, max, point)) {
+ return false;
+ }
+
+ // Cast vertical ray from point to outside polygon and counting crossings. Point is in
+ // polygon
+ // iff number of edges crossed is odd.
+
+ // Find a Y value that's definitely outside the polygon.
+ float maxY = max.y + 1;
+ Vector2D outsidePoint = Vector2D.get(point.x, maxY);
+
+ // Check how many edges lie between (p.x, p.y) and (p.x, maxY).
+ boolean inside = false;
+ for (int i = 0; i < vertices.size(); i++) {
+ Vector2D p1 = vertices.get(i);
+ Vector2D p2;
+ if (i < vertices.size() - 1) {
+ p2 = vertices.get(i + 1);
+ } else {
+ p2 = vertices.get(0);
+ }
+
+ // First check endpoints. Hitting left-most point counts, hitting right-most
+ // doesn't (to weed out case where ray hits 2 lines at their joining vertex) }
+ if (p1.y >= point.y && Math.abs(p1.x - point.x) <= EPSILON) {
+ if (p2.x >= point.x) {
+ inside = !inside;
+ }
+ continue;
+ } else if (p2.y >= point.y && Math.abs(p2.x - point.x) <= EPSILON) {
+ if (p1.x >= point.x) {
+ inside = !inside;
+ }
+ continue;
+ }
+
+ // Now check for intersection.
+ if (Util.lineSegmentIntersectsLineSegment(p1, p2, point, outsidePoint)) {
+ inside = !inside;
+ }
+ }
+ outsidePoint.release();
+ return inside;
+ }
+
+ public LineSegment getIntersectingLineSegment(Vector2D p, Vector2D q) {
+ for (int i = 0; i < vertices.size(); i++) {
+ Vector2D p1 = vertices.get(i);
+ Vector2D p2;
+ if (i < vertices.size() - 1) {
+ p2 = vertices.get(i + 1);
+ } else {
+ p2 = vertices.get(0);
+ }
+
+ if (Util.lineSegmentIntersectsLineSegment(p1, p2, p, q)) {
+ return new LineSegment(p1, p2);
+ }
+ }
+ return null;
+ }
+
+ public void draw(Canvas canvas) {
+ for (int i = 0; i < vertices.size(); i++) {
+ Vector2D start = vertices.get(i);
+ Vector2D end;
+ if (i < vertices.size() - 1) {
+ end = vertices.get(i + 1);
+ } else {
+ end = vertices.get(0);
+ }
+ Vector2D midpoint = Util.getMidpoint(start, end);
+ Vector2D normal = Vector2D.get(end).subtract(start).toNormal();
+
+ canvas.drawCircle(start.x, start.y, VERTEX_RADIUS, vertexPaint);
+ canvas.drawLine(start.x, start.y, end.x, end.y, linePaint);
+ canvas.drawCircle(midpoint.x, midpoint.y, VERTEX_RADIUS / 2, midpointPaint);
+ canvas.drawLine(
+ midpoint.x,
+ midpoint.y,
+ midpoint.x + normal.x * 20,
+ midpoint.y + normal.y * 20,
+ linePaint);
+
+ midpoint.release();
+ normal.release();
+ }
+ }
+
+ public void setPaintColors(int vertexColor, int lineColor, int midpointColor) {
+ vertexPaint.setColor(vertexColor);
+ linePaint.setColor(lineColor);
+ midpointPaint.setColor(midpointColor);
+ }
+
+ public JSONArray toJSON() throws JSONException {
+ JSONArray json = new JSONArray();
+ for (int i = 0; i < vertices.size(); i++) {
+ JSONObject vertexJson = new JSONObject();
+ Vector2D vertex = vertices.get(i);
+ vertexJson.put("x", (double) vertex.x);
+ vertexJson.put("y", (double) vertex.y);
+ json.put(vertexJson);
+ }
+ return json;
+ }
+
+ /**
+ * A class to specify the starting and ending point of a line segment. Currently only used in
+ * determining which line segment is being collided with, so we can determine the normal vector.
+ */
+ public static class LineSegment {
+ public Vector2D start;
+ public Vector2D end;
+
+ public LineSegment(Vector2D start, Vector2D end) {
+ this.start = start;
+ this.end = end;
+ }
+
+ public Vector2D getDirection() {
+ return Vector2D.get(end).subtract(start);
+ }
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Util.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Util.java
new file mode 100644
index 000000000..45f3ed6a0
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Util.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.physics;
+
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import java.util.List;
+
+/** A utility class for physics-related functions. */
+public final class Util {
+ private static final String TAG = Util.class.getSimpleName();
+ private static final float EPSILON = 0.0001f;
+ private static final int COLLINEAR = 0;
+ private static final int CLOCKWISE = 1;
+ private static final int COUNTERCLOCKWISE = 2;
+
+ private Util() {
+ // Don't allow instantiation of this class.
+ }
+
+ /**
+ * Return whether the point is within the axis-aligned rectangle defined by p and q.
+ *
+ * @param p first bounding point.
+ * @param q second bounding point.
+ * @param point the point to check.
+ * @return true if point is within the bounds defined by p and q, false otherwise.
+ */
+ public static boolean pointIsWithinBounds(Vector2D p, Vector2D q, Vector2D point) {
+ return point.x >= Math.min(p.x, q.x)
+ && point.x <= Math.max(p.x, q.x)
+ && point.y >= Math.min(p.y, q.y)
+ && point.y <= Math.max(p.y, q.y);
+ }
+
+ /**
+ * Find the orientation of the ordered triplet of points. They are either clockwise,
+ * counterclockwise, or collinear. Implementation based on: http://goo.gl/a44iML
+ */
+ private static int orientation(Vector2D p, Vector2D q, Vector2D r) {
+ float value = (q.y - p.y) * (r.x - q.x) - (r.y - q.y) * (q.x - p.x);
+
+ // Use this instead of Math.abs(value) here because it is faster.
+ if (value < EPSILON && value > -EPSILON) {
+ return COLLINEAR;
+ }
+ return value > 0 ? CLOCKWISE : COUNTERCLOCKWISE;
+ }
+
+ /**
+ * Compute whether or not lines 1 and 2 intersect. Implementation based on:
+ * http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
+ *
+ * @param p1 The starting point of line 1
+ * @param q1 The ending point of line 1
+ * @param p2 The starting point of line 2
+ * @param q2 The ending point of line 2
+ * @return true if 1 and 2 intersect, false otherwise.
+ */
+ public static boolean lineSegmentIntersectsLineSegment(
+ Vector2D p1, Vector2D q1, Vector2D p2, Vector2D q2) {
+ int o1 = orientation(p1, q1, p2);
+ int o2 = orientation(p1, q1, q2);
+ int o3 = orientation(p2, q2, p1);
+ int o4 = orientation(p2, q2, q1);
+
+ // General case
+ if (o1 != o2 && o3 != o4) {
+ return true;
+ }
+
+ // Special cases
+ if (o1 == 0 && pointIsWithinBounds(p1, q1, p2)
+ || o2 == 0 && pointIsWithinBounds(p1, q1, q2)
+ || o3 == 0 && pointIsWithinBounds(p2, q2, p1)
+ || o4 == 0 && pointIsWithinBounds(p2, q2, q1)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return whether or not two rectangles intersect. This uses a basic form of the separating axis
+ * theorem which should be faster than running a full polygon-to-polygon check.
+ *
+ * @return true if the rectangles intersect, false otherwise.
+ */
+ public static boolean rectIntersectsRect(
+ float x1, float y1, float w1, float h1, float x2, float y2, float w2, float h2) {
+ float halfWidth1 = w1 / 2;
+ float halfWidth2 = w2 / 2;
+ float halfHeight1 = h1 / 2;
+ float halfHeight2 = h2 / 2;
+
+ float horizontalThreshold = halfWidth1 + halfWidth2;
+ float verticalThreshold = halfHeight1 + halfHeight2;
+
+ float horizontalDistance = Math.abs(x1 + halfWidth1 - (x2 + halfWidth2));
+ float verticalDistance = Math.abs(y1 + halfHeight1 - (y2 + halfHeight2));
+
+ return horizontalDistance < horizontalThreshold && verticalDistance < verticalThreshold;
+ }
+
+ /**
+ * Use the separating axis theorem to determine whether or not two convex polygons intersect.
+ *
+ * @return true if the polygons intersect, false otherwise.
+ */
+ public static boolean convexPolygonIntersectsConvexPolygon(Polygon p1, Polygon p2) {
+ for (int i = 0; i < p1.normals.size(); i++) {
+ Vector2D normal = p1.normals.get(i);
+ float p1Min = getMinProjectionInDirection(normal, p1.vertices);
+ float p1Max = getMaxProjectionInDirection(normal, p1.vertices);
+ float p2Min = getMinProjectionInDirection(normal, p2.vertices);
+ float p2Max = getMaxProjectionInDirection(normal, p2.vertices);
+ if (p1Max < p2Min || p2Max < p1Min) {
+ // If there is a separating axis, these polygons do not intersect.
+ return false;
+ }
+ }
+ for (int i = 0; i < p2.normals.size(); i++) {
+ Vector2D normal = p2.normals.get(i);
+ float p1Min = getMinProjectionInDirection(normal, p1.vertices);
+ float p1Max = getMaxProjectionInDirection(normal, p1.vertices);
+ float p2Min = getMinProjectionInDirection(normal, p2.vertices);
+ float p2Max = getMaxProjectionInDirection(normal, p2.vertices);
+ if (p1Max < p2Min || p2Max < p1Min) {
+ // If there is a separating axis, these polygons do not intersect.
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static float getMaxProjectionInDirection(Vector2D direction, List points) {
+ float max = points.get(0).dot(direction);
+ for (int i = 1; i < points.size(); i++) {
+ max = Math.max(max, points.get(i).dot(direction));
+ }
+ return max;
+ }
+
+ private static float getMinProjectionInDirection(Vector2D direction, List points) {
+ float min = points.get(0).dot(direction);
+ for (int i = 1; i < points.size(); i++) {
+ min = Math.min(min, points.get(i).dot(direction));
+ }
+ return min;
+ }
+
+ public static Vector2D getMidpoint(Vector2D p, Vector2D q) {
+ float deltaX = q.x - p.x;
+ float deltaY = q.y - p.y;
+ return Vector2D.get(p.x + deltaX / 2, p.y + deltaY / 2);
+ }
+
+ public static float clamp(float value, float lowerBound, float upperBound) {
+ return Math.max(lowerBound, Math.min(value, upperBound));
+ }
+
+ public static int clamp(int value, int lowerBound, int upperBound) {
+ return Math.max(lowerBound, Math.min(value, upperBound));
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/DoodleMediaPlayer.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/DoodleMediaPlayer.java
new file mode 100644
index 000000000..c032217e4
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/DoodleMediaPlayer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.sound;
+
+import android.media.MediaPlayer;
+
+/** A wrapper around MediaPlayer so that we can extend its behavior. */
+public interface DoodleMediaPlayer {
+ void start();
+
+ boolean isPlaying();
+
+ void pause();
+
+ MediaPlayer getMediaPlayer();
+
+ void setNextMediaPlayer(MediaPlayer mediaPlayer);
+
+ void setVolume(float volume);
+
+ void mute();
+
+ void unmute();
+
+ void release();
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/DoodleMediaPlayerImpl.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/DoodleMediaPlayerImpl.java
new file mode 100644
index 000000000..334f93807
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/DoodleMediaPlayerImpl.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.sound;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import com.google.android.apps.santatracker.util.SantaLog;
+
+/**
+ * A wrapper around a MediaPlayer so that we can define our own LoopingMediaPlayer and have it use a
+ * common interface.
+ */
+public class DoodleMediaPlayerImpl implements DoodleMediaPlayer {
+ private static final String TAG = DoodleMediaPlayerImpl.class.getSimpleName();
+
+ private MediaPlayer mediaPlayer;
+ private float volume;
+
+ private DoodleMediaPlayerImpl(Context context, int resId) {
+ mediaPlayer = MediaPlayer.create(context, resId);
+ }
+
+ public static DoodleMediaPlayerImpl create(Context context, int resId) {
+ return new DoodleMediaPlayerImpl(context, resId);
+ }
+
+ @Override
+ public void start() {
+ try {
+ mediaPlayer.start();
+ } catch (IllegalStateException e) {
+ SantaLog.w(TAG, "start() failed: " + e.toString());
+ }
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return mediaPlayer.isPlaying();
+ }
+
+ @Override
+ public void pause() {
+ try {
+ mediaPlayer.pause();
+ } catch (IllegalStateException e) {
+ SantaLog.w(TAG, "pause() failed: " + e.toString());
+ }
+ }
+
+ @Override
+ public MediaPlayer getMediaPlayer() {
+ return mediaPlayer;
+ }
+
+ @Override
+ public void setNextMediaPlayer(MediaPlayer next) {
+ try {
+ mediaPlayer.setNextMediaPlayer(next);
+ } catch (IllegalStateException e) {
+ SantaLog.w(TAG, "setNextMediaPlayer() failed: " + e.toString());
+ }
+ }
+
+ @Override
+ public void setVolume(float volume) {
+ this.volume = volume;
+ try {
+ mediaPlayer.setVolume(volume, volume);
+ } catch (IllegalStateException e) {
+ SantaLog.w(TAG, "setVolume() failed: " + e.toString());
+ }
+ }
+
+ @Override
+ public void mute() {
+ try {
+ mediaPlayer.setVolume(0, 0);
+ } catch (IllegalStateException e) {
+ SantaLog.w(TAG, "mute() failed: " + e.toString());
+ }
+ }
+
+ @Override
+ public void unmute() {
+ try {
+ mediaPlayer.setVolume(volume, volume);
+ } catch (IllegalStateException e) {
+ SantaLog.w(TAG, "unmute() failed: " + e.toString());
+ }
+ }
+
+ @Override
+ public void release() {
+ try {
+ mediaPlayer.release();
+ } catch (IllegalStateException e) {
+ SantaLog.w(TAG, "release() failed: " + e.toString());
+ }
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/LoopingMediaPlayer.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/LoopingMediaPlayer.java
new file mode 100644
index 000000000..79c995742
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/LoopingMediaPlayer.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.sound;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import com.google.android.apps.santatracker.util.SantaLog;
+
+/** A wrapper around a MediaPlayer which allows for a gapless, looping track. */
+public class LoopingMediaPlayer implements DoodleMediaPlayer {
+ private static final String TAG = LoopingMediaPlayer.class.getSimpleName();
+
+ private MediaPlayer currentPlayer;
+ private MediaPlayer nextPlayer;
+ private AssetFileDescriptor soundFileDescriptor;
+ private float volume;
+
+ private LoopingMediaPlayer(Context context, int resId) {
+ soundFileDescriptor = context.getResources().openRawResourceFd(resId);
+
+ currentPlayer = MediaPlayer.create(context, resId);
+ nextPlayer = MediaPlayer.create(context, resId);
+ currentPlayer.setNextMediaPlayer(nextPlayer);
+
+ currentPlayer.setOnCompletionListener(
+ new OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mediaPlayer) {
+ try {
+ currentPlayer.reset();
+ currentPlayer.setDataSource(
+ soundFileDescriptor.getFileDescriptor(),
+ soundFileDescriptor.getStartOffset(),
+ soundFileDescriptor.getLength());
+ currentPlayer.prepare();
+ nextPlayer.setNextMediaPlayer(currentPlayer);
+ } catch (Exception e) {
+ SantaLog.w(TAG, "onCompletion: unexpected exception", e);
+ }
+ }
+ });
+ nextPlayer.setOnCompletionListener(
+ new OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mediaPlayer) {
+ try {
+ nextPlayer.reset();
+ nextPlayer.setDataSource(
+ soundFileDescriptor.getFileDescriptor(),
+ soundFileDescriptor.getStartOffset(),
+ soundFileDescriptor.getLength());
+ nextPlayer.prepare();
+ currentPlayer.setNextMediaPlayer(nextPlayer);
+ } catch (Exception e) {
+ SantaLog.w(TAG, "onCompletion: unexpected exception", e);
+ }
+ }
+ });
+ }
+
+ public static LoopingMediaPlayer create(Context context, int resId) {
+ return new LoopingMediaPlayer(context, resId);
+ }
+
+ @Override
+ public void start() {
+ try {
+ currentPlayer.start();
+ } catch (IllegalStateException e) {
+ SantaLog.w(TAG, "start() failed: " + e.toString());
+ }
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return currentPlayer.isPlaying() || nextPlayer.isPlaying();
+ }
+
+ @Override
+ public void pause() {
+ try {
+ currentPlayer.pause();
+ nextPlayer.pause();
+ } catch (Exception e) {
+ SantaLog.w(TAG, "pause() failed: " + e.toString());
+ }
+ }
+
+ @Override
+ public MediaPlayer getMediaPlayer() {
+ return currentPlayer;
+ }
+
+ @Override
+ public void setNextMediaPlayer(MediaPlayer mediaPlayer) {
+ try {
+ currentPlayer.setNextMediaPlayer(mediaPlayer);
+ } catch (Exception e) {
+ SantaLog.w(TAG, "setNextMediaPlayer() failed: ", e);
+ }
+ }
+
+ @Override
+ public void setVolume(float volume) {
+ this.volume = volume;
+ try {
+ currentPlayer.setVolume(volume, volume);
+ nextPlayer.setVolume(volume, volume);
+ } catch (Exception e) {
+ SantaLog.w(TAG, "setVolume() failed: ", e);
+ }
+ }
+
+ @Override
+ public void mute() {
+ try {
+ currentPlayer.setVolume(0, 0);
+ nextPlayer.setVolume(0, 0);
+ } catch (Exception e) {
+ SantaLog.w(TAG, "mute() failed: ", e);
+ }
+ }
+
+ @Override
+ public void unmute() {
+ try {
+ currentPlayer.setVolume(volume, volume);
+ nextPlayer.setVolume(volume, volume);
+ } catch (Exception e) {
+ SantaLog.w(TAG, "unmute() failed: ", e);
+ }
+ }
+
+ @Override
+ public void release() {
+ try {
+ currentPlayer.release();
+ nextPlayer.release();
+ soundFileDescriptor.close();
+ } catch (Exception e) {
+ SantaLog.w(TAG, "release() failed: ", e);
+ }
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/SoundManager.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/SoundManager.java
new file mode 100644
index 000000000..0e31c1a53
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/SoundManager.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.shared.sound;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.os.AsyncTask;
+import com.google.android.apps.santatracker.AudioConstants;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.util.SantaLog;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** A manager for all of the different sounds which will be played in the games. */
+public class SoundManager {
+ private static final String TAG = SoundManager.class.getSimpleName();
+ public static boolean soundsAreMuted;
+ private static SoundManager instance;
+ private final Object mediaPlayerLock = new Object();
+ private final Object soundPoolLock = new Object();
+
+ // A map from resource ID to a media player which can play the sound clip.
+ private Map mediaPlayerMap;
+ // A map from resource ID to a sound pool containing the sound clip.
+ private Map soundPoolMap;
+ // A set of resource IDs for sounds which have been individually muted. These shouldn't be
+ // unmuted
+ // when the mute option is toggled.
+ private Set mutedSounds;
+
+ private SantaPreferences mSantaPreferences;
+
+ private SoundManager() {
+ mediaPlayerMap = new HashMap<>();
+ soundPoolMap = new HashMap<>();
+ mutedSounds = new HashSet<>();
+ }
+
+ public static SoundManager getInstance() {
+ if (instance == null) {
+ instance = new SoundManager();
+ }
+ return instance;
+ }
+
+ public void loadLongSound(Context context, int resId, boolean looping, float volume) {
+ if (mediaPlayerMap.containsKey(resId)) {
+ return;
+ }
+ DoodleMediaPlayer mediaPlayer;
+ if (looping) {
+ mediaPlayer = LoopingMediaPlayer.create(context, resId);
+ } else {
+ mediaPlayer = DoodleMediaPlayerImpl.create(context, resId);
+ }
+ mediaPlayer.setVolume(volume);
+ if (soundsAreMuted) {
+ mediaPlayer.mute();
+ }
+ synchronized (mediaPlayerLock) {
+ mediaPlayerMap.put(resId, mediaPlayer);
+ }
+ }
+
+ public void loadLongSound(Context context, int resId, boolean looping) {
+ // Make this quiet by default so that it doesn't overpower the in-game sounds.
+ loadLongSound(context, resId, looping, AudioConstants.DEFAULT_BACKGROUND_VOLUME);
+ }
+
+ public void loadShortSound(
+ final Context context, final int resId, final boolean looping, final float volume) {
+ if (soundPoolMap.containsKey(resId)) {
+ return;
+ }
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ SoundPoolContainer container =
+ new SoundPoolContainer(context, resId, looping, volume);
+ synchronized (soundPoolLock) {
+ soundPoolMap.put(resId, container);
+ }
+ return null;
+ }
+ }.execute();
+ }
+
+ public void loadShortSound(Context context, int resId) {
+ loadShortSound(context, resId, false, AudioConstants.DEFAULT_SOUND_EFFECT_VOLUME);
+ }
+
+ public void play(int resId) {
+ if (mediaPlayerMap.containsKey(resId)) {
+ mediaPlayerMap.get(resId).start();
+ } else if (soundPoolMap.containsKey(resId)) {
+ SoundPoolContainer container = soundPoolMap.get(resId);
+ float vol = soundsAreMuted ? 0 : container.volume;
+ container.streamId =
+ container.soundPool.play(
+ container.soundId, vol, vol, 1, container.looping ? -1 : 0, 1);
+ }
+ }
+
+ public void pauseAll() {
+ synchronized (mediaPlayerLock) {
+ for (int resId : mediaPlayerMap.keySet()) {
+ pause(resId);
+ }
+ }
+ synchronized (soundPoolLock) {
+ for (int resId : soundPoolMap.keySet()) {
+ pause(resId);
+ }
+ }
+ }
+
+ public void pause(int resId) {
+ if (mediaPlayerMap.containsKey(resId)) {
+ DoodleMediaPlayer mediaPlayer = mediaPlayerMap.get(resId);
+ if (mediaPlayer.isPlaying()) {
+ mediaPlayer.pause();
+ }
+ } else if (soundPoolMap.containsKey(resId)) {
+ soundPoolMap.get(resId).soundPool.autoPause();
+ }
+ }
+
+ public void pauseShortSounds() {
+ synchronized (soundPoolLock) {
+ for (SoundPoolContainer container : soundPoolMap.values()) {
+ container.soundPool.autoPause();
+ }
+ }
+ }
+
+ public void resumeShortSounds() {
+ synchronized (soundPoolLock) {
+ for (SoundPoolContainer container : soundPoolMap.values()) {
+ container.soundPool.autoResume();
+ }
+ }
+ }
+
+ public void playLongSoundsInSequence(int[] soundIds) {
+ for (int i = 0; i < soundIds.length; i++) {
+ if (isPlayingLongSound(soundIds[i])) {
+ // Don't try to play long sounds which are already playing.
+ return;
+ }
+ }
+
+ try {
+ DoodleMediaPlayer[] sounds = new DoodleMediaPlayer[soundIds.length];
+ for (int i = 0; i < soundIds.length; i++) {
+ sounds[i] = mediaPlayerMap.get(soundIds[i]);
+ if (sounds[i] == null) {
+ return;
+ }
+ if (i > 0) {
+ sounds[i - 1].setNextMediaPlayer(sounds[i].getMediaPlayer());
+ }
+ }
+ sounds[0].start();
+ } catch (IllegalStateException e) {
+ SantaLog.d(TAG, "playLongSoundsInSequence() failed: " + e.toString());
+ }
+ }
+
+ public boolean isPlayingLongSound(int resId) {
+ if (mediaPlayerMap.containsKey(resId)) {
+ return mediaPlayerMap.get(resId).isPlaying();
+ }
+ return false;
+ }
+
+ public void releaseAll() {
+ synchronized (mediaPlayerLock) {
+ for (DoodleMediaPlayer mediaPlayer : mediaPlayerMap.values()) {
+ mediaPlayer.release();
+ }
+ mediaPlayerMap.clear();
+ }
+ synchronized (soundPoolLock) {
+ for (SoundPoolContainer container : soundPoolMap.values()) {
+ container.soundPool.release();
+ }
+ soundPoolMap.clear();
+ }
+ }
+
+ public void release(int resId) {
+ if (mediaPlayerMap.containsKey(resId)) {
+ mediaPlayerMap.get(resId).release();
+ mediaPlayerMap.remove(resId);
+ }
+ if (soundPoolMap.containsKey(resId)) {
+ soundPoolMap.get(resId).soundPool.release();
+ soundPoolMap.remove(resId);
+ }
+ }
+
+ public void mute() {
+ synchronized (mediaPlayerLock) {
+ for (int resId : mediaPlayerMap.keySet()) {
+ muteInternal(resId, false);
+ }
+ }
+ synchronized (soundPoolLock) {
+ for (int resId : soundPoolMap.keySet()) {
+ muteInternal(resId, false);
+ }
+ }
+ soundsAreMuted = true;
+ }
+
+ public void mute(int resId) {
+ muteInternal(resId, true);
+ }
+
+ private void muteInternal(int resId, boolean addToMutedSounds) {
+ if (mediaPlayerMap.containsKey(resId)) {
+ mediaPlayerMap.get(resId).mute();
+ }
+ if (soundPoolMap.containsKey(resId)) {
+ SoundPoolContainer container = soundPoolMap.get(resId);
+ container.soundPool.setVolume(container.streamId, 0, 0);
+ }
+ if (addToMutedSounds) {
+ mutedSounds.add(resId);
+ }
+ }
+
+ public void unmute() {
+ soundsAreMuted = false;
+ synchronized (mediaPlayerLock) {
+ for (int resId : mediaPlayerMap.keySet()) {
+ if (!mutedSounds.contains(resId)) {
+ unmuteInternal(resId, false);
+ }
+ }
+ }
+ synchronized (soundPoolLock) {
+ for (int resId : soundPoolMap.keySet()) {
+ if (!mutedSounds.contains(resId)) {
+ unmuteInternal(resId, false);
+ }
+ }
+ }
+ }
+
+ public void unmute(int resId) {
+ unmuteInternal(resId, true);
+ }
+
+ private void unmuteInternal(int resId, boolean removeFromMutedSounds) {
+ if (soundsAreMuted) {
+ return;
+ }
+ if (mediaPlayerMap.containsKey(resId)) {
+ mediaPlayerMap.get(resId).unmute();
+ }
+ if (soundPoolMap.containsKey(resId)) {
+ SoundPoolContainer container = soundPoolMap.get(resId);
+ container.soundPool.setVolume(container.streamId, container.volume, container.volume);
+ }
+ if (removeFromMutedSounds) {
+ mutedSounds.remove(resId);
+ }
+ }
+
+ public void storeMutePreference(final Context context) {
+ if (mSantaPreferences == null) {
+ mSantaPreferences = new SantaPreferences(context.getApplicationContext());
+ }
+ mSantaPreferences.setMuted(soundsAreMuted);
+ }
+
+ public void loadMutePreference(final Context context) {
+ if (mSantaPreferences == null) {
+ mSantaPreferences = new SantaPreferences(context.getApplicationContext());
+ }
+ if (mSantaPreferences.isMuted()) {
+ mute();
+ } else {
+ unmute();
+ }
+ }
+
+ private static class SoundPoolContainer {
+ public final SoundPool soundPool;
+ public final int soundId;
+ public final boolean looping;
+ public final float volume;
+ public int streamId;
+
+ public SoundPoolContainer(Context context, int resId, boolean looping, float volume) {
+ soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
+ soundId = soundPool.load(context, resId, 1);
+ this.looping = looping;
+ this.volume = volume;
+ }
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/GameFragment.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/GameFragment.java
new file mode 100644
index 000000000..0d0c6297b
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/GameFragment.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.views;
+
+import static com.google.android.apps.santatracker.doodles.shared.EventBus.GAME_LOADED;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.DEFAULT_DOODLE_NAME;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.LOADING_COMPLETE;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Fragment;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import com.google.android.apps.santatracker.doodles.BaseDoodleActivity;
+import com.google.android.apps.santatracker.doodles.R;
+import com.google.android.apps.santatracker.doodles.shared.AndroidUtils;
+import com.google.android.apps.santatracker.doodles.shared.EventBus;
+import com.google.android.apps.santatracker.doodles.shared.GameLoop;
+import com.google.android.apps.santatracker.doodles.shared.HistoryManager;
+import com.google.android.apps.santatracker.doodles.shared.LogicRefreshThread;
+import com.google.android.apps.santatracker.doodles.shared.UIRefreshHandler;
+import com.google.android.apps.santatracker.doodles.shared.UIUtil;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.Builder;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogTimer;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogger;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleNullLogger;
+import com.google.android.apps.santatracker.doodles.shared.sound.SoundManager;
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+
+/** Base class for Pineapple game fragments. */
+public abstract class GameFragment extends Fragment
+ implements GameLoop, ScoreView.OnShareClickedListener {
+
+ // Minimum length of the title screen.
+ public static final long TITLE_DURATION_MS = 1000;
+
+ // Require 128MB and if we don't have it, downsample the resources.
+ private static final int AVAILABLE_MEMORY_REQUIRED = (2 << 27);
+
+ // Note this is a context, not an activity. Use getActivity() for an activity.
+ public Context context;
+ public final DoodleLogger logger;
+ public boolean isDestroyed = false;
+ protected FrameLayout wrapper;
+ protected View titleView;
+ protected ScoreView scoreView;
+ protected PauseView pauseView;
+ protected LogicRefreshThread logicRefreshThread;
+ protected UIRefreshHandler uiRefreshHandler;
+ protected SoundManager soundManager;
+ protected HistoryManager historyManager;
+ protected boolean isFinishedLoading = false;
+ protected boolean isPaused = false;
+ protected PauseView.GamePausedListener gamePausedListener =
+ new PauseView.GamePausedListener() {
+ @Override
+ public void onPause() {
+ onGamePause();
+ }
+
+ @Override
+ public void onResume() {
+ onGameResume();
+ }
+
+ @Override
+ public void onReplay() {
+ onGameReplay();
+ }
+
+ @Override
+ public String gameType() {
+ return getGameType();
+ }
+
+ @Override
+ public float score() {
+ return getScore();
+ }
+ };
+ protected ScoreView.LevelFinishedListener levelFinishedListener =
+ new ScoreView.LevelFinishedListener() {
+ @Override
+ public void onReplay() {
+ onGameReplay();
+ }
+
+ @Override
+ public String gameType() {
+ return getGameType();
+ }
+
+ @Override
+ public float score() {
+ return getScore();
+ }
+
+ @Override
+ public int shareImageId() {
+ return getShareImageId();
+ }
+ };
+ protected boolean resumeAfterLoading;
+ private ImageView titleImageView;
+ private AsyncTask asyncLoadGameTask;
+
+ public GameFragment() {
+ this.logger = new DoodleNullLogger();
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ // TODO: Remove context from the field. Workaround not to change the existing code
+ this.context = context;
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ this.context = null;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (context == null) {
+ return;
+ }
+ resume();
+ if ((pauseView == null || pauseView.isPauseButtonEnabled)
+ && (scoreView == null || scoreView.getVisibility() != View.VISIBLE)) {
+ // If we aren't paused or finished, keep the screen on.
+ AndroidUtils.forceScreenToStayOn(getActivity());
+ }
+ if (soundManager != null) {
+ soundManager.loadMutePreference(context);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (pauseView != null) {
+ pauseView.pause();
+ }
+ resumeAfterLoading = false;
+ if (uiRefreshHandler != null) {
+ logicRefreshThread.stopHandler();
+ uiRefreshHandler.stop();
+ historyManager.save();
+ }
+ if (soundManager != null) {
+ soundManager.pauseAll();
+ soundManager.storeMutePreference(context);
+ }
+ AndroidUtils.allowScreenToTurnOff(getActivity());
+ }
+
+ // To be overrided by games if they want to do more cleanup on destroy.
+ protected void onDestroyHelper() {}
+
+ @Override
+ public void onDestroyView() {
+ isDestroyed = true;
+ if (asyncLoadGameTask != null) {
+ asyncLoadGameTask.cancel(true);
+ }
+ EventBus.getInstance().clearListeners();
+ AnimatedSprite.clearCache();
+ if (soundManager != null) {
+ soundManager.releaseAll();
+ }
+ onDestroyHelper();
+ super.onDestroyView();
+ }
+
+ protected void resume() {
+ if (uiRefreshHandler == null) {
+ resumeAfterLoading = true;
+ } else {
+ logicRefreshThread.startHandler(this);
+ if (titleView == null || titleView.getVisibility() != View.VISIBLE) {
+ playMusic();
+ }
+ }
+ }
+
+ /**
+ * Loads the game. Do not override this function. Instead use the three helper functions:
+ * firstPassLoadOnUiThread, secondPassLoadOnBackgroundThread, finalPassLoadOnUiThread.
+ */
+ public final void loadGame() {
+ ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
+ ActivityManager activityManager =
+ (ActivityManager)
+ getActivity().getSystemService(android.content.Context.ACTIVITY_SERVICE);
+ activityManager.getMemoryInfo(mi);
+ if (mi.availMem < AVAILABLE_MEMORY_REQUIRED || AnimatedSprite.lastUsedSampleSize > 2) {
+ // Low available memory, go ahead and load things with a larger sample size.
+ AnimatedSprite.lastUsedSampleSize = 2;
+ }
+
+ firstPassLoadOnUiThread();
+ secondPassLoadOnBackgroundThread();
+ finalPassLoadOnUiThread();
+
+ asyncLoadGameTask =
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ if (getActivity() != null) {
+ secondPassLoadOnBackgroundThread();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (getActivity() != null) {
+ finalPassLoadOnUiThread();
+ }
+ }
+ };
+ asyncLoadGameTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ protected void firstPassLoadOnUiThread() {}
+
+ protected void secondPassLoadOnBackgroundThread() {
+ EventBus.getInstance().clearListeners();
+ }
+
+ protected void finalPassLoadOnUiThread() {}
+
+ protected void onFinishedLoading() {
+ DoodleLogTimer logTimer = DoodleLogTimer.getInstance();
+ long latencyMs = logTimer.timeElapsedMs();
+ logger.logEvent(
+ new Builder(DEFAULT_DOODLE_NAME, LOADING_COMPLETE)
+ .withEventSubType(getGameType())
+ .withLatencyMs(latencyMs)
+ .build());
+ EventBus.getInstance().sendEvent(GAME_LOADED, latencyMs);
+ logTimer.reset();
+ if (pauseView != null) {
+ pauseView.onFinishedLoading();
+ }
+ if (scoreView != null) {
+ scoreView.setVisibility(View.VISIBLE);
+ }
+
+ isFinishedLoading = true;
+ }
+
+ public boolean isFinishedLoading() {
+ return isFinishedLoading;
+ }
+
+ protected void startHandlers() {
+ logicRefreshThread = new LogicRefreshThread();
+ logicRefreshThread.start();
+ uiRefreshHandler = new UIRefreshHandler();
+
+ // It's annoying when the GC kicks in during gameplay and makes the game stutter. Hint
+ // that now would be a good time to free up some space before the game starts.
+ System.gc();
+ if (resumeAfterLoading) {
+ resume();
+ }
+ }
+
+ protected void playMusic() {
+ soundManager.play(R.raw.fruit_doodle_music);
+ }
+
+ protected void hideTitle() {
+ if (titleView != null && titleView.getVisibility() == View.VISIBLE) {
+ UIUtil.fadeOutAndHide(
+ titleView,
+ 1,
+ 500,
+ new Runnable() {
+ @Override
+ public void run() {
+ if (titleImageView != null) {
+ titleImageView.setImageDrawable(null);
+ titleImageView = null;
+ }
+ }
+ });
+ }
+
+ playMusic();
+ }
+
+ public boolean isGamePaused() {
+ return isPaused;
+ }
+
+ public void onGamePause() {
+ isPaused = true;
+ }
+
+ protected void onGameResume() {
+ isPaused = false;
+ }
+
+ protected void onGameReplay() {
+ replay();
+ isPaused = false;
+ DoodleLogTimer.getInstance().reset();
+ }
+
+ protected abstract float getScore();
+
+ protected abstract int getShareImageId();
+
+ protected boolean onTitleTapped() {
+ return false;
+ }
+
+ protected void replay() {
+ getActivity()
+ .runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ scoreView.resetToStartState();
+ }
+ });
+ }
+
+ protected void loadSounds() {
+ soundManager.loadLongSound(context, R.raw.fruit_doodle_music, true);
+ soundManager.loadShortSound(context, R.raw.menu_item_click);
+ soundManager.loadShortSound(context, R.raw.ui_positive_sound);
+ }
+
+ protected ScoreView getScoreView() {
+ ScoreView scoreView = new ScoreView(context, this);
+ scoreView.setLogger(logger);
+ scoreView.setListener(levelFinishedListener);
+ return scoreView;
+ }
+
+ protected PauseView getPauseView() {
+ PauseView pauseView = new PauseView(context);
+ pauseView.setLogger(logger);
+ pauseView.setListener(gamePausedListener);
+ return pauseView;
+ }
+
+ @Override
+ public void onShareClicked() {
+ Activity activity = getActivity();
+ if (activity instanceof BaseDoodleActivity) {
+ AppInvitesFragment invites = ((BaseDoodleActivity) activity).getAppInvitesFragment();
+ if (invites != null) {
+ invites.sendGenericInvite();
+ }
+ }
+ }
+
+ protected abstract String getGameType();
+
+ public void onBackPressed() {
+ pauseView.pause();
+ }
+
+ public abstract boolean isGameOver();
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/GameOverlayButton.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/GameOverlayButton.java
new file mode 100644
index 000000000..c49f9ae18
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/GameOverlayButton.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.views;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import com.google.android.apps.santatracker.doodles.R;
+
+/** A button in the game overlay screens (pause and end screens). */
+public class GameOverlayButton extends RelativeLayout {
+
+ public GameOverlayButton(Context context) {
+ this(context, null);
+ }
+
+ public GameOverlayButton(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public GameOverlayButton(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ inflate(context, R.layout.game_overlay_button, this);
+ TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.GameOverlayButton, 0, 0);
+
+ int imageRes =
+ ta.getResourceId(
+ R.styleable.GameOverlayButton_imageSrc,
+ com.google.android.apps.santatracker.common.R.drawable.common_btn_pause);
+ ImageView icon = (ImageView) findViewById(R.id.game_overlay_button_image);
+ icon.setImageResource(imageRes);
+
+ String text = ta.getString(R.styleable.GameOverlayButton_text);
+ TextView description = (TextView) findViewById(R.id.game_overlay_button_description);
+ if (text == null || text.isEmpty()) {
+ description.setVisibility(GONE);
+ } else {
+ description.setText(text);
+ }
+ ta.recycle();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/PauseView.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/PauseView.java
new file mode 100644
index 000000000..84ac9a9b5
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/PauseView.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.views;
+
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.DEFAULT_DOODLE_NAME;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.HOME_CLICKED;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.MUTE_CLICKED;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.PAUSE_CLICKED;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.REPLAY_CLICKED;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.UNMUTE_CLICKED;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.UNPAUSE_CLICKED;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import com.google.android.apps.santatracker.doodles.R;
+import com.google.android.apps.santatracker.doodles.shared.AndroidUtils;
+import com.google.android.apps.santatracker.doodles.shared.EventBus;
+import com.google.android.apps.santatracker.doodles.shared.UIUtil;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.Builder;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogTimer;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogger;
+import com.google.android.apps.santatracker.doodles.shared.sound.SoundManager;
+
+/** The overlay which is shown when a game is paused. */
+public class PauseView extends FrameLayout {
+
+ public static final int FADE_DURATION_MS = 400; // The pause button fading in and out.
+ private static final int BIG_PAUSE_FADE_IN_MS = 200; // Fading in paused screen elements.
+ private static final int FADE_IN_MS = 500; // Fading in paused screen elements.
+ private static final int FADE_OUT_MS = 200; // Fading out paused screen elements.
+ private static final int BUMP_MS = 200; // The paused text over-zooms a bit on pause.
+ private static final int RELAX_MS = 200; // The paused text shrinks a bit after zooming up.
+ private static final float ZOOM_UP_SCALE_OVERSHOOT = 1.2f;
+ public boolean isPauseButtonEnabled = true;
+ private GamePausedListener listener;
+ private DoodleLogger logger;
+
+ private ImageButton muteButton;
+ private GameOverlayButton pauseButton;
+ private View resumeButton;
+ private View buttonContainer;
+ private View background;
+ private float backgroundAlpha;
+ private float pauseButtonAlpha;
+
+ public PauseView(Context context) {
+ this(context, null);
+ }
+
+ public PauseView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PauseView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ loadLayout(context);
+ hidePauseScreen();
+ }
+
+ public void setLogger(DoodleLogger logger) {
+ this.logger = logger;
+ }
+
+ public GamePausedListener getListener() {
+ return this.listener;
+ }
+
+ public void setListener(GamePausedListener listener) {
+ this.listener = listener;
+ }
+
+ public void hidePauseButton() {
+ isPauseButtonEnabled = false;
+ UIUtil.fadeOutAndHide(pauseButton, FADE_DURATION_MS, pauseButtonAlpha);
+ }
+
+ public void showPauseButton() {
+ isPauseButtonEnabled = true;
+ UIUtil.showAndFadeIn(pauseButton, FADE_DURATION_MS, pauseButtonAlpha);
+ }
+
+ public void onFinishedLoading() {
+ setVisibility(View.VISIBLE);
+ }
+
+ protected void loadLayout(final Context context) {
+ setVisibility(View.INVISIBLE);
+ LayoutInflater inflater = LayoutInflater.from(context);
+
+ View view = inflater.inflate(R.layout.pause_view, this);
+
+ buttonContainer = view.findViewById(R.id.button_container);
+
+ muteButton = (ImageButton) view.findViewById(R.id.mute_button);
+ if (SoundManager.soundsAreMuted) {
+ muteButton.setImageResource(
+ com.google.android.apps.santatracker.common.R.drawable.common_btn_speaker_off);
+ }
+ muteButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ boolean shouldMute = !SoundManager.soundsAreMuted;
+
+ String logEventName = shouldMute ? MUTE_CLICKED : UNMUTE_CLICKED;
+ logger.logEvent(
+ new Builder(DEFAULT_DOODLE_NAME, logEventName)
+ .withEventSubType(listener.gameType())
+ .build());
+
+ muteButton.setImageResource(
+ shouldMute
+ ? com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .drawable
+ .common_btn_speaker_off
+ : com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .drawable
+ .common_btn_speaker_on);
+ muteButton.setContentDescription(
+ context.getResources()
+ .getString(
+ shouldMute
+ ? com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .unmute
+ : com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .mute));
+ EventBus.getInstance().sendEvent(EventBus.MUTE_SOUNDS, shouldMute);
+ }
+ });
+
+ pauseButton = (GameOverlayButton) view.findViewById(R.id.pause_button);
+ pauseButtonAlpha = pauseButton.getAlpha();
+ pauseButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ pause();
+ }
+ });
+
+ resumeButton = view.findViewById(R.id.resume_button);
+ resumeButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ logger.logEvent(
+ new Builder(DEFAULT_DOODLE_NAME, UNPAUSE_CLICKED)
+ .withEventSubType(listener.gameType())
+ .build());
+ unpause();
+ }
+ });
+
+ View replayButton = view.findViewById(R.id.replay_button);
+ replayButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ logger.logEvent(
+ new Builder(DEFAULT_DOODLE_NAME, REPLAY_CLICKED)
+ .withEventSubType(listener.gameType())
+ .withLatencyMs(DoodleLogTimer.getInstance().timeElapsedMs())
+ .withEventValue1(listener.score())
+ .build());
+ replay();
+ }
+ });
+
+ View menuButton = view.findViewById(R.id.menu_button);
+ menuButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ EventBus.getInstance()
+ .sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
+
+ logger.logEvent(
+ new Builder(DEFAULT_DOODLE_NAME, HOME_CLICKED)
+ .withEventSubType(listener.gameType())
+ .withLatencyMs(DoodleLogTimer.getInstance().timeElapsedMs())
+ .withEventValue1(listener.score())
+ .build());
+
+ AndroidUtils.finishActivity(context);
+ }
+ });
+
+ background = view.findViewById(R.id.pause_view_background);
+ backgroundAlpha = background.getAlpha();
+ }
+
+ private void replay() {
+ DoodleLogTimer.getInstance().unpause();
+ hidePauseScreen();
+ if (listener != null) {
+ EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
+ listener.onReplay();
+ }
+ }
+
+ /** Pauses the current game. */
+ public void pause() {
+ if (!isPauseButtonEnabled) {
+ return;
+ }
+ logger.logEvent(
+ new Builder(DEFAULT_DOODLE_NAME, PAUSE_CLICKED)
+ .withEventSubType(listener.gameType())
+ .withLatencyMs(DoodleLogTimer.getInstance().timeElapsedMs())
+ .build());
+ DoodleLogTimer.getInstance().pause();
+ if (listener != null) {
+ EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
+ listener.onPause();
+ }
+ showPauseScreen();
+ AndroidUtils.allowScreenToTurnOff(getContext());
+ }
+
+ private void unpause() {
+ DoodleLogTimer.getInstance().unpause();
+ hidePauseScreen();
+ if (listener != null) {
+ EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
+ listener.onResume();
+ }
+ AndroidUtils.forceScreenToStayOn(getContext());
+ }
+
+ private void showPauseScreen() {
+ isPauseButtonEnabled = false;
+ SoundManager soundManager = SoundManager.getInstance();
+ soundManager.mute(R.raw.fruit_doodle_music);
+ soundManager.pauseShortSounds();
+
+ if (SoundManager.soundsAreMuted) {
+ muteButton.setImageResource(
+ com.google.android.apps.santatracker.common.R.drawable.common_btn_speaker_off);
+ } else {
+ muteButton.setImageResource(
+ com.google.android.apps.santatracker.common.R.drawable.common_btn_speaker_on);
+ }
+
+ muteButton.setAlpha(0.0f);
+ resumeButton.setAlpha(0.0f);
+ buttonContainer.setAlpha(0);
+ background.setAlpha(0);
+
+ muteButton.setVisibility(VISIBLE);
+ resumeButton.setVisibility(VISIBLE);
+ buttonContainer.setVisibility(VISIBLE);
+ background.setVisibility(VISIBLE);
+
+ ValueAnimator fadeBigPauseIn =
+ UIUtil.animator(
+ BIG_PAUSE_FADE_IN_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ resumeButton.setAlpha(
+ (float) valueAnimator.getAnimatedValue("textAlpha"));
+ background.setAlpha(
+ (float) valueAnimator.getAnimatedValue("bgAlpha"));
+ }
+ },
+ UIUtil.floatValue("textAlpha", 0, 1),
+ UIUtil.floatValue("bgAlpha", 0, backgroundAlpha));
+
+ ValueAnimator fadePauseButtonOut =
+ UIUtil.animator(
+ BIG_PAUSE_FADE_IN_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ pauseButton.setAlpha(
+ (float) valueAnimator.getAnimatedValue("pauseButtonAlpha"));
+ }
+ },
+ UIUtil.floatValue("pauseButtonAlpha", pauseButtonAlpha, 0));
+ fadePauseButtonOut.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ pauseButton.setVisibility(INVISIBLE);
+ }
+ });
+
+ ValueAnimator zoomUp =
+ UIUtil.animator(
+ BUMP_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float scale = (float) valueAnimator.getAnimatedValue("scale");
+ resumeButton.setScaleX(scale);
+ resumeButton.setScaleY(scale);
+ }
+ },
+ UIUtil.floatValue("scale", 0, ZOOM_UP_SCALE_OVERSHOOT));
+
+ ValueAnimator relax =
+ UIUtil.animator(
+ RELAX_MS,
+ new OvershootInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float scale = (float) valueAnimator.getAnimatedValue("scale");
+ resumeButton.setScaleX(scale);
+ resumeButton.setScaleY(scale);
+ }
+ },
+ UIUtil.floatValue("scale", ZOOM_UP_SCALE_OVERSHOOT, 1));
+
+ ValueAnimator fadeIn =
+ UIUtil.animator(
+ FADE_IN_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ muteButton.setAlpha(
+ (float) valueAnimator.getAnimatedValue("alpha"));
+ buttonContainer.setAlpha(
+ (float) valueAnimator.getAnimatedValue("alpha"));
+ }
+ },
+ UIUtil.floatValue("alpha", 0, 1));
+ AnimatorSet animations = new AnimatorSet();
+ animations.play(fadeBigPauseIn).with(zoomUp);
+ animations.play(fadePauseButtonOut).with(zoomUp);
+ animations.play(relax).after(zoomUp);
+ animations.play(fadeIn).after(zoomUp);
+ animations.start();
+ }
+
+ private void hidePauseScreen() {
+ SoundManager soundManager = SoundManager.getInstance();
+ soundManager.unmute(R.raw.fruit_doodle_music);
+ soundManager.resumeShortSounds();
+
+ ValueAnimator fadeOut =
+ UIUtil.animator(
+ FADE_OUT_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ muteButton.setAlpha(
+ (float) valueAnimator.getAnimatedValue("overlayAlpha"));
+ background.setAlpha(
+ (float) valueAnimator.getAnimatedValue("bgAlpha"));
+ buttonContainer.setAlpha(
+ (float) valueAnimator.getAnimatedValue("overlayAlpha"));
+
+ resumeButton.setAlpha(
+ (float) valueAnimator.getAnimatedValue("overlayAlpha"));
+ resumeButton.setScaleX(
+ (float) valueAnimator.getAnimatedValue("iconScale"));
+ resumeButton.setScaleY(
+ (float) valueAnimator.getAnimatedValue("iconScale"));
+ }
+ },
+ UIUtil.floatValue("overlayAlpha", 1, 0),
+ UIUtil.floatValue("bgAlpha", backgroundAlpha, 0),
+ UIUtil.floatValue("iconScale", 1, 2));
+ fadeOut.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ muteButton.setVisibility(INVISIBLE);
+ resumeButton.setVisibility(INVISIBLE);
+ buttonContainer.setVisibility(INVISIBLE);
+ background.setVisibility(INVISIBLE);
+
+ isPauseButtonEnabled = true;
+ }
+ });
+
+ pauseButton.setAlpha(0.0f);
+ pauseButton.setVisibility(VISIBLE);
+ ValueAnimator fadePauseButtonIn =
+ UIUtil.animator(
+ FADE_OUT_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ pauseButton.setAlpha(
+ (float) valueAnimator.getAnimatedValue("alpha"));
+ }
+ },
+ UIUtil.floatValue("alpha", 0, pauseButtonAlpha));
+
+ AnimatorSet animations = new AnimatorSet();
+ animations.play(fadeOut).with(fadePauseButtonIn);
+ animations.start();
+ }
+
+ /** A listener for interacting with the PauseView. */
+ public interface GamePausedListener {
+ void onPause();
+
+ void onResume();
+
+ void onReplay();
+
+ String gameType();
+
+ float score();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/ScoreView.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/ScoreView.java
new file mode 100644
index 000000000..18e636687
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/ScoreView.java
@@ -0,0 +1,706 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.views;
+
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.DEFAULT_DOODLE_NAME;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.GAME_OVER;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.HOME_CLICKED;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.REPLAY_CLICKED;
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.SHARE_CLICKED;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.BounceInterpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import com.google.android.apps.santatracker.doodles.R;
+import com.google.android.apps.santatracker.doodles.shared.AndroidUtils;
+import com.google.android.apps.santatracker.doodles.shared.EventBus;
+import com.google.android.apps.santatracker.doodles.shared.UIUtil;
+import com.google.android.apps.santatracker.doodles.shared.animation.ElasticOutInterpolator;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.Builder;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogTimer;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogger;
+import com.google.android.apps.santatracker.util.SantaLog;
+
+/** Displays the score during the game and shows the end screen when the game is over. */
+public class ScoreView extends FrameLayout {
+ private static final String TAG = ScoreView.class.getSimpleName();
+ // Durations of various animations.
+ private static final int BUMP_MS = 300; // Bump when a point is scored.
+ private static final int ZOOM_UP_MS = 900; // Zooming score to middle of screen.
+ private static final int ZOOM_DOWN_MS = 400; // Zooming score to its end position.
+ private static final int SHARE_FADE_IN_MS = 500; // Fading in the share image.
+ private static final int SHARE_DROP_MS = 400; // Dropping the share image into place.
+ private static final int SHARE_Y_OFFSET_PX = -200; // Offset of the share image before it drops.
+ private static final int BG_FADE_IN_MS = 400; // Fading in the end screen background.
+ private static final int RESET_FADE_IN_MS = 300; // Fading in the score view when game is reset.
+ private static final int STAR_BOUNCE_IN_MS = 600; // Bouncing the stars into the end screen.
+ private static final int STAR_FADE_IN_MS = 500; // Fading the stars into the end screen.
+ private static final float STAR_BIG_SCALE = 2.0f;
+ protected DoodleLogger logger;
+ protected LevelFinishedListener listener;
+ // The score in the upper-left corner during the game. This also gets zoomed up to the
+ // middle of the screen at the end of the game.
+ private TextView currentScore;
+ // An invisible placeholder. Lets us use the android layout engine for figuring out where
+ // the score is positioned on the final screen. At the end of the game, currentScore animates
+ // from its original position/size to the position/size of finalScorePlaceholder.
+ private TextView finalScorePlaceholder;
+ // An invisible placeholder which is positioned at the center of the screen and is used as the
+ // intermediate position/size before the score drops into its final position.
+ private TextView centeredScorePlaceholder;
+ // Text that says "Game Over"
+ private TextView gameOverText;
+ // Widgets on the end screen.
+ private TextView bestScore;
+ private ImageView shareImage;
+ private LinearLayout menuItems;
+ private GameOverlayButton shareButton;
+ // A semi-opaque background which darkens the game during the end screen.
+ private View background;
+ // Initial state for the views involved in the end-screen animation, stored so it can be
+ // restored if "replay" is tapped.
+ private float currentScoreX = Float.NaN;
+ private float currentScoreY = Float.NaN;
+ private int currentScoreMarginStart;
+ private int currentScoreMarginTop;
+ private float currentScoreTextSizePx;
+ private float currentScoreAlpha;
+ private float finalScoreMaxWidth;
+ private float finalScoreMaxHeight;
+ private float centeredScoreMaxWidth;
+ private float centeredScoreMaxHeight;
+ private float backgroundAlpha;
+ private int mCurrentScoreValue;
+ private LinearLayout currentStars;
+ private RelativeLayout finalStars;
+ private int filledStarCount;
+ private boolean canReplay;
+
+ private OnShareClickedListener shareClickedListener;
+
+ public ScoreView(Context context, OnShareClickedListener shareClickedListener) {
+ this(context, (AttributeSet) null);
+ this.shareClickedListener = shareClickedListener;
+ }
+
+ public ScoreView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ScoreView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ loadLayout(context);
+ resetToStartState();
+ }
+
+ public void setLogger(DoodleLogger logger) {
+ this.logger = logger;
+ }
+
+ protected void loadLayout(final Context context) {
+ setVisibility(View.INVISIBLE);
+ LayoutInflater inflater = LayoutInflater.from(context);
+ View view = inflater.inflate(R.layout.score_view, this);
+
+ gameOverText = (TextView) view.findViewById(R.id.text_game_over);
+ gameOverText.setVisibility(INVISIBLE);
+
+ currentScore = (TextView) view.findViewById(R.id.current_score);
+ // Store these for later so we can put currentScore back where it started after animating
+ // it.
+ currentScoreTextSizePx = currentScore.getTextSize();
+ currentScoreAlpha = currentScore.getAlpha();
+ currentScore.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ currentScoreX = currentScore.getX();
+ currentScoreY = currentScore.getY();
+ }
+ });
+ RelativeLayout.LayoutParams params =
+ (RelativeLayout.LayoutParams) currentScore.getLayoutParams();
+ if (VERSION.SDK_INT >= 17) {
+ currentScoreMarginStart = params.getMarginStart();
+ } else {
+ currentScoreMarginStart = params.leftMargin;
+ }
+ currentScoreMarginTop = params.topMargin;
+
+ finalScorePlaceholder = (TextView) view.findViewById(R.id.final_score_placeholder);
+ finalScorePlaceholder.setVisibility(INVISIBLE);
+ finalScoreMaxWidth = getResources().getDimension(R.dimen.final_score_max_width);
+ finalScoreMaxHeight = getResources().getDimension(R.dimen.final_score_max_height);
+ finalScorePlaceholder.addTextChangedListener(
+ new TextWatcher() {
+ @Override
+ public void beforeTextChanged(
+ CharSequence charSequence, int i, int i1, int i2) {}
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ UIUtil.fitToBounds(
+ finalScorePlaceholder, finalScoreMaxWidth, finalScoreMaxHeight);
+ }
+ });
+
+ centeredScorePlaceholder = (TextView) view.findViewById(R.id.centered_score_placeholder);
+ centeredScorePlaceholder.setVisibility(INVISIBLE);
+ centeredScoreMaxWidth = getResources().getDimension(R.dimen.centered_score_max_width);
+ centeredScoreMaxHeight = getResources().getDimension(R.dimen.centered_score_max_height);
+ centeredScorePlaceholder.addTextChangedListener(
+ new TextWatcher() {
+ @Override
+ public void beforeTextChanged(
+ CharSequence charSequence, int i, int i1, int i2) {}
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ UIUtil.fitToBounds(
+ centeredScorePlaceholder,
+ centeredScoreMaxWidth,
+ centeredScoreMaxHeight);
+ }
+ });
+
+ currentStars = (LinearLayout) view.findViewById(R.id.current_stars);
+ currentStars
+ .removeAllViews(); // Remove the stickers that are in the XML for testing layout.
+ finalStars = (RelativeLayout) view.findViewById(R.id.final_stars);
+
+ bestScore = (TextView) view.findViewById(R.id.best_score);
+ shareImage = (ImageView) view.findViewById(R.id.share_image);
+ shareImage.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ replay();
+ }
+ });
+
+ menuItems = (LinearLayout) view.findViewById(R.id.menu_items);
+ View replayButton = view.findViewById(R.id.replay_button);
+ replayButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ replay();
+ }
+ });
+
+ shareButton = (GameOverlayButton) view.findViewById(R.id.share_button);
+ shareButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ EventBus.getInstance()
+ .sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
+ logger.logEvent(
+ new Builder(DEFAULT_DOODLE_NAME, SHARE_CLICKED)
+ .withEventSubType(listener.gameType())
+ .withEventValue1(listener.shareImageId())
+ .build());
+
+ if (shareClickedListener != null) {
+ shareClickedListener.onShareClicked();
+ }
+ }
+ });
+
+ View moreGamesButton = view.findViewById(R.id.menu_button);
+ moreGamesButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ goToMoreGames(context);
+ }
+ });
+ background = view.findViewById(R.id.score_view_background);
+ backgroundAlpha = background.getAlpha(); // Store for later use.
+ }
+
+ protected void goToMoreGames(Context context) {
+ EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
+ logger.logEvent(
+ new Builder(DEFAULT_DOODLE_NAME, HOME_CLICKED)
+ .withEventSubType(listener.gameType())
+ .build());
+ AndroidUtils.finishActivity(context);
+ }
+
+ private void replay() {
+ if (canReplay && listener != null) {
+ EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
+ logger.logEvent(
+ new Builder(DEFAULT_DOODLE_NAME, REPLAY_CLICKED)
+ .withEventSubType(listener.gameType())
+ .build());
+
+ listener.onReplay();
+ AndroidUtils.forceScreenToStayOn(getContext());
+ }
+ }
+
+ public void setListener(LevelFinishedListener listener) {
+ this.listener = listener;
+ }
+
+ /**
+ * Updates the best score field on the end screen to display the given best score.
+ *
+ * @param newScore The score to be displayed as the best score.
+ */
+ public void updateBestScore(CharSequence newScore) {
+ bestScore.setText(
+ AndroidUtils.getText(
+ getResources(),
+ com.google.android.apps.santatracker.common.R.string.end_screen_best_score,
+ newScore));
+ }
+
+ /**
+ * Sets the end screen header to the given text.
+ *
+ *
+ *
+ *
This header will be shown in place of the best score.
+ *
+ * @param text The text to be put into the header.
+ */
+ public void setHeaderText(CharSequence text) {
+ bestScore.setText(text);
+ }
+
+ public void updateCurrentScore(CharSequence newScore, boolean shouldBump) {
+ currentScore.setText(newScore);
+ finalScorePlaceholder.setText(newScore);
+ centeredScorePlaceholder.setText(newScore);
+ if (shouldBump) {
+ animateBump(currentScore);
+ }
+ }
+
+ public void setShareDrawable(Drawable drawable) {
+ shareImage.setImageDrawable(drawable);
+ }
+
+ public void clearAllStars() {
+ currentStars.removeAllViews();
+ filledStarCount = 0;
+ for (int i = 0; i < finalStars.getChildCount(); i++) {
+ FrameLayout star = (FrameLayout) finalStars.getChildAt(i);
+ star.findViewById(R.id.fill).setVisibility(INVISIBLE);
+ }
+ }
+
+ public void addStar() {
+ if (filledStarCount < 3) {
+ filledStarCount++;
+ int currentStarDimens = (int) AndroidUtils.dipToPixels(40);
+ addStarToLayout(
+ currentStars, currentStarDimens, LinearLayout.LayoutParams.MATCH_PARENT);
+ EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.ui_positive_sound);
+ }
+ }
+
+ public int getStarCount() {
+ return filledStarCount;
+ }
+
+ // Width & height are in pixels.
+ private void addStarToLayout(LinearLayout layout, int width, int height) {
+ ImageView image = new ImageView(getContext());
+ image.setImageResource(R.drawable.pineapple_star_filled);
+ animateBump(image);
+ layout.addView(image, new LinearLayout.LayoutParams(width, height));
+ }
+
+ public void resetToStartState() {
+ SantaLog.i(TAG, "Reset to start state");
+ currentStars.setVisibility(VISIBLE);
+
+ bestScore.setVisibility(INVISIBLE);
+ shareImage.setVisibility(INVISIBLE);
+ menuItems.setVisibility(INVISIBLE);
+ shareButton.setVisibility(INVISIBLE);
+ background.setVisibility(INVISIBLE);
+ finalStars.setVisibility(INVISIBLE);
+ gameOverText.setVisibility(INVISIBLE);
+
+ if (!Float.isNaN(currentScoreX)) {
+ currentScore.setX(currentScoreX);
+ }
+ if (!Float.isNaN(currentScoreY)) {
+ currentScore.setY(currentScoreY);
+ }
+ currentScore.setTextSize(TypedValue.COMPLEX_UNIT_PX, currentScoreTextSizePx);
+ RelativeLayout.LayoutParams params =
+ (RelativeLayout.LayoutParams) currentScore.getLayoutParams();
+
+ if (VERSION.SDK_INT >= 17) {
+ params.setMarginStart(currentScoreMarginStart);
+ } else {
+ params.leftMargin = currentScoreMarginStart;
+ }
+ params.topMargin = currentScoreMarginTop;
+
+ updateCurrentScore(Integer.toString(0), false);
+ clearAllStars();
+
+ currentScore.setAlpha(0);
+ ValueAnimator fadeInCurrentScore =
+ UIUtil.animator(
+ RESET_FADE_IN_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ currentScore.setAlpha(
+ (float) valueAnimator.getAnimatedValue("alpha"));
+ }
+ },
+ UIUtil.floatValue("alpha", 0, currentScoreAlpha));
+ fadeInCurrentScore.start();
+ }
+
+ public void animateToEndState() {
+ AndroidUtils.allowScreenToTurnOff(getContext());
+ SantaLog.i(TAG, "Animate to end state");
+ canReplay = false;
+ setVisibility(View.VISIBLE);
+ logger.logEvent(
+ new Builder(DEFAULT_DOODLE_NAME, GAME_OVER)
+ .withEventSubType(listener.gameType())
+ .withLatencyMs(DoodleLogTimer.getInstance().timeElapsedMs())
+ .withEventValue1(listener.score())
+ .withEventValue2(getStarCount())
+ .build());
+
+ // TODO: Fade this out instead of making it invisible (will have to remove
+ // the layout:alignComponents that attach it current score, else it will move along with the
+ // score.)
+ currentStars.setVisibility(INVISIBLE);
+
+ // Initial state: controls & background are visible but alpha = 0
+ bestScore.setAlpha(0);
+ shareImage.setAlpha(0.0f);
+ background.setAlpha(0);
+ finalStars.setAlpha(0);
+
+ bestScore.setVisibility(VISIBLE);
+ shareImage.setVisibility(VISIBLE);
+ background.setVisibility(VISIBLE);
+ finalStars.setVisibility(VISIBLE);
+ gameOverText.setVisibility(VISIBLE);
+
+ // Offset the share image and stars so that they can bounce in.
+ final float shareImageY = shareImage.getY();
+ final float finalStarsY = finalStars.getY();
+ shareImage.setY(shareImageY + SHARE_Y_OFFSET_PX);
+
+ // Zoom the score to center of screen.
+ // I tried several other ways of doing this animation, none of which worked:
+ // 1. Using TranslateAnimation & ScaleAnimation instead of .animate(): Positions didn't work
+ // right when scaling, maybe because these animate how a view is displayed but not the
+ // actual
+ // view properties.
+ // 2. Using TranslateAnimation & ObjectAnimator: couldn't add ObjectAnimator to the same
+ // Animation set as TranslateAnimation.
+ // 3. Using .animate() to get a PropertyAnimator. Couldn't tween textSize without using
+ // .setUpdateListener, which requires API 19.
+ // 4. Small textSize, scaling up from 1: Text is blurry at scales > 1.
+ // 5. Large textSize, scaling up to 1: Final position was wrong, I couldn't figure out why.
+ // 6. Medium textSize, scaling up to 2.5: Error: font size too large to fit in cache. Tried
+ // turning off HW accel which fixed the cache errors but positioning was still wrong.
+ ValueAnimator zoomUp =
+ UIUtil.animator(
+ ZOOM_UP_MS,
+ new ElasticOutInterpolator(0.35f),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ currentScore.setX((float) valueAnimator.getAnimatedValue("x"));
+ currentScore.setY((float) valueAnimator.getAnimatedValue("y"));
+ currentScore.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ (float) valueAnimator.getAnimatedValue("textSize"));
+ RelativeLayout.LayoutParams params =
+ (RelativeLayout.LayoutParams)
+ currentScore.getLayoutParams();
+ if (VERSION.SDK_INT >= 17) {
+ params.setMarginStart(
+ (int)
+ (float)
+ valueAnimator.getAnimatedValue(
+ "marginStart"));
+ } else {
+ params.leftMargin =
+ (int)
+ (float)
+ valueAnimator.getAnimatedValue(
+ "marginStart");
+ }
+ params.topMargin =
+ (int) (float) valueAnimator.getAnimatedValue("topMargin");
+ currentScore.setAlpha(
+ (float)
+ valueAnimator.getAnimatedValue(
+ "currentScoreAlpha"));
+ }
+ },
+ UIUtil.floatValue(
+ "x", currentScore.getX(), centeredScorePlaceholder.getX()),
+ UIUtil.floatValue(
+ "y", currentScore.getY(), centeredScorePlaceholder.getY()),
+ UIUtil.floatValue(
+ "textSize",
+ currentScoreTextSizePx,
+ centeredScorePlaceholder.getTextSize()),
+ UIUtil.floatValue("marginStart", currentScoreMarginStart, 0),
+ UIUtil.floatValue("topMargin", currentScoreMarginTop, 0),
+ UIUtil.floatValue("currentScoreAlpha", currentScoreAlpha, 1));
+
+ // Zoom the score up to its final position.
+ ValueAnimator zoomBackDown =
+ UIUtil.animator(
+ ZOOM_DOWN_MS,
+ new BounceInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ currentScore.setX((float) valueAnimator.getAnimatedValue("x"));
+ currentScore.setY((float) valueAnimator.getAnimatedValue("y"));
+ currentScore.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ (float) valueAnimator.getAnimatedValue("textSize"));
+ }
+ },
+ UIUtil.floatValue(
+ "x", centeredScorePlaceholder.getX(), finalScorePlaceholder.getX()),
+ UIUtil.floatValue(
+ "y", centeredScorePlaceholder.getY(), finalScorePlaceholder.getY()),
+ UIUtil.floatValue(
+ "textSize",
+ centeredScorePlaceholder.getTextSize(),
+ finalScorePlaceholder.getTextSize()));
+
+ ValueAnimator fadeInBackground =
+ UIUtil.animator(
+ BG_FADE_IN_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ background.setAlpha(
+ (float) valueAnimator.getAnimatedValue("bgAlpha"));
+ }
+ },
+ UIUtil.floatValue("bgAlpha", 0, backgroundAlpha));
+
+ ValueAnimator fadeInBestScore =
+ UIUtil.animator(
+ BG_FADE_IN_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ bestScore.setAlpha(
+ (float) valueAnimator.getAnimatedValue("bgAlpha"));
+ }
+ },
+ UIUtil.floatValue("bgAlpha", 0, backgroundAlpha));
+
+ ValueAnimator fadeInMenuItems =
+ UIUtil.animator(
+ BG_FADE_IN_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float alpha = (float) valueAnimator.getAnimatedValue("alpha");
+ menuItems.setAlpha(alpha);
+ shareButton.setAlpha(alpha);
+ }
+ },
+ UIUtil.floatValue("alpha", 0, 1));
+ fadeInMenuItems.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Don't set menu items to be visible until the animation starts so that
+ // they aren't
+ // clickable until they start to appear.
+ menuItems.setVisibility(VISIBLE);
+ menuItems.setAlpha(0);
+
+ shareButton.setVisibility(VISIBLE);
+ shareButton.setAlpha(0);
+
+ canReplay = true;
+ }
+ });
+
+ ValueAnimator fadeInShareImageAndFinalStars =
+ UIUtil.animator(
+ SHARE_FADE_IN_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float alpha = (float) valueAnimator.getAnimatedValue("alpha");
+ shareImage.setAlpha(alpha);
+ finalStars.setAlpha(alpha);
+ }
+ },
+ UIUtil.floatValue("alpha", 0, 1));
+
+ ValueAnimator dropShareImageAndFinalStars =
+ UIUtil.animator(
+ SHARE_DROP_MS,
+ new BounceInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float yOffset = (float) valueAnimator.getAnimatedValue("yOffset");
+ shareImage.setY(shareImageY + yOffset);
+ }
+ },
+ UIUtil.floatValue("yOffset", SHARE_Y_OFFSET_PX, 0));
+
+ AnimatorSet animations = new AnimatorSet();
+
+ int numStars = finalStars.getChildCount();
+ ValueAnimator bounce = null;
+ long starStartDelay = ZOOM_UP_MS + SHARE_FADE_IN_MS + SHARE_DROP_MS / 2;
+ for (int i = 0; i < filledStarCount; i++) {
+ FrameLayout star = (FrameLayout) finalStars.getChildAt(numStars - i - 1);
+ ValueAnimator fade = getStarFadeIn((ImageView) star.findViewById(R.id.fill));
+ bounce = getStarBounceIn((ImageView) star.findViewById(R.id.fill));
+ animations.play(fade).after(starStartDelay + STAR_FADE_IN_MS * i);
+ animations.play(bounce).after(starStartDelay + STAR_FADE_IN_MS * i);
+ }
+ if (bounce != null) {
+ animations.play(fadeInMenuItems).after(bounce);
+ } else {
+ animations.play(fadeInMenuItems).after(starStartDelay + STAR_FADE_IN_MS);
+ }
+ animations.play(fadeInBackground).with(zoomUp);
+ animations.play(fadeInBestScore).after(fadeInBackground);
+ animations.play(zoomBackDown).after(zoomUp);
+ animations.play(fadeInShareImageAndFinalStars).after(zoomUp);
+ animations.play(dropShareImageAndFinalStars).after(fadeInShareImageAndFinalStars);
+ animations.start();
+ }
+
+ private ValueAnimator getStarFadeIn(final ImageView star) {
+ star.setAlpha(0.0f);
+ star.setVisibility(VISIBLE);
+ ValueAnimator fadeIn =
+ UIUtil.animator(
+ STAR_FADE_IN_MS,
+ new AccelerateDecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float alpha = (float) valueAnimator.getAnimatedValue("alpha");
+ star.setAlpha(alpha);
+ }
+ },
+ UIUtil.floatValue("alpha", 0, 1));
+ fadeIn.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ EventBus.getInstance()
+ .sendEvent(EventBus.PLAY_SOUND, R.raw.ui_positive_sound);
+ }
+ });
+ return fadeIn;
+ }
+
+ private ValueAnimator getStarBounceIn(final ImageView star) {
+ return UIUtil.animator(
+ STAR_BOUNCE_IN_MS,
+ new BounceInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float scale = (float) valueAnimator.getAnimatedValue("scale");
+ star.setScaleX(scale);
+ star.setScaleY(scale);
+ }
+ },
+ UIUtil.floatValue("scale", STAR_BIG_SCALE, 1));
+ }
+
+ private void animateBump(final View view) {
+ ValueAnimator tween =
+ UIUtil.animator(
+ BUMP_MS,
+ new OvershootInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float scale = (float) valueAnimator.getAnimatedValue("scale");
+ view.setScaleX(scale);
+ view.setScaleY(scale);
+ }
+ },
+ UIUtil.floatValue("scale", 1.5f, 1));
+ tween.start();
+ }
+
+ public interface OnShareClickedListener {
+ void onShareClicked();
+ }
+
+ /** A listener for events which occur from the level finished screen. */
+ public interface LevelFinishedListener {
+ void onReplay();
+
+ String gameType();
+
+ float score();
+
+ int shareImageId();
+ }
+}
diff --git a/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/StarView.java b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/StarView.java
new file mode 100644
index 000000000..ca328fdc8
--- /dev/null
+++ b/doodles-lib/src/main/java/com/google/android/apps/santatracker/doodles/shared/views/StarView.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.shared.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import com.google.android.apps.santatracker.doodles.R;
+
+/**
+ * A view for the stars on the end screen. This is just a wrapper class so that we can contain star
+ * layout behavior in a single layout file instead of having to specify each one individually.
+ */
+public class StarView extends FrameLayout {
+
+ public StarView(Context context) {
+ this(context, null);
+ }
+
+ public StarView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public StarView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ inflate(context, R.layout.star_view, this);
+ }
+}
diff --git a/doodles-lib/src/main/res/drawable-nodpi/debug_marker.webp b/doodles-lib/src/main/res/drawable-nodpi/debug_marker.webp
new file mode 100644
index 000000000..ccaf3c161
Binary files /dev/null and b/doodles-lib/src/main/res/drawable-nodpi/debug_marker.webp differ
diff --git a/doodles-lib/src/main/res/drawable-nodpi/pineapple_star_empty.webp b/doodles-lib/src/main/res/drawable-nodpi/pineapple_star_empty.webp
new file mode 100644
index 000000000..5fc23747a
Binary files /dev/null and b/doodles-lib/src/main/res/drawable-nodpi/pineapple_star_empty.webp differ
diff --git a/doodles-lib/src/main/res/drawable-nodpi/pineapple_star_filled.webp b/doodles-lib/src/main/res/drawable-nodpi/pineapple_star_filled.webp
new file mode 100644
index 000000000..0713e5f40
Binary files /dev/null and b/doodles-lib/src/main/res/drawable-nodpi/pineapple_star_filled.webp differ
diff --git a/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_00.webp b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_00.webp
new file mode 100644
index 000000000..a898bc520
Binary files /dev/null and b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_00.webp differ
diff --git a/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_01.webp b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_01.webp
new file mode 100644
index 000000000..d1a3fa91b
Binary files /dev/null and b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_01.webp differ
diff --git a/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_02.webp b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_02.webp
new file mode 100644
index 000000000..faefd61b3
Binary files /dev/null and b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_02.webp differ
diff --git a/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_03.webp b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_03.webp
new file mode 100644
index 000000000..44335d602
Binary files /dev/null and b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_03.webp differ
diff --git a/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_04.webp b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_04.webp
new file mode 100644
index 000000000..9a64f83b5
Binary files /dev/null and b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_04.webp differ
diff --git a/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_05.webp b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_05.webp
new file mode 100644
index 000000000..5e74b7d7b
Binary files /dev/null and b/doodles-lib/src/main/res/drawable-nodpi/tutoappear_new_05.webp differ
diff --git a/doodles-lib/src/main/res/layout/activity_view.xml b/doodles-lib/src/main/res/layout/activity_view.xml
new file mode 100644
index 000000000..937aff3c4
--- /dev/null
+++ b/doodles-lib/src/main/res/layout/activity_view.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/doodles-lib/src/main/res/layout/fact_view.xml b/doodles-lib/src/main/res/layout/fact_view.xml
new file mode 100644
index 000000000..0c8542626
--- /dev/null
+++ b/doodles-lib/src/main/res/layout/fact_view.xml
@@ -0,0 +1,26 @@
+
+
+
diff --git a/doodles/src/main/res/layout/game_overlay_button.xml b/doodles-lib/src/main/res/layout/game_overlay_button.xml
similarity index 84%
rename from doodles/src/main/res/layout/game_overlay_button.xml
rename to doodles-lib/src/main/res/layout/game_overlay_button.xml
index 77c526e80..c405325e3 100644
--- a/doodles/src/main/res/layout/game_overlay_button.xml
+++ b/doodles-lib/src/main/res/layout/game_overlay_button.xml
@@ -1,12 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doodles/src/main/res/layout/score_view.xml b/doodles-lib/src/main/res/layout/score_view.xml
similarity index 88%
rename from doodles/src/main/res/layout/score_view.xml
rename to doodles-lib/src/main/res/layout/score_view.xml
index 8e0be02c1..3176d3908 100644
--- a/doodles/src/main/res/layout/score_view.xml
+++ b/doodles-lib/src/main/res/layout/score_view.xml
@@ -1,12 +1,12 @@
-
-
-
@@ -185,27 +186,29 @@
+ android:theme="@style/ThemeOverlay.Purple" />
+ android:theme="@style/ThemeOverlay.Purple" />
-
+ app:imageSrc="@drawable/common_btn_share" />
diff --git a/doodles/src/main/res/layout/star_view.xml b/doodles-lib/src/main/res/layout/star_view.xml
similarity index 82%
rename from doodles/src/main/res/layout/star_view.xml
rename to doodles-lib/src/main/res/layout/star_view.xml
index 251c7eed3..ec5898d9f 100644
--- a/doodles/src/main/res/layout/star_view.xml
+++ b/doodles-lib/src/main/res/layout/star_view.xml
@@ -1,12 +1,12 @@
+
+
+ 20dp
+ 30dp
+ 60dp
+ 240dp
+ 70dp
+ 8dp
+ 16sp
+ 90dp
+
diff --git a/doodles-lib/src/main/res/values-sw600dp/dimens.xml b/doodles-lib/src/main/res/values-sw600dp/dimens.xml
new file mode 100644
index 000000000..4438dce37
--- /dev/null
+++ b/doodles-lib/src/main/res/values-sw600dp/dimens.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ 20dp
+ 16sp
+ 120dp
+ 80dp
+ 140dp
+ 100dp
+ 280dp
+ 20dp
+ 70dp
+ 8dp
+ 16sp
+ 100dp
+ 80dp
+ 70dp
+ 240dp
+
diff --git a/doodles-lib/src/main/res/values/attrs.xml b/doodles-lib/src/main/res/values/attrs.xml
new file mode 100644
index 000000000..326f2e596
--- /dev/null
+++ b/doodles-lib/src/main/res/values/attrs.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doodles-lib/src/main/res/values/colors.xml b/doodles-lib/src/main/res/values/colors.xml
new file mode 100644
index 000000000..bbbc8a57f
--- /dev/null
+++ b/doodles-lib/src/main/res/values/colors.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ #ffffff
+ #fed32b
+ #000000
+ #ffffff
+ - 0.80
+
+ #aa032702
+ #00032702
+
diff --git a/doodles-lib/src/main/res/values/dimens.xml b/doodles-lib/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..e54734a4a
--- /dev/null
+++ b/doodles-lib/src/main/res/values/dimens.xml
@@ -0,0 +1,58 @@
+
+
+
+
+ 48dp
+ 25dp
+ 25dp
+ 76dp
+ 76dp
+ 24dp
+ 40dp
+ 3dp
+ 130dp
+ 25dp
+ 100sp
+ 100dp
+ 80dp
+ 40dp
+ 348dp
+ 148dp
+ 5dp
+ 70dp
+ 50dp
+ 100dp
+ 80dp
+ 15dp
+ 12sp
+ 200dp
+ 40dp
+ 10dp
+ 10dp
+ 20dp
+ 60dp
+ 6dp
+ 12sp
+ 10dp
+ 80dp
+ 46dp
+ 40dp
+ 138dp
+ 20sp
+ 20dp
+ 40dp
+
diff --git a/doodles-lib/src/main/res/values/values_analytics.xml b/doodles-lib/src/main/res/values/values_analytics.xml
new file mode 100644
index 000000000..d81ffb249
--- /dev/null
+++ b/doodles-lib/src/main/res/values/values_analytics.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ Swimming
+ Running
+ PresentToss
+
diff --git a/doodles/.gitignore b/doodles/.gitignore
deleted file mode 100644
index 796b96d1c..000000000
--- a/doodles/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/doodles/build.gradle b/doodles/build.gradle
deleted file mode 100644
index 23109ee45..000000000
--- a/doodles/build.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion rootProject.ext.compileSdkVersion
- buildToolsVersion rootProject.ext.tools
-
- defaultConfig {
- minSdkVersion rootProject.ext.minSdkVersion
- targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 1
- versionName "1.0"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile rootProject.ext.appCompat
- compile rootProject.ext.supportAnnotations
- compile rootProject.ext.firebaseConfig
-
- compile project(':common')
-}
diff --git a/doodles/src/main/AndroidManifest.xml b/doodles/src/main/AndroidManifest.xml
deleted file mode 100644
index df7748116..000000000
--- a/doodles/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/PineappleActivity.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/PineappleActivity.java
deleted file mode 100644
index 0a09f4cf6..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/PineappleActivity.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles;
-
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-
-import com.google.android.apps.santatracker.doodles.shared.DoodleConfig;
-import com.google.android.apps.santatracker.doodles.shared.GameFragment;
-import com.google.android.apps.santatracker.doodles.shared.LaunchDecisionMaker;
-import com.google.android.apps.santatracker.doodles.shared.PineappleDebugLogger;
-import com.google.android.apps.santatracker.invites.AppInvitesFragment;
-import com.google.android.apps.santatracker.util.MeasurementManager;
-import com.google.firebase.analytics.FirebaseAnalytics;
-
-/**
- * Main activity to route to the various doodle games.
- */
-public class PineappleActivity extends FragmentActivity {
- private static final String TAG = PineappleActivity.class.getSimpleName();
- private PineappleDebugLogger logger;
- private AppInvitesFragment appInvitesFragment;
- private FirebaseAnalytics mAnalytics;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_view);
-
- // Check for game direct launch
- DoodleConfig config;
- if (getIntent() != null && getIntent().hasExtra(LaunchDecisionMaker.START_GAME_KEY)) {
- config = new DoodleConfig(getIntent().getExtras(), null);
- } else {
- throw new IllegalStateException("Extra START_GAME_KEY required");
- }
-
- // [ANALYTICS]
- mAnalytics = FirebaseAnalytics.getInstance(this);
- String gameKey = getIntent().getStringExtra(LaunchDecisionMaker.START_GAME_KEY);
- switch (gameKey) {
- case LaunchDecisionMaker.WATERPOLO_GAME_VALUE:
- MeasurementManager.recordScreenView(mAnalytics,
- getString(R.string.analytics_screen_waterpolo));
- break;
- case LaunchDecisionMaker.RUNNING_GAME_VALUE:
- MeasurementManager.recordScreenView(mAnalytics,
- getString(R.string.analytics_screen_running));
- break;
- case LaunchDecisionMaker.SWIMMING_GAME_VALUE:
- MeasurementManager.recordScreenView(mAnalytics,
- getString(R.string.analytics_screen_swimming));
- break;
- }
-
- appInvitesFragment = AppInvitesFragment.getInstance(this);
-
- logger = new PineappleDebugLogger();
- Fragment fragment = LaunchDecisionMaker.makeFragment(this, config, logger);
- FragmentManager fragmentManager = getFragmentManager();
- FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
- fragmentTransaction.add(R.id.activity_wrapper, fragment, "menu");
- fragmentTransaction.commit();
- }
-
- @Override
- public void onBackPressed() {
- // Get the current game fragment
- final Fragment fragment = getFragmentManager().findFragmentById(R.id.activity_wrapper);
-
- if (fragment instanceof GameFragment) {
- GameFragment gameFragment = (GameFragment) fragment;
-
- // Pause the game, or go back to the home screen if the game is paused already
- if (gameFragment.isGamePaused() || !gameFragment.isFinishedLoading() || gameFragment.isGameOver()) {
- super.onBackPressed();
- } else {
- gameFragment.onBackPressed();
- }
- } else {
- super.onBackPressed();
- }
- }
-
- public AppInvitesFragment getAppInvitesFragment() {
- return appInvitesFragment;
- }
-}
-
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/BackgroundActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/BackgroundActor.java
deleted file mode 100644
index d8ce592d8..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/BackgroundActor.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.pursuit;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.Camera;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-
-/**
- * A background actor that follows the camera.
- */
-public class BackgroundActor extends Actor {
-
- private AnimatedSprite backgroundSprite;
-
- private AnimatedSprite treeSprite;
- private AnimatedSprite umbrellaSprite;
-
- private Camera camera;
-
- public BackgroundActor(Resources resources, Camera camera) {
- this.camera = camera;
-
- backgroundSprite =
- AnimatedSprite.fromFrames(resources, Sprites.snowballrunner_background);
- treeSprite =
- AnimatedSprite.fromFrames(resources, Sprites.snowball_runner_trees1);
- umbrellaSprite =
- AnimatedSprite.fromFrames(resources, Sprites.snowball_runner_trees2);
-
- backgroundSprite.setAnchor(backgroundSprite.frameWidth / 2, 0);
- }
-
- @Override
- public void draw(Canvas canvas) {
- backgroundSprite.setScale(scale, scale);
- float h = backgroundSprite.frameHeight * scale * (960f / 980f);
-
- // Always draws the background at 0, 0, where the camera is.
- // Draws the background sprite three times so the background is not cut off on larger
- // devices where the device dimension exceeds the background sprite's size.
- backgroundSprite.setPosition(position.x, camera.position.y + -camera.position.y % h);
- backgroundSprite.draw(canvas);
-
- backgroundSprite.setPosition(position.x, camera.position.y + h + (-camera.position.y % h));
- backgroundSprite.draw(canvas);
-
- backgroundSprite.setPosition(position.x, camera.position.y - h + (-camera.position.y % h));
- backgroundSprite.draw(canvas);
- }
-
- public void drawTop(Canvas canvas) {
- float h = backgroundSprite.frameHeight * scale;
-
- // Makes repeating trees and umbrellas with integer division.
- float rightTreeY = h * 0.5f + ((h * 2) * ((int) ((camera.position.y + h) / (h * 2))));
- float rightUmbrellaY = h * 1.5f + ((h * 2) * ((int) ((camera.position.y) / (h * 2))));
-
- float leftTreeY = (h * 2) * ((int) ((camera.position.y + (h * 1.5f)) / (h * 2)));
- float leftUmbrellaY =
- h + ((h * 2) * ((int) ((camera.position.y + (h * 0.5f)) / (h * 2))));
-
- treeSprite.setScale(scale, scale);
- treeSprite.setPosition(PursuitModel.HALF_WIDTH - (scale * treeSprite.frameWidth) * 0.35f,
- rightTreeY + (h * 0.08f) - ((camera.position.y - rightTreeY) / h) * h * 0.12f);
- treeSprite.draw(canvas);
-
- treeSprite.setScale(-scale, scale);
- treeSprite.setPosition(-(scale * treeSprite.frameWidth) * 0.3f,
- leftTreeY + (h * 0.08f) - ((camera.position.y - leftTreeY) / h) * h * 0.12f);
- treeSprite.draw(canvas);
-
- umbrellaSprite.setScale(scale, scale);
- umbrellaSprite.setPosition(
- PursuitModel.HALF_WIDTH - (scale * umbrellaSprite.frameWidth) * 0.3f,
- rightUmbrellaY - ((camera.position.y - rightUmbrellaY) / h) * h * 0.07f);
- umbrellaSprite.draw(canvas);
-
- umbrellaSprite.setScale(-scale, scale);
- umbrellaSprite.setPosition(-(scale * umbrellaSprite.frameWidth) * 0.5f,
- leftUmbrellaY - ((camera.position.y - leftUmbrellaY) / h) * h * 0.07f);
- umbrellaSprite.draw(canvas);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PlayerActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PlayerActor.java
deleted file mode 100644
index 792f7caa4..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PlayerActor.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.pursuit;
-
-import android.content.res.Resources;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-
-/**
- * The player actor that has two more animation states than runner actor: celebrating running and
- * sweating running.
- */
-public class PlayerActor extends RunnerActor {
- boolean isSweating;
- boolean isCelebrating;
-
- protected AnimatedSprite celebrateSprite;
- protected AnimatedSprite sweatSprite;
-
- public PlayerActor(Resources resources, int lane) {
- super(resources, RunnerType.STRAWBERRY, lane);
- isSweating = false;
- isCelebrating = false;
-
- sweatSprite = AnimatedSprite.fromFrames(resources, Sprites.snowballrun_running_losing);
- celebrateSprite = AnimatedSprite.fromFrames(resources, Sprites.snowballrun_running_normal);
-
- setSpriteAnchorUpright(sweatSprite);
- setSpriteAnchorUpright(celebrateSprite);
- }
-
- @Override
- public void setRunnerState(RunnerState state) {
- super.setRunnerState(state);
- if (state == RunnerState.RUNNING) {
- if (isCelebrating) {
- currentSprite = celebrateSprite;
- } else if (isSweating) {
- currentSprite = sweatSprite;
- } else {
- currentSprite = runningSprite;
- }
- }
- }
-
- public void setSweat(boolean sweat) {
- if (this.isSweating == sweat) {
- return;
- }
-
- this.isSweating = sweat;
-
- if (state != RunnerState.RUNNING || isCelebrating) {
- return;
- }
-
- if (sweat) {
- sweatSprite.setFrameIndex(runningSprite.getFrameIndex());
- currentSprite = sweatSprite;
- } else {
- runningSprite.setFrameIndex(sweatSprite.getFrameIndex() % runningSprite.getNumFrames());
- currentSprite = runningSprite;
- }
- }
-
- public void setCelebrate(boolean celebrate) {
- if (this.isCelebrating == celebrate) {
- return;
- }
-
- this.isCelebrating = celebrate;
-
- if (state != RunnerState.RUNNING) {
- return;
- }
-
- if (celebrate) {
- celebrateSprite.setFrameIndex(runningSprite.getFrameIndex());
- currentSprite = celebrateSprite;
- } else {
- runningSprite.setFrameIndex(celebrateSprite.getFrameIndex() % runningSprite.getNumFrames());
- currentSprite = runningSprite;
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PowerUpActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PowerUpActor.java
deleted file mode 100644
index 4ce390fed..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PowerUpActor.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.pursuit;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite.AnimatedSpriteListener;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-
-/**
- * A power up that speeds up the player.
- */
-public class PowerUpActor extends Actor {
- private static final int Z_INDEX = 0;
- public static final float RADIUS_WORLD = 25f;
-
- private boolean isPickedUp;
-
- private AnimatedSprite sprite;
-
- public PowerUpActor(float x, float y, Resources resources) {
- zIndex = Z_INDEX;
- position.x = x;
- position.y = y;
- isPickedUp = false;
-
- sprite = AnimatedSprite.fromFrames(resources, Sprites.running_powerup);
- sprite.setFPS(18);
- sprite.setAnchor(sprite.frameWidth / 2, sprite.frameHeight / 2);
- sprite.setLoop(false);
- sprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onFinished() {
- super.onFinished();
- hidden = true;
- }
- });
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
- if (isPickedUp) {
- sprite.update(deltaMs);
- }
- }
-
- public void pickUp() {
- isPickedUp = true;
- }
-
- public boolean isPickedUp() {
- return isPickedUp;
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- if (hidden) {
- return;
- }
- float framesPercent = ((float) sprite.getFrameIndex()) / sprite.getNumFrames();
- sprite.setPosition(position.x, position.y + (3f * framesPercent * scale * sprite.frameHeight));
- sprite.setScale(scale, scale);
- sprite.draw(canvas);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitFragment.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitFragment.java
deleted file mode 100644
index 03ccfa613..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitFragment.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.pursuit;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.pursuit.PursuitModel.ScoreListener;
-import com.google.android.apps.santatracker.doodles.pursuit.PursuitModel.State;
-import com.google.android.apps.santatracker.doodles.pursuit.PursuitModel.StateChangedListener;
-import com.google.android.apps.santatracker.doodles.shared.AndroidUtils;
-import com.google.android.apps.santatracker.doodles.shared.DoodleConfig;
-import com.google.android.apps.santatracker.doodles.shared.EventBus;
-import com.google.android.apps.santatracker.doodles.shared.EventBus.EventBusListener;
-import com.google.android.apps.santatracker.doodles.shared.GameFragment;
-import com.google.android.apps.santatracker.doodles.shared.GameType;
-import com.google.android.apps.santatracker.doodles.shared.HistoryManager;
-import com.google.android.apps.santatracker.doodles.shared.PineappleLogger;
-import com.google.android.apps.santatracker.doodles.shared.UIUtil;
-import com.google.android.apps.santatracker.doodles.shared.sound.SoundManager;
-import com.google.android.apps.santatracker.util.MeasurementManager;
-import com.google.firebase.analytics.FirebaseAnalytics;
-
-import java.text.NumberFormat;
-import java.util.Locale;
-
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.RUNNING_GAME_TYPE;
-
-/**
- * Fragment for the second version of the running game.
- * Manages input & threads, delegates to PursuitModel & PursuitView for the rest.
- */
-public class PursuitFragment extends GameFragment implements EventBusListener {
- private static final String TAG = PursuitFragment.class.getSimpleName();
-
- private static final long GAME_OVER_DELAY_MILLISECONDS = 1000;
- private static final long GAME_OVER_SHORT_DELAY_MILLISECONDS = 150;
-
- private PursuitView gameView;
- private TextView countdownView;
- private PursuitModel model;
- private boolean mIsGameOver = false;
-
- public PursuitFragment() {
- super();
- }
-
- public PursuitFragment(Context context, DoodleConfig doodleConfig, PineappleLogger logger) {
- super(context, doodleConfig, logger);
- }
-
- @Override
- public void update(float deltaMs) {
- if (!isPaused) {
- model.update(deltaMs);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- if (context == null) {
- return null;
- }
-
- historyManager = new HistoryManager(context, new HistoryManager.HistoryListener() {
- @Override
- public void onFinishedLoading() {
- }
- @Override
- public void onFinishedSaving() {
- }
- });
-
- wrapper = new FrameLayout(context);
-
- titleView = getTitleView(R.drawable.snowballrunner_loadingscreen, R.string.running);
- wrapper.addView(titleView);
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- loadGame();
- }
- });
- return wrapper;
- }
-
- @Override
- protected void firstPassLoadOnUiThread() {
- wrapper.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return onTouchEvent(event);
- }
- });
-
- countdownView = new TextView(context);
- countdownView.setGravity(Gravity.CENTER);
- countdownView.setTextColor(context.getResources().getColor(R.color.ui_text_yellow));
- countdownView.setTypeface(Typeface.DEFAULT_BOLD);
- countdownView.setText("0");
- countdownView.setVisibility(View.INVISIBLE);
- Locale locale = context.getResources().getConfiguration().locale;
- countdownView.setText(NumberFormat.getInstance(locale).format(3));
- Point screenDimens = AndroidUtils.getScreenSize();
- UIUtil.fitToBounds(countdownView, screenDimens.x / 10, screenDimens.y / 10);
-
- final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
- gameView = new PursuitView(context);
- wrapper.addView(gameView, 0, lp);
-
- wrapper.addView(countdownView, 1, lp);
- scoreView = getScoreView();
- wrapper.addView(scoreView, 2, lp);
- pauseView = getPauseView();
- pauseView.hidePauseButton();
- wrapper.addView(pauseView, 3, lp);
- }
-
- @Override
- protected void secondPassLoadOnBackgroundThread() {
- super.secondPassLoadOnBackgroundThread();
- EventBus.getInstance().register(this);
-
- model = new PursuitModel(context.getResources(), getActivity().getApplicationContext());
- gameView.setModel(model);
- model.setStateListener(new StateChangedListener() {
- @Override
- public void onStateChanged(State state) {
- if (state == State.SETUP) {
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- hideTitle();
- pauseView.showPauseButton();
- }
- });
- } else if (state == State.SUCCESS) {
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (model == null) {
- return;
- }
- mIsGameOver = true;
- float timeInSeconds = model.getScore();
- int starCount = 4 - model.getFinishingPlace();
-
- long delay = GAME_OVER_SHORT_DELAY_MILLISECONDS;
- if (model.getFinishingPlace() == 1) {
- delay = GAME_OVER_DELAY_MILLISECONDS;
- }
-
- Double bestTimeInSeconds = historyManager.getBestScore(GameType.PURSUIT);
- Integer bestStarCount = historyManager.getBestStarCount(GameType.PURSUIT);
-
- if (bestTimeInSeconds == null || timeInSeconds < bestTimeInSeconds) {
- bestTimeInSeconds = (double) timeInSeconds;
- historyManager.setBestScore(GameType.PURSUIT, bestTimeInSeconds);
- historyManager.save();
- }
-
- if (bestStarCount == null || starCount > bestStarCount) {
- bestStarCount = starCount;
- historyManager.setBestStarCount(GameType.PURSUIT, bestStarCount);
- historyManager.save();
- }
-
- pauseView.hidePauseButton();
-
- final int finalStarCount = starCount;
- final double finalBestTimeInSeconds = bestTimeInSeconds;
-
- scoreView.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (finalStarCount >= 1) {
- scoreView.addStar();
- }
- if (finalStarCount >= 2) {
- scoreView.addStar();
- }
- if (finalStarCount >= 3) {
- scoreView.addStar();
- }
- scoreView.updateBestScore(AndroidUtils.getText(context.getResources(),
- R.string.pursuit_score, finalBestTimeInSeconds));
- scoreView.setShareDrawable(getShareImageDrawable(finalStarCount));
- updateShareText();
-
- scoreView.animateToEndState();
- }
- }, delay);
- }
- });
-
- // [ANALYTICS]
- FirebaseAnalytics analytics = FirebaseAnalytics.getInstance(getActivity());
- MeasurementManager.recordRunningEnd(analytics,
- 4 - model.getFinishingPlace(),
- (int) model.getScore());
- } else if (state == State.FAIL) {
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- pauseView.hidePauseButton();
- mIsGameOver = true;
-
- scoreView.postDelayed(new Runnable() {
- @Override
- public void run() {
- scoreView.setHeaderText(null);
-
- updateShareText();
- scoreView.setShareDrawable(getShareImageDrawable(0));
- scoreView.animateToEndState();
- }
- }, GAME_OVER_DELAY_MILLISECONDS);
- }});
-
- // [ANALYTICS]
- FirebaseAnalytics analytics = FirebaseAnalytics.getInstance(getActivity());
- MeasurementManager.recordRunningEnd(analytics, 0, 0);
- } else if (state == State.READY) {
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mIsGameOver = false;
- pauseView.showPauseButton();
- }
- });
- }
- }
- });
-
- model.setScoreListener(new ScoreListener() {
- @Override
- public void newScore(final float timeInSeconds) {
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- scoreView.updateCurrentScore(
- AndroidUtils.getText(context.getResources(),
- R.string.pursuit_score, timeInSeconds), false);
- }
- });
- }
- });
- model.setCountdownView(countdownView);
- }
- @Override
- protected void finalPassLoadOnUiThread() {
- soundManager = SoundManager.getInstance();
- loadSounds();
-
- onFinishedLoading();
- startHandlers();
- }
-
- @Override
- protected void loadSounds() {
- super.loadSounds();
- soundManager.loadShortSound(context, R.raw.bmx_cheering);
- soundManager.loadShortSound(context, R.raw.jumping_jump);
- soundManager.loadShortSound(context, R.raw.present_throw_character_appear);
- soundManager.loadShortSound(context, R.raw.running_foot_loop_fast, true, 2f);
- soundManager.loadShortSound(context, R.raw.running_foot_power_up);
- soundManager.loadShortSound(context, R.raw.running_foot_power_up_fast, false, 0.4f);
- soundManager.loadShortSound(context, R.raw.running_foot_power_squish);
- soundManager.loadShortSound(context, R.raw.present_throw_block);
- }
-
- @Override
- protected void replay() {
- super.replay();
- model.gameReplay();
- }
-
- private Drawable getShareImageDrawable(int starCount) {
- return ContextCompat.getDrawable(getActivity(), R.drawable.winner);
- }
-
- @Override
- protected void onDestroyHelper() {
- if (gameView != null) {
- gameView.setModel(null);
- }
- model = null;
- }
-
- @Override
- public void onEventReceived(int type, Object data) {
- if (isDestroyed) {
- return;
- }
- if (type == EventBus.PLAY_SOUND && soundManager != null) {
- int resId = (int) data;
- soundManager.play(resId);
- } else if (type == EventBus.PAUSE_SOUND && soundManager != null) {
- int resId = (int) data;
- soundManager.pause(resId);
- } else if (type == EventBus.MUTE_SOUNDS && soundManager != null) {
- boolean shouldMute = (boolean) data;
- if (shouldMute) {
- soundManager.mute();
- } else {
- soundManager.unmute();
- }
- } else if (type == EventBus.GAME_LOADED) {
- mIsGameOver = false;
- long loadTimeMs = (long) data;
- model.titleDurationMs = Math.max(0, model.titleDurationMs - loadTimeMs);
- Log.d(TAG, "Waiting " + model.titleDurationMs + "ms and then hiding title.");
- }
- }
-
- public boolean onTouchEvent(MotionEvent event) {
- if (model != null) {
- model.touch(event);
- return true;
- }
- return false;
- }
-
- @Override
- protected void resume() {
- super.resume();
- if (uiRefreshHandler != null) {
- uiRefreshHandler.start(gameView);
- }
- }
-
- private void updateShareText() {
- }
-
- @Override
- protected String getGameType() {
- return RUNNING_GAME_TYPE;
- }
-
- @Override
- protected float getScore() {
- if (model == null) {
- return 0;
- }
- return model.getScore();
- }
-
- @Override
- protected int getShareImageId() {
- return scoreView.getStarCount();
- }
-
- public boolean isGameOver() {
- return mIsGameOver;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitLevel.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitLevel.java
deleted file mode 100644
index 3adecbd55..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitLevel.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.pursuit;
-
-import android.content.res.Resources;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * PursuitLevel is a programmatic representation of a .mp file which contains rows of power ups.
- * Lanes devoid of power ups are represented by '.'.
- * A power up is represented by 1.
- * Example: '.1.' is equivalent to a row with a power up in the middle lane.
- */
-public class PursuitLevel {
- private List level;
-
- public PursuitLevel(int resId, Resources resources) {
- level = new ArrayList<>();
- BufferedReader reader = new BufferedReader(
- new InputStreamReader(resources.openRawResource(resId)));
- try {
- String line = reader.readLine();
- while (line != null) {
- level.add(line.toCharArray());
- line = reader.readLine();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- public char[] getRowArray(int row) {
- return level.get(row % level.size());
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitModel.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitModel.java
deleted file mode 100644
index 7af7e0c04..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitModel.java
+++ /dev/null
@@ -1,1244 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.pursuit;
-
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.os.Build;
-import android.os.Vibrator;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.widget.TextView;
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.pursuit.RunnerActor.RunnerState;
-import com.google.android.apps.santatracker.doodles.pursuit.RunnerActor.RunnerType;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.ActorHelper;
-import com.google.android.apps.santatracker.doodles.shared.ActorTween;
-import com.google.android.apps.santatracker.doodles.shared.ActorTween.Callback;
-import com.google.android.apps.santatracker.doodles.shared.AndroidUtils;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.Camera;
-import com.google.android.apps.santatracker.doodles.shared.CameraShake;
-import com.google.android.apps.santatracker.doodles.shared.EmptyTween;
-import com.google.android.apps.santatracker.doodles.shared.EventBus;
-import com.google.android.apps.santatracker.doodles.shared.FakeButtonActor;
-import com.google.android.apps.santatracker.doodles.shared.GameFragment;
-import com.google.android.apps.santatracker.doodles.shared.Interpolator;
-import com.google.android.apps.santatracker.doodles.shared.RectangularInstructionActor;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-import com.google.android.apps.santatracker.doodles.shared.Tween;
-import com.google.android.apps.santatracker.doodles.shared.TweenManager;
-import com.google.android.apps.santatracker.doodles.shared.UIUtil;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import java.text.NumberFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * All the game logic for the second version of the running game. Mostly consists of managing
- * a list of Actors.
- */
-public class PursuitModel {
- private static final String TAG = PursuitModel.class.getSimpleName();
-
- /**
- * High-level phases of the game are controlled by a state machine which uses these states.
- */
- public enum State {
- TITLE,
- SETUP,
- READY,
- RUNNING,
- SUCCESS,
- FAIL,
- }
-
- /**
- * Will be notified when game enters a new state.
- */
- public interface StateChangedListener {
- void onStateChanged(State state);
- }
-
- /**
- * Will be notified when the score changes.
- */
- public interface ScoreListener {
- void newScore(float timeInSeconds);
- }
-
- /**
- * The other fruit the player is racing against.
- */
- public class OpponentActor extends RunnerActor {
- // Flag that indicates whether the opponent has crossed the finishing line.
- // Used for decelerating the opponent.
- public boolean isFinished;
-
- OpponentActor(Resources resources, RunnerType type, int lane) {
- super(resources, type, lane);
- isFinished = false;
- }
- }
-
- // Game-world uses a fixed coordinate system which, for simplicity, matches the resolution of the
- // art assets (i.e. 1 pixel in the art == 1 game-world unit). Measurements from art assets
- // directly translate to game-world units. Example: player1.png's racket is 57 pixels from the
- // bottom, so if the ball is 57 game-world units above the bottom of player's sprite, the ball is
- // at the same height as the racket). The view is responsible for scaling to fit the screen.
-
- // Visualization Constants
- public static final int HEIGHT = 960;
- public static final int WIDTH = 540;
- public static final int HALF_WIDTH = WIDTH / 2;
- public static final int HALF_HEIGHT = HEIGHT / 2;
- public static final int EXPECTED_PLAYER_WIDTH = (int) (WIDTH * 0.14f);
-
- // UI Constants
- private static final float BUTTON_SCALE = 0.8f;
- private static final float TUTORIAL_DURATION = 4f;
- private static final int COUNTDOWN_LABEL_COLOR = 0xffffbb39;
-
- // High level Android variables that lays the foundation for the game.
- private final Resources resources;
- private final TweenManager tweenManager = new TweenManager();
- private final Vibrator vibrator;
- private final EventBus eventBus;
- private final Locale locale;
-
- // Temporary Power-up generation variables that will be replaced by a more robust system.
- private float powerUpTimer;
- private int mapRow = 0;
- private PursuitLevel map;
-
- // Gameplay Constants.
-
- // Currently this game is divided into lanes.
- // Power ups are added at the center of each discrete lane.
- // As the game speed is increased, more lanes are added, increasing the gameplay area.
- public static final int NUM_LANES = 3;
- public static final int MIN_LANE = 0;
- public static final int MAX_LANE = NUM_LANES - 1;
- public static final int INITIAL_LANE = NUM_LANES / 2;
- public static final int LANE_SIZE = WIDTH / (NUM_LANES + 2);
-
- // All duration in seconds.
- private static final float LANE_SWITCH_DURATION = 0.10f;
- public static final float RUNNER_ENTER_DURATION = 1.72f;
- private static final float RUNNER_STANDING_DURATION = 0.5f;
- private static final float SETUP_DURATION =
- RUNNER_ENTER_DURATION + RUNNER_STANDING_DURATION + 0.6f;
-
- // Speed constants.
- public static final float BASE_SPEED = 340f;
-
- private static final float PLAYER_MINIMUM_SPEED = BASE_SPEED;
- private static final float PLAYER_MAXIMUM_SPEED = BASE_SPEED + 450f;
- private static final float PLAYER_DECELERATION = 300f;
-
- private static final float MANGO_SPEED = BASE_SPEED + 250f;
- private static final float GRAPE_SPEED = BASE_SPEED + 315f;
- private static final float APRICOT_SPEED = BASE_SPEED + 280f;
-
- private static final float WATERMELON_MINIMUM_SPEED = PLAYER_MINIMUM_SPEED + 100f;
- private static final float WATERMELON_MAXIMUM_SPEED = PLAYER_MAXIMUM_SPEED + 25f;
- private static final float WATERMELON_SPEED_INCREASE_PER_SECOND = 2.5f;
- private static final float WATERMELON_SPEED_DECREASE_PER_SECOND = 6f;
-
- private static final float OPPONENT_SLOW_DOWN_DURATION = 0.24f;
- private static final float OPPONENT_SLOW_DOWN_AMOUNT = 450f;
-
- private static final float RUNNER_FINISH_DURATION = 5f;
- private static final float WATERMELON_FINISH_DURATION = 4f;
-
- // Size constants.
- private static final float STRAWBERRY_RADIUS = 15f;
- private static final float MANGO_RADIUS = 28f;
- private static final float GRAPE_RADIUS = 12f;
- private static final float APRICOT_RADIUS = 22f;
-
- // Misc. constants.
- private static final float POWER_UP_SPEED_BOOST = 320f;
- private static final float TOTAL_DISTANCE = 16000f;
- private static final int VIBRATION_MS = 60;
-
- // Positional constants.
- private static final float PLAYER_INITIAL_POSITION_Y = HEIGHT * 0.25f;
- private static final float OPPONENT_INITIAL_POSITION_Y = HEIGHT * 0.4f;
- private static final float RIBBON_INITIALIZATION_POSITION_Y
- = PLAYER_INITIAL_POSITION_Y + TOTAL_DISTANCE;
-
- // Camera constants.
- private static final float CAMERA_LOOKAHEAD_INCREASE_PER_SECOND = 0.8f;
- private static final float CAMERA_LOOKAHEAD_DECREASE_PER_SECOND = 2.5f;
- private static final float CAMERA_MAXIMUM_LOOKAHEAD = -(HEIGHT * 0.25f);
-
- // Gameplay variables.
- private float baseScale;
- private State state;
- private float laneSwitchTimer = 0f;
- private float cameraLookAhead; // The camera trails behind the player when player is going fast.
- private boolean watermelonHasEntered = false;
- private boolean playerHasStarted = false;
-
- // Player performance variables.
- private float time;
- private int finishingPlace; // First place = 1.
-
- // Listeners.
- private StateChangedListener stateListener;
- private ScoreListener scoreListener;
-
- // Public so view can render the actors.
- public final List actors = Collections.synchronizedList(new ArrayList());
- public final List ui = Collections.synchronizedList(new ArrayList());
-
- private final List powerUps = new ArrayList();
- private final List opponents = new ArrayList();
-
- public final CameraShake cameraShake; // Public so view can read the amount of shake.
- public final Camera camera;
-
- // Actors.
- public PlayerActor player;
- public OpponentActor mango;
- public OpponentActor grape;
- public OpponentActor apricot;
-
- public WatermelonActor watermelon;
- public RibbonActor ribbon;
-
- // UI Buttons.
- public FakeButtonActor leftButton;
- public FakeButtonActor rightButton;
- private RectangularInstructionActor instructions;
- private TextView countdownView;
-
- // Background
- public BackgroundActor backgroundActor;
-
- // public so that the GameFragment can update the duration.
- public long titleDurationMs = GameFragment.TITLE_DURATION_MS;
-
- public PursuitModel(Resources resources, Context context) {
- this.resources = resources;
-
- eventBus = EventBus.getInstance();
-
- vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- locale = resources.getConfiguration().locale;
-
- cameraShake = new CameraShake();
- camera = new Camera(WIDTH, HEIGHT);
- camera.position.x = -HALF_WIDTH;
-
- map = new PursuitLevel(R.raw.running, resources);
-
- initActors();
- showTitle();
- }
-
- private void initActors() {
- // Initialize background
- backgroundActor = new BackgroundActor(resources, camera);
- backgroundActor.scale = 1.15f;
-
- player = new PlayerActor(resources, INITIAL_LANE);
- player.setRadius(STRAWBERRY_RADIUS);
- ribbon = new RibbonActor(0, RIBBON_INITIALIZATION_POSITION_Y, resources);
- watermelon = new WatermelonActor(resources);
-
- // Scale actors to fit screen
- baseScale = (1f * EXPECTED_PLAYER_WIDTH) / player.currentSprite.frameWidth;
-
- player.scale = baseScale * 1.07f;
- watermelon.scale = baseScale * 1.6f;
- ribbon.scale = baseScale * 1.03f;
-
- // Add the essential actors to screen
- synchronized (actors) {
- actors.add(player);
- actors.add(ribbon);
- actors.add(watermelon);
- }
-
- // Add the opponents
- mango = addOpponent(RunnerType.MANGO, 0, OPPONENT_INITIAL_POSITION_Y, MANGO_RADIUS);
- grape = addOpponent(RunnerType.GRAPE, 1, OPPONENT_INITIAL_POSITION_Y, GRAPE_RADIUS);
- apricot = addOpponent(RunnerType.APRICOT, 2, OPPONENT_INITIAL_POSITION_Y, APRICOT_RADIUS);
-
- AnimatedSprite runningTutorialSprite =
- AnimatedSprite.fromFrames(resources, Sprites.tutorial_running);
- runningTutorialSprite.setFPS(7);
-
- // Initialize and add the UI actors
- instructions = new RectangularInstructionActor(resources, runningTutorialSprite);
- instructions.hidden = true;
-
- instructions.scale = 0.54f;
- instructions.position.set(HALF_WIDTH - instructions.getScaledWidth() / 2,
- HEIGHT * 0.65f - instructions.getScaledHeight() / 2);
-
- leftButton = new FakeButtonActor(
- AnimatedSprite.fromFrames(resources, Sprites.running_button));
- leftButton.rotation = -(float) Math.PI / 2;
- leftButton.sprite.setFPS(12);
- leftButton.scale = BUTTON_SCALE;
- leftButton.position.x = WIDTH * 0.22f - (leftButton.sprite.frameWidth * BUTTON_SCALE / 2);
- leftButton.position.y = HEIGHT - 20;
- leftButton.alpha = 0;
-
- rightButton = new FakeButtonActor(
- AnimatedSprite.fromFrames(resources, Sprites.running_button));
- rightButton.rotation = (float) Math.PI / 2;
- rightButton.sprite.setFPS(12);
- rightButton.scale = BUTTON_SCALE;
- rightButton.position.x = WIDTH * 0.78f + (rightButton.sprite.frameWidth * BUTTON_SCALE / 2);
- rightButton.position.y = HEIGHT - (rightButton.sprite.frameHeight * BUTTON_SCALE) - 20;
- rightButton.alpha = 0;
-
- ui.add(instructions);
- ui.add(leftButton);
- ui.add(rightButton);
- }
-
- // Resets the gameplay variables for a replay or a first play.
- private void resetGame() {
- Log.i(TAG, "Reset game.");
-
- camera.position.y = 0;
-
- laneSwitchTimer = 0;
-
- player.setRunnerState(RunnerState.CROUCH);
- player.setSweat(false);
- player.setCelebrate(false);
-
- mango.setRunnerState(RunnerState.CROUCH);
- mango.isFinished = false;
- grape.setRunnerState(RunnerState.CROUCH);
- grape.isFinished = false;
- apricot.setRunnerState(RunnerState.CROUCH);
- apricot.isFinished = false;
-
- tweenManager.removeAll();
-
- watermelonHasEntered = false;
- playerHasStarted = false;
- cameraLookAhead = 0;
-
- watermelon.position.y = -HEIGHT * 0.5f;
- watermelon.velocity.y = 0;
-
- player.position.x = 0;
- player.position.y = PLAYER_INITIAL_POSITION_Y;
- player.velocity.y = 0;
-
- ribbon.position.y = RIBBON_INITIALIZATION_POSITION_Y;
-
- synchronized (actors) {
- for (PowerUpActor powerUp : powerUps) {
- actors.remove(powerUp);
- }
- }
- powerUps.clear();
-
- mango.position.y = OPPONENT_INITIAL_POSITION_Y;
- grape.position.y = OPPONENT_INITIAL_POSITION_Y;
- apricot.position.y = OPPONENT_INITIAL_POSITION_Y;
-
- mango.velocity.y = 0;
- grape.velocity.y = 0;
- apricot.velocity.y = 0;
-
- leftButton.alpha = 1;
- rightButton.alpha = 1;
-
- powerUpTimer = 0;
- mapRow = 0;
-
- time = 0;
- finishingPlace = 0;
- updateScore();
-
- eventBus.sendEvent(EventBus.PAUSE_SOUND, R.raw.running_foot_loop_fast);
- }
-
- private void beginGame() {
- mango.setRunnerState(RunnerState.RUNNING);
- grape.setRunnerState(RunnerState.RUNNING);
- apricot.setRunnerState(RunnerState.RUNNING);
-
- player.velocity.y = 0;
- watermelon.velocity.y = PLAYER_MAXIMUM_SPEED;
-
- tweenManager.add(new Tween(0.6f) {
- @Override
- protected void updateValues(float percentDone) {
- mango.velocity.y = percentDone * MANGO_SPEED;
- grape.velocity.y = percentDone * GRAPE_SPEED;
- apricot.velocity.y = percentDone * APRICOT_SPEED;
- }
- });
-
- eventBus.sendEvent(EventBus.PLAY_SOUND, R.raw.running_foot_loop_fast);
- eventBus.sendEvent(EventBus.PLAY_SOUND, R.raw.running_foot_power_up);
- }
-
- public void update(float deltaMs) {
- synchronized (this) {
- float timeInSeconds = deltaMs / 1000f;
-
- camera.update(deltaMs);
- cameraShake.update(deltaMs);
-
- if (state == State.RUNNING) {
- if (!(player.getRunnerState() == RunnerState.RUNNING_LEFT
- || player.getRunnerState() == RunnerState.RUNNING_RIGHT)) {
- player.setRunnerState(RunnerState.RUNNING);
- }
-
- if (player.getRunnerState() == RunnerState.RUNNING) {
- updatePlayerSpeed(timeInSeconds);
- }
- }
-
- if (state != State.TITLE) {
- for (int i = 0; i < actors.size(); i++) {
- actors.get(i).update(deltaMs);
- }
-
- for (int i = 0; i < ui.size(); i++) {
- ui.get(i).update(deltaMs);
- }
- }
-
- tweenManager.update(deltaMs);
- synchronized (actors) {
- Collections.sort(actors);
- }
-
- laneSwitchTimer = Math.max(0, laneSwitchTimer - timeInSeconds);
-
- if (state == State.RUNNING || (state == State.SUCCESS && finishingPlace == 1)) {
- updateRunningCamera(timeInSeconds);
- }
-
- if (state == State.RUNNING) {
- // Reads the next row of power ups from PursuitLevel and adds them,
- // Then resets the timer.
- powerUpTimer -= timeInSeconds;
- if (powerUpTimer <= 0) {
- powerUpTimer = 0.18f - 0.06f * (Math.max(0, player.velocity.y) / PLAYER_MAXIMUM_SPEED);
- addPowerUpRow();
- mapRow++;
- }
- checkPowerUps();
-
- updateWatermelon(timeInSeconds);
-
- // If player is getting close to the watermelon, make the sprite sweat.
- if (player.position.y - player.getRadius()
- < watermelon.position.y + WatermelonActor.VERTICAL_RADIUS_WORLD * 2.7f) {
- player.setSweat(true);
- } else {
- player.setSweat(false);
- }
-
- // Check success and fail states based on player's location
- if (watermelonCollidesWithRunner(player)) {
- gameFail();
- } else if (player.position.y > ribbon.position.y) {
- gameSuccess();
- }
-
- // Update the score
- time += timeInSeconds;
- updateScore();
- }
-
- checkOpponentsPlayerCollision();
- checkOpponentsFinished();
- checkOpponentsWatermelonCollision();
- }
- }
-
- // If the state is running, update the camera according to the player's position and speed.
- // If the player is sufficiently fast, the camera trails behind the player a little.
- private void updateRunningCamera(float timeInSeconds) {
- float targetCameraLookahead =
- CAMERA_MAXIMUM_LOOKAHEAD * Math.max(0, player.velocity.y - PLAYER_MINIMUM_SPEED)
- / (PLAYER_MAXIMUM_SPEED - PLAYER_MINIMUM_SPEED);
- float deltaCameraLookahead = (targetCameraLookahead - cameraLookAhead);
- if (deltaCameraLookahead > 0) {
- deltaCameraLookahead *= CAMERA_LOOKAHEAD_DECREASE_PER_SECOND * timeInSeconds;
- } else {
- deltaCameraLookahead *= CAMERA_LOOKAHEAD_INCREASE_PER_SECOND * timeInSeconds;
- }
-
- cameraLookAhead += deltaCameraLookahead;
- camera.position.y = -PLAYER_INITIAL_POSITION_Y + cameraLookAhead + player.position.y;
- }
-
- // Decelerates the player.
- private void updatePlayerSpeed(float timeInSeconds) {
- if (!playerHasStarted) {
- player.velocity.y += timeInSeconds * PLAYER_MINIMUM_SPEED * 1.7f;
- if (player.velocity.y > PLAYER_MINIMUM_SPEED) {
- playerHasStarted = true;
- }
- } else {
- player.velocity.y = player.velocity.y - PLAYER_DECELERATION * timeInSeconds;
- // Give speed lower and upper bound
- player.velocity.y = Math.max(player.velocity.y, PLAYER_MINIMUM_SPEED);
- player.velocity.y = Math.min(player.velocity.y, PLAYER_MAXIMUM_SPEED);
- }
- }
-
- // Changes the watermelon's speed to roughly match the player's.
- private void updateWatermelon(float timeInSeconds) {
- float targetSpeed = BASE_SPEED + ((player.velocity.y - BASE_SPEED) * 1.08f);
- // Give speed lower and upper bound
- targetSpeed = Math.max(targetSpeed, WATERMELON_MINIMUM_SPEED);
- targetSpeed = Math.min(targetSpeed, WATERMELON_MAXIMUM_SPEED);
-
- float targetSpeedDelta = targetSpeed - watermelon.velocity.y;
-
- float watermelonPlayerDistance =
- (player.position.y - player.getRadius())
- - (watermelon.position.y + WatermelonActor.VERTICAL_RADIUS_WORLD);
-
- if (targetSpeedDelta < 0 || !watermelonHasEntered) {
- // The watermelon decrease speed is inversely proportional to the distance between the
- // watermelon and the player. The closer the player the larger the decrease.
- float decreaseFactor = 1 + ((1 / Math.max(1, watermelonPlayerDistance / 40)) * 7f);
- float decreaseSpeed = WATERMELON_SPEED_DECREASE_PER_SECOND * decreaseFactor;
- watermelon.velocity.y += targetSpeedDelta * Math.min(1, decreaseSpeed * timeInSeconds);
- } else if (targetSpeedDelta > 0) {
- // The watermelon increase speed is proportional to the distance between the watermelon and
- // the player. The further the player the larger the increase.
- // The watermelon decrease speed is also affected by the amount of time played. The longer
- // the time the greater the increase.
- float increaseFactor = Math.max(0.6f, 1 + ((watermelonPlayerDistance - 140) / 240));
- float timeFactor = 0.3f + (0.7f * Math.min(1, time / 20f));
- float increaseSpeed = WATERMELON_SPEED_INCREASE_PER_SECOND * increaseFactor * timeFactor;
- watermelon.velocity.y += targetSpeedDelta * Math.min(1, increaseSpeed * timeInSeconds);
- }
-
- if (watermelon.position.y > camera.position.y) {
- watermelonHasEntered = true;
- }
-
- if (watermelonHasEntered) {
- watermelon.position.y = Math.max(watermelon.position.y, camera.position.y);
- watermelon.position.y = Math.min(watermelon.position.y, camera.position.y
- - CAMERA_MAXIMUM_LOOKAHEAD * 1.13f);
- }
- }
-
- private void updateScore() {
- scoreListener.newScore(time);
- }
-
- // Check power ups for collision with the player.
- private void checkPowerUps() {
- for (int i = powerUps.size() - 1; i >= 0; i--) {
- PowerUpActor powerUp = powerUps.get(i);
-
- // Check if PowerUp collides with the Player.
- double powerUpDistance = ActorHelper.distanceBetween(player, powerUp);
- if (powerUpDistance < (PowerUpActor.RADIUS_WORLD + player.getRadius())
- && !powerUp.isPickedUp()) {
- powerUpPlayer(powerUp);
- }
- }
- }
-
- // Checks if any of the opponents collide with the player.
- // If there is a collision, set the player's position to the runner's.
- private void checkOpponentsPlayerCollision() {
- // If the player is currently moving just ignore any collision.
- if (laneSwitchTimer > 0) {
- return;
- }
-
- for (OpponentActor opponent : opponents) {
- if (opponent.getLane() == player.getLane()) {
- float diffY = player.position.y - opponent.position.y;
- float combinedRadius = opponent.getRadius() + player.getRadius();
-
- if (0 <= diffY && diffY < combinedRadius) {
- // Player is in front of opponent
- opponent.position.y =
- player.position.y - combinedRadius;
- } else if (-combinedRadius < diffY && diffY < 0) {
- // Player is behind the opponent
- player.position.y =
- opponent.position.y - combinedRadius;
- }
- }
- }
- }
-
- // Checks if any of the opponents are touching the watermelon. If so, squish the runner.
- private void checkOpponentsWatermelonCollision() {
- for (OpponentActor opponent : opponents) {
- if (opponent.getRunnerState() == RunnerState.RUNNING
- && watermelonCollidesWithRunner(opponent)) {
- eventBus.sendEvent(EventBus.PLAY_SOUND, R.raw.running_foot_power_squish);
- opponent.setRunnerState(RunnerState.DYING);
- opponent.velocity.y = 0;
- }
- }
- }
-
- private void checkOpponentsFinished() {
- for (final OpponentActor opponent : opponents) {
- if (opponent.position.y > ribbon.position.y) {
- // The opponent crosses the finish line, break the ribbon and decelerate the opponent.
-
- if (!opponent.isFinished) {
- opponent.isFinished = true;
-
- final float initialSpeed = opponent.velocity.y;
- tweenManager.add(new Tween(RUNNER_FINISH_DURATION) {
- @Override
- protected void updateValues(float percentDone) {
- if (opponent.state == RunnerState.RUNNING) {
- opponent.velocity.y = (1 - percentDone) * initialSpeed;
- if (percentDone > 0.95f) {
- opponent.setRunnerState(RunnerState.STANDING);
- }
- } else {
- opponent.velocity.y = 0;
- }
- }
- });
- }
- }
- }
- }
-
- // TODO: Replace with an ellipse to circle collision model.
- public boolean watermelonCollidesWithRunner(RunnerActor actor) {
- if (actor.position.y - actor.getRadius()
- < watermelon.position.y + WatermelonActor.VERTICAL_RADIUS_WORLD
- && actor.getLane() != INITIAL_LANE) {
- return true;
- }
- return actor.position.y - actor.getRadius()
- < watermelon.position.y + WatermelonActor.VERTICAL_RADIUS_WORLD * 1.2f
- && actor.getLane() == INITIAL_LANE;
- }
-
- private void gameSetup() {
- Log.i(TAG, "Game Mode: Setup.");
- setState(State.SETUP);
-
- player.position.y = PLAYER_INITIAL_POSITION_Y;
- mango.position.y = OPPONENT_INITIAL_POSITION_Y;
- grape.position.y = OPPONENT_INITIAL_POSITION_Y;
- apricot.position.y = OPPONENT_INITIAL_POSITION_Y;
-
- mango.setRunnerState(RunnerState.STANDING);
- grape.setRunnerState(RunnerState.STANDING);
- apricot.setRunnerState(RunnerState.STANDING);
-
- final Tween mangoCrouchDelay = new EmptyTween(0.6f) {
- @Override
- protected void onFinish() {
- mango.setRunnerState(RunnerState.CROUCH);
- }
- };
- tweenManager.add(mangoCrouchDelay);
-
- final Tween grapeCrouchDelay = new EmptyTween(0.9f) {
- @Override
- protected void onFinish() {
- grape.setRunnerState(RunnerState.CROUCH);
- }
- };
- tweenManager.add(grapeCrouchDelay);
-
- final Tween apricotCrouchDelay = new EmptyTween(1.2f) {
- @Override
- protected void onFinish() {
- apricot.setRunnerState(RunnerState.CROUCH);
- }
- };
- tweenManager.add(apricotCrouchDelay);
-
- runnerEnter(player);
-
- watermelon.position.y = HEIGHT * -0.5f;
-
- final Tween setupDuration = new EmptyTween(SETUP_DURATION) {
- @Override
- protected void onFinish() {
- if (state == State.SETUP) {
- gameFirstPlay();
- }
- }
- };
- tweenManager.add(setupDuration);
- }
-
- private void gameFirstPlay() {
- Log.i(TAG, "Game Mode: Tutorial.");
- instructions.show();
- tweenManager.add(new EmptyTween(TUTORIAL_DURATION) {
- @Override
- protected void onFinish() {
- instructions.hide();
- }
- });
- tweenManager.add(new EmptyTween(TUTORIAL_DURATION + 0.3f) {
- @Override
- protected void onFinish() {
- startCountdownAnimation();
- }
- });
- tweenManager.add(new EmptyTween(TUTORIAL_DURATION + 0.3f + 3f) {
- @Override
- protected void onFinish() {
- gameStart();
- countdownView.post(new Runnable() {
- @Override
- public void run() {
- countdownView.setVisibility(View.INVISIBLE);
- }
- });
- }
- });
- player.setLane(INITIAL_LANE);
- showButtons();
- setState(State.READY);
- }
-
- public void gameReplay() {
- Log.i(TAG, "Game Restart.");
- resetGame();
- instructions.hide(); // In case the player hits the replay button before the game begins.
- tweenManager.add(new EmptyTween(0.2f) {
- @Override
- protected void onFinish() {
- startCountdownAnimation();
- }
- });
- tweenManager.add(new EmptyTween(0.2f + 3f) {
- @Override
- protected void onFinish() {
- gameStart();
- countdownView.post(new Runnable() {
- @Override
- public void run() {
- countdownView.setVisibility(View.INVISIBLE);
- }
- });
- }
- });
- player.setLane(INITIAL_LANE);
- setState(State.READY);
- }
-
- private void gameStart() {
- Log.i(TAG, "Game Start.");
- beginGame();
- setState(State.RUNNING);
- }
-
- private void gameSuccess() {
- Log.i(TAG, "You're winner!");
- // Make the watermelon match the player's speed so the player won't get squished.
-
- hideButtons();
-
- int runnersBehindPlayer = 0;
- for (RunnerActor opponent : opponents) {
- if (player.position.y >= opponent.position.y) {
- runnersBehindPlayer++;
- }
- }
- finishingPlace = 4 - runnersBehindPlayer;
-
- if (finishingPlace == 1) {
- // If the player gets first place, stop the watermelon and decelerate the player.
-
- // Play the strawberry's celebration animation.
- player.setCelebrate(true);
- final float currentSpeed = player.velocity.y;
- final float targetSpeed = BASE_SPEED * 0.75f;
-
- tweenManager.add(new Tween(RUNNER_FINISH_DURATION) {
- @Override
- protected void updateValues(float percentDone) {
- float diffSpeed = targetSpeed - currentSpeed;
- player.velocity.y = currentSpeed + (diffSpeed * percentDone);
- }
- });
-
- tweenManager.add(new Tween(WATERMELON_FINISH_DURATION) {
- @Override
- protected void updateValues(float percentDone) {
- watermelon.velocity.y = currentSpeed * (1 - percentDone);
- }
- });
- } else {
- // If the player does not get first place, stop both the watermelon and the player.
-
- // If the player finishes fourth, make the strawberry cry.
- if (finishingPlace == 4) {
- player.setSweat(true);
- }
-
- final float currentSpeed = player.velocity.y;
-
- tweenManager.add(new Tween(WATERMELON_FINISH_DURATION * 0.5f) {
- @Override
- protected void updateValues(float percentDone) {
- watermelon.velocity.y = currentSpeed * (1 - percentDone);
- }
- });
-
- tweenManager.add(new Tween(RUNNER_FINISH_DURATION) {
- @Override
- protected void updateValues(float percentDone) {
- if (player.getRunnerState() == RunnerState.RUNNING) {
- player.velocity.y = currentSpeed * (1 - percentDone);
- if (percentDone > 0.95f) {
- player.setRunnerState(RunnerState.STANDING);
- player.velocity.y = 0;
- }
- }
- }
- });
- }
-
- eventBus.sendEvent(EventBus.PLAY_SOUND, R.raw.bmx_cheering);
- eventBus.sendEvent(EventBus.PAUSE_SOUND, R.raw.running_foot_loop_fast);
-
- setState(State.SUCCESS);
- }
-
- private void gameFail() {
- // Change the player's appearance.
- player.setRunnerState(RunnerState.DYING);
-
- // Stops the player.
- player.velocity.y = 0;
-
- hideButtons();
-
- // Zoom the camera on the player.
- zoomCameraTo(-HALF_WIDTH, player.position.y - HEIGHT * 0.75f, camera.scale, 1.5f);
-
- eventBus.sendEvent(EventBus.PLAY_SOUND, R.raw.bmx_cheering);
- eventBus.sendEvent(EventBus.PAUSE_SOUND, R.raw.running_foot_loop_fast);
-
- vibrator.vibrate(VIBRATION_MS);
-
- setState(State.FAIL);
- }
-
- private void showButtons() {
- tweenManager.add(new ActorTween(leftButton).withAlpha(1).withDuration(0.5f));
- tweenManager.add(new ActorTween(rightButton).withAlpha(1).withDuration(0.5f));
- }
-
- private void hideButtons() {
- tweenManager.add(new ActorTween(leftButton).withAlpha(0).withDuration(0.5f));
- tweenManager.add(new ActorTween(rightButton).withAlpha(0).withDuration(0.5f));
- }
-
- private void startCountdownAnimation() {
- countdownView.post(new Runnable() {
- @Override
- public void run() {
- countdownView.setVisibility(View.VISIBLE);
- }
- });
- final NumberFormat numberFormatter = NumberFormat.getInstance(locale);
- countdownBump(numberFormatter.format(3));
- tweenManager.add(new EmptyTween(1) {
- @Override
- protected void onFinish() {
- countdownBump(numberFormatter.format(2));
- }
- });
- tweenManager.add(new EmptyTween(2) {
- @Override
- protected void onFinish() {
- countdownBump(numberFormatter.format(1));
- }
- });
- }
-
- private void countdownBump(final String text) {
- countdownView.post(new Runnable() {
- @Override
- public void run() {
- float countdownStartScale = countdownView.getScaleX() * 1.25f;
- float countdownEndScale = countdownView.getScaleX();
-
- countdownView.setVisibility(View.VISIBLE);
- countdownView.setText(text);
-
- if (!"Nexus 9".equals(Build.MODEL)) {
- ValueAnimator scaleAnimation = UIUtil.animator(200,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float scaleValue = (float) valueAnimator.getAnimatedValue("scale");
- countdownView.setScaleX(scaleValue);
- countdownView.setScaleY(scaleValue);
- }
- },
- UIUtil.floatValue("scale", countdownStartScale, countdownEndScale)
- );
- scaleAnimation.start();
- }
- }
- });
- }
-
- // Make a row of power ups using the current row from PursuitLevel.
- private void addPowerUpRow() {
- char[] row = map.getRowArray(mapRow);
- float rowPositionY = player.position.y + HEIGHT;
- for (int i = 0; i < row.length; i++) {
- if (row[i] == '1') {
- if (ribbon.position.y > rowPositionY) {
- addPowerUp(getLanePositionX(i), rowPositionY);
- }
- }
- }
- }
-
- private PowerUpActor addPowerUp(float x, float y) {
- PowerUpActor powerUp =
- new PowerUpActor(x, y, resources);
- powerUp.scale = baseScale;
- synchronized (actors) {
- actors.add(powerUp);
- }
- powerUps.add(powerUp);
- return powerUp;
- }
-
- // Creates and adds a OpponentActor to the list of all actors.
- private OpponentActor addOpponent(RunnerType type, int lane, float y, float radius) {
- OpponentActor opponent = makeOpponent(type, lane, y, radius);
- synchronized (actors) {
- actors.add(opponent);
- }
- opponents.add(opponent);
- return opponent;
- }
-
- // Creates a OpponentActor.
- private OpponentActor makeOpponent(RunnerType type, int lane, float y, float radius) {
- OpponentActor opponent = new OpponentActor(resources, type, lane);
- opponent.position.x = getLanePositionX(lane);
- opponent.position.y = y;
- opponent.scale = baseScale;
- opponent.setRadius(radius);
- return opponent;
- }
-
- // Wait for one second for the title to display and then starts the game.
- private void showTitle() {
- setState(State.TITLE);
- tweenManager.add(new EmptyTween(titleDurationMs / 1000.0f) {
- @Override
- protected void onFinish() {
- if (state == State.TITLE) {
- gameSetup();
- }
- }
- });
- }
-
- // Handles all incoming motion events. Currently just calls down() and up().
- public void touch(MotionEvent event) {
- final int action = event.getActionMasked();
-
- int index = event.getActionIndex();
- Point screenSize = AndroidUtils.getScreenSize();
- float touchX = (event.getX(index) / screenSize.x) * PursuitModel.WIDTH;
- float touchY = (event.getY(index) / screenSize.y) * PursuitModel.HEIGHT;
-
- Vector2D worldPos = camera.getWorldCoords(touchX, touchY);
-
- if (action == MotionEvent.ACTION_DOWN
- || action == MotionEvent.ACTION_POINTER_DOWN) {
- down(worldPos.x, worldPos.y);
- }
- }
-
- // Called every time the user touches down on the screen.
- // Currently does not support multi-touch.
- private void down(float touchX, float touchY) {
- Log.i(TAG, "Touch down at: " + touchX + ", " + touchY);
-
- if (state == State.RUNNING || state == State.READY) {
- int newLane = getLaneNumberFromPositionX(touchX);
- if (newLane > player.getLane() && player.getLane() < MAX_LANE) {
- rightButton.press();
- movePlayer(player.getLane() + 1);
- } else if (newLane < player.getLane() && player.getLane() > MIN_LANE) {
- leftButton.press();
- movePlayer(player.getLane() - 1);
- }
- }
- }
-
- private void movePlayer(final int newLane) {
- // The player cannot move if it is already moving.
- if (laneSwitchTimer > 0) {
- return;
- }
-
- // If an opponent and the player are expected to collide after the lane change, the player does
- // not move.
- // If the player is slightly in front of an opponent after the lane change, the player moves and
- // the opponent moves back slightly.
- for (RunnerActor opponent : opponents) {
- if (opponent.getLane() == newLane) {
- float targetOpponentY = opponent.position.y + opponent.velocity.y * LANE_SWITCH_DURATION;
- float targetPlayerY =
- player.position.y + (player.velocity.y * LANE_SWITCH_DURATION * 0.8f);
- float yDistance = targetOpponentY - targetPlayerY;
- float combinedRadius = opponent.getRadius() + player.getRadius();
-
- if (0.2f * combinedRadius < yDistance && yDistance < combinedRadius) {
- Log.i(TAG, "Cannot switch to lane " + newLane);
- eventBus.sendEvent(EventBus.PLAY_SOUND, R.raw.present_throw_block);
- bumpPlayer(opponent);
- return;
- } else if (-1.2f * combinedRadius < yDistance && yDistance <= 0.2f * combinedRadius) {
- Log.i(TAG, "Switching to lane " + newLane + " with slowdown");
- slowDownOpponent(opponent);
- }
- }
- }
-
- // Make the tween
- if (state == State.RUNNING) {
- tweenManager.add(new ActorTween(player)
- .toX(getLanePositionX(newLane))
- .withDuration(LANE_SWITCH_DURATION)
- .withInterpolator(Interpolator.LINEAR));
- laneSwitchTimer = LANE_SWITCH_DURATION;
- } else {
- // If the player is not currently running. Play the running left and right animation when
- // switching lanes.
- if (player.lane > newLane) {
- player.setRunnerState(RunnerState.RUNNING_LEFT);
- } else {
- player.setRunnerState(RunnerState.RUNNING_RIGHT);
- }
- tweenManager.add(new ActorTween(player)
- .toX(getLanePositionX(newLane))
- .withDuration(LANE_SWITCH_DURATION * 2f)
- .withInterpolator(Interpolator.LINEAR)
- .whenFinished(new Callback() {
- @Override
- public void call() {
- if (player.getRunnerState() == RunnerState.RUNNING_LEFT
- || player.getRunnerState() == RunnerState.RUNNING_RIGHT) {
- player.setRunnerState(RunnerState.CROUCH);
- } else if (player.getRunnerState() == RunnerState.CROUCH) {
- player.setRunnerState(RunnerState.RUNNING);
- }
- }
- }));
- laneSwitchTimer = LANE_SWITCH_DURATION * 2f;
- }
-
- player.setLane(newLane);
- eventBus.sendEvent(EventBus.PLAY_SOUND, R.raw.jumping_jump);
- }
-
- // Temporarily reduces the speed of an opponent so the player can go in front of it when changing
- // lanes.
- private void slowDownOpponent(final RunnerActor opponent) {
-
- final float initialSpeed = opponent.velocity.y;
- float targetOpponentY = opponent.position.y + opponent.velocity.y * LANE_SWITCH_DURATION;
- float targetPlayerY =
- player.position.y + (player.velocity.y * LANE_SWITCH_DURATION * 0.8f);
- float diffY = targetOpponentY - targetPlayerY;
- float combinedRadius = opponent.getRadius() + player.getRadius();
-
- // The further the opponent is head of the opponent, the greater the decrease in speed.
- final float slowDownAmount =
- OPPONENT_SLOW_DOWN_AMOUNT
- + (OPPONENT_SLOW_DOWN_AMOUNT * Math.max(-0.2f, (diffY / combinedRadius)));
-
- tweenManager.add(new Tween(OPPONENT_SLOW_DOWN_DURATION) {
- @Override
- protected void updateValues(float percentDone) {
- if (opponent.getRunnerState() == RunnerState.RUNNING) {
- float percentageSlowdown = 1 - percentDone;
- float speed =
- initialSpeed - (slowDownAmount * percentageSlowdown);
- opponent.velocity.y = speed;
- } else {
- opponent.velocity.y = 0;
- }
- }
- });
- }
-
- private void bumpPlayer(RunnerActor opponent) {
- float targetX = player.position.x + (opponent.position.x - player.position.x) * 0.25f;
- float endX = player.position.x;
-
- final ActorTween outTween = new ActorTween(player)
- .toX(endX)
- .withDuration(LANE_SWITCH_DURATION * 0.5f)
- .withInterpolator(Interpolator.EASE_IN_AND_OUT);
-
- final ActorTween inTween = new ActorTween(player)
- .toX(targetX)
- .withDuration(LANE_SWITCH_DURATION * 0.5f)
- .withInterpolator(Interpolator.EASE_IN_AND_OUT)
- .whenFinished(new Callback() {
- @Override
- public void call() {
- tweenManager.add(outTween);
- }
- });
- laneSwitchTimer = LANE_SWITCH_DURATION;
- tweenManager.add(inTween);
- }
-
- private ActorTween runnerEnter(final RunnerActor runner) {
- // Enter from the left.
- runner.setRunnerState(RunnerState.ENTERING);
- eventBus.sendEvent(EventBus.PLAY_SOUND, R.raw.present_throw_character_appear);
-
- // Stop and stand for 0.5 seconds.
- final Tween standingDelay = new EmptyTween(RUNNER_STANDING_DURATION) {
- @Override
- protected void onFinish() {
- runner.setRunnerState(RunnerState.CROUCH);
- }
- };
-
- ActorTween tween = new ActorTween(runner)
- .withDuration(RUNNER_ENTER_DURATION)
- .withInterpolator(Interpolator.LINEAR)
- .whenFinished(new Callback() {
- @Override
- public void call() {
- // Crouch down after standing still.
- runner.setRunnerState(RunnerState.STANDING);
- eventBus.sendEvent(EventBus.PAUSE_SOUND, R.raw.present_throw_character_appear);
- tweenManager.add(standingDelay);
- }
- });
-
- tweenManager.add(tween);
- return tween;
- }
-
- // Returns the world x of a lane.
- private int getLanePositionX(int lane) {
- return LANE_SIZE * (lane - INITIAL_LANE);
- }
-
- // Return the lane number corresponding to an area of the screen.
- // Warning: This function may return lane numbers that don't exist. Example: By clicking on the
- // area that is immediately left of lane 0, -1 is returned.
- private int getLaneNumberFromPositionX(float x) {
- int lane = INITIAL_LANE + Math.round(x / LANE_SIZE);
- Log.i(TAG, "Lane: " + lane);
- return lane;
- }
-
- // Called when the player picks up a power up.
- private void powerUpPlayer(PowerUpActor powerUpActor) {
- powerUpActor.pickUp();
- player.velocity.y += POWER_UP_SPEED_BOOST;
- eventBus.sendEvent(EventBus.PLAY_SOUND, R.raw.running_foot_power_up_fast);
- }
-
- public void setState(State newState) {
- Log.i(TAG, "State changed to " + newState);
- state = newState;
- if (stateListener != null) {
- stateListener.onStateChanged(newState);
- }
- }
-
- private void zoomCameraTo(float x, float y, float scale, float secondsDuration) {
- final float x1 = camera.position.x;
- final float y1 = camera.position.y;
- final float camScale1 = camera.scale;
- // Limit (x, y) so that the edge of the camera doesn't go past the normal edges (i.e. the edges
- // at scale==1).
- final float x2 = x;
- final float y2 = y;
- final float camScale2 = scale;
-
- tweenManager.add(new Tween(secondsDuration) {
- @Override
- protected void updateValues(float percentDone) {
- Interpolator interp = Interpolator.EASE_IN_AND_OUT;
-
- camera.position.x = interp.getValue(percentDone, x1, x2);
- camera.position.y = interp.getValue(percentDone, y1, y2);
-
- // Tween the height, then use that to calculate scale, because tweening scale
- // directly leads to a wobbly pan (scale isn't linear).
- float height1 = HEIGHT / camScale1;
- float height2 = HEIGHT / camScale2;
- camera.scale = HEIGHT / interp.getValue(percentDone, height1, height2);
- }
- });
- }
-
- public void setStateListener(StateChangedListener stateListener) {
- this.stateListener = stateListener;
- }
-
- public void setScoreListener(ScoreListener listener) {
- this.scoreListener = listener;
- updateScore();
- }
-
- public void setCountdownView(TextView countdownView) {
- this.countdownView = countdownView;
- }
-
- public float getScore() {
- return time; // Milliseconds
- }
-
- public int getFinishingPlace() {
- return finishingPlace;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitView.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitView.java
deleted file mode 100644
index d9bd15acf..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/PursuitView.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.pursuit;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.View;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-
-/**
- * Handles rendering for the second version of the running game.
- */
-public class PursuitView extends View {
- private static final String TAG = PursuitView.class.getSimpleName();
-
- private PursuitModel model;
- private float currentScale;
- private float currentOffsetX = 0; // In game units
- private float currentOffsetY = 0;
-
- public PursuitView(Context context) {
- this(context, null);
- }
-
- public PursuitView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PursuitView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public void setModel(PursuitModel model) {
- this.model = model;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (model == null) {
- return;
- }
- synchronized (model) {
- super.onDraw(canvas);
- canvas.save();
-
- // Fit-to-screen & center.
- currentScale = Math.min(canvas.getWidth() / (float) PursuitModel.WIDTH,
- canvas.getHeight() / (float) PursuitModel.HEIGHT);
-
- currentOffsetX = (canvas.getWidth() / currentScale - PursuitModel.WIDTH) / 2;
- currentOffsetY = (canvas.getHeight() / currentScale - PursuitModel.HEIGHT) / 2;
-
- canvas.scale(currentScale * model.camera.scale, currentScale * model.camera.scale);
- canvas.translate(currentOffsetX - model.cameraShake.position.x - model.camera.position.x,
- currentOffsetY - model.cameraShake.position.y - model.camera.position.y);
-
- // Draws the beach and the sidewalk.
- model.backgroundActor.draw(canvas);
-
- // Draws the actors
- synchronized (model.actors) {
- for (int i = 0; i < model.actors.size(); i++) {
- Actor actor = model.actors.get(i);
- if (!actor.hidden) {
- actor.draw(canvas);
- }
- }
- }
-
- // Draws tree, umbrellas and their shadows.
- model.backgroundActor.drawTop(canvas);
-
- canvas.restore();
- canvas.save();
-
- canvas.scale(currentScale * model.camera.scale, currentScale * model.camera.scale);
- canvas.translate(currentOffsetX, currentOffsetY);
-
- // Draws the UI
- for (int i = 0; i < model.ui.size(); i++) {
- Actor actor = model.ui.get(i);
- if (!actor.hidden) {
- actor.draw(canvas);
- }
- }
-
- canvas.restore();
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/RibbonActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/RibbonActor.java
deleted file mode 100644
index 5064afa4d..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/RibbonActor.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.pursuit;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-
-/**
- * The breakable ribbon that acts as the finish line for the running game.
- */
-public class RibbonActor extends Actor {
-
- private static final int Z_INDEX = 0;
-
- // Assets.
- private AnimatedSprite sprite;
-
- public RibbonActor(float x, float y, Resources resources) {
- zIndex = Z_INDEX;
-
- position.x = x;
- position.y = y;
-
- sprite = AnimatedSprite.fromFrames(resources, Sprites.running_finish_line);
-
- sprite.setAnchor(sprite.frameWidth / 2,
- sprite.frameHeight / 2);
-
- sprite.setPaused(true);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- sprite.setPosition(position.x, position.y);
- sprite.setScale(scale, scale);
- sprite.draw(canvas);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/RunnerActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/RunnerActor.java
deleted file mode 100644
index d1ad74b53..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/RunnerActor.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.pursuit;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite.AnimatedSpriteListener;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-
-/**
- * A fruit that runs down the screen.
- */
-public class RunnerActor extends Actor {
- /**
- * Currently there are four different kinds of fruits.
- */
-
- public enum RunnerType {
- STRAWBERRY(Sprites.snowballrun_running_normal, R.drawable.snowballrun_running_starting_runner,
- Sprites.snowballrun_running_sidestep, Sprites.snowballrun_running_appearing, R.drawable.snowballrun_standing_elf,
- Sprites.snowballrun_elf_squish, R.drawable.snowballrun_elf_squished_05),
-
- APRICOT(Sprites.snowballrun_running_snowman_opponent, R.drawable.snowballrun_running_starting_snowman,
- Sprites.empty_frame, Sprites.empty_frame, R.drawable.snowballrun_standing_snowman,
- Sprites.running_apricot_squish, R.drawable.snowballrun_snowman_squished_09),
-
- GRAPE(Sprites.snowballrun_running_elf_opponent, R.drawable.snowballrun_running_starting_elfopponent,
- Sprites.empty_frame, Sprites.empty_frame, R.drawable.snowballrun_standing_elfopponent,
- Sprites.running_elfopponent_squish, R.drawable.snowballrun_elfopponent_squished_09),
-
- MANGO(Sprites.snowballrun_running_reindeer_opponent, R.drawable.snowballrun_running_starting_reindeer,
- Sprites.empty_frame, Sprites.empty_frame, R.drawable.snowballrun_standing_reindeer,
- Sprites.snowballrun_reindeer_squish, R.drawable.snowballrun_reindeer_squished_05);
-
- public int[] runRes, crouchRes, runLeftRes, enteringRes, standRes, dyingRes, deadRes;
-
- RunnerType(int[] runRes, int crouchRes,
- int[] runLeftRes, int[] enteringRes, int standRes,
- int[] dyingRes, int deadRes) {
-
- this.runRes = runRes;
- this.crouchRes = new int[] { crouchRes };
- this.runLeftRes = runLeftRes;
- this.enteringRes = enteringRes;
- this.standRes = new int[] { standRes };
- this.dyingRes = dyingRes;
- this.deadRes = new int[] { deadRes };
- }
- }
-
- // TODO: Because the running left animation is no longer used for the opponents'
- // entrance, consider moving RUNNING_LEFT and RUNNING_RIGHT to PlayerActor.
- enum RunnerState {
- RUNNING,
- CROUCH,
- ENTERING,
- RUNNING_LEFT,
- RUNNING_RIGHT,
- STANDING,
- DYING,
- DEAD,
- }
-
- private static final long EYE_BLINK_DELAY_MILLISECONDS = 1300;
- private static final int RUNNING_Z_INDEX = 10;
- private static final int DEAD_Z_INDEX = 3;
-
- protected int lane;
-
- protected RunnerType type;
-
- protected RunnerState state;
-
- protected float radius;
-
- protected AnimatedSprite currentSprite;
-
- protected AnimatedSprite runningSprite;
- protected AnimatedSprite crouchSprite;
- protected AnimatedSprite enteringSprite;
- protected AnimatedSprite runningLeftSprite;
- protected AnimatedSprite runningRightSprite;
- protected AnimatedSprite standingSprite;
- protected AnimatedSprite deadSprite;
- protected AnimatedSprite dyingSprite;
-
- RunnerActor(Resources resources, RunnerType type, int lane) {
- this.lane = lane;
- this.type = type;
-
- runningSprite = AnimatedSprite.fromFrames(resources, type.runRes);
- crouchSprite = AnimatedSprite.fromFrames(resources, type.crouchRes);
- enteringSprite = AnimatedSprite.fromFrames(resources, type.enteringRes);
- runningLeftSprite = AnimatedSprite.fromFrames(resources, type.runLeftRes);
- runningRightSprite = AnimatedSprite.fromFrames(resources, type.runLeftRes);
- standingSprite = AnimatedSprite.fromFrames(resources, type.standRes);
- deadSprite = AnimatedSprite.fromFrames(resources, type.deadRes);
- dyingSprite = AnimatedSprite.fromFrames(resources, type.dyingRes);
-
- enteringSprite.setLoop(false);
- enteringSprite.setFPS((int) ((enteringSprite.getNumFrames() + 1) / PursuitModel.RUNNER_ENTER_DURATION));
-
- setSpriteAnchorUpright(runningSprite);
- setSpriteAnchorWithYOffset(crouchSprite, 0);
- setSpriteAnchorUpright(enteringSprite);
- setSpriteAnchorUpright(runningLeftSprite);
- setSpriteAnchorUpright(runningRightSprite);
- setSpriteAnchorUpright(standingSprite);
- setSpriteAnchorCenter(deadSprite);
- switch (type) {
- case STRAWBERRY:
- setSpriteAnchorWithYOffset(dyingSprite, 0);
- break;
- case MANGO:
- setSpriteAnchorWithYOffset(dyingSprite, 0);
- break;
- case GRAPE:
- setSpriteAnchorWithYOffset(dyingSprite, 0);
- break;
- case APRICOT:
- setSpriteAnchorWithYOffset(dyingSprite, 0);
- break;
- }
-
- currentSprite = runningSprite;
- state = RunnerState.RUNNING;
- zIndex = RUNNING_Z_INDEX;
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
-
- if (state == RunnerState.RUNNING) {
- currentSprite.setFPS(3 + (int) (5 * velocity.y / PursuitModel.BASE_SPEED));
- }
-
- currentSprite.update(deltaMs);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- if (currentSprite == null) {
- return;
- }
-
- float runnerScale = scale * 1.50f;
- currentSprite.setScale(runnerScale, runnerScale);
- currentSprite.setPosition(position.x, position.y);
-
- if (currentSprite == runningRightSprite) {
- currentSprite.setScale(-runnerScale, runnerScale);
- }
-
- currentSprite.draw(canvas);
- }
-
- public void setLane(int lane) {
- this.lane = lane;
- }
-
- public int getLane() {
- return lane;
- }
-
- public void setRadius(float radius) {
- this.radius = radius;
- }
-
- public float getRadius() {
- return radius;
- }
-
- public void setRunnerState(RunnerState state) {
- if (this.state == state) {
- return;
- }
-
- this.state = state;
-
- switch (state) {
- case RUNNING:
- currentSprite = runningSprite;
- break;
- case CROUCH:
- currentSprite = crouchSprite;
- break;
- case ENTERING:
- currentSprite = enteringSprite;
- break;
- case RUNNING_LEFT:
- currentSprite = runningLeftSprite;
- break;
- case RUNNING_RIGHT:
- currentSprite = runningRightSprite;
- break;
- case STANDING:
- currentSprite = standingSprite;
- break;
- case DYING:
- currentSprite = dyingSprite;
- dyingSprite.setLoop(false);
- dyingSprite.setFrameIndex(0);
- dyingSprite.clearListeners();
-
- int frame = 3;
- switch(type) {
- case APRICOT:
- frame = 2;
- break;
- case GRAPE:
- frame = 2;
- break;
- }
- final int finalFrame = frame;
-
- dyingSprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onFrame(int index) {
- super.onFrame(index);
- if (index == finalFrame) {
- setRunnerState(RunnerState.DEAD);
- }
- }
- });
- break;
- case DEAD:
- currentSprite = deadSprite;
- break;
- }
-
- if (state == RunnerState.DEAD) {
- zIndex = DEAD_Z_INDEX;
- } else {
- zIndex = RUNNING_Z_INDEX;
- }
- }
-
- public RunnerState getRunnerState() {
- return state;
- }
-
- // yOffset is in percentage not game units, or pixels
- // yOffset of 0.5f centers the sprite vertically
- // yOffset of 0 draws the sprite starting from its y position
-
- protected static void setSpriteAnchorCenter(AnimatedSprite sprite) {
- setSpriteAnchorWithYOffset(sprite, sprite.frameHeight * 0.5f);
- }
-
- protected static void setSpriteAnchorUpright(AnimatedSprite sprite) {
- setSpriteAnchorWithYOffset(sprite, 0);
- }
-
- protected static void setSpriteAnchorWithYOffset(AnimatedSprite sprite, float yOffset) {
- sprite.setAnchor(sprite.frameWidth * 0.5f, sprite.frameHeight - yOffset);
- }
-
-}
-
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/WatermelonActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/WatermelonActor.java
deleted file mode 100644
index 17e6ffa78..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/pursuit/WatermelonActor.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.pursuit;
-
-import android.content.res.Resources;
-import com.google.android.apps.santatracker.doodles.shared.WatermelonBaseActor;
-
-/**
- * The watermelon that's always behind the player and the other running fruits.
- * If the player or the running fruits gets too close to the watermelon, they get squished.
- */
-public class WatermelonActor extends WatermelonBaseActor {
-
- public static final float VERTICAL_RADIUS_WORLD = 90f;
- public static final float HORIZONTAL_RADIUS_WORLD = 60f;
-
- private static final int Z_INDEX = 5;
-
- public WatermelonActor(Resources resources) {
- super(resources);
- zIndex = Z_INDEX;
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
-
- int fps = (int) (10 * velocity.y / PursuitModel.BASE_SPEED);
- bodySprite.setFPS(fps);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Actor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Actor.java
deleted file mode 100644
index 99946c919..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Actor.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.graphics.Canvas;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Base class for different characters on the screen.
- */
-public class Actor implements Comparable {
- public static final float INFINITE_MASS = 0.0f;
- public static final String TYPE = "Actor";
- public static final String TYPE_KEY = "type";
- public static final String X_KEY = "x";
- public static final String Y_KEY = "y";
-
- public Vector2D positionBeforeFrame;
- // Assumes (0, 0) is upper-left corner of screen, with +y down and +x right.
- public Vector2D position;
- public Vector2D velocity;
-
- // Doesn't do anything yet (except for TextActors)
- public float scale = 1.0f;
-
- // The rotation of the actor in radians. Positive means clockwise, negative means anticlockwise.
- public float rotation = 0.0f;
-
- // Doesn't do anything yet (except for in tennis)
- public boolean hidden = false;
-
- // Specify z-index so that actors can be sorted before drawing. Higher is in front, lower in back.
- public int zIndex = 0;
-
- // 0: transparent, 1: opaque.
- public float alpha = 1;
-
- // Bounciness.
- public float restitution = 1.0f;
- public float inverseMass = INFINITE_MASS;
-
- public Actor() {
- this(Vector2D.get(0, 0), Vector2D.get(0, 0));
- }
-
- public Actor(Vector2D position, Vector2D velocity) {
- this.position = position;
- this.positionBeforeFrame = Vector2D.get(position);
- this.velocity = velocity;
- }
-
- public void update(float deltaMs) {
- positionBeforeFrame.set(this.position);
- float deltaSeconds = deltaMs / 1000.0f;
- this.position.x += velocity.x * deltaSeconds;
- this.position.y += velocity.y * deltaSeconds;
- }
-
- public void draw(Canvas canvas) {
- // Nothing to do for base class implementation.
- }
-
- @Override
- public int compareTo(Actor another) {
- int zDiff = zIndex - another.zIndex;
- if (zDiff != 0) {
- return zDiff;
- } else {
- // As a fallback, compare the y positions. Obstacles with smaller y values (i.e., higher on
- // the screen) should come first.
- float positionDiff = position.y - another.position.y;
- if (positionDiff > 0) {
- return 1;
- } else if (positionDiff < 0) {
- return -1;
- } else {
- return 0;
- }
- }
- }
-
- public JSONObject toJSON() throws JSONException {
- return null;
- }
-
- public String getType() {
- return TYPE;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ActorHelper.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ActorHelper.java
deleted file mode 100644
index cb6039a32..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ActorHelper.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * A collection of helper functions for Actor.
- */
-public class ActorHelper {
- public static float distanceBetween(Actor a, Actor b) {
- return distanceBetween(a.position.x, a.position.y, b.position.x, b.position.y);
- }
-
- public static float distanceBetween(float x1, float y1, float x2, float y2) {
- float dx = x1 - x2;
- float dy = y1 - y2;
- return (float) Math.sqrt(dx * dx + dy * dy);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ActorTween.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ActorTween.java
deleted file mode 100644
index 707ab27d1..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ActorTween.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * Tweens properties of Actors (currently, position & scale).
- *
- * If any of the from-values is left unset, the from value will be initialized on the first
- * update of the tween. This allows us to tween from an unspecified value, and have it do so
- * smoothly when the tween actually runs, regardless of the actual starting value.
- */
-public class ActorTween extends Tween {
- private static final String TAG = ActorTween.class.getSimpleName();
-
- private final Actor actor;
- private Interpolator interpolator;
- // Use Float objects set to null so that we can detect & support unspecified values.
- private Float xStart = null;
- private Float yStart = null;
- private Float xEnd = null;
- private Float yEnd = null;
- private Float scaleStart;
- private Float scaleEnd;
- private Callback finishedCallback = null;
- private Float rotationStart;
- private Float rotationEnd;
- private Float alphaStart;
- private Float alphaEnd;
-
- private boolean firstUpdate = true;
-
- public ActorTween(Actor actor) {
- super(0);
- this.actor = actor;
- interpolator = Interpolator.LINEAR;
- }
-
- public ActorTween from(float x, float y) {
- fromX(x);
- fromY(y);
- return this;
- }
-
- public ActorTween fromX(float x) {
- xStart = x;
- return this;
- }
-
- public ActorTween fromY(float y) {
- yStart = y;
- return this;
- }
-
- public ActorTween to(float x, float y) {
- toX(x);
- toY(y);
- return this;
- }
-
- public ActorTween toX(float x) {
- xEnd = x;
- return this;
- }
-
- public ActorTween toY(float y) {
- yEnd = y;
- return this;
- }
-
- public ActorTween scale(float fromScale, float toScale) {
- this.scaleStart = fromScale;
- this.scaleEnd = toScale;
- return this;
- }
-
- public ActorTween scale(float toScale) {
- this.scaleEnd = toScale;
- return this;
- }
-
- public ActorTween withRotation(float fromRadians, float toRadians) {
- this.rotationStart = fromRadians;
- this.rotationEnd = toRadians;
- return this;
- }
-
- public ActorTween withRotation(float toRadians) {
- this.rotationEnd = toRadians;
- return this;
- }
-
- public ActorTween withAlpha(float fromAlpha, float toAlpha) {
- this.alphaStart = fromAlpha;
- this.alphaEnd = toAlpha;
- return this;
- }
-
- public ActorTween withAlpha(float toAlpha) {
- this.alphaEnd = toAlpha;
- return this;
- }
-
- public ActorTween withDuration(float seconds) {
- this.durationSeconds = seconds;
- return this;
- }
-
- public ActorTween withInterpolator(Interpolator interpolator) {
- this.interpolator = interpolator;
- return this;
- }
-
- public ActorTween whenFinished(Callback c) {
- finishedCallback = c;
- return this;
- }
-
- protected void setInitialValues() {
- xStart = (xStart != null) ? xStart : actor.position.x;
- yStart = (yStart != null) ? yStart : actor.position.y;
- scaleStart = (scaleStart != null) ? scaleStart : actor.scale;
- rotationStart = (rotationStart != null) ? rotationStart : actor.rotation;
- alphaStart = (alphaStart != null) ? alphaStart : actor.alpha;
- }
-
- @Override
- protected void updateValues(float percentDone) {
- if (firstUpdate) {
- firstUpdate = false;
- setInitialValues();
- }
- // Perform null checks here so that we don't interpolate over unspecified fields.
- if (xEnd != null) {
- actor.position.x = interpolator.getValue(percentDone, xStart, xEnd);
- }
- if (yEnd != null) {
- actor.position.y = interpolator.getValue(percentDone, yStart, yEnd);
- }
- if (scaleEnd != null) {
- actor.scale = interpolator.getValue(percentDone, scaleStart, scaleEnd);
- }
- if (rotationEnd != null) {
- actor.rotation = interpolator.getValue(percentDone, rotationStart, rotationEnd);
- }
- if (alphaEnd != null) {
- actor.alpha = interpolator.getValue(percentDone, alphaStart, alphaEnd);
- }
- }
-
- @Override
- protected void onFinish() {
- if (finishedCallback != null) {
- finishedCallback.call();
- }
- }
-
- /**
- * Callback to be called at the end of a tween (can be used to chain tweens together, to hide
- * an actor when a tween finishes, etc.)
- */
- public interface Callback {
- void call();
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/AndroidUtils.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/AndroidUtils.java
deleted file mode 100644
index 1a8faf7b0..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/AndroidUtils.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.os.Environment;
-import android.text.Html;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.WindowManager;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Utility functions to make it easier to interact with Android APIs.
- */
-public final class AndroidUtils {
- private static final String TAG = AndroidUtils.class.getSimpleName();
-
- private AndroidUtils() {
- // Don't instantiate this class.
- }
-
- public static Activity getActivityFromContext(Context context) {
- while (context instanceof ContextWrapper) {
- if (context instanceof Activity) {
- return (Activity) context;
- }
- context = ((ContextWrapper) context).getBaseContext();
- }
- return null;
- }
-
- public static void allowScreenToTurnOff(Context context) {
- getActivityFromContext(context).getWindow().clearFlags(
- WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
-
- public static void forceScreenToStayOn(Context context) {
- getActivityFromContext(context).getWindow().addFlags(
- WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
-
- public static Point getScreenSize() {
- DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
- return new Point(displayMetrics.widthPixels, displayMetrics.heightPixels);
- }
-
- public static float dipToPixels(float dipValue) {
- DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
- return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics);
- }
-
- public static float pixelsToDips(float pixelValue) {
- DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
- float dip = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, metrics);
- return pixelValue / dip;
- }
-
- public static boolean isExternalStorageWritable() {
- String state = Environment.getExternalStorageState();
- return Environment.MEDIA_MOUNTED.equals(state);
- }
-
- public static boolean isExternalStorageReadable() {
- String state = Environment.getExternalStorageState();
- return Environment.MEDIA_MOUNTED.equals(state)
- || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
- }
-
- /**
- * Handles loading text from our resources, including interpreting and tags.
- */
- public static CharSequence getText(Resources res, int id, Object... formatArgs) {
- try {
- return Html.fromHtml(res.getString(id, formatArgs));
- } catch (java.util.MissingFormatArgumentException e) {
- Log.e(TAG, "unable to format string id: " + id, e);
- }
- return "";
- }
-
- /**
- * Re-orients a coordinate system based on default device rotation.
- * Implementation based on: http://goo.gl/kRajPd
- *
- * @param displayRotation Display rotation, from Display.getRotation()
- * @param eventValues Event values gathered from the raw sensor event.
- * @return The adjusted event values, with display rotation taken into account.
- */
- public static float[] getAdjustedAccelerometerValues(int displayRotation, float[] eventValues) {
- float[] adjustedValues = new float[3];
- final int axisSwap[][] = {
- { 1, -1, 0, 1}, // ROTATION_0
- {-1, -1, 1, 0}, // ROTATION_90
- {-1, 1, 0, 1}, // ROTATION_180
- { 1, 1, 1, 0} // ROTATION_270
- };
-
- final int[] axisFactors = axisSwap[displayRotation];
- adjustedValues[0] = ((float) axisFactors[0]) * eventValues[axisFactors[2]];
- adjustedValues[1] = ((float) axisFactors[1]) * eventValues[axisFactors[3]];
- adjustedValues[2] = eventValues[2];
-
- return adjustedValues;
- }
-
- /**
- * Reads all bytes from an input stream into a byte array.
- * Does not close the stream.
- *
- * @param in the input stream to read from
- * @return a byte array containing all the bytes from the stream
- * @throws IOException if an I/O error occurs
- */
- public static byte[] toByteArray(InputStream in) throws IOException {
- // Presize the ByteArrayOutputStream since we know how large it will need
- // to be, unless that value is less than the default ByteArrayOutputStream
- // size (32).
- ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(32, in.available()));
- copy(in, out);
- return out.toByteArray();
- }
-
- /**
- * Copies all bytes from the input stream to the output stream.
- * Does not close or flush either stream.
- *
- * @param from the input stream to read from
- * @param to the output stream to write to
- * @return the number of bytes copied
- * @throws IOException if an I/O error occurs
- */
- private static long copy(InputStream from, OutputStream to)
- throws IOException {
- byte[] buf = new byte[8192];
- long total = 0;
- while (true) {
- int r = from.read(buf);
- if (r == -1) {
- break;
- }
- to.write(buf, 0, r);
- total += r;
- }
- return total;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/AnimatedSprite.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/AnimatedSprite.java
deleted file mode 100644
index c916fccf4..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/AnimatedSprite.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.Log;
-import android.util.Pair;
-import com.google.android.apps.santatracker.doodles.shared.physics.Util;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * An animated image. This also handles static, non-animated images: those are just animations with
- * only 1 frame.
- */
-public class AnimatedSprite {
- private static final String TAG = AnimatedSprite.class.getSimpleName();
- private static final int DEFAULT_FPS = 24;
- private static final int NUM_TRIES_TO_LOAD_FROM_MEMORY = 3;
-
- // When loading any sprite, this was the last successful sampleSize. We start loading the next
- // Sprite with this sampleSize.
- public static int lastUsedSampleSize = 1;
-
- private static BitmapCache bitmapCache;
-
- private Bitmap[] frames;
- public int frameWidth;
- public int frameHeight;
- private int fps = DEFAULT_FPS;
- private int numFrames;
- private boolean loop = true;
- private boolean paused = false;
- private boolean flippedX = false;
- private List listeners;
- private Vector2D position = Vector2D.get();
- public Vector2D anchor = Vector2D.get();
- private boolean hidden;
- private float scaleX = 1;
- private float scaleY = 1;
- private float rotation;
- private Paint paint;
- private int sampleSize = 1;
-
- private float frameIndex;
-
- // These are fields in order to avoid allocating memory in draw(). Not threadsafe, but why would
- // draw get called from multiple threads?
- private Rect srcRect = new Rect();
- private RectF dstRect = new RectF();
-
- /**
- * Use fromFrames() to construct an AnimatedSprite.
- */
- private AnimatedSprite(Bitmap[] frames, int sampleSize) {
- this.frames = frames;
- this.sampleSize = sampleSize;
- if (lastUsedSampleSize < sampleSize) {
- lastUsedSampleSize = sampleSize;
- }
- numFrames = this.frames.length;
- if (numFrames == 0) {
- throw new IllegalArgumentException("Can't have AnimatedSprite with zero frames.");
- }
- frameWidth = frames[0].getWidth() * sampleSize;
- frameHeight = frames[0].getHeight() * sampleSize;
- listeners = new ArrayList<>();
- paint = new Paint();
- paint.setAntiAlias(true);
- paint.setFilterBitmap(true);
- }
-
- /**
- * Return AnimatedSprite built from separate images (one image per frame).
- */
- public static AnimatedSprite fromFrames(Resources resources, int[] ids) {
- int sampleSize = lastUsedSampleSize;
- Bitmap frames[] = new Bitmap[ids.length];
- for (int i = 0; i < ids.length; i++) {
- Pair pair = getBitmapFromCache(ids[i], 0);
- if (pair != null) {
- frames[i] = pair.first;
- sampleSize = pair.second;
- }
- if (frames[i] == null) {
- final BitmapFactory.Options options = new BitmapFactory.Options();
- for (int tries = 0; tries < NUM_TRIES_TO_LOAD_FROM_MEMORY; tries++) {
- try {
- // Decode bitmap with inSampleSize set
- options.inSampleSize = sampleSize;
- frames[i] = BitmapFactory.decodeResource(resources, ids[i], options);
- } catch (OutOfMemoryError oom) {
- sampleSize *= 2;
- Log.d(TAG, "loading failed, trying sampleSize: " + sampleSize, oom);
- }
- }
- putBitmapInCache(frames[i], ids[i], 0, sampleSize);
- }
- }
- return new AnimatedSprite(frames, sampleSize);
- }
-
- /**
- * Return AnimatedSprite built from the given Bitmap objects. (For testing).
- */
- public static AnimatedSprite fromBitmapsForTest(Bitmap frames[]) {
- return new AnimatedSprite(frames, 1);
- }
-
- /**
- * Return AnimatedSprite built from the same frames as another animated sprite. This isn't a
- * deep clone, only the frames & FPS of the original sprite are copied.
- */
- public static AnimatedSprite fromAnimatedSprite(AnimatedSprite other) {
- AnimatedSprite sprite = new AnimatedSprite(other.frames, other.sampleSize);
- sprite.setFPS(other.fps);
- return sprite;
- }
-
- private static Pair getBitmapFromCache(int id, int frame) {
- if (bitmapCache == null) {
- bitmapCache = new BitmapCache();
- }
- return bitmapCache.getBitmapFromCache(id, frame);
- }
-
- private static void putBitmapInCache(Bitmap bitmap, int id, int frame, int sampleSize) {
- if (bitmapCache == null) {
- bitmapCache = new BitmapCache();
- }
- bitmapCache.putBitmapInCache(bitmap, id, frame, sampleSize);
- }
-
- /**
- * Set whether to loop the animation or not.
- */
- public void setLoop(boolean loop) {
- this.loop = loop;
- }
-
- public void setFlippedX(boolean value) {
- this.flippedX = value;
- }
-
- public boolean isFlippedX() {
- return flippedX;
- }
-
- public void setPaused(boolean value) {
- this.paused = value;
- }
-
- /**
- * Pause this sprite and return a process chain which can be updated to unpause the sprite after
- * the specified length of time.
- *
- * @param durationMs how many milliseconds to pause the sprite for.
- * @return a process chain which will unpause the sprite after the duration has completed.
- */
- public ProcessChain pauseFor(long durationMs) {
- setPaused(true);
- CallbackProcess unpause = new CallbackProcess() {
- @Override
- public void updateLogic(float deltaMs) {
- setPaused(false);
- }
- };
- return new WaitProcess(durationMs).then(unpause);
- }
-
- /**
- * Change the speed of the animation.
- */
- public void setFPS(int fps) {
- this.fps = fps;
- }
-
- /**
- * Sets the current frame.
- */
- public void setFrameIndex(int frame) {
- frameIndex = frame;
- }
-
- public int getFrameIndex() {
- return (int) frameIndex;
- }
-
- public int getNumFrames() {
- return numFrames;
- }
-
- public float getDurationSeconds() {
- return numFrames / (float) fps;
- }
-
- /**
- * Update the animation based on deltaMs having passed.
- */
- public void update(float deltaMs) {
- if (paused) {
- return;
- }
-
- float deltaFrames = (deltaMs / 1000.0f) * fps;
-
- // In order to make sure that we don't skip any frames when notifying listeners, this carefully
- // accumulates deltaFrames instead of just immediately adding it into frameIndex. Be careful
- // of floating point precision issues below.
- while (deltaFrames > 0) {
- // First, try accumulating the remaining deltaFrames and see if we make it to the next frame.
- float newFrameIndex = frameIndex + deltaFrames;
- if ((int) newFrameIndex == (int) frameIndex) {
- // Didn't make it to the next frame. Done accumulating.
- frameIndex = newFrameIndex;
- deltaFrames = 0;
- } else {
- // Move forward to next frame, notify listeners, then keep accumlating.
- float oldFrameIndex = frameIndex;
- frameIndex = 1 + (int) frameIndex; // ignores numFrames, will handle it below.
- deltaFrames -= frameIndex - oldFrameIndex;
-
- if (frameIndex < numFrames) {
- sendOnFrameNotification((int) frameIndex);
- } else {
- if (loop) {
- frameIndex = 0;
- sendOnLoopNotification();
- sendOnFrameNotification((int) frameIndex);
- } else {
- frameIndex = numFrames - 1;
- sendOnFinishNotification();
- // In this branch, there are no further onFrame notifications.
- deltaFrames = 0; // No more changes to frameIndex, done accumulating.
- }
- }
- }
- }
- }
-
-
- void sendOnLoopNotification() {
- for (int i = 0; i < listeners.size(); i++) { // Avoiding iterators to avoid garbage.
- // Call the on-loop callbacks.
- listeners.get(i).onLoop();
- }
- }
-
- void sendOnFinishNotification() {
- for (int i = 0; i < listeners.size(); i++) { // Avoiding iterators to avoid garbage
- // Call the on-finished callbacks.
- listeners.get(i).onFinished();
- }
- }
-
- void sendOnFrameNotification(int frame) {
- for (int i = 0; i < listeners.size(); i++) { // Avoiding iterators to avoid garbage.
- listeners.get(i).onFrame(frame);
- }
- }
-
- public void draw(Canvas canvas) {
- if (!hidden) {
- // Integer cast should round down, but clamp it just in case the synchronization with the
- // update thread isn't perfect.
- int frameIndexFloor = Util.clamp((int) frameIndex, 0, numFrames - 1);
- float scaleX = flippedX ? -this.scaleX : this.scaleX;
-
- canvas.save();
- srcRect.set(0, 0, frameWidth, frameHeight);
- dstRect.set(-anchor.x, -anchor.y, -anchor.x + frameWidth, -anchor.y + frameHeight);
-
- canvas.translate(position.x, position.y);
- canvas.scale(scaleX, scaleY, 0, 0);
- canvas.rotate((float) Math.toDegrees(rotation), 0, 0);
-
- canvas.drawBitmap(frames[frameIndexFloor], srcRect, dstRect, paint);
- canvas.restore();
- }
- }
-
- // Unlike Actors, AnimatedSprites use setters instead of public fields for position, scale, etc.
- // This matches how it works on iOS, which uses setters because the actual values must be passed
- // down into SKNodes.
- public void setPosition(float x, float y) {
- position.x = x;
- position.y = y;
- }
-
- /**
- * @param alpha: 0.0 = transparent, 1.0 = opaque.
- */
- public void setAlpha(float alpha) {
- paint.setAlpha((int) (alpha * 255));
- }
-
- // You can use this to more closely match the logic of iOS, where there is no draw method so the
- // only way to hide something is to set this flag. On Android, you can also just not call draw
- // if you want a sprite hidden.
- public void setHidden(boolean hidden) {
- this.hidden = hidden;
- }
-
- public void setScale(float scaleX, float scaleY) {
- this.scaleX = scaleX;
- this.scaleY = scaleY;
- }
-
- public float getScaledWidth() {
- return scaleX * frameWidth;
- }
-
- public float getScaledHeight() {
- return scaleY * frameHeight;
- }
-
- public void setRotation(float rotation) {
- this.rotation = rotation;
- }
-
- // Sets the anchor point which determines where the sprite is drawn relative to its position. This
- // is also the point around which sprite rotates & scales. (x, y) are in pixels, relative to
- // top-left corner. Initially set to the upper-left corner.
- public void setAnchor(float x, float y) {
- anchor.x = x;
- anchor.y = y;
- }
-
- public void addListener(AnimatedSpriteListener listener) {
- listeners.add(listener);
- }
-
- public void clearListeners() {
- listeners.clear();
- }
-
- public int getNumListeners() {
- return listeners.size();
- }
-
- public static void clearCache() {
- if (AnimatedSprite.bitmapCache != null) {
- AnimatedSprite.bitmapCache.clear();
- }
- }
-
- // Reverse the frames of the animation. This doesn't update frameIndex, which may or may not
- // be what you want.
- public void reverseFrames() {
- for (int i = 0; i < frames.length / 2; i++) {
- Bitmap temp = frames[i];
- frames[i] = frames[frames.length - i - 1];
- frames[frames.length - i - 1] = temp;
- }
- }
-
- /**
- * A class which can be implemented to provide callbacks for AnimatedSprite events.
- */
- public static class AnimatedSpriteListener {
- public void onFinished() {
- }
-
- public void onLoop() {
- }
-
- public void onFrame(int index) {
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/BallActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/BallActor.java
deleted file mode 100644
index 041321c05..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/BallActor.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Represents ball in Tennis.
- */
-public class BallActor extends Actor {
-
- private static final float MIN_SHADOW_SCALE = 0.4f;
- private static final float MAX_SHADOW_SCALE = 0.7f;
- // Highest the ball can be. Going higher than this may mess up scaling & shadow positioning.
- public static final int BALL_MAX_HEIGHT = 75;
-
- private static final int MAX_STREAK_LENGTH = 10; // Max # of frames back the streak can go.
-
- // Slightly less than the sprite's frameWidth because there are semi-transparent pixels.
- public static final int BALL_RADIUS = 14;
- private static final double STREAK_ALPHA = 0.25; // 1 = opaque, 0 = transparent
- private static final float BALL_MIN_SCALE = 0.5f; // Ball is scaled between BALL_MIN_SCALE and 1.
-
- private AnimatedSprite ball;
- private AnimatedSprite fireball; // Flaming version of ball sprite (optional).
- private AnimatedSprite shadow; // Might be null, if ball shouldn't have a shadow.
- private float height = 0; // Height above the court.
- // Whether or not the ball should be scaled larger at higher heights (to aid illusion of ball
- // arcing through the air)
- public boolean shouldScaleWithHeight;
- private final Paint debugPaint;
- // The streak is drawn by storing a history of positions & scales going back streakLength
- // updates into the past.
- private List streakPositions = new ArrayList<>();
- private List streakHeights = new ArrayList<>();
- private int streakIndex = 0;
- // This is the actual streak length. On iOS, it must be <= MAX_STREAK_LENGTH in order to fit in
- // the arrays, so we're keeping it <= here too.
- private int streakLength = 10;
- private boolean shouldDrawStreak = true;
- private Paint streakPaint = new Paint();
- private Paint streakDebugPaint = new Paint();
-
- // This is a field so that we don't have to create a new one every frame. Not threadsafe,
- // but draw() shouldn't be called from multiple threads so it should be ok.
- private Path path = new Path();
-
- // Fireball is optional.
- public BallActor(AnimatedSprite shadow, AnimatedSprite ball, AnimatedSprite fireball,
- int streakLength) {
- this.ball = ball;
- ball.setAnchor(ball.frameWidth / 2, ball.frameHeight);
- if (fireball != null) {
- fireball.setAnchor(fireball.frameWidth / 2, BALL_RADIUS);
- this.fireball = fireball;
- }
-
- this.shadow = shadow;
- this.streakLength = streakLength;
- if (streakLength > MAX_STREAK_LENGTH) {
- throw new IllegalArgumentException("Error: Streak length exceeds MAX_STREAK_LENGTH");
- }
- shouldScaleWithHeight = true;
- debugPaint = new Paint();
- debugPaint.setColor(0xff000000);
-
- streakPaint.setColor(android.graphics.Color.WHITE);
- streakPaint.setStyle(Paint.Style.FILL);
- streakPaint.setAlpha((int) (STREAK_ALPHA * 256));
-
- streakDebugPaint.setColor(android.graphics.Color.BLACK);
- streakDebugPaint.setStyle(Paint.Style.STROKE);
-
- clearStreak();
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
- ball.update(deltaMs);
- if (fireball != null) {
- fireball.update(deltaMs);
- }
- if (shadow != null) {
- shadow.update(deltaMs);
- }
-
- streakIndex++;
- streakPositions.get(streakIndex % streakLength).set(position);
- streakHeights.set(streakIndex % streakLength, height);
-
- // Prepare sprites for drawing.
- if (shadow != null) {
- float shadowScale = MIN_SHADOW_SCALE + (MAX_SHADOW_SCALE - MIN_SHADOW_SCALE)
- * (1 - Math.min(BALL_MAX_HEIGHT, height) / BALL_MAX_HEIGHT);
- if (!shouldScaleWithHeight) {
- shadowScale = MAX_SHADOW_SCALE * scale;
- }
- shadow.setScale(shadowScale, shadowScale);
- shadow.setPosition(position.x - shadowScale * shadow.frameWidth / 2,
- position.y - shadowScale * shadow.frameHeight / 2);
- }
-
- float ballScale = calculateScale(height);
- ball.setScale(ballScale, ballScale);
- ball.setRotation(rotation);
- ball.setPosition(position.x, position.y - height);
- if (fireball != null) {
- fireball.setScale(ballScale, ballScale);
- fireball.setRotation((float) (Math.atan2(velocity.y, velocity.x) + Math.PI / 2));
- fireball.setPosition(position.x, position.y - height - BALL_RADIUS);
- }
-
- // Streak. Start by working down the left side of the streak.
- path.rewind();
- for (int i = 0; i < streakLength; i++) {
- int index = (streakIndex - i) % streakLength;
- Vector2D pos = streakPositions.get(index);
- float ballRadius = BALL_RADIUS * calculateScale(streakHeights.get(index));
- float taperedRadius = ballRadius * (1 - i / (float) streakLength);
- float x = pos.x - taperedRadius;
- float y = pos.y - ballRadius - streakHeights.get(index);
- if (i == 0) {
- path.moveTo(x, y);
- } else {
- path.lineTo(x, y);
- }
- }
-
- // Now finish by going back up right side of streak.
- for (int i = streakLength - 1; i >= 0; i--) {
- int index = (streakIndex - i) % streakLength;
- Vector2D pos = streakPositions.get(index);
- float ballRadius = BALL_RADIUS * calculateScale(streakHeights.get(index));
- float taperedRadius = ballRadius * (1 - i / (float) streakLength);
- float x = pos.x + taperedRadius;
- float y = pos.y - ballRadius - streakHeights.get(index);
- path.lineTo(x, y);
- }
- path.close();
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- // Shadow, ball, and streak are all drawn together here, so nothing can be drawn above shadow
- // but below the ball.
-
- if (!hidden) {
- if (shadow != null) {
- shadow.draw(canvas);
- }
- ball.draw(canvas);
- if (fireball != null) {
- fireball.draw(canvas);
- }
- if (shouldDrawStreak) {
- canvas.drawPath(path, streakPaint);
- }
-
- if (Debug.DRAW_POSITIONS) {
- canvas.drawPath(path, streakDebugPaint);
- canvas.drawCircle(position.x, position.y, 2, debugPaint);
- canvas.drawCircle(position.x, position.y - height, 2, debugPaint);
- }
- }
- }
-
- public void clearStreak() {
- streakPositions.clear();
- streakHeights.clear();
- for (int i = 0; i < streakLength; i++) {
- streakPositions.add(Vector2D.get(position));
- streakHeights.add(height);
- }
- streakIndex = streakLength; // Start here so we never get negative indexes.
- }
-
- public void setHeight(float height) {
- this.height = height;
- }
-
- public float getHeight() {
- return height;
- }
-
- public void setColorForDebug(int color) {
- debugPaint.setColor(color);
- }
-
- public void showFireball(boolean shouldShowFireball) {
- if (fireball != null) {
- fireball.setHidden(!shouldShowFireball);
- ball.setHidden(shouldShowFireball);
- shouldDrawStreak = !shouldShowFireball;
- }
- }
-
- private float calculateScale(float height) {
- if (!shouldScaleWithHeight) {
- return scale;
- }
- return BALL_MIN_SCALE + (1 - BALL_MIN_SCALE) * height / BALL_MAX_HEIGHT;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/BitmapCache.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/BitmapCache.java
deleted file mode 100644
index d90b8695e..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/BitmapCache.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.graphics.Bitmap;
-import android.util.Pair;
-
-import java.util.HashMap;
-
-/**
- * Cache of bitmaps (and sampleSizes), mapped by resource ID and frame number.
- *
- * Note: This cache must be manually cleared in order to free up memory. This is under the
- * assumption that any bitmaps currently used by the app should be in the cache.
- */
-public class BitmapCache {
- public static final String TAG = BitmapCache.class.getSimpleName();
-
- private HashMap> bitmapCache = new HashMap<>();
-
- public Pair getBitmapFromCache(int id, int frame) {
- Pair pair = bitmapCache.get(bitmapCacheKey(id, frame));
- return pair;
- }
-
- public void putBitmapInCache(Bitmap bitmap, int id, int frame, int sampleSize) {
- bitmapCache.put(bitmapCacheKey(id, frame), new Pair(bitmap, sampleSize));
- }
-
- public void clear() {
- bitmapCache.clear();
- }
-
- private static String bitmapCacheKey(int id, int frameNumber) {
- return id + ":" + frameNumber;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/CallbackProcess.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/CallbackProcess.java
deleted file mode 100644
index a0eae4d19..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/CallbackProcess.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * A process which should be run once and then be finished.
- */
-public abstract class CallbackProcess extends Process {
- private boolean didRun = false;
-
- @Override
- public void update(float deltaMs) {
- if (!didRun) {
- updateLogic(deltaMs);
- didRun = true;
- }
- }
-
- @Override
- public boolean isFinished() {
- return didRun;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Camera.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Camera.java
deleted file mode 100644
index a66a96876..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Camera.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import static com.google.android.apps.santatracker.doodles.shared.Interpolator.EASE_IN_AND_OUT;
-
-/**
- * A camera class to contain the scale, translation, and rotation of the world. Note that the camera
- * is defined to be positioned at the top-left corner of the screen.
- */
-public class Camera extends Actor {
-
- public int screenWidth;
- public int screenHeight;
-
- public Camera(int screenWidth, int screenHeight) {
- this.position = Vector2D.get();
- scale = 1.0f;
-
- this.screenWidth = screenWidth;
- this.screenHeight = screenHeight;
- }
-
- /**
- * Get the world coordinates for the screen coordinates specified.
- *
- * @param x The x value in screen space.
- * @param y The y value in screen space.
- */
- public Vector2D getWorldCoords(float x, float y) {
- return Vector2D.get(xToWorld(x), yToWorld(y));
- }
-
- public float xToWorld(float x) {
- return position.x + x / scale;
- }
-
- public float yToWorld(float y) {
- return position.y + y / scale;
- }
-
- /**
- * Converts a length from screen scale to world space.
- *
- * @param dimension: the length in screen space.
- * @return the length in world space.
- */
- public float toWorldScale(float dimension) {
- return dimension / scale;
- }
-
- /**
- * Move the center of the camera's viewport to the specified position.
- *
- * @param position The position to center the camera on.
- */
- public void focusOn(Vector2D position) {
- this.position.set(getPositionToFocusOn(position, scale));
- }
-
- /**
- * Get the camera position needed to focus on the specified position at the specified scale.
- *
- * @param position The position to center on.
- * @param scale The scale at which to focus.
- */
- private Vector2D getPositionToFocusOn(Vector2D position, float scale) {
- return Vector2D.get(position).subtract((screenWidth / 2) / scale, (screenHeight / 2) / scale);
- }
-
- /**
- * Move the camer immediately so that it can see the bounding box specified by the min and max
- * position vectors.
- *
- * @param levelMinPosition The desired minimum visible portion of the level.
- * @param levelMaxPosition The desired maximum visible portion of the level.
- */
- public void moveImmediatelyTo(Vector2D levelMinPosition, Vector2D levelMaxPosition) {
- Vector2D levelDimens = Vector2D.get(levelMaxPosition).subtract(levelMinPosition);
-
- float pannedScale = Math.min(screenWidth / levelDimens.x, screenHeight / levelDimens.y);
- Vector2D screenDimensInWorldCoords = Vector2D.get(screenWidth, screenHeight)
- .scale(1 / pannedScale);
-
- // pannedPosition = levelMinPosition - (screenDimensInWorldCoords - levelDimens) / 2
- Vector2D pannedPosition = Vector2D.get(levelMinPosition).subtract(
- (screenDimensInWorldCoords.x - levelDimens.x) * 0.5f,
- (screenDimensInWorldCoords.y - levelDimens.y) * 0.5f);
-
- position.set(pannedPosition);
- scale = pannedScale;
-
- screenDimensInWorldCoords.release();
- levelDimens.release();
- pannedPosition.release();
- }
-
- /**
- * Pan to the specified position over the specified duration.
- *
- * @param levelMinPosition The desired minimum visible portion of the level.
- * @param levelMaxPosition The desired maximum visible portion of the level.
- * @param duration How many seconds the pan should take.
- * @return The tween to pan the camera.
- */
- public Tween panTo(
- final Vector2D levelMinPosition, final Vector2D levelMaxPosition, float duration) {
-
- final Vector2D startMin = Vector2D.get(position);
- final Vector2D startMax = getMaxVisiblePosition();
-
- Tween panTween = new Tween(duration) {
- @Override
- protected void updateValues(float percentDone) {
-
- float xMin = EASE_IN_AND_OUT.getValue(percentDone, startMin.x, levelMinPosition.x);
- float xMax = EASE_IN_AND_OUT.getValue(percentDone, startMax.x, levelMaxPosition.x);
- float yMin = EASE_IN_AND_OUT.getValue(percentDone, startMin.y, levelMinPosition.y);
- float yMax = EASE_IN_AND_OUT.getValue(percentDone, startMax.y, levelMaxPosition.y);
-
- Vector2D min = Vector2D.get(xMin, yMin);
- Vector2D max = Vector2D.get(xMax, yMax);
- moveImmediatelyTo(min, max);
- min.release();
- max.release();
- }
-
- @Override
- protected void onFinish() {
- startMin.release();
- startMax.release();
- }
- };
- return panTween;
- }
-
- private Vector2D getMaxVisiblePosition() {
- return Vector2D.get(position.x + screenWidth / scale, position.y + screenHeight / scale);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/CameraShake.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/CameraShake.java
deleted file mode 100644
index 6a05a95ae..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/CameraShake.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * Tracks a vibration which reduces over time, suitable for screen shake effects.
- * (Use position as the camera's offset when rendering)
- */
-public class CameraShake extends Actor {
-
- private float frequency = 0;
- private float magnitude = 0;
- private float falloff = 0;
- private float msTillNextShake = 0;
-
- public void shake(float frequency, float magnitude, float falloff) {
- this.frequency = frequency;
- this.magnitude = magnitude;
- this.falloff = falloff;
- msTillNextShake = 1000 / frequency;
- }
-
- @Override
- public void update(float deltaMs) {
- if (magnitude == 0) {
- return;
- }
-
- msTillNextShake -= deltaMs;
- if (msTillNextShake < 0) {
- msTillNextShake = 1000 / frequency;
- magnitude *= falloff;
- // Tiny amounts of shake take too long to fall off, and they look bad, so just quickly
- // kill the shake once it falls below a low threshold.
- if (this.magnitude < 2) {
- this.magnitude = 0;
- }
- }
- position.x = (float) ((Math.random() - 0.5) * magnitude);
- position.y = (float) ((Math.random() - 0.5) * magnitude);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Debug.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Debug.java
deleted file mode 100644
index 1fa598e83..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Debug.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.res.Resources;
-import com.google.android.apps.santatracker.doodles.tilt.ColoredRectangleActor;
-
-/**
- * Debug flags (collected in one place).
- */
-public class Debug {
- // Draw positions of things as they move around. Tennis only for now.
- public static final boolean DRAW_POSITIONS = false;
-
- // Draw the targets that the players hit towards. Tennis only for now.
- public static final boolean DRAW_TARGETS = false;
-
- // Draw the location of each hit. Tennis only for now.
- public static final boolean MARK_HITS = false;
-
- // Play without user input. Tennis only for now.
- public static final boolean AUTO_PLAY = false;
-
- // Slow down or speed up everything (scales deltaMs).
- public static final float SPEED_MULTIPLIER = 1f;
-
- // Skip frames (slows game down without affecting deltaMs).
- public static final float FRAME_SKIP = 0;
-
- public static final boolean SHOW_SECONDARY_MENU_ICONS = false;
-
- // Return a SpriteActor of an "X" marker, centered over (x, y). For marking positions for
- // debugging.
- public static SpriteActor makeDebugMarkerX(Resources resources, float x, float y) {
- AnimatedSprite sprite = AnimatedSprite.fromFrames(resources, Sprites.debug_marker);
- return new SpriteActor(sprite,
- Vector2D.get(x - sprite.frameWidth / 2, y - sprite.frameHeight / 2),
- Vector2D.get(0, 0));
- }
-
- // Return a tiny rectangle actor, centered over (x, y). For marking positions for debugging.
- public static Actor makeDebugMarkerPoint(float x, float y) {
- float size = 3;
- return new ColoredRectangleActor(Vector2D.get(x - size / 2, y - size / 2),
- Vector2D.get(size, size), "fairway");
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/DoodleConfig.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/DoodleConfig.java
deleted file mode 100644
index 65ba7a4ca..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/DoodleConfig.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-
-/**
- * Class that holds the config we need for the doodle.
- */
-public class DoodleConfig {
-
- /**
- * Function to call to actually go to the search results page.
- */
- public interface QueryRunner {
- public void runQuery();
- }
-
- // Public to be read, but final so we know it doesn't change.
- @Nullable
- public final Bundle extraData;
- @Nullable
- public final QueryRunner queryRunner;
-
- public DoodleConfig(@Nullable Bundle extraData, @Nullable QueryRunner queryRunner) {
- this.extraData = extraData;
- this.queryRunner = queryRunner;
- }
-
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ElasticOutInterpolator.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ElasticOutInterpolator.java
deleted file mode 100644
index 05f9cd50e..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ElasticOutInterpolator.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.view.animation.Interpolator;
-
-/**
- * An interpolator which has a rubber bound effect around its end point, bouncing back and forth
- * before settling at its final value.
- *
- * Implementation copied from outElastic here: https://goo.gl/SJZllG
- */
-public class ElasticOutInterpolator implements Interpolator {
-
- private final float period;
-
- public ElasticOutInterpolator() {
- period = 0.4f;
- }
-
- public ElasticOutInterpolator(float period) {
- this.period = period;
- }
-
- @Override
- public float getInterpolation(float value) {
- return (float) (Math.pow(2, -10 * value)
- * Math.sin((value - period / 4) * (2 * Math.PI) / period) + 1);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/EmptyTween.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/EmptyTween.java
deleted file mode 100644
index c4114fac4..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/EmptyTween.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * A tween that does nothing in its updateValues method. Can be used to insert pauses in chains
- * of tweens. The useful part of this is that it calls finishedCallback after durationSeconds,
- * and it fits into the existing Tween framework.
- */
-public class EmptyTween extends Tween {
-
- public EmptyTween(float durationSeconds) {
- super(durationSeconds);
- }
-
- @Override
- protected void updateValues(float percentDone) {
- // Nothing to do, just waiting for the tween to end.
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/EventBus.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/EventBus.java
deleted file mode 100644
index 178a08b08..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/EventBus.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A simple event bus for passing events between objects.
- */
-public class EventBus {
- private static EventBus instance;
-
- public static final int VIBRATE = 0;
- public static final int SCORE_CHANGED = 1;
- public static final int SHAKE_SCREEN = 2;
- public static final int BRONZE = 3;
- public static final int SILVER = 4;
- public static final int GOLD = 5;
- public static final int SWIMMING_DIVE = 6;
- public static final int GAME_STATE_CHANGED = 7;
- public static final int PLAY_SOUND = 8;
- public static final int PAUSE_SOUND = 9;
- public static final int MUTE_SOUNDS = 10;
- public static final int GAME_OVER = 11;
- public static final int GAME_LOADED = 12;
-
- private final Object lock = new Object();
-
- /**
- * Interface for objects which want to listen to the event bus.
- */
- public interface EventBusListener {
- void onEventReceived(int type, Object data);
- }
-
- public static EventBus getInstance() {
- if (instance == null) {
- instance = new EventBus();
- }
- return instance;
- }
-
- // Listeners for specific events.
- private Map> specificListeners;
- // Listeners for all events.
- private Set globalListeners;
-
- private EventBus() {
- globalListeners = new HashSet<>();
- specificListeners = new HashMap<>();
- }
-
- /**
- * Register for a specific event. Listener will only be called for events of that type.
- */
- public void register(EventBusListener listener, int type) {
- synchronized (lock) {
- if (!specificListeners.containsKey(type)) {
- specificListeners.put(type, new HashSet());
- }
- specificListeners.get(type).add(listener);
- }
- }
-
- /**
- * Register for all events. Listener will be called for events of any type.
- */
- public void register(EventBusListener listener) {
- synchronized (lock) {
- globalListeners.add(listener);
- }
- }
-
- /**
- * Send an event without data.
- */
- public void sendEvent(int type) {
- sendEvent(type, null);
- }
-
- /**
- * Send an event with data. Type of the data is up to the caller.
- */
- public void sendEvent(int type, Object data) {
- synchronized (lock) {
- try {
- Set listeners = specificListeners.get(type);
- if (listeners != null) {
- for (EventBusListener listener : listeners) {
- listener.onEventReceived(type, data);
- }
- }
- for (EventBusListener listener : globalListeners) {
- listener.onEventReceived(type, data);
- }
- } catch (ClassCastException e) {
- // This was happening when 2 games were running at the same time (which shouldn't be
- // possible, but was happening in monkey testing). Game A's listener would try casting
- // the data arg to the expected type for Game A, but this would fail if Game B sent a data
- // of a different type.
- //
- // Ignore this and continue running.
- }
- }
- }
-
- /**
- * Removes all the listeners from this EventBus.
- */
- public void clearListeners() {
- synchronized (lock) {
- specificListeners.clear();
- globalListeners.clear();
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ExternalStoragePermissions.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ExternalStoragePermissions.java
deleted file mode 100644
index 2bcba2905..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ExternalStoragePermissions.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * Contains data about whether or not external storage is readable or writable.
- */
-public class ExternalStoragePermissions {
-
- public boolean isExternalStorageReadable() {
- return AndroidUtils.isExternalStorageReadable();
- }
-
- public boolean isExternalStorageWritable() {
- return AndroidUtils.isExternalStorageWritable();
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/FakeButtonActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/FakeButtonActor.java
deleted file mode 100644
index 59631a399..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/FakeButtonActor.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.graphics.Canvas;
-
-/**
- * An actor that looks like a button, but doesn't actually have any logic to detect or respond to
- * clicks. We use this to provide a UI affordance to the user. Even though all our games allow you
- * to click anywhere on the screen, having something that looks like a button helps the users
- * to know how to play the game.
- */
-public class FakeButtonActor extends Actor {
-
- public final AnimatedSprite sprite; // Public so you can get to frameWidth/frameHeight.
- private final int lastFrameIndex;
-
- /**
- * The sprite for the button should conform to the following:
- * 1. The last frame of the animation will be the "idle" state of the button.
- * 2. When the button is pressed, the animation will be played through, starting from frame 0
- * and ending back on the last frame.
- * 3. The FPS of the sprite should be set to give the button press animation the desired duration.
- */
- public FakeButtonActor(AnimatedSprite sprite) {
- super();
- this.sprite = sprite;
- sprite.setLoop(false);
- lastFrameIndex = sprite.getNumFrames() - 1;
- sprite.setFrameIndex(lastFrameIndex);
- }
-
- public void press() {
- sprite.setFrameIndex(0);
- }
-
- public void pressAndHold() {
- sprite.setFrameIndex(0);
- sprite.setPaused(true);
- }
-
- public void release() {
- sprite.setPaused(false);
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
- sprite.update(deltaMs);
- sprite.setPosition(position.x, position.y);
- sprite.setRotation(rotation);
- sprite.setHidden(hidden);
- sprite.setAlpha(alpha);
- sprite.setScale(scale, scale);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- sprite.draw(canvas);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameFragment.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameFragment.java
deleted file mode 100644
index 12c5e1535..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameFragment.java
+++ /dev/null
@@ -1,416 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.Fragment;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.TextView;
-
-import com.google.android.apps.santatracker.doodles.PineappleActivity;
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.PauseView.GamePausedListener;
-import com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.Builder;
-import com.google.android.apps.santatracker.doodles.shared.ScoreView.LevelFinishedListener;
-import com.google.android.apps.santatracker.doodles.shared.sound.SoundManager;
-import com.google.android.apps.santatracker.invites.AppInvitesFragment;
-import com.google.android.apps.santatracker.util.FontHelper;
-
-import static com.google.android.apps.santatracker.doodles.shared.EventBus.GAME_LOADED;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.DEFAULT_DOODLE_NAME;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.LOADING_COMPLETE;
-
-/**
- * Base class for Pineapple game fragments.
- */
-public abstract class GameFragment extends Fragment implements
- GameLoop, ScoreView.OnShareClickedListener {
-
- // Minimum length of the title screen.
- public static final long TITLE_DURATION_MS = 1000;
-
- // Require 128MB and if we don't have it, downsample the resources.
- private static final int AVAILABLE_MEMORY_REQUIRED = (2 << 27);
-
- // Note this is a context, not an activity. Use getActivity() for an activity.
- public final Context context;
- public final DoodleConfig doodleConfig;
- public final PineappleLogger logger;
-
- protected FrameLayout wrapper;
- protected View titleView;
- private ImageView titleImageView;
- protected ScoreView scoreView;
- protected PauseView pauseView;
- protected GamePausedListener gamePausedListener = new GamePausedListener() {
- @Override
- public void onPause() {
- onGamePause();
- }
-
- @Override
- public void onResume() {
- onGameResume();
- }
-
- @Override
- public void onReplay() {
- onGameReplay();
- }
-
- @Override
- public String gameType() {
- return getGameType();
- }
-
- @Override
- public float score() {
- return getScore();
- }
- };
-
- protected LevelFinishedListener levelFinishedListener = new LevelFinishedListener() {
- @Override
- public void onReplay() {
- onGameReplay();
- }
-
- @Override
- public String gameType() {
- return getGameType();
- }
-
- @Override
- public float score() {
- return getScore();
- }
-
- @Override
- public int shareImageId() {
- return getShareImageId();
- }
- };
-
- protected LogicRefreshThread logicRefreshThread;
- protected UIRefreshHandler uiRefreshHandler;
- protected SoundManager soundManager;
- protected HistoryManager historyManager;
- private AsyncTask asyncLoadGameTask;
-
- protected boolean isFinishedLoading = false;
- protected boolean isPaused = false;
- protected boolean resumeAfterLoading;
- public boolean isDestroyed = false;
-
- public GameFragment() {
- this.context = getActivity();
- this.doodleConfig = null;
- this.logger = new PineappleNullLogger();
- }
-
- public GameFragment(Context context, DoodleConfig doodleConfig, PineappleLogger logger) {
- this.context = context;
- this.doodleConfig = doodleConfig;
- this.logger = logger;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (context == null) {
- return;
- }
- resume();
- if ((pauseView == null || pauseView.isPauseButtonEnabled)
- && (scoreView == null || scoreView.getVisibility() != View.VISIBLE)) {
- // If we aren't paused or finished, keep the screen on.
- AndroidUtils.forceScreenToStayOn(getActivity());
- }
- if (soundManager != null) {
- soundManager.loadMutePreference(context);
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (pauseView != null) {
- pauseView.pause();
- }
- resumeAfterLoading = false;
- if (uiRefreshHandler != null) {
- logicRefreshThread.stopHandler();
- uiRefreshHandler.stop();
- historyManager.save();
- }
- if (soundManager != null) {
- soundManager.pauseAll();
- soundManager.storeMutePreference(context);
- }
- AndroidUtils.allowScreenToTurnOff(getActivity());
- }
-
- // To be overrided by games if they want to do more cleanup on destroy.
- protected void onDestroyHelper() {
- }
-
- @Override
- public void onDestroyView() {
- isDestroyed = true;
- if (asyncLoadGameTask != null) {
- asyncLoadGameTask.cancel(true);
- }
- EventBus.getInstance().clearListeners();
- AnimatedSprite.clearCache();
- if (soundManager != null) {
- soundManager.releaseAll();
- }
- onDestroyHelper();
- super.onDestroyView();
- }
-
- protected void resume() {
- if (uiRefreshHandler == null) {
- resumeAfterLoading = true;
- } else {
- logicRefreshThread.startHandler(this);
- if (titleView.getVisibility() != View.VISIBLE) {
- playMusic();
- }
- }
- }
-
- /**
- * Loads the game. Do not override this function. Instead use the three helper functions:
- * firstPassLoadOnUiThread, secondPassLoadOnBackgroundThread, finalPassLoadOnUiThread.
- */
- public final void loadGame() {
- ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
- ActivityManager activityManager = (ActivityManager) getActivity()
- .getSystemService(android.content.Context.ACTIVITY_SERVICE);
- activityManager.getMemoryInfo(mi);
- if (mi.availMem < AVAILABLE_MEMORY_REQUIRED || AnimatedSprite.lastUsedSampleSize > 2) {
- // Low available memory, go ahead and load things with a larger sample size.
- AnimatedSprite.lastUsedSampleSize = 2;
- }
- final Handler handler = new Handler(Looper.getMainLooper());
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (context != null && getActivity() != null) {
- firstPassLoadOnUiThread();
- asyncLoadGameTask = new AsyncTask () {
- @Override
- protected Void doInBackground(Void... params) {
- if (context != null && getActivity() != null) {
- secondPassLoadOnBackgroundThread();
- }
- return null;
- }
- @Override
- protected void onPostExecute(Void result) {
- if (context != null && getActivity() != null) {
- finalPassLoadOnUiThread();
- }
- }
- };
- asyncLoadGameTask.execute();
- }
- }
- }, 100);
- }
-
- protected void firstPassLoadOnUiThread() {
- }
- protected void secondPassLoadOnBackgroundThread() {
- EventBus.getInstance().clearListeners();
- }
- protected void finalPassLoadOnUiThread() {
- }
-
- protected void onFinishedLoading() {
- PineappleLogTimer logTimer = PineappleLogTimer.getInstance();
- long latencyMs = logTimer.timeElapsedMs();
- logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, LOADING_COMPLETE)
- .withEventSubType(getGameType())
- .withLatencyMs(latencyMs).build());
- EventBus.getInstance().sendEvent(GAME_LOADED, latencyMs);
- logTimer.reset();
- if (pauseView != null) {
- pauseView.onFinishedLoading();
- }
- if (scoreView != null) {
- scoreView.setVisibility(View.VISIBLE);
- }
-
- isFinishedLoading = true;
- }
-
- public boolean isFinishedLoading() {
- return isFinishedLoading;
- }
-
- protected void startHandlers() {
- logicRefreshThread = new LogicRefreshThread();
- logicRefreshThread.start();
- uiRefreshHandler = new UIRefreshHandler();
-
- // It's annoying when the GC kicks in during gameplay and makes the game stutter. Hint
- // that now would be a good time to free up some space before the game starts.
- System.gc();
- if (resumeAfterLoading) {
- resume();
- }
- }
-
- protected void playMusic() {
- soundManager.play(R.raw.fruit_doodle_music);
- }
-
- protected void hideTitle() {
- UIUtil.fadeOutAndHide(titleView, 1, 500, new Runnable() {
- @Override
- public void run() {
- if (titleImageView != null) {
- titleImageView.setImageDrawable(null);
- titleImageView = null;
- }
- }
- });
- playMusic();
- }
-
- public boolean isGamePaused() {
- return isPaused;
- }
-
- public void onGamePause() {
- isPaused = true;
- }
-
- protected void onGameResume() {
- isPaused = false;
- }
-
- protected void onGameReplay() {
- replay();
- isPaused = false;
- PineappleLogTimer.getInstance().reset();
- }
-
- protected abstract float getScore();
- protected abstract int getShareImageId();
-
- protected boolean onTitleTapped() {
- return false;
- }
-
- protected void replay() {
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- scoreView.resetToStartState();
- }
- });
- }
-
- protected void loadSounds() {
- soundManager.loadLongSound(context, R.raw.fruit_doodle_music, true);
- soundManager.loadShortSound(context, R.raw.menu_item_click);
- soundManager.loadShortSound(context, R.raw.ui_positive_sound);
- }
-
- protected ScoreView getScoreView() {
- ScoreView scoreView = new ScoreView(context, this);
- scoreView.setDoodleConfig(doodleConfig);
- scoreView.setLogger(logger);
- scoreView.setListener(levelFinishedListener);
- return scoreView;
- }
-
- protected PauseView getPauseView() {
- PauseView pauseView = new PauseView(context);
- pauseView.setDoodleConfig(doodleConfig);
- pauseView.setLogger(logger);
- pauseView.setListener(gamePausedListener);
- return pauseView;
- }
-
- protected View getTitleView(int resId, int textId) {
- FrameLayout titleLayout = new FrameLayout(context);
- titleImageView = new ImageView(context) {
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return onTitleTapped();
- }
- };
- titleImageView.setImageResource(resId);
- titleImageView.setScaleType(ScaleType.CENTER_CROP);
-
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
- titleLayout.addView(titleImageView, lp);
-
- // Add fun fact.
- boolean defaultEnabledValue = doodleConfig == null || doodleConfig.extraData == null;
-
- // Check if we are english, or we are allowed to run in all languages.
- if (textId != 0) {
- LayoutInflater inflater = LayoutInflater.from(context);
- TextView textView = (TextView) inflater.inflate(R.layout.fact_view, titleLayout, false);
-
- // Set text and font
- textView.setText(AndroidUtils.getText(context.getResources(), textId));
- FontHelper.makeLobster(textView);
-
- lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM);
- titleLayout.addView(textView, lp);
- }
-
- return titleLayout;
- }
-
- @Override
- public void onShareClicked() {
- Activity activity = getActivity();
- if (activity instanceof PineappleActivity) {
- AppInvitesFragment invites = ((PineappleActivity) activity).getAppInvitesFragment();
- if (invites != null) {
- invites.sendGenericInvite();
- }
- }
- }
-
- protected abstract String getGameType();
-
- public void onBackPressed() {
- pauseView.pause();
- }
-
- public abstract boolean isGameOver();
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameLoop.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameLoop.java
deleted file mode 100644
index 7fe832381..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameLoop.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * Interface to handle the updating of game logic.
- */
-public interface GameLoop {
-
- /**
- * @param deltaMs Milliseconds since the last time update was called. Will be capped to avoid
- * big jumps.
- */
- void update(float deltaMs);
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameOverlayButton.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameOverlayButton.java
deleted file mode 100644
index 78cf42117..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameOverlayButton.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-import com.google.android.apps.santatracker.doodles.R;
-
-/**
- * A button in the game overlay screens (pause and end screens).
- */
-public class GameOverlayButton extends RelativeLayout {
-
- public GameOverlayButton(Context context) {
- this(context, null);
- }
-
- public GameOverlayButton(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public GameOverlayButton(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- inflate(context, R.layout.game_overlay_button, this);
- TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.GameOverlayButton, 0, 0);
-
- int imageRes =
- ta.getResourceId(R.styleable.GameOverlayButton_imageSrc, R.drawable.common_btn_pause);
- ImageView icon = (ImageView) findViewById(R.id.game_overlay_button_image);
- icon.setImageResource(imageRes);
-
- String text = ta.getString(R.styleable.GameOverlayButton_text);
- TextView description = (TextView) findViewById(R.id.game_overlay_button_description);
- if (text == null || text.isEmpty()) {
- description.setVisibility(GONE);
- } else {
- description.setText(text);
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameType.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameType.java
deleted file mode 100644
index 660684b89..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/GameType.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * Enum for all the different game types we have.
- * Used by each type to get their proper history content.
- */
-public enum GameType {
- GOLF,
- TENNIS,
- SWIMMING,
- JUMPING,
- BMX,
- WATER_POLO,
- PURSUIT,
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/HistoryManager.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/HistoryManager.java
deleted file mode 100644
index 068f2b7d6..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/HistoryManager.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.util.Log;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Maintains the history and stats of what the user has accomplished.
- *
- * Note that this class handles the serializing into JSON instead of each game. This was done
- * to make it easier to make a game picker that showed your status on each game. Since there are
- * canonical types it would then know how to read them. We add a setArbitraryData and
- * getArbitaryData for any game that wants to put other kind of information in.
- */
-public class HistoryManager {
- private static final String TAG = HistoryManager.class.getSimpleName();
-
- public static final String BEST_PLACE_KEY = "place";
- public static final String BEST_STAR_COUNT_KEY = "stars";
- public static final String BEST_TIME_MILLISECONDS_KEY = "time";
- public static final String BEST_SCORE_KEY = "score";
- public static final String BEST_DISTANCE_METERS_KEY = "distance";
- public static final String ARBITRARY_DATA_KEY = "arb";
-
- private static final String FILENAME = "history.json";
-
- private final Context context;
- private volatile JSONObject history;
-
- /**
- * Listener for when the history is loaded.
- */
- public static interface HistoryListener {
- public void onFinishedLoading();
- public void onFinishedSaving();
- }
- private HistoryListener listener;
-
- /**
- * Creates a history manager.
- * HistoryListener can be null.
- */
- public HistoryManager(Context context, HistoryListener listener) {
- this.context = context;
- this.listener = listener;
- // While history is loading from disk, we ignore any changes clients might ask for.
- history = null;
- load();
- }
-
- public void setListener(HistoryListener listener) {
- this.listener = listener;
- }
-
- /**
- * Gets the json object for a particular game type.
- */
- private JSONObject getGameObject(GameType gameType) throws JSONException {
- if (history == null) {
- throw new JSONException("null history");
- }
- JSONObject gameObject = history.optJSONObject(gameType.toString());
- if (gameObject == null) {
- gameObject = new JSONObject();
- }
- return gameObject;
- }
-
- /************************ Setters *****************************/
- /**
- * Set the best place (1st, 2nd, 3rd) for a game type.
- * NOTE: It's expected for the client to figure out if it is the best place.
- */
- public void setBestPlace(GameType gameType, int place) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- gameObject.put(BEST_PLACE_KEY, place);
- history.put(gameType.toString(), gameObject);
- } catch (JSONException e) {
- Log.e(TAG, "error setting place", e);
- }
- }
-
- /**
- * Set the best star count for a game type.
- * NOTE: It's expected for the client to figure out if it is the best star count.
- */
- public void setBestStarCount(GameType gameType, int count) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- gameObject.put(BEST_STAR_COUNT_KEY, count);
- history.put(gameType.toString(), gameObject);
- } catch (JSONException e) {
- Log.e(TAG, "error setting place", e);
- }
- }
-
- /**
- * Set the best time for a game type.
- * NOTE: it's expected for the client to figure out if it is the best time since some will want
- * bigger and some will want smaller numbers.
- */
- public void setBestTime(GameType gameType, long timeInMilliseconds) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- gameObject.put(BEST_TIME_MILLISECONDS_KEY, timeInMilliseconds);
- history.put(gameType.toString(), gameObject);
- } catch (JSONException e) {
- Log.e(TAG, "error setting time", e);
- }
- }
-
- /**
- * Set the best score for a game type.
- * NOTE: it's expected for the client to figure out if it is the best score since some will want
- * bigger and some will want smaller numbers.
- */
- public void setBestScore(GameType gameType, double score) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- gameObject.put(BEST_SCORE_KEY, score);
- history.put(gameType.toString(), gameObject);
- } catch (JSONException e) {
- Log.e(TAG, "error setting score", e);
- }
- }
-
- /**
- * Set the best distance for a game type.
- * NOTE: it's expected for the client to figure out if it is the best distance since some will
- * want bigger and some will want smaller numbers.
- */
- public void setBestDistance(GameType gameType, double distanceInMeters) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- gameObject.put(BEST_DISTANCE_METERS_KEY, distanceInMeters);
- history.put(gameType.toString(), gameObject);
- } catch (JSONException e) {
- Log.e(TAG, "error setting distance", e);
- }
- }
-
- /**
- * Sets an arbitrary jsonObject a game might want.
- */
- public void setArbitraryData(GameType gameType, JSONObject data) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- gameObject.put(ARBITRARY_DATA_KEY, data);
- history.put(gameType.toString(), gameObject);
- } catch (JSONException e) {
- Log.e(TAG, "error setting distance", e);
- }
- }
-
- /************************ Getters *****************************/
-
- /**
- * Returns the best place so far. Null if no value has been given yet.
- */
- public Integer getBestPlace(GameType gameType) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- return gameObject.getInt(BEST_PLACE_KEY);
- } catch (JSONException e) {
- return null;
- }
- }
-
- /**
- * Returns the best star count so far. Null if no value has been given yet.
- */
- public Integer getBestStarCount(GameType gameType) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- return gameObject.getInt(BEST_STAR_COUNT_KEY);
- } catch (JSONException e) {
- return null;
- }
- }
-
- /**
- * Returns the best time so far. Null if no value has been given yet.
- */
- public Long getBestTime(GameType gameType) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- return gameObject.getLong(BEST_TIME_MILLISECONDS_KEY);
- } catch (JSONException e) {
- return null;
- }
- }
-
- /**
- * Returns the best score so far. Null if no value has been given yet.
- */
- public Double getBestScore(GameType gameType) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- return gameObject.getDouble(BEST_SCORE_KEY);
- } catch (JSONException e) {
- return null;
- }
- }
-
- /**
- * Returns the best distance so far. Null if no value has been given yet.
- */
- public Double getBestDistance(GameType gameType) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- return gameObject.getDouble(BEST_DISTANCE_METERS_KEY);
- } catch (JSONException e) {
- return null;
- }
- }
-
- /**
- * Returns arbitrary JSONObject a game might want. Null if no value has been given yet.
- */
- public JSONObject getArbitraryData(GameType gameType) {
- try {
- JSONObject gameObject = getGameObject(gameType);
- return gameObject.getJSONObject(ARBITRARY_DATA_KEY);
- } catch (JSONException e) {
- return null;
- }
- }
-
- /********************** File Management **************************/
- /**
- * Saves the file in the background.
- */
- public void save() {
- new AsyncTask () {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- FileOutputStream outputStream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
- byte[] bytes = history.toString().getBytes();
- outputStream.write(bytes);
- outputStream.close();
- Log.i(TAG, "Saved: " + history);
- } catch (IOException e) {
- Log.w(TAG, "Couldn't save JSON at: " + FILENAME);
- } catch (Exception e) {
- Log.w(TAG, "Crazy exception happened", e);
- }
- return null;
- }
- @Override
- protected void onPostExecute(Void result) {
- if (listener != null) {
- listener.onFinishedSaving();
- }
- }
- }.execute();
- }
-
- /**
- * Loads the history object from file. Then merges with any changes that might have occured while
- * we waited for it to load.
- */
- private void load() {
- new AsyncTask () {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- File file = new File(context.getFilesDir(), FILENAME);
- int length = (int) file.length();
- if (length <= 0) {
- history = new JSONObject();
- return null;
- }
-
- byte[] bytes = new byte[length];
- FileInputStream inputStream = new FileInputStream(file);
- inputStream.read(bytes);
- inputStream.close();
-
- history = new JSONObject(new String(bytes, "UTF-8"));
- Log.i(TAG, "Loaded: " + history);
- } catch (JSONException e) {
- Log.w(TAG, "Couldn't create JSON for: " + FILENAME);
- } catch (UnsupportedEncodingException e) {
- Log.d(TAG, "Couldn't decode: " + FILENAME);
- } catch (IOException e) {
- Log.w(TAG, "Couldn't read history: " + FILENAME);
- }
- return null;
- }
- @Override
- protected void onPostExecute(Void result) {
- if (listener != null) {
- listener.onFinishedLoading();
- }
- }
- }.execute();
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Interpolator.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Interpolator.java
deleted file mode 100644
index 7fa07aec1..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Interpolator.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * Interpolates values for Tweens. Used to implement easing and repeated movement.
- */
-public interface Interpolator {
-
- Interpolator LINEAR = new Interpolator() {
- @Override
- public float getValue(float percent, float initialValue, float finalValue) {
- return initialValue + (finalValue - initialValue) * percent;
- }
- };
- Interpolator EASE_IN = new Interpolator() {
- @Override
- public float getValue(float percent, float initialValue, float finalValue) {
- return initialValue + (finalValue - initialValue) * percent * percent;
- }
- };
- Interpolator EASE_OUT = new Interpolator() {
- @Override
- public float getValue(float percent, float initialValue, float finalValue) {
- return initialValue - ((finalValue - initialValue) * percent * (percent - 2));
- }
- };
- Interpolator EASE_IN_AND_OUT = new Interpolator() {
- @Override
- public float getValue(float percent, float initialValue, float finalValue) {
- // Simple sigmoid function: y = 3 * x^2 - 2 * x^3
- return LINEAR.getValue(3 * percent * percent - 2 * percent * percent * percent,
- initialValue, finalValue);
- }
- };
- Interpolator FAST_IN = new Interpolator() {
- @Override
- public float getValue(float percent, float initialValue, float finalValue) {
- return initialValue + (finalValue - initialValue) * (-percent * (percent - 2));
- }
- };
- Interpolator FAST_IN_AND_HOLD = new Interpolator() {
- @Override
- public float getValue(float percent, float initialValue, float finalValue) {
- percent *= 2;
- if (percent > 1) {
- percent = 1;
- }
- return initialValue + (finalValue - initialValue) * (-percent * (percent - 2));
- }
- };
-
- Interpolator OVERSHOOT = new Interpolator() {
- @Override
- public float getValue(float percent, float initialValue, float finalValue) {
- percent -= 1;
- percent = percent * percent * (3 * percent + 2) + 1;
- return initialValue + (finalValue - initialValue) * percent;
- }
- };
-
- float getValue(float percent, float initialValue, float finalValue);
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/LaunchDecisionMaker.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/LaunchDecisionMaker.java
deleted file mode 100644
index 2b8f38a1f..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/LaunchDecisionMaker.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-
-import com.google.android.apps.santatracker.doodles.pursuit.PursuitFragment;
-import com.google.android.apps.santatracker.doodles.tilt.SwimmingFragment;
-import com.google.android.apps.santatracker.doodles.waterpolo.WaterPoloFragment;
-
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.DOODLE_LAUNCHED;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.RUNNING_GAME_TYPE;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.SWIMMING_GAME_TYPE;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.WATERPOLO_GAME_TYPE;
-
-/**
- * Determines which Fragment we should start with based on the DoodleConfig data. For example,
- * starting with the Swimming game versus going to the Main Menu.
- */
-public class LaunchDecisionMaker {
-
- // Key for value of final Present Drop score.
- public static final String EXTRA_PRESENT_DROP_SCORE = "presentDropScore";
- public static final String EXTRA_PRESENT_DROP_STARS = "presentDropStars";
-
- // These are the keys and values we expect from the DoodleConfig setup in doodle canon.
- public static final String START_GAME_KEY = "startUp";
- public static final String RUNNING_GAME_VALUE = "running";
- public static final String WATERPOLO_GAME_VALUE = "waterpolo";
- public static final String SWIMMING_GAME_VALUE = "swimming";
-
- public static void finishActivity(Context context) {
- AndroidUtils.getActivityFromContext(context).finish();
- }
-
- public static void finishActivityWithResult(Context context, int resultCode, Bundle extras) {
- Activity activity = AndroidUtils.getActivityFromContext(context);
- Intent intent = activity.getIntent();
- intent.putExtras(extras);
- activity.setResult(resultCode, intent);
- activity.finish();
- }
- public static Fragment makeFragment(@Nullable Context context,
- @Nullable DoodleConfig doodleConfig, PineappleLogger logger) {
- String gameType = null;
- Fragment gameFragment = null;
- if (doodleConfig != null && doodleConfig.extraData != null) {
- // Check if we have a startup value.
- CharSequence startUp = doodleConfig.extraData.getCharSequence(START_GAME_KEY);
- if (startUp != null) {
- // Launch the right game if so.
- if (RUNNING_GAME_VALUE.equals(startUp)) {
- gameType = RUNNING_GAME_TYPE;
- gameFragment = new PursuitFragment(context, doodleConfig, logger);
- } else if (WATERPOLO_GAME_VALUE.equals(startUp)) {
- gameType = WATERPOLO_GAME_TYPE;
- gameFragment = new WaterPoloFragment(context, doodleConfig, logger);
- } else if (SWIMMING_GAME_VALUE.equals(startUp)) {
- gameType = SWIMMING_GAME_TYPE;
- gameFragment = new SwimmingFragment(context, doodleConfig, logger, false);
- }
- }
- }
-
- if (gameFragment != null) {
- logger.logGameLaunchEvent(context, gameType, DOODLE_LAUNCHED);
- PineappleLogTimer.getInstance().reset();
- return gameFragment;
- } else {
- throw new IllegalArgumentException("Invalid DoodleConfig: " + doodleConfig);
- }
- }
-
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/LogicRefreshThread.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/LogicRefreshThread.java
deleted file mode 100644
index 6bfb0d073..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/LogicRefreshThread.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-/**
- * Thread subclass which handles refreshing the game logic.
- */
-public class LogicRefreshThread extends Thread {
- private static final int REFRESH_MODEL = 0;
-
- // Wait at least this long between updates.
- // Update at 120 FPS so that stutters due to draw-loop synchronization are less noticeable.
- private static final int MODEL_INTERVAL_MS = 1000 / 60;
-
- private Handler handler;
- private final ConditionVariable handlerCreatedCV = new ConditionVariable();
-
- // Toggled in start/stop, and used in handleMessage to conditionally schedule the next refresh.
- private volatile boolean running;
-
- private GameLoop gameLoop;
- private long lastTick;
- private int framesSkippedSinceLastUpdate = 0;
-
- public LogicRefreshThread() {
- setPriority(Thread.MAX_PRIORITY);
- }
-
- @Override
- public void run() {
- Looper.prepare();
-
- handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (running && gameLoop != null) {
- if (msg.what == REFRESH_MODEL) {
- float deltaMs = System.currentTimeMillis() - lastTick;
- // Cap deltaMs. Better for game to appear to slow down than have skips/jumps.
- deltaMs = Math.min(100, deltaMs);
- deltaMs *= Debug.SPEED_MULTIPLIER;
- lastTick = System.currentTimeMillis();
-
- framesSkippedSinceLastUpdate++;
- if (framesSkippedSinceLastUpdate >= Debug.FRAME_SKIP) {
- framesSkippedSinceLastUpdate = 0;
- if (gameLoop != null) {
- gameLoop.update(deltaMs);
- }
- }
-
- // Wait different amounts of time depending on how much time the game loop took.
- // Wait at least 1ms to avoid a mysterious memory leak.
- long timeToUpdate = System.currentTimeMillis() - lastTick;
- sendEmptyMessageDelayed(REFRESH_MODEL, Math.max(1, MODEL_INTERVAL_MS - timeToUpdate));
- }
- }
- }
- };
- handlerCreatedCV.open();
-
- Looper.loop();
- }
-
- public void startHandler(GameLoop gameLoop) {
- this.gameLoop = gameLoop;
- running = true;
- lastTick = System.currentTimeMillis();
-
- handlerCreatedCV.block();
- handler.sendEmptyMessage(REFRESH_MODEL);
- }
-
- public void stopHandler() {
- running = false;
- gameLoop = null;
-
- handlerCreatedCV.block();
- handler.removeMessages(REFRESH_MODEL);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/MultiSpriteActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/MultiSpriteActor.java
deleted file mode 100644
index 9a1a6f1c1..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/MultiSpriteActor.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.res.Resources;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * An actor which has multiple sprites which it can switch between.
- */
-public class MultiSpriteActor extends SpriteActor {
- private static final String TAG = MultiSpriteActor.class.getSimpleName();
- public Map sprites;
-
- public MultiSpriteActor(Map sprites, String selectedSpriteKey,
- Vector2D position, Vector2D velocity) {
- super(sprites.get(selectedSpriteKey), position, velocity);
- this.sprites = sprites;
- }
-
- public void setSprite(String key) {
- if (sprites.containsKey(key)) {
- sprite = sprites.get(key);
- } else {
- Log.w(TAG, "Couldn't set sprite, unrecognized key: " + key);
- }
- }
-
- /**
- * A class which makes it easier to re-construct MultiSpriteActors.
- */
- public static class Data {
- public String key;
- public int[] idList;
- public int numFrames;
- public Data(String key, int[] idList) {
- this.key = key;
- this.idList = idList;
- this.numFrames = idList.length;
- }
- public AnimatedSprite getSprite(Resources resources) {
- if (idList != null) {
- return AnimatedSprite.fromFrames(resources, idList);
- }
- return null;
- }
- }
-
- public static MultiSpriteActor create(
- Data[] data, String selectedSprite, Vector2D position, Resources resources) {
- Map sprites = new HashMap<>();
- for (int i = 0; i < data.length; i++) {
- sprites.put(data[i].key, data[i].getSprite(resources));
- }
- return new MultiSpriteActor(sprites, selectedSprite, position, Vector2D.get());
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PauseView.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PauseView.java
deleted file mode 100644
index 674225a0b..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PauseView.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.OvershootInterpolator;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.Builder;
-import com.google.android.apps.santatracker.doodles.shared.sound.SoundManager;
-
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.DEFAULT_DOODLE_NAME;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.HOME_CLICKED;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.MUTE_CLICKED;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.PAUSE_CLICKED;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.REPLAY_CLICKED;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.UNMUTE_CLICKED;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.UNPAUSE_CLICKED;
-
-/**
- * The overlay which is shown when a game is paused.
- */
-public class PauseView extends FrameLayout {
-
- private static final int BIG_PAUSE_FADE_IN_MS = 200; // Fading in paused screen elements.
- private static final int FADE_IN_MS = 500; // Fading in paused screen elements.
- private static final int FADE_OUT_MS = 200; // Fading out paused screen elements.
- private static final int BUMP_MS = 200; // The paused text over-zooms a bit on pause.
- private static final int RELAX_MS = 200; // The paused text shrinks a bit after zooming up.
- private static final float ZOOM_UP_SCALE_OVERSHOOT = 1.2f;
-
- public static final int FADE_DURATION_MS = 400; // The pause button fading in and out.
-
- /**
- * A listener for interacting with the PauseView.
- */
- public interface GamePausedListener {
- void onPause();
- void onResume();
- void onReplay();
-
- String gameType();
- float score();
- }
- private GamePausedListener listener;
- private DoodleConfig doodleConfig;
- private PineappleLogger logger;
-
- private ImageButton muteButton;
- private GameOverlayButton pauseButton;
- private View resumeButton;
- private View buttonContainer;
- private View background;
-
- public boolean isPauseButtonEnabled = true;
- private float backgroundAlpha;
- private float pauseButtonAlpha;
-
- public PauseView(Context context) {
- this(context, null);
- }
-
- public PauseView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PauseView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- loadLayout(context);
- hidePauseScreen();
- }
-
- public void setDoodleConfig(DoodleConfig doodleConfig) {
- this.doodleConfig = doodleConfig;
- }
-
- public void setLogger(PineappleLogger logger) {
- this.logger = logger;
- }
-
- public GamePausedListener getListener() {
- return this.listener;
- }
-
- public void setListener(GamePausedListener listener) {
- this.listener = listener;
- }
-
- public void hidePauseButton() {
- isPauseButtonEnabled = false;
- UIUtil.fadeOutAndHide(pauseButton, FADE_DURATION_MS, pauseButtonAlpha);
- }
-
- public void showPauseButton() {
- isPauseButtonEnabled = true;
- UIUtil.showAndFadeIn(pauseButton, FADE_DURATION_MS, pauseButtonAlpha);
- }
-
- public void onFinishedLoading() {
- setVisibility(View.VISIBLE);
- }
-
- protected void loadLayout(final Context context) {
- setVisibility(View.INVISIBLE);
- LayoutInflater inflater = LayoutInflater.from(context);
-
- View view = inflater.inflate(R.layout.pause_view, this);
-
- buttonContainer = view.findViewById(R.id.button_container);
-
- muteButton = (ImageButton) view.findViewById(R.id.mute_button);
- if (SoundManager.soundsAreMuted) {
- muteButton.setImageResource(R.drawable.common_btn_speaker_off);
- }
- muteButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- boolean shouldMute = !SoundManager.soundsAreMuted;
-
- String logEventName = shouldMute ? MUTE_CLICKED : UNMUTE_CLICKED;
- logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, logEventName)
- .withEventSubType(listener.gameType()).build());
-
- muteButton.setImageResource(
- shouldMute ? R.drawable.common_btn_speaker_off : R.drawable.common_btn_speaker_on);
- muteButton.setContentDescription(context.getResources().getString(
- shouldMute ? R.string.unmute : R.string.mute));
- EventBus.getInstance().sendEvent(EventBus.MUTE_SOUNDS, shouldMute);
- }
- });
-
- pauseButton = (GameOverlayButton) view.findViewById(R.id.pause_button);
- pauseButtonAlpha = pauseButton.getAlpha();
- pauseButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- pause();
- }
- });
-
- resumeButton = view.findViewById(R.id.resume_button);
- resumeButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, UNPAUSE_CLICKED)
- .withEventSubType(listener.gameType()).build());
- unpause();
- }
- });
-
- View replayButton = view.findViewById(R.id.replay_button);
- replayButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, REPLAY_CLICKED)
- .withEventSubType(listener.gameType())
- .withLatencyMs(PineappleLogTimer.getInstance().timeElapsedMs())
- .withEventValue1(listener.score())
- .build());
- replay();
- }
- });
-
- View menuButton = view.findViewById(R.id.menu_button);
- menuButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
-
- logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, HOME_CLICKED)
- .withEventSubType(listener.gameType())
- .withLatencyMs(PineappleLogTimer.getInstance().timeElapsedMs())
- .withEventValue1(listener.score())
- .build());
-
- LaunchDecisionMaker.finishActivity(context);
- }
- });
-
- background = view.findViewById(R.id.pause_view_background);
- backgroundAlpha = background.getAlpha();
- }
-
- private void replay() {
- PineappleLogTimer.getInstance().unpause();
- hidePauseScreen();
- if (listener != null) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
- listener.onReplay();
- }
- }
-
- /**
- * Pauses the current game.
- */
- public void pause() {
- if (!isPauseButtonEnabled) {
- return;
- }
- logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, PAUSE_CLICKED)
- .withEventSubType(listener.gameType())
- .withLatencyMs(PineappleLogTimer.getInstance().timeElapsedMs())
- .build());
- PineappleLogTimer.getInstance().pause();
- if (listener != null) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
- listener.onPause();
- }
- showPauseScreen();
- AndroidUtils.allowScreenToTurnOff(getContext());
- }
-
- private void unpause() {
- PineappleLogTimer.getInstance().unpause();
- hidePauseScreen();
- if (listener != null) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
- listener.onResume();
- }
- AndroidUtils.forceScreenToStayOn(getContext());
- }
-
- private void showPauseScreen() {
- isPauseButtonEnabled = false;
- SoundManager soundManager = SoundManager.getInstance();
- soundManager.mute(R.raw.fruit_doodle_music);
- soundManager.pauseShortSounds();
-
- if (SoundManager.soundsAreMuted) {
- muteButton.setImageResource(R.drawable.common_btn_speaker_off);
- } else {
- muteButton.setImageResource(R.drawable.common_btn_speaker_on);
- }
-
- muteButton.setAlpha(0.0f);
- resumeButton.setAlpha(0.0f);
- buttonContainer.setAlpha(0);
- background.setAlpha(0);
-
- muteButton.setVisibility(VISIBLE);
- resumeButton.setVisibility(VISIBLE);
- buttonContainer.setVisibility(VISIBLE);
- background.setVisibility(VISIBLE);
-
- ValueAnimator fadeBigPauseIn = UIUtil.animator(BIG_PAUSE_FADE_IN_MS,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- resumeButton.setAlpha((float) valueAnimator.getAnimatedValue("textAlpha"));
- background.setAlpha((float) valueAnimator.getAnimatedValue("bgAlpha"));
- }
- },
- UIUtil.floatValue("textAlpha", 0, 1),
- UIUtil.floatValue("bgAlpha", 0, backgroundAlpha)
- );
-
- ValueAnimator fadePauseButtonOut = UIUtil.animator(BIG_PAUSE_FADE_IN_MS,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- pauseButton.setAlpha((float) valueAnimator.getAnimatedValue("pauseButtonAlpha"));
- }
- },
- UIUtil.floatValue("pauseButtonAlpha", pauseButtonAlpha, 0)
- );
- fadePauseButtonOut.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- pauseButton.setVisibility(INVISIBLE);
- }
- });
-
- ValueAnimator zoomUp = UIUtil.animator(BUMP_MS, new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float scale = (float) valueAnimator.getAnimatedValue("scale");
- resumeButton.setScaleX(scale);
- resumeButton.setScaleY(scale);
- }
- },
- UIUtil.floatValue("scale", 0, ZOOM_UP_SCALE_OVERSHOOT)
- );
-
- ValueAnimator relax = UIUtil.animator(RELAX_MS, new OvershootInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float scale = (float) valueAnimator.getAnimatedValue("scale");
- resumeButton.setScaleX(scale);
- resumeButton.setScaleY(scale);
- }
- },
- UIUtil.floatValue("scale", ZOOM_UP_SCALE_OVERSHOOT, 1));
-
- ValueAnimator fadeIn = UIUtil.animator(FADE_IN_MS, new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- muteButton.setAlpha((float) valueAnimator.getAnimatedValue("alpha"));
- buttonContainer.setAlpha((float) valueAnimator.getAnimatedValue("alpha"));
- }
- },
- UIUtil.floatValue("alpha", 0, 1)
- );
- AnimatorSet animations = new AnimatorSet();
- animations.play(fadeBigPauseIn).with(zoomUp);
- animations.play(fadePauseButtonOut).with(zoomUp);
- animations.play(relax).after(zoomUp);
- animations.play(fadeIn).after(zoomUp);
- animations.start();
- }
-
- private void hidePauseScreen() {
- SoundManager soundManager = SoundManager.getInstance();
- soundManager.unmute(R.raw.fruit_doodle_music);
- soundManager.resumeShortSounds();
-
- ValueAnimator fadeOut = UIUtil.animator(FADE_OUT_MS, new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- muteButton.setAlpha((float) valueAnimator.getAnimatedValue("overlayAlpha"));
- background.setAlpha((float) valueAnimator.getAnimatedValue("bgAlpha"));
- buttonContainer.setAlpha((float) valueAnimator.getAnimatedValue("overlayAlpha"));
-
- resumeButton.setAlpha((float) valueAnimator.getAnimatedValue("overlayAlpha"));
- resumeButton.setScaleX((float) valueAnimator.getAnimatedValue("iconScale"));
- resumeButton.setScaleY((float) valueAnimator.getAnimatedValue("iconScale"));
-
- }
- },
- UIUtil.floatValue("overlayAlpha", 1, 0),
- UIUtil.floatValue("bgAlpha", backgroundAlpha, 0),
- UIUtil.floatValue("iconScale", 1, 2)
- );
- fadeOut.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- muteButton.setVisibility(INVISIBLE);
- resumeButton.setVisibility(INVISIBLE);
- buttonContainer.setVisibility(INVISIBLE);
- background.setVisibility(INVISIBLE);
-
- isPauseButtonEnabled = true;
- }
- });
-
- pauseButton.setAlpha(0.0f);
- pauseButton.setVisibility(VISIBLE);
- ValueAnimator fadePauseButtonIn = UIUtil.animator(FADE_OUT_MS,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- pauseButton.setAlpha((float) valueAnimator.getAnimatedValue("alpha"));
- }
- },
- UIUtil.floatValue("alpha", 0, pauseButtonAlpha)
- );
-
- AnimatorSet animations = new AnimatorSet();
- animations.play(fadeOut).with(fadePauseButtonIn);
- animations.start();
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleDebugLogger.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleDebugLogger.java
deleted file mode 100644
index 0fe2652a9..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleDebugLogger.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.util.Log;
-
-/**
- * A stub logger for the purposes of testing output of log statements in the Pineapple 2016 games.
- */
-public class PineappleDebugLogger extends PineappleLogger {
- private static final String TAG = PineappleDebugLogger.class.getSimpleName();
-
- @Override
- public void logEvent(PineappleLogEvent event) {
- Log.d(TAG, event.toString());
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleLogEvent.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleLogEvent.java
deleted file mode 100644
index 14bc60a30..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleLogEvent.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.support.annotation.Nullable;
-
-/**
- * A class used for constructing log events for the Pineapple 2016 games.
- */
-public class PineappleLogEvent {
- public static final String DEFAULT_DOODLE_NAME = "olympics";
-
- public static final String MUTE_CLICKED = "clicked mute";
- public static final String UNMUTE_CLICKED = "clicked unmute";
- public static final String PAUSE_CLICKED = "clicked pause";
- public static final String UNPAUSE_CLICKED = "clicked unpause";
- public static final String REPLAY_CLICKED = "clicked replay";
- public static final String SHARE_CLICKED = "clicked share";
- public static final String HOME_CLICKED = "clicked home";
- public static final String LOADING_COMPLETE = "loading complete";
- public static final String DOODLE_LAUNCHED = "doodle launched";
- public static final String GAME_OVER = "game over";
-
- public static final String DISTINCT_GAMES_PLAYED = "distinct games";
- public static final String RUNNING_GAME_TYPE = "running";
- public static final String WATERPOLO_GAME_TYPE = "waterpolo";
- public static final String SWIMMING_GAME_TYPE = "swimming";
-
-
- public final String doodleName;
- public final String eventName;
- @Nullable public final String eventSubType;
- @Nullable public final Float eventValue1;
- @Nullable public final Float eventValue2;
- @Nullable public final Long latencyMs;
-
- private PineappleLogEvent(String doodleName, String eventName,
- @Nullable String eventSubType, @Nullable Float eventValue1,
- @Nullable Float eventValue2, @Nullable Long latencyMs) {
- this.doodleName = doodleName;
- this.eventName = eventName;
- this.eventSubType = eventSubType;
- this.eventValue1 = eventValue1;
- this.eventValue2 = eventValue2;
- this.latencyMs = latencyMs;
- }
-
- @Override
- public String toString() {
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append("PineappleLogEvent(" + doodleName);
- stringBuilder.append(", " + eventName);
- stringBuilder.append(", " + eventSubType);
- stringBuilder.append(", " + eventValue1);
- stringBuilder.append(", " + eventValue2);
- stringBuilder.append(", " + latencyMs + ")");
- return stringBuilder.toString();
- }
-
- /**
- * A helper class to build PineappleLogEvents.
- */
- public static class Builder {
- private String doodleName;
- private String eventName;
- @Nullable private String eventSubType;
- @Nullable private Float eventValue1;
- @Nullable private Float eventValue2;
- @Nullable private Long latencyMs;
-
- public Builder(String doodleName, String eventName) {
- this.doodleName = doodleName;
- this.eventName = eventName;
- this.eventSubType = null;
- this.eventValue1 = null;
- this.eventValue2 = null;
- this.latencyMs = null;
- }
-
- public Builder withEventSubType(String eventSubType) {
- this.eventSubType = eventSubType;
- return this;
- }
-
- public Builder withEventValue1(float eventValue1) {
- this.eventValue1 = eventValue1;
- return this;
- }
-
- public Builder withEventValue2(float eventValue2) {
- this.eventValue2 = eventValue2;
- return this;
- }
-
- public Builder withLatencyMs(long latencyMs) {
- this.latencyMs = latencyMs;
- return this;
- }
-
- public PineappleLogEvent build() {
- return new PineappleLogEvent(
- doodleName, eventName, eventSubType, eventValue1, eventValue2, latencyMs);
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleLogTimer.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleLogTimer.java
deleted file mode 100644
index 8778e6bd6..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleLogTimer.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.support.annotation.VisibleForTesting;
-
-/**
- * A helper for timing log events in the Pineapple 2016 games.
- */
-public class PineappleLogTimer {
- private static PineappleLogTimer instance;
-
- public static PineappleLogTimer getInstance() {
- if (instance == null) {
- instance = new PineappleLogTimer();
- }
- return instance;
- }
-
- private long startTimeMs;
- private long pauseTimeMs;
- private boolean isPaused;
- private LogClock clock;
-
- private PineappleLogTimer() {
- this(new LogClock());
- }
-
- @VisibleForTesting
- PineappleLogTimer(LogClock clock) {
- this.clock = clock;
- startTimeMs = clock.currentTimeMillis();
- }
-
- public void reset() {
- startTimeMs = clock.currentTimeMillis();
- pauseTimeMs = clock.currentTimeMillis();
- unpause();
- }
-
- public long timeElapsedMs() {
- if (isPaused) {
- return pauseTimeMs - startTimeMs;
- }
- return clock.currentTimeMillis() - startTimeMs;
- }
-
- public void pause() {
- if (!isPaused) {
- pauseTimeMs = clock.currentTimeMillis();
- isPaused = true;
- }
- }
-
- public void unpause() {
- if (isPaused) {
- long pauseDurationMs = clock.currentTimeMillis() - pauseTimeMs;
- startTimeMs += pauseDurationMs;
- isPaused = false;
- }
- }
-
- /**
- * Wrapper around System.currentTimeMillis so that we can test PineappleLogTimer.
- */
- @VisibleForTesting
- static class LogClock {
- public long currentTimeMillis() {
- return System.currentTimeMillis();
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleLogger.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleLogger.java
deleted file mode 100644
index 2017e0b9f..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleLogger.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.AsyncTask;
-
-import com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.Builder;
-
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.DEFAULT_DOODLE_NAME;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.DISTINCT_GAMES_PLAYED;
-
-/**
- * Interface for logging DoodleEvents within the pineapple games.
- */
-public abstract class PineappleLogger {
- private static final String PREFS_NAME = "PineappleLoggerPrefs";
-
- public abstract void logEvent(PineappleLogEvent event);
-
- public void logGameLaunchEvent(
- final Context context, final String gameType, final String eventName) {
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... voids) {
- SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS_NAME, 0);
- SharedPreferences.Editor editor = sharedPreferences.edit();
-
- int gamePlays = sharedPreferences.getInt(gameType, 0);
- int distinctGamesPlayed = sharedPreferences.getInt(DISTINCT_GAMES_PLAYED, 0);
- if (gamePlays == 0) {
- // If this is our first time playing this game, increment distinct games played.
- editor.putInt(DISTINCT_GAMES_PLAYED, ++distinctGamesPlayed);
- }
- editor.putInt(gameType, ++gamePlays);
- editor.commit();
-
- logEvent(
- new Builder(DEFAULT_DOODLE_NAME, eventName)
- .withEventSubType(gameType)
- .withEventValue1(gamePlays)
- .withEventValue2(distinctGamesPlayed)
- .build());
-
- return null;
- }
- }.execute();
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleNullLogger.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleNullLogger.java
deleted file mode 100644
index 6cb019566..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/PineappleNullLogger.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * A stub of a logger which does nothing.
- */
-public class PineappleNullLogger extends PineappleLogger {
-
- @Override
- public void logEvent(PineappleLogEvent event) {
- // Do nothing.
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Process.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Process.java
deleted file mode 100644
index e5f3b9ca4..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Process.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * A generic class for running some piece of code. This class is most useful when used inside of
- * a process chain, which allows you explicitly define portions of code which should be run in
- * serial (generally inside of the update loop).
- */
-public abstract class Process {
-
- public Process() {
- }
-
- /**
- * The outer update function for this process. Note that, when implementing the logic for the
- * process, updateLogic() should generally be overridden instead of update().
- * @param deltaMs
- */
- public void update(float deltaMs) {
- if (!isFinished()) {
- updateLogic(deltaMs);
- }
- }
-
- public ProcessChain then(Process other) {
- return new ProcessChain(this).then(other);
- }
-
- public ProcessChain then(ProcessChain pc) {
- return new ProcessChain(this).then(pc);
- }
-
- public abstract void updateLogic(float deltaMs);
-
- public abstract boolean isFinished();
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ProcessChain.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ProcessChain.java
deleted file mode 100644
index 8da81311f..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ProcessChain.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-
-/**
- * A chain of processes, which are executed in the order in which they are added. When a process
- * is finished, it is removed from the chain and the next process in line is executed.
- */
-public class ProcessChain {
- private Queue processes;
-
- public ProcessChain(Process p) {
- processes = new LinkedList<>();
- processes.add(p);
- }
-
- public ProcessChain then(Process p) {
- processes.add(p);
- return this;
- }
-
- public ProcessChain then(ProcessChain pc) {
- processes.addAll(pc.processes);
- return this;
- }
-
- public void update(float deltaMs) {
- if (processes.isEmpty()) {
- return;
- }
- Process activeProcess = processes.peek();
- activeProcess.update(deltaMs);
- if (activeProcess.isFinished()) {
- processes.remove();
- }
- }
-
- public boolean isFinished() {
- return processes.isEmpty();
- }
-
- public static void updateChains(List processChains, float deltaMs) {
- // Remove finished chains.
- for (int i = processChains.size() - 1; i >= 0; i--) {
- ProcessChain chain = processChains.get(i);
- if (chain.isFinished()) {
- processChains.remove(i);
- }
- }
- // Update still-running chains.
- for (int i = 0; i < processChains.size(); i++) {
- processChains.get(i).update(deltaMs);
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/RadialGradientTextView.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/RadialGradientTextView.java
deleted file mode 100644
index 3c678ab87..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/RadialGradientTextView.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.RadialGradient;
-import android.graphics.Shader.TileMode;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-/**
- * A text view with a radial gradient, used for the score display in game.
- */
-public class RadialGradientTextView extends TextView {
- private static final int CENTER_COLOR = 0xfffeec51;
- private static final int EDGE_COLOR = 0xfffdbe38;
-
- private RadialGradient textGradient;
-
- public RadialGradientTextView(Context context) {
- this(context, null);
- }
-
- public RadialGradientTextView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public RadialGradientTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- // Draw the shadow.
- getPaint().setShadowLayer(getShadowRadius(), getShadowDx(), getShadowDy(), getShadowColor());
- getPaint().setShader(null);
- super.onDraw(canvas);
-
- // Draw the gradient filled text.
- getPaint().clearShadowLayer();
- getPaint().setShader(textGradient);
- super.onDraw(canvas);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- if (w > 0 && h > 0) {
- textGradient = new RadialGradient(
- w / 2, h / 2, Math.min(w, h) / 2, CENTER_COLOR, EDGE_COLOR, TileMode.CLAMP);
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/RectangularInstructionActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/RectangularInstructionActor.java
deleted file mode 100644
index a67bbc6da..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/RectangularInstructionActor.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite.AnimatedSpriteListener;
-
-/**
- * An actor that shows instructions for a game.
- */
-public class RectangularInstructionActor extends Actor {
-
- private static final int RECTANGLE_CENTER_X = 257;
- private static final int RECTANGLE_CENTER_Y = 343;
-
- private static final int FRAME_BUBBLE_APPEARS = 2;
-
- public AnimatedSprite rectangle;
- public AnimatedSprite diagram;
- private float diagramScale = 1;
- private float diagramAlpha = 1;
- private boolean animationIsReversed = false;
-
- /**
- * @param diagram Animated sprite showing instructions in a loop.
- */
- public RectangularInstructionActor(Resources resources, AnimatedSprite diagram) {
- this.rectangle = AnimatedSprite.fromFrames(resources, Sprites.tutoappear_new);
- this.diagram = diagram;
-
- // Off-center anchor point lets us match the rectangle's animation with a simple scale.
- diagram.setAnchor(diagram.frameWidth / 2, diagram.frameHeight / 2);
-
- rectangle.setLoop(false);
- rectangle.addListener(new AnimatedSpriteListener() {
- @Override
- public void onFrame(int index) {
- // Scale (and fade) the diagram to match the rectangle.
- int maxFrame = rectangle.getNumFrames() - 1;
- index = animationIsReversed ? maxFrame - index : index;
- float percent = maxFrame == 0 ? 1 : (float) index / maxFrame;
- if (index < FRAME_BUBBLE_APPEARS) {
- percent = 0;
- }
- diagramScale = percent;
- diagramAlpha = percent;
- }
-
- @Override
- public void onFinished() {
- if (animationIsReversed) {
- hidden = true;
- }
- }
- });
- }
-
- public void show() {
- if (animationIsReversed) {
- reverseAnimation();
- }
- hidden = false;
- rectangle.setFrameIndex(0);
- diagramAlpha = 0;
- diagramScale = 0;
- update(0);
- }
-
- public void hide() {
- if (!animationIsReversed) {
- reverseAnimation();
- }
- rectangle.setFrameIndex(0);
- update(0);
- }
-
- private void reverseAnimation() {
- rectangle.reverseFrames();
- animationIsReversed = !animationIsReversed;
- }
-
- public void setDiagram(AnimatedSprite diagram) {
- diagram.setAnchor(diagram.frameWidth / 2, diagram.frameHeight / 2);
- this.diagram = diagram;
- }
-
- public float getScaledWidth() {
- return rectangle.frameWidth * scale;
- }
-
- public float getScaledHeight() {
- return rectangle.frameHeight * scale;
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
-
- rectangle.update(deltaMs);
- rectangle.setPosition(position.x, position.y);
- rectangle.setRotation(rotation);
- rectangle.setHidden(hidden);
- rectangle.setAlpha(alpha);
- rectangle.setScale(scale, scale);
-
- diagram.update(deltaMs);
- // Center diagram in rectangle.
- diagram.setPosition(position.x + RECTANGLE_CENTER_X * scale,
- position.y + RECTANGLE_CENTER_Y * scale);
- diagram.setRotation(rotation);
- diagram.setHidden(hidden);
- // Diagram has to take both this.alpha and diagramAlpha into account.
- diagram.setAlpha(alpha * diagramAlpha);
- // Same with scale.
- diagram.setScale(scale * diagramScale * 1.3f, scale * diagramScale * 1.3f);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- if (!hidden) {
- rectangle.draw(canvas);
- diagram.draw(canvas);
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ScoreView.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ScoreView.java
deleted file mode 100644
index 5eb449915..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/ScoreView.java
+++ /dev/null
@@ -1,657 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Build.VERSION;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.BounceInterpolator;
-import android.view.animation.OvershootInterpolator;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.Builder;
-import com.google.android.apps.santatracker.util.FontHelper;
-
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.DEFAULT_DOODLE_NAME;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.GAME_OVER;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.HOME_CLICKED;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.REPLAY_CLICKED;
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.SHARE_CLICKED;
-
-/**
- * Displays the score during the game and shows the end screen when the game is over.
- */
-public class ScoreView extends FrameLayout {
- private static final String TAG = ScoreView.class.getSimpleName();
-
- public interface OnShareClickedListener {
- void onShareClicked();
- }
-
- // Durations of various animations.
- private static final int BUMP_MS = 300; // Bump when a point is scored.
- private static final int ZOOM_UP_MS = 900; // Zooming score to middle of screen.
- private static final int ZOOM_DOWN_MS = 400; // Zooming score to its end position.
- private static final int SHARE_FADE_IN_MS = 500; // Fading in the share image.
- private static final int SHARE_DROP_MS = 400; // Dropping the share image into place.
- private static final int SHARE_Y_OFFSET_PX = -200; // Offset of the share image before it drops.
- private static final int BG_FADE_IN_MS = 400; // Fading in the end screen background.
- private static final int RESET_FADE_IN_MS = 300; // Fading in the score view when game is reset.
- private static final int STAR_BOUNCE_IN_MS = 600; // Bouncing the stars into the end screen.
- private static final int STAR_FADE_IN_MS = 500; // Fading the stars into the end screen.
- private static final float STAR_BIG_SCALE = 2.0f;
-
- // The score in the upper-left corner during the game. This also gets zoomed up to the
- // middle of the screen at the end of the game.
- private TextView currentScore;
-
- // An invisible placeholder. Lets us use the android layout engine for figuring out where
- // the score is positioned on the final screen. At the end of the game, currentScore animates
- // from its original position/size to the position/size of finalScorePlaceholder.
- private TextView finalScorePlaceholder;
-
- // An invisible placeholder which is positioned at the center of the screen and is used as the
- // intermediate position/size before the score drops into its final position.
- private TextView centeredScorePlaceholder;
-
- // Text that says "Game Over"
- private TextView gameOverText;
-
- // Widgets on the end screen.
- private TextView bestScore;
- private ImageView shareImage;
- private LinearLayout menuItems;
- private GameOverlayButton shareButton;
-
- // A semi-opaque background which darkens the game during the end screen.
- private View background;
-
- // Initial state for the views involved in the end-screen animation, stored so it can be
- // restored if "replay" is tapped.
- private float currentScoreX = Float.NaN;
- private float currentScoreY = Float.NaN;
- private int currentScoreMarginStart;
- private int currentScoreMarginTop;
- private float currentScoreTextSizePx;
- private float currentScoreAlpha;
- private float finalScoreMaxWidth;
- private float finalScoreMaxHeight;
- private float centeredScoreMaxWidth;
- private float centeredScoreMaxHeight;
- private float backgroundAlpha;
-
- private int mCurrentScoreValue;
-
- private LinearLayout currentStars;
- private RelativeLayout finalStars;
- private int filledStarCount;
-
- private DoodleConfig doodleConfig;
- protected PineappleLogger logger;
- private boolean canReplay;
-
- private OnShareClickedListener shareClickedListener;
-
- /**
- * A listener for events which occur from the level finished screen.
- */
- public interface LevelFinishedListener {
- void onReplay();
-
- String gameType();
- float score();
- int shareImageId();
- }
- protected LevelFinishedListener listener;
-
- public ScoreView(Context context, OnShareClickedListener shareClickedListener) {
- this(context, (AttributeSet) null);
- this.shareClickedListener = shareClickedListener;
- }
-
- public ScoreView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ScoreView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- loadLayout(context);
- resetToStartState();
- }
-
- public void setDoodleConfig(DoodleConfig doodleConfig) {
- this.doodleConfig = doodleConfig;
- }
-
- public void setLogger(PineappleLogger logger) {
- this.logger = logger;
- }
-
- protected void loadLayout(final Context context) {
- setVisibility(View.INVISIBLE);
- LayoutInflater inflater = LayoutInflater.from(context);
- View view = inflater.inflate(R.layout.score_view, this);
-
- gameOverText = (TextView) view.findViewById(R.id.text_game_over);
- gameOverText.setVisibility(INVISIBLE);
- FontHelper.makeLobster(gameOverText, false /* italic */);
-
- currentScore = (TextView) view.findViewById(R.id.current_score);
- // Store these for later so we can put currentScore back where it started after animating it.
- currentScoreTextSizePx = currentScore.getTextSize();
- currentScoreAlpha = currentScore.getAlpha();
- currentScore.post(new Runnable() {
- @Override
- public void run() {
- currentScoreX = currentScore.getX();
- currentScoreY = currentScore.getY();
- }
- });
- RelativeLayout.LayoutParams params =
- (RelativeLayout.LayoutParams) currentScore.getLayoutParams();
- if (VERSION.SDK_INT >= 17) {
- currentScoreMarginStart = params.getMarginStart();
- } else {
- currentScoreMarginStart = params.leftMargin;
- }
- currentScoreMarginTop = params.topMargin;
-
- finalScorePlaceholder = (TextView) view.findViewById(R.id.final_score_placeholder);
- finalScorePlaceholder.setVisibility(INVISIBLE);
- finalScoreMaxWidth = getResources().getDimension(R.dimen.final_score_max_width);
- finalScoreMaxHeight = getResources().getDimension(R.dimen.final_score_max_height);
- finalScorePlaceholder.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- UIUtil.fitToBounds(finalScorePlaceholder, finalScoreMaxWidth, finalScoreMaxHeight);
- }
- });
-
- centeredScorePlaceholder = (TextView) view.findViewById(R.id.centered_score_placeholder);
- centeredScorePlaceholder.setVisibility(INVISIBLE);
- centeredScoreMaxWidth = getResources().getDimension(R.dimen.centered_score_max_width);
- centeredScoreMaxHeight = getResources().getDimension(R.dimen.centered_score_max_height);
- centeredScorePlaceholder.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- UIUtil.fitToBounds(centeredScorePlaceholder, centeredScoreMaxWidth, centeredScoreMaxHeight);
- }
- });
-
- currentStars = (LinearLayout)
- view.findViewById(R.id.current_stars);
- currentStars.removeAllViews(); // Remove the stickers that are in the XML for testing layout.
- finalStars = (RelativeLayout)
- view.findViewById(R.id.final_stars);
-
- bestScore = (TextView) view.findViewById(R.id.best_score);
- shareImage = (ImageView) view.findViewById(R.id.share_image);
- shareImage.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- replay();
- }
- });
-
- menuItems = (LinearLayout) view.findViewById(R.id.menu_items);
- View replayButton = view.findViewById(R.id.replay_button);
- replayButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- replay();
- }
- });
-
- shareButton = (GameOverlayButton) view.findViewById(R.id.share_button);
- shareButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
- logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, SHARE_CLICKED)
- .withEventSubType(listener.gameType())
- .withEventValue1(listener.shareImageId())
- .build());
-
- if (shareClickedListener != null) {
- shareClickedListener.onShareClicked();
- }
- }
- });
-
- View moreGamesButton = view.findViewById(R.id.menu_button);
- moreGamesButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- goToMoreGames(context);
- }
- });
- background = view.findViewById(R.id.score_view_background);
- backgroundAlpha = background.getAlpha(); // Store for later use.
- }
-
- protected void goToMoreGames(Context context) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
- logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, HOME_CLICKED)
- .withEventSubType(listener.gameType())
- .build());
- LaunchDecisionMaker.finishActivity(context);
- }
-
- private void replay() {
- if (canReplay && listener != null) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
- logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, REPLAY_CLICKED)
- .withEventSubType(listener.gameType())
- .build());
-
- listener.onReplay();
- AndroidUtils.forceScreenToStayOn(getContext());
- }
- }
-
- public void setListener(LevelFinishedListener listener) {
- this.listener = listener;
- }
-
- /**
- * Updates the best score field on the end screen to display the given best score.
- *
- * @param newScore The score to be displayed as the best score.
- */
- public void updateBestScore(CharSequence newScore) {
- bestScore.setText(
- AndroidUtils.getText(getResources(), R.string.end_screen_best_score, newScore));
- }
-
- /**
- * Sets the end screen header to the given text.
- *
- * This header will be shown in place of the best score.
- *
- * @param text The text to be put into the header.
- */
- public void setHeaderText(CharSequence text) {
- bestScore.setText(text);
- }
-
- public void updateCurrentScore(CharSequence newScore, boolean shouldBump) {
- currentScore.setText(newScore);
- finalScorePlaceholder.setText(newScore);
- centeredScorePlaceholder.setText(newScore);
- if (shouldBump) {
- animateBump(currentScore);
- }
- }
-
- public void setShareDrawable(Drawable drawable) {
- shareImage.setImageDrawable(drawable);
- }
-
- public void clearAllStars() {
- currentStars.removeAllViews();
- filledStarCount = 0;
- for (int i = 0; i < finalStars.getChildCount(); i++) {
- FrameLayout star = (FrameLayout) finalStars.getChildAt(i);
- star.findViewById(R.id.fill).setVisibility(INVISIBLE);
- }
- }
-
- public void addStar() {
- if (filledStarCount < 3) {
- filledStarCount++;
- int currentStarDimens = (int) AndroidUtils.dipToPixels(40);
- addStarToLayout(currentStars, currentStarDimens, LinearLayout.LayoutParams.MATCH_PARENT);
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.ui_positive_sound);
- }
- }
-
- public int getStarCount() {
- return filledStarCount;
- }
-
- // Width & height are in pixels.
- private void addStarToLayout(LinearLayout layout, int width, int height) {
- ImageView image = new ImageView(getContext());
- image.setImageResource(R.drawable.pineapple_star_filled);
- animateBump(image);
- layout.addView(image, new LinearLayout.LayoutParams(width, height));
- }
-
- public void resetToStartState() {
- Log.i(TAG, "Reset to start state");
- currentStars.setVisibility(VISIBLE);
-
- bestScore.setVisibility(INVISIBLE);
- shareImage.setVisibility(INVISIBLE);
- menuItems.setVisibility(INVISIBLE);
- shareButton.setVisibility(INVISIBLE);
- background.setVisibility(INVISIBLE);
- finalStars.setVisibility(INVISIBLE);
- gameOverText.setVisibility(INVISIBLE);
-
- if (!Float.isNaN(currentScoreX)) {
- currentScore.setX(currentScoreX);
- }
- if (!Float.isNaN(currentScoreY)) {
- currentScore.setY(currentScoreY);
- }
- currentScore.setTextSize(TypedValue.COMPLEX_UNIT_PX, currentScoreTextSizePx);
- RelativeLayout.LayoutParams params =
- (RelativeLayout.LayoutParams) currentScore.getLayoutParams();
-
- if (VERSION.SDK_INT >= 17) {
- params.setMarginStart(currentScoreMarginStart);
- } else {
- params.leftMargin = currentScoreMarginStart;
- }
- params.topMargin = currentScoreMarginTop;
-
- updateCurrentScore(Integer.toString(0), false);
- clearAllStars();
-
- currentScore.setAlpha(0);
- ValueAnimator fadeInCurrentScore = UIUtil.animator(RESET_FADE_IN_MS,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- currentScore.setAlpha((float) valueAnimator.getAnimatedValue("alpha"));
- }
- },
- UIUtil.floatValue("alpha", 0, currentScoreAlpha)
- );
- fadeInCurrentScore.start();
- }
-
- public void animateToEndState() {
- AndroidUtils.allowScreenToTurnOff(getContext());
- Log.i(TAG, "Animate to end state");
- canReplay = false;
- setVisibility(View.VISIBLE);
- logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, GAME_OVER)
- .withEventSubType(listener.gameType())
- .withLatencyMs(PineappleLogTimer.getInstance().timeElapsedMs())
- .withEventValue1(listener.score())
- .withEventValue2(getStarCount())
- .build());
-
- // TODO: Fade this out instead of making it invisible (will have to remove
- // the layout:alignComponents that attach it current score, else it will move along with the
- // score.)
- currentStars.setVisibility(INVISIBLE);
-
- // Initial state: controls & background are visible but alpha = 0
- bestScore.setAlpha(0);
- shareImage.setAlpha(0.0f);
- background.setAlpha(0);
- finalStars.setAlpha(0);
-
- bestScore.setVisibility(VISIBLE);
- shareImage.setVisibility(VISIBLE);
- background.setVisibility(VISIBLE);
- finalStars.setVisibility(VISIBLE);
- gameOverText.setVisibility(VISIBLE);
-
- // Offset the share image and stars so that they can bounce in.
- final float shareImageY = shareImage.getY();
- final float finalStarsY = finalStars.getY();
- shareImage.setY(shareImageY + SHARE_Y_OFFSET_PX);
-
-
- // Zoom the score to center of screen.
- // I tried several other ways of doing this animation, none of which worked:
- // 1. Using TranslateAnimation & ScaleAnimation instead of .animate(): Positions didn't work
- // right when scaling, maybe because these animate how a view is displayed but not the actual
- // view properties.
- // 2. Using TranslateAnimation & ObjectAnimator: couldn't add ObjectAnimator to the same
- // Animation set as TranslateAnimation.
- // 3. Using .animate() to get a PropertyAnimator. Couldn't tween textSize without using
- // .setUpdateListener, which requires API 19.
- // 4. Small textSize, scaling up from 1: Text is blurry at scales > 1.
- // 5. Large textSize, scaling up to 1: Final position was wrong, I couldn't figure out why.
- // 6. Medium textSize, scaling up to 2.5: Error: font size too large to fit in cache. Tried
- // turning off HW accel which fixed the cache errors but positioning was still wrong.
- ValueAnimator zoomUp = UIUtil.animator(ZOOM_UP_MS, new ElasticOutInterpolator(0.35f),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- currentScore.setX((float) valueAnimator.getAnimatedValue("x"));
- currentScore.setY((float) valueAnimator.getAnimatedValue("y"));
- currentScore.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- (float) valueAnimator.getAnimatedValue("textSize"));
- RelativeLayout.LayoutParams params =
- (RelativeLayout.LayoutParams) currentScore.getLayoutParams();
- if (VERSION.SDK_INT >= 17) {
- params.setMarginStart((int) (float) valueAnimator.getAnimatedValue("marginStart"));
- } else {
- params.leftMargin = (int) (float) valueAnimator.getAnimatedValue("marginStart");
- }
- params.topMargin = (int) (float) valueAnimator.getAnimatedValue("topMargin");
- currentScore.setAlpha((float) valueAnimator.getAnimatedValue("currentScoreAlpha"));
- }
- },
- UIUtil.floatValue("x", currentScore.getX(), centeredScorePlaceholder.getX()),
- UIUtil.floatValue("y", currentScore.getY(), centeredScorePlaceholder.getY()),
- UIUtil.floatValue("textSize",
- currentScoreTextSizePx, centeredScorePlaceholder.getTextSize()),
- UIUtil.floatValue("marginStart", currentScoreMarginStart, 0),
- UIUtil.floatValue("topMargin", currentScoreMarginTop, 0),
- UIUtil.floatValue("currentScoreAlpha", currentScoreAlpha, 1)
- );
-
- // Zoom the score up to its final position.
- ValueAnimator zoomBackDown = UIUtil.animator(ZOOM_DOWN_MS, new BounceInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- currentScore.setX((float) valueAnimator.getAnimatedValue("x"));
- currentScore.setY((float) valueAnimator.getAnimatedValue("y"));
- currentScore.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- (float) valueAnimator.getAnimatedValue("textSize"));
- }
- },
- UIUtil.floatValue("x", centeredScorePlaceholder.getX(), finalScorePlaceholder.getX()),
- UIUtil.floatValue("y", centeredScorePlaceholder.getY(), finalScorePlaceholder.getY()),
- UIUtil.floatValue("textSize",
- centeredScorePlaceholder.getTextSize(), finalScorePlaceholder.getTextSize())
- );
-
- ValueAnimator fadeInBackground = UIUtil.animator(BG_FADE_IN_MS,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- background.setAlpha((float) valueAnimator.getAnimatedValue("bgAlpha"));
- }
- },
- UIUtil.floatValue("bgAlpha", 0, backgroundAlpha)
- );
-
- ValueAnimator fadeInBestScore = UIUtil.animator(BG_FADE_IN_MS,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- bestScore.setAlpha((float) valueAnimator.getAnimatedValue("bgAlpha"));
- }
- },
- UIUtil.floatValue("bgAlpha", 0, backgroundAlpha)
- );
-
- ValueAnimator fadeInMenuItems = UIUtil.animator(BG_FADE_IN_MS,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float alpha = (float) valueAnimator.getAnimatedValue("alpha");
- menuItems.setAlpha(alpha);
- shareButton.setAlpha(alpha);
-
- }
- },
- UIUtil.floatValue("alpha", 0, 1)
- );
- fadeInMenuItems.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- // Don't set menu items to be visible until the animation starts so that they aren't
- // clickable until they start to appear.
- menuItems.setVisibility(VISIBLE);
- menuItems.setAlpha(0);
-
- shareButton.setVisibility(VISIBLE);
- shareButton.setAlpha(0);
-
- canReplay = true;
- }
- });
-
- ValueAnimator fadeInShareImageAndFinalStars = UIUtil.animator(SHARE_FADE_IN_MS,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float alpha = (float) valueAnimator.getAnimatedValue("alpha");
- shareImage.setAlpha(alpha);
- finalStars.setAlpha(alpha);
- }
- },
- UIUtil.floatValue("alpha", 0, 1)
- );
-
- ValueAnimator dropShareImageAndFinalStars = UIUtil.animator(SHARE_DROP_MS,
- new BounceInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float yOffset = (float) valueAnimator.getAnimatedValue("yOffset");
- shareImage.setY(shareImageY + yOffset);
- }
- },
- UIUtil.floatValue("yOffset", SHARE_Y_OFFSET_PX, 0)
- );
-
- AnimatorSet animations = new AnimatorSet();
-
- int numStars = finalStars.getChildCount();
- ValueAnimator bounce = null;
- long starStartDelay = ZOOM_UP_MS + SHARE_FADE_IN_MS + SHARE_DROP_MS / 2;
- for (int i = 0; i < filledStarCount; i++) {
- FrameLayout star = (FrameLayout) finalStars.getChildAt(numStars - i - 1);
- ValueAnimator fade = getStarFadeIn((ImageView) star.findViewById(R.id.fill));
- bounce = getStarBounceIn((ImageView) star.findViewById(R.id.fill));
- animations.play(fade).after(starStartDelay + STAR_FADE_IN_MS * i);
- animations.play(bounce).after(starStartDelay + STAR_FADE_IN_MS * i);
- }
- if (bounce != null) {
- animations.play(fadeInMenuItems).after(bounce);
- } else {
- animations.play(fadeInMenuItems).after(starStartDelay + STAR_FADE_IN_MS);
- }
- animations.play(fadeInBackground).with(zoomUp);
- animations.play(fadeInBestScore).after(fadeInBackground);
- animations.play(zoomBackDown).after(zoomUp);
- animations.play(fadeInShareImageAndFinalStars).after(zoomUp);
- animations.play(dropShareImageAndFinalStars).after(fadeInShareImageAndFinalStars);
- animations.start();
- }
-
- private ValueAnimator getStarFadeIn(final ImageView star) {
- star.setAlpha(0.0f);
- star.setVisibility(VISIBLE);
- ValueAnimator fadeIn = UIUtil.animator(STAR_FADE_IN_MS,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float alpha = (float) valueAnimator.getAnimatedValue("alpha");
- star.setAlpha(alpha);
- }
- },
- UIUtil.floatValue("alpha", 0, 1)
- );
- fadeIn.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.ui_positive_sound);
- }
- });
- return fadeIn;
- }
-
- private ValueAnimator getStarBounceIn(final ImageView star) {
- return UIUtil.animator(STAR_BOUNCE_IN_MS,
- new BounceInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float scale = (float) valueAnimator.getAnimatedValue("scale");
- star.setScaleX(scale);
- star.setScaleY(scale);
- }
- },
- UIUtil.floatValue("scale", STAR_BIG_SCALE, 1)
- );
- }
-
- private void animateBump(final View view) {
- ValueAnimator tween = UIUtil.animator(BUMP_MS, new OvershootInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float scale = (float) valueAnimator.getAnimatedValue("scale");
- view.setScaleX(scale);
- view.setScaleY(scale);
- }
- },
- UIUtil.floatValue("scale", 1.5f, 1));
- tween.start();
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/SpriteActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/SpriteActor.java
deleted file mode 100644
index 960f7f031..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/SpriteActor.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.graphics.Canvas;
-
-/**
- * A generic actor class for game objects.
- */
-public class SpriteActor extends Actor {
- public static final String TYPE = "Sprite actor";
-
- public AnimatedSprite sprite;
- private final String type;
- // If true, then this.scale will be ignored. This would allow you to call sprite.setScale(x, y)
- // without the effect getting overwritten.
- public boolean ignoreScale = false;
-
- public SpriteActor(AnimatedSprite sprite, Vector2D position, Vector2D velocity) {
- this(sprite, position, velocity, TYPE);
- }
-
- public SpriteActor(AnimatedSprite sprite, Vector2D position, Vector2D velocity, String type) {
- super(position, velocity);
- this.sprite = sprite;
- this.type = type;
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
- sprite.update(deltaMs);
- }
-
- @Override
- public void draw(Canvas canvas) {
- draw(canvas, 0, 0, sprite.frameWidth * scale, sprite.frameHeight * scale);
- }
-
- public void draw(
- Canvas canvas, float xOffset, float yOffset, float width, float height) {
- if (!hidden) {
- sprite.setRotation(rotation);
- sprite.setPosition(position.x + xOffset, position.y + yOffset);
- if (!ignoreScale) {
- sprite.setScale(width / sprite.frameWidth, height / sprite.frameHeight);
- }
- sprite.setAlpha(alpha);
- sprite.draw(canvas);
- }
- }
-
- @Override
- public String getType() {
- return type;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Sprites.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Sprites.java
deleted file mode 100644
index 14b291324..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Sprites.java
+++ /dev/null
@@ -1,729 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import com.google.android.apps.santatracker.doodles.R;
-
-/**
- * Frame data for all the sprites.
- */
-public final class Sprites {
-
- public static int[] debug_marker = {
- R.drawable.debug_marker,
- };
- public static int[] empty_frame = {
- R.drawable.empty_frame,
- };
- public static int[] google = {
- R.drawable.google,
- };
- public static int[] ic_launcher = {
- R.drawable.ic_launcher,
- };
- public static int[] intro = {
- R.drawable.intro,
- };
-
- public static int[] pineapple_star_filled = {
- R.drawable.pineapple_star_filled,
- };
-
- public static int[] snowball = {
- R.drawable.snowball01,
- R.drawable.snowball02,
- R.drawable.snowball03,
- R.drawable.snowball04,
- R.drawable.snowball05,
- R.drawable.snowball06,
- R.drawable.snowball07,
- R.drawable.snowball08,
- };
-
- public static int[] melon_shadow = {
- R.drawable.melon_shadow,
- };
-
- public static int[] snowballrun_running_snowman_opponent = {
- R.drawable.snowballrun_running_snowman_opponent_01,
- R.drawable.snowballrun_running_snowman_opponent_02,
- R.drawable.snowballrun_running_snowman_opponent_03,
- R.drawable.snowballrun_running_snowman_opponent_04,
- R.drawable.snowballrun_running_snowman_opponent_05,
- R.drawable.snowballrun_running_snowman_opponent_06,
- R.drawable.snowballrun_running_snowman_opponent_07,
- R.drawable.snowballrun_running_snowman_opponent_08,
- R.drawable.snowballrun_running_snowman_opponent_09,
- R.drawable.snowballrun_running_snowman_opponent_10,
- R.drawable.snowballrun_running_snowman_opponent_11,
- R.drawable.snowballrun_running_snowman_opponent_12,
- R.drawable.snowballrun_running_snowman_opponent_13,
- R.drawable.snowballrun_running_snowman_opponent_14,
- R.drawable.snowballrun_running_snowman_opponent_15,
- R.drawable.snowballrun_running_snowman_opponent_16,
- };
-
- public static int[] running_apricot_squish = {
- R.drawable.snowballrun_snowman_squished_05,
- R.drawable.snowballrun_snowman_squished_06,
- R.drawable.snowballrun_snowman_squished_07,
- R.drawable.snowballrun_snowman_squished_08,
- };
-
- public static int[] snowballrunner_background = {
- R.drawable.snowballrunner_background,
- };
-
- public static int[] snowball_runner_trees1 = {
- R.drawable.snowball_runner_trees1,
- };
-
- public static int[] snowball_runner_trees2 = {
- R.drawable.snowball_runner_trees2,
- };
-
- public static int[] running_button = {
- R.drawable.running_button_00,
- R.drawable.running_button_01,
- R.drawable.running_button_02,
- };
-
- public static int[] snowballrun_elf_squish = {
- R.drawable.snowballrun_elf_squished_01,
- R.drawable.snowballrun_elf_squished_02,
- R.drawable.snowballrun_elf_squished_03,
- R.drawable.snowballrun_elf_squished_04,
- };
-
- public static int[] running_finish_line = {
- R.drawable.running_finish_line,
- };
-
- public static int[] snowballrun_running_elf_opponent = {
- R.drawable.snowballrun_running_elf_opponent_01,
- R.drawable.snowballrun_running_elf_opponent_02,
- R.drawable.snowballrun_running_elf_opponent_03,
- R.drawable.snowballrun_running_elf_opponent_04,
- R.drawable.snowballrun_running_elf_opponent_05,
- R.drawable.snowballrun_running_elf_opponent_06,
- R.drawable.snowballrun_running_elf_opponent_07,
- R.drawable.snowballrun_running_elf_opponent_08,
- R.drawable.snowballrun_running_elf_opponent_09,
- R.drawable.snowballrun_running_elf_opponent_10,
- R.drawable.snowballrun_running_elf_opponent_11,
- R.drawable.snowballrun_running_elf_opponent_12,
- R.drawable.snowballrun_running_elf_opponent_13,
- R.drawable.snowballrun_running_elf_opponent_14,
- R.drawable.snowballrun_running_elf_opponent_15,
- R.drawable.snowballrun_running_elf_opponent_16,
- };
-
- public static int[] running_elfopponent_squish = {
- R.drawable.snowballrun_elfopponent_squished_05,
- R.drawable.snowballrun_elfopponent_squished_06,
- R.drawable.snowballrun_elfopponent_squished_07,
- R.drawable.snowballrun_elfopponent_squished_08,
- };
-
- public static int[] snowballrun_running_reindeer_opponent = {
- R.drawable.snowballrun_running_reindeer_opponent_01,
- R.drawable.snowballrun_running_reindeer_opponent_02,
- R.drawable.snowballrun_running_reindeer_opponent_03,
- R.drawable.snowballrun_running_reindeer_opponent_04,
- R.drawable.snowballrun_running_reindeer_opponent_05,
- R.drawable.snowballrun_running_reindeer_opponent_06,
- R.drawable.snowballrun_running_reindeer_opponent_07,
- R.drawable.snowballrun_running_reindeer_opponent_08,
- R.drawable.snowballrun_running_reindeer_opponent_09,
- R.drawable.snowballrun_running_reindeer_opponent_10,
- R.drawable.snowballrun_running_reindeer_opponent_11,
- R.drawable.snowballrun_running_reindeer_opponent_12,
- R.drawable.snowballrun_running_reindeer_opponent_13,
- R.drawable.snowballrun_running_reindeer_opponent_14,
- R.drawable.snowballrun_running_reindeer_opponent_15,
- R.drawable.snowballrun_running_reindeer_opponent_16,
- };
-
- public static int[] snowballrun_reindeer_squish = {
- R.drawable.snowballrun_reindeer_squished_01,
- R.drawable.snowballrun_reindeer_squished_02,
- R.drawable.snowballrun_reindeer_squished_03,
- R.drawable.snowballrun_reindeer_squished_04,
- };
-
- public static int[] running_powerup = {
- R.drawable.running_powerup_00,
- R.drawable.running_powerup_01,
- R.drawable.running_powerup_02,
- R.drawable.running_powerup_03,
- R.drawable.running_powerup_04,
- R.drawable.running_powerup_05,
- R.drawable.running_powerup_06,
- R.drawable.running_powerup_07,
- };
-
- public static int[] snowballrun_running_losing = {
- R.drawable.snowballrun_running_losing01,
- R.drawable.snowballrun_running_losing02,
- R.drawable.snowballrun_running_losing03,
- R.drawable.snowballrun_running_losing04,
- R.drawable.snowballrun_running_losing05,
- R.drawable.snowballrun_running_losing06,
- R.drawable.snowballrun_running_losing07,
- R.drawable.snowballrun_running_losing08,
- R.drawable.snowballrun_running_losing09,
- R.drawable.snowballrun_running_losing10,
- R.drawable.snowballrun_running_losing11,
- R.drawable.snowballrun_running_losing12,
- R.drawable.snowballrun_running_losing13,
- R.drawable.snowballrun_running_losing14,
- R.drawable.snowballrun_running_losing15,
- R.drawable.snowballrun_running_losing16,
- };
-
- public static int[] snowballrun_running_normal = {
- R.drawable.snowballrun_running_normal01,
- R.drawable.snowballrun_running_normal02,
- R.drawable.snowballrun_running_normal03,
- R.drawable.snowballrun_running_normal04,
- R.drawable.snowballrun_running_normal05,
- R.drawable.snowballrun_running_normal06,
- R.drawable.snowballrun_running_normal07,
- R.drawable.snowballrun_running_normal08,
- R.drawable.snowballrun_running_normal09,
- R.drawable.snowballrun_running_normal10,
- R.drawable.snowballrun_running_normal11,
- R.drawable.snowballrun_running_normal12,
- R.drawable.snowballrun_running_normal13,
- R.drawable.snowballrun_running_normal14,
- R.drawable.snowballrun_running_normal15,
- R.drawable.snowballrun_running_normal16,
- };
-
- public static int[] snowballrun_running_sidestep = {
- R.drawable.snowballrun_sidestep,
- };
-
- public static int[] snowballrun_running_appearing = {
- R.drawable.snowballrun_running_appearing_01,
- R.drawable.snowballrun_running_appearing_02,
- R.drawable.snowballrun_running_appearing_03,
- R.drawable.snowballrun_running_appearing_04,
- R.drawable.snowballrun_running_appearing_05,
- R.drawable.snowballrun_running_appearing_06,
- R.drawable.snowballrun_running_appearing_07,
- R.drawable.snowballrun_running_appearing_08,
- R.drawable.snowballrun_running_appearing_09,
- R.drawable.snowballrun_running_appearing_10,
- R.drawable.snowballrun_running_appearing_11,
- R.drawable.snowballrun_running_appearing_12,
- };
-
- public static int[] penguin_swim_banner = {
- R.drawable.penguin_swim_banner,
- };
-
- public static int[] penguin_swim_dazed = {
- R.drawable.penguin_swim_dazed_01,
- R.drawable.penguin_swim_dazed_02,
- R.drawable.penguin_swim_dazed_03,
- R.drawable.penguin_swim_dazed_04,
- R.drawable.penguin_swim_dazed_05,
- R.drawable.penguin_swim_dazed_06,
- R.drawable.penguin_swim_dazed_07,
- R.drawable.penguin_swim_dazed_08,
- R.drawable.penguin_swim_dazed_09,
- R.drawable.penguin_swim_dazed_10,
- R.drawable.penguin_swim_dazed_11,
- R.drawable.penguin_swim_dazed_12,
- R.drawable.penguin_swim_dazed_13,
- R.drawable.penguin_swim_dazed_14,
- R.drawable.penguin_swim_dazed_15,
- R.drawable.penguin_swim_dazed_16,
- R.drawable.penguin_swim_dazed_17,
- R.drawable.penguin_swim_dazed_18,
- R.drawable.penguin_swim_dazed_19,
- R.drawable.penguin_swim_dazed_20,
- R.drawable.penguin_swim_dazed_21,
- };
-
- public static int[] penguin_swim_descending = {
- R.drawable.penguin_swim_descending_01,
- R.drawable.penguin_swim_descending_02,
- R.drawable.penguin_swim_descending_03,
- R.drawable.penguin_swim_descending_04,
- R.drawable.penguin_swim_descending_05,
- R.drawable.penguin_swim_descending_06,
- R.drawable.penguin_swim_descending_07,
- R.drawable.penguin_swim_descending_08,
- R.drawable.penguin_swim_descending_09,
- };
-
- public static int[] penguin_swim_elf = {
- R.drawable.penguin_swim_elf_01,
- R.drawable.penguin_swim_elf_02,
- R.drawable.penguin_swim_elf_03,
- R.drawable.penguin_swim_elf_04,
- R.drawable.penguin_swim_elf_05,
- R.drawable.penguin_swim_elf_06,
- R.drawable.penguin_swim_elf_07,
- R.drawable.penguin_swim_elf_08,
- R.drawable.penguin_swim_elf_09,
- R.drawable.penguin_swim_elf_10,
- R.drawable.penguin_swim_elf_11,
- R.drawable.penguin_swim_elf_12,
- R.drawable.penguin_swim_elf_13,
- R.drawable.penguin_swim_elf_14,
- R.drawable.penguin_swim_elf_15,
- };
-
- public static int[] penguin_swim_frozen = {
- R.drawable.penguin_swim_frozen_01,
- R.drawable.penguin_swim_frozen_02,
- R.drawable.penguin_swim_frozen_03,
- R.drawable.penguin_swim_frozen_04,
- R.drawable.penguin_swim_frozen_05,
- R.drawable.penguin_swim_frozen_06,
- R.drawable.penguin_swim_frozen_07,
- R.drawable.penguin_swim_frozen_08,
- R.drawable.penguin_swim_frozen_09,
- R.drawable.penguin_swim_frozen_10,
- R.drawable.penguin_swim_frozen_11,
- R.drawable.penguin_swim_frozen_12,
- R.drawable.penguin_swim_frozen_13,
- R.drawable.penguin_swim_frozen_14,
- R.drawable.penguin_swim_frozen_15,
- R.drawable.penguin_swim_frozen_16,
- R.drawable.penguin_swim_frozen_17,
- R.drawable.penguin_swim_frozen_18,
- R.drawable.penguin_swim_frozen_19,
- R.drawable.penguin_swim_frozen_20,
- R.drawable.penguin_swim_frozen_21,
- R.drawable.penguin_swim_frozen_22,
- R.drawable.penguin_swim_frozen_23,
- R.drawable.penguin_swim_frozen_24,
- R.drawable.penguin_swim_frozen_25,
- R.drawable.penguin_swim_frozen_26,
- R.drawable.penguin_swim_frozen_27,
- R.drawable.penguin_swim_frozen_28,
- R.drawable.penguin_swim_frozen_29,
- };
-
- public static int[] penguin_swim_ice = {
- R.drawable.penguin_swim_ice_01,
- R.drawable.penguin_swim_ice_02,
- R.drawable.penguin_swim_ice_03,
- R.drawable.penguin_swim_ice_04,
- R.drawable.penguin_swim_ice_05,
- R.drawable.penguin_swim_ice_06,
- R.drawable.penguin_swim_ice_07,
- R.drawable.penguin_swim_ice_08,
- R.drawable.penguin_swim_ice_09,
- R.drawable.penguin_swim_ice_10,
- R.drawable.penguin_swim_ice_11,
- R.drawable.penguin_swim_ice_12,
- R.drawable.penguin_swim_ice_13,
- R.drawable.penguin_swim_ice_14,
- R.drawable.penguin_swim_ice_15,
- };
-
- public static int[] penguin_swim_idle = {
- R.drawable.penguin_swim_start_01,
- R.drawable.penguin_swim_start_02,
- R.drawable.penguin_swim_start_03,
- R.drawable.penguin_swim_start_04,
- R.drawable.penguin_swim_start_05,
- R.drawable.penguin_swim_start_06,
- R.drawable.penguin_swim_start_07,
- R.drawable.penguin_swim_start_08,
- };
-
- public static int[] penguin_swim_start = {
- R.drawable.penguin_swim_start_09,
- R.drawable.penguin_swim_start_10,
- R.drawable.penguin_swim_start_11,
- R.drawable.penguin_swim_start_12,
- R.drawable.penguin_swim_start_13,
- R.drawable.penguin_swim_start_14,
- R.drawable.penguin_swim_start_15,
- R.drawable.penguin_swim_start_16,
- };
-
- public static int[] penguin_swim_canegrab = {
- R.drawable.penguin_swim_canegrab_01,
- R.drawable.penguin_swim_canegrab_02,
- R.drawable.penguin_swim_canegrab_03,
- R.drawable.penguin_swim_canegrab_04,
- R.drawable.penguin_swim_canegrab_05,
- R.drawable.penguin_swim_canegrab_06,
- R.drawable.penguin_swim_canegrab_07,
- R.drawable.penguin_swim_canegrab_08,
- R.drawable.penguin_swim_canegrab_09,
- R.drawable.penguin_swim_canegrab_10,
- R.drawable.penguin_swim_canegrab_11,
- R.drawable.penguin_swim_canegrab_12,
- R.drawable.penguin_swim_canegrab_13,
- R.drawable.penguin_swim_canegrab_14,
- R.drawable.penguin_swim_canegrab_15,
- R.drawable.penguin_swim_canegrab_16,
- R.drawable.penguin_swim_canegrab_17,
- R.drawable.penguin_swim_canegrab_18,
- R.drawable.penguin_swim_canegrab_19,
- R.drawable.penguin_swim_canegrab_20,
- R.drawable.penguin_swim_canegrab_21,
- R.drawable.penguin_swim_canegrab_22,
- R.drawable.penguin_swim_canegrab_23,
- R.drawable.penguin_swim_canegrab_24,
- R.drawable.penguin_swim_canegrab_25,
- R.drawable.penguin_swim_canegrab_26,
- R.drawable.penguin_swim_canegrab_27,
- R.drawable.penguin_swim_canegrab_28,
- R.drawable.penguin_swim_canegrab_29,
- R.drawable.penguin_swim_canegrab_30,
- };
-
- public static int[] swimming_rings = {
- R.drawable.swimming_rings_00,
- R.drawable.swimming_rings_01,
- R.drawable.swimming_rings_02,
- R.drawable.swimming_rings_03,
- R.drawable.swimming_rings_04,
- R.drawable.swimming_rings_05,
- R.drawable.swimming_rings_06,
- R.drawable.swimming_rings_07,
- R.drawable.swimming_rings_08,
- R.drawable.swimming_rings_09,
- };
-
- public static int[] penguin_swim_ascending = {
- R.drawable.penguin_swim_ascending_01,
- R.drawable.penguin_swim_ascending_02,
- R.drawable.penguin_swim_ascending_03,
- R.drawable.penguin_swim_ascending_04,
- R.drawable.penguin_swim_ascending_05,
- R.drawable.penguin_swim_ascending_06,
- R.drawable.penguin_swim_ascending_07,
- R.drawable.penguin_swim_ascending_08,
- R.drawable.penguin_swim_ascending_09,
- };
-
- public static int[] penguin_swim_candy = {
- R.drawable.penguin_swim_candy_04,
- };
-
- public static int[] penguin_swim_swimming = {
- R.drawable.penguin_swim_swimming_01,
- R.drawable.penguin_swim_swimming_02,
- R.drawable.penguin_swim_swimming_03,
- R.drawable.penguin_swim_swimming_04,
- R.drawable.penguin_swim_swimming_05,
- R.drawable.penguin_swim_swimming_06,
- R.drawable.penguin_swim_swimming_07,
- R.drawable.penguin_swim_swimming_08,
- R.drawable.penguin_swim_swimming_09,
- R.drawable.penguin_swim_swimming_10,
- R.drawable.penguin_swim_swimming_11,
- R.drawable.penguin_swim_swimming_12,
- R.drawable.penguin_swim_swimming_13,
- R.drawable.penguin_swim_swimming_14,
- R.drawable.penguin_swim_swimming_15,
- R.drawable.penguin_swim_swimming_16,
- };
-
- public static int[] penguin_swim_swimmingunderwater = {
- R.drawable.penguin_swim_swimmingunderwater_01,
- R.drawable.penguin_swim_swimmingunderwater_02,
- R.drawable.penguin_swim_swimmingunderwater_03,
- R.drawable.penguin_swim_swimmingunderwater_04,
- R.drawable.penguin_swim_swimmingunderwater_05,
- R.drawable.penguin_swim_swimmingunderwater_06,
- R.drawable.penguin_swim_swimmingunderwater_07,
- R.drawable.penguin_swim_swimmingunderwater_08,
- R.drawable.penguin_swim_swimmingunderwater_09,
- R.drawable.penguin_swim_swimmingunderwater_10,
- R.drawable.penguin_swim_swimmingunderwater_11,
- R.drawable.penguin_swim_swimmingunderwater_12,
- R.drawable.penguin_swim_swimmingunderwater_13,
- R.drawable.penguin_swim_swimmingunderwater_14,
- R.drawable.penguin_swim_swimmingunderwater_15,
- R.drawable.penguin_swim_swimmingunderwater_16,
- };
-
- public static int[] tutoappear_new = {
- R.drawable.tutoappear_new_00,
- R.drawable.tutoappear_new_01,
- R.drawable.tutoappear_new_02,
- R.drawable.tutoappear_new_03,
- R.drawable.tutoappear_new_04,
- R.drawable.tutoappear_new_05,
- };
-
- public static int[] tutorial_running = {
- R.drawable.snowballrun_tutorials_01,
- R.drawable.snowballrun_tutorials_02,
- R.drawable.snowballrun_tutorials_03,
- R.drawable.snowballrun_tutorials_04,
- R.drawable.snowballrun_tutorials_05,
- R.drawable.snowballrun_tutorials_06,
- R.drawable.snowballrun_tutorials_07,
- R.drawable.snowballrun_tutorials_08,
- R.drawable.snowballrun_tutorials_09,
- R.drawable.snowballrun_tutorials_10,
- R.drawable.snowballrun_tutorials_11,
- R.drawable.snowballrun_tutorials_12,
- R.drawable.snowballrun_tutorials_13,
- R.drawable.snowballrun_tutorials_14,
- R.drawable.snowballrun_tutorials_15,
- R.drawable.snowballrun_tutorials_16,
- R.drawable.snowballrun_tutorials_17,
- R.drawable.snowballrun_tutorials_18,
- };
- public static int[] tutorial_swimming = {
- R.drawable.penguin_swim_tutorials_01,
- R.drawable.penguin_swim_tutorials_02,
- R.drawable.penguin_swim_tutorials_03,
- R.drawable.penguin_swim_tutorials_04,
- R.drawable.penguin_swim_tutorials_05,
- R.drawable.penguin_swim_tutorials_06,
- R.drawable.penguin_swim_tutorials_07,
- R.drawable.penguin_swim_tutorials_08,
- };
-
- public static int[] present_throw_tutorials = {
- R.drawable.present_throw_tutorials_01,
- R.drawable.present_throw_tutorials_02,
- R.drawable.present_throw_tutorials_03,
- R.drawable.present_throw_tutorials_04,
- R.drawable.present_throw_tutorials_05,
- R.drawable.present_throw_tutorials_06,
- R.drawable.present_throw_tutorials_07,
- R.drawable.present_throw_tutorials_08,
- R.drawable.present_throw_tutorials_09,
- R.drawable.present_throw_tutorials_10,
- R.drawable.present_throw_tutorials_11,
- R.drawable.present_throw_tutorials_12,
- R.drawable.present_throw_tutorials_13,
- R.drawable.present_throw_tutorials_14,
- };
-
- public static int[] present_throw_def_green_left = {
- R.drawable.present_throw_blocker_green_01,
- R.drawable.present_throw_blocker_green_02,
- R.drawable.present_throw_blocker_green_03,
- R.drawable.present_throw_blocker_green_04,
- R.drawable.present_throw_blocker_green_05,
- R.drawable.present_throw_blocker_green_06,
- R.drawable.present_throw_blocker_green_07,
- R.drawable.present_throw_blocker_green_08,
- };
-
- public static int[] present_throw_def_green_right = {
- R.drawable.present_throw_blocker_green_09,
- R.drawable.present_throw_blocker_green_10,
- R.drawable.present_throw_blocker_green_11,
- R.drawable.present_throw_blocker_green_12,
- R.drawable.present_throw_blocker_green_13,
- R.drawable.present_throw_blocker_green_14,
- R.drawable.present_throw_blocker_green_15,
- R.drawable.present_throw_blocker_green_16,
- };
-
- public static int[] present_throw_def_green_emerge = {
- R.drawable.present_throw_newblocker_green_01,
- R.drawable.present_throw_newblocker_green_02,
- R.drawable.present_throw_newblocker_green_03,
- R.drawable.present_throw_newblocker_green_04,
- R.drawable.present_throw_newblocker_green_05,
- R.drawable.present_throw_newblocker_green_06,
- R.drawable.present_throw_newblocker_green_07,
- R.drawable.present_throw_newblocker_green_08,
- R.drawable.present_throw_newblocker_green_09,
- R.drawable.present_throw_newblocker_green_10,
- R.drawable.present_throw_newblocker_green_11,
- R.drawable.present_throw_newblocker_green_12,
- };
-
- public static int[] present_throw_def_green_blocking = {
- R.drawable.present_throw_blocking_green_01,
- R.drawable.present_throw_blocking_green_02,
- R.drawable.present_throw_blocking_green_03,
- R.drawable.present_throw_blocking_green_04,
- R.drawable.present_throw_blocking_green_05,
- R.drawable.present_throw_blocking_green_06,
- R.drawable.present_throw_blocking_green_07,
- R.drawable.present_throw_blocking_green_08,
- R.drawable.present_throw_blocking_green_09,
- R.drawable.present_throw_blocking_green_10,
- R.drawable.present_throw_blocking_green_11,
- R.drawable.present_throw_blocking_green_12,
- };
-
- public static int[] present_throw_def_red_left = {
- R.drawable.present_throw_blocker_red_01,
- R.drawable.present_throw_blocker_red_02,
- R.drawable.present_throw_blocker_red_03,
- R.drawable.present_throw_blocker_red_04,
- R.drawable.present_throw_blocker_red_05,
- R.drawable.present_throw_blocker_red_06,
- R.drawable.present_throw_blocker_red_07,
- R.drawable.present_throw_blocker_red_08,
- };
-
- public static int[] present_throw_def_red_right = {
- R.drawable.present_throw_blocker_red_09,
- R.drawable.present_throw_blocker_red_10,
- R.drawable.present_throw_blocker_red_11,
- R.drawable.present_throw_blocker_red_12,
- R.drawable.present_throw_blocker_red_13,
- R.drawable.present_throw_blocker_red_14,
- R.drawable.present_throw_blocker_red_15,
- R.drawable.present_throw_blocker_red_16,
- };
-
- public static int[] present_throw_def_red_emerge = {
- R.drawable.present_throw_newblocker_red_01,
- R.drawable.present_throw_newblocker_red_02,
- R.drawable.present_throw_newblocker_red_03,
- R.drawable.present_throw_newblocker_red_04,
- R.drawable.present_throw_newblocker_red_05,
- R.drawable.present_throw_newblocker_red_06,
- R.drawable.present_throw_newblocker_red_07,
- R.drawable.present_throw_newblocker_red_08,
- R.drawable.present_throw_newblocker_red_09,
- R.drawable.present_throw_newblocker_red_10,
- R.drawable.present_throw_newblocker_red_11,
- R.drawable.present_throw_newblocker_red_12,
- };
-
- public static int[] present_throw_def_red_blocking = {
- R.drawable.present_throw_blocking_red_01,
- R.drawable.present_throw_blocking_red_02,
- R.drawable.present_throw_blocking_red_03,
- R.drawable.present_throw_blocking_red_04,
- R.drawable.present_throw_blocking_red_05,
- R.drawable.present_throw_blocking_red_06,
- R.drawable.present_throw_blocking_red_07,
- R.drawable.present_throw_blocking_red_08,
- R.drawable.present_throw_blocking_red_09,
- R.drawable.present_throw_blocking_red_10,
- R.drawable.present_throw_blocking_red_11,
- R.drawable.present_throw_blocking_red_12,
- };
-
- public static int[] present_throw_def_orange_left = {
- R.drawable.present_throw_blocker_orange_01,
- R.drawable.present_throw_blocker_orange_02,
- R.drawable.present_throw_blocker_orange_03,
- R.drawable.present_throw_blocker_orange_04,
- R.drawable.present_throw_blocker_orange_05,
- R.drawable.present_throw_blocker_orange_06,
- R.drawable.present_throw_blocker_orange_07,
- R.drawable.present_throw_blocker_orange_08,
- };
-
- public static int[] present_throw_def_orange_right = {
- R.drawable.present_throw_blocker_orange_09,
- R.drawable.present_throw_blocker_orange_10,
- R.drawable.present_throw_blocker_orange_11,
- R.drawable.present_throw_blocker_orange_12,
- R.drawable.present_throw_blocker_orange_13,
- R.drawable.present_throw_blocker_orange_14,
- R.drawable.present_throw_blocker_orange_15,
- R.drawable.present_throw_blocker_orange_16,
- };
-
- public static int[] present_throw_def_orange_emerge = {
- R.drawable.present_throw_newblocker_orange_01,
- R.drawable.present_throw_newblocker_orange_02,
- R.drawable.present_throw_newblocker_orange_03,
- R.drawable.present_throw_newblocker_orange_04,
- R.drawable.present_throw_newblocker_orange_05,
- R.drawable.present_throw_newblocker_orange_06,
- R.drawable.present_throw_newblocker_orange_07,
- R.drawable.present_throw_newblocker_orange_08,
- R.drawable.present_throw_newblocker_orange_09,
- R.drawable.present_throw_newblocker_orange_10,
- R.drawable.present_throw_newblocker_orange_11,
- R.drawable.present_throw_newblocker_orange_12,
- };
-
- public static int[] present_throw_def_orange_blocking = {
- R.drawable.present_throw_blocking_orange_01,
- R.drawable.present_throw_blocking_orange_02,
- R.drawable.present_throw_blocking_orange_03,
- R.drawable.present_throw_blocking_orange_04,
- R.drawable.present_throw_blocking_orange_05,
- R.drawable.present_throw_blocking_orange_06,
- R.drawable.present_throw_blocking_orange_07,
- R.drawable.present_throw_blocking_orange_08,
- R.drawable.present_throw_blocking_orange_09,
- R.drawable.present_throw_blocking_orange_10,
- R.drawable.present_throw_blocking_orange_11,
- R.drawable.present_throw_blocking_orange_12,
- };
-
- public static int[] present_throw_reloading = {
- R.drawable.present_throw_reloading_01,
- R.drawable.present_throw_reloading_02,
- R.drawable.present_throw_reloading_03,
- };
-
- public static int[] present_throw_celebrate = {
- R.drawable.present_throw_celebrating_01,
- R.drawable.present_throw_celebrating_02,
- };
-
- public static int[] present_throw_idle = {
- R.drawable.present_throw_throwing_01,
- };
-
- public static int[] present_throw_santabag = {
- R.drawable.present_throw_santabag,
- };
-
- public static int[] present_throw_thrownpresent = {
- R.drawable.present_throw_thrownpresent_orange,
- };
-
- public static int[] present_throw_floor = {
- R.drawable.present_throw_floor,
- };
-
- public static int[] present_throw_elfbag = {
- R.drawable.present_throw_elfbag,
- };
-
- public static int[] orange_present_falling = {
- R.drawable.orange_present1,
- R.drawable.orange_present2,
- R.drawable.orange_present3,
- R.drawable.orange_present4,
- };
-
- public static int[] waterpolo_target = {
- R.drawable.waterpolo_target,
- };
-
- public static int[] present_throw_throwing = {
- R.drawable.present_throw_throwing_01,
- R.drawable.present_throw_throwing_02,
- R.drawable.present_throw_throwing_03,
- R.drawable.present_throw_throwing_04,
- R.drawable.present_throw_throwing_05,
- R.drawable.present_throw_throwing_06,
- };
-
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/StarView.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/StarView.java
deleted file mode 100644
index 5a728a5e2..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/StarView.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-import com.google.android.apps.santatracker.doodles.R;
-
-/**
- * A view for the stars on the end screen. This is just a wrapper class so that we can contain
- * star layout behavior in a single layout file instead of having to specify each one individually.
- */
-public class StarView extends FrameLayout {
-
- public StarView(Context context) {
- this(context, null);
- }
-
- public StarView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public StarView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- inflate(context, R.layout.star_view, this);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/TextActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/TextActor.java
deleted file mode 100644
index 107763969..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/TextActor.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.res.AssetManager;
-import android.graphics.BlurMaskFilter;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.text.TextPaint;
-
-/**
- * Draws text on the screen.
- */
-public class TextActor extends Actor {
- private static final String TAG = TextActor.class.getSimpleName();
-
- private String text;
- private Paint paint;
- private Rect bounds = new Rect();
- private float previousAlpha;
-
- public boolean hidden;
- private boolean centeredVertically;
-
- public TextActor(String text) {
- this.paint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
- // Set text size using px instead of dip so that the scale of text needed to match a bitmap
- // sprite stays the same across devices.
- paint.setTextSize(12);
- setColor(Color.BLACK);
- setText(text);
- }
-
- public void setText(String text) {
- this.text = text;
- paint.getTextBounds(text, 0, text.length(), bounds);
- }
-
- public String getText() {
- return text;
- }
-
- /**
- * @return The scaled width of the text.
- */
- public float getWidth() {
- return bounds.width() * scale;
- }
-
- /**
- * @return The scaled height of the text.
- */
- public float getHeight() {
- return bounds.height() * scale;
- }
-
- public void scaleToFitScreen(int screenWidth, int screenHeight) {
- scale = Math.min(
- screenWidth / bounds.width(),
- screenHeight / bounds.height());
- }
-
- public void setColor(int color) {
- paint.setColor(color);
- previousAlpha = paint.getAlpha() / 255f;
- }
-
- public void setFont(AssetManager assetManager, String fontPath) {
- Typeface typeface = Typeface.createFromAsset(assetManager, fontPath);
- paint.setTypeface(typeface);
- }
-
- public void enableBlur(float blurRadius) {
- paint.setMaskFilter(new BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.NORMAL));
- }
-
- public void alignCenter() {
- paint.setTextAlign(Align.CENTER);
- }
-
- public void setBold(boolean bold) {
- paint.setTypeface(bold ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
- }
-
- public void centerVertically(boolean center) {
- this.centeredVertically = center;
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (previousAlpha != alpha) {
- previousAlpha = alpha;
- paint.setAlpha(Math.round(alpha * 255));
- }
- if (hidden) {
- return;
- }
- float yOffset = centeredVertically ? -getHeight() / 2 : 0;
- canvas.save();
- canvas.scale(scale, scale);
- // 1. drawText has y=0 at the baseline of the text. To make this work like other actors, y=0
- // should be the top of the bounding box, so add bounds.top to y.
- // 2. drawText also has rounding error on the (x, y) coordinates, which makes text jitter
- // around if you are tweening scale, so use canvas.translate() to position instead.
- canvas.translate(position.x / scale, (position.y + yOffset) / scale - bounds.top);
- canvas.drawText(text, 0, 0, paint);
- canvas.restore();
-
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Tween.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Tween.java
deleted file mode 100644
index 7a5230f8b..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Tween.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * Base class for tweens. Handles the basic bookkeeping, then delegates to subclasses via
- * updateValues() for updating specific values.
- */
-public abstract class Tween {
- protected float durationSeconds = 0;
- private float elapsedSeconds = 0;
- private float percentDone = 0;
-
- public Tween(float durationSeconds) {
- this.durationSeconds = durationSeconds;
- }
-
- /**
- * @return true if update should continue to be called, false if tween is finished and should
- * be removed.
- */
- public boolean update(double deltaMs) {
- boolean wasFinished = isFinished();
- if (wasFinished) {
- return false;
- }
- elapsedSeconds += deltaMs / 1000f;
- percentDone = elapsedSeconds / durationSeconds;
- if (percentDone > 1) {
- percentDone = 1;
- }
- updateValues(percentDone);
- if (!wasFinished && isFinished()) {
- onFinish();
- }
- return !isFinished();
- }
-
- /**
- * Subclasses should define this method to update their value(s) every frame. Suggested
- * implementation:
- * currentValue = interpolator.getValue(percentDone, startValue, endValue);
- */
- protected abstract void updateValues(float percentDone);
-
- /**
- * Subclasses can override this to execute code when the Tween finishes.
- */
- protected void onFinish() {
-
- }
-
- // Cancels the tween. Doesn't reset values back to their starting value or the final value. Just
- // leaves state where it is and stops updating. onFinish will be called.
- public void cancel() {
- if (!isFinished()) {
- // Advance elapsedSeconds and percentDone to the end so future update calls won't do anything.
- elapsedSeconds = durationSeconds;
- percentDone = 1;
- onFinish();
- }
- }
-
- public boolean isFinished() {
- return percentDone >= 1;
- }
-
- public float durationSeconds() {
- return durationSeconds;
- }
-
- public Process asProcess() {
- return new Process() {
- @Override
- public void updateLogic(float deltaMs) {
- Tween.this.update(deltaMs);
- }
-
- @Override
- public boolean isFinished() {
- return Tween.this.isFinished();
- }
- };
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/TweenManager.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/TweenManager.java
deleted file mode 100644
index 8facd6be6..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/TweenManager.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Manages a list of tweens, taking care of removing them when they are done, and adding them (even
- * in the middle of iterating over the list of tweens).
- */
-public class TweenManager {
- private final List tweens = new ArrayList<>();
- private final List incomingTweens = new ArrayList<>();
- private boolean shouldRemoveAll = false;
-
- public void update(float deltaMs) {
- // First, check whether removeAll was called since the last update.
- if (shouldRemoveAll) {
- finishRemovingAll();
- return;
- }
- try {
- // Move everything from incomingTweens to tweens (before iterating over tweens)
- for (int i = 0; i < incomingTweens.size(); i++) { // Avoiding iterator to avoid garbage.
- tweens.add(incomingTweens.get(i));
- }
- incomingTweens.clear();
-
- // Now iterate through tweens.
- for (int i = tweens.size() - 1; i >= 0; i--) { // Avoiding iterator to avoid garbage.
- Tween tween = tweens.get(i);
- boolean finished = tween == null || !tween.update(deltaMs);
- if (shouldRemoveAll) {
- finishRemovingAll();
- return;
- }
- if (finished) {
- tweens.remove(i);
- }
- }
- } catch (Exception e) { // do nothing
- }
- }
-
- public void add(Tween tween) {
- if (tween.durationSeconds() < 0) {
- throw new IllegalArgumentException("Tween duration should not be negative");
- }
- // Don't add to main list of tweens directly, to avoid ConcurrentModificationException.
- incomingTweens.add(tween);
- }
-
- /**
- * Removes all the tweens at the next possible opportunity. This isn't synchronous, but will
- * happen before any more tween.update calls occur.
- */
- public void removeAll() {
- // Remove incoming tweens immediately. No risk of removing while iterating for these, and we
- // shouldn't clear this later in finishRemovingAll in case more have been added since then,
- // so clear immediately.
- incomingTweens.clear();
-
- // There is a risk of removing while iterating for tweens though, so set a flag instead of
- // immediately clearing it.
- shouldRemoveAll = true;
- }
-
- private void finishRemovingAll() {
- // Don't clear incomingTweens here, on the assumption that A) removeAll already cleared it
- // and B) there's a possibility more have been added since then, and they *shouldn't* get
- // cleared.
- tweens.clear();
- shouldRemoveAll = false;
- }
-
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIRefreshHandler.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIRefreshHandler.java
deleted file mode 100644
index 41b702005..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIRefreshHandler.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.os.Handler;
-import android.os.Message;
-import android.view.View;
-
-/**
- * Handler subclass which handles refreshing the UI.
- */
-public class UIRefreshHandler extends Handler {
- private static final int REFRESH_UI_MESSAGE = 0;
- // Refresh the UI at a higher rate so that we can keep the drawing pipeline filled.
- private static final int UI_INTERVAL_MS = 1000 / 120;
-
- // Toggled in start/stop, and used in handleMessage to conditionally schedule the next refresh.
- private volatile boolean running;
-
- private View view;
-
- public UIRefreshHandler() {
- }
-
- public void start(View view) {
- running = true;
- this.view = view;
- sendEmptyMessage(REFRESH_UI_MESSAGE);
- }
-
- public void stop() {
- running = false;
- view = null;
- removeMessages(REFRESH_UI_MESSAGE);
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (running) {
- if (msg.what == REFRESH_UI_MESSAGE) {
- long timeBeforeDraw = System.currentTimeMillis();
- if (view != null) {
- // invalidate
- view.invalidate();
- }
- // Wait different amounts of time depending on how much time the draw took.
- // Wait at least 1ms to avoid a mysterious memory leak.
- long timeToDraw = System.currentTimeMillis() - timeBeforeDraw;
- sendEmptyMessageDelayed(REFRESH_UI_MESSAGE, Math.max(1, UI_INTERVAL_MS - timeToDraw));
- }
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIUtil.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIUtil.java
deleted file mode 100644
index 8470fd712..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/UIUtil.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.PropertyValuesHolder;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.widget.TextView;
-
-/**
- * Utility class for working with Android views.
- */
-public final class UIUtil {
-
- private UIUtil() {
- // Don't instantiate this class.
- }
-
- /**
- * Shortcut to create a ValuesAnimator with the given configuration.
- */
- public static ValueAnimator animator(long durationMillis, TimeInterpolator interpolator,
- AnimatorUpdateListener listener, PropertyValuesHolder... propertyValuesHolders) {
- ValueAnimator tween = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolders);
- tween.setDuration(durationMillis);
- tween.setInterpolator(interpolator);
- tween.addUpdateListener(listener);
- return tween;
- }
-
- /**
- * Shortcut for making a PropertyValuesHolder for floats.
- */
- public static PropertyValuesHolder floatValue(String name, float start, float end) {
- return PropertyValuesHolder.ofFloat(name, start, end);
- }
-
- public static void fadeOutAndHide(final View v, long durationMs, float startAlpha,
- final Runnable onFinishRunnable) {
-
- if (v.getVisibility() != View.VISIBLE) {
- return; // Already hidden.
- }
- ValueAnimator fadeOut = animator(durationMs,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- v.setAlpha((float) valueAnimator.getAnimatedValue("alpha"));
- }
- },
- floatValue("alpha", startAlpha, 0)
- );
- fadeOut.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- v.setVisibility(View.INVISIBLE);
- if (onFinishRunnable != null) {
- onFinishRunnable.run();
- }
- }
- });
- fadeOut.start();
- }
- public static void fadeOutAndHide(final View v, long durationMs, float startAlpha) {
- fadeOutAndHide(v, durationMs, startAlpha, null);
- }
-
- public static void fadeOutAndHide(final View v, long durationMs) {
- fadeOutAndHide(v, durationMs, 1);
- }
-
- public static void showAndFadeIn(final View v, long durationMs, float endAlpha) {
- if (v.getVisibility() == View.VISIBLE) {
- return; // Already visible.
- }
- v.setAlpha(0);
- v.setVisibility(View.VISIBLE);
-
- ValueAnimator fadeIn = animator(durationMs,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- v.setAlpha((float) valueAnimator.getAnimatedValue("alpha"));
- }
- },
- floatValue("alpha", 0, endAlpha)
- );
- fadeIn.start();
- }
-
- public static void showAndFadeIn(final View v, long durationMs) {
- showAndFadeIn(v, durationMs, 1);
- }
-
- public static void fitToBounds(TextView textView, float widthPx, float heightPx) {
- textView.measure(0, 0);
- float currentWidthPx = textView.getMeasuredWidth();
- float currentHeightPx = textView.getMeasuredHeight();
- float textSize = textView.getTextSize();
-
- float scale = Math.min(widthPx / currentWidthPx, heightPx / currentHeightPx);
- textView.setTextSize(textSize * scale);
- }
-
- /**
- * Translates in Y from startPercent to endPercent (expecting 0 for 0%, 1 for 100%).
- * Hides at the end based on hideOnEnd.
- */
- public static void panUpAndHide(final View v, float startPercent, float endPercent,
- long durationMs, boolean hideOnEnd) {
- if (v.getVisibility() != View.VISIBLE) {
- return; // Already hidden.
- }
- ValueAnimator panUp = animator(durationMs,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- v.setY(((float) valueAnimator.getAnimatedValue()) * v.getHeight());
- }
- },
- floatValue("translateY", startPercent, endPercent)
- );
- if (hideOnEnd) {
- panUp.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- v.setVisibility(View.INVISIBLE);
- }
- });
- }
- panUp.start();
- }
-
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Vector2D.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Vector2D.java
deleted file mode 100644
index 3dfbd1692..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/Vector2D.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import java.util.Stack;
-
-/**
- * A basic 2D vector, with convenience functions to interact with it.
- */
-public class Vector2D {
- private static final int MAX_POOL_SIZE = 50;
- private static final Stack vectorPool = new Stack<>();
-
- public float x;
- public float y;
-
- private Vector2D(float x, float y) {
- this.x = x;
- this.y = y;
- }
-
- public static Vector2D get() {
- return get(0, 0);
- }
-
- public static synchronized Vector2D get(float x, float y) {
- if (!vectorPool.isEmpty()) {
- Vector2D v = vectorPool.pop();
- v.set(x, y);
- return v;
- } else {
- return new Vector2D(x, y);
- }
- }
-
- public static Vector2D get(Vector2D other) {
- return get(other.x, other.y);
- }
-
- /**
- * Release this vector back into the vector pool. Note that, once this has been called, the
- * vector object may be re-used, and there is no guarantee that the released object will act as
- * expected.
- */
- public void release() {
- if (vectorPool.size() < MAX_POOL_SIZE) {
- vectorPool.push(this);
- }
- }
-
- public Vector2D normalize() {
- float length = getLength();
- if (length == 0) {
- set(0, 0);
- } else {
- set (x / length, y / length);
- }
- return this;
- }
-
- public Vector2D toNormal() {
- return set(y, -x).normalize();
- }
-
- public float getLength() {
- return getLength(x, y);
- }
-
- public static float getLength(float x, float y) {
- return (float) Math.sqrt(x * x + y * y);
- }
-
- public Vector2D add(Vector2D rhs) {
- set(this.x + rhs.x, this.y + rhs.y);
- return this;
- }
-
- public Vector2D add(float x, float y) {
- set(this.x + x, this.y + y);
- return this;
- }
-
- public Vector2D subtract(Vector2D rhs) {
- set(this.x - rhs.x, this.y - rhs.y);
- return this;
- }
-
- public Vector2D subtract(float x, float y) {
- set(this.x - x, this.y - y);
- return this;
- }
-
- public Vector2D scale(float factor) {
- set(this.x * factor, this.y * factor);
- return this;
- }
-
- public float dot(Vector2D rhs) {
- return x * rhs.x + y * rhs.y;
- }
-
- public Vector2D rotate(float radians) {
- double cos = Math.cos(radians);
- double sin = Math.sin(radians);
- set((float) (x * cos - y * sin), (float) (x * sin + y * cos));
- return this;
- }
-
- public Vector2D set(Vector2D other) {
- x = other.x;
- y = other.y;
- return this;
- }
-
- public Vector2D set(float x, float y) {
- this.x = x;
- this.y = y;
- return this;
- }
-
- @Override
- public String toString() {
- return "(" + x + ", " + y + ")";
- }
-
- public float distanceTo(Vector2D other) {
- float dx = x - other.x;
- float dy = y - other.y;
- return (float) Math.sqrt(dx * dx + dy * dy);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/WaitProcess.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/WaitProcess.java
deleted file mode 100644
index 204232df8..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/WaitProcess.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-/**
- * A process which just waits for the specified amount of time.
- */
-public class WaitProcess extends Process {
- private long elapsedMs;
- private long durationMs;
-
- public WaitProcess(long durationMs) {
- this.durationMs = durationMs;
- this.elapsedMs = 0;
- }
-
- @Override
- public void updateLogic(float deltaMs) {
- elapsedMs += deltaMs;
- }
-
- @Override
- public boolean isFinished() {
- return elapsedMs >= durationMs;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/WatermelonBaseActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/WatermelonBaseActor.java
deleted file mode 100644
index 4e7d7ab87..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/WatermelonBaseActor.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-
-/**
- * The base watermelon actor used by both the golf and running game.
- */
-public class WatermelonBaseActor extends Actor {
-
- public AnimatedSprite bodySprite;
- public AnimatedSprite shadowSprite;
-
- public WatermelonBaseActor(Resources resources) {
- bodySprite = AnimatedSprite.fromFrames(resources, Sprites.snowball);
- shadowSprite = AnimatedSprite.fromFrames(resources, Sprites.melon_shadow);
-
- bodySprite.setAnchor(bodySprite.frameWidth / 2, bodySprite.frameHeight / 2);
- shadowSprite.setAnchor(shadowSprite.frameWidth * 0.5f, shadowSprite.frameHeight * 0.35f);
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
-
- bodySprite.update(deltaMs);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- drawSprite(shadowSprite, canvas);
- drawSprite(bodySprite, canvas);
- }
-
- private void drawSprite(AnimatedSprite sprite, Canvas canvas) {
- sprite.setPosition(position.x, position.y);
- sprite.setScale(scale, scale);
- sprite.draw(canvas);
- }
-
- private void drawSpriteYInverted(AnimatedSprite sprite, Canvas canvas) {
- sprite.setPosition(position.x, position.y);
- sprite.setScale(scale, -scale);
- sprite.draw(canvas);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Polygon.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Polygon.java
deleted file mode 100644
index fd250e482..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Polygon.java
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared.physics;
-
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.tilt.Constants;
-import com.google.android.apps.santatracker.doodles.tilt.SwimmingFragment;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A general polygon class (either concave or convex) which can tell whether or not a point
- * is inside of it.
- *
- * NOTE: vertex winding order affects the normals of the line segments, and can affect things
- * like collisions. A non-inverted (normals pointed out) polygon should have its vertices wound
- * clockwise.
- *
- */
-public class Polygon {
- private static final String TAG = Polygon.class.getSimpleName();
- private static final float EPSILON = 0.0001f;
- private static final float VERTEX_RADIUS = 10;
-
- private Paint vertexPaint;
- private Paint midpointPaint;
- private Paint linePaint;
-
- public List vertices;
- public List normals;
- public Vector2D min;
- public Vector2D max;
-
- private boolean isInverted;
-
- public Polygon(List vertices) {
- this.vertices = vertices;
- min = Vector2D.get(0, 0);
- max = Vector2D.get(0, 0);
-
- vertexPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- vertexPaint.setColor(Color.RED);
-
- midpointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- midpointPaint.setColor(Color.GREEN);
- midpointPaint.setAlpha(100);
-
- linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- linePaint.setColor(Color.WHITE);
- linePaint.setStrokeWidth(5);
-
- updateExtents();
- updateInversionStatus();
- calculateNormals();
- }
-
- public void updateExtents() {
- min.set(this.vertices.get(0));
- max.set(this.vertices.get(0));
- for (int i = 0; i < vertices.size(); i++) {
- Vector2D point = vertices.get(i);
- min.x = Math.min(min.x, point.x);
- min.y = Math.min(min.y, point.y);
- max.x = Math.max(max.x, point.x);
- max.y = Math.max(max.y, point.y);
- }
- }
-
- public void calculateNormals() {
- normals = new ArrayList<>();
- for (int i = 0; i < vertices.size(); i++) {
- Vector2D start = vertices.get(i);
- Vector2D end = vertices.get((i + 1) % vertices.size());
- normals.add(Vector2D.get(end).subtract(start).toNormal());
- }
- }
-
- public float getWidth() {
- return max.x - min.x;
- }
-
- public float getHeight() {
- return max.y - min.y;
- }
-
- public void moveTo(float x, float y) {
- float deltaX = x - min.x;
- float deltaY = y - min.y;
- move(deltaX, deltaY);
- }
-
- public void move(float x, float y) {
- for (int i = 0; i < vertices.size(); i++) {
- Vector2D vertex = vertices.get(i);
- vertex.x += x;
- vertex.y += y;
- }
- // Rather than update the extents by checking all of the vertices here, we can just update them
- // manually (they will move by the same amount as the rest of the vertices).
- min.x += x;
- min.y += y;
- max.x += x;
- max.y += y;
- }
-
- public void moveVertex(int index, Vector2D delta) {
- Vector2D vertex = vertices.get(index);
- vertex.x += delta.x;
- vertex.y += delta.y;
- updateExtents();
- updateInversionStatus();
- }
-
- public void addVertexAfter(int index) {
- int nextIndex = index < vertices.size() - 1 ? index + 1 : 0;
- Vector2D newVertex = Util.getMidpoint(vertices.get(index), vertices.get(nextIndex));
- vertices.add(nextIndex, newVertex);
- updateExtents();
- calculateNormals();
- }
-
- public void removeVertexAt(int index) {
- vertices.remove(index);
- updateExtents();
- }
-
- /**
- * Return the index of the vertex selected by the given point.
- *
- * @param point the point at which to check for a selected vertex.
- * @param scale the scale of the world, for slackening the selection radius if needed.
- * @return the index of the selected vertex, or -1 if no vertex was selected.
- */
- public int getSelectedIndex(Vector2D point, float scale) {
- for (int i = 0; i < vertices.size(); i++) {
- Vector2D vertex = vertices.get(i);
- if (point.distanceTo(vertex)
- < Math.max(Constants.SELECTION_RADIUS, Constants.SELECTION_RADIUS / scale)) {
- return i;
- }
- }
- return -1;
- }
-
- public int getMidpointIndex(Vector2D point, float scale) {
- for (int i = 0; i < vertices.size(); i++) {
- Vector2D start = vertices.get(i);
- Vector2D end = vertices.get(i < vertices.size() - 1 ? i + 1 : 0);
- if (point.distanceTo(Util.getMidpoint(start, end))
- < Math.max(Constants.SELECTION_RADIUS, Constants.SELECTION_RADIUS / scale)) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Return whether or not this polygon is inverted (i.e., whether or not the polygon has a normal
- * which points inwards.
- *
- * @return true if the polygon is inverted, false otherwise.
- */
- public boolean isInverted() {
- return isInverted;
- }
-
- /**
- * Calculate whether or not this polygon is inverted. This checks to see if the point which is one
- * unit in the normal direction on the polygon's first segment is within the bounds of the
- * polygon. If this is the case, then the normal points inwards and the polygon is inverted.
- * Otherwise, the polygon is not inverted.
- *
- * Note: This doesn't deal with polygons which are partially inverted. These sorts of polygons
- * should be avoided, as they will break this function.
- */
- private void updateInversionStatus() {
- Vector2D start = vertices.get(0);
- Vector2D end = vertices.get(1);
- Vector2D midpoint = Util.getMidpoint(start, end);
- Vector2D normal = Vector2D.get(end).subtract(start).toNormal().scale(0.1f);
-
- if (contains(midpoint.add(normal))) {
- isInverted = true;
- } else {
- isInverted = false;
- }
- normal.release();
- midpoint.release();
- }
-
- /**
- * Return whether or not this polygon's collision boundaries contain a given point. A polygon
- * contains a point iff the point is contained within the polygon's collision boundaries,
- * regardless of the direction of the polygon's normals.
- *
- * @param point the point to check
- * @return true if this polygon contains the point, false otherwise.
- */
- public boolean contains(Vector2D point) {
- // If the bounding box doesn't contain the point, we don't need to do any more calculations.
- if (!Util.pointIsWithinBounds(min, max, point)) {
- return false;
- }
-
- // Cast vertical ray from point to outside polygon and counting crossings. Point is in polygon
- // iff number of edges crossed is odd.
-
- // Find a Y value that's definitely outside the polygon.
- float maxY = max.y + 1;
- Vector2D outsidePoint = Vector2D.get(point.x, maxY);
-
- // Check how many edges lie between (p.x, p.y) and (p.x, maxY).
- boolean inside = false;
- for (int i = 0; i < vertices.size(); i++) {
- Vector2D p1 = vertices.get(i);
- Vector2D p2;
- if (i < vertices.size() - 1) {
- p2 = vertices.get(i + 1);
- } else {
- p2 = vertices.get(0);
- }
-
- // First check endpoints. Hitting left-most point counts, hitting right-most
- // doesn't (to weed out case where ray hits 2 lines at their joining vertex) }
- if (p1.y >= point.y && Math.abs(p1.x - point.x) <= EPSILON) {
- if (p2.x >= point.x) {
- inside = !inside;
- }
- continue;
- } else if (p2.y >= point.y && Math.abs(p2.x - point.x) <= EPSILON) {
- if (p1.x >= point.x) {
- inside = !inside;
- }
- continue;
- }
-
- // Now check for intersection.
- if (Util.lineSegmentIntersectsLineSegment(p1, p2, point, outsidePoint)) {
- inside = !inside;
- }
- }
- outsidePoint.release();
- return inside;
- }
-
- public LineSegment getIntersectingLineSegment(Vector2D p, Vector2D q) {
- for (int i = 0; i < vertices.size(); i++) {
- Vector2D p1 = vertices.get(i);
- Vector2D p2;
- if (i < vertices.size() - 1) {
- p2 = vertices.get(i + 1);
- } else {
- p2 = vertices.get(0);
- }
-
- if (Util.lineSegmentIntersectsLineSegment(p1, p2, p, q)) {
- return new LineSegment(p1, p2);
- }
- }
- return null;
- }
-
- public void draw(Canvas canvas) {
- if (!(SwimmingFragment.editorMode)) {
- return;
- }
- for (int i = 0; i < vertices.size(); i++) {
- Vector2D start = vertices.get(i);
- Vector2D end;
- if (i < vertices.size() - 1) {
- end = vertices.get(i + 1);
- } else {
- end = vertices.get(0);
- }
- Vector2D midpoint = Util.getMidpoint(start, end);
- Vector2D normal = Vector2D.get(end).subtract(start).toNormal();
-
- canvas.drawCircle(start.x, start.y, VERTEX_RADIUS, vertexPaint);
- canvas.drawLine(start.x, start.y, end.x, end.y, linePaint);
- canvas.drawCircle(midpoint.x, midpoint.y, VERTEX_RADIUS / 2, midpointPaint);
- canvas.drawLine(midpoint.x, midpoint.y,
- midpoint.x + normal.x * 20, midpoint.y + normal.y * 20, linePaint);
-
- midpoint.release();
- normal.release();
- }
- }
-
- public void setPaintColors(int vertexColor, int lineColor, int midpointColor) {
- vertexPaint.setColor(vertexColor);
- linePaint.setColor(lineColor);
- midpointPaint.setColor(midpointColor);
- }
-
- public JSONArray toJSON() throws JSONException {
- JSONArray json = new JSONArray();
- for (int i = 0; i < vertices.size(); i++) {
- JSONObject vertexJson = new JSONObject();
- Vector2D vertex = vertices.get(i);
- vertexJson.put("x", (double) vertex.x);
- vertexJson.put("y", (double) vertex.y);
- json.put(vertexJson);
- }
- return json;
- }
-
- public static Polygon fromJSON(JSONArray json) throws JSONException {
- List vertices = new ArrayList<>();
- for (int i = 0; i < json.length(); i++) {
- JSONObject vertexJson = json.getJSONObject(i);
- Vector2D vertex =
- Vector2D.get((float) vertexJson.getDouble("x"), (float) vertexJson.getDouble("y"));
- vertices.add(vertex);
- }
- return new Polygon(vertices);
- }
-
- /**
- * A class to specify the starting and ending point of a line segment. Currently only used in
- * determining which line segment is being collided with, so we can determine the normal vector.
- */
- public static class LineSegment {
- public Vector2D start;
- public Vector2D end;
-
- public LineSegment(Vector2D start, Vector2D end) {
- this.start = start;
- this.end = end;
- }
-
- public Vector2D getDirection() {
- return Vector2D.get(end).subtract(start);
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Util.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Util.java
deleted file mode 100644
index e55ae0627..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/physics/Util.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared.physics;
-
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-
-import java.util.List;
-
-/**
- * A utility class for physics-related functions.
- */
-public final class Util {
- private static final String TAG = Util.class.getSimpleName();
- private static final float EPSILON = 0.0001f;
- private static final int COLLINEAR = 0;
- private static final int CLOCKWISE = 1;
- private static final int COUNTERCLOCKWISE = 2;
-
- private Util() {
- // Don't allow instantiation of this class.
- }
-
- /**
- * Return whether the point is within the axis-aligned rectangle defined by p and q.
- *
- * @param p first bounding point.
- * @param q second bounding point.
- * @param point the point to check.
- * @return true if point is within the bounds defined by p and q, false otherwise.
- */
- public static boolean pointIsWithinBounds(Vector2D p, Vector2D q, Vector2D point) {
- return point.x >= Math.min(p.x, q.x) && point.x <= Math.max(p.x, q.x)
- && point.y >= Math.min(p.y, q.y) && point.y <= Math.max(p.y, q.y);
- }
-
- /**
- * Find the orientation of the ordered triplet of points. They are either clockwise,
- * counterclockwise, or collinear.
- * Implementation based on: http://goo.gl/a44iML
- */
- private static int orientation(Vector2D p, Vector2D q, Vector2D r) {
- float value = (q.y - p.y) * (r.x - q.x) - (r.y - q.y) * (q.x - p.x);
-
- // Use this instead of Math.abs(value) here because it is faster.
- if (value < EPSILON && value > -EPSILON) {
- return COLLINEAR;
- }
- return value > 0 ? CLOCKWISE : COUNTERCLOCKWISE;
- }
-
- /**
- * Compute whether or not lines 1 and 2 intersect.
- * Implementation based on:
- * http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
- *
- * @param p1 The starting point of line 1
- * @param q1 The ending point of line 1
- * @param p2 The starting point of line 2
- * @param q2 The ending point of line 2
- * @return true if 1 and 2 intersect, false otherwise.
- */
- public static boolean lineSegmentIntersectsLineSegment(
- Vector2D p1, Vector2D q1, Vector2D p2, Vector2D q2) {
- int o1 = orientation(p1, q1, p2);
- int o2 = orientation(p1, q1, q2);
- int o3 = orientation(p2, q2, p1);
- int o4 = orientation(p2, q2, q1);
-
- // General case
- if (o1 != o2 && o3 != o4) {
- return true;
- }
-
- // Special cases
- if (o1 == 0 && pointIsWithinBounds(p1, q1, p2)
- || o2 == 0 && pointIsWithinBounds(p1, q1, q2)
- || o3 == 0 && pointIsWithinBounds(p2, q2, p1)
- || o4 == 0 && pointIsWithinBounds(p2, q2, q1)) {
- return true;
- }
- return false;
- }
-
- /**
- * Return whether or not two rectangles intersect. This uses a basic form of the separating axis
- * theorem which should be faster than running a full polygon-to-polygon check.
- *
- * @return true if the rectangles intersect, false otherwise.
- */
- public static boolean rectIntersectsRect(float x1, float y1, float w1, float h1,
- float x2, float y2, float w2, float h2) {
- float halfWidth1 = w1 / 2;
- float halfWidth2 = w2 / 2;
- float halfHeight1 = h1 / 2;
- float halfHeight2 = h2 / 2;
-
- float horizontalThreshold = halfWidth1 + halfWidth2;
- float verticalThreshold = halfHeight1 + halfHeight2;
-
- float horizontalDistance = Math.abs(x1 + halfWidth1 - (x2 + halfWidth2));
- float verticalDistance = Math.abs(y1 + halfHeight1 - (y2 + halfHeight2));
-
- return horizontalDistance < horizontalThreshold && verticalDistance < verticalThreshold;
- }
-
- /**
- * Use the separating axis theorem to determine whether or not two convex polygons intersect.
- *
- * @return true if the polygons intersect, false otherwise.
- */
- public static boolean convexPolygonIntersectsConvexPolygon(Polygon p1, Polygon p2) {
- for (int i = 0; i < p1.normals.size(); i++) {
- Vector2D normal = p1.normals.get(i);
- float p1Min = getMinProjectionInDirection(normal, p1.vertices);
- float p1Max = getMaxProjectionInDirection(normal, p1.vertices);
- float p2Min = getMinProjectionInDirection(normal, p2.vertices);
- float p2Max = getMaxProjectionInDirection(normal, p2.vertices);
- if (p1Max < p2Min || p2Max < p1Min) {
- // If there is a separating axis, these polygons do not intersect.
- return false;
- }
- }
- for (int i = 0; i < p2.normals.size(); i++) {
- Vector2D normal = p2.normals.get(i);
- float p1Min = getMinProjectionInDirection(normal, p1.vertices);
- float p1Max = getMaxProjectionInDirection(normal, p1.vertices);
- float p2Min = getMinProjectionInDirection(normal, p2.vertices);
- float p2Max = getMaxProjectionInDirection(normal, p2.vertices);
- if (p1Max < p2Min || p2Max < p1Min) {
- // If there is a separating axis, these polygons do not intersect.
- return false;
- }
- }
- return true;
- }
-
- private static float getMaxProjectionInDirection(Vector2D direction, List points) {
- float max = points.get(0).dot(direction);
- for (int i = 1; i < points.size(); i++) {
- max = Math.max(max, points.get(i).dot(direction));
- }
- return max;
- }
-
- private static float getMinProjectionInDirection(Vector2D direction, List points) {
- float min = points.get(0).dot(direction);
- for (int i = 1; i < points.size(); i++) {
- min = Math.min(min, points.get(i).dot(direction));
- }
- return min;
- }
-
- public static Vector2D getMidpoint(Vector2D p, Vector2D q) {
- float deltaX = q.x - p.x;
- float deltaY = q.y - p.y;
- return Vector2D.get(p.x + deltaX / 2, p.y + deltaY / 2);
- }
-
- public static float clamp(float value, float lowerBound, float upperBound) {
- return Math.max(lowerBound, Math.min(value, upperBound));
- }
-
- public static int clamp(int value, int lowerBound, int upperBound) {
- return Math.max(lowerBound, Math.min(value, upperBound));
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/LoopingMediaPlayer.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/LoopingMediaPlayer.java
deleted file mode 100644
index 01b64afb9..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/LoopingMediaPlayer.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared.sound;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.util.Log;
-
-/**
- * A wrapper around a MediaPlayer which allows for a gapless, looping track.
- */
-public class LoopingMediaPlayer implements PineappleMediaPlayer {
- private static final String TAG = LoopingMediaPlayer.class.getSimpleName();
-
- private MediaPlayer currentPlayer;
- private MediaPlayer nextPlayer;
- private AssetFileDescriptor soundFileDescriptor;
- private float volume;
-
- public static LoopingMediaPlayer create(Context context, int resId) {
- return new LoopingMediaPlayer(context, resId);
- }
-
- private LoopingMediaPlayer(Context context, int resId) {
- soundFileDescriptor = context.getResources().openRawResourceFd(resId);
-
- currentPlayer = MediaPlayer.create(context, resId);
- nextPlayer = MediaPlayer.create(context, resId);
- currentPlayer.setNextMediaPlayer(nextPlayer);
-
- currentPlayer.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mediaPlayer) {
- try {
- currentPlayer.reset();
- currentPlayer.setDataSource(soundFileDescriptor.getFileDescriptor(),
- soundFileDescriptor.getStartOffset(), soundFileDescriptor.getLength());
- currentPlayer.prepare();
- nextPlayer.setNextMediaPlayer(currentPlayer);
- } catch (Exception e) {
- Log.w(TAG, "onCompletion: unexpected exception", e);
- }
- }
- });
- nextPlayer.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mediaPlayer) {
- try {
- nextPlayer.reset();
- nextPlayer.setDataSource(soundFileDescriptor.getFileDescriptor(),
- soundFileDescriptor.getStartOffset(), soundFileDescriptor.getLength());
- nextPlayer.prepare();
- currentPlayer.setNextMediaPlayer(nextPlayer);
- } catch (Exception e) {
- Log.w(TAG, "onCompletion: unexpected exception", e);
- }
- }
- });
- }
-
- @Override
- public void start() {
- try {
- currentPlayer.start();
- } catch (IllegalStateException e) {
- Log.w(TAG, "start() failed: " + e.toString());
- }
- }
-
- @Override
- public boolean isPlaying() {
- return currentPlayer.isPlaying() || nextPlayer.isPlaying();
- }
-
- @Override
- public void pause() {
- try {
- currentPlayer.pause();
- nextPlayer.pause();
- } catch (Exception e) {
- Log.w(TAG, "pause() failed: " + e.toString());
- }
- }
-
- @Override
- public MediaPlayer getMediaPlayer() {
- return currentPlayer;
- }
-
- @Override
- public void setNextMediaPlayer(MediaPlayer mediaPlayer) {
- try {
- currentPlayer.setNextMediaPlayer(mediaPlayer);
- } catch (Exception e) {
- Log.w(TAG, "setNextMediaPlayer() failed: ", e);
- }
- }
-
- @Override
- public void setVolume(float volume) {
- this.volume = volume;
- try {
- currentPlayer.setVolume(volume, volume);
- nextPlayer.setVolume(volume, volume);
- } catch (Exception e) {
- Log.w(TAG, "setVolume() failed: ", e);
- }
- }
-
- @Override
- public void mute() {
- try {
- currentPlayer.setVolume(0, 0);
- nextPlayer.setVolume(0, 0);
- } catch (Exception e) {
- Log.w(TAG, "mute() failed: ", e);
- }
- }
-
- @Override
- public void unmute() {
- try {
- currentPlayer.setVolume(volume, volume);
- nextPlayer.setVolume(volume, volume);
- } catch (Exception e) {
- Log.w(TAG, "unmute() failed: ", e);
- }
- }
-
- @Override
- public void release() {
- try {
- currentPlayer.release();
- nextPlayer.release();
- soundFileDescriptor.close();
- } catch (Exception e) {
- Log.w(TAG, "release() failed: ", e);
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/PineappleMediaPlayer.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/PineappleMediaPlayer.java
deleted file mode 100644
index e7d88d873..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/PineappleMediaPlayer.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared.sound;
-
-import android.media.MediaPlayer;
-
-/**
- * A wrapper around MediaPlayer so that we can extend its behavior.
- */
-public interface PineappleMediaPlayer {
- void start();
- boolean isPlaying();
- void pause();
- MediaPlayer getMediaPlayer();
- void setNextMediaPlayer(MediaPlayer mediaPlayer);
- void setVolume(float volume);
- void mute();
- void unmute();
- void release();
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/PineappleMediaPlayerImpl.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/PineappleMediaPlayerImpl.java
deleted file mode 100644
index 0623be205..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/PineappleMediaPlayerImpl.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared.sound;
-
-import android.content.Context;
-import android.media.MediaPlayer;
-import android.util.Log;
-
-/**
- * A wrapper around a MediaPlayer so that we can define our own LoopingMediaPlayer and have it
- * use a common interface.
- */
-public class PineappleMediaPlayerImpl implements PineappleMediaPlayer {
- private static final String TAG = PineappleMediaPlayerImpl.class.getSimpleName();
-
- private MediaPlayer mediaPlayer;
- private float volume;
-
- public static PineappleMediaPlayerImpl create(Context context, int resId) {
- return new PineappleMediaPlayerImpl(context, resId);
- }
-
- private PineappleMediaPlayerImpl(Context context, int resId) {
- mediaPlayer = MediaPlayer.create(context, resId);
- }
-
- @Override
- public void start() {
- try {
- mediaPlayer.start();
- } catch (IllegalStateException e) {
- Log.w(TAG, "start() failed: " + e.toString());
- }
- }
-
- @Override
- public boolean isPlaying() {
- return mediaPlayer.isPlaying();
- }
-
- @Override
- public void pause() {
- try {
- mediaPlayer.pause();
- } catch (IllegalStateException e) {
- Log.w(TAG, "pause() failed: " + e.toString());
- }
- }
-
- @Override
- public MediaPlayer getMediaPlayer() {
- return mediaPlayer;
- }
-
- @Override
- public void setNextMediaPlayer(MediaPlayer next) {
- try {
- mediaPlayer.setNextMediaPlayer(next);
- } catch (IllegalStateException e) {
- Log.w(TAG, "setNextMediaPlayer() failed: " + e.toString());
- }
- }
-
- @Override
- public void setVolume(float volume) {
- this.volume = volume;
- try {
- mediaPlayer.setVolume(volume, volume);
- } catch (IllegalStateException e) {
- Log.w(TAG, "setVolume() failed: " + e.toString());
- }
- }
-
- @Override
- public void mute() {
- try {
- mediaPlayer.setVolume(0, 0);
- } catch (IllegalStateException e) {
- Log.w(TAG, "mute() failed: " + e.toString());
- }
- }
-
- @Override
- public void unmute() {
- try {
- mediaPlayer.setVolume(volume, volume);
- } catch (IllegalStateException e) {
- Log.w(TAG, "unmute() failed: " + e.toString());
- }
- }
-
- @Override
- public void release() {
- try {
- mediaPlayer.release();
- } catch (IllegalStateException e) {
- Log.w(TAG, "release() failed: " + e.toString());
- }
- }
-
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/SoundManager.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/SoundManager.java
deleted file mode 100644
index b063f09f1..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/shared/sound/SoundManager.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.shared.sound;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.media.AudioManager;
-import android.media.SoundPool;
-import android.os.AsyncTask;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A manager for all of the different sounds which will be played in the games.
- */
-public class SoundManager {
- private static final String TAG = SoundManager.class.getSimpleName();
- private static final String PREFS_NAME = "PineappleSoundManager";
- private static final String IS_MUTED_PREF = "MutePref";
- private static SoundManager instance;
- public static boolean soundsAreMuted;
-
- private final Object mediaPlayerLock = new Object();
- private final Object soundPoolLock = new Object();
-
- // A map from resource ID to a media player which can play the sound clip.
- private Map mediaPlayerMap;
- // A map from resource ID to a sound pool containing the sound clip.
- private Map soundPoolMap;
- // A set of resource IDs for sounds which have been individually muted. These shouldn't be unmuted
- // when the mute option is toggled.
- private Set mutedSounds;
- private SoundManager() {
- mediaPlayerMap = new HashMap<>();
- soundPoolMap = new HashMap<>();
- mutedSounds = new HashSet<>();
- }
-
- public static SoundManager getInstance() {
- if (instance == null) {
- instance = new SoundManager();
- }
- return instance;
- }
-
- public void loadLongSound(Context context, int resId, boolean looping, float volume) {
- if (mediaPlayerMap.containsKey(resId)) {
- return;
- }
- PineappleMediaPlayer mediaPlayer;
- if (looping) {
- mediaPlayer = LoopingMediaPlayer.create(context, resId);
- } else {
- mediaPlayer = PineappleMediaPlayerImpl.create(context, resId);
- }
- mediaPlayer.setVolume(volume);
- if (soundsAreMuted) {
- mediaPlayer.mute();
- }
- synchronized (mediaPlayerLock) {
- mediaPlayerMap.put(resId, mediaPlayer);
- }
- }
-
- public void loadLongSound(Context context, int resId, boolean looping) {
- // Make this quiet by default so that it doesn't overpower the in-game sounds.
- loadLongSound(context, resId, looping, 0.1f);
- }
-
- public void loadShortSound(final Context context, final int resId, final boolean looping,
- final float volume) {
- if (soundPoolMap.containsKey(resId)) {
- return;
- }
- new AsyncTask () {
- @Override
- protected Void doInBackground(Void... params) {
- SoundPoolContainer container = new SoundPoolContainer(context, resId, looping, volume);
- synchronized (soundPoolLock) {
- soundPoolMap.put(resId, container);
- }
- return null;
- }
- }.execute();
- }
-
- public void loadShortSound(Context context, int resId) {
- loadShortSound(context, resId, false, 1f);
- }
-
- public void play(int resId) {
- if (mediaPlayerMap.containsKey(resId)) {
- mediaPlayerMap.get(resId).start();
- } else if (soundPoolMap.containsKey(resId)) {
- SoundPoolContainer container = soundPoolMap.get(resId);
- float vol = soundsAreMuted ? 0 : container.volume;
- container.streamId =
- container.soundPool.play(container.soundId, vol, vol, 1, container.looping ? -1 : 0, 1);
- }
- }
-
- public void pauseAll() {
- synchronized (mediaPlayerLock) {
- for (int resId : mediaPlayerMap.keySet()) {
- pause(resId);
- }
- }
- synchronized (soundPoolLock) {
- for (int resId : soundPoolMap.keySet()) {
- pause(resId);
- }
- }
- }
-
- public void pause(int resId) {
- if (mediaPlayerMap.containsKey(resId)) {
- PineappleMediaPlayer mediaPlayer = mediaPlayerMap.get(resId);
- if (mediaPlayer.isPlaying()) {
- mediaPlayer.pause();
- }
- } else if (soundPoolMap.containsKey(resId)) {
- soundPoolMap.get(resId).soundPool.autoPause();
- }
- }
-
- public void pauseShortSounds() {
- synchronized (soundPoolLock) {
- for (SoundPoolContainer container : soundPoolMap.values()) {
- container.soundPool.autoPause();
- }
- }
- }
-
- public void resumeShortSounds() {
- synchronized (soundPoolLock) {
- for (SoundPoolContainer container : soundPoolMap.values()) {
- container.soundPool.autoResume();
- }
- }
- }
-
- public void playLongSoundsInSequence(int[] soundIds) {
- for (int i = 0; i < soundIds.length; i++) {
- if (isPlayingLongSound(soundIds[i])) {
- // Don't try to play long sounds which are already playing.
- return;
- }
- }
-
- try {
- PineappleMediaPlayer[] sounds = new PineappleMediaPlayer[soundIds.length];
- for (int i = 0; i < soundIds.length; i++) {
- sounds[i] = mediaPlayerMap.get(soundIds[i]);
- if (sounds[i] == null) {
- return;
- }
- if (i > 0) {
- sounds[i - 1].setNextMediaPlayer(sounds[i].getMediaPlayer());
- }
- }
- sounds[0].start();
- } catch (IllegalStateException e) {
- Log.d(TAG, "playLongSoundsInSequence() failed: " + e.toString());
- }
- }
-
- public boolean isPlayingLongSound(int resId) {
- if (mediaPlayerMap.containsKey(resId)) {
- return mediaPlayerMap.get(resId).isPlaying();
- }
- return false;
- }
-
- public void releaseAll() {
- synchronized (mediaPlayerLock) {
- for (PineappleMediaPlayer mediaPlayer : mediaPlayerMap.values()) {
- mediaPlayer.release();
- }
- mediaPlayerMap.clear();
- }
- synchronized (soundPoolLock) {
- for (SoundPoolContainer container : soundPoolMap.values()) {
- container.soundPool.release();
- }
- soundPoolMap.clear();
- }
- }
-
- public void release(int resId) {
- if (mediaPlayerMap.containsKey(resId)) {
- mediaPlayerMap.get(resId).release();
- mediaPlayerMap.remove(resId);
- }
- if (soundPoolMap.containsKey(resId)) {
- soundPoolMap.get(resId).soundPool.release();
- soundPoolMap.remove(resId);
- }
- }
-
- public void mute() {
- synchronized (mediaPlayerLock) {
- for (int resId : mediaPlayerMap.keySet()) {
- muteInternal(resId, false);
- }
- }
- synchronized (soundPoolLock) {
- for (int resId : soundPoolMap.keySet()) {
- muteInternal(resId, false);
- }
- }
- soundsAreMuted = true;
- }
-
- public void mute(int resId) {
- muteInternal(resId, true);
- }
-
- private void muteInternal(int resId, boolean addToMutedSounds) {
- if (mediaPlayerMap.containsKey(resId)) {
- mediaPlayerMap.get(resId).mute();
- }
- if (soundPoolMap.containsKey(resId)) {
- SoundPoolContainer container = soundPoolMap.get(resId);
- container.soundPool.setVolume(container.streamId, 0, 0);
- }
- if (addToMutedSounds) {
- mutedSounds.add(resId);
- }
- }
-
- public void unmute() {
- soundsAreMuted = false;
- synchronized (mediaPlayerLock) {
- for (int resId : mediaPlayerMap.keySet()) {
- if (!mutedSounds.contains(resId)) {
- unmuteInternal(resId, false);
- }
- }
- }
- synchronized (soundPoolLock) {
- for (int resId : soundPoolMap.keySet()) {
- if (!mutedSounds.contains(resId)) {
- unmuteInternal(resId, false);
- }
- }
- }
- }
-
- public void unmute(int resId) {
- unmuteInternal(resId, true);
- }
-
- private void unmuteInternal(int resId, boolean removeFromMutedSounds) {
- if (soundsAreMuted) {
- return;
- }
- if (mediaPlayerMap.containsKey(resId)) {
- mediaPlayerMap.get(resId).unmute();
- }
- if (soundPoolMap.containsKey(resId)) {
- SoundPoolContainer container = soundPoolMap.get(resId);
- container.soundPool.setVolume(container.streamId, container.volume, container.volume);
- }
- if (removeFromMutedSounds) {
- mutedSounds.remove(resId);
- }
- }
-
- public void storeMutePreference(final Context context) {
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... voids) {
- SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS_NAME, 0);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putBoolean(IS_MUTED_PREF, soundsAreMuted);
- editor.commit();
- return null;
- }
- }.execute();
- }
-
- public void loadMutePreference(final Context context) {
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... voids) {
- SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS_NAME, 0);
- if (sharedPreferences.getBoolean(IS_MUTED_PREF, false)) {
- mute();
- } else {
- unmute();
- }
- return null;
- }
- }.execute();
- }
-
- private static class SoundPoolContainer {
- public final SoundPool soundPool;
- public final int soundId;
- public final boolean looping;
- public final float volume;
- public int streamId;
- public SoundPoolContainer(Context context, int resId, boolean looping, float volume) {
- soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
- soundId = soundPool.load(context, resId, 1);
- this.looping = looping;
- this.volume = volume;
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/BounceActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/BounceActor.java
deleted file mode 100644
index 3a762a172..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/BounceActor.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.EventBus;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon.LineSegment;
-import com.google.android.apps.santatracker.doodles.shared.physics.Util;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * An actor which causes a colliding object to bounce off of it.
- */
-public class BounceActor extends CollisionActor {
- public static final String TYPE = "Bouncy";
-
- private static final float VIBRATE_VELOCITY_THRESHOLD = 150;
-
- public BounceActor(Polygon collisionBody) {
- super(collisionBody);
- }
-
- /**
- * Cause the other actor to bounce off of this actor.
- * Implementation based on: http://goo.gl/2gcLVd
- */
- @Override
- public boolean resolveCollision(Actor other, float deltaMs) {
- // Short-circuit this call if a bounding box check is not passed.
- if (!collisionBody.isInverted() &&
- !Util.pointIsWithinBounds(collisionBody.min, collisionBody.max, other.position)) {
- // For a non-inverted polygon, we will collide with the body by moving inside of it. If the
- // other actor is not inside of the bounds of the collision body, it cannot cause a collision.
- return false;
- }
-
- float deltaSeconds = deltaMs / 1000.0f;
- Vector2D relativeVelocity = Vector2D.get(other.velocity.x - velocity.x,
- other.velocity.y - velocity.y);
-
- // Find which line segment was crossed in order to intersect with the collision actor.
- LineSegment intersectingSegment = collisionBody.getIntersectingLineSegment(
- other.position, other.positionBeforeFrame);
- if (intersectingSegment == null) {
- relativeVelocity.release();
- return false;
- }
- Vector2D normal = intersectingSegment.getDirection().toNormal();
-
- // Calculate relative velocity in terms of the normal direction.
- float velocityAlongNormal = relativeVelocity.dot(normal);
-
- // Don't collide if velocities are separating.
- if (velocityAlongNormal > 0) {
- relativeVelocity.release();
- normal.release();
- return false;
- } else if (velocityAlongNormal < -VIBRATE_VELOCITY_THRESHOLD) {
- EventBus.getInstance().sendEvent(EventBus.VIBRATE);
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.golf_hit_wall);
- }
-
- // Correct the position by moving the other actor along the normal of the colliding line
- // segment until it passes back over the colliding segment. This works for the general case.
- // Add the normal vector so that collisions with tiny velocities aren't affected by floating
- // point precision.
- Vector2D correctedPosition = Vector2D.get(other.position).add(normal).subtract(
- normal.x * velocityAlongNormal * deltaSeconds,
- normal.y * velocityAlongNormal * deltaSeconds);
-
- // This code checks to see if the corrected position causes the actor's path to intersect
- // another line segment of the polygon. This could happen if the actor is colliding with a
- // corner of this collision object. If this is the case, then we should just do the safe thing
- // and undo the other actor's position update before applying the impulse.
- intersectingSegment = collisionBody.getIntersectingLineSegment(
- other.positionBeforeFrame, correctedPosition);
- boolean movedBall = false;
- if (intersectingSegment != null) {
- // It is possible, if moving through the corner of an obstacle, that this correction causes
- // the colliding actor to fall through this actor anyway. Correct for that case by moving
- // the colliding actor back to its original position.
- other.position.set(other.positionBeforeFrame);
- } else {
- other.position.set(correctedPosition);
- movedBall = true;
- }
-
- // Calculate restitution.
- float e = Math.min(other.restitution, restitution);
-
- // Calculate impulse.
- float j = -(1 + e) * velocityAlongNormal;
- j /= other.inverseMass + inverseMass;
-
- // Apply impulse.
- Vector2D impulse = normal.scale(j);
- other.velocity.add(impulse.x * other.inverseMass, impulse.y * other.inverseMass);
- velocity.subtract(impulse.x * inverseMass, impulse.y * inverseMass);
-
- relativeVelocity.release();
- correctedPosition.release();
- normal.release();
- return movedBall;
- }
-
- @Override
- public String getType() {
- return BounceActor.TYPE;
- }
-
- public static BounceActor fromJSON(JSONObject json) throws JSONException {
- return new BounceActor(Polygon.fromJSON(json.getJSONArray(POLYGON_KEY)));
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/BoundingBoxSpriteActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/BoundingBoxSpriteActor.java
deleted file mode 100644
index b0a8c63e8..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/BoundingBoxSpriteActor.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.util.Log;
-
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.SpriteActor;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-import com.google.android.apps.santatracker.doodles.shared.physics.Util;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-
-/**
- * A sprite actor which contains a pre-set convex collision body.
- */
-public class BoundingBoxSpriteActor extends CollisionActor implements Touchable {
- private static final String TAG = BoundingBoxSpriteActor.class.getSimpleName();
-
- public static final String DUCK = "duck";
- public static final String ICE_CUBE = "cube1";
- public static final String HAND_GRAB = "hand grab";
-
- private static final Random RANDOM = new Random();
- protected static final float SCALE = 2;
-
- /**
- * A utility class which contains data needed to create BoundingBoxSpriteActors.
- */
- protected static class Data {
- public int[] resIds;
- public int numFrames;
- public int zIndex;
- public Vector2D spriteOffset;
- public Vector2D[] vertexOffsets;
- public Data(int[] resIds, int zIndex, Vector2D spriteOffset, Vector2D[] vertexOffsets) {
- this.resIds = resIds;
- this.numFrames = resIds == null ? 0 : resIds.length;
- this.zIndex = zIndex;
- this.spriteOffset = spriteOffset;
- this.vertexOffsets = vertexOffsets;
- }
- }
- public static final Map TYPE_TO_RESOURCE_MAP;
- static {
- TYPE_TO_RESOURCE_MAP = new HashMap<>();
-
- Vector2D[] duckVertexOffsets = {
- Vector2D.get(0, 0),
- Vector2D.get(87, 0),
- Vector2D.get(87, 186),
- Vector2D.get(0, 186),
- };
- TYPE_TO_RESOURCE_MAP.put(DUCK,
- new Data(Sprites.penguin_swim_elf, 1, Vector2D.get(0, 0).scale(SCALE),
- duckVertexOffsets));
-
- Vector2D[] iceCube1VertexOffsets = {
- Vector2D.get(0, 0), Vector2D.get(101.9f, 0),
- Vector2D.get(101.9f, 100.2f), Vector2D.get(0, 100.2f)
- };
- TYPE_TO_RESOURCE_MAP.put(ICE_CUBE,
- new Data(Sprites.penguin_swim_ice, 1, Vector2D.get(0, 0).scale(SCALE),
- iceCube1VertexOffsets));
-
- // This is just a placeholder so that we can create hand grabs programatically. This data
- // shouldn't actually be used.
- TYPE_TO_RESOURCE_MAP.put(HAND_GRAB, new Data(null, 0, Vector2D.get(0, 0).scale(SCALE),
- null));
- }
-
- public String type;
- public final SpriteActor spriteActor;
- public Vector2D spriteOffset;
-
- public BoundingBoxSpriteActor(
- Polygon collisionBody, SpriteActor spriteActor, Vector2D spriteOffset, String type) {
- super(collisionBody);
-
- this.spriteOffset = spriteOffset;
- this.type = type;
- this.spriteActor = spriteActor;
- scale = SCALE;
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
- spriteActor.update(deltaMs);
- spriteActor.position.set(position.x, position.y);
- }
-
- @Override
- public void draw(Canvas canvas) {
- spriteActor.draw(canvas, spriteOffset.x, spriteOffset.y,
- spriteActor.sprite.frameWidth * scale, spriteActor.sprite.frameHeight * scale);
- collisionBody.draw(canvas);
- }
-
- @Override
- public boolean canHandleTouchAt(Vector2D worldCoords, float cameraScale) {
- Vector2D lowerRight = Vector2D.get(position).add(spriteOffset)
- .add(spriteActor.sprite.frameWidth * scale,
- spriteActor.sprite.frameHeight * scale);
- boolean retVal = super.canHandleTouchAt(worldCoords, cameraScale)
- || Util.pointIsWithinBounds(Vector2D.get(position).add(spriteOffset),
- lowerRight, worldCoords);
-
- lowerRight.release();
- return retVal;
- }
-
- @Override
- public void startTouchAt(Vector2D worldCoords, float cameraScale) {
- selectedIndex = collisionBody.getSelectedIndex(worldCoords, cameraScale);
- }
-
- @Override
- public boolean handleMoveEvent(Vector2D delta) {
- collisionBody.move(-delta.x, -delta.y);
- position.set(collisionBody.min);
- // NOTE: Leave this commented-out section here. This is used when adding new
- // BoundingBoxSpriteActors in order to fine-tune the collision boundaries and sprite offsets.
- /*
- boolean moved;
- if (selectedIndex >= 0) {
- collisionBody.moveVertex(selectedIndex, Vector2D.get(delta).scale(-1));
- Log.d(TAG, "min: " + collisionBody.min);
- Log.d(TAG, "max: " + collisionBody.max);
- } else {
- spriteOffset.subtract(delta);
- Log.d(TAG, "Sprite offset: " + spriteOffset);
- }
- */
- return true;
- }
-
- @Override
- public boolean handleLongPress() {
- // NOTE: Leave this commented-out section here. This is used when adding new
- // BoundingBoxSpriteActors in order to fine-tune the collision boundaries and sprite offsets.
- /*
- if (selectedIndex >= 0) {
- if (canRemoveCollisionVertex()) {
- // If we can, just remove the vertex.
- collisionBody.removeVertexAt(selectedIndex);
- return true;
- }
- } else if (midpointIndex >= 0) {
- // Long press on a midpoint, add a vertex to the selected obstacle's polygon.
- collisionBody.addVertexAfter(midpointIndex);
- return true;
- }
- */
- return false;
- }
-
- @Override
- public boolean resolveCollision(Actor other, float deltaMs) {
- if (other instanceof SwimmerActor) {
- return resolveCollisionInternal((SwimmerActor) other);
- }
- return false;
- }
-
- @Override
- public String getType() {
- return type;
- }
-
- @Override
- public JSONObject toJSON() throws JSONException {
- JSONObject json = new JSONObject();
- json.put(TYPE_KEY, getType());
- json.put(X_KEY, position.x);
- json.put(Y_KEY, position.y);
- return json;
- }
-
- protected boolean resolveCollisionInternal(SwimmerActor swimmer) {
- if (swimmer.isInvincible || swimmer.isUnderwater) {
- return false;
- }
- if (swimmer.collisionBody.min.y > collisionBody.max.y
- || swimmer.collisionBody.max.y < collisionBody.min.y) {
- // Perform a short-circuiting check which fails if the swimmer is outside of the vertical
- // boundaries of this collision body.
- return false;
- }
-
- // NOTE: We've since removed the diagonal ice cube, so we don't have any
- // non-axis-aligned rectangles to check collisions with. However, there may still be a few
- // artifacts of complex polygon collisions in the code.
-
- // CAN and ICE_CUBE objects are just axis-aligned rectangles. Use the faster
- // rectangle-to-rectangle collision code in these cases.
- if (Util.rectIntersectsRect(swimmer.collisionBody.min.x, swimmer.collisionBody.min.y,
- swimmer.collisionBody.getWidth(), swimmer.collisionBody.getHeight(),
- collisionBody.min.x, collisionBody.min.y,
- collisionBody.getWidth(), collisionBody.getHeight())) {
-
- // If the swimmer is colliding with the side of an obstacle, make the swimmer slide along it
- // instead of colliding.
- if (swimmer.positionBeforeFrame.y < collisionBody.max.y) {
- swimmer.moveTo(swimmer.positionBeforeFrame.x, swimmer.position.y);
- } else {
- swimmer.collide(type);
- }
- }
- return false;
- }
-
- public static BoundingBoxSpriteActor create(Vector2D position, String type, Resources resources) {
- if (!TYPE_TO_RESOURCE_MAP.containsKey(type)) {
- Log.e(TAG, "Unknown object type: " + type);
- return null;
- }
- Data data = TYPE_TO_RESOURCE_MAP.get(type);
-
- BoundingBoxSpriteActor actor;
- if (type.equals(HAND_GRAB)) {
- actor = HandGrabActor.create(position, resources);
- } else {
- actor = new BoundingBoxSpriteActor(
- getBoundingBox(position, data.vertexOffsets, SCALE),
- new SpriteActor(AnimatedSprite.fromFrames(resources, data.resIds),
- Vector2D.get(position), Vector2D.get(0, 0)),
- Vector2D.get(data.spriteOffset),
- type);
- }
-
- actor.zIndex = data.zIndex;
-
- // Start at a random frame index so that all of the sprites aren't synced up.
- actor.spriteActor.sprite.setFrameIndex(RANDOM.nextInt(actor.spriteActor.sprite.getNumFrames()));
-
- return actor;
- }
-
- public static BoundingBoxSpriteActor fromJSON(JSONObject json, Context context)
- throws JSONException {
- String type = json.getString(Actor.TYPE_KEY);
- Vector2D position = Vector2D.get((float) json.getDouble(X_KEY), (float) json.getDouble(Y_KEY));
- return create(position, type, context.getResources());
- }
-
- protected static Polygon getBoundingBox(
- Vector2D position, Vector2D[] vertexOffsets, float scale) {
- List vertices = new ArrayList<>();
- for (int i = 0; i < vertexOffsets.length; i++) {
- vertices.add(Vector2D.get(position).add(Vector2D.get(vertexOffsets[i]).scale(scale)));
- }
- return new Polygon(vertices);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/CollisionActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/CollisionActor.java
deleted file mode 100644
index b888cc802..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/CollisionActor.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.graphics.Canvas;
-
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * An actor which represents an object in the world which causes some reaction when it is collided
- * with.
- */
-public class CollisionActor extends Actor implements Touchable {
- public static final String TYPE = "collision";
- protected static final String POLYGON_KEY = "polygon";
-
- // These measurements are in "world units", which are really just arbitrary units where one world
- // unit == one pixel at the default camera zoom level.
- public static final float DEFAULT_WIDTH = 200;
- public static final float DEFAULT_HEIGHT = 200;
- public Polygon collisionBody;
-
- protected int selectedIndex = -1;
- protected int midpointIndex = -1;
-
- public CollisionActor(Polygon collisionBody) {
- super(Vector2D.get(collisionBody.min.x, collisionBody.min.y), Vector2D.get());
- this.collisionBody = collisionBody;
- restitution = 0.7f;
- inverseMass = Actor.INFINITE_MASS; // Give collision actors infinite mass.
- zIndex = 10;
- }
-
- @Override
- public void update(float deltaMs) {
- positionBeforeFrame.set(position);
- float deltaX = velocity.x * deltaMs / 1000.0f;
- float deltaY = velocity.y * deltaMs / 1000.0f;
- collisionBody.move(deltaX, deltaY);
- position.set(collisionBody.min);
- }
-
- @Override
- public boolean canHandleTouchAt(Vector2D worldCoords, float cameraScale) {
- return collisionBody.getSelectedIndex(worldCoords, cameraScale) >= 0
- || collisionBody.getMidpointIndex(worldCoords, cameraScale) >= 0;
- }
-
- @Override
- public void startTouchAt(Vector2D worldCoords, float cameraScale) {
- selectedIndex = collisionBody.getSelectedIndex(worldCoords, cameraScale);
- midpointIndex = collisionBody.getMidpointIndex(worldCoords, cameraScale);
- }
-
- @Override
- public boolean handleMoveEvent(Vector2D delta) {
- if (selectedIndex >= 0) {
- Vector2D positionDelta = Vector2D.get(delta).scale(-1);
- collisionBody.moveVertex(selectedIndex, positionDelta);
- positionDelta.release();
- return true;
- }
- return false;
- }
-
- @Override
- public boolean handleLongPress() {
- if (selectedIndex >= 0) {
- if (canRemoveCollisionVertex()) {
- // If we can, just remove the vertex.
- collisionBody.removeVertexAt(selectedIndex);
- return true;
- }
- } else if (midpointIndex >= 0) {
- // Long press on a midpoint, add a vertex to the selected obstacle's polygon.
- collisionBody.addVertexAfter(midpointIndex);
- return true;
- }
- return false;
- }
-
- @Override
- public String getType() {
- return CollisionActor.TYPE;
- }
-
- @Override
- public JSONObject toJSON() throws JSONException {
- JSONObject json = new JSONObject();
- json.put(TYPE_KEY, getType());
- json.put(POLYGON_KEY, collisionBody.toJSON());
- return json;
- }
-
- /**
- * Resolve a collision with another physics actor.
- *
- * @param other The actor being collided with.
- * @param deltaMs The length of the collision frame.
- * @return true if resolving the collision moves the other actor, false otherwise.
- */
- public boolean resolveCollision(Actor other, float deltaMs) {
- return false;
- }
-
- public void draw(Canvas canvas) {
- collisionBody.draw(canvas);
- }
-
- public boolean canRemoveCollisionVertex() {
- return collisionBody.vertices.size() > 3;
- }
-
- public static CollisionActor fromJSON(JSONObject json) throws JSONException {
- return new CollisionActor(Polygon.fromJSON(json.getJSONArray(POLYGON_KEY)));
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ColoredRectangleActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ColoredRectangleActor.java
deleted file mode 100644
index 36e6d633d..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ColoredRectangleActor.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Cap;
-import android.graphics.Paint.Style;
-
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.physics.Util;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * An actor class which represents an arbitrarily-colored rectangle.
- */
-public class ColoredRectangleActor extends Actor implements Touchable {
- /* Default color is black*/
- public static final String UNSPECIFIED = "unspecified";
-
- /* Golf colors */
- public static final String TEE_GREEN = "tee";
- public static final String FAIRWAY_GREEN = "fairway";
-
- /* Swimming colors */
- public static final String DISTANCE_30M = "30m";
- public static final String DISTANCE_50M = "50m";
- public static final String DISTANCE_100M = "100m";
- public static final String DISTANCE_LEVEL_LENGTH = "level length";
- public static final String DISTANCE_PR = "pr";
- public static final String STARTING_BLOCK = "start";
-
- public static final String DIMENS_X_KEY = "dimens x";
- public static final String DIMENS_Y_KEY = "dimens y";
-
-
- public static final Map TYPE_TO_COLOR_MAP;
- static {
- TYPE_TO_COLOR_MAP = new HashMap<>();
- /* Golf */
- TYPE_TO_COLOR_MAP.put(TEE_GREEN, Constants.LIGHT_GREEN);
- TYPE_TO_COLOR_MAP.put(FAIRWAY_GREEN, Constants.DARK_GREEN);
- /* Swimming */
- TYPE_TO_COLOR_MAP.put(DISTANCE_30M, 0x44cd7f32);
- TYPE_TO_COLOR_MAP.put(DISTANCE_50M, 0x44c0c0c0);
- TYPE_TO_COLOR_MAP.put(DISTANCE_100M, 0x44ffd700);
- TYPE_TO_COLOR_MAP.put(DISTANCE_LEVEL_LENGTH, 0x44ffffff);
- TYPE_TO_COLOR_MAP.put(DISTANCE_PR, 0x4400cc00);
- TYPE_TO_COLOR_MAP.put(STARTING_BLOCK, 0xff4993a4);
- TYPE_TO_COLOR_MAP.put(UNSPECIFIED, 0xff000000);
- }
-
- /**
- * A direction used to pull the boundaries of the colored rectangle.
- */
- private enum Direction {
- NONE,
- UP,
- DOWN,
- LEFT,
- RIGHT,
- }
- private Direction selectedDirection;
-
- public String type;
- public Vector2D dimens;
- private Paint paint;
- private Paint midpointPaint;
-
- private Vector2D upMidpoint = Vector2D.get();
- private Vector2D downMidpoint = Vector2D.get();
- private Vector2D leftMidpoint = Vector2D.get();
- private Vector2D rightMidpoint = Vector2D.get();
-
- public ColoredRectangleActor(Vector2D position, Vector2D dimens) {
- this(position, dimens, UNSPECIFIED);
- }
-
- public ColoredRectangleActor(Vector2D position, Vector2D dimens, String type) {
- super(position, Vector2D.get());
-
- this.dimens = dimens;
- this.type = type;
- this.zIndex = -1;
-
- if (type.equals(FAIRWAY_GREEN)) {
- this.zIndex = -2;
- }
-
- selectedDirection = Direction.NONE;
- paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- paint.setColor(TYPE_TO_COLOR_MAP.get(type));
- paint.setStyle(Style.FILL);
-
- midpointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- midpointPaint.setColor(Color.WHITE);
-
- updateExtents();
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.drawRect(position.x, position.y, position.x + dimens.x, position.y + dimens.y, paint);
- }
-
- public void setStyle(Style style) {
- paint.setStyle(style);
- }
-
- public void setStrokeWidth(float width) {
- paint.setStrokeWidth(width);
- paint.setStrokeCap(Cap.ROUND);
- }
-
- public void setColor(int color) {
- paint.setColor(color);
- }
-
- @Override
- public String getType() {
- return type;
- }
-
- @Override
- public boolean canHandleTouchAt(Vector2D worldCoords, float cameraScale) {
- Vector2D lowerRight = Vector2D.get(position).add(dimens);
- boolean retVal = Util.pointIsWithinBounds(position, lowerRight, worldCoords)
- || worldCoords.distanceTo(upMidpoint) < Constants.SELECTION_RADIUS
- || worldCoords.distanceTo(downMidpoint) < Constants.SELECTION_RADIUS
- || worldCoords.distanceTo(leftMidpoint) < Constants.SELECTION_RADIUS
- || worldCoords.distanceTo(rightMidpoint) < Constants.SELECTION_RADIUS;
-
- lowerRight.release();
- return retVal;
- }
-
- @Override
- public void startTouchAt(Vector2D worldCoords, float cameraScale) {
- if (worldCoords.distanceTo(upMidpoint) < Constants.SELECTION_RADIUS) {
- selectedDirection = Direction.UP;
- } else if (worldCoords.distanceTo(downMidpoint) < Constants.SELECTION_RADIUS) {
- selectedDirection = Direction.DOWN;
- } else if (worldCoords.distanceTo(leftMidpoint) < Constants.SELECTION_RADIUS) {
- selectedDirection = Direction.LEFT;
- } else if (worldCoords.distanceTo(rightMidpoint) < Constants.SELECTION_RADIUS) {
- selectedDirection = Direction.RIGHT;
- } else {
- selectedDirection = Direction.NONE;
- }
- }
-
- @Override
- public boolean handleMoveEvent(Vector2D delta) {
- if (selectedDirection == Direction.NONE) {
- position.subtract(delta);
- } else if (selectedDirection == Direction.UP) {
- position.y -= delta.y;
- dimens.y += delta.y;
- } else if (selectedDirection == Direction.DOWN) {
- dimens.y -= delta.y;
- } else if (selectedDirection == Direction.LEFT) {
- position.x -= delta.x;
- dimens.x += delta.x;
- } else {
- // Direction.RIGHT
- dimens.x -= delta.x;
- }
- updateExtents();
- return true;
- }
-
- @Override
- public boolean handleLongPress() {
- return false;
- }
-
- @Override
- public JSONObject toJSON() throws JSONException {
- JSONObject json = new JSONObject();
- json.put(TYPE_KEY, getType());
- json.put(X_KEY, position.x);
- json.put(Y_KEY, position.y);
- json.put(DIMENS_X_KEY, dimens.x);
- json.put(DIMENS_Y_KEY, dimens.y);
- return json;
- }
-
- private void updateExtents() {
- upMidpoint.set(position).add(dimens.x / 2, 0);
- downMidpoint.set(position).add(dimens.x / 2, dimens.y);
- leftMidpoint.set(position).add(0, dimens.y / 2);
- rightMidpoint.set(position).add(dimens.x, dimens.y / 2);
- }
-
- public static ColoredRectangleActor fromJSON(JSONObject json) throws JSONException {
- String type = json.getString(Actor.TYPE_KEY);
- Vector2D position = Vector2D.get(
- (float) json.optDouble(X_KEY, 0), (float) json.optDouble(Y_KEY, 0));
- Vector2D dimens = Vector2D.get(
- (float) json.optDouble(DIMENS_X_KEY, 0), (float) json.optDouble(DIMENS_Y_KEY, 0));
- return new ColoredRectangleActor(position, dimens, type);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/DistanceMarkerActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/DistanceMarkerActor.java
deleted file mode 100644
index e0c8f0049..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/DistanceMarkerActor.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-
-/**
- * A colored rectangle actor which marks distance in the swimming game.
- */
-public class DistanceMarkerActor extends ColoredRectangleActor {
- private static final int HEIGHT = 200;
-
- public DistanceMarkerActor(int positionInMeters, String type) {
- this(positionInMeters, type, HEIGHT);
- }
-
- public DistanceMarkerActor(int positionInMeters, String type, float height) {
- super(Vector2D.get(0, SwimmingModel.getWorldYFromMeters(positionInMeters)),
- Vector2D.get(SwimmingModel.LEVEL_WIDTH, height), type);
- zIndex = -2;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/DiveView.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/DiveView.java
deleted file mode 100644
index 1733183c5..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/DiveView.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.ImageView;
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.Interpolator;
-import com.google.android.apps.santatracker.doodles.shared.PauseView;
-import com.google.android.apps.santatracker.doodles.shared.Process;
-import com.google.android.apps.santatracker.doodles.shared.ProcessChain;
-import com.google.android.apps.santatracker.doodles.shared.Tween;
-import com.google.android.apps.santatracker.doodles.shared.UIUtil;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * The dive cooldown UI for the swimming game.
- */
-public class DiveView extends ImageView {
- private static final int CLOCK_DURATION_MS =
- SwimmerActor.DIVE_DURATION_MS + SwimmerActor.DIVE_COOLDOWN_MS;
- private static final float BUMP_SCALE = 1.4f;
- private static final long BUMP_DURATION_MS = 200;
-
- private RectF viewBounds;
- private Paint paint;
- private Paint imagePaint;
-
- private float clockAngle;
- private Bitmap diveArrowBitmap;
- private Rect diveArrowBounds;
- private List processChains;
-
- public DiveView(Context context) {
- this(context, null);
- }
-
- public DiveView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public DiveView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- paint.setColor(0xffffffff);
- imagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- imagePaint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP));
- viewBounds = new RectF(0, 0, getWidth(), getHeight());
- diveArrowBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.swimming_dive_arrow);
- diveArrowBounds = new Rect(0, 0, diveArrowBitmap.getWidth(), diveArrowBitmap.getHeight());
- processChains = new ArrayList<>();
-
- setLayerType(LAYER_TYPE_HARDWARE, null);
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- float startAngle = -90;
- float sweepAngle = clockAngle;
-
- canvas.drawArc(viewBounds, startAngle, sweepAngle, true, paint);
- canvas.drawBitmap(diveArrowBitmap, diveArrowBounds, viewBounds, imagePaint);
- }
-
- @Override
- public void onSizeChanged(int w, int h, int oldw, int oldh) {
- viewBounds = new RectF(0, 0, getWidth(), getHeight());
- }
-
- public void update(float deltaMs) {
- ProcessChain.updateChains(processChains, deltaMs);
- }
-
- public void startCooldown() {
- setImageColorSaturation(0);
- show();
- Process cooldown = new Tween(CLOCK_DURATION_MS / 1000.0f) {
- @Override
- protected void updateValues(float percentDone) {
- clockAngle = Interpolator.LINEAR.getValue(percentDone, 0, 360);
- postInvalidate();
- }
-
- @Override
- protected void onFinish() {
- setImageColorSaturation(1);
- bump();
- }
- }.asProcess();
- processChains.add(new ProcessChain(cooldown));
- }
-
- public void hide() {
- UIUtil.fadeOutAndHide(this, PauseView.FADE_DURATION_MS);
- }
-
- public void show() {
- clockAngle = 0;
- UIUtil.showAndFadeIn(this, PauseView.FADE_DURATION_MS);
- }
-
- private void bump() {
- ValueAnimator bumpAnimation = UIUtil.animator(BUMP_DURATION_MS,
- new DecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- final float scale = (float) valueAnimator.getAnimatedValue("scale");
- post(new Runnable() {
- @Override
- public void run() {
- setScaleX(scale);
- setScaleY(scale);
- }
- });
- }
- },
- UIUtil.floatValue("scale", BUMP_SCALE, 1)
- );
- bumpAnimation.start();
- }
-
- private void setImageColorSaturation(float saturation) {
- ColorMatrix cm = new ColorMatrix();
- cm.setSaturation(saturation);
- imagePaint.setColorFilter(new ColorMatrixColorFilter(cm));
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/HandGrabActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/HandGrabActor.java
deleted file mode 100644
index 3d0effe28..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/HandGrabActor.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite.AnimatedSpriteListener;
-import com.google.android.apps.santatracker.doodles.shared.EventBus;
-import com.google.android.apps.santatracker.doodles.shared.MultiSpriteActor;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * The hand grab obstacle in the swimming game.
- */
-public class HandGrabActor extends BoundingBoxSpriteActor {
- private static final String X_SPRITE = "x";
- private static final String LEMON_GRAB_SPRITE = "lemon grab";
- private static final String LEMON_GRAB_SPRITE_FLIPPED = "lemon grab flipped";
-
- private static final float COLLISION_DISTANCE_THRESHOLD = 100;
- private static final Vector2D[] VERTEX_OFFSETS = {
- Vector2D.get(0, 0),
- Vector2D.get(110f, 0),
- Vector2D.get(110f, 146.0f),
- Vector2D.get(0, 146.0f)
- };
-
- private static final Map OFFSET_MAP;
- static {
- OFFSET_MAP = new HashMap<>();
- OFFSET_MAP.put(X_SPRITE, Vector2D.get());
- OFFSET_MAP.put(LEMON_GRAB_SPRITE, Vector2D.get(-100, -100));
- OFFSET_MAP.put(LEMON_GRAB_SPRITE_FLIPPED, Vector2D.get(100, -100));
- }
-
- public HandGrabActor(Polygon collisionBody, MultiSpriteActor spriteActor) {
- super(collisionBody, spriteActor,
- Vector2D.get(OFFSET_MAP.get(X_SPRITE)).scale(SwimmerActor.SWIMMER_SCALE), HAND_GRAB);
- zIndex = -1;
- scale = SwimmerActor.SWIMMER_SCALE;
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (hidden) {
- return;
- }
- super.draw(canvas);
- }
-
- public void setSprite(String key) {
- if (key.equals(X_SPRITE)) {
- zIndex = -1;
- } else if (key.equals(LEMON_GRAB_SPRITE)) {
- zIndex = 3;
- }
- ((MultiSpriteActor) spriteActor).setSprite(key);
-
- if (isFlippedX() && key.equals(LEMON_GRAB_SPRITE)) {
- spriteOffset.set(OFFSET_MAP.get(LEMON_GRAB_SPRITE_FLIPPED)).scale(scale);
- } else {
- spriteOffset.set(OFFSET_MAP.get(key)).scale(scale);
- }
- }
-
- public boolean isFlippedX() {
- return ((MultiSpriteActor) spriteActor).sprites.get(LEMON_GRAB_SPRITE).isFlippedX();
- }
-
- @Override
- protected boolean resolveCollisionInternal(final SwimmerActor swimmer) {
- if (swimmer.isInvincible || swimmer.isUnderwater) {
- return false;
- }
-
- // Only collide if the two actor's minimum collision coordinates are close together. This is
- // so that the swimmer will only collide with the hand grab if it is right over the x.
- if (swimmer.collisionBody.min.distanceTo(collisionBody.min) < COLLISION_DISTANCE_THRESHOLD) {
- swimmer.collide(type);
-
- setSprite(LEMON_GRAB_SPRITE);
- spriteActor.sprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onFrame(int index) {
- if (index == 10) {
- swimmer.hidden = true;
- swimmer.spriteActor.sprite.setPaused(true);
- EventBus.getInstance().sendEvent(EventBus.VIBRATE);
- }
-
- }
-
- @Override
- public void onFinished() {
- hidden = true;
- swimmer.isDead = true;
- }
- });
- }
- return false;
- }
-
- public static HandGrabActor create(Vector2D position, Resources res) {
- Map spriteMap = new HashMap<>();
- boolean shouldFlip = position.x + VERTEX_OFFSETS[1].x / 2 < SwimmingModel.LEVEL_WIDTH / 2;
-
- AnimatedSprite lemonGrabSprite = AnimatedSprite.fromFrames(res, Sprites.penguin_swim_canegrab);
- lemonGrabSprite.setLoop(false);
- lemonGrabSprite.setFlippedX(shouldFlip);
-
- spriteMap.put(LEMON_GRAB_SPRITE, lemonGrabSprite);
- spriteMap.put(X_SPRITE, AnimatedSprite.fromFrames(res, Sprites.penguin_swim_candy));
-
- MultiSpriteActor spriteActor =
- new MultiSpriteActor(spriteMap, X_SPRITE, position, Vector2D.get(0, 0));
-
- Polygon boundingBox = getBoundingBox(position, VERTEX_OFFSETS, SwimmerActor.SWIMMER_SCALE);
-
- return new HandGrabActor(boundingBox, spriteActor);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/LevelManager.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/LevelManager.java
deleted file mode 100644
index 0d03fd8ba..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/LevelManager.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.content.Context;
-import android.support.annotation.VisibleForTesting;
-import android.util.Log;
-
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.ExternalStoragePermissions;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.util.List;
-
-/**
- * A helper class which handles the saving and loading of levels for the doodle games.
- *
- * The {@code LevelManager} stores levels in a simple JSON format. The {@code Actor} classes wishing
- * to be managed by a {@code LevelManager} should handle their own serialization and
- * deserialization.
- */
-public abstract class LevelManager {
- public static final String TAG = LevelManager.class.getSimpleName();
- private static final String ACTORS_KEY = "actors";
-
- protected final Context context;
- private final ExternalStoragePermissions storagePermissions;
-
- public LevelManager(Context context) {
- this(context, new ExternalStoragePermissions());
- }
-
- @VisibleForTesting
- LevelManager(Context context, ExternalStoragePermissions storagePermissions) {
- this.context = context;
- this.storagePermissions = storagePermissions;
- }
-
- /**
- * Saves a level to persistent storage.
- *
- * @param level The level to save.
- * @param filename The name of the level's file on disk. This file will be stored inside of a
- * preset directory, defined by the LevelManager implementation.
- */
- public void saveLevel(T level, String filename) {
- File levelsDir = getLevelsDir();
- if (!levelsDir.exists() && !levelsDir.mkdirs()) {
- // If we are unable to find or make the desired output directory, log a warning and fail.
- Log.w(TAG, "Unable to reach dir: " + levelsDir.getAbsolutePath());
- return;
- }
- try {
- FileOutputStream outputStream = new FileOutputStream(new File(levelsDir, filename));
- saveLevel(level, outputStream);
- } catch (FileNotFoundException e) {
- Log.w(TAG, "Unable to save file: " + filename);
- }
- }
-
- /**
- * Writes a level to the provided OutputStream.
- *
- * @param level The level to save.
- * @param outputStream The stream to which the level should be written.
- */
- @VisibleForTesting
- void saveLevel(T level, OutputStream outputStream) {
- try {
- saveActors(level.getActors(), outputStream);
- } catch (JSONException e) {
- Log.w(TAG, "Unable to create level JSON.");
- } catch (IOException e) {
- Log.w(TAG, "Unable to write actors to output stream.");
- }
- }
-
- /**
- * Loads a level from persistent storage.
- *
- * This loads first tries to load a level from assets, falling back to external storage if
- * necessary. If it still cannot load the level, the default level will be loaded.
- *
- * @param filename The name of the file to load. This file should be stored in a preset directory,
- * specified by the LevelManager implementation.
- * @return The loaded level.
- */
- public T loadLevel(String filename) {
- if (filename == null) {
- Log.w(TAG, "Couldn't load level with null filename, using default level instead.");
- return loadDefaultLevel();
- }
-
- BufferedReader externalStorageReader = null;
- try {
- externalStorageReader =
- new BufferedReader(
- new InputStreamReader(
- new FileInputStream(new File(getLevelsDir(), filename)), "UTF-8"));
- } catch (IOException e) {
- Log.d(TAG, "Unable to load file from external storage: " + filename);
- }
-
- BufferedReader assetsReader = null;
- try {
- assetsReader =
- new BufferedReader(new InputStreamReader(context.getAssets().open(filename), "UTF-8"));
- } catch (IOException e) {
- Log.d(TAG, "Unable to load file from assets: " + filename);
- }
-
- T model = loadLevel(externalStorageReader, assetsReader);
- model.setLevelName(filename);
- return model;
- }
-
- /**
- * Loads a level from either assets, or from external storage.
- *
- * This first tries to load a level from assets, falling back to external storage if
- * necessary. If it still cannot load the level, the default level will be loaded.
- *
- * This method should only be used for testing LevelManager. Real use cases should generally
- * use {@code loadLevel(String filename)}.
- *
- * @param externalStorageReader
- * @param assetsInputReader
- * @return The loaded level.
- */
- @VisibleForTesting
- T loadLevel(BufferedReader externalStorageReader, BufferedReader assetsInputReader) {
- JSONObject json = null;
- if (assetsInputReader != null) {
- json = readLevelJson(assetsInputReader);
- Log.d(TAG, "Loaded level from assets.");
- }
- if (json == null
- && externalStorageReader != null
- && storagePermissions.isExternalStorageReadable()) {
- json = readLevelJson(externalStorageReader);
- Log.d(TAG, "Loaded level from external storage.");
- }
- if (json == null) {
- Log.w(TAG, "Couldn't load level data, using default level instead.");
- return loadDefaultLevel();
- }
-
- T model = getEmptyModel();
- try {
- JSONArray actors = json.getJSONArray(ACTORS_KEY);
- for (int i = 0; i < actors.length(); i++) {
- Actor actor = loadActorFromJSON(actors.getJSONObject(i));
- if (actor != null) {
- model.addActor(actor);
- }
- }
- } catch (JSONException e) {
- Log.w(TAG, "Couldn't load actors, using default level instead.");
- return loadDefaultLevel();
- }
- return model;
- }
-
- /**
- * Initializes the default level and returns it.
- *
- * In general, this should be an empty or minimal level.
- *
- * @return the initialized default level.
- */
- public abstract T loadDefaultLevel();
-
- /**
- * Returns the external storage directory within which levels of this type should be saved.
- *
- * @return The base directory which should be used to save levels.
- */
- protected abstract File getLevelsDir();
-
- /**
- * Loads a single actor from JSON.
- *
- * @param json The JSON representation of the actor to be loaded
- * @return The loaded actor, or null if the actor could not be loaded.
- * @throws JSONException if the JSON is malformed, or fails to be parsed.
- */
- @VisibleForTesting
- abstract Actor loadActorFromJSON(JSONObject json) throws JSONException;
-
- /**
- * Returns an empty, or minimal model of the appropriate type. Generally, this should be the same
- * as asking for a {@code new T()}.
- *
- * @return The empty model.
- */
- protected abstract T getEmptyModel();
-
- /**
- * Returns a JSONArray containing the JSON representation of the passed-in list of actors.
- *
- * If a given actor does not provide a JSON representation, it will not appear in the returned
- * JSONArray.
- *
- * @param actors The actors to convert into JSON.
- * @return The JSON representation of the list of actors.
- * @throws JSONException If the parsing of an actor into JSON fails.
- */
- @VisibleForTesting
- JSONArray getActorsJson(List actors) throws JSONException {
- JSONArray actorsJson = new JSONArray();
- for (Actor actor : actors) {
- JSONObject json = actor.toJSON();
- if (json != null) {
- actorsJson.put(json);
- }
- }
- return actorsJson;
- }
-
- /**
- * Writes a list of actors to an OutputStream.
- *
- * @param actors The actors to be written.
- * @param outputStream The OuputStream which should be used to write the list of actors.
- * @throws JSONException If the conversion of actors into JSON fails.
- * @throws IOException If we fail to write to the OutputStream.
- */
- private void saveActors(List actors, OutputStream outputStream)
- throws JSONException, IOException {
- JSONObject json = new JSONObject();
- json.put(ACTORS_KEY, getActorsJson(actors));
- Log.d(TAG, json.toString(2));
-
- if (storagePermissions.isExternalStorageWritable()) {
- writeLevelJson(json, outputStream);
- } else {
- Log.w(TAG, "External storage is not writable");
- }
- }
-
- /**
- * Writes a JSONObject to an OutputStream.
- *
- * @param json The object to be written.
- * @param outputStream The stream to be written to.
- * @throws IOException If we fail to write to the OutputStream.
- */
- private void writeLevelJson(JSONObject json, OutputStream outputStream) throws IOException {
- outputStream.write(json.toString().getBytes());
- outputStream.close();
- }
-
- /**
- * Read a JSONObject from a BufferedReader.
- *
- * @param reader The BufferedReader which contains the JSON to be read.
- * @return A JSONObject parsed from the contents of the BufferedReader.
- */
- private JSONObject readLevelJson(BufferedReader reader) {
- try {
- String levelData = "";
- String line = reader.readLine();
- while (line != null) {
- levelData += line;
- line = reader.readLine();
- }
- reader.close();
- return new JSONObject(levelData);
- } catch (IOException e) {
- Log.w(TAG, "readLevelJson: Couldn't read JSON.");
- } catch (JSONException e) {
- Log.w(TAG, "readLevelJson: Couldn't create JSON.");
- }
- return null;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ObstacleManager.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ObstacleManager.java
deleted file mode 100644
index cfa092d3d..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ObstacleManager.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.content.Context;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * A class for managing obstacles in the swimming game.
- */
-public class ObstacleManager extends Actor {
- private static final String TAG = ObstacleManager.class.getSimpleName();
- private static final int NUM_INITIAL_CHUNKS = 4;
- private static final int RETAIN_THRESHOLD = 2000;
-
- private LinkedList levelChunks;
- private SwimmerActor swimmer;
-
- public ObstacleManager(SwimmerActor swimmer, Context context) {
- levelChunks = new LinkedList<>();
- SwimmingLevelChunk.generateAllLevelChunks(-1000, context);
- for (int i = 1; i < NUM_INITIAL_CHUNKS; i++) {
- levelChunks.add(SwimmingLevelChunk.getNextChunk());
- }
- this.swimmer = swimmer;
- zIndex = 1;
- }
-
- @Override
- public void update(float deltaMs) {
- for (int i = 0; i < levelChunks.size(); i++) {
- levelChunks.get(i).update(deltaMs);
- }
-
- if (!levelChunks.isEmpty()) {
- SwimmingLevelChunk lastChunk = levelChunks.getLast();
- // If the swimmer is within 2000 units of the end of the last chunk, add a new one.
- if (swimmer.position.y - lastChunk.endY < RETAIN_THRESHOLD) {
- SwimmingLevelChunk nextChunk = SwimmingLevelChunk.getNextChunk();
- if (nextChunk != null) {
- levelChunks.add(nextChunk);
- }
- }
-
- if (levelChunks.getFirst().endY - swimmer.position.y > RETAIN_THRESHOLD) {
- levelChunks.remove(0);
- }
- }
- }
-
- public void resolveCollisions(SwimmerActor swimmer, float deltaMs) {
- for (int i = 0; i < levelChunks.size(); i++) {
- levelChunks.get(i).resolveCollisions(swimmer, deltaMs);
- }
- }
-
- public List getActors() {
- List actors = new ArrayList<>();
- for (int i = 0; i < levelChunks.size(); i++) {
- actors.addAll(levelChunks.get(i).obstacles);
- }
- return actors;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ScreenBoundaryActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ScreenBoundaryActor.java
deleted file mode 100644
index bd279bb65..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ScreenBoundaryActor.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.graphics.Color;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * An actor which defines a screen boundary. Used for camera positioning.
- */
-public class ScreenBoundaryActor extends CollisionActor {
- public static final String TYPE = "Screen boundary";
-
- private static int currentZIndex = 0;
-
- public ScreenBoundaryActor(Polygon collisionBody) {
- super(collisionBody);
- // orange, green, semi-transparent green.
- collisionBody.setPaintColors(0xffffa500, Color.BLACK, 0x6400ff00);
-
- // Give ScreenBoundaryActors increasing z-indices, as these determine sort order, and
- // the behavior of ScreenBoundaryActors is based on sort order.
- zIndex = currentZIndex++;
- }
-
- @Override
- public boolean resolveCollision(Actor other, float deltaMs) {
- return false;
- }
-
- @Override
- public String getType() {
- return ScreenBoundaryActor.TYPE;
- }
-
- public static ScreenBoundaryActor fromJSON(JSONObject json) throws JSONException {
- return new ScreenBoundaryActor(Polygon.fromJSON(json.getJSONArray(POLYGON_KEY)));
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/StickyActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/StickyActor.java
deleted file mode 100644
index 48a3ae220..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/StickyActor.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.graphics.Color;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * An actor which slows down the colliding actor upon entry.
- */
-public class StickyActor extends CollisionActor {
- public static final String TYPE = "Sticky";
- // A "stickiness" factor, which is essentially friction, but doesn't take into account the tilt
- // of the device.
- private float stickiness = 2.0f;
- public boolean isEnabled = true;
-
- public StickyActor(Polygon collisionBody) {
- super(collisionBody);
- // cyan, yellow, semi-transparent green.
- collisionBody.setPaintColors(Color.CYAN, 0xffffff00, 0x6400ff00);
- }
-
- /**
- * Cause the other actor to be slowed down by this actor.
- */
- @Override
- public boolean resolveCollision(Actor other, float deltaMs) {
- if (!isEnabled || other.velocity.getLength() == 0 || !collisionBody.contains(other.position)) {
- // Don't bother resolving the collision if the other actor is either not moving, or if the
- // other actor is not colliding with this object.
- return false;
- }
- float deltaSeconds = deltaMs / 1000.0f;
- Vector2D acceleration = Vector2D.get(other.velocity).normalize();
- if (other.velocity.getLength() < 400) {
- acceleration.scale(0.01f * stickiness * Constants.GRAVITY);
- } else {
- acceleration.scale(stickiness * Constants.GRAVITY);
- }
- if (Math.signum(other.velocity.x - acceleration.x * deltaSeconds)
- != Math.signum(other.velocity.x)) {
- // Don't allow stickiness to accelerate the ball in the other direction.
- other.velocity.x = 0;
- other.velocity.y = 0;
- } else {
- other.velocity.x -= acceleration.x * deltaSeconds;
- other.velocity.y -= acceleration.y * deltaSeconds;
- }
- return false;
- }
-
- @Override
- public String getType() {
- return StickyActor.TYPE;
- }
-
- public static StickyActor fromJSON(JSONObject json) throws JSONException {
- return new StickyActor(Polygon.fromJSON(json.getJSONArray(POLYGON_KEY)));
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmerActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmerActor.java
deleted file mode 100644
index 73b797b12..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmerActor.java
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite.AnimatedSpriteListener;
-import com.google.android.apps.santatracker.doodles.shared.CallbackProcess;
-import com.google.android.apps.santatracker.doodles.shared.EventBus;
-import com.google.android.apps.santatracker.doodles.shared.GameFragment;
-import com.google.android.apps.santatracker.doodles.shared.MultiSpriteActor;
-import com.google.android.apps.santatracker.doodles.shared.ProcessChain;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.WaitProcess;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-import com.google.android.apps.santatracker.doodles.shared.physics.Util;
-
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * The player-controlled swimmer in the swimming game.
- */
-public class SwimmerActor extends BoundingBoxSpriteActor {
- private static final String TAG = SwimmerActor.class.getSimpleName();
-
- private static final float ACCELERATION_Y = -500;
- private static final float MIN_SPEED = 400;
- private static final float DEFAULT_MAX_SPEED = 800;
- private static final long SPEED_STEP_DURATION_MS = 10000;
- private static final float TILT_VELOCITY = 10000;
-
- public static final float SWIMMER_SCALE = 1.6f;
- public static final int DIVE_DURATION_MS = 1500;
- public static final int DIVE_COOLDOWN_MS = 5000;
- public static final String SWIMMER_ACTOR_TYPE = "swimmer";
- public static final String KICKOFF_IDLE_SPRITE = "kickoff_idle";
- public static final String KICKOFF_START_SPRITE = "kickoff_start";
- public static final String RINGS_SPRITE = "rings";
- public static final String SWIM_LOOP_SPRITE = "swimming";
- public static final String CAN_COLLIDE_SPRITE = "can_collide";
- public static final String FREEZE_SPRITE = "freeze";
- public static final String DIVE_DOWN_SPRITE = "dive";
- public static final String UNDER_LOOP_SPRITE = "under_loop";
- public static final String RISE_UP_SPRITE = "rise_up";
- public static final float KICKOFF_IDLE_Y_OFFSET = -240;
-
- private static final Vector2D[] VERTEX_OFFSETS = {
- Vector2D.get(0, 0), Vector2D.get(96, 0),
- Vector2D.get(96, 90), Vector2D.get(0, 90)
- };
-
- private static final Map OFFSET_MAP;
-
- static {
- OFFSET_MAP = new HashMap<>();
- OFFSET_MAP.put(KICKOFF_IDLE_SPRITE, Vector2D.get(0, 0));
- OFFSET_MAP.put(KICKOFF_START_SPRITE, Vector2D.get(0, 0));
- OFFSET_MAP.put(RINGS_SPRITE, Vector2D.get(-60, -20)); // TODO
- OFFSET_MAP.put(SWIM_LOOP_SPRITE, Vector2D.get(0, 0));
- OFFSET_MAP.put(CAN_COLLIDE_SPRITE, Vector2D.get(0, 0));
- OFFSET_MAP.put(FREEZE_SPRITE, Vector2D.get(0, 0));
- OFFSET_MAP.put(DIVE_DOWN_SPRITE, Vector2D.get(0, 0));
- OFFSET_MAP.put(UNDER_LOOP_SPRITE, Vector2D.get(0, 0));
- OFFSET_MAP.put(RISE_UP_SPRITE, Vector2D.get(0, 0));
- }
-
- public boolean controlsEnabled = true;
- public boolean isInvincible = false;
- public boolean isUnderwater = false;
- public boolean isDead = false;
-
- private MultiSpriteActor multiSpriteActor;
- private AnimatedSprite canCollideSprite;
- private AnimatedSprite freezeSprite;
- private String collidedObjectType;
-
- private AnimatedSprite ringsSprite;
- private Vector2D ringsSpriteOffset;
-
- private float restartSpeed = MIN_SPEED;
- private float maxSpeed = DEFAULT_MAX_SPEED;
- private long currentSpeedStepTime = 0;
-
- private boolean diveEnabled = false;
- private float targetX;
-
- private List processChains = new ArrayList<>();
-
- public SwimmerActor(Polygon collisionBody, MultiSpriteActor spriteActor) {
- super(collisionBody, spriteActor,
- Vector2D.get(OFFSET_MAP.get(KICKOFF_IDLE_SPRITE)).scale(SWIMMER_SCALE), SWIMMER_ACTOR_TYPE);
-
- multiSpriteActor = spriteActor;
- canCollideSprite = multiSpriteActor.sprites.get(CAN_COLLIDE_SPRITE);
- canCollideSprite.setLoop(false);
- freezeSprite = multiSpriteActor.sprites.get(FREEZE_SPRITE);
- freezeSprite.setLoop(false);
- multiSpriteActor.sprites.get(DIVE_DOWN_SPRITE).addListener(new AnimatedSpriteListener() {
- @Override
- public void onLoop() {
- zIndex = -3;
- setSprite(UNDER_LOOP_SPRITE);
- }
- });
- multiSpriteActor.sprites.get(RISE_UP_SPRITE).addListener(new AnimatedSpriteListener() {
- @Override
- public void onLoop() {
- zIndex = 0;
- setSprite(SWIM_LOOP_SPRITE);
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_dive_up);
- }
- });
- multiSpriteActor.sprites.get(SWIM_LOOP_SPRITE).addListener(new AnimatedSpriteListener() {
- @Override
- public void onFrame(int index) {
- if (index == 5 || index == 13) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_ice_splash_a);
- }
- }
- });
-
- ringsSprite = multiSpriteActor.sprites.get(RINGS_SPRITE);
- ringsSprite.setPaused(true);
- ringsSprite.setHidden(true);
- ringsSprite.setScale(SWIMMER_SCALE, SWIMMER_SCALE);
- ringsSprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onLoop() {
- ringsSprite.setHidden(true);
- ringsSprite.setPaused(true);
- }
- });
- ringsSpriteOffset = Vector2D.get(OFFSET_MAP.get(RINGS_SPRITE)).scale(SWIMMER_SCALE);
-
- multiSpriteActor.sprites.get(KICKOFF_START_SPRITE).addListener(new AnimatedSpriteListener() {
- @Override
- public void onLoop() {
- diveEnabled = true;
- diveDown();
- }
-
- @Override
- public void onFrame(int index) {
- if (index == 3) {
- velocity.set(0, -restartSpeed);
- }
- }
- });
-
- zIndex = 0;
- alpha = 1.0f;
- scale = SWIMMER_SCALE;
- targetX = position.x;
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
-
- ProcessChain.updateChains(processChains, deltaMs);
-
- // Update x position based on tilt.
- float frameVelocityX = TILT_VELOCITY * deltaMs / 1000;
- float positionDeltaX = targetX - position.x;
- if (Math.abs(positionDeltaX) < frameVelocityX) {
- // We will overshoot if we apply the frame velocity. Just go straight to the target position.
- moveTo(targetX, position.y);
- } else {
- moveTo(position.x + Math.signum(positionDeltaX) * frameVelocityX, position.y);
- }
-
- // Update acceleration and frame rate if necessary.
- if (velocity.getLength() > 1) {
- velocity.y = Math.max(-maxSpeed, velocity.y + ACCELERATION_Y * deltaMs / 1000);
- if (velocity.y == -maxSpeed) {
- multiSpriteActor.sprites.get(SWIM_LOOP_SPRITE).setFPS(24);
- } else {
- multiSpriteActor.sprites.get(SWIM_LOOP_SPRITE).setFPS(48);
- }
- currentSpeedStepTime += deltaMs;
- if (currentSpeedStepTime > SPEED_STEP_DURATION_MS) {
- maxSpeed += 500;
- currentSpeedStepTime = 0;
- }
- }
-
- ringsSprite.update(deltaMs);
- ringsSprite.setPosition(spriteActor.position.x + ringsSpriteOffset.x,
- spriteActor.position.y + ringsSpriteOffset.y);
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (hidden) {
- return;
- }
- spriteActor.draw(canvas, spriteOffset.x, spriteOffset.y,
- spriteActor.sprite.frameWidth * scale, spriteActor.sprite.frameHeight * scale);
- ringsSprite.draw(canvas);
- collisionBody.draw(canvas);
- }
-
- @Override
- public JSONObject toJSON() {
- return null;
- }
-
- public void setSprite(String key) {
- multiSpriteActor.setSprite(key);
- spriteOffset.set(OFFSET_MAP.get(key)).scale(SWIMMER_SCALE);
- }
-
- public void moveTo(float x, float y) {
- collisionBody.moveTo(x, y);
- position.set(x, y);
- spriteActor.position.set(x, y);
- targetX = x;
- }
-
- public void updateTargetPositionFromTilt(Vector2D tilt, float levelWidth) {
- if (controlsEnabled) {
- // Decrease the amount of tilt necessary to move the swimmer.
- float tiltPercentage = (float) (tilt.x / (Math.PI / 2));
- tiltPercentage *= 2.5f;
-
- int levelPadding = 60;
- targetX = Util.clamp(
- (levelWidth / 2) - (collisionBody.getWidth() / 2) + (tiltPercentage * levelWidth / 2),
- 0 - collisionBody.getWidth() + levelPadding,
- levelWidth - (2 * collisionBody.getWidth()) - levelPadding);
- }
- }
-
- public void startSwimming() {
- setSprite(KICKOFF_START_SPRITE);
- }
-
- public void collide(String objectType) {
- restartSpeed = Math.max(MIN_SPEED, Math.abs(velocity.y / 4));
- maxSpeed = restartSpeed;
- currentSpeedStepTime = 0;
- moveTo(positionBeforeFrame.x, positionBeforeFrame.y);
- velocity.set(0, 0);
- controlsEnabled = false;
-
- if (objectType.equals(DUCK) || objectType.equals(ICE_CUBE)) {
- EventBus.getInstance().sendEvent(EventBus.SHAKE_SCREEN);
- EventBus.getInstance().sendEvent(EventBus.VIBRATE);
-
- if (objectType.equals(DUCK)) {
- setSprite(SwimmerActor.CAN_COLLIDE_SPRITE);
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_duck_collide);
- } else { // Ice cube.
- setSprite(SwimmerActor.FREEZE_SPRITE);
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_ice_collide);
- }
- } else { // Octopus.
- // Just play the sound for the octopus. It vibrates later (when it actually grabs the lemon).
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_grab);
- }
- collidedObjectType = objectType;
- isDead = true;
- }
-
- public void endGameWithoutCollision() {
- controlsEnabled = false;
- collidedObjectType = HAND_GRAB;
- isDead = true;
- }
-
- public String getCollidedObjectType() {
- return collidedObjectType;
- }
-
- public void diveDown() {
- if (controlsEnabled && diveEnabled) {
- EventBus.getInstance().sendEvent(EventBus.SWIMMING_DIVE);
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_dive_down);
- ringsSprite.setHidden(false);
- ringsSprite.setPaused(false);
- isUnderwater = true;
- setSprite(DIVE_DOWN_SPRITE);
- diveEnabled = false;
-
- ProcessChain waitThenRiseUp = new WaitProcess(DIVE_DURATION_MS).then(new CallbackProcess() {
- @Override
- public void updateLogic(float deltaMs) {
- isUnderwater = false;
- setSprite(RISE_UP_SPRITE);
- }
- }).then(new WaitProcess(DIVE_COOLDOWN_MS)).then(new CallbackProcess() {
- @Override
- public void updateLogic(float deltaMs) {
- diveEnabled = true;
- }
- });
- processChains.add(waitThenRiseUp);
- }
- }
-
- public static final SwimmerActor create(Vector2D position, Resources res,
- final GameFragment gameFragment) {
- if (gameFragment.isDestroyed) {
- return null;
- }
- Map spriteMap = new HashMap<>();
- spriteMap.put(KICKOFF_IDLE_SPRITE,
- AnimatedSprite.fromFrames(res, Sprites.penguin_swim_idle));
- if (gameFragment.isDestroyed) {
- return null;
- }
- spriteMap.put(KICKOFF_START_SPRITE,
- AnimatedSprite.fromFrames(res, Sprites.penguin_swim_start));
- if (gameFragment.isDestroyed) {
- return null;
- }
- spriteMap.put(RINGS_SPRITE,
- AnimatedSprite.fromFrames(res, Sprites.swimming_rings));
- if (gameFragment.isDestroyed) {
- return null;
- }
- spriteMap.put(SWIM_LOOP_SPRITE,
- AnimatedSprite.fromFrames(res, Sprites.penguin_swim_swimming));
- if (gameFragment.isDestroyed) {
- return null;
- }
- spriteMap.put(CAN_COLLIDE_SPRITE,
- AnimatedSprite.fromFrames(res, Sprites.penguin_swim_dazed));
- if (gameFragment.isDestroyed) {
- return null;
- }
- spriteMap.put(FREEZE_SPRITE,
- AnimatedSprite.fromFrames(res, Sprites.penguin_swim_frozen));
- if (gameFragment.isDestroyed) {
- return null;
- }
- spriteMap.put(DIVE_DOWN_SPRITE,
- AnimatedSprite.fromFrames(res, Sprites.penguin_swim_descending));
- if (gameFragment.isDestroyed) {
- return null;
- }
- spriteMap.put(UNDER_LOOP_SPRITE,
- AnimatedSprite.fromFrames(res, Sprites.penguin_swim_swimmingunderwater));
- if (gameFragment.isDestroyed) {
- return null;
- }
- spriteMap.put(RISE_UP_SPRITE,
- AnimatedSprite.fromFrames(res, Sprites.penguin_swim_ascending));
- if (gameFragment.isDestroyed) {
- return null;
- }
- MultiSpriteActor spriteActor =
- new MultiSpriteActor(spriteMap, KICKOFF_IDLE_SPRITE, position, Vector2D.get(0, 0));
- if (gameFragment.isDestroyed) {
- return null;
- }
-
- return new SwimmerActor(getBoundingBox(position, VERTEX_OFFSETS, SWIMMER_SCALE), spriteActor);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingFragment.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingFragment.java
deleted file mode 100644
index 89914edc2..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingFragment.java
+++ /dev/null
@@ -1,896 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.graphics.Point;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Build.VERSION;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Vibrator;
-import android.support.annotation.Nullable;
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.FrameLayout.LayoutParams;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.ToggleButton;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.AndroidUtils;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.Camera;
-import com.google.android.apps.santatracker.doodles.shared.CameraShake;
-import com.google.android.apps.santatracker.doodles.shared.DoodleConfig;
-import com.google.android.apps.santatracker.doodles.shared.EventBus;
-import com.google.android.apps.santatracker.doodles.shared.EventBus.EventBusListener;
-import com.google.android.apps.santatracker.doodles.shared.GameFragment;
-import com.google.android.apps.santatracker.doodles.shared.GameType;
-import com.google.android.apps.santatracker.doodles.shared.HistoryManager;
-import com.google.android.apps.santatracker.doodles.shared.HistoryManager.HistoryListener;
-import com.google.android.apps.santatracker.doodles.shared.PineappleLogger;
-import com.google.android.apps.santatracker.doodles.shared.RectangularInstructionActor;
-import com.google.android.apps.santatracker.doodles.shared.SpriteActor;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-import com.google.android.apps.santatracker.doodles.shared.UIUtil;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.sound.SoundManager;
-import com.google.android.apps.santatracker.doodles.tilt.SwimmingModel.SwimmingState;
-import com.google.android.apps.santatracker.util.MeasurementManager;
-import com.google.firebase.analytics.FirebaseAnalytics;
-
-import java.io.File;
-import java.text.NumberFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.SWIMMING_GAME_TYPE;
-
-/**
- * The main fragment for the swimming game.
- */
-public class SwimmingFragment extends GameFragment
- implements SensorEventListener, EventBusListener {
- private static final String TAG = SwimmingFragment.class.getSimpleName();
- public static final String CURRENT_LEVEL_KEY = "CURRENT_LEVEL";
- private static final float ACCELEROMETER_SCALE = 1.5f / 9.8f; // Scale to range -1.5:1.5.
- private static final int END_VIEW_ON_DEATH_DELAY_MS = 1400;
-
- public static boolean editorMode = false;
-
- private SwimmingView swimmingView;
- private DiveView diveView;
- private TextView countdownView;
- private Button saveButton;
- private Button loadButton;
- private Button resetButton;
- private Button deleteButton;
- private ToggleButton collisionModeButton;
- private SwimmingModel model;
-
- private SwimmingLevelManager levelManager;
- private SensorManager sensorManager;
- private Sensor accelerometerSensor;
- private int displayRotation;
-
- private int playCount = 0;
- private long titleDurationMs = GameFragment.TITLE_DURATION_MS;
- private SwimmingModel tempLevel;
- private boolean mIsGameOver = false;
-
- public SwimmingFragment() {
- super();
- }
-
- public SwimmingFragment(Context context, DoodleConfig doodleConfig, PineappleLogger logger,
- boolean editorMode) {
- super(context, doodleConfig, logger);
- SwimmingFragment.editorMode = editorMode;
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- if (context == null) {
- return null;
- }
-
- levelManager = new SwimmingLevelManager(context);
-
- wrapper = new FrameLayout(context);
-
- titleView = getTitleView(R.drawable.penguin_swim_loadingscreen, R.string.swimming);
- wrapper.addView(titleView);
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- loadGame();
- }
- });
- return wrapper;
- }
-
- @Override
- protected void firstPassLoadOnUiThread() {
- final FrameLayout.LayoutParams wrapperLP =
- new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-
- final SwimmingFragment that = this;
- scoreView = getScoreView();
- pauseView = getPauseView();
-
- int diveViewBottomMargin = (int) context.getResources().getDimension(
- R.dimen.dive_margin_bottom);
- int diveViewStartMargin = (int) context.getResources().getDimension(
- R.dimen.dive_margin_left);
- int diveViewSize = (int) context.getResources().getDimension(
- R.dimen.dive_image_size);
-
- FrameLayout.LayoutParams diveViewLP = new LayoutParams(diveViewSize, diveViewSize);
- diveViewLP.setMargins(diveViewStartMargin, 0, 0, diveViewBottomMargin);
- diveViewLP.gravity = Gravity.BOTTOM | Gravity.LEFT;
-
- if (VERSION.SDK_INT >= 17) {
- diveViewLP.setMarginStart(diveViewStartMargin);
- }
- diveView = new DiveView(context);
-
- countdownView = new TextView(context);
- countdownView.setGravity(Gravity.CENTER);
- countdownView.setTextColor(context.getResources().getColor(R.color.ui_text_yellow));
- countdownView.setTypeface(Typeface.DEFAULT_BOLD);
- countdownView.setText("0");
- countdownView.setVisibility(View.INVISIBLE);
- Locale locale = context.getResources().getConfiguration().locale;
- countdownView.setText(NumberFormat.getInstance(locale).format(3));
- Point screenDimens = AndroidUtils.getScreenSize();
- UIUtil.fitToBounds(countdownView, screenDimens.x / 10, screenDimens.y / 10);
-
- LinearLayout gameView = new LinearLayout(context);
- gameView.setOrientation(LinearLayout.VERTICAL);
-
- // Add game view.
- swimmingView = new SwimmingView(context);
- LinearLayout.LayoutParams lp =
- new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT,
- 7);
- gameView.addView(swimmingView, lp);
-
- if (editorMode) {
- LinearLayout buttonWrapper = new LinearLayout(context);
- buttonWrapper.setOrientation(LinearLayout.HORIZONTAL);
- lp =
- new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT,
- 1);
- gameView.addView(buttonWrapper, lp);
-
- resetButton =
- getButton(
- R.string.reset_level,
- new OnClickListener() {
- @Override
- public void onClick(View v) {
- SwimmingModel level = levelManager.loadDefaultLevel();
- initializeLevel(level, false);
-
- getActivity().getSharedPreferences(
- context.getString(R.string.swimming), Context.MODE_PRIVATE)
- .edit()
- .putString(CURRENT_LEVEL_KEY, null)
- .commit();
- }
- });
- deleteButton =
- getButton(
- R.string.delete_levels,
- new OnClickListener() {
- @Override
- public void onClick(View v) {
- DialogFragment dialogFragment = new DeleteLevelDialogFragment();
- dialogFragment.show(getActivity().getFragmentManager(), "delete");
- }
- });
- loadButton =
- getButton(
- R.string.load_level,
- new OnClickListener() {
- @Override
- public void onClick(View v) {
- DialogFragment dialogFragment = new LoadLevelDialogFragment(that);
- dialogFragment.show(getActivity().getFragmentManager(), "load");
- }
- });
- saveButton =
- getButton(
- R.string.save_level,
- new OnClickListener() {
- @Override
- public void onClick(View v) {
- DialogFragment dialogFragment = new SaveLevelDialogFragment(that);
- dialogFragment.show(getActivity().getFragmentManager(), "save");
- }
- });
- collisionModeButton = new ToggleButton(context);
- collisionModeButton.setText(R.string.scenery_mode);
- collisionModeButton.setTextOff(context.getString(R.string.scenery_mode));
- collisionModeButton.setTextOn(context.getString(R.string.collision_mode));
- collisionModeButton.setOnCheckedChangeListener(
- new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- model.collisionMode = isChecked;
- }
- });
-
- lp =
- new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.WRAP_CONTENT,
- LinearLayout.LayoutParams.MATCH_PARENT,
- 1);
- buttonWrapper.addView(deleteButton, lp);
- buttonWrapper.addView(resetButton, lp);
- buttonWrapper.addView(loadButton, lp);
- buttonWrapper.addView(saveButton, lp);
- buttonWrapper.addView(collisionModeButton, lp);
- }
-
- sensorManager = (SensorManager) getActivity().getSystemService(Context.SENSOR_SERVICE);
- accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- if (accelerometerSensor == null) {
- // TODO: The game won't be playable without this, so what should we do?
- Log.d(TAG, "Accelerometer sensor is null");
- }
- displayRotation = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
- .getDefaultDisplay().getRotation();
-
- wrapper.addView(gameView, 0, wrapperLP);
- wrapper.addView(countdownView, 1);
- wrapper.addView(diveView, 2, diveViewLP);
- wrapper.addView(scoreView, 3);
- wrapper.addView(pauseView, 4);
- }
-
- @Override
- protected void secondPassLoadOnBackgroundThread() {
- super.secondPassLoadOnBackgroundThread();
-
- tempLevel = new SwimmingModel();
- final SwimmingModel level = tempLevel;
- historyManager = new HistoryManager(
- context,
- new HistoryListener() {
- @Override
- public void onFinishedLoading() {
- addBestTimeLine(level);
- }
-
- @Override
- public void onFinishedSaving() {}
- });
-
- initializeLevel(tempLevel, true);
- }
-
- @Override
- protected void finalPassLoadOnUiThread() {
- soundManager = SoundManager.getInstance();
- loadSounds();
-
- onFinishedLoading();
- startHandlers();
- tempLevel = null;
- }
-
- @Override
- protected void replay() {
- super.replay();
- SwimmingModel level = new SwimmingModel();
- initializeLevel(level, false);
- }
-
- @Override
- protected void resume() {
- super.resume();
- if (uiRefreshHandler != null) {
- sensorManager.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
- uiRefreshHandler.start(swimmingView);
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (sensorManager != null) {
- sensorManager.unregisterListener(this);
- }
- // Clear the path list because it's static and otherwise it hangs around forever.
- if (SwimmingLevelChunk.pathList != null) {
- SwimmingLevelChunk.pathList.clear();
- }
- }
-
- @Override
- protected void onDestroyHelper() {
- if (swimmingView != null) {
- swimmingView.setModel(null);
- }
- model = null;
- tempLevel = null;
- levelManager = null;
-
- if (SwimmingLevelChunk.swimmingLevelChunks != null) {
- SwimmingLevelChunk.swimmingLevelChunks.clear();
- }
- if (SwimmingLevelChunk.pathList != null) {
- SwimmingLevelChunk.pathList.clear();
- }
- }
-
- @Override
- public void update(float deltaMs) {
- if (isPaused) {
- return;
- }
- model.update(deltaMs);
- diveView.update(deltaMs);
-
- // If we are in editor mode, hide the intro animation right away and skip the camera pan.
- // Otherwise, wait until it has finished playing before fading and then run the camera pan.
- if ((editorMode || model.timeElapsed >= titleDurationMs)
- && model.getState() == SwimmingState.INTRO) {
- if (editorMode) {
- Log.d(TAG, "Hiding intro animation right away.");
-
- // Run on UI thread since background threads can't touch the intro view.
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- titleView.setVisibility(View.GONE);
- }
- });
- model.setState(SwimmingState.WAITING);
- } else {
- Log.d(TAG, "Fading out and hiding intro animation.");
-
- // Run on UI thread since background threads can't touch the intro view.
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- hideTitle();
- }
- });
- model.setState(SwimmingState.WAITING);
- }
- }
-
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (event.values == null || model == null) {
- return;
- }
- int sensorType = event.sensor.getType();
- if (sensorType == Sensor.TYPE_ACCELEROMETER) {
- float[] adjustedEventValues =
- AndroidUtils.getAdjustedAccelerometerValues(displayRotation, event.values);
- float x = -adjustedEventValues[0] * ACCELEROMETER_SCALE;
- float y = -adjustedEventValues[1] * ACCELEROMETER_SCALE;
- // Accelerometer input is very noisy, so we filter it using a (simple) low-pass filter.
- float weight = 0.1f;
- model.tilt.x = model.tilt.x + weight * (x - model.tilt.x);
- model.tilt.y = model.tilt.y + weight * (y - model.tilt.y);
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int i) {
- }
-
- @Override
- public void onEventReceived(int type, final Object data) {
- if (isDestroyed) {
- return;
- }
- switch(type) {
- case EventBus.SCORE_CHANGED:
- int distance = integerValue(data);
- boolean shouldAddStar = false;
-
- if (model.currentScoreThreshold < SwimmingModel.SCORE_THRESHOLDS.length
- && distance >= SwimmingModel.SCORE_THRESHOLDS[model.currentScoreThreshold]) {
- // Pop in star.
- shouldAddStar = true;
- model.currentScoreThreshold++;
- }
- updateScoreUi(distance, shouldAddStar);
- break;
- case EventBus.SWIMMING_DIVE:
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- diveView.startCooldown();
- }
- });
- break;
-
- case EventBus.GAME_STATE_CHANGED:
- SwimmingState state = (SwimmingState) data;
- mIsGameOver = false;
- if (state == SwimmingState.FINISHED) {
- mIsGameOver = true;
- calculateBestDistance();
- calculateStars();
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- pauseView.hidePauseButton();
- }
- });
- final Handler handler = new Handler(Looper.getMainLooper());
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (model == null) {
- return;
- }
- int bestDistance = historyManager.getBestDistance(GameType.SWIMMING).intValue();
- String collidedObjectType = model.swimmer.getCollidedObjectType();
- scoreView.updateBestScore(
- AndroidUtils.getText(
- context.getResources(), R.string.swimming_score, bestDistance));
- scoreView.setShareDrawable(
- getShareImageDrawable(collidedObjectType));
-
- diveView.hide();
- scoreView.animateToEndState();
- }
- }, END_VIEW_ON_DEATH_DELAY_MS);
- }
-
- // [ANALYTICS]
- FirebaseAnalytics analytics = FirebaseAnalytics.getInstance(getActivity());
- MeasurementManager.recordSwimmingEnd(analytics,
- model.getStarCount(),
- model.distanceMeters,
- model.swimmer.getCollidedObjectType());
- break;
-
- case EventBus.PLAY_SOUND:
- int resId = (int) data;
- if (soundManager != null) {
- soundManager.play(resId);
- }
- break;
-
- case EventBus.MUTE_SOUNDS:
- boolean shouldMute = (boolean) data;
- if (soundManager != null) {
- if (shouldMute) {
- soundManager.mute();
- } else {
- soundManager.unmute();
- }
- }
- break;
-
- case EventBus.GAME_LOADED:
- long loadTimeMs = (long) data;
- titleDurationMs = Math.max(0, titleDurationMs - loadTimeMs);
- Log.d(TAG, "Waiting " + titleDurationMs + "ms and then hiding title.");
- break;
- }
-
- }
-
- @Nullable
- private Drawable getShareImageDrawable(String collidedObjectType) {
- return ContextCompat.getDrawable(getActivity(), R.drawable.winner);
- }
-
- private void initializeLevel(final SwimmingModel newLevel, boolean shouldPlayIntro) {
- EventBus.getInstance().clearListeners();
- EventBus.getInstance().register(newLevel, EventBus.VIBRATE);
- EventBus.getInstance().register(newLevel, EventBus.SHAKE_SCREEN);
- EventBus.getInstance().register(newLevel, EventBus.GAME_STATE_CHANGED);
-
- // Initialize Activity-specific stuff.
- EventBus.getInstance().register(this);
-
- Point size = AndroidUtils.getScreenSize();
- newLevel.screenWidth = size.x;
- newLevel.screenHeight = size.y;
- newLevel.addActor(new Camera(newLevel.screenWidth, newLevel.screenHeight));
- newLevel.addActor(new CameraShake());
- // Center the level horizontally on the screen and scale to fit.
- newLevel.camera.moveImmediatelyTo(
- Vector2D.get(0, 0),
- Vector2D.get(SwimmingModel.LEVEL_WIDTH, 0));
- newLevel.playCount = playCount++;
- newLevel.locale = getResources().getConfiguration().locale;
-
- if (isDestroyed) {
- return;
- }
- SwimmerActor swimmer = SwimmerActor.create(Vector2D.get(0, 0), context.getResources(),
- this);
- if (swimmer == null) {
- return;
- }
- swimmer.moveTo(
- SwimmingModel.LEVEL_WIDTH / 2 - swimmer.collisionBody.getWidth() / 2,
- SwimmerActor.KICKOFF_IDLE_Y_OFFSET);
- newLevel.addActor(swimmer);
-
-
- DistanceMarkerActor startingBlock =
- new DistanceMarkerActor(0, ColoredRectangleActor.STARTING_BLOCK, 1000);
- newLevel.addActor(startingBlock);
-
- SpriteActor banner = new SpriteActor(
- AnimatedSprite.fromFrames(context.getResources(), Sprites.penguin_swim_banner),
- Vector2D.get(SwimmingModel.LEVEL_WIDTH / 2, startingBlock.position.y), Vector2D.get());
- banner.scale = 2;
- banner.zIndex = 2;
- banner.sprite.setAnchor(banner.sprite.frameWidth / 2, banner.sprite.frameHeight - 50);
- newLevel.addActor(banner);
-
- newLevel.addActor(new DistanceMarkerActor(30, ColoredRectangleActor.DISTANCE_30M));
- newLevel.addActor(new DistanceMarkerActor(50, ColoredRectangleActor.DISTANCE_50M));
- newLevel.addActor(new DistanceMarkerActor(100, ColoredRectangleActor.DISTANCE_100M));
- newLevel.addActor(new DistanceMarkerActor(
- SwimmingLevelChunk.LEVEL_LENGTH_IN_METERS, ColoredRectangleActor.DISTANCE_LEVEL_LENGTH));
- if (historyManager != null) {
- addBestTimeLine(newLevel);
- }
-
- if (isDestroyed) {
- return;
- }
- ObstacleManager obstacleManager = new ObstacleManager(swimmer, context);
- newLevel.addActor(obstacleManager);
-
- newLevel.camera.position.y = -newLevel.camera.toWorldScale(newLevel.screenHeight);
- newLevel.clampCameraPosition(); // Clamp camera so that it doesn't jump when we start swimming.
-
- AnimatedSprite swimmingTutorialSprite =
- AnimatedSprite.fromFrames(context.getResources(), Sprites.tutorial_swimming);
- swimmingTutorialSprite.setFPS(6);
- RectangularInstructionActor instructions = new RectangularInstructionActor(
- context.getResources(), swimmingTutorialSprite);
- instructions.hidden = true;
- instructions.scale = (newLevel.screenWidth * 0.6f) / instructions.rectangle.frameWidth;
- // Put instructions at top right, slightly below pause button.
- instructions.position.set(newLevel.screenWidth / 2 - instructions.getScaledWidth() / 2, 70);
- newLevel.addUiActor(instructions);
-
- newLevel.setCountdownView(countdownView);
-
- if (!shouldPlayIntro) {
- // Skip State.INTRO.
- newLevel.setState(SwimmingState.WAITING);
- }
- if (isDestroyed) {
- return;
- }
- model = newLevel;
- swimmingView.setModel(model);
-
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- initializeLevelUiThread(newLevel);
- }
- });
-
- // Hint that this would be a good time to do some garbage collection since we should be able
- // to get rid of any stuff from the previous level at this time.
- System.gc();
- }
-
- // Initialize level parts that need to happen in UI thread.
- private void initializeLevelUiThread(final SwimmingModel newLevel) {
- if (isDestroyed) {
- return;
- }
- scoreView.updateCurrentScore(
- AndroidUtils.getText(context.getResources(), R.string.swimming_score, 0), false);
- pauseView.showPauseButton();
- diveView.show();
- newLevel.vibrator = (Vibrator) getActivity().getSystemService(Activity.VIBRATOR_SERVICE);
- }
-
- @Override
- protected void loadSounds() {
- super.loadSounds();
- soundManager.loadShortSound(context, R.raw.swimming_dive_down);
- soundManager.loadShortSound(context, R.raw.swimming_dive_up);
- soundManager.loadShortSound(context, R.raw.swimming_ice_splash_a);
- soundManager.loadShortSound(context, R.raw.swimming_duck_collide);
- soundManager.loadShortSound(context, R.raw.swimming_ice_collide);
- soundManager.loadShortSound(context, R.raw.swimming_grab);
- }
-
- private void addBestTimeLine(SwimmingModel level) {
- Double bestDistance = historyManager.getBestDistance(GameType.SWIMMING);
- if (bestDistance != null) {
- level.addActor(new DistanceMarkerActor(bestDistance.intValue(),
- ColoredRectangleActor.DISTANCE_PR));
- }
- }
-
- private void calculateStars() {
- Integer bestStarCount = historyManager.getBestStarCount(GameType.SWIMMING);
- int modelStarCount = model.getStarCount();
- if (bestStarCount == null || bestStarCount < modelStarCount) {
- historyManager.setBestStarCount(GameType.SWIMMING, modelStarCount);
- }
- }
-
- private void calculateBestDistance() {
- Double bestDistance = historyManager.getBestDistance(GameType.SWIMMING);
- if (bestDistance == null || bestDistance < model.distanceMeters) {
- historyManager.setBestDistance(GameType.SWIMMING, model.distanceMeters);
- }
- }
-
- private Button getButton(int textId, OnClickListener onClickListener) {
- Button button = new Button(context);
- button.setText(textId);
- button.setOnClickListener(onClickListener);
- return button;
- }
-
- private void updateScoreUi(final int score, final boolean shouldShowStars) {
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- scoreView.updateCurrentScore(
- AndroidUtils.getText(context.getResources(), R.string.swimming_score, score),
- shouldShowStars);
- if (shouldShowStars) {
- scoreView.addStar();
- }
- }
- });
-
- }
-
- private int integerValue(Object object) {
- if (object instanceof Integer) {
- return (int) object;
- } else {
- throw new IllegalArgumentException("Unknown event data type");
- }
- }
-
- @Override
- public void onGamePause() {
- if (model != null && model.swimmer != null) {
- model.swimmer.controlsEnabled = false;
- }
- super.onGamePause();
- }
-
- @Override
- protected void onGameResume() {
- if (model != null && model.swimmer != null) {
- model.swimmer.controlsEnabled = true;
- }
- super.onGameResume();
- }
-
- @Override
- protected void onGameReplay() {
- if (model != null && model.swimmer != null) {
- model.swimmer.controlsEnabled = true;
- }
- super.onGameReplay();
- }
-
- @Override
- protected boolean onTitleTapped() {
- if (model != null && model.getState() == SwimmingState.INTRO) {
- // If the user taps the screen while the intro animation is playing, end the intro
- // immediately and transition into the game.
- model.timeElapsed = GameFragment.TITLE_DURATION_MS;
- return true;
- }
- return false;
- }
-
- @Override
- protected String getGameType() {
- return SWIMMING_GAME_TYPE;
- }
-
- @Override
- protected float getScore() {
- return model.distanceMeters;
- }
-
- @Override
- protected int getShareImageId() {
- String collidedObjectType = model.swimmer.getCollidedObjectType();
- if (BoundingBoxSpriteActor.ICE_CUBE.equals(collidedObjectType)) {
- return 0;
- } else if (BoundingBoxSpriteActor.DUCK.equals(collidedObjectType)) {
- return 1;
- } else { // Octopus hand grab.
- return 2;
- }
- }
-
- /**
- * A dialog fragment for loading a swimming level.
- */
- public static class LoadLevelDialogFragment extends DialogFragment {
- private SwimmingFragment swimmingFragment;
- public LoadLevelDialogFragment(SwimmingFragment swimmingFragment) {
- this.swimmingFragment = swimmingFragment;
- }
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final String[] fileList =
- SwimmingLevelManager.levelsDir.exists()
- ? SwimmingLevelManager.levelsDir.list()
- : new String[0];
-
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder
- .setTitle(R.string.load_level)
- .setItems(
- fileList,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- SwimmingModel level = swimmingFragment.levelManager.loadLevel(fileList[which]);
- swimmingFragment.initializeLevel(level, false);
-
- // Save the current level so we automatically come back to it later.
- getActivity()
- .getSharedPreferences(
- swimmingFragment.context.getString(R.string.swimming),
- Context.MODE_PRIVATE)
- .edit()
- .putString(CURRENT_LEVEL_KEY, fileList[which])
- .commit();
- }
- });
- return builder.create();
- }
- }
-
- /**
- * A dialog fragment for saving a swimming level.
- */
- public static class SaveLevelDialogFragment extends DialogFragment {
- private SwimmingFragment swimmingFragment;
- public SaveLevelDialogFragment(SwimmingFragment swimmingFragment) {
- this.swimmingFragment = swimmingFragment;
- }
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final EditText editText = new EditText(swimmingFragment.context);
- editText.setText(swimmingFragment.model.levelName);
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder
- .setTitle(R.string.save_level)
- .setView(editText)
- .setPositiveButton(
- R.string.save,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- swimmingFragment.levelManager.saveLevel(
- swimmingFragment.model, editText.getText().toString());
- }
- })
- .setNegativeButton(
- R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- // Do nothing.
- }
- });
- return builder.create();
- }
- }
-
- /**
- * A dialog fragment for deleting a swimming level.
- */
- public static class DeleteLevelDialogFragment extends DialogFragment {
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final List selectedItems = new ArrayList<>();
- final String[] fileList =
- SwimmingLevelManager.levelsDir.exists()
- ? SwimmingLevelManager.levelsDir.list()
- : new String[0];
-
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder
- .setTitle(R.string.delete_levels)
- .setMultiChoiceItems(
- fileList,
- null,
- new DialogInterface.OnMultiChoiceClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which, boolean isChecked) {
- if (isChecked) {
- selectedItems.add(which);
- } else if (selectedItems.contains(which)) {
- selectedItems.remove(Integer.valueOf(which));
- }
- }
- })
- .setPositiveButton(
- R.string.delete,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- for (Integer selection : selectedItems) {
- File file = new File(SwimmingLevelManager.levelsDir, fileList[selection]);
- file.delete();
- }
- }
- })
- .setNegativeButton(
- R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- // Do nothing.
- }
- });
- return builder.create();
- }
- }
-
- public boolean isGameOver() {
- return mIsGameOver;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingLevelChunk.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingLevelChunk.java
deleted file mode 100644
index 21f561f94..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingLevelChunk.java
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapFactory.Options;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.util.Log;
-
-import com.google.android.apps.santatracker.doodles.Config;
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-import com.google.android.apps.santatracker.doodles.shared.physics.Util;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-import java.util.Random;
-
-/**
- * One chunk of a level in the swimming game.
- */
-public class SwimmingLevelChunk extends Actor {
- private static final String TAG = SwimmingLevelChunk.class.getSimpleName();
-
- public static final int LEVEL_LENGTH_IN_METERS = 500;
- public static Queue swimmingLevelChunks;
-
- public static List pathList;
- public static final int CHUNK_HEIGHT = 5000;
- public static final int NUM_ROWS = 100;
- public static final int NUM_COLS = 50;
- public static final float COL_WIDTH = SwimmingModel.LEVEL_WIDTH / (float) NUM_COLS;
- public static final float ROW_HEIGHT = CHUNK_HEIGHT / (float) NUM_ROWS;
- private static final int SOLUTION_PATH_NUM_COLS = 50;
- private static final Random RANDOM = new Random();
-
- private static final List TYPES;
- static {
- TYPES = new ArrayList<>();
- TYPES.add(BoundingBoxSpriteActor.ICE_CUBE);
- TYPES.add(BoundingBoxSpriteActor.DUCK);
- TYPES.add(BoundingBoxSpriteActor.HAND_GRAB);
- }
-
- private final float DEFAULT_OBSTACLE_DENSITY;
-
- public final float startY;
- public final float endY;
- public List obstacles;
- private SolutionPath solutionPath;
- private boolean mirrored;
-
- public static SwimmingLevelChunk create(float startY, Context context) {
- if (pathList == null || pathList.size() == 0) {
- loadChunkTemplates(context.getResources());
- }
- // Increase the probability that the random chunk will be a "middle open" chunk.
- int pathIndex = Math.min(pathList.size() - 1, RANDOM.nextInt(pathList.size() + 1));
- SolutionPath solutionPath = pathList.get(pathIndex);
- return new SwimmingLevelChunk(startY, solutionPath, RANDOM.nextBoolean(), context);
- }
-
- public static void generateAllLevelChunks(float startY, Context context) {
- long startTime = System.currentTimeMillis();
- swimmingLevelChunks = new LinkedList<>();
- SwimmingLevelChunk chunk = create(startY, context);
- while (SwimmingModel.getMetersFromWorldY(chunk.endY) < LEVEL_LENGTH_IN_METERS) {
- swimmingLevelChunks.add(chunk);
- chunk = create(chunk.endY, context);
- }
- Log.d(TAG, "generateAllLevelChunks: finished in "
- + ((System.currentTimeMillis() - startTime) / 1000.0f)
- + " seconds.");
- }
-
- public static SwimmingLevelChunk getNextChunk() {
- if (!swimmingLevelChunks.isEmpty()) {
- return swimmingLevelChunks.remove();
- }
- return null;
- }
-
- private SwimmingLevelChunk(float startY, SolutionPath solutionPath,
- boolean mirrored, Context context) {
-
- // Get swimming obstacle density from config
- Config config = new Config();
- DEFAULT_OBSTACLE_DENSITY = (float) config.SWIMMING_OBSTACLE_DENSITY;
-
- this.solutionPath = solutionPath;
- this.mirrored = mirrored;
- this.startY = startY;
- generateObstacles(context);
- removeObstaclesFromSolutionPath(startY);
- this.endY = startY - solutionPath.getChunkHeight();
- }
-
- @Override
- public void update(float deltaMs) {
- for (int i = 0; i < obstacles.size(); i++) {
- obstacles.get(i).update(deltaMs);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- //solutionPath.draw(canvas, startY, mirrored);
- for (int i = 0; i < obstacles.size(); i++) {
- obstacles.get(i).draw(canvas);
- }
- }
-
- public void resolveCollisions(SwimmerActor swimmer, float deltaMs) {
- for (int i = 0; i < obstacles.size(); i++) {
- obstacles.get(i).resolveCollision(swimmer, deltaMs);
- }
- }
-
- public static void loadChunkTemplates(Resources res) {
- pathList = new ArrayList<>();
-
- Options decodeOptions = new Options();
- decodeOptions.inScaled = false;
-
- Bitmap b = BitmapFactory.decodeResource(res, R.raw.diamond, decodeOptions);
- pathList.add(new GridSolutionPath(b));
-
- b = BitmapFactory.decodeResource(res, R.raw.zig, decodeOptions);
- pathList.add(new GridSolutionPath(b));
-
- b = BitmapFactory.decodeResource(res, R.raw.ziggeroo, decodeOptions);
- pathList.add(new GridSolutionPath(b));
-
- b = BitmapFactory.decodeResource(res, R.raw.fork_in, decodeOptions);
- pathList.add(new GridSolutionPath(b));
-
- b = BitmapFactory.decodeResource(res, R.raw.fork_out, decodeOptions);
- pathList.add(new GridSolutionPath(b));
-
- b = BitmapFactory.decodeResource(res, R.raw.middle_open, decodeOptions);
- pathList.add(new GridSolutionPath(b));
- }
-
- private void generateObstacles(Context context) {
- obstacles = new ArrayList<>();
- for (int i = 0; i < solutionPath.getNumRows() * DEFAULT_OBSTACLE_DENSITY; i++) {
- float x = RANDOM.nextInt((4 * SwimmingModel.LEVEL_WIDTH) / 5);
- float y = startY - (SwimmingModel.LEVEL_WIDTH / 5)
- - RANDOM.nextInt((int) solutionPath.getChunkHeight() - (SwimmingModel.LEVEL_WIDTH / 5));
-
- int metersY = SwimmingModel.getMetersFromWorldY(y);
- int type;
- if (metersY < SwimmingModel.SCORE_THRESHOLDS[0]) {
- // Only show ice cubes before the bronze threshold.
- type = 0;
- } else if (metersY < SwimmingModel.SCORE_THRESHOLDS[1]) {
- // Show ice cubes and cans before the silver threshold.
- type = RANDOM.nextInt(TYPES.size() - 1);
- } else {
- // After the silver threshold, use all obstacles.
- boolean isInMiddleThreeLanes = SwimmingModel.LEVEL_WIDTH / 5 <= x
- && x <= 3 * SwimmingModel.LEVEL_WIDTH / 5;
- if (isInMiddleThreeLanes) {
- // Only place octograbs in the middle three lanes. If we are generating an obstacle in the
- // middle 3 lanes, give it a higher chance of being an octograb.
- type = Math.min(TYPES.size() - 1, RANDOM.nextInt(TYPES.size() + 1));
- } else {
- // If we are outside of the middle 3 lanes, give each other option equal weight.
- type = RANDOM.nextInt(TYPES.size() - 1);
- }
- }
-
- BoundingBoxSpriteActor obstacle = BoundingBoxSpriteActor.create(
- Vector2D.get(x, y), TYPES.get(type), context.getResources());
- Polygon obstacleBody = obstacle.collisionBody;
- boolean shouldAdd = true;
- for (int j = 0; j < obstacles.size(); j++) {
- Polygon otherBody = obstacles.get(j).collisionBody;
- if (Util.rectIntersectsRect(
- otherBody.min.x, otherBody.min.y,
- otherBody.getWidth(), otherBody.getHeight(),
- obstacleBody.min.x, obstacleBody.min.y,
- obstacleBody.getWidth(), obstacleBody.getHeight())) {
- shouldAdd = false;
- }
- }
- if (shouldAdd) {
- obstacles.add(obstacle);
- }
- }
- }
-
- private void removeObstaclesFromSolutionPath(float startY) {
- for (int i = obstacles.size() - 1; i >= 0; i--) {
- BoundingBoxSpriteActor obstacle = obstacles.get(i);
- Vector2D min = obstacle.collisionBody.min;
- Vector2D max = obstacle.collisionBody.max;
- if (solutionPath.intersects(startY, min.x, min.y, max.x, max.y, mirrored)) {
- obstacles.remove(i);
- }
- }
- }
-
- private interface SolutionPath {
- boolean intersects(float startY, float minX, float minY, float maxX, float maxY,
- boolean mirrored);
- void draw(Canvas canvas, float startY, boolean mirrored);
- int getEndCol(boolean mirrored);
- float getChunkHeight();
- int getNumRows();
- }
-
- private static class SolutionPathImpl implements SolutionPath {
- private static final int DRIFT_SAME = 0;
- private static final int DRIFT_REVERSE = 1;
- private final int[] driftDistribution = new int[] { 50, 75, 100 };
- private SolutionPathRow[] rows;
- private int endCol;
- private int drift;
- private Paint paint;
-
- public SolutionPathImpl(int startCol) {
- rows = new SolutionPathRow[NUM_ROWS];
- paint = new Paint();
- paint.setColor(Color.DKGRAY);
-
- for (int i = 0; i < rows.length; i++) {
-
- // Decide which way to drift.
- int driftToken = RANDOM.nextInt(100);
- if (driftToken < driftDistribution[DRIFT_SAME]) {
- if (drift == 0) {
- // If the path is going straight, switch it to a random direction.
- drift = RANDOM.nextBoolean() ? 1 : -1;
- }
- } else if (driftToken < driftDistribution[DRIFT_REVERSE]) {
- drift = 0;
- } else {
- drift *= -1;
- }
- if (startCol == 0) {
- drift = 1;
- } else if (startCol == NUM_COLS - SOLUTION_PATH_NUM_COLS - 1) {
- drift = -1;
- }
- startCol = Util.clamp(startCol + drift, 0, NUM_COLS - SOLUTION_PATH_NUM_COLS - 1);
-
- rows[i] = new SolutionPathRow(startCol, SOLUTION_PATH_NUM_COLS);
- }
- this.endCol = startCol;
- }
-
- @Override
- public boolean intersects(float startY, float minX, float minY, float maxX, float maxY,
- boolean mirrored) {
- // Subtract y from startY because the level proceeds in the negative y direction.
- int minRowIndex = (int) Math.max(0, (startY - maxY) / ROW_HEIGHT);
- int maxRowIndex = (int) Math.max(0, (startY - minY) / ROW_HEIGHT);
- for (int i = minRowIndex; i <= maxRowIndex; i++) {
- if (rows[i].intersects(Math.min(minX, SwimmingModel.LEVEL_WIDTH), mirrored)
- || rows[i].intersects(Math.min(maxX, SwimmingModel.LEVEL_WIDTH), mirrored)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void draw(Canvas canvas, float startY, boolean mirrored) {
- float y = startY;
- for (int i = 0; i < rows.length; i++) {
- float startX = rows[i].startX;
- float endX = rows[i].endX;
- if (mirrored) {
- startX = SwimmingModel.LEVEL_WIDTH - rows[i].endX;
- endX = SwimmingModel.LEVEL_WIDTH - rows[i].startX;
- }
- canvas.drawRect(startX, y, endX, y + ROW_HEIGHT, paint);
- y -= ROW_HEIGHT;
- }
- }
-
- @Override
- public int getEndCol(boolean mirrored) {
- return mirrored ? NUM_COLS - 1 - endCol : endCol;
- }
-
- @Override
- public float getChunkHeight() {
- return CHUNK_HEIGHT;
- }
-
- @Override
- public int getNumRows() {
- return rows.length;
- }
- }
-
- private static class GridSolutionPath implements SolutionPath {
-
- public final float chunkHeight;
- public final int numRows;
- public final float rowHeight;
-
- private boolean[][] grid;
- private Paint paint;
-
- /**
- * Initialize this solution path with a bitmap. The length of the solution path will scale with
- * the height of the image, where 1px in the image = 1 grid unit in the chunk. The width of the
- * solution path is fixed to NUM_COLS and will just sample the bitmap at NUM_COLS points. Any
- * bitmap with a higher horizontal resolution than NUM_COLS will be down-sampled, and any bitmap
- * with a lower resolution will have single pixels being sampled more than once horizontally.
- *
- * In order to maintain visual consistency with the supplied bitmap, it is recommended that
- * the input PNGs are 50px wide.
- */
- public GridSolutionPath(Bitmap bitmap) {
- int bitmapWidth = bitmap.getWidth();
- int bitmapHeight = bitmap.getHeight();
- chunkHeight = bitmapHeight * COL_WIDTH;
- numRows = bitmapHeight;
- rowHeight = chunkHeight / numRows;
-
- Log.d(TAG, "bitmapHeight: " + bitmapHeight);
- Log.d(TAG, "chunkHeight: " + chunkHeight);
- Log.d(TAG, "numRows: " + numRows);
- Log.d(TAG, "rowHeight: " + rowHeight);
-
- paint = new Paint();
- paint.setColor(Color.DKGRAY);
- grid = new boolean[numRows][NUM_COLS];
-
- for (int i = 0; i < grid.length; i++) {
- for (int j = 0; j < grid[0].length; j++) {
- int bitmapX = (int) ((((float) j) / grid[0].length) * bitmapWidth);
- int bitmapY = (int) ((((float) i) / grid.length) * bitmapHeight);
- int pixel = bitmap.getPixel(bitmapX, bitmapY);
- grid[i][j] = (pixel & 0x00ffffff) != 0;
- }
- }
- }
-
- @Override
- public boolean intersects(float startY, float minX, float minY, float maxX, float maxY,
- boolean mirrored) {
- if (mirrored) {
- float tmpMinX = minX;
- minX = SwimmingModel.LEVEL_WIDTH - maxX;
- maxX = SwimmingModel.LEVEL_WIDTH - tmpMinX;
- }
-
- // Subtract y from startY because the level proceeds in the negative y direction.
- int minRowIndex = (int) Math.max(0, (startY - maxY) / rowHeight);
- int maxRowIndex = (int) Math.max(0, (startY - minY) / rowHeight);
- int minColIndex = (int) Math.min(NUM_COLS - 1, minX / COL_WIDTH);
- int maxColIndex = (int) Math.min(NUM_COLS - 1, maxX / COL_WIDTH);
- for (int i = minRowIndex; i <= maxRowIndex; i++) {
- for (int j = minColIndex; j <= maxColIndex; j++) {
- if (grid[i][j]) {
- return true;
- }
- }
- }
- return false;
- }
-
- @Override
- public void draw(Canvas canvas, float startY, boolean mirrored) {
- float y = startY - rowHeight;
- for (int i = 0; i < grid.length; i++) {
- float x = 0;
- for (int j = 0; j < grid[0].length; j++) {
- if (!mirrored && grid[i][j]) {
- canvas.drawRect(x, y, x + COL_WIDTH, y + rowHeight, paint);
- } else if (mirrored && grid[i][grid[0].length - 1 - j]) {
- canvas.drawRect(x, y, x + COL_WIDTH, y + rowHeight, paint);
- }
- x += COL_WIDTH;
- }
- y -= rowHeight;
- }
- }
-
- @Override
- public int getEndCol(boolean mirrored) {
- return mirrored ? NUM_COLS - 1 : 0;
- }
-
- @Override
- public float getChunkHeight() {
- return chunkHeight;
- }
-
- @Override
- public int getNumRows() {
- return numRows;
- }
- }
-
- private static class SolutionPathRow {
- // The first column which is in the solution path.
- public final int startCol;
- // The column after the last column in the solution path.
- public final int endCol;
- public final float startX;
- public final float endX;
- public SolutionPathRow(int startCol, int numCols) {
- this.startCol = startCol;
- this.endCol = startCol + numCols;
- this.startX = startCol * COL_WIDTH;
- this.endX = endCol * COL_WIDTH;
- }
-
- public boolean intersects(float x, boolean mirrored) {
- float startX = this.startX;
- float endX = this.endX;
- if (mirrored) {
- startX = SwimmingModel.LEVEL_WIDTH - this.endX;
- endX = SwimmingModel.LEVEL_WIDTH - this.startX;
- }
- return startX <= x && x <= endX;
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingLevelManager.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingLevelManager.java
deleted file mode 100644
index b4e07b7b6..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingLevelManager.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.content.Context;
-import android.os.Environment;
-import android.util.Log;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * A level manager for the Pineapple 2016 swimming game.
- */
-public class SwimmingLevelManager extends LevelManager {
-
- public static File levelsDir = null;
-
- private static final int DEFAULT_OBSTACLE_Y = -1000;
-
- public SwimmingLevelManager(Context context) {
- super(context);
- }
-
- @Override
- public SwimmingModel loadDefaultLevel() {
- SwimmingModel model = getEmptyModel();
-
- List types = new ArrayList<>(BoundingBoxSpriteActor.TYPE_TO_RESOURCE_MAP.keySet());
- for (int i = 0; i < types.size(); i++) {
- int x = i * SwimmingModel.LEVEL_WIDTH / types.size();
- int y = DEFAULT_OBSTACLE_Y;
- BoundingBoxSpriteActor obstacle =
- BoundingBoxSpriteActor.create(Vector2D.get(x, y), types.get(i), context.getResources());
- model.addActor(obstacle);
- }
- return model;
- }
-
- @Override
- protected File getLevelsDir() {
- if (levelsDir == null) {
- levelsDir = new File(Environment.getExternalStorageDirectory(), "swimming_levels");
- }
- return levelsDir;
- }
-
- @Override
- Actor loadActorFromJSON(JSONObject json) throws JSONException {
- String type = json.getString(Actor.TYPE_KEY);
- Actor actor = null;
- if (BoundingBoxSpriteActor.TYPE_TO_RESOURCE_MAP.containsKey(type)) {
- actor = BoundingBoxSpriteActor.fromJSON(json, context);
- } else {
- Log.w(TAG, "Unable to create object of type: " + type);
- }
- return actor;
- }
-
- @Override
- protected SwimmingModel getEmptyModel() {
- return new SwimmingModel();
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingModel.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingModel.java
deleted file mode 100644
index 9197f2059..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingModel.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.os.Build;
-import android.os.Vibrator;
-import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.widget.TextView;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.CallbackProcess;
-import com.google.android.apps.santatracker.doodles.shared.Camera;
-import com.google.android.apps.santatracker.doodles.shared.CameraShake;
-import com.google.android.apps.santatracker.doodles.shared.EventBus;
-import com.google.android.apps.santatracker.doodles.shared.EventBus.EventBusListener;
-import com.google.android.apps.santatracker.doodles.shared.Process;
-import com.google.android.apps.santatracker.doodles.shared.ProcessChain;
-import com.google.android.apps.santatracker.doodles.shared.RectangularInstructionActor;
-import com.google.android.apps.santatracker.doodles.shared.UIUtil;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.WaitProcess;
-import com.google.android.apps.santatracker.doodles.shared.physics.Util;
-import java.text.NumberFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * The model for the swimming game.
- */
-public class SwimmingModel implements TiltModel, EventBusListener {
- private static final String TAG = SwimmingModel.class.getSimpleName();
-
- public static final int LEVEL_WIDTH = 1280;
- public static final int[] SCORE_THRESHOLDS = { 30, 50, 100 };
-
- private static final float SHAKE_FREQUENCY = 33;
- private static final float SHAKE_MAGNITUDE = 40;
- private static final float SHAKE_FALLOFF = 0.9f;
- private static final long VIBRATION_DURATION_MS = 50;
- private static final int WORLD_TO_METER_RATIO = 700;
- private static final long WAITING_STATE_DELAY_MS = 1000;
- private static final long COUNTDOWN_DELAY_MS = 4000;
- private static final float COUNTDOWN_BUMP_SCALE = 1.5f;
-
- public final List collisionObjectTypes;
-
- public String levelName;
- public boolean collisionMode = true;
-
- public List actors;
- public List uiActors; // Will be drawn above actors.
- public Camera camera;
- public CameraShake cameraShake;
- public SwimmerActor swimmer;
- public RectangularInstructionActor instructions;
- public TextView countdownView;
- public ObstacleManager obstacleManager;
- public Vibrator vibrator;
- public Locale locale;
- public int distanceMeters;
- public int currentScoreThreshold = 0;
-
- public int screenWidth;
- public int screenHeight;
- public Vector2D tilt;
-
- // Measures the time elapsed in the current state. This value is reset to 0 upon entering a new
- // state.
- public long timeElapsed = 0;
- private float countdownTimeMs = 3000;
- private List processChains = new ArrayList<>();
- public int playCount;
-
- /**
- * States for the swimming game.
- */
- public enum SwimmingState {
- INTRO,
- WAITING,
- SWIMMING,
- FINISHED,
- }
- private SwimmingState state;
-
- public SwimmingModel() {
- tilt = Vector2D.get(0, 0);
- actors = new ArrayList<>();
- uiActors = new ArrayList<>();
- state = SwimmingState.INTRO;
-
- collisionObjectTypes = new ArrayList<>();
- collisionObjectTypes.addAll(BoundingBoxSpriteActor.TYPE_TO_RESOURCE_MAP.keySet());
- }
-
- @Override
- public void onEventReceived(int type, Object data) {
- switch(type) {
- case EventBus.VIBRATE:
- if (vibrator != null) {
- vibrator.vibrate(VIBRATION_DURATION_MS);
- }
- break;
-
- case EventBus.SHAKE_SCREEN:
- cameraShake.shake(SHAKE_FREQUENCY, SHAKE_MAGNITUDE, SHAKE_FALLOFF);
- break;
-
- case EventBus.GAME_STATE_CHANGED:
- SwimmingState state = (SwimmingState) data;
- if (state == SwimmingState.WAITING) {
- long countdownDelayMs = WAITING_STATE_DELAY_MS;
- if (playCount == 0) {
- // Wait for the crossfade to finish then show instructions.
- processChains.add(new WaitProcess(WAITING_STATE_DELAY_MS).then(new CallbackProcess() {
- @Override
- public void updateLogic(float deltaMs) {
- if (getState() == SwimmingState.WAITING) {
- instructions.show();
- }
- }
- }).then(new WaitProcess(COUNTDOWN_DELAY_MS).then(new CallbackProcess() {
- @Override
- public void updateLogic(float deltaMs) {
- instructions.hide();
- }
- })));
- // If we're showing the instructions, wait until the instructions is hidden before
- // starting the countdown.
- countdownDelayMs += COUNTDOWN_DELAY_MS + 300;
- }
- // Start countdown.
- processChains.add(new WaitProcess(countdownDelayMs).then(new Process() {
- @Override
- public void updateLogic(float deltaMs) {
- final float oldCountdownTimeMs = countdownTimeMs;
- float newCountdownTimeMs = countdownTimeMs - deltaMs;
- if ((long) newCountdownTimeMs / 1000 != (long) oldCountdownTimeMs / 1000) {
- countdownView.post(new Runnable() {
- @Override
- public void run() {
- countdownView.setVisibility(View.VISIBLE);
- // Use the old integer value so that the countdown goes 3, 2, 1 and not 2, 1, 0.
- String countdownValue =
- NumberFormat.getInstance(locale).format((long) oldCountdownTimeMs / 1000);
- setTextAndBump(countdownView, countdownValue);
- }
- });
- }
- countdownTimeMs = newCountdownTimeMs;
- }
-
- @Override
- public boolean isFinished() {
- return countdownTimeMs <= 0;
- }
- }).then(new CallbackProcess() {
- @Override
- public void updateLogic(float deltaMs) {
- countdownView.post(new Runnable() {
- @Override
- public void run() {
- countdownView.setVisibility(View.INVISIBLE);
- }
- });
- setState(SwimmingState.SWIMMING);
- }
- }));
- } else if (state == SwimmingState.SWIMMING) {
- swimmer.startSwimming();
- }
- }
- }
-
- public void setState(SwimmingState state) {
- if (this.state != state) {
- this.state = state;
- timeElapsed = 0;
- EventBus.getInstance().sendEvent(EventBus.GAME_STATE_CHANGED, state);
- }
- }
-
- public SwimmingState getState() {
- return state;
- }
-
- public void update(float deltaMs) {
- synchronized (this) {
- timeElapsed += deltaMs;
-
- ProcessChain.updateChains(processChains, deltaMs);
-
- for (int i = 0; i < actors.size(); i++) {
- actors.get(i).update(deltaMs);
- }
-
- for (int i = 0; i < uiActors.size(); i++) {
- uiActors.get(i).update(deltaMs);
- }
-
- if (state == SwimmingState.SWIMMING || state == SwimmingState.WAITING) {
- swimmer.updateTargetPositionFromTilt(tilt, LEVEL_WIDTH);
-
- int newDistance = getMetersFromWorldY(swimmer.position.y);
- if (newDistance != distanceMeters) {
- if (newDistance >= SwimmingLevelChunk.LEVEL_LENGTH_IN_METERS) {
- swimmer.endGameWithoutCollision();
- }
- distanceMeters = Math.min(SwimmingLevelChunk.LEVEL_LENGTH_IN_METERS, newDistance);
- EventBus.getInstance().sendEvent(EventBus.SCORE_CHANGED, distanceMeters);
- }
- if (swimmer.isDead) {
- setState(SwimmingState.FINISHED);
- }
-
- resolveCollisions(deltaMs);
-
- clampCameraPosition();
- }
- }
- }
-
- public void clampCameraPosition() {
- if (!SwimmingFragment.editorMode) {
- float swimmerHeight = swimmer.collisionBody.getHeight();
- float minCameraOffset = camera.toWorldScale(screenHeight) - 3.5f * swimmerHeight;
- float maxCameraOffset = camera.toWorldScale(screenHeight) - 4.0f * swimmerHeight;
- camera.position.set(camera.position.x, Util.clamp(camera.position.y,
- swimmer.position.y - minCameraOffset, swimmer.position.y - maxCameraOffset));
- }
- }
-
- public void resolveCollisions(float deltaMs) {
- obstacleManager.resolveCollisions(swimmer, deltaMs);
- }
-
- public void drawActors(Canvas canvas) {
- List actorsToDraw = new ArrayList<>(actors);
- actorsToDraw.addAll(obstacleManager.getActors());
- Collections.sort(actorsToDraw);
- for (int i = 0; i < actorsToDraw.size(); i++) {
- actorsToDraw.get(i).draw(canvas);
- }
- }
-
- public void drawUiActors(Canvas canvas) {
- for (int i = 0; i < uiActors.size(); i++) {
- uiActors.get(i).draw(canvas);
- }
- }
-
- public int getStarCount() {
- return currentScoreThreshold;
- }
-
- public void onTouchDown() {
- if (getState() == SwimmingState.SWIMMING) {
- swimmer.diveDown();
- }
- }
-
- public void createActor(Vector2D position, String objectType, Resources resources) {
- if (BoundingBoxSpriteActor.TYPE_TO_RESOURCE_MAP.containsKey(objectType)) {
- actors.add(BoundingBoxSpriteActor.create(position, objectType, resources));
- }
- }
-
- public void sortActors() {
- Collections.sort(actors);
- }
-
- @Override
- public List getActors() {
- return actors;
- }
-
- @Override
- public void addActor(Actor actor) {
- if (actor instanceof SwimmerActor) {
- this.swimmer = (SwimmerActor) actor;
- } else if (actor instanceof Camera) {
- this.camera = (Camera) actor;
- } else if (actor instanceof CameraShake) {
- this.cameraShake = (CameraShake) actor;
- } else if (actor instanceof ObstacleManager) {
- this.obstacleManager = (ObstacleManager) actor;
- }
- actors.add(actor);
- sortActors();
- }
-
- public void addUiActor(Actor actor) {
- if (actor instanceof RectangularInstructionActor) {
- this.instructions = (RectangularInstructionActor) actor;
- }
- uiActors.add(actor);
- }
-
- public void setCountdownView(TextView countdownView) {
- this.countdownView = countdownView;
- }
-
- @Override
- public void setLevelName(String levelName) {
- this.levelName = levelName;
- }
-
- public static int getMetersFromWorldY(float distance) {
- return Math.max(0, (int) (-distance / WORLD_TO_METER_RATIO));
- }
-
- public static int getWorldYFromMeters(int meters) {
- return -meters * WORLD_TO_METER_RATIO;
- }
-
- private void setTextAndBump(final TextView textView, String text) {
- float endScale = textView.getScaleX();
- float startScale = COUNTDOWN_BUMP_SCALE * textView.getScaleX();
- textView.setText(text);
- if (!"Nexus 9".equals(Build.MODEL)) {
- ValueAnimator scaleAnimation = UIUtil.animator(200,
- new AccelerateDecelerateInterpolator(),
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float scaleValue = (float) valueAnimator.getAnimatedValue("scale");
- textView.setScaleX(scaleValue);
- textView.setScaleY(scaleValue);
- }
- },
- UIUtil.floatValue("scale", startScale, endScale)
- );
- scaleAnimation.start();
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingView.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingView.java
deleted file mode 100644
index d3a5237ed..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/SwimmingView.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.HapticFeedbackConstants;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.View;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-
-/**
- * The game view for the swimming game.
- */
-public class SwimmingView extends View {
- private static final String TAG = SwimmingView.class.getSimpleName();
- public static final int WATER_BLUE = 0xffa6ffff;
- public static final int LINES_BLUE = 0xff00d4d4;
-
- private static final int LANE_LINE_WIDTH = 10;
- public static final int NUM_LANES = 5;
-
- private Paint swimmingLinesPaint;
-
- private SwimmingModel model;
- private GestureDetector editorGestureDetector;
- private ScaleGestureDetector scaleDetector;
- private GestureListener gestureListener;
-
- public SwimmingView(Context context) {
- this(context, null);
- }
-
- public SwimmingView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SwimmingView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- swimmingLinesPaint = new Paint();
- swimmingLinesPaint.setColor(LINES_BLUE);
- swimmingLinesPaint.setStyle(Style.FILL);
-
- gestureListener = new GestureListener();
- editorGestureDetector = new GestureDetector(context, gestureListener);
- scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- if (model == null) {
- return;
- }
- synchronized (model) {
- // Draw background
- canvas.drawColor(WATER_BLUE);
-
- canvas.save();
-
- canvas.scale(model.camera.scale, model.camera.scale);
- canvas.translate(
- -model.camera.position.x + model.cameraShake.position.x,
- -model.camera.position.y + model.cameraShake.position.y);
-
- drawSwimmingLines(canvas);
-
- model.drawActors(canvas);
-
- canvas.restore();
-
- model.drawUiActors(canvas);
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- boolean handledTouch = false;
- if (SwimmingFragment.editorMode) {
- // Handle touch events in the editor.
-
- // Let the ScaleGestureDetector inspect all events.
- handledTouch = scaleDetector.onTouchEvent(event);
-
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- int index = event.getActionIndex();
- // If the user touches the screen, check to see if they have selected a polygon vertex,
- // which they can then drag around the screen.
- Vector2D worldCoords = model.camera.getWorldCoords(event.getX(index), event.getY(index));
- // Reset selection.
- gestureListener.selectedActor = null;
-
- // model.actors will be sorted by z-index. Iterate over it backwards so that touches on
- // elements are handled in reverse z-index order (i.e., actors in front will be selected
- // before actors in back).
- for (int i = model.actors.size() - 1; i >= 0; i--) {
- Actor actor = model.actors.get(i);
- if (actor instanceof Touchable
- && ((Touchable) actor).canHandleTouchAt(worldCoords, model.camera.scale)) {
- if (model.collisionMode == (actor instanceof CollisionActor)) {
- // Only allow interactions with objects within the current selection mode (i.e.,
- // only collision objects in collision mode, only scenery objects in non-collision
- // mode.
- gestureListener.selectedActor = actor;
- ((Touchable) actor).startTouchAt(worldCoords, model.camera.scale);
- break;
- }
- }
- }
- break;
- }
- handledTouch = editorGestureDetector.onTouchEvent(event) || handledTouch;
- } else {
- // Handle in-game touch events.
- if (model != null) {
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- model.onTouchDown();
- handledTouch = true;
- break;
- }
- }
- }
- return handledTouch || super.onTouchEvent(event);
- }
-
- public void setModel(SwimmingModel model) {
- this.model = model;
- }
-
- private void drawSwimmingLines(Canvas canvas) {
- int laneWidth = SwimmingModel.LEVEL_WIDTH / NUM_LANES;
- for (int i = 1; i < NUM_LANES; i++) {
- canvas.drawRect(laneWidth * i - LANE_LINE_WIDTH / 2.0f, model.camera.yToWorld(0),
- laneWidth * i + LANE_LINE_WIDTH / 2.0f, model.camera.yToWorld(canvas.getHeight()),
- swimmingLinesPaint);
- }
- }
-
- private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- model.camera.scale *= detector.getScaleFactor();
- model.camera.scale = Math.max(0.1f, Math.min(model.camera.scale, 5.0f));
- return true;
- }
- }
-
- private class GestureListener extends SimpleOnGestureListener {
- public Actor selectedActor;
-
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- Vector2D delta = Vector2D.get(distanceX / model.camera.scale, distanceY / model.camera.scale);
- if (selectedActor != null) {
- ((Touchable) selectedActor).handleMoveEvent(delta);
- } else {
- model.camera.position.add(delta);
- }
- delta.release();
- return true;
- }
-
- @Override
- public void onLongPress(MotionEvent event) {
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- if (selectedActor != null) {
- boolean handled = ((Touchable) selectedActor).handleLongPress();
- if (!handled) {
- // If the selected actor doesn't handle the long press, the default behavior is to remove
- // it.
- model.actors.remove(selectedActor);
- }
- } else {
- // Long press is not on a touchable actor. Create a new object and place it
- // where the long press occurred.
- DialogFragment dialogFragment = new CreateObjectDialogFragment(
- model.camera.getWorldCoords(event.getX(), event.getY()));
- dialogFragment.show(((Activity) getContext()).getFragmentManager(), "create_object");
- }
- }
- }
-
- private class CreateObjectDialogFragment extends DialogFragment {
- private Vector2D center;
-
- public CreateObjectDialogFragment(Vector2D center) {
- this.center = center;
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final String[] items;
- items = new String[model.collisionObjectTypes.size()];
- model.collisionObjectTypes.toArray(items);
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(com.google.android.apps.santatracker.doodles.R.string.create_object)
- .setItems(items, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- model.createActor(center, items[which], getResources());
- }
- });
- return builder.create();
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/TestTiltModel.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/TestTiltModel.java
deleted file mode 100644
index 86b008b58..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/TestTiltModel.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A basic tilt model to be used for testing.
- */
-public class TestTiltModel implements TiltModel {
-
- private List actors;
-
- public TestTiltModel() {
- actors = new ArrayList<>();
- }
-
- @Override
- public List getActors() {
- return actors;
- }
-
- @Override
- public void addActor(Actor actor) {
- actors.add(actor);
- }
-
- @Override
- public void setLevelName(String levelName) {
- // Do nothing.
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/TiltModel.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/TiltModel.java
deleted file mode 100644
index c6db5374b..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/TiltModel.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-
-import java.util.List;
-
-/**
- * A model in one of the tilt games, used to unify the loading of levels in the swimming and golf
- * games.
- */
-public interface TiltModel {
- List getActors();
-
- void addActor(Actor actor);
-
- void setLevelName(String levelName);
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ToggleableBounceActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ToggleableBounceActor.java
deleted file mode 100644
index 5ec2ea2ff..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/ToggleableBounceActor.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.graphics.Canvas;
-import android.graphics.Color;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * A BounceActor which can be toggled on an off with trigger polygons.
- */
-public class ToggleableBounceActor extends BounceActor {
- public static final String TYPE = "Toggleable Bouncy";
- protected static final String ON_TRIGGER_KEY = "on trigger";
- protected static final String OFF_TRIGGER_KEY = "off trigger";
-
- private CollisionActor onTrigger;
- private CollisionActor offTrigger;
- public boolean enabled;
-
- public ToggleableBounceActor(Polygon collisionBody,
- CollisionActor onTrigger, CollisionActor offTrigger) {
- super(collisionBody);
- this.onTrigger = onTrigger;
- this.offTrigger = offTrigger;
-
- collisionBody.setPaintColors(Color.RED, Color.LTGRAY, 0x6400ff00);
- onTrigger.collisionBody.setPaintColors(Color.GREEN, Color.RED, 0x6400ff00);
- offTrigger.collisionBody.setPaintColors(Color.BLACK, Color.RED, 0x6400ff00);
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
- onTrigger.update(deltaMs);
- offTrigger.update(deltaMs);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- onTrigger.draw(canvas);
- offTrigger.draw(canvas);
- }
-
- @Override
- public String getType() {
- return ToggleableBounceActor.TYPE;
- }
-
- @Override
- public boolean canHandleTouchAt(Vector2D worldCoords, float cameraScale) {
- return super.canHandleTouchAt(worldCoords, cameraScale)
- || onTrigger.canHandleTouchAt(worldCoords, cameraScale)
- || offTrigger.canHandleTouchAt(worldCoords, cameraScale);
- }
-
- @Override
- public void startTouchAt(Vector2D worldCoords, float cameraScale) {
- super.startTouchAt(worldCoords, cameraScale);
- onTrigger.startTouchAt(worldCoords, cameraScale);
- offTrigger.startTouchAt(worldCoords, cameraScale);
- }
-
- @Override
- public boolean handleMoveEvent(Vector2D delta) {
- return super.handleMoveEvent(delta)
- || onTrigger.handleMoveEvent(delta) || offTrigger.handleMoveEvent(delta);
- }
-
- @Override
- public boolean handleLongPress() {
- return super.handleLongPress() || onTrigger.handleLongPress() || offTrigger.handleLongPress();
- }
-
- @Override
- public boolean resolveCollision(Actor other, float deltaMs) {
- // Resolve trigger collisions.
- if (onTrigger.collisionBody.contains(other.position)) {
- enabled = true;
- collisionBody.setPaintColors(Color.RED, Color.WHITE, 0x6400ff00);
- } else if (offTrigger.collisionBody.contains(other.position)) {
- enabled = false;
- collisionBody.setPaintColors(Color.RED, Color.LTGRAY, 0x6400ff00);
- }
-
- // Handle the actual collision.
- if (enabled) {
- return super.resolveCollision(other, deltaMs);
- }
- return false;
- }
-
- @Override
- public JSONObject toJSON() throws JSONException {
- JSONObject json = super.toJSON();
- json.put(ON_TRIGGER_KEY, onTrigger.toJSON());
- json.put(OFF_TRIGGER_KEY, offTrigger.toJSON());
- return json;
- }
-
- public static ToggleableBounceActor fromJSON(JSONObject json) throws JSONException {
- return new ToggleableBounceActor(Polygon.fromJSON(json.getJSONArray(POLYGON_KEY)),
- CollisionActor.fromJSON(json.getJSONObject(ON_TRIGGER_KEY)),
- CollisionActor.fromJSON(json.getJSONObject(OFF_TRIGGER_KEY)));
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/Touchable.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/Touchable.java
deleted file mode 100644
index ae23312ce..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/Touchable.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-
-/**
- * An actor which can be touched in the level editor.
- */
-public interface Touchable {
- boolean canHandleTouchAt(Vector2D worldCoords, float cameraScale);
- void startTouchAt(Vector2D worldCoords, float cameraScale);
-
- /**
- * Handle a move event internally.
- * @param delta the movement vector
- * @return true if the move event has been handled, false otherwise.
- */
- boolean handleMoveEvent(Vector2D delta);
-
- /**
- * Handle a long press internally.
- * @return true if the long press has been handled, false otherwise.
- */
- boolean handleLongPress();
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/WatermelonActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/WatermelonActor.java
deleted file mode 100644
index 70de7b965..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/WatermelonActor.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.WatermelonBaseActor;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-import com.google.android.apps.santatracker.doodles.shared.physics.Util;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * An actor for a watermelon which rolls down the screen.
- */
-public class WatermelonActor extends BounceActor {
- private static final float SCALE = 2.0f;
- private static final float LEG_WIDTH = 50;
-
- public long ageMs;
- public long delayMs;
- private WatermelonBaseActor melonActor;
-
- private Vector2D spriteOffset = Vector2D.get(-LEG_WIDTH, 0);
-
- public WatermelonActor(WatermelonBaseActor actor, Polygon collisionBody) {
- super(collisionBody);
- this.melonActor = actor;
- }
-
- public void setVelocity(Vector2D velocity) {
- this.velocity.set(velocity);
- melonActor.velocity.set(velocity);
- }
-
- public void setPosition(Vector2D position) {
- float collisionBodyOffsetX = position.x - this.position.x;
- float collisionBodyOffsetY = position.y - this.position.y;
- collisionBody.move(collisionBodyOffsetX, collisionBodyOffsetY);
-
- this.position.set(position).add(spriteOffset);
- melonActor.position.set(position)
- .add(spriteOffset.scale(0.5f))
- .add(melonActor.bodySprite.frameWidth, melonActor.bodySprite.frameHeight);
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
- melonActor.update(deltaMs);
- ageMs += deltaMs;
- }
-
- @Override
- public void draw(Canvas canvas) {
- melonActor.draw(canvas);
- super.draw(canvas);
- }
-
- @Override
- public boolean resolveCollision(Actor other, float deltaMs) {
- // Just do bounding box checking since the watermelon's hitbox is a rectangle.
- if (Util.pointIsWithinBounds(collisionBody.min, collisionBody.max, other.position)) {
- return false;
- }
- return false;
- }
-
- public static WatermelonActor create(Vector2D position, Resources resources) {
- WatermelonBaseActor actor = new WatermelonBaseActor(resources);
- Polygon collisionBody = getCollisionPolygon(position,
- actor.bodySprite.frameWidth * SCALE, 3 * actor.bodySprite.frameHeight / 4 * SCALE);
- actor.scale = SCALE;
-
- WatermelonActor watermelon = new WatermelonActor(actor, collisionBody);
- watermelon.setPosition(position);
- return watermelon;
- }
-
- private static Polygon getCollisionPolygon(Vector2D spritePosition, float width, float height) {
- List vertices = new ArrayList<>();
- // top left
- vertices.add(Vector2D.get(spritePosition.x + LEG_WIDTH, spritePosition.y));
- // top right
- vertices.add(Vector2D.get(spritePosition.x + width, spritePosition.y));
- // bottom right
- vertices.add(
- Vector2D.get(spritePosition.x + width, spritePosition.y + height));
- // bottom left
- vertices.add(
- Vector2D.get(spritePosition.x + LEG_WIDTH, spritePosition.y + height));
- return new Polygon(vertices);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/WatermelonEmitter.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/WatermelonEmitter.java
deleted file mode 100644
index d766ad258..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/tilt/WatermelonEmitter.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.tilt;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
-import java.util.ArrayList;
-import java.util.List;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * An emitter of watermelons which can run over the golf ball.
- */
-public class WatermelonEmitter extends CollisionActor {
- public static final String TYPE = "Watermelon emitter";
-
- public static final int NUM_WATERMELONS = 3;
- public static final float WATERMELON_VELOCITY = 750.0f;
- public static final long LIFETIME_MS = 7500;
-
- private List watermelons = new ArrayList<>();
- private long elapsedMs;
-
- public WatermelonEmitter(Polygon collisionBody, Resources resources) {
- super(collisionBody);
- this.zIndex = 1;
- collisionBody.setPaintColors(0xffff6385, 0xff6cfc9b, 0x6400ff00);
-
- for (int i = 0; i < NUM_WATERMELONS; i++) {
- WatermelonActor watermelon = WatermelonActor.create(collisionBody.min, resources);
- watermelon.setVelocity(Vector2D.get(0, WATERMELON_VELOCITY));
- watermelon.delayMs = (i + 1) * LIFETIME_MS / NUM_WATERMELONS;
- watermelons.add(watermelon);
- }
- }
-
- @Override
- public void update(float deltaMs) {
- elapsedMs += deltaMs;
- for (int i = 0; i < watermelons.size(); i++) {
- WatermelonActor watermelon = watermelons.get(i);
- if (elapsedMs > watermelon.delayMs) {
- watermelon.update(deltaMs);
- if (watermelon.ageMs > LIFETIME_MS) {
- reset(watermelon);
- }
- }
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- for (int i = 0; i < watermelons.size(); i++) {
- WatermelonActor watermelon = watermelons.get(i);
- watermelon.draw(canvas);
- }
- }
-
- @Override
- public boolean resolveCollision(Actor other, float deltaMs) {
- for (int i = 0; i < watermelons.size(); i++) {
- WatermelonActor watermelon = watermelons.get(i);
- if (watermelon.resolveCollision(other, deltaMs)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public String getType() {
- return WatermelonEmitter.TYPE;
- }
-
- private void reset(WatermelonActor actor) {
- actor.setPosition(collisionBody.min);
- actor.ageMs = 0;
- }
-
- public static WatermelonEmitter fromJSON(JSONObject json, Context context) throws JSONException {
- return new WatermelonEmitter(
- Polygon.fromJSON(json.getJSONArray(POLYGON_KEY)), context.getResources());
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloActor.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloActor.java
deleted file mode 100644
index 35530ef60..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloActor.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.waterpolo;
-
-
-import android.graphics.Canvas;
-import android.util.Log;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.ActorTween.Callback;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite.AnimatedSpriteListener;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.shared.physics.Util;
-import com.google.android.apps.santatracker.doodles.tilt.ColoredRectangleActor;
-import java.util.HashMap;
-
-/**
- * Actor for both the player and the opponent in the water polo game.
- * The debug position marker is omitted.
- */
-public class WaterPoloActor extends Actor {
- private static final String TAG = WaterPoloActor.class.getSimpleName();
-
- /**
- * Labels for all the different sprites which make up the actor.
- */
- public enum WaterPoloActorPart {
- BodyIdle,
- BodyIdleNoBall, // Used at end of game.
- BodyEntrance, // Used when character enters the game.
- BodyLeft,
- BodyRight,
- BodyBlock,
- BodyThrow,
- BodyPickUpBall, // Catch a new ball from the slide.
- }
-
- private static final int RELEASE_THROW_FRAME = 2;
-
- HashMap sprites;
- WaterPoloActorPart body;
- AnimatedSprite currentSprite;
-
- float collisionWidthUnscaled;
- float collisionHeightUnscaled;
- ColoredRectangleActor collisionBox;
- Callback shotBlockCallback;
-
- public WaterPoloActor() {
- sprites = new HashMap<>();
- body = WaterPoloActorPart.BodyIdle;
- }
-
- @Override
- public void update(float deltaMs) {
- super.update(deltaMs);
- if (currentSprite == null) {
- return;
- }
- currentSprite.update(deltaMs);
- if (collisionBox != null) {
- collisionBox.dimens =
- Vector2D.get(collisionWidthUnscaled * scale, collisionHeightUnscaled * scale);
-
- // The collision box should be centered on the sprite.
- float centerX = position.x - (currentSprite.anchor.x) + (currentSprite.frameWidth * 0.5f);
- float centerY = position.y - (currentSprite.anchor.y) + (currentSprite.frameHeight * 0.5f);
- collisionBox.position.set(centerX - collisionBox.dimens.x * 0.5f,
- centerY - collisionBox.dimens.y * 0.5f);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- if (currentSprite == null) {
- Log.e(TAG, "Body part null: " + body);
- return;
- }
- currentSprite.setPosition(position.x, position.y);
- currentSprite.setScale(scale, scale);
- currentSprite.setHidden(hidden);
- currentSprite.draw(canvas);
- }
-
- // Add a sprite to the actor for the given part. You need to call this for a part before
- // trying to use that part. Offsets are measured from the actor's position
- public void addSprite(WaterPoloActorPart part, int xOffset, int yOffset, AnimatedSprite sprite) {
- // After picking up the ball, go straight to idle.
- if (part == WaterPoloActorPart.BodyPickUpBall) {
- sprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onLoop() {
- idle();
- }
- });
- }
-
- // This makes sure the sprite scales from self.position
- sprite.setAnchor(-xOffset, yOffset);
-
- if (part != body) {
- sprite.setHidden(true);
- }
- sprites.put(part, sprite);
- }
-
- // The collision box position, relative to self.position, is hardcoded in update, because the
- // opponents all have the same position.
- public void setCollisionBox(float width, float height) {
- collisionWidthUnscaled = width;
- collisionHeightUnscaled = height;
- collisionBox = new ColoredRectangleActor(
- Vector2D.get(0, 0),
- Vector2D.get(0, 0),
- ColoredRectangleActor.UNSPECIFIED);
- }
-
- // Returns YES iff (x, y) lies within the actor's collision box.
- public boolean canBlock(float x, float y) {
- if (hidden || body == WaterPoloActorPart.BodyEntrance || collisionBox == null) {
- return false;
- }
- Vector2D worldCoords = Vector2D.get(x, y);
- Vector2D lowerRight = Vector2D.get(collisionBox.position).add(collisionBox.dimens);
- return Util.pointIsWithinBounds(collisionBox.position, lowerRight, worldCoords);
- }
-
- public void idle() {
- setBodyUnchecked(WaterPoloActorPart.BodyIdle);
- }
-
- public void idleNoBall() {
- setBodyUnchecked(WaterPoloActorPart.BodyIdleNoBall);
- }
-
- public void pickUpBall() {
- setBodyUnchecked(WaterPoloActorPart.BodyPickUpBall);
- }
-
- public void swimLeft() {
- if (body == WaterPoloActorPart.BodyBlock) {
- AnimatedSprite sprite = sprites.get(WaterPoloActorPart.BodyBlock);
- sprite.clearListeners();
- sprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onLoop() {
- setBodyUnchecked(WaterPoloActorPart.BodyLeft);
- }
-
- @Override
- public void onFrame(int index) {
- if (index == 3) {
- if (shotBlockCallback != null) {
- shotBlockCallback.call();
- shotBlockCallback = null;
- }
- }
- }
- });
- } else {
- setBodyUnchecked(WaterPoloActorPart.BodyLeft);
- }
- }
-
- public void swimRight() {
- if (body == WaterPoloActorPart.BodyBlock) {
- AnimatedSprite sprite = sprites.get(WaterPoloActorPart.BodyBlock);
- sprite.clearListeners();
- sprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onLoop() {
- setBodyUnchecked(WaterPoloActorPart.BodyRight);
- }
-
- @Override
- public void onFrame(int index) {
- if (index == 3) {
- if (shotBlockCallback != null) {
- shotBlockCallback.call();
- shotBlockCallback = null;
- }
- }
- }
- });
- } else {
- setBodyUnchecked(WaterPoloActorPart.BodyRight);
- }
- }
-
- // The callback gets called when the actor is at the top of the blocking jump (so the game
- // can deflect the ball at that instant).
- public void blockShot(final Callback callback) {
- AnimatedSprite sprite = sprites.get(WaterPoloActorPart.BodyBlock);
- sprite.clearListeners();
- final WaterPoloActorPart previousBody = body;
- shotBlockCallback = callback;
- sprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onLoop() {
- setBodyUnchecked(previousBody);
- }
-
- @Override
- public void onFrame(int index) {
- if (index == 3) {
- if (shotBlockCallback != null) {
- shotBlockCallback.call();
- shotBlockCallback = null;
- }
- }
- }
- });
- setBodyUnchecked(WaterPoloActorPart.BodyBlock);
- }
-
- // releaseCallback gets called when the actor releases the ball (so the game can swap in the real
- // ball). endCallback will be called when the actor is done throwing and starts picking up another
- // ball (so the game can start the grapeOnSlide animation at the correct time).
- public void throwBall(final Callback releaseCallback, final Callback endCallback) {
- AnimatedSprite sprite = sprites.get(WaterPoloActorPart.BodyThrow);
- sprite.clearListeners();
- sprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onLoop() {
- pickUpBall();
- endCallback.call();
- }
-
- @Override
- public void onFrame(int index) {
- if (index == RELEASE_THROW_FRAME) {
- releaseCallback.call();
- }
- }
- });
- setBodyUnchecked(WaterPoloActorPart.BodyThrow);
- }
-
- // Callback is called at the end of the animation, when actor is ready to play.
- public void enter(final Callback callback) {
- AnimatedSprite sprite = sprites.get(WaterPoloActorPart.BodyEntrance);
- sprite.clearListeners();
- sprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onLoop() {
- callback.call();
- }
- });
- setBodyUnchecked(WaterPoloActorPart.BodyEntrance);
- }
-
- private void setBodyUnchecked(WaterPoloActorPart part) {
- AnimatedSprite newSprite = sprites.get(part);
- if (newSprite == null) {
- Log.e(TAG, "Error: sprite " + part + " not loaded.");
- assert(false);
- }
- newSprite.setFrameIndex(0);
- body = part;
- update(0);
-
- currentSprite = newSprite;
- }
-
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloFragment.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloFragment.java
deleted file mode 100644
index e40d7a492..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloFragment.java
+++ /dev/null
@@ -1,372 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.waterpolo;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.AndroidUtils;
-import com.google.android.apps.santatracker.doodles.shared.DoodleConfig;
-import com.google.android.apps.santatracker.doodles.shared.EventBus;
-import com.google.android.apps.santatracker.doodles.shared.EventBus.EventBusListener;
-import com.google.android.apps.santatracker.doodles.shared.GameFragment;
-import com.google.android.apps.santatracker.doodles.shared.GameType;
-import com.google.android.apps.santatracker.doodles.shared.HistoryManager;
-import com.google.android.apps.santatracker.doodles.shared.PauseView;
-import com.google.android.apps.santatracker.doodles.shared.PineappleLogger;
-import com.google.android.apps.santatracker.doodles.shared.ScoreView;
-import com.google.android.apps.santatracker.doodles.shared.sound.SoundManager;
-import com.google.android.apps.santatracker.doodles.waterpolo.WaterPoloModel.State;
-
-import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.WATERPOLO_GAME_TYPE;
-
-/**
- * Activity for the ported water polo game.
- * Manages input & threads, delegates to WaterPoloModel & WaterPoloView for the rest.
- */
-public class WaterPoloFragment extends GameFragment implements EventBusListener {
- private static final String TAG = WaterPoloFragment.class.getSimpleName();
-
- private WaterPoloView gameView;
- private WaterPoloModel model;
- private WaterPoloGestureDetector gestureDetector;
- private EventBus eventBus;
- private boolean mIsGameOver = false;
-
- public WaterPoloFragment() {
- super();
- }
-
- public WaterPoloFragment(Context context, DoodleConfig doodleConfig,
- PineappleLogger logger) {
- super(context, doodleConfig, logger);
- }
-
- @Override
- protected ScoreView getScoreView() {
- WaterPoloScoreView scoreView = new WaterPoloScoreView(context, this);
- scoreView.setDoodleConfig(doodleConfig);
- scoreView.setLogger(logger);
- scoreView.setListener(levelFinishedListener);
- scoreView.setModel(model);
- return scoreView;
- }
-
- @Override
- protected PauseView getPauseView() {
- WaterPoloPauseView pauseView = new WaterPoloPauseView(context);
- pauseView.setDoodleConfig(doodleConfig);
- pauseView.setLogger(logger);
- pauseView.setListener(gamePausedListener);
-
- return pauseView;
- }
-
- @Override
- public void update(float deltaMs) {
- if (!isPaused) {
- model.update((long) deltaMs);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- if (context == null) {
- return null;
- }
- eventBus = EventBus.getInstance();
- historyManager = new HistoryManager(context, new HistoryManager.HistoryListener() {
- @Override
- public void onFinishedLoading() {
- }
-
- @Override
- public void onFinishedSaving() {
- }
- });
-
- wrapper = new FrameLayout(context);
-
- titleView = getTitleView(R.drawable.present_throw_loadingscreen, R.string.presentthrow);
- wrapper.addView(titleView);
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- loadGame();
- }
- });
- return wrapper;
- }
-
- @Override
- protected void firstPassLoadOnUiThread() {
- wrapper.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return onTouchEvent(event);
- }
- });
- final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
- // Add game view & finish view.
- gameView = new WaterPoloView(context);
- wrapper.addView(gameView, 0, lp);
-
- pauseView = getPauseView();
- wrapper.addView(pauseView, 1);
- scoreView = getScoreView();
- wrapper.addView(scoreView, 1);
- }
-
- @Override
- protected void secondPassLoadOnBackgroundThread() {
- super.secondPassLoadOnBackgroundThread();
- eventBus.register(this);
- model = new WaterPoloModel(context.getResources(),
- getActivity().getApplicationContext());
- gameView.setModel(model);
- if(scoreView instanceof WaterPoloScoreView) {
- ((WaterPoloScoreView)scoreView).setModel(model);
- }
- }
-
- @Override
- protected void finalPassLoadOnUiThread() {
- gestureDetector = new WaterPoloGestureDetector() {
- @Override
- public void onPan(float radians) {
- model.onFling(radians);
- }
- };
-
- soundManager = SoundManager.getInstance();
- loadSounds();
-
- onFinishedLoading();
- startHandlers();
- }
-
- @Override
- protected void replay() {
- super.replay();
- model.reset(false);
- }
-
- @Override
- protected void onDestroyHelper() {
- if (gameView != null) {
- gameView.setModel(null);
- }
- model = null;
- }
-
- @Override
- public void onEventReceived(int type, Object data) {
- if (isDestroyed) {
- return;
- }
- if (type == EventBus.SCORE_CHANGED) {
- final Integer score = (Integer) data;
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- scoreView.updateCurrentScore(
- AndroidUtils.getText(context.getResources(), R.string.waterpolo_score, score),
- true);
- }
- });
- } else if (type == EventBus.PLAY_SOUND && soundManager != null) {
- int resId = (int) data;
- soundManager.play(resId);
- } else if (type == EventBus.PAUSE_SOUND && soundManager != null) {
- int resId = (int) data;
- soundManager.pause(resId);
- } else if (type == EventBus.MUTE_SOUNDS && soundManager != null) {
- boolean shouldMute = (boolean) data;
- if (shouldMute) {
- soundManager.mute();
- } else {
- soundManager.unmute();
- }
- } else if (type == EventBus.GAME_STATE_CHANGED) {
- mIsGameOver = false;
- WaterPoloModel.State state = (WaterPoloModel.State) data;
- if (state == State.WAITING) {
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- hideTitle();
- pauseView.showPauseButton();
- }
- });
- } else if (state == State.GAME_OVER) {
- mIsGameOver = true;
- wrapper.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (model == null) {
- return;
- }
- final int currentScore = model.score;
- boolean shouldSave = false;
- Integer bestStarCount = historyManager.getBestStarCount(GameType.WATER_POLO);
- int starCount = getStarCount();
- if (bestStarCount == null || bestStarCount < starCount) {
- historyManager.setBestStarCount(GameType.WATER_POLO, starCount);
- shouldSave = true;
- }
- Double temp = historyManager.getBestScore(GameType.WATER_POLO);
- int bestScore = temp == null ? -1 : (int) Math.round(temp);
- if (currentScore > bestScore) {
- bestScore = currentScore;
- historyManager.setBestScore(GameType.WATER_POLO, currentScore);
- shouldSave = true;
- }
- if (shouldSave) {
- historyManager.save();
- }
-
- final int finalStarCount = starCount;
- final int finalBestScore = bestScore;
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < finalStarCount; i++) {
- scoreView.addStar();
- }
- pauseView.hidePauseButton();
- scoreView.updateBestScore(
- AndroidUtils.getText(
- context.getResources(), R.string.waterpolo_score, finalBestScore));
- scoreView.setShareDrawable(getShareImageDrawable(finalStarCount));
-
- scoreView.animateToEndState();
- }
- });
- }
- }, 2000);
- }
- } else if (type == EventBus.GAME_LOADED) {
- long loadTimeMs = (long) data;
- model.titleDurationMs = Math.max(0, model.titleDurationMs - loadTimeMs);
- Log.d(TAG, "Waiting " + model.titleDurationMs + "ms and then hiding title.");
- }
- }
-
- private Drawable getShareImageDrawable(int starCount) {
- return ContextCompat.getDrawable(getActivity(), R.drawable.winner);
- }
-
- @Override
- protected void loadSounds() {
- super.loadSounds();
- soundManager.loadShortSound(context, R.raw.swimming_ice_splash_a);
- soundManager.loadShortSound(context, R.raw.present_throw_character_appear);
- soundManager.loadShortSound(context, R.raw.tennis_eliminate);
- soundManager.loadShortSound(context, R.raw.present_throw_block);
- soundManager.loadShortSound(context, R.raw.present_throw_goal);
- soundManager.loadShortSound(context, R.raw.present_throw_throw);
- }
-
- public boolean onTouchEvent(MotionEvent event) {
- if (gestureDetector != null) {
- gestureDetector.onTouchEvent(event);
- return true;
- }
- return false;
- }
-
- @Override
- protected void resume() {
- super.resume();
- if (uiRefreshHandler != null) {
- uiRefreshHandler.start(gameView);
- }
- }
-
- @Override
- protected String getGameType() {
- return WATERPOLO_GAME_TYPE;
- }
-
- @Override
- protected float getScore() {
- return model.score;
- }
-
- @Override
- protected int getShareImageId() {
- return getStarCount();
- }
-
- private int getStarCount() {
- int starCount = 0;
- if (model.score > WaterPoloModel.ONE_STAR_THRESHOLD) {
- starCount++;
- }
- if (model.score > WaterPoloModel.TWO_STAR_THRESHOLD) {
- starCount++;
- }
- if (model.score > WaterPoloModel.THREE_STAR_THRESHOLD) {
- starCount++;
- }
- return starCount;
- }
-
- /**
- * The gesture detector for the water polo game. Supports the fling gesture.
- */
- private abstract class WaterPoloGestureDetector {
- float downX;
- float downY;
-
- void onTouchEvent(MotionEvent event) {
- switch(event.getAction()) {
- case (MotionEvent.ACTION_DOWN):
- downX = event.getX();
- downY = event.getY();
-
- break;
- case (MotionEvent.ACTION_UP):
- case (MotionEvent.ACTION_OUTSIDE):
- float velocityX = event.getX() - downX;
- float velocityY = event.getY() - downY;
-
- onPan((float) Math.atan2(velocityY, velocityX));
- break;
- }
- }
-
- public abstract void onPan(float radians);
- }
-
- @Override
- public boolean isGameOver() {
- return mIsGameOver;
- }
-}
-
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloModel.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloModel.java
deleted file mode 100644
index 660ef883b..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloModel.java
+++ /dev/null
@@ -1,944 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.waterpolo;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Paint;
-import android.os.Vibrator;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-import com.google.android.apps.santatracker.doodles.shared.ActorHelper;
-import com.google.android.apps.santatracker.doodles.shared.ActorTween;
-import com.google.android.apps.santatracker.doodles.shared.ActorTween.Callback;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite;
-import com.google.android.apps.santatracker.doodles.shared.AnimatedSprite.AnimatedSpriteListener;
-import com.google.android.apps.santatracker.doodles.shared.BallActor;
-import com.google.android.apps.santatracker.doodles.shared.CameraShake;
-import com.google.android.apps.santatracker.doodles.shared.EmptyTween;
-import com.google.android.apps.santatracker.doodles.shared.EventBus;
-import com.google.android.apps.santatracker.doodles.shared.GameFragment;
-import com.google.android.apps.santatracker.doodles.shared.Interpolator;
-import com.google.android.apps.santatracker.doodles.shared.RectangularInstructionActor;
-import com.google.android.apps.santatracker.doodles.shared.SpriteActor;
-import com.google.android.apps.santatracker.doodles.shared.Sprites;
-import com.google.android.apps.santatracker.doodles.shared.TextActor;
-import com.google.android.apps.santatracker.doodles.shared.Tween;
-import com.google.android.apps.santatracker.doodles.shared.TweenManager;
-import com.google.android.apps.santatracker.doodles.shared.Vector2D;
-import com.google.android.apps.santatracker.doodles.tilt.ColoredRectangleActor;
-import com.google.android.apps.santatracker.doodles.waterpolo.WaterPoloActor.WaterPoloActorPart;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Model for the Water Polo game.
- */
-public class WaterPoloModel {
-
- /**
- * A grape that the apple throws.
- */
- class GrapeActor extends BallActor {
- public boolean shotBlocked;
- public int bounces;
- GrapeActor(AnimatedSprite ballSprite) {
- super(null, ballSprite, null, 3);
- bounces = 0;
- shotBlocked = false;
- }
- }
-
- /**
- * High-level phases of the game are controlled by a state machine which uses these states.
- */
- enum State {
- TITLE,
- WAITING,
- PLAYING,
- GAME_OVER
- }
-
- private static final String TAG = WaterPoloModel.class.getSimpleName();
-
- private static final float SHAKE_FALLOFF = 0.9f;
- private static final int VIBRATION_SMALL = 40;
-
- public static final int WATER_POLO_HEIGHT = 960;
- public static final int WATER_POLO_WIDTH = 540;
-
- public static final int ONE_STAR_THRESHOLD = 1;
- public static final int TWO_STAR_THRESHOLD = 10;
- public static final int THREE_STAR_THRESHOLD = 20;
-
- private static final float TIME_LEFT_TEXT_X = WATER_POLO_WIDTH * 0.5f;
- private static final float TIME_LEFT_TEXT_Y = 12;
- private static final float TIME_LEFT_TEXT_SCALE = 3.2f;
- private static final String TIME_LEFT_UNDER_TEXT = "88:88";
- private static final int TIME_LEFT_TEXT_RGB = 0xFFFFBD2E;
- private static final int TIME_LEFT_TEXT_GLOW_RGB = 0x88FF9A2E;
- private static final int TIME_LEFT_UNDER_RGB = 0xFF3B200D;
-
- private static final float POINT_TEXT_ANIMATION_TIME = 1.6f;
-
- private static final int POINT_MINUS_TEXT_RGB = 0xffd61e1e;
- private static final int POINT_PLUS_TEXT_RGB = 0xff4dab1f;
-
- // Currently throw delay is not supported. The constant and related variables are left in to
- // facilitate prototyping in case throw delay would be supported in the future.
- private static final float THROW_DELAY_SECONDS = 0.4f;
- private static final float TOTAL_TIME = 30f;
- private static final int BALL_SPEED = 1300;
-
- private static final int OPPONENT_ONE_ENTRANCE_THRESHOLD = 1;
- private static final int OPPONENT_TWO_ENTRANCE_THRESHOLD = 4;
- private static final int OPPONENT_THREE_ENTRANCE_THRESHOLD = 10;
-
- private static final int OPPONENT_ONE_SPEED = 60;
- private static final int OPPONENT_TWO_SPEED = 120;
- private static final int OPPONENT_THREE_SPEED = 170;
-
- private static final int MS_BETWEEN_TARGET_FLASHES = 1000;
-
- public CameraShake cameraShake;
- public List actors;
- private List effects;
-
- private Resources resources;
- private EventBus eventBus;
- private final Vibrator vibrator;
- private TweenManager tweenManager;
-
- private WaterPoloActor player;
-
- private WaterPoloActor opponentOne;
- private WaterPoloActor opponentTwo;
- private WaterPoloActor opponentThree;
-
- private ColoredRectangleActor timeLeftFrameBorder;
- private ColoredRectangleActor timeLeftFrame;
- private TextActor timeLeftText;
- private TextActor timeLeftTextGlow;
- private TextActor timeLeftUnder;
-
- private List balls;
- private SpriteActor slideBack;
- private SpriteActor targetLeft;
- private SpriteActor targetMiddle;
- private SpriteActor targetRight;
- private SpriteActor goal;
-
- private static final float GOAL_BOX_X = 108;
- private static final float GOAL_BOX_Y = 75;
- private static final float GOAL_BOX_WIDTH = 333;
- private static final float GOAL_BOX_HEIGHT = 98;
-
- RectangularInstructionActor instructions;
-
- public State state;
- public int score;
- public int ballsThrown;
- public long titleDurationMs = GameFragment.TITLE_DURATION_MS;
- private float time;
- private float msTillNextTargetPulse;
- private boolean scoredAtLeastOneShot;
- private boolean canThrow;
-
- private boolean didReset;
-
- public WaterPoloModel(Resources resources, Context context) {
- this.resources = resources;
- actors = new ArrayList<>();
- effects = new ArrayList<>();
- balls = new ArrayList<>();
- cameraShake = new CameraShake();
- actors.add(cameraShake);
- tweenManager = new TweenManager();
- eventBus = EventBus.getInstance();
- vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- createActors();
- reset(true);
- }
-
- private void createActors() {
- Actor pool = actorWithIds(Sprites.present_throw_floor);
- pool.zIndex = -3;
-
- // The pool image is 780x960, but the center 540x960 of that image is what corresponds
- // to the game area, so offset the image to the left. The edges only show on screens with aspect
- // ratios wider than 9:16.
- pool.position.x = -120;
- actors.add(pool);
-
- goal = actorWithIds(Sprites.present_throw_santabag);
- goal.position.set(-14, 72);
- goal.zIndex = -4;
- goal.sprite.setLoop(false);
- goal.sprite.setPaused(true);
- goal.sprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onFinished() {
- goal.sprite.setFrameIndex(0);
- goal.sprite.setPaused(true);
- }
- });
- actors.add(goal);
-
- slideBack = actorWithIds(Sprites.present_throw_elfbag);
- slideBack.zIndex = 4;
- actors.add(slideBack);
-
- moveSlide(0);
-
- timeLeftFrame = new ColoredRectangleActor(
- Vector2D.get(WATER_POLO_WIDTH * 0.35f, TIME_LEFT_TEXT_Y - 6),
- Vector2D.get(WATER_POLO_WIDTH * 0.3f, 50));
- timeLeftFrame.setColor(0xff000000);
- timeLeftFrame.zIndex = 4;
- actors.add(timeLeftFrame);
-
- timeLeftFrameBorder = new ColoredRectangleActor(
- Vector2D.get(WATER_POLO_WIDTH * 0.35f, TIME_LEFT_TEXT_Y - 6),
- Vector2D.get(WATER_POLO_WIDTH * 0.3f, 50));
- timeLeftFrameBorder.setStyle(Paint.Style.STROKE);
- timeLeftFrameBorder.setStrokeWidth(5);
- timeLeftFrameBorder.setColor(0xff555555);
- timeLeftFrameBorder.zIndex = 4;
- actors.add(timeLeftFrameBorder);
-
- timeLeftUnder = new TextActor(TIME_LEFT_UNDER_TEXT);
- timeLeftUnder.position.set(TIME_LEFT_TEXT_X, TIME_LEFT_TEXT_Y);
- timeLeftUnder.scale = TIME_LEFT_TEXT_SCALE;
- timeLeftUnder.setBold(true);
- timeLeftUnder.setFont(resources.getAssets(), "dseg7.ttf");
- timeLeftUnder.setColor(TIME_LEFT_UNDER_RGB);
- timeLeftUnder.alignCenter();
- timeLeftUnder.zIndex = 4;
- actors.add(timeLeftUnder);
-
- timeLeftTextGlow = new TextActor("00:30");
- timeLeftTextGlow.position.set(TIME_LEFT_TEXT_X, TIME_LEFT_TEXT_Y);
- timeLeftTextGlow.scale = TIME_LEFT_TEXT_SCALE;
- timeLeftTextGlow.setBold(true);
- timeLeftTextGlow.enableBlur(0.6f);
- timeLeftTextGlow.setFont(resources.getAssets(), "dseg7.ttf");
- timeLeftTextGlow.setColor(TIME_LEFT_TEXT_GLOW_RGB);
- timeLeftTextGlow.alignCenter();
- timeLeftTextGlow.zIndex = 5;
- actors.add(timeLeftTextGlow);
-
- timeLeftText = new TextActor("00:30");
- timeLeftText.position.set(TIME_LEFT_TEXT_X, TIME_LEFT_TEXT_Y);
- timeLeftText.scale = TIME_LEFT_TEXT_SCALE;
- timeLeftText.setBold(true);
- timeLeftText.setFont(resources.getAssets(), "dseg7.ttf");
- timeLeftText.setColor(TIME_LEFT_TEXT_RGB);
- timeLeftText.alignCenter();
- timeLeftText.zIndex = 6;
- actors.add(timeLeftText);
-
- targetLeft = createTarget(166, 128);
- actors.add(targetLeft);
-
- targetMiddle = createTarget(273, 128);
- actors.add(targetMiddle);
-
- targetRight = createTarget(380, 128);
- actors.add(targetRight);
-
- // TODO: Change block sprites
- opponentOne = createOpponent(0.8f,
- Sprites.present_throw_def_orange_left,
- Sprites.present_throw_def_orange_right,
- Sprites.present_throw_def_orange_emerge,
- Sprites.present_throw_def_orange_blocking);
- actors.add(opponentOne);
-
- opponentTwo = createOpponent(0.9f,
- Sprites.present_throw_def_green_left,
- Sprites.present_throw_def_green_right,
- Sprites.present_throw_def_green_emerge,
- Sprites.present_throw_def_green_blocking);
- actors.add(opponentTwo);
-
- opponentThree = createOpponent(1.0f,
- Sprites.present_throw_def_red_left,
- Sprites.present_throw_def_red_right,
- Sprites.present_throw_def_red_emerge,
- Sprites.present_throw_def_red_blocking);
- actors.add(opponentThree);
-
- player = new WaterPoloActor();
- player.addSprite(WaterPoloActorPart.BodyIdle, -84, 180,
- spriteWithIds(Sprites.present_throw_idle));
- player.addSprite(WaterPoloActorPart.BodyThrow, -84, 180,
- spriteWithIds(Sprites.present_throw_throwing));
- player.addSprite(WaterPoloActorPart.BodyPickUpBall, -84, 180,
- spriteWithIds(Sprites.present_throw_reloading));
- player.addSprite(WaterPoloActorPart.BodyIdleNoBall, -84, 180,
- spriteWithIds(Sprites.present_throw_celebrate, 2));
-
- actors.add(player);
-
- AnimatedSprite diagram = spriteWithIds(Sprites.present_throw_tutorials);
- diagram.setFPS(7);
-
- instructions = new RectangularInstructionActor(resources, diagram);
- instructions.hidden = true;
- instructions.scale = 0.6f;
- instructions.position.set(WATER_POLO_WIDTH * 0.5f - instructions.getScaledWidth() / 2,
- WATER_POLO_HEIGHT * 0.46f - instructions.getScaledHeight() / 2f);
- actors.add(instructions);
- }
-
- SpriteActor createTarget(float x, float y) {
- SpriteActor target = actorWithIds(Sprites.waterpolo_target);
- target.sprite.setAnchor(target.sprite.frameWidth / 2, target.sprite.frameHeight / 2);
- target.position.set(x, y);
- target.hidden = true;
- return target;
- }
-
- WaterPoloActor createOpponent(float scale,
- int[] leftSprite, int[] rightSprite,
- int[] emergeSprite, int[] blockSprite) {
- WaterPoloActor opponent = new WaterPoloActor();
-
- opponent.addSprite(WaterPoloActorPart.BodyEntrance, -45, 142,
- spriteWithIds(emergeSprite, 12));
- opponent.addSprite(WaterPoloActorPart.BodyLeft, -45, 142,
- spriteWithIds(leftSprite, 6));
- opponent.addSprite(WaterPoloActorPart.BodyRight, -45, 142,
- spriteWithIds(rightSprite, 6));
- opponent.addSprite(WaterPoloActorPart.BodyBlock, -45, 142,
- spriteWithIds(blockSprite));
- opponent.scale = scale;
- opponent.setCollisionBox(100, 90);
- return opponent;
- }
-
- /**
- * Moves the slide left / right. Used to attach the slide to the side of the screen on different
- * aspect ratio screens.
- */
- public void moveSlide(float slideOffsetX) {
- slideBack.position.set(300 + slideOffsetX, 760);
- }
-
- // Put everything back to the beginning state.
- // Used at start of game & also if user clicks replay.
- public void reset(boolean firstPlay) {
- tweenManager.removeAll();
- if (firstPlay) {
- setState(State.TITLE);
- } else {
- setState(State.WAITING);
- }
-
- // Remove all temporary effects
- for (int i = effects.size() - 1; i >= 0; i--) {
- Actor effect = effects.get(i);
- actors.remove(effect);
- effects.remove(effect);
- }
-
- // Remove all balls
- for (int i = balls.size() - 1; i >= 0; i--) {
- BallActor ball = balls.get(i);
- actors.remove(ball);
- balls.remove(ball);
- }
-
- score = 0;
- scoredAtLeastOneShot = false;
- ballsThrown = 0;
- eventBus.sendEvent(EventBus.SCORE_CHANGED, score);
-
- // No opponents at start of game (give them 1 easy point to re-inforce that they are supposed
- // to get goals, not attack the goalie).
- opponentOne.hidden = true;
- opponentTwo.hidden = true;
- opponentThree.hidden = true;
-
- msTillNextTargetPulse = MS_BETWEEN_TARGET_FLASHES;
-
- // Y positions picked so opponents look evenly spaced when in perspective.
- // X positions picked to make opponents come up on the left side (because they swim right
- // first, so coming up on the left side gives room to swim) but not all at the same position
- // (staggered looks better).
- opponentOne.position.set(171, 300);
- opponentTwo.position.set(271, 440);
- opponentThree.position.set(171, 600);
- player.position.set(198, 845);
- player.idle();
-
- canThrow = true;
- maybePickUpAnotherBall();
- if (firstPlay) {
- // TODO: If you swipe before the instructions show, they still show.
- // Fix this by adding a state machine like the android games have.
- tweenManager.add(new EmptyTween(1.3f) {
- @Override
- protected void onFinish() {
- if (ballsThrown == 0) {
- instructions.show();
- }
- }
- });
- }
- didReset = true;
- time = TOTAL_TIME;
- }
-
- public void update(long deltaMs) {
- // Track whether reset was called at any point.
- // If so, this short-circuits the rest of the update.
- didReset = false;
-
- if (state == State.PLAYING) {
- time = time - ((int) deltaMs / 1000f);
- if (time <= 0) {
- time = 0;
- player.idleNoBall();
- setState(State.GAME_OVER);
- }
- }
-
- updateTimeLeftText();
-
- tweenManager.update(deltaMs);
- if (didReset) {
- return;
- }
-
- for (int i = actors.size() - 1; i >= 0; i--) {
- Actor actor = actors.get(i);
- actor.update(deltaMs);
- if (didReset) {
- return;
- }
- }
-
- for (int i = balls.size() - 1; i >= 0; i--) {
- updateBall(balls.get(i));
- }
-
- // Show the targets until the player gets at least 1 shot in the goal.
- if (!scoredAtLeastOneShot) {
- float msTillNextTargetPulseBefore = msTillNextTargetPulse;
- msTillNextTargetPulse -= deltaMs;
- if (msTillNextTargetPulseBefore > 0 && msTillNextTargetPulse <= 0) {
- pulseTargets();
- }
- }
-
- Collections.sort(actors);
- }
-
- void updateTimeLeftText() {
- String timeLeftString = "00:" + String.format(Locale.ENGLISH, "%02d", (int) time);
- timeLeftText.setText(timeLeftString);
- timeLeftTextGlow.setText(timeLeftString);
- }
-
- private void updateBall(final GrapeActor ball) {
- // Make the ball shrink as it travels into the distance.
- float ballStartY = 753;
- float ballEndY = 157;
- if (!ball.shotBlocked) {
- ball.scale = 0.5f + (0.5f * (ball.position.y - ballEndY) / (ballStartY - ballEndY));
- }
-
- // Only allow blocking if ball is traveling towards goal (otherwise ball can get stuck bouncing
- // between opponents).
- if (ball.velocity.y < 0) {
- final WaterPoloActor blockingOpponent = getBlockingOpponentIfAny(ball);
- if (blockingOpponent != null) {
- // Draw a -1 at the ball
- addPointText(ball.position.x, ball.position.y - 150, "-1", POINT_MINUS_TEXT_RGB);
-
- newScore(score - 1);
- blockShot(ball, blockingOpponent);
- }
- }
-
- // TODO: at small scales, ball isn't centered over its position.
- // TODO: randomize vertical position of splats
- if (!ball.shotBlocked && goalBoxContains(ball.position.x, ball.position.y)) {
- // Draw a +1 at the ball.
-
- if (ball.bounces == 2) {
- addPointText(ball.position.x, ball.position.y, "+2", POINT_PLUS_TEXT_RGB);
- newScore(score + 2);
- } else {
- addPointText(ball.position.x, ball.position.y, "+1", POINT_PLUS_TEXT_RGB);
- newScore(score + 1);
- }
- scoreShot(ball);
- }
-
- // Check if ball has left screen. If so, count it as a miss and get ready for another throw.
- if ((ball.position.y < 0 && !ball.shotBlocked)) {
- missShot(ball);
- }
-
- // Check if ball touches the left and right wall and has not bounced off the wall twice.
- // If so, bounce it off the wall.
- // If not, remove the ball from play.
- if (ball.position.x <= 0 || ball.position.x >= WATER_POLO_WIDTH) {
- if (ball.bounces > 1 || ball.shotBlocked) {
- missShot(ball);
- } else {
- ball.velocity.x = -ball.velocity.x;
- ball.rotation = (float) (Math.atan(ball.velocity.y / ball.velocity.x) - (Math.PI / 2));
- ball.update(20);
- ball.bounces++;
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.present_throw_block);
- }
- }
-
- // Ditto for the bottom wall.
- if (ball.position.y > WATER_POLO_HEIGHT) {
- if (ball.bounces > 1 || ball.shotBlocked) {
- missShot(ball);
- } else {
- ball.velocity.y = -ball.velocity.y;
- ball.rotation = (float) (Math.atan(ball.velocity.y / ball.velocity.x) - (Math.PI / 2));
- ball.update(20);
- ball.bounces++;
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.present_throw_block);
- }
- }
- }
-
- private void addBall(float radians) {
- AnimatedSprite ballSprite = spriteWithIds(Sprites.present_throw_thrownpresent);
- GrapeActor ball = new GrapeActor(ballSprite);
- ball.shouldScaleWithHeight = false;
- ball.zIndex = 20;
- ball.position.set(player.position.x + 78, player.position.y - 89);
- ball.clearStreak();
-
- ball.rotation = radians + (float) Math.PI / 2;
- ball.velocity.set(BALL_SPEED * (float) Math.cos(radians),
- BALL_SPEED * (float) Math.sin(radians));
- ball.hidden = false;
-
- actors.add(ball);
- balls.add(ball);
- }
-
-
- private void blockBall(final GrapeActor ball, WaterPoloActor blockingOpponent) {
- float rotation = ball.velocity.x / 200;
- float directionX = ball.velocity.x * 0.5f;
-
- if (ball.velocity.x > 0) {
- directionX += 25;
- } else {
- directionX -= 25;
- }
-
- float initialX = ball.position.x;
- float finalX = initialX + directionX;
-
- float initialY = ball.position.y;
- float midY = initialY - 140;
- float finalY = initialY + 220;
-
- ball.velocity.set(0, 0);
- ball.shotBlocked = true;
-
- final ActorTween secondYTween = new ActorTween(ball) {
- @Override
- protected void onFinish() {
- if (actors.contains(ball)) {
- missShot(ball);
- }
- }
- }
- .fromY(midY)
- .toY(finalY)
- .withInterpolator(Interpolator.EASE_IN)
- .withDuration(0.4f);
-
- final ActorTween firstYTween = new ActorTween(ball) {
- @Override
- protected void onFinish() {
- tweenManager.add(secondYTween);
- }
- }
- .fromY(initialY)
- .toY(midY)
- .withInterpolator(Interpolator.EASE_OUT)
- .withDuration(0.4f);
-
- final ActorTween xTween = new ActorTween(ball)
- .fromX(initialX)
- .toX(finalX)
- .withRotation(0, rotation)
- .withDuration(0.8f);
-
- tweenManager.add(firstYTween);
- tweenManager.add(xTween);
- }
-
- private boolean goalBoxContains(float x, float y) {
- if (x < GOAL_BOX_X || x > GOAL_BOX_X + GOAL_BOX_WIDTH) {
- return false;
- }
- if (y < GOAL_BOX_Y || y > GOAL_BOX_Y + GOAL_BOX_HEIGHT) {
- return false;
- }
- return true;
- }
-
- private void pulseTargets() {
- targetLeft.hidden = false;
- targetMiddle.hidden = false;
- targetRight.hidden = false;
-
- targetLeft.alpha = 1;
- targetMiddle.alpha = 1;
- targetRight.alpha = 1;
-
- final float startScale = 1;
- final float endScale = 0.6f;
- final float bounceScale = 0.8f;
-
- final Tween fadeout = new Tween(0.2f) {
- @Override
- protected void updateValues(float percentDone) {
- float alpha = Interpolator.FAST_IN.getValue(percentDone, 1, 0);
- targetLeft.alpha = alpha;
- targetMiddle.alpha = alpha;
- targetRight.alpha = alpha;
- }
-
- @Override
- protected void onFinish() {
- targetLeft.hidden = true;
- targetMiddle.hidden = true;
- targetRight.hidden = true;
- msTillNextTargetPulse = MS_BETWEEN_TARGET_FLASHES;
- }
- };
-
- final Tween bounceThree = new Tween(0.15f) {
- @Override
- protected void updateValues(float percentDone) {
- float scale = Interpolator.FAST_IN.getValue(percentDone, bounceScale, endScale);
- targetLeft.scale = scale;
- targetMiddle.scale = scale;
- targetRight.scale = scale;
- }
-
- @Override
- protected void onFinish() {
- tweenManager.add(fadeout);
- }
- };
-
- final Tween bounceTwo = new Tween(0.15f) {
- @Override
- protected void updateValues(float percentDone) {
- float scale = Interpolator.FAST_IN.getValue(percentDone, bounceScale, endScale);
- targetLeft.scale = scale;
- targetMiddle.scale = scale;
- targetRight.scale = scale;
- }
-
- @Override
- protected void onFinish() {
- tweenManager.add(score == 0 ? bounceThree : fadeout);
- }
- };
-
- final Tween bounceOne = new Tween(0.15f) {
- @Override
- protected void updateValues(float percentDone) {
- float scale = Interpolator.FAST_IN.getValue(percentDone, bounceScale, endScale);
- targetLeft.scale = scale;
- targetMiddle.scale = scale;
- targetRight.scale = scale;
- }
-
- @Override
- protected void onFinish() {
- tweenManager.add(score == 0 ? bounceTwo : fadeout);
- }
- };
-
- Tween shrinkIn = new Tween(0.3f) {
- @Override
- protected void updateValues(float percentDone) {
- float scale = Interpolator.FAST_IN.getValue(percentDone, startScale, endScale);
- targetLeft.scale = scale;
- targetMiddle.scale = scale;
- targetRight.scale = scale;
- }
-
- @Override
- protected void onFinish() {
- tweenManager.add(score == 0 ? bounceOne : fadeout);
- }
- };
-
- tweenManager.add(shrinkIn);
- }
-
- WaterPoloActor getBlockingOpponentIfAny(GrapeActor ball) {
- if (ball.shotBlocked) {
- return null; // Already blocked once, let it go.
- }
-
- if (opponentOne.canBlock(ball.position.x, ball.position.y)) {
- return opponentOne;
- } else if (opponentTwo.canBlock(ball.position.x, ball.position.y)) {
- return opponentTwo;
- } else if (opponentThree.canBlock(ball.position.x, ball.position.y)) {
- return opponentThree;
- }
- return null;
- }
-
- private void blockShot(final GrapeActor ball, final WaterPoloActor blockingOpponent) {
- // Blocked!
- ball.shotBlocked = true;
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.tennis_eliminate);
-
- blockingOpponent.blockShot(new Callback() {
- @Override
- public void call() {
- blockBall(ball, blockingOpponent);
- shake(1, VIBRATION_SMALL);
- }
- });
- }
-
- private void missShot(GrapeActor ball) {
- actors.remove(ball);
- balls.remove(ball);
- }
-
- private void scoreShot(GrapeActor ball) {
- actors.remove(ball);
- balls.remove(ball);
-
- // Swap ball for splat.
- final SpriteActor splat = actorWithIds(Sprites.orange_present_falling);
- splat.position.set(ball.position.x - splat.sprite.frameWidth / 2,
- ball.position.y - splat.sprite.frameHeight);
- splat.zIndex = -1;
- splat.sprite.setLoop(false);
- splat.sprite.addListener(new AnimatedSpriteListener() {
- @Override
- public void onFinished() {
- actors.remove(splat);
- effects.remove(splat);
- }
- });
- actors.add(splat);
- effects.add(splat);
-
- // Shake the goal.
- goal.sprite.setPaused(false);
-
- // Play goal score sound.
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.present_throw_goal);
-
- // Shake the screen a little bit.
- shake(1, VIBRATION_SMALL);
-
- // Reset ball.
- ball.velocity.set(0, 0);
- ball.position.set(player.position.x, player.position.y);
-
- // Time for more opponents?
- if (score >= OPPONENT_ONE_ENTRANCE_THRESHOLD && opponentOne.hidden) {
- bringInOpponent(opponentOne, OPPONENT_ONE_SPEED);
- }
- if (score >= OPPONENT_TWO_ENTRANCE_THRESHOLD && opponentTwo.hidden) {
- bringInOpponent(opponentTwo, OPPONENT_TWO_SPEED);
- }
- if (score >= OPPONENT_THREE_ENTRANCE_THRESHOLD && opponentThree.hidden) {
- bringInOpponent(opponentThree, OPPONENT_THREE_SPEED);
- }
-
- scoredAtLeastOneShot = true;
- }
-
- private void newScore(int newScore) {
- score = newScore;
- eventBus.sendEvent(EventBus.SCORE_CHANGED, score);
- }
-
- private void addPointText(float x, float y, String pointText, int color) {
- final TextActor pointTextActor = new TextActor(pointText);
-
- x = x - 30;
- y = y - 70;
-
- pointTextActor.setColor(color);
-
- pointTextActor.setBold(true);
- pointTextActor.position.set(x, y);
- pointTextActor.scale = 5;
- pointTextActor.zIndex = 1000;
- actors.add(pointTextActor);
- effects.add(pointTextActor);
- tweenManager.add(new ActorTween(pointTextActor) {
- @Override
- protected void onFinish() {
- actors.remove(pointTextActor);
- effects.remove(pointTextActor);
- }
- }
- .withDuration(POINT_TEXT_ANIMATION_TIME)
- .withAlpha(1, 0)
- .toY(y - 80)
- .withInterpolator(Interpolator.FAST_IN));
- }
-
- private void bringInOpponent(final WaterPoloActor opponent, final float speed) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.present_throw_character_appear);
- opponent.hidden = false;
- opponent.enter(new Callback() {
- @Override
- public void call() {
- tweenOpponentRight(opponent, speed);
- }
- });
- }
-
-// Move opponent to the right. Chain a tween to the left at the end for endless motion.
- private void tweenOpponentRight(final WaterPoloActor opponent, final float speed) {
- if (state != State.PLAYING) {
- // Stop moving opponents side-to-side at end of game.
- tweenOpponentLeft(opponent, speed);
- return;
- }
- tweenOpponent(opponent, 400, speed, new Callback() {
- @Override
- public void call() {
- tweenOpponentLeft(opponent, speed);
- }
- });
- opponent.swimRight();
- }
-
- // Move opponent to the left. Chain a tween to the right at the end for endless motion.
- private void tweenOpponentLeft(final WaterPoloActor opponent, final float speed) {
- tweenOpponent(opponent, 0, speed, new Callback() {
- @Override
- public void call() {
- tweenOpponentRight(opponent, speed);
- }
- });
- opponent.swimLeft();
- }
-
- // Helper function for moving opponents left & right.
- private void tweenOpponent(WaterPoloActor opponent, float x, float speed, Callback next) {
- ActorTween tween = new ActorTween(opponent)
- .toX(x)
- .withDuration(ActorHelper.distanceBetween(opponent.position.x, 0, x, 0) / speed)
- .whenFinished(next);
- tweenManager.add(tween);
- }
-
- private void maybePickUpAnotherBall() {
- if (state == State.GAME_OVER) {
- // No-op
- } else if (ballsThrown == 0) {
- // No-op
- } else {
- // No-op
- }
- }
-
- public void onFling(final float radians) {
- if (state == State.GAME_OVER || balls.size() > 0) {
- return;
- }
- if (state == State.WAITING) {
- instructions.hide();
- setState(State.PLAYING);
- }
- canThrow = false; // No more throws until ball either goes in goal or leaves screen.
- tweenManager.add(new EmptyTween(THROW_DELAY_SECONDS) {
- @Override
- protected void onFinish() {
- canThrow = true;
- }
- });
- ballsThrown++;
-
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.present_throw_throw);
-
- player.throwBall(new Callback() {
- @Override
- public void call() {
- addBall(radians);
- }
- }, new Callback() {
- @Override
- public void call() {
- maybePickUpAnotherBall();
- }
- });
- }
-
- private SpriteActor actorWithIds(int[] ids) {
- return new SpriteActor(spriteWithIds(ids), Vector2D.get(0, 0), Vector2D.get(0, 0));
- }
-
- private AnimatedSprite spriteWithIds(int[] ids, int fps) {
- AnimatedSprite sprite = spriteWithIds(ids);
- sprite.setFPS(fps);
-
- return sprite;
- }
-
- private AnimatedSprite spriteWithIds(int[] ids) {
- return AnimatedSprite.fromFrames(resources, ids);
- }
-
- private void shake(float screenShakeMagnitude, long vibrationMs) {
- vibrator.vibrate(vibrationMs);
- if (screenShakeMagnitude > 0) {
- cameraShake.shake(33, screenShakeMagnitude, SHAKE_FALLOFF);
- }
- }
-
- public void setState(State newState) {
- state = newState;
- eventBus.sendEvent(EventBus.GAME_STATE_CHANGED, newState);
-
- if (newState == State.TITLE) {
- tweenManager.add(new EmptyTween(titleDurationMs / 1000.0f) {
- @Override
- protected void onFinish() {
- setState(State.WAITING);
- }
- });
- }
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloPauseView.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloPauseView.java
deleted file mode 100644
index 400390d5d..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloPauseView.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.waterpolo;
-
-import android.content.Context;
-import android.view.View;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.PauseView;
-
-/**
- * Special subclass of {@link PauseView} for Waterpolo.
- */
-public class WaterPoloPauseView extends PauseView {
-
- public WaterPoloPauseView(Context context) {
- super(context);
- }
-
- @Override
- protected void loadLayout(Context context) {
- super.loadLayout(context);
-
- // Hide replay button
- findViewById(R.id.replay_button).setVisibility(View.GONE);
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloScoreView.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloScoreView.java
deleted file mode 100644
index ecfe19508..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloScoreView.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.waterpolo;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.google.android.apps.santatracker.doodles.R;
-import com.google.android.apps.santatracker.doodles.shared.EventBus;
-import com.google.android.apps.santatracker.doodles.shared.GameOverlayButton;
-import com.google.android.apps.santatracker.doodles.shared.LaunchDecisionMaker;
-import com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent;
-import com.google.android.apps.santatracker.doodles.shared.ScoreView;
-
-
-/**
- * ScoreView for WaterPolo game.
- */
-public class WaterPoloScoreView extends ScoreView {
-
- private WaterPoloModel mModel;
-
- public WaterPoloScoreView(Context context, OnShareClickedListener shareClickedListener) {
- super(context, shareClickedListener);
- }
-
- public WaterPoloScoreView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public WaterPoloScoreView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void goToMoreGames(Context context) {
- EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.menu_item_click);
- logger.logEvent(new PineappleLogEvent
- .Builder(PineappleLogEvent.DEFAULT_DOODLE_NAME, PineappleLogEvent.HOME_CLICKED)
- .withEventSubType(listener.gameType())
- .build());
- if(mModel != null) {
- int stars;
- if (mModel.score > WaterPoloModel.THREE_STAR_THRESHOLD) {
- stars = 3;
- } else if (mModel.score > WaterPoloModel.TWO_STAR_THRESHOLD) {
- stars = 2;
- } else {
- stars = 1;
- }
-
- Bundle bundle = new Bundle();
- bundle.putInt(LaunchDecisionMaker.EXTRA_PRESENT_DROP_SCORE, mModel.score);
- bundle.putInt(LaunchDecisionMaker.EXTRA_PRESENT_DROP_STARS, stars);
- LaunchDecisionMaker.finishActivityWithResult(context, Activity.RESULT_OK, bundle);
- } else {
- LaunchDecisionMaker.finishActivity(context);
- }
- }
-
- @Override
- protected void loadLayout(Context context) {
- super.loadLayout(context);
-
- // Disable replay for this game
- View replayButton = findViewById(R.id.replay_button);
- replayButton.setVisibility(View.GONE);
-
- // Disable sharing for this game
- GameOverlayButton shareButton = (GameOverlayButton) findViewById(R.id.share_button);
- shareButton.setVisibility(View.GONE);
- }
-
- public void setModel(WaterPoloModel model) {
- mModel = model;
- }
-}
diff --git a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloView.java b/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloView.java
deleted file mode 100644
index aef85a370..000000000
--- a/doodles/src/main/java/com/google/android/apps/santatracker/doodles/waterpolo/WaterPoloView.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.apps.santatracker.doodles.waterpolo;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.google.android.apps.santatracker.doodles.shared.Actor;
-
-/**
- * Handles rendering for the water polo game.
- * Copy and pasted from JumpingView.
- */
-public class WaterPoloView extends View {
- private static final String TAG = WaterPoloView.class.getSimpleName();
-
- private static final int COLOR_FLOOR = 0xFFA6FFFF;
-
- private WaterPoloModel model;
- private float currentScale;
- private float currentOffsetX = 0; // In game units
- private float currentOffsetY = 0;
-
- public WaterPoloView(Context context) {
- this(context, null);
- }
-
- public WaterPoloView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public WaterPoloView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public void setModel(WaterPoloModel model) {
- this.model = model;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (model == null) {
- return;
- }
- synchronized (model) {
- super.onDraw(canvas);
- canvas.save();
- canvas.drawColor(COLOR_FLOOR);
-
- // Fit-to-screen & center.
- currentScale = Math.min(canvas.getWidth() / (float) WaterPoloModel.WATER_POLO_WIDTH,
- canvas.getHeight() / (float) WaterPoloModel.WATER_POLO_HEIGHT);
-
- currentOffsetX = (canvas.getWidth() / currentScale - WaterPoloModel.WATER_POLO_WIDTH) / 2;
- currentOffsetY = (canvas.getHeight() / currentScale - WaterPoloModel.WATER_POLO_HEIGHT) / 2;
-
- model.moveSlide(currentOffsetX);
-
- canvas.scale(currentScale, currentScale);
- canvas.translate(currentOffsetX - model.cameraShake.position.x,
- currentOffsetY - model.cameraShake.position.y);
-
- for (int i = 0; i < model.actors.size(); i++) { // Avoiding iterator to avoid garbage.
- Actor actor = model.actors.get(i);
- if (!actor.hidden) {
- actor.draw(canvas);
- }
- }
- canvas.restore();
- }
- }
-}
diff --git a/doodles/src/main/res/drawable-nodpi/debug_marker.png b/doodles/src/main/res/drawable-nodpi/debug_marker.png
deleted file mode 100644
index 55fcdd01f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/debug_marker.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/empty_frame.png b/doodles/src/main/res/drawable-nodpi/empty_frame.png
deleted file mode 100644
index cbca00f67..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/empty_frame.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/intro.png b/doodles/src/main/res/drawable-nodpi/intro.png
deleted file mode 100644
index 0dae3c045..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/intro.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/melon_shadow.png b/doodles/src/main/res/drawable-nodpi/melon_shadow.png
deleted file mode 100644
index 22ffc246e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/melon_shadow.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/orange_present1.png b/doodles/src/main/res/drawable-nodpi/orange_present1.png
deleted file mode 100644
index 1493c3136..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/orange_present1.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/orange_present2.png b/doodles/src/main/res/drawable-nodpi/orange_present2.png
deleted file mode 100644
index b8f57b2d6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/orange_present2.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/orange_present3.png b/doodles/src/main/res/drawable-nodpi/orange_present3.png
deleted file mode 100644
index 646a47206..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/orange_present3.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/orange_present4.png b/doodles/src/main/res/drawable-nodpi/orange_present4.png
deleted file mode 100644
index 4d97992b6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/orange_present4.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_01.png
deleted file mode 100644
index af446ab19..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_02.png
deleted file mode 100644
index 91aa79119..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_03.png
deleted file mode 100644
index bc0a6dc4f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_04.png
deleted file mode 100644
index a8707c9c7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_05.png
deleted file mode 100644
index 0f46fdfad..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_06.png
deleted file mode 100644
index 45a981aff..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_07.png
deleted file mode 100644
index 14158040d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_08.png
deleted file mode 100644
index 057e33722..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_09.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_09.png
deleted file mode 100644
index 748648666..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ascending_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_banner.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_banner.png
deleted file mode 100644
index d3277580f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_banner.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_01.png
deleted file mode 100644
index 11d8fb2fb..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_02.png
deleted file mode 100644
index d2fb0b876..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_03.png
deleted file mode 100644
index 6f251d964..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_04.png
deleted file mode 100644
index 90124eeb8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_05.png
deleted file mode 100644
index 3a83b8131..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_candy_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_01.png
deleted file mode 100644
index 228a1e68a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_02.png
deleted file mode 100644
index 7e8a4287f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_03.png
deleted file mode 100644
index 28f03896f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_04.png
deleted file mode 100644
index 1ba04f3a8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_05.png
deleted file mode 100644
index 711111fb8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_06.png
deleted file mode 100644
index c91dd01c5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_07.png
deleted file mode 100644
index 3c9c30050..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_08.png
deleted file mode 100644
index 7f0300b18..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_09.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_09.png
deleted file mode 100644
index 6ed643869..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_10.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_10.png
deleted file mode 100644
index 57cb1ecd1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_11.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_11.png
deleted file mode 100644
index bf1e1efb6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_12.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_12.png
deleted file mode 100644
index c11c6377e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_13.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_13.png
deleted file mode 100644
index 5cd5a3269..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_14.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_14.png
deleted file mode 100644
index b2843bba2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_15.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_15.png
deleted file mode 100644
index 968eb5857..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_16.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_16.png
deleted file mode 100644
index 0fa3d7522..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_17.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_17.png
deleted file mode 100644
index b3937c3bf..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_17.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_18.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_18.png
deleted file mode 100644
index 1c34d325f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_18.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_19.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_19.png
deleted file mode 100644
index 7dc8f2cff..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_19.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_20.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_20.png
deleted file mode 100644
index 30858df0e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_20.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_21.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_21.png
deleted file mode 100644
index 9cbe130e8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_21.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_22.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_22.png
deleted file mode 100644
index b3718be5d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_22.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_23.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_23.png
deleted file mode 100644
index 1e9b9e1c5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_23.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_24.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_24.png
deleted file mode 100644
index a9c24fe6c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_24.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_25.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_25.png
deleted file mode 100644
index f200f0bfb..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_25.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_26.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_26.png
deleted file mode 100644
index 4c830b5da..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_26.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_27.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_27.png
deleted file mode 100644
index 5f725379e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_27.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_28.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_28.png
deleted file mode 100644
index 2188d9083..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_28.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_29.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_29.png
deleted file mode 100644
index 2fb1c2a56..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_29.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_30.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_30.png
deleted file mode 100644
index 74079a0eb..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_canegrab_30.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_01.png
deleted file mode 100644
index de24a1b06..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_02.png
deleted file mode 100644
index 352669f64..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_03.png
deleted file mode 100644
index ecae2036c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_04.png
deleted file mode 100644
index 02120291a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_05.png
deleted file mode 100644
index 15200ab77..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_06.png
deleted file mode 100644
index b3047eaac..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_07.png
deleted file mode 100644
index b115bc5fb..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_08.png
deleted file mode 100644
index 48d2211d1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_09.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_09.png
deleted file mode 100644
index 8157cf8c0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_10.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_10.png
deleted file mode 100644
index 79486fcec..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_11.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_11.png
deleted file mode 100644
index dbcbf2008..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_12.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_12.png
deleted file mode 100644
index e3d04bfe9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_13.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_13.png
deleted file mode 100644
index 1db8900e7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_14.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_14.png
deleted file mode 100644
index 4e7b67b9f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_15.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_15.png
deleted file mode 100644
index 2a4827393..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_16.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_16.png
deleted file mode 100644
index 5f4422b0b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_17.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_17.png
deleted file mode 100644
index 88f752f0b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_17.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_18.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_18.png
deleted file mode 100644
index d57bbe5e6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_18.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_19.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_19.png
deleted file mode 100644
index 02edef24b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_19.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_20.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_20.png
deleted file mode 100644
index 63ab62528..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_20.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_21.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_21.png
deleted file mode 100644
index d0e1c2820..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_dazed_21.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_01.png
deleted file mode 100644
index 13714aefc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_02.png
deleted file mode 100644
index 2ed1cc454..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_03.png
deleted file mode 100644
index 71ef2029f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_04.png
deleted file mode 100644
index b319cb133..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_05.png
deleted file mode 100644
index 0f46fdfad..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_06.png
deleted file mode 100644
index 57428f1e4..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_07.png
deleted file mode 100644
index 5dab82643..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_08.png
deleted file mode 100644
index f8ed1923b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_09.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_09.png
deleted file mode 100644
index 5d4ffc73c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_descending_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_01.png
deleted file mode 100644
index edd8331bc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_02.png
deleted file mode 100644
index f0f6cbeba..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_03.png
deleted file mode 100644
index fc972a269..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_04.png
deleted file mode 100644
index cee0ff22d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_05.png
deleted file mode 100644
index 7cabcba4e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_06.png
deleted file mode 100644
index ae534854c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_07.png
deleted file mode 100644
index 9b2ce5f05..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_08.png
deleted file mode 100644
index 5559997a0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_09.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_09.png
deleted file mode 100644
index d42997839..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_10.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_10.png
deleted file mode 100644
index ae534854c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_11.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_11.png
deleted file mode 100644
index 7cabcba4e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_12.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_12.png
deleted file mode 100644
index cee0ff22d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_13.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_13.png
deleted file mode 100644
index fc972a269..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_14.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_14.png
deleted file mode 100644
index f0f6cbeba..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_15.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_15.png
deleted file mode 100644
index edd8331bc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_elf_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_01.png
deleted file mode 100644
index 662c8f4f6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_02.png
deleted file mode 100644
index c088a9631..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_03.png
deleted file mode 100644
index 10e3ae82e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_04.png
deleted file mode 100644
index 368f07d5a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_05.png
deleted file mode 100644
index aa22cc071..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_06.png
deleted file mode 100644
index 44d1fedbc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_07.png
deleted file mode 100644
index a0b1a7fd9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_08.png
deleted file mode 100644
index 1357fdff5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_09.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_09.png
deleted file mode 100644
index ab438a5e5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_10.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_10.png
deleted file mode 100644
index 83579501d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_11.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_11.png
deleted file mode 100644
index 5488f606a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_12.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_12.png
deleted file mode 100644
index 2f1756718..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_13.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_13.png
deleted file mode 100644
index 5e78c6ca3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_14.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_14.png
deleted file mode 100644
index 8b14191f3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_15.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_15.png
deleted file mode 100644
index 7d9411c05..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_16.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_16.png
deleted file mode 100644
index 9e4feff87..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_17.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_17.png
deleted file mode 100644
index 979d56262..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_17.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_18.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_18.png
deleted file mode 100644
index 3f491b68c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_18.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_19.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_19.png
deleted file mode 100644
index 7f925e55e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_19.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_20.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_20.png
deleted file mode 100644
index 454b8cc1f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_20.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_21.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_21.png
deleted file mode 100644
index 1482e0824..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_21.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_22.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_22.png
deleted file mode 100644
index 7dfcb5bb1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_22.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_23.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_23.png
deleted file mode 100644
index e425d8fd9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_23.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_24.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_24.png
deleted file mode 100644
index 6794e4396..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_24.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_25.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_25.png
deleted file mode 100644
index 72e7e836c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_25.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_26.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_26.png
deleted file mode 100644
index d2f80e62d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_26.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_27.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_27.png
deleted file mode 100644
index 4eeeb37bc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_27.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_28.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_28.png
deleted file mode 100644
index c52f8d645..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_28.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_29.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_29.png
deleted file mode 100644
index 567f9ebac..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_frozen_29.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_01.png
deleted file mode 100644
index cb7b00a1d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_02.png
deleted file mode 100644
index b6ca31472..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_03.png
deleted file mode 100644
index 87b9cc23d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_04.png
deleted file mode 100644
index 89b9d559e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_05.png
deleted file mode 100644
index 1e237f879..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_06.png
deleted file mode 100644
index 1fdf4b09e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_07.png
deleted file mode 100644
index d3ec1baba..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_08.png
deleted file mode 100644
index 1070730e9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_09.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_09.png
deleted file mode 100644
index d3ec1baba..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_10.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_10.png
deleted file mode 100644
index 1fdf4b09e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_11.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_11.png
deleted file mode 100644
index 1e237f879..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_12.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_12.png
deleted file mode 100644
index 89b9d559e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_13.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_13.png
deleted file mode 100644
index 87b9cc23d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_14.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_14.png
deleted file mode 100644
index b6ca31472..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_15.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_15.png
deleted file mode 100644
index cb7b00a1d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_ice_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_loadingscreen.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_loadingscreen.png
deleted file mode 100644
index 1d0a3ade0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_loadingscreen.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_01.png
deleted file mode 100644
index 3194e8786..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_02.png
deleted file mode 100644
index 3194e8786..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_03.png
deleted file mode 100644
index ca73a9ff0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_04.png
deleted file mode 100644
index ca73a9ff0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_05.png
deleted file mode 100644
index 3194e8786..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_06.png
deleted file mode 100644
index 3194e8786..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_07.png
deleted file mode 100644
index ca73a9ff0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_08.png
deleted file mode 100644
index ca73a9ff0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_09.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_09.png
deleted file mode 100644
index fb315c1bf..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_10.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_10.png
deleted file mode 100644
index fb315c1bf..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_11.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_11.png
deleted file mode 100644
index 04c2350e1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_12.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_12.png
deleted file mode 100644
index 35bf340aa..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_13.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_13.png
deleted file mode 100644
index 4bed13f78..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_14.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_14.png
deleted file mode 100644
index 4bed13f78..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_15.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_15.png
deleted file mode 100644
index 3bd5a8bc9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_16.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_start_16.png
deleted file mode 100644
index d84a7e680..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_start_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_01.png
deleted file mode 100644
index bdcd1b73d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_02.png
deleted file mode 100644
index 7ae84ffde..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_03.png
deleted file mode 100644
index 251a4b441..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_04.png
deleted file mode 100644
index 1a0712b81..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_05.png
deleted file mode 100644
index cbf66a58b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_06.png
deleted file mode 100644
index 01c49bf65..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_07.png
deleted file mode 100644
index 7d981540a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_08.png
deleted file mode 100644
index b5951235d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_09.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_09.png
deleted file mode 100644
index b47c9773e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_10.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_10.png
deleted file mode 100644
index 2940e13aa..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_11.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_11.png
deleted file mode 100644
index 478f73a90..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_12.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_12.png
deleted file mode 100644
index ef0ca5fb2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_13.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_13.png
deleted file mode 100644
index 2f3546fa7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_14.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_14.png
deleted file mode 100644
index 1a8d6c2b1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_15.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_15.png
deleted file mode 100644
index 3c8e4a823..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_16.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_16.png
deleted file mode 100644
index aa068a3fc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimming_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_01.png
deleted file mode 100644
index a273ae147..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_02.png
deleted file mode 100644
index f2e5a0784..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_03.png
deleted file mode 100644
index 44fe1d289..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_04.png
deleted file mode 100644
index 97d521c15..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_05.png
deleted file mode 100644
index 6fa9bcf73..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_06.png
deleted file mode 100644
index 05fc5c70e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_07.png
deleted file mode 100644
index 8ed969802..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_08.png
deleted file mode 100644
index 11b700b05..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_09.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_09.png
deleted file mode 100644
index dec050d83..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_10.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_10.png
deleted file mode 100644
index e72519cf7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_11.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_11.png
deleted file mode 100644
index d51e20e34..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_12.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_12.png
deleted file mode 100644
index 36d6da611..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_13.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_13.png
deleted file mode 100644
index 567f9ebac..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_14.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_14.png
deleted file mode 100644
index 567f9ebac..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_15.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_15.png
deleted file mode 100644
index 567f9ebac..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_16.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_16.png
deleted file mode 100644
index 567f9ebac..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_swimmingunderwater_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_01.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_01.png
deleted file mode 100644
index 8565c6c47..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_02.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_02.png
deleted file mode 100644
index 5fd79d0b6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_03.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_03.png
deleted file mode 100644
index 8565c6c47..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_04.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_04.png
deleted file mode 100644
index 5fd79d0b6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_05.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_05.png
deleted file mode 100644
index 0d0ea2fd1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_06.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_06.png
deleted file mode 100644
index b805315e5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_07.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_07.png
deleted file mode 100644
index fb93e8704..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_08.png b/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_08.png
deleted file mode 100644
index 8284356fe..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swim_tutorials_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swimming_candycane_endscreen.png b/doodles/src/main/res/drawable-nodpi/penguin_swimming_candycane_endscreen.png
deleted file mode 100644
index 9d7aa266c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swimming_candycane_endscreen.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swimming_dazed_endscreen.png b/doodles/src/main/res/drawable-nodpi/penguin_swimming_dazed_endscreen.png
deleted file mode 100644
index d459bfb4b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swimming_dazed_endscreen.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/penguin_swimming_frozen_endscreen.png b/doodles/src/main/res/drawable-nodpi/penguin_swimming_frozen_endscreen.png
deleted file mode 100644
index 1d93f1933..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/penguin_swimming_frozen_endscreen.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/pineapple_star_empty.png b/doodles/src/main/res/drawable-nodpi/pineapple_star_empty.png
deleted file mode 100644
index 44f8012b1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/pineapple_star_empty.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/pineapple_star_filled.png b/doodles/src/main/res/drawable-nodpi/pineapple_star_filled.png
deleted file mode 100644
index c85ffb5bd..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/pineapple_star_filled.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_01.png
deleted file mode 100644
index 5c9f2a949..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_02.png
deleted file mode 100644
index ceaaffb9d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_03.png
deleted file mode 100644
index 5e8ab1af3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_04.png
deleted file mode 100644
index ae7ae0d17..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_05.png
deleted file mode 100644
index 5c9f2a949..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_06.png
deleted file mode 100644
index ceaaffb9d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_07.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_07.png
deleted file mode 100644
index 5e8ab1af3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_08.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_08.png
deleted file mode 100644
index ae7ae0d17..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_09.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_09.png
deleted file mode 100644
index f06d3c8e0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_10.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_10.png
deleted file mode 100644
index c61f811c7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_11.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_11.png
deleted file mode 100644
index 5b6896165..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_12.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_12.png
deleted file mode 100644
index 1595f45a6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_13.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_13.png
deleted file mode 100644
index f06d3c8e0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_14.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_14.png
deleted file mode 100644
index c61f811c7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_15.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_15.png
deleted file mode 100644
index 5b6896165..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_16.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_16.png
deleted file mode 100644
index 1595f45a6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_green_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_01.png
deleted file mode 100644
index 2e254cd3f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_02.png
deleted file mode 100644
index 084fa9a54..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_03.png
deleted file mode 100644
index 6153dfc6c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_04.png
deleted file mode 100644
index 084fa9a54..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_05.png
deleted file mode 100644
index 2e254cd3f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_06.png
deleted file mode 100644
index 084fa9a54..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_07.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_07.png
deleted file mode 100644
index 6153dfc6c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_08.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_08.png
deleted file mode 100644
index 084fa9a54..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_09.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_09.png
deleted file mode 100644
index 1e57ff8b2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_10.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_10.png
deleted file mode 100644
index 1b410d0ce..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_11.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_11.png
deleted file mode 100644
index 39070b5d0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_12.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_12.png
deleted file mode 100644
index 1b410d0ce..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_13.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_13.png
deleted file mode 100644
index 1e57ff8b2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_14.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_14.png
deleted file mode 100644
index 1b410d0ce..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_15.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_15.png
deleted file mode 100644
index 39070b5d0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_16.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_16.png
deleted file mode 100644
index 1b410d0ce..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_orange_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_01.png
deleted file mode 100644
index b43b8ff70..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_02.png
deleted file mode 100644
index 12e81acb7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_03.png
deleted file mode 100644
index cd1ba4373..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_04.png
deleted file mode 100644
index 12e81acb7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_05.png
deleted file mode 100644
index b43b8ff70..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_06.png
deleted file mode 100644
index 12e81acb7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_07.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_07.png
deleted file mode 100644
index cd1ba4373..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_08.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_08.png
deleted file mode 100644
index 12e81acb7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_09.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_09.png
deleted file mode 100644
index cf9762ea0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_10.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_10.png
deleted file mode 100644
index 936c9fbb6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_11.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_11.png
deleted file mode 100644
index 8c7253854..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_12.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_12.png
deleted file mode 100644
index 936c9fbb6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_13.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_13.png
deleted file mode 100644
index cf9762ea0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_14.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_14.png
deleted file mode 100644
index 936c9fbb6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_15.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_15.png
deleted file mode 100644
index 8c7253854..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_16.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_16.png
deleted file mode 100644
index 936c9fbb6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocker_red_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_01.png
deleted file mode 100644
index 88a82a6ff..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_02.png
deleted file mode 100644
index cb2c8c4d4..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_03.png
deleted file mode 100644
index c07eb4e1c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_04.png
deleted file mode 100644
index c7ddecab5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_05.png
deleted file mode 100644
index dffdf459f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_06.png
deleted file mode 100644
index 2061b74f0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_07.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_07.png
deleted file mode 100644
index f73cc34f2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_08.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_08.png
deleted file mode 100644
index 5139ea973..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_09.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_09.png
deleted file mode 100644
index f3cac212c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_10.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_10.png
deleted file mode 100644
index e91015830..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_11.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_11.png
deleted file mode 100644
index f1288f857..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_12.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_12.png
deleted file mode 100644
index e506d1b96..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_green_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_01.png
deleted file mode 100644
index 52d81f277..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_02.png
deleted file mode 100644
index 8f977e42d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_03.png
deleted file mode 100644
index 12f9ad263..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_04.png
deleted file mode 100644
index b82a1ebb6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_05.png
deleted file mode 100644
index 565b47d38..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_06.png
deleted file mode 100644
index 5e2463f59..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_07.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_07.png
deleted file mode 100644
index 829772a24..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_08.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_08.png
deleted file mode 100644
index b5967fae4..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_09.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_09.png
deleted file mode 100644
index c68e3dbf7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_10.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_10.png
deleted file mode 100644
index 574ed2e73..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_11.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_11.png
deleted file mode 100644
index 6b4fb122a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_12.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_12.png
deleted file mode 100644
index 71606c6ef..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_orange_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_01.png
deleted file mode 100644
index c90895d59..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_02.png
deleted file mode 100644
index b10bebfd5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_03.png
deleted file mode 100644
index 7e714dcb2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_04.png
deleted file mode 100644
index 518c6478d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_05.png
deleted file mode 100644
index 513ce2d90..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_06.png
deleted file mode 100644
index 6e13efb7a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_07.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_07.png
deleted file mode 100644
index 5e777b2c2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_08.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_08.png
deleted file mode 100644
index d66fab739..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_09.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_09.png
deleted file mode 100644
index ea5d3d36a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_10.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_10.png
deleted file mode 100644
index 2f2e2af29..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_11.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_11.png
deleted file mode 100644
index 63c83c6d9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_12.png b/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_12.png
deleted file mode 100644
index c84ac7215..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_blocking_red_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_celebrating_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_celebrating_01.png
deleted file mode 100644
index ea1cad029..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_celebrating_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_celebrating_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_celebrating_02.png
deleted file mode 100644
index ed2d0f21b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_celebrating_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_elfbag.png b/doodles/src/main/res/drawable-nodpi/present_throw_elfbag.png
deleted file mode 100644
index bde14c25f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_elfbag.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_floor.png b/doodles/src/main/res/drawable-nodpi/present_throw_floor.png
deleted file mode 100644
index 8eb101c60..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_floor.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_loadingscreen.png b/doodles/src/main/res/drawable-nodpi/present_throw_loadingscreen.png
deleted file mode 100644
index 34c4c65b7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_loadingscreen.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_01.png
deleted file mode 100644
index b682452cc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_02.png
deleted file mode 100644
index ed61dad0b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_03.png
deleted file mode 100644
index 9d6a87320..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_04.png
deleted file mode 100644
index f1e6dd40f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_05.png
deleted file mode 100644
index efe58cca3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_06.png
deleted file mode 100644
index 764725bd6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_07.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_07.png
deleted file mode 100644
index f11ecf92f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_08.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_08.png
deleted file mode 100644
index 4bc140cc2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_09.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_09.png
deleted file mode 100644
index 55a804246..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_10.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_10.png
deleted file mode 100644
index 6c71be80a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_11.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_11.png
deleted file mode 100644
index ca1f7bb0d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_12.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_12.png
deleted file mode 100644
index ca1f7bb0d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_green_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_01.png
deleted file mode 100644
index e60560141..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_02.png
deleted file mode 100644
index 2f0d744dc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_03.png
deleted file mode 100644
index ace47293e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_04.png
deleted file mode 100644
index dbb705db8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_05.png
deleted file mode 100644
index 7764b7826..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_06.png
deleted file mode 100644
index 4302e8b01..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_07.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_07.png
deleted file mode 100644
index a2f035078..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_08.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_08.png
deleted file mode 100644
index 052464f99..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_09.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_09.png
deleted file mode 100644
index c6a210194..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_10.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_10.png
deleted file mode 100644
index 59b4d6a3a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_11.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_11.png
deleted file mode 100644
index 59b4d6a3a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_12.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_12.png
deleted file mode 100644
index 59b4d6a3a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_orange_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_01.png
deleted file mode 100644
index e60560141..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_02.png
deleted file mode 100644
index 2f0d744dc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_03.png
deleted file mode 100644
index ace47293e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_04.png
deleted file mode 100644
index dbb705db8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_05.png
deleted file mode 100644
index 02d9a5ce6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_06.png
deleted file mode 100644
index 47b07f655..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_07.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_07.png
deleted file mode 100644
index e32411422..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_08.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_08.png
deleted file mode 100644
index 29c6d1c6d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_09.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_09.png
deleted file mode 100644
index 105128f4b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_10.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_10.png
deleted file mode 100644
index 8c08a0037..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_11.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_11.png
deleted file mode 100644
index 8c08a0037..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_12.png b/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_12.png
deleted file mode 100644
index 8c08a0037..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_newblocker_red_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_reloading_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_reloading_01.png
deleted file mode 100644
index 72319ff2c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_reloading_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_reloading_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_reloading_02.png
deleted file mode 100644
index 17ae082f1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_reloading_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_reloading_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_reloading_03.png
deleted file mode 100644
index 8c5c28972..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_reloading_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_santabag.png b/doodles/src/main/res/drawable-nodpi/present_throw_santabag.png
deleted file mode 100644
index 9ef03f31b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_santabag.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_throwing_01.png
deleted file mode 100644
index 7b523b823..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_throwing_02.png
deleted file mode 100644
index 86fb3d1fb..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_throwing_03.png
deleted file mode 100644
index 783c32117..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_throwing_04.png
deleted file mode 100644
index 93a29ec3f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_throwing_05.png
deleted file mode 100644
index 72034e09a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_throwing_06.png
deleted file mode 100644
index fcdc14c55..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_throwing_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_thrownpresent_orange.png b/doodles/src/main/res/drawable-nodpi/present_throw_thrownpresent_orange.png
deleted file mode 100644
index e5818f069..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_thrownpresent_orange.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_01.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_01.png
deleted file mode 100644
index 7bd5161d9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_02.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_02.png
deleted file mode 100644
index 679277fe8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_03.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_03.png
deleted file mode 100644
index ab5af3bf3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_04.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_04.png
deleted file mode 100644
index 0fef855d5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_05.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_05.png
deleted file mode 100644
index df3d4e028..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_06.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_06.png
deleted file mode 100644
index 63648bb42..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_07.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_07.png
deleted file mode 100644
index ed861356d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_08.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_08.png
deleted file mode 100644
index 7123b47d0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_09.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_09.png
deleted file mode 100644
index 5482259f0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_10.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_10.png
deleted file mode 100644
index 532f846dd..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_11.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_11.png
deleted file mode 100644
index ae8e6a175..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_12.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_12.png
deleted file mode 100644
index 4ea20eeca..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_13.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_13.png
deleted file mode 100644
index 60ed8f0e1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_14.png b/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_14.png
deleted file mode 100644
index 6ba378a75..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/present_throw_tutorials_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_button_00.png b/doodles/src/main/res/drawable-nodpi/running_button_00.png
deleted file mode 100644
index 659d2149c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_button_00.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_button_01.png b/doodles/src/main/res/drawable-nodpi/running_button_01.png
deleted file mode 100644
index f804cba55..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_button_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_button_02.png b/doodles/src/main/res/drawable-nodpi/running_button_02.png
deleted file mode 100644
index 48e0ae38b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_button_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_finish_line.png b/doodles/src/main/res/drawable-nodpi/running_finish_line.png
deleted file mode 100644
index 8b709079e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_finish_line.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_powerup_00.png b/doodles/src/main/res/drawable-nodpi/running_powerup_00.png
deleted file mode 100644
index 1f5278241..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_powerup_00.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_powerup_01.png b/doodles/src/main/res/drawable-nodpi/running_powerup_01.png
deleted file mode 100644
index 323cefacc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_powerup_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_powerup_02.png b/doodles/src/main/res/drawable-nodpi/running_powerup_02.png
deleted file mode 100644
index 4ff6c6ddb..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_powerup_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_powerup_03.png b/doodles/src/main/res/drawable-nodpi/running_powerup_03.png
deleted file mode 100644
index 7a1b6ceff..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_powerup_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_powerup_04.png b/doodles/src/main/res/drawable-nodpi/running_powerup_04.png
deleted file mode 100644
index da6362e6f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_powerup_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_powerup_05.png b/doodles/src/main/res/drawable-nodpi/running_powerup_05.png
deleted file mode 100644
index c4ff04c80..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_powerup_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_powerup_06.png b/doodles/src/main/res/drawable-nodpi/running_powerup_06.png
deleted file mode 100644
index 65a4ca8de..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_powerup_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/running_powerup_07.png b/doodles/src/main/res/drawable-nodpi/running_powerup_07.png
deleted file mode 100644
index 04c15d08f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/running_powerup_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowball01.png b/doodles/src/main/res/drawable-nodpi/snowball01.png
deleted file mode 100644
index 452537e51..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowball01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowball02.png b/doodles/src/main/res/drawable-nodpi/snowball02.png
deleted file mode 100644
index c1e3069aa..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowball02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowball03.png b/doodles/src/main/res/drawable-nodpi/snowball03.png
deleted file mode 100644
index 8be397010..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowball03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowball04.png b/doodles/src/main/res/drawable-nodpi/snowball04.png
deleted file mode 100644
index df180b0c6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowball04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowball05.png b/doodles/src/main/res/drawable-nodpi/snowball05.png
deleted file mode 100644
index d9d1128bc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowball05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowball06.png b/doodles/src/main/res/drawable-nodpi/snowball06.png
deleted file mode 100644
index bef53e535..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowball06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowball07.png b/doodles/src/main/res/drawable-nodpi/snowball07.png
deleted file mode 100644
index 31e8658af..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowball07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowball08.png b/doodles/src/main/res/drawable-nodpi/snowball08.png
deleted file mode 100644
index 65b6ab448..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowball08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowball_runner_trees1.png b/doodles/src/main/res/drawable-nodpi/snowball_runner_trees1.png
deleted file mode 100644
index d0a845baf..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowball_runner_trees1.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowball_runner_trees2.png b/doodles/src/main/res/drawable-nodpi/snowball_runner_trees2.png
deleted file mode 100644
index d7a046359..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowball_runner_trees2.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_01.png b/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_01.png
deleted file mode 100644
index b3102fb70..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_02.png b/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_02.png
deleted file mode 100644
index 7e3f701de..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_03.png b/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_03.png
deleted file mode 100644
index 57884c805..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_04.png b/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_04.png
deleted file mode 100644
index 64fe3baaf..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_05.png
deleted file mode 100644
index 5b1c9761e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_elf_squished_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_05.png
deleted file mode 100644
index cf1210e8e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_06.png b/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_06.png
deleted file mode 100644
index d30ab5ea1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_07.png b/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_07.png
deleted file mode 100644
index 5c20c1151..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_08.png b/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_08.png
deleted file mode 100644
index c8035cb3c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_09.png b/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_09.png
deleted file mode 100644
index e43a34514..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_elfopponent_squished_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_01.png b/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_01.png
deleted file mode 100644
index f485afff9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_02.png b/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_02.png
deleted file mode 100644
index 76f71db75..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_03.png b/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_03.png
deleted file mode 100644
index 6f1f2efd5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_04.png b/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_04.png
deleted file mode 100644
index 2dd902358..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_05.png
deleted file mode 100644
index ea1bb4ce4..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_reindeer_squished_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_01.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_01.png
deleted file mode 100644
index 083de4884..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_02.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_02.png
deleted file mode 100644
index 713264e48..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_03.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_03.png
deleted file mode 100644
index 694fd4d09..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_04.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_04.png
deleted file mode 100644
index 9d78ff3fc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_05.png
deleted file mode 100644
index 6bdcbf82b..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_06.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_06.png
deleted file mode 100644
index 0544872b0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_07.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_07.png
deleted file mode 100644
index 17879fbe7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_08.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_08.png
deleted file mode 100644
index 631f95cbe..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_09.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_09.png
deleted file mode 100644
index 9e4eaa7b1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_10.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_10.png
deleted file mode 100644
index dcf7fc64e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_11.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_11.png
deleted file mode 100644
index ccf1518c7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_12.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_12.png
deleted file mode 100644
index ccf1518c7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_appearing_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_01.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_01.png
deleted file mode 100644
index 6ff7b68ef..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_02.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_02.png
deleted file mode 100644
index 306050c3a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_03.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_03.png
deleted file mode 100644
index cf9f2ce70..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_04.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_04.png
deleted file mode 100644
index 73f176b10..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_05.png
deleted file mode 100644
index 6ff7b68ef..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_06.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_06.png
deleted file mode 100644
index 306050c3a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_07.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_07.png
deleted file mode 100644
index cf9f2ce70..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_08.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_08.png
deleted file mode 100644
index 73f176b10..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_09.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_09.png
deleted file mode 100644
index 40e833530..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_10.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_10.png
deleted file mode 100644
index 545bc38f3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_11.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_11.png
deleted file mode 100644
index 572acfd5a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_12.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_12.png
deleted file mode 100644
index 87668305a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_13.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_13.png
deleted file mode 100644
index 40e833530..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_14.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_14.png
deleted file mode 100644
index 545bc38f3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_15.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_15.png
deleted file mode 100644
index 572acfd5a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_16.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_16.png
deleted file mode 100644
index 87668305a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_elf_opponent_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing01.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing01.png
deleted file mode 100644
index ef70e3e55..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing02.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing02.png
deleted file mode 100644
index 6cc4beed0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing03.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing03.png
deleted file mode 100644
index 35c465ae5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing04.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing04.png
deleted file mode 100644
index 255795ef7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing05.png
deleted file mode 100644
index ef70e3e55..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing06.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing06.png
deleted file mode 100644
index 6cc4beed0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing07.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing07.png
deleted file mode 100644
index 35c465ae5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing08.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing08.png
deleted file mode 100644
index 255795ef7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing09.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing09.png
deleted file mode 100644
index ef70e3e55..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing10.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing10.png
deleted file mode 100644
index 6cc4beed0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing11.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing11.png
deleted file mode 100644
index 35c465ae5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing12.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing12.png
deleted file mode 100644
index 255795ef7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing13.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing13.png
deleted file mode 100644
index ef70e3e55..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing14.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing14.png
deleted file mode 100644
index 6cc4beed0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing15.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing15.png
deleted file mode 100644
index 35c465ae5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing16.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing16.png
deleted file mode 100644
index 255795ef7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_losing16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal01.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal01.png
deleted file mode 100644
index 173d5d746..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal02.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal02.png
deleted file mode 100644
index a7881ac98..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal03.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal03.png
deleted file mode 100644
index f65cfc1a8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal04.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal04.png
deleted file mode 100644
index 1e6083ffd..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal05.png
deleted file mode 100644
index 173d5d746..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal06.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal06.png
deleted file mode 100644
index a7881ac98..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal07.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal07.png
deleted file mode 100644
index f65cfc1a8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal08.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal08.png
deleted file mode 100644
index 1e6083ffd..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal09.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal09.png
deleted file mode 100644
index 173d5d746..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal10.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal10.png
deleted file mode 100644
index a7881ac98..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal11.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal11.png
deleted file mode 100644
index f65cfc1a8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal12.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal12.png
deleted file mode 100644
index 1e6083ffd..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal13.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal13.png
deleted file mode 100644
index 173d5d746..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal14.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal14.png
deleted file mode 100644
index a7881ac98..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal15.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal15.png
deleted file mode 100644
index f65cfc1a8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal16.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal16.png
deleted file mode 100644
index 1e6083ffd..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_normal16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_01.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_01.png
deleted file mode 100644
index af68faf19..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_02.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_02.png
deleted file mode 100644
index 1eac24824..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_03.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_03.png
deleted file mode 100644
index d936df008..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_04.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_04.png
deleted file mode 100644
index d609979cf..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_05.png
deleted file mode 100644
index af68faf19..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_06.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_06.png
deleted file mode 100644
index 1eac24824..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_07.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_07.png
deleted file mode 100644
index d936df008..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_08.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_08.png
deleted file mode 100644
index d609979cf..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_09.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_09.png
deleted file mode 100644
index 3c8a8c2a8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_10.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_10.png
deleted file mode 100644
index 24e7e8d84..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_11.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_11.png
deleted file mode 100644
index 92d1adba0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_12.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_12.png
deleted file mode 100644
index e1fe958dc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_13.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_13.png
deleted file mode 100644
index 3c8a8c2a8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_14.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_14.png
deleted file mode 100644
index 24e7e8d84..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_15.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_15.png
deleted file mode 100644
index 92d1adba0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_16.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_16.png
deleted file mode 100644
index e1fe958dc..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_reindeer_opponent_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_01.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_01.png
deleted file mode 100644
index 871852cc1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_02.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_02.png
deleted file mode 100644
index 7ab70007e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_03.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_03.png
deleted file mode 100644
index 2474d298f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_04.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_04.png
deleted file mode 100644
index 2a41f0f69..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_05.png
deleted file mode 100644
index 871852cc1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_06.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_06.png
deleted file mode 100644
index 7ab70007e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_07.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_07.png
deleted file mode 100644
index 2474d298f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_08.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_08.png
deleted file mode 100644
index 2a41f0f69..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_09.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_09.png
deleted file mode 100644
index fc9317e52..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_10.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_10.png
deleted file mode 100644
index ccde5899d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_11.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_11.png
deleted file mode 100644
index 203540533..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_12.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_12.png
deleted file mode 100644
index 01cd58010..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_13.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_13.png
deleted file mode 100644
index fc9317e52..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_14.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_14.png
deleted file mode 100644
index ccde5899d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_15.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_15.png
deleted file mode 100644
index 203540533..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_16.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_16.png
deleted file mode 100644
index 01cd58010..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_snowman_opponent_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_elfopponent.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_elfopponent.png
deleted file mode 100644
index d4863e0e7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_elfopponent.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_reindeer.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_reindeer.png
deleted file mode 100644
index 79f6d46ea..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_reindeer.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_runner.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_runner.png
deleted file mode 100644
index 00443180d..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_runner.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_snowman.png b/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_snowman.png
deleted file mode 100644
index 6498612d5..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_running_starting_snowman.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_sidestep.png b/doodles/src/main/res/drawable-nodpi/snowballrun_sidestep.png
deleted file mode 100644
index 31316baa9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_sidestep.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_05.png
deleted file mode 100644
index 2700abdad..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_06.png b/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_06.png
deleted file mode 100644
index cf598a9b8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_07.png b/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_07.png
deleted file mode 100644
index fd98092c3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_08.png b/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_08.png
deleted file mode 100644
index 342524c8e..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_09.png b/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_09.png
deleted file mode 100644
index 20e140294..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_snowman_squished_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_standing_elf.png b/doodles/src/main/res/drawable-nodpi/snowballrun_standing_elf.png
deleted file mode 100644
index 93254928c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_standing_elf.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_standing_elfopponent.png b/doodles/src/main/res/drawable-nodpi/snowballrun_standing_elfopponent.png
deleted file mode 100644
index d9dbe11d8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_standing_elfopponent.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_standing_reindeer.png b/doodles/src/main/res/drawable-nodpi/snowballrun_standing_reindeer.png
deleted file mode 100644
index f263a71e6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_standing_reindeer.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_standing_snowman.png b/doodles/src/main/res/drawable-nodpi/snowballrun_standing_snowman.png
deleted file mode 100644
index 5ed548682..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_standing_snowman.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_01.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_01.png
deleted file mode 100644
index 55783baca..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_02.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_02.png
deleted file mode 100644
index 96f13b0de..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_03.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_03.png
deleted file mode 100644
index e7b0f987c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_04.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_04.png
deleted file mode 100644
index 1834e92e2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_05.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_05.png
deleted file mode 100644
index 09d7522d4..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_06.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_06.png
deleted file mode 100644
index c22a797cd..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_07.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_07.png
deleted file mode 100644
index d9f51b7cd..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_08.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_08.png
deleted file mode 100644
index b9974ffda..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_09.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_09.png
deleted file mode 100644
index 4faaa17d6..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_10.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_10.png
deleted file mode 100644
index 9ec4be9a9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_10.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_11.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_11.png
deleted file mode 100644
index 976e957a3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_11.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_12.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_12.png
deleted file mode 100644
index ae1dd2ca9..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_12.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_13.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_13.png
deleted file mode 100644
index 848820873..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_13.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_14.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_14.png
deleted file mode 100644
index 0dc3f0737..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_14.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_15.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_15.png
deleted file mode 100644
index 603ee7a78..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_15.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_16.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_16.png
deleted file mode 100644
index d7885a8b1..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_16.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_17.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_17.png
deleted file mode 100644
index c557c6fbf..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_17.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_18.png b/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_18.png
deleted file mode 100644
index 0803a0d4c..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrun_tutorials_18.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrunner_background.png b/doodles/src/main/res/drawable-nodpi/snowballrunner_background.png
deleted file mode 100644
index 665ba710a..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrunner_background.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/snowballrunner_loadingscreen.png b/doodles/src/main/res/drawable-nodpi/snowballrunner_loadingscreen.png
deleted file mode 100644
index 87f88c346..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/snowballrunner_loadingscreen.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/swimming_rings_00.png b/doodles/src/main/res/drawable-nodpi/swimming_rings_00.png
deleted file mode 100644
index 47e106517..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/swimming_rings_00.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/swimming_rings_01.png b/doodles/src/main/res/drawable-nodpi/swimming_rings_01.png
deleted file mode 100644
index c31b96cd3..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/swimming_rings_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/swimming_rings_02.png b/doodles/src/main/res/drawable-nodpi/swimming_rings_02.png
deleted file mode 100644
index 162976753..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/swimming_rings_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/swimming_rings_03.png b/doodles/src/main/res/drawable-nodpi/swimming_rings_03.png
deleted file mode 100644
index 9fcff99b2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/swimming_rings_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/swimming_rings_04.png b/doodles/src/main/res/drawable-nodpi/swimming_rings_04.png
deleted file mode 100644
index 6e1ea4051..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/swimming_rings_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/swimming_rings_05.png b/doodles/src/main/res/drawable-nodpi/swimming_rings_05.png
deleted file mode 100644
index e60b150da..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/swimming_rings_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/swimming_rings_06.png b/doodles/src/main/res/drawable-nodpi/swimming_rings_06.png
deleted file mode 100644
index 3828b4856..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/swimming_rings_06.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/swimming_rings_07.png b/doodles/src/main/res/drawable-nodpi/swimming_rings_07.png
deleted file mode 100644
index 1dfb33ab0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/swimming_rings_07.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/swimming_rings_08.png b/doodles/src/main/res/drawable-nodpi/swimming_rings_08.png
deleted file mode 100644
index e7e39c8c0..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/swimming_rings_08.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/swimming_rings_09.png b/doodles/src/main/res/drawable-nodpi/swimming_rings_09.png
deleted file mode 100644
index a956e4ecd..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/swimming_rings_09.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/tutoappear_new_00.png b/doodles/src/main/res/drawable-nodpi/tutoappear_new_00.png
deleted file mode 100644
index 72905ab2f..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/tutoappear_new_00.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/tutoappear_new_01.png b/doodles/src/main/res/drawable-nodpi/tutoappear_new_01.png
deleted file mode 100644
index 6121a0ef2..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/tutoappear_new_01.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/tutoappear_new_02.png b/doodles/src/main/res/drawable-nodpi/tutoappear_new_02.png
deleted file mode 100644
index fbd1c3849..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/tutoappear_new_02.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/tutoappear_new_03.png b/doodles/src/main/res/drawable-nodpi/tutoappear_new_03.png
deleted file mode 100644
index 5510d7ce8..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/tutoappear_new_03.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/tutoappear_new_04.png b/doodles/src/main/res/drawable-nodpi/tutoappear_new_04.png
deleted file mode 100644
index 184edbfb7..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/tutoappear_new_04.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/tutoappear_new_05.png b/doodles/src/main/res/drawable-nodpi/tutoappear_new_05.png
deleted file mode 100644
index bec000cfe..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/tutoappear_new_05.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable-nodpi/waterpolo_target.png b/doodles/src/main/res/drawable-nodpi/waterpolo_target.png
deleted file mode 100644
index 99413ce53..000000000
Binary files a/doodles/src/main/res/drawable-nodpi/waterpolo_target.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable/dim_overlay.xml b/doodles/src/main/res/drawable/dim_overlay.xml
deleted file mode 100644
index 5f24df407..000000000
--- a/doodles/src/main/res/drawable/dim_overlay.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
diff --git a/doodles/src/main/res/drawable/game_button_default.xml b/doodles/src/main/res/drawable/game_button_default.xml
deleted file mode 100644
index 20dca3249..000000000
--- a/doodles/src/main/res/drawable/game_button_default.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
diff --git a/doodles/src/main/res/drawable/game_button_focused.xml b/doodles/src/main/res/drawable/game_button_focused.xml
deleted file mode 100644
index f4bf6cfa4..000000000
--- a/doodles/src/main/res/drawable/game_button_focused.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
diff --git a/doodles/src/main/res/drawable/game_button_pressed.xml b/doodles/src/main/res/drawable/game_button_pressed.xml
deleted file mode 100644
index d68cf964a..000000000
--- a/doodles/src/main/res/drawable/game_button_pressed.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
diff --git a/doodles/src/main/res/drawable/google.png b/doodles/src/main/res/drawable/google.png
deleted file mode 100644
index daefaa4a8..000000000
Binary files a/doodles/src/main/res/drawable/google.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable/ic_launcher.png b/doodles/src/main/res/drawable/ic_launcher.png
deleted file mode 100644
index e039fd8c0..000000000
Binary files a/doodles/src/main/res/drawable/ic_launcher.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable/pineapple_icon_pause.png b/doodles/src/main/res/drawable/pineapple_icon_pause.png
deleted file mode 100644
index c7c2b6e3a..000000000
Binary files a/doodles/src/main/res/drawable/pineapple_icon_pause.png and /dev/null differ
diff --git a/doodles/src/main/res/drawable/swimming_dive_arrow.png b/doodles/src/main/res/drawable/swimming_dive_arrow.png
deleted file mode 100644
index 4eb9f3ea4..000000000
Binary files a/doodles/src/main/res/drawable/swimming_dive_arrow.png and /dev/null differ
diff --git a/doodles/src/main/res/layout/activity_view.xml b/doodles/src/main/res/layout/activity_view.xml
deleted file mode 100644
index fe3fd2683..000000000
--- a/doodles/src/main/res/layout/activity_view.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
diff --git a/doodles/src/main/res/layout/fact_view.xml b/doodles/src/main/res/layout/fact_view.xml
deleted file mode 100644
index 101d8d5e4..000000000
--- a/doodles/src/main/res/layout/fact_view.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
diff --git a/doodles/src/main/res/layout/pause_view.xml b/doodles/src/main/res/layout/pause_view.xml
deleted file mode 100644
index 3d0303c26..000000000
--- a/doodles/src/main/res/layout/pause_view.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doodles/src/main/res/raw/diamond.png b/doodles/src/main/res/raw/diamond.png
deleted file mode 100644
index c34ef760a..000000000
Binary files a/doodles/src/main/res/raw/diamond.png and /dev/null differ
diff --git a/doodles/src/main/res/raw/fork_in.png b/doodles/src/main/res/raw/fork_in.png
deleted file mode 100644
index a764f007e..000000000
Binary files a/doodles/src/main/res/raw/fork_in.png and /dev/null differ
diff --git a/doodles/src/main/res/raw/fork_out.png b/doodles/src/main/res/raw/fork_out.png
deleted file mode 100644
index 1354c96d3..000000000
Binary files a/doodles/src/main/res/raw/fork_out.png and /dev/null differ
diff --git a/doodles/src/main/res/raw/fruit_doodle_music.ogg b/doodles/src/main/res/raw/fruit_doodle_music.ogg
deleted file mode 100644
index 7b2617d10..000000000
Binary files a/doodles/src/main/res/raw/fruit_doodle_music.ogg and /dev/null differ
diff --git a/doodles/src/main/res/raw/golf_hit_wall.ogg b/doodles/src/main/res/raw/golf_hit_wall.ogg
deleted file mode 100644
index 0d6bf74c4..000000000
Binary files a/doodles/src/main/res/raw/golf_hit_wall.ogg and /dev/null differ
diff --git a/doodles/src/main/res/raw/middle_open.png b/doodles/src/main/res/raw/middle_open.png
deleted file mode 100644
index f8f2adcc0..000000000
Binary files a/doodles/src/main/res/raw/middle_open.png and /dev/null differ
diff --git a/doodles/src/main/res/raw/present_throw_wall_hit_a.ogg b/doodles/src/main/res/raw/present_throw_wall_hit_a.ogg
deleted file mode 100644
index 7b7aff10b..000000000
Binary files a/doodles/src/main/res/raw/present_throw_wall_hit_a.ogg and /dev/null differ
diff --git a/doodles/src/main/res/raw/tennis_bounce.ogg b/doodles/src/main/res/raw/tennis_bounce.ogg
deleted file mode 100644
index aa682f2aa..000000000
Binary files a/doodles/src/main/res/raw/tennis_bounce.ogg and /dev/null differ
diff --git a/doodles/src/main/res/raw/zig.png b/doodles/src/main/res/raw/zig.png
deleted file mode 100644
index a47ab8fea..000000000
Binary files a/doodles/src/main/res/raw/zig.png and /dev/null differ
diff --git a/doodles/src/main/res/raw/ziggeroo.png b/doodles/src/main/res/raw/ziggeroo.png
deleted file mode 100644
index a8e72ba2c..000000000
Binary files a/doodles/src/main/res/raw/ziggeroo.png and /dev/null differ
diff --git a/doodles/src/main/res/values-af/strings.xml b/doodles/src/main/res/values-af/strings.xml
deleted file mode 100644
index 91f6439a2..000000000
--- a/doodles/src/main/res/values-af/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Sneeubal-hardloop
- Pikkewynswem
- Geskenkgooi
- Beste: %1$s
- Demp
- Ontdemp
- Maak toe
- Laat wag
- Speletjie verby
- Hervat
- Herspeel
- Tuis
- Deel
- BESTE TELLING: <b>%1$s</b>
- %1$.1f s.
- %1$d m
-
diff --git a/doodles/src/main/res/values-ar-rXB/strings.xml b/doodles/src/main/res/values-ar-rXB/strings.xml
deleted file mode 100644
index 33b460e3b..000000000
--- a/doodles/src/main/res/values-ar-rXB/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Present Throw
- Best: %1$s
- Mute
- Unmute
- Close
- Pause
- Game Over
- Resume
- Replay
- Home
- Share
- BEST SCORE: <b>%1$s</b>
- %1$.1fs
- %1$dm
-
diff --git a/doodles/src/main/res/values-bg/strings.xml b/doodles/src/main/res/values-bg/strings.xml
deleted file mode 100644
index a25e8dd87..000000000
--- a/doodles/src/main/res/values-bg/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Замерване със снежни топки
- Плуващият пингвин
- Хвърляне на подаръци
- Най-добър резултат: %1$s
- Без звук
- Включване на звука
- Затваряне
- Поставяне на пауза
- Край на играта
- Възобновяване
- Повторно пускане
- Начална страница
- Споделяне
- НАЙ-ДОБЪР РЕЗУЛТАТ: <b>%1$s</b>
- %1$.1f сек
- %1$d м
-
diff --git a/doodles/src/main/res/values-ca/strings.xml b/doodles/src/main/res/values-ca/strings.xml
deleted file mode 100644
index 92d706c69..000000000
--- a/doodles/src/main/res/values-ca/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Fuig de la bola de neu
- Pingüí nedador
- Llançament de regals
- Millor puntuació: %1$s
- Silencia
- Deixa de silenciar
- Tanca
- Posa en pausa
- Fi de la partida
- Reprèn
- Torna a jugar
- Inici
- Comparteix
- MILLOR PUNTUACIÓ: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-da/strings.xml b/doodles/src/main/res/values-da/strings.xml
deleted file mode 100644
index 526c8f7bd..000000000
--- a/doodles/src/main/res/values-da/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Løb fra snebolden
- Pingvin på svømmetur
- Gavekast
- Højeste: %1$s
- Slå lyden fra
- Slå lyden til
- Luk
- Sæt på pause
- Spillet er slut
- Genoptag
- Spil videre
- Gå til start
- Del
- HØJESTE SCORE: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-de-rAT/strings.xml b/doodles/src/main/res/values-de-rAT/strings.xml
deleted file mode 100644
index 701d52621..000000000
--- a/doodles/src/main/res/values-de-rAT/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Schneeballrennen
- Pinguinschwimmen
- Geschenkewerfen
- Bestes Ergebnis: %1$s
- Stummschalten
- Stummschaltung aufheben
- Schließen
- Pausieren
- Game Over
- Fortsetzen
- Wiederholen
- Weiter
- Teilen
- BESTES ERGEBNIS: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-de-rCH/strings.xml b/doodles/src/main/res/values-de-rCH/strings.xml
deleted file mode 100644
index 2326ccec5..000000000
--- a/doodles/src/main/res/values-de-rCH/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Schneeballrennen
- Pinguinschwimmen
- Geschenkewerfen
- Bestes Ergebnis: %1$s
- Stummschalten
- Stummschaltung aufheben
- Schliessen
- Pausieren
- Game Over
- Fortsetzen
- Wiederholen
- Weiter
- Teilen
- BESTES ERGEBNIS: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-de/strings.xml b/doodles/src/main/res/values-de/strings.xml
deleted file mode 100644
index 701d52621..000000000
--- a/doodles/src/main/res/values-de/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Schneeballrennen
- Pinguinschwimmen
- Geschenkewerfen
- Bestes Ergebnis: %1$s
- Stummschalten
- Stummschaltung aufheben
- Schließen
- Pausieren
- Game Over
- Fortsetzen
- Wiederholen
- Weiter
- Teilen
- BESTES ERGEBNIS: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-en-rGB/strings.xml b/doodles/src/main/res/values-en-rGB/strings.xml
deleted file mode 100644
index f0dcff5be..000000000
--- a/doodles/src/main/res/values-en-rGB/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Present Throw
- Best: %1$s
- Mute
- Unmute
- Close
- Pause
- Game Over
- Resume
- Replay
- Home
- Share
- BEST SCORE: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-en-rIE/strings.xml b/doodles/src/main/res/values-en-rIE/strings.xml
deleted file mode 100644
index f0dcff5be..000000000
--- a/doodles/src/main/res/values-en-rIE/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Present Throw
- Best: %1$s
- Mute
- Unmute
- Close
- Pause
- Game Over
- Resume
- Replay
- Home
- Share
- BEST SCORE: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-en-rIN/strings.xml b/doodles/src/main/res/values-en-rIN/strings.xml
deleted file mode 100644
index f0dcff5be..000000000
--- a/doodles/src/main/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Present Throw
- Best: %1$s
- Mute
- Unmute
- Close
- Pause
- Game Over
- Resume
- Replay
- Home
- Share
- BEST SCORE: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-en-rSG/strings.xml b/doodles/src/main/res/values-en-rSG/strings.xml
deleted file mode 100644
index f0dcff5be..000000000
--- a/doodles/src/main/res/values-en-rSG/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Present Throw
- Best: %1$s
- Mute
- Unmute
- Close
- Pause
- Game Over
- Resume
- Replay
- Home
- Share
- BEST SCORE: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-en-rXA/strings.xml b/doodles/src/main/res/values-en-rXA/strings.xml
deleted file mode 100644
index 5cb1e691f..000000000
--- a/doodles/src/main/res/values-en-rXA/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- [Šñöŵбåļļ Ŕûñ one two]
- [Þéñĝûîñ Šŵîm one two]
- [Þŕéšéñţ Ţĥŕöŵ one two]
- [Бéšţ: ᐅ%1$sᐊ one two three]
- [Mûţé one]
- [Ûñmûţé one]
- [Çļöšé one]
- [Þåûšé one]
- [Ĝåmé Övéŕ one two]
- [Ŕéšûmé one]
- [Ŕéþļåý one]
- [Ĥömé one]
- [Šĥåŕé one]
- [БÉŠŢ ŠÇÖŔÉ: <b>ᐅ%1$sᐊ</b> one two three four]
- [ᐅ%1$.1fᐊš one]
- [ᐅ%1$dᐊm one]
-
diff --git a/doodles/src/main/res/values-en-rXC/strings.xml b/doodles/src/main/res/values-en-rXC/strings.xml
deleted file mode 100644
index 0610930b5..000000000
--- a/doodles/src/main/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Present Throw
- Best: %1$s
- Mute
- Unmute
- Close
- Pause
- Game Over
- Resume
- Replay
- Home
- Share
- BEST SCORE: <b>%1$s</b>
- %1$.1fs
- %1$dm
-
diff --git a/doodles/src/main/res/values-en-rZA/strings.xml b/doodles/src/main/res/values-en-rZA/strings.xml
deleted file mode 100644
index f0dcff5be..000000000
--- a/doodles/src/main/res/values-en-rZA/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Present Throw
- Best: %1$s
- Mute
- Unmute
- Close
- Pause
- Game Over
- Resume
- Replay
- Home
- Share
- BEST SCORE: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rAR/strings.xml b/doodles/src/main/res/values-es-rAR/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rAR/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rBO/strings.xml b/doodles/src/main/res/values-es-rBO/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rBO/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rCL/strings.xml b/doodles/src/main/res/values-es-rCL/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rCL/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rCO/strings.xml b/doodles/src/main/res/values-es-rCO/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rCO/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rCR/strings.xml b/doodles/src/main/res/values-es-rCR/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rCR/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rDO/strings.xml b/doodles/src/main/res/values-es-rDO/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rDO/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rEC/strings.xml b/doodles/src/main/res/values-es-rEC/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rEC/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rGT/strings.xml b/doodles/src/main/res/values-es-rGT/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rGT/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rHN/strings.xml b/doodles/src/main/res/values-es-rHN/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rHN/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rMX/strings.xml b/doodles/src/main/res/values-es-rMX/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rMX/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rNI/strings.xml b/doodles/src/main/res/values-es-rNI/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rNI/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rPA/strings.xml b/doodles/src/main/res/values-es-rPA/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rPA/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rPE/strings.xml b/doodles/src/main/res/values-es-rPE/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rPE/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rPR/strings.xml b/doodles/src/main/res/values-es-rPR/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rPR/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rPY/strings.xml b/doodles/src/main/res/values-es-rPY/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rPY/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rSV/strings.xml b/doodles/src/main/res/values-es-rSV/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rSV/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rUS/strings.xml b/doodles/src/main/res/values-es-rUS/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rUS/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rUY/strings.xml b/doodles/src/main/res/values-es-rUY/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rUY/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es-rVE/strings.xml b/doodles/src/main/res/values-es-rVE/strings.xml
deleted file mode 100644
index 4b9aa7320..000000000
--- a/doodles/src/main/res/values-es-rVE/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Carrera de bolas de nieve
- Nada con los pingüinos
- Lanzamiento de regalos
- Mejor puntuación: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin del juego
- Reanudar
- Volver a jugar
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-es/strings.xml b/doodles/src/main/res/values-es/strings.xml
deleted file mode 100644
index b0c7de262..000000000
--- a/doodles/src/main/res/values-es/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Persecución en la nieve
- ¡Al agua, pingüinos!
- Lanzamiento de regalos
- Mejor: %1$s
- Silenciar
- Dejar de silenciar
- Cerrar
- Pausar
- Fin de la partida
- Reanudar
- Reiniciar partida
- Inicio
- Compartir
- MEJOR PUNTUACIÓN: <b>%1$s</b>
- %1$.1f s
- %1$d min
-
diff --git a/doodles/src/main/res/values-et/strings.xml b/doodles/src/main/res/values-et/strings.xml
deleted file mode 100644
index 3be0db6b8..000000000
--- a/doodles/src/main/res/values-et/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Lumepallijooks
- Pingviiniujumine
- Kingiheide
- Parim: %1$s
- Vaigista
- Tühista vaigistus
- Sulge
- Peata
- Mäng läbi
- Jätka
- Mängi uuesti
- Avakuva
- Jaga
- PARIM TULEMUS: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-fi/strings.xml b/doodles/src/main/res/values-fi/strings.xml
deleted file mode 100644
index 151917856..000000000
--- a/doodles/src/main/res/values-fi/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Lumipallojuoksu
- Pingviiniuinti
- Lahjanheitto
- Paras: %1$s
- Mykistä
- Poista mykistys
- Sulje
- Keskeytä
- Peli loppui
- Jatka
- Pelaa uudelleen
- Etusivu
- Jaa
- PARAS TULOS: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-fil/strings.xml b/doodles/src/main/res/values-fil/strings.xml
deleted file mode 100644
index 2fee00918..000000000
--- a/doodles/src/main/res/values-fil/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Present Throw
- Pinakamataas: %1$s
- I-mute
- I-unmute
- Isara
- I-pause
- Tapos na ang Laro
- Magpatuloy
- I-replay
- Home
- bahagi
- PINAKAMATAAS NA SCORE: <b>%1$s</b>
- %1$.1fs
- %1$dm
-
diff --git a/doodles/src/main/res/values-fr-rCA/strings.xml b/doodles/src/main/res/values-fr-rCA/strings.xml
deleted file mode 100644
index f412f57ce..000000000
--- a/doodles/src/main/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Évitement de la boule de neige
- Nage du pingouin
- Lancer de cadeaux
- Meilleur score : %1$s
- Couper le son
- Réactiver le son
- Fermer
- Interrompre
- Partie terminée!
- Reprendre
- Jouer de nouveau
- Accueil
- Partager
- MEILLEUR SCORE : <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-fr-rCH/strings.xml b/doodles/src/main/res/values-fr-rCH/strings.xml
deleted file mode 100644
index f725196ce..000000000
--- a/doodles/src/main/res/values-fr-rCH/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Échapper aux boules de neige
- La nage du pingouin
- Lancer de cadeaux
- Meilleur score : %1$s
- Couper le son
- Réactiver le son
- Fermer
- Mettre en pause
- Jeu terminé
- Reprendre
- Réessayer
- Accueil
- Partager
- MEILLEUR SCORE : <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-fr/strings.xml b/doodles/src/main/res/values-fr/strings.xml
deleted file mode 100644
index f725196ce..000000000
--- a/doodles/src/main/res/values-fr/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Échapper aux boules de neige
- La nage du pingouin
- Lancer de cadeaux
- Meilleur score : %1$s
- Couper le son
- Réactiver le son
- Fermer
- Mettre en pause
- Jeu terminé
- Reprendre
- Réessayer
- Accueil
- Partager
- MEILLEUR SCORE : <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-gsw/strings.xml b/doodles/src/main/res/values-gsw/strings.xml
deleted file mode 100644
index 701d52621..000000000
--- a/doodles/src/main/res/values-gsw/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Schneeballrennen
- Pinguinschwimmen
- Geschenkewerfen
- Bestes Ergebnis: %1$s
- Stummschalten
- Stummschaltung aufheben
- Schließen
- Pausieren
- Game Over
- Fortsetzen
- Wiederholen
- Weiter
- Teilen
- BESTES ERGEBNIS: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-hr/strings.xml b/doodles/src/main/res/values-hr/strings.xml
deleted file mode 100644
index c8c372115..000000000
--- a/doodles/src/main/res/values-hr/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Grudanje vilenjaka
- Plivačka utrka pingvina
- Bacanje darova
- Najbolji rezultat: %1$s
- Isključi zvuk
- Uključi zvuk
- Zatvori
- Pauziraj
- Igra je gotova
- Nastavi
- Ponovna igra
- Početna
- Dijeli
- NAJBOLJI REZULTAT: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-id/strings.xml b/doodles/src/main/res/values-id/strings.xml
deleted file mode 100644
index 4f2ee6582..000000000
--- a/doodles/src/main/res/values-id/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Lari dari Kejaran Bola Salju
- Renang Penguin
- Melempar Hadiah
- Terbaik: %1$s
- Matikan suara
- Nyalakan suara
- Tutup
- Jeda
- Game Selesai
- Lanjutkan
- Main Lagi
- Beranda
- Bagikan
- SKOR TERBAIK: <b>%1$s</b>
- %1$.1f dtk
- %1$d m
-
diff --git a/doodles/src/main/res/values-in/strings.xml b/doodles/src/main/res/values-in/strings.xml
deleted file mode 100644
index 4f2ee6582..000000000
--- a/doodles/src/main/res/values-in/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Lari dari Kejaran Bola Salju
- Renang Penguin
- Melempar Hadiah
- Terbaik: %1$s
- Matikan suara
- Nyalakan suara
- Tutup
- Jeda
- Game Selesai
- Lanjutkan
- Main Lagi
- Beranda
- Bagikan
- SKOR TERBAIK: <b>%1$s</b>
- %1$.1f dtk
- %1$d m
-
diff --git a/doodles/src/main/res/values-it/strings.xml b/doodles/src/main/res/values-it/strings.xml
deleted file mode 100644
index 300c25610..000000000
--- a/doodles/src/main/res/values-it/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Corri elfo, corri
- Nuota pinguino
- Lancio dei regali
- Punteggio migliore: %1$s
- Disattiva l\'audio
- Riattiva l\'audio
- Chiudi
- Pausa
- Game over
- Riprendi
- Gioca ancora
- Home page
- Condividi
- PUNTEGGIO MIGLIORE: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-ja/strings.xml b/doodles/src/main/res/values-ja/strings.xml
deleted file mode 100644
index 4bd2fc72a..000000000
--- a/doodles/src/main/res/values-ja/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- 雪玉ダッシュ
- ペンギン スイミング
- Present Throw
- ベストスコア: %1$s
- ミュート
- ミュートを解除
- 閉じる
- 一時停止
- ゲームオーバー
- 再開
- もう一度プレイ
- ホーム
- 共有
- ベストスコア: <b>%1$s</b>
- %1$.1f 秒
- %1$d m
-
diff --git a/doodles/src/main/res/values-ko/strings.xml b/doodles/src/main/res/values-ko/strings.xml
deleted file mode 100644
index a32e3920d..000000000
--- a/doodles/src/main/res/values-ko/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- 눈덩이 피해 달리기
- 펭귄 수영
- 선물 던지기
- 최고 득점: %1$s
- 음소거
- 음소거 해제
- 닫기
- 일시중지
- 게임 종료
- 계속
- 다시 시작
- 홈
- 공유
- 최고 득점: <b>%1$s</b>
- %1$.1f초
- %1$d미터
-
diff --git a/doodles/src/main/res/values-ln/strings.xml b/doodles/src/main/res/values-ln/strings.xml
deleted file mode 100644
index f725196ce..000000000
--- a/doodles/src/main/res/values-ln/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Échapper aux boules de neige
- La nage du pingouin
- Lancer de cadeaux
- Meilleur score : %1$s
- Couper le son
- Réactiver le son
- Fermer
- Mettre en pause
- Jeu terminé
- Reprendre
- Réessayer
- Accueil
- Partager
- MEILLEUR SCORE : <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-lt/strings.xml b/doodles/src/main/res/values-lt/strings.xml
deleted file mode 100644
index 37a796604..000000000
--- a/doodles/src/main/res/values-lt/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Bėgimas nuo sniego kamuolio
- Plaukiojantis pingvinas
- Dovanų svaidymas
- Geriausias rezultatas: %1$s
- Nutildyti
- Įjungti garsą
- Uždaryti
- Pristabdyti
- Žaidimo pabaiga
- Tęsti
- Paleisti iš naujo
- Pagrindinis puslapis
- Bendrinti
- GERIAUSIAS REZULTATAS: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-lv/strings.xml b/doodles/src/main/res/values-lv/strings.xml
deleted file mode 100644
index 0d21cee83..000000000
--- a/doodles/src/main/res/values-lv/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Bēgšana no sniega pikām
- Pingvīna peldēšana
- Dāvanu mešanas spēle (Present Throw)
- Labākais rezultāts: %1$s
- Izslēgt skaņu
- Ieslēgt skaņu
- Aizvērt
- Apturēt
- Spēle beigusies
- Atsākt
- Spēlēt atkārtoti
- Sākums
- Kopīgot
- LABĀKAIS REZULTĀTS: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-ml/strings.xml b/doodles/src/main/res/values-ml/strings.xml
deleted file mode 100644
index b556feedc..000000000
--- a/doodles/src/main/res/values-ml/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- സ്നോബോൾ റൺ
- പെൻഗ്വിൻ സ്വിം
- പ്രസന്റ് ത്രോ
- മികച്ചത്: %1$s
- മ്യൂട്ടുചെയ്യുക
- അൺമ്യൂട്ടുചെയ്യുക
- അടയ്ക്കുക
- താൽക്കാലികമായി നിർത്തുക
- ഗെയിം കഴിഞ്ഞു
- പുനരാരംഭിക്കുക
- വീണ്ടും പ്ലേ ചെയ്യുക
- ഹോം
- പങ്കിടുക
- മികച്ച സ്കോർ: <b>%1$s</b>
- %1$.1fസെക്കൻഡ്
- %1$dമിനിറ്റ്
-
diff --git a/doodles/src/main/res/values-mo/strings.xml b/doodles/src/main/res/values-mo/strings.xml
deleted file mode 100644
index 5c3479b67..000000000
--- a/doodles/src/main/res/values-mo/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Fugi de bulgăre
- Înotul pinguinului
- Azvârle cadouri
- Cel mai bun: %1$s
- Dezactivează
- Activează
- Închide
- Întrerupe
- Joc încheiat
- Reia
- Redă
- Pagina de pornire
- Arată
- CEL MAI BUN SCOR: <b>%1$s</b>
- Secunde: %1$.1f
- Metri: %1$d
-
diff --git a/doodles/src/main/res/values-nb/strings.xml b/doodles/src/main/res/values-nb/strings.xml
deleted file mode 100644
index 48c244564..000000000
--- a/doodles/src/main/res/values-nb/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snøballflukt
- Pingvinsvømming
- Gavekasting
- Beste: %1$s
- Slå av lyden
- Slå på lyden
- Lukk
- Sett på pause
- Spillet er over
- Gjenoppta
- Spill på nytt
- Startside
- Del
- BESTE RESULTAT: <b>%1$s</b>
- %1$.1f s
- %1$d min
-
diff --git a/doodles/src/main/res/values-no/strings.xml b/doodles/src/main/res/values-no/strings.xml
deleted file mode 100644
index 48c244564..000000000
--- a/doodles/src/main/res/values-no/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snøballflukt
- Pingvinsvømming
- Gavekasting
- Beste: %1$s
- Slå av lyden
- Slå på lyden
- Lukk
- Sett på pause
- Spillet er over
- Gjenoppta
- Spill på nytt
- Startside
- Del
- BESTE RESULTAT: <b>%1$s</b>
- %1$.1f s
- %1$d min
-
diff --git a/doodles/src/main/res/values-pl/strings.xml b/doodles/src/main/res/values-pl/strings.xml
deleted file mode 100644
index c211bb643..000000000
--- a/doodles/src/main/res/values-pl/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Ucieczka przed śnieżką
- Pływające pingwiny
- Rzut prezentem
- Najlepszy wynik: %1$s
- Wycisz
- Wyłącz wyciszenie
- Zamknij
- Wstrzymaj
- Koniec gry
- Wznów
- Zagraj jeszcze raz
- Ekran główny
- Udostępnij
- NAJLEPSZY WYNIK: <b>%1$s</b>j
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-pt-rBR/strings.xml b/doodles/src/main/res/values-pt-rBR/strings.xml
deleted file mode 100644
index 171154a70..000000000
--- a/doodles/src/main/res/values-pt-rBR/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Corrida da bola de neve
- Nado do pinguim
- Presente ao alvo
- Melhor pontuação: %1$s
- Desativar som
- Ativar som
- Fechar
- Pausar
- Fim de jogo
- Retomar
- Jogar novamente
- Página inicial
- Compartilhar
- MELHOR PONTUAÇÃO: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-pt-rPT/strings.xml b/doodles/src/main/res/values-pt-rPT/strings.xml
deleted file mode 100644
index ddf5ca49e..000000000
--- a/doodles/src/main/res/values-pt-rPT/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Atirar presentes
- Melhor: %1$s
- Desativar som
- Reativar som
- Fechar
- Interromper
- Fim do jogo
- Retomar
- Reiniciar jogo
- Página inicial
- Partilhar
- MELHOR PONTUAÇÃO: <b>%1$s</b>
- %1$.1f seg.
- %1$d min.
-
diff --git a/doodles/src/main/res/values-pt/strings.xml b/doodles/src/main/res/values-pt/strings.xml
deleted file mode 100644
index 171154a70..000000000
--- a/doodles/src/main/res/values-pt/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Corrida da bola de neve
- Nado do pinguim
- Presente ao alvo
- Melhor pontuação: %1$s
- Desativar som
- Ativar som
- Fechar
- Pausar
- Fim de jogo
- Retomar
- Jogar novamente
- Página inicial
- Compartilhar
- MELHOR PONTUAÇÃO: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-ro/strings.xml b/doodles/src/main/res/values-ro/strings.xml
deleted file mode 100644
index 5c3479b67..000000000
--- a/doodles/src/main/res/values-ro/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Fugi de bulgăre
- Înotul pinguinului
- Azvârle cadouri
- Cel mai bun: %1$s
- Dezactivează
- Activează
- Închide
- Întrerupe
- Joc încheiat
- Reia
- Redă
- Pagina de pornire
- Arată
- CEL MAI BUN SCOR: <b>%1$s</b>
- Secunde: %1$.1f
- Metri: %1$d
-
diff --git a/doodles/src/main/res/values-ru/strings.xml b/doodles/src/main/res/values-ru/strings.xml
deleted file mode 100644
index f6522d3ff..000000000
--- a/doodles/src/main/res/values-ru/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Убеги от снежка
- Заплыв пингвинов
- Доставь подарок
- Лучший результат: %1$s
- Отключить звук
- Включить звук
- Закрыть
- Пауза
- Игра окончена
- Продолжить
- Сыграть ещё раз
- Главная
- Поделиться
- ЛУЧШИЙ РЕЗУЛЬТАТ: <b>%1$s</b>
- %1$.1f сек.
- %1$d м
-
diff --git a/doodles/src/main/res/values-sl/strings.xml b/doodles/src/main/res/values-sl/strings.xml
deleted file mode 100644
index 08e48fb7d..000000000
--- a/doodles/src/main/res/values-sl/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Beg pred snežno kepo
- Pingvinovo plavanje
- Metanje daril
- Najboljši rezultat: %1$s
- Izklopi zvok
- Vklopi zvok
- Zapri
- Začasno ustavi
- Igra je končana
- Nadaljuj
- Ponovno predvajaj
- Domača stran
- Daj v skupno rabo
- NAJBOLJŠI REZULTAT: <b>%1$s</b>
- %1$.1f s
- %1$d m
-
diff --git a/doodles/src/main/res/values-sv/strings.xml b/doodles/src/main/res/values-sv/strings.xml
deleted file mode 100644
index 04d683562..000000000
--- a/doodles/src/main/res/values-sv/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Klappkast
- Bäst: %1$s
- Ljud av
- Ljud på
- Stäng
- Pausa
- Spelet är slut
- Återuppta
- Spela igen
- Startsida
- Dela
- TOPPRESULTAT: <b>%1$s</b>
- %1$.1f sek
- %1$d min
-
diff --git a/doodles/src/main/res/values-sw360dp/dimens.xml b/doodles/src/main/res/values-sw360dp/dimens.xml
deleted file mode 100644
index 609d6502a..000000000
--- a/doodles/src/main/res/values-sw360dp/dimens.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
- 20dp
- 30dp
- 60dp
- 240dp
- 70dp
- 8dp
- 16sp
- 90dp
-
diff --git a/doodles/src/main/res/values-sw600dp/dimens.xml b/doodles/src/main/res/values-sw600dp/dimens.xml
deleted file mode 100644
index cc5042f98..000000000
--- a/doodles/src/main/res/values-sw600dp/dimens.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
- 20dp
- 16sp
- 120dp
- 80dp
- 140dp
- 100dp
- 280dp
- 20dp
- 70dp
- 8dp
- 16sp
- 100dp
- 80dp
- 70dp
- 240dp
-
diff --git a/doodles/src/main/res/values-ta/strings.xml b/doodles/src/main/res/values-ta/strings.xml
deleted file mode 100644
index 66cb4c6a6..000000000
--- a/doodles/src/main/res/values-ta/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- ஸ்னோபால் ரன்
- பென்குயின் ஸ்விம்
- பிரசென்ட் த்ரோ
- சிறந்தது: %1$s
- ஒலியடக்கும் பொத்தான்
- ஒலி இயக்கும் பொத்தான்
- மூடும் பொத்தான்
- இடைநிறுத்தும் பொத்தான்
- ஆட்டம் முடிந்தது
- மீண்டும் தொடங்கு
- மீண்டும் இயக்கு
- முகப்பு
- பகிர்
- சிறந்த ஸ்கோர்: <b>%1$s</b>
- %1$.1fவி
- %1$dமீ
-
diff --git a/doodles/src/main/res/values-th/strings.xml b/doodles/src/main/res/values-th/strings.xml
deleted file mode 100644
index 552831d9f..000000000
--- a/doodles/src/main/res/values-th/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- โยนของขวัญ
- ดีที่สุด: %1$s
- ปิดเสียง
- เปิดเสียง
- ปิด
- หยุดชั่วคราว
- จบเกม
- เล่นต่อ
- เล่นอีกครั้ง
- หน้าแรก
- แชร์
- คะแนนที่ดีที่สุด: <b>%1$s</b>
- %1$.1f วินาที
- %1$d นาที
-
diff --git a/doodles/src/main/res/values-tl/strings.xml b/doodles/src/main/res/values-tl/strings.xml
deleted file mode 100644
index 2fee00918..000000000
--- a/doodles/src/main/res/values-tl/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Snowball Run
- Penguin Swim
- Present Throw
- Pinakamataas: %1$s
- I-mute
- I-unmute
- Isara
- I-pause
- Tapos na ang Laro
- Magpatuloy
- I-replay
- Home
- bahagi
- PINAKAMATAAS NA SCORE: <b>%1$s</b>
- %1$.1fs
- %1$dm
-
diff --git a/doodles/src/main/res/values-uk/strings.xml b/doodles/src/main/res/values-uk/strings.xml
deleted file mode 100644
index a370d8e9b..000000000
--- a/doodles/src/main/res/values-uk/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Утікай від сніжки
- Заплив пінгвіна
- Роздача подарунків
- Найкращий результат: %1$s
- Вимкнути звук
- Увімкнути звук
- Закрити
- Призупинити
- Гру закінчено
- Відновити
- Грати ще раз
- Головний екран
- Поділитися
- НАЙКРАЩИЙ РЕЗУЛЬТАТ: <b>%1$s</b>
- %1$.1f с
- %1$d хв
-
diff --git a/doodles/src/main/res/values-vi/strings.xml b/doodles/src/main/res/values-vi/strings.xml
deleted file mode 100644
index 84010b445..000000000
--- a/doodles/src/main/res/values-vi/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Chạy bóng tuyết
- Chim cánh cụt bơi lội
- Ném quà
- Điểm tốt nhất: %1$s
- Tắt tiếng
- Bật tiếng
- Đóng
- Tạm dừng
- Kết thúc
- Tiếp tục
- Phát lại
- Trang chủ
- Chia sẻ
- ĐIỂM TỐT NHẤT: <b>%1$s</b>
- %1$.1f giây
- %1$d mét
-
diff --git a/doodles/src/main/res/values-zh-rCN/strings.xml b/doodles/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index f0bf698b2..000000000
--- a/doodles/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- 雪球跑跑跑
- 企鹅爱游泳
- 扔礼物
- 最高得分:%1$s
- 静音
- 取消静音
- 关闭
- 暂停
- 游戏结束
- 继续
- 再玩一次
- 主屏幕
- 分享
- 最高得分:<b>%1$s</b>
- %1$.1f 秒
- %1$d 米
-
diff --git a/doodles/src/main/res/values-zh-rHK/strings.xml b/doodles/src/main/res/values-zh-rHK/strings.xml
deleted file mode 100644
index be496e2be..000000000
--- a/doodles/src/main/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- 雪球大暴走
- 企鵝暢泳
- 拋禮物
- 最佳成績:%1$s
- 靜音
- 取消靜音
- 關閉
- 暫停
- 遊戲結束
- 繼續
- 再玩一次
- 主畫面
- 分享
- 最佳成績:<b>%1$s</b>
- %1$.1f 秒
- %1$d 分鐘
-
diff --git a/doodles/src/main/res/values-zh-rTW/strings.xml b/doodles/src/main/res/values-zh-rTW/strings.xml
deleted file mode 100644
index 39b2d8234..000000000
--- a/doodles/src/main/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- 雪球大進擊
- 企鵝游泳大賽
- 投擲禮物
- 最佳成績:%1$s
- 靜音
- 取消靜音
- 關閉
- 暫停
- 遊戲結束
- 繼續
- 再玩一次
- 主畫面
- 分享
- 最佳成績:<b>%1$s</b>
- %1$.1f 秒
- %1$d 公尺
-
diff --git a/doodles/src/main/res/values-zh/strings.xml b/doodles/src/main/res/values-zh/strings.xml
deleted file mode 100644
index f0bf698b2..000000000
--- a/doodles/src/main/res/values-zh/strings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- 雪球跑跑跑
- 企鹅爱游泳
- 扔礼物
- 最高得分:%1$s
- 静音
- 取消静音
- 关闭
- 暂停
- 游戏结束
- 继续
- 再玩一次
- 主屏幕
- 分享
- 最高得分:<b>%1$s</b>
- %1$.1f 秒
- %1$d 米
-
diff --git a/doodles/src/main/res/values/attrs.xml b/doodles/src/main/res/values/attrs.xml
deleted file mode 100644
index 54a8b78a9..000000000
--- a/doodles/src/main/res/values/attrs.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doodles/src/main/res/values/colors.xml b/doodles/src/main/res/values/colors.xml
deleted file mode 100644
index 191101b76..000000000
--- a/doodles/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
- #ffffff
- #fed32b
- #000000
- #ffffff
- - 0.80
-
- #aa032702
- #00032702
-
diff --git a/doodles/src/main/res/values/dimens.xml b/doodles/src/main/res/values/dimens.xml
deleted file mode 100644
index 0547af35c..000000000
--- a/doodles/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
- 48dp
- 25dp
- 25dp
- 76dp
- 76dp
- 24dp
- 40dp
- 3dp
- 130dp
- 25dp
- 100sp
- 100dp
- 80dp
- 40dp
- 348dp
- 148dp
- 5dp
- 70dp
- 50dp
- 100dp
- 80dp
- 15dp
- 12sp
- 200dp
- 60dp
- 0dp
- 25dp
- 180dp
- 40dp
- 10dp
- 10dp
- 20dp
- 60dp
- 6dp
- 12sp
- 10dp
- 80dp
- 46dp
- 40dp
- 138dp
- 20sp
- 20dp
- 40dp
-
diff --git a/doodles/src/main/res/values/strings.xml b/doodles/src/main/res/values/strings.xml
deleted file mode 100644
index 21963b00a..000000000
--- a/doodles/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
- Snowball Run
- Penguin Swim
- Present Throw
- Best: %1$s
- Mute
- Unmute
- Close
- Pause
-
- Game Over
-
- Load
- Save
- Reset
- Delete
- Delete
- Save
- Cancel
- Create object
- Collision
- Scenery
-
- Resume
- Replay
- Home
- Share
-
- BEST SCORE: <b>%1$s</b>
-
-
-
- %1$.1fs
-
-
- %1$dm
-
- %1$d
- %1$d
- +%1$d
- %1$d
-
diff --git a/doodles/src/main/res/values/values_analytics.xml b/doodles/src/main/res/values/values_analytics.xml
deleted file mode 100644
index 2896fbba0..000000000
--- a/doodles/src/main/res/values/values_analytics.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- Swimming
- Running
- Waterpolo
-
diff --git a/gradle.properties b/gradle.properties
index 689f8bfff..30b4b4f16 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,42 @@
+#
+# Copyright 2019. Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+
org.gradle.daemon=true
+org.gradle.configureondemand=true
org.gradle.parallel=true
-org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m
+org.gradle.caching=true
+
+org.gradle.jvmargs=-Xmx6g -XX:MaxPermSize=1536m
+
+# https://developer.android.com/studio/build/build-cache.html
+android.enableBuildCache=true
+android.enableR8=true
+
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 8c0fb64a8..7a3265ee9 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ed9bb705d..acc201290 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Jan 12 15:23:35 EST 2015
+#Fri Jan 11 14:26:09 AEDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
diff --git a/gradlew b/gradlew
index 91a7e269e..cccdd3d51 100755
--- a/gradlew
+++ b/gradlew
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
##############################################################################
##
@@ -6,20 +6,38 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
-warn ( ) {
+warn () {
echo "$*"
}
-die ( ) {
+die () {
echo
echo "$*"
echo
@@ -30,6 +48,7 @@ die ( ) {
cygwin=false
msys=false
darwin=false
+nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
@@ -40,31 +59,11 @@ case "`uname`" in
MINGW* )
msys=true
;;
+ NONSTOP* )
+ nonstop=true
+ ;;
esac
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
-APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -90,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -114,6 +113,7 @@ fi
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -154,11 +154,19 @@ if $cygwin ; then
esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index aec99730b..e95643d6a 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail
:init
-@rem Get command-line arguments, handling Windowz variants
+@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
diff --git a/gumball/build.gradle b/gumball/build.gradle
new file mode 100644
index 000000000..30448f8f8
--- /dev/null
+++ b/gumball/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.dynamic-feature'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.tools
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+ }
+}
+
+dependencies {
+ implementation project(':santa-tracker')
+
+ implementation rootProject.files('third_party/jbox2d/jbox2d-library-2.2.1.1.jar')
+}
diff --git a/gumball/src/main/AndroidManifest.xml b/gumball/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..8b780fdd0
--- /dev/null
+++ b/gumball/src/main/AndroidManifest.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Edges.java b/gumball/src/main/java/com/google/android/apps/gumball/Edges.java
similarity index 96%
rename from santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Edges.java
rename to gumball/src/main/java/com/google/android/apps/gumball/Edges.java
index 0c01c9ada..97dd04f0a 100644
--- a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Edges.java
+++ b/gumball/src/main/java/com/google/android/apps/gumball/Edges.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
+ * Copyright 2019. Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,26 +14,23 @@
* limitations under the License.
*/
-package com.google.android.apps.santatracker.games.gumball;
+package com.google.android.apps.gumball;
import org.jbox2d.collision.shapes.EdgeShape;
import org.jbox2d.common.Vec2;
-/**
- * Static methods to get the edge paths of the scene
- *
- */
+/** Static methods to get the edge paths of the scene */
public class Edges {
public static EdgeShape[] getCaneEnd() {
EdgeShape[] edgeShapes = new EdgeShape[3];
- //rounded part
+ // rounded part
edgeShapes[0] = new EdgeShape();
edgeShapes[0].set(new Vec2(0.22f, .858f), new Vec2(0.22f, 1.02f));
- //bottom
+ // bottom
edgeShapes[1] = new EdgeShape();
edgeShapes[1].set(new Vec2(0.04f, .84f), new Vec2(.2f, .84f));
- //top
+ // top
edgeShapes[2] = new EdgeShape();
edgeShapes[2].set(new Vec2(0.03f, 1.04f), new Vec2(.2f, 1.04f));
return edgeShapes;
@@ -41,13 +38,13 @@ public static EdgeShape[] getCaneEnd() {
public static EdgeShape[] getCaneEndFlip() {
EdgeShape[] edgeShapes = new EdgeShape[3];
- //rounded part
+ // rounded part
edgeShapes[0] = new EdgeShape();
edgeShapes[0].set(new Vec2(0.04f, .858f), new Vec2(0.04f, 1.02f));
- //bottom
+ // bottom
edgeShapes[1] = new EdgeShape();
edgeShapes[1].set(new Vec2(0.06f, .845f), new Vec2(.263f, .845f));
- //top
+ // top
edgeShapes[2] = new EdgeShape();
edgeShapes[2].set(new Vec2(0.06f, 1.04f), new Vec2(.263f, 1.04f));
return edgeShapes;
@@ -55,13 +52,13 @@ public static EdgeShape[] getCaneEndFlip() {
public static EdgeShape[] getCaneEndReverse() {
EdgeShape[] edgeShapes = new EdgeShape[3];
- //rounded part
+ // rounded part
edgeShapes[0] = new EdgeShape();
edgeShapes[0].set(new Vec2(0.22f, .128f), new Vec2(0.22f, .29f));
- //bottom
+ // bottom
edgeShapes[1] = new EdgeShape();
edgeShapes[1].set(new Vec2(0.04f, .11f), new Vec2(.2f, .11f));
- //top
+ // top
edgeShapes[2] = new EdgeShape();
edgeShapes[2].set(new Vec2(0.03f, .31f), new Vec2(.2f, .31f));
return edgeShapes;
@@ -69,13 +66,13 @@ public static EdgeShape[] getCaneEndReverse() {
public static EdgeShape[] getCaneEndReverseFlip() {
EdgeShape[] edgeShapes = new EdgeShape[3];
- //rounded part
+ // rounded part
edgeShapes[0] = new EdgeShape();
edgeShapes[0].set(new Vec2(0.04f, .128f), new Vec2(0.04f, .29f));
- //connector
+ // connector
edgeShapes[1] = new EdgeShape();
edgeShapes[1].set(new Vec2(0.06f, .31f), new Vec2(0.04f, .29f));
- //top
+ // top
edgeShapes[2] = new EdgeShape();
edgeShapes[2].set(new Vec2(0.06f, .31f), new Vec2(.263f, .31f));
return edgeShapes;
@@ -304,10 +301,10 @@ public static EdgeShape[] getCaneMainSmallAngleNineShapes() {
edgeShapes[0].set(new Vec2(0.01f, 0.935f), new Vec2(3.66f, 0.325f));
edgeShapes[1] = new EdgeShape();
edgeShapes[1].set(new Vec2(0.25f, 0.675f), new Vec2(3.6f, 0.105f));
- //backstop
+ // backstop
edgeShapes[2] = new EdgeShape();
edgeShapes[2].set(new Vec2(0.15f, 0.755f), new Vec2(0.28f, 1.55f));
- //end
+ // end
edgeShapes[3] = new EdgeShape();
edgeShapes[3].set(new Vec2(3.66f, 0.128f), new Vec2(3.70f, 0.295f));
return edgeShapes;
@@ -319,10 +316,10 @@ public static EdgeShape[] getCaneMainSmallAngleTwelveShapes() {
edgeShapes[0].set(new Vec2(0.01f, 0.73f), new Vec2(2.04f, 0.305f));
edgeShapes[1] = new EdgeShape();
edgeShapes[1].set(new Vec2(0.25f, 0.475f), new Vec2(2.0f, 0.1f));
- //backstop
+ // backstop
edgeShapes[2] = new EdgeShape();
edgeShapes[2].set(new Vec2(0.18f, 0.725f), new Vec2(0.29f, 1.30f));
- //end
+ // end
edgeShapes[3] = new EdgeShape();
edgeShapes[3].set(new Vec2(2.01f, 0.128f), new Vec2(2.05f, 0.293f));
return edgeShapes;
@@ -334,10 +331,10 @@ public static EdgeShape[] getCaneMainTinyAngleSixShapes() {
edgeShapes[0].set(new Vec2(0.10f, 0.33f), new Vec2(1.9f, 0.425f));
edgeShapes[1] = new EdgeShape();
edgeShapes[1].set(new Vec2(0.15f, 0.105f), new Vec2(1.8f, 0.20f));
- //backstop
+ // backstop
edgeShapes[2] = new EdgeShape();
edgeShapes[2].set(new Vec2(1.94f, 0.425f), new Vec2(1.91f, 1.07f));
- //end
+ // end
edgeShapes[3] = new EdgeShape();
edgeShapes[3].set(new Vec2(0.07f, 0.128f), new Vec2(0.06f, 0.313f));
@@ -356,7 +353,7 @@ public static EdgeShape[] getCaneMainSmallAngleSixShapes() {
edgeShapes[0].set(new Vec2(0.05f, 0.515f), new Vec2(2.69f, 0.329f));
edgeShapes[1] = new EdgeShape();
edgeShapes[1].set(new Vec2(0.30f, 0.285f), new Vec2(2.66f, 0.119f));
- //backstop
+ // backstop
edgeShapes[2] = new EdgeShape();
edgeShapes[2].set(new Vec2(0.15f, 0.455f), new Vec2(0.27f, 1.15f));
return edgeShapes;
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Gumball.java b/gumball/src/main/java/com/google/android/apps/gumball/Gumball.java
similarity index 77%
rename from santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Gumball.java
rename to gumball/src/main/java/com/google/android/apps/gumball/Gumball.java
index 6ea080128..77fba5d38 100644
--- a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Gumball.java
+++ b/gumball/src/main/java/com/google/android/apps/gumball/Gumball.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2016 Google Inc. All Rights Reserved.
+ * Copyright 2019. Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.google.android.apps.santatracker.games.gumball;
+package com.google.android.apps.gumball;
import java.util.UUID;
-/**
- */
public class Gumball {
public float mXInitPos;
@@ -27,7 +25,5 @@ public class Gumball {
public UUID mSoundPoolId;
public int mGumballColorId;
- public Gumball() {
-
- }
+ public Gumball() {}
}
diff --git a/gumball/src/main/java/com/google/android/apps/gumball/GumballActivity.java b/gumball/src/main/java/com/google/android/apps/gumball/GumballActivity.java
new file mode 100644
index 000000000..eeaeaafed
--- /dev/null
+++ b/gumball/src/main/java/com/google/android/apps/gumball/GumballActivity.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.gumball;
+
+import android.os.Bundle;
+import com.google.android.apps.playgames.common.PlayGamesActivity;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+public class GumballActivity extends PlayGamesActivity {
+
+ private static final String TAG = GumballActivity.class.getSimpleName();
+
+ private TiltGameFragment mGumballFragment;
+ private FirebaseAnalytics mMeasurement;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mGumballFragment = TiltGameFragment.newInstance();
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(
+ com.google.android.apps.playgames.R.id.mainFragmentContainer,
+ mGumballFragment)
+ .commit();
+
+ // App Measurement
+ mMeasurement = FirebaseAnalytics.getInstance(this);
+ MeasurementManager.recordScreenView(
+ mMeasurement,
+ getString(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .analytics_screen_gumball));
+ }
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.activity_gumball;
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mGumballFragment != null) {
+ mGumballFragment.onBackKeyPressed();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public void onSignInSucceeded() {
+ super.onSignInSucceeded();
+ mGumballFragment.onSignInSucceeded();
+ }
+
+ @Override
+ public String getGameId() {
+ return getResources().getString(com.google.android.apps.playgames.R.string.gumball_game_id);
+ }
+
+ @Override
+ public String getGameTitle() {
+ return getString(com.google.android.apps.santatracker.common.R.string.gumball);
+ }
+
+ @Override
+ public void onSignInFailed() {
+ super.onSignInFailed();
+ mGumballFragment.onSignInFailed();
+ }
+}
diff --git a/gumball/src/main/java/com/google/android/apps/gumball/PhysicsWorld.java b/gumball/src/main/java/com/google/android/apps/gumball/PhysicsWorld.java
new file mode 100644
index 000000000..a184bdda5
--- /dev/null
+++ b/gumball/src/main/java/com/google/android/apps/gumball/PhysicsWorld.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.gumball;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.jbox2d.collision.shapes.CircleShape;
+import org.jbox2d.collision.shapes.EdgeShape;
+import org.jbox2d.collision.shapes.PolygonShape;
+import org.jbox2d.collision.shapes.Shape;
+import org.jbox2d.common.Vec2;
+import org.jbox2d.dynamics.Body;
+import org.jbox2d.dynamics.BodyDef;
+import org.jbox2d.dynamics.BodyType;
+import org.jbox2d.dynamics.FixtureDef;
+import org.jbox2d.dynamics.World;
+
+/** Wraps the game world and physics simulation for the gumball game. */
+public class PhysicsWorld {
+
+ /** All {@link org.jbox2d.dynamics.Body} objects in the world. */
+ private List mBodies = new ArrayList();
+ /** The Physics world. */
+ private World mWorld;
+ /** Bodies that are to be removed from the scene. */
+ public List mBodiesToBeRemoved = new ArrayList();
+ /** Render refresh rate. */
+ private static final float FRAME_RATE = 1.0f / 45.0f;
+ /** Create the physics world and draws the boundries */
+ public void create(Vec2 gravity) {
+
+ // Create Physics World with Gravity
+ mWorld = new World(gravity);
+ mWorld.setAllowSleep(false);
+ mWorld.setSleepingAllowed(false);
+ mWorld.setAutoClearForces(true);
+
+ BodyDef groundBodyDef = new BodyDef();
+
+ // Create Ground Box
+ groundBodyDef.position.set(new Vec2(5.0f, -2.0f));
+ Body groundBody = mWorld.createBody(groundBodyDef);
+ PolygonShape polygonShape = new PolygonShape();
+
+ // Create top bound
+ groundBodyDef.position.set(new Vec2(5.0f, 32.0f));
+ groundBody = mWorld.createBody(groundBodyDef);
+ groundBody.createFixture(polygonShape, 1.0f);
+
+ polygonShape.setAsBox(2.0f, 18.0f);
+
+ // Create left wall
+ groundBodyDef.position.set(new Vec2(-2.0f, 16.0f));
+ groundBody = mWorld.createBody(groundBodyDef);
+ groundBody.createFixture(polygonShape, 1.0f);
+
+ // Create right wall
+ groundBodyDef.position.set(new Vec2(12.0f, 16.0f));
+ groundBody = mWorld.createBody(groundBodyDef);
+ groundBody.createFixture(polygonShape, 1.0f);
+ }
+
+ /** Adds a gumball to the scene. */
+ public void addGumball(
+ float x,
+ float y,
+ Gumball gumball,
+ float density,
+ float radius,
+ float bounce,
+ float friction,
+ BodyType bodyType) {
+ // Create Shape with Properties
+ CircleShape circleShape = new CircleShape();
+ circleShape.m_radius = radius;
+ addItem(x, y, circleShape, bounce, gumball, density, friction, bodyType);
+ }
+
+ public void addPipeSides(
+ float x,
+ float y,
+ int data,
+ float density,
+ float bounce,
+ float friction,
+ BodyType bodyType) {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(.23f, -1f), new Vec2(.01f, .48f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(1.4f, -1f), new Vec2(1.55f, .45f));
+ addItem(x, y, edgeShapes, bounce, data, density, friction, bodyType);
+ }
+
+ public void addPipeBottom(
+ float x,
+ float y,
+ int data,
+ float density,
+ float bounce,
+ float friction,
+ BodyType bodyType) {
+ EdgeShape[] edgeShapes = new EdgeShape[1];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(.83f, 0f), new Vec2(2.40f, 0f));
+ addItem(x, y, edgeShapes, bounce, data, density, friction, bodyType);
+ }
+
+ public void addFloor(
+ float x,
+ float y,
+ int data,
+ float density,
+ float bounce,
+ float friction,
+ BodyType bodyType) {
+ EdgeShape[] edgeShapes = new EdgeShape[1];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(-9f, -.8f), new Vec2(9f, -.8f));
+ addItem(x, y, edgeShapes, bounce, data, density, friction, bodyType);
+ }
+
+ public void addItem(
+ float x,
+ float y,
+ Shape[] shapes,
+ float bounce,
+ int data,
+ float density,
+ float friction,
+ BodyType bodyType) {
+
+ // Create Dynamic Body
+ BodyDef bodyDef = new BodyDef();
+ bodyDef.position.set(x, y);
+ bodyDef.userData = data;
+ bodyDef.type = bodyType;
+ Body body = mWorld.createBody(bodyDef);
+ mBodies.add(body);
+
+ for (int i = 0; i < shapes.length; i++) {
+ // Assign shape to Body
+ FixtureDef fixtureDef = new FixtureDef();
+ fixtureDef.shape = shapes[i];
+ fixtureDef.density = density;
+ fixtureDef.friction = friction;
+ fixtureDef.restitution = bounce;
+
+ body.createFixture(fixtureDef);
+ }
+ }
+
+ public void addItem(
+ float x,
+ float y,
+ Shape shape,
+ float bounce,
+ int data,
+ float density,
+ float friction,
+ BodyType bodyType) {
+
+ // Create Dynamic Body
+ BodyDef bodyDef = new BodyDef();
+ bodyDef.position.set(x, y);
+ bodyDef.userData = data;
+ bodyDef.type = bodyType;
+ Body body = mWorld.createBody(bodyDef);
+ mBodies.add(body);
+
+ // Assign shape to Body
+ FixtureDef fixtureDef = new FixtureDef();
+ fixtureDef.shape = shape;
+ fixtureDef.density = density;
+ fixtureDef.friction = friction;
+ fixtureDef.restitution = bounce;
+ body.createFixture(fixtureDef);
+ }
+
+ public void addItem(
+ float x,
+ float y,
+ Shape shape,
+ float bounce,
+ Gumball gumball,
+ float density,
+ float friction,
+ BodyType bodyType) {
+
+ // Create Dynamic Body
+ BodyDef bodyDef = new BodyDef();
+ bodyDef.position.set(x, y);
+ bodyDef.userData = gumball;
+ bodyDef.type = bodyType;
+ Body body = mWorld.createBody(bodyDef);
+ mBodies.add(body);
+
+ // Assign shape to Body
+ FixtureDef fixtureDef = new FixtureDef();
+ fixtureDef.shape = shape;
+ fixtureDef.density = density;
+ fixtureDef.friction = friction;
+ fixtureDef.restitution = bounce;
+ body.createFixture(fixtureDef);
+ }
+
+ /** Updates the physics world by removing all pending bodies. */
+ public void update() {
+ // Update Physics World
+ for (int i = 0; i < mBodiesToBeRemoved.size(); i++) {
+ mWorld.destroyBody(mBodiesToBeRemoved.get(i));
+ }
+ mBodiesToBeRemoved.clear();
+ mWorld.step(FRAME_RATE, 10, 10);
+ mWorld.clearForces();
+ }
+
+ /** Gets a reference to the world. */
+ public World getWorld() {
+ return mWorld;
+ }
+}
diff --git a/gumball/src/main/java/com/google/android/apps/gumball/TiltGameFragment.java b/gumball/src/main/java/com/google/android/apps/gumball/TiltGameFragment.java
new file mode 100644
index 000000000..9192b792b
--- /dev/null
+++ b/gumball/src/main/java/com/google/android/apps/gumball/TiltGameFragment.java
@@ -0,0 +1,1215 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.gumball;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.drawable.AnimationDrawable;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.SoundPool;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationUtils;
+import android.view.animation.TranslateAnimation;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.fragment.app.Fragment;
+import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
+import com.google.android.apps.playgames.Utils;
+import com.google.android.apps.playgames.common.GameConstants;
+import com.google.android.apps.playgames.common.PlayGamesActivity;
+import com.google.android.apps.playgames.customviews.CircleView;
+import com.google.android.apps.playgames.customviews.LevelTextView;
+import com.google.android.apps.santatracker.common.CheckableImageButton;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+import com.google.android.apps.santatracker.util.ImmersiveModeHelper;
+import com.google.android.apps.santatracker.util.SoundPoolUtils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import org.jbox2d.callbacks.ContactImpulse;
+import org.jbox2d.callbacks.ContactListener;
+import org.jbox2d.collision.Manifold;
+import org.jbox2d.common.Vec2;
+import org.jbox2d.dynamics.Body;
+import org.jbox2d.dynamics.BodyType;
+import org.jbox2d.dynamics.contacts.Contact;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** Gumball game fragment. */
+public class TiltGameFragment extends Fragment
+ implements SensorEventListener, ContactListener, AnimationListener, OnClickListener {
+
+ /** Bounce rate of objects in the physics world. */
+ public static final float WORLD_OBJECT_BOUNCE = 0.2f;
+ /** Density of objects in the physics world. */
+ public static final float WORLD_OBJECT_DENSITY = 185.77f;
+ /** Friction of objects in the physics world. */
+ public static final float WORLD_OBJECT_FRICTION = 0.2f;
+ /** Friction of floor objects in the physics world. */
+ public static final float WORLD_FLOOR_FRICTION = 0.8f;
+ /** Initial X position of the floor and pipes in the physics world. */
+ public static final float WORLD_FLOOR_X = 3.37f;
+ /*
+ * Initial Y position of the floor and pipes in the physics world.
+ */
+ public static final float WORLD_FLOOR_Y = 0f;
+ /** Holder for sound pool id to handle playbacks, connects and disconnects. */
+ private final HashMap mSoundPoolId = new HashMap<>();
+ /** View that contains the main game. */
+ private TiltGameView mGameView;
+ /** Box2D physics world for this game. */
+ private PhysicsWorld mWorld;
+ /**
+ * Current rotation of the device. Used to adjust sensor readings if the screen is rotate in
+ * portrait or landscape.
+ *
+ * @see android.view.Display#getRotation()
+ */
+ private int mRotation;
+ /** Main game thread. */
+ private Runnable mGameThread;
+ /**
+ * Previous value of the sensor's Y reading. Used to calculate the rotational offset between
+ * sensor events.
+ */
+ private float mPreviousSensorY = 0f;
+ /** MediaPlayer that plays the background music. */
+ private MediaPlayer mBackgroundMusic;
+ /** Index of loaded sound effect in sound pool for small bounce. */
+ private int mSoundBounceSmall = -1;
+ /** Index of loaded sound effect in sound pool for medium bounce. */
+ private int mSoundBounceMed = -1;
+ /** Index of loaded sound effect in sound pool for large bounce. */
+ private int mSoundBounceLarge = -1;
+ /** Index of loaded sound effect in sound pool for ball in machine. */
+ private int mSoundBallInMachine = -1;
+ /** Index of loaded sound effect in sound pool for failed ball. */
+ private int mSoundBallFail = -1;
+ /** Index of loaded sound effect in sound pool for dropped ball. */
+ private int mSoundBallDrop = -1;
+ /** Index of loaded sound effect in sound pool for game over. */
+ private int mSoundGameOver = -1;
+ /** Scale down animation for level. */
+ private Animation mAnimationScaleLevelDown;
+ /** Fading out animation for level. */
+ private Animation mAnimationLevelFadeOut;
+ /** Scaling up animation for level. */
+ private Animation mAnimationLevelScaleUp;
+ /** Outlet animation for balls. */
+ private Animation mAnimationOutlet;
+ /** Alpha animation for timer updates. */
+ private Animation mAnimationTimerAlpha;
+ /** View for end of level circle overlay. */
+ private CircleView mEndLevelCircle;
+ /** View that shows the current level number. */
+ private LevelTextView mLevelNumberText;
+ /** Sound pool from which all sounds are played back. */
+ private SoundPool mSoundPool;
+ /** Number of balls left in the game. */
+ private int mGameBallsLeft = 2;
+
+ /** Current play level. Zero indexed, first level is 0. */
+ private int mCurrentLevelNum = 0;
+
+ /** View for the ball outlet at the top of the screen. */
+ private View mGameOutlet;
+
+ /** Root view of the game layout. */
+ private View mRootView;
+
+ /** Gumballs that are queued to be dropped through the outlet. */
+ private Queue mGumballQueue;
+
+ /** The current, active gumball on screen. */
+ private Gumball mCurrentGumball;
+
+ /** X position of outlet in the last animation. */
+ private float mOutletPreviousXPos = 0;
+
+ /** Array of the ball indicator views at the bottom of the screen. */
+ private ImageView mViewIndicators[] = new ImageView[6];
+
+ /** Number of gumballs collected in the current game. */
+ private int mNumberCollected = 0;
+
+ /**
+ * Refresh rate for the game countdown timer.
+ *
+ * @see GameCountdown
+ */
+ private int mFramesPerSecond = 60;
+
+ /** Time left in the current game. Value in milliseconds. */
+ private long mTimeLeftInMillis = GameConstants.GUMBALL_INIT_TIME;
+
+ /** Countdown timer for the current game. */
+ private GameCountdown mCountDownTimer = null;
+
+ /** Countdown timer text. */
+ private TextView mViewCountdown;
+
+ /** Score text. */
+ private TextView mViewScore;
+
+ /** Total score of current game. */
+ private int mMatchScore = 0;
+
+ /**
+ * Number of balls that have respawned in the current level. Used to calculate the total game
+ * score.
+ */
+ private int mCountLevelBallRespawns = 0;
+
+ /** Flag indicating if the game is paused. */
+ private boolean wasPaused = false;
+
+ private ImageView mViewPlayButton;
+
+ private ImageView mViewPauseButton;
+
+ private ImageButton mViewBigPlayButton;
+
+ private ImageView mViewCancelBar;
+
+ private ImageView mViewInviteButton;
+
+ private View mViewMatchPauseOverlay;
+
+ private View mViewPlayAgainBackground;
+
+ private View mViewPlayAgainMain;
+
+ private Button mViewPlayAgainButton;
+
+ private TextView mViewPlayAgainScore;
+
+ private TextView mViewPlayAgainLevel;
+
+ private Animation mAnimationPlayAgainBackground;
+
+ private Animation mAnimationPlayAgainMain;
+
+ /** Display offset on X axis for outlet in pixels. */
+ private int mOutletOffset;
+
+ /** View that displays the instructions from {@link #mDrawableTransition} */
+ private ImageView mViewInstructions;
+
+ /** Drawable that contains all images for the instructions. */
+ private AnimationDrawable mDrawableTransition;
+
+ private SharedPreferences mSharedPreferences;
+
+ private ImageView mViewGPlusSignIn;
+
+ private View mViewGPlusLayout;
+
+ private ImageButton mViewMainMenuButton;
+
+ private AppInvitesFragment mInvitesFragment;
+
+ private CheckableImageButton mMuteButton;
+ private SantaPreferences mSantaPreferences;
+
+ /** Gets an instance of this fragment */
+ public static TiltGameFragment newInstance() {
+ TiltGameFragment fragment = new TiltGameFragment();
+ return fragment;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mSantaPreferences = new SantaPreferences(context);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ mRootView = inflater.inflate(R.layout.fragment_gumball, container, false);
+ mRootView.setKeepScreenOn(true);
+
+ mViewPlayAgainScore =
+ mRootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_score);
+ mViewPlayAgainScore.setText(String.valueOf(mMatchScore));
+ mViewPlayAgainLevel =
+ mRootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_level);
+ mViewPlayAgainLevel.setText(String.valueOf(mCurrentLevelNum));
+ mViewPlayAgainBackground =
+ mRootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_bkgrd);
+ mViewPlayAgainMain =
+ mRootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_main);
+ mViewPlayAgainButton =
+ mRootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_btn);
+ mViewPlayAgainButton.setOnClickListener(this);
+
+ mViewGPlusSignIn =
+ mRootView.findViewById(com.google.android.apps.santatracker.R.id.gplus_button);
+ mViewGPlusSignIn.setOnClickListener(this);
+ mViewGPlusLayout =
+ mRootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_gplus);
+ mViewGPlusLayout.setVisibility(View.GONE);
+
+ // Initialise all animations
+ // Construct an animation to blink the timer indefinitely
+ mAnimationTimerAlpha = new AlphaAnimation(0.0f, 1.0f);
+ mAnimationTimerAlpha.setDuration(1000);
+ mAnimationTimerAlpha.setRepeatMode(Animation.REVERSE);
+ mAnimationTimerAlpha.setRepeatCount(Animation.INFINITE);
+
+ // Load all other animations
+ mAnimationPlayAgainBackground =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.play_again_bkgrd_anim);
+ mAnimationPlayAgainBackground.setFillAfter(true);
+ mAnimationPlayAgainBackground.setAnimationListener(this);
+ mAnimationPlayAgainMain =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.play_again_main_anim);
+ mAnimationPlayAgainMain.setFillAfter(true);
+ mAnimationPlayAgainMain.setAnimationListener(this);
+ mAnimationScaleLevelDown =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.scale_level_anim_down);
+ mAnimationScaleLevelDown.setAnimationListener(this);
+ mAnimationLevelFadeOut =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.level_fade_out_anim);
+ mAnimationLevelFadeOut.setAnimationListener(this);
+ mAnimationLevelScaleUp =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.scale_up_level_anim);
+ mAnimationLevelScaleUp.setAnimationListener(this);
+
+ mViewMainMenuButton =
+ mRootView.findViewById(com.google.android.apps.santatracker.R.id.main_menu_button);
+ mViewMainMenuButton.setVisibility(View.GONE);
+ mViewMainMenuButton.setOnClickListener(this);
+
+ // App Invites Button
+ mViewInviteButton =
+ mRootView.findViewById(com.google.android.apps.santatracker.R.id.invite_button);
+ mViewInviteButton.setVisibility(View.GONE);
+ mViewInviteButton.setOnClickListener(this);
+
+ mGameOutlet = mRootView.findViewById(R.id.tiltGameOutlet);
+ mOutletOffset =
+ getResources()
+ .getInteger(com.google.android.apps.playgames.R.integer.outlet_offset);
+
+ mViewIndicators[0] = (ImageView) mRootView.findViewById(R.id.indicator1);
+ mViewIndicators[1] = (ImageView) mRootView.findViewById(R.id.indicator2);
+ mViewIndicators[2] = (ImageView) mRootView.findViewById(R.id.indicator3);
+ mViewIndicators[3] = (ImageView) mRootView.findViewById(R.id.indicator4);
+ mViewIndicators[4] = (ImageView) mRootView.findViewById(R.id.indicator5);
+ mViewIndicators[5] = (ImageView) mRootView.findViewById(R.id.indicator6);
+ mViewCountdown = (TextView) mRootView.findViewById(R.id.tiltTimer);
+
+ mLevelNumberText = (LevelTextView) mRootView.findViewById(R.id.tilt_end_level_number);
+ mLevelNumberText.setVisibility(View.GONE);
+ mEndLevelCircle = (CircleView) mRootView.findViewById(R.id.tilt_end_level_circle);
+ mEndLevelCircle.setVisibility(View.GONE);
+
+ mViewPlayButton = (ImageView) mRootView.findViewById(R.id.tilt_play_button);
+ mViewPlayButton.setOnClickListener(this);
+ mViewPlayButton.setVisibility(View.GONE);
+ mViewPauseButton = (ImageView) mRootView.findViewById(R.id.tilt_pause_button);
+ mViewPauseButton.setOnClickListener(this);
+ mViewPauseButton.setVisibility(View.VISIBLE);
+ mViewMatchPauseOverlay = mRootView.findViewById(R.id.tilt_pause_overlay);
+ mViewMatchPauseOverlay.setVisibility(View.GONE);
+ mViewBigPlayButton = (ImageButton) mRootView.findViewById(R.id.tilt_big_play_button);
+ mViewBigPlayButton.setOnClickListener(this);
+ mViewCancelBar = (ImageView) mRootView.findViewById(R.id.tilt_cancel_bar);
+ mViewCancelBar.setOnClickListener(this);
+ mViewCancelBar.setVisibility(View.GONE);
+
+ mViewScore = (TextView) mRootView.findViewById(R.id.tilt_score);
+ mViewScore.setText(String.valueOf(mMatchScore));
+
+ mGameView = (TiltGameView) mRootView.findViewById(R.id.tiltGameView);
+
+ // Create the Box2D physics world.
+ mWorld = new PhysicsWorld();
+ Vec2 gravity = new Vec2(0.0f, 0.0f);
+ mWorld.create(gravity);
+ mGameView.setModel(mWorld);
+ mWorld.getWorld().setContactListener(this);
+
+ mGumballQueue = new LinkedList<>();
+
+ // Initialise the sound pool and audio playback
+ mSoundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
+ mSoundBounceSmall = mSoundPool.load(getActivity(), R.raw.gbg_ball_bounce_1, 1);
+ mSoundBounceMed = mSoundPool.load(getActivity(), R.raw.gbg_ball_bounce_2, 1);
+ mSoundBounceLarge = mSoundPool.load(getActivity(), R.raw.gbg_ball_bounce_3, 1);
+ mSoundBallInMachine = mSoundPool.load(getActivity(), R.raw.gbg_ball_into_machine, 1);
+ mSoundBallFail = mSoundPool.load(getActivity(), R.raw.gbg_ball_fall_out, 1);
+ mSoundBallDrop = mSoundPool.load(getActivity(), R.raw.gbg_new_ball_bounce_drop, 1);
+ mSoundGameOver =
+ mSoundPool.load(getActivity(), com.google.android.apps.playgames.R.raw.gameover, 1);
+
+ // Display the instructions if they haven't been seen before
+ mSharedPreferences =
+ getActivity()
+ .getSharedPreferences(
+ GameConstants.PREFERENCES_FILENAME, getActivity().MODE_PRIVATE);
+ if (!mSharedPreferences.getBoolean(GameConstants.GUMBALL_INSTRUCTIONS_VIEWED, false)) {
+ mDrawableTransition = new AnimationDrawable();
+
+ mDrawableTransition.addFrame(
+ VectorDrawableCompat.create(
+ getResources(),
+ com.google.android.apps.playgames.R.drawable.instructions_shake_1,
+ null),
+ 300);
+ mDrawableTransition.addFrame(
+ VectorDrawableCompat.create(
+ getResources(),
+ com.google.android.apps.playgames.R.drawable.instructions_shake_2,
+ null),
+ 300);
+ mDrawableTransition.addFrame(
+ VectorDrawableCompat.create(
+ getResources(),
+ com.google.android.apps.playgames.R.drawable.instructions_shake_3,
+ null),
+ 300);
+ mDrawableTransition.setOneShot(false);
+ mViewInstructions =
+ mRootView.findViewById(com.google.android.apps.playgames.R.id.instructions);
+
+ mViewInstructions.setImageDrawable(mDrawableTransition);
+ mViewInstructions.post(
+ new Runnable() {
+ public void run() {
+ mDrawableTransition.start();
+ }
+ });
+
+ // Hide the instructions after 2 seconds
+ mViewInstructions.postDelayed(new HideInstructionsRunnable(), 2200);
+ }
+
+ mMuteButton = mRootView.findViewById(R.id.mute_button);
+ mMuteButton.setChecked(!mSantaPreferences.isMuted());
+ mMuteButton.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSantaPreferences.toggleMuted();
+ mMuteButton.setChecked(!mSantaPreferences.isMuted());
+ onMuteChanged(mSantaPreferences.isMuted());
+ }
+ });
+
+ return mRootView;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mInvitesFragment = AppInvitesFragment.getInstance(getActivity());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Resume the game play if the game was not paused
+ if (!wasPaused) {
+ mRotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
+ SensorManager sensorManager =
+ (SensorManager) getActivity().getSystemService(Activity.SENSOR_SERVICE);
+ Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (sensor != null) {
+ sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
+ }
+ mCountDownTimer = new GameCountdown(mFramesPerSecond, mTimeLeftInMillis);
+ mCountDownTimer.start();
+ mGameView.setGameCountDown(mCountDownTimer);
+ }
+
+ // Start the game loop if it is not initialised yet
+ if (mGameThread == null) {
+ mGameThread =
+ new Runnable() {
+ public void run() {
+ synchronized (mWorld) {
+ if (!wasPaused) {
+ if (mCurrentLevelNum == 0) {
+ mCurrentLevelNum++;
+ loadLevel(mCurrentLevelNum);
+ }
+ mWorld.update();
+ mGameView.invalidate();
+ }
+ }
+ getActivity().getWindow().getDecorView().postDelayed(mGameThread, 10);
+ }
+ };
+ }
+ getActivity().getWindow().getDecorView().postDelayed(mGameThread, 1000);
+
+ loadBackgroundMusic();
+ updateSignInButtonVisibility();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ pauseGame();
+ stopBackgroundMusic();
+ getActivity().getWindow().getDecorView().removeCallbacks(mGameThread);
+ }
+
+ private void stopBackgroundMusic() {
+ if (mBackgroundMusic != null) {
+ mBackgroundMusic.stop();
+ mBackgroundMusic.release();
+ mBackgroundMusic = null;
+ }
+ }
+
+ private void loadBackgroundMusic() {
+ if (mSantaPreferences.isMuted()) {
+ return;
+ }
+ mBackgroundMusic =
+ MediaPlayer.create(
+ getActivity(),
+ com.google.android.apps.santatracker.R.raw.santatracker_musicloop);
+ mBackgroundMusic.setLooping(true);
+ mBackgroundMusic.setVolume(.2f, .2f);
+ mBackgroundMusic.start();
+ }
+
+ /** Hide the sign in button if sign in was successful. */
+ public void onSignInSucceeded() {
+ setSignInButtonVisibility(false);
+ }
+
+ public void onSignInFailed() {}
+
+ @Override
+ public void onClick(View view) {
+ if (view.equals(mViewPauseButton)) {
+ // Pause the game
+ pauseGame();
+ } else if (view.equals(mViewPlayButton) || view.equals(mViewBigPlayButton)) {
+ // Continue the game
+ unPauseGame();
+ } else if (view.equals(mViewPlayAgainButton)) {
+ // Reload the background music for a new game
+ if (mBackgroundMusic != null) {
+ mBackgroundMusic.stop();
+ mBackgroundMusic.release();
+ }
+ loadBackgroundMusic();
+
+ // Reset the game variables
+ mCurrentLevelNum = 0;
+ mTimeLeftInMillis = GameConstants.GUMBALL_INIT_TIME;
+ mMatchScore = 0;
+ mViewScore.setText(String.valueOf(mMatchScore));
+ wasPaused = false;
+
+ // Hide the pause screen
+ mViewPlayAgainBackground.clearAnimation();
+ mViewPlayAgainMain.clearAnimation();
+ mViewPlayAgainBackground.setVisibility(View.GONE);
+ mViewPlayAgainMain.setVisibility(View.GONE);
+ mViewGPlusLayout.setVisibility(View.GONE);
+ mViewMainMenuButton.setVisibility(View.GONE);
+ mViewInviteButton.setVisibility(View.GONE);
+ } else if (view.equals(mViewGPlusSignIn)) {
+ // Start sign-in flow.
+ PlayGamesActivity act = Utils.getPlayGamesActivity(this);
+ if (act != null) {
+ act.startSignIn();
+ }
+ } else if (view.equals(mViewCancelBar) || (view.equals(mViewMainMenuButton))) {
+ // Exit and return to previous Activity.
+ returnToBackClass();
+ } else if (view.equals(mViewInviteButton)) {
+ // Send App Invite
+ mInvitesFragment.sendGameInvite(
+ getString(com.google.android.apps.santatracker.common.R.string.gumball),
+ getString(com.google.android.apps.playgames.R.string.gumball_game_id),
+ mMatchScore);
+ }
+ }
+
+ private void returnToBackClass() {
+ getActivity().finish();
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ float x, y;
+ if (getActivity() != null) {
+ // Store the current screen rotation (used to offset the readings of the sensor).
+ mRotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
+ }
+
+ // Handle screen rotations by interpreting the sensor readings here
+ if (mRotation == Surface.ROTATION_0) {
+ x = -event.values[0];
+ y = -event.values[1];
+ } else if (mRotation == Surface.ROTATION_90) {
+ x = event.values[1];
+ y = -event.values[0];
+ } else if (mRotation == Surface.ROTATION_180) {
+ x = event.values[0];
+ y = event.values[1];
+ } else {
+ x = -event.values[1];
+ y = event.values[0];
+ }
+ // keep y low to simulate gravity
+ if (mPreviousSensorY == 0f) {
+ mPreviousSensorY = -9;
+ } else if (mPreviousSensorY > y) {
+ mPreviousSensorY = y;
+ }
+ // restrict x to ~+-45 degrees
+ x *= 0.5;
+ mWorld.getWorld().setGravity(new Vec2(x, mPreviousSensorY));
+ }
+
+ @Override
+ public void beginContact(Contact contact) {}
+
+ /**
+ * Handle contact with objects in the Box 2D world. Here the main game logic is implemented:
+ * When a ball hits the bottom pipe, it is removed and the next level or ball is started. When
+ * the ball goes over the edge, it is removed and a new ball is dropped from the pipe again.
+ */
+ @Override
+ public void endContact(Contact contact) {
+ // If the gumball goes in the pipe, remove it from the scene (Case 1/2)
+ if (contact.getFixtureA().getBody().getUserData() != null
+ && !(contact.getFixtureA().getBody().getUserData() instanceof Gumball)
+ && (contact.getFixtureA().getBody().getUserData().equals(TiltGameView.PIPE_BOTTOM)
+ || contact.getFixtureA()
+ .getBody()
+ .getUserData()
+ .equals(TiltGameView.PIPE_SIDES))) {
+ mWorld.mBodiesToBeRemoved.add(contact.getFixtureB().getBody());
+ mSoundPoolId.remove(
+ ((Gumball) contact.getFixtureB().getBody().getUserData()).mSoundPoolId);
+ onBallInPipe();
+ } else if (contact.getFixtureB().getBody().getUserData() != null
+ && !(contact.getFixtureB().getBody().getUserData() instanceof Gumball)
+ && (
+ // If the gumball goes in the pipe, remove it from the scene (Case 2/2)
+ contact.getFixtureA().getBody().getUserData().equals(TiltGameView.PIPE_BOTTOM)
+ || contact.getFixtureA()
+ .getBody()
+ .getUserData()
+ .equals(TiltGameView.PIPE_SIDES))) {
+ mWorld.mBodiesToBeRemoved.add(contact.getFixtureA().getBody());
+ mSoundPoolId.remove(
+ ((Gumball) contact.getFixtureA().getBody().getUserData()).mSoundPoolId);
+ onBallInPipe();
+ } else if (contact.getFixtureA().getBody().getUserData() != null
+ && !(contact.getFixtureA().getBody().getUserData() instanceof Gumball)
+ && contact.getFixtureA().getBody().getUserData().equals(TiltGameView.GAME_FLOOR)) {
+ // If the gumball goes over the edge, remove it and respawn (Case 1/2)
+ Gumball gumball = ((Gumball) contact.getFixtureB().getBody().getUserData());
+ mWorld.mBodiesToBeRemoved.add(contact.getFixtureB().getBody());
+ mSoundPoolId.remove(gumball.mSoundPoolId);
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundBallFail);
+ }
+ mWorld.getWorld().step(1.0f / 60.0f, 10, 10);
+ moveOutlet((mCurrentGumball.mXInitPos));
+ mCountLevelBallRespawns++;
+ } else if (contact.getFixtureB().getBody().getUserData() != null
+ && !(contact.getFixtureB().getBody().getUserData() instanceof Gumball)
+ && contact.getFixtureB().getBody().getUserData().equals(TiltGameView.GAME_FLOOR)) {
+ // If the gumball goes over the edge, remove it and respawn (Case 2/2)
+ Gumball gumball = ((Gumball) contact.getFixtureB().getBody().getUserData());
+ mWorld.mBodiesToBeRemoved.add(contact.getFixtureA().getBody());
+ mSoundPoolId.remove(gumball.mSoundPoolId);
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundBallFail);
+ }
+ mWorld.getWorld().step(1.0f / 60.0f, 10, 10);
+ moveOutlet((mCurrentGumball.mXInitPos));
+ mCountLevelBallRespawns++;
+ }
+ }
+
+ /**
+ * Successfully dropped a ball in the pipe. Add the next ball and go to the next level if no
+ * balls are left in this level.
+ */
+ private void onBallInPipe() {
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundBallInMachine);
+ }
+ mGameBallsLeft--;
+ mNumberCollected++;
+ changeIndicator();
+ mMatchScore += 50 * Math.max(1f, (mCurrentLevelNum - mCountLevelBallRespawns));
+ mViewScore.setText(String.valueOf(mMatchScore));
+ if (mGameBallsLeft == 0 && mViewPlayAgainBackground.getVisibility() != View.VISIBLE) {
+ // No balls are left in this level, go to the next one
+ mCurrentLevelNum++;
+ mLevelNumberText.setLevelNumber(mCurrentLevelNum);
+ mLevelNumberText.startAnimation(mAnimationLevelScaleUp);
+ mEndLevelCircle.startAnimation(mAnimationScaleLevelDown);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jbox2d.callbacks.ContactListener#postSolve(org.jbox2d.dynamics.contacts
+ * .Contact, org.jbox2d.callbacks.ContactImpulse)
+ */
+
+ /**
+ * Play a sound on impact (when a ball is dropped). The sound depends on the severity of the
+ * impact.
+ *
+ * @see #playBounceSound(float)
+ */
+ @Override
+ public void postSolve(Contact contact, ContactImpulse impulse) {
+ // Get both collision objects
+ Object dataA = contact.getFixtureA().getBody().getUserData();
+ Object dataB = contact.getFixtureB().getBody().getUserData();
+
+ // Check if one of the objects is NOT a gumball, but a candy cane.
+ boolean hitCane = false;
+ if (dataA != null
+ && !(dataA instanceof Gumball)
+ && (Integer) dataA > TiltGameView.GUMBALL_PURPLE) {
+ hitCane = true;
+ } else if (dataB != null
+ && !(dataB instanceof Gumball)
+ && (Integer) dataB > TiltGameView.GUMBALL_PURPLE) {
+ hitCane = true;
+ }
+
+ if (hitCane && impulse.normalImpulses[0] > 80) {
+ playBounceSound(impulse.normalImpulses[0]);
+ }
+ }
+
+ /** Plays a 'bounce' sound through the sound pool, depending on the impulse. */
+ private void playBounceSound(float impulse) {
+ if (mSantaPreferences.isMuted()) {
+ return;
+ }
+ if (impulse > 80) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundBounceLarge);
+ } else if (impulse > 60) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundBounceMed);
+ } else if (impulse > 30) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundBounceSmall);
+ }
+ }
+
+ @Override
+ public void preSolve(Contact contact, Manifold arg1) {}
+
+ /** Add a gumball to the game and play the ball drop sound. */
+ private void addGumball(float xPos, float yPos) {
+ Gumball gumball = new Gumball();
+ gumball.mXInitPos = xPos;
+ gumball.mYInitPos = yPos;
+ gumball.mSoundPoolId = UUID.randomUUID();
+ mSoundPoolId.put(gumball.mSoundPoolId, false);
+ mGameView.addGumball(gumball);
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundBallDrop);
+ }
+ }
+
+ private JSONObject readLevelFile(int levelNumber) throws IOException, JSONException {
+ // load the appropriate levels file from a raw resource.
+ InputStream is = getResources().openRawResource(getCurrentLevelRawFile());
+ int size = is.available();
+ byte[] buffer = new byte[size];
+ is.read(buffer);
+ is.close();
+ String json = new String(buffer, "UTF-8");
+ JSONObject level = new JSONObject(json);
+
+ return level;
+ }
+
+ private int getCurrentLevelRawFile() {
+ if (mCurrentLevelNum > 13) {
+ mCurrentLevelNum = (mCurrentLevelNum % 13) + 1;
+ }
+ switch (mCurrentLevelNum) {
+ case 1:
+ return R.raw.level1;
+ case 2:
+ return R.raw.level2;
+ case 3:
+ return R.raw.level3;
+ case 4:
+ return R.raw.level4;
+ case 5:
+ return R.raw.level5;
+ case 6:
+ return R.raw.level6;
+ case 7:
+ return R.raw.level7;
+ case 8:
+ return R.raw.level8;
+ case 9:
+ return R.raw.level9;
+ case 10:
+ return R.raw.level10;
+ case 11:
+ return R.raw.level11;
+ case 12:
+ return R.raw.level12;
+ case 13:
+ return R.raw.level13;
+ default:
+ return R.raw.level1;
+ }
+ }
+
+ /** Loads a level from the levels json file and sets up the game world. */
+ private void loadLevel(int levelNumber) {
+
+ // Reset the current game state
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+ mCountLevelBallRespawns = 0;
+ mNumberCollected = 0;
+ mViewPlayAgainLevel.setText(String.valueOf(levelNumber));
+ Body body = mWorld.getWorld().getBodyList();
+ while (body != null) {
+ if (body.m_userData == null) {
+ body = body.getNext();
+ continue;
+ }
+ mWorld.mBodiesToBeRemoved.add(body);
+ body = body.getNext();
+ }
+ mWorld.getWorld().step(1.0f / 60.0f, 10, 10);
+
+ try {
+ // Read the level file and extract the candy cane positions
+ JSONObject level = readLevelFile(levelNumber);
+ JSONArray canes = level.getJSONArray("candycanes");
+
+ for (int i = 0; i < canes.length(); i++) {
+ JSONObject canePart = canes.getJSONObject(i);
+ int type = canePart.getInt("type");
+ float xPos = (float) canePart.getDouble("xPos");
+ float yPos = (float) canePart.getDouble("yPos");
+ // Add the candy cane to the game world, the values represent the
+ mWorld.addItem(
+ xPos,
+ yPos,
+ Edges.getEdges(type),
+ WORLD_OBJECT_BOUNCE,
+ type,
+ WORLD_OBJECT_DENSITY,
+ WORLD_OBJECT_FRICTION,
+ BodyType.STATIC);
+ }
+
+ // Add the sides and floor to the game world to catch dropped balls.
+ // Note that the WORLD_FRICTION is used as the bounce rate of the floors.
+ mWorld.addItem(
+ WORLD_FLOOR_X,
+ WORLD_FLOOR_Y,
+ Edges.getPipeSideEdges(),
+ WORLD_OBJECT_BOUNCE,
+ TiltGameView.PIPE_SIDES,
+ WORLD_OBJECT_DENSITY,
+ WORLD_OBJECT_FRICTION,
+ BodyType.STATIC);
+ mWorld.addFloor(
+ WORLD_FLOOR_X,
+ WORLD_FLOOR_Y,
+ TiltGameView.GAME_FLOOR,
+ WORLD_OBJECT_DENSITY,
+ WORLD_OBJECT_FRICTION,
+ WORLD_FLOOR_FRICTION,
+ BodyType.STATIC);
+ mWorld.addPipeBottom(
+ WORLD_FLOOR_X,
+ WORLD_FLOOR_Y,
+ TiltGameView.PIPE_BOTTOM,
+ WORLD_OBJECT_DENSITY,
+ WORLD_OBJECT_FRICTION,
+ WORLD_FLOOR_FRICTION,
+ BodyType.STATIC);
+
+ // Add the gumballs
+ JSONArray gumballs = level.getJSONArray("gumballs");
+ mGameBallsLeft = gumballs.length();
+ setIndicators(mGameBallsLeft);
+ for (int j = 0; j < gumballs.length(); j++) {
+ JSONObject gumball = gumballs.getJSONObject(j);
+ float xPos = (float) gumball.getDouble("xPos");
+ float yPos = (float) gumball.getDouble("yPos");
+ Gumball gumballObject = new Gumball();
+ gumballObject.mXInitPos = xPos;
+ gumballObject.mYInitPos = yPos;
+ mGumballQueue.add(gumballObject);
+ }
+ mCurrentGumball = mGumballQueue.poll();
+
+ // Start the timer
+ if (mCurrentGumball != null) {
+ if (mCurrentLevelNum > 1) {
+ // Do not include gumball dropping time in countdown calculation.
+ mTimeLeftInMillis += GameConstants.GUMBALL_ADDED_TIME;
+ }
+ mCountDownTimer = new GameCountdown(mFramesPerSecond, mTimeLeftInMillis);
+ mCountDownTimer.start();
+ mGameView.setGameCountDown(mCountDownTimer);
+
+ // Move the outlet to its initial position
+ moveOutlet((mCurrentGumball.mXInitPos));
+ }
+ } catch (IOException e) {
+ } catch (JSONException e) {
+ }
+ }
+
+ /**
+ * Update the state of the indicators at the bottom of the screen to the number of balls
+ * collected.
+ */
+ private void setIndicators(int numGumballs) {
+ for (int i = 0; i < mViewIndicators.length; i++) {
+ int stateResource = R.drawable.gbg_gumball_indicator_collected_disabled;
+ if (i + 1 <= numGumballs) {
+ stateResource = R.drawable.gbg_gumball_indicator_pending;
+ }
+ mViewIndicators[i].setImageResource(stateResource);
+ }
+ }
+
+ /** Mark the last indicator for which a ball was collected in the 'collected' state. */
+ private void changeIndicator() {
+ mViewIndicators[mNumberCollected - 1].setImageResource(
+ R.drawable.gbg_gumball_indicator_collected);
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ if (animation == mAnimationScaleLevelDown) {
+ // After the level scale down animation, fade out the level number and end circle
+ mLevelNumberText.startAnimation(mAnimationLevelFadeOut);
+ mEndLevelCircle.startAnimation(mAnimationLevelFadeOut);
+ } else if (animation == mAnimationLevelFadeOut) {
+ // After the level fade out animation reset and hide all other end level views
+ mEndLevelCircle.clearAnimation();
+ mLevelNumberText.clearAnimation();
+ mLevelNumberText.setVisibility(View.GONE);
+ mEndLevelCircle.setVisibility(View.GONE);
+ } else if (animation == mAnimationOutlet) {
+ // After the outlet has moved to the correct position, add gumball
+ addGumball(mCurrentGumball.mXInitPos, mCurrentGumball.mYInitPos);
+ if (mGumballQueue.peek() != null) {
+ // Move it to the next position if there is a gumball left in the queue
+ mCurrentGumball = mGumballQueue.poll();
+ moveOutlet(mCurrentGumball.mXInitPos);
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation arg0) {
+ // do nothing
+
+ }
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ if (animation == mAnimationScaleLevelDown) {
+ // Show the circle level end and level text views when the animation starts
+ mEndLevelCircle.setVisibility(View.VISIBLE);
+ mLevelNumberText.setVisibility(View.VISIBLE);
+ } else if (animation == mAnimationLevelFadeOut) {
+ // Load the next level after the end level animation is over
+ loadLevel(mCurrentLevelNum);
+ } else if (animation == mAnimationPlayAgainBackground) {
+ // Show the 'play again' screen when the animation starts and cancel the timer
+ mViewPlayAgainBackground.setVisibility(View.VISIBLE);
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+ } else if (animation == mAnimationPlayAgainMain) {
+ mViewPlayAgainMain.setVisibility(View.VISIBLE);
+ setSignInButtonVisibility(true);
+ }
+ }
+
+ /** Set the visibility of the sign in button if the user is not already signed in. */
+ private void setSignInButtonVisibility(boolean show) {
+ mViewGPlusLayout.setVisibility(show && !Utils.isSignedIn(this) ? View.VISIBLE : View.GONE);
+ }
+
+ /** Hide the sign in button when the user signs in and the button is still visible on screen. */
+ private void updateSignInButtonVisibility() {
+ if (mViewGPlusLayout.getVisibility() == View.VISIBLE && Utils.isSignedIn(this)) {
+ setSignInButtonVisibility(false);
+ }
+ }
+
+ /** Start an animation to move the outlet to the x position in pixels. */
+ private void moveOutlet(float xPos) {
+ float scale = mRootView.getWidth() / 10.0f;
+ mAnimationOutlet =
+ new TranslateAnimation(mOutletPreviousXPos, (scale * xPos) - mOutletOffset, 0, 0);
+ mAnimationOutlet.setDuration(700);
+ mAnimationOutlet.setFillAfter(true);
+ mAnimationOutlet.setStartOffset(400);
+ mAnimationOutlet.setAnimationListener(this);
+ mGameOutlet.startAnimation(mAnimationOutlet);
+ mOutletPreviousXPos = (scale * xPos) - mOutletOffset;
+ }
+
+ /** Pause the game when the back key is pressed. */
+ public void onBackKeyPressed() {
+ if (mViewPlayAgainMain.getVisibility() == View.VISIBLE) {
+ returnToBackClass();
+ } else {
+ if (mViewPauseButton.getVisibility() != View.GONE) { // check if already handled
+ pauseGame();
+ } else {
+ // Exit and return to previous Activity.
+ returnToBackClass();
+ }
+ }
+ }
+
+ /** Pause the game and display the pause game screen. */
+ private void pauseGame() {
+ mViewPauseButton.setVisibility(View.GONE);
+ mViewPlayButton.setVisibility(View.VISIBLE);
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ wasPaused = true;
+ }
+ mViewMatchPauseOverlay.setVisibility(View.VISIBLE);
+ mViewCancelBar.setVisibility(View.VISIBLE);
+
+ SensorManager sensorManager =
+ (SensorManager) getActivity().getSystemService(Activity.SENSOR_SERVICE);
+ sensorManager.unregisterListener(this);
+ ImmersiveModeHelper.setImmersiveStickyWithActionBar(getActivity().getWindow());
+ }
+
+ /** Continue the paused game. Restart the countdown timer and hide the pause game screen. */
+ private void unPauseGame() {
+ mViewPauseButton.setVisibility(View.VISIBLE);
+ mViewPlayButton.setVisibility(View.GONE);
+ mViewMatchPauseOverlay.setVisibility(View.GONE);
+ mViewCancelBar.setVisibility(View.GONE);
+ mCountDownTimer = new GameCountdown(mFramesPerSecond, mTimeLeftInMillis);
+ mCountDownTimer.start();
+ mGameView.setGameCountDown(mCountDownTimer);
+ wasPaused = false;
+ SensorManager sensorManager =
+ (SensorManager) getActivity().getSystemService(Activity.SENSOR_SERVICE);
+ Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (sensor != null) {
+ sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
+ }
+ ImmersiveModeHelper.setImmersiveSticky(getActivity().getWindow());
+ }
+
+ private void onMuteChanged(boolean muted) {
+ if (muted) {
+ stopBackgroundMusic();
+ mSoundPool.autoPause();
+ } else {
+ loadBackgroundMusic();
+ mSoundPool.autoResume();
+ }
+ }
+
+ /** Submit score to play games services */
+ private void submitScore(int resId, int score) {
+ PlayGamesActivity act = Utils.getPlayGamesActivity(this);
+ if (act != null) {
+ act.postSubmitScore(resId, score);
+ }
+ }
+
+ /**
+ * Countdown for the main game. Updates the countdown on screen and stops the game when the
+ * timer runs out.
+ */
+ public class GameCountdown {
+
+ private final long mMillisDuration;
+ private final long mMillisTickDuration;
+ private Boolean animationStarted = false;
+ private long mTicksLeft;
+
+ private boolean mStarted = false;
+
+ private long mSecondsTextValue = -1;
+
+ /**
+ * @param framesPerSecond assumed frame rate
+ * @param millisInFuture duration of game at this frame rate
+ */
+ public GameCountdown(int framesPerSecond, long millisInFuture) {
+ mMillisDuration = millisInFuture;
+ mMillisTickDuration = 1000 / framesPerSecond;
+ mTicksLeft = mMillisDuration / mMillisTickDuration;
+ }
+
+ /** Stop the timer. */
+ public void cancel() {
+ mTicksLeft = 0;
+ mStarted = false;
+ }
+
+ /** Starts the timer. */
+ public void start() {
+ mStarted = true;
+ mSecondsTextValue = -1;
+ long seconds = TimeUnit.MILLISECONDS.toSeconds(mTicksLeft * mMillisTickDuration);
+ if (seconds >= 6) {
+ animationStarted = false;
+ mViewCountdown.clearAnimation();
+ mViewCountdown.setTextColor(Color.WHITE);
+ mViewCountdown.setTypeface(Typeface.DEFAULT);
+ }
+ }
+
+ /** Update the displayed timer. When the timer is below 6s the text color changes to red. */
+ public void tick() {
+ if (mStarted) {
+ --mTicksLeft;
+ mTimeLeftInMillis = mTicksLeft * mMillisTickDuration;
+ if (mTimeLeftInMillis < 6000 && !animationStarted) {
+ animationStarted = true;
+ mViewCountdown.setTextColor(Color.RED);
+ mViewCountdown.setTypeface(Typeface.DEFAULT_BOLD);
+ mViewCountdown.clearAnimation();
+ mViewCountdown.startAnimation(mAnimationTimerAlpha);
+ }
+ if (mSecondsTextValue != mTimeLeftInMillis / 1000) {
+ mViewCountdown.setText(
+ String.format(
+ "%d:%02d",
+ TimeUnit.MILLISECONDS.toMinutes(mTimeLeftInMillis),
+ TimeUnit.MILLISECONDS.toSeconds(mTimeLeftInMillis)));
+ mSecondsTextValue = mTimeLeftInMillis / 1000;
+ }
+ if (mTimeLeftInMillis == 0) {
+ finished();
+ }
+ }
+ }
+
+ /**
+ * Shut down the count down timer. Cancel all pending animations and display the 'play
+ * again' screen.
+ */
+ private void finished() {
+ mViewCountdown.clearAnimation();
+ animationStarted = false;
+ mViewCountdown.setTextColor(Color.WHITE);
+ mViewCountdown.setTypeface(Typeface.DEFAULT);
+ if (mViewPlayAgainBackground.getVisibility() != View.VISIBLE && !wasPaused) {
+ wasPaused = true;
+ submitScore(GameConstants.LEADERBOARDS_GUMBALL, mMatchScore);
+ if (mBackgroundMusic != null) {
+ mBackgroundMusic.stop();
+ mBackgroundMusic.release();
+ mBackgroundMusic = null;
+ }
+ mViewPlayAgainScore.setText(String.valueOf(mMatchScore));
+ mViewPlayAgainBackground.startAnimation(mAnimationPlayAgainBackground);
+ mViewPlayAgainMain.startAnimation(mAnimationPlayAgainMain);
+ mViewPlayAgainBackground.setVisibility(View.VISIBLE);
+ mViewPlayAgainMain.setVisibility(View.VISIBLE);
+ mViewMainMenuButton.setVisibility(View.VISIBLE);
+ mViewInviteButton.setVisibility(View.VISIBLE);
+ setSignInButtonVisibility(true);
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundGameOver, .2f);
+ }
+ }
+
+ cancel();
+ }
+ }
+
+ /** Hide the instructions and mark them as viewed. */
+ private class HideInstructionsRunnable implements Runnable {
+
+ @Override
+ public void run() {
+ mDrawableTransition.stop();
+ wasPaused = false;
+ mViewInstructions.setVisibility(View.GONE);
+ Editor edit = mSharedPreferences.edit();
+ edit.putBoolean(GameConstants.GUMBALL_INSTRUCTIONS_VIEWED, true);
+ edit.apply();
+ }
+ }
+}
diff --git a/gumball/src/main/java/com/google/android/apps/gumball/TiltGameView.java b/gumball/src/main/java/com/google/android/apps/gumball/TiltGameView.java
new file mode 100644
index 000000000..8ac3da9a9
--- /dev/null
+++ b/gumball/src/main/java/com/google/android/apps/gumball/TiltGameView.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.gumball;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import java.util.Random;
+import org.jbox2d.collision.shapes.CircleShape;
+import org.jbox2d.collision.shapes.EdgeShape;
+import org.jbox2d.collision.shapes.Shape;
+import org.jbox2d.common.Vec2;
+import org.jbox2d.dynamics.Body;
+import org.jbox2d.dynamics.BodyType;
+import org.jbox2d.dynamics.Fixture;
+
+/**
+ * Custom view which contains the elements used in the physics word. It handles the painting of all
+ * bitmaps for the game, including all levels (canes), the pipe and gumballs.
+ */
+public class TiltGameView extends View {
+
+ public static final float GUMBALL_DENSITY = 185.77f;
+ public static final float GUMBALL_RADIUS = 0.258f;
+ public static final float GUMBALL_BOUNCE = 0.2f;
+ public static final float GUMBALL_FRICTION = 0.8f;
+ /** The physics world for the gumball game. */
+ private PhysicsWorld mWorld;
+
+ /** Bitmaps for all elements on screen. */
+ private Bitmap mGumballBlue;
+
+ private Bitmap mGumballYellow;
+ private Bitmap mGumballRed;
+ private Bitmap mGumballGreen;
+ private Bitmap mGumballOrange;
+ private Bitmap mGumballPurple;
+ private Bitmap mCaneMainLong;
+ private Bitmap mCaneMainLongReverse;
+ private Bitmap mCaneMainMed;
+ private Bitmap mCaneMainMedReverse;
+ private Bitmap mCaneMainSmall;
+ private Bitmap mCaneMainSmallReverse;
+ private Bitmap mCaneMainTiny;
+ private Bitmap mCaneMainTinyReverse;
+ private Bitmap mCaneHook;
+ private Bitmap mCaneHookFlip;
+ private Bitmap mCaneHookReverse;
+ private Bitmap mCaneHookReverseFlip;
+ private Bitmap mCaneEnd;
+ private Bitmap mCaneEndFlip;
+ private Bitmap mCaneEndReverse;
+ private Bitmap mCaneEndReverseFlip;
+
+ private Bitmap mCaneMainSmallAngleNine;
+ private Bitmap mCaneMainSmallAngleSix;
+ private Bitmap mCaneMainSmallAngleTwelve;
+ private Bitmap mCaneMainReverseTinyAngleTwelve;
+ private Bitmap mCaneMainLargeAngleSix;
+ private Bitmap mCaneMainMedAngleSix;
+
+ /** Bitmap of a pipe where gumballs drop. */
+ private Bitmap mPipeSides;
+
+ /** Default paint object that is used to draw all bitmaps to the screen. */
+ private Paint mPaint = new Paint();
+
+ /** Identifiers for objects in the world. */
+ public static final int GUMBALL_RED = 0;
+
+ public static final int GUMBALL_BLUE = 1;
+ public static final int GUMBALL_YELLOW = 2;
+ public static final int GUMBALL_GREEN = 3;
+ public static final int GUMBALL_ORANGE = 4;
+ public static final int GUMBALL_PURPLE = 5;
+ public static final int[] GUMBALLS =
+ new int[] {
+ GUMBALL_RED,
+ GUMBALL_BLUE,
+ GUMBALL_YELLOW,
+ GUMBALL_GREEN,
+ GUMBALL_ORANGE,
+ GUMBALL_PURPLE
+ };
+
+ public static final int CANE_MAIN_LONG = 6;
+ public static final int CANE_MAIN_LONG_REVERSE = 7;
+ public static final int CANE_MAIN_MEDIUM = 8;
+ public static final int CANE_MAIN_MEDIUM_REVERSE = 9;
+ public static final int CANE_MAIN_SMALL = 10;
+ public static final int CANE_MAIN_SMALL_REVERSE = 11;
+ public static final int CANE_MAIN_TINY = 12;
+ public static final int CANE_MAIN_TINY_REVERSE = 13;
+ public static final int CANE_HOOK = 14;
+ public static final int CANE_HOOK_FLIP = 15;
+ public static final int CANE_HOOK_REVERSE = 16;
+ public static final int CANE_HOOK_REVERSE_FLIP = 17;
+ public static final int CANE_END = 18;
+ public static final int CANE_END_FLIP = 19;
+ public static final int CANE_END_REVERSE = 20;
+ public static final int CANE_END_REVERSE_FLIP = 21;
+
+ public static final int CANE_MAIN_SMALL_ANGLE_NINE = 22;
+ public static final int CANE_MAIN_SMALL_ANGLE_SIX = 23;
+ public static final int CANE_MAIN_SMALL_ANGLE_TWELVE = 24;
+ public static final int CANE_MAIN_REVERSE_TINY_ANGLE_SIX = 25;
+ public static final int CANE_MAIN_LARGE_ANGLE_SIX = 26;
+ public static final int CANE_MAIN_MED_ANGLE_SIX = 27;
+
+ public static final int PIPE_SIDES = -1;
+ /**
+ * Bottom of the pipe user data, this is separate from the side because the gumball is removed
+ * from the scene on collision with it.
+ */
+ public static final int PIPE_BOTTOM = -2;
+
+ public static final int GAME_FLOOR = -3;
+
+ private static Random sRandomGenerator = new Random();
+
+ private TiltGameFragment.GameCountdown mGameCountDown;
+
+ public TiltGameView(Context context) {
+ super(context);
+ init();
+ }
+
+ public TiltGameView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ setClickable(true);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ // Load the bitmaps as soon as we have the size of the view to scale them appropriately.
+ if (mGumballBlue == null) {
+ loadBitmaps();
+ }
+ }
+
+ private void loadBitmaps() {
+ // set the bitmaps
+ Resources res = getResources();
+ int[] gumballBlue = {
+ R.drawable.gbg_gumball_blue_1920,
+ R.drawable.gbg_gumball_blue_800,
+ R.drawable.gbg_gumball_blue_480
+ };
+ int[] gumballRed = {
+ R.drawable.gbg_gumball_red_1920,
+ R.drawable.gbg_gumball_red_800,
+ R.drawable.gbg_gumball_red_480
+ };
+ int[] gumballYellow = {
+ R.drawable.gbg_gumball_yellow_1920,
+ R.drawable.gbg_gumball_yellow_800,
+ R.drawable.gbg_gumball_yellow_480
+ };
+ int[] gumballGreen = {
+ R.drawable.gbg_gumball_green_1920,
+ R.drawable.gbg_gumball_green_800,
+ R.drawable.gbg_gumball_green_480
+ };
+ int[] gumballOrange = {
+ R.drawable.gbg_gumball_orange_1920,
+ R.drawable.gbg_gumball_orange_800,
+ R.drawable.gbg_gumball_orange_480
+ };
+ int[] gumballPurple = {
+ R.drawable.gbg_gumball_purple_1920,
+ R.drawable.gbg_gumball_purple_800,
+ R.drawable.gbg_gumball_purple_480
+ };
+ int[] caneMain = {
+ R.drawable.gbg_candycane_main_1920,
+ R.drawable.gbg_candycane_main_800,
+ R.drawable.gbg_candycane_main_480
+ };
+ int[] caneMainReverse = {
+ R.drawable.gbg_candycane_main_reverse_1920,
+ R.drawable.gbg_candycane_main_reverse_800,
+ R.drawable.gbg_candycane_main_reverse_480
+ };
+ int[] caneHook = {
+ R.drawable.gbg_candycane_hook_1920,
+ R.drawable.gbg_candycane_hook_800,
+ R.drawable.gbg_candycane_hook_480
+ };
+ int[] caneHookReverse = {
+ R.drawable.gbg_candycane_hook_reverse_1920,
+ R.drawable.gbg_candycane_hook_reverse_800,
+ R.drawable.gbg_candycane_hook_reverse_480
+ };
+ int[] caneEnd = {
+ R.drawable.gbg_candycane_end_1920,
+ R.drawable.gbg_candycane_end_800,
+ R.drawable.gbg_candycane_end_480
+ };
+ int[] caneEndReverse = {
+ R.drawable.gbg_candycane_end_reverse_1920,
+ R.drawable.gbg_candycane_end_reverse_800,
+ R.drawable.gbg_candycane_end_reverse_480
+ };
+ int[] pipes = {
+ R.drawable.gbg_gumball_funnel_1920,
+ R.drawable.gbg_gumball_funnel_800,
+ R.drawable.gbg_gumball_funnel_480
+ };
+ int[] caneMainAngleNine = {
+ R.drawable.gbg_candycane_main_angle_nine_1920,
+ R.drawable.gbg_candycane_main_angle_nine_960,
+ R.drawable.gbg_candycane_main_angle_nine_480
+ };
+ int[] caneMainAngleSix = {
+ R.drawable.gbg_candycane_small_angle_six_1920,
+ R.drawable.gbg_candycane_small_angle_six_960,
+ R.drawable.gbg_candycane_small_angle_six_480
+ };
+ int[] caneMainAngleTwelve = {
+ R.drawable.gbg_candycane_small_angle_twelve_1920,
+ R.drawable.gbg_candycane_small_angle_twelve_960,
+ R.drawable.gbg_candycane_small_angle_twelve_480
+ };
+ int[] caneMainTinyReverseAngleSix = {
+ R.drawable.gbg_candycane_tiny_reverse_angle_six_1920,
+ R.drawable.gbg_candycane_tiny_reverse_angle_six_960,
+ R.drawable.gbg_candycane_tiny_reverse_angle_six_480
+ };
+ int[] caneMainLargeAngleSix = {
+ R.drawable.gbg_candycane_large_angle_six_1920,
+ R.drawable.gbg_candycane_large_angle_six_960,
+ R.drawable.gbg_candycane_large_angle_six_480
+ };
+ int[] caneMainMedAngleSix = {
+ R.drawable.gbg_candycane_med_angle_six_1920,
+ R.drawable.gbg_candycane_med_angle_six_960,
+ R.drawable.gbg_candycane_med_angle_six_480
+ };
+ int[] sizes = {1920, 960, 480};
+
+ final int viewWidth = getWidth();
+ int size = 0;
+ for (int i = 1; i <= sizes.length; i++) {
+ if (viewWidth <= sizes[i - 1]) {
+ size = i - 1;
+ } else {
+ break;
+ }
+ }
+
+ mGumballBlue = resizeImage(res, gumballBlue, sizes, size, viewWidth, -360f, true, 1);
+ mGumballRed = resizeImage(res, gumballRed, sizes, size, viewWidth, -360f, true, 1);
+ mGumballYellow = resizeImage(res, gumballYellow, sizes, size, viewWidth, -360f, true, 1);
+ mGumballGreen = resizeImage(res, gumballGreen, sizes, size, viewWidth, -360f, true, 1);
+ mGumballOrange = resizeImage(res, gumballOrange, sizes, size, viewWidth, -360f, true, 1);
+ mGumballPurple = resizeImage(res, gumballPurple, sizes, size, viewWidth, -360f, true, 1);
+
+ mCaneMainLong = resizeImage(res, caneMain, sizes, size, viewWidth, 180f, false, 1);
+ mCaneMainLongReverse =
+ resizeImage(res, caneMainReverse, sizes, size, viewWidth, 180f, false, 1);
+ mCaneMainMed = resizeImage(res, caneMain, sizes, size, viewWidth, 180f, false, .75f);
+ mCaneMainMedReverse =
+ resizeImage(res, caneMainReverse, sizes, size, viewWidth, 180f, false, .75f);
+
+ mCaneMainSmall = resizeImage(res, caneMain, sizes, size, viewWidth, 180f, false, .50f);
+ mCaneMainSmallReverse =
+ resizeImage(res, caneMainReverse, sizes, size, viewWidth, 180f, false, .50f);
+ mCaneMainTiny = resizeImage(res, caneMain, sizes, size, viewWidth, 180f, false, .25f);
+ mCaneMainTinyReverse =
+ resizeImage(res, caneMainReverse, sizes, size, viewWidth, 180f, false, .25f);
+
+ mCaneMainSmallAngleNine =
+ resizeImage(res, caneMainAngleNine, sizes, size, viewWidth, 180f, true, 1f);
+ mCaneMainSmallAngleSix =
+ resizeImage(res, caneMainAngleSix, sizes, size, viewWidth, 180f, true, 1f);
+ mCaneMainSmallAngleTwelve =
+ resizeImage(res, caneMainAngleTwelve, sizes, size, viewWidth, 180f, true, 1f);
+ mCaneMainReverseTinyAngleTwelve =
+ resizeImage(
+ res, caneMainTinyReverseAngleSix, sizes, size, viewWidth, 180f, true, 1f);
+ mCaneMainLargeAngleSix =
+ resizeImage(res, caneMainLargeAngleSix, sizes, size, viewWidth, 180f, true, 1f);
+ mCaneMainMedAngleSix =
+ resizeImage(res, caneMainMedAngleSix, sizes, size, viewWidth, 180f, true, 1f);
+
+ mCaneHook = resizeImage(res, caneHook, sizes, size, viewWidth, 180f, false, 1);
+ mCaneHookFlip = resizeImage(res, caneHook, sizes, size, viewWidth, 180f, true, 1);
+ mCaneHookReverse =
+ resizeImage(res, caneHookReverse, sizes, size, viewWidth, 180f, false, 1);
+ mCaneHookReverseFlip =
+ resizeImage(res, caneHookReverse, sizes, size, viewWidth, 180f, true, 1);
+ mCaneEnd = resizeImage(res, caneEnd, sizes, size, viewWidth, 180f, false, 1);
+ mCaneEndFlip = resizeImage(res, caneEnd, sizes, size, viewWidth, 180f, true, 1);
+ mCaneEndReverse = resizeImage(res, caneEndReverse, sizes, size, viewWidth, 180f, false, 1);
+ mCaneEndReverseFlip =
+ resizeImage(res, caneEndReverse, sizes, size, viewWidth, 180f, true, 1);
+ mPipeSides = resizeImage(res, pipes, sizes, size, viewWidth, 180f, true, 1);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // Advance the countdown
+ if (mGameCountDown != null) {
+ mGameCountDown.tick();
+ }
+
+ // Load bitmaps if they haven't been initialised yet
+ if (mGumballBlue == null) {
+ loadBitmaps();
+ }
+ // reset and initialise the canvas for a fresh draw
+ canvas.drawColor(Color.TRANSPARENT);
+ canvas.translate(0, getHeight());
+ canvas.scale(1.0f, -1.0f);
+ float scale = getWidth() / 10.0f;
+ mPaint.setAntiAlias(true);
+
+ if (isInEditMode()) {
+ return;
+ }
+
+ // Iterate through all of the bodies in the game world and draw the corresponding bitmaps
+ Body body = mWorld.getWorld().getBodyList();
+ while (body != null) {
+ if (body.m_userData == null || body.m_userData.equals(PIPE_BOTTOM)) {
+ body = body.getNext();
+ continue;
+ }
+ // Skip bodies with empty fixtures or shapes
+ Fixture fixture = body.getFixtureList();
+ if (fixture == null) {
+ body = body.getNext();
+ continue;
+ }
+ Shape shape = fixture.getShape();
+ if (shape == null) {
+ body = body.getNext();
+ continue;
+ }
+
+ // Get the position.
+ Vec2 position = body.getPosition();
+
+ // Get the bitmap of this body.
+ Bitmap bitmap = null;
+ if (body.getUserData() instanceof Gumball) {
+ // For a gumball, load the correct color
+ Gumball gumball = (Gumball) body.getUserData();
+ if (gumball.mGumballColorId == GUMBALL_BLUE) {
+ bitmap = mGumballBlue;
+ } else if (gumball.mGumballColorId == GUMBALL_YELLOW) {
+ bitmap = mGumballYellow;
+ } else if (gumball.mGumballColorId == GUMBALL_RED) {
+ bitmap = mGumballRed;
+ } else if (gumball.mGumballColorId == GUMBALL_GREEN) {
+ bitmap = mGumballGreen;
+ } else if (gumball.mGumballColorId == GUMBALL_ORANGE) {
+ bitmap = mGumballOrange;
+ } else if (gumball.mGumballColorId == GUMBALL_PURPLE) {
+ bitmap = mGumballPurple;
+ }
+ } else if (body.m_userData.equals(CANE_MAIN_LONG)) {
+ bitmap = mCaneMainLong;
+ } else if (body.m_userData.equals(CANE_MAIN_LONG_REVERSE)) {
+ bitmap = mCaneMainLongReverse;
+ } else if (body.m_userData.equals(CANE_MAIN_MEDIUM)) {
+ bitmap = mCaneMainMed;
+ } else if (body.m_userData.equals(CANE_MAIN_MEDIUM_REVERSE)) {
+ bitmap = mCaneMainMedReverse;
+ } else if (body.m_userData.equals(CANE_MAIN_SMALL)) {
+ bitmap = mCaneMainSmall;
+ } else if (body.m_userData.equals(CANE_MAIN_SMALL_REVERSE)) {
+ bitmap = mCaneMainSmallReverse;
+ } else if (body.m_userData.equals(CANE_MAIN_TINY)) {
+ bitmap = mCaneMainTiny;
+ } else if (body.m_userData.equals(CANE_MAIN_TINY_REVERSE)) {
+ bitmap = mCaneMainTinyReverse;
+ } else if (body.m_userData.equals(CANE_HOOK)) {
+ bitmap = mCaneHook;
+ } else if (body.m_userData.equals(CANE_HOOK_FLIP)) {
+ bitmap = mCaneHookFlip;
+ } else if (body.m_userData.equals(CANE_HOOK_REVERSE)) {
+ bitmap = mCaneHookReverse;
+ } else if (body.m_userData.equals(CANE_HOOK_REVERSE_FLIP)) {
+ bitmap = mCaneHookReverseFlip;
+ } else if (body.m_userData.equals(CANE_END)) {
+ bitmap = mCaneEnd;
+ } else if (body.m_userData.equals(CANE_END_FLIP)) {
+ bitmap = mCaneEndFlip;
+ } else if (body.m_userData.equals(CANE_END_REVERSE)) {
+ bitmap = mCaneEndReverse;
+ } else if (body.m_userData.equals(CANE_END_REVERSE_FLIP)) {
+ bitmap = mCaneEndReverseFlip;
+ } else if (body.m_userData.equals(PIPE_SIDES)) {
+ bitmap = mPipeSides;
+ } else if (body.m_userData.equals(CANE_MAIN_SMALL_ANGLE_NINE)) {
+ bitmap = mCaneMainSmallAngleNine;
+ } else if (body.m_userData.equals(CANE_MAIN_SMALL_ANGLE_SIX)) {
+ bitmap = mCaneMainSmallAngleSix;
+ } else if (body.m_userData.equals(CANE_MAIN_SMALL_ANGLE_TWELVE)) {
+ bitmap = mCaneMainSmallAngleTwelve;
+ } else if (body.m_userData.equals(CANE_MAIN_REVERSE_TINY_ANGLE_SIX)) {
+ bitmap = mCaneMainReverseTinyAngleTwelve;
+ } else if (body.m_userData.equals(CANE_MAIN_LARGE_ANGLE_SIX)) {
+ bitmap = mCaneMainLargeAngleSix;
+ } else if (body.m_userData.equals(CANE_MAIN_MED_ANGLE_SIX)) {
+ bitmap = mCaneMainMedAngleSix;
+ }
+
+ if (shape instanceof CircleShape && bitmap != null) {
+ // Draw a gumball
+ CircleShape circleShape = (CircleShape) shape;
+ canvas.save();
+ canvas.rotate(
+ (float) (180 * body.getAngle() / Math.PI),
+ scale * position.x,
+ scale * position.y);
+ canvas.drawBitmap(
+ bitmap,
+ scale * (position.x - circleShape.m_radius),
+ scale * (position.y - circleShape.m_radius),
+ mPaint);
+ canvas.restore();
+
+ } else if (shape instanceof EdgeShape && bitmap != null) {
+ // Draw all other objects
+ final int saveCount = canvas.save();
+ canvas.rotate(
+ (float) (180 * body.getAngle() / Math.PI),
+ scale * position.x,
+ scale * position.y);
+ canvas.drawBitmap(bitmap, scale * (position.x), scale * (position.y), mPaint);
+ canvas.restoreToCount(saveCount);
+ }
+
+ // Continue drawing with the next body in the world
+ body = body.getNext();
+ }
+ }
+
+ public void setGameCountDown(TiltGameFragment.GameCountdown gameCountDown) {
+ mGameCountDown = gameCountDown;
+ }
+
+ /** Returns the index of a randomly colored gumball. */
+ public static int getRandomGumballId() {
+ int index = sRandomGenerator.nextInt(GUMBALLS.length);
+ int id = GUMBALLS[index];
+ return id;
+ }
+
+ /** Gets the correct bitmap based on screen size and rotates and flips the image. */
+ private static Bitmap resizeImage(
+ Resources res,
+ int[] resourceId,
+ int[] sizes,
+ int size,
+ int viewWidth,
+ float rotationDegrees,
+ boolean isFlipped,
+ float caneScale) {
+
+ Matrix matrix = new Matrix();
+ Bitmap bmp = BitmapFactory.decodeResource(res, resourceId[size]);
+
+ if (rotationDegrees != 361f) {
+ matrix.setRotate(rotationDegrees, bmp.getWidth() / 2, bmp.getHeight() / 2);
+ }
+ if (isFlipped) {
+ matrix.preScale(-1, 1);
+ }
+ float scale = ((float) viewWidth) / sizes[size];
+ matrix.postScale(scale, scale);
+
+ bmp =
+ Bitmap.createBitmap(
+ bmp,
+ 0,
+ 0,
+ (int) (bmp.getWidth() * caneScale),
+ bmp.getHeight(),
+ matrix,
+ true);
+
+ return bmp;
+ }
+
+ /** Sets the Box 2D physics world to draw. */
+ public void setModel(PhysicsWorld world) {
+ mWorld = world;
+ }
+
+ /** Adds a gumball for drawing. */
+ public void addGumball(Gumball gumball) {
+ gumball.mGumballColorId = getRandomGumballId();
+ mWorld.addGumball(
+ gumball.mXInitPos,
+ gumball.mYInitPos,
+ gumball,
+ GUMBALL_DENSITY,
+ GUMBALL_RADIUS,
+ GUMBALL_BOUNCE,
+ GUMBALL_FRICTION,
+ BodyType.DYNAMIC);
+ }
+}
diff --git a/gumball/src/main/res/drawable-hdpi/gbg_bg_main_inner.webp b/gumball/src/main/res/drawable-hdpi/gbg_bg_main_inner.webp
new file mode 100644
index 000000000..f2ff12e32
Binary files /dev/null and b/gumball/src/main/res/drawable-hdpi/gbg_bg_main_inner.webp differ
diff --git a/gumball/src/main/res/drawable-hdpi/gbg_bg_main_outer_bottom.9.png b/gumball/src/main/res/drawable-hdpi/gbg_bg_main_outer_bottom.9.png
new file mode 100644
index 000000000..aeb5dfd33
Binary files /dev/null and b/gumball/src/main/res/drawable-hdpi/gbg_bg_main_outer_bottom.9.png differ
diff --git a/gumball/src/main/res/drawable-hdpi/gbg_bg_main_outer_top.9.png b/gumball/src/main/res/drawable-hdpi/gbg_bg_main_outer_top.9.png
new file mode 100644
index 000000000..8ac4c3d48
Binary files /dev/null and b/gumball/src/main/res/drawable-hdpi/gbg_bg_main_outer_top.9.png differ
diff --git a/gumball/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected.webp b/gumball/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected.webp
new file mode 100644
index 000000000..baea8af95
Binary files /dev/null and b/gumball/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected.webp differ
diff --git a/gumball/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected_disabled.webp b/gumball/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected_disabled.webp
new file mode 100644
index 000000000..8ba03fb78
Binary files /dev/null and b/gumball/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected_disabled.webp differ
diff --git a/gumball/src/main/res/drawable-hdpi/gbg_gumball_indicator_pending.webp b/gumball/src/main/res/drawable-hdpi/gbg_gumball_indicator_pending.webp
new file mode 100644
index 000000000..f61c7a3a4
Binary files /dev/null and b/gumball/src/main/res/drawable-hdpi/gbg_gumball_indicator_pending.webp differ
diff --git a/gumball/src/main/res/drawable-hdpi/gbg_gumball_outlet.9.png b/gumball/src/main/res/drawable-hdpi/gbg_gumball_outlet.9.png
new file mode 100644
index 000000000..fcb8a7fff
Binary files /dev/null and b/gumball/src/main/res/drawable-hdpi/gbg_gumball_outlet.9.png differ
diff --git a/gumball/src/main/res/drawable-mdpi/gbg_bg_main_inner.webp b/gumball/src/main/res/drawable-mdpi/gbg_bg_main_inner.webp
new file mode 100644
index 000000000..9f37d4ba1
Binary files /dev/null and b/gumball/src/main/res/drawable-mdpi/gbg_bg_main_inner.webp differ
diff --git a/gumball/src/main/res/drawable-mdpi/gbg_bg_main_outer_bottom.webp b/gumball/src/main/res/drawable-mdpi/gbg_bg_main_outer_bottom.webp
new file mode 100644
index 000000000..84f33bd0d
Binary files /dev/null and b/gumball/src/main/res/drawable-mdpi/gbg_bg_main_outer_bottom.webp differ
diff --git a/gumball/src/main/res/drawable-mdpi/gbg_bg_main_outer_top.webp b/gumball/src/main/res/drawable-mdpi/gbg_bg_main_outer_top.webp
new file mode 100644
index 000000000..f0f7edd47
Binary files /dev/null and b/gumball/src/main/res/drawable-mdpi/gbg_bg_main_outer_top.webp differ
diff --git a/gumball/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected.webp b/gumball/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected.webp
new file mode 100644
index 000000000..b4bca6090
Binary files /dev/null and b/gumball/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected.webp differ
diff --git a/gumball/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected_disabled.webp b/gumball/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected_disabled.webp
new file mode 100644
index 000000000..2deeec63b
Binary files /dev/null and b/gumball/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected_disabled.webp differ
diff --git a/gumball/src/main/res/drawable-mdpi/gbg_gumball_indicator_pending.webp b/gumball/src/main/res/drawable-mdpi/gbg_gumball_indicator_pending.webp
new file mode 100644
index 000000000..a481de006
Binary files /dev/null and b/gumball/src/main/res/drawable-mdpi/gbg_gumball_indicator_pending.webp differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_outlet.9.png b/gumball/src/main/res/drawable-mdpi/gbg_gumball_outlet.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_outlet.9.png
rename to gumball/src/main/res/drawable-mdpi/gbg_gumball_outlet.9.png
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_bg_gumball_indicatorp.9.png b/gumball/src/main/res/drawable-nodpi/gbg_bg_gumball_indicatorp.9.png
new file mode 100644
index 000000000..23eb77ddf
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_bg_gumball_indicatorp.9.png differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_1920.9.png
new file mode 100644
index 000000000..bd8783cec
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_480.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_480.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_candycane_end_480.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_800.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_800.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_candycane_end_800.9.png
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_1920.9.png
new file mode 100644
index 000000000..989132caa
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_1920.9.png differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_480.9.png
new file mode 100644
index 000000000..a5e49c7d2
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_480.9.png differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_800.9.png
new file mode 100644
index 000000000..bae58266a
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_1920.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_1920.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_1920.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_480.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_480.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_480.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_800.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_800.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_800.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_1920.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_1920.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_1920.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_480.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_480.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_480.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_800.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_800.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_800.9.png
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six_1920.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six_1920.webp
new file mode 100644
index 000000000..01891833e
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six_1920.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six_480.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six_480.webp
new file mode 100644
index 000000000..af55d0adf
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six_480.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six_960.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six_960.webp
new file mode 100644
index 000000000..0ce76b482
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six_960.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_1920.9.png
new file mode 100644
index 000000000..2032f05e9
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_1920.9.png differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_480.9.png
new file mode 100644
index 000000000..118fd7f22
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_480.9.png differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_800.9.png
new file mode 100644
index 000000000..a81c5e31c
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_800.9.png differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine_1920.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine_1920.webp
new file mode 100644
index 000000000..2665cf76d
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine_1920.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine_480.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine_480.webp
new file mode 100644
index 000000000..8409b1e95
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine_480.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine_960.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine_960.webp
new file mode 100644
index 000000000..65989f949
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine_960.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_1920.9.png
new file mode 100644
index 000000000..b1abfa100
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_480.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_480.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_480.9.png
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_800.9.png
new file mode 100644
index 000000000..170d22b0a
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_800.9.png differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six_1920.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six_1920.webp
new file mode 100644
index 000000000..d2df25075
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six_1920.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six_480.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six_480.webp
new file mode 100644
index 000000000..41e646292
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six_480.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six_960.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six_960.webp
new file mode 100644
index 000000000..b37738954
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six_960.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six_1920.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six_1920.webp
new file mode 100644
index 000000000..3cc8f2e24
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six_1920.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six_480.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six_480.webp
new file mode 100644
index 000000000..d010e646e
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six_480.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six_960.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six_960.webp
new file mode 100644
index 000000000..4a46b8611
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six_960.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve_1920.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve_1920.webp
new file mode 100644
index 000000000..4e94c7378
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve_1920.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve_480.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve_480.webp
new file mode 100644
index 000000000..06ae70c0c
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve_480.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve_960.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve_960.webp
new file mode 100644
index 000000000..b060de01b
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve_960.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six_1920.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six_1920.webp
new file mode 100644
index 000000000..e1102a712
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six_1920.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six_480.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six_480.webp
new file mode 100644
index 000000000..2e7bbb130
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six_480.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six_960.webp b/gumball/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six_960.webp
new file mode 100644
index 000000000..94f4e92eb
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six_960.webp differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_blue_1920.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_1920.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_blue_1920.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_blue_480.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_480.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_blue_480.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_blue_800.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_800.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_blue_800.9.png
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_gumball_funnel_1920.webp b/gumball/src/main/res/drawable-nodpi/gbg_gumball_funnel_1920.webp
new file mode 100644
index 000000000..e658c7f03
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_gumball_funnel_1920.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_gumball_funnel_480.webp b/gumball/src/main/res/drawable-nodpi/gbg_gumball_funnel_480.webp
new file mode 100644
index 000000000..b29f3a2e2
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_gumball_funnel_480.webp differ
diff --git a/gumball/src/main/res/drawable-nodpi/gbg_gumball_funnel_800.webp b/gumball/src/main/res/drawable-nodpi/gbg_gumball_funnel_800.webp
new file mode 100644
index 000000000..7bae780a6
Binary files /dev/null and b/gumball/src/main/res/drawable-nodpi/gbg_gumball_funnel_800.webp differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_green_1920.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_1920.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_green_1920.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_green_480.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_480.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_green_480.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_green_800.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_800.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_green_800.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_orange_1920.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_1920.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_orange_1920.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_orange_480.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_480.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_orange_480.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_orange_800.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_800.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_orange_800.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_purple_1920.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_1920.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_purple_1920.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_purple_480.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_480.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_purple_480.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_purple_800.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_800.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_purple_800.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_red_1920.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_1920.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_red_1920.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_red_480.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_480.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_red_480.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_red_800.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_800.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_red_800.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_1920.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_yellow_1920.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_1920.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_yellow_1920.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_480.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_yellow_480.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_480.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_yellow_480.9.png
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_800.9.png b/gumball/src/main/res/drawable-nodpi/gbg_gumball_yellow_800.9.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_800.9.png
rename to gumball/src/main/res/drawable-nodpi/gbg_gumball_yellow_800.9.png
diff --git a/gumball/src/main/res/drawable-xhdpi/gbg_bg_main_inner.webp b/gumball/src/main/res/drawable-xhdpi/gbg_bg_main_inner.webp
new file mode 100644
index 000000000..d6ea96604
Binary files /dev/null and b/gumball/src/main/res/drawable-xhdpi/gbg_bg_main_inner.webp differ
diff --git a/gumball/src/main/res/drawable-xhdpi/gbg_bg_main_outer_bottom.9.png b/gumball/src/main/res/drawable-xhdpi/gbg_bg_main_outer_bottom.9.png
new file mode 100644
index 000000000..38e98e42a
Binary files /dev/null and b/gumball/src/main/res/drawable-xhdpi/gbg_bg_main_outer_bottom.9.png differ
diff --git a/gumball/src/main/res/drawable-xhdpi/gbg_bg_main_outer_top.9.png b/gumball/src/main/res/drawable-xhdpi/gbg_bg_main_outer_top.9.png
new file mode 100644
index 000000000..9fc7186f0
Binary files /dev/null and b/gumball/src/main/res/drawable-xhdpi/gbg_bg_main_outer_top.9.png differ
diff --git a/gumball/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected.webp b/gumball/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected.webp
new file mode 100644
index 000000000..00e7d1855
Binary files /dev/null and b/gumball/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected.webp differ
diff --git a/gumball/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected_disabled.webp b/gumball/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected_disabled.webp
new file mode 100644
index 000000000..1d031d67f
Binary files /dev/null and b/gumball/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected_disabled.webp differ
diff --git a/gumball/src/main/res/drawable-xhdpi/gbg_gumball_indicator_pending.webp b/gumball/src/main/res/drawable-xhdpi/gbg_gumball_indicator_pending.webp
new file mode 100644
index 000000000..e51a90079
Binary files /dev/null and b/gumball/src/main/res/drawable-xhdpi/gbg_gumball_indicator_pending.webp differ
diff --git a/gumball/src/main/res/drawable-xhdpi/gbg_gumball_outlet.9.png b/gumball/src/main/res/drawable-xhdpi/gbg_gumball_outlet.9.png
new file mode 100644
index 000000000..168782801
Binary files /dev/null and b/gumball/src/main/res/drawable-xhdpi/gbg_gumball_outlet.9.png differ
diff --git a/gumball/src/main/res/drawable-xxhdpi/gbg_bg_main_inner.webp b/gumball/src/main/res/drawable-xxhdpi/gbg_bg_main_inner.webp
new file mode 100644
index 000000000..0fd356921
Binary files /dev/null and b/gumball/src/main/res/drawable-xxhdpi/gbg_bg_main_inner.webp differ
diff --git a/gumball/src/main/res/drawable-xxhdpi/gbg_bg_main_outer_top.9.png b/gumball/src/main/res/drawable-xxhdpi/gbg_bg_main_outer_top.9.png
new file mode 100644
index 000000000..2b9368d23
Binary files /dev/null and b/gumball/src/main/res/drawable-xxhdpi/gbg_bg_main_outer_top.9.png differ
diff --git a/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected.webp b/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected.webp
new file mode 100644
index 000000000..a67112d75
Binary files /dev/null and b/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected.webp differ
diff --git a/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected_disabled.webp b/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected_disabled.webp
new file mode 100644
index 000000000..fca1fa60b
Binary files /dev/null and b/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected_disabled.webp differ
diff --git a/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_pending.webp b/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_pending.webp
new file mode 100644
index 000000000..df98e690d
Binary files /dev/null and b/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_pending.webp differ
diff --git a/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_outlet.9.png b/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_outlet.9.png
new file mode 100644
index 000000000..680deb856
Binary files /dev/null and b/gumball/src/main/res/drawable-xxhdpi/gbg_gumball_outlet.9.png differ
diff --git a/gumball/src/main/res/layout/activity_gumball.xml b/gumball/src/main/res/layout/activity_gumball.xml
new file mode 100644
index 000000000..768ee97a8
--- /dev/null
+++ b/gumball/src/main/res/layout/activity_gumball.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/gumball/src/main/res/layout/fragment_gumball.xml b/gumball/src/main/res/layout/fragment_gumball.xml
new file mode 100644
index 000000000..304ed68ee
--- /dev/null
+++ b/gumball/src/main/res/layout/fragment_gumball.xml
@@ -0,0 +1,286 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level1.json b/gumball/src/main/res/raw-sw600dp/level1.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level1.json
rename to gumball/src/main/res/raw-sw600dp/level1.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level10.json b/gumball/src/main/res/raw-sw600dp/level10.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level10.json
rename to gumball/src/main/res/raw-sw600dp/level10.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level11.json b/gumball/src/main/res/raw-sw600dp/level11.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level11.json
rename to gumball/src/main/res/raw-sw600dp/level11.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level12.json b/gumball/src/main/res/raw-sw600dp/level12.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level12.json
rename to gumball/src/main/res/raw-sw600dp/level12.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level13.json b/gumball/src/main/res/raw-sw600dp/level13.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level13.json
rename to gumball/src/main/res/raw-sw600dp/level13.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level2.json b/gumball/src/main/res/raw-sw600dp/level2.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level2.json
rename to gumball/src/main/res/raw-sw600dp/level2.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level3.json b/gumball/src/main/res/raw-sw600dp/level3.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level3.json
rename to gumball/src/main/res/raw-sw600dp/level3.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level4.json b/gumball/src/main/res/raw-sw600dp/level4.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level4.json
rename to gumball/src/main/res/raw-sw600dp/level4.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level5.json b/gumball/src/main/res/raw-sw600dp/level5.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level5.json
rename to gumball/src/main/res/raw-sw600dp/level5.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level6.json b/gumball/src/main/res/raw-sw600dp/level6.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level6.json
rename to gumball/src/main/res/raw-sw600dp/level6.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level7.json b/gumball/src/main/res/raw-sw600dp/level7.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level7.json
rename to gumball/src/main/res/raw-sw600dp/level7.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level8.json b/gumball/src/main/res/raw-sw600dp/level8.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level8.json
rename to gumball/src/main/res/raw-sw600dp/level8.json
diff --git a/santa-tracker/src/main/res/raw-sw600dp/level9.json b/gumball/src/main/res/raw-sw600dp/level9.json
similarity index 100%
rename from santa-tracker/src/main/res/raw-sw600dp/level9.json
rename to gumball/src/main/res/raw-sw600dp/level9.json
diff --git a/gumball/src/main/res/raw/gbg_ball_bounce_1.m4a b/gumball/src/main/res/raw/gbg_ball_bounce_1.m4a
new file mode 100644
index 000000000..007aada74
Binary files /dev/null and b/gumball/src/main/res/raw/gbg_ball_bounce_1.m4a differ
diff --git a/gumball/src/main/res/raw/gbg_ball_bounce_2.m4a b/gumball/src/main/res/raw/gbg_ball_bounce_2.m4a
new file mode 100644
index 000000000..b6ccf87fe
Binary files /dev/null and b/gumball/src/main/res/raw/gbg_ball_bounce_2.m4a differ
diff --git a/gumball/src/main/res/raw/gbg_ball_bounce_3.m4a b/gumball/src/main/res/raw/gbg_ball_bounce_3.m4a
new file mode 100644
index 000000000..1d3e84333
Binary files /dev/null and b/gumball/src/main/res/raw/gbg_ball_bounce_3.m4a differ
diff --git a/gumball/src/main/res/raw/gbg_ball_fall_out.m4a b/gumball/src/main/res/raw/gbg_ball_fall_out.m4a
new file mode 100644
index 000000000..cdfb7c1e0
Binary files /dev/null and b/gumball/src/main/res/raw/gbg_ball_fall_out.m4a differ
diff --git a/gumball/src/main/res/raw/gbg_ball_into_machine.m4a b/gumball/src/main/res/raw/gbg_ball_into_machine.m4a
new file mode 100644
index 000000000..745d9207e
Binary files /dev/null and b/gumball/src/main/res/raw/gbg_ball_into_machine.m4a differ
diff --git a/gumball/src/main/res/raw/gbg_new_ball_bounce_drop.m4a b/gumball/src/main/res/raw/gbg_new_ball_bounce_drop.m4a
new file mode 100644
index 000000000..10569b7ab
Binary files /dev/null and b/gumball/src/main/res/raw/gbg_new_ball_bounce_drop.m4a differ
diff --git a/santa-tracker/src/main/res/raw/level1.json b/gumball/src/main/res/raw/level1.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level1.json
rename to gumball/src/main/res/raw/level1.json
diff --git a/santa-tracker/src/main/res/raw/level10.json b/gumball/src/main/res/raw/level10.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level10.json
rename to gumball/src/main/res/raw/level10.json
diff --git a/santa-tracker/src/main/res/raw/level11.json b/gumball/src/main/res/raw/level11.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level11.json
rename to gumball/src/main/res/raw/level11.json
diff --git a/santa-tracker/src/main/res/raw/level12.json b/gumball/src/main/res/raw/level12.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level12.json
rename to gumball/src/main/res/raw/level12.json
diff --git a/santa-tracker/src/main/res/raw/level13.json b/gumball/src/main/res/raw/level13.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level13.json
rename to gumball/src/main/res/raw/level13.json
diff --git a/santa-tracker/src/main/res/raw/level2.json b/gumball/src/main/res/raw/level2.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level2.json
rename to gumball/src/main/res/raw/level2.json
diff --git a/santa-tracker/src/main/res/raw/level3.json b/gumball/src/main/res/raw/level3.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level3.json
rename to gumball/src/main/res/raw/level3.json
diff --git a/santa-tracker/src/main/res/raw/level4.json b/gumball/src/main/res/raw/level4.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level4.json
rename to gumball/src/main/res/raw/level4.json
diff --git a/santa-tracker/src/main/res/raw/level5.json b/gumball/src/main/res/raw/level5.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level5.json
rename to gumball/src/main/res/raw/level5.json
diff --git a/santa-tracker/src/main/res/raw/level6.json b/gumball/src/main/res/raw/level6.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level6.json
rename to gumball/src/main/res/raw/level6.json
diff --git a/santa-tracker/src/main/res/raw/level7.json b/gumball/src/main/res/raw/level7.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level7.json
rename to gumball/src/main/res/raw/level7.json
diff --git a/santa-tracker/src/main/res/raw/level8.json b/gumball/src/main/res/raw/level8.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level8.json
rename to gumball/src/main/res/raw/level8.json
diff --git a/santa-tracker/src/main/res/raw/level9.json b/gumball/src/main/res/raw/level9.json
similarity index 100%
rename from santa-tracker/src/main/res/raw/level9.json
rename to gumball/src/main/res/raw/level9.json
diff --git a/gumball/src/main/res/values-af/strings.xml b/gumball/src/main/res/values-af/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-af/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-ar-rXB/strings.xml b/gumball/src/main/res/values-ar-rXB/strings.xml
new file mode 100644
index 000000000..d232ee873
--- /dev/null
+++ b/gumball/src/main/res/values-ar-rXB/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-bg/strings.xml b/gumball/src/main/res/values-bg/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-bg/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-ca/strings.xml b/gumball/src/main/res/values-ca/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-ca/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-da/strings.xml b/gumball/src/main/res/values-da/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-da/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-de-rAT/strings.xml b/gumball/src/main/res/values-de-rAT/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-de-rAT/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-de-rCH/strings.xml b/gumball/src/main/res/values-de-rCH/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-de-rCH/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-de/strings.xml b/gumball/src/main/res/values-de/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-de/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-en-rGB/strings.xml b/gumball/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-en-rIE/strings.xml b/gumball/src/main/res/values-en-rIE/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-en-rIE/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-en-rIN/strings.xml b/gumball/src/main/res/values-en-rIN/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-en-rIN/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-en-rSG/strings.xml b/gumball/src/main/res/values-en-rSG/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-en-rSG/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-en-rXA/strings.xml b/gumball/src/main/res/values-en-rXA/strings.xml
new file mode 100644
index 000000000..a59153e39
--- /dev/null
+++ b/gumball/src/main/res/values-en-rXA/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ [Ĝûmбåļļ one]
+
diff --git a/gumball/src/main/res/values-en-rXC/strings.xml b/gumball/src/main/res/values-en-rXC/strings.xml
new file mode 100644
index 000000000..d8297c59b
--- /dev/null
+++ b/gumball/src/main/res/values-en-rXC/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-en-rZA/strings.xml b/gumball/src/main/res/values-en-rZA/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-en-rZA/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rAR/strings.xml b/gumball/src/main/res/values-es-rAR/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rAR/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rBO/strings.xml b/gumball/src/main/res/values-es-rBO/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rBO/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rCL/strings.xml b/gumball/src/main/res/values-es-rCL/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rCL/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rCO/strings.xml b/gumball/src/main/res/values-es-rCO/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rCO/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rCR/strings.xml b/gumball/src/main/res/values-es-rCR/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rCR/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rDO/strings.xml b/gumball/src/main/res/values-es-rDO/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rDO/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rEC/strings.xml b/gumball/src/main/res/values-es-rEC/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rEC/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rGT/strings.xml b/gumball/src/main/res/values-es-rGT/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rGT/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rHN/strings.xml b/gumball/src/main/res/values-es-rHN/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rHN/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rMX/strings.xml b/gumball/src/main/res/values-es-rMX/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rMX/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rNI/strings.xml b/gumball/src/main/res/values-es-rNI/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rNI/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rPA/strings.xml b/gumball/src/main/res/values-es-rPA/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rPA/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rPE/strings.xml b/gumball/src/main/res/values-es-rPE/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rPE/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rPR/strings.xml b/gumball/src/main/res/values-es-rPR/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rPR/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rPY/strings.xml b/gumball/src/main/res/values-es-rPY/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rPY/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rSV/strings.xml b/gumball/src/main/res/values-es-rSV/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rSV/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rUS/strings.xml b/gumball/src/main/res/values-es-rUS/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rUS/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rUY/strings.xml b/gumball/src/main/res/values-es-rUY/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rUY/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es-rVE/strings.xml b/gumball/src/main/res/values-es-rVE/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-es-rVE/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-es/strings.xml b/gumball/src/main/res/values-es/strings.xml
new file mode 100644
index 000000000..58c3a50fb
--- /dev/null
+++ b/gumball/src/main/res/values-es/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Piruletas
+
diff --git a/gumball/src/main/res/values-et/strings.xml b/gumball/src/main/res/values-et/strings.xml
new file mode 100644
index 000000000..5724ff99a
--- /dev/null
+++ b/gumball/src/main/res/values-et/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Nätsukomm
+
diff --git a/gumball/src/main/res/values-fi/strings.xml b/gumball/src/main/res/values-fi/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-fi/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-fil/strings.xml b/gumball/src/main/res/values-fil/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-fil/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-fr-rCA/strings.xml b/gumball/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-fr-rCH/strings.xml b/gumball/src/main/res/values-fr-rCH/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-fr-rCH/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-fr/strings.xml b/gumball/src/main/res/values-fr/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-fr/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-gsw/strings.xml b/gumball/src/main/res/values-gsw/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-gsw/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-hr/strings.xml b/gumball/src/main/res/values-hr/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-hr/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-id/strings.xml b/gumball/src/main/res/values-id/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-id/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-in/strings.xml b/gumball/src/main/res/values-in/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-in/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-it/strings.xml b/gumball/src/main/res/values-it/strings.xml
new file mode 100644
index 000000000..851344ee6
--- /dev/null
+++ b/gumball/src/main/res/values-it/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Palline di zucchero
+
diff --git a/gumball/src/main/res/values-ja/strings.xml b/gumball/src/main/res/values-ja/strings.xml
new file mode 100644
index 000000000..f9e783b50
--- /dev/null
+++ b/gumball/src/main/res/values-ja/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ ガムボール
+
diff --git a/gumball/src/main/res/values-ko/strings.xml b/gumball/src/main/res/values-ko/strings.xml
new file mode 100644
index 000000000..ce1c45765
--- /dev/null
+++ b/gumball/src/main/res/values-ko/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ 풍선껌
+
diff --git a/gumball/src/main/res/values-ln/strings.xml b/gumball/src/main/res/values-ln/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-ln/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-lt/strings.xml b/gumball/src/main/res/values-lt/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-lt/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-lv/strings.xml b/gumball/src/main/res/values-lv/strings.xml
new file mode 100644
index 000000000..3f64e96e6
--- /dev/null
+++ b/gumball/src/main/res/values-lv/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Spēle Gumball
+
diff --git a/gumball/src/main/res/values-ml/strings.xml b/gumball/src/main/res/values-ml/strings.xml
new file mode 100644
index 000000000..9eac4ea03
--- /dev/null
+++ b/gumball/src/main/res/values-ml/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ ഗംബോൾ
+
diff --git a/gumball/src/main/res/values-mo/strings.xml b/gumball/src/main/res/values-mo/strings.xml
new file mode 100644
index 000000000..38fe689bc
--- /dev/null
+++ b/gumball/src/main/res/values-mo/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Acadele buclucașe
+
diff --git a/gumball/src/main/res/values-nb/strings.xml b/gumball/src/main/res/values-nb/strings.xml
new file mode 100644
index 000000000..47f07bd28
--- /dev/null
+++ b/gumball/src/main/res/values-nb/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Rulledrops
+
diff --git a/gumball/src/main/res/values-no/strings.xml b/gumball/src/main/res/values-no/strings.xml
new file mode 100644
index 000000000..47f07bd28
--- /dev/null
+++ b/gumball/src/main/res/values-no/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Rulledrops
+
diff --git a/gumball/src/main/res/values-pl/strings.xml b/gumball/src/main/res/values-pl/strings.xml
new file mode 100644
index 000000000..20f56b3cc
--- /dev/null
+++ b/gumball/src/main/res/values-pl/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Guma w kulkach
+
diff --git a/gumball/src/main/res/values-pt-rBR/strings.xml b/gumball/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000..e501bcf6e
--- /dev/null
+++ b/gumball/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Ladeira dos chicletes
+
diff --git a/gumball/src/main/res/values-pt-rPT/strings.xml b/gumball/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-pt/strings.xml b/gumball/src/main/res/values-pt/strings.xml
new file mode 100644
index 000000000..e501bcf6e
--- /dev/null
+++ b/gumball/src/main/res/values-pt/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Ladeira dos chicletes
+
diff --git a/gumball/src/main/res/values-ro/strings.xml b/gumball/src/main/res/values-ro/strings.xml
new file mode 100644
index 000000000..38fe689bc
--- /dev/null
+++ b/gumball/src/main/res/values-ro/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Acadele buclucașe
+
diff --git a/gumball/src/main/res/values-ru/strings.xml b/gumball/src/main/res/values-ru/strings.xml
new file mode 100644
index 000000000..e53d985ba
--- /dev/null
+++ b/gumball/src/main/res/values-ru/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Жвачка
+
diff --git a/gumball/src/main/res/values-sl/strings.xml b/gumball/src/main/res/values-sl/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-sl/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-sv/strings.xml b/gumball/src/main/res/values-sv/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-sv/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-ta/strings.xml b/gumball/src/main/res/values-ta/strings.xml
new file mode 100644
index 000000000..589812fc0
--- /dev/null
+++ b/gumball/src/main/res/values-ta/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ கம்பால்
+
diff --git a/gumball/src/main/res/values-th/strings.xml b/gumball/src/main/res/values-th/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-th/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-tl/strings.xml b/gumball/src/main/res/values-tl/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-tl/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-uk/strings.xml b/gumball/src/main/res/values-uk/strings.xml
new file mode 100644
index 000000000..b953f5bf2
--- /dev/null
+++ b/gumball/src/main/res/values-uk/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Жувальна гумка
+
diff --git a/gumball/src/main/res/values-vi/strings.xml b/gumball/src/main/res/values-vi/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-vi/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-zh-rCN/strings.xml b/gumball/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values-zh-rHK/strings.xml b/gumball/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 000000000..707c284e3
--- /dev/null
+++ b/gumball/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ 口香糖
+
diff --git a/gumball/src/main/res/values-zh-rTW/strings.xml b/gumball/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..d8d4a11d1
--- /dev/null
+++ b/gumball/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ 口香糖球
+
diff --git a/gumball/src/main/res/values-zh/strings.xml b/gumball/src/main/res/values-zh/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values-zh/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values/colors.xml b/gumball/src/main/res/values/colors.xml
new file mode 100644
index 000000000..56165f71c
--- /dev/null
+++ b/gumball/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/gumball/src/main/res/values/strings.xml b/gumball/src/main/res/values/strings.xml
new file mode 100644
index 000000000..8bb704db6
--- /dev/null
+++ b/gumball/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Gumball
+
diff --git a/gumball/src/main/res/values/styles.xml b/gumball/src/main/res/values/styles.xml
new file mode 100644
index 000000000..6c65774ee
--- /dev/null
+++ b/gumball/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/jetpack/build.gradle b/jetpack/build.gradle
new file mode 100644
index 000000000..bb459cb2d
--- /dev/null
+++ b/jetpack/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.dynamic-feature'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.tools
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+ }
+}
+
+dependencies {
+ // Using api dependency to use some dependencies defined in santa-tracker. e.g. core-ktx
+ api project(':santa-tracker')
+}
diff --git a/jetpack/src/debug/res/values/game_ids.xml b/jetpack/src/debug/res/values/game_ids.xml
new file mode 100644
index 000000000..7b545187a
--- /dev/null
+++ b/jetpack/src/debug/res/values/game_ids.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ 80719462666
+ CgkIioKF2qwCEAIQEw
+ CgkIioKF2qwCEAIQFA
+
diff --git a/jetpack/src/debug/res/values/game_ids_jetpack.xml b/jetpack/src/debug/res/values/game_ids_jetpack.xml
new file mode 100644
index 000000000..f215d5b11
--- /dev/null
+++ b/jetpack/src/debug/res/values/game_ids_jetpack.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+CgkIioKF2qwCEAIQAg
+CgkIioKF2qwCEAIQAw
+CgkIioKF2qwCEAIQBA
+CgkIioKF2qwCEAIQBg
+CgkIioKF2qwCEAIQBw
+CgkIioKF2qwCEAIQCA
+CgkIioKF2qwCEAIQCQ
+CgkIioKF2qwCEAIQCg
+CgkIioKF2qwCEAIQCw
+CgkIioKF2qwCEAIQDA
+CgkIioKF2qwCEAIQDQ
+CgkIioKF2qwCEAIQDg
+CgkIioKF2qwCEAIQDw
+CgkIioKF2qwCEAIQEA
+CgkIioKF2qwCEAIQEQ
+CgkIioKF2qwCEAIQEg
+
+
diff --git a/santa-tracker/src/dogfood/res/values/game_ids.xml b/jetpack/src/dogfood/res/values/game_ids.xml
similarity index 100%
rename from santa-tracker/src/dogfood/res/values/game_ids.xml
rename to jetpack/src/dogfood/res/values/game_ids.xml
diff --git a/santa-tracker/src/dogfood/res/values/game_ids_jetpack.xml b/jetpack/src/dogfood/res/values/game_ids_jetpack.xml
similarity index 100%
rename from santa-tracker/src/dogfood/res/values/game_ids_jetpack.xml
rename to jetpack/src/dogfood/res/values/game_ids_jetpack.xml
diff --git a/jetpack/src/main/AndroidManifest.xml b/jetpack/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..de1ffc56f
--- /dev/null
+++ b/jetpack/src/main/AndroidManifest.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/BaseScene.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/BaseScene.kt
new file mode 100644
index 000000000..0c48b2e52
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/BaseScene.kt
@@ -0,0 +1,667 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.jetpack
+
+import android.app.UiModeManager
+import android.content.Context
+import android.content.res.Configuration
+import com.google.android.apps.playgames.simpleengine.Renderer
+import com.google.android.apps.playgames.simpleengine.Scene
+import com.google.android.apps.playgames.simpleengine.SceneManager
+import com.google.android.apps.playgames.simpleengine.SmoothValue
+import com.google.android.apps.playgames.simpleengine.game.GameObject
+import com.google.android.apps.playgames.simpleengine.game.World
+import com.google.android.apps.playgames.simpleengine.ui.Button
+import com.google.android.apps.playgames.simpleengine.ui.SimpleUI
+import com.google.android.apps.playgames.simpleengine.ui.Widget
+import com.google.android.apps.santatracker.util.ImmersiveModeHelper
+import java.util.Random
+
+abstract class BaseScene : Scene(), Widget.WidgetTriggerListener, GameEndedListener {
+
+ // digit object factory (to display score, etc)
+ protected lateinit var digitFactory: DigitObjectFactory
+ protected lateinit var objectFactory: GameObjectFactory
+
+ protected lateinit var world: World
+ protected lateinit var renderer: Renderer
+ protected val random = Random()
+
+ // score bar object
+ private var scoreBarObj: GameObject? = null
+
+ // score bar label object
+ private var scoreBarLabelObj: GameObject? = null
+
+ // score digit objects
+ private var scoreDigitObj = arrayOfNulls(GameConfig.ScoreDisplay.DIGIT_COUNT)
+
+ // timer digit objects
+ private var timeDigitObj = arrayOfNulls(GameConfig.TimeDisplay.DIGIT_COUNT)
+
+ // countdown objects
+ var countdownDigitObj: GameObject? = null
+
+ // player's current score
+ override var score = 0
+ protected set
+ protected var displayedScore = SmoothValue(0.0f, GameConfig.ScoreDisplay.UPDATE_SPEED)
+
+ // game ended?
+ protected var gameEnded = false
+
+ protected var isInGameEndingTransition = false
+
+ // our UI (buttons, etc)
+ private lateinit var simpleUI: SimpleUI
+
+ // sfx IDs
+ private var gameOverSfx: Int = 0
+
+ // paused?
+ protected var paused = false
+
+ // in start countdown?
+ protected var inStartCountdown = false
+ protected var startCountdownTimeRemaining: Float = 0.toFloat()
+
+ // back key pressed?
+ private var backKeyPending = false
+
+ // DPAD_CENTER key pressed?
+ private var confirmKeyPending: Boolean = false
+ private var confirmKeyEventTime: Long = 0
+
+ // isRunning on Tv?
+ protected var isTv: Boolean = false
+
+ // pause button
+ private var pauseButton: Button? = null
+
+ // speaker on and mute buttons
+ private var speakerOnButton: Button? = null
+ private var speakerMuteButton: Button? = null
+
+ // pause curtain, that is, the full screen object we display as a translucent
+ // screen over the whole display when the game is paused
+ private lateinit var pauseCurtain: GameObject
+
+ // the big play button
+ private lateinit var bigPlayButton: Button
+ private var bigStartGameButton: Button? = null
+
+ // quit button
+ private var quitButton: Button? = null
+ private var quitBarLabel: GameObject? = null
+
+ // game objects that compose the Sign In ui
+ private var signInBarObj: GameObject? = null
+ private var signInButton: Button? = null
+ private var signInTextObj: GameObject? = null
+
+ // to be implemented by subclasses
+ protected abstract val bgmResId: Int
+
+ protected abstract val displayedTime: Float
+
+ // are we signed in
+ private var signedIn = false
+
+ private val scoreDigitVisibleObj: Array
+ get() {
+ val numDigits = Math.max(
+ (displayedScore.value.toInt() + 1).toString().length,
+ GameConfig.ScoreBar.MIN_DIGITS_VISIBLE)
+ val scoreDigitVisibleObj = arrayOfNulls(numDigits)
+ for (i in scoreDigitObj.size - numDigits until scoreDigitObj.size) {
+ scoreDigitVisibleObj[i - (scoreDigitObj.size - numDigits)] = scoreDigitObj[i]
+ }
+ return scoreDigitVisibleObj
+ }
+
+ protected abstract fun makeNewScene(): BaseScene
+
+ override fun onInstall() {
+
+ // are we signed in?
+ val act = SceneManager.getInstance().activity as SceneActivity?
+ act?.let {
+ signedIn = act.isSignedIn
+ val manger = act.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
+ isTv = Configuration.UI_MODE_TYPE_TELEVISION == manger.currentModeType
+ }
+
+ renderer = SceneManager.getInstance().renderer
+ world = World(renderer)
+ digitFactory = DigitObjectFactory(renderer, world)
+ digitFactory.requestWhiteTextures(GameConfig.ScoreDisplay.DIGIT_SIZE)
+ objectFactory = GameObjectFactory(renderer, world)
+ objectFactory.requestTextures()
+
+ simpleUI = SimpleUI(renderer)
+
+ if (isTv) {
+ digitFactory.makeDigitObjects(
+ GameConfig.ScoreDisplay.DIGIT_COUNT,
+ GameConfig.TYPE_DECOR,
+ renderer.getRelativePos(
+ GameConfig.ScoreDisplay.POS_X_REL, GameConfig.ScoreDisplay.POS_X_DELTA),
+ renderer.getRelativePos(
+ GameConfig.ScoreDisplay.POS_Y_REL_TV,
+ GameConfig.ScoreDisplay.POS_Y_DELTA_TV),
+ GameConfig.ScoreDisplay.DIGIT_SIZE,
+ GameConfig.ScoreDisplay.DIGIT_SPACING,
+ scoreDigitObj)
+ bigPlayButton = objectFactory.makeBigPlayButton(this, MSG_RESUME)
+ bigPlayButton.hide()
+ simpleUI.add(bigPlayButton)
+
+ val x = GameConfig.TimeDisplay.POS_X_DELTA + GameConfig.TimeDisplay.ICON_SIZE
+ digitFactory.makeDigitObjects(
+ GameConfig.TimeDisplay.DIGIT_COUNT,
+ GameConfig.TYPE_DECOR,
+ renderer.getRelativePos(GameConfig.TimeDisplay.POS_X_REL, x),
+ renderer.getRelativePos(
+ GameConfig.TimeDisplay.POS_Y_REL_TV,
+ GameConfig.TimeDisplay.POS_Y_DELTA_TV),
+ GameConfig.TimeDisplay.DIGIT_SIZE,
+ GameConfig.TimeDisplay.DIGIT_SPACING,
+ timeDigitObj)
+
+ pauseCurtain = objectFactory.makePauseCurtain()
+ pauseCurtain.hide()
+ } else {
+ scoreBarObj = objectFactory.makeScoreBar()
+ scoreBarLabelObj = objectFactory.makeScoreBarLabel()
+ digitFactory.makeDigitObjects(
+ GameConfig.ScoreDisplay.DIGIT_COUNT,
+ GameConfig.TYPE_DECOR,
+ renderer.getRelativePos(
+ GameConfig.ScoreDisplay.POS_X_REL, GameConfig.ScoreDisplay.POS_X_DELTA),
+ renderer.getRelativePos(
+ GameConfig.ScoreDisplay.POS_Y_REL, GameConfig.ScoreDisplay.POS_Y_DELTA),
+ GameConfig.ScoreDisplay.DIGIT_SIZE,
+ GameConfig.ScoreDisplay.DIGIT_SPACING,
+ scoreDigitObj)
+ hideObjects(scoreDigitObj)
+ bringObjectsToFront(scoreDigitVisibleObj)
+ val x = GameConfig.TimeDisplay.POS_X_DELTA + GameConfig.TimeDisplay.ICON_SIZE
+ digitFactory.makeDigitObjects(
+ GameConfig.TimeDisplay.DIGIT_COUNT,
+ GameConfig.TYPE_DECOR,
+ renderer.getRelativePos(GameConfig.TimeDisplay.POS_X_REL, x),
+ renderer.getRelativePos(
+ GameConfig.TimeDisplay.POS_Y_REL, GameConfig.TimeDisplay.POS_Y_DELTA),
+ GameConfig.TimeDisplay.DIGIT_SIZE,
+ GameConfig.TimeDisplay.DIGIT_SPACING,
+ timeDigitObj)
+ countdownDigitObj = digitFactory.makeDigitObject(
+ GameConfig.TYPE_DECOR, 0.0f, 0.0f, GameConfig.Countdown.DIGIT_SIZE)
+ countdownDigitObj?.hide()
+ quitButton = objectFactory.makeQuitButton(this, MSG_QUIT)
+ quitBarLabel = objectFactory.makeQuitBarLabel()
+ quitButton?.hide()
+ quitBarLabel?.hide()
+ simpleUI.add(quitButton)
+
+ pauseButton = objectFactory.makePauseButton(this, MSG_PAUSE)
+ simpleUI.add(pauseButton)
+
+ speakerMuteButton = objectFactory.makeSpeakerMuteButton(this, MSG_UNMUTE)
+ speakerOnButton = objectFactory.makeSpeakerOnButton(this, MSG_MUTE)
+ simpleUI.add(speakerMuteButton)
+ simpleUI.add(speakerOnButton)
+ pauseCurtain = objectFactory.makePauseCurtain()
+ pauseCurtain.hide()
+
+ bigPlayButton = objectFactory.makeBigPlayButton(this, MSG_RESUME)
+ bigPlayButton.hide()
+ simpleUI.add(bigPlayButton)
+ bigStartGameButton = objectFactory.makeBigPlayButton(this, MSG_START)
+ bigStartGameButton?.hide()
+ simpleUI.add(bigStartGameButton)
+ }
+
+ val soundManager = SceneManager.getInstance().soundManager
+ if (soundManager != null) {
+ soundManager.requestBackgroundMusic(bgmResId)
+ gameOverSfx = soundManager.requestSfx(com.google.android.apps.playgames.R.raw.gameover)
+ if (soundManager.isMuted) {
+ muteSpeaker()
+ } else {
+ unmuteSpeaker()
+ }
+ }
+ }
+
+ override fun onUninstall() {}
+
+ override fun doStandbyFrame(deltaT: Float) {}
+
+ override fun doFrame(deltaT: Float) {
+ var deltaT = deltaT
+ if (paused) {
+ deltaT = 0.0f
+ }
+
+ if (backKeyPending) {
+ processBackKey()
+ }
+
+ if (confirmKeyPending) {
+ // TODO: move a focus based on KeyEvent
+ processCenterKey()
+ }
+
+ // If Activity lost focus and we're playing the game, pause
+ if (!SceneManager.getInstance().shouldBePlaying() && !gameEnded && !paused) {
+ pauseGame()
+ }
+
+ if (!gameEnded) {
+ updateScore(deltaT)
+ updateTime(deltaT)
+ } else {
+ updateScore(deltaT)
+ checkSignInWidgetsNeeded()
+ }
+
+ world.doFrame(deltaT)
+ }
+
+ private fun processBackKey() {
+ backKeyPending = false
+ if (gameEnded || paused && isTv) {
+ quitGame()
+ } else if (paused) {
+ unPauseGame()
+ } else {
+ pauseGame()
+ }
+ }
+
+ private fun processCenterKey() {
+ val currTime = System.currentTimeMillis()
+ if (currTime - confirmKeyEventTime < CENTER_KEY_DELAY_MS) {
+ bigPlayButton.setPressed(true)
+ } else {
+ confirmKeyPending = false
+ bigPlayButton.setPressed(false)
+ if (paused) {
+ unPauseGame()
+ } else if (gameEnded) {
+ // re-start new game
+ SceneManager.getInstance().requestNewScene(makeNewScene())
+ }
+ }
+ }
+
+ private fun updateScore(deltaT: Float) {
+ if (gameEnded) {
+ displayedScore.value = score.toFloat()
+ } else {
+ displayedScore.target = score.toFloat()
+ displayedScore.update(deltaT)
+ }
+ digitFactory.setDigits(Math.round(displayedScore.value), scoreDigitObj)
+ bringObjectsToFront(scoreDigitVisibleObj)
+ }
+
+ protected open fun endGame() {
+ gameEnded = true
+ // show the podium object
+ objectFactory.makePodium()
+
+ // move score to final position
+ val x = renderer.getRelativePos(
+ GameConfig.Podium.ScoreDisplay.X_REL,
+ GameConfig.Podium.ScoreDisplay.X_DELTA)
+ val y = renderer.getRelativePos(
+ GameConfig.Podium.ScoreDisplay.Y_REL,
+ GameConfig.Podium.ScoreDisplay.Y_DELTA)
+ displaceObjectsTo(scoreDigitObj, x, y)
+ bringObjectsToFront(scoreDigitVisibleObj)
+
+ // hide time counter
+ hideObjects(timeDigitObj)
+
+ // make the "your score is" label
+ objectFactory.makeScoreLabel()
+
+ // create the end of game UI and add the "play again" button to it
+ simpleUI.add(objectFactory.makeReturnToMapButton(this, MSG_RETURN_WITH_VALUE))
+
+ if (isTv) {
+ // TODO: tv specific ui layout
+ } else {
+ // hide the score bar
+ scoreBarObj?.hide()
+ scoreBarLabelObj?.hide()
+ pauseButton?.hide()
+ speakerMuteButton?.hide()
+ speakerOnButton?.hide()
+ // TODO: real message
+
+ // create the sign in bar and sign in button
+ if (!signedIn) {
+ signInBarObj = objectFactory.makeSignInBar()
+ signInTextObj = objectFactory.makeSignInText()
+ signInButton = objectFactory.makeSignInButton(this, MSG_SIGN_IN)
+ simpleUI.add(signInButton)
+ }
+ }
+
+ // disable the background music
+ SceneManager.getInstance().soundManager.enableBgm(false)
+
+ // play the game over sfx
+ SceneManager.getInstance().soundManager.playSfx(gameOverSfx)
+ }
+
+ private fun displaceObjectsTo(objs: Array, x: Float, y: Float) {
+ val first = objs[0] ?: throw IllegalStateException()
+ val deltaX = x - first.x
+ val deltaY = y - first.y
+ var i = 0
+ while (i < objs.size) {
+ objs[i]?.displaceBy(deltaX, deltaY)
+ i++
+ }
+ }
+
+ private fun bringObjectsToFront(objs: Array) {
+ var i = 0
+ while (i < objs.size) {
+ objs[i]?.show()
+ objs[i]?.bringToFront()
+ i++
+ }
+ }
+
+ private fun hideObjects(objs: Array) {
+ var i = 0
+ while (i < objs.size) {
+ objs[i]?.hide()
+ i++
+ }
+ }
+
+ private fun updateTime(deltaT: Float) {
+ var seconds = Math.ceil(displayedTime.toDouble()).toInt()
+ seconds = if (seconds < 0) 0 else if (seconds > 99) 99 else seconds
+ digitFactory.setDigits(seconds, timeDigitObj)
+ bringObjectsToFront(timeDigitObj)
+ }
+
+ override fun onScreenResized(width: Int, height: Int) {}
+
+ override fun onPointerDown(pointerId: Int, x: Float, y: Float) {
+ super.onPointerDown(pointerId, x, y)
+ simpleUI.onPointerDown(pointerId, x, y)
+ }
+
+ override fun onPointerUp(pointerId: Int, x: Float, y: Float) {
+ super.onPointerUp(pointerId, x, y)
+ simpleUI.onPointerUp(pointerId, x, y)
+ }
+
+ protected fun startGameScreen() {
+ paused = true
+ SceneManager.getInstance().soundManager.stopSound()
+ if (isTv) {
+ pauseCurtain.show()
+ pauseCurtain.bringToFront()
+
+ bigPlayButton.show()
+ bigPlayButton.bringToFront()
+ } else {
+ pauseButton?.hide()
+
+ pauseCurtain.show()
+ pauseCurtain.bringToFront()
+
+ quitButton?.hide()
+ quitBarLabel?.hide()
+ speakerOnButton?.bringToFront()
+ speakerMuteButton?.bringToFront()
+ speakerMuteButton?.setEnabled(true)
+ speakerOnButton?.setEnabled(true)
+ bigStartGameButton?.show()
+ bigStartGameButton?.bringToFront()
+ }
+
+ if (SceneManager.getInstance().activity != null) {
+ SceneManager.getInstance()
+ .activity
+ .runOnUiThread {
+ ImmersiveModeHelper.setImmersiveStickyWithActionBar(
+ SceneManager.getInstance().activity.window)
+ }
+ }
+ }
+
+ private fun pauseGame() {
+ if (!paused && !gameEnded) {
+ paused = true
+ SceneManager.getInstance().soundManager.stopSound()
+
+ if (isTv) {
+ pauseCurtain.show()
+ pauseCurtain.bringToFront()
+
+ bigPlayButton.show()
+ bigPlayButton.bringToFront()
+ } else {
+ pauseButton?.hide()
+
+ pauseCurtain.show()
+ pauseCurtain.bringToFront()
+
+ bigPlayButton.show()
+ bigPlayButton.bringToFront()
+
+ quitButton?.show()
+ quitBarLabel?.show()
+ quitButton?.bringToFront()
+ quitBarLabel?.bringToFront()
+
+ speakerMuteButton?.setEnabled(false)
+ speakerOnButton?.setEnabled(false)
+ }
+
+ if (SceneManager.getInstance().activity != null) {
+ SceneManager.getInstance()
+ .activity
+ .runOnUiThread {
+ val activity = SceneManager.getInstance().activity
+ if (activity != null) {
+ ImmersiveModeHelper.setImmersiveStickyWithActionBar(
+ activity.window)
+ }
+ }
+ }
+ }
+ }
+
+ private fun muteSpeaker() {
+ if (!gameEnded) {
+ SceneManager.getInstance().soundManager.mute()
+ speakerOnButton?.hide()
+ speakerMuteButton?.show()
+ speakerMuteButton?.bringToFront()
+ }
+ }
+
+ private fun unmuteSpeaker() {
+ if (!gameEnded) {
+ SceneManager.getInstance().soundManager.unmute()
+ speakerMuteButton?.hide()
+ speakerOnButton?.show()
+ speakerOnButton?.bringToFront()
+ }
+ }
+
+ private fun startCountdown() {
+ inStartCountdown = true
+ startCountdownTimeRemaining = GameConfig.Countdown.TIME.toFloat()
+ bigStartGameButton?.hide()
+ digitFactory.setDigit(countdownDigitObj, GameConfig.Countdown.TIME)
+ countdownDigitObj?.show()
+ countdownDigitObj?.bringToFront()
+ }
+
+ protected fun unPauseGame() {
+ if (!paused) {
+ return
+ }
+ paused = false
+ SceneManager.getInstance().soundManager.enableBgm(true)
+ SceneManager.getInstance().soundManager.resumeSound()
+ if (isTv) {
+ pauseCurtain.hide()
+ bigPlayButton.hide()
+ } else {
+ pauseButton?.show()
+ pauseCurtain.hide()
+ quitButton?.hide()
+ quitBarLabel?.hide()
+ bigPlayButton.hide()
+ countdownDigitObj?.hide()
+ speakerMuteButton?.setEnabled(true)
+ speakerOnButton?.setEnabled(true)
+ }
+
+ if (SceneManager.getInstance().activity != null) {
+ SceneManager.getInstance()
+ .activity
+ .runOnUiThread {
+ ImmersiveModeHelper.setImmersiveSticky(
+ SceneManager.getInstance().activity.window)
+ }
+ }
+ }
+
+ override fun onPointerMove(pointerId: Int, x: Float, y: Float, deltaX: Float, deltaY: Float) {
+ simpleUI.onPointerMove(pointerId, x, y, deltaX, deltaY)
+ }
+
+ override fun onWidgetTriggered(message: Int) {
+ when (message) {
+ MSG_RETURN_WITH_VALUE -> returnWithScore()
+ MSG_REPLAY -> SceneManager.getInstance().requestNewScene(makeNewScene())
+ MSG_SIGN_IN -> {
+ (SceneManager.getInstance().activity as SceneActivity).beginUserInitiatedSignIn()
+ }
+ MSG_PAUSE -> pauseGame()
+ MSG_RESUME -> unPauseGame()
+ MSG_QUIT -> quitGame()
+ MSG_SHARE -> share()
+ MSG_MUTE -> muteSpeaker()
+ MSG_UNMUTE -> unmuteSpeaker()
+ MSG_START -> startCountdown()
+ MSG_GO_TO_END_GAME -> goToEndGameWithDelay(GameConfig.EndGame.DELAY)
+ }
+ }
+
+ private fun quitGame() {
+ val act = SceneManager.getInstance().activity
+ if (act != null && act is SceneActivity) {
+ act.postQuitGame()
+ }
+ }
+
+ private fun returnWithScore() {
+ val act = SceneManager.getInstance().activity
+ if (act != null && act is SceneActivity) {
+ act.postReturnWithScore(score)
+ }
+ }
+
+ private fun goToEndGameWithDelay(delay: Int) {
+ val act = SceneManager.getInstance().activity
+ (act as SceneActivity).setGameEndedListener(this)
+ act.postDelayedGoToEndGame(delay)
+ }
+
+ protected fun goToEndGame() {
+ val act = SceneManager.getInstance().activity
+ if (act != null && act is SceneActivity) {
+ act.setGameEndedListener(this)
+ act.postGoToEndGame()
+ }
+ }
+
+ override fun onGameEnded() {
+ gameEnded = true
+ }
+
+ private fun share() {
+ val act = SceneManager.getInstance().activity
+ if (act != null && act is SceneActivity) {
+ act.share()
+ }
+ }
+
+ private fun checkSignInWidgetsNeeded() {
+ if (signedIn) {
+ signInBarObj?.hide()
+ signInTextObj?.hide()
+ signInButton?.hide()
+ }
+ }
+
+ // Caution: Called from the UI thread!
+ fun setSignedIn(signedIn: Boolean) {
+ this.signedIn = signedIn
+ }
+
+ // Caution: Called from the UI thread!
+ fun onBackKeyPressed(): Boolean {
+ // raise a flag and process later (on the game thread)
+ backKeyPending = true
+ return true
+ }
+
+ // Caution: Called from the UI thread!
+ fun onConfirmKeyPressed(): Boolean {
+ // raise a flag and process later (on the game thread)
+ if (confirmKeyPending) {
+ return true
+ }
+
+ confirmKeyPending = true
+ confirmKeyEventTime = System.currentTimeMillis()
+ return true
+ }
+
+ companion object {
+
+ // widget trigger messages
+ private const val MSG_RETURN_WITH_VALUE = 1001
+ private const val MSG_SIGN_IN = 1002
+ private const val MSG_PAUSE = 1003
+ private const val MSG_RESUME = 1004
+ private const val MSG_QUIT = 1005
+ private const val MSG_SHARE = 1006
+ private const val MSG_REPLAY = 1007
+ private const val MSG_MUTE = 1008
+ private const val MSG_UNMUTE = 1009
+ private const val MSG_START = 1010
+ const val MSG_GO_TO_END_GAME = 1011
+ private const val CENTER_KEY_DELAY_MS: Long = 500
+ }
+}
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/DigitObjectFactory.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/DigitObjectFactory.kt
new file mode 100644
index 000000000..bcdc10bc2
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/DigitObjectFactory.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.jetpack
+
+import com.google.android.apps.playgames.simpleengine.Renderer
+import com.google.android.apps.playgames.simpleengine.game.GameObject
+import com.google.android.apps.playgames.simpleengine.game.World
+
+class DigitObjectFactory(private var renderer: Renderer, private var world: World) {
+
+ private var whiteDigitTex = IntArray(10)
+ private var negativeTex = IntArray(1)
+
+ fun requestWhiteTextures(maxDigitWidth: Float) {
+ val res = intArrayOf(
+ com.google.android.apps.playgames.R.drawable.games_digit_0,
+ com.google.android.apps.playgames.R.drawable.games_digit_1,
+ com.google.android.apps.playgames.R.drawable.games_digit_2,
+ com.google.android.apps.playgames.R.drawable.games_digit_3,
+ com.google.android.apps.playgames.R.drawable.games_digit_4,
+ com.google.android.apps.playgames.R.drawable.games_digit_5,
+ com.google.android.apps.playgames.R.drawable.games_digit_6,
+ com.google.android.apps.playgames.R.drawable.games_digit_7,
+ com.google.android.apps.playgames.R.drawable.games_digit_8,
+ com.google.android.apps.playgames.R.drawable.games_digit_9
+ )
+ for (i in 0..9) {
+ whiteDigitTex[i] = renderer.requestImageTex(
+ res[i], "white_digit_$i", Renderer.DIM_WIDTH, maxDigitWidth)
+ }
+ negativeTex[0] = renderer.requestImageTex(
+ com.google.android.apps.playgames.R.drawable.games_digit_negative,
+ "digit_negative",
+ Renderer.DIM_WIDTH,
+ maxDigitWidth)
+ }
+
+ fun makeDigitObject(type: Int, x: Float, y: Float, size: Float): GameObject {
+ return world.newGameObjectWithImage(type, x, y, whiteDigitTex[0], size, size)
+ }
+
+ fun setDigit(digitObject: GameObject?, digit: Int) {
+ var digit = digit
+ digit = if (digit > 9) 9 else if (digit < 0) 0 else digit
+ digitObject?.getSprite(0)?.texIndex = whiteDigitTex[digit]
+ }
+
+ fun makeDigitObjects(
+ count: Int,
+ type: Int,
+ x: Float,
+ y: Float,
+ size: Float,
+ stride: Float,
+ result: Array
+ ) {
+ var x = x
+ var i = 0
+ while (i < count) {
+ result[i] = makeDigitObject(type, x, y, size)
+ x += stride
+ i++
+ }
+ }
+
+ fun setDigits(
+ valueToShow: Int,
+ digitObjects: Array,
+ start: Int = 0,
+ length: Int = digitObjects.size
+ ) {
+ var valueToShow = valueToShow
+ if (valueToShow >= 0) {
+ var i: Int = start + length - 1
+ while (i >= start) {
+ setDigit(digitObjects[i], valueToShow % 10)
+ valueToShow /= 10
+ --i
+ }
+ } else {
+ valueToShow = -valueToShow
+ var i: Int = start + length - 1
+ while (i >= start) {
+ if (i == start) {
+ digitObjects[i]?.getSprite(0)!!.texIndex = negativeTex[0]
+ } else {
+ setDigit(digitObjects[i], valueToShow % 10)
+ valueToShow /= 10
+ }
+ --i
+ }
+ }
+ }
+}
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/GameConfig.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/GameConfig.kt
new file mode 100644
index 000000000..c1dd32d90
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/GameConfig.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.jetpack
+
+import com.google.android.apps.playgames.simpleengine.Renderer
+
+class GameConfig {
+
+ // score popup settings
+ object ScorePopup {
+ const val DIGIT_SIZE = 0.04f
+ const val DIGIT_SPACING = 0.022f
+ const val POPUP_VEL_Y = 0.1f
+ const val POPUP_EXPIRE = 0.8f
+ }
+
+ // score bar settings
+ object ScoreBar {
+ object PauseButton {
+ const val X_REL = Renderer.REL_RIGHT
+ const val X_DELTA = -0.23f
+ const val Y_REL = Renderer.REL_TOP
+ const val Y_DELTA = -0.09f
+ const val WIDTH = 0.2f
+ const val HEIGHT = 0.2f
+ const val SPRITE_WIDTH = 0.15f
+ const val SPRITE_HEIGHT = 0.15f
+ }
+
+ object ScoreBarLabel {
+ const val WIDTH = ScoreBar.WIDTH
+ const val X_REL = Renderer.REL_LEFT
+ const val X_DELTA = 0.58f * WIDTH
+ const val Y_REL = Renderer.REL_TOP
+ const val Y_DELTA = -0.13f
+ const val FONT_SIZE = 20.0f
+ }
+
+ const val WIDTH = 0.42f
+ const val X_REL = Renderer.REL_LEFT
+ const val X_DELTA = 0.6f * WIDTH
+ const val Y_REL = Renderer.REL_TOP
+ const val Y_DELTA = -0.08f
+ const val MIN_DIGITS_VISIBLE = 2
+ }
+
+ object Countdown {
+ const val TIME = 3
+ const val DIGIT_SIZE = PauseScreen.BigPlayButton.WIDTH
+ }
+
+ object EndGame {
+ const val DELAY = 2000
+ }
+
+ // sound display settings
+ object Speaker {
+ const val WIDTH = 0.2f
+ const val HEIGHT = 0.2f
+ const val X_REL = Renderer.REL_RIGHT
+ const val X_DELTA = -.07f
+ const val Y_REL = Renderer.REL_TOP
+ const val Y_DELTA = ScoreBar.PauseButton.Y_DELTA
+ const val SPRITE_WIDTH = 0.15f
+ const val SPRITE_HEIGHT = 0.15f
+ }
+
+ // score display settings
+ object ScoreDisplay {
+
+ const val DIGIT_SIZE = 0.09f
+ const val DIGIT_SPACING = DIGIT_SIZE * 0.5f
+ const val DIGIT_COUNT = 6
+ const val POS_X_REL = Renderer.REL_LEFT
+ const val POS_X_DELTA = 0.04f
+ const val POS_Y_REL = Renderer.REL_TOP
+ const val POS_Y_DELTA = -0.062f
+
+ const val POS_Y_REL_TV = Renderer.REL_TOP
+ const val POS_Y_DELTA_TV = -0.093f
+
+ const val UPDATE_SPEED = 1000.0f
+ }
+
+ // time display settings
+ object TimeDisplay {
+
+ const val ICON_SIZE = 0.06f
+ const val DIGIT_SIZE = ScoreDisplay.DIGIT_SIZE
+ const val DIGIT_SPACING = ScoreDisplay.DIGIT_SPACING
+ const val DIGIT_COUNT = 2
+ const val POS_X_REL = Renderer.REL_LEFT
+ const val POS_X_DELTA = ScoreBar.WIDTH / 2 + ScoreBar.X_DELTA + 0.1f
+ const val POS_Y_REL = Renderer.REL_TOP
+ const val POS_Y_DELTA = ScoreDisplay.POS_Y_DELTA
+ const val POS_Y_REL_TV = Renderer.REL_TOP
+ const val POS_Y_DELTA_TV = ScoreDisplay.POS_Y_DELTA_TV
+ }
+
+ // podium (level end) screen settings
+ object Podium {
+
+ // score label (the static text that says "Score")
+ object ScoreLabel {
+
+ const val X_REL = Renderer.REL_CENTER
+ const val X_DELTA = 0.15f
+ const val Y_REL = Renderer.REL_CENTER
+ const val Y_DELTA = 0.2f
+ const val FONT_SIZE = 25.0f
+ }
+
+ // where do we display the score in the podium screen
+ object ScoreDisplay {
+
+ const val X_REL = Renderer.REL_CENTER
+ const val X_DELTA = 0.07f
+ const val Y_REL = Renderer.REL_CENTER
+ const val Y_DELTA = 0.1f
+ }
+
+ // "play again" button
+ object ReplayButton {
+
+ const val FONT_SIZE = 25.0f
+ const val X_REL = Renderer.REL_CENTER
+ const val X_DELTA = 0.0f
+ const val Y_REL = Renderer.REL_CENTER
+ const val Y_DELTA = -0.13f
+ const val WIDTH = 0.6f
+ const val HEIGHT = 0.12f
+ const val NORMAL_COLOR = -0xd961bd
+ const val HIGHLIGHT_COLOR = -0xd24fb5
+ }
+
+ const val WIDTH = 0.8f
+ const val X_REL = Renderer.REL_CENTER
+ const val X_DELTA = 0.0f
+ const val Y_REL = Renderer.REL_CENTER
+ const val Y_DELTA = 0.1f
+ }
+
+ // Sign in bar
+ object SignInBar {
+
+ const val COLOR = -0x7f000001
+ const val X_REL = Renderer.REL_CENTER
+ const val X_DELTA = 0.0f
+ const val Y_REL = Renderer.REL_BOTTOM
+ const val HEIGHT = 0.2f
+ const val WIDTH = 10.0f
+ const val Y_DELTA = 0.5f * HEIGHT
+ }
+
+ // Sign in button
+ object SignInButton {
+
+ const val WIDTH = 0.4f
+ // (120/402 is the height/width of the image asset)
+ const val HEIGHT = WIDTH * (120.0f / 402.0f)
+ const val X_REL = Renderer.REL_LEFT
+ const val X_DELTA = WIDTH * 0.5f + 0.05f
+ const val Y_REL = Renderer.REL_BOTTOM
+ const val Y_DELTA = 0.1f
+
+ const val TEXT_DELTA_X = 0.05f
+
+ const val FONT_SIZE = 20.0f
+ }
+
+ // Sign in encouragement text
+ object SignInText {
+
+ const val COLOR = -0xda50cf
+ const val X_REL = Renderer.REL_LEFT
+ const val X_DELTA =
+ SignInButton.X_DELTA + SignInButton.WIDTH * 0.5f + 0.05f
+ const val Y_REL = Renderer.REL_BOTTOM
+ const val Y_DELTA = 0.1f
+ const val ANCHOR = Renderer.TEXT_ANCHOR_MIDDLE or Renderer.TEXT_ANCHOR_LEFT
+ const val FONT_SIZE = 20.0f
+ }
+
+ // mute screen settings
+ object PauseScreen {
+
+ object BigPlayButton {
+
+ const val X_REL = Renderer.REL_CENTER
+ const val X_DELTA = 0.02f
+ const val Y_REL = Renderer.REL_CENTER
+ const val Y_DELTA = 0.0f
+ const val WIDTH = 0.4f
+ const val HEIGHT = 0.4f
+ const val SPRITE_WIDTH = 0.4f
+ }
+
+ object QuitBar {
+
+ object QuitBarLabel {
+ const val X_REL = Renderer.REL_CENTER
+ const val X_DELTA = 0.0f
+ const val Y_REL = Renderer.REL_CENTER
+ const val Y_DELTA = -0.35f
+ const val WIDTH = .7f
+ const val FONT_SIZE = 35.0f
+ }
+
+ const val X_REL = Renderer.REL_CENTER
+ const val X_DELTA = 0.0f
+ const val Y_REL = Renderer.REL_CENTER
+ const val Y_DELTA = -0.35f
+ const val WIDTH = .7f
+ const val HEIGHT = WIDTH * 0.33f
+ const val SPRITE_WIDTH = WIDTH
+ }
+
+ const val CURTAIN_COLOR = -0x7f000001
+ }
+
+ companion object {
+
+ // type code for decorative objects (HUD, etc)
+ const val TYPE_DECOR = 9999
+ }
+}
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/GameEndedListener.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/GameEndedListener.kt
new file mode 100644
index 000000000..b88abd5e9
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/GameEndedListener.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.jetpack
+
+interface GameEndedListener {
+
+ val score: Int
+ fun onGameEnded()
+}
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/GameFragment.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/GameFragment.kt
new file mode 100644
index 000000000..da1df0dfc
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/GameFragment.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.jetpack
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import com.google.android.apps.playgames.simpleengine.GameView
+
+class GameFragment : Fragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return GameView(activity).apply {
+ isFocusable = true
+ isFocusableInTouchMode = true
+ keepScreenOn = true
+ }
+ }
+}
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/GameObjectFactory.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/GameObjectFactory.kt
new file mode 100644
index 000000000..dc21e7c3c
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/GameObjectFactory.kt
@@ -0,0 +1,481 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.jetpack
+
+import com.google.android.apps.playgames.simpleengine.Renderer
+import com.google.android.apps.playgames.simpleengine.game.GameObject
+import com.google.android.apps.playgames.simpleengine.game.World
+import com.google.android.apps.playgames.simpleengine.ui.Button
+import com.google.android.apps.playgames.simpleengine.ui.Widget
+import java.util.Arrays
+
+class GameObjectFactory(private var renderer: Renderer, private var world: World) {
+
+ // Textures
+ private var texClock: Int = 0
+ private var texPodium: Int = 0
+ private var playAgainTex: Int = 0
+ private var scoreLabelTex: Int = 0
+ private var signInLabelTex: Int = 0
+ private var signInNormalTex: Int = 0
+ private var signInHighlightTex: Int = 0
+ private var signInTextTex: Int = 0
+ private var scoreBarTex: Int = 0
+ private var scoreBarLabelTex: Int = 0
+ private var pauseIconTex: Int = 0
+ private var pauseIconPressedTex: Int = 0
+ private var speakerMuteIconTex: Int = 0
+ private var speakerOnIconTex: Int = 0
+ private var bigPlayButtonNormalTex: Int = 0
+ private var bigPlayButtonHighlightTex: Int = 0
+ private var quitBarTex: Int = 0
+ private var quitBarPressedTex: Int = 0
+ private var quitBarLabelTex: Int = 0
+ private var inviteBarTex: Int = 0
+ private var inviteBarPressedTex: Int = 0
+
+ private var tmpDigits = arrayOfNulls(5)
+
+ fun makeScorePopup(x: Float, y: Float, score: Int, df: DigitObjectFactory) {
+ val digits: Int = if (score >= 0) {
+ when {
+ score >= 10000 -> 5
+ score >= 1000 -> 4
+ score >= 100 -> 3
+ score >= 10 -> 2
+ else -> 1
+ }
+ } else {
+ when {
+ score <= -10000 -> 6
+ score <= -1000 -> 5
+ score <= -100 -> 4
+ score <= -10 -> 3
+ else -> 2
+ }
+ }
+
+ Arrays.fill(tmpDigits, null)
+ df.makeDigitObjects(
+ digits,
+ GameConfig.TYPE_DECOR,
+ x,
+ y,
+ GameConfig.ScorePopup.DIGIT_SIZE,
+ GameConfig.ScorePopup.DIGIT_SPACING,
+ tmpDigits)
+ df.setDigits(score, tmpDigits, 0, digits)
+
+ var i = 0
+ while (i < digits) {
+ val o = tmpDigits[i]
+ o?.velY = GameConfig.ScorePopup.POPUP_VEL_Y
+ o?.timeToLive = GameConfig.ScorePopup.POPUP_EXPIRE
+ i++
+ }
+ }
+
+ fun requestTextures() {
+ texClock = renderer.requestImageTex(
+ R.drawable.jetpack_clock,
+ "jetpack_clock",
+ Renderer.DIM_WIDTH,
+ GameConfig.TimeDisplay.ICON_SIZE)
+ texPodium = renderer.requestImageTex(
+ R.drawable.jetpack_podium,
+ "jetpack_podium",
+ Renderer.DIM_WIDTH,
+ GameConfig.Podium.WIDTH)
+
+ playAgainTex = renderer.requestTextTex(
+ com.google.android.apps.santatracker.common.R.string.return_to_map,
+ "return_to_map",
+ GameConfig.Podium.ReplayButton.FONT_SIZE)
+ scoreLabelTex = renderer.requestTextTex(
+ com.google.android.apps.santatracker.common.R.string.score,
+ "score",
+ GameConfig.Podium.ScoreLabel.FONT_SIZE)
+
+ signInLabelTex = renderer.requestTextTex(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .common_signin_button_text,
+ "jetpack_sign_in",
+ GameConfig.SignInButton.FONT_SIZE)
+ signInNormalTex = renderer.requestImageTex(
+ R.drawable.jetpack_signin,
+ "jetpack_siginin",
+ Renderer.DIM_WIDTH,
+ GameConfig.SignInButton.WIDTH)
+ signInHighlightTex = renderer.requestImageTex(
+ R.drawable.jetpack_signin_pressed,
+ "jetpack_signin_pressed",
+ Renderer.DIM_WIDTH,
+ GameConfig.SignInButton.WIDTH)
+ signInTextTex = renderer.requestTextTex(
+ com.google.android.apps.santatracker.common.R.string.why_sign_in,
+ "jetpack_why_sign_in",
+ GameConfig.SignInText.FONT_SIZE,
+ GameConfig.SignInText.ANCHOR,
+ GameConfig.SignInText.COLOR)
+ scoreBarTex = renderer.requestImageTex(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .drawable
+ .icon_ribbon_upsidedown_short,
+ "games_scorebar",
+ Renderer.DIM_WIDTH,
+ GameConfig.ScoreBar.WIDTH)
+ scoreBarLabelTex = renderer.requestTextTex(
+ com.google.android.apps.santatracker.common.R.string.score,
+ "score_bar_label",
+ GameConfig.ScoreBar.ScoreBarLabel.FONT_SIZE)
+ pauseIconTex = renderer.requestImageTex(
+ com.google.android.apps.santatracker.common.R.drawable.common_btn_pause,
+ "games_pause",
+ Renderer.DIM_WIDTH,
+ GameConfig.ScoreBar.PauseButton.SPRITE_WIDTH)
+ pauseIconPressedTex = renderer.requestImageTex(
+ com.google.android.apps.santatracker.common.R.drawable.common_btn_pause,
+ "games_pause_pressed",
+ Renderer.DIM_WIDTH,
+ GameConfig.ScoreBar.PauseButton.SPRITE_WIDTH)
+ speakerMuteIconTex = renderer.requestImageTex(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .drawable
+ .common_btn_speaker_off,
+ "speaker_mute",
+ Renderer.DIM_WIDTH,
+ GameConfig.Speaker.SPRITE_WIDTH)
+ speakerOnIconTex = renderer.requestImageTex(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .drawable
+ .common_btn_speaker_on,
+ "speaker_on",
+ Renderer.DIM_WIDTH,
+ GameConfig.Speaker.SPRITE_WIDTH)
+ bigPlayButtonNormalTex = renderer.requestImageTex(
+ com.google.android.apps.santatracker.common.R.drawable.btn_play_yellow,
+ "btn_play_yellow",
+ Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.BigPlayButton.SPRITE_WIDTH)
+ bigPlayButtonHighlightTex = renderer.requestImageTex(
+ com.google.android.apps.santatracker.common.R.drawable.btn_play_yellow,
+ "btn_play_pressed",
+ Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.BigPlayButton.SPRITE_WIDTH)
+ quitBarTex = renderer.requestImageTex(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .drawable
+ .purple_rectangle_button,
+ "quit_button",
+ Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH)
+ quitBarLabelTex = renderer.requestTextTex(
+ com.google.android.apps.santatracker.common.R.string.back_to_village,
+ "quit_bar_label",
+ GameConfig.PauseScreen.QuitBar.QuitBarLabel.FONT_SIZE)
+ quitBarPressedTex = renderer.requestImageTex(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .drawable
+ .purple_rectangle_button,
+ "quit_button_pressed",
+ Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH)
+ inviteBarTex = renderer.requestImageTex(
+ com.google.android.apps.santatracker.common.R.drawable.games_share,
+ "games_share",
+ Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH)
+ inviteBarPressedTex = renderer.requestImageTex(
+ com.google.android.apps.santatracker.common.R.drawable.games_share_pressed,
+ "games_share_pressed",
+ Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH)
+ }
+
+ fun makePodium(): GameObject {
+ val x = renderer.getRelativePos(GameConfig.Podium.X_REL, GameConfig.Podium.X_DELTA)
+ val y = renderer.getRelativePos(GameConfig.Podium.Y_REL, GameConfig.Podium.Y_DELTA)
+ return world.newGameObjectWithImage(
+ GameConfig.TYPE_DECOR, x, y, texPodium, GameConfig.Podium.WIDTH,
+ java.lang.Float.NaN)
+ }
+
+ fun makeScoreBar(): GameObject {
+ val x = renderer.getRelativePos(GameConfig.ScoreBar.X_REL, GameConfig.ScoreBar.X_DELTA)
+ val y = renderer.getRelativePos(GameConfig.ScoreBar.Y_REL, GameConfig.ScoreBar.Y_DELTA)
+ return world.newGameObjectWithImage(
+ GameConfig.TYPE_DECOR, x, y, scoreBarTex, GameConfig.ScoreBar.WIDTH,
+ java.lang.Float.NaN)
+ }
+
+ fun makeScoreBarLabel(): GameObject {
+ val x = renderer.getRelativePos(
+ GameConfig.ScoreBar.ScoreBarLabel.X_REL,
+ GameConfig.ScoreBar.ScoreBarLabel.X_DELTA)
+ val y = renderer.getRelativePos(
+ GameConfig.ScoreBar.ScoreBarLabel.Y_REL,
+ GameConfig.ScoreBar.ScoreBarLabel.Y_DELTA)
+ return world.newGameObjectWithImage(
+ GameConfig.TYPE_DECOR,
+ x,
+ y,
+ scoreBarLabelTex,
+ GameConfig.ScoreBar.ScoreBarLabel.WIDTH,
+ java.lang.Float.NaN)
+ }
+
+ fun makeQuitBarLabel(): GameObject {
+ val x = renderer.getRelativePos(
+ GameConfig.PauseScreen.QuitBar.QuitBarLabel.X_REL,
+ GameConfig.PauseScreen.QuitBar.QuitBarLabel.X_DELTA)
+ val y = renderer.getRelativePos(
+ GameConfig.PauseScreen.QuitBar.QuitBarLabel.Y_REL,
+ GameConfig.PauseScreen.QuitBar.QuitBarLabel.Y_DELTA)
+ return world.newGameObjectWithImage(
+ GameConfig.TYPE_DECOR,
+ x,
+ y,
+ quitBarLabelTex,
+ GameConfig.PauseScreen.QuitBar.QuitBarLabel.WIDTH,
+ java.lang.Float.NaN)
+ }
+
+ fun makeScoreLabel(): GameObject {
+ // create the "score" static label
+ val x = renderer.getRelativePos(
+ GameConfig.Podium.ScoreLabel.X_REL, GameConfig.Podium.ScoreLabel.X_DELTA)
+ val y = renderer.getRelativePos(
+ GameConfig.Podium.ScoreLabel.Y_REL, GameConfig.Podium.ScoreLabel.Y_DELTA)
+ return world.newGameObjectWithImage(
+ GameConfig.TYPE_DECOR, x, y, scoreLabelTex, java.lang.Float.NaN,
+ java.lang.Float.NaN)
+ }
+
+ fun makeReturnToMapButton(listener: Widget.WidgetTriggerListener, message: Int): Button {
+ val x = renderer.getRelativePos(
+ GameConfig.Podium.ReplayButton.X_REL,
+ GameConfig.Podium.ReplayButton.X_DELTA)
+ val y = renderer.getRelativePos(
+ GameConfig.Podium.ReplayButton.Y_REL,
+ GameConfig.Podium.ReplayButton.Y_DELTA)
+ val returnToMapButton = Button(
+ renderer,
+ x,
+ y,
+ GameConfig.Podium.ReplayButton.WIDTH,
+ GameConfig.Podium.ReplayButton.HEIGHT)
+ returnToMapButton.addFlatBackground(
+ GameConfig.Podium.ReplayButton.NORMAL_COLOR,
+ GameConfig.Podium.ReplayButton.HIGHLIGHT_COLOR)
+ returnToMapButton.addTex(playAgainTex)
+ returnToMapButton.setClickListener(listener, message)
+ return returnToMapButton
+ }
+
+ fun makeSignInBar(): GameObject {
+ val x = renderer.getRelativePos(GameConfig.SignInBar.X_REL, GameConfig.SignInBar.X_DELTA)
+ val y = renderer.getRelativePos(GameConfig.SignInBar.Y_REL, GameConfig.SignInBar.Y_DELTA)
+ return world.newGameObjectWithColor(
+ GameConfig.TYPE_DECOR,
+ x,
+ y,
+ GameConfig.SignInBar.COLOR,
+ GameConfig.SignInBar.WIDTH,
+ GameConfig.SignInBar.HEIGHT)
+ }
+
+ fun makeSignInText(): GameObject {
+ val x = renderer.getRelativePos(
+ GameConfig.SignInText.X_REL, GameConfig.SignInText.X_DELTA)
+ val y = renderer.getRelativePos(
+ GameConfig.SignInText.Y_REL, GameConfig.SignInText.Y_DELTA)
+ return world.newGameObjectWithImage(
+ GameConfig.TYPE_DECOR, x, y, signInTextTex, java.lang.Float.NaN,
+ java.lang.Float.NaN)
+ }
+
+ fun makeSignInButton(listener: Widget.WidgetTriggerListener, message: Int): Button {
+ val x = renderer.getRelativePos(
+ GameConfig.SignInButton.X_REL, GameConfig.SignInButton.X_DELTA)
+ val y = renderer.getRelativePos(
+ GameConfig.SignInButton.Y_REL, GameConfig.SignInButton.Y_DELTA)
+ val signInButton = Button(
+ renderer,
+ x,
+ y,
+ GameConfig.SignInButton.WIDTH,
+ GameConfig.SignInButton.HEIGHT)
+ signInButton.addNormalTex(signInNormalTex)
+ signInButton.addHighlightTex(signInHighlightTex)
+ signInButton.addTex(
+ signInLabelTex, GameConfig.SignInButton.TEXT_DELTA_X, 0.0f, java.lang.Float.NaN,
+ java.lang.Float.NaN)
+ signInButton.setClickListener(listener, message)
+ return signInButton
+ }
+
+ private fun makeSpeakerOnOrMuteButton(
+ isMute: Boolean,
+ listener: Widget.WidgetTriggerListener,
+ message: Int
+ ): Button {
+ val x = renderer.getRelativePos(GameConfig.Speaker.X_REL, GameConfig.Speaker.X_DELTA)
+ val y = renderer.getRelativePos(GameConfig.Speaker.Y_REL, GameConfig.Speaker.Y_DELTA)
+ val button = Button(renderer, x, y, GameConfig.Speaker.WIDTH, GameConfig.Speaker.HEIGHT)
+ button.addNormalTex(
+ if (isMute) speakerMuteIconTex else speakerOnIconTex,
+ 0.0f,
+ 0.0f,
+ GameConfig.Speaker.SPRITE_WIDTH,
+ GameConfig.Speaker.SPRITE_HEIGHT)
+ button.setClickListener(listener, message)
+ return button
+ }
+
+ fun makePauseButton(listener: Widget.WidgetTriggerListener, message: Int): Button {
+ val x = renderer.getRelativePos(
+ GameConfig.ScoreBar.PauseButton.X_REL,
+ GameConfig.ScoreBar.PauseButton.X_DELTA)
+ val y = renderer.getRelativePos(
+ GameConfig.ScoreBar.PauseButton.Y_REL,
+ GameConfig.ScoreBar.PauseButton.Y_DELTA)
+ val button = Button(
+ renderer,
+ x,
+ y,
+ GameConfig.ScoreBar.PauseButton.WIDTH,
+ GameConfig.ScoreBar.PauseButton.HEIGHT)
+ button.addNormalTex(
+ pauseIconTex,
+ 0.0f,
+ 0.0f,
+ GameConfig.ScoreBar.PauseButton.SPRITE_WIDTH,
+ GameConfig.ScoreBar.PauseButton.SPRITE_HEIGHT)
+ button.addHighlightTex(
+ pauseIconPressedTex,
+ 0.0f,
+ 0.0f,
+ GameConfig.ScoreBar.PauseButton.SPRITE_WIDTH,
+ GameConfig.ScoreBar.PauseButton.SPRITE_HEIGHT)
+ button.setClickListener(listener, message)
+ return button
+ }
+
+ fun makeSpeakerOnButton(listener: Widget.WidgetTriggerListener, message: Int): Button {
+ return makeSpeakerOnOrMuteButton(false, listener, message)
+ }
+
+ fun makeSpeakerMuteButton(listener: Widget.WidgetTriggerListener, message: Int): Button {
+ return makeSpeakerOnOrMuteButton(true, listener, message)
+ }
+
+ fun makePauseCurtain(): GameObject {
+ val o = world.newGameObject(GameConfig.TYPE_DECOR, 0.0f, 0.0f)
+ val sp = o.getSprite(o.addSprite())
+ sp!!.width = renderer.width + 0.1f // safety margin
+ sp.height = renderer.height + 0.1f // safety margin
+ sp.color = GameConfig.PauseScreen.CURTAIN_COLOR
+ sp.tintFactor = 0.0f
+ return o
+ }
+
+ fun makeBigPlayButton(listener: Widget.WidgetTriggerListener, message: Int): Button {
+ val x = renderer.getRelativePos(
+ GameConfig.PauseScreen.BigPlayButton.X_REL,
+ GameConfig.PauseScreen.BigPlayButton.X_DELTA)
+ val y = renderer.getRelativePos(
+ GameConfig.PauseScreen.BigPlayButton.Y_REL,
+ GameConfig.PauseScreen.BigPlayButton.Y_DELTA)
+ val button = Button(
+ renderer,
+ x,
+ y,
+ GameConfig.PauseScreen.BigPlayButton.WIDTH,
+ GameConfig.PauseScreen.BigPlayButton.HEIGHT)
+ button.addNormalTex(
+ bigPlayButtonNormalTex,
+ 0.0f,
+ 0.0f,
+ GameConfig.PauseScreen.BigPlayButton.SPRITE_WIDTH,
+ java.lang.Float.NaN)
+ button.addHighlightTex(
+ bigPlayButtonHighlightTex,
+ 0.0f,
+ 0.0f,
+ GameConfig.PauseScreen.BigPlayButton.SPRITE_WIDTH,
+ java.lang.Float.NaN)
+ button.setClickListener(listener, message)
+ return button
+ }
+
+ fun makeQuitButton(listener: Widget.WidgetTriggerListener, message: Int): Button {
+ val x = renderer.getRelativePos(
+ GameConfig.PauseScreen.QuitBar.X_REL,
+ GameConfig.PauseScreen.QuitBar.X_DELTA)
+ val y = renderer.getRelativePos(
+ GameConfig.PauseScreen.QuitBar.Y_REL,
+ GameConfig.PauseScreen.QuitBar.Y_DELTA)
+ val button = Button(
+ renderer,
+ x,
+ y,
+ GameConfig.PauseScreen.QuitBar.WIDTH,
+ GameConfig.PauseScreen.QuitBar.HEIGHT)
+ button.addNormalTex(
+ quitBarTex, 0.0f, 0.0f, GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH,
+ java.lang.Float.NaN)
+ button.addHighlightTex(
+ quitBarPressedTex,
+ 0.0f,
+ 0.0f,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH,
+ java.lang.Float.NaN)
+ button.setClickListener(listener, message)
+ return button
+ }
+}
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackActivity.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackActivity.kt
new file mode 100644
index 000000000..5214d0d30
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackActivity.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.jetpack
+
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.os.Bundle
+import com.google.android.apps.playgames.simpleengine.Scene
+import com.google.android.apps.playgames.simpleengine.SceneManager
+import com.google.android.apps.santatracker.util.MeasurementManager
+import com.google.firebase.analytics.FirebaseAnalytics
+
+class JetpackActivity : SceneActivity(), SensorEventListener {
+ private lateinit var firebaseAnalytics: FirebaseAnalytics
+ private lateinit var sensorManager: SensorManager
+
+ override val gameScene: Scene
+ get() = JetpackScene()
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // App Measurement
+ firebaseAnalytics = FirebaseAnalytics.getInstance(this)
+ MeasurementManager.recordScreenView(
+ firebaseAnalytics,
+ getString(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .analytics_screen_jetpack))
+
+ var presentIsLarge = false
+ if (intent != null && intent.extras != null) {
+ presentIsLarge = intent
+ .extras!!
+ .getBoolean(
+ getString(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .extra_large_present_key),
+ false)
+ }
+
+ SceneManager.getInstance().largePresentMode = presentIsLarge
+
+ sensorManager = getSystemService(android.app.Activity.SENSOR_SERVICE) as SensorManager
+ }
+
+ override fun getLayoutId(): Int {
+ return R.layout.activity_jetpack
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
+ if (sensor != null) {
+ sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST)
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ sensorManager.unregisterListener(this)
+ }
+
+ override fun onBackPressed() {
+ var handled = false
+ val scene = SceneManager.getInstance().currentScene
+ if (scene is JetpackScene) {
+ handled = scene.onBackKeyPressed()
+ }
+ if (!handled) {
+ super.onBackPressed()
+ }
+ }
+
+ override fun onSensorChanged(event: SensorEvent) {
+ SceneManager.getInstance().onSensorChanged(event)
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) = Unit
+
+ override fun getGameId(): String {
+ return getString(com.google.android.apps.playgames.R.string.jetpack_game_id)
+ }
+
+ override fun getGameTitle(): String {
+ return getString(com.google.android.apps.santatracker.common.R.string.elf_jetpack)
+ }
+
+ companion object {
+ internal const val JETPACK_SCORE = "jetpack_score"
+ }
+}
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackConfig.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackConfig.kt
new file mode 100644
index 000000000..a38b98823
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackConfig.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.jetpack
+
+object JetpackConfig {
+
+ // background music resource id
+ const val BGM_RES_ID = com.google.android.apps.santatracker.R.raw.santatracker_musicloop
+
+ // leaderboard
+ const val LEADERBOARD = R.string.leaderboard_jetpack_high_scores
+
+ // player settings
+ object Player {
+ const val WIDTH = 0.15f
+ const val INJURED_WIDTH = 0.26f
+ const val COLLIDER_WIDTH = 0.15f
+ const val COLLIDER_HEIGHT = 0.20f
+ const val MAX_SPEED = 20.0f
+
+ // how large is the "no fly zone" near the edges of the screen, where
+ // the player can't go
+ const val HORIZ_MOVEMENT_MARGIN = COLLIDER_WIDTH / 2
+ const val VERT_MOVEMENT_MARGIN = COLLIDER_HEIGHT / 2 + 0.08f
+
+ object SpriteAngle {
+ // the sprite angle is proportional to how far the player is from the target position
+ const val ANGLE_CONST = 5000.0f
+ // per 1.0 units of distance from target
+ const val MAX_ANGLE = 22.0f
+ const val MAX_CHANGE_RATE = 600.0f
+ const val FILTER_SAMPLES = 2
+ }
+
+ // jetpack fire animation
+ object Fire {
+ const val WIDTH = 0.4f * Player.WIDTH
+ const val ANIM_PERIOD = 0.5f
+ const val ANIM_AMPLITUDE = 0.05f
+ }
+ }
+
+ // input settings
+ object Input {
+ const val SENSOR_SENSITIVITY = 0.002f
+ const val KEY_SENSIVITY = 0.05f
+ const val X_ZERO_BUFFER_ZONE = 0.1f
+
+ object Sensor {
+ fun transformX(x: Float): Float {
+ var sensorX = x
+ sensorX = Math.max(Math.min(6f, sensorX), -6f)
+ when {
+ Math.abs(sensorX) < X_ZERO_BUFFER_ZONE -> sensorX = 0f
+ sensorX > 0 -> sensorX -= X_ZERO_BUFFER_ZONE
+ sensorX < 0 -> sensorX += X_ZERO_BUFFER_ZONE
+ }
+ return sensorX * JetpackConfig.Input.SENSOR_SENSITIVITY
+ }
+ }
+ }
+
+ // item settings
+ object Items {
+ const val CANDY_WIDTH = 0.05f
+ const val CANDY_COLLIDER_HEIGHT = CANDY_WIDTH * 2
+ const val CANDY_COLLIDER_WIDTH = 0.1f
+ const val PRESENT_WIDTH = 0.12f
+ const val PRESENT_COLLIDER_WIDTH = 0.10f
+ const val PRESENT_COLLIDER_HEIGHT = PRESENT_COLLIDER_WIDTH * 2
+ const val SMALL_WIDTH = 0.05f
+ const val SMALL_COLLIDER_WIDTH = 0.05f
+ const val SMALL_COLLIDER_HEIGHT = SMALL_COLLIDER_WIDTH * 2
+
+ // item spawn settings
+ const val SPAWN_INTERVAL = 0.4f
+ const val SPAWN_Y = 0.8f
+ const val FALL_SPEED_MIN = 0.2f
+ const val FALL_SPEED_MAX = 0.35f
+ const val FALL_SPEED_LEVEL_MULT = 1.05f
+ const val DELETE_Y = -0.8f
+
+ // what's the initial value for the small items?
+ const val BASE_VALUE = 1
+
+ // index of the "base value" variable of an item
+ const val IVAR_BASE_VALUE = 0
+
+ // index of the "item type" integer variable of an item
+ const val IVAR_TYPE = 1
+
+ // candy rotational speed
+ const val CANDY_ROTATE_SPEED = 180.0f
+
+ // maximum interval between two item collections for them to be
+ // considered a combo
+ const val COMBO_INTERVAL = 0.25f
+ }
+
+ object ComboPopup {
+ const val SIZE = 0.1f
+ const val VEL_Y = GameConfig.ScorePopup.POPUP_VEL_Y * 0.3f
+ }
+
+ object Clouds {
+ const val COUNT = 6
+ const val WIDTH = 0.2f
+ const val SPAWN_Y = 0.8f
+ const val SPEED_MIN = 0.3f
+ const val SPEED_MAX = 0.5f
+ const val DELETE_Y = -0.8f
+ }
+
+ object Time {
+ // how much time the player has at the beginning
+ const val INITIAL = 10.0f
+
+ // maximum remaining time player can have
+ const val MAX = INITIAL * 3
+
+ // how many seconds are recovered by picking up an item, at the beginning of the game
+ const val RECOVERED_BY_ITEM = 2.0f
+
+ // by how many seconds the time reward decreases per level gained
+ const val RECOVERED_DECREASE_PER_LEVEL = 0.5f
+
+ // the minimum # of seconds recovered by catching a present
+ const val RECOVERED_MIN = 1.0f
+
+ // the # of seconds that hitting coal reduces the time by
+ const val COAL_TIME_PENALTY = 5.0f
+
+ // the # of seconds that a player looks injured after getting hit by a bad item
+ const val HIT_TIME = 0.5f
+
+ // the # of millis that device vibrates - SMALL
+ const val VIBRATE_SMALL: Long = 40
+ }
+
+ object Progression {
+ // how many items must be collected to go up a level?
+ const val ITEMS_PER_LEVEL = 10
+ // by how much the score multiplier increases when we go up a level?
+ const val SCORE_LEVEL_MULT = 1.5f
+ }
+
+ // Achievements
+ object Achievements {
+
+ // combo-based achievements
+ val COMBO_ACHS = intArrayOf(R.string.achievement_jetpack_2_combo,
+ R.string.achievement_jetpack_3_combo, R.string.achievement_jetpack_4_combo)
+
+ // score-based
+ val SCORE_ACHS = intArrayOf(R.string.achievement_jetpack_beginner_score_500,
+ R.string.achievement_jetpack_intermediate_score_1000,
+ R.string.achievement_jetpack_pro_score_5000,
+ R.string.achievement_jetpack_advanced_score_10000,
+ R.string.achievement_jetpack_expert_score_50000)
+
+ // score necessary for the corresponding SCORE_ACHS achievement
+ val SCORE_FOR_ACH = intArrayOf(500, 1000, 5000, 10000, 50000)
+
+ // flight time achievements (one increment = one second)
+ val TOTAL_TIME_ACHS = intArrayOf(R.string.achievement_jetpack_flight_time_15,
+ R.string.achievement_jetpack_flight_time_30,
+ R.string.achievement_jetpack_flight_time_60)
+
+ // total presents achievements
+ val TOTAL_PRESENTS_ACHS = intArrayOf(R.string.achievement_jetpack_a_dozen_presents,
+ R.string.achievement_jetpack_a_dozen_dozen_presents)
+
+ // total candy achievements
+ val TOTAL_CANDY_ACHS = intArrayOf(R.string.achievement_jetpack_candy_for_one_month_30,
+ R.string.achievement_jetpack_candy_for_one_year_365)
+
+ // interval between consecutive sending of incremental achievements
+ val INC_ACH_SEND_INTERVAL = 15.0f
+ }
+}
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackObjectFactory.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackObjectFactory.kt
new file mode 100644
index 000000000..f2d59167a
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackObjectFactory.kt
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.jetpack
+
+import com.google.android.apps.playgames.simpleengine.Renderer
+import com.google.android.apps.playgames.simpleengine.game.GameObject
+import com.google.android.apps.playgames.simpleengine.game.World
+import java.util.GregorianCalendar
+import java.util.Random
+
+class JetpackObjectFactory internal constructor(
+ private var renderer: Renderer,
+ private var world: World
+) {
+ private val random = Random()
+
+ // Textures
+ private var texPlayer: Int = 0
+ private var texItemCandy: IntArray? = null
+ private var texItemCoal: IntArray? = null
+ private var texItemPresent: IntArray? = null
+ private var texCloud: Int = 0
+ private var comboTex: IntArray? = null
+ private var texFire: Int = 0
+ private var texPlayerHitOverlay: Int = 0
+ private var texPlayerHit: Int = 0
+ private var texBackground: Int = 0
+
+ private val backgroundFromCurrentTime: Int
+ get() {
+ val calendar = GregorianCalendar()
+ val hour = calendar.get(GregorianCalendar.HOUR_OF_DAY)
+ return if (hour in 6..20) {
+ com.google.android.apps.santatracker.common.R.drawable.jetpack_background_day
+ } else {
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .drawable
+ .jetpack_background_evening
+ }
+ }
+
+ internal fun makePlayer(): GameObject {
+ val p = world.newGameObjectWithImage(
+ JetpackScene.TYPE_PLAYER,
+ 0.0f,
+ renderer.bottom + JetpackConfig.Player.VERT_MOVEMENT_MARGIN,
+ texPlayer,
+ JetpackConfig.Player.WIDTH,
+ java.lang.Float.NaN)
+ p.setBoxCollider(JetpackConfig.Player.COLLIDER_WIDTH, JetpackConfig.Player.COLLIDER_HEIGHT)
+
+ p.getSprite(p.addSprite()).apply {
+ texIndex = texFire
+ width = JetpackConfig.Player.Fire.WIDTH
+ height = java.lang.Float.NaN
+ tintFactor = 0.0f
+ }
+ return p
+ }
+
+ internal fun makePlayerHit(p: GameObject) {
+ p.deleteSprites()
+
+ p.getSprite(p.addSprite()).apply {
+ texIndex = texPlayerHit
+ width = JetpackConfig.Player.INJURED_WIDTH
+ height = java.lang.Float.NaN
+ tintFactor = 0.0f
+ }
+
+ p.getSprite(p.addSprite()).apply {
+ texIndex = texFire
+ width = JetpackConfig.Player.Fire.WIDTH
+ height = java.lang.Float.NaN
+ tintFactor = 0.0f
+ }
+ }
+
+ internal fun recoverPlayerHit(p: GameObject) {
+ p.deleteSprites()
+
+ p.getSprite(p.addSprite()).apply {
+ texIndex = texPlayer
+ width = JetpackConfig.Player.WIDTH
+ height = java.lang.Float.NaN
+ tintFactor = 0.0f
+ }
+
+ p.getSprite(p.addSprite()).apply {
+ texIndex = texFire
+ width = JetpackConfig.Player.Fire.WIDTH
+ height = java.lang.Float.NaN
+ tintFactor = 0.0f
+ }
+ }
+
+ private fun getItemTypeGivenProbs(coal: Float, candy: Float, presents: Float): Int {
+ val randFloat = random.nextFloat()
+ return when {
+ randFloat < coal -> ITEM_COAL
+ randFloat < coal + candy -> ITEM_CANDY
+ else -> ITEM_PRESENT
+ }
+ }
+
+ private fun getItemType(bigPresentMode: Boolean, currentScore: Float): Int {
+ return if (bigPresentMode) {
+ when {
+ currentScore < 10 -> getItemTypeGivenProbs(0f, 0f, 1.00f)
+ currentScore < 20 -> getItemTypeGivenProbs(.1f, .4f, .5f)
+ currentScore < 30 -> getItemTypeGivenProbs(.15f, .45f, .40f)
+ currentScore < 40 -> getItemTypeGivenProbs(.2f, .45f, .35f)
+ currentScore < 50 -> getItemTypeGivenProbs(.25f, .45f, .3f)
+ else -> getItemTypeGivenProbs(.3f, .4f, .30f)
+ }
+ } else {
+ when {
+ currentScore < 10 -> getItemTypeGivenProbs(0f, 0.25f, 0.75f)
+ currentScore < 20 -> getItemTypeGivenProbs(.1f, .55f, .35f)
+ currentScore < 30 -> getItemTypeGivenProbs(.15f, .60f, .25f)
+ currentScore < 40 -> getItemTypeGivenProbs(.2f, .60f, .20f)
+ currentScore < 50 -> getItemTypeGivenProbs(.25f, .55f, .2f)
+ else -> getItemTypeGivenProbs(.3f, .50f, .2f)
+ }
+ }
+ }
+
+ fun makeBackground(): GameObject {
+ return world.newGameObjectWithImage(
+ GameConfig.TYPE_DECOR,
+ 0.0f,
+ 0.0f,
+ texBackground,
+ renderer.width + 0.02f,
+ renderer.height + 0.02f)
+ }
+
+ internal fun makeRandomItem(
+ fallSpeedMultiplier: Float,
+ bigPresentMode: Boolean,
+ currentScore: Float
+ ): GameObject {
+ val minX = renderer.left + 2 * JetpackConfig.Items.PRESENT_WIDTH
+ val maxX = renderer.right - 2 * JetpackConfig.Items.PRESENT_WIDTH
+ val x = minX + random.nextFloat() * (maxX - minX)
+ // 0 is candy, 1 is coal, 2 is present
+ val itemType = getItemType(bigPresentMode, currentScore)
+ val itemSubtype = random.nextInt(4) // one of the 4 subtypes
+
+ val tex: Int
+ val width: Float
+ val colliderWidth: Float
+ val colliderHeight: Float
+ var p: GameObject?
+ when (itemType) {
+ ITEM_CANDY -> {
+ val texItemCandySnapshot = texItemCandy ?: throw IllegalStateException()
+ tex = texItemCandySnapshot[itemSubtype]
+ width = JetpackConfig.Items.CANDY_WIDTH
+ colliderWidth = JetpackConfig.Items.CANDY_COLLIDER_WIDTH
+ colliderHeight = JetpackConfig.Items.CANDY_COLLIDER_HEIGHT
+ p = world.newGameObjectWithImage(
+ JetpackScene.TYPE_GOOD_ITEM,
+ x,
+ JetpackConfig.Items.SPAWN_Y,
+ tex,
+ width,
+ java.lang.Float.NaN).apply {
+ ivar[JetpackConfig.Items.IVAR_BASE_VALUE] = JetpackConfig.Items.BASE_VALUE
+ }
+ }
+ ITEM_COAL -> {
+ val texItemCoalSnapshot = texItemCoal ?: throw IllegalStateException()
+ tex = texItemCoalSnapshot[0]
+ width = JetpackConfig.Items.SMALL_WIDTH
+ colliderWidth = JetpackConfig.Items.SMALL_COLLIDER_WIDTH
+ colliderHeight = JetpackConfig.Items.SMALL_COLLIDER_HEIGHT
+ p = world.newGameObjectWithImage(
+ JetpackScene.TYPE_BAD_ITEM,
+ x,
+ JetpackConfig.Items.SPAWN_Y,
+ tex,
+ width,
+ java.lang.Float.NaN).apply {
+ ivar[JetpackConfig.Items.IVAR_BASE_VALUE] = -JetpackConfig.Items.BASE_VALUE
+ }
+ }
+ ITEM_PRESENT -> {
+ val texItemPresentSnapshot = texItemPresent ?: throw IllegalStateException()
+ tex = texItemPresentSnapshot[itemSubtype]
+ width = JetpackConfig.Items.PRESENT_WIDTH
+ colliderWidth = JetpackConfig.Items.PRESENT_COLLIDER_WIDTH
+ colliderHeight = JetpackConfig.Items.PRESENT_COLLIDER_HEIGHT
+ p = world.newGameObjectWithImage(
+ JetpackScene.TYPE_GOOD_ITEM,
+ x,
+ JetpackConfig.Items.SPAWN_Y,
+ tex,
+ width,
+ java.lang.Float.NaN).apply {
+ ivar[JetpackConfig.Items.IVAR_BASE_VALUE] = JetpackConfig.Items.BASE_VALUE * 2
+ }
+ }
+ else -> {
+ val texItemPresentSnapshot = texItemPresent ?: throw IllegalStateException()
+ tex = texItemPresentSnapshot[itemSubtype]
+ width = JetpackConfig.Items.PRESENT_WIDTH
+ colliderWidth = JetpackConfig.Items.PRESENT_COLLIDER_WIDTH
+ colliderHeight = JetpackConfig.Items.PRESENT_COLLIDER_HEIGHT
+ p = world.newGameObjectWithImage(JetpackScene.TYPE_GOOD_ITEM, x,
+ JetpackConfig.Items.SPAWN_Y, tex, width, java.lang.Float.NaN).apply {
+ ivar[JetpackConfig.Items.IVAR_BASE_VALUE] = JetpackConfig.Items.BASE_VALUE * 2
+ }
+ }
+ }
+
+ p.velY =
+ -(JetpackConfig.Items.FALL_SPEED_MIN + random.nextFloat() * (JetpackConfig.Items.FALL_SPEED_MAX - JetpackConfig.Items.FALL_SPEED_MIN))
+ p.velY *= fallSpeedMultiplier
+ p.setBoxCollider(colliderWidth, colliderHeight)
+ p.ivar[JetpackConfig.Items.IVAR_TYPE] = itemType
+ p.bringToFront()
+ return p
+ }
+
+ internal fun makeCloud(): GameObject {
+ return world.newGameObjectWithImage(
+ GameConfig.TYPE_DECOR,
+ 0.0f,
+ 0.0f,
+ texCloud,
+ JetpackConfig.Clouds.WIDTH,
+ java.lang.Float.NaN)
+ }
+
+ internal fun makeComboPopup(comboItems: Int, x: Float, y: Float): GameObject {
+ val comboTex = comboTex ?: throw IllegalStateException()
+ var i = comboItems - 2
+ i = if (i < 0) 0 else if (i >= comboTex.size) comboTex.size - 1 else i
+ val o = world.newGameObjectWithImage(
+ GameConfig.TYPE_DECOR,
+ x,
+ y,
+ comboTex[i],
+ JetpackConfig.ComboPopup.SIZE,
+ java.lang.Float.NaN)
+ o.velY = JetpackConfig.ComboPopup.VEL_Y
+ o.timeToLive = GameConfig.ScorePopup.POPUP_EXPIRE
+ return o
+ }
+
+ fun requestTextures() {
+ // request player texture
+ texPlayer = renderer.requestImageTex(
+ R.drawable.jetpack_player,
+ "jetpack_player",
+ Renderer.DIM_WIDTH,
+ JetpackConfig.Player.WIDTH)
+
+ // request item textures
+ texItemCandy = IntArray(4)
+ val texItemCandySnapshot = texItemCandy ?: throw IllegalStateException()
+ var i = 0
+ for (resId in intArrayOf(R.drawable.jetpack_candy1, R.drawable.jetpack_candy2,
+ R.drawable.jetpack_candy3, R.drawable.jetpack_candy4)) {
+ texItemCandySnapshot[i++] = renderer.requestImageTex(
+ resId, "candy", Renderer.DIM_WIDTH, JetpackConfig.Items.CANDY_WIDTH)
+ }
+ texItemPresent = IntArray(4)
+ val texItemPresentSnapshot = texItemPresent ?: throw IllegalStateException()
+ i = 0
+ for (resId in intArrayOf(R.drawable.jetpack_present1, R.drawable.jetpack_present2,
+ R.drawable.jetpack_present3, R.drawable.jetpack_present4)) {
+ texItemPresentSnapshot[i++] = renderer.requestImageTex(
+ resId,
+ "present",
+ Renderer.DIM_WIDTH,
+ JetpackConfig.Items.PRESENT_WIDTH)
+ }
+
+ i = 0
+ val coalDrawables = intArrayOf(R.drawable.jetpack_coal)
+ texItemCoal = IntArray(coalDrawables.size)
+ val texItemCoalSnapshot = texItemCoal ?: throw IllegalStateException()
+ for (resId in coalDrawables) {
+ texItemCoalSnapshot[i++] = renderer.requestImageTex(
+ resId, "small", Renderer.DIM_WIDTH, JetpackConfig.Items.SMALL_WIDTH)
+ }
+
+ texCloud = renderer.requestImageTex(
+ R.drawable.jetpack_cloud,
+ "jetpack_cloud",
+ Renderer.DIM_WIDTH,
+ JetpackConfig.Clouds.WIDTH)
+
+ texBackground = renderer.requestImageTex(
+ backgroundFromCurrentTime,
+ "jetpack_background",
+ Renderer.DIM_WIDTH,
+ renderer.width)
+
+ comboTex = IntArray(3).apply {
+ this[0] = renderer.requestImageTex(
+ R.drawable.jetpack_combo_2x,
+ "jetpack_combo_2x",
+ Renderer.DIM_WIDTH,
+ JetpackConfig.ComboPopup.SIZE)
+ this[1] = renderer.requestImageTex(
+ R.drawable.jetpack_combo_3x,
+ "jetpack_combo_3x",
+ Renderer.DIM_WIDTH,
+ JetpackConfig.ComboPopup.SIZE)
+ this[2] = renderer.requestImageTex(
+ R.drawable.jetpack_combo_4x,
+ "jetpack_combo_4x",
+ Renderer.DIM_WIDTH,
+ JetpackConfig.ComboPopup.SIZE)
+ }
+
+ texFire = renderer.requestImageTex(
+ R.drawable.jetpack_fire,
+ "jetpack_fire",
+ Renderer.DIM_WIDTH,
+ JetpackConfig.Player.Fire.WIDTH)
+ texPlayerHit = renderer.requestImageTex(
+ R.drawable.jetpack_player_hit,
+ "jetpack_player_hit",
+ Renderer.DIM_WIDTH,
+ JetpackConfig.Player.WIDTH)
+ texPlayerHitOverlay = renderer.requestImageTex(
+ R.drawable.jetpack_player_hit_overlay,
+ "jetpack_player_hit_overlay",
+ Renderer.DIM_WIDTH,
+ JetpackConfig.Player.WIDTH)
+ }
+
+ companion object {
+
+ // item subtypes
+ const val ITEM_PRESENT = 0
+ const val ITEM_CANDY = 1
+ const val ITEM_COAL = 2
+ }
+}
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackScene.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackScene.kt
new file mode 100644
index 000000000..c88ef2a1d
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/JetpackScene.kt
@@ -0,0 +1,613 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.jetpack
+
+import android.view.KeyEvent
+import com.google.android.apps.playgames.simpleengine.Logger
+import com.google.android.apps.playgames.simpleengine.SceneManager
+import com.google.android.apps.playgames.simpleengine.SmoothValue
+import com.google.android.apps.playgames.simpleengine.game.GameObject
+import java.util.ArrayList
+import java.util.HashSet
+
+class JetpackScene : BaseScene() {
+
+ // player
+ private lateinit var playerObj: GameObject
+
+ // background
+ private lateinit var background: GameObject
+
+ // our object factory
+ private lateinit var factory: JetpackObjectFactory
+
+ // current difficulty level
+ private var level = 1
+
+ // player is currently injured
+ private var playerHit = false
+
+ // time remaining of player's injury
+ private var playerHitTime = JetpackConfig.Time.HIT_TIME
+
+ // total items collected
+ private var itemsCollected = 0
+
+ // item fall speed multipler (increases with level)
+ private var fallMult = 3.0f
+
+ // score multipler (increases with level)
+ private var scoreMult = 1.0f
+
+ private var spriteAngle = SmoothValue(
+ 0.0f,
+ JetpackConfig.Player.SpriteAngle.MAX_CHANGE_RATE,
+ -JetpackConfig.Player.SpriteAngle.MAX_ANGLE,
+ JetpackConfig.Player.SpriteAngle.MAX_ANGLE,
+ JetpackConfig.Player.SpriteAngle.FILTER_SAMPLES)
+
+ private var playerTargetX = 0.0f
+ private var playerTargetY = 0.0f
+
+ // working array
+ private var tmpList = ArrayList()
+
+ // how long til we spawn the next item?
+ private var spawnCountdown = JetpackConfig.Items.SPAWN_INTERVAL
+
+ // cloud sprites
+ private var cloudObj = arrayOfNulls(JetpackConfig.Clouds.COUNT)
+
+ // time remaining
+ override var displayedTime = JetpackConfig.Time.INITIAL
+
+ // sfx IDs
+ private lateinit var itemSfxSuccess: IntArray
+
+ private val combo = Combo()
+
+ // set of achievements we know we unlocked (to prevent repeated API calls)
+ private val unlockedAchievements = HashSet()
+
+ // achievement increments we are pending to send
+ private var achPendingPresents = 0
+ private var achPendingCandy = 0
+ private var achPendingSeconds = 0f
+
+ // countdown to next sending of incremental achievements
+ private var incAchCountdown = JetpackConfig.Achievements.INC_ACH_SEND_INTERVAL
+
+ // what pointer Id is the one that's steering the elf
+ private var activePointerId = -1
+
+ // accumulated play time
+ private var playTime = 0.0f
+
+ override val bgmResId = JetpackConfig.BGM_RES_ID
+
+ // current combo
+ private inner class Combo {
+
+ internal var items = 0
+ internal var countdown = 0.0f
+ internal var centroidX: Float = 0.toFloat()
+ internal var centroidY: Float = 0.toFloat()
+ internal var points: Float = 0.toFloat()
+ internal var timeRecovery: Float = 0.toFloat()
+
+ internal fun reset() {
+ items = 0
+ timeRecovery = 0.0f
+ points = timeRecovery
+ centroidY = points
+ centroidX = centroidY
+ countdown = centroidX
+ }
+ }
+
+ override fun makeNewScene(): BaseScene {
+ return JetpackScene()
+ }
+
+ override fun onInstall() {
+ super.onInstall()
+ factory = JetpackObjectFactory(renderer, world)
+ factory.requestTextures()
+ background = factory.makeBackground()
+ background.sendToBack()
+
+ playerObj = factory.makePlayer()
+ playerTargetX = 0.0f
+ playerTargetY = renderer.bottom + JetpackConfig.Player.VERT_MOVEMENT_MARGIN
+ val soundManager = SceneManager.getInstance().soundManager
+ itemSfxSuccess = IntArray(3).apply {
+ this[0] = soundManager.requestSfx(R.raw.jetpack_score1)
+ this[1] = soundManager.requestSfx(R.raw.jetpack_score2)
+ this[2] = soundManager.requestSfx(R.raw.jetpack_score3)
+ }
+ SceneManager.getInstance().vibrator
+ // start paused
+ startGameScreen()
+ }
+
+ override fun isGameEnded(): Boolean {
+ return gameEnded
+ }
+
+ override fun doFrame(deltaT: Float) {
+ if (!paused) {
+ if (!gameEnded) {
+ playTime += deltaT
+ updatePlayer(deltaT)
+ if (!playerHit) {
+ detectCollectedItems()
+ }
+ updatePlayerHit(deltaT)
+ updateTimeRemaining(deltaT)
+ updateCombo(deltaT)
+ checkLevelUp()
+ achPendingSeconds += deltaT
+ }
+ updateClouds()
+ updateCandy(deltaT)
+ killMissedPresents()
+
+ incAchCountdown -= deltaT
+ sendIncrementalAchievements(false)
+
+ spawnCountdown -= deltaT
+ if (!isInGameEndingTransition && spawnCountdown < 0.0f) {
+ spawnCountdown = JetpackConfig.Items.SPAWN_INTERVAL
+ factory.makeRandomItem(
+ fallMult,
+ SceneManager.getInstance().largePresentMode,
+ displayedScore.value)
+ }
+ super.doFrame(deltaT)
+ }
+ if (inStartCountdown) {
+ updatePlayer(deltaT)
+ val newCount = startCountdownTimeRemaining - deltaT
+ if (newCount <= 0) {
+ inStartCountdown = false
+ unPauseGame()
+ } else if (newCount.toInt() < startCountdownTimeRemaining.toInt()) {
+ digitFactory.setDigit(countdownDigitObj, Math.min(newCount.toInt() + 1, 3))
+ countdownDigitObj?.bringToFront()
+ }
+ startCountdownTimeRemaining = newCount
+ }
+ if (gameEnded) {
+ goToEndGame()
+ }
+ }
+
+ override fun endGame() {
+ isInGameEndingTransition = true
+
+ // force send all incremental achievements
+ sendIncrementalAchievements(true)
+
+ // submit our score
+ submitScore(JetpackConfig.LEADERBOARD, score)
+
+ // Start end game activity
+ onWidgetTriggered(BaseScene.MSG_GO_TO_END_GAME)
+ }
+
+ private fun updateTimeRemaining(deltaT: Float) {
+ displayedTime -= deltaT
+ if (displayedTime < 0.0f && !isInGameEndingTransition) {
+ endGame()
+ }
+ }
+
+ private fun updatePlayerHit(deltaT: Float) {
+ playerHitTime -= deltaT
+ if (playerHitTime < 0.0f && playerHit) {
+ factory.recoverPlayerHit(playerObj)
+ playerHit = false
+ }
+ }
+
+ private fun sineWave(period: Float, amplitude: Float, t: Float): Float {
+ return Math.sin(2.0 * Math.PI * t.toDouble() / period).toFloat() * amplitude
+ }
+
+ private fun updatePlayer(deltaT: Float) {
+ spriteAngle.target = (playerObj.x - playerTargetX) *
+ JetpackConfig.Player.SpriteAngle.ANGLE_CONST
+ spriteAngle.update(deltaT)
+ playerObj.getSprite(0)!!.rotation = spriteAngle.value
+ playerObj.getSprite(1)!!.rotation = spriteAngle.value
+ playerObj.getSprite(1)!!.width = JetpackConfig.Player.Fire.WIDTH * (1.0f + sineWave(
+ JetpackConfig.Player.Fire.ANIM_PERIOD,
+ JetpackConfig.Player.Fire.ANIM_AMPLITUDE,
+ playTime))
+ playerObj.getSprite(1)!!.height = java.lang.Float.NaN // proportional to width
+
+ if (isTv) {
+ // On TV, player moves based on its speed.
+ playerObj.displaceBy(playerObj.velX * deltaT, playerObj.velY * deltaT)
+ } else {
+ playerObj.displaceTowards(
+ playerTargetX, playerTargetY, deltaT * JetpackConfig.Player.MAX_SPEED)
+ }
+ }
+
+ private fun updateClouds() {
+ var i = 0
+ while (i < cloudObj.size) {
+ var o: GameObject? = cloudObj[i]
+ if (o == null) {
+ o = factory.makeCloud()
+ cloudObj[i] = o
+ setupNewCloud(o)
+ } else if (o.y < JetpackConfig.Clouds.DELETE_Y) {
+ setupNewCloud(o)
+ }
+ i++
+ }
+ }
+
+ private fun updateCombo(deltaT: Float) {
+ combo.countdown -= deltaT
+ if (combo.items > 0 && combo.countdown <= 0.0f) {
+ endCombo()
+ }
+ }
+
+ private fun isCandy(o: GameObject): Boolean {
+ return o.type == TYPE_GOOD_ITEM && o.ivar[JetpackConfig.Items.IVAR_TYPE] == JetpackObjectFactory.ITEM_CANDY
+ }
+
+ private fun isPresent(o: GameObject): Boolean {
+ return o.type == TYPE_GOOD_ITEM && o.ivar[JetpackConfig.Items.IVAR_TYPE] == JetpackObjectFactory.ITEM_PRESENT
+ }
+
+ private fun isCoal(o: GameObject): Boolean {
+ return o.type == TYPE_BAD_ITEM && o.ivar[JetpackConfig.Items.IVAR_TYPE] == JetpackObjectFactory.ITEM_COAL
+ }
+
+ private fun updateCandy(deltaT: Float) {
+ var i = 0
+ while (i < world.gameObjects.size) {
+ val o = world.gameObjects[i]
+ if (isCandy(o)) {
+ o.getSprite(0)!!.rotation += deltaT * JetpackConfig.Items.CANDY_ROTATE_SPEED
+ }
+ i++
+ }
+ }
+
+ private fun setupNewCloud(o: GameObject) {
+ o.displaceTo(
+ renderer.left + random.nextFloat() * (renderer.right - renderer.left),
+ JetpackConfig.Clouds.SPAWN_Y)
+ o.velY =
+ -(JetpackConfig.Clouds.SPEED_MIN + random.nextFloat() * (JetpackConfig.Clouds.SPEED_MAX - JetpackConfig.Clouds.SPEED_MIN))
+ }
+
+ private fun killMissedPresents() {
+ var i = 0
+ while (i < world.gameObjects.size) {
+ val o = world.gameObjects[i]
+ if ((o.type == TYPE_GOOD_ITEM || o.type == TYPE_BAD_ITEM) && o.y < JetpackConfig.Items.DELETE_Y) {
+ o.dead = true
+ }
+ i++
+ }
+ }
+
+ private fun addScore(score: Float) {
+ this.score += score.toInt()
+ unlockScoreBasedAchievements()
+ }
+
+ private fun addTime(time: Float) {
+ displayedTime += time
+ if (displayedTime > JetpackConfig.Time.MAX) {
+ displayedTime = JetpackConfig.Time.MAX
+ }
+ }
+
+ private fun pickUpItem(item: GameObject) {
+ val value = item.ivar[JetpackConfig.Items.IVAR_BASE_VALUE]
+ if (isCoal(item)) {
+ factory.makePlayerHit(playerObj)
+ playerHit = true
+ playerHitTime = JetpackConfig.Time.HIT_TIME
+ objectFactory.makeScorePopup(item.x, item.y, value, digitFactory)
+ SceneManager.getInstance()
+ .soundManager
+ .playSfx(itemSfxSuccess[random.nextInt(itemSfxSuccess.size)])
+ SceneManager.getInstance().vibrator.vibrate(JetpackConfig.Time.VIBRATE_SMALL)
+ addTime(-JetpackConfig.Time.COAL_TIME_PENALTY)
+ } else {
+ if (isCandy(item)) {
+ achPendingCandy++
+ } else if (isPresent(item)) {
+ achPendingPresents++
+ }
+ objectFactory.makeScorePopup(item.x, item.y, value, digitFactory)
+ SceneManager.getInstance()
+ .soundManager
+ .playSfx(itemSfxSuccess[random.nextInt(itemSfxSuccess.size)])
+
+ val timeRecovery = Math.max(JetpackConfig.Time.RECOVERED_BY_ITEM -
+ level * JetpackConfig.Time.RECOVERED_DECREASE_PER_LEVEL,
+ JetpackConfig.Time.RECOVERED_MIN)
+ addTime(timeRecovery)
+
+ combo.centroidX = (combo.centroidX * combo.items + item.x) / (combo.items + 1)
+ combo.centroidY = (combo.centroidY * combo.items + item.y) / (combo.items + 1)
+ combo.items++
+ combo.countdown = JetpackConfig.Items.COMBO_INTERVAL
+ combo.points += value
+ combo.timeRecovery = timeRecovery
+ }
+ item.dead = true
+ itemsCollected++
+
+ addScore(value.toFloat())
+
+ // play sfx
+ }
+
+ private fun detectCollectedItems() {
+ world.detectCollisions(playerObj, tmpList, true)
+ var i = 0
+ while (i < tmpList.size) {
+ val o = tmpList[i]
+ if (o.type == TYPE_GOOD_ITEM || o.type == TYPE_BAD_ITEM) {
+ pickUpItem(o)
+ }
+ i++
+ }
+ }
+
+ private fun endCombo() {
+ if (combo.items > 1) {
+ factory.makeComboPopup(combo.items, combo.centroidX, combo.centroidY)
+
+ // give bonus
+ addScore(combo.points * combo.items)
+ addTime(combo.timeRecovery * combo.items)
+
+ // unlock combo-based achievements
+ unlockComboBasedAchievements(combo.items)
+ }
+ combo.reset()
+ }
+
+ override fun onPointerDown(pointerId: Int, x: Float, y: Float) {
+ super.onPointerDown(pointerId, x, y)
+ if (activePointerId < 0) {
+ activePointerId = pointerId
+ }
+ }
+
+ override fun onPointerUp(pointerId: Int, x: Float, y: Float) {
+ super.onPointerUp(pointerId, x, y)
+ if (activePointerId == pointerId) {
+ activePointerId = -1
+ }
+ }
+
+ override fun onPointerMove(pointerId: Int, x: Float, y: Float, deltaX: Float, deltaY: Float) {
+ super.onPointerMove(pointerId, x, y, deltaX, deltaY)
+
+ // if paused, do nothing.
+ if (paused) {
+ return
+ }
+
+ // if no finger owns the steering of the elf, adopt this one.
+ if (activePointerId < 0) {
+ activePointerId = pointerId
+ }
+ }
+
+ override fun onKeyDown(keyCode: Int, repeatCount: Int) {
+
+ when (keyCode) {
+ KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_BUTTON_A -> onConfirmKeyPressed()
+ KeyEvent.KEYCODE_BUTTON_B -> onBackKeyPressed()
+ }
+
+ if (!paused) {
+ val absVelocity =
+ JetpackConfig.Player.WIDTH * (1.5f + repeatCount.toFloat() * JetpackConfig.Input.KEY_SENSIVITY)
+ when (keyCode) {
+ KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT -> {
+ playerTargetX -= 0.17f
+ playerObj.velX = -absVelocity
+ }
+ KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_RIGHT -> {
+ playerTargetX += 0.17f
+ playerObj.velX = absVelocity
+ }
+ }
+ // don't let the player wander off screen
+ limitPlayerMovement()
+ }
+ }
+
+ override fun onKeyUp(keyCode: Int) {
+ when (keyCode) {
+ KeyEvent.KEYCODE_DPAD_UP -> {
+ // if it's going up, stop it.
+ playerTargetY = playerObj.y
+ if (playerObj.velY > 0) {
+ playerObj.velY = 0f
+ }
+ }
+ KeyEvent.KEYCODE_DPAD_DOWN -> {
+ // if it's going down, stop it.
+ playerTargetY = playerObj.y
+ if (playerObj.velY < 0) {
+ playerObj.velY = 0f
+ }
+ }
+ KeyEvent.KEYCODE_DPAD_LEFT -> {
+ // if it's going left, stop it.
+ playerTargetX = playerObj.x
+ if (playerObj.velX < 0) {
+ playerObj.velX = 0f
+ }
+ }
+ KeyEvent.KEYCODE_DPAD_RIGHT -> {
+ // if it's going right, stop it.
+ playerTargetX = playerObj.x
+ if (playerObj.velX > 0) {
+ playerObj.velX = 0f
+ }
+ }
+ }
+
+ // don't let the player wander off screen
+ limitPlayerMovement()
+ }
+
+ override fun onSensorChanged(x: Float, y: Float, accuracy: Int) {
+ playerTargetX += JetpackConfig.Input.Sensor.transformX(x)
+ // don't let the player wander off screen
+ limitPlayerMovement()
+ }
+
+ private fun limitPlayerMovement() {
+ val minX = renderer.left + JetpackConfig.Player.HORIZ_MOVEMENT_MARGIN
+ val maxX = renderer.right - JetpackConfig.Player.HORIZ_MOVEMENT_MARGIN
+ val minY = renderer.bottom + JetpackConfig.Player.VERT_MOVEMENT_MARGIN
+ val maxY = renderer.top - JetpackConfig.Player.VERT_MOVEMENT_MARGIN
+
+ if (isTv) {
+ playerObj.velX = when {
+ playerObj.x + playerObj.velX < minX -> minX - playerObj.x
+ playerObj.x + playerObj.velX > maxX -> maxX - playerObj.x
+ else -> playerObj.velX
+ }
+
+ playerObj.velY = when {
+ playerObj.y + playerObj.velY < minY -> minY - playerObj.y
+ playerObj.y + playerObj.velY > maxY -> maxY - playerObj.y
+ else -> playerObj.velY
+ }
+ } else {
+ playerTargetX =
+ if (playerTargetX < minX) minX else if (playerTargetX > maxX) maxX else playerTargetX
+ playerTargetY =
+ if (playerTargetY < minY) minY else if (playerTargetY > maxY) maxY else playerTargetY
+ }
+ }
+
+ private fun checkLevelUp() {
+ val dueLevel = itemsCollected / JetpackConfig.Progression.ITEMS_PER_LEVEL
+ while (level < dueLevel) {
+ level++
+ Logger.d("Level up! Now at level $level")
+ fallMult *= JetpackConfig.Items.FALL_SPEED_LEVEL_MULT
+ scoreMult *= JetpackConfig.Progression.SCORE_LEVEL_MULT
+ }
+ }
+
+ private fun unlockScoreBasedAchievements() {
+ var i = 0
+ while (i < JetpackConfig.Achievements.SCORE_ACHS.size) {
+ if (score >= JetpackConfig.Achievements.SCORE_FOR_ACH[i]) {
+ unlockAchievement(JetpackConfig.Achievements.SCORE_ACHS[i])
+ }
+ i++
+ }
+ }
+
+ private fun unlockComboBasedAchievements(comboSize: Int) {
+ var i = 0
+ while (i < JetpackConfig.Achievements.COMBO_ACHS.size) {
+ // COMBO_ACHS[n] is the achievement to unlock for a combo of size n + 2
+ if (comboSize >= i + 2) {
+ unlockAchievement(JetpackConfig.Achievements.COMBO_ACHS[i])
+ }
+ i++
+ }
+ }
+
+ private fun sendIncrementalAchievements(force: Boolean) {
+ if (!force && incAchCountdown > 0.0f) {
+ // it's not time to send yet
+ return
+ }
+ if (SceneManager.getInstance().activity == null) {
+ // no Activity (maybe we're in the background), so can't send yet
+ return
+ }
+
+ if (achPendingCandy > 0) {
+ incrementAchievements(JetpackConfig.Achievements.TOTAL_CANDY_ACHS, achPendingCandy)
+ achPendingCandy = 0
+ }
+ if (achPendingPresents > 0) {
+ incrementAchievements(
+ JetpackConfig.Achievements.TOTAL_PRESENTS_ACHS, achPendingPresents)
+ achPendingPresents = 0
+ }
+ if (achPendingSeconds >= 1.0f) {
+ val seconds = Math.floor(achPendingSeconds.toDouble()).toInt()
+ incrementAchievements(JetpackConfig.Achievements.TOTAL_TIME_ACHS, seconds)
+ achPendingSeconds -= seconds.toFloat()
+ }
+
+ // submit score as well, since we're at it.
+ submitScore(JetpackConfig.LEADERBOARD, score)
+
+ // reset countdown
+ incAchCountdown = JetpackConfig.Achievements.INC_ACH_SEND_INTERVAL
+ }
+
+ private fun unlockAchievement(resId: Int) {
+ val act = SceneManager.getInstance().activity as SceneActivity
+ if (!unlockedAchievements.contains(resId)) {
+ act.postUnlockAchievement(resId)
+ unlockedAchievements.add(resId)
+ }
+ }
+
+ private fun incrementAchievements(resId: IntArray, steps: Int) {
+ for (i in resId) {
+ incrementAchievement(i, steps)
+ }
+ }
+
+ private fun incrementAchievement(resId: Int, steps: Int) {
+ val act = SceneManager.getInstance().activity as SceneActivity
+ if (steps > 0) {
+ act.postIncrementAchievement(resId, steps)
+ }
+ }
+
+ private fun submitScore(resId: Int, score: Int) {
+ val act = SceneManager.getInstance().activity as SceneActivity
+ act.postSubmitScore(resId, score.toLong())
+ }
+
+ companion object {
+ // GameObject types:
+ internal const val TYPE_PLAYER = 0
+ internal const val TYPE_GOOD_ITEM = 1
+ internal const val TYPE_BAD_ITEM = 2
+ }
+}
diff --git a/jetpack/src/main/java/com/google/android/apps/jetpack/SceneActivity.kt b/jetpack/src/main/java/com/google/android/apps/jetpack/SceneActivity.kt
new file mode 100644
index 000000000..415123787
--- /dev/null
+++ b/jetpack/src/main/java/com/google/android/apps/jetpack/SceneActivity.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.jetpack
+
+import android.app.Activity
+import android.os.Bundle
+import android.os.Handler
+import android.view.View
+import androidx.core.os.postDelayed
+import com.google.android.apps.jetpack.JetpackActivity.Companion.JETPACK_SCORE
+import com.google.android.apps.playgames.common.PlayGamesActivity
+import com.google.android.apps.playgames.simpleengine.Scene
+import com.google.android.apps.playgames.simpleengine.SceneManager
+import com.google.android.apps.santatracker.games.EndOfGameView
+import com.google.android.apps.santatracker.invites.AppInvitesFragment
+
+abstract class SceneActivity : PlayGamesActivity() {
+
+ private var invitesFragment: AppInvitesFragment? = null
+ private var gameEndedListener: GameEndedListener? = null
+
+ protected abstract val gameScene: Scene
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (savedInstanceState == null) {
+ SceneManager.getInstance().requestNewScene(gameScene)
+ }
+
+ invitesFragment = AppInvitesFragment.getInstance(this)
+ }
+
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ super.onWindowFocusChanged(hasFocus)
+ SceneManager.getInstance().onFocusChanged(hasFocus)
+ }
+
+ public override fun onPause() {
+ super.onPause()
+ SceneManager.getInstance().onPause()
+ }
+
+ public override fun onResume() {
+ super.onResume()
+ SceneManager.getInstance().onResume(this)
+ if (SceneManager.getInstance().currentScene?.isGameEnded == true) {
+ postGoToEndGame()
+ }
+ }
+
+ override fun onSignInFailed() {
+ super.onSignInFailed()
+
+ // communicate to the BaseScene that we are no longer signed in
+ val s = SceneManager.getInstance().currentScene
+ if (s is BaseScene) {
+ s.setSignedIn(false)
+ }
+ }
+
+ override fun onSignInSucceeded() {
+ super.onSignInSucceeded()
+
+ // communicate to the BaseScene that we are no longer signed in
+ val s = SceneManager.getInstance().currentScene
+ if (s is BaseScene) {
+ s.setSignedIn(true)
+ }
+ }
+
+ override fun launchStartupActivity() {
+ finish()
+ }
+
+ fun postQuitGame() {
+ runOnUiThread { launchStartupActivity() }
+ }
+
+ fun postReturnWithScore(score: Int) {
+ runOnUiThread { returnWithScore(score) }
+ }
+
+ private fun returnWithScore(score: Int) {
+ val intent = this.intent
+ intent.putExtra(JETPACK_SCORE, score)
+ this.setResult(Activity.RESULT_OK, intent)
+ finish()
+ }
+
+ fun setGameEndedListener(gameEndedListener: GameEndedListener) {
+ this.gameEndedListener = gameEndedListener
+ }
+
+ fun postDelayedGoToEndGame(delay: Int) {
+ runOnUiThread {
+ Handler().postDelayed(delay.toLong()) {
+ gameEndedListener?.let {
+ it.onGameEnded()
+ goToEndGame(it.score)
+ }
+ }
+ }
+ }
+
+ fun postGoToEndGame() {
+ runOnUiThread {
+ gameEndedListener?.let {
+ val score = it.score
+ it.onGameEnded()
+ goToEndGame(score)
+ }
+ }
+ }
+
+ private fun replay() {
+ val gameView = findViewById(R.id.jetpack_end_game_view)
+ gameView.visibility = View.INVISIBLE
+ SceneManager.getInstance().requestNewScene(gameScene)
+ }
+
+ private fun goToEndGame(score: Int) {
+ // Show the end-game view
+ val gameView = findViewById(R.id.jetpack_end_game_view)
+ gameView.initialize(
+ score,
+ { replay() },
+ { returnWithScore(score) })
+ gameView.visibility = View.VISIBLE
+ }
+
+ fun share() {
+ invitesFragment?.sendGenericInvite()
+ }
+}
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_candy1.png b/jetpack/src/main/res/drawable-nodpi/jetpack_candy1.png
new file mode 100644
index 000000000..43ef34a1e
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_candy1.png differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_candy2.png b/jetpack/src/main/res/drawable-nodpi/jetpack_candy2.png
new file mode 100644
index 000000000..27dc37b70
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_candy2.png differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_candy3.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_candy3.webp
new file mode 100644
index 000000000..2539c7cae
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_candy3.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_candy4.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_candy4.webp
new file mode 100644
index 000000000..9f66ba9d1
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_candy4.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_clock.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_clock.webp
new file mode 100644
index 000000000..b5194717e
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_clock.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_cloud.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_cloud.webp
new file mode 100644
index 000000000..724780b25
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_cloud.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_coal.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_coal.webp
new file mode 100644
index 000000000..ab16bd4b0
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_coal.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_combo_2x.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_combo_2x.webp
new file mode 100644
index 000000000..f731d81aa
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_combo_2x.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_combo_3x.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_combo_3x.webp
new file mode 100644
index 000000000..d83a47f4d
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_combo_3x.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_combo_4x.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_combo_4x.webp
new file mode 100644
index 000000000..b1aa2657b
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_combo_4x.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_fire.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_fire.webp
new file mode 100644
index 000000000..a3482baaa
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_fire.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_player.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_player.webp
new file mode 100644
index 000000000..32bd3e3d7
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_player.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_player_hit.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_player_hit.webp
new file mode 100644
index 000000000..351f8bfb6
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_player_hit.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_player_hit_overlay.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_player_hit_overlay.webp
new file mode 100644
index 000000000..124207d06
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_player_hit_overlay.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_podium.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_podium.webp
new file mode 100644
index 000000000..7b0944cf1
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_podium.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_present1.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_present1.webp
new file mode 100644
index 000000000..532476ea4
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_present1.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_present2.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_present2.webp
new file mode 100644
index 000000000..5b05b2c7d
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_present2.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_present3.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_present3.webp
new file mode 100644
index 000000000..64413382d
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_present3.webp differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_present4.png b/jetpack/src/main/res/drawable-nodpi/jetpack_present4.png
similarity index 100%
rename from santa-tracker/src/main/res/drawable-nodpi/jetpack_present4.png
rename to jetpack/src/main/res/drawable-nodpi/jetpack_present4.png
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_signin.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_signin.webp
new file mode 100644
index 000000000..7c01dc87b
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_signin.webp differ
diff --git a/jetpack/src/main/res/drawable-nodpi/jetpack_signin_pressed.webp b/jetpack/src/main/res/drawable-nodpi/jetpack_signin_pressed.webp
new file mode 100644
index 000000000..dff705b0a
Binary files /dev/null and b/jetpack/src/main/res/drawable-nodpi/jetpack_signin_pressed.webp differ
diff --git a/jetpack/src/main/res/layout/activity_jetpack.xml b/jetpack/src/main/res/layout/activity_jetpack.xml
new file mode 100644
index 000000000..087daf675
--- /dev/null
+++ b/jetpack/src/main/res/layout/activity_jetpack.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/raw/jetpack_score1.mp3 b/jetpack/src/main/res/raw/jetpack_score1.mp3
similarity index 100%
rename from santa-tracker/src/main/res/raw/jetpack_score1.mp3
rename to jetpack/src/main/res/raw/jetpack_score1.mp3
diff --git a/santa-tracker/src/main/res/raw/jetpack_score2.mp3 b/jetpack/src/main/res/raw/jetpack_score2.mp3
similarity index 100%
rename from santa-tracker/src/main/res/raw/jetpack_score2.mp3
rename to jetpack/src/main/res/raw/jetpack_score2.mp3
diff --git a/santa-tracker/src/main/res/raw/jetpack_score3.mp3 b/jetpack/src/main/res/raw/jetpack_score3.mp3
similarity index 100%
rename from santa-tracker/src/main/res/raw/jetpack_score3.mp3
rename to jetpack/src/main/res/raw/jetpack_score3.mp3
diff --git a/jetpack/src/main/res/values/colors.xml b/jetpack/src/main/res/values/colors.xml
new file mode 100644
index 000000000..56165f71c
--- /dev/null
+++ b/jetpack/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/jetpack/src/main/res/values/styles.xml b/jetpack/src/main/res/values/styles.xml
new file mode 100644
index 000000000..6c65774ee
--- /dev/null
+++ b/jetpack/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/jetpack/src/release/res/values/game_ids.xml b/jetpack/src/release/res/values/game_ids.xml
new file mode 100644
index 000000000..7b545187a
--- /dev/null
+++ b/jetpack/src/release/res/values/game_ids.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ 80719462666
+ CgkIioKF2qwCEAIQEw
+ CgkIioKF2qwCEAIQFA
+
diff --git a/jetpack/src/release/res/values/game_ids_jetpack.xml b/jetpack/src/release/res/values/game_ids_jetpack.xml
new file mode 100644
index 000000000..f215d5b11
--- /dev/null
+++ b/jetpack/src/release/res/values/game_ids_jetpack.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+CgkIioKF2qwCEAIQAg
+CgkIioKF2qwCEAIQAw
+CgkIioKF2qwCEAIQBA
+CgkIioKF2qwCEAIQBg
+CgkIioKF2qwCEAIQBw
+CgkIioKF2qwCEAIQCA
+CgkIioKF2qwCEAIQCQ
+CgkIioKF2qwCEAIQCg
+CgkIioKF2qwCEAIQCw
+CgkIioKF2qwCEAIQDA
+CgkIioKF2qwCEAIQDQ
+CgkIioKF2qwCEAIQDg
+CgkIioKF2qwCEAIQDw
+CgkIioKF2qwCEAIQEA
+CgkIioKF2qwCEAIQEQ
+CgkIioKF2qwCEAIQEg
+
+
diff --git a/memory/build.gradle b/memory/build.gradle
new file mode 100644
index 000000000..e4cd2fdcb
--- /dev/null
+++ b/memory/build.gradle
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.dynamic-feature'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.tools
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+ }
+}
+
+dependencies {
+ implementation project(':santa-tracker')
+}
diff --git a/memory/src/main/AndroidManifest.xml b/memory/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..c970988e5
--- /dev/null
+++ b/memory/src/main/AndroidManifest.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/memory/src/main/java/com/google/android/apps/memory/MemoryActivity.java b/memory/src/main/java/com/google/android/apps/memory/MemoryActivity.java
new file mode 100644
index 000000000..b430e7ec9
--- /dev/null
+++ b/memory/src/main/java/com/google/android/apps/memory/MemoryActivity.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.memory;
+
+import android.os.Bundle;
+import com.google.android.apps.playgames.common.PlayGamesActivity;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+public class MemoryActivity extends PlayGamesActivity {
+
+ private MemoryMatchFragment mMemoryMatchFragment;
+ private FirebaseAnalytics mMeasurement;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mMemoryMatchFragment = MemoryMatchFragment.newInstance();
+ this.getSupportFragmentManager()
+ .beginTransaction()
+ .replace(
+ com.google.android.apps.playgames.R.id.mainFragmentContainer,
+ mMemoryMatchFragment)
+ .commit();
+
+ // App Measurement
+ mMeasurement = FirebaseAnalytics.getInstance(this);
+ MeasurementManager.recordScreenView(
+ mMeasurement,
+ getString(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .analytics_screen_memory));
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mMemoryMatchFragment != null) {
+ mMemoryMatchFragment.onBackKeyPressed();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.activity_memory;
+ }
+
+ @Override
+ public void onSignInSucceeded() {
+ super.onSignInSucceeded();
+ mMemoryMatchFragment.onSignInSucceeded();
+ }
+
+ @Override
+ public String getGameId() {
+ return getResources().getString(com.google.android.apps.playgames.R.string.memory_game_id);
+ }
+
+ @Override
+ public String getGameTitle() {
+ return getString(com.google.android.apps.santatracker.common.R.string.memory);
+ }
+
+ @Override
+ public void onSignInFailed() {
+ super.onSignInFailed();
+ mMemoryMatchFragment.onSignInFailed();
+ }
+}
diff --git a/memory/src/main/java/com/google/android/apps/memory/MemoryCard.java b/memory/src/main/java/com/google/android/apps/memory/MemoryCard.java
new file mode 100644
index 000000000..0147b7608
--- /dev/null
+++ b/memory/src/main/java/com/google/android/apps/memory/MemoryCard.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.memory;
+
+import android.view.View;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Card in the memory game. Contains the front of the card (the card image) and its back (its
+ * cloak).
+ */
+public class MemoryCard {
+
+ public int mCardImageId;
+ public int mCardCloakId;
+ public View mView;
+
+ public MemoryCard(int cardImageId, int cardCloakId) {
+ mCardImageId = cardImageId;
+ mCardCloakId = cardCloakId;
+ }
+
+ /**
+ * Generate a randomised list of {@link
+ * com.google.android.apps.santatracker.games.matching.MemoryCard}s.
+ *
+ * @param numCards Number of cards to generate
+ * @param cardImages List of card image references
+ * @param cardCloaks List of card cloak image references
+ */
+ public static ArrayList getGameCards(
+ int numCards, List cardImages, List cardCloaks) {
+ Collections.shuffle(cardImages);
+ Collections.shuffle(cardCloaks);
+ ArrayList cards = new ArrayList();
+ for (int i = 0; i < (numCards / 2); i++) {
+ cards.add(new MemoryCard(cardImages.get(i), cardCloaks.get(i)));
+ cards.add(new MemoryCard(cardImages.get(i), cardCloaks.get(i)));
+ }
+ Collections.shuffle(cards);
+ return cards;
+ }
+}
diff --git a/memory/src/main/java/com/google/android/apps/memory/MemoryMatchFragment.java b/memory/src/main/java/com/google/android/apps/memory/MemoryMatchFragment.java
new file mode 100644
index 000000000..7ff9d02e8
--- /dev/null
+++ b/memory/src/main/java/com/google/android/apps/memory/MemoryMatchFragment.java
@@ -0,0 +1,1070 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.memory;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Drawable.Callback;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.SoundPool;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.TranslateAnimation;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.fragment.app.Fragment;
+import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
+import com.google.android.apps.playgames.Utils;
+import com.google.android.apps.playgames.common.GameConstants;
+import com.google.android.apps.playgames.common.PlayGamesActivity;
+import com.google.android.apps.playgames.customviews.CircleView;
+import com.google.android.apps.playgames.customviews.LevelTextView;
+import com.google.android.apps.santatracker.common.CheckableImageButton;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+import com.google.android.apps.santatracker.util.ImmersiveModeHelper;
+import com.google.android.apps.santatracker.util.SoundPoolUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/** Fragment that contains the memory match game. */
+public class MemoryMatchFragment extends Fragment
+ implements OnClickListener, AnimationListener, Callback {
+
+ /** Drawables for all card faces. */
+ private static final Integer[] CARD_FACE_DRAWABLES =
+ new Integer[] {
+ R.drawable.mmg_card_ball, R.drawable.mmg_card_balloon,
+ R.drawable.mmg_card_beachball,
+ R.drawable.mmg_card_candle, R.drawable.mmg_card_globe, R.drawable.mmg_card_gumball,
+ R.drawable.mmg_card_penguin, R.drawable.mmg_card_rabbit,
+ R.drawable.mmg_card_reindeer,
+ R.drawable.mmg_card_snowman, R.drawable.mmg_card_tree, R.drawable.mmg_card_trophy
+ };
+ /** Drawables for all card face cloaks (background color). */
+ private static final Integer[] CARD_CLOAK_DRAWABLES =
+ new Integer[] {
+ R.drawable.mmg_card_cloak_blue_dark, R.drawable.mmg_card_cloak_blue_light,
+ R.drawable.mmg_card_cloak_orange, R.drawable.mmg_card_cloak_purple,
+ R.drawable.mmg_card_cloak_red, R.drawable.mmg_card_cloak_orange
+ };
+
+ private static final int DOOR_CLOSE_DELAY_MILLIS = 1500;
+
+ /** Current game level. */
+ private int mLevelNumber = 1;
+
+ /** Number of correct moves required for this level. */
+ private int mCorrectMovesRequired = 0;
+
+ /** Count of correct moves in this level so far. */
+ private int mCurrentCorrectMoves = 0;
+
+ /** Total score of the game so far. */
+ private int mMatchScore = 0;
+
+ /** Count of the number of wrong selections in the level so far. */
+ private int mWrongAnswers = 0;
+
+ /** First card that has been selected and is visible. */
+ private View mVisibleCard1 = null;
+
+ /** Second card that has been selected and is visible. */
+ private View mVisibleCard2 = null;
+
+ /** First card that was visible and is being animated to become hidden again. */
+ private View mHiddenCard1;
+
+ /** Second card that was visible and is being animated to become hidden again. */
+ private View mHiddenCard2;
+
+ /** Views that represent the cards (doors) on screen. */
+ private View[] mViewCard = new View[12];
+
+ /** List of card faces. This list is shuffled before each level. */
+ private List mCardFaceIds = Arrays.asList(CARD_FACE_DRAWABLES);
+
+ /** List of card cloaks (backgrounds). This list is shuffled before each level. */
+ private List mCardCloakIds = Arrays.asList(CARD_CLOAK_DRAWABLES);
+
+ /** Time left in the game in milliseconds. */
+ private long mTimeLeftInMillis = GameConstants.MATCH_INIT_TIME;
+ /** Countdown timer refresh interval in milliseconds. */
+ private long mCountDownInterval = 1000;
+ /** Countdown timer that drives the game logic. */
+ private GameCountdown mCountDownTimer = null;
+ /**
+ * Flag to indicate the state of this Fragment and stop the game correctly when the countdown
+ * expires.
+ */
+ private boolean wasPaused = false;
+
+ private Animation mAnimationRightPaneSlideOut;
+ private Animation mAnimationLeftPaneSlideOut;
+ private Animation mAnimationLeftPaneSlideIn;
+ private Animation mAnimationRightPaneSlideIn;
+ private Animation mAnimationScaleLevelDown;
+ private Animation mAnimationLevelFadeOut;
+ private Animation mAnimationLevelScaleUp;
+ private Animation mAnimationPlayAgainBackground;
+ private Animation mAnimationPlayAgainMain;
+ private Animation mAnimationCardCover;
+ private TranslateAnimation mAnimationSnowman;
+ private Animation mAnimationTimerAlpha;
+ private TranslateAnimation mAnimationSnowmanBack;
+ private AnimationSet mAnimationSetSnowman;
+
+ private CircleView mEndLevelCircle;
+ private TextView mScoreText;
+ private LevelTextView mLevelNumberText;
+
+ private int mSoundDoorOpen = -1;
+ private int mSoundDoorClose = -1;
+ private int mSoundMatchWrong = -1;
+ private int mSoundMatchRight = -1;
+ private int mSoundBeep = -1;
+ private int mSoundGameOver = -1;
+ private MediaPlayer mBackgroundMusic;
+ private SoundPool mSoundPool;
+ private Handler mDoorCloseHandler;
+ private Runnable mDoorCloseRunnable;
+ private boolean mDoorCloseTimerTicking = false;
+ private long mDoorCloseTimerStart = 0;
+ private long mDoorCloseTimeRemaining = DOOR_CLOSE_DELAY_MILLIS;
+
+ private TextView mTimerTextView;
+ private View mViewPlayAgainBackground;
+ private View mViewPlayAgainMain;
+ private TextView mTextPlayAgainScore;
+ private TextView mTextPlayAgainLevel;
+ private ImageView mButtonPlay;
+ private ImageView mButtonPause;
+ private ImageButton mButtonBigPlay;
+ private Button mPlayAgainBtn;
+ private ImageButton mButtonCancelBar;
+ private ImageButton mButtonMenu;
+ private ImageView mInviteButton;
+ private ImageView mViewInstructions;
+ private View mViewPauseOverlay;
+ private View mViewBonusSnowman;
+ private AnimationDrawable mInstructionDrawable;
+ private ImageView mViewGPlusSignIn;
+ private View mLayoutGPlus;
+
+ /** Handler that dismisses the game instructions when the game is started for the first time. */
+ private Handler mDelayHandler = new Handler();
+
+ /**
+ * Handler and Runnable to fix the occasional "nothing is clickable" bug, The timeout is set as
+ * slightly longer than our max animation duration.
+ */
+ private static final long ANIMATION_TIMEOUT = 1005;
+
+ private Handler mClickabilityHandler = new Handler();
+ private Runnable mMakeClickableRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ isClickable = true;
+ }
+ };
+
+ /** Preferences that store whether the game instructions have been viewed. */
+ private SharedPreferences mPreferences;
+
+ /**
+ * Indicates whether the screen is clickable. It is disabled when a full screen animation is in
+ * progress at the end of the level or at the end of the game.
+ */
+ private boolean isClickable = true;
+
+ private AppInvitesFragment mInvitesFragment;
+
+ private CheckableImageButton mMuteButton;
+ private SantaPreferences mSantaPreferences;
+
+ /** Create a new instance of this fragment. */
+ public static MemoryMatchFragment newInstance() {
+ return new MemoryMatchFragment();
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mSantaPreferences = new SantaPreferences(context);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_memory_match, container, false);
+ rootView.setKeepScreenOn(true);
+
+ // Initialise the sound pool and all sound effects
+ mSoundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
+ mSoundDoorOpen = mSoundPool.load(getActivity(), R.raw.mmg_open_door_3, 1);
+ mSoundDoorClose = mSoundPool.load(getActivity(), R.raw.mmg_close_door, 1);
+ mSoundMatchWrong = mSoundPool.load(getActivity(), R.raw.mmg_wrong, 1);
+ mSoundMatchRight = mSoundPool.load(getActivity(), R.raw.mmg_right, 1);
+ mSoundGameOver =
+ mSoundPool.load(getActivity(), com.google.android.apps.playgames.R.raw.gameover, 1);
+ mSoundBeep = mSoundPool.load(getActivity(), R.raw.mmg_open_door_2, 1);
+
+ mDoorCloseHandler = new Handler();
+ mDoorCloseRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ hideBothPreviousCards();
+ mDoorCloseTimerTicking = false;
+ mDoorCloseTimerStart = 0;
+ mDoorCloseTimeRemaining = DOOR_CLOSE_DELAY_MILLIS;
+ }
+ };
+
+ // Set up all animations.
+ loadAnimations();
+
+ // G+ sign-in views
+ mViewGPlusSignIn =
+ rootView.findViewById(com.google.android.apps.santatracker.R.id.gplus_button);
+ mViewGPlusSignIn.setOnClickListener(this);
+ mLayoutGPlus =
+ rootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_gplus);
+ mLayoutGPlus.setVisibility(View.GONE);
+
+ // 'Play again' screen views
+ mTextPlayAgainScore =
+ rootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_score);
+ mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
+ mTextPlayAgainLevel =
+ rootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_level);
+ mTextPlayAgainLevel.setText(String.valueOf(mLevelNumber));
+ mViewPlayAgainBackground =
+ rootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_bkgrd);
+ mViewPlayAgainMain =
+ rootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_main);
+ mPlayAgainBtn =
+ rootView.findViewById(com.google.android.apps.santatracker.R.id.play_again_btn);
+ mPlayAgainBtn.setOnClickListener(this);
+
+ // Level, countdown and score views at the bottom of the screen
+ mTimerTextView = rootView.findViewById(R.id.match_timer);
+ mLevelNumberText = rootView.findViewById(R.id.card_end_level_number);
+ mLevelNumberText.setVisibility(View.GONE);
+ mScoreText = rootView.findViewById(R.id.match_score);
+ mScoreText.setText(String.valueOf(mMatchScore));
+
+ // End of level animated circle
+ mEndLevelCircle = rootView.findViewById(R.id.card_end_level_circle);
+ mEndLevelCircle.setVisibility(View.GONE);
+
+ // The snowman that is animated as a bonus when the player is particularly awesome
+ mViewBonusSnowman = rootView.findViewById(R.id.match_snowman);
+
+ // 'Pause' screen views
+ mButtonMenu =
+ rootView.findViewById(com.google.android.apps.santatracker.R.id.main_menu_button);
+ mButtonMenu.setOnClickListener(this);
+ mButtonMenu.setVisibility(View.GONE);
+ mInviteButton =
+ rootView.findViewById(com.google.android.apps.santatracker.R.id.invite_button);
+ mInviteButton.setOnClickListener(this);
+ mInviteButton.setVisibility(View.GONE);
+ mButtonPlay = rootView.findViewById(R.id.match_play_button);
+ mButtonPlay.setOnClickListener(this);
+ mButtonPlay.setVisibility(View.GONE);
+ mButtonPause = rootView.findViewById(R.id.match_pause_button);
+ mButtonPause.setOnClickListener(this);
+ mButtonPause.setVisibility(View.VISIBLE);
+ mViewPauseOverlay = rootView.findViewById(R.id.match_pause_overlay);
+ mViewPauseOverlay.setVisibility(View.GONE);
+ mButtonBigPlay = rootView.findViewById(R.id.match_big_play_button);
+ mButtonBigPlay.setOnClickListener(this);
+ mButtonCancelBar = rootView.findViewById(R.id.match_cancel_bar);
+ mButtonCancelBar.setOnClickListener(this);
+ mButtonCancelBar.setVisibility(View.GONE);
+
+ // Playing cards (doors)
+ mViewCard[0] = rootView.findViewById(R.id.card_position_1);
+ mViewCard[1] = rootView.findViewById(R.id.card_position_2);
+ mViewCard[2] = rootView.findViewById(R.id.card_position_3);
+ mViewCard[3] = rootView.findViewById(R.id.card_position_4);
+ mViewCard[4] = rootView.findViewById(R.id.card_position_5);
+ mViewCard[5] = rootView.findViewById(R.id.card_position_6);
+ mViewCard[6] = rootView.findViewById(R.id.card_position_7);
+ mViewCard[7] = rootView.findViewById(R.id.card_position_8);
+ mViewCard[8] = rootView.findViewById(R.id.card_position_9);
+ mViewCard[9] = rootView.findViewById(R.id.card_position_10);
+ mViewCard[10] = rootView.findViewById(R.id.card_position_11);
+ mViewCard[11] = rootView.findViewById(R.id.card_position_12);
+
+ // Display the instructions if they haven't been seen by the player yet.
+ mPreferences =
+ getActivity()
+ .getSharedPreferences(
+ GameConstants.PREFERENCES_FILENAME, Context.MODE_PRIVATE);
+ if (!mPreferences.getBoolean(GameConstants.MATCH_INSTRUCTIONS_VIEWED, false)) {
+ // Instructions haven't been viewed yet. Construct an AnimationDrawable with
+ // instructions.
+ mInstructionDrawable = new AnimationDrawable();
+ mInstructionDrawable.addFrame(
+ VectorDrawableCompat.create(
+ getResources(),
+ com.google.android.apps.playgames.R.drawable.instructions_touch_1,
+ null),
+ 300);
+ mInstructionDrawable.addFrame(
+ VectorDrawableCompat.create(
+ getResources(),
+ com.google.android.apps.playgames.R.drawable.instructions_touch_2,
+ null),
+ 300);
+ mInstructionDrawable.setOneShot(false);
+ mViewInstructions =
+ rootView.findViewById(com.google.android.apps.playgames.R.id.instructions);
+ mViewInstructions.setImageDrawable(mInstructionDrawable);
+ mInstructionDrawable.start();
+
+ // Set a timer to hide the instructions after 2 seconds
+ mViewInstructions.postDelayed(new StartGameDelay(), 2000);
+ } else {
+ // Instructions have already been viewed. Start the first level.
+ setUpLevel();
+ }
+
+ mMuteButton = rootView.findViewById(R.id.mute_button);
+ mMuteButton.setChecked(!mSantaPreferences.isMuted());
+ mMuteButton.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSantaPreferences.toggleMuted();
+ mMuteButton.setChecked(!mSantaPreferences.isMuted());
+ onMuteChanged(mSantaPreferences.isMuted());
+ }
+ });
+
+ return rootView;
+ }
+
+ /** Load and initialise all animations required for the game. */
+ private void loadAnimations() {
+ mAnimationTimerAlpha = new AlphaAnimation(0.0f, 1.0f);
+ mAnimationTimerAlpha.setDuration(1000);
+ mAnimationTimerAlpha.setRepeatMode(Animation.REVERSE);
+ mAnimationTimerAlpha.setRepeatCount(Animation.INFINITE);
+
+ mAnimationPlayAgainBackground =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.play_again_bkgrd_anim);
+ mAnimationPlayAgainBackground.setFillAfter(true);
+ mAnimationPlayAgainBackground.setAnimationListener(this);
+ mAnimationCardCover =
+ AnimationUtils.loadAnimation(
+ getActivity(), com.google.android.apps.playgames.R.anim.card_answer_flash);
+ mAnimationCardCover.setFillAfter(true);
+ mAnimationPlayAgainMain =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.play_again_main_anim);
+ mAnimationPlayAgainMain.setFillAfter(true);
+ mAnimationPlayAgainMain.setAnimationListener(this);
+ // Special bonus animation to play if the player is particularly awesome.
+ mAnimationSetSnowman = new AnimationSet(true);
+ mAnimationSnowman = new TranslateAnimation(150, 0, 150, 0);
+ mAnimationSnowman.setDuration(1000);
+ mAnimationSetSnowman.addAnimation(mAnimationSnowman);
+ mAnimationSnowmanBack = new TranslateAnimation(0, 150, 0, 150);
+ mAnimationSnowmanBack.setDuration(1000);
+ mAnimationSnowmanBack.setStartOffset(1500);
+ mAnimationSnowmanBack.setAnimationListener(this);
+ mAnimationSetSnowman.addAnimation(mAnimationSnowmanBack);
+ mAnimationSetSnowman.setAnimationListener(this);
+
+ mAnimationRightPaneSlideOut =
+ AnimationUtils.loadAnimation(getActivity(), android.R.anim.slide_out_right);
+ mAnimationRightPaneSlideOut.setFillAfter(true);
+ mAnimationLeftPaneSlideOut =
+ AnimationUtils.loadAnimation(getActivity(), R.anim.left_pane_slide_out);
+ mAnimationLeftPaneSlideOut.setFillAfter(true);
+ mAnimationLeftPaneSlideIn =
+ AnimationUtils.loadAnimation(getActivity(), android.R.anim.slide_in_left);
+ mAnimationLeftPaneSlideIn.setFillAfter(true);
+ mAnimationRightPaneSlideIn =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.right_pane_slide_in);
+ mAnimationRightPaneSlideIn.setFillAfter(true);
+ mAnimationScaleLevelDown =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.scale_level_anim_down);
+ mAnimationScaleLevelDown.setAnimationListener(this);
+ mAnimationLevelFadeOut =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.level_fade_out_anim);
+ mAnimationLevelFadeOut.setAnimationListener(this);
+ mAnimationLevelScaleUp =
+ AnimationUtils.loadAnimation(
+ getActivity(),
+ com.google.android.apps.playgames.R.anim.scale_up_level_anim);
+ mAnimationLevelScaleUp.setAnimationListener(this);
+ mAnimationRightPaneSlideOut.setAnimationListener(this);
+ mAnimationLeftPaneSlideOut.setAnimationListener(this);
+ }
+
+ /**
+ * Runnable that stars the game after the instructions have been viewed. It hides the
+ * instructions, marks them as viewed and starts the game.
+ */
+ private class StartGameDelay implements Runnable {
+
+ @Override
+ public void run() {
+ // Start the first level.
+ setUpLevel();
+
+ // Hide the instructions.
+ mInstructionDrawable.stop();
+ mViewInstructions.setVisibility(View.GONE);
+ // Mark the instructions as 'viewed'.
+ Editor edit = mPreferences.edit();
+ edit.putBoolean(GameConstants.MATCH_INSTRUCTIONS_VIEWED, true);
+ edit.commit();
+ }
+ }
+
+ public void onSignInSucceeded() {
+ setSignInButtonVisibility(false);
+ }
+
+ public void onSignInFailed() {}
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mInvitesFragment = AppInvitesFragment.getInstance(getActivity());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ isClickable = true;
+ if (wasPaused && mViewPauseOverlay.getVisibility() != View.VISIBLE) {
+ mCountDownTimer = new GameCountdown(mTimeLeftInMillis, mCountDownInterval);
+ mCountDownTimer.start();
+ wasPaused = false;
+ }
+ if (!mSantaPreferences.isMuted()) {
+ loadBackgroundMusic();
+ }
+ updateSignInButtonVisibility();
+ }
+
+ /** Toggles visibility of the G+ sign in layout if the user is not already signed in. */
+ private void setSignInButtonVisibility(boolean show) {
+ mLayoutGPlus.setVisibility(show && !Utils.isSignedIn(this) ? View.VISIBLE : View.GONE);
+ }
+
+ private void updateSignInButtonVisibility() {
+ if (mLayoutGPlus.getVisibility() == View.VISIBLE && Utils.isSignedIn(this)) {
+ setSignInButtonVisibility(false);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ pauseGame();
+ stopBackgroundMusic();
+ if (mCountDownTimer != null && mViewPauseOverlay.getVisibility() != View.VISIBLE) {
+ mCountDownTimer.cancel();
+ wasPaused = true;
+ }
+ }
+
+ private void stopBackgroundMusic() {
+ if (mBackgroundMusic != null) {
+ mBackgroundMusic.stop();
+ mBackgroundMusic.release();
+ mBackgroundMusic = null;
+ }
+ }
+
+ private void loadBackgroundMusic() {
+ mBackgroundMusic =
+ MediaPlayer.create(
+ getActivity(),
+ com.google.android.apps.santatracker.R.raw.santatracker_musicloop);
+ mBackgroundMusic.setLooping(true);
+ mBackgroundMusic.setVolume(.1f, .1f);
+ mBackgroundMusic.start();
+ }
+
+ /**
+ * Starts the next level. Shuffles the cards, sets up the views and starts the countdown for the
+ * next level.
+ */
+ private void setUpLevel() {
+ mCurrentCorrectMoves = 0;
+ mWrongAnswers = 0;
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+
+ // Display the level number
+ mTextPlayAgainLevel.setText(String.valueOf(mLevelNumber));
+
+ // Lock all doors, unlock them individually per level later
+ for (View card : mViewCard) {
+ setUpLockedCard(card);
+ }
+
+ if (mLevelNumber > 1) {
+ // Add the 'next level' bonus time
+ mTimeLeftInMillis += GameConstants.MATCH_ADD_TIME_NEXT_LEVEL;
+ }
+
+ int pairsRequired = Math.min(mLevelNumber, 5) + 1;
+ mCorrectMovesRequired = pairsRequired;
+ ArrayList memoryCards =
+ MemoryCard.getGameCards(pairsRequired * 2, mCardFaceIds, mCardCloakIds);
+ int[] cardSlots;
+
+ if (mLevelNumber == 1) {
+ cardSlots = new int[] {2, 3, 8, 9};
+ } else if (mLevelNumber == 2) {
+ cardSlots = new int[] {0, 1, 2, 3, 4, 5};
+ } else if (mLevelNumber == 3) {
+ cardSlots = new int[] {1, 2, 3, 4, 7, 8, 9, 10};
+ } else if (mLevelNumber == 4) {
+ cardSlots = new int[] {0, 1, 2, 3, 4, 5, 7, 8, 9, 10};
+ } else {
+ cardSlots = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+ }
+ for (int i = 0; i < cardSlots.length; i++) {
+ setUpMemoryCard(mViewCard[cardSlots[i]], memoryCards.get(i));
+ }
+
+ // Start the countdown for the new level
+ mCountDownTimer = new GameCountdown(mTimeLeftInMillis, mCountDownInterval);
+ mCountDownTimer.start();
+ }
+
+ /** Sets the card displayed by this view to the 'locked' state. */
+ private void setUpLockedCard(View view) {
+ view.setOnClickListener(null);
+ view.findViewById(R.id.card_locked).setVisibility(View.VISIBLE);
+ view.findViewById(R.id.card_cloak).setVisibility(View.GONE);
+ view.findViewById(R.id.card_frame).setVisibility(View.GONE);
+ view.findViewById(R.id.card_image).setVisibility(View.GONE);
+ view.findViewById(R.id.card_pane_right).clearAnimation();
+ view.findViewById(R.id.card_pane_left).clearAnimation();
+ view.findViewById(R.id.card_pane_left).setVisibility(View.GONE);
+ view.findViewById(R.id.card_pane_right).setVisibility(View.GONE);
+ view.findViewById(R.id.card_cover).setVisibility(View.GONE);
+ }
+
+ /**
+ * Sets the viewCard that displays a card to show the face and cloaking indicated by the {@link
+ * MemoryCard}.
+ */
+ private void setUpMemoryCard(View viewCard, MemoryCard card) {
+ viewCard.setOnClickListener(this);
+ card.mView = viewCard;
+ viewCard.setTag(card);
+ viewCard.findViewById(R.id.card_locked).setVisibility(View.GONE);
+ viewCard.findViewById(R.id.card_frame).setVisibility(View.VISIBLE);
+ ((ImageView) viewCard.findViewById(R.id.card_cloak)).setImageResource(card.mCardCloakId);
+ viewCard.findViewById(R.id.card_cloak).setVisibility(View.VISIBLE);
+ ((ImageView) viewCard.findViewById(R.id.card_image)).setImageResource(card.mCardImageId);
+ viewCard.findViewById(R.id.card_image).setVisibility(View.VISIBLE);
+ viewCard.findViewById(R.id.card_pane_right).clearAnimation();
+ viewCard.findViewById(R.id.card_pane_left).clearAnimation();
+ viewCard.findViewById(R.id.card_pane_left).setVisibility(View.VISIBLE);
+ viewCard.findViewById(R.id.card_pane_right).setVisibility(View.VISIBLE);
+ viewCard.findViewById(R.id.card_cover).setVisibility(View.INVISIBLE);
+ }
+
+ /** Plays a sound unveils the given card. */
+ private void showCard(View view) {
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundDoorOpen);
+ }
+ view.findViewById(R.id.card_pane_right).startAnimation(mAnimationRightPaneSlideOut);
+
+ view.findViewById(R.id.card_pane_left).startAnimation(mAnimationLeftPaneSlideOut);
+ }
+
+ /** Plays a sound and hides the given card. */
+ private void hideCard(View view) {
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundDoorClose);
+ }
+ view.findViewById(R.id.card_pane_left).startAnimation(mAnimationLeftPaneSlideIn);
+ view.findViewById(R.id.card_pane_right).startAnimation(mAnimationRightPaneSlideIn);
+ }
+
+ @Override
+ public void onClick(View view) {
+ // Check if the cards are not currently clickable and skip if necessary.
+ if (view.getTag() != null && isClickable) {
+ // A card has been clicked.
+ onCardClick(view);
+ } else if (view.equals(mPlayAgainBtn)) {
+ // The 'play again' button has been clicked. Stop the music and restart.
+ stopBackgroundMusic();
+ if (!mSantaPreferences.isMuted()) {
+ loadBackgroundMusic();
+ }
+
+ // Reset the game state.
+ resetGameState();
+
+ // Reset the UI and clear all animations.
+ mScoreText.setText(String.valueOf(mMatchScore));
+ mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
+ mViewPlayAgainBackground.clearAnimation();
+ mViewPlayAgainMain.clearAnimation();
+ mViewPlayAgainBackground.setVisibility(View.GONE);
+ mViewPlayAgainMain.setVisibility(View.GONE);
+ mButtonMenu.setVisibility(View.GONE);
+ mInviteButton.setVisibility(View.GONE);
+ setSignInButtonVisibility(false);
+ } else if (view.equals(mButtonPause)) {
+ // Pause button.
+ pauseGame();
+ } else if (view.equals(mButtonPlay) || view.equals(mButtonBigPlay)) {
+ // Play button, unmute the game.
+ resumeGame();
+ } else if (view.equals(mButtonCancelBar) || view.equals(mButtonMenu)) {
+ // Exit the game.
+ exit();
+ } else if (view.equals(mViewGPlusSignIn)) {
+ // Start sign-in flow.
+ PlayGamesActivity activity = Utils.getPlayGamesActivity(this);
+ if (activity != null) {
+ activity.startSignIn();
+ }
+ } else if (view.equals(mInviteButton)) {
+ // Send app invite
+ mInvitesFragment.sendGameInvite(
+ getString(com.google.android.apps.santatracker.common.R.string.memory),
+ getString(com.google.android.apps.playgames.R.string.memory_game_id),
+ mMatchScore);
+ }
+ }
+
+ private void resetGameState() {
+ mLevelNumber = 1;
+ mTimeLeftInMillis = GameConstants.MATCH_INIT_TIME;
+ setUpLevel();
+ mMatchScore = 0;
+ }
+
+ /**
+ * Hides the cards of the previous turn. Should be called when the previous turn was an
+ * incorrect match.
+ */
+ private void hideBothPreviousCards() {
+ if (mVisibleCard1 != null && mVisibleCard2 != null) {
+ hideCard(mVisibleCard1);
+ hideCard(mVisibleCard2);
+ mVisibleCard2.setOnClickListener(this);
+ mVisibleCard1.setOnClickListener(this);
+ mVisibleCard1 = null;
+ mVisibleCard2 = null;
+ }
+ }
+
+ /**
+ * Handles onClick events for views that represent cards. Unveils the card and checks for a
+ * match if another card has already been unveiled.
+ */
+ private void onCardClick(View view) {
+ MemoryCard card1 = (MemoryCard) view.getTag();
+ if (mVisibleCard1 != null && mVisibleCard2 != null) {
+ // Two cards are already unveiled, hide them both
+ // This is also triggered by a timer but this occurs if the user
+ // plays the next turn before the timer is up.
+ hideBothPreviousCards();
+ mVisibleCard1 = view;
+ mVisibleCard1.setOnClickListener(null);
+ showCard(mVisibleCard1);
+ } else if (mVisibleCard1 != null && mVisibleCard2 == null) {
+ // One card is already unveiled and a second one has been selected
+ MemoryCard card2 = (MemoryCard) mVisibleCard1.getTag();
+
+ if (card1.mCardImageId == card2.mCardImageId) {
+ // The second card matches the face of the first one - we have a winner!
+ mVisibleCard2 = view;
+ mVisibleCard2.setOnClickListener(null);
+ mVisibleCard1.setOnClickListener(null);
+ showCard(view);
+ if (mVisibleCard1.findViewById(R.id.card_cover).getVisibility() != View.GONE) {
+ // Play an animation to flash the background of both cards in green
+ mVisibleCard1.findViewById(R.id.card_cover).setBackgroundColor(Color.GREEN);
+ mVisibleCard2.findViewById(R.id.card_cover).setBackgroundColor(Color.GREEN);
+ mVisibleCard1.findViewById(R.id.card_cover).clearAnimation();
+ mVisibleCard2.findViewById(R.id.card_cover).clearAnimation();
+ mVisibleCard1.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
+ mVisibleCard2.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
+ mAnimationCardCover.setAnimationListener(this);
+ mHiddenCard1 = mVisibleCard1;
+ mHiddenCard2 = mVisibleCard2;
+ mVisibleCard1.findViewById(R.id.card_cover).startAnimation(mAnimationCardCover);
+ mVisibleCard2.findViewById(R.id.card_cover).startAnimation(mAnimationCardCover);
+
+ mVisibleCard2 = null;
+ mVisibleCard1 = null;
+
+ // Add the cards to the tally of correct cards
+ mCurrentCorrectMoves++;
+ increaseScoreMatch();
+
+ // Check if this level is finished
+ checkNextLevel();
+ }
+ } else {
+ // The second card does not match the first one - this is not a match.
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundMatchWrong);
+ }
+ mWrongAnswers++;
+ mVisibleCard2 = view;
+ mVisibleCard2.setOnClickListener(null);
+ showCard(mVisibleCard2);
+ // Play an animation to flash the background of both cards in red
+ mVisibleCard1.findViewById(R.id.card_cover).setBackgroundColor(Color.RED);
+ mVisibleCard2.findViewById(R.id.card_cover).setBackgroundColor(Color.RED);
+ mVisibleCard1.findViewById(R.id.card_cover).clearAnimation();
+ mVisibleCard2.findViewById(R.id.card_cover).clearAnimation();
+ mVisibleCard1.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
+ mVisibleCard2.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
+ mAnimationCardCover.setAnimationListener(null);
+ mVisibleCard1.findViewById(R.id.card_cover).startAnimation(mAnimationCardCover);
+ mVisibleCard2.findViewById(R.id.card_cover).startAnimation(mAnimationCardCover);
+
+ // 1.5 seconds after this turn was deemed incorrect, hide both cards.
+ mDoorCloseHandler.postDelayed(mDoorCloseRunnable, DOOR_CLOSE_DELAY_MILLIS);
+ mDoorCloseTimerStart = System.currentTimeMillis();
+ mDoorCloseTimerTicking = true;
+ }
+ } else {
+ // This is the first card that has been unveiled.
+ mVisibleCard1 = view;
+ mVisibleCard1.setOnClickListener(null);
+ showCard(mVisibleCard1);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mDoorCloseHandler != null && mDoorCloseRunnable != null) {
+ mDoorCloseHandler.removeCallbacks(mDoorCloseRunnable);
+ }
+ super.onStop();
+ }
+
+ /** Advances the game to the next level if all matching pairs have been unveiled. */
+ private void checkNextLevel() {
+ if (mCurrentCorrectMoves >= mCorrectMovesRequired) {
+ // Increment the level count
+ increaseScoreLevel();
+ mLevelNumber++;
+ mLevelNumberText.setLevelNumber(mLevelNumber);
+ // Start the 'next level' animation after a short delay.
+ mDelayHandler.postDelayed(
+ new Runnable() {
+ @Override
+ public void run() {
+ mLevelNumberText.startAnimation(mAnimationLevelScaleUp);
+ mEndLevelCircle.startAnimation(mAnimationScaleLevelDown);
+ setUpLevel();
+ }
+ },
+ 750);
+ }
+ }
+
+ private void exit() {
+ getActivity().finish();
+ }
+
+ private void resumeGame() {
+ mButtonPause.setVisibility(View.VISIBLE);
+ mButtonPlay.setVisibility(View.GONE);
+ mCountDownTimer = new GameCountdown(mTimeLeftInMillis, mCountDownInterval);
+
+ // If the user had gotten the previous turn incorrect and the CloseDoor timer was
+ // ticking before the mute, continue the timer.
+ if (mDoorCloseHandler != null && mDoorCloseRunnable != null && mDoorCloseTimerTicking) {
+ mDoorCloseHandler.postDelayed(mDoorCloseRunnable, mDoorCloseTimeRemaining);
+ mDoorCloseTimerStart = System.currentTimeMillis();
+ }
+ mCountDownTimer.start();
+ mViewPauseOverlay.setVisibility(View.GONE);
+ mButtonCancelBar.setVisibility(View.GONE);
+ ImmersiveModeHelper.setImmersiveSticky(getActivity().getWindow());
+ }
+
+ /** CountDownTimer that handles the game timing logic of the memory match game. */
+ public class GameCountdown extends CountDownTimer {
+
+ private Boolean animationStarted = false;
+
+ public GameCountdown(long millisInFuture, long countDownInterval) {
+ super(millisInFuture, countDownInterval);
+ }
+
+ @Override
+ public void onFinish() {
+ // When the countdown is over, end the game.
+ mTimerTextView.clearAnimation();
+ animationStarted = false;
+ mTimerTextView.setTextColor(Color.WHITE);
+ mTimerTextView.setTypeface(Typeface.DEFAULT);
+ if (mViewPlayAgainBackground.getVisibility() != View.VISIBLE && !wasPaused) {
+ submitScore(GameConstants.LEADERBOARDS_MATCH, mMatchScore);
+ stopBackgroundMusic();
+
+ // Show the 'play again' screen.
+ mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
+ mViewPlayAgainBackground.startAnimation(mAnimationPlayAgainBackground);
+ mViewPlayAgainMain.startAnimation(mAnimationPlayAgainMain);
+ mViewPlayAgainBackground.setVisibility(View.VISIBLE);
+ mViewPlayAgainMain.setVisibility(View.VISIBLE);
+ mButtonMenu.setVisibility(View.VISIBLE);
+ mInviteButton.setVisibility(View.VISIBLE);
+ setSignInButtonVisibility(true);
+
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundGameOver, 0.2f);
+ }
+ }
+ }
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+
+ long seconds = TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished);
+ if (seconds >= 6) {
+ animationStarted = false;
+ mTimerTextView.clearAnimation();
+ mTimerTextView.setTypeface(Typeface.DEFAULT);
+ mTimerTextView.setTextColor(Color.WHITE);
+ } else if (!animationStarted) {
+ // Start flashing the countdown time
+ animationStarted = true;
+ mTimerTextView.setTypeface(Typeface.DEFAULT_BOLD);
+ mTimerTextView.setTextColor(Color.RED);
+ mTimerTextView.clearAnimation();
+ mTimerTextView.startAnimation(mAnimationTimerAlpha);
+ }
+
+ // Update the displayed countdown time
+ mTimeLeftInMillis = millisUntilFinished;
+ mTimerTextView.setText(
+ String.format(
+ "%d:%02d",
+ TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished),
+ seconds
+ - TimeUnit.MINUTES.toSeconds(
+ TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished))));
+ }
+ }
+
+ /**
+ * Increases the current score when a match is found. The score is based on the current level.
+ */
+ private void increaseScoreMatch() {
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundMatchRight);
+ }
+ mMatchScore += (50 * (Math.pow(1.1, mLevelNumber - 1)));
+ mScoreText.setText(String.valueOf(mMatchScore));
+ mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
+ }
+
+ /** Increases the current score when advancing to the next level. */
+ private void increaseScoreLevel() {
+ mMatchScore += (500 * (Math.pow(1.1, mLevelNumber - 1)));
+ if (mLevelNumber > 2 && mWrongAnswers == 0) {
+ // Show an amazing bonus animation if this the player is particularly skillful ;)
+ mViewBonusSnowman.startAnimation(mAnimationSnowman);
+ }
+ mScoreText.setText(String.valueOf(mMatchScore));
+ mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // The game is clickable again now that the animation has ended
+ isClickable = true;
+ mClickabilityHandler.removeCallbacksAndMessages(null);
+
+ if (animation == mAnimationScaleLevelDown) {
+ // After the scale level down animation, fade out the end level circle
+ mLevelNumberText.startAnimation(mAnimationLevelFadeOut);
+ mEndLevelCircle.startAnimation(mAnimationLevelFadeOut);
+ } else if (animation == mAnimationLevelFadeOut) {
+ // Hide the end level circle after the animation has finished
+ mEndLevelCircle.clearAnimation();
+ mLevelNumberText.clearAnimation();
+ mLevelNumberText.setVisibility(View.GONE);
+ mEndLevelCircle.setVisibility(View.GONE);
+ } else if (animation == mAnimationSetSnowman) {
+ mViewBonusSnowman.clearAnimation();
+ mViewBonusSnowman.setVisibility(View.GONE);
+ } else if (animation == mAnimationCardCover) {
+ // Reset the state and animations of both cards after they have been hidden again
+ mHiddenCard1.clearAnimation();
+ mHiddenCard2.clearAnimation();
+
+ mHiddenCard1.findViewById(R.id.card_pane_right).clearAnimation();
+ mHiddenCard1.findViewById(R.id.card_pane_left).clearAnimation();
+ mHiddenCard2.findViewById(R.id.card_pane_right).clearAnimation();
+ mHiddenCard2.findViewById(R.id.card_pane_left).clearAnimation();
+ mHiddenCard1.findViewById(R.id.card_pane_right).setVisibility(View.GONE);
+ mHiddenCard1.findViewById(R.id.card_pane_left).setVisibility(View.GONE);
+ mHiddenCard2.findViewById(R.id.card_pane_right).setVisibility(View.GONE);
+ mHiddenCard2.findViewById(R.id.card_pane_left).setVisibility(View.GONE);
+ mHiddenCard1.findViewById(R.id.card_cover).setBackgroundColor(Color.TRANSPARENT);
+ mHiddenCard2.findViewById(R.id.card_cover).setBackgroundColor(Color.TRANSPARENT);
+ mHiddenCard1.findViewById(R.id.card_cover).setVisibility(View.GONE);
+ mHiddenCard2.findViewById(R.id.card_cover).setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {}
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ // Mark the game as not clickable when an animation is in progress
+ isClickable = false;
+
+ // Mark the correct views as visible before the start of an animation.
+ if (animation == mAnimationScaleLevelDown) {
+ mEndLevelCircle.setVisibility(View.VISIBLE);
+ mLevelNumberText.setVisibility(View.VISIBLE);
+ } else if (animation == mAnimationPlayAgainBackground) {
+ mViewPlayAgainBackground.setVisibility(View.VISIBLE);
+ } else if (animation == mAnimationPlayAgainMain) {
+ mViewPlayAgainMain.setVisibility(View.VISIBLE);
+ setSignInButtonVisibility(true);
+ } else if (animation == mAnimationSetSnowman) {
+ mViewBonusSnowman.setVisibility(View.VISIBLE);
+ mViewBonusSnowman.postDelayed(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (!mSantaPreferences.isMuted()) {
+ SoundPoolUtils.playSoundEffect(mSoundPool, mSoundBeep);
+ }
+ }
+ },
+ 800);
+ }
+
+ // Set a clickability timeout
+ mClickabilityHandler.postDelayed(mMakeClickableRunnable, ANIMATION_TIMEOUT);
+ }
+
+ @Override
+ public void invalidateDrawable(Drawable who) {}
+
+ @Override
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {}
+
+ @Override
+ public void unscheduleDrawable(Drawable who, Runnable what) {}
+
+ /** Pauses the game when the back key is pressed. */
+ public void onBackKeyPressed() {
+ if (mViewPlayAgainMain.getVisibility() == View.VISIBLE) {
+ exit();
+ } else {
+ if (mButtonPause.getVisibility() != View.GONE) { // check if already handled
+ pauseGame();
+ } else {
+ // Exit the game.
+ exit();
+ }
+ }
+ }
+
+ private void pauseGame() {
+ mButtonPause.setVisibility(View.GONE);
+ mButtonPlay.setVisibility(View.VISIBLE);
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+
+ // If the user got the previous turn incorrect and the CloseDoor timer is ticking,
+ // stop the CloseDoor timer but remember how much time is left on it.
+ if (mDoorCloseHandler != null && mDoorCloseRunnable != null && mDoorCloseTimerTicking) {
+ mDoorCloseHandler.removeCallbacks(mDoorCloseRunnable);
+ mDoorCloseTimeRemaining =
+ DOOR_CLOSE_DELAY_MILLIS - (System.currentTimeMillis() - mDoorCloseTimerStart);
+ }
+ mViewPauseOverlay.setVisibility(View.VISIBLE);
+ mButtonCancelBar.setVisibility(View.VISIBLE);
+ ImmersiveModeHelper.setImmersiveStickyWithActionBar(getActivity().getWindow());
+ }
+
+ private void onMuteChanged(boolean muted) {
+ if (muted) {
+ stopBackgroundMusic();
+ mSoundPool.autoPause();
+ } else {
+ loadBackgroundMusic();
+ mSoundPool.autoResume();
+ }
+ }
+
+ /** Submit score to Play Games services and the leader board. */
+ private void submitScore(int resId, int score) {
+ PlayGamesActivity act = Utils.getPlayGamesActivity(this);
+ if (act != null) {
+ act.postSubmitScore(resId, score);
+ }
+ }
+}
diff --git a/memory/src/main/res/anim/left_pane_slide_out.xml b/memory/src/main/res/anim/left_pane_slide_out.xml
new file mode 100644
index 000000000..f4a02a47a
--- /dev/null
+++ b/memory/src/main/res/anim/left_pane_slide_out.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/memory/src/main/res/drawable-hdpi/mmg_background_left.webp b/memory/src/main/res/drawable-hdpi/mmg_background_left.webp
new file mode 100644
index 000000000..88af4669b
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_background_left.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_background_right.webp b/memory/src/main/res/drawable-hdpi/mmg_background_right.webp
new file mode 100644
index 000000000..e48a5edf6
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_background_right.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_background_top.webp b/memory/src/main/res/drawable-hdpi/mmg_background_top.webp
new file mode 100644
index 000000000..f7128c824
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_background_top.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_ball.webp b/memory/src/main/res/drawable-hdpi/mmg_card_ball.webp
new file mode 100644
index 000000000..af28e3cef
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_ball.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_balloon.webp b/memory/src/main/res/drawable-hdpi/mmg_card_balloon.webp
new file mode 100644
index 000000000..f95197adb
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_balloon.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_beachball.webp b/memory/src/main/res/drawable-hdpi/mmg_card_beachball.webp
new file mode 100644
index 000000000..4c7ae8dee
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_beachball.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_candle.webp b/memory/src/main/res/drawable-hdpi/mmg_card_candle.webp
new file mode 100644
index 000000000..48fb9c28c
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_candle.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_cloak_blue_dark.webp b/memory/src/main/res/drawable-hdpi/mmg_card_cloak_blue_dark.webp
new file mode 100644
index 000000000..0e904d6eb
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_cloak_blue_dark.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_cloak_blue_light.webp b/memory/src/main/res/drawable-hdpi/mmg_card_cloak_blue_light.webp
new file mode 100644
index 000000000..2b110cf9b
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_cloak_blue_light.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_cloak_orange.webp b/memory/src/main/res/drawable-hdpi/mmg_card_cloak_orange.webp
new file mode 100644
index 000000000..e42a3a94b
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_cloak_orange.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_cloak_purple.webp b/memory/src/main/res/drawable-hdpi/mmg_card_cloak_purple.webp
new file mode 100644
index 000000000..ca1f42d72
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_cloak_purple.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_cloak_red.webp b/memory/src/main/res/drawable-hdpi/mmg_card_cloak_red.webp
new file mode 100644
index 000000000..05169e4e3
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_cloak_red.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_frame.webp b/memory/src/main/res/drawable-hdpi/mmg_card_frame.webp
new file mode 100644
index 000000000..9678c6a99
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_frame.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_globe.webp b/memory/src/main/res/drawable-hdpi/mmg_card_globe.webp
new file mode 100644
index 000000000..30a131f71
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_globe.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_gumball.webp b/memory/src/main/res/drawable-hdpi/mmg_card_gumball.webp
new file mode 100644
index 000000000..d4fcb77b4
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_gumball.webp differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_locked.jpg b/memory/src/main/res/drawable-hdpi/mmg_card_locked.jpg
similarity index 100%
rename from santa-tracker/src/main/res/drawable-hdpi/mmg_card_locked.jpg
rename to memory/src/main/res/drawable-hdpi/mmg_card_locked.jpg
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_penguin.webp b/memory/src/main/res/drawable-hdpi/mmg_card_penguin.webp
new file mode 100644
index 000000000..b320f0ee0
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_penguin.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_rabbit.webp b/memory/src/main/res/drawable-hdpi/mmg_card_rabbit.webp
new file mode 100644
index 000000000..099dc2964
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_rabbit.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_reindeer.webp b/memory/src/main/res/drawable-hdpi/mmg_card_reindeer.webp
new file mode 100644
index 000000000..e5689f562
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_reindeer.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_snowman.webp b/memory/src/main/res/drawable-hdpi/mmg_card_snowman.webp
new file mode 100644
index 000000000..9b04934ba
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_snowman.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_tree.webp b/memory/src/main/res/drawable-hdpi/mmg_card_tree.webp
new file mode 100644
index 000000000..572c362c5
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_tree.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_card_trophy.webp b/memory/src/main/res/drawable-hdpi/mmg_card_trophy.webp
new file mode 100644
index 000000000..7f9da445d
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_card_trophy.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_pane_left.webp b/memory/src/main/res/drawable-hdpi/mmg_pane_left.webp
new file mode 100644
index 000000000..ffffab85d
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_pane_left.webp differ
diff --git a/memory/src/main/res/drawable-hdpi/mmg_pane_right.webp b/memory/src/main/res/drawable-hdpi/mmg_pane_right.webp
new file mode 100644
index 000000000..8ce549b39
Binary files /dev/null and b/memory/src/main/res/drawable-hdpi/mmg_pane_right.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_background_left.webp b/memory/src/main/res/drawable-mdpi/mmg_background_left.webp
new file mode 100644
index 000000000..c03158b71
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_background_left.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_background_right.webp b/memory/src/main/res/drawable-mdpi/mmg_background_right.webp
new file mode 100644
index 000000000..2055ee0b7
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_background_right.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_background_top.webp b/memory/src/main/res/drawable-mdpi/mmg_background_top.webp
new file mode 100644
index 000000000..3d77c721f
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_background_top.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_ball.webp b/memory/src/main/res/drawable-mdpi/mmg_card_ball.webp
new file mode 100644
index 000000000..0f89451a4
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_ball.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_balloon.webp b/memory/src/main/res/drawable-mdpi/mmg_card_balloon.webp
new file mode 100644
index 000000000..743ccf7a9
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_balloon.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_beachball.webp b/memory/src/main/res/drawable-mdpi/mmg_card_beachball.webp
new file mode 100644
index 000000000..c67b562f1
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_beachball.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_candle.webp b/memory/src/main/res/drawable-mdpi/mmg_card_candle.webp
new file mode 100644
index 000000000..1b323cc1a
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_candle.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_cloak_blue_dark.webp b/memory/src/main/res/drawable-mdpi/mmg_card_cloak_blue_dark.webp
new file mode 100644
index 000000000..d42d2daac
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_cloak_blue_dark.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_cloak_blue_light.webp b/memory/src/main/res/drawable-mdpi/mmg_card_cloak_blue_light.webp
new file mode 100644
index 000000000..dc137d5f2
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_cloak_blue_light.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_cloak_orange.webp b/memory/src/main/res/drawable-mdpi/mmg_card_cloak_orange.webp
new file mode 100644
index 000000000..abc7de5e3
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_cloak_orange.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_cloak_purple.webp b/memory/src/main/res/drawable-mdpi/mmg_card_cloak_purple.webp
new file mode 100644
index 000000000..de6143a5a
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_cloak_purple.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_cloak_red.webp b/memory/src/main/res/drawable-mdpi/mmg_card_cloak_red.webp
new file mode 100644
index 000000000..48f3f426e
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_cloak_red.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_frame.webp b/memory/src/main/res/drawable-mdpi/mmg_card_frame.webp
new file mode 100644
index 000000000..1e83182ff
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_frame.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_globe.webp b/memory/src/main/res/drawable-mdpi/mmg_card_globe.webp
new file mode 100644
index 000000000..96f150c30
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_globe.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_gumball.webp b/memory/src/main/res/drawable-mdpi/mmg_card_gumball.webp
new file mode 100644
index 000000000..37a84e2e6
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_gumball.webp differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_locked.jpg b/memory/src/main/res/drawable-mdpi/mmg_card_locked.jpg
similarity index 100%
rename from santa-tracker/src/main/res/drawable-mdpi/mmg_card_locked.jpg
rename to memory/src/main/res/drawable-mdpi/mmg_card_locked.jpg
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_penguin.webp b/memory/src/main/res/drawable-mdpi/mmg_card_penguin.webp
new file mode 100644
index 000000000..525a8d8bd
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_penguin.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_rabbit.webp b/memory/src/main/res/drawable-mdpi/mmg_card_rabbit.webp
new file mode 100644
index 000000000..8e35a77e6
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_rabbit.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_reindeer.webp b/memory/src/main/res/drawable-mdpi/mmg_card_reindeer.webp
new file mode 100644
index 000000000..2cf84971d
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_reindeer.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_snowman.webp b/memory/src/main/res/drawable-mdpi/mmg_card_snowman.webp
new file mode 100644
index 000000000..9c6a5f0c5
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_snowman.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_tree.webp b/memory/src/main/res/drawable-mdpi/mmg_card_tree.webp
new file mode 100644
index 000000000..06b870247
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_tree.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_card_trophy.webp b/memory/src/main/res/drawable-mdpi/mmg_card_trophy.webp
new file mode 100644
index 000000000..4b0273c01
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_card_trophy.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_pane_left.webp b/memory/src/main/res/drawable-mdpi/mmg_pane_left.webp
new file mode 100644
index 000000000..bf6378468
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_pane_left.webp differ
diff --git a/memory/src/main/res/drawable-mdpi/mmg_pane_right.webp b/memory/src/main/res/drawable-mdpi/mmg_pane_right.webp
new file mode 100644
index 000000000..3a9b4a46a
Binary files /dev/null and b/memory/src/main/res/drawable-mdpi/mmg_pane_right.webp differ
diff --git a/memory/src/main/res/drawable-nodpi/snowman_face.webp b/memory/src/main/res/drawable-nodpi/snowman_face.webp
new file mode 100644
index 000000000..ae0628bf2
Binary files /dev/null and b/memory/src/main/res/drawable-nodpi/snowman_face.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_background_left.webp b/memory/src/main/res/drawable-xhdpi/mmg_background_left.webp
new file mode 100644
index 000000000..505d81612
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_background_left.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_background_right.webp b/memory/src/main/res/drawable-xhdpi/mmg_background_right.webp
new file mode 100644
index 000000000..3e0a246df
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_background_right.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_background_top.webp b/memory/src/main/res/drawable-xhdpi/mmg_background_top.webp
new file mode 100644
index 000000000..87c22368a
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_background_top.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_ball.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_ball.webp
new file mode 100644
index 000000000..8598aae92
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_ball.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_balloon.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_balloon.webp
new file mode 100644
index 000000000..1a7cd76ba
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_balloon.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_beachball.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_beachball.webp
new file mode 100644
index 000000000..e36e79a48
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_beachball.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_candle.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_candle.webp
new file mode 100644
index 000000000..1ff533bbb
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_candle.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_dark.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_dark.webp
new file mode 100644
index 000000000..bb8f8673d
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_dark.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_light.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_light.webp
new file mode 100644
index 000000000..a40109a15
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_light.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_orange.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_orange.webp
new file mode 100644
index 000000000..790a97f04
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_orange.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_purple.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_purple.webp
new file mode 100644
index 000000000..cbb9d274e
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_purple.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_red.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_red.webp
new file mode 100644
index 000000000..bafa4a987
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_cloak_red.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_frame.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_frame.webp
new file mode 100644
index 000000000..3a6123912
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_frame.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_globe.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_globe.webp
new file mode 100644
index 000000000..da848a719
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_globe.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_gumball.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_gumball.webp
new file mode 100644
index 000000000..a46ae0513
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_gumball.webp differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_locked.jpg b/memory/src/main/res/drawable-xhdpi/mmg_card_locked.jpg
similarity index 100%
rename from santa-tracker/src/main/res/drawable-xhdpi/mmg_card_locked.jpg
rename to memory/src/main/res/drawable-xhdpi/mmg_card_locked.jpg
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_penguin.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_penguin.webp
new file mode 100644
index 000000000..8e2dd1e3b
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_penguin.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_rabbit.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_rabbit.webp
new file mode 100644
index 000000000..e45cb0367
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_rabbit.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_reindeer.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_reindeer.webp
new file mode 100644
index 000000000..a6401237c
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_reindeer.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_snowman.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_snowman.webp
new file mode 100644
index 000000000..ca6625f71
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_snowman.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_tree.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_tree.webp
new file mode 100644
index 000000000..279f2e4e9
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_tree.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_card_trophy.webp b/memory/src/main/res/drawable-xhdpi/mmg_card_trophy.webp
new file mode 100644
index 000000000..88aae8735
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_card_trophy.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_pane_left.webp b/memory/src/main/res/drawable-xhdpi/mmg_pane_left.webp
new file mode 100644
index 000000000..b730421d9
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_pane_left.webp differ
diff --git a/memory/src/main/res/drawable-xhdpi/mmg_pane_right.webp b/memory/src/main/res/drawable-xhdpi/mmg_pane_right.webp
new file mode 100644
index 000000000..7fa2cb29f
Binary files /dev/null and b/memory/src/main/res/drawable-xhdpi/mmg_pane_right.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_background_left.webp b/memory/src/main/res/drawable-xxhdpi/mmg_background_left.webp
new file mode 100644
index 000000000..14bce2369
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_background_left.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_background_right.webp b/memory/src/main/res/drawable-xxhdpi/mmg_background_right.webp
new file mode 100644
index 000000000..39297d08c
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_background_right.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_background_top.webp b/memory/src/main/res/drawable-xxhdpi/mmg_background_top.webp
new file mode 100644
index 000000000..548caccab
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_background_top.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_ball.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_ball.webp
new file mode 100644
index 000000000..0b87c4669
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_ball.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_balloon.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_balloon.webp
new file mode 100644
index 000000000..395fb9997
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_balloon.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_beachball.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_beachball.webp
new file mode 100644
index 000000000..ce319436d
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_beachball.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_candle.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_candle.webp
new file mode 100644
index 000000000..510e6d9b0
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_candle.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_dark.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_dark.webp
new file mode 100644
index 000000000..f58ba778d
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_dark.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_light.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_light.webp
new file mode 100644
index 000000000..04ef21fc4
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_light.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_orange.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_orange.webp
new file mode 100644
index 000000000..7160ef47c
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_orange.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_purple.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_purple.webp
new file mode 100644
index 000000000..cd0150300
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_purple.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_red.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_red.webp
new file mode 100644
index 000000000..00418b025
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_cloak_red.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_frame.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_frame.webp
new file mode 100644
index 000000000..322e0cf0e
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_frame.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_globe.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_globe.webp
new file mode 100644
index 000000000..0114ac733
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_globe.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_gumball.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_gumball.webp
new file mode 100644
index 000000000..fdab7a1ee
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_gumball.webp differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_locked.jpg b/memory/src/main/res/drawable-xxhdpi/mmg_card_locked.jpg
similarity index 100%
rename from santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_locked.jpg
rename to memory/src/main/res/drawable-xxhdpi/mmg_card_locked.jpg
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_penguin.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_penguin.webp
new file mode 100644
index 000000000..ca40d1815
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_penguin.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_rabbit.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_rabbit.webp
new file mode 100644
index 000000000..e426dd07a
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_rabbit.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_reindeer.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_reindeer.webp
new file mode 100644
index 000000000..30da95fba
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_reindeer.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_snowman.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_snowman.webp
new file mode 100644
index 000000000..fcd91e3b8
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_snowman.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_tree.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_tree.webp
new file mode 100644
index 000000000..19af49b90
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_tree.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_card_trophy.webp b/memory/src/main/res/drawable-xxhdpi/mmg_card_trophy.webp
new file mode 100644
index 000000000..1257b90bb
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_card_trophy.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_pane_left.webp b/memory/src/main/res/drawable-xxhdpi/mmg_pane_left.webp
new file mode 100644
index 000000000..6f69930c9
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_pane_left.webp differ
diff --git a/memory/src/main/res/drawable-xxhdpi/mmg_pane_right.webp b/memory/src/main/res/drawable-xxhdpi/mmg_pane_right.webp
new file mode 100644
index 000000000..efbc8f404
Binary files /dev/null and b/memory/src/main/res/drawable-xxhdpi/mmg_pane_right.webp differ
diff --git a/memory/src/main/res/layout/activity_memory.xml b/memory/src/main/res/layout/activity_memory.xml
new file mode 100644
index 000000000..14c24334c
--- /dev/null
+++ b/memory/src/main/res/layout/activity_memory.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/memory/src/main/res/layout/fragment_memory_match.xml b/memory/src/main/res/layout/fragment_memory_match.xml
new file mode 100644
index 000000000..d6e122c03
--- /dev/null
+++ b/memory/src/main/res/layout/fragment_memory_match.xml
@@ -0,0 +1,268 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/item_memory_card.xml b/memory/src/main/res/layout/item_memory_card.xml
similarity index 88%
rename from santa-tracker/src/main/res/layout/item_memory_card.xml
rename to memory/src/main/res/layout/item_memory_card.xml
index 4c09930aa..262528cf2 100644
--- a/santa-tracker/src/main/res/layout/item_memory_card.xml
+++ b/memory/src/main/res/layout/item_memory_card.xml
@@ -1,12 +1,12 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/memory/src/main/res/values/styles.xml b/memory/src/main/res/values/styles.xml
new file mode 100644
index 000000000..6c65774ee
--- /dev/null
+++ b/memory/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/penguinswim/build.gradle b/penguinswim/build.gradle
new file mode 100644
index 000000000..70acb9f7b
--- /dev/null
+++ b/penguinswim/build.gradle
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.dynamic-feature'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.tools
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ }
+}
+
+dependencies {
+ implementation project(':santa-tracker')
+ implementation rootProject.ext.supportAnnotations
+}
diff --git a/penguinswim/src/main/AndroidManifest.xml b/penguinswim/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..fc4e91c2c
--- /dev/null
+++ b/penguinswim/src/main/AndroidManifest.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/BoundingBoxSpriteActor.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/BoundingBoxSpriteActor.java
new file mode 100644
index 000000000..5e905bef5
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/BoundingBoxSpriteActor.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import com.google.android.apps.santatracker.doodles.shared.Debug;
+import com.google.android.apps.santatracker.doodles.shared.Touchable;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import com.google.android.apps.santatracker.doodles.shared.actor.Actor;
+import com.google.android.apps.santatracker.doodles.shared.actor.SpriteActor;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite;
+import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
+import com.google.android.apps.santatracker.doodles.shared.physics.Util;
+import com.google.android.apps.santatracker.util.SantaLog;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** A sprite actor which contains a pre-set convex collision body. */
+public class BoundingBoxSpriteActor extends CollisionActor implements Touchable {
+ public static final String DUCK = "duck";
+ public static final String ICE_CUBE = "cube1";
+ public static final String HAND_GRAB = "hand grab";
+ public static final Map TYPE_TO_RESOURCE_MAP;
+ protected static final float SCALE = 2;
+ private static final String TAG = BoundingBoxSpriteActor.class.getSimpleName();
+ private static final Random RANDOM = new Random();
+
+ static {
+ TYPE_TO_RESOURCE_MAP = new HashMap<>();
+
+ Vector2D[] duckVertexOffsets = {
+ Vector2D.get(0, 0), Vector2D.get(87, 0), Vector2D.get(87, 186), Vector2D.get(0, 186),
+ };
+ TYPE_TO_RESOURCE_MAP.put(
+ DUCK,
+ new Data(
+ PenguinSwimSprites.penguin_swim_elf,
+ 1,
+ Vector2D.get(0, 0).scale(SCALE),
+ duckVertexOffsets));
+
+ Vector2D[] iceCube1VertexOffsets = {
+ Vector2D.get(0, 0), Vector2D.get(101.9f, 0),
+ Vector2D.get(101.9f, 100.2f), Vector2D.get(0, 100.2f)
+ };
+ TYPE_TO_RESOURCE_MAP.put(
+ ICE_CUBE,
+ new Data(
+ PenguinSwimSprites.penguin_swim_ice,
+ 1,
+ Vector2D.get(0, 0).scale(SCALE),
+ iceCube1VertexOffsets));
+
+ // This is just a placeholder so that we can create hand grabs programatically. This data
+ // shouldn't actually be used.
+ TYPE_TO_RESOURCE_MAP.put(
+ HAND_GRAB, new Data(null, 0, Vector2D.get(0, 0).scale(SCALE), null));
+ }
+
+ public final SpriteActor spriteActor;
+ public String type;
+ public Vector2D spriteOffset;
+
+ public BoundingBoxSpriteActor(
+ Polygon collisionBody, SpriteActor spriteActor, Vector2D spriteOffset, String type) {
+ super(collisionBody);
+
+ this.spriteOffset = spriteOffset;
+ this.type = type;
+ this.spriteActor = spriteActor;
+ scale = SCALE;
+ }
+
+ public static BoundingBoxSpriteActor create(
+ Vector2D position, String type, Resources resources) {
+ if (!TYPE_TO_RESOURCE_MAP.containsKey(type)) {
+ SantaLog.e(TAG, "Unknown object type: " + type);
+ return null;
+ }
+ Data data = TYPE_TO_RESOURCE_MAP.get(type);
+
+ BoundingBoxSpriteActor actor;
+ if (type.equals(HAND_GRAB)) {
+ actor = HandGrabActor.create(position, resources);
+ } else {
+ actor =
+ new BoundingBoxSpriteActor(
+ getBoundingBox(position, data.vertexOffsets, SCALE),
+ new SpriteActor(
+ AnimatedSprite.fromFrames(resources, data.resIds),
+ Vector2D.get(position),
+ Vector2D.get(0, 0)),
+ Vector2D.get(data.spriteOffset),
+ type);
+ }
+
+ actor.zIndex = data.zIndex;
+
+ // Start at a random frame index so that all of the sprites aren't synced up.
+ actor.spriteActor.sprite.setFrameIndex(
+ RANDOM.nextInt(actor.spriteActor.sprite.getNumFrames()));
+
+ return actor;
+ }
+
+ public static BoundingBoxSpriteActor fromJSON(JSONObject json, Context context)
+ throws JSONException {
+ String type = json.getString(Actor.TYPE_KEY);
+ Vector2D position =
+ Vector2D.get((float) json.getDouble(X_KEY), (float) json.getDouble(Y_KEY));
+ return create(position, type, context.getResources());
+ }
+
+ protected static Polygon getBoundingBox(
+ Vector2D position, Vector2D[] vertexOffsets, float scale) {
+ List vertices = new ArrayList<>();
+ for (int i = 0; i < vertexOffsets.length; i++) {
+ vertices.add(Vector2D.get(position).add(Vector2D.get(vertexOffsets[i]).scale(scale)));
+ }
+ return new Polygon(vertices);
+ }
+
+ @Override
+ public void update(float deltaMs) {
+ super.update(deltaMs);
+ spriteActor.update(deltaMs);
+ spriteActor.position.set(position.x, position.y);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ spriteActor.draw(
+ canvas,
+ spriteOffset.x,
+ spriteOffset.y,
+ spriteActor.sprite.frameWidth * scale,
+ spriteActor.sprite.frameHeight * scale);
+
+ if (Debug.DRAW_COLLISION_BOUNDS) {
+ collisionBody.draw(canvas);
+ }
+ }
+
+ @Override
+ public boolean canHandleTouchAt(Vector2D worldCoords, float cameraScale) {
+ Vector2D lowerRight =
+ Vector2D.get(position)
+ .add(spriteOffset)
+ .add(
+ spriteActor.sprite.frameWidth * scale,
+ spriteActor.sprite.frameHeight * scale);
+ boolean retVal =
+ super.canHandleTouchAt(worldCoords, cameraScale)
+ || Util.pointIsWithinBounds(
+ Vector2D.get(position).add(spriteOffset), lowerRight, worldCoords);
+
+ lowerRight.release();
+ return retVal;
+ }
+
+ @Override
+ public void startTouchAt(Vector2D worldCoords, float cameraScale) {
+ selectedIndex = collisionBody.getSelectedIndex(worldCoords, cameraScale);
+ }
+
+ @Override
+ public boolean handleMoveEvent(Vector2D delta) {
+ collisionBody.move(-delta.x, -delta.y);
+ position.set(collisionBody.min);
+ // NOTE: Leave this commented-out section here. This is used when adding new
+ // BoundingBoxSpriteActors in order to fine-tune the collision boundaries and sprite
+ // offsets.
+ /*
+ boolean moved;
+ if (selectedIndex >= 0) {
+ collisionBody.moveVertex(selectedIndex, Vector2D.get(delta).scale(-1));
+ SantaLog.d(TAG, "min: " + collisionBody.min);
+ SantaLog.d(TAG, "max: " + collisionBody.max);
+ } else {
+ spriteOffset.subtract(delta);
+ SantaLog.d(TAG, "Sprite offset: " + spriteOffset);
+ }
+ */
+ return true;
+ }
+
+ @Override
+ public boolean handleLongPress() {
+ // NOTE: Leave this commented-out section here. This is used when adding new
+ // BoundingBoxSpriteActors in order to fine-tune the collision boundaries and sprite
+ // offsets.
+ /*
+ if (selectedIndex >= 0) {
+ if (canRemoveCollisionVertex()) {
+ // If we can, just remove the vertex.
+ collisionBody.removeVertexAt(selectedIndex);
+ return true;
+ }
+ } else if (midpointIndex >= 0) {
+ // Long press on a midpoint, add a vertex to the selected obstacle's polygon.
+ collisionBody.addVertexAfter(midpointIndex);
+ return true;
+ }
+ */
+ return false;
+ }
+
+ @Override
+ public boolean resolveCollision(Actor other, float deltaMs) {
+ if (other instanceof SwimmerActor) {
+ return resolveCollisionInternal((SwimmerActor) other);
+ }
+ return false;
+ }
+
+ @Override
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public JSONObject toJSON() throws JSONException {
+ JSONObject json = new JSONObject();
+ json.put(TYPE_KEY, getType());
+ json.put(X_KEY, position.x);
+ json.put(Y_KEY, position.y);
+ return json;
+ }
+
+ protected boolean resolveCollisionInternal(SwimmerActor swimmer) {
+ if (swimmer.isInvincible || swimmer.isUnderwater) {
+ return false;
+ }
+ if (swimmer.collisionBody.min.y > collisionBody.max.y
+ || swimmer.collisionBody.max.y < collisionBody.min.y) {
+ // Perform a short-circuiting check which fails if the swimmer is outside of the
+ // vertical
+ // boundaries of this collision body.
+ return false;
+ }
+
+ // NOTE: We've since removed the diagonal ice cube, so we don't have any
+ // non-axis-aligned rectangles to check collisions with. However, there may still be a few
+ // artifacts of complex polygon collisions in the code.
+
+ // CAN and ICE_CUBE objects are just axis-aligned rectangles. Use the faster
+ // rectangle-to-rectangle collision code in these cases.
+ if (Util.rectIntersectsRect(
+ swimmer.collisionBody.min.x,
+ swimmer.collisionBody.min.y,
+ swimmer.collisionBody.getWidth(),
+ swimmer.collisionBody.getHeight(),
+ collisionBody.min.x,
+ collisionBody.min.y,
+ collisionBody.getWidth(),
+ collisionBody.getHeight())) {
+
+ // If the swimmer is colliding with the side of an obstacle, make the swimmer slide
+ // along it
+ // instead of colliding.
+ if (swimmer.positionBeforeFrame.y < collisionBody.max.y) {
+ swimmer.moveTo(swimmer.positionBeforeFrame.x, swimmer.position.y);
+ } else {
+ swimmer.collide(type);
+ }
+ }
+ return false;
+ }
+
+ /** A utility class which contains data needed to create BoundingBoxSpriteActors. */
+ protected static class Data {
+ public int[] resIds;
+ public int numFrames;
+ public int zIndex;
+ public Vector2D spriteOffset;
+ public Vector2D[] vertexOffsets;
+
+ public Data(int[] resIds, int zIndex, Vector2D spriteOffset, Vector2D[] vertexOffsets) {
+ this.resIds = resIds;
+ this.numFrames = resIds == null ? 0 : resIds.length;
+ this.zIndex = zIndex;
+ this.spriteOffset = spriteOffset;
+ this.vertexOffsets = vertexOffsets;
+ }
+ }
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/CollisionActor.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/CollisionActor.java
new file mode 100644
index 000000000..6c90cfd95
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/CollisionActor.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import android.graphics.Canvas;
+import com.google.android.apps.santatracker.doodles.shared.Debug;
+import com.google.android.apps.santatracker.doodles.shared.Touchable;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import com.google.android.apps.santatracker.doodles.shared.actor.Actor;
+import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * An actor which represents an object in the world which causes some reaction when it is collided
+ * with.
+ */
+public class CollisionActor extends Actor implements Touchable {
+ public static final String TYPE = "collision";
+ // These measurements are in "world units", which are really just arbitrary units where one
+ // world
+ // unit == one pixel at the default camera zoom level.
+ public static final float DEFAULT_WIDTH = 200;
+ public static final float DEFAULT_HEIGHT = 200;
+ protected static final String POLYGON_KEY = "polygon";
+ public Polygon collisionBody;
+
+ protected int selectedIndex = -1;
+ protected int midpointIndex = -1;
+
+ public CollisionActor(Polygon collisionBody) {
+ super(Vector2D.get(collisionBody.min.x, collisionBody.min.y), Vector2D.get());
+ this.collisionBody = collisionBody;
+ restitution = 0.7f;
+ inverseMass = Actor.INFINITE_MASS; // Give collision actors infinite mass.
+ zIndex = 10;
+ }
+
+ public static CollisionActor fromJSON(JSONObject json) throws JSONException {
+ return new CollisionActor(Polygon.fromJSON(json.getJSONArray(POLYGON_KEY)));
+ }
+
+ @Override
+ public void update(float deltaMs) {
+ positionBeforeFrame.set(position);
+ float deltaX = velocity.x * deltaMs / 1000.0f;
+ float deltaY = velocity.y * deltaMs / 1000.0f;
+ collisionBody.move(deltaX, deltaY);
+ position.set(collisionBody.min);
+ }
+
+ @Override
+ public boolean canHandleTouchAt(Vector2D worldCoords, float cameraScale) {
+ return collisionBody.getSelectedIndex(worldCoords, cameraScale) >= 0
+ || collisionBody.getMidpointIndex(worldCoords, cameraScale) >= 0;
+ }
+
+ @Override
+ public void startTouchAt(Vector2D worldCoords, float cameraScale) {
+ selectedIndex = collisionBody.getSelectedIndex(worldCoords, cameraScale);
+ midpointIndex = collisionBody.getMidpointIndex(worldCoords, cameraScale);
+ }
+
+ @Override
+ public boolean handleMoveEvent(Vector2D delta) {
+ if (selectedIndex >= 0) {
+ Vector2D positionDelta = Vector2D.get(delta).scale(-1);
+ collisionBody.moveVertex(selectedIndex, positionDelta);
+ positionDelta.release();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean handleLongPress() {
+ if (selectedIndex >= 0) {
+ if (canRemoveCollisionVertex()) {
+ // If we can, just remove the vertex.
+ collisionBody.removeVertexAt(selectedIndex);
+ return true;
+ }
+ } else if (midpointIndex >= 0) {
+ // Long press on a midpoint, add a vertex to the selected obstacle's polygon.
+ collisionBody.addVertexAfter(midpointIndex);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String getType() {
+ return CollisionActor.TYPE;
+ }
+
+ @Override
+ public JSONObject toJSON() throws JSONException {
+ JSONObject json = new JSONObject();
+ json.put(TYPE_KEY, getType());
+ json.put(POLYGON_KEY, collisionBody.toJSON());
+ return json;
+ }
+
+ /**
+ * Resolve a collision with another physics actor.
+ *
+ * @param other The actor being collided with.
+ * @param deltaMs The length of the collision frame.
+ * @return true if resolving the collision moves the other actor, false otherwise.
+ */
+ public boolean resolveCollision(Actor other, float deltaMs) {
+ return false;
+ }
+
+ public void draw(Canvas canvas) {
+ if (Debug.DRAW_COLLISION_BOUNDS) {
+ collisionBody.draw(canvas);
+ }
+ }
+
+ public boolean canRemoveCollisionVertex() {
+ return collisionBody.vertices.size() > 3;
+ }
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/DistanceMarkerActor.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/DistanceMarkerActor.java
new file mode 100644
index 000000000..be4e25a7d
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/DistanceMarkerActor.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import com.google.android.apps.santatracker.doodles.shared.ColoredRectangleActor;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+
+/** A colored rectangle actor which marks distance in the swimming game. */
+public class DistanceMarkerActor extends ColoredRectangleActor {
+ private static final int HEIGHT = 200;
+
+ public DistanceMarkerActor(int positionInMeters, String type) {
+ this(positionInMeters, type, HEIGHT);
+ }
+
+ public DistanceMarkerActor(int positionInMeters, String type, float height) {
+ super(
+ Vector2D.get(0, SwimmingModel.getWorldYFromMeters(positionInMeters)),
+ Vector2D.get(SwimmingModel.LEVEL_WIDTH, height),
+ type);
+ zIndex = -2;
+ }
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/DiveView.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/DiveView.java
new file mode 100644
index 000000000..dbfddaf89
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/DiveView.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.animation.DecelerateInterpolator;
+import androidx.appcompat.widget.AppCompatImageView;
+import com.google.android.apps.santatracker.doodles.shared.Process;
+import com.google.android.apps.santatracker.doodles.shared.ProcessChain;
+import com.google.android.apps.santatracker.doodles.shared.UIUtil;
+import com.google.android.apps.santatracker.doodles.shared.animation.Interpolator;
+import com.google.android.apps.santatracker.doodles.shared.animation.Tween;
+import com.google.android.apps.santatracker.doodles.shared.views.PauseView;
+import java.util.ArrayList;
+import java.util.List;
+
+/** The dive cooldown UI for the swimming game. */
+public class DiveView extends AppCompatImageView {
+ private static final int CLOCK_DURATION_MS =
+ SwimmerActor.DIVE_DURATION_MS + SwimmerActor.DIVE_COOLDOWN_MS;
+ private static final float BUMP_SCALE = 1.4f;
+ private static final long BUMP_DURATION_MS = 200;
+
+ private RectF viewBounds;
+ private Paint paint;
+ private Paint imagePaint;
+
+ private float clockAngle;
+ private Bitmap diveArrowBitmap;
+ private Rect diveArrowBounds;
+ private List processChains;
+
+ public DiveView(Context context) {
+ this(context, null);
+ }
+
+ public DiveView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DiveView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setColor(0xffffffff);
+ imagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ imagePaint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP));
+ viewBounds = new RectF(0, 0, getWidth(), getHeight());
+ diveArrowBitmap =
+ BitmapFactory.decodeResource(getResources(), R.drawable.swimming_dive_arrow);
+ diveArrowBounds = new Rect(0, 0, diveArrowBitmap.getWidth(), diveArrowBitmap.getHeight());
+ processChains = new ArrayList<>();
+
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ float startAngle = -90;
+ float sweepAngle = clockAngle;
+
+ canvas.drawArc(viewBounds, startAngle, sweepAngle, true, paint);
+ canvas.drawBitmap(diveArrowBitmap, diveArrowBounds, viewBounds, imagePaint);
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ viewBounds = new RectF(0, 0, getWidth(), getHeight());
+ }
+
+ public void update(float deltaMs) {
+ ProcessChain.updateChains(processChains, deltaMs);
+ }
+
+ public void startCooldown() {
+ setImageColorSaturation(0);
+ show();
+ Process cooldown =
+ new Tween(CLOCK_DURATION_MS / 1000.0f) {
+ @Override
+ protected void updateValues(float percentDone) {
+ clockAngle = Interpolator.LINEAR.getValue(percentDone, 0, 360);
+ postInvalidate();
+ }
+
+ @Override
+ protected void onFinish() {
+ setImageColorSaturation(1);
+ bump();
+ }
+ }.asProcess();
+ processChains.add(new ProcessChain(cooldown));
+ }
+
+ public void hide() {
+ UIUtil.fadeOutAndHide(this, PauseView.FADE_DURATION_MS);
+ }
+
+ public void show() {
+ clockAngle = 0;
+ UIUtil.showAndFadeIn(this, PauseView.FADE_DURATION_MS);
+ }
+
+ private void bump() {
+ ValueAnimator bumpAnimation =
+ UIUtil.animator(
+ BUMP_DURATION_MS,
+ new DecelerateInterpolator(),
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ final float scale = (float) valueAnimator.getAnimatedValue("scale");
+ post(
+ new Runnable() {
+ @Override
+ public void run() {
+ setScaleX(scale);
+ setScaleY(scale);
+ }
+ });
+ }
+ },
+ UIUtil.floatValue("scale", BUMP_SCALE, 1));
+ bumpAnimation.start();
+ }
+
+ private void setImageColorSaturation(float saturation) {
+ ColorMatrix cm = new ColorMatrix();
+ cm.setSaturation(saturation);
+ imagePaint.setColorFilter(new ColorMatrixColorFilter(cm));
+ }
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/HandGrabActor.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/HandGrabActor.java
new file mode 100644
index 000000000..f18b43f63
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/HandGrabActor.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import com.google.android.apps.santatracker.doodles.shared.EventBus;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import com.google.android.apps.santatracker.doodles.shared.actor.MultiSpriteActor;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite.AnimatedSpriteListener;
+import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
+import java.util.HashMap;
+import java.util.Map;
+
+/** The hand grab obstacle in the swimming game. */
+public class HandGrabActor extends BoundingBoxSpriteActor {
+ private static final String X_SPRITE = "x";
+ private static final String LEMON_GRAB_SPRITE = "lemon grab";
+ private static final String LEMON_GRAB_SPRITE_FLIPPED = "lemon grab flipped";
+
+ private static final float COLLISION_DISTANCE_THRESHOLD = 100;
+ private static final Vector2D[] VERTEX_OFFSETS = {
+ Vector2D.get(0, 0),
+ Vector2D.get(110f, 0),
+ Vector2D.get(110f, 146.0f),
+ Vector2D.get(0, 146.0f)
+ };
+
+ private static final Map OFFSET_MAP;
+
+ static {
+ OFFSET_MAP = new HashMap<>();
+ OFFSET_MAP.put(X_SPRITE, Vector2D.get());
+ OFFSET_MAP.put(LEMON_GRAB_SPRITE, Vector2D.get(-100, -100));
+ OFFSET_MAP.put(LEMON_GRAB_SPRITE_FLIPPED, Vector2D.get(100, -100));
+ }
+
+ public HandGrabActor(Polygon collisionBody, MultiSpriteActor spriteActor) {
+ super(
+ collisionBody,
+ spriteActor,
+ Vector2D.get(OFFSET_MAP.get(X_SPRITE)).scale(SwimmerActor.SWIMMER_SCALE),
+ HAND_GRAB);
+ zIndex = -1;
+ scale = SwimmerActor.SWIMMER_SCALE;
+ }
+
+ public static HandGrabActor create(Vector2D position, Resources res) {
+ Map spriteMap = new HashMap<>();
+ boolean shouldFlip = position.x + VERTEX_OFFSETS[1].x / 2 < SwimmingModel.LEVEL_WIDTH / 2;
+
+ AnimatedSprite lemonGrabSprite =
+ AnimatedSprite.fromFrames(res, PenguinSwimSprites.penguin_swim_canegrab);
+ lemonGrabSprite.setLoop(false);
+ lemonGrabSprite.setFlippedX(shouldFlip);
+
+ spriteMap.put(LEMON_GRAB_SPRITE, lemonGrabSprite);
+ spriteMap.put(
+ X_SPRITE, AnimatedSprite.fromFrames(res, PenguinSwimSprites.penguin_swim_candy));
+
+ MultiSpriteActor spriteActor =
+ new MultiSpriteActor(spriteMap, X_SPRITE, position, Vector2D.get(0, 0));
+
+ Polygon boundingBox = getBoundingBox(position, VERTEX_OFFSETS, SwimmerActor.SWIMMER_SCALE);
+
+ return new HandGrabActor(boundingBox, spriteActor);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (hidden) {
+ return;
+ }
+ super.draw(canvas);
+ }
+
+ public void setSprite(String key) {
+ if (key.equals(X_SPRITE)) {
+ zIndex = -1;
+ } else if (key.equals(LEMON_GRAB_SPRITE)) {
+ zIndex = 3;
+ }
+ ((MultiSpriteActor) spriteActor).setSprite(key);
+
+ if (isFlippedX() && key.equals(LEMON_GRAB_SPRITE)) {
+ spriteOffset.set(OFFSET_MAP.get(LEMON_GRAB_SPRITE_FLIPPED)).scale(scale);
+ } else {
+ spriteOffset.set(OFFSET_MAP.get(key)).scale(scale);
+ }
+ }
+
+ public boolean isFlippedX() {
+ return ((MultiSpriteActor) spriteActor).sprites.get(LEMON_GRAB_SPRITE).isFlippedX();
+ }
+
+ @Override
+ protected boolean resolveCollisionInternal(final SwimmerActor swimmer) {
+ if (swimmer.isInvincible || swimmer.isUnderwater) {
+ return false;
+ }
+
+ // Only collide if the two actor's minimum collision coordinates are close together. This is
+ // so that the swimmer will only collide with the hand grab if it is right over the x.
+ if (swimmer.collisionBody.min.distanceTo(collisionBody.min)
+ < COLLISION_DISTANCE_THRESHOLD) {
+ swimmer.collide(type);
+
+ setSprite(LEMON_GRAB_SPRITE);
+ spriteActor.sprite.addListener(
+ new AnimatedSpriteListener() {
+ @Override
+ public void onFrame(int index) {
+ if (index == 10) {
+ swimmer.hidden = true;
+ swimmer.spriteActor.sprite.setPaused(true);
+ EventBus.getInstance().sendEvent(EventBus.VIBRATE);
+ }
+ }
+
+ @Override
+ public void onFinished() {
+ hidden = true;
+ swimmer.isDead = true;
+ }
+ });
+ }
+ return false;
+ }
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/LevelManager.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/LevelManager.java
new file mode 100644
index 000000000..84fa98dd1
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/LevelManager.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import android.content.Context;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.apps.santatracker.doodles.shared.ExternalStoragePermissions;
+import com.google.android.apps.santatracker.doodles.shared.actor.Actor;
+import com.google.android.apps.santatracker.util.SantaLog;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.List;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * A helper class which handles the saving and loading of levels for the doodle games.
+ *
+ * The {@code LevelManager} stores levels in a simple JSON format. The {@code Actor} classes
+ * wishing to be managed by a {@code LevelManager} should handle their own serialization and
+ * deserialization.
+ */
+public abstract class LevelManager {
+ public static final String TAG = LevelManager.class.getSimpleName();
+ private static final String ACTORS_KEY = "actors";
+
+ protected final Context context;
+ private final ExternalStoragePermissions storagePermissions;
+
+ public LevelManager(Context context) {
+ this(context, new ExternalStoragePermissions());
+ }
+
+ @VisibleForTesting
+ LevelManager(Context context, ExternalStoragePermissions storagePermissions) {
+ this.context = context;
+ this.storagePermissions = storagePermissions;
+ }
+
+ /**
+ * Saves a level to persistent storage.
+ *
+ * @param level The level to save.
+ * @param filename The name of the level's file on disk. This file will be stored inside of a
+ * preset directory, defined by the LevelManager implementation.
+ */
+ public void saveLevel(T level, String filename) {
+ File levelsDir = getLevelsDir();
+ if (!levelsDir.exists() && !levelsDir.mkdirs()) {
+ // If we are unable to find or make the desired output directory, log a warning and
+ // fail.
+ SantaLog.w(TAG, "Unable to reach dir: " + levelsDir.getAbsolutePath());
+ return;
+ }
+ try {
+ FileOutputStream outputStream = new FileOutputStream(new File(levelsDir, filename));
+ saveLevel(level, outputStream);
+ } catch (FileNotFoundException e) {
+ SantaLog.w(TAG, "Unable to save file: " + filename);
+ }
+ }
+
+ /**
+ * Writes a level to the provided OutputStream.
+ *
+ * @param level The level to save.
+ * @param outputStream The stream to which the level should be written.
+ */
+ @VisibleForTesting
+ void saveLevel(T level, OutputStream outputStream) {
+ try {
+ saveActors(level.getActors(), outputStream);
+ } catch (JSONException e) {
+ SantaLog.w(TAG, "Unable to create level JSON.");
+ } catch (IOException e) {
+ SantaLog.w(TAG, "Unable to write actors to output stream.");
+ }
+ }
+
+ /**
+ * Loads a level from persistent storage.
+ *
+ *
+ *
+ *
This loads first tries to load a level from assets, falling back to external storage if
+ * necessary. If it still cannot load the level, the default level will be loaded.
+ *
+ * @param filename The name of the file to load. This file should be stored in a preset
+ * directory, specified by the LevelManager implementation.
+ * @return The loaded level.
+ */
+ public T loadLevel(String filename) {
+ if (filename == null) {
+ SantaLog.w(TAG, "Couldn't load level with null filename, using default level instead.");
+ return loadDefaultLevel();
+ }
+
+ BufferedReader externalStorageReader = null;
+ try {
+ externalStorageReader =
+ new BufferedReader(
+ new InputStreamReader(
+ new FileInputStream(new File(getLevelsDir(), filename)),
+ "UTF-8"));
+ } catch (IOException e) {
+ SantaLog.d(TAG, "Unable to load file from external storage: " + filename);
+ }
+
+ BufferedReader assetsReader = null;
+ try {
+ assetsReader =
+ new BufferedReader(
+ new InputStreamReader(context.getAssets().open(filename), "UTF-8"));
+ } catch (IOException e) {
+ SantaLog.d(TAG, "Unable to load file from assets: " + filename);
+ }
+
+ T model = loadLevel(externalStorageReader, assetsReader);
+ model.setLevelName(filename);
+ return model;
+ }
+
+ /**
+ * Loads a level from either assets, or from external storage.
+ *
+ *
+ *
+ *
This first tries to load a level from assets, falling back to external storage if
+ * necessary. If it still cannot load the level, the default level will be loaded.
+ *
+ *
+ *
+ *
This method should only be used for testing LevelManager. Real use cases should generally
+ * use {@code loadLevel(String filename)}.
+ *
+ * @param externalStorageReader
+ * @param assetsInputReader
+ * @return The loaded level.
+ */
+ @VisibleForTesting
+ T loadLevel(BufferedReader externalStorageReader, BufferedReader assetsInputReader) {
+ JSONObject json = null;
+ if (assetsInputReader != null) {
+ json = readLevelJson(assetsInputReader);
+ SantaLog.d(TAG, "Loaded level from assets.");
+ }
+ if (json == null
+ && externalStorageReader != null
+ && storagePermissions.isExternalStorageReadable()) {
+ json = readLevelJson(externalStorageReader);
+ SantaLog.d(TAG, "Loaded level from external storage.");
+ }
+ if (json == null) {
+ SantaLog.w(TAG, "Couldn't load level data, using default level instead.");
+ return loadDefaultLevel();
+ }
+
+ T model = getEmptyModel();
+ try {
+ JSONArray actors = json.getJSONArray(ACTORS_KEY);
+ for (int i = 0; i < actors.length(); i++) {
+ Actor actor = loadActorFromJSON(actors.getJSONObject(i));
+ if (actor != null) {
+ model.addActor(actor);
+ }
+ }
+ } catch (JSONException e) {
+ SantaLog.w(TAG, "Couldn't load actors, using default level instead.");
+ return loadDefaultLevel();
+ }
+ return model;
+ }
+
+ /**
+ * Initializes the default level and returns it.
+ *
+ *
+ *
+ *
In general, this should be an empty or minimal level.
+ *
+ * @return the initialized default level.
+ */
+ public abstract T loadDefaultLevel();
+
+ /**
+ * Returns the external storage directory within which levels of this type should be saved.
+ *
+ * @return The base directory which should be used to save levels.
+ */
+ protected abstract File getLevelsDir();
+
+ /**
+ * Loads a single actor from JSON.
+ *
+ * @param json The JSON representation of the actor to be loaded
+ * @return The loaded actor, or null if the actor could not be loaded.
+ * @throws JSONException if the JSON is malformed, or fails to be parsed.
+ */
+ @VisibleForTesting
+ abstract Actor loadActorFromJSON(JSONObject json) throws JSONException;
+
+ /**
+ * Returns an empty, or minimal model of the appropriate type. Generally, this should be the
+ * same as asking for a {@code new T()}.
+ *
+ * @return The empty model.
+ */
+ protected abstract T getEmptyModel();
+
+ /**
+ * Returns a JSONArray containing the JSON representation of the passed-in list of actors.
+ *
+ *
+ *
+ *
If a given actor does not provide a JSON representation, it will not appear in the
+ * returned JSONArray.
+ *
+ * @param actors The actors to convert into JSON.
+ * @return The JSON representation of the list of actors.
+ * @throws JSONException If the parsing of an actor into JSON fails.
+ */
+ @VisibleForTesting
+ JSONArray getActorsJson(List actors) throws JSONException {
+ JSONArray actorsJson = new JSONArray();
+ for (Actor actor : actors) {
+ JSONObject json = actor.toJSON();
+ if (json != null) {
+ actorsJson.put(json);
+ }
+ }
+ return actorsJson;
+ }
+
+ /**
+ * Writes a list of actors to an OutputStream.
+ *
+ * @param actors The actors to be written.
+ * @param outputStream The OuputStream which should be used to write the list of actors.
+ * @throws JSONException If the conversion of actors into JSON fails.
+ * @throws IOException If we fail to write to the OutputStream.
+ */
+ private void saveActors(List actors, OutputStream outputStream)
+ throws JSONException, IOException {
+ JSONObject json = new JSONObject();
+ json.put(ACTORS_KEY, getActorsJson(actors));
+ SantaLog.d(TAG, json.toString(2));
+
+ if (storagePermissions.isExternalStorageWritable()) {
+ writeLevelJson(json, outputStream);
+ } else {
+ SantaLog.w(TAG, "External storage is not writable");
+ }
+ }
+
+ /**
+ * Writes a JSONObject to an OutputStream.
+ *
+ * @param json The object to be written.
+ * @param outputStream The stream to be written to.
+ * @throws IOException If we fail to write to the OutputStream.
+ */
+ private void writeLevelJson(JSONObject json, OutputStream outputStream) throws IOException {
+ outputStream.write(json.toString().getBytes());
+ outputStream.close();
+ }
+
+ /**
+ * Read a JSONObject from a BufferedReader.
+ *
+ * @param reader The BufferedReader which contains the JSON to be read.
+ * @return A JSONObject parsed from the contents of the BufferedReader.
+ */
+ private JSONObject readLevelJson(BufferedReader reader) {
+ try {
+ String levelData = "";
+ String line = reader.readLine();
+ while (line != null) {
+ levelData += line;
+ line = reader.readLine();
+ }
+ reader.close();
+ return new JSONObject(levelData);
+ } catch (IOException e) {
+ SantaLog.w(TAG, "readLevelJson: Couldn't read JSON.");
+ } catch (JSONException e) {
+ SantaLog.w(TAG, "readLevelJson: Couldn't create JSON.");
+ }
+ return null;
+ }
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/ObstacleManager.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/ObstacleManager.java
new file mode 100644
index 000000000..6302bedd8
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/ObstacleManager.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import android.content.Context;
+import com.google.android.apps.santatracker.doodles.shared.actor.Actor;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/** A class for managing obstacles in the swimming game. */
+public class ObstacleManager extends Actor {
+ private static final String TAG = ObstacleManager.class.getSimpleName();
+ private static final int NUM_INITIAL_CHUNKS = 4;
+ private static final int RETAIN_THRESHOLD = 2000;
+
+ private LinkedList levelChunks;
+ private SwimmerActor swimmer;
+
+ public ObstacleManager(SwimmerActor swimmer, Context context) {
+ levelChunks = new LinkedList<>();
+ SwimmingLevelChunk.generateAllLevelChunks(-1000, context);
+ for (int i = 1; i < NUM_INITIAL_CHUNKS; i++) {
+ levelChunks.add(SwimmingLevelChunk.getNextChunk());
+ }
+ this.swimmer = swimmer;
+ zIndex = 1;
+ }
+
+ @Override
+ public void update(float deltaMs) {
+ for (int i = 0; i < levelChunks.size(); i++) {
+ levelChunks.get(i).update(deltaMs);
+ }
+
+ if (!levelChunks.isEmpty()) {
+ SwimmingLevelChunk lastChunk = levelChunks.getLast();
+ // If the swimmer is within 2000 units of the end of the last chunk, add a new one.
+ if (swimmer.position.y - lastChunk.endY < RETAIN_THRESHOLD) {
+ SwimmingLevelChunk nextChunk = SwimmingLevelChunk.getNextChunk();
+ if (nextChunk != null) {
+ levelChunks.add(nextChunk);
+ }
+ }
+
+ if (levelChunks.getFirst().endY - swimmer.position.y > RETAIN_THRESHOLD) {
+ levelChunks.remove(0);
+ }
+ }
+ }
+
+ public void resolveCollisions(SwimmerActor swimmer, float deltaMs) {
+ for (int i = 0; i < levelChunks.size(); i++) {
+ levelChunks.get(i).resolveCollisions(swimmer, deltaMs);
+ }
+ }
+
+ public List getActors() {
+ List actors = new ArrayList<>();
+ for (int i = 0; i < levelChunks.size(); i++) {
+ actors.addAll(levelChunks.get(i).obstacles);
+ }
+ return actors;
+ }
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/PenguinSwimActivity.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/PenguinSwimActivity.java
new file mode 100644
index 000000000..21514364b
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/PenguinSwimActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.SWIMMING_GAME_TYPE;
+
+import android.app.Fragment;
+import com.google.android.apps.santatracker.doodles.BaseDoodleActivity;
+import com.google.android.apps.santatracker.doodles.shared.logging.DoodleDebugLogger;
+
+public class PenguinSwimActivity extends BaseDoodleActivity {
+ @Override
+ protected String getGameType() {
+ return SWIMMING_GAME_TYPE;
+ }
+
+ @Override
+ protected int getAnalyticsStringResource() {
+ return R.string.analytics_screen_swimming;
+ }
+
+ @Override
+ protected Fragment makeFragment(DoodleDebugLogger logger) {
+ return SwimmingFragment.newInstance(false);
+ }
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/PenguinSwimSprites.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/PenguinSwimSprites.java
new file mode 100644
index 000000000..96eb1cddb
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/PenguinSwimSprites.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+public interface PenguinSwimSprites {
+
+ public static int[] penguin_swim_banner = {
+ R.drawable.penguin_swim_banner,
+ };
+
+ public static int[] penguin_swim_dazed = {
+ R.drawable.penguin_swim_dazed_01,
+ R.drawable.penguin_swim_dazed_02,
+ R.drawable.penguin_swim_dazed_03,
+ R.drawable.penguin_swim_dazed_04,
+ R.drawable.penguin_swim_dazed_05,
+ R.drawable.penguin_swim_dazed_06,
+ R.drawable.penguin_swim_dazed_07,
+ R.drawable.penguin_swim_dazed_08,
+ R.drawable.penguin_swim_dazed_09,
+ R.drawable.penguin_swim_dazed_10,
+ R.drawable.penguin_swim_dazed_11,
+ R.drawable.penguin_swim_dazed_12,
+ R.drawable.penguin_swim_dazed_13,
+ R.drawable.penguin_swim_dazed_14,
+ R.drawable.penguin_swim_dazed_15,
+ R.drawable.penguin_swim_dazed_16,
+ R.drawable.penguin_swim_dazed_17,
+ R.drawable.penguin_swim_dazed_18,
+ R.drawable.penguin_swim_dazed_19,
+ R.drawable.penguin_swim_dazed_20,
+ R.drawable.penguin_swim_dazed_21,
+ };
+
+ public static int[] penguin_swim_descending = {
+ R.drawable.penguin_swim_descending_01,
+ R.drawable.penguin_swim_descending_02,
+ R.drawable.penguin_swim_descending_03,
+ R.drawable.penguin_swim_descending_04,
+ R.drawable.penguin_swim_descending_05,
+ R.drawable.penguin_swim_descending_06,
+ R.drawable.penguin_swim_descending_07,
+ R.drawable.penguin_swim_descending_08,
+ R.drawable.penguin_swim_descending_09,
+ };
+
+ public static int[] penguin_swim_elf = {
+ R.drawable.penguin_swim_elf_01,
+ R.drawable.penguin_swim_elf_02,
+ R.drawable.penguin_swim_elf_03,
+ R.drawable.penguin_swim_elf_04,
+ R.drawable.penguin_swim_elf_05,
+ R.drawable.penguin_swim_elf_06,
+ R.drawable.penguin_swim_elf_07,
+ R.drawable.penguin_swim_elf_08,
+ R.drawable.penguin_swim_elf_09,
+ R.drawable.penguin_swim_elf_10,
+ R.drawable.penguin_swim_elf_11,
+ R.drawable.penguin_swim_elf_12,
+ R.drawable.penguin_swim_elf_13,
+ R.drawable.penguin_swim_elf_14,
+ R.drawable.penguin_swim_elf_15,
+ };
+
+ public static int[] penguin_swim_frozen = {
+ R.drawable.penguin_swim_frozen_01,
+ R.drawable.penguin_swim_frozen_02,
+ R.drawable.penguin_swim_frozen_03,
+ R.drawable.penguin_swim_frozen_04,
+ R.drawable.penguin_swim_frozen_05,
+ R.drawable.penguin_swim_frozen_06,
+ R.drawable.penguin_swim_frozen_07,
+ R.drawable.penguin_swim_frozen_08,
+ R.drawable.penguin_swim_frozen_09,
+ R.drawable.penguin_swim_frozen_10,
+ R.drawable.penguin_swim_frozen_11,
+ R.drawable.penguin_swim_frozen_12,
+ R.drawable.penguin_swim_frozen_13,
+ R.drawable.penguin_swim_frozen_14,
+ R.drawable.penguin_swim_frozen_15,
+ R.drawable.penguin_swim_frozen_16,
+ R.drawable.penguin_swim_frozen_17,
+ R.drawable.penguin_swim_frozen_18,
+ R.drawable.penguin_swim_frozen_19,
+ R.drawable.penguin_swim_frozen_20,
+ R.drawable.penguin_swim_frozen_21,
+ R.drawable.penguin_swim_frozen_22,
+ R.drawable.penguin_swim_frozen_23,
+ R.drawable.penguin_swim_frozen_24,
+ R.drawable.penguin_swim_frozen_25,
+ R.drawable.penguin_swim_frozen_26,
+ R.drawable.penguin_swim_frozen_27,
+ R.drawable.penguin_swim_frozen_28,
+ };
+
+ public static int[] penguin_swim_ice = {
+ R.drawable.penguin_swim_ice_01,
+ R.drawable.penguin_swim_ice_02,
+ R.drawable.penguin_swim_ice_03,
+ R.drawable.penguin_swim_ice_04,
+ R.drawable.penguin_swim_ice_05,
+ R.drawable.penguin_swim_ice_06,
+ R.drawable.penguin_swim_ice_07,
+ R.drawable.penguin_swim_ice_08,
+ R.drawable.penguin_swim_ice_09,
+ R.drawable.penguin_swim_ice_10,
+ R.drawable.penguin_swim_ice_11,
+ R.drawable.penguin_swim_ice_12,
+ R.drawable.penguin_swim_ice_13,
+ R.drawable.penguin_swim_ice_14,
+ R.drawable.penguin_swim_ice_15,
+ };
+
+ public static int[] penguin_swim_idle = {
+ R.drawable.penguin_swim_start_01,
+ R.drawable.penguin_swim_start_02,
+ R.drawable.penguin_swim_start_03,
+ R.drawable.penguin_swim_start_04,
+ R.drawable.penguin_swim_start_05,
+ R.drawable.penguin_swim_start_06,
+ R.drawable.penguin_swim_start_07,
+ R.drawable.penguin_swim_start_08,
+ };
+
+ public static int[] penguin_swim_start = {
+ R.drawable.penguin_swim_start_09,
+ R.drawable.penguin_swim_start_10,
+ R.drawable.penguin_swim_start_11,
+ R.drawable.penguin_swim_start_12,
+ R.drawable.penguin_swim_start_13,
+ R.drawable.penguin_swim_start_14,
+ R.drawable.penguin_swim_start_15,
+ R.drawable.penguin_swim_start_16,
+ };
+
+ public static int[] penguin_swim_canegrab = {
+ R.drawable.penguin_swim_canegrab_01,
+ R.drawable.penguin_swim_canegrab_02,
+ R.drawable.penguin_swim_canegrab_03,
+ R.drawable.penguin_swim_canegrab_04,
+ R.drawable.penguin_swim_canegrab_05,
+ R.drawable.penguin_swim_canegrab_06,
+ R.drawable.penguin_swim_canegrab_07,
+ R.drawable.penguin_swim_canegrab_08,
+ R.drawable.penguin_swim_canegrab_09,
+ R.drawable.penguin_swim_canegrab_10,
+ R.drawable.penguin_swim_canegrab_11,
+ R.drawable.penguin_swim_canegrab_12,
+ R.drawable.penguin_swim_canegrab_13,
+ R.drawable.penguin_swim_canegrab_14,
+ R.drawable.penguin_swim_canegrab_15,
+ R.drawable.penguin_swim_canegrab_16,
+ R.drawable.penguin_swim_canegrab_17,
+ R.drawable.penguin_swim_canegrab_18,
+ R.drawable.penguin_swim_canegrab_19,
+ R.drawable.penguin_swim_canegrab_20,
+ R.drawable.penguin_swim_canegrab_21,
+ R.drawable.penguin_swim_canegrab_22,
+ R.drawable.penguin_swim_canegrab_23,
+ R.drawable.penguin_swim_canegrab_24,
+ R.drawable.penguin_swim_canegrab_25,
+ R.drawable.penguin_swim_canegrab_26,
+ R.drawable.penguin_swim_canegrab_27,
+ R.drawable.penguin_swim_canegrab_28,
+ R.drawable.penguin_swim_canegrab_29,
+ R.drawable.penguin_swim_canegrab_30,
+ };
+
+ public static int[] swimming_rings = {
+ R.drawable.swimming_rings_00,
+ R.drawable.swimming_rings_01,
+ R.drawable.swimming_rings_02,
+ R.drawable.swimming_rings_03,
+ R.drawable.swimming_rings_04,
+ R.drawable.swimming_rings_05,
+ R.drawable.swimming_rings_06,
+ R.drawable.swimming_rings_07,
+ R.drawable.swimming_rings_08,
+ R.drawable.swimming_rings_09,
+ };
+
+ public static int[] penguin_swim_ascending = {
+ R.drawable.penguin_swim_ascending_01,
+ R.drawable.penguin_swim_ascending_02,
+ R.drawable.penguin_swim_ascending_03,
+ R.drawable.penguin_swim_ascending_04,
+ R.drawable.penguin_swim_ascending_05,
+ R.drawable.penguin_swim_ascending_06,
+ R.drawable.penguin_swim_ascending_07,
+ R.drawable.penguin_swim_ascending_08,
+ R.drawable.penguin_swim_ascending_09,
+ };
+
+ public static int[] penguin_swim_candy = {
+ R.drawable.penguin_swim_candy_04,
+ };
+
+ public static int[] penguin_swim_swimming = {
+ R.drawable.penguin_swim_swimming_01,
+ R.drawable.penguin_swim_swimming_02,
+ R.drawable.penguin_swim_swimming_03,
+ R.drawable.penguin_swim_swimming_04,
+ R.drawable.penguin_swim_swimming_05,
+ R.drawable.penguin_swim_swimming_06,
+ R.drawable.penguin_swim_swimming_07,
+ R.drawable.penguin_swim_swimming_08,
+ R.drawable.penguin_swim_swimming_09,
+ R.drawable.penguin_swim_swimming_10,
+ R.drawable.penguin_swim_swimming_11,
+ R.drawable.penguin_swim_swimming_12,
+ R.drawable.penguin_swim_swimming_13,
+ R.drawable.penguin_swim_swimming_14,
+ R.drawable.penguin_swim_swimming_15,
+ R.drawable.penguin_swim_swimming_16,
+ };
+
+ public static int[] penguin_swim_swimmingunderwater = {
+ R.drawable.penguin_swim_swimmingunderwater_01,
+ R.drawable.penguin_swim_swimmingunderwater_02,
+ R.drawable.penguin_swim_swimmingunderwater_03,
+ R.drawable.penguin_swim_swimmingunderwater_04,
+ R.drawable.penguin_swim_swimmingunderwater_05,
+ R.drawable.penguin_swim_swimmingunderwater_06,
+ R.drawable.penguin_swim_swimmingunderwater_07,
+ R.drawable.penguin_swim_swimmingunderwater_08,
+ R.drawable.penguin_swim_swimmingunderwater_09,
+ R.drawable.penguin_swim_swimmingunderwater_10,
+ R.drawable.penguin_swim_swimmingunderwater_11,
+ R.drawable.penguin_swim_swimmingunderwater_12,
+ };
+
+ public static int[] tutorial_swimming = {
+ R.drawable.penguin_swim_tutorials_01,
+ R.drawable.penguin_swim_tutorials_02,
+ R.drawable.penguin_swim_tutorials_03,
+ R.drawable.penguin_swim_tutorials_04,
+ R.drawable.penguin_swim_tutorials_05,
+ R.drawable.penguin_swim_tutorials_06,
+ R.drawable.penguin_swim_tutorials_07,
+ R.drawable.penguin_swim_tutorials_08,
+ };
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/SwimmerActor.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/SwimmerActor.java
new file mode 100644
index 000000000..567fa4283
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/SwimmerActor.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import com.google.android.apps.santatracker.doodles.shared.CallbackProcess;
+import com.google.android.apps.santatracker.doodles.shared.Debug;
+import com.google.android.apps.santatracker.doodles.shared.EventBus;
+import com.google.android.apps.santatracker.doodles.shared.ProcessChain;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import com.google.android.apps.santatracker.doodles.shared.WaitProcess;
+import com.google.android.apps.santatracker.doodles.shared.actor.MultiSpriteActor;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite.AnimatedSpriteListener;
+import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
+import com.google.android.apps.santatracker.doodles.shared.physics.Util;
+import com.google.android.apps.santatracker.doodles.shared.views.GameFragment;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.json.JSONObject;
+
+/** The player-controlled swimmer in the swimming game. */
+public class SwimmerActor extends BoundingBoxSpriteActor {
+ public static final float SWIMMER_SCALE = 1.6f;
+ public static final int DIVE_DURATION_MS = 1500;
+ public static final int DIVE_COOLDOWN_MS = 5000;
+ public static final String SWIMMER_ACTOR_TYPE = "swimmer";
+ public static final String KICKOFF_IDLE_SPRITE = "kickoff_idle";
+ public static final String KICKOFF_START_SPRITE = "kickoff_start";
+ public static final String RINGS_SPRITE = "rings";
+ public static final String SWIM_LOOP_SPRITE = "swimming";
+ public static final String CAN_COLLIDE_SPRITE = "can_collide";
+ public static final String FREEZE_SPRITE = "freeze";
+ public static final String DIVE_DOWN_SPRITE = "dive";
+ public static final String UNDER_LOOP_SPRITE = "under_loop";
+ public static final String RISE_UP_SPRITE = "rise_up";
+ public static final float KICKOFF_IDLE_Y_OFFSET = -240;
+ private static final String TAG = SwimmerActor.class.getSimpleName();
+ private static final float ACCELERATION_Y = -500;
+ private static final float MIN_SPEED = 400;
+ private static final float DEFAULT_MAX_SPEED = 800;
+ private static final long SPEED_STEP_DURATION_MS = 10000;
+ private static final float TILT_VELOCITY = 10000;
+ private static final Vector2D[] VERTEX_OFFSETS = {
+ Vector2D.get(0, 0), Vector2D.get(96, 0),
+ Vector2D.get(96, 90), Vector2D.get(0, 90)
+ };
+
+ private static final Map OFFSET_MAP;
+
+ static {
+ OFFSET_MAP = new HashMap<>();
+ OFFSET_MAP.put(KICKOFF_IDLE_SPRITE, Vector2D.get(0, 0));
+ OFFSET_MAP.put(KICKOFF_START_SPRITE, Vector2D.get(0, 0));
+ OFFSET_MAP.put(RINGS_SPRITE, Vector2D.get(-60, -20)); // TODO
+ OFFSET_MAP.put(SWIM_LOOP_SPRITE, Vector2D.get(0, 0));
+ OFFSET_MAP.put(CAN_COLLIDE_SPRITE, Vector2D.get(0, 0));
+ OFFSET_MAP.put(FREEZE_SPRITE, Vector2D.get(0, 0));
+ OFFSET_MAP.put(DIVE_DOWN_SPRITE, Vector2D.get(0, 0));
+ OFFSET_MAP.put(UNDER_LOOP_SPRITE, Vector2D.get(0, 0));
+ OFFSET_MAP.put(RISE_UP_SPRITE, Vector2D.get(0, 0));
+ }
+
+ public boolean controlsEnabled = true;
+ public boolean isInvincible = false;
+ public boolean isUnderwater = false;
+ public boolean isDead = false;
+
+ private MultiSpriteActor multiSpriteActor;
+ private AnimatedSprite canCollideSprite;
+ private AnimatedSprite freezeSprite;
+ private String collidedObjectType;
+
+ private AnimatedSprite ringsSprite;
+ private Vector2D ringsSpriteOffset;
+
+ private float restartSpeed = MIN_SPEED;
+ private float maxSpeed = DEFAULT_MAX_SPEED;
+ private long currentSpeedStepTime = 0;
+
+ private boolean diveEnabled = false;
+ private float targetX;
+
+ private List processChains = new ArrayList<>();
+
+ public SwimmerActor(Polygon collisionBody, MultiSpriteActor spriteActor) {
+ super(
+ collisionBody,
+ spriteActor,
+ Vector2D.get(OFFSET_MAP.get(KICKOFF_IDLE_SPRITE)).scale(SWIMMER_SCALE),
+ SWIMMER_ACTOR_TYPE);
+
+ multiSpriteActor = spriteActor;
+ canCollideSprite = multiSpriteActor.sprites.get(CAN_COLLIDE_SPRITE);
+ canCollideSprite.setLoop(false);
+ freezeSprite = multiSpriteActor.sprites.get(FREEZE_SPRITE);
+ freezeSprite.setLoop(false);
+ multiSpriteActor
+ .sprites
+ .get(DIVE_DOWN_SPRITE)
+ .addListener(
+ new AnimatedSpriteListener() {
+ @Override
+ public void onLoop() {
+ zIndex = -3;
+ setSprite(UNDER_LOOP_SPRITE);
+ }
+ });
+ multiSpriteActor
+ .sprites
+ .get(RISE_UP_SPRITE)
+ .addListener(
+ new AnimatedSpriteListener() {
+ @Override
+ public void onLoop() {
+ zIndex = 0;
+ setSprite(SWIM_LOOP_SPRITE);
+ EventBus.getInstance()
+ .sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_dive_up);
+ }
+ });
+ multiSpriteActor
+ .sprites
+ .get(SWIM_LOOP_SPRITE)
+ .addListener(
+ new AnimatedSpriteListener() {
+ @Override
+ public void onFrame(int index) {
+ if (index == 5 || index == 13) {
+ EventBus.getInstance()
+ .sendEvent(
+ EventBus.PLAY_SOUND,
+ R.raw.swimming_ice_splash_a);
+ }
+ }
+ });
+
+ ringsSprite = multiSpriteActor.sprites.get(RINGS_SPRITE);
+ ringsSprite.setPaused(true);
+ ringsSprite.setHidden(true);
+ ringsSprite.setScale(SWIMMER_SCALE, SWIMMER_SCALE);
+ ringsSprite.addListener(
+ new AnimatedSpriteListener() {
+ @Override
+ public void onLoop() {
+ ringsSprite.setHidden(true);
+ ringsSprite.setPaused(true);
+ }
+ });
+ ringsSpriteOffset = Vector2D.get(OFFSET_MAP.get(RINGS_SPRITE)).scale(SWIMMER_SCALE);
+
+ multiSpriteActor
+ .sprites
+ .get(KICKOFF_START_SPRITE)
+ .addListener(
+ new AnimatedSpriteListener() {
+ @Override
+ public void onLoop() {
+ diveEnabled = true;
+ diveDown();
+ }
+
+ @Override
+ public void onFrame(int index) {
+ if (index == 3) {
+ velocity.set(0, -restartSpeed);
+ }
+ }
+ });
+
+ zIndex = 0;
+ alpha = 1.0f;
+ scale = SWIMMER_SCALE;
+ targetX = position.x;
+ }
+
+ public static final SwimmerActor create(
+ Vector2D position, Resources res, final GameFragment gameFragment) {
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+ Map spriteMap = new HashMap<>();
+ spriteMap.put(
+ KICKOFF_IDLE_SPRITE,
+ AnimatedSprite.fromFrames(res, PenguinSwimSprites.penguin_swim_idle));
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+ spriteMap.put(
+ KICKOFF_START_SPRITE,
+ AnimatedSprite.fromFrames(res, PenguinSwimSprites.penguin_swim_start));
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+ spriteMap.put(
+ RINGS_SPRITE, AnimatedSprite.fromFrames(res, PenguinSwimSprites.swimming_rings));
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+ spriteMap.put(
+ SWIM_LOOP_SPRITE,
+ AnimatedSprite.fromFrames(res, PenguinSwimSprites.penguin_swim_swimming));
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+ spriteMap.put(
+ CAN_COLLIDE_SPRITE,
+ AnimatedSprite.fromFrames(res, PenguinSwimSprites.penguin_swim_dazed));
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+ spriteMap.put(
+ FREEZE_SPRITE,
+ AnimatedSprite.fromFrames(res, PenguinSwimSprites.penguin_swim_frozen));
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+ spriteMap.put(
+ DIVE_DOWN_SPRITE,
+ AnimatedSprite.fromFrames(res, PenguinSwimSprites.penguin_swim_descending));
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+ spriteMap.put(
+ UNDER_LOOP_SPRITE,
+ AnimatedSprite.fromFrames(res, PenguinSwimSprites.penguin_swim_swimmingunderwater));
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+ spriteMap.put(
+ RISE_UP_SPRITE,
+ AnimatedSprite.fromFrames(res, PenguinSwimSprites.penguin_swim_ascending));
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+ MultiSpriteActor spriteActor =
+ new MultiSpriteActor(spriteMap, KICKOFF_IDLE_SPRITE, position, Vector2D.get(0, 0));
+ if (gameFragment.isDestroyed) {
+ return null;
+ }
+
+ return new SwimmerActor(
+ getBoundingBox(position, VERTEX_OFFSETS, SWIMMER_SCALE), spriteActor);
+ }
+
+ @Override
+ public void update(float deltaMs) {
+ super.update(deltaMs);
+
+ ProcessChain.updateChains(processChains, deltaMs);
+
+ // Update x position based on tilt.
+ float frameVelocityX = TILT_VELOCITY * deltaMs / 1000;
+ float positionDeltaX = targetX - position.x;
+ if (Math.abs(positionDeltaX) < frameVelocityX) {
+ // We will overshoot if we apply the frame velocity. Just go straight to the target
+ // position.
+ moveTo(targetX, position.y);
+ } else {
+ moveTo(position.x + Math.signum(positionDeltaX) * frameVelocityX, position.y);
+ }
+
+ // Update acceleration and frame rate if necessary.
+ if (velocity.getLength() > 1) {
+ velocity.y = Math.max(-maxSpeed, velocity.y + ACCELERATION_Y * deltaMs / 1000);
+ if (velocity.y == -maxSpeed) {
+ multiSpriteActor.sprites.get(SWIM_LOOP_SPRITE).setFPS(24);
+ } else {
+ multiSpriteActor.sprites.get(SWIM_LOOP_SPRITE).setFPS(48);
+ }
+ currentSpeedStepTime += deltaMs;
+ if (currentSpeedStepTime > SPEED_STEP_DURATION_MS) {
+ maxSpeed += 500;
+ currentSpeedStepTime = 0;
+ }
+ }
+
+ ringsSprite.update(deltaMs);
+ ringsSprite.setPosition(
+ spriteActor.position.x + ringsSpriteOffset.x,
+ spriteActor.position.y + ringsSpriteOffset.y);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (hidden) {
+ return;
+ }
+ spriteActor.draw(
+ canvas,
+ spriteOffset.x,
+ spriteOffset.y,
+ spriteActor.sprite.frameWidth * scale,
+ spriteActor.sprite.frameHeight * scale);
+ ringsSprite.draw(canvas);
+
+ if (Debug.DRAW_COLLISION_BOUNDS) {
+ collisionBody.draw(canvas);
+ }
+ }
+
+ @Override
+ public JSONObject toJSON() {
+ return null;
+ }
+
+ public void setSprite(String key) {
+ multiSpriteActor.setSprite(key);
+ spriteOffset.set(OFFSET_MAP.get(key)).scale(SWIMMER_SCALE);
+ }
+
+ public void moveTo(float x, float y) {
+ collisionBody.moveTo(x, y);
+ position.set(x, y);
+ spriteActor.position.set(x, y);
+ targetX = x;
+ }
+
+ public void updateTargetPositionFromTilt(Vector2D tilt, float levelWidth) {
+ if (controlsEnabled) {
+ // Decrease the amount of tilt necessary to move the swimmer.
+ float tiltPercentage = (float) (tilt.x / (Math.PI / 2));
+ tiltPercentage *= 2.5f;
+
+ int levelPadding = 60;
+ targetX =
+ Util.clamp(
+ (levelWidth / 2)
+ - (collisionBody.getWidth() / 2)
+ + (tiltPercentage * levelWidth / 2),
+ 0 - collisionBody.getWidth() + levelPadding,
+ levelWidth - (2 * collisionBody.getWidth()) - levelPadding);
+ }
+ }
+
+ public void startSwimming() {
+ setSprite(KICKOFF_START_SPRITE);
+ }
+
+ public void collide(String objectType) {
+ restartSpeed = Math.max(MIN_SPEED, Math.abs(velocity.y / 4));
+ maxSpeed = restartSpeed;
+ currentSpeedStepTime = 0;
+ moveTo(positionBeforeFrame.x, positionBeforeFrame.y);
+ velocity.set(0, 0);
+ controlsEnabled = false;
+
+ if (objectType.equals(DUCK) || objectType.equals(ICE_CUBE)) {
+ EventBus.getInstance().sendEvent(EventBus.SHAKE_SCREEN);
+ EventBus.getInstance().sendEvent(EventBus.VIBRATE);
+
+ if (objectType.equals(DUCK)) {
+ setSprite(SwimmerActor.CAN_COLLIDE_SPRITE);
+ EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_duck_collide);
+ } else { // Ice cube.
+ setSprite(SwimmerActor.FREEZE_SPRITE);
+ EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_ice_collide);
+ }
+ } else { // Octopus.
+ // Just play the sound for the octopus. It vibrates later (when it actually grabs the
+ // lemon).
+ EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_grab);
+ }
+ collidedObjectType = objectType;
+ isDead = true;
+ }
+
+ public void endGameWithoutCollision() {
+ controlsEnabled = false;
+ collidedObjectType = HAND_GRAB;
+ isDead = true;
+ }
+
+ public String getCollidedObjectType() {
+ return collidedObjectType;
+ }
+
+ public void diveDown() {
+ if (controlsEnabled && diveEnabled) {
+ EventBus.getInstance().sendEvent(EventBus.SWIMMING_DIVE);
+ EventBus.getInstance().sendEvent(EventBus.PLAY_SOUND, R.raw.swimming_dive_down);
+ ringsSprite.setHidden(false);
+ ringsSprite.setPaused(false);
+ isUnderwater = true;
+ setSprite(DIVE_DOWN_SPRITE);
+ diveEnabled = false;
+
+ ProcessChain waitThenRiseUp =
+ new WaitProcess(DIVE_DURATION_MS)
+ .then(
+ new CallbackProcess() {
+ @Override
+ public void updateLogic(float deltaMs) {
+ isUnderwater = false;
+ setSprite(RISE_UP_SPRITE);
+ }
+ })
+ .then(new WaitProcess(DIVE_COOLDOWN_MS))
+ .then(
+ new CallbackProcess() {
+ @Override
+ public void updateLogic(float deltaMs) {
+ diveEnabled = true;
+ }
+ });
+ processChains.add(waitThenRiseUp);
+ }
+ }
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/SwimmingFragment.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/SwimmingFragment.java
new file mode 100644
index 000000000..81cc78c92
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/SwimmingFragment.java
@@ -0,0 +1,999 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import static com.google.android.apps.santatracker.doodles.shared.logging.DoodleLogEvent.SWIMMING_GAME_TYPE;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Point;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Vibrator;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import com.google.android.apps.santatracker.doodles.penguinswim.SwimmingModel.SwimmingState;
+import com.google.android.apps.santatracker.doodles.shared.AndroidUtils;
+import com.google.android.apps.santatracker.doodles.shared.ColoredRectangleActor;
+import com.google.android.apps.santatracker.doodles.shared.EventBus;
+import com.google.android.apps.santatracker.doodles.shared.EventBus.EventBusListener;
+import com.google.android.apps.santatracker.doodles.shared.GameType;
+import com.google.android.apps.santatracker.doodles.shared.HistoryManager;
+import com.google.android.apps.santatracker.doodles.shared.HistoryManager.HistoryListener;
+import com.google.android.apps.santatracker.doodles.shared.UIUtil;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import com.google.android.apps.santatracker.doodles.shared.actor.Camera;
+import com.google.android.apps.santatracker.doodles.shared.actor.CameraShake;
+import com.google.android.apps.santatracker.doodles.shared.actor.RectangularInstructionActor;
+import com.google.android.apps.santatracker.doodles.shared.actor.SpriteActor;
+import com.google.android.apps.santatracker.doodles.shared.animation.AnimatedSprite;
+import com.google.android.apps.santatracker.doodles.shared.sound.SoundManager;
+import com.google.android.apps.santatracker.doodles.shared.views.GameFragment;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.android.apps.santatracker.util.SantaLog;
+import com.google.firebase.analytics.FirebaseAnalytics;
+import java.io.File;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicReference;
+
+/** The main fragment for the swimming game. */
+public class SwimmingFragment extends GameFragment
+ implements SensorEventListener, EventBusListener {
+ public static final String CURRENT_LEVEL_KEY = "CURRENT_LEVEL";
+ private static final String TAG = SwimmingFragment.class.getSimpleName();
+ private static final float ACCELEROMETER_SCALE = 1.5f / 9.8f; // Scale to range -1.5:1.5.
+ private static final int END_VIEW_ON_DEATH_DELAY_MS = 1400;
+ private static final String EDITOR_MODE = "editor_mode";
+
+ public static boolean editorMode = false;
+
+ private SwimmingView swimmingView;
+ private DiveView diveView;
+ private TextView countdownView;
+ private Button saveButton;
+ private Button loadButton;
+ private Button resetButton;
+ private Button deleteButton;
+ private ToggleButton collisionModeButton;
+
+ private final AtomicReference modelRef = new AtomicReference<>();
+ private SwimmingLevelManager levelManager;
+ private SensorManager sensorManager;
+ private Sensor accelerometerSensor;
+ private int displayRotation;
+
+ private int playCount = 0;
+ private long titleDurationMs = GameFragment.TITLE_DURATION_MS;
+ private SwimmingModel tempLevel;
+ private boolean mIsGameOver = false;
+
+ public static SwimmingFragment newInstance(boolean editorMode) {
+ SwimmingFragment fragment = new SwimmingFragment();
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(EDITOR_MODE, editorMode);
+ fragment.setArguments(bundle);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Bundle args = getArguments();
+ if (args != null) {
+ editorMode = args.getBoolean(EDITOR_MODE);
+ }
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ if (context == null) {
+ return null;
+ }
+
+ levelManager = new SwimmingLevelManager(context);
+
+ wrapper = new FrameLayout(context);
+ return wrapper;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ loadGame();
+ }
+
+ @Override
+ protected void firstPassLoadOnUiThread() {
+ final FrameLayout.LayoutParams wrapperLP =
+ new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+
+ final SwimmingFragment that = this;
+ scoreView = getScoreView();
+ pauseView = getPauseView();
+
+ int diveViewBottomMargin =
+ (int) context.getResources().getDimension(R.dimen.dive_margin_bottom);
+ int diveViewStartMargin =
+ (int) context.getResources().getDimension(R.dimen.dive_margin_left);
+ int diveViewSize = (int) context.getResources().getDimension(R.dimen.dive_image_size);
+
+ FrameLayout.LayoutParams diveViewLP = new LayoutParams(diveViewSize, diveViewSize);
+ diveViewLP.setMargins(diveViewStartMargin, 0, 0, diveViewBottomMargin);
+ diveViewLP.gravity = Gravity.BOTTOM | Gravity.LEFT;
+
+ diveViewLP.setMarginStart(diveViewStartMargin);
+ diveView = new DiveView(context);
+
+ countdownView = new TextView(context);
+ countdownView.setGravity(Gravity.CENTER);
+ countdownView.setTextColor(context.getResources().getColor(R.color.ui_text_yellow));
+ countdownView.setTypeface(Typeface.DEFAULT_BOLD);
+ countdownView.setText("0");
+ countdownView.setVisibility(View.INVISIBLE);
+ Locale locale = context.getResources().getConfiguration().locale;
+ countdownView.setText(NumberFormat.getInstance(locale).format(3));
+ Point screenDimens = AndroidUtils.getScreenSize();
+ UIUtil.fitToBounds(countdownView, screenDimens.x / 10, screenDimens.y / 10);
+
+ LinearLayout gameView = new LinearLayout(context);
+ gameView.setOrientation(LinearLayout.VERTICAL);
+
+ // Add game view.
+ swimmingView = new SwimmingView(context);
+ LinearLayout.LayoutParams lp =
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ 7);
+ gameView.addView(swimmingView, lp);
+
+ if (editorMode) {
+ LinearLayout buttonWrapper = new LinearLayout(context);
+ buttonWrapper.setOrientation(LinearLayout.HORIZONTAL);
+ lp =
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ 1);
+ gameView.addView(buttonWrapper, lp);
+
+ resetButton =
+ getButton(
+ com.google.android.apps.santatracker.common.R.string.reset_level,
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ SwimmingModel level = levelManager.loadDefaultLevel();
+ initializeLevel(level, false);
+
+ getActivity()
+ .getSharedPreferences(
+ context.getString(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .swimming),
+ Context.MODE_PRIVATE)
+ .edit()
+ .putString(CURRENT_LEVEL_KEY, null)
+ .apply();
+ }
+ });
+ deleteButton =
+ getButton(
+ com.google.android.apps.santatracker.common.R.string.delete_levels,
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ DialogFragment dialogFragment = new DeleteLevelDialogFragment();
+ dialogFragment.show(
+ getActivity().getFragmentManager(), "delete");
+ }
+ });
+ loadButton =
+ getButton(
+ com.google.android.apps.santatracker.common.R.string.load_level,
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ LoadLevelDialogFragment dialogFragment =
+ new LoadLevelDialogFragment();
+ dialogFragment.setSwimmingFragment(that);
+ dialogFragment.show(getActivity().getFragmentManager(), "load");
+ }
+ });
+ saveButton =
+ getButton(
+ com.google.android.apps.santatracker.common.R.string.save_level,
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ SaveLevelDialogFragment dialogFragment =
+ SaveLevelDialogFragment.newInstance(
+ that.modelRef.get().levelName);
+ dialogFragment.setSwimmingFragment(that);
+ dialogFragment.show(getActivity().getFragmentManager(), "save");
+ }
+ });
+ collisionModeButton = new ToggleButton(context);
+ collisionModeButton.setText(
+ com.google.android.apps.santatracker.common.R.string.scenery_mode);
+ collisionModeButton.setTextOff(
+ context.getString(
+ com.google.android.apps.santatracker.common.R.string.scenery_mode));
+ collisionModeButton.setTextOn(
+ context.getString(
+ com.google.android.apps.santatracker.common.R.string.collision_mode));
+ collisionModeButton.setOnCheckedChangeListener(
+ new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ final SwimmingModel model = modelRef.get();
+ model.collisionMode = isChecked;
+ }
+ });
+
+ lp =
+ new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ 1);
+ buttonWrapper.addView(deleteButton, lp);
+ buttonWrapper.addView(resetButton, lp);
+ buttonWrapper.addView(loadButton, lp);
+ buttonWrapper.addView(saveButton, lp);
+ buttonWrapper.addView(collisionModeButton, lp);
+ }
+
+ sensorManager = (SensorManager) getActivity().getSystemService(Context.SENSOR_SERVICE);
+ accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (accelerometerSensor == null) {
+ // TODO: The game won't be playable without this, so what should we do?
+ SantaLog.d(TAG, "Accelerometer sensor is null");
+ }
+ displayRotation =
+ ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay()
+ .getRotation();
+
+ wrapper.addView(gameView, 0, wrapperLP);
+ wrapper.addView(countdownView, 1);
+ wrapper.addView(diveView, 2, diveViewLP);
+ wrapper.addView(scoreView, 3);
+ wrapper.addView(pauseView, 4);
+ }
+
+ @Override
+ protected void secondPassLoadOnBackgroundThread() {
+ super.secondPassLoadOnBackgroundThread();
+
+ tempLevel = new SwimmingModel();
+ final SwimmingModel level = tempLevel;
+ historyManager =
+ new HistoryManager(
+ context,
+ new HistoryListener() {
+ @Override
+ public void onFinishedLoading() {
+ addBestTimeLine(level);
+ }
+
+ @Override
+ public void onFinishedSaving() {}
+ });
+
+ initializeLevel(tempLevel, true);
+ }
+
+ @Override
+ protected void finalPassLoadOnUiThread() {
+ soundManager = SoundManager.getInstance();
+ loadSounds();
+
+ onFinishedLoading();
+ startHandlers();
+ tempLevel = null;
+ }
+
+ @Override
+ protected void replay() {
+ super.replay();
+ SwimmingModel level = new SwimmingModel();
+ initializeLevel(level, false);
+ }
+
+ @Override
+ protected void resume() {
+ super.resume();
+ if (uiRefreshHandler != null) {
+ sensorManager.registerListener(
+ this, accelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
+ uiRefreshHandler.start(swimmingView);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (sensorManager != null) {
+ sensorManager.unregisterListener(this);
+ }
+ // Clear the path list because it's static and otherwise it hangs around forever.
+ if (SwimmingLevelChunk.pathList != null) {
+ SwimmingLevelChunk.pathList.clear();
+ }
+ }
+
+ @Override
+ protected void onDestroyHelper() {
+ if (swimmingView != null) {
+ swimmingView.setModel(null);
+ }
+ modelRef.set(null);
+ tempLevel = null;
+ levelManager = null;
+
+ if (SwimmingLevelChunk.swimmingLevelChunks != null) {
+ SwimmingLevelChunk.swimmingLevelChunks.clear();
+ }
+ if (SwimmingLevelChunk.pathList != null) {
+ SwimmingLevelChunk.pathList.clear();
+ }
+ }
+
+ @Override
+ public void update(float deltaMs) {
+ final SwimmingModel model = modelRef.get();
+
+ if (isPaused || model == null) {
+ return;
+ }
+ model.update(deltaMs);
+ diveView.update(deltaMs);
+
+ // If we are in editor mode, hide the intro animation right away and skip the camera pan.
+ // Otherwise, wait until it has finished playing before fading and then run the camera pan.
+ if ((editorMode || model.timeElapsed >= titleDurationMs)
+ && model.getState() == SwimmingState.INTRO) {
+ if (editorMode) {
+ SantaLog.d(TAG, "Hiding intro animation right away.");
+
+ model.setState(SwimmingState.WAITING);
+ } else {
+ SantaLog.d(TAG, "Fading out and hiding intro animation.");
+
+ // Run on UI thread since background threads can't touch the intro view.
+ getActivity()
+ .runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ hideTitle();
+ }
+ });
+ model.setState(SwimmingState.WAITING);
+ }
+ }
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ final SwimmingModel model = modelRef.get();
+ if (event.values == null || model == null) {
+ return;
+ }
+ int sensorType = event.sensor.getType();
+ if (sensorType == Sensor.TYPE_ACCELEROMETER) {
+ float[] adjustedEventValues =
+ AndroidUtils.getAdjustedAccelerometerValues(displayRotation, event.values);
+ float x = -adjustedEventValues[0] * ACCELEROMETER_SCALE;
+ float y = -adjustedEventValues[1] * ACCELEROMETER_SCALE;
+ // Accelerometer input is very noisy, so we filter it using a (simple) low-pass filter.
+ float weight = 0.1f;
+ model.tilt.x = model.tilt.x + weight * (x - model.tilt.x);
+ model.tilt.y = model.tilt.y + weight * (y - model.tilt.y);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int i) {}
+
+ @Override
+ public void onEventReceived(int type, final Object data) {
+ final SwimmingModel model = modelRef.get();
+ if (isDestroyed || model == null) {
+ return;
+ }
+ switch (type) {
+ case EventBus.SCORE_CHANGED:
+ int distance = integerValue(data);
+ boolean shouldAddStar = false;
+
+ if (model.currentScoreThreshold < SwimmingModel.SCORE_THRESHOLDS.length
+ && distance
+ >= SwimmingModel.SCORE_THRESHOLDS[model.currentScoreThreshold]) {
+ // Pop in star.
+ shouldAddStar = true;
+ model.currentScoreThreshold++;
+ }
+ updateScoreUi(distance, shouldAddStar);
+ break;
+ case EventBus.SWIMMING_DIVE:
+ getActivity()
+ .runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ diveView.startCooldown();
+ }
+ });
+ break;
+
+ case EventBus.GAME_STATE_CHANGED:
+ SwimmingState state = (SwimmingState) data;
+ mIsGameOver = false;
+ if (state == SwimmingState.FINISHED) {
+ mIsGameOver = true;
+ calculateBestDistance();
+ calculateStars();
+ getActivity()
+ .runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ pauseView.hidePauseButton();
+ }
+ });
+ final Handler handler = new Handler(Looper.getMainLooper());
+ handler.postDelayed(
+ new Runnable() {
+ @Override
+ public void run() {
+ final SwimmingModel model = modelRef.get();
+ if (model == null) {
+ return;
+ }
+ int bestDistance =
+ historyManager
+ .getBestDistance(GameType.SWIMMING)
+ .intValue();
+ String collidedObjectType =
+ model.swimmer.getCollidedObjectType();
+ scoreView.updateBestScore(
+ AndroidUtils.getText(
+ context.getResources(),
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .swimming_score,
+ bestDistance));
+ scoreView.setShareDrawable(
+ getShareImageDrawable(collidedObjectType));
+
+ diveView.hide();
+ scoreView.animateToEndState();
+ }
+ },
+ END_VIEW_ON_DEATH_DELAY_MS);
+ }
+
+ // [ANALYTICS]
+ FirebaseAnalytics analytics = FirebaseAnalytics.getInstance(getActivity());
+ MeasurementManager.recordSwimmingEnd(
+ analytics,
+ model.getStarCount(),
+ model.distanceMeters,
+ model.swimmer.getCollidedObjectType());
+ break;
+
+ case EventBus.PLAY_SOUND:
+ int resId = (int) data;
+ if (soundManager != null) {
+ soundManager.play(resId);
+ }
+ break;
+
+ case EventBus.MUTE_SOUNDS:
+ boolean shouldMute = (boolean) data;
+ if (soundManager != null) {
+ if (shouldMute) {
+ soundManager.mute();
+ } else {
+ soundManager.unmute();
+ }
+ }
+ break;
+
+ case EventBus.GAME_LOADED:
+ long loadTimeMs = (long) data;
+ titleDurationMs = Math.max(0, titleDurationMs - loadTimeMs);
+ SantaLog.d(TAG, "Waiting " + titleDurationMs + "ms and then hiding title.");
+ break;
+ }
+ }
+
+ @Nullable
+ private Drawable getShareImageDrawable(String collidedObjectType) {
+ return ContextCompat.getDrawable(
+ getActivity(), com.google.android.apps.santatracker.common.R.drawable.winner);
+ }
+
+ private void initializeLevel(final SwimmingModel newLevel, boolean shouldPlayIntro) {
+ EventBus.getInstance().clearListeners();
+ EventBus.getInstance().register(newLevel, EventBus.VIBRATE);
+ EventBus.getInstance().register(newLevel, EventBus.SHAKE_SCREEN);
+ EventBus.getInstance().register(newLevel, EventBus.GAME_STATE_CHANGED);
+
+ // Initialize Activity-specific stuff.
+ EventBus.getInstance().register(this);
+
+ Point size = AndroidUtils.getScreenSize();
+ newLevel.screenWidth = size.x;
+ newLevel.screenHeight = size.y;
+ newLevel.addActor(new Camera(newLevel.screenWidth, newLevel.screenHeight));
+ newLevel.addActor(new CameraShake());
+ // Center the level horizontally on the screen and scale to fit.
+ newLevel.camera.moveImmediatelyTo(
+ Vector2D.get(0, 0), Vector2D.get(SwimmingModel.LEVEL_WIDTH, 0));
+ newLevel.playCount = playCount++;
+ newLevel.locale = getResources().getConfiguration().locale;
+
+ if (isDestroyed) {
+ return;
+ }
+ SwimmerActor swimmer =
+ SwimmerActor.create(Vector2D.get(0, 0), context.getResources(), this);
+ if (swimmer == null) {
+ return;
+ }
+ swimmer.moveTo(
+ SwimmingModel.LEVEL_WIDTH / 2 - swimmer.collisionBody.getWidth() / 2,
+ SwimmerActor.KICKOFF_IDLE_Y_OFFSET);
+ newLevel.addActor(swimmer);
+
+ DistanceMarkerActor startingBlock =
+ new DistanceMarkerActor(0, ColoredRectangleActor.STARTING_BLOCK, 1000);
+ newLevel.addActor(startingBlock);
+
+ SpriteActor banner =
+ new SpriteActor(
+ AnimatedSprite.fromFrames(
+ context.getResources(), PenguinSwimSprites.penguin_swim_banner),
+ Vector2D.get(SwimmingModel.LEVEL_WIDTH / 2, startingBlock.position.y),
+ Vector2D.get());
+ banner.scale = 2;
+ banner.zIndex = 2;
+ banner.sprite.setAnchor(banner.sprite.frameWidth / 2, banner.sprite.frameHeight - 50);
+ newLevel.addActor(banner);
+
+ newLevel.addActor(new DistanceMarkerActor(30, ColoredRectangleActor.DISTANCE_30M));
+ newLevel.addActor(new DistanceMarkerActor(50, ColoredRectangleActor.DISTANCE_50M));
+ newLevel.addActor(new DistanceMarkerActor(100, ColoredRectangleActor.DISTANCE_100M));
+ newLevel.addActor(
+ new DistanceMarkerActor(
+ SwimmingLevelChunk.LEVEL_LENGTH_IN_METERS,
+ ColoredRectangleActor.DISTANCE_LEVEL_LENGTH));
+ if (historyManager != null) {
+ addBestTimeLine(newLevel);
+ }
+
+ if (isDestroyed) {
+ return;
+ }
+ ObstacleManager obstacleManager = new ObstacleManager(swimmer, context);
+ newLevel.addActor(obstacleManager);
+
+ newLevel.camera.position.y = -newLevel.camera.toWorldScale(newLevel.screenHeight);
+ newLevel.clampCameraPosition(); // Clamp camera so that it doesn't jump when we start
+ // swimming.
+
+ AnimatedSprite swimmingTutorialSprite =
+ AnimatedSprite.fromFrames(
+ context.getResources(), PenguinSwimSprites.tutorial_swimming);
+ swimmingTutorialSprite.setFPS(6);
+ RectangularInstructionActor instructions =
+ new RectangularInstructionActor(context.getResources(), swimmingTutorialSprite);
+ instructions.hidden = true;
+ instructions.scale = (newLevel.screenWidth * 0.6f) / instructions.rectangle.frameWidth;
+ // Put instructions at top right, slightly below pause button.
+ instructions.position.set(newLevel.screenWidth / 2 - instructions.getScaledWidth() / 2, 70);
+ newLevel.addUiActor(instructions);
+
+ newLevel.setCountdownView(countdownView);
+
+ if (!shouldPlayIntro) {
+ // Skip State.INTRO.
+ newLevel.setState(SwimmingState.WAITING);
+ }
+ if (isDestroyed) {
+ return;
+ }
+ modelRef.set(newLevel);
+ swimmingView.setModel(newLevel);
+
+ getActivity()
+ .runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ initializeLevelUiThread(newLevel);
+ }
+ });
+
+ // Hint that this would be a good time to do some garbage collection since we should be able
+ // to get rid of any stuff from the previous level at this time.
+ System.gc();
+ }
+
+ // Initialize level parts that need to happen in UI thread.
+ private void initializeLevelUiThread(final SwimmingModel newLevel) {
+ if (isDestroyed) {
+ return;
+ }
+ scoreView.updateCurrentScore(
+ AndroidUtils.getText(
+ context.getResources(),
+ com.google.android.apps.santatracker.common.R.string.swimming_score,
+ 0),
+ false);
+ pauseView.showPauseButton();
+ diveView.show();
+ newLevel.vibrator = (Vibrator) getActivity().getSystemService(Activity.VIBRATOR_SERVICE);
+ }
+
+ @Override
+ protected void loadSounds() {
+ super.loadSounds();
+ soundManager.loadShortSound(context, R.raw.swimming_dive_down);
+ soundManager.loadShortSound(context, R.raw.swimming_dive_up);
+ soundManager.loadShortSound(context, R.raw.swimming_ice_splash_a);
+ soundManager.loadShortSound(context, R.raw.swimming_duck_collide);
+ soundManager.loadShortSound(context, R.raw.swimming_ice_collide);
+ soundManager.loadShortSound(context, R.raw.swimming_grab);
+ }
+
+ private void addBestTimeLine(SwimmingModel level) {
+ Double bestDistance = historyManager.getBestDistance(GameType.SWIMMING);
+ if (bestDistance != null) {
+ level.addActor(
+ new DistanceMarkerActor(
+ bestDistance.intValue(), ColoredRectangleActor.DISTANCE_PR));
+ }
+ }
+
+ private void calculateStars() {
+ final SwimmingModel model = modelRef.get();
+ Integer bestStarCount = historyManager.getBestStarCount(GameType.SWIMMING);
+ int modelStarCount = model.getStarCount();
+ if (bestStarCount == null || bestStarCount < modelStarCount) {
+ historyManager.setBestStarCount(GameType.SWIMMING, modelStarCount);
+ }
+ }
+
+ private void calculateBestDistance() {
+ final SwimmingModel model = modelRef.get();
+ Double bestDistance = historyManager.getBestDistance(GameType.SWIMMING);
+ if (bestDistance == null || bestDistance < model.distanceMeters) {
+ historyManager.setBestDistance(GameType.SWIMMING, model.distanceMeters);
+ }
+ }
+
+ private Button getButton(int textId, OnClickListener onClickListener) {
+ Button button = new Button(context);
+ button.setText(textId);
+ button.setOnClickListener(onClickListener);
+ return button;
+ }
+
+ private void updateScoreUi(final int score, final boolean shouldShowStars) {
+ getActivity()
+ .runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ scoreView.updateCurrentScore(
+ AndroidUtils.getText(
+ context.getResources(),
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .swimming_score,
+ score),
+ shouldShowStars);
+ if (shouldShowStars) {
+ scoreView.addStar();
+ }
+ }
+ });
+ }
+
+ private int integerValue(Object object) {
+ if (object instanceof Integer) {
+ return (int) object;
+ } else {
+ throw new IllegalArgumentException("Unknown event data type");
+ }
+ }
+
+ @Override
+ public void onGamePause() {
+ final SwimmingModel model = modelRef.get();
+ if (model != null && model.swimmer != null) {
+ model.swimmer.controlsEnabled = false;
+ }
+ super.onGamePause();
+ }
+
+ @Override
+ protected void onGameResume() {
+ final SwimmingModel model = modelRef.get();
+ if (model != null && model.swimmer != null) {
+ model.swimmer.controlsEnabled = true;
+ }
+ super.onGameResume();
+ }
+
+ @Override
+ protected void onGameReplay() {
+ final SwimmingModel model = modelRef.get();
+ if (model != null && model.swimmer != null) {
+ model.swimmer.controlsEnabled = true;
+ }
+ super.onGameReplay();
+ }
+
+ @Override
+ protected boolean onTitleTapped() {
+ final SwimmingModel model = modelRef.get();
+ if (model != null && model.getState() == SwimmingState.INTRO) {
+ // If the user taps the screen while the intro animation is playing, end the intro
+ // immediately and transition into the game.
+ model.timeElapsed = GameFragment.TITLE_DURATION_MS;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected String getGameType() {
+ return SWIMMING_GAME_TYPE;
+ }
+
+ @Override
+ protected float getScore() {
+ final SwimmingModel model = modelRef.get();
+ return model.distanceMeters;
+ }
+
+ @Override
+ protected int getShareImageId() {
+ final SwimmingModel model = modelRef.get();
+ String collidedObjectType = model.swimmer.getCollidedObjectType();
+ if (BoundingBoxSpriteActor.ICE_CUBE.equals(collidedObjectType)) {
+ return 0;
+ } else if (BoundingBoxSpriteActor.DUCK.equals(collidedObjectType)) {
+ return 1;
+ } else { // Octopus hand grab.
+ return 2;
+ }
+ }
+
+ public boolean isGameOver() {
+ return mIsGameOver;
+ }
+
+ /** A dialog fragment for loading a swimming level. */
+ public static class LoadLevelDialogFragment extends DialogFragment {
+
+ @Nullable private SwimmingFragment swimmingFragment;
+
+ public void setSwimmingFragment(@NonNull SwimmingFragment swimmingFragment) {
+ this.swimmingFragment = swimmingFragment;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final String[] fileList =
+ SwimmingLevelManager.levelsDir.exists()
+ ? SwimmingLevelManager.levelsDir.list()
+ : new String[0];
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(com.google.android.apps.santatracker.common.R.string.load_level)
+ .setItems(
+ fileList,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (swimmingFragment == null) {
+ throw new IllegalStateException();
+ }
+ SwimmingModel level =
+ swimmingFragment.levelManager.loadLevel(
+ fileList[which]);
+ swimmingFragment.initializeLevel(level, false);
+
+ // Save the current level so we automatically come back to it
+ // later.
+ getActivity()
+ .getSharedPreferences(
+ swimmingFragment.context.getString(
+ com.google
+ .android
+ .apps
+ .santatracker
+ .common
+ .R
+ .string
+ .swimming),
+ Context.MODE_PRIVATE)
+ .edit()
+ .putString(CURRENT_LEVEL_KEY, fileList[which])
+ .apply();
+ }
+ });
+ return builder.create();
+ }
+ }
+
+ /** A dialog fragment for saving a swimming level. */
+ public static class SaveLevelDialogFragment extends DialogFragment {
+
+ private static final String LEVEL_NAME = "level_name";
+
+ @Nullable private SwimmingFragment swimmingFragment;
+ private String levelName;
+
+ public void setSwimmingFragment(@NonNull SwimmingFragment swimmingFragment) {
+ this.swimmingFragment = swimmingFragment;
+ }
+
+ public static SaveLevelDialogFragment newInstance(String levelName) {
+ SaveLevelDialogFragment fragment = new SaveLevelDialogFragment();
+ Bundle bundle = new Bundle();
+ bundle.putString(LEVEL_NAME, levelName);
+ fragment.setArguments(bundle);
+ return fragment;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final EditText editText = new EditText(getActivity());
+ this.levelName = getArguments().getString(LEVEL_NAME);
+ editText.setText(levelName);
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(com.google.android.apps.santatracker.common.R.string.save_level)
+ .setView(editText)
+ .setPositiveButton(
+ com.google.android.apps.santatracker.common.R.string.save,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ if (swimmingFragment == null) {
+ throw new IllegalStateException();
+ }
+ swimmingFragment.levelManager.saveLevel(
+ swimmingFragment.modelRef.get(),
+ editText.getText().toString());
+ }
+ })
+ .setNegativeButton(
+ com.google.android.apps.santatracker.common.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ // Do nothing.
+ }
+ });
+ return builder.create();
+ }
+ }
+
+ /** A dialog fragment for deleting a swimming level. */
+ public static class DeleteLevelDialogFragment extends DialogFragment {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final List selectedItems = new ArrayList<>();
+ final String[] fileList =
+ SwimmingLevelManager.levelsDir.exists()
+ ? SwimmingLevelManager.levelsDir.list()
+ : new String[0];
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(com.google.android.apps.santatracker.common.R.string.delete_levels)
+ .setMultiChoiceItems(
+ fileList,
+ null,
+ new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(
+ DialogInterface dialog, int which, boolean isChecked) {
+ if (isChecked) {
+ selectedItems.add(which);
+ } else if (selectedItems.contains(which)) {
+ selectedItems.remove(Integer.valueOf(which));
+ }
+ }
+ })
+ .setPositiveButton(
+ com.google.android.apps.santatracker.common.R.string.delete,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ for (Integer selection : selectedItems) {
+ File file =
+ new File(
+ SwimmingLevelManager.levelsDir,
+ fileList[selection]);
+ file.delete();
+ }
+ }
+ })
+ .setNegativeButton(
+ com.google.android.apps.santatracker.common.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ // Do nothing.
+ }
+ });
+ return builder.create();
+ }
+ }
+}
diff --git a/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/SwimmingLevelChunk.java b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/SwimmingLevelChunk.java
new file mode 100644
index 000000000..2cbb3e895
--- /dev/null
+++ b/penguinswim/src/main/java/com/google/android/apps/santatracker/doodles/penguinswim/SwimmingLevelChunk.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright 2019. Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.apps.santatracker.doodles.penguinswim;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import com.google.android.apps.santatracker.doodles.Config;
+import com.google.android.apps.santatracker.doodles.shared.Vector2D;
+import com.google.android.apps.santatracker.doodles.shared.actor.Actor;
+import com.google.android.apps.santatracker.doodles.shared.physics.Polygon;
+import com.google.android.apps.santatracker.doodles.shared.physics.Util;
+import com.google.android.apps.santatracker.util.SantaLog;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Random;
+
+/** One chunk of a level in the swimming game. */
+public class SwimmingLevelChunk extends Actor {
+ public static final int LEVEL_LENGTH_IN_METERS = 500;
+ public static final int CHUNK_HEIGHT = 5000;
+ public static final int NUM_ROWS = 100;
+ public static final int NUM_COLS = 50;
+ public static final float COL_WIDTH = SwimmingModel.LEVEL_WIDTH / (float) NUM_COLS;
+ public static final float ROW_HEIGHT = CHUNK_HEIGHT / (float) NUM_ROWS;
+ private static final String TAG = SwimmingLevelChunk.class.getSimpleName();
+ private static final int SOLUTION_PATH_NUM_COLS = 50;
+ private static final Random RANDOM = new Random();
+ private static final List TYPES;
+ public static Queue