Bleeding: Add global word to ActionFreqeuncy map, factor to analysis

package.
This commit is contained in:
asofold 2012-09-02 08:01:19 +02:00
parent 1936a2f158
commit 386a70eda4
9 changed files with 273 additions and 107 deletions

View File

@ -26,7 +26,7 @@ public class ActionList {
/** This is a very bad design decision, but it's also really convenient to define this here. */ /** This is a very bad design decision, but it's also really convenient to define this here. */
public final String permissionSilent; public final String permissionSilent;
/** If there are no actions registered, we still return an Array. It's just empty/size=0. */ /** If there are no actions registered, we still return an Array. It's just empty/maxSize=0. */
private final static Action[] emptyArray = new Action[0]; private final static Action[] emptyArray = new Action[0];
/** The actions of this ActionList, "bundled" by treshold (violation level). */ /** The actions of this ActionList, "bundled" by treshold (violation level). */

View File

@ -4,7 +4,9 @@ import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.checks.Check; import fr.neatmonster.nocheatplus.checks.Check;
import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.chat.MessageLetterCount.WordLetterCount; import fr.neatmonster.nocheatplus.checks.chat.analysis.LetterEngine;
import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount;
import fr.neatmonster.nocheatplus.checks.chat.analysis.WordLetterCount;
import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager; import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
/** /**
@ -14,6 +16,8 @@ import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
*/ */
public class GlobalChat extends Check{ public class GlobalChat extends Check{
private final LetterEngine engine = new LetterEngine();
public GlobalChat() { public GlobalChat() {
super(CheckType.CHAT_GLOBALCHAT); super(CheckType.CHAT_GLOBALCHAT);
} }
@ -79,6 +83,8 @@ public class GlobalChat extends Check{
// (Following: random/made up criteria.) // (Following: random/made up criteria.)
// TODO: Create tests for all methods with wordlists, fake chat (refactor for that).
// Full message processing. ------------ // Full message processing. ------------
// Upper case. // Upper case.
@ -128,7 +134,9 @@ public class GlobalChat extends Check{
wWords /= (float) letterCounts.words.length; wWords /= (float) letterCounts.words.length;
score += wWords; score += wWords;
// TODO: LetterEngine ? + core checks // Engine:
final float wEngine = engine.feed(letterCounts);
score += wEngine;
// Wrapping it up. -------------------- // Wrapping it up. --------------------
// Add weight to frequency counts. // Add weight to frequency counts.

View File

@ -1,104 +0,0 @@
package fr.neatmonster.nocheatplus.checks.chat;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Letter count for a message and sub words, including upper case count.<br>
* NOTE: this is a pretty heavy implementation for testing purposes.
* @author mc_dev
*
*/
public class MessageLetterCount {
/**
* Letter count for a word.
* @author mc_dev
*
*/
public static final class WordLetterCount{
public final String word;
public final Map<Character, Integer> counts;
public final int upperCase;
public final int notLetter;
public WordLetterCount(final String word){
this.word = word;
char[] a = word.toCharArray();
// Preserve insertion order.
counts = new LinkedHashMap<Character, Integer>(a.length);
int upperCase = 0;
int notLetter = 0;
for (int i = 0; i < a.length; i++){
final char c = a[i];
final Character key;
if (!Character.isLetter(c)) notLetter ++;
if (Character.isUpperCase(c)){
upperCase ++;
key = Character.toLowerCase(c);
}
else key = c;
final Integer count = counts.remove(key);
if (count == null) counts.put(key, 1);
else counts.put(key, count.intValue() + 1);
}
this.notLetter = notLetter;
this.upperCase = upperCase;
}
public float getNotLetterRatio(){
return (float) notLetter / (float) word.length();
}
public float getLetterCountRatio(){
return (float) counts.size() / (float) word.length();
}
public float getUpperCaseRatio(){
return (float) upperCase / (float) word.length();
}
}
public final String message;
public final String split;
public final WordLetterCount[] words;
public final WordLetterCount fullCount;
/**
* Constructor for splitting by a space.
* @param message
*/
public MessageLetterCount(final String message){
this(message, " ");
}
/**
*
* @param message
* @param split
*/
public MessageLetterCount(final String message, final String split){
this.message = message;
this.split = split;
final String[] parts = message.split(split);
words = new WordLetterCount[parts.length];
fullCount = new WordLetterCount(message);
// (Do not store 60 times "a".)
final Map<String, WordLetterCount> done = new HashMap<String, MessageLetterCount.WordLetterCount>(words.length);
for (int i = 0; i < parts.length; i++){
final String word = parts[i];
if (done.containsKey(word)){
words[i] = done.get(word);
continue;
}
done.put(word, words[i] = new WordLetterCount(word));
}
done.clear();
}
}

View File

@ -0,0 +1,64 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public abstract class AbstractWordProcessor implements WordProcessor{
/**
* Remove a number of entries from a map, in order of iteration over a key set.
* @param map
* @param number Number of entries to remove.
*/
public static final <K> void releaseMap(final Map<K, ?> map, int number){
final List<K> rem = new LinkedList<K>();
int i = 0;
for (final K key : map.keySet()){
rem.add(key);
i++;
if (i > number) break;
}
for (final K key : rem){
map.remove(key);
}
}
protected String name;
public AbstractWordProcessor(String name){
this.name = name;
}
@Override
public String getProcessorName(){
return name;
}
@Override
public float process(final MessageLetterCount message) {
// Does the looping, scores are summed up and divided by number of words.
start(message);
final long ts = System.currentTimeMillis();
float score = 0;
for (int index = 0; index < message.words.length; index++){
final WordLetterCount word = message.words[index];
final String key = word.word.toLowerCase();
score += loop(ts, index, key, word);
}
score /= (float) message.words.length;
// System.out.println(getProcessorName() +": " + score);
return score;
}
public void start(final MessageLetterCount message){
// Override if needed.
}
/**
* Process one word.
* @param index
* @param message
* @return Score.
*/
public abstract float loop(final long ts, final int index, final String key, final WordLetterCount word);
}

View File

@ -0,0 +1,47 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis;
import java.util.LinkedHashMap;
import fr.neatmonster.nocheatplus.utilities.ActionFrequency;
/**
* Words mapped to ActionFrequency queues.
* @author mc_dev
*
*/
public class FlatWordBuckets extends AbstractWordProcessor{
final int maxSize;
final LinkedHashMap<String, ActionFrequency> entries;
final long durBucket;
final int nBuckets;
final float factor;
public FlatWordBuckets(int maxSize, int nBuckets, long durBucket, float factor){
super("FlatWordBuckets");
this.maxSize = maxSize;
entries = new LinkedHashMap<String, ActionFrequency>(maxSize);
this.nBuckets = nBuckets;
this.durBucket = durBucket;
this.factor = factor;
}
@Override
public void start(MessageLetterCount message) {
if (entries.size() + message.words.length > maxSize)
releaseMap(entries, maxSize / 10);
}
@Override
public float loop(long ts, int index, String key,
WordLetterCount message) {
ActionFrequency freq = entries.get(key);
if (freq == null){
freq = new ActionFrequency(nBuckets, durBucket);
entries.put(key, freq);
return 0.0f;
}
freq.update(ts);
float score = Math.min(1.0f, freq.getScore(factor));
freq.add(ts, 1.0f);
return score;
}
}

View File

@ -0,0 +1,31 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis;
import java.util.ArrayList;
import java.util.List;
/**
* Process words.
* @author mc_dev
*
*/
public class LetterEngine {
protected final List<WordProcessor> processors = new ArrayList<WordProcessor>();
public LetterEngine(){
// Add word processors.
processors.add(new FlatWordBuckets(1000, 4, 1500, 0.9f));
}
public float feed(final MessageLetterCount letterCount){
float score = 0;
// Run all processors.
for (final WordProcessor processor : processors){
final float refScore = processor.process(letterCount);
// TODO: max or sum or average or flexible (?)...
score = Math.max(score, refScore);
}
return score;
}
}

View File

@ -0,0 +1,56 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis;
import java.util.HashMap;
import java.util.Map;
/**
* Letter count for a message and sub words, including upper case count.<br>
* NOTE: this is a pretty heavy implementation for testing purposes.
* @author mc_dev
*
*/
public class MessageLetterCount {
public final String message;
public final String split;
public final WordLetterCount[] words;
public final WordLetterCount fullCount;
/**
* Constructor for splitting by a space.
* @param message
*/
public MessageLetterCount(final String message){
this(message, " ");
}
/**
*
* @param message
* @param split
*/
public MessageLetterCount(final String message, final String split){
this.message = message;
this.split = split;
final String[] parts = message.split(split);
words = new WordLetterCount[parts.length];
fullCount = new WordLetterCount(message);
// (Do not store 60 times "a".)
final Map<String, WordLetterCount> done = new HashMap<String, WordLetterCount>(words.length);
for (int i = 0; i < parts.length; i++){
final String word = parts[i];
if (done.containsKey(word)){
words[i] = done.get(word);
continue;
}
done.put(word, words[i] = new WordLetterCount(word));
}
done.clear();
}
}

View File

@ -0,0 +1,52 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Letter count for a word.
* @author mc_dev
*
*/
public final class WordLetterCount{
public final String word;
public final Map<Character, Integer> counts;
public final int upperCase;
public final int notLetter;
public WordLetterCount(final String word){
this.word = word;
char[] a = word.toCharArray();
// Preserve insertion order.
counts = new LinkedHashMap<Character, Integer>(a.length);
int upperCase = 0;
int notLetter = 0;
for (int i = 0; i < a.length; i++){
final char c = a[i];
final Character key;
if (!Character.isLetter(c)) notLetter ++;
if (Character.isUpperCase(c)){
upperCase ++;
key = Character.toLowerCase(c);
}
else key = c;
final Integer count = counts.remove(key);
if (count == null) counts.put(key, 1);
else counts.put(key, count.intValue() + 1);
}
this.notLetter = notLetter;
this.upperCase = upperCase;
}
public float getNotLetterRatio(){
return (float) notLetter / (float) word.length();
}
public float getLetterCountRatio(){
return (float) counts.size() / (float) word.length();
}
public float getUpperCaseRatio(){
return (float) upperCase / (float) word.length();
}
}

View File

@ -0,0 +1,12 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis;
public interface WordProcessor{
public String getProcessorName();
/**
*
* @param message
* @return A number ranging from 0 to 1. 0 means no matching, 1 means high repetition score.
*/
public float process(MessageLetterCount message);
}