package io.vivarium.core; import io.vivarium.core.processor.Processor; import io.vivarium.core.processor.ProcessorBlueprint; import io.vivarium.core.sensor.Sensor; import io.vivarium.serialization.ClassRegistry; import io.vivarium.serialization.SerializedParameter; import io.vivarium.serialization.VivariumObject; import io.vivarium.util.Functions; import io.vivarium.util.Rand; import lombok.EqualsAndHashCode; import lombok.ToString; @EqualsAndHashCode(callSuper = true) @ToString @SuppressWarnings("serial") // Default serialization is never used for a durable store public class Creature extends VivariumObject { static { ClassRegistry.getInstance().register(Creature.class); } // Meta information @SerializedParameter private int _id; @SerializedParameter private CreatureBlueprint _creatureBlueprint; @SerializedParameter private double _generation; // Processor @SerializedParameter private Processor[] _processors; @SerializedParameter private double[] _inputs; @SerializedParameter private double[] _memoryUnits; @SerializedParameter private double[] _soundInputs; @SerializedParameter private double[] _soundOutputs; @SerializedParameter private double[] _signInputs; @SerializedParameter private double[] _signOutputs; // State @SerializedParameter private boolean _hasActed = true; // Defaults to true to prevent a newborn from acting before it plans @SerializedParameter private boolean _wasSuccessful = true; @SerializedParameter private Gender _gender; @SerializedParameter private double _randomSeed; @SerializedParameter private int _age; @SerializedParameter private int _gestation; @SerializedParameter private int _food; @SerializedParameter private Direction _facing; @SerializedParameter private Action _action = Action.SPAWN; @SerializedParameter private int _health; // Fetus @SerializedParameter private Creature _fetus; protected Creature() { } public Creature(CreatureBlueprint creatureBlueprint) { this(creatureBlueprint, null, null); } public Creature(CreatureBlueprint creatureBlueprint, Creature prototype) { this(creatureBlueprint, prototype, null); } public Creature(Creature prototype) { this(prototype._creatureBlueprint, prototype, null); } public Creature(Creature parent1, Creature parent2) { this(parent1._creatureBlueprint, parent1, parent2); } public Creature(CreatureBlueprint creatureBlueprint, Creature parent1, Creature parent2) { this._creatureBlueprint = creatureBlueprint; // Compute creature generation if (parent1 != null) { if (parent2 != null) { this._generation = Functions.logarithmicAverage(parent1._generation, parent2._generation) + 1; } else { this._generation = parent1._generation; } } else { this._generation = 1; } // Create processors to control the Creature ProcessorBlueprint[] processorBlueprints = _creatureBlueprint.getProcessorBlueprints(); _processors = new Processor[processorBlueprints.length]; for (int i = 0; i < processorBlueprints.length; i++) { Processor processor1 = parent1 != null ? parent1._processors[i] : null; Processor processor2 = parent2 != null ? parent2._processors[i] : null; _processors[i] = createProcessor(processorBlueprints[i], processor1, processor2); } // Sets the size of all the processor i/o arrays _inputs = new double[_creatureBlueprint.getMultiplexerInputCount()]; _memoryUnits = new double[_creatureBlueprint.getMemoryUnitCount()]; _soundInputs = new double[_creatureBlueprint.getSoundChannelCount()]; _soundOutputs = new double[_creatureBlueprint.getSoundChannelCount()]; _signInputs = new double[_creatureBlueprint.getSignChannelCount()]; _signOutputs = new double[_creatureBlueprint.getSignChannelCount()]; // Set gender double randomNumber = Rand.getInstance().getRandomPositiveDouble(); if (randomNumber < _creatureBlueprint.getFemaleThreshold()) { this._gender = Gender.FEMALE; } else { this._gender = Gender.MALE; } // Set the per Creature random seed (this is // currently only used to set animation offsets) this._randomSeed = Rand.getInstance().getRandomPositiveDouble(); // Set defaults this._age = 0; this._gestation = 0; this._food = _creatureBlueprint.getMaximumFood(); this._facing = Direction.NORTH; this._action = Action.REST; this._health = 2000; } private Processor createProcessor(ProcessorBlueprint blueprint, Processor processor1, Processor processor2) { if (processor1 != null) { if (processor2 != null) { // Processor combined from genetic legacy return blueprint.makeProcessorWithParents(processor1, processor2); } else { // Processor from single parent (might still mutate) return blueprint.makeProcessorWithParents(processor1, processor1); } } else { if (processor2 != null) { // Single parent is only an error if parent2 is non-null, otherwise assumed to be a clone throw new Error("Creature with single parent"); } else { // Create a new processor return blueprint.makeProcessor(); } } } public void tick(int flameHit) { // Reset action this._hasActed = false; // Age this._age++; // Gestate if preggars if (this._gestation > 0) { this._gestation++; this._food += _creatureBlueprint.getPregnantFoodRate(); } this._food += _creatureBlueprint.getBaseFoodRate(); this._health -= 10 * flameHit; // healing if (this._health < _creatureBlueprint.getMaximumHealth()) { this._health++; this._food -= 2; } } public Action getAction() { return (_action); } public void listenToCreature(Creature u, double distanceSquared) { int soundChannels = Math.min(this._creatureBlueprint.getSoundChannelCount(), u._creatureBlueprint.getSoundChannelCount()); for (int i = 0; i < soundChannels; i++) { this._soundInputs[i] += u._soundOutputs[i] / distanceSquared; } } public void lookAtCreature(Creature u) { int signChannels = Math.min(this._creatureBlueprint.getSignChannelCount(), u._creatureBlueprint.getSignChannelCount()); for (int i = 0; i < signChannels; i++) { this._signInputs[i] = u._signOutputs[i]; } } public void planAction(GridWorld w, int r, int c) { _action = determineAction(w, r, c); } private Action determineAction(GridWorld w, int r, int c) { Action involuntaryAction = getInvoluntaryAction(); if (involuntaryAction != null) { return (involuntaryAction); } else { // Run sensors int inputIndex = 0; Sensor[] sensors = _creatureBlueprint.getSensors(); for (int i = 0; i < sensors.length; i++) { Sensor sensor = sensors[i]; inputIndex += sensor.sense(w, _inputs, inputIndex, r, c, this); } // Read memory units System.arraycopy(_memoryUnits, 0, _inputs, _creatureBlueprint.getHardProcessorInputs(), this._memoryUnits.length); // Read sound inputs for (int i = 0; i < this.getBlueprint().getSoundChannelCount(); i++) { _inputs[_creatureBlueprint.getHardProcessorInputs() + this._memoryUnits.length + i] = _soundInputs[i]; } // Read sign inputs for (int i = 0; i < this.getBlueprint().getSignChannelCount(); i++) { _inputs[_creatureBlueprint.getHardProcessorInputs() + this._memoryUnits.length + this._soundInputs.length + i] = _signInputs[i]; } // Main processor computation double[] outputs = this._creatureBlueprint.getMultiplexer().outputs(_inputs, _processors); // Save memory units System.arraycopy(outputs, _creatureBlueprint.getHardProcessorOutputs(), _memoryUnits, 0, this._memoryUnits.length); // Clear the sound inputs and set the sound outputs for (int i = 0; i < _creatureBlueprint.getSoundChannelCount(); i++) { this._soundInputs[i] = 0; this._soundOutputs[i] = outputs[_creatureBlueprint.getHardProcessorOutputs() + this._memoryUnits.length + i]; } // Clear the sign inputs and set the sign outputs for (int i = 0; i < this.getBlueprint().getSignChannelCount(); i++) { this._signInputs[i] = 0; this._signOutputs[i] = outputs[_creatureBlueprint.getHardProcessorOutputs() + this._memoryUnits.length + this._soundInputs.length + i]; } // Hard coded outputs (actionable outputs) int maxActionOutput = 0; for (int i = 1; i < _creatureBlueprint.getHardProcessorOutputs(); i++) { if (outputs[i] > outputs[maxActionOutput]) { maxActionOutput = i; } } // Return the output conversion to action return (Action.convertIntegerToAction(maxActionOutput)); } } public Action getInvoluntaryAction() { // Forced actions // Old Creatures Die if (this._age > _creatureBlueprint.getMaximumAge() || this._food < 1 || this._health < 1) { return (Action.DIE); } // Pregnant Creatures can give birth else if (this._gestation > _creatureBlueprint.getMaximumGestation()) { return (Action.BIRTH); } // If there are no involuntary actions, return null return (null); } public Direction getFacing() { return (this._facing); } public double getRandomSeed() { return (this._randomSeed); } public void setIsFemale(boolean isFemale) { this._gender = isFemale ? Gender.FEMALE : Gender.MALE; } public boolean getIsFemale() { return (this._gender == Gender.FEMALE); } public Creature getFetus() { return (_fetus); } public double[] getInputs() { return _inputs; } public Processor[] getProcessors() { return (_processors); } public double[] getMemoryUnits() { return (_memoryUnits); } public double[] getSoundOutputs() { return (_soundOutputs); } public double[] getSignOutputs() { return (_signOutputs); } public void executeAction(Action action) { this._hasActed = true; executeAction(action, null); } public void executeAction(Action action, Creature target) { _wasSuccessful = true; switch (action) { case BIRTH: this._gestation = 0; break; case BREED: assert target != null; if (this._gender == Gender.FEMALE && this._gestation < 1) { this._gestation = 1; this._fetus = createOffspringWith(target); } this._food += _creatureBlueprint.getBreedingFoodRate(); break; case MOVE: this._food += _creatureBlueprint.getMovingFoodRate(); break; case REST: case DIE: // change nothing break; case TURN_LEFT: this._facing = Direction.stepCounterclockwise(this._facing); break; case TURN_RIGHT: this._facing = Direction.stepClockwise(this._facing); break; case EAT: this._food += _creatureBlueprint.getEatingFoodRate(); if (this._food > _creatureBlueprint.getMaximumFood()) { this._food = _creatureBlueprint.getMaximumFood(); } break; case FIGHT: assert target != null; target._health += _creatureBlueprint.getFightingDamageAmount(); this._food += _creatureBlueprint.getFightingFoodRate(); break; default: System.err.println("Non-Fatal Error, unhandled action"); new Error().printStackTrace(); break; } } public void failAction(Action action) { _wasSuccessful = false; switch (action) { case BIRTH: // If gestation has been reached but the birth failed, // let the Creature have a round of freedom before trying to // give birth again this._gestation -= 2; break; case BREED: // Trying to breed for no good reason is still tiring this._food += _creatureBlueprint.getBreedingFoodRate(); break; case EAT: // Trying to eat nothing and failing costs nothing (this has been changed because the file format // doesn't have a variable for this cost currently. This cost could be reintroduced later) break; case MOVE: this._food += _creatureBlueprint.getMovingFoodRate(); break; case DIE: break; case REST: case TURN_LEFT: case TURN_RIGHT: System.err.println("Action class " + action + " should not fail"); break; case FIGHT: this._food += _creatureBlueprint.getFightingFoodRate(); break; default: System.err.println("Non-Fatal Error, unhandled action"); new Error().printStackTrace(); break; } } private Creature createOffspringWith(Creature breedingTarget) { return new Creature(this, breedingTarget); } public int getID() { return (this._id); } public CreatureBlueprint getBlueprint() { return this._creatureBlueprint; } public double getGeneration() { return this._generation; } public int getAge() { return (this._age); } public int getGestation() { return (this._gestation); } public int getFood() { return (this._food); } public int getHealth() { return (this._health); } public CreatureBlueprint getCreatureBlueprint() { return _creatureBlueprint; } public void setGeneration(double generation) { this._generation = generation; } public void setRandomSeed(double randomSeed) { this._randomSeed = randomSeed; } public void setAge(int age) { this._age = age; } public void setGestation(int gestation) { this._gestation = gestation; } public void setFood(int food) { this._food = food; } public void setFacing(Direction facing) { this._facing = facing; } public void setFetus(Creature fetus) { this._fetus = fetus; } /* * Returns true if the creature has already acted during this simulation tick. */ public boolean hasActed() { return this._hasActed; } public boolean wasSuccessful() { return this._wasSuccessful; } public void setID(int id) { this._id = id; } @Override public void finalizeSerialization() { // Do nothing } }