VSGameManager.java :  » Game » FFGenie-2010.3-src » ffg » game » vs » Java Open Source

Java Open Source » Game » FFGenie 2010.3 src 
FFGenie 2010.3 src » ffg » game » vs » VSGameManager.java
/*
 * DTGameManager.java
 *
 * Created on 17 May 2006, 10:33
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package ffg.game.vs;

import static ffg.util.FFGConstants.*;

import ca.odell.glazedlists.swing.TableComparatorChooser;

import ffg.entity.AFLPlayer;
import ffg.entity.AFLPosition;
import ffg.entity.AFLPositions;
import ffg.entity.AFLTeam;
import ffg.game.FFGPanelException;
import ffg.game.GameManager;
import ffg.game.UpdateScoresException;
import ffg.gui.SaveException;
import ffg.gui.table.column.FFGTableColumn;
import ffg.gui.table.filter.FFGTableFilter;
import ffg.gui.table.filter.FilterKey;
import ffg.gui.table.filter.FilterOperator;
import ffg.gui.table.filter.FilterStringTranslator;
import ffg.gui.table.filter.MatchMode;
import ffg.gui.table.renderer.AlternateRowTableCellRenderer;
import ffg.gui.table.renderer.FFGTableCellRenderer;
import ffg.util.BareBonesBrowserLaunch;
import ffg.util.FFGConstants;

import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import java.util.zip.GZIPInputStream;
import javax.swing.Icon;
import javax.swing.JOptionPane;
import javax.swing.JTable;

/**
 * A {@link GameManager} that is the base class of all VS-based competitions.
 * @param <E> The subclass of {@link VSGameStats} 
 * @author eugene
 */
public abstract class VSGameManager<E extends VSGameStats> extends GameManager<E> implements PropertyChangeListener {
    public static final AFLPosition[] GAME_POSITIONS = new AFLPosition[] {
        AFLPosition.Back,AFLPosition.Back,AFLPosition.Back,AFLPosition.Back,AFLPosition.Back,AFLPosition.Back,AFLPosition.Back,AFLPosition.Back,AFLPosition.Back,
        AFLPosition.Centre,AFLPosition.Centre,AFLPosition.Centre,AFLPosition.Centre,AFLPosition.Centre,AFLPosition.Centre,AFLPosition.Centre,AFLPosition.Centre,
        AFLPosition.Ruck,AFLPosition.Ruck,AFLPosition.Ruck,AFLPosition.Ruck,
        AFLPosition.Forward,AFLPosition.Forward,AFLPosition.Forward,AFLPosition.Forward,AFLPosition.Forward,AFLPosition.Forward,AFLPosition.Forward,AFLPosition.Forward,AFLPosition.Forward
    };
//    /**
//     * The number of rounds in the dream team competition.
//     */
//    public static final int N_ROUNDS = 22;
    /**
     * STATS_FILE_PROPERTY
     */
    public static final String STATS_FILE_PROPERTY = "Stats File";
    /**
     * ROUNDS_COMPLETED_PROPERTY
     */
    public static final String ROUNDS_COMPLETED_PROPERTY = "Rounds Completed";
    /**
     * PLAYER_EDITING_PROPERTY
     */
    public static final String PLAYER_EDITING_PROPERTY = "Player Being Edited";
    /**
     * NUMBER_EDITED_PROPERTY
     */
    public static final String NUMBER_EDITED_PROPERTY = "Number Edited";
    /**
     * UPDATE_STRING_PROPERTY
     */
    public static final String UPDATE_STRING_PROPERTY = "Update String";
    /**
     * MULTIPLIER_PROPERTY
     */
    public static final String MULTIPLIER_PROPERTY = "Multiplier";
//    /**
//     * PERCENTAGE_PROPERTY
//     */
//    public static final String PERCENTAGE_PROPERTY = "Percentage";
    // Used to extract portions from the html.
//    private static final String BEGIN_RESULTS_STRING = "<!--end brief results table -->";
//    private static final String END_RESULTS_STRING = "<!--end expanded results table-->";
//    private static final Pattern ROW_PATTERN = Pattern.compile(
//    "<tr align=\"center\">\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"t\\d_Head\"><strong>(\\d+)</strong></td>\\s+"
//    + "<td class=\"mtm_teamc\\d+\" align=\".*?\">\\$(.*?)\\&nbsp;\\&nbsp;.*?<span class=\"mtm_.*?text\">.*?</td>\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"mtm_teamc\\d+?\">(\\-?\\d+)</td>\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"mtm_teamc\\d+?\">(\\-?\\d+)</td>\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"mtm_teamc\\d+?\">(\\-?\\d+)</td>\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"mtm_teamc\\d+?\">(\\-?\\d+)</td>\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"mtm_teamc\\d+?\">(\\-?\\d+)</td>\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"mtm_teamc\\d+?\">(\\-?\\d+)</td>\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"mtm_teamc\\d+?\">(\\-?\\d+)</td>\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"mtm_teamc\\d+?\">(\\-?\\d+)</td>\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"mtm_teamc\\d+?\">(\\-?\\d+)</td>\\s+"
//    + "<td style=\"width:\\d\\%\" class=\"mtm_teamc\\d+?\"><strong>(\\-?\\d+)</strong></td>\\s+"
//    );
    private static final Pattern ROW_PATTERN = Pattern.compile(
        "<td style=\"width.*?\" class=\".*?\"><strong>(\\d+)</strong></td>\\s+"
        + "<td class=\".*?\">.*?</td>\\s+"
        + "<td class=\".*?\" align=\".*?\">\\$(.*?)\\&nbsp;\\&nbsp;.*?<span class=\".*?\">.*?</td>\\s+"
        + "<td style=\".*?\" class=\".*?\">(.*?)</td>\\s+"
        + "<td style=\".*?\" class=\".*?\">(.*?)</td>\\s+"
        + "<td style=\".*?\" class=\".*?\">(.*?)</td>\\s+"
        + "<td style=\".*?\" class=\".*?\">(.*?)</td>\\s+"
        + "<td style=\".*?\" class=\".*?\">(.*?)</td>\\s+"
        + "<td style=\".*?\" class=\".*?\">(.*?)</td>\\s+"
        + "<td style=\".*?\" class=\".*?\">(.*?)</td>\\s+"
        + "<td style=\".*?\" class=\".*?\">(.*?)</td>\\s+"
        + "<td style=\".*?\" class=\".*?\">(.*?)</td>\\s+"
        + "<td style=\".*?\" class=\".*?\"><strong>(.*?)</strong></td>\\s+"
    );
    
    private static final int ROW_ROUND = 1;
    private static final int ROW_PRICE = 2;
    private static final int ROW_KICKS = 3;
    private static final int ROW_HANDBALLS = 4;
    private static final int ROW_MARKS = 5;
    private static final int ROW_TACKLES = 6;
    private static final int ROW_FREESFOR = 7;
    private static final int ROW_FREESAGAINST = 8;
    private static final int ROW_HITOUTS = 9;
    private static final int ROW_GOALS = 10;
    private static final int ROW_BEHINDS = 11;
    private static final int ROW_SCORE = 12;
    
    private static final Pattern PLAYER_ROUND_PATTERN = Pattern.compile(
//        "<td class=\"t\\dC\\d\" align=\"left\">(\\d+)\\.\\s+<a class=\".*?\" href=\"/\\?p=researchmini&pid=(\\d+)\"><strong>(.*?),</strong> (.*?)</a>\\s+.*?<strong>.*?\\$(.*?)</strong>.*?\\s+"
//        + "\\s+<span class=\".*?\">.*?</span>\\s+"
//        + "\\s+<br /><span class=\"txt_abrv\">.*?</td>\\s+"
//        + "<td class=\"t\\dC\\d\">(\\d+)</td>\\s+"
//        + "<td class=\"t\\dC\\d\">(.*?)</td>\\s+"
//        + "<td class=\"t\\dC\\d\">(\\d+) \\(.*?\\)</td>\\s+"
//        + "<td class=\"t\\dC\\d\">(.*?)</td>\\s+"
//        + "<td class=\"t\\dC\\d\"><strong>([\\d\\-]+)</strong></td>\\s+"
//        + "<td class=\"t\\dC\\d\"><strong>([\\d,]+)</strong></td>\\s+"
//        + "<td class=\"t\\dC\\d\"><a href=\"\""
        "<td class=\"t\\dC\\d\" align=\"left\">(\\d+)\\.\\s+<a class=\".*?\" href=\"/\\?p=researchmini&pid=(\\d+)\"><strong>(.*?),</strong> (.*?)</a>\\s+"
        + ".*?<strong>.*?\\$(.*?)</strong>.*?\\s+"
        + "\\s+<span class=\".*?\">.*?</span>\\s+"
        + "\\s+<br /><span class=\"txt_abrv\">.*?</td>\\s+"
        + "<td class=\"t\\dC\\d\">(\\d+)</td>\\s+"
        + "<td class=\"t\\dC\\d\">(.*?)</td>\\s+"
        + "<td class=\"t\\dC\\d\">(\\d+) \\(.*?\\)</td>\\s+"
        + "<td class=\"t\\dC\\d\">(.*?)</td>\\s+"
        + "<td class=\"t\\dC\\d\">(.*?)</td>\\s+"
        + "<td class=\"t\\dC\\d\"><strong>([\\d\\-]+)</strong></td>\\s+"
        + "<td class=\"t\\dC\\d\"><strong>([\\d,]+)</strong></td>\\s+"
        + "<td class=\"t\\dC\\d\"><a href=\"\""
    );
    // group numbers in the above regular expressions
    private static final int RANK_GROUP = 1;
    private static final int ORDINAL_GROUP = 2;
    private static final int LAST_NAME_GROUP = 3;
    private static final int FIRST_NAME_GROUP = 4;
    private static final int CURRENT_PRICE_GROUP = 5;
    private static final int ROUNDS_PLAYED_GROUP = 6;
    private static final int CURRENT_SELECTIONS_GROUP = 7;
    private static final int LAST_RANK_GROUP = 8;
    private static final int MVP_GROUP = 9;
    private static final int AVERAGE_SCORE_GROUP = 10;
    private static final int ROUND_SCORE_GROUP = 11;
    private static final int TOTAL_SCORE_GROUP = 12;
    
    
//    private static final Pattern ROUND_NUMBER_PATTERN = Pattern.compile("<div class=\"gamestatus_roun\">(\\d+)</div>");
    
    private static final Pattern SELECTIONS_PATTERN = Pattern.compile(
        "Current competition ranking:</td>\\s+"
        + "<td.*?><strong>(.*?)</strong></td></tr>\\s+"
        + "<tr><td.*?>Total \\# of selections:</td>\\s+"
        + "<td.*?><strong>(.*?)</strong></td>"
    );

    private static final int RANKING_GROUP = 1;
    private static final int SELECTIONS_GROUP = 2;
    
    /**
     * The renderer of this game manager.
     */
    private FFGTableCellRenderer tableCellRenderer;
    /**
     * Flags whether a current update is occuring
     */
    private final AtomicBoolean updating = new AtomicBoolean(false);
    /**
     * Which players are played in the latest round (i.e. active).
     */
    private final Set<AFLPlayer> playersActive;
    /**
     * The price table associated with this game manager.
     */
    private final VSPriceTable<E> priceTable;
    /**
     * The file containing the statistics for this game manager.
     */
    private File statsFile;
    
    /**
     * Used in the property change listener.
     */
    private int roundsCompleted = 0;
    /**
     * Variables in the VS formula.
     * Multiplier is basically the average $/avg (over the past 3 rounds)
     * Percentage is the proportion of the difference b/w the current price and the multiplier price to move to. It has a linear relationship to the multiplier.
     */
    private double multiplier;
    private double scale;
//    private double percentage;
    
    /**
     * Filters, sorting and columns
     */
    private MatchMode oldMatchMode = MatchMode.ALL;
    private final List<FilterHolder> oldFilters = new ArrayList<FilterHolder>();
    private final List<SortHolder> oldSort = new ArrayList<SortHolder>();
    private final List<Integer> hiddenColumnIndices = new ArrayList<Integer>();
    
    // for updating.
    private static final int N_THREADS = 4;
    private ExecutorService executor;
    private final AtomicInteger processedPlayersCount = new AtomicInteger(0);
    
    /**
     * Value holder class for filters
     */
    private static class FilterHolder {
        private final int keyIndex;
        private final int operatorIndex;
        private final Object value;
        
        public FilterHolder(int keyIndex, int operatorIndex, Object value) {
            this.keyIndex = keyIndex;
            this.operatorIndex = operatorIndex;
            this.value = value;
        }
        
        public int getKeyIndex() {
            return keyIndex;
        }
        
        public int getOperatorIndex() {
            return operatorIndex;
        }
        
        public Object getValue() {
            return value;
        }
    }
    
    /**
     * Value holder class for sorts
     */
    private static class SortHolder {
        private final int columnIndex;
        private final int comparatorIndex;
        private final boolean reverse;
        
        public SortHolder(int columnIndex, int comparatorIndex, boolean reverse) {
            this.columnIndex = columnIndex;
            this.comparatorIndex = comparatorIndex;
            this.reverse = reverse;
        }
        
        public int getColumnIndex() {
            return columnIndex;
        }
        
        public int getComparatorIndex() {
            return comparatorIndex;
        }
        
        public boolean isReverse() {
            return reverse;
        }
    }
    
    /**
     * Creates a new instance of VSGameManager
     */
    public VSGameManager() {
        super();
        
        setMultiplier(getVSGameType().getDefaultMultiplier());
//        percentage = getVSGameType().getDefaultPercentage();
        
        playersActive = new HashSet<AFLPlayer>();
        for (AFLPlayer player : getVSGameType().getPlayers()) {
            VSGameStats stats = getStatsForPlayer(player);
            stats.addToPricesAt(0, getStartingValueForPlayer(player));
        }
        priceTable = new VSPriceTable<E>(this);
        setSaved(false);
    }
    
    /**
     * Creates a new instance of VSGameManager from the given <code>statsFile</code>
     * @param statsFile The file containing the statistics.
     * @throws FFGPanelException error occurs in reading the file.
     */
    public VSGameManager(File statsFile) throws FFGPanelException {
        super();
        
        setMultiplier(getVSGameType().getDefaultMultiplier());
//        percentage = getVSGameType().getDefaultPercentage();
        
        playersActive = new HashSet<AFLPlayer>();
        
        setStatsFile(statsFile);
        loadStats();
        priceTable = new VSPriceTable<E>(this);
        restoreTableState();
        setSaved(true);
    }

    public VSGameManager(VSGameManager<E> toCopy) {
        super();

        setMultiplier(getVSGameType().getDefaultMultiplier());

        playersActive = new HashSet<AFLPlayer>();
//        for (AFLPlayer player : getVSGameType().getPlayers()) {
//            VSGameStats stats = getStatsForPlayer(player);
//            stats.addToPricesAt(0, getStartingValueForPlayer(player));
//        }
        for (Entry<AFLPlayer, E> entry : toCopy.playerStats.entrySet()) {
            E stats = entry.getValue();
        }
        priceTable = new VSPriceTable<E>(this);
        setSaved(false);
    }

    public void downloadStats() throws UpdateScoresException {
        Scanner roundScanner = null;
        Scanner fileScanner = null;
        try {
            final String gameTypeName = getVSGameType().name();
            String roundFile = String.format(ROUND_FILE_FORMAT, gameTypeName);
            URL url = new URL(String.format(BASE_STATS_URL, YEAR, gameTypeName, roundFile));
            roundScanner = new Scanner(url.openStream());
            if (!roundScanner.hasNextLine()) {
                throw new UpdateScoresException(String.format("Could not load %s from URL %s", roundFile, url.toString()), true);
            }
            int roundNumber = Integer.parseInt(roundScanner.nextLine());
            if (roundNumber < getRoundsCompleted()) {
                int result = JOptionPane.showConfirmDialog(null, String.format("The current file has stats up to round %d, but the latest file on the website has stats up to round %d.%nDo you wish to continue?", getRoundsCompleted(), roundNumber), "Confirm Download", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
                if (result == JOptionPane.NO_OPTION) {
                    return;
                }
            } else if (roundNumber == getRoundsCompleted()) {
                int result = JOptionPane.showConfirmDialog(null, String.format("The current file and the latest file on the website has stats up to round %d.%nThey may contain the exact same stats.%n Do you wish to continue?", roundNumber), "Confirm Download", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
                if (result == JOptionPane.NO_OPTION) {
                    return;
                }
            }
            String statsFileName = String.format("%s-R%d.ffz", gameTypeName, roundNumber);
            URL fileURL = new URL(String.format(BASE_STATS_URL, YEAR, gameTypeName, statsFileName));
            fileScanner = new Scanner(new GZIPInputStream(fileURL.openStream()));
            fileScanner.useDelimiter(GET_ALL_STRINGS_PATTERN);
            if (!fileScanner.hasNext()) {
                throw new UpdateScoresException(String.format("Could not load %s from URL %s", statsFileName, fileURL.toString()), true);
            }
//            System.out.println("fileURL = '" + fileURL + "'");
            String stats = fileScanner.next();
            importStats(new StringReader(stats));
        }
        catch (IOException ioe) {
            throw new UpdateScoresException(ioe, true);
        }
        finally {
            if (roundScanner != null) {
                roundScanner.close();
            }
            if (fileScanner != null) {
                fileScanner.close();
            }
        }
    }
    
    /**
     * Restores filters and sorting.
     */
    private void restoreTableState() {
        // hidden columns
        if (!hiddenColumnIndices.isEmpty()) {
            final List<FFGTableColumn<E, ?>> allColumns = priceTable.getAllTableColumns();
            for (int index : hiddenColumnIndices) {
                if (index < allColumns.size()) {
                    final int finalIndex = index;
                    EventQueue.invokeLater(new Runnable() {
//                        @Override
                        public void run() {
                            allColumns.get(finalIndex).setHidden(true);
                        }
                    });
                }
            }
            hiddenColumnIndices.clear();
        }
        // filters
        if (!oldFilters.isEmpty()) {
            List<FilterKey<E, ?>> allFilterColumns = priceTable.getFilterTableColumns();
            List<FFGTableFilter<E>> newFilters = new ArrayList<FFGTableFilter<E>>();
            for (FilterHolder filterHolder : oldFilters) {
                FilterKey<E, ?> key = allFilterColumns.get(filterHolder.getKeyIndex());
                FilterOperator operator = key.getFilterOperators().get(filterHolder.getOperatorIndex());
                newFilters.add(new FFGTableFilter<E>(key, operator, filterHolder.getValue()));
            }
            priceTable.setFilters(newFilters, oldMatchMode);
            oldFilters.clear();
        }
        
        //sorting
        if (!oldSort.isEmpty()) {
            final TableComparatorChooser<E> chooser = priceTable.getTableComparatorChooser();
            for (SortHolder sortHolder : oldSort) {
                final SortHolder finalHolder = sortHolder;
                EventQueue.invokeLater(new Runnable() {
//                    @Override
                    public void run() {
                        chooser.appendComparator(finalHolder.getColumnIndex(), finalHolder.getComparatorIndex(), finalHolder.isReverse());
                    }
                });
            }
            oldSort.clear();
        }
    }
    
    /**
     * Abstract method for creating a sub class of {@link VSGameStats} for the particular comp.
     * @param player The player for whom the stats are being created.
     * @return <E> The subclass of {@link VSGameStats}
     */
    protected abstract E createGameStatsSub(AFLPlayer player);
    
//    /**
//     * The number of rounds in this competition.
//     * @return as above
//     */
//    protected abstract int getMaxRoundNumber();
    
    @Override 
    /**
     * Creates the subclass of {@link VSGameStats} and adds a property change listener to it.
     */
    protected E createGameStats(AFLPlayer player) {
        E stats = createGameStatsSub(player);
        stats.addPropertyChangeListener(this);
        return stats;
    }
    
    /**
     * The renderer for the table.
     * @return as above
     */
    @Override 
    public FFGTableCellRenderer getBaseTableCellRenderer() {
        if (tableCellRenderer == null) {
            tableCellRenderer = new VSTableCellRenderer(this, getVSGameType().getEvenColor(), getVSGameType().getOddColor());
        }
        return tableCellRenderer;
    }
    
    /**
     * The file name containing the statistics for this game manager
     * @return      as above
     */
    public String getStatsFileName() {
        return statsFile == null ? "Untitled" : statsFile.getName();
    }
    
    /**
     * The file path containing the statistics for this game manager
     * @return      as above
     */
    public String getStatsFileAbsoluteName() {
        return statsFile == null ? "Untitled" : statsFile.getAbsolutePath();
    }
    
    /**
     * The file object containing the statistics for this game manager
     * @return      as above
     */
    public File getStatsFile() {
        return statsFile;
    }
    
    /**
     * Sets the file containing the statistics for this game manager
     * @param newFile       The stats file
     */
    public void setStatsFile(File newFile) {
        File oldFile = statsFile;
        statsFile = newFile;
        propChangeSupport.firePropertyChange(STATS_FILE_PROPERTY, oldFile, statsFile);
    }
    
    /**
     * Loads the statistics from the current stats file
     * @throws FFGPanelException        if there were problems reading and parsing the statistics from the file.
     */
    private void loadStats() throws FFGPanelException {
        if (updating.compareAndSet(false, true)) {
            Scanner scanner = null;
            try {
                scanner = new Scanner(statsFile);
//                String year = scanner.nextLine();
                String initialLine = scanner.nextLine();
                String realFirstLine = initialLine;
                try {
                    VSGameType.valueOf(initialLine);
                    realFirstLine = scanner.nextLine();
                } catch (IllegalArgumentException iae) {
                }
                String[] firstLine = realFirstLine.split(",");
                int roundNumber = Integer.parseInt(firstLine[0]);
                if (firstLine.length > 1) {
                    setMultiplier(Double.parseDouble(firstLine[1]));
                }
                setRoundsCompleted(roundNumber);

                int nPlayers = Integer.parseInt(scanner.nextLine());
                scanner.nextLine(); // headings
                while (nPlayers-- > 0) {
                    String line = scanner.nextLine();
                    Scanner lineScanner = new Scanner(line).useDelimiter(",");
                    int playerID = Integer.parseInt(lineScanner.next());
                    AFLPlayer curPlayer = AFLPlayer.getPlayerByOrdinal(playerID);
                    E stats = getStatsForPlayer(curPlayer);

                    //prices
                    for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                        String next = lineScanner.next();
                        if (i < roundNumber) {
                            stats.addToPricesAt(i, Integer.parseInt(next));
                        } else if (roundNumber == 0) {
                            stats.addToPricesAt(0, Integer.parseInt(next));
                        }
                    }

                    lineScanner.next(); // blank

                    //scores
                    for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                        String next = lineScanner.next();
                        if (i < roundNumber) {
                            stats.addToScoresAt(i, Integer.parseInt(next));
                        } else if (roundNumber == 0) {
                            stats.addToScoresAt(0, Integer.parseInt(next));
                        }
                    }

                    lineScanner.next(); // blank

                    //selections
                    for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                        String next = lineScanner.next();
                        if (i < roundNumber) {
                            stats.addToSelectionsAt(i, Integer.parseInt(next));
                        } else if (roundNumber == 0) {
                            stats.addToSelectionsAt(0, Integer.parseInt(next));
                        }
                    }

    //                lineScanner.next(); // blank
    //
    //                //ranks
    ////                for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
//                    for (int i = 0; i < roundNumber; i++) {
    //                    stats.addToRanksAt(i, Integer.parseInt(lineScanner.next()));
    //                }
                }

                // hidden columns
                String[] indices = scanner.nextLine().split("\\,");
                for (String index : indices) {
                    if (index.length() > 0) {
                        hiddenColumnIndices.add(Integer.parseInt(index));
                    }
                }

                // filters
                int nFilters = Integer.parseInt(scanner.nextLine());
                oldMatchMode = MatchMode.valueOf(scanner.nextLine());
                while (nFilters-- > 0) {
                    String filterInfo = scanner.nextLine();
                    String[] values = filterInfo.split(TAB);
                    int filterKeyIndex = Integer.parseInt(values[0]);
                    int operatorIndex = Integer.parseInt(values[1]);
                    String value = values[2];
                    Object obj = FilterStringTranslator.fromString(value);
                    oldFilters.add(new FilterHolder(filterKeyIndex, operatorIndex, obj));
                }

                // sorting
                int nSortColumns =  Integer.parseInt(scanner.nextLine());
                while (nSortColumns-- > 0) {
                    String sortInfo = scanner.nextLine();
                    String[] values = sortInfo.split(TAB);
                    int columnIndex = Integer.parseInt(values[0]);
                    int comparatorIndex = Integer.parseInt(values[1]);
                    boolean reverse = Boolean.parseBoolean(values[2]);
                    oldSort.add(new SortHolder(columnIndex, comparatorIndex, reverse));
                }
            } catch (FileNotFoundException ex) {
                throw new FFGPanelException("Could not find stats file: " + statsFile.getAbsolutePath());
            } catch (RuntimeException rte) {
                rte.printStackTrace();
                throw new FFGPanelException(String.format("Invalid file:%n%s%nIt is likely this is a stats file, and should be IMPORTED (under the Actions menu) rather than OPENED.", rte.getMessage()), rte);
            } finally {
                if (scanner != null) {
                    scanner.close();
                }
                updating.set(false);
            }
        } else {
            throw new FFGPanelException("Cannot load stats while update is in progress");
        }
    }
    
    /**
     * Writer stats to the given <code>writer</code>
     * @param writer as above
     * @throws java.io.IOException If an error occurred during writing of the stats.
     */
    @Override
    public void exportStats(Writer writer) throws IOException {
        if (updating.get()) {
            throw new IOException("Cannot export while an update is occurring.");
        }
        
        PrintWriter printWriter = null;
        try {
            if (writer instanceof PrintWriter) {
                printWriter = (PrintWriter) writer;
            } else {
                printWriter = new PrintWriter(new BufferedWriter(writer));
            }

//            printWriter.println(YEAR);
            printWriter.println(getVSGameType().name());
            printWriter.println(String.format("%d,%.9f", getRoundsCompleted(), getMultiplier()));
            
            List<AFLPlayer> allPlayers = getVSGameType().getPlayers();
            printWriter.println(allPlayers.size());
            
            // headings
            printWriter.print("Id");
            // price headings
            for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                printWriter.print(",Price");
                printWriter.print(i + 1);
            }
            printWriter.print(",");
            // score headings
            for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                printWriter.print(",Score");
                printWriter.print(i + 1);
            }
            printWriter.print(",");
            // selection headings
            for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                printWriter.print(",Selection");
                printWriter.print(i + 1);
            }
            printWriter.print(",");
            // rank headings
            for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                printWriter.print(",Rank");
                printWriter.print(i + 1);
            }
            printWriter.println();
            
            for (AFLPlayer player : allPlayers) {
                printWriter.print(player.getOrdinal());
                E gameStats = getStatsForPlayer(player);
                // prices
                for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                    printWriter.printf(",%d", gameStats.getPriceAt(i));
                }
                printWriter.print(",");
                // scores
                for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                    printWriter.printf(",%d", gameStats.getScoreAt(i));
                }
                printWriter.print(",");
                // selections
                for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                    printWriter.printf(",%d", gameStats.getSelectionAt(i));
                }
//                printWriter.print(",");
//                // ranks
//                for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
//                    printWriter.printf(",%d", gameStats.getRankAt(i));
//                }
                printWriter.println();
            }
        }
        finally {
            if (printWriter != null) {
                printWriter.close();
            }
        }
    }
    
    /**
     * Read stats from the reader
     * @param reader as above
     * @throws java.io.IOException if an error occurred during reading of the stats
     */
    @Override
    public void importStats(Reader reader) throws IOException {
        if (updating.compareAndSet(false, true)) {
            Scanner scanner = null;
            try {
                scanner = new Scanner(reader);

//                scanner.nextLine(); // year
                String initialLine = scanner.nextLine();
                String realFirstLine = initialLine;
                try {
                    VSGameType savedGameType = VSGameType.valueOf(initialLine);
                    if (savedGameType != getVSGameType()) {
                        throw new IOException(String.format("Incompatible stats file. Current game type '%s' does not match saved game type '%s'", getVSGameType(), savedGameType));
                    }
                    realFirstLine = scanner.nextLine();
                } catch (IllegalArgumentException iae) {
                    throw new IOException("Unknown VS game type = " + initialLine);
                }
                String[] firstLine = realFirstLine.split(",");
                int roundNumber = Integer.parseInt(firstLine[0]);
                if (firstLine.length > 1) {
                    setMultiplier(Double.parseDouble(firstLine[1]));
                }
                setRoundsCompleted(roundNumber);

                int nPlayers = Integer.parseInt(scanner.nextLine());
                scanner.nextLine(); // headings
                while (nPlayers-- > 0) {
                    String line = scanner.nextLine();
                    Scanner lineScanner = new Scanner(line).useDelimiter(",");
                    int playerID = Integer.parseInt(lineScanner.next());
                    AFLPlayer curPlayer = AFLPlayer.getPlayerByOrdinal(playerID);
                    E stats = getStatsForPlayer(curPlayer);

                    stats.clear();

                    //prices
                    for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                        String next = lineScanner.next();
                        if (i < roundNumber) {
                            stats.addToPricesAt(i, Integer.parseInt(next));
                        }
                    }

                    lineScanner.next(); // blank

                    //scores
                    for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                        String next = lineScanner.next();
                        if (i < roundNumber) {
                            stats.addToScoresAt(i, Integer.parseInt(next));
                        }
                    }

                    lineScanner.next(); // blank

                    //selections
                    for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
                        String next = lineScanner.next();
                        if (i < roundNumber) {
                            stats.addToSelectionsAt(i, Integer.parseInt(next));
                        }
                    }

    //                lineScanner.next(); // blank
    //
    //                //ranks
    //                for (int i = 0; i < getVSGameType().getMaxRoundNumber(); i++) {
    //                    stats.addToRanksAt(i, Integer.parseInt(lineScanner.next()));
    //                }
                }
            }
            finally {
                setSaved(false);
                updating.set(false);
                if (scanner != null) {
                    scanner.close();
                }
            }
        } else {
            throw new IOException("Cannot import stats while updating is occurring.");
        }
    }
    
    
    /**
     * Return the number of rounds completed, according to the statistics
     * @return  as above
     */
    public int getRoundsCompleted() {
        return roundsCompleted;
    }
    
    /**
     * Sets the number of rounds completed
     * @param roundsCompleted       The number of rounds completed.
     */
    public void setRoundsCompleted(int roundsCompleted) {
        int oldRoundsCompleted = this.roundsCompleted;
        this.roundsCompleted = roundsCompleted;
        
        propChangeSupport.firePropertyChange(ROUNDS_COMPLETED_PROPERTY, oldRoundsCompleted, roundsCompleted);
    }
    
    /**
     * Save the current statistics to the current statistics file
     * @throws SaveException        If there were problems occuring during the save.
     */
    public void save() throws SaveException {
        saveAs(statsFile);
    }
    
    /**
     * Save stats to the given <code>file</code>
     * @param file The file to save to
     * @throws ffg.gui.SaveException Any error occurring while saving.
     */
    @Override 
    public void saveAs(File file) throws SaveException {
        if (updating.get()) {
            throw new SaveException("Cannot save while an update is occurring.");
        }

        // rename the old file
        File movedFile = new File(file.getAbsolutePath() + ".bak");
        if (movedFile.exists()) {
            movedFile.delete();
        }
        file.renameTo(movedFile);
        
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException ex) {
                throw new SaveException("Could not save to " + file.getAbsolutePath() + ". Reason: " + ex.getMessage(), ex);
            }
        }
        PrintWriter printWriter = null;
        try {
            printWriter = new PrintWriter(new BufferedWriter(new FileWriter(file)));

            StringWriter str = new StringWriter();
            exportStats(str);
            printWriter.print(str.toString());
            
            // hidden columns
            for (Iterator<Integer> iterator = priceTable.getHiddenColumnIndices().iterator(); iterator.hasNext(); ) {
                printWriter.print(iterator.next());
                if (iterator.hasNext()) {
                    printWriter.print(",");
                }
            }
            printWriter.println();
            
            // filters
            List<FFGTableFilter<E>> filters = priceTable.getTableFilters();
            List<FilterKey<E, ?>> allFilterColumns = priceTable.getFilterTableColumns();
            printWriter.println(filters.size());
            printWriter.println(priceTable.getMatchMode().name());
            for (FFGTableFilter<E> filter : filters) {
                FilterKey<E, ?> filterKey = filter.getKey();
                int keyIndex = allFilterColumns.indexOf(filterKey);
                int operatorIndex = filterKey.getFilterOperators().indexOf(filter.getFilterOperator());
                Object filterValue = filter.getValue();
                String valueString = FilterStringTranslator.toString(filterValue);
                printWriter.printf("%d\t%d\t%s\n", keyIndex, operatorIndex, valueString);
            }
            
            // sorting
            TableComparatorChooser<E> comparatorChooser = priceTable.getTableComparatorChooser();
            List<Integer> sortingColumns = comparatorChooser.getSortingColumns();
            printWriter.println(sortingColumns.size());
            for (int column : sortingColumns) {
                int comparatorIndex = comparatorChooser.getColumnComparatorIndex(column);
                boolean reverse = comparatorChooser.isColumnReverse(column);
                printWriter.printf("%d\t%d\t%s\n", column, comparatorIndex, reverse);
            }
            
            printWriter.close();
            setSaved(true);
            setStatsFile(file);
        } catch (IOException ex) {
            throw new SaveException("Could not save to " + file.getAbsolutePath() + ". Reason: " + ex.getMessage(), ex);
        } finally {
            if (printWriter != null) {
                printWriter.close();
            }
        }
    }
    
    /**
     * Returns <code>true</code> if the player is considered 'active', that is has he played in the last round.
     * @param player the player to test.
     * @return as above
     */
    @Override 
    public boolean isPlayerActive(AFLPlayer player) {
        if (getRoundsCompleted() == 0) {
            return true;
        } else {
            return playersActive.contains(player);
        }
    }
    
    /**
     * Implements the {@link PropertyChangeListener} interface
     * @param evt
     */
//    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        // calculate whether player was active in the last round.
        if (VSGameStats.SCORES_PROPERTY.equals(evt.getPropertyName())) {
            VSGameStats stats = (VSGameStats) evt.getSource();
            int lastRound = stats.getLastRoundPlayed();
            
            if (lastRound == getRoundsCompleted()) {
                playersActive.add(stats.getPlayer());
            } else {
                playersActive.remove(stats.getPlayer());
            }
        }
    }
    
    /**
     * Returns the {@link DTPriceTable} associated with this game manager.
     * @return 
     */
    public VSPriceTable getPriceTable() {
        return priceTable;
    }

    public void viewPlayerPage(E stats) throws BareBonesBrowserLaunch.BrowserLaunchException {
        VSGameType gameType = getVSGameType();
        BareBonesBrowserLaunch.launch(String.format(PLAYER_RESEARCH_FORMAT, gameType.getBaseURL(), gameType.getGameId(stats.getPlayer())));
    }
    
    /**
     * Updates the player's round number <code>roundNumber</code> score
     * @param player The player to update
     * @param roundNumber The round number to update
     * @throws ffg.game.UpdateScoresException An error occurs retrieving from the website
     */
    private void updatePlayer(AFLPlayer player, int roundNumber) throws UpdateScoresException {
        System.out.println("updating = " + player);
        E stats = getStatsForPlayer(player);
        Scanner playerScanner = null;
        DecimalFormat priceFormat = FFGConstants.getCommaDecimalFormat();
        try {
//            URL url = new URL(String.format(getURLFormat(), player.getOrdinal()));
            Integer id = getVSGameType().getGameId(player);
            if (id == null) {
                return;
            }
            URL url = new URL(String.format(PLAYER_RESEARCH_FORMAT, getVSGameType().getBaseURL(), id));
            playerScanner = new Scanner(url.openStream());
            playerScanner.useDelimiter(GET_ALL_STRINGS_PATTERN);
            String htmlContent = playerScanner.next();
            
//            int beginIndex = htmlContent.indexOf(BEGIN_RESULTS_STRING) + BEGIN_RESULTS_STRING.length() + 1;
//            int endIndex = htmlContent.indexOf(END_RESULTS_STRING);
//            htmlContent = htmlContent.substring(beginIndex, endIndex);
            
            Matcher selectionsMatcher = SELECTIONS_PATTERN.matcher(htmlContent);
            if (selectionsMatcher.find()) {
//                int rank = Integer.parseInt(selectionsMatcher.group(RANKING_GROUP));
//                stats.addToRanksAt(roundNumber - 1 - 1, rank);
                int selections = priceFormat.parse(selectionsMatcher.group(SELECTIONS_GROUP)).intValue();
                stats.addToSelectionsAt(roundNumber - 1 - 1, selections);
                System.out.println("roundNumber = '" + roundNumber + "'");
                System.out.println("selections = '" + selections + "'");
//                System.out.println("rank = '" + rank + "'");
            } else {
                System.err.println(htmlContent);
                throw new UpdateScoresException("No selections found for player " + player, false);
            }
            
            Matcher rowMatcher = ROW_PATTERN.matcher(htmlContent);
            while (rowMatcher.find()) {
                int round = Integer.parseInt(rowMatcher.group(ROW_ROUND));
                int price = priceFormat.parse(rowMatcher.group(ROW_PRICE)).intValue();
                int score = Integer.parseInt(rowMatcher.group(ROW_SCORE));
                stats.addToPricesAt(round - 1, price);
                stats.addToScoresAt(round - 1, score);
                System.out.println("round = '" + round + "'");
                System.out.println("price = '" + price + "'");
                System.out.println("score = '" + score + "'");
            }
            stats.fillPricesOutToRound(roundNumber - 1);
            
            propChangeSupport.firePropertyChange(NUMBER_EDITED_PROPERTY, null, processedPlayersCount.incrementAndGet());
            propChangeSupport.firePropertyChange(PLAYER_EDITING_PROPERTY, null, player);
        } catch (ParseException ex) {
            ex.printStackTrace();
            throw new UpdateScoresException(ex, false);
        } catch (MalformedURLException ex) {
            ex.printStackTrace();
            throw new UpdateScoresException(ex, false);
        } catch (IOException ex) {
            ex.printStackTrace();
            throw new UpdateScoresException(ex, false);
        } finally {
            if (playerScanner != null) {
                playerScanner.close();
            }
        }
    }
    
    /**
     * Updates the last round by retrieving the scores from the DT website.
     * @return <code>true</code> if successful
     * @throws UpdateScoresException if there were complications arising from reading the
     * statistics from the website, or if the current set of statistics do not match the
     * statistics from the website
     */
    public boolean updateLast() throws UpdateScoresException {
        if (updating.compareAndSet(false, true)) {
            try {
                // get the current round number
                Scanner scanner = null;
                try {
                    URL roundNumberURL = new URL(getVSGameType().getBaseURL());
                    scanner = new Scanner(roundNumberURL.openStream());
                    scanner.useDelimiter(GET_ALL_STRINGS_PATTERN);
                    Matcher roundMatcher = getVSGameType().getRoundNumberPattern().matcher(scanner.next());
                    if (roundMatcher.find()) {
                        int roundNumber = Integer.parseInt(roundMatcher.group(1));
                        if (roundNumber == getVSGameType().getMaxRoundNumber()) {
//                            roundNumber--;
                            // problem is both round 21 and round 22 display round 22. need to look at the word 'game closed'
                        } else if (roundNumber - 1 != getRoundsCompleted() + 1) {
                            throw new UpdateScoresException(String.format("Expecting to retrieve data for Round %d but the web site has only data up to Round %d\nPlease find the latest data file and load it.", getRoundsCompleted() + 1, roundNumber - 1), true);
                        } else {
                            roundNumber--;
                        }
                        setRoundsCompleted(roundNumber);
                    } else {
                        throw new UpdateScoresException("Couldn't find current round number", true);
                    }
                } catch (MalformedURLException ex) {
                    throw new UpdateScoresException(ex, false);
                } catch (IOException ioe) {
                    throw new UpdateScoresException(ioe, false);
                } finally {
                    if (scanner != null) {
                        scanner.close();
                    }
                }
                
                if (!updating.get()) {
                    return false;
                }
                
                setSaved(false);
                playersActive.clear();
                
                processedPlayersCount.set(0);
                AtomicInteger atomicInteger = new AtomicInteger(0);
                List<Callable<Void>> callables = new ArrayList<Callable<Void>>(N_THREADS);
                for (int i = 0; i < N_THREADS; i++) {
                    callables.add(new UpdatePageCallable(atomicInteger));
                }
                try {
                    executor = Executors.newFixedThreadPool(N_THREADS);
                    List<Future<Void>> futures = executor.invokeAll(callables);
                    for (Future<Void> f : futures) {
                        try {
                            f.get();
                        }
                        catch (InterruptedException ie) {
                            ie.printStackTrace();
                        }
                        catch (ExecutionException ee) {
                            ee.printStackTrace();
                        }
                    }
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
                double newMultiplier = calculateFormulaParams().multiplier;
                setMultiplier(newMultiplier);
//                setPercentage(percentageForMultiplier(newMultiplier));
                return executor != null;
            } finally {
                updating.set(false);
            }
        } else {
            return false;
        }
    }
    
    /**
     * A {@link Callable} that updates from a queue of players.
     */
    private class UpdatePlayerCallable implements Callable<Void> {
        private final Queue<AFLPlayer> playerQueue;
        private final int roundNumber;
        
        public UpdatePlayerCallable(Queue<AFLPlayer> playerQueue, int roundNumber) {
            this.playerQueue = playerQueue;
            this.roundNumber = roundNumber;
        }

//        @Override
        public Void call() throws UpdateScoresException {
            AFLPlayer player;
            while (!Thread.currentThread().isInterrupted() && (player = playerQueue.poll()) != null) {
                updatePlayer(player, roundNumber);
            }
            System.out.println("Thread.currentThread().isInterrupted() = '" + Thread.currentThread().isInterrupted() + "'");
            return null;
        }
    }

    /**
     * Update stats from a html page of scores
     * @param htmlContent contains many player's scores from the last round
     * @throws ffg.game.UpdateScoresException if any error occured reading from the scores.
     */
    private void updatePage(String htmlContent) throws UpdateScoresException {
        Matcher matcher = PLAYER_ROUND_PATTERN.matcher(htmlContent);
        DecimalFormat decimalFormat = FFGConstants.getCommaDecimalFormat();
        int roundIndex = getRoundsCompleted() - 1;

//        System.out.println(htmlContent);
//        System.out.println("--------------------");

        while (matcher.find()) {
            int ordinal = Integer.parseInt(matcher.group(ORDINAL_GROUP));
            AFLPlayer player = getVSGameType().getPlayer(ordinal);
            E stats = getStatsForPlayer(player);

//            System.out.println("player = '" + player + "'");

            try {
                stats.addToPricesAt(roundIndex, decimalFormat.parse(matcher.group(CURRENT_PRICE_GROUP)).intValue());
                stats.addToSelectionsAt(roundIndex, decimalFormat.parse(matcher.group(CURRENT_SELECTIONS_GROUP)).intValue());
//                stats.addToRanksAt(roundIndex, decimalFormat.parse(matcher.group(RANK_GROUP)).intValue());
            } catch (ParseException ex) {
                throw new UpdateScoresException(ex, false);
            }
            stats.addToScoresAt(roundIndex, Integer.parseInt(matcher.group(ROUND_SCORE_GROUP)));
            stats.fillPricesOutToRound(roundIndex);
            
            propChangeSupport.firePropertyChange(NUMBER_EDITED_PROPERTY, null, processedPlayersCount.incrementAndGet());
            propChangeSupport.firePropertyChange(PLAYER_EDITING_PROPERTY, null, player);
        }

        System.out.println("end");
    }
    
    /**
     * {@link Callable} that updates from a page.
     */
    private class UpdatePageCallable implements Callable<Void> {
        private final AtomicInteger pageNumber;
        /**
         * Creates a new instance of UpdatePageCallable
         * @param pageNumber The counter, for multithreaded purposes.
         */
        public UpdatePageCallable(AtomicInteger pageNumber) {
            this.pageNumber = pageNumber;
        }

//        @Override
        public Void call() throws UpdateScoresException {
            int number;
            int nRoundScorePages = (int) Math.ceil(getPlayers().size() / (double) getVSGameType().getPlayerPerRoundPage());
            while (!Thread.currentThread().isInterrupted() && ((number = pageNumber.incrementAndGet()) <= nRoundScorePages)) {
                URL url = null;
                try {
                    url = new URL(String.format(FFGConstants.ROUND_SCORES_FORMAT, getVSGameType().getBaseURL(), number));
                } catch (MalformedURLException ex) {
                    throw new UpdateScoresException(ex, false);
                }
                Scanner scanner = null;
                try {
                    scanner = new Scanner(url.openStream());
                    scanner.useDelimiter(GET_ALL_STRINGS_PATTERN);
                    String htmlContent = scanner.next();
                    if (!Thread.currentThread().isInterrupted()) {
                        System.out.println("updating page " + number);
                        updatePage(htmlContent);
                    }
                }
                
                catch (IOException ex) {
                    throw new UpdateScoresException(ex, false);
                } finally {
                    if (scanner != null) {
                        scanner.close();
                    }
                }
            }
            return null;
        }
    }
    
    /**
     * Updates all rounds by retrieving the scores from the DT website.
     * @return 
     * @throws UpdateScoresException if there were complications arising from reading the
     * statistics from the website
     */
    public boolean updateAll() throws UpdateScoresException {
        if (updating.compareAndSet(false, true)) {
            try {
                int roundNumber = getRoundsCompleted();
                setRoundsCompleted(0);
                
                for (E dtStats : playerStats.values()) {
                    dtStats.clear();
                }
                playersActive.clear();
                
                Scanner scanner = null;
                try {
                    URL roundNumberURL = new URL(getVSGameType().getBaseURL());
                    scanner = new Scanner(roundNumberURL.openStream());
                    scanner.useDelimiter(GET_ALL_STRINGS_PATTERN);
                    Matcher roundMatcher = getVSGameType().getRoundNumberPattern().matcher(scanner.next());
                    if (roundMatcher.find()) {
                        roundNumber = Integer.parseInt(roundMatcher.group(1));
                        setRoundsCompleted(roundNumber - 1);
                    }
                } catch (MalformedURLException ex) {
                    throw new UpdateScoresException(ex, false);
                } catch (IOException ioe) {
                    throw new UpdateScoresException(ioe, false);
                } finally {
                    if (scanner != null) {
                        scanner.close();
                    }
                }
                if (!updating.get()) {
                    return false;
                }
                
                Collection<AFLPlayer> curPlayers = getPlayers();
                Queue<AFLPlayer> queue = new ConcurrentLinkedQueue<AFLPlayer>(curPlayers);
                
                setSaved(false);
                processedPlayersCount.set(0);
                
                List<Callable<Void>> callables = new ArrayList<Callable<Void>>(N_THREADS);
                for (int i = 0; i < N_THREADS; i++) {
                    callables.add(new UpdatePlayerCallable(queue, roundNumber));
                }
                
                List<Future<Void>> futures = null;
                try {
                    executor = Executors.newFixedThreadPool(N_THREADS);
                    long now = System.currentTimeMillis();
                    futures = executor.invokeAll(callables);
                    System.out.printf("time = %d\n", (System.currentTimeMillis() - now));
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                } finally {
                    if (futures != null) {
                        for (Future<Void> f : futures) {
                            try {
                                f.get();
                            }
                            catch (ExecutionException ee) {
                                if (ee.getCause() instanceof UpdateScoresException) {
                                    throw ((UpdateScoresException) ee.getCause());
                                } else {
                                    throw new UpdateScoresException(ee, false);
                                }
                            }
                            catch (InterruptedException ie) {
                                ie.printStackTrace();
                            }
                        }
                    }
                }
                double newMultiplier = calculateFormulaParams().multiplier;
                setMultiplier(newMultiplier);
//                setPercentage(percentageForMultiplier(newMultiplier));
                return executor != null;
            } finally {
                updating.set(false);
            }
        } else {
            return false;
        }
    }
    
    /**
     * The Dream Team rows are alternating colors.
     */
    public static class VSTableCellRenderer extends AlternateRowTableCellRenderer {
        private final Color defaultForeground;
        private final int defaultHorizontalAlignment;
        private final int defaultIconTextGap;
        public VSTableCellRenderer(VSGameManager manager, Color even, Color odd) {
            super(manager, even, odd);
            
            defaultForeground = getForeground();
            defaultHorizontalAlignment = getHorizontalAlignment();
            defaultIconTextGap = getIconTextGap();
        }
        
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            // set defaults
            setFont(getNormalFont());
            setEnabled(true);
            setForeground(defaultForeground);
            setHorizontalAlignment(defaultHorizontalAlignment);
            setIcon(null);
            setDisabledIcon(null);
            setIconTextGap(defaultIconTextGap);
            
            super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
            return this;
        }
    }
    
    /**
     * The current multiplier
     * @return as above
     */
    public double getMultiplier() {
        return multiplier;
    }

    public double getScale() {
        return scale;
    }
    
    /**
     * Sets the current multiplier
     * @param multiplier The new current multiplier
     */
    public void setMultiplier(double multiplier) {
        double oldMultiplier = this.multiplier;
        if (Double.compare(oldMultiplier, multiplier) != 0) {
            this.multiplier = multiplier;
            scale = getVSGameType().getDefaultMultiplier() / (3 * multiplier + getVSGameType().getDefaultMultiplier());
            propChangeSupport.firePropertyChange(MULTIPLIER_PROPERTY, oldMultiplier, multiplier);
            
            setSaved(false);
        }
    }
    
//    /**
//     * The current percentage
//     * @return as above
//     */
//    public double getPercentage() {
//        return percentage;
//    }
//
//    /**
//     * Sets the current percentage
//     * @param percentage The new current percentage
//     */
//    public void setPercentage(double percentage) {
//        double oldPercentage = this.percentage;
//        this.percentage = percentage;
//        propChangeSupport.firePropertyChange(PERCENTAGE_PROPERTY, oldPercentage, percentage);
//        if (Double.compare(oldPercentage, percentage) != 0) {
//            setSaved(false);
//        }
//    }
    
    /**
     * Calculate the multiplier from the stats. It is simply, for all eligible players
     * (eligible being played in the last round and has played in at least 3 rounds)
     * sum of their current price / sum of their 3 round averages.
     * @return The new multiplier
     */
    public FormulaParams calculateFormulaParams() {
        int totalPrices = 0;
        int totalAggregates = 0;
        int round = getRoundsCompleted();
        int scoresToGet = round < 3 ? round : 3;
        int count = 0;
        int roundNo = round == 1 ? 0 : round - 1 - 1;
        for (E stats : playerStats.values()) {
            if (stats.isEligible(round)) {
                count++;
                totalPrices += stats.getPriceAt(roundNo);
                int lastScores = stats.getLastNScores(scoresToGet);
                totalAggregates += lastScores;
                System.out.println(lastScores / (double) scoresToGet);
            }
        }
        System.out.println("count = '" + count + "'");
        System.out.println("totalPrices = '" + totalPrices + "'");
        System.out.println("totalAggregates = '" + totalAggregates + "'");
        
        if (totalAggregates == 0) {
//            return getVSGameType().getDefaultMultiplier();
            return new FormulaParams(count, totalPrices, 0, getVSGameType().getDefaultMultiplier());
        } else {
//            return totalPrices / (totalAggregates / (double) scoresToGet);
            double totalThreeRoundAverages = (totalAggregates / (double) scoresToGet);
            return new FormulaParams(count, totalPrices, totalThreeRoundAverages, totalPrices / totalThreeRoundAverages);
        }
    }

    public static class FormulaParams {
        public final int numPlayers;
        public final int totalPrices;
        public final double totalThreeRoundAverages;
        public final double multiplier;

        public FormulaParams(int numPlayers, int totalPrices, double totalThreeRoundAverages, double multiplier) {
            this.numPlayers = numPlayers;
            this.totalPrices = totalPrices;
            this.totalThreeRoundAverages = totalThreeRoundAverages;
            this.multiplier = multiplier;
        }
    }
    
//    /**
//     * Using the slope and intercept, calculate the percentage from the given <code>multiplier</code>.
//     * Given there is a linear relationship from these two variables.
//     * @param multiplier
//     * @return the new percentage
//     */
//    public double percentageForMultiplier(double multiplier) {
//        return getVSGameType().getSlope() * multiplier + getVSGameType().getIntercept();
//    }
    
    /**
     * Stop any current updates
     */
    public void cancelUpdate() {
        updating.set(false);
        if (executor != null) {
            executor.shutdownNow();
            executor = null;
        }
    }
    
    /**
     * Returns <code>true</code> if some kind of update is currently occurring.
     * @return
     */
    public boolean isUpdating() {
        return updating.get();
    }
    
    /**
     * Returns the type of virtual sports game.
     * @return as above
     */
    public abstract VSGameType getVSGameType();
    
    /**
     * Given the player, return the positions for this particular game type
     * @param player The player in question
     * @return as above
     */
    @Override
    public AFLPositions getPositionsForPlayer(AFLPlayer player) {
        return getVSGameType().getPositionsForPlayer(player);
    }
    
    /**
     * Given the player, return the staring price for this particular game type
     * @param player The player in question
     * @return as above
     */
    @Override
    public int getStartingValueForPlayer(AFLPlayer player) {
        return getVSGameType().getStartingValueForPlayer(player);
    }
    
    /**
     * Given the team, return the icon for this particular game type
     * @param team The team in question
     * @return as above
     */
    @Override
    public Icon getIconForTeam(AFLTeam team) {
        return getVSGameType().getTeamIcon(team);
    }
    
    /**
     * Given the team, return the disabled icon for this particular game type
     * @param team The team in question
     * @return as above
     */
    @Override
    public Icon getIconForTeamDisabled(AFLTeam team) {
        return getVSGameType().getTeamDisabledIcon(team);
    }

    /**
     * Name to be displayed
     * @return The display name of this game type
     */
    public String getFullName() {
        return getVSGameType().toString();
    }
    
    /**
     * Given the player, return the game id for this particular game type
     * @param player The player in question
     * @return as above
     */
    @Override
    public Integer getGameId(AFLPlayer player) {
        return getVSGameType().getGameId(player);
    }

    @Override
    public E getStatsForPlayer(AFLPlayer player) {
        return super.getStatsForPlayer(player);
    }

//    public static void main(String[] args) throws Exception {
//        Scanner scanner = new Scanner(new File("W:\\test.txt"));
//        scanner.useDelimiter(GET_ALL_STRINGS_PATTERN);
//        String s = scanner.next();
//        scanner.close();
//        Matcher m = PLAYER_ROUND_PATTERN.matcher(s);
//        int counter = 1;
//        while (m.find()) {
//            System.out.println(counter++);
//            System.out.println("m.group(3) = '" + m.group(3) + "'");
//            System.out.println("m.group(4) = '" + m.group(4) + "'");
//        }
//    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.