505 lines
14 KiB
Java
505 lines
14 KiB
Java
package com.rubenvandeven.emotionhero;
|
|
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
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,
|
|
1,0, hit.score / 200f + 0.5f ); // play back the sound slower
|
|
// depending on hit 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());
|
|
|
|
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
|
|
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);
|
|
}
|
|
|
|
}
|