/*
* 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.mit;
import gwap.NotEnoughDataException;
import gwap.model.Person;
import gwap.model.action.Bet;
import gwap.model.action.Characterization;
import gwap.model.action.LocationAssignment;
import gwap.model.action.StatementAnnotation;
import gwap.model.resource.Location;
import gwap.model.resource.Location.LocationType;
import gwap.model.resource.LocationHierarchy;
import gwap.model.resource.Resource;
import gwap.model.resource.Statement;
import gwap.model.resource.StatementToken;
import gwap.tools.CharacterizationBean;
import gwap.tools.CharacterizationBean.ResultForType;
import gwap.tools.Pair;
import gwap.wrapper.LocationPercentage;
import gwap.wrapper.Percentage;
import gwap.wrapper.Score;
import gwap.wrapper.StatementTokenPercentage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
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.log.Log;
/**
* @author Fabian Kneißl
*/
@AutoCreate
@Name("mitPokerScoring")
@Scope(ScopeType.STATELESS)
public class PokerScoring {
public static final int CHARACTERIZATION_BONUS_30_PERCENT = 5;
public static final int CHARACTERIZATION_BONUS_50_PERCENT = 10;
public static final int ANNOTATION_LIKE_MAJORITY = 10;
public static final int ANNOTATION_LIKE_THIRD = 5;
private static final int LOCATION_ASSIGNMENT_MAX_SCORE = 10;
private static final double LOCATION_ASSIGNMENT_ND_FACTOR = 33;
private static final int BET_MAX_SCORE = 100;
private static final double BET_ND_FACTOR = 11;
public static final int POKER_CORRECT = 10;
public static final int POKER_CORRECT_DIFFICULT = 20;
public static final int POKER_OWNER_PER_GUESS = 5;
public static final int POKER_OWNER_PER_GUESS_LT_20 = 2;
public static final int MIN_NR_FOR_STATISTICS = 3;
@In private EntityManager entityManager;
@Logger private Log log;
@In private CharacterizationBean characterizationBean;
/**
* Returns the score for a person's LocationAssignment
*
* @param la
* @return null if the location does not equal the predefined location
*/
public Score locationAssignment(LocationAssignment la) {
Percentage percentage = getFuzzyPercentage(la.getLocation(), la.getResource());
// 100 - percentage, because f(0)=maximumScore
if (percentage.getTotal() <= 0)
return null;
int score = getNormalDistributedScore(100-percentage.getPercentage(), LOCATION_ASSIGNMENT_ND_FACTOR, LOCATION_ASSIGNMENT_MAX_SCORE);
la.setScore(score);
log.info("Score for LocationAssignment #0 with percentage #1 is #2", la, percentage, score);
return new Score(score, percentage);
}
/**
* Calculates the score for a given LocationAssignment in the poker game
*
* @param locationAssignment
* @return null if the location does not equal the predefined location
*/
public Score poker(LocationAssignment locationAssignment) {
Query query = entityManager.createNamedQuery("pokerBet.byResourceAndLocation");
query.setParameter("resource", locationAssignment.getResource());
query.setParameter("location", locationAssignment.getLocation());
List<Bet> bets = query.getResultList();
if (bets.isEmpty())
return null;
else { // LocationAssignment.location is the same as bet.location
Percentage percentage = getRawPercentage(locationAssignment.getLocation(), locationAssignment.getResource());
int score = POKER_CORRECT;
if (percentage.getTotal() >= 2 && percentage.getFraction() <= 0.4)
score = POKER_CORRECT_DIFFICULT;
locationAssignment.setScore(score);
return new Score(score, percentage);
}
}
public Integer characterization(Resource resource, Characterization[] characterizations) throws NotEnoughDataException {
int score = 0;
boolean validCharacterization = false;
for (Characterization c : characterizations) {
if (CharacterizationBean.isValueSet(c)) {
validCharacterization = true;
break;
}
}
if (validCharacterization) {
boolean hasEnoughData = false;
for (Characterization c : characterizations) {
if (CharacterizationBean.isValueSet(c) && !CharacterizationBean.isValueUnknown(c)) {
ResultForType result = characterizationBean.getResult(resource.getId(), c.getName());
Percentage percentage = new Percentage(0, result.getTotal());
// Check if value is there
for (Pair<Integer, Integer> p : result.getResult()) {
if (c.getValue().equals(p.a)) {
percentage = new Percentage(p.b, result.getTotal());
break;
}
}
if (percentage.getTotal() >= MIN_NR_FOR_STATISTICS) {
hasEnoughData = true;
int scoreSingle = 0;
if (percentage.getFraction() >= 0.5)
scoreSingle = CHARACTERIZATION_BONUS_50_PERCENT;
else if (percentage.getFraction() >= 0.3)
scoreSingle = CHARACTERIZATION_BONUS_30_PERCENT;
c.setScore(scoreSingle);
score += scoreSingle;
}
}
}
if (!hasEnoughData)
throw new NotEnoughDataException();
}
return score;
}
public Integer highlighting(StatementAnnotation annotation) throws NotEnoughDataException {
int score = 0;
annotation.setScore(score);
Percentage percentage = getHighlightingPercentage(annotation);
if (percentage != null) {
if (percentage.getTotal() > MIN_NR_FOR_STATISTICS) {
if (percentage.getPercentage() >= 50) {
score = ANNOTATION_LIKE_MAJORITY;
} else if (percentage.getPercentage() >= 30) {
score = ANNOTATION_LIKE_THIRD;
}
} else if (percentage.getTotal() == MIN_NR_FOR_STATISTICS) {
List<StatementAnnotation> allAnnotations = entityManager.createNamedQuery("statementAnnotation.byStatement")
.setParameter("statement", annotation.getStatement())
.getResultList();
for (int i = 0; i < allAnnotations.size(); i++) {
StatementAnnotation sa = allAnnotations.get(i);
if (percentage.getPercentage() >= 50) {
sa.setScore(ANNOTATION_LIKE_MAJORITY);
} else if (percentage.getPercentage() >= 30) {
sa.setScore(ANNOTATION_LIKE_THIRD);
}
if (sa.getId().equals(annotation.getId()))
score = sa.getScore();
}
} else {
throw new NotEnoughDataException();
}
}
annotation.setScore(score);
return score;
}
public int getPersonScoreSum(Person person) {
if (person == null || person.getId() == null)
return 0;
try {
person = entityManager.find(Person.class, person.getId());
// Calculate scores for bets not yet having a score
List<Bet> bets = (List<Bet>)entityManager.createNamedQuery("bet.byPersonWithoutScore").setParameter("person", person).getResultList();
for (Bet bet: bets) {
updateScoreForBet(bet);
}
Query q = entityManager.createNamedQuery("highscore.mit.bySinglePerson");
q.setParameter("person", person);
int actionScore = numberToInt((Number) ((gwap.model.Highscore)q.getSingleResult()).getScore());
return actionScore;
} catch (NoResultException nre) {
return 0;
} catch (Exception e) {
log.warn("Could not calculate person score sum", e);
return 0;
}
}
public void calculateAllBetScores() {
List<Bet> bets = entityManager.createNamedQuery("bet.allWithPerson").getResultList();
log.info("Calculating scores for #0 bets...", bets.size());
for (Bet bet : bets)
updateScoreForBet(bet);
bets = entityManager.createNamedQuery("pokerBet.allWithPerson").getResultList();
log.info("Calculating scores for #0 poker bets...", bets.size());
for (Bet bet : bets)
updateScoreForPokerBet(bet);
log.info("Finished calculating scores.");
}
public void updateScoreForBets(Resource resource) {
List<Bet> bets = entityManager.createNamedQuery("bet.byResource").setParameter("resource", resource).getResultList();
for (Bet bet : bets)
updateScoreForBet(bet);
}
public void updateScoreForBet(Bet bet) {
entityManager.refresh(bet);
bet.setScore(0);
bet.setCurrentMatch(0);
if (bet.getPoints() != null) {
Percentage percentage = getFuzzyPercentage(bet.getLocation(), bet.getResource());
if (percentage.getTotal() > 1) { // start when at least one more user gave a characterisation
double deviation = Math.abs(percentage.getPercentage() - bet.getPoints());
int score = getNormalDistributedScore(deviation, BET_ND_FACTOR, BET_MAX_SCORE);
log.info("Score for bet #0 with deviation #1 (bet on #3%, really #4%) is #2", bet, deviation, score, bet.getPoints(), percentage);
bet.setScore(score);
bet.setCurrentMatch(percentage.getPercentage().intValue());
}
}
entityManager.flush();
}
public void updateScoreForPokerBets(Resource resource) {
List<Bet> bets = entityManager.createNamedQuery("pokerBet.byResource")
.setParameter("resource", resource)
.getResultList();
for (Bet bet : bets)
updateScoreForPokerBet(bet);
}
public void updateScoreForPokerBet(Bet bet) {
entityManager.refresh(bet);
bet.setScore(0);
bet.setCurrentMatch(0);
Percentage percentage = getRawPercentage(bet.getLocation(), bet.getResource());
// Need to add rounds where the resource was skipped
Query q = entityManager.createNamedQuery("gameRound.nrRoundsWithResourceAndNoLocationAssignment");
q.setParameter("gameTypeName", "mitPoker");
q.setParameter("resource", bet.getResource());
long nrRounds = ((Number)q.getSingleResult()).longValue();
percentage = new Percentage(percentage.getSum()-1, percentage.getTotal()-1+nrRounds); // excluding the user's bet
if (percentage.getTotal() > 0) {
int score = 0;
score = (int) (Math.max(1-percentage.getFraction(), 0) * percentage.getTotal()*POKER_OWNER_PER_GUESS);
bet.setScore(score);
bet.setCurrentMatch(percentage.getPercentage().intValue());
log.info("Score for poker bet #0 (#1 bet on the same, total #2) is #3", bet, (long)percentage.getSum(), percentage.getTotal(), score);
}
entityManager.flush();
}
/**
* Probability density function of the normal distribution with one variance-like factor.
* Example: sigma=11 => f(0)=maximumScore, f(36)=1
* @param normalScore
* @param sigma affects the stretch of the function
* @param maximumScore maximum score to reach
* @return values between maximumScore and 0
*/
private int getNormalDistributedScore(double normalScore, double sigma, int maximumScore) {
double score = maximumScore * Math.exp(-0.5*Math.pow(normalScore / sigma, 2));
return (int) Math.round(score);
}
private Percentage getRawPercentage(Location location, Resource resource) {
Number sum = (Number) entityManager.createNamedQuery("locationAssignment.countByResourceAndLocation")
.setParameter("resource", resource).setParameter("location", location).getSingleResult();
Number total = (Number) entityManager.createNamedQuery("locationAssignment.countByResource")
.setParameter("resource", resource).getSingleResult();
return new Percentage(sum, total);
}
private Percentage getFuzzyPercentage(Location location, Resource resource) {
Number sum = (Number) entityManager.createNamedQuery("locationAssignment.scoringSumByResourceAndLocation")
.setParameter("resource", resource).setParameter("location", location).getSingleResult();
Number total = (Number) entityManager.createNamedQuery("locationAssignment.countByResource")
.setParameter("resource", resource).getSingleResult();
return new Percentage(sum, total);
}
/**
*
* @param originalLocation the location choosen by the user
* @param comparedLocation the compared location
* @param parents the list of Locations in the higher hierarchy - can be made by calling getLocationHierarchyTree()
* @return score for matching
*/
public double getFuzzyMatchingValue(Location originalLocation, Location comparedLocation) {
try {
Number correlation = (Number) entityManager.createNamedQuery("locationHierarchy.correlationByLocations")
.setParameter("location", comparedLocation)
.setParameter("sublocation", originalLocation)
.getSingleResult();
return correlation.doubleValue();
} catch (NoResultException e) {
log.info("Got no result for locations #0 / #1 :(", originalLocation, comparedLocation);
return 0;
}
}
public List<Location> getLocationHierachyTree(Location rootLocation){
List<Location> tree = new ArrayList<Location>();
Location currentLocation = rootLocation;
while(!currentLocation.getType().equals(LocationType.AREA) && !currentLocation.getType().equals(LocationType.COUNTRY)){
LocationHierarchy nextLocation = (LocationHierarchy) entityManager
.createNamedQuery("locationHierarchy.bySublocationId")
.setParameter("sublocationId", currentLocation.getId())
.getSingleResult();
tree.add(nextLocation.getLocation());
currentLocation = nextLocation.getLocation();
}
return tree;
}
public Percentage getHighlightingPercentage(StatementAnnotation annotation) {
if (annotation.getStatementTokens().size() == 0)
return null;
try {
@SuppressWarnings("unchecked")
List<Long> similarAnnotationsId = entityManager
.createNamedQuery("statementAnnotation.similarAnnotations")
.setParameter("statement", annotation.getStatement())
.setParameter("statementAnnotation", annotation)
.setParameter("minMatchingTokens", (long)annotation.getStatementTokens().size())
.getResultList();
int same = 0;
for (Long id : similarAnnotationsId) {
StatementAnnotation a = entityManager.find(StatementAnnotation.class, id);
if (a.getStatementTokens().size() == annotation.getStatementTokens().size())
same++;
}
int total = numberToInt((Number) entityManager.createNamedQuery("statementAnnotation.countNotEmptyByStatement")
.setParameter("statement", annotation.getStatement())
.getSingleResult());
Percentage percentage = new Percentage(same, total);
log.info("Highlighting: #0: #1% the same (sum: #2, count: #3)", annotation.getStatement(), percentage.getPercentage(), percentage.getSum(), percentage.getTotal());
return percentage;
} catch (Exception e) {
log.warn("Could not calculate highlighting percentage", e);
return null;
}
}
public Percentage getStatementPercentageFrom(Resource resource, Location location) {
try {
int same = numberToInt((Number) entityManager.createNamedQuery("locationAssignment.countByResourceAndLocation").setParameter("resource", resource).setParameter("location", location).getSingleResult());
int total = numberToInt((Number) entityManager.createNamedQuery("locationAssignment.countByResource").setParameter("resource", resource).getSingleResult());
Percentage percentage = new Percentage(same, total);
log.info("Location assignment: #0: #1% for Location #2", resource, percentage, location);
return percentage;
} catch (Exception e) {
log.warn("Could not calculate statement percentage", e);
return null;
}
}
public List<LocationPercentage> getStatementPercentages(Resource resource) {
List<LocationPercentage> percentages = new ArrayList<LocationPercentage>();
try {
int total = numberToInt((Number) entityManager.createNamedQuery("locationAssignment.countByResource").setParameter("resource", resource).getSingleResult());
List<Object[]> list = entityManager.createNamedQuery("locationAssignment.countAndLocationByResource").setParameter("resource", resource).getResultList();
for (Object[] countAndLocation : list) {
Number count = (Number) countAndLocation[0];
Long locationId = (Long) countAndLocation[1];
Location location = entityManager.find(Location.class, locationId);
percentages.add(new LocationPercentage(location, count, total));
}
return percentages;
} catch (Exception e) {
log.warn("Could not calculate statement percentages", e);
return null;
}
}
public List<StatementTokenPercentage> getHighlightingResult(Statement statement) {
List<StatementTokenPercentage> highlightingResult = new ArrayList<StatementTokenPercentage>();
Query q = entityManager.createNamedQuery("statementAnnotation.byToken" )
.setParameter("statementId", statement.getId());
List<Object[]> annotationCounts = q.getResultList();
Map<Long, Integer> annotationCountsMap = new HashMap<Long, Integer>();
Query q2 = entityManager.createNamedQuery("statementAnnotation.countByStatement" )
.setParameter("statementId", statement.getId());
int total = ((Number)q2.getSingleResult()).intValue();
for (Object[] row : annotationCounts) {
int count = ((Number)row[1]).intValue();
annotationCountsMap.put((Long)row[0], count);
}
for (int i = 0; i<statement.getStatementTokens().size(); i++){
StatementToken token = statement.getStatementTokens().get(i);
Integer sum = annotationCountsMap.get(token.getId());
if (sum == null)
sum = 0;
highlightingResult.add(new StatementTokenPercentage(token, sum, total));
}
return highlightingResult;
}
public int getHighlightingTotal(Statement statement) {
Query q2 = entityManager.createNamedQuery("statementAnnotation.countByStatement" )
.setParameter("statementId", statement.getId());
int total = ((Number)q2.getSingleResult()).intValue();
return total;
}
public Bet getBet(Statement statement, Person person) {
try {
Query query = entityManager.createNamedQuery("bet.byResourceAndPerson");
query.setParameter("person", person);
query.setParameter("resource", statement);
query.setMaxResults(1);
Bet bet = (Bet) query.getSingleResult();
return bet;
} catch (Exception e) {
log.warn("No LocationAssignment defined", e);
return null;
}
}
private int numberToInt(Number n) {
if (n == null)
return 0;
else
return n.intValue();
}
public String getStatementTokenBorder(StatementTokenPercentage stp, Person person){
StatementToken statementToken = stp.getStatementToken();
Query q = entityManager.createNamedQuery("statementAnnotation.byStatementAndPerson");
q.setParameter("person", person);
q.setParameter("statement", statementToken.getStatement());
q.setMaxResults(1);
List<StatementAnnotation> resultList = q.getResultList();
if (resultList.size() > 0)
for (StatementToken token : resultList.get(0).getStatementTokens()) {
if (token.getId().equals(statementToken.getId()))
return "border: 4px solid black;";
}
return "";
}
}