/* * This file is part of gwap, an open platform for games with a purpose * * Copyright (C) 2013 * Project play4science * Lehr- und Forschungseinheit für Programmier- und Modellierungssprachen * Ludwig-Maximilians-Universität München * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package gwap.game.quiz; import gwap.game.quiz.tools.QuizQuestionBean; import gwap.model.Person; import gwap.model.action.PerceptionPair; import java.util.Date; import java.util.Random; import javax.persistence.EntityManager; import javax.persistence.Query; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Create; import org.jboss.seam.annotations.Destroy; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.faces.FacesMessages; import org.jboss.seam.international.LocaleSelector; import org.jboss.seam.log.Log; /** * Gives a recommendation for the answers i.e. Answer A: 50% Answer B: 25% = * AnswerC AnswerD=0% * * @author Jonas Hölzler */ @Name("ratingEvaluator") @Scope(ScopeType.APPLICATION) public class RatingEvaluator { private PerceptionPair[] userPairList; int VOLLER_BONUS = 1; int HALBER_BONUS = 0; int KEIN_BONUS = -1; @Create public void init() { log.info("Creating"); } @Destroy public void destroy() { log.info("Destroying"); } @Logger private Log log; @In private FacesMessages facesMessages; @In private EntityManager entityManager; @In private LocaleSelector localeSelector; /** * Calculates the recommandation for feedback: Answer A, ANswer B, Answer C, * Answer D * * @param question * @param userPerceptionRating * @param avgUserRating * @return int[4] */ public int[] calculateRecommendationForFeedbackJoker( QuizQuestionBean question, UserPerceptionRating userPerceptionRating, int[] avgUserRating) { this.userPairList = userPerceptionRating.getPairs(); if (avgUserRating[0] == -1) { int[] algorithmicRating = calcEstimatedWoelfflinValue(question); return algorithmicRating; } else { return avgUserRating; } } public int[] calcAvgUserRating(QuizQuestionBean question) { int[] rating = new int[5]; String[] s = new String[] { "LinearVsMalerisch", "VielheitVsEinheit", "FlaecheVsTiefe", "KlarheitVsUnklarheitUndBewegtheit", "GeschlossenVsOffen" }; for (int i = 0; i < 5; ++i) { String pairname = s[i]; Query query = entityManager .createNamedQuery("PerceptionPair.averageRatingByResourceAndPairname"); query.setParameter("resource", question.getArtResource()); query.setParameter("pairname", pairname); try { rating[i] = ((Double) query.getSingleResult()).intValue(); } catch (NullPointerException e) { rating[i] = -1; } } return rating; } /* * calculates the Woelfflin guess value (0,0,0,0,0) for year < 1480 * (100,100,100,100,100) for year > 1680 linear interpolation between 1480 * and 1680 */ private int[] calcEstimatedWoelfflinValue(QuizQuestionBean question) { int questionNum = question.getQuestionNumber(); assert (questionNum > 0 && questionNum <= 15); if (questionNum <= 5) { return generateRandomRecommendation(question); } else { int[] algorithmicRating = generateRealRecommendation(question); // now calculate distances using norm int[] distances = new int[4]; for (int i = 0; i < 4; ++i) { distances[i] = calculateDistance(new int[]{algorithmicRating[i],algorithmicRating[i],algorithmicRating[i],algorithmicRating[i],algorithmicRating[i]}); } // now all four distances are between 0 and 100 // we want to return the overall probabilty // i.e. the p(A) + p(B) + p(C) + p(D) = 1 float denom = distances[0] + distances[1] + distances[2] + distances[3]; int[] result = new int[4]; result[0] = (int) (distances[0] / denom * 100); result[1] = (int) (distances[1] / denom * 100); result[2] = (int) (distances[2] / denom * 100); result[3] = (int) (distances[3] / denom * 100); return result; } } /** * * @param rating Array[5] * @return The Woelfflin Distance */ private int calculateDistance(int[] rating) { Float distance; if (rating[0] < 0) { distance = new Random().nextFloat() * 100f; } else { distance = 0f; for (int i = 0; i < 5; ++i) { // for (PerceptionPair p : userPairList) { distance += Math.abs(userPairList[i].getValue() - rating[i]) / 100.0f; } distance = distance / 5.0f; distance = distance * 100.0f; } return distance.intValue(); } private int[] generateRealRecommendation(QuizQuestionBean question) { Person[] persons = question.getAnswers(); int[] algorithmicRating = new int[4]; for (int i = 0; i < 4; ++i) { algorithmicRating[i] = calcWoelfflinValue(persons[i], question); } return algorithmicRating; } /** * * @param person * @param question * @return Calculates the Woelfflin Value i.e. value between 0 and 100 */ private int calcWoelfflinValue(Person person, QuizQuestionBean question) { Float algorithmicRating; int year = estimateYearForPerson(person, question); if (year < 1420) { return -1; } else if (year < 1510) { algorithmicRating = (year - 1420f) / (1510f - 1420f); } else if (year < 1600) { algorithmicRating = 1 - ((year - 1510f) / (1600f - 1510f)); } else if (year < 1680) { algorithmicRating = (year - 1600f) / (1680f - 1600f); } else if (year < 1760) { algorithmicRating = 1f; } else if (year < 1800) { algorithmicRating = 1 - ((year - 1760f) / (1800f - 1760f)); } else { return -1; } Float tmp = (algorithmicRating * 100f); return tmp.intValue(); } /* * estimates the year for a person */ private int estimateYearForPerson(Person person, QuizQuestionBean question) { Date d = person.getDeath(); int year; if (d == null) { try { year = Integer.parseInt(question.getArtResource() .getDateCreated()); } catch (NumberFormatException e) { year = 1600; } } else { year = d.getYear() + 1900 - 30; } return year; } private int[] generateRandomRecommendation(QuizQuestionBean question) { int[] result = new int[4]; int questionNum = question.getQuestionNumber(); assert (questionNum > 0); int correctAnswerPos = question.getCorrectAnswerPos(); Random R = new Random(); int sum = 0; for (int i = 0; i < result.length; ++i) { if (i != correctAnswerPos) { result[i] = R.nextInt(questionNum * 5); sum += result[i]; } } result[correctAnswerPos] = 100 - sum; return result; } /** * * @param quizQuestion * @param recommendedRating * @param avgUserRating Array[5] * @return Calculates the bonus: Full/Half/No Bonus */ public int calculateBonus(QuizQuestionBean quizQuestion, UserPerceptionRating recommendedRating, int[] avgUserRating) { Person correctPerson = quizQuestion.getCorrectAnswer(); float distance; if (avgUserRating[0] < 0) { int[] algorithmicRating = new int[5]; for (int i = 0; i < 5; ++i) { algorithmicRating[i] = calcWoelfflinValue(correctPerson, quizQuestion); } distance = calculateDistance(algorithmicRating); } else { distance = calculateDistance(avgUserRating); } log.info("Distance: "+ distance); if (distance < 25) { return VOLLER_BONUS; } else if (distance < 40) { return HALBER_BONUS; } else { return KEIN_BONUS; } } }