/** * Copyright 1999-2009 The Pegadi Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Server for recording scores. * * @author HÃ¥vard Wigtil <havardw at pvv.org> * @author Jan-Preben Mossin <jpmossin@underdusken.no> * @author Marvin B. Lillehaug <lillehau@underdusken.no> */ package org.pegadi.server.score; import no.dusken.common.model.Person; import org.pegadi.games.Score; import org.pegadi.games.tetris.TetrisScore; import org.pegadi.server.ScoreServer; import org.pegadi.server.user.UserServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.core.simple.ParameterizedRowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import javax.sql.DataSource; import java.sql.*; import java.util.*; import java.util.Date; public class ScoreServerImpl implements ScoreServer { /** * User server to look up user names. */ protected UserServer userServer; private TetrisScoreRowMapper mapper; private JdbcTemplate template; private final Logger log = LoggerFactory.getLogger(getClass()); public ScoreServerImpl() { mapper = new TetrisScoreRowMapper(); } public void setDataSource(DataSource dataSource) { template = new JdbcTemplate(dataSource); } public void setUserServer(UserServer userServer) { this.userServer = userServer; } /** * Starts score recording for a new game. The <code>Score</code> object that is * returned <i>must</i> be used when calling {@link #updateScore } and * {@link #endGame }, as each score has an unique ID. * * @param domain The domain for the game. * @return A new Score object, with the score set to 0. If the domain is not known, * this method will return <code>null</code>. */ public Score startGame(final Person person, String domain) { if(domain.equals("tetris")) { final String insertSql = "insert into score_tetris (userID, score, level, linecount, starttime, active) values (?, ?, ?, ?, ?, true)"; KeyHolder keyHolder = new GeneratedKeyHolder(); // The key/id of the new score is needed template.update( new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection con) throws SQLException { PreparedStatement statement = con.prepareStatement(insertSql, new String[] {"ID"}); statement.setString(1, person.getUsername()); statement.setLong(2, 0); // score statement.setInt(3,1); // level statement.setInt(4,0); // lines statement.setTimestamp(5, new Timestamp((new GregorianCalendar()).getTimeInMillis())); return statement; } }, keyHolder ); int scoreId = keyHolder.getKey().intValue(); return new TetrisScore(scoreId, person.getUsername(), person.getName(),1); } else { log.error("Domain '{}' not implemented yet!", domain); return null; } } /** * Updates the score for a running game. The <code>Score</code> object must have the * same ID as the object returned by {@link #startGame}, and the client must set the * new value for score before updating. * * @param score The current score. */ public void updateScore(Score score) { if(score instanceof TetrisScore) { TetrisScore tscore = (TetrisScore) score; template.update("Update score_tetris Set score=?, level=?, linecount=? Where ID=? And userID=?", tscore.getScore(), tscore.getLevel(), tscore.getLines(), tscore.getID(), tscore.getUserID()); } else { log.error("Update for Score not implemented yet!"); } } /** * Records the final score for a game.The <code>Score</code> object must have the * same ID as the object returned by {@link #startGame}, and the client must set the * final value for the score. * * @param score The final score. * @return The same score object, with the <code>active</code> property set to false. * @see org.pegadi.games.Score#isActive */ public Score endGame(Score score) { if(score instanceof TetrisScore) { TetrisScore tscore = (TetrisScore) score; template.update("update score_tetris set score=?, level=?, linecount=?, starttime=?, active=false where ID=? and userID=?", tscore.getScore(), tscore.getLevel(), tscore.getLines(), new Timestamp((new GregorianCalendar()).getTimeInMillis()), tscore.getID(), tscore.getUserID() ); Date date = new Date(); long rank = getTetrisRank(tscore.getID()); return new TetrisScore(tscore.getID(), tscore.getUserID(), tscore.getUserName(), rank, tscore.getScore(), tscore.getLevel(), tscore.getLines(), date); } else { log.error("Update for Score not implemented yet!"); return null; } } /** * Cancels a game in progress. * * @param score The game to cancel. */ public void cancelGame(String userID, Score score) { } /** * Returns the <code>count</code> best scores ever. * Because user information is not in the same db as tetris scores * filtering for actives uses UserServer * * @param count Number of scores to return. * @param domain The game domain * @return List of scores. * @see org.pegadi.games.Score */ public List<? extends Score> getHighScore(String domain, int count, boolean activesOnly) { if(domain.equals("tetris")) { if(activesOnly) { return getActiveTetrisHighScores(count); } else { return getTetrisHighScores(0, count); } } else { log.error("Domain not implemented yet!"); return null; } } private List<TetrisScore> getTetrisHighScores(int start, int count) { String query = "select * from score_tetris where active=? order by score desc limit ?,?"; return template.query(query, mapper, false, start, count); } // This method querys the db for more scores until activeScores.size = count private List<TetrisScore> getActiveTetrisHighScores(int count) { List<TetrisScore> activeScores = new ArrayList<TetrisScore>(count); List<TetrisScore> allScores; int start = 0; do { allScores = getTetrisHighScores(start, count); for(TetrisScore score : allScores) { String userId = score.getUserID(); if(userServer.isActive(userId)) { activeScores.add(score); } } start += count; } while(activeScores.size() < count); return activeScores; } /** * Returns the <code>count</code> best scores for the given user ID. * * @param userID user ID to return scores for. * @param count Number of scores to return. * @param domain The game domain * @return List of scores. * @see org.pegadi.games.Score */ public List<? extends Score> getUserScore(String domain, String userID, int count) { if(domain.equals("tetris")) { return template.query("select * from score_tetris where userID=? and active=false order by score Desc limit ?", mapper, userID, count); } else { log.error("Domain not implemented yet!"); return null; } } /** * Returns the <code>count</code> best scores the given date. * * @param day The date to return scores from. * @param count Number of scores to return. * @param domain The game domain * @return List of scores. * @see org.pegadi.games.Score */ public List<? extends Score> getDayScore(String domain, Date day, int count) { if(domain.equals("tetris")) { Calendar cal = Calendar.getInstance(); Date start, end; cal.setTime(day); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); start = cal.getTime(); cal.set(Calendar.HOUR_OF_DAY, 23); cal.set(Calendar.MINUTE, 59); cal.set(Calendar.SECOND, 59); end = cal.getTime(); Timestamp startstamp = new Timestamp(start.getTime()); Timestamp endstamp = new Timestamp(end.getTime()); return template.query("select * from score_tetris where starttime > ? and starttime < ? order by score desc limit ?", mapper, startstamp, endstamp, count); } else { log.error("Domain '{}' not implemented yet.", domain); return null; } } /** * Returns the score's rank in the database. * This is tyhe number of scores in the database that are better * than <code>score</code> + 1; */ private long getTetrisRank(long score) { long rank = template.queryForLong("select count(*) from score_tetris where score > ?", score); return (++rank); } private class TetrisScoreRowMapper implements ParameterizedRowMapper<TetrisScore> { public TetrisScore mapRow(ResultSet rs, int i) throws SQLException { long score = rs.getLong("score"); String username = rs.getString("userID"); Person user = userServer.getUserByUsername(username); String name = user.getName(); return new TetrisScore(rs.getLong("ID"), username, name, getTetrisRank(score), score, rs.getInt("level"), rs.getInt("linecount"), rs.getTimestamp("starttime")); } } }