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 com.softwaremagico.tm.InvalidXmlElementException;
import com.softwaremagico.tm.character.CharacterPlayer;
import com.softwaremagico.tm.character.characteristics.CharacteristicName;
import com.softwaremagico.tm.character.exceptions.RestrictedElementException;
import com.softwaremagico.tm.character.exceptions.UnofficialElementNotAllowedException;
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;
import java.util.*;
import java.util.Map.Entry;
public abstract class RandomSelector> {
protected static final int MAX_PROBABILITY = 1000000;
protected static final int TERRIBLE_PROBABILITY = -20;
protected static final int DIFFICULT_PROBABILITY = -10;
protected static final int BAD_PROBABILITY = -5;
protected static final int PENALIZED_PROBABILITY = -1;
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 VERY_GOOD_PROBABILITY = 31;
protected static final int BASIC_MULTIPLIER = 5;
protected static final int HIGH_MULTIPLIER = 10;
public static final Random random = new Random();
private final CharacterPlayer characterPlayer;
private final Set> preferences;
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, RestrictedElementException, UnofficialElementNotAllowedException {
this(characterPlayer, null, preferences, new HashSet<>(), new HashSet<>());
}
protected RandomSelector(CharacterPlayer characterPlayer,
IElementWithRandomElements elementWithRandomElements, Set> preferences,
Set mandatoryValues, Set suggestedElements) throws InvalidXmlElementException,
RestrictedElementException, UnofficialElementNotAllowedException {
this.characterPlayer = characterPlayer;
this.preferences = preferences;
this.suggestedElements = suggestedElements;
this.mandatoryValues = mandatoryValues;
this.elementWithRandomElements = elementWithRandomElements;
updateWeights();
assignMandatoryValues(mandatoryValues);
assignMandatory();
}
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, UnofficialElementNotAllowedException;
/**
* 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, RestrictedElementException,
UnofficialElementNotAllowedException;
/**
* 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, RestrictedElementException;
private void assignMandatory() throws InvalidXmlElementException {
final List shuffledList = new ArrayList<>(getAllElements());
Collections.shuffle(shuffledList);
for (final Element element : shuffledList) {
try {
assignIfMandatory(element);
} catch (ImpossibleToAssignMandatoryElementException | RestrictedElementException 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 {
//Restricted elements has 0 weight.
if (element.isRestricted(characterPlayer)) {
return 0;
}
if (characterPlayer != null && characterPlayer.getSettings().isOnlyOfficialAllowed() && !element.isOfficial()) {
return 0;
}
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 *= GOOD_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 multiplier 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_MULTIPLIER;
}
// 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_MULTIPLIER;
}
// 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_MULTIPLIER;
}
// Probability definition by preference.
if (randomDefinition.getProbability() != null) {
multiplier *= randomDefinition.getProbability().getProbabilityMultiplier();
RandomGenerationLog.debug(this.getClass().getName(), "Random definition defines with bonus probability of '"
+ randomDefinition.getProbability().getProbabilityMultiplier() + "'.");
}
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.");
}
if (!element.getRestrictedToRaces().isEmpty() && !element.getRestrictedToRaces().contains(getCharacterPlayer().getRace())) {
throw new InvalidRandomElementSelectedException("Element '" + element + "' is restricted to '" +
element.getRestrictedToRaces() + "'.");
}
if (element.getRestrictedToFactionGroup() != null && (getCharacterPlayer().getFaction() == null ||
!Objects.equals(element.getRestrictedToFactionGroup(), getCharacterPlayer().getFaction().getFactionGroup()))) {
throw new InvalidRandomElementSelectedException("Element '" + element + "' is restricted to '" +
element.getRestrictedToFactionGroup() + "'.");
}
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().getCharacteristicValue(CharacteristicName.TECH)) {
throw new InvalidRandomElementSelectedException("The tech level of the character is insufficient.");
}
if (getCharacterPlayer() != null && randomDefinition.getMaximumTechLevel() != null && randomDefinition
.getMaximumTechLevel() < getCharacterPlayer().getCharacteristicValue(CharacteristicName.TECH)) {
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() + "'.");
}
// Faction groups restriction.
if (getCharacterPlayer() != null && getCharacterPlayer().getFaction() != null
&& !randomDefinition.getRestrictedFactionGroups().isEmpty()
&& !randomDefinition.getRestrictedFactionGroups().contains(getCharacterPlayer().getFaction().getFactionGroup())) {
throw new InvalidRandomElementSelectedException(
"Element restricted to factions groups '" + randomDefinition.getRestrictedFactionGroups() + "'.");
}
}
/**
* Assign a weight to an element depending on the preferences selected.
*
* @param 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 = random.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