All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.parlor.rating.server.RatingDelegate Maven / Gradle / Ivy

The newest version!
//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/vilya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.parlor.rating.server;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

import com.google.common.collect.Lists;
import com.google.inject.Inject;

import com.samskivert.util.IntMap;
import com.samskivert.util.IntMaps;
import com.samskivert.util.Invoker;
import com.samskivert.jdbc.RepositoryUnit;

import com.threerings.util.Name;
import com.threerings.util.TimeUtil;

import com.threerings.presents.annotation.MainInvoker;

import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;

import com.threerings.parlor.game.data.GameObject;
import com.threerings.parlor.game.server.GameManager;
import com.threerings.parlor.game.server.GameManagerDelegate;
import com.threerings.parlor.rating.data.RatingCodes;
import com.threerings.parlor.rating.server.persist.RatingRecord;
import com.threerings.parlor.rating.server.persist.RatingRepository;

/**
 * Rates players after each game and handles persisting the results.
 */
public abstract class RatingDelegate extends GameManagerDelegate
    implements RatingCodes
{
    @Override
    public void didInit (PlaceConfig config)
    {
        super.didInit(config);
        _gmgr = (GameManager)_plmgr;
    }

    @Override
    public void didStartup (PlaceObject plobj)
    {
        super.didStartup(plobj);
        _gobj = (GameObject) plobj;
    }

    @Override
    public void bodyEntered (int bodyOid)
    {
        super.bodyEntered(bodyOid);

        // if the game is in play and this is a player, load their ratings
        BodyObject occupant = (BodyObject)_omgr.getObject(bodyOid);
        if (shouldRateGame() && _gobj.isInPlay() && isPlayer(occupant)) {
            PlayerRating rating = maybeCreateRating(occupant);
            if (rating != null) {
                loadRatings(Collections.singleton(rating));
            }
        }
    }

    @Override
    public void bodyLeft (int bodyOid)
    {
        super.bodyLeft(bodyOid);

        // flush this player's rating if its modified
        for (PlayerRating rating : _ratings.values()) {
            if (rating.playerOid == bodyOid && rating.modified) {
                saveRatings(Collections.singleton(rating.cloneForSave()));
                break;
            }
        }
    }

    @Override
    public void gameWillStart ()
    {
        super.gameWillStart();

        // if this game is not to be rated, stop here
        if (!shouldRateGame()) {
            return;
        }

        // note the time at which we started
        _startStamp = System.currentTimeMillis();

        // this contains the persistent player id for each position in a seated table game
        _playerIds = new int[_gmgr.getPlayerSlots()];

        // load up the ratings for all players in this game; also make a note of the persistent
        // player id of each player position for seated table games
        List toLoad = Lists.newArrayList();
        for (int ii = 0, ll = _gobj.occupants.size(); ii < ll; ii++) {
            BodyObject bobj = (BodyObject)_omgr.getObject(_gobj.occupants.get(ii));
            int pidx = _gmgr.getPlayerIndex(bobj.getVisibleName());
            if (pidx != -1) {
                _playerIds[pidx] = _gmgr.getPlayerPersistentId(bobj);
            }
            PlayerRating rating = maybeCreateRating(bobj);
            if (rating != null) {
                toLoad.add(rating);
            }
        }
        loadRatings(toLoad);
    }

    @Override
    public void gameDidEnd ()
    {
        super.gameDidEnd();

        // if this game is not to be rated, stop here
        if (!shouldRateGame()) {
            return;
        }

        // don't update ratings if the game did not run long enough
        int gameSecs = TimeUtil.elapsedSeconds(_startStamp, System.currentTimeMillis());
        if (gameSecs < minimumRatedDuration()) {
            return;
        }

        // compute our updated ratings
        updateRatings();

        // any players who are no longer in the room need their ratings flushed immediately
        List flushes = Lists.newArrayList();
        for (PlayerRating rating : _ratings.values()) {
            if (rating.modified && !_gobj.occupants.contains(rating.playerOid)) {
                flushes.add(rating.cloneForSave());
            }
        }
        saveRatings(flushes);
    }

    protected PlayerRating maybeCreateRating (BodyObject bobj)
    {
        // if this occupant is not a player (or not ratable), skip 'em
        int playerId = _gmgr.getPlayerPersistentId(bobj);
        if (playerId == 0) {
            return null;
        }
        // if this player's ratings are already loaded and their oids match, no need to reload
        PlayerRating orating = _ratings.get(playerId);
        if (orating != null && orating.playerOid == bobj.getOid()) {
            return null;
        }
        return new PlayerRating(bobj, playerId);
    }

    /**
     * Loads up rating information for the specified set of player ids and stores them in the
     * {@link #_ratings} table.
     */
    protected void loadRatings (final Collection ratings)
    {
        if (ratings.size() == 0) {
            return;
        }

        final int gameId = getGameId();
        _invoker.postUnit(new RepositoryUnit("loadRatings(" + gameId + ")") {
            @Override public void invokePersist () throws Exception {
                // map the records by player id so that we can correlate with the db results
                IntMap map = IntMaps.newHashIntMap();
                for (PlayerRating rating : ratings) {
                    map.put(rating.playerId, rating);
                }

                // load up the ratings data from the database, update the records
                Integer[] playerIds = map.keySet().toArray(new Integer[map.size()]);
                for (RatingRecord record : _repo.getRatings(gameId, playerIds)) {
                    PlayerRating rating = map.get(record.playerId);
                    if (rating != null) {
                        rating.rating = record.rating;
                        rating.experience = record.experience;
                    } // else { hell frozen over, pigs flying }
                }
            }

            @Override public void handleSuccess () {
                // stuff our populated records into the _ratings mapping
                for (PlayerRating rating : ratings) {
                    _ratings.put(rating.playerId, rating);
                }
            }
        });
    }

    protected void saveRatings (final Collection ratings)
    {
        if (ratings.size() == 0) {
            return;
        }

        final int gameId = getGameId();
        _invoker.postUnit(new RepositoryUnit("saveRatings(" + gameId + ")") {
            @Override public void invokePersist () throws Exception {
                for (PlayerRating rating : ratings) {
                    _repo.setRating(gameId, rating.playerId, rating.rating, rating.experience);
                }
            }

            @Override public void handleSuccess () {
                // let subclasses publish the new ratings if they so desire
                for (PlayerRating rating : ratings) {
                    updateRatingInMemory(gameId, rating);
                }
            }
        });
    }

    /**
     * Computes updated ratings for the players of this game. The default implementation uses the
     * {@link GameObject#winners} field to determine winners and losers and uses a FIDE/ELO
     * algorithm to compute updated ratings.
     */
    protected void updateRatings ()
    {
        // if no one won (or we're not a seated game), no ratings need be computed
        if (_gobj.getWinnerCount() == 0 || _playerIds.length == 0) {
            return;
        }

        PlayerRating[] ratings = new PlayerRating[_ratings.size()];
        for (int ii = 0; ii < ratings.length; ii++) {
            ratings[ii] = _ratings.get(_playerIds[ii]);
        }

        // compute the update ratings for all players
        int[] nratings = new int[_playerIds.length];
        for (int ii = 0; ii < nratings.length; ii++) {
            float W = _gobj.isDraw() ? 0.5f : _gobj.isWinner(ii) ? 1f : 0f;
            nratings[ii] = Rating.computeRating(ratings, ii, W);
        }

        // and write them back to their rating records
        for (int ii = 0; ii < nratings.length; ii++) {
            PlayerRating rating = ratings[ii];
            if (rating != null && nratings[ii] > 0) {
                rating.rating = nratings[ii];
                rating.experience++;
                rating.modified = true;
            }
        }
    }

    /**
     * Returns the game id to use when reading and writing ratings.
     */
    protected int getGameId ()
    {
        return _gmgr.getGameConfig().getGameId();
    }

    /**
     * Returns true if this game should be rated, false otherwise.
     */
    protected boolean shouldRateGame ()
    {
        return _gmgr.getGameConfig().rated;
    }

    /**
     * Return the minimum time (in seconds) a game must've lasted for it to count towards rating.
     */
    protected abstract int minimumRatedDuration ();

    /**
     * Optionally store update ratings in memory e.g. in the user object.
     *
     * This method is called on the dobj thread.
     */
    protected abstract void updateRatingInMemory (int gameId, PlayerRating rating);

    /**
     * Encapsulates the rating/experience tuple representing a player's rating for a game.
     */
    protected static class PlayerRating extends Rating
        implements Cloneable
    {
        /** The oid of the rated player. */
        public int playerOid;

        /** The name of the rated player. */
        public Name playerName;

        /** The id of the rated player. */
        public int playerId;

        /** Whether or not this rating needs to be written back to the database. */
        public boolean modified;

        /**
         * Sets up a new {@link PlayerRating} object with default values.
         */
        public PlayerRating (BodyObject player, int playerId)
        {
            this.playerOid = player.getOid();
            this.playerName = player.getVisibleName();
            this.playerId = playerId;
        }

        /**
         * Duplicates this rating and clears its modified status.
         */
        public PlayerRating cloneForSave ()
        {
            try {
                PlayerRating rating = (PlayerRating)this.clone();
                modified = false;
                return rating;
            } catch (CloneNotSupportedException cnse) {
                throw new AssertionError(cnse);
            }
        }
    }

    /** An appropriately casted reference to our GameManager. */
    protected GameManager _gmgr;

    /** An appropriately casted reference to our GameObject. */
    protected GameObject _gobj;

    /** Contains the persistent id of the players in this game. */
    protected int[] _playerIds;

    /** The ratings for each player as they were at the beginning of the game. */
    protected IntMap _ratings = IntMaps.newHashIntMap();

    /** A timestamp set at the beginning of the game, used to calculate its duration. */
    protected long _startStamp;

    // our dependencies
    @Inject protected RatingRepository _repo;
    @Inject protected @MainInvoker Invoker _invoker;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy