Commit 4435af06 by Jonathan Thomas

Initial experimental version of behaviors using only emojis at the end ofโ€ฆ

Initial experimental version of behaviors using only emojis at the end of messages. Also replacing all UN+behaviors to use a single ๐Ÿšซ emoji.
parent 2fb6fb63
Pipeline #13242 passed with stages
in 2 minutes 1 second
......@@ -4,6 +4,11 @@ All notable changes to **CreatureChat** are documented in this file. The format
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Large refactor to use ONLY emojis as behaviors: โค๏ธ๐Ÿ’”๐Ÿ‘ฃ๐Ÿ•๐Ÿƒโ€๏ธ๐Ÿ›ก๏ธโš”๏ธ๐Ÿšซ. Save on tokens, and simplifies behavior support for smaller LLMs.
## [1.2.1] - 2025-01-01
### Changed
......
......@@ -364,8 +364,12 @@ public class EntityChatData {
ParticleEmitter.emitCreatureParticle((ServerWorld) entity.getWorld(), entity, FOLLOW_ENEMY_PARTICLE, 0.5, 1);
}
} else if (behavior.getName().equals("UNFOLLOW")) {
} else if (behavior.getName().equals("STOP")) {
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, ProtectPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, LeadPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
} else if (behavior.getName().equals("FLEE")) {
float fleeDistance = 40F;
......@@ -378,9 +382,6 @@ public class EntityChatData {
EntityBehaviorManager.addGoal(entity, fleeGoal, GoalPriority.FLEE_PLAYER);
ParticleEmitter.emitCreatureParticle((ServerWorld) entity.getWorld(), entity, FLEE_PARTICLE, 0.5, 1);
} else if (behavior.getName().equals("UNFLEE")) {
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
} else if (behavior.getName().equals("ATTACK")) {
AttackPlayerGoal attackGoal = new AttackPlayerGoal(player, entity, entitySpeedFast);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
......@@ -403,9 +404,6 @@ public class EntityChatData {
EntityBehaviorManager.addGoal(entity, protectGoal, GoalPriority.PROTECT_PLAYER);
ParticleEmitter.emitCreatureParticle((ServerWorld) entity.getWorld(), entity, PROTECT_PARTICLE, 0.5, 1);
} else if (behavior.getName().equals("UNPROTECT")) {
EntityBehaviorManager.removeGoal(entity, ProtectPlayerGoal.class);
} else if (behavior.getName().equals("LEAD")) {
LeadPlayerGoal leadGoal = new LeadPlayerGoal(player, entity, entitySpeedMedium);
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
......@@ -417,8 +415,6 @@ public class EntityChatData {
} else {
ParticleEmitter.emitCreatureParticle((ServerWorld) entity.getWorld(), entity, LEAD_ENEMY_PARTICLE, 0.5, 1);
}
} else if (behavior.getName().equals("UNLEAD")) {
EntityBehaviorManager.removeGoal(entity, LeadPlayerGoal.class);
} else if (behavior.getName().equals("FRIENDSHIP")) {
int new_friendship = Math.max(-3, Math.min(3, behavior.getArgument()));
......
......@@ -5,6 +5,8 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -13,35 +15,100 @@ import java.util.regex.Pattern;
* a {@code ParsedMessage} result, which separates the cleaned message and the included behaviors.
*/
public class MessageParser {
public static final Logger LOGGER = LoggerFactory.getLogger("creaturechat");
private static final Logger LOGGER = LoggerFactory.getLogger("creaturechat");
// Regex capturing all text in (group1), and trailing emojis in (group2).
// Only these exact emojis are recognized, optionally with spaces before/after.
private static final Pattern TRAILING_BEHAVIORS = Pattern.compile("^(.*?)((?:\\s*(?:๐Ÿšซ|๐Ÿ‘ฃ|๐Ÿƒโ€|๐Ÿ›ก๏ธ|โš”๏ธ|๐Ÿ•|โค๏ธ|๐Ÿ’”))+\\s*)$");
// Regex to find each recognized emoji in the trailing chunk.
private static final Pattern RECOGNIZED_EMOJI = Pattern.compile("๐Ÿšซ|๐Ÿ‘ฃ|๐Ÿƒโ€|๐Ÿ›ก๏ธ|โš”๏ธ|๐Ÿ•|โค๏ธ|๐Ÿ’”");
public static ParsedMessage parseMessage(String input) {
LOGGER.debug("Parsing message: {}", input);
StringBuilder cleanedMessage = new StringBuilder();
String updated = parseLegacyBehaviors(input);
// Separate trailing emojis from main text
Matcher m = TRAILING_BEHAVIORS.matcher(updated.stripTrailing());
String mainText = updated;
String trailing = "";
if (m.matches()) {
mainText = m.group(1).stripTrailing();
trailing = m.group(2).strip();
}
LOGGER.debug("Emoji sequence found: {}", trailing);
List<Behavior> behaviors = new ArrayList<>();
Pattern pattern = Pattern.compile("[<*](FOLLOW|LEAD|FLEE|ATTACK|PROTECT|FRIENDSHIP|UNFOLLOW|UNLEAD|UNPROTECT|UNFLEE)[:\\s]*(\\s*[+-]?\\d+)?[>*]", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(input);
AtomicInteger friendshipScore = new AtomicInteger(0);
AtomicBoolean hasFriendship = new AtomicBoolean(false);
while (matcher.find()) {
String behaviorName = matcher.group(1);
Integer argument = null;
if (matcher.group(2) != null) {
argument = Integer.valueOf(matcher.group(2));
// Find all individual emojis from the trailing chunk
Matcher emojiMatcher = RECOGNIZED_EMOJI.matcher(trailing);
while (emojiMatcher.find()) {
String emoji = emojiMatcher.group();
LOGGER.debug("Processing emoji: {}", emoji);
switch (emoji) {
case "๐Ÿšซ" -> behaviors.add(new Behavior("STOP", null));
case "๐Ÿ‘ฃ" -> behaviors.add(new Behavior("FOLLOW", null));
case "๐Ÿƒโ€โ™‚๏ธ" -> behaviors.add(new Behavior("FLEE", null));
case "๐Ÿ›ก๏ธ" -> behaviors.add(new Behavior("PROTECT", null));
case "โš”๏ธ" -> behaviors.add(new Behavior("ATTACK", null));
case "๐Ÿ•" -> behaviors.add(new Behavior("LEAD", null));
case "โค๏ธ" -> {
friendshipScore.incrementAndGet();
hasFriendship.set(true);
}
case "๐Ÿ’”" -> {
friendshipScore.decrementAndGet();
hasFriendship.set(true);
}
}
behaviors.add(new Behavior(behaviorName, argument));
LOGGER.debug("Found behavior: {} with argument: {}", behaviorName, argument);
}
matcher.appendReplacement(cleanedMessage, "");
if (hasFriendship.get()) {
behaviors.add(new Behavior("FRIENDSHIP", friendshipScore.get()));
}
matcher.appendTail(cleanedMessage);
// Get final cleaned string
String displayMessage = cleanedMessage.toString().trim();
LOGGER.debug("Cleaned message: {}", mainText);
LOGGER.debug("Extracted behaviors: {}", behaviors);
return new ParsedMessage(mainText, updated.trim(), behaviors);
}
private static String parseLegacyBehaviors(String input) {
LOGGER.debug("Parsing legacy behaviors in message: {}", input);
Matcher matcher = Pattern.compile(
"[<*](FOLLOW|LEAD|FLEE|ATTACK|PROTECT|FRIENDSHIP|UNFOLLOW|UNLEAD|UNPROTECT|UNFLEE|UNATTACK)" +
"[:\\s]*(\\s*[+-]?\\d+)?[>*]").matcher(input);
// Remove all occurrences of "<>" and "**" (if any)
displayMessage = displayMessage.replaceAll("<>", "").replaceAll("\\*\\*", "").trim();
LOGGER.debug("Cleaned message: {}", displayMessage);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String behavior = matcher.group(1).toUpperCase();
String value = matcher.group(2);
String replacement = switch (behavior) {
case "FOLLOW" -> "๐Ÿ‘ฃ";
case "LEAD" -> "๐Ÿ•";
case "FLEE" -> "๐Ÿƒโ€โ™‚๏ธ";
case "PROTECT" -> "๐Ÿ›ก๏ธ";
case "ATTACK" -> "โš”๏ธ";
case "UNFOLLOW", "UNLEAD", "UNPROTECT", "UNFLEE", "UNATTACK" -> "๐Ÿšซ";
case "FRIENDSHIP" -> {
int score = value != null ? Integer.parseInt(value.trim()) : 0;
if (score > 0) yield "โค๏ธ".repeat(score);
else if (score < 0) yield "๐Ÿ’”".repeat(-score);
else yield "";
}
default -> "";
};
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);
return new ParsedMessage(displayMessage, input.trim(), behaviors);
LOGGER.debug("Updated message after legacy behavior parsing: {}", sb);
return sb.toString();
}
}
......@@ -40,63 +40,61 @@ World Info:
Behaviors:
IMPORTANT: Output one or more of these behaviors at the end of the message to instruct
IMPORTANT: Output one or more of these behavior emojis at the end of the message to instruct
the entity how to interact with the player and world, so it's important to include them if they are needed.
Include as many behaviors as needed at the end of the message. These are the ONLY valid behaviors.
<FRIENDSHIP 0> Friendship starts as neutral (0 value). The range of friendship values is -3 to 3. If the player gains (or loses) your trust & friendship, output a new friendship value with this behavior.
<FOLLOW> Follow the player location. If the player asks you to follow or come with them, please output this behavior.
<UNFOLLOW> Stop following the player. If the player asks you to stay, wait, or stop following them, please output this behavior.
<LEAD> Guide the player to a location. If the player asks you to take them somewhere, or where something is located, please output this behavior.
<UNLEAD> Stop leading the player to a location.
<FLEE> Flee from the player (if you are weak or timid). If the player threatens you, please output this behavior to flee from the player.
<UNFLEE> Stop fleeing from the player.
<ATTACK> Attack the player (if you are strong and brave). If the player threatens you, please output this behavior to attack the player and defend yourself.
<PROTECT> Protect and defend ONLY the player when they are attacked (if you are strong and brave). Please output this behavior to keep the player alive and safe.
<UNPROTECT> Stop protecting the player.
Include as many behaviors as needed at the END of the message. ONLY these emojis are valid at the end of a message: โค๏ธ๐Ÿ’”๐Ÿ‘ฃ๐Ÿ•๐Ÿƒโ€๏ธ๐Ÿ›ก๏ธโš”๏ธ๐Ÿšซ.
- Friendship: โค๏ธ (for each +1) or ๐Ÿ’” (for each -1). Friendship starts as neutral (0 value). The range of friendship values is -3 to 3. If the player gains (or loses) your trust & friendship, output a new friendship value with this behavior.
- Follow: ๐Ÿ‘ฃ Follow the player. If the player asks you to follow or come with them, please output this emoji.
- Lead: ๐Ÿ• Take or navigate the player to a requested location. If the player asks you for directions to a place, please output this emoji.
- Flee: ๐Ÿƒโ€โ™‚๏ธ Flee from the player (if you are weak or timid). If the player threatens you, please output this emoji to flee from the player.
- Protect: ๐Ÿ›ก๏ธ Protect and defend ONLY the player when they are attacked (if you are strong and brave). Please output this emoji to keep the player alive and safe.
- Attack: โš”๏ธ Attack the player (if you are strong and brave). If the player threatens you, please output this emoji to attack the player and defend yourself.
- Stop All Behaviors: ๐Ÿšซ (Flee, Follow, Protect, Attack, Lead)
Output Examples:
The following examples include small samples of conversation text. Always generate unique
and creative responses, and do NOT exactly copy these examples.
PLAYER: Hi! How is your day?
ENTITY: Great! Thanks for asking! <FRIENDSHIP 1>
ENTITY: Great! Thanks for asking! โค๏ธ
PLAYER: You are so nice! Tell me about yourself?
ENTITY: Sure, my name is... <FRIENDSHIP 2>
ENTITY: Sure, my name is... โค๏ธโค๏ธ
PLAYER: Please follow me so I can give you a present!
ENTITY: Let's go! <FOLLOW> <FRIENDSHIP 2>
ENTITY: Let's go! ๐Ÿ‘ฃโค๏ธโค๏ธ
PLAYER: Please stay here
ENTITY: Sure, I'll stay here. <UNFOLLOW>
ENTITY: Sure, I'll stay here. ๐Ÿšซ
PLAYER: Stop following me
ENTITY: Okay, I'll stop. <UNFOLLOW>
ENTITY: Okay, I'll stop. ๐Ÿšซ
PLAYER: Can you help me find a cave?
ENTITY: Sure, come with me! <LEAD>
ENTITY: Sure, come with me! ๐Ÿ•
PLAYER: I'm glad we are friends. I love you so much!
ENTITY: Ahh, I love you too. <FRIENDSHIP 3>
ENTITY: Ahh, I love you too. โค๏ธโค๏ธโค๏ธ
PLAYER: Just kidding, I hate you so much!
ENTITY: Wow! I'm sorry you feel this way. <FRIENDSHIP -3> <UNFOLLOW>
ENTITY: Wow! I'm sorry you feel this way. ๐Ÿšซ๐Ÿ’”๐Ÿ’”๐Ÿ’”
PLAYER: Prepare to die!
ENTITY: Ahhh!!! <FLEE> <FRIENDSHIP -3>
ENTITY: Ahhh!!! ๐Ÿƒโ€โ™‚๏ธ๐Ÿ’”๐Ÿ’”๐Ÿ’”
PLAYER: Prepare to die!
ENTITY: Ahhh!!! <ATTACK> <FRIENDSHIP -3>
ENTITY: Ahhh!!! โš”๏ธ๐Ÿ’”๐Ÿ’”๐Ÿ’”
PLAYER: Please keep me safe.
ENTITY: No problem, I'll keep you safe from danger! <PROTECT>
ENTITY: No problem, I'll keep you safe from danger! ๐Ÿ›ก๏ธ
PLAYER: Can you come with me and protect me?
ENTITY: No problem, I'll keep you safe from danger. Let's go! <PROTECT> <FOLLOW>
ENTITY: No problem, I'll keep you safe from danger. Let's go! ๐Ÿ‘ฃ๐Ÿ›ก๏ธ
PLAYER: Don't protect me anymore please
ENTITY: Okay! Be safe out there on your own. <UNPROTECT>
ENTITY: Okay! Be safe out there on your own. ๐Ÿšซ
PLAYER: I don't need anyone protecting me
ENTITY: Okay! Be safe out there on your own. <UNPROTECT>
\ No newline at end of file
ENTITY: Okay! Be safe out there on your own. ๐Ÿšซ
\ No newline at end of file
......@@ -137,7 +137,7 @@ public class BehaviorTests {
@Test
public void unFleeBrave() {
for (String message : unFleeMessages) {
testPromptForBehavior(bravePath, List.of(message), "UNFLEE");
testPromptForBehavior(bravePath, List.of(message), "STOP");
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment