eu.lp0.cursus.scoring.scores.impl.GenericRaceLapsData.java Source code

Java tutorial

Introduction

Here is the source code for eu.lp0.cursus.scoring.scores.impl.GenericRaceLapsData.java

Source

/*
   cursus - Race series management program
   Copyright 2012-2014  Simon Arlott
    
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
    
   This program 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 Affero General Public License for more details.
    
   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.lp0.cursus.scoring.scores.impl;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Ordering;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import eu.lp0.cursus.db.data.Penalty;
import eu.lp0.cursus.db.data.Pilot;
import eu.lp0.cursus.db.data.Race;
import eu.lp0.cursus.db.data.RaceAttendee;
import eu.lp0.cursus.db.data.RaceTally;
import eu.lp0.cursus.scoring.data.RacePenaltiesData;
import eu.lp0.cursus.scoring.data.ScoredData;
import eu.lp0.cursus.scoring.scores.base.AbstractRaceLapsData;
import eu.lp0.cursus.util.PilotRaceNumberComparator;

@SuppressFBWarnings({ "NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE" })
public class GenericRaceLapsData<T extends ScoredData & RacePenaltiesData> extends AbstractRaceLapsData<T> {
    private static final int EXPECTED_MAXIMUM_LAPS = 32;
    private static final int EXPECTED_MAXIMUM_PENALTIES = 2;
    private final boolean scoreBeforeStart;
    private final boolean scoreAfterFinish;

    public GenericRaceLapsData(T scores, boolean scoreBeforeStart, boolean scoreAfterFinish) {
        super(scores);

        this.scoreBeforeStart = scoreBeforeStart;
        this.scoreAfterFinish = scoreAfterFinish;
    }

    @Override
    protected List<Pilot> calculateRaceLapsInOrder(Race race, Map<Pilot, Integer> laps) {
        ListMultimap<Integer, Pilot> raceOrder = ArrayListMultimap.create(EXPECTED_MAXIMUM_LAPS,
                scores.getPilots().size());

        extractRaceLaps(race, laps, raceOrder, null);

        // Get penalties for each pilot
        ListMultimap<Pilot, Penalty> cancelLaps = ArrayListMultimap.create(EXPECTED_MAXIMUM_PENALTIES,
                scores.getPilots().size());
        ListMultimap<Pilot, Penalty> adjustLaps = ArrayListMultimap.create(EXPECTED_MAXIMUM_PENALTIES,
                scores.getPilots().size());
        for (RaceAttendee attendee : Maps.filterKeys(race.getAttendees(), Predicates.in(scores.getPilots()))
                .values()) {
            for (Penalty penalty : Iterables.concat(Ordering.natural().immutableSortedCopy(attendee.getPenalties()),
                    scores.getSimulatedRacePenalties(attendee.getPilot(), race))) {
                if (penalty.getValue() != 0) {
                    switch (penalty.getType()) {
                    case CANCEL_LAPS:
                        cancelLaps.put(attendee.getPilot(), penalty);
                        break;

                    case ADJUST_LAPS:
                        adjustLaps.put(attendee.getPilot(), penalty);
                        break;

                    default:
                        break;
                    }
                }
            }
        }

        // Apply lap cancellation penalties
        if (!cancelLaps.isEmpty()) {
            final Multiset<Pilot> pilotLaps = HashMultiset.create(laps.size());

            for (Map.Entry<Pilot, Integer> pilotLapCount : laps.entrySet()) {
                pilotLaps.setCount(pilotLapCount.getKey(), pilotLapCount.getValue());
            }

            for (Map.Entry<Pilot, Penalty> entry : cancelLaps.entries()) {
                int value = entry.getValue().getValue();
                if (value > 0) {
                    pilotLaps.remove(entry.getKey(), value);
                } else {
                    pilotLaps.add(entry.getKey(), Math.abs(value));
                }
            }

            extractRaceLaps(race, laps, raceOrder, new Predicate<Pilot>() {
                @Override
                public boolean apply(@Nullable Pilot pilot) {
                    return pilotLaps.remove(pilot);
                }
            });
        }

        // Save pilot order
        List<Pilot> origPilotOrder = getPilotOrder(raceOrder);
        SortedSet<Pilot> noLaps = new TreeSet<Pilot>(new PilotRaceNumberComparator());
        Set<Integer> changed = new HashSet<Integer>();

        // It is intentional that pilots can end up having 0 laps but be considered to have completed the race
        for (Map.Entry<Pilot, Penalty> entry : adjustLaps.entries()) {
            Pilot pilot = entry.getKey();
            int lapCount = laps.get(pilot);

            raceOrder.remove(lapCount, pilot);
            changed.add(lapCount);

            lapCount += entry.getValue().getValue();
            if (lapCount <= 0) {
                lapCount = 0;
                noLaps.add(pilot);
            }
            laps.put(pilot, lapCount);

            raceOrder.put(lapCount, pilot);
            changed.add(lapCount);
        }

        // Apply original pilot order
        if (!changed.isEmpty()) {
            origPilotOrder.addAll(noLaps);

            for (Integer lapCount : changed) {
                raceOrder.replaceValues(lapCount,
                        Ordering.explicit(origPilotOrder).immutableSortedCopy(raceOrder.get(lapCount)));
            }

            return getPilotOrder(raceOrder);
        } else {
            return origPilotOrder;
        }
    }

    private void extractRaceLaps(Race race, Map<Pilot, Integer> laps, ListMultimap<Integer, Pilot> raceOrder,
            Predicate<Pilot> filter) {
        for (Pilot pilot : scores.getPilots()) {
            laps.put(pilot, 0);
        }

        raceOrder.clear();

        for (Pilot pilot : extractRaceLaps(race, filter)) {
            int lapCount = laps.get(pilot);

            raceOrder.remove(lapCount, pilot);
            laps.put(pilot, ++lapCount);
            raceOrder.put(lapCount, pilot);
        }
    }

    protected Iterable<Pilot> extractRaceLaps(Race race, Predicate<Pilot> filter) {
        // If they aren't marked as attending the race as a pilot, they don't get scored
        Predicate<Pilot> attending = Predicates.in(filteredPilots(race));
        if (filter != null) {
            filter = Predicates.and(attending, filter);
        } else {
            filter = attending;
        }

        // Convert a list of race events into a list of valid pilot laps
        return Iterables.filter(Iterables.transform(Iterables.unmodifiableIterable(race.getTallies()),
                new Function<RaceTally, Pilot>() {
                    boolean scoring = scoreBeforeStart;

                    @Override
                    public Pilot apply(@Nonnull RaceTally tally) {
                        switch (tally.getType()) {
                        case START:
                            scoring = true;
                            break;

                        case LAP:
                            if (scoring) {
                                return tally.getPilot();
                            }
                            break;

                        case INVALID_LAP:
                            break;

                        case FINISH:
                            scoring = scoreAfterFinish;
                            break;
                        }

                        return null;
                    }

                }), filter);
    }

    protected List<Pilot> getPilotOrder(ListMultimap<Integer, Pilot> raceOrder) {
        List<Pilot> pilotOrder = new ArrayList<Pilot>(scores.getPilots().size());
        for (Integer lap : Ordering.natural().reverse().immutableSortedCopy(raceOrder.keySet())) {
            pilotOrder.addAll(raceOrder.get(lap));
        }
        return pilotOrder;
    }

    private Set<Pilot> filteredPilots(Race race) {
        ImmutableSet.Builder<Pilot> pilots = ImmutableSet.builder();
        for (RaceAttendee attendee : Maps.filterKeys(race.getAttendees(), Predicates.in(scores.getFleet()))
                .values()) {
            if (attendee.getType() == RaceAttendee.Type.PILOT) {
                pilots.add(attendee.getPilot());
            }
        }
        return pilots.build();
    }
}