/*
*
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license.
*
* Project Oxford: http://ProjectOxford.ai
*
* Project Oxford Mimicker Alarm Github:
* https://github.com/Microsoft/ProjectOxford-Apps-MimickerAlarm
*
* Copyright (c) Microsoft Corporation
* All rights reserved.
*
* MIT License:
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.microsoft.mimickeralarm.mimics;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.microsoft.mimickeralarm.R;
import com.microsoft.mimickeralarm.mimics.MimicFactory.MimicResultListener;
import com.microsoft.mimickeralarm.utilities.Loggable;
import com.microsoft.mimickeralarm.utilities.Logger;
import java.util.Random;
public class MimicNoNetworkFragment extends Fragment {
public static final String NO_NETWORK_FRAGMENT_TAG = "mimic_no_network";
private final static int TIMEOUT_MILLISECONDS = 30000;
MimicResultListener mCallback;
private CountDownTimerView mTimer;
private TextView mInstructionText;
private MimicStateBanner mStateBanner;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_no_network_mimic, container, false);
mStateBanner = (MimicStateBanner) view.findViewById(R.id.mimic_state);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
Game game = new Game(this);
game.setLayoutParams(params);
((LinearLayout) view.findViewById(R.id.game_container)).addView(game);
mTimer = (CountDownTimerView) view.findViewById(R.id.countdown_timer);
mTimer.init(TIMEOUT_MILLISECONDS, new CountDownTimerView.Command() {
@Override
public void execute() {
gameFailure();
}
});
mInstructionText = (TextView) view.findViewById(R.id.instruction_text);
mInstructionText.setText(R.string.game_nonetwork_prompt);
Logger.init(getActivity());
Loggable.UserAction userAction = new Loggable.UserAction(Loggable.Key.ACTION_GAME_NONETWORK);
Logger.track(userAction);
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mCallback = (MimicResultListener) context;
}
@Override
public void onDetach() {
super.onDetach();
mCallback = null;
}
@Override
public void onStart() {
super.onStart();
mTimer.start();
}
@Override
public void onStop() {
super.onStop();
mTimer.stop();
}
protected void gameFailure() {
String failureMessage = getString(R.string.mimic_time_up_message);
Loggable.UserAction userAction = new Loggable.UserAction(Loggable.Key.ACTION_GAME_NONETWORK_TIMEOUT);
Logger.track(userAction);
mStateBanner.failure(failureMessage, new MimicStateBanner.Command() {
@Override
public void execute() {
mCallback.onMimicFailure();
}
});
}
protected void gameSuccess() {
mTimer.stop();
String successMessage = getString(R.string.mimic_success_message);
Loggable.UserAction userAction = new Loggable.UserAction(Loggable.Key.ACTION_GAME_NONETWORK_SUCCESS);
Logger.track(userAction);
mStateBanner.success(successMessage, new MimicStateBanner.Command() {
@Override
public void execute() {
mCallback.onMimicSuccess(null);
}
});
}
private class Game extends SurfaceView implements View.OnLayoutChangeListener, SurfaceHolder.Callback {
private int mWidth, mHeight;
private GameLoop mGameLoop;
private GameEngine mGameEngine;
private MimicNoNetworkFragment mParentFragment;
public Game(MimicNoNetworkFragment parent) {
super(getActivity());
addOnLayoutChangeListener(this);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
mGameEngine = new GameEngine();
mGameLoop = new GameLoop(holder, mGameEngine);
mParentFragment = parent;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int tapsRemaining = mGameEngine.touch(event);
if (tapsRemaining == 2) {
mInstructionText.setText(R.string.game_nonetwork_prompt2);
}
else if (tapsRemaining == 1) {
mInstructionText.setText(R.string.game_nonetwork_prompt3);
}
else if (tapsRemaining <= 0){
stopLoop();
mParentFragment.gameSuccess();
}
return super.onTouchEvent(event);
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
mWidth = right - left;
mHeight = bottom - top;
mGameEngine.setDimensions(mWidth, mHeight);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mGameLoop.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopLoop();
}
private void stopLoop() {
mGameLoop.setRunning(false);
boolean retry = true;
while (retry)
{
try
{
mGameLoop.join();
retry = false;
}
catch (InterruptedException e) {
Logger.trackException(e);
}
}
}
}
private class GameLoop extends Thread {
private final SurfaceHolder mSurfaceHolder;
private final long DELAY = 4;
private boolean mRunning;
private GameEngine mGameEngine;
public GameLoop(SurfaceHolder surfaceHolder, GameEngine gameEngine) {
mSurfaceHolder = surfaceHolder;
mGameEngine = gameEngine;
mRunning = true;
}
@Override
public void run() {
while (mRunning)
{
mGameEngine.update();
Canvas canvas = mSurfaceHolder.lockCanvas(null);
if (canvas != null) {
synchronized (mSurfaceHolder) {
mGameEngine.draw(canvas);
}
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
try {
Thread.sleep(DELAY);
}
catch (InterruptedException ex) {
Logger.trackException(ex);
}
}
}
public boolean IsRunning() {
return mRunning;
}
public void setRunning(boolean state) {
mRunning = state;
}
}
private class GameEngine {
private final float EPSILON = 0.002f;
private Paint mPaint, mBackgroundPaint;
private float mX, mY;
private int mWidth, mHeight;
private Vector2D mVelocity;
private boolean mInitialized = false;
private RectF mHitbox;
private int mTapsRemaining = 3;
private Bitmap mAsset1;
private Bitmap mAsset2;
public GameEngine() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.GREEN);
mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBackgroundPaint.setColor(Color.WHITE);
mX = 0; mY = 0;
mAsset1 = BitmapFactory.decodeResource(getResources(), R.drawable.offline_game_1);
mAsset2 = BitmapFactory.decodeResource(getResources(), R.drawable.offline_game_2);
mHitbox = new RectF(0, 0, mAsset1.getWidth(), mAsset1.getHeight());
}
public void setDimensions(int width, int height) {
if (!mInitialized) {
mX = width / 2;
mY = height / 2;
mWidth = width;
mHeight = height;
mVelocity = new Vector2D(new Random().nextFloat(), new Random().nextFloat());
mVelocity.normalize();
mVelocity.mult(10f);
mInitialized = true;
update();
}
}
public void update() {
float newX = mX + mVelocity.x();
float newY = mY + mVelocity.y();
if (newX < EPSILON) {
mVelocity.x(-mVelocity.x());
mX = EPSILON;
}
else if (newX > mWidth - mHitbox.width()- EPSILON) {
mVelocity.x(-mVelocity.x());
mX = mWidth - mHitbox.width() - EPSILON;
}
else {
mX = newX;
}
if (newY < EPSILON) {
mVelocity.y(-mVelocity.y());
mY = EPSILON;
}
else if (newY > mHeight - mHitbox.height() - EPSILON) {
mVelocity.y(-mVelocity.y());
mY = mHeight - mHitbox.height() - EPSILON;
}
else {
mY = newY;
}
mHitbox.offsetTo(mX, mY);
}
public void draw(Canvas canvas) {
canvas.drawRect(0, 0, mWidth, mHeight, mBackgroundPaint);
canvas.save();
canvas.translate(mX, mY);
canvas.drawBitmap(mAsset1, 0, 0, mPaint);
canvas.translate(mAsset1.getWidth(), 0);
for (int i = 0; i < mTapsRemaining; i++) {
mPaint.setAlpha((int)(Math.pow(0.8, i) * 255));
canvas.drawBitmap(mAsset2, 0, 0, mPaint);
canvas.scale(0.8f, 0.8f);
canvas.translate(75, -50);
}
mPaint.setAlpha(255);
canvas.restore();
}
public int touch(MotionEvent event) {
if (mHitbox.contains(event.getX(), event.getY())){
mVelocity.mult(1.5f);
mTapsRemaining--;
}
return mTapsRemaining;
}
}
private class Vector2D {
private float [] v = new float[2];
public Vector2D(float x, float y) {
v[0] = x;
v[1] = y;
}
public float x() {
return v[0];
}
public float y() {
return v[1];
}
public void x(float newX) {
v[0] = newX;
}
public void y(float newY) {
v[1] = newY;
}
public float length() {
return (float) Math.sqrt(x() * x() + y() * y());
}
public void normalize() {
float length = length();
if (length > 0){
v[0] = v[0] / length;
v[1] = v[1] / length;
}
}
public void mult(float f) {
v[0] *= f;
v[1] *= f;
}
}
}