package io.vivarium.visualizer; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.SelectBox; import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.utils.viewport.ScreenViewport; import com.google.common.collect.Lists; import io.vivarium.core.Action; import io.vivarium.core.BubbleWorld; import io.vivarium.core.BubbleWorldBlueprint; import io.vivarium.core.Creature; import io.vivarium.core.CreatureBlueprint; import io.vivarium.core.bubble.BubblePosition; import io.vivarium.core.processor.NeuralNetworkBlueprint; import io.vivarium.core.processor.Processor; import io.vivarium.visualizer.enums.CreatureRenderMode; import io.vivarium.visualizer.enums.MouseClickMode; import io.vivarium.visualizer.enums.SimulationSpeed; public class EmojiVivarium extends ApplicationAdapter implements InputProcessor { private static final double SIZE = 30; private static final int RENDER_BLOCK_SIZE = 32; private static final int SOURCE_BLOCK_SIZE = 512; // Simulation information private BubbleWorldBlueprint _bubbleWorldBlueprint; private BubbleWorld _bubbleWorld; // Simulation + Animation private int framesSinceTick = 0; private boolean _enableInterpolation = false; private Map<Integer, BubbleCreatureDelegate> _animationCreatureDelegates = new HashMap<>(); private Creature _selectedCreature; // Low Level Graphics information private SpriteBatch _batch; private Texture _img; // Graphical settings private CreatureRenderMode _creatureRenderMode = CreatureRenderMode.GENDER; private int _ticks = 1; private int _overFrames = 1; private MouseClickMode _mouseClickMode = MouseClickMode.SELECT_CREATURE; // High Level Graphics information private Stage stage; private Skin skin; private Label fpsLabel; private Label populationLabel; private Label generationLabel; private Label foodSupplyLabel; private Label breedingCostLabel; private Label creatureIdLabel; private Label creatureAgeLabel; private Label creatureFoodLabel; private Label creatureGestationLabel; private Label creatureHealthLabel; // Input tracking private int _xDownWorld = -1; private int _yDownWorld = -1; public EmojiVivarium() { } @Override public void create() { // Create simulation _bubbleWorldBlueprint = BubbleWorldBlueprint.makeDefault(); CreatureBlueprint creatureBlueprint = CreatureBlueprint.makeDefault(0, 0, 0); NeuralNetworkBlueprint nnBlueprint = (NeuralNetworkBlueprint) creatureBlueprint.getProcessorBlueprints()[0]; // nnBlueprint.setRandomInitializationProportion(1); nnBlueprint.setMutationRateExponent(-6); nnBlueprint.setNormalizeAfterMutation(0); _bubbleWorldBlueprint.setCreatureBlueprints(Lists.newArrayList(creatureBlueprint)); // _blueprint.setSignEnabled(true); _bubbleWorldBlueprint.setSize(SIZE); _bubbleWorld = new BubbleWorld(_bubbleWorldBlueprint); // Start with selected creature List<Creature> creatures = _bubbleWorld.getCreatures(); if (creatures.size() > 41) { _selectedCreature = creatures.get(41); } // Setup Input Listeners Gdx.input.setInputProcessor(this); // Low level grahpics _batch = new SpriteBatch(); _img = new Texture("emoji_sprites.png"); buildSidebarUI(); } private void buildSidebarUI() { skin = new Skin(Gdx.files.internal("data/uiskin.json")); // stage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false, new PolygonSpriteBatch()); stage = new Stage(new ScreenViewport()); // Gdx.input.setInputProcessor(stage); // Render Mode final Label renderModeLabel = new Label("Render Mode: ", skin); final SelectBox<String> renderModeSelectBox = new SelectBox<>(skin); renderModeSelectBox.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { _creatureRenderMode = CreatureRenderMode.valueOf(renderModeSelectBox.getSelected()); } }); String[] creatureRenderModeStrings = new String[CreatureRenderMode.values().length]; for (int i = 0; i < CreatureRenderMode.values().length; i++) { creatureRenderModeStrings[i] = CreatureRenderMode.values()[i].toString(); } renderModeSelectBox.setItems(creatureRenderModeStrings); renderModeSelectBox.setSelected(_creatureRenderMode.toString()); // Click Mode final Label clickModeLabel = new Label("Click Mode: ", skin); final SelectBox<String> clickModeSelectBox = new SelectBox<>(skin); clickModeSelectBox.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { _mouseClickMode = MouseClickMode.valueOf(clickModeSelectBox.getSelected()); _xDownWorld = -1; _yDownWorld = -1; } }); String[] clickModeStrings = new String[MouseClickMode.values().length]; for (int i = 0; i < MouseClickMode.values().length; i++) { clickModeStrings[i] = MouseClickMode.values()[i].toString(); } clickModeSelectBox.setItems(clickModeStrings); clickModeSelectBox.setSelected(_mouseClickMode.toString()); // Simulation speed final Label simulationSpeedLabel = new Label("Simulation Speed: ", skin); final SelectBox<String> simulationSpeedSelectBox = new SelectBox<>(skin); simulationSpeedSelectBox.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { SimulationSpeed simulationSpeed = SimulationSpeed.valueOf(simulationSpeedSelectBox.getSelected()); _ticks = simulationSpeed.getTicks(); _overFrames = simulationSpeed.getPerFrame(); _enableInterpolation = simulationSpeed.getEnableInterpolation(); } }); String[] simulationSpeedStrings = new String[SimulationSpeed.values().length]; for (int i = 0; i < SimulationSpeed.values().length; i++) { simulationSpeedStrings[i] = SimulationSpeed.values()[i].toString(); } simulationSpeedSelectBox.setItems(simulationSpeedStrings); simulationSpeedSelectBox.setSelected(SimulationSpeed.getDefault().toString()); // FPS Display fpsLabel = new Label("fps:", skin); // World Stats populationLabel = new Label("population:", skin); generationLabel = new Label("generation:", skin); foodSupplyLabel = new Label("food:", skin); breedingCostLabel = new Label("breeding cost:", skin); // Layout Table worldTable = new Table(); worldTable.setPosition(200, getHeight() - 150); worldTable.add(renderModeLabel).colspan(2); worldTable.add(renderModeSelectBox).maxWidth(100); worldTable.row(); worldTable.add(clickModeLabel).colspan(2); worldTable.add(clickModeSelectBox).maxWidth(100); worldTable.row(); worldTable.add(simulationSpeedLabel).colspan(2); worldTable.add(simulationSpeedSelectBox).maxWidth(100); worldTable.row(); worldTable.add(fpsLabel).colspan(4); worldTable.row(); worldTable.add(populationLabel).colspan(4); worldTable.row(); worldTable.add(generationLabel).colspan(4); worldTable.row(); worldTable.add(foodSupplyLabel).colspan(4); worldTable.row(); worldTable.add(breedingCostLabel).colspan(4); stage.addActor(worldTable); // Creature Stats creatureIdLabel = new Label("creature id:", skin); creatureAgeLabel = new Label("age:", skin); creatureFoodLabel = new Label("food:", skin); creatureGestationLabel = new Label("gestation:", skin); creatureHealthLabel = new Label("health:", skin); Table creatureTable = new Table(); creatureTable.setPosition(200, getHeight() - 500); creatureTable.add(creatureIdLabel).colspan(4); creatureTable.row(); creatureTable.add(creatureAgeLabel).colspan(4); creatureTable.row(); creatureTable.add(creatureFoodLabel).colspan(4); creatureTable.row(); creatureTable.add(creatureGestationLabel).colspan(4); creatureTable.row(); creatureTable.add(creatureHealthLabel).colspan(4); stage.addActor(creatureTable); } @Override public void render() { Gdx.gl.glClearColor(0, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); _batch.begin(); _batch.setColor(Color.WHITE); float interpolationFraction = _enableInterpolation ? (float) framesSinceTick / _overFrames : 1; drawTerrain(interpolationFraction); drawFood(); drawCreatures(interpolationFraction); _batch.end(); setLabels(); drawCreatureMultiplexer(); stage.act(Gdx.graphics.getDeltaTime()); stage.draw(); framesSinceTick++; if (framesSinceTick >= _overFrames) { for (int i = 0; i < _ticks; i++) { _bubbleWorld.tick(); updateCreatureDelegates(); } framesSinceTick = 0; } mouseBrush(); } private void drawCreatureMultiplexer() { if (_selectedCreature != null) { ShapeRenderer sr = new ShapeRenderer(); sr.begin(ShapeType.Filled); for (int j = 0; j < _selectedCreature.getInputs().length; j++) { drawMultiplexerCell(sr, 0, j, (float) _selectedCreature.getInputs()[j]); } for (int i = 0; i < _selectedCreature.getProcessors().length; i++) { Processor p = _selectedCreature.getProcessors()[i]; for (int j = 0; j < p.outputs().length; j++) { drawMultiplexerCell(sr, i + 1, j, (float) p.outputs()[j]); } } sr.end(); } } private void drawMultiplexerCell(ShapeRenderer sr, int x, int y, float color) { sr.setColor(Color.WHITE); sr.rect(50 + x * 10, 500 - y * 10, 9, 9); sr.setColor(color, color, color, 1); sr.rect(51 + x * 10, 501 - y * 10, 7, 7); } private void mouseBrush() { if (this._xDownWorld > -1 && this._yDownWorld > -1) { if (this._mouseClickMode.isPaintbrushMode()) { applyMouseBrush(_xDownWorld, _yDownWorld); } } } private void applyMouseBrush(int x, int y) { // TODO: Re-implement } private void setLabels() { fpsLabel.setText("fps: " + Gdx.graphics.getFramesPerSecond()); populationLabel.setText("population: " + _bubbleWorld.getCreatureCount()); List<Creature> creatures = _bubbleWorld.getCreatures(); double generation = 0; for (Creature creature : creatures) { generation += creature.getGeneration(); } generation /= creatures.size(); generationLabel.setText("generation: " + ((int) (generation * 100) / 100.0)); foodSupplyLabel.setText("food: " + _bubbleWorld.getItemCount()); breedingCostLabel.setText("breeding cost: " + (-1 * _bubbleWorld.getWorldBlueprint().getCreatureBlueprints().get(0).getBreedingFoodRate())); if (_selectedCreature != null) { creatureIdLabel.setText("creature id: " + _selectedCreature.getID()); creatureAgeLabel.setText("age: " + _selectedCreature.getAge()); creatureFoodLabel.setText("food: " + _selectedCreature.getFood()); creatureGestationLabel.setText("gestation: " + _selectedCreature.getGestation()); creatureHealthLabel.setText("health: " + _selectedCreature.getHealth()); } } private void drawSprite(EmojiVivariumSprite sprite, float xPos, float yPos, float angle, float scale) { float x = (float) (SIZE / 2 * RENDER_BLOCK_SIZE + xPos * RENDER_BLOCK_SIZE); float y = getHeight() - yPos * RENDER_BLOCK_SIZE - RENDER_BLOCK_SIZE; float originX = RENDER_BLOCK_SIZE / 2; float originY = RENDER_BLOCK_SIZE / 2; float width = RENDER_BLOCK_SIZE; float height = RENDER_BLOCK_SIZE; float rotation = angle; // In degrees int srcX = sprite.x * SOURCE_BLOCK_SIZE; int srcY = sprite.y * SOURCE_BLOCK_SIZE; int srcW = SOURCE_BLOCK_SIZE; int srcH = SOURCE_BLOCK_SIZE; boolean flipX = false; boolean flipY = false; _batch.draw(_img, x, y, originX, originY, width, height, scale, scale, rotation, srcX, srcY, srcW, srcH, flipX, flipY); } private void drawTerrain(float interpolationFraction) { // TODO: THIS } private void drawFood() { // TODO: THIS } private void drawCreatures(float interpolationFraction) { for (BubbleCreatureDelegate delegate : _animationCreatureDelegates.values()) { drawCreature(delegate, interpolationFraction); } } private void drawCreature(BubbleCreatureDelegate delegate, float interpolationFraction) { Creature creature = delegate.getCreature(); switch (_creatureRenderMode) { case GENDER: setColorOnGenderAndPregnancy(delegate, interpolationFraction); break; case HEALTH: setColorOnHealth(creature); break; case AGE: setColorOnAge(creature); break; case HUNGER: setColorOnFood(creature); break; case MEMORY: setColorOnMemory(creature); break; case SIGN: setColorOnSignLanguage(creature); break; } float scale = delegate.getScale(interpolationFraction); drawSprite(EmojiVivariumSprite.CREATURE_1, delegate.getX(interpolationFraction), delegate.getY(interpolationFraction), delegate.getHeading(interpolationFraction), scale); if (creature == _selectedCreature) { _batch.setColor(Color.WHITE); drawSprite(EmojiVivariumSprite.HALO_CREATURE_1, delegate.getX(interpolationFraction), delegate.getY(interpolationFraction), delegate.getHeading(interpolationFraction), scale); } } private void updateCreatureDelegates() { // Find creatures that are dying and mark them as such. These creatures will no longer // be present in the world, but it's expensive to check this. Fortunately, a creature // will use the die action as its last act in the world. Any existing delegate // with a creature that has used dye either needs to animate its death, or has // already done so. Set<Integer> creatureIDsToRemove = new HashSet<>(); for (Entry<Integer, BubbleCreatureDelegate> delegatePair : _animationCreatureDelegates.entrySet()) { if (delegatePair.getValue().isDying()) { creatureIDsToRemove.add(delegatePair.getKey()); } else if (delegatePair.getValue().getCreature().getAction() == Action.DIE) { delegatePair.getValue().die(); } } // Remove creatures that have finished dying from the animation delegates. for (Integer creatureID : creatureIDsToRemove) { _animationCreatureDelegates.remove(creatureID); } // For all other creatures, update their animation delegate, or add a new one if they // don't have an animation delegate yet. List<Creature> creatures = _bubbleWorld.getCreatures(); List<BubblePosition> creaturePositions = _bubbleWorld.getCreaturePositions(); for (int i = 0; i < creatures.size(); i++) { Creature creature = creatures.get(i); BubblePosition creaturePosition = creaturePositions.get(i); if (_animationCreatureDelegates.containsKey(creature.getID())) { _animationCreatureDelegates.get(creature.getID()).updateSnapshot(); } else { _animationCreatureDelegates.put(creature.getID(), new BubbleCreatureDelegate(creature, creaturePosition)); } } } public void setColorOnGenderAndPregnancy(BubbleCreatureDelegate delegate, float interpolationFraction) { if (delegate.getCreature().getIsFemale()) { float pregnancyFraction = delegate.getPregnancy(interpolationFraction); float red = (0.4f - 0) * pregnancyFraction + 0.0f; float green = (0 - 0.8f) * pregnancyFraction + 0.8f; float blue = (0.4f - 0.8f) * pregnancyFraction + 0.8f; _batch.setColor(new Color(red, green, blue, 1)); } else { _batch.setColor(new Color(0.8f, 0, 0, 1)); } } public void setColorOnFood(Creature creature) { float food = ((float) creature.getFood()) / creature.getBlueprint().getMaximumFood(); _batch.setColor(new Color(1, food, food, 1)); } public void setColorOnHealth(Creature creature) { float health = ((float) creature.getHealth()) / creature.getBlueprint().getMaximumHealth(); _batch.setColor(new Color(1, health, health, 1)); } public void setColorOnAge(Creature creature) { float age = ((float) creature.getAge()) / creature.getBlueprint().getMaximumAge(); _batch.setColor(new Color(age, 1, age, 1)); } public void setColorOnMemory(Creature creature) { double[] memories = creature.getMemoryUnits(); float[] displayMemories = { 1, 1, 1 }; for (int i = 0; i < memories.length && i < displayMemories.length; i++) { displayMemories[i] = (float) memories[i]; } _batch.setColor(new Color(displayMemories[0], displayMemories[1], displayMemories[2], 1)); } public void setColorOnSignLanguage(Creature creature) { double[] signs = creature.getSignOutputs(); float[] displaySigns = { 1, 1, 1 }; for (int i = 0; i < signs.length && i < displaySigns.length; i++) { displaySigns[i] = (float) signs[i]; } _batch.setColor(new Color(displaySigns[0], displaySigns[1], displaySigns[2], 1)); } public static int getHeight() { return (int) (SIZE * RENDER_BLOCK_SIZE); } public static int getWidth() { return (int) (SIZE * RENDER_BLOCK_SIZE); } @Override public boolean keyDown(int keycode) { stage.keyDown(keycode); return false; } @Override public boolean keyUp(int keycode) { stage.keyUp(keycode); return false; } @Override public boolean keyTyped(char character) { stage.keyTyped(character); return false; } @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { stage.touchDown(screenX, screenY, pointer, button); // TODO: THIS return true; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { stage.touchUp(screenX, screenY, pointer, button); // TODO: THIS return true; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { stage.touchDragged(screenX, screenY, pointer); // TODO: THIS return false; } @Override public boolean mouseMoved(int screenX, int screenY) { stage.mouseMoved(screenX, screenY); return false; } @Override public boolean scrolled(int amount) { stage.scrolled(amount); return false; } }