edu.oregonstate.eecs.mcplan.domains.planetwars.PwSimulator.java Source code

Java tutorial

Introduction

Here is the source code for edu.oregonstate.eecs.mcplan.domains.planetwars.PwSimulator.java

Source

/* LICENSE
Copyright (c) 2013-2016, Jesse Hostetler (jessehostetler@gmail.com)
All rights reserved.
    
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    
1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
    
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/**
 * 
 */
package edu.oregonstate.eecs.mcplan.domains.planetwars;

import java.util.ArrayDeque;

import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.math3.random.RandomGenerator;

import edu.oregonstate.eecs.mcplan.JointAction;
import edu.oregonstate.eecs.mcplan.Pair;
import edu.oregonstate.eecs.mcplan.sim.UndoSimulator;
import edu.oregonstate.eecs.mcplan.util.Fn;

/**
 * @author jhostetler
 *
 */
public class PwSimulator implements UndoSimulator<PwState, PwEvent> {
    //   public class GrowthEvent implements PwEvent
    //   {
    //      public final PwPlanet planet;
    //      public final int[] change;
    //      private boolean done = false;
    //
    //      public GrowthEvent( final PwPlanet planet, final int[] change )
    //      {
    //         this.planet = planet;
    //         this.change = change;
    //      }
    //
    //      @Override
    //      public GrowthEvent create()
    //      {
    //         return new GrowthEvent( planet, change );
    //      }
    //
    //      @Override
    //      public void doAction( final PwState state )
    //      {
    //         assert( !done );
    //         final PwPlayer owner = planet.owner();
    //         for( final PwUnit u : game.units() ) {
    //            planet.setPopulation( owner, u, planet.population( owner, u ) + change[u.id] );
    //            assert( planet.population( owner, u ) >= 0 );
    //            assert( planet.population( owner, u ) <= planet.capacity );
    //         }
    //         done = true;
    //      }
    //
    //      @Override
    //      public void undoAction( final PwState state )
    //      {
    //         assert( done );
    //         final PwPlayer owner = planet.owner();
    //         for( final PwUnit u : game.units() ) {
    //            planet.setPopulation( owner, u, planet.population( owner, u ) - change[u.id] );
    //            assert( planet.population( owner, u ) >= 0 );
    //            assert( planet.population( owner, u ) <= planet.capacity );
    //         }
    //         done = false;
    //      }
    //
    //      @Override
    //      public boolean isDone()
    //      {
    //         return done;
    //      }
    //   }

    /**
     * Applies production points toward a particular Unit on a particular
     * Planet. This event never creates a new unit, even if there are enough
     * production points to pay for the unit.
     */
    private static class PartialProductionEvent extends PwEvent {
        private final PwPlanet planet;
        private final PwUnit type;
        private final int production;
        private boolean done = false;

        public PartialProductionEvent(final PwPlanet planet, final PwUnit type, final int production) {
            this.planet = planet;
            this.type = type;
            this.production = production;
        }

        @Override
        public void undoAction(final PwState s) {
            assert (done);
            planet.setStoredProduction(type, planet.storedProduction(type) - production);
            done = false;
        }

        @Override
        public void doAction(final RandomGenerator rng, final PwState s) {
            assert (!done);
            planet.setStoredProduction(type, planet.storedProduction(type) + production);
            done = true;
        }

        @Override
        public boolean isDone() {
            return done;
        }

        @Override
        public PartialProductionEvent create() {
            return new PartialProductionEvent(planet, type, production);
        }

        @Override
        public String toString() {
            return "PartialProductionEvent(planet = " + planet.id + ", production = " + production + ")";
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder(7, 11).append(planet).append(type).append(production).toHashCode();
        }

        @Override
        public boolean equals(final Object obj) {
            if (!(obj instanceof PartialProductionEvent)) {
                return false;
            }
            final PartialProductionEvent that = (PartialProductionEvent) obj;
            return planet == that.planet && type == that.type && production == that.production;
        }
    }

    /**
     * Creates a new unit on a particular Planet. Does not affect stored
     * production points.
     */
    private static class EntityCreateEvent extends PwEvent {
        public final PwPlanet planet;
        public final PwUnit type;
        private boolean done = false;

        public EntityCreateEvent(final PwPlanet planet, final PwUnit type) {
            this.planet = planet;
            this.type = type;
        }

        @Override
        public void doAction(final RandomGenerator rng, final PwState s) {
            assert (!done);
            planet.incrementPopulation(planet.owner(), type);
            planet.setStoredProduction(type, 0);
            done = true;
        }

        @Override
        public void undoAction(final PwState state) {
            assert (done);
            planet.decrementPopulation(planet.owner(), type);
            planet.setStoredProduction(type, type.cost);
            done = false;
        }

        @Override
        public EntityCreateEvent create() {
            return new EntityCreateEvent(planet, type);
        }

        @Override
        public boolean isDone() {
            return done;
        }

        @Override
        public String toString() {
            return "EntityCreateEvent(planet = " + planet.id + ", type = " + type + ")";
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder(3, 11).append(planet).append(type).toHashCode();
        }

        @Override
        public boolean equals(final Object obj) {
            if (!(obj instanceof EntityCreateEvent)) {
                return false;
            }
            final EntityCreateEvent that = (EntityCreateEvent) obj;
            return planet == that.planet && type == that.type;
        }
    }

    /**
     * Advances all units on all Routes and decrements the 'setup' timers.
     */
    public static class ClockEvent extends PwEvent {
        private final PwPlanet[] planets;
        private final PwRoute[] routes;
        private boolean done = false;

        public ClockEvent(final PwPlanet[] planets, final PwRoute[] routes) {
            this.planets = planets;
            this.routes = routes;
        }

        @Override
        public ClockEvent create() {
            return new ClockEvent(planets, routes);
        }

        @Override
        public void doAction(final RandomGenerator rng, final PwState s) {
            assert (!done);
            for (final PwPlanet p : planets) {
                if (p.owner() != PwPlayer.Neutral && p.getSetup() > 0) {
                    p.decrementSetup();
                }
            }
            for (final PwRoute route : routes) {
                route.forward();
            }
            done = true;
        }

        @Override
        public boolean isDone() {
            return done;
        }

        @Override
        public void undoAction(final PwState s) {
            assert (done);
            for (final PwPlanet p : planets) {
                if (p.owner() != PwPlayer.Neutral && p.getSetup() < p.setup_time) {
                    p.incrementSetup();
                }
            }
            for (final PwRoute route : routes) {
                route.backward();
            }
            done = false;
        }

        @Override
        public int hashCode() {
            return ClockEvent.class.hashCode();
        }

        @Override
        public boolean equals(final Object obj) {
            return obj instanceof ClockEvent;
        }

        @Override
        public String toString() {
            return "RouteForwardEvent";
        }
    }

    /**
     * Transfers all units with 0 arrival time from the specified Route to
     * their destination Planet.
     */
    private class ArrivalEvent extends PwEvent {
        private final PwRoute route;
        private boolean done = false;

        private final int[][] ab_arrivals = new int[PwPlayer.Ncompetitors][game.Nunits()];
        private final int[][] ba_arrivals = new int[PwPlayer.Ncompetitors][game.Nunits()];

        public ArrivalEvent(final PwRoute route) {
            this.route = route;
        }

        @Override
        public void doAction(final RandomGenerator rng, final PwState s) {
            assert (!done);
            for (final PwPlayer player : PwPlayer.competitors) {
                for (final PwUnit unit : game.units()) {
                    final int ab = route.populationAB(0, player, unit);
                    route.b.incrementPopulation(player, unit, ab);
                    ab_arrivals[player.id][unit.id] = ab;

                    final int ba = route.populationBA(0, player, unit);
                    route.a.incrementPopulation(player, unit, ba);
                    ba_arrivals[player.id][unit.id] = ba;
                }
            }
            done = true;
        }

        @Override
        public void undoAction(final PwState s) {
            assert (done);
            for (final PwPlayer player : PwPlayer.competitors) {
                for (final PwUnit unit : game.units()) {
                    final int ab = ab_arrivals[player.id][unit.id];
                    route.b.decrementPopulation(player, unit, ab);
                    route.setPopulationAB(0, player, unit, ab);

                    final int ba = ba_arrivals[player.id][unit.id];
                    route.a.decrementPopulation(player, unit, ba);
                    route.setPopulationBA(0, player, unit, ba);
                }
            }
            done = false;
        }

        @Override
        public ArrivalEvent create() {
            return new ArrivalEvent(route);
        }

        @Override
        public boolean isDone() {
            return done;
        }

        @Override
        public String toString() {
            return "ArrivalEvent[" + route + "]";
        }

        @Override
        public int hashCode() {
            return 13 + 17 * route.hashCode();
        }

        @Override
        public boolean equals(final Object obj) {
            if (!(obj instanceof ArrivalEvent)) {
                return false;
            }
            final ArrivalEvent that = (ArrivalEvent) obj;
            return route == that.route;
        }
    }

    /**
     * Represents a battle. Two armies fight when they are both at the same
     * planet. If the defending army is destroyed, ownership changes and
     * all stored production is lost. In the event of a tie, Neutral takes
     * control.
     */
    private class BattleEvent extends PwEvent {
        public final PwPlanet planet;

        private boolean done = false;
        private PwPlayer old_owner = null;
        private final int[][] old_population = new int[PwPlayer.Ncompetitors][game.Nunits()];
        private final int[] old_carry = new int[PwPlayer.Ncompetitors];
        private PwUnit old_production = null;
        private final int[] old_stored = new int[game.Nunits()];
        private int old_overflow = 0;
        private final int old_setup = 0;

        public BattleEvent(final PwPlanet planet) {
            this.planet = planet;
        }

        @Override
        public void undoAction(final PwState s) {
            assert (done);
            planet.setOwner(old_owner);
            for (final PwPlayer y : PwPlayer.competitors) {
                for (int i = 0; i < game.Nunits(); ++i) {
                    planet.setPopulation(y, game.unit(i), old_population[y.id][i]);
                }
                planet.setCarryDamage(y, old_carry[y.id]);
            }
            planet.setProduction(old_production);
            planet.setStoredProduction(old_stored);
            planet.setOverflowProduction(old_overflow);
            planet.setSetup(old_setup);
            done = false;
        }

        @Override
        public void doAction(final RandomGenerator rng, final PwState s) {
            assert (!done);
            old_owner = planet.owner();
            for (final PwPlayer player : PwPlayer.competitors) {
                Fn.memcpy(old_population[player.id], planet.population(player));
                old_carry[player.id] = planet.carryDamage(player);
            }
            old_production = planet.nextProduced();
            Fn.memcpy(old_stored, planet.storedProduction(), planet.storedProduction().length);
            old_overflow = planet.overflowProduction();

            final int[] damage = game.damage(planet);
            final int dmin = damage[PwPlayer.Min.id] + planet.carryDamage(PwPlayer.Max);
            final int dmax = damage[PwPlayer.Max.id] + planet.carryDamage(PwPlayer.Min);
            final Pair<int[], Integer> smin = game.survivors(planet.population(PwPlayer.Min), dmax);
            final Pair<int[], Integer> smax = game.survivors(planet.population(PwPlayer.Max), dmin);

            planet.setPopulation(PwPlayer.Min, smin.first);
            planet.setPopulation(PwPlayer.Max, smax.first);

            final boolean rmin = planet.supply(PwPlayer.Min) > 0;
            final boolean rmax = planet.supply(PwPlayer.Max) > 0;
            if (rmin && rmax) {
                planet.setCarryDamage(PwPlayer.Min, smin.second);
                planet.setCarryDamage(PwPlayer.Max, smax.second);
            } else {
                // Owner change?
                boolean change = false;
                if (rmin) {
                    if (old_owner != PwPlayer.Min) {
                        planet.setOwner(PwPlayer.Min);
                        change = true;
                    }
                } else if (rmax) {
                    if (old_owner != PwPlayer.Max) {
                        planet.setOwner(PwPlayer.Max);
                        change = true;
                    }
                } else {
                    planet.setOwner(PwPlayer.Neutral);
                    change = true;
                }

                // Clear planet status if owner changed
                if (change) {
                    planet.setStoredProduction(Fn.repeat(0, game.Nunits()));
                    planet.setProduction(game.defaultProduction());
                    planet.clearCarryDamage();
                    planet.setOverflowProduction(0);
                    planet.resetSetup();
                }
            }

            done = true;
        }

        @Override
        public boolean isDone() {
            return done;
        }

        @Override
        public BattleEvent create() {
            return new BattleEvent(planet);
        }

        @Override
        public String toString() {
            return "BattleEvent[" + planet + "]";
        }

        @Override
        public int hashCode() {
            return 11 + 13 * planet.hashCode();
        }

        @Override
        public boolean equals(final Object obj) {
            if (!(obj instanceof BattleEvent)) {
                return false;
            }
            final BattleEvent that = (BattleEvent) obj;
            return planet == that.planet;
        }
    }

    /**
     * Change of ownership *without* loss of production. Should only be used
     * to change from Neutral -> not Neutral.
     */
    private static class OwnerChangeEvent extends PwEvent {
        public final PwPlanet planet;
        public final PwPlayer new_owner;

        private PwPlayer old_owner = null;

        public OwnerChangeEvent(final PwPlanet p, final PwPlayer y) {
            planet = p;
            new_owner = y;
        }

        @Override
        public void doAction(final RandomGenerator rng, final PwState s) {
            assert (old_owner == null);
            old_owner = planet.owner();
            assert (old_owner == PwPlayer.Neutral);
            planet.setOwner(new_owner);

            planet.resetSetup();
        }

        @Override
        public void undoAction(final PwState s) {
            assert (old_owner != null);
            planet.setOwner(old_owner);
            old_owner = null;
        }

        @Override
        public OwnerChangeEvent create() {
            return new OwnerChangeEvent(planet, new_owner);
        }

        @Override
        public boolean isDone() {
            return old_owner != null;
        }

        @Override
        public String toString() {
            return "OwnerChangeEvent[p=" + planet.id + ", y=" + new_owner.id + "]";
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder(11, 17).append(planet).append(new_owner).toHashCode();
        }

        @Override
        public boolean equals(final Object obj) {
            if (!(obj instanceof OwnerChangeEvent)) {
                return false;
            }
            final OwnerChangeEvent that = (OwnerChangeEvent) obj;
            return planet == that.planet && new_owner == that.new_owner;
        }
    }

    // -----------------------------------------------------------------------

    private final PwGame game;
    private final PwState s;
    private int depth_ = 0;

    private final ArrayDeque<ArrayDeque<PwEvent>> event_history = new ArrayDeque<ArrayDeque<PwEvent>>();

    public PwSimulator(final PwGame game, final PwState initial_state) {
        this.game = game;
        s = initial_state;
    }

    @Override
    public int nagents() {
        return 2;
    }

    @Override
    public long horizon() {
        return game.T - s.t;
    }

    @Override
    public PwState state() {
        return s;
    }

    private void applyEvent(final PwEvent e) {
        e.doAction(s);
        event_history.peek().add(e);
    }

    private void applyProduction(final PwPlanet p) {
        int production = (int) ((0.5 + game.rng.nextDouble()) * p.production());

        while (production > 0) {
            final PwUnit next = p.nextProduced();
            final int rem = next.cost - p.storedProduction(next);
            final int delta = Math.min(rem, production);
            production -= delta;
            if (rem - delta == 0) {
                if (rem < next.cost) {
                    // Unit was partially built last turn; need this event
                    // to restore the partial build for undo.
                    applyEvent(new PartialProductionEvent(p, next, rem));
                }
                if (s.supply(p.owner()) < game.max_population) {
                    applyEvent(new EntityCreateEvent(p, next));
                } else {
                    // Player is at population cap
                    break;
                }
            } else {
                assert (production == 0);
                applyEvent(new PartialProductionEvent(p, next, delta));
            }
        }
    }

    protected void advance() {
        for (int t = 0; t < game.epoch; ++t) {
            // Event ordering:
            // 1. Production
            // 2. Units move
            // 3. Units arrive / Reinforcements added
            // 4. Battles

            // 1. Production
            for (int i = 0; i < s.planets.length; ++i) {
                final PwPlanet p = s.planets[i];
                if (p.owner() != PwPlayer.Neutral && p.getSetup() == 0) {
                    applyProduction(p);
                }
            }

            // 2. Units move
            applyEvent(new ClockEvent(s.planets, s.routes));

            // 3. Units arrive
            for (final PwRoute route : s.routes) {
                applyEvent(new ArrivalEvent(route));
            }

            // 4. Battles
            for (final PwPlanet p : s.planets) {
                final boolean min_present = p.supply(PwPlayer.Min) > 0;
                final boolean max_present = p.supply(PwPlayer.Max) > 0;
                if (min_present && max_present) {
                    applyEvent(new BattleEvent(p));
                } else if (p.owner() == PwPlayer.Neutral) {
                    if (min_present) {
                        applyEvent(new OwnerChangeEvent(p, PwPlayer.Min));
                    } else if (max_present) {
                        applyEvent(new OwnerChangeEvent(p, PwPlayer.Max));
                    }
                }
            }
        }
    }

    @Override
    public double[] reward() {
        final PwPlayer winner = s.winner();
        if (winner == PwPlayer.Min) {
            return new double[] { 1, -1 };
        } else if (winner == PwPlayer.Max) {
            return new double[] { -1, 1 };
        } else {
            return new double[] { 0, 0 };
        }
    }

    @Override
    public boolean isTerminalState() {
        return s.isTerminal();
    }

    @Override
    public void takeAction(final JointAction<PwEvent> a) {
        final ArrayDeque<PwEvent> frame = new ArrayDeque<PwEvent>();
        event_history.push(frame);

        for (final PwEvent e : a) {
            applyEvent(e);
        }

        advance();
        s.t += 1;
        depth_ += frame.size();
    }

    @Override
    public void untakeLastAction() {
        final ArrayDeque<PwEvent> frame = event_history.pop();
        final int frame_size = frame.size();
        while (!frame.isEmpty()) {
            final PwEvent e = frame.pop();
            e.undoAction(s);
        }
        s.t -= 1;
        depth_ -= frame_size;
    }

    @Override
    public long depth() {
        return depth_;
    }

    @Override
    public long t() {
        return s.t;
    }

    @Override
    public int[] turn() {
        return new int[] { 0, 1 };
    }

    @Override
    public String detailString() {
        return "PwSimulator";
    }
}