com.softwaremagico.tm.random.RandomSelector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of think-machine-random Show documentation
Show all versions of think-machine-random Show documentation
Think Machine - A Fading Suns character generator (random character creation)
package com.softwaremagico.tm.random;
/*-
* #%L
* Think Machine (Core)
* %%
* Copyright (C) 2017 - 2018 Softwaremagico
* %%
* This software is designed by Jorge Hortelano Otero. Jorge Hortelano Otero
* Valencia (Spain).
*
* This program 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 2 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
* this program; If not, see .
* #L%
*/
import java.util.Collection;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import com.softwaremagico.tm.InvalidXmlElementException;
import com.softwaremagico.tm.character.CharacterPlayer;
import com.softwaremagico.tm.character.characteristics.CharacteristicName;
import com.softwaremagico.tm.log.RandomGenerationLog;
import com.softwaremagico.tm.random.definition.RandomElementDefinition;
import com.softwaremagico.tm.random.exceptions.ImpossibleToAssignMandatoryElementException;
import com.softwaremagico.tm.random.exceptions.InvalidRandomElementSelectedException;
import com.softwaremagico.tm.random.selectors.IRandomPreference;
public abstract class RandomSelector> {
protected static final int MAX_PROBABILITY = 1000000;
protected static final int BAD_PROBABILITY = -20;
protected static final int DIFFICULT_PROBABILITY = -10;
protected static final int BASIC_PROBABILITY = 1;
protected static final int LITTLE_PROBABILITY = 6;
protected static final int FAIR_PROBABILITY = 11;
protected static final int GOOD_PROBABILITY = 21;
protected static final int BASIC_MULTIPLICATOR = 5;
protected static final int HIGH_MULTIPLICATOR = 10;
private CharacterPlayer characterPlayer;
private final Set preferences;
protected Random rand = new Random();
private final Set suggestedElements;
private final Set mandatoryValues;
// Weight -> Element.
private TreeMap weightedElements;
private int totalWeight;
private final IElementWithRandomElements elementWithRandomElements;
protected RandomSelector(CharacterPlayer characterPlayer, Set preferences)
throws InvalidXmlElementException {
this(characterPlayer, null, preferences, new HashSet(), new HashSet());
}
protected RandomSelector(CharacterPlayer characterPlayer,
IElementWithRandomElements elementWithRandomElements, Set preferences,
Set mandatoryValues, Set suggestedElements) throws InvalidXmlElementException {
this.characterPlayer = characterPlayer;
this.preferences = preferences;
this.suggestedElements = suggestedElements;
this.mandatoryValues = mandatoryValues;
this.elementWithRandomElements = elementWithRandomElements;
updateWeights();
assignMandatoryValues(mandatoryValues);
assignMandatories();
}
protected void updateWeights() throws InvalidXmlElementException {
weightedElements = assignElementsWeight();
totalWeight = assignTotalWeight();
}
private Integer assignTotalWeight() {
try {
return weightedElements.lastKey();
} catch (NoSuchElementException nse) {
return 0;
}
}
protected CharacterPlayer getCharacterPlayer() {
return characterPlayer;
}
protected Set getPreferences() {
if (preferences == null) {
return new HashSet();
}
return preferences;
}
protected abstract Collection getAllElements() throws InvalidXmlElementException;
public abstract void assign() throws InvalidXmlElementException, InvalidRandomElementSelectedException;
/**
* This mandatories values are defined by the user and must be assigned directly
* without random approach. This method uses a list of elements that must be
* directly assigned to the user.
*
* @param mandatoryValues set of elements to be assigned.
* @throws InvalidXmlElementException
*/
protected abstract void assignMandatoryValues(Set mandatoryValues) throws InvalidXmlElementException;
/**
* Must check if an element is mandatory, and if it is, it must be assigned.
* This method defines a set of property for any element, that if accomplished
* must be assigned.
*
* @param element element to check.
* @throws InvalidXmlElementException
* @throws ImpossibleToAssignMandatoryElementException
*/
protected abstract void assignIfMandatory(Element element)
throws InvalidXmlElementException, ImpossibleToAssignMandatoryElementException;
private void assignMandatories() throws InvalidXmlElementException {
for (final Element element : getAllElements()) {
try {
assignIfMandatory(element);
} catch (ImpossibleToAssignMandatoryElementException e) {
throw new InvalidXmlElementException("Mandatory element cannot be assigned.", e);
}
}
}
private TreeMap assignElementsWeight() throws InvalidXmlElementException {
final TreeMap weightedElements = new TreeMap<>();
int count = 1;
for (final Element element : getAllElements()) {
try {
validateElement(element);
} catch (InvalidRandomElementSelectedException e) {
// Element not valid. Ignore it.
continue;
}
final int weight = getTotalWeight(element);
if (weight > 0) {
weightedElements.put(count, element);
count += weight;
}
}
return weightedElements;
}
public int getTotalWeight(Element element) {
try {
int weight = getWeight(element);
weight = (int) ((weight) * getRandomDefinitionBonus(element));
if (weight > 0) {
// Some probabilities are defined directly.
if (element.getRandomDefinition().getStaticProbability() != null) {
weight = element.getRandomDefinition().getStaticProbability();
}
// Suggested ones.
if (suggestedElements != null && suggestedElements.contains(element)) {
weight *= FAIR_PROBABILITY;
}
}
return weight;
} catch (InvalidRandomElementSelectedException e) {
return 0;
}
}
private double getRandomDefinitionBonus(Element element) {
return getRandomDefinitionBonus(element.getRandomDefinition());
}
protected double getRandomDefinitionBonus(RandomElementDefinition randomDefinition) {
double multiplier = 1d;
if (randomDefinition == null) {
return multiplier;
}
if (randomDefinition.getProbabilityMultiplier() != null) {
RandomGenerationLog.debug(this.getClass().getName(),
"Random definition multiplicator is '" + randomDefinition.getProbabilityMultiplier() + "'.");
multiplier *= randomDefinition.getProbabilityMultiplier();
}
// Recommended to race.
if (getCharacterPlayer() != null && getCharacterPlayer().getRace() != null
&& randomDefinition.getRecommendedRaces().contains(getCharacterPlayer().getRace())) {
RandomGenerationLog.debug(this.getClass().getName(),
"Random definition as recommended for '" + getCharacterPlayer().getRace() + "'.");
multiplier *= BASIC_MULTIPLICATOR;
}
// Recommended to my faction group.
if (getCharacterPlayer() != null && getCharacterPlayer().getFaction() != null && randomDefinition
.getRecommendedFactionsGroups().contains(getCharacterPlayer().getFaction().getFactionGroup())) {
RandomGenerationLog.debug(this.getClass().getName(), "Random definition as recommended for '"
+ getCharacterPlayer().getFaction().getFactionGroup() + "'.");
multiplier *= BASIC_MULTIPLICATOR;
}
// Recommended to my faction.
if (getCharacterPlayer() != null && getCharacterPlayer().getFaction() != null
&& randomDefinition.getRecommendedFactions().contains(getCharacterPlayer().getFaction())) {
RandomGenerationLog.debug(this.getClass().getName(),
"Random definition as recommended for '" + getCharacterPlayer().getFaction() + "'.");
multiplier *= HIGH_MULTIPLICATOR;
}
// Probability definition by preference.
if (randomDefinition.getProbability() != null) {
multiplier *= randomDefinition.getProbability().getProbabilityMultiplicator();
RandomGenerationLog.debug(this.getClass().getName(), "Random definition defines with bonus probability of '"
+ randomDefinition.getProbability().getProbabilityMultiplicator() + "'.");
}
RandomGenerationLog.debug(this.getClass().getName(),
"Random definitions bonus multiplier is '" + multiplier + "'.");
return multiplier;
}
public void validateElement(Element element) throws InvalidRandomElementSelectedException {
if (element == null) {
throw new InvalidRandomElementSelectedException("Null elements not allowed.");
}
try {
validateElement(element.getRandomDefinition());
} catch (InvalidRandomElementSelectedException e) {
throw new InvalidRandomElementSelectedException("Invalid element '" + element + "'.", e);
}
}
public void validateElement(RandomElementDefinition randomDefinition) throws InvalidRandomElementSelectedException {
if (randomDefinition == null) {
return;
}
// Check technology limitations.
if (getCharacterPlayer() != null && randomDefinition.getMinimumTechLevel() != null && randomDefinition
.getMinimumTechLevel() > getCharacterPlayer().getCharacteristic(CharacteristicName.TECH).getValue()) {
throw new InvalidRandomElementSelectedException("The tech level of the character is insufficient.");
}
if (getCharacterPlayer() != null && randomDefinition.getMaximumTechLevel() != null && randomDefinition
.getMaximumTechLevel() < getCharacterPlayer().getCharacteristic(CharacteristicName.TECH).getValue()) {
throw new InvalidRandomElementSelectedException("The tech level of the character is too high.");
}
// Race limitation
if (getCharacterPlayer() != null && randomDefinition.getRestrictedRaces() != null
&& !randomDefinition.getRestrictedRaces().isEmpty()
&& !randomDefinition.getRestrictedRaces().contains(getCharacterPlayer().getRace())) {
throw new InvalidRandomElementSelectedException(
"Element restricted to races '" + randomDefinition.getRestrictedRaces() + "'.");
}
if (getCharacterPlayer() != null && randomDefinition.getForbiddenRaces() != null
&& randomDefinition.getForbiddenRaces().contains(getCharacterPlayer().getRace())) {
throw new InvalidRandomElementSelectedException(
"Element forbidden to races '" + randomDefinition.getForbiddenRaces() + "'.");
}
// Faction restriction.
if (getCharacterPlayer() != null && getCharacterPlayer().getFaction() != null
&& !randomDefinition.getRestrictedFactions().isEmpty()
&& !randomDefinition.getRestrictedFactions().contains(getCharacterPlayer().getFaction())) {
throw new InvalidRandomElementSelectedException(
"Element restricted to factions '" + randomDefinition.getRestrictedFactions() + "'.");
}
if (getCharacterPlayer() != null && getCharacterPlayer().getFaction() != null
&& !randomDefinition.getRecommendedFactionsGroups().isEmpty() && !randomDefinition
.getRecommendedFactionsGroups().contains(getCharacterPlayer().getFaction().getFactionGroup())) {
throw new InvalidRandomElementSelectedException(
"Element restricted to factions '" + randomDefinition.getRecommendedFactionsGroups() + "'.");
}
// Faction groups restriction.
if (getCharacterPlayer() != null && getCharacterPlayer().getFaction() != null
&& !randomDefinition.getRestrictedFactions().isEmpty()
&& (getCharacterPlayer().getFaction().getFactionGroup() == null || !randomDefinition
.getRestrictedFactionGroups().contains(getCharacterPlayer().getFaction().getFactionGroup()))) {
throw new InvalidRandomElementSelectedException(
"Element restricted to factions '" + randomDefinition.getRestrictedFactions() + "'.");
}
}
/**
* Assign a weight to an element depending on the preferences selected.
*
* @param Element element to get the weight
* @return weight as integer
*/
protected abstract int getWeight(Element element) throws InvalidRandomElementSelectedException;
/**
* Selects a characteristic depending on its weight.
*
* @throws InvalidRandomElementSelectedException
*/
protected Element selectElementByWeight() throws InvalidRandomElementSelectedException {
if (weightedElements == null || weightedElements.isEmpty() || totalWeight == 0) {
throw new InvalidRandomElementSelectedException("No elements to select");
}
final int value = rand.nextInt(totalWeight) + 1;
Element selectedElement = weightedElements.values().iterator().next();
final SortedMap view = weightedElements.headMap(value, true);
try {
selectedElement = view.get(view.lastKey());
} catch (NoSuchElementException nse) {
// If weight of first element is greater than 1, it is possible that
// the value is less that the first element weight. That means that
// 'view' would be empty launching a NoSuchElementException. Select
// the first one by default.
}
return selectedElement;
}
protected void removeElementWeight(Element element) {
Integer keyToDelete = null;
for (final Entry entry : weightedElements.entrySet()) {
if (entry.getValue().equals(element)) {
keyToDelete = entry.getKey();
break;
}
}
if (keyToDelete != null) {
final int weightToDelete = getAssignedWeight(weightedElements.get(keyToDelete));
// Remove desired element.
weightedElements.remove(keyToDelete);
final TreeMap elementsToUpdate = new TreeMap<>(weightedElements);
// Update keys weight
for (final Entry entry : elementsToUpdate.entrySet()) {
if (entry.getKey() >= keyToDelete) {
final int currentWeight = entry.getKey();
weightedElements.remove(entry.getKey());
weightedElements.put(currentWeight - weightToDelete, entry.getValue());
}
}
}
}
protected void updateWeight(Element element, int newWeight) {
removeElementWeight(element);
weightedElements.put(newWeight, element);
}
public TreeMap getWeightedElements() {
return weightedElements;
}
public Integer getAssignedWeight(Element element) {
if (element == null) {
return null;
}
int previousWeight = 0;
for (final Entry entry : weightedElements.entrySet()) {
if (entry.getValue().equals(element)) {
return entry.getKey() - previousWeight;
}
previousWeight = entry.getKey();
}
return null;
}
public boolean isMandatory(Element element) {
return mandatoryValues.contains(element);
}
public IElementWithRandomElements getElementWithRandomElements() {
return elementWithRandomElements;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy