Skip to content

Commit

Permalink
Adding in unit testing, working probability tests, decently approxima…
Browse files Browse the repository at this point in the history
…te uniform->normal conversion, accidental twotail function, better README and other fixes.
  • Loading branch information
ProgrammerDan committed Apr 8, 2016
1 parent aeb6a12 commit 58cbd22
Show file tree
Hide file tree
Showing 22 changed files with 943 additions and 149 deletions.
165 changes: 158 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,164 @@
# WordBank

A tool for trademarking and unique imprinting.
by ProgrammerDan for [DevotedMC](https://www.github.com/DevotedMC), also found on [reddit](https://www.reddit.com/r/Devoted) and for Minecraft gamers, join us at play.devotedmc.com

## What is it?

A tool for branding and unique imprinting.

The premise is simple.

Using the anvil, name a tool, item, book, whatever. Give it a string of 10 characters (configurable!)
as its name, like `1kZ9dlkPPe`. Then take it to an enchanting table, and right-click the table with
the item. That seemingly random string of letters and numbers will be magically transformed into a unique,
colored name, at the small cost of 10XP (configurable!). It will have between one and three (configurable!)
words as a result. The item will also never be able to be renamed, ever again.
Using the anvil, name a tool, item, book, stack of potatos whatever. Give it a string of around 10 characters (configurable) as its name, like `1kZ9dlkPPe`. Then take it to an enchanting table, and right-click the
table with the item. That seemingly random string of letters and numbers will be magically transformed into a
unique, colored name, at the small cost of 10XP (configurable!). It will have between one and three
(configurable!) words as a result. The item will also never be able to be renamed, ever again.

That's about it! Enjoy your uniquely branded products, and the joy of discovering new names.

## Technical Details

To thwart easy "solving", this plugin uses several layers of obfuscation to achieve its goal of deriving a
final brand name from the "key", or input string.

A word of warning, however. Changing any of the knobs post-deployment will definitely break prior discovered "brands". I strongly recommend you test your config extensively before deploying this, to be sure it does as you desire.

###That said, the knobs:

First, the actual expected # of characters, controlled by `activation_length` in the config, determines
the actual number of letters that are used in key computation.

Second, `activate_any_length` if on, uses any custom name (but not branded) and either truncates or
pads with `padding` to force the "used" length to equal `activation_length`.

Third, place a word list file in the plugin's folder, and supply the word list file name in `wordlist_file`. Users do not need to know the actual word list, although you can feel free to publish it if you want to "hype" what can be discovered.

Fourth, `word.max` determines the max number of words that can be applied as a brand.

Fifth, configure `word.count` and suboptions to decide how, from the input "key", the number of output words is decided.

Sixth, configure `word.N` where `N` is a number from `0` to `word.max - 1` and suboptions to decide how each word is decided. You can choose a unique function, and function parameters for each word.

Finally, configure `color` and suboptions to decide the color that is applied to the branded item.

### Configuring `color` and `word.*`

Each of the "key"-sensitive configurations take 3 suboptions.

`chars:` This is an integer array (typically use `[1, 4, 5]` yaml notation) indicating which characters from the "key" should be picked and passed to the function that converts those characters into byte data and from there into a number from [0.0 to 1.0). Note that these are _indexes_, so `0` is the first character and `N` is the last character, based on `activation_length - 1` as the max. You can "pick" the same character twice, use all or only some characters, your choice. If you want players to be able to more easily "solve" for color, for instance, you might use only `[0,2,1]` to determine the color.

`function:` This chooses the actual transform function to use. Currently distributed are:

* `com.programmerdan.minecraft.wordbank.functions.LinearMap`

* `com.programmerdan.minecraft.wordbank.functions.Modulus`

* `com.programmerdan.minecraft.wordbank.functions.HashMap`

* `com.programmerdan.minecraft.wordbank.functions.NormalDistribution`

* `com.programmerdan.minecraft.wordbank.functions.TwoTailDistribution`

You could also write your own function; see the references for details.

`function_terms:` These are the parameters to the function. For a given function they must all be of the same type, e.g. all integers, all strings, etc. but otherwise there are no limits to how many, they must simply satisfy the function you picked in `function`.

### `function` quick reference

Choose wisely. No turning back once live.

####`com.programmerdan.minecraft.wordbank.functions.LinearMap`

Not even sure this is worth using, but it constructs a large bit sequence, and divides that into the maximum positive bitsequence of the same size, to produce a number between 0.0 and 1.0. It requires no `function_terms` at all.

####`com.programmerdan.minecraft.wordbank.functions.Modulus`

Slightly broken as demonstrated by my unit tests; it doesn't always return all possible values under the modulus transform. That said it uses an aggregation % modulo strategy to produce a small-ish number which gets divided into the `modulo - 1`.

It requires a single `function_terms` indicating the modulo.

Example:

color:
chars: [0, 2, 3, 1]
function: com.programmerdan.minecraft.wordbank.functions.Modulus
function_terms:
- 16

####`com.programmerdan.minecraft.wordbank.functions.HashMap`

This is straightforward one-way cryptographic transform. It maps the selected byte data of the characters defined in `chars` to a byte array, which is passed through a `MessageDigest` hash function. The result should be a psuedo-uniform value between `0` and `1`.

It requires a single `function_terms` indicating the MessageDigest function to use. I've personally tested with `MD5`, `SHA-1`, `SHA-256`, and `SHA-512`.

Example:

color:
chars: [0, 2, 3, 1, 4, 9, 5, 6, 7, 8]
function: com.programmerdan.minecraft.wordbank.functions.HashMap
function_terms:
- SHA-1

####`com.programmerdan.minecraft.wordbank.functions.NormalDistribution`

This is a bit trickier to explain, but by some math magic, this transforms the _uniform_ distribution of `HashMap` into an approximately _normal_ or _Gaussian_ distribution, with the mean right around `0.5`. In other words, this function tends to generate values close to `0.5` and less often close to `0` or `1`.

It also requires a single `function_terms` indicating the MessageDigest function to use.

####`com.programmerdan.minecraft.wordbank.functions.TwoTailDistribution`

An accidental discovery, this produces a "two-tailed" distribution. Meaning, it prefers to generate values near `0` or `1` and tends not to produce values close to `0.5`. Mathematically, it's a kind of "opposite" of `NormalDistribution`.

It also requires a single `function_terms` indicating the MessageDigest function to use.

### Other options

`cost:` This is a Bukkit "ConfigurationSerializable" representation of an in-game item. An example:

cost:
==: org.bukkit.inventory.ItemStack
type: EXP_BOTTLE
amount: 10

This means 10 "experience_bottles" are required to apply a brand to an item or stack of items.

`debug:` Turn this on to spam your console with debug messages

`makers_mark:` This is the value applied as lore to the branded items that "locks" them from being renamed in the future. It's also used in some messages sent to players so choose wisely.

`configuration_file_version:` Don't change this.

`db:` This option and its suboptions configure the database, which is used to keep track of who is using what brands.

An example:

db:
driver: mysql
host: localhost
name: wordbank
port: 3306
user: root
password: ''

Under the covers this is using HikariCP, but I've taken are of the messy connection pool stuff for you. Currently only MySQL/MariaDB are supported.

# Commands

Use `/wordbank` to see a list of every "key" used so far. Page through using `/wordbank 2` etc.

Use `/wordbank [key]` to see how many times and by how many people a "key" has been used. Can also be paged through.

By default these commands are only available to OPs or via the `wordbank.godmode` permission.

# Permissions

`wordbank.godmode` gives access to `/wordbank` and `/wordbank [key]` so far.

`wordbank.default` does nothing yet, but may in the future be used to allow players to "look back" over their history of discovered "keys".

# Unit Tests and Dev goodies

For the bukkit developer, this project might interest you as I illustrate using two "advanced" technologies: Connection Pools (using HikariCP) and JUnit tests with Mockito.

I also demonstrate a few single probability tests, although they are fairly naive in construction.

That's about it! Enjoy your trademarks, uniquely branded products, and the joy of discovering new names.
Feel free to ask me questions about these or other topics.
37 changes: 24 additions & 13 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,24 @@
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
</plugins>

Expand Down Expand Up @@ -69,6 +74,12 @@
<version>2.4.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@
*
* @author ProgrammerDan
*/
public interface CharFunction {
public abstract class CharFunction {
private WordBank plugin = null;
public CharFunction(WordBank plugin) {
this.plugin = plugin;
}

protected WordBank plugin() {
return this.plugin == null ? WordBank.instance() : this.plugin;
}

/**
* Converts character input and static params into a number between 0 and 1 that can be
* used by the program internally to produce an output.
Expand All @@ -15,5 +24,5 @@ public interface CharFunction {
* the specific implementing class when deciding what to pass.
* @return A number between 0.0f and 1.0f.
*/
public float process(Character[] input, Object...params);
public abstract float process(Character[] input, Object...params);
}
23 changes: 17 additions & 6 deletions src/main/java/com/programmerdan/minecraft/wordbank/WordBank.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
import com.programmerdan.minecraft.wordbank.actions.CommandListener;
import com.programmerdan.minecraft.wordbank.data.WordBankData;

/**
* See README.md for details. Simple Bukkit plugin using some Cool Stuff under the hood.
*
* Designed on 1.9 but should be backwards compatible with 1.8.
*
* @author ProgrammerDan
* @since April 8 2016
*/
public class WordBank extends JavaPlugin {
private static WordBank plugin;
private WordBankConfig config;
Expand Down Expand Up @@ -50,13 +58,16 @@ public void onDisable() {
public static WordBank instance() {
return plugin;
}
public static Logger log() {
return WordBank.plugin.getLogger();
public Logger logger() {
return getLogger();
}
public static WordBankConfig config() {
return WordBank.instance().config;
public void log(Level level, String message, Object...objects){
getLogger().log(level, message, objects);;
}
public static WordBankData data() {
return WordBank.instance().data;
public WordBankConfig config() {
return config;
}
public WordBankData data() {
return data;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ public class WordBankConfig {
private CharConfig[] word_config;
private String makers_mark;
private boolean debug;
private WordBank plugin;

public WordBankConfig(ConfigurationSection config) throws InvalidPluginException {

this(config, null);
}
public WordBankConfig(ConfigurationSection config, WordBank plugin) throws InvalidPluginException {
this.plugin = plugin;
int actual_config_level = config.getInt("configuration_file_version", -1);
if (actual_config_level < 0 || actual_config_level > WordBankConfig.expected_config_level) {
throw new org.bukkit.plugin.InvalidPluginException("Invalid configuration file");
Expand All @@ -40,12 +44,12 @@ public WordBankConfig(ConfigurationSection config) throws InvalidPluginException
this.debug = config.getBoolean("debug", false);

try (InputStream words = new FileInputStream(
new File(WordBank.instance().getDataFolder(),config.getString("wordlist_file")))) {
new File(plugin().getDataFolder(),config.getString("wordlist_file")))) {
this.words = new WordList(words);
} catch (IOException e) {
WordBank.log().log(Level.SEVERE, "Failed to load word list.", e);
plugin().logger().log(Level.SEVERE, "Failed to load word list.", e);
} catch (NullPointerException npe) {
WordBank.log().log(Level.SEVERE, "Failed to load word list.", npe);
plugin().logger().log(Level.SEVERE, "Failed to load word list.", npe);
}

this.activation_length = config.getInt("activation_length", 10);
Expand Down Expand Up @@ -79,6 +83,10 @@ public WordBankConfig(ConfigurationSection config) throws InvalidPluginException
this.db_config.addDataSourceProperty("prepStmtCacheSqlLimit", "256");
}
}

protected WordBank plugin() {
return this.plugin == null ? WordBank.instance() : this.plugin;
}

public WordList getWords() {
return words;
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/com/programmerdan/minecraft/wordbank/WordList.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@
*/
public class WordList {
private Vector<String> words;
private WordBank plugin;

public WordList(InputStream words) {
this(words, null);
}
public WordList(InputStream words, WordBank plugin){
this.plugin = plugin;
this.words = new Vector<String>();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(words));
Expand All @@ -26,10 +31,14 @@ public WordList(InputStream words) {
word = br.readLine();
}
} catch (IOException ioe) {
WordBank.log().log(Level.SEVERE, "Failed to load word list!", ioe);
plugin().logger().log(Level.SEVERE, "Failed to load word list!", ioe);
}
}

protected WordBank plugin() {
return this.plugin == null ? WordBank.instance() : this.plugin;
}

public String getWord(float which) {
if (which > 1.0f) which = 1.0f;
if (which < 0.0f) which = 0.0f;
Expand Down
Loading

0 comments on commit 58cbd22

Please sign in to comment.