/* * The MIT License * * Copyright 2015 nikku. * * 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 kickr.analytics; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import jskills.GameInfo; import jskills.Player; import jskills.PlayerInfo; import jskills.Rating; import jskills.RatingUpdates; import jskills.Team; import jskills.TeamInfo; import jskills.TrueSkillCalculator; import kickr.db.dao.MatchDAO; import kickr.db.entity.Match; import kickr.db.entity.MatchResult; /** * * @author nikku */ public class Analytics { private static GameInfo DEFAULT_GAME_INFO = GameInfo.getDefaultGameInfo(); private static Rating NULL_RATING = DEFAULT_GAME_INFO.getDefaultRating(); private static RoundResult NULL_RESULT = new RoundResult(NULL_RATING, 0, 0); private static class RoundResult { private Rating rating; private int score; private int gamesPlayed; public RoundResult(Rating rating, int score, int gamesPlayed) { this.rating = rating; this.score = score; this.gamesPlayed = gamesPlayed; } public Rating getRating() { return rating; } public int getScore() { return score; } public RoundResult update(Rating rating, int scoreUpdate) { return new RoundResult(rating, score + scoreUpdate, this.gamesPlayed + 1); } } private static class Ratings { private int round; private Map<Player, List<RoundResult>> allResults; public Ratings() { round = 0; allResults = new HashMap<>(); } protected List<RoundResult> getResults(Player player) { List<RoundResult> results = allResults.computeIfAbsent(player, p -> new ArrayList<>()); int loggedResults = results.size(); RoundResult lastResult = loggedResults == 0 ? NULL_RESULT : results.get(loggedResults - 1); if (loggedResults < round) { results.addAll(Collections.nCopies(round - loggedResults, lastResult)); } return results; } public void addResults(Map<Player, Rating> newRatings, Team team, int scoreUpdate) { for (Player player: team.getPlayers()) { addResult(player, newRatings.get(player), scoreUpdate); } } private void addResult(Player player, Rating rating, int scoreUpdate) { List<RoundResult> results = getResults(player); RoundResult lastResult = getLastResult(player); results.add(lastResult.update(rating, scoreUpdate)); } public void print() { ////// RATINGS //////////////////// System.out.println(""); System.out.println(""); // HEADER System.out.print("Name;"); for (int i = 0; i < round; i++) { System.out.print(i + ";"); } System.out.println(""); // COLUMNS allResults.forEach((player, roundResults) -> { System.out.printf("%3s;", player.getId()); roundResults.forEach(roundResult -> { System.out.printf("%.3f;", roundResult.getRating().getConservativeRating()); }); System.out.println(); }); ////// SCORES //////////////////// System.out.println(""); System.out.println(""); // HEADER System.out.print("Name;"); for (int i = 0; i < round; i++) { System.out.print(i + ";"); } System.out.println(""); // COLUMNS allResults.forEach((player, roundResults) -> { System.out.printf("%3s;", player.getId()); roundResults.forEach(roundResult -> { System.out.printf("%d;", roundResult.getScore()); }); System.out.println(); }); // HEADER System.out.println(""); System.out.println(""); System.out.print("Name;"); for (int i = 0; i < round; i++) { System.out.print(i + ";"); } System.out.println(""); // COLUMNS allResults.forEach((player, roundResults) -> { System.out.printf("%3s;", player.getId()); RoundResult lastResult = roundResults.get(roundResults.size() - 1); System.out.printf("%d;", lastResult.gamesPlayed); System.out.println(); }); } public Rating getRating(Player player) { return getLastResult(player).getRating(); } public void fillBlanks() { // ensure we have the latest results for _ALL_ players for (Player player: allResults.keySet()) { getResults(player); } } public void roundFinished() { round++; } private RoundResult getLastResult(Player player) { List<RoundResult> results = getResults(player); return round > 0 ? results.get(round - 1) : NULL_RESULT; } private int getRound() { return round; } } private final Ratings ratings; public Analytics() { this.ratings = new Ratings(); } public Rating getRating(Player player) { return ratings.getRating(player); } public void run(MatchDAO matchDao) { List<Match> matches = matchDao.getMatches(null); for (Match match: matches) { GameInfo gameInfo = GameInfo.getDefaultGameInfo(); Team team1 = createTeam(match.getTeam1()); Team team2 = createTeam(match.getTeam2()); List<Team> teams = Arrays.asList(team1, team2); MatchResult result = match.getResult(); System.out.println(); System.out.println(); System.out.println("GAME " + ratings.getRound()); System.out.println(team1); System.out.println("----- VS -----"); System.out.println(team2); System.out.printf("----- %s:%s -----\n", match.getResult().getTeam1Wins(), match.getResult().getTeam2Wins()); System.out.printf("----- %s:%s -----\n", result.getTotalGames() - result.getTeam1Wins(), result.getTotalGames() - result.getTeam2Wins()); RatingUpdates newRatings = TrueSkillCalculator.calculateNewRatings( gameInfo, teams, result.getTotalGames() - result.getTeam1Wins(), result.getTotalGames() - result.getTeam2Wins() ); /** // 48 int winPoints = 42; // 24 int lossPoints = 18; // 0.80 double probability = newRatings.getProbability(); // 48 * (1 - 0.80) + 6 int winBounty = (int) (Math.ceil(winPoints * (1 - probability)) + 6); int lossBounty = (int) (-1 * (Math.ceil(lossPoints * (1 - probability))) + 6); */ // 0.80 double probability = newRatings.getProbability(); int winBounty = Scores.calculateWinPoints(probability); int lossBounty = Scores.calculateLossPoints(probability); System.out.printf("----- probability: %.3f, win/loss: %s / %s\n", probability, winBounty, lossBounty); ratings.addResults(newRatings, team1, result.getTeam1Wins() > result.getTeam2Wins() ? winBounty : lossBounty); ratings.addResults(newRatings, team2, result.getTeam2Wins() > result.getTeam1Wins() ? winBounty : lossBounty); ratings.roundFinished(); } ratings.fillBlanks(); ratings.print(); } private Team createTeam(kickr.db.entity.Team kickrTeam) { kickr.db.entity.Player kickrOffense = kickrTeam.getOffense(); kickr.db.entity.Player kickrDefense = kickrTeam.getDefense(); Player<String> offense = new PlayerInfo<>(kickrOffense.getAlias()); Player<String> defense = new PlayerInfo<>(kickrDefense.getAlias()); TeamInfo team = new TeamInfo(); team.addPlayer(offense, getRating(offense)); if (!kickrOffense.equals(kickrDefense)) { team.addPlayer(defense, getRating(defense)); } return team; } }