emotionhero/app/src/main/java/com/rubenvandeven/emotionhero/Scenario.java

524 lines
16 KiB
Java

package com.rubenvandeven.emotionhero;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.v8.renderscript.RenderScript;
import android.util.Log;
import com.affectiva.android.affdex.sdk.Frame;
import com.affectiva.android.affdex.sdk.detector.Face;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import io.github.silvaren.easyrs.tools.Nv21Image;
/**
* Created by ruben on 16/08/16.
*/
public class Scenario {
public int id;
public Frame.ByteArrayFrame currentFrame;
public static final int LVL_NONE = 0;
public static final int LVL_ANGER = 1;
public static final int LVL_JOY = 2;
public static final int LVL_SURPRISE = 3;
public static final int LVL_SADDNESS= 4;
// the levels in the right order.
public static final ArrayList<Integer> SCENARIOS = new ArrayList<Integer>() {{
add(LVL_JOY);
add(LVL_ANGER);
add(LVL_SADDNESS);
add(LVL_SURPRISE);
}};
static int DESIRED_FPS = 25;
float duration = 0;
/**
* Minimum score to be able to pass to the next level.
* (check minimumScore || minimumAchievements)
*/
float minimumScore = 0;
/**
* Number of achievements required to have, before being able to go to next level
* (check minimumScore || minimumAchievements)
*/
float minimumAchievements= 0;
/**
* Achievements one get obtain in this level
*/
ArrayList<Achievement> achievements = new ArrayList<>();
/**
* If a game is beign played.
*/
Game game;
/**
* @deprecated
*/
long startTime = 0;
/**
* The timer that provides the tick
*/
Timer timer;
/**
* Increment on each tick
*/
float runningTime = -1;
boolean isRunning = false;
private GamingActivity _activity;
int maxScore = 0;
protected RenderScript rs;
protected Paint squarePaint;
/**
* The scorres in this moment, as to draw them on the screen.
* Indexes are Emotion ordinals
*/
private Face currentFace;
ArrayList<Target> targets = new ArrayList<>();
class Target {
public int index;
public Emotion emotion;
public float value;
public float timestamp;
public boolean isHit = false;
}
/**
* Constructor
*/
public Scenario(int lvl_id, GamingActivity activity)
{
// go to first scenario if unexisting is given
if(!SCENARIOS.contains(lvl_id)) {
lvl_id = SCENARIOS.get(0);
}
this.id = lvl_id;
_activity = activity;
createTargets();
}
public void init() {
timer = new Timer("ScenarioTimer");
TimerTask tickTask;
tickTask = new TimerTask() {
@Override
public void run() {
// if (System.currentTimeMillis() - scheduledExecutionTime() >=
// MAX_TARDINESS)
// return; // Too late; skip this execution.
tick();
}
};
timer.schedule(tickTask, 0, 1000/DESIRED_FPS);
this.game = new Game(null, this, 0, 0, new Date(), null);
rs = RenderScript.create(_activity);
squarePaint = new Paint();
squarePaint.setColor(Color.YELLOW);
}
/**
* To be called on each timer tick
*/
public void tick()
{
if(!isRunning)
return;
runningTime += 1.0f/DESIRED_FPS;
if(isFinished()) {
stop();
return;
}
for(Target target: targets) {
// skip targets that are already scored
if(target.isHit == true) {
continue;
}
if(target.timestamp <= runningTime) {
Hit hit = new Hit(target, game, currentFace);
_activity.sound.play(_activity.soundIds.get(_activity.SOUND_SCORE), 1, 1,
2,0, hit.score / 200f + 0.5f ); // play back the sound slower
// depending on hit value
if(hit.bonus > 0) {
_activity.sound.play(_activity.soundIds.get(_activity.SOUND_BONUS), 0.7f, 0.7f,
1,0, hit.bonus / 100f + 0.8f ); // play back the sound slower
// depending on bonus value
}
target.isHit = true;
Bitmap outputBitmap = null;
if(currentFrame != null) {
// convert NV21 byteArrayFrame from camera to RGB bitmap.
Frame.ByteArrayFrame byteArrayFrame = (Frame.ByteArrayFrame) currentFrame;
outputBitmap = Nv21Image.nv21ToBitmap(rs, byteArrayFrame.getByteArray(), byteArrayFrame.getWidth(), byteArrayFrame.getHeight());
Frame.ROTATE rotation = byteArrayFrame.getTargetRotation();
// ie BY_90_CCW -90.0
Log.v("Scenario", "frame rotation: " + rotation.toString() + " " + rotation.toDouble());
// process rotation... (maybe faster if done on byte array?)
if(rotation != Frame.ROTATE.NO_ROTATION) {
Matrix matrix = new Matrix();
matrix.postRotate((int) rotation.toDouble());
// int width = rotation == Frame.ROTATE.BY_180 ? outputBitmap.getWidth() : outputBitmap.getHeight();
// int height = rotation == Frame.ROTATE.BY_180 ? outputBitmap.getHeight() : outputBitmap.getWidth();
outputBitmap = Bitmap.createBitmap(outputBitmap , 0, 0, outputBitmap .getWidth(), outputBitmap .getHeight(), matrix, true);
}
// Deprecated: now we send only fragments of the images anyway.
// Canvas canvas = new Canvas(outputBitmap);
// RectF rect = Hit.getBoundingboxForPoints(currentFace.getFacePoints());
// canvas.drawRect(rect, squarePaint);
/*
Also possible: only grayscale image - only loops over first part of the bytearray, is it faster?
int[] pixels;
int p;
int size = width*height;
for(int i = 0; i < size; i++) {
p = data[i] & 0xFF;
pixels[i] = 0xff000000 | p<<16 | p<<8 | p;
}
Bitmap bm = Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
*/
}
hit.frame = outputBitmap;
}
}
}
public Target getTargetByIndex(int target_index) {
return targets.get(target_index-1);
}
/**
* Add a target on given timestamp
* @param emotion
* @param value
* @param timestamp
*/
public void setTarget(Emotion emotion, float value, float timestamp)
{
// Log.e(GamingActivity.LOG_TAG, "Set target:" + Float.toString(timestamp) + " " + Float.toString(duration));
if((timestamp + 1) > duration)
{
duration = timestamp + 1; // always a bit onger than last target, as it otherwise the game does not finish pretty
}
Target target = new Target();
target.timestamp = timestamp;
target.value = value;
target.emotion = emotion;
target.index = targets.size() + 1;
targets.add(target);
maxScore = getMaxScore();
}
/**
* Add target after existing targets, give delta with last item instead of absolute time.
* @param emotion
* @param value
* @param interval
*/
public void addTarget(Emotion emotion, float value, float interval)
{
float timestamp;
if(targets.isEmpty()) {
timestamp = interval;
} else {
timestamp = targets.get(targets.size() - 1).timestamp + interval;
}
setTarget(emotion, value, timestamp);
}
public float getHitTotalValue()
{
float value = 0;
for (Hit hit : game.hits.values()) {
value += hit.score;
}
return value;
}
public float getHitTotalMisValue()
{
float value = 0;
for (Hit hit : game.hits.values()) {
value += 100-hit.score;
}
return value;
}
public float getBonusTotalValue()
{
float value = 0;
for (Hit hit : game.hits.values()) {
value += hit.bonus;
}
return value;
}
public float getHitPercentage() {
return (getHitTotalValue()/maxScore) * 100;
}
public float getBonusPercentage() {
// maxScore for bonus == normal maxScore
return (getBonusTotalValue()/maxScore) * 100;
}
public float getMissedPercentage() {
return (getHitTotalMisValue()/maxScore) * 100;
}
/**
* Check whether given timestamp is within duration of the scenario
* @param timestamp
* @return
*/
public boolean isWithinTime(float timestamp)
{
return timestamp <= duration;
}
public ArrayList<Target> getTargets() {
return targets;
}
/**
* Get the time within the scenario (so since start() has been called)
*/
public float getTime() {
return runningTime;
// if not started, don't move the labels, if started, move them by diff_y
// if(startTime == 0) {
// return 0;
// } else {
// float diff_t = ((System.currentTimeMillis() - startTime)) / (float) 1000;
// if(diff_t > duration) { // never larger than scenario duration
// return duration;
// }
// return diff_t;
// }
}
public void start()
{
startTime = System.currentTimeMillis();
isRunning = true;
}
public void pause() {
isRunning = false;
}
public void stop()
{
isRunning = false;
}
// TODO: create AttributeScoreCollection class, with this method.
public void setCurrentFace(Face face)
{
currentFace = face;
}
public boolean isFinished()
{
return runningTime > duration;
}
public Score getScore() {
if(!isFinished()) {
return null;
}
Score score = new Score(id, getHitTotalValue());
score.setTargets(targets);
return score;
}
public int getMaxScore() {
return targets.size() * 100;
}
public void createTargets() {
AchievementCollection achievementCollection = AchievementCollection.getInstance();
switch(id) {
case LVL_ANGER:
setTarget(Emotion.ANGER, 100, 1);
setTarget(Emotion.ANGER, 100, 2);
setTarget(Emotion.ANGER, 10, 3);
setTarget(Emotion.ANGER, 20, 4);
setTarget(Emotion.ANGER, 40, 5);
setTarget(Emotion.ANGER, 70, 6);
setTarget(Emotion.ANGER, 100, 7);
setMinimumScoreFromPercentage(10);
minimumAchievements = 2;
achievements.add(achievementCollection.get(1));
achievements.add(achievementCollection.get(3));
break;
case LVL_JOY:
setTarget(Emotion.JOY, 100, 1);
setTarget(Emotion.JOY, 100, 2);
setTarget(Emotion.JOY, 100, 4);
setTarget(Emotion.CONTEMPT, 20, 4);
setTarget(Emotion.ANGER, 100, 5);
setTarget(Emotion.JOY, 100, 7);
setTarget(Emotion.ANGER, 100, 9);
setTarget(Emotion.JOY, 100, 11);
setTarget(Emotion.JOY, 70, 12);
setTarget(Emotion.JOY, 60, 13);
setTarget(Emotion.JOY, 30, 14);
setTarget(Emotion.JOY, 10, 14.5f);
setTarget(Emotion.ANGER, 100, 16);
setTarget(Emotion.JOY, 100, 17);
setTarget(Emotion.JOY, 100, 18);
setTarget(Emotion.JOY, 100, 19);
setTarget(Emotion.JOY, 100, 20);
setMinimumScoreFromPercentage(10);
minimumAchievements = 2;
achievements.add(achievementCollection.get(2));
achievements.add(achievementCollection.get(4));
break;
case LVL_SURPRISE:
setTarget(Emotion.SURPRISE, 20, 1);
setTarget(Emotion.SURPRISE, 50, 2);
setTarget(Emotion.SURPRISE, 80, 3);
setTarget(Emotion.SURPRISE, 100, 4);
setMinimumScoreFromPercentage(10);
minimumAchievements = 2;
achievements.add(achievementCollection.get(5));
achievements.add(achievementCollection.get(6));
break;
case LVL_SADDNESS:
setTarget(Emotion.SADNESS, 20, 1);
setTarget(Emotion.SADNESS, 50, 2);
setTarget(Emotion.SADNESS, 80, 3);
setTarget(Emotion.SADNESS, 100, 4);
setMinimumScoreFromPercentage(10);
minimumAchievements = 2;
break;
}
}
public void setMinimumScoreFromPercentage(float percentage) {
this.minimumScore = maxScore * (percentage / 100f);
}
public int getNextLevelId() {
int nextIdx = SCENARIOS.indexOf(id) + 1;
if(SCENARIOS.size() <= nextIdx) {
return SCENARIOS.get(0);
}
return SCENARIOS.get(nextIdx);
}
public boolean isFinalLevel() {
if(SCENARIOS.get(SCENARIOS.size()-1) == id)
return true;
return false;
}
public boolean isHigherThen(int lvl_id) {
return isHigherLevel(id, lvl_id);
}
public static boolean isHigherLevel(int lvl_id, int then_lvl_id) {
if(lvl_id == then_lvl_id) {
return false;
}
return SCENARIOS.indexOf(lvl_id) > SCENARIOS.indexOf(then_lvl_id);
}
public String toString() {
switch(id) {
case LVL_ANGER:
return "\"Smile like you mean it\"";
case LVL_JOY:
return "The seven";
case LVL_SURPRISE:
return "\"Let's talk business\"";
case LVL_SADDNESS:
return "A sad sad situation";
}
return "...";
}
public String getDescription() {
switch(id) {
case LVL_ANGER:
return "You're CEO of a big company. Remarkably, research shows that CEO's of big companies look generally more angry and disgusted when the company is doing well.\n" +
"\n" +
"However, you're company is not doing well. Make sure you hide this for the press that is watching every move!";
case LVL_JOY:
return "You're at a job interview, and you know your face is being monitored.\n" +
"Make sure that you express your enthusiasm for the job!";
case LVL_SURPRISE:
return "Your friends have organised a surprise party and you've found out! However, you don't want them to know this. First keep calm and then make sure you're as surprised and joyful as possible!\n";
case LVL_SADDNESS:
return "You're at the funeral of your great aunt. You don't really know her, but still want to show your respect.";
}
return "...";
}
public Hit getHitForTarget(Target target) {
return game.hits.get(target.index);
}
}