/*
* Copyright (C) 2010 Markus Echterhoff <tam@edu.uni-klu.ac.at>
*
* This file is part of EvoPaint.
*
* EvoPaint is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EvoPaint. If not, see <http://www.gnu.org/licenses/>.
*/
package evopaint.pixel.rulebased.actions;
import evopaint.Configuration;
import evopaint.gui.rulesetmanager.util.DimensionsListener;
import evopaint.gui.util.AutoSelectOnFocusSpinner;
import evopaint.interfaces.IRandomNumberGenerator;
import evopaint.pixel.ColorDimensions;
import evopaint.pixel.rulebased.Action;
import evopaint.pixel.rulebased.RuleBasedPixel;
import evopaint.pixel.rulebased.targeting.ActionMetaTarget;
import evopaint.util.mapping.AbsoluteCoordinate;
import evopaint.util.mapping.RelativeCoordinate;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
*
* @author Markus Echterhoff <tam@edu.uni-klu.ac.at>
*/
public class PartnerProcreationAction extends Action {
private int partnerEnergyChange;
private ColorDimensions dimensions;
private float ourShare;
private boolean mixRules;
public PartnerProcreationAction(int energyChange, int partnerEnergyChange, ActionMetaTarget partner, ColorDimensions dimensions, float ourShare, boolean mixRuleSet) {
super(energyChange, partner);
this.partnerEnergyChange = partnerEnergyChange;
this.dimensions = dimensions;
this.ourShare = ourShare;
this.mixRules = mixRuleSet;
}
public PartnerProcreationAction() {
this.dimensions = new ColorDimensions(true, true, true);
ourShare = 0.5f;
this.mixRules = true;
}
public PartnerProcreationAction(PartnerProcreationAction partnerProcreationAction) {
super(partnerProcreationAction);
this.dimensions = partnerProcreationAction.dimensions;
this.ourShare = partnerProcreationAction.ourShare;
this.mixRules = partnerProcreationAction.mixRules;
}
public int getType() {
return Action.PARTNER_PROCREATION;
}
@Override
public int countGenes() {
// partner energy change and mix rules are not genes, but options
return super.countGenes() + dimensions.countGenes() + 1;
}
@Override
public void mutate(int mutatedGene, IRandomNumberGenerator rng) {
int numGenesSuper = super.countGenes();
if (mutatedGene < numGenesSuper) {
super.mutate(mutatedGene, rng);
return;
}
mutatedGene -= numGenesSuper;
int numGenesDimensions = dimensions.countGenes();
if (mutatedGene < numGenesDimensions) {
dimensions = new ColorDimensions(dimensions);
dimensions.mutate(mutatedGene, rng);
return;
}
mutatedGene -= numGenesDimensions;
if (mutatedGene == 0) {
ourShare = rng.nextFloat();
return;
}
assert false; // we have an error in our mutatedGene calculation
}
@Override
public void mixWith(Action theirAction, float theirShare, IRandomNumberGenerator rng) {
super.mixWith(theirAction, theirShare, rng);
PartnerProcreationAction a = (PartnerProcreationAction)theirAction;
dimensions = new ColorDimensions(dimensions);
dimensions.mixWith(a.dimensions, theirShare, rng);
if (rng.nextFloat() < theirShare) {
partnerEnergyChange = a.partnerEnergyChange;
}
if (rng.nextFloat() < theirShare) {
ourShare = a.ourShare;
}
}
public ColorDimensions getDimensions() {
return dimensions;
}
public void setDimensions(ColorDimensions dimensionsToMix) {
this.dimensions = dimensionsToMix;
}
public float getOurShare() {
return ourShare;
}
public void setOurShare(float ourShare) {
this.ourShare = ourShare;
}
public String getName() {
return "procreate with partner";
}
public int execute(RuleBasedPixel actor, RelativeCoordinate direction, Configuration configuration) {
RuleBasedPixel partner = configuration.world.get(actor.getLocation(), direction);
if (partner == null) {
return 0;
}
if (partner.getEnergy() + partnerEnergyChange < 0) {
return 0;
}
AbsoluteCoordinate randomFreeSpot =
configuration.world.getRandomFreeNeighborCoordinateOf(
actor.getLocation(), configuration.rng);
if (randomFreeSpot == null) {
return 0;
}
RuleBasedPixel newPixel = null;
if (mixRules) {
newPixel = new RuleBasedPixel(actor);
newPixel.mixWith(partner, 1 - ourShare, configuration.rng);
if (configuration.rng.nextDouble() <= configuration.mutationRate) {
newPixel.mutate(configuration);
}
}
else {
newPixel = new RuleBasedPixel(actor, actor.getRules());
newPixel.getPixelColor().mixWith(partner.getPixelColor(), 1 - ourShare, dimensions);
if (configuration.rng.nextDouble() <= configuration.mutationRate) {
newPixel.getPixelColor().mutate(configuration.rng);
}
}
newPixel.setLocation(randomFreeSpot);
configuration.world.set(newPixel);
partner.changeEnergy(partnerEnergyChange);
return energyChange;
}
@Override
public Map<String, String>addParametersString(Map<String, String> parametersMap) {
parametersMap = super.addParametersString(parametersMap);
if (partnerEnergyChange > 0) {
parametersMap.put("partner's reward", Integer.toString(partnerEnergyChange));
}
else if (partnerEnergyChange < 0) {
parametersMap.put("partner's cost", Integer.toString((-1) * partnerEnergyChange));
}
parametersMap.put("dimensions", dimensions.toString());
parametersMap.put("our share in %", Integer.toString(Math.round(ourShare * 100)));
parametersMap.put("mode", "color " + (mixRules ? "and rules" : "only"));
return parametersMap;
}
@Override
public Map<String, String>addParametersHTML(Map<String, String> parametersMap) {
parametersMap = super.addParametersHTML(parametersMap);
if (partnerEnergyChange > 0) {
parametersMap.put("partner's reward", Integer.toString(partnerEnergyChange));
}
else if (partnerEnergyChange < 0) {
parametersMap.put("partner's cost", Integer.toString((-1) * partnerEnergyChange));
}
parametersMap.put("dimensions", dimensions.toHTML());
parametersMap.put("our share in %", Integer.toString(Math.round(ourShare * 100)));
parametersMap.put("mode", "color " + (mixRules ? "and rules" : "only"));
return parametersMap;
}
@Override
public LinkedHashMap<String,JComponent> addParametersGUI(LinkedHashMap<String, JComponent> parametersMap) {
parametersMap = super.addParametersGUI(parametersMap);
SpinnerNumberModel partnerEnergyModel = new SpinnerNumberModel(partnerEnergyChange, Integer.MIN_VALUE, Integer.MAX_VALUE, 1);
JSpinner partnerEnergyChangeSpinner = new AutoSelectOnFocusSpinner(partnerEnergyModel);
partnerEnergyChangeSpinner.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
partnerEnergyChange = (Integer) ((JSpinner) e.getSource()).getValue();
}
});
parametersMap.put("Partner's Energy Change", partnerEnergyChangeSpinner);
JPanel dimensionsPanel = new JPanel();
JToggleButton btnH = new JToggleButton("H");
JToggleButton btnS = new JToggleButton("S");
JToggleButton btnB = new JToggleButton("B");
DimensionsListener dimensionsListener = new DimensionsListener(dimensions, btnH, btnS, btnB);
btnH.addActionListener(dimensionsListener);
btnS.addActionListener(dimensionsListener);
btnB.addActionListener(dimensionsListener);
if (dimensions.hue) {
btnH.setSelected(true);
}
if (dimensions.saturation) {
btnS.setSelected(true);
}
if (dimensions.brightness) {
btnB.setSelected(true);
}
dimensionsPanel.add(btnH);
dimensionsPanel.add(btnS);
dimensionsPanel.add(btnB);
parametersMap.put("Dimensions", dimensionsPanel);
SpinnerNumberModel ourSharePercentSpinnerModel =
new SpinnerNumberModel(Math.round(ourShare * 100), 0, 100, 1);
JSpinner ourSharePercentSpinner =
new AutoSelectOnFocusSpinner(ourSharePercentSpinnerModel);
ourSharePercentSpinner.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
ourShare =
((Integer)((JSpinner)e.getSource()).getValue()).floatValue() / 100;
}
});
parametersMap.put("Our share in %", ourSharePercentSpinner);
final JCheckBox mixRuleSetCheckBox = new JCheckBox();
mixRuleSetCheckBox.setSelected(mixRules);
mixRuleSetCheckBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
mixRules = mixRuleSetCheckBox.isSelected();
}
});
parametersMap.put("Mix rules", mixRuleSetCheckBox);
return parametersMap;
}
}