001    ///////////////////////////////////////////////////////////////////////////////
002    // Copyright (c) 2006, Frank S. Nestel, All Rights Reserved.
003    //
004    // This library is free software; you can redistribute it and/or
005    // modify it under the terms of the GNU Lesser General Public
006    // License as published by the Free Software Foundation; either
007    // version 2.1 of the License, or (at your option) any later version.
008    //
009    // This library is distributed in the hope that it will be useful,
010    // but WITHOUT ANY WARRANTY; without even the implied warranty of
011    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012    // GNU General Public License for more details.
013    //
014    // You should have received a copy of the GNU Lesser General Public
015    // License along with this program; if not, write to the Free Software
016    // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
017    ///////////////////////////////////////////////////////////////////////////////
018    
019    package de.spieleck.app.turn.pairing;
020    
021    import java.util.Arrays;
022    import java.util.Random;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    
026    import org.apache.log4j.Logger;
027    
028    import de.spieleck.app.turn.Game;
029    import de.spieleck.app.turn.Player;
030    import de.spieleck.app.turn.SplittingMode;
031    import de.spieleck.app.turn.PlayerRegistry;
032    import de.spieleck.app.turn.splitting.BaseSplitter;
033    
034    /**
035     * A pairing mode which considers mixing players and placing close ranked players together.
036     *
037     * <p><a href="$URL: https://svn.sourceforge.net/svnroot/jtourney/src/de/spieleck/app/turn/pairing/MixingPairing.java $">$URL: https://svn.sourceforge.net/svnroot/jtourney/src/de/spieleck/app/turn/pairing/MixingPairing.java $</a></p>
038     *
039     * @author Frank S. Nestel
040     * @author $Author: nestefan $
041     * @version $Revision: 2 $ $Date: 2006-03-20 14:33:27 +0100 (Mo, 20 Mrz 2006) $ $Author: nestefan $
042     */
043    public class MixingPairing
044        extends BasePairing
045    {
046        private final static Logger L = Logger.getLogger(MixingPairing.class);
047    
048        private HashMap<Player,WrappedPlayer> ref = new HashMap<Player,WrappedPlayer>();
049    
050        public Game[] pairing(int round, PlayerRegistry pRegistry, SplittingMode sm)
051        {
052            Player[] standing = pRegistry.getActivePlayers();
053            Arrays.sort(standing);
054            int n = standing.length;
055            WrappedPlayer[] wps = new WrappedPlayer[n];
056            for(int i = 0; i < n; i++)
057            {
058                wps[i] = new WrappedPlayer(standing[i], i);
059                ref.put(standing[i], wps[i]);
060            }
061            Arrays.sort(wps);
062    for(int i = 0; i < wps.length; i++) L.debug(" "+i+". "+wps[i].p+"  "+wps[i].differentOpponents+" "+wps[i].opponents+" "+wps[i].rank);
063            int[] splitting = sm.split(n);
064            int[] counts = BaseSplitter.transposeSplit(splitting);
065            //
066            // Put the small numbers on the end!
067            for(int i = 0; 2*i < counts.length; i++)
068            {
069                int h = counts[i];
070                counts[i] = counts[counts.length - 1 - i];
071                counts[counts.length - 1 - i] = h;
072            }
073    for(int i = 0; i < counts.length; i++) L.debug(" c["+i+"]="+counts[i]);        
074            // 
075            // locate players with too few opponents
076            GameImpl[] res = new GameImpl[counts.length];
077            for(int i = 0; i < res.length; i++)
078                res[i] = new GameImpl(i);
079            // Find players with below average different opponents
080            int m = standing.length / 2;
081            int limit = m;
082            while ( limit > 0 
083                    && wps[limit].differentOpponents == wps[m].differentOpponents )
084                limit--;
085            // First seat those unlucky ones
086            for(int i = 0; i <= limit; i++)
087                bestFit(counts, res, wps[i]);
088            L.debug("pairing: limit="+limit);
089            // Rearange remaining games to small games first!
090            int start = 0;
091            while ( start < counts.length && res[start].size() > 0 ) start++;
092            Arrays.sort(counts, start, counts.length);
093            // Now seat by a "hardest" first strategy
094            for(int i = wps.length - 1; i > limit; i--)
095                bestFit(counts, res, wps[i]);
096            return res;
097        }
098    
099        protected void bestFit(int[] counts, GameImpl[] res, WrappedPlayer py)
100        {
101            int iOpt = 0;
102            double distOpt = Double.MAX_VALUE;
103            for(int i = 0; i < counts.length; i++)
104            {
105                if ( res[i].size() < counts[i] )
106                {
107                    double dist = dist(py, res[i]);
108                    if ( dist < distOpt
109                        || ( dist == distOpt && res[i].size() > res[iOpt].size() )
110                        )
111                    {
112                        distOpt = dist;
113                        iOpt = i;
114                    }
115                }
116            }
117            L.debug(res[iOpt]+" + "+py+" d="+distOpt);
118            res[iOpt].add(py.p);
119        }
120    
121        protected double dist(WrappedPlayer py, GameImpl game)
122        {
123            Player p = py.p;
124            Iterator<Player> players = game.players();
125            if ( ! players.hasNext() )
126            {
127                return 0.5;
128            }
129            int deltaRank = 0;
130            int common = 0;
131            int count = 1;
132            while ( players.hasNext() )
133            {
134                Player p2 = players.next();
135                int pa = p.getPlayedAgainst(p2);
136                common += (1 << (2 * pa));
137                int dr = Math.abs(py.rank - ref.get(p).rank);
138                if ( dr > deltaRank )
139                    deltaRank += dr;
140                count++;
141            }
142            double fak = 1.0 + 2.0 * deltaRank / ref.size();
143            return fak * common / Math.sqrt(count);
144        }
145    
146        public String toString()
147        {
148            return "MixingPairing";
149        }
150    
151        private static class WrappedPlayer
152            implements Comparable
153        {
154            Player p;
155            int differentOpponents = 0;
156            int opponents = 0;
157            int rank;
158    
159            public WrappedPlayer(Player p, int rank)
160            {
161                this.p = p;
162                this.rank = rank;
163                Player[] opps = p.getRegistry().getPlayers();
164                for(int i = 0; i < opps.length; i++)
165                {
166                    int c = p.getPlayedAgainst(opps[i]);
167                    if ( c > 0 )
168                    {
169                        differentOpponents++;
170                        opponents += c;
171                    }
172                }
173            }
174    
175            public int compareTo(Object o)
176            {
177                if ( !(o instanceof WrappedPlayer) )
178                {
179                    return -1;
180                }
181                int h;
182                WrappedPlayer p2 = (WrappedPlayer) o;
183                h = differentOpponents - p2.differentOpponents;
184                if ( h != 0 )
185                    return h;
186                h = opponents - p2.opponents;
187                if ( h != 0 )
188                    return h;
189                return rank - p2.rank;
190            }
191    
192            public String toString()
193            {
194                return p+":"+rank+":"+differentOpponents+":"+opponents;
195            }
196        }
197    }