initial commit!
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"rust-analyzer.check.command": "clippy",
|
||||
"editor.formatOnSave": true,
|
||||
"rust-analyzer.cargo.features": "all",
|
||||
"files.eol": "\n",
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"gitea.owner": "LowLevelDevs",
|
||||
"gitea.repo": "damn_simple_architecture"
|
||||
}
|
||||
Generated
+1677
File diff suppressed because it is too large
Load Diff
+10
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "vibeengine"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.12.22", features = ["blocking", "json"] }
|
||||
ron = "0.10.1"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
toml = "0.9.0"
|
||||
@@ -0,0 +1 @@
|
||||
data_dir = "./game"
|
||||
+2179
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,119 @@
|
||||
GameObject(
|
||||
imports: [
|
||||
"traits.ron",
|
||||
"relationships.ron",
|
||||
],
|
||||
|
||||
characters: [
|
||||
Entity (
|
||||
name: "generic villager",
|
||||
profession: None,
|
||||
location: None,
|
||||
|
||||
personality: Some((
|
||||
traits: [],
|
||||
values: [],
|
||||
fears: [],
|
||||
goals: [],
|
||||
)),
|
||||
|
||||
speech: Some((
|
||||
style: None,
|
||||
common_phrases: [],
|
||||
topics_of_interest: [],
|
||||
vocabulary_level: None,
|
||||
accent: None,
|
||||
)),
|
||||
|
||||
knowledge: Some((
|
||||
areas: [],
|
||||
secrets: [],
|
||||
skills: [],
|
||||
)),
|
||||
|
||||
relationships: {
|
||||
"player": (
|
||||
stages: [
|
||||
"relationships.neutral",
|
||||
"relationships.enemy",
|
||||
"relationships.friend",
|
||||
"relationships.ally",
|
||||
],
|
||||
default: "relationships.neutral",
|
||||
),
|
||||
},
|
||||
),
|
||||
Entity (
|
||||
name: "hammerick",
|
||||
profession: Some("blacksmith"),
|
||||
location: Some("village"),
|
||||
personality: Some((
|
||||
traits: [
|
||||
"traits.generous",
|
||||
"traits.curious",
|
||||
"traits.patient",
|
||||
],
|
||||
values: [
|
||||
"values.honesty",
|
||||
"values.community",
|
||||
"values.craftsmanship",
|
||||
],
|
||||
fears: [
|
||||
"fears.failure",
|
||||
"fears.public_shame",
|
||||
"fears.losing_loved_ones",
|
||||
],
|
||||
goals: [
|
||||
"goals.happy_family",
|
||||
"goals.successful_business",
|
||||
"goals.community_respect",
|
||||
],
|
||||
)),
|
||||
speech: Some((
|
||||
style: Some("friendly"),
|
||||
common_phrases: [
|
||||
"Ah, good on ya!",
|
||||
"Don't you worry 'bout that!",
|
||||
"By the fires of the forge!",
|
||||
],
|
||||
topics_of_interest: [
|
||||
"woodworking",
|
||||
"cooking",
|
||||
"village_gossip",
|
||||
"local_news",
|
||||
],
|
||||
vocabulary_level: Some("basic"),
|
||||
accent: Some("rural english"),
|
||||
)),
|
||||
knowledge: Some((
|
||||
areas: [
|
||||
"village_center",
|
||||
"blacksmith_shop",
|
||||
"forest_path",
|
||||
],
|
||||
secrets: [
|
||||
"secret_passage_location",
|
||||
"blacksmithing_secret",
|
||||
],
|
||||
skills: [
|
||||
"casting",
|
||||
"forging",
|
||||
"hammering",
|
||||
"woodworking",
|
||||
"cooking",
|
||||
],
|
||||
)),
|
||||
relationships: {
|
||||
"player": (
|
||||
stages: [
|
||||
"relationships.neutral",
|
||||
"relationships.enemy",
|
||||
"relationships.friend",
|
||||
"relationships.ally",
|
||||
],
|
||||
default: "relationships.neutral",
|
||||
),
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
use std::{collections::HashMap, fs};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn main() {
|
||||
let s = fs::read_to_string("Config.toml").expect("Config.toml not found");
|
||||
let config: Config = toml::from_str(&s).expect("Could not parse Config.toml");
|
||||
println!("Data directory: {}", config.data_dir);
|
||||
|
||||
// game is the first arg
|
||||
let filedir =
|
||||
config.data_dir + "/" + &std::env::args().nth(1).expect("No game specified") + "/main.ron";
|
||||
let s = fs::read_to_string(filedir).expect("Game file not found");
|
||||
let game: GameObject = ron::from_str(&s).expect("Could not parse game file");
|
||||
println!("Game: {game:#?}");
|
||||
|
||||
let character = game
|
||||
.characters
|
||||
.iter()
|
||||
.find(|entity| entity.name == "hammerick")
|
||||
.cloned()
|
||||
.expect("Character hammerick not found");
|
||||
|
||||
let mut history = HashMap::new();
|
||||
|
||||
if let Some(speech) = character.speech.as_ref() {
|
||||
println!(
|
||||
"{}",
|
||||
speech.generate(
|
||||
&character,
|
||||
"player",
|
||||
"what's been going on around the village today?",
|
||||
&mut history
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Simple request structure
|
||||
#[derive(Serialize)]
|
||||
struct ChatRequest {
|
||||
messages: Vec<Message>,
|
||||
temperature: f32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Message {
|
||||
role: String,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ChatResponse {
|
||||
choices: Vec<Choice>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Choice {
|
||||
message: Message,
|
||||
}
|
||||
|
||||
fn chat_with_lm_studio(
|
||||
user_prompt: &str,
|
||||
system_prompt: &str,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
let messages = vec![
|
||||
Message {
|
||||
role: "system".to_string(),
|
||||
content: system_prompt.to_string(),
|
||||
},
|
||||
Message {
|
||||
role: "user".to_string(),
|
||||
content: user_prompt.to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
let request = ChatRequest {
|
||||
messages,
|
||||
temperature: 0.7,
|
||||
};
|
||||
|
||||
let response = client
|
||||
.post("http://localhost:1234/v1/chat/completions")
|
||||
.json(&request)
|
||||
.send()?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Request failed: {}", response.text()?).into());
|
||||
}
|
||||
|
||||
let response: ChatResponse = response.json()?;
|
||||
|
||||
if let Some(choice) = response.choices.into_iter().next() {
|
||||
Ok(choice.message.content)
|
||||
} else {
|
||||
Err("No response from model".into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Config {
|
||||
data_dir: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct GameObject {
|
||||
imports: Vec<String>,
|
||||
characters: Vec<Entity>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Entity {
|
||||
name: String,
|
||||
profession: Option<String>,
|
||||
location: Option<String>,
|
||||
personality: Option<Personality>,
|
||||
speech: Option<Speech>,
|
||||
knowledge: Option<Knowledge>,
|
||||
relationships: HashMap<String, Relationship>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Personality {
|
||||
traits: Vec<String>,
|
||||
values: Vec<String>,
|
||||
fears: Vec<String>,
|
||||
goals: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Speech {
|
||||
style: Option<String>,
|
||||
common_phrases: Vec<String>,
|
||||
topics_of_interest: Vec<String>,
|
||||
vocabulary_level: Option<String>,
|
||||
accent: Option<String>,
|
||||
}
|
||||
|
||||
impl Speech {
|
||||
pub fn generate(
|
||||
&self,
|
||||
character: &Entity,
|
||||
user_name: &str,
|
||||
message: &str,
|
||||
history: &mut HashMap<String, String>,
|
||||
) -> String {
|
||||
let name: String = character.name.clone();
|
||||
let profession: String = character
|
||||
.profession
|
||||
.as_ref()
|
||||
.unwrap_or(&"unknown".to_string())
|
||||
.clone();
|
||||
let location: String = character
|
||||
.location
|
||||
.as_ref()
|
||||
.unwrap_or(&"unknown".to_string())
|
||||
.clone();
|
||||
let personality: Personality = character.personality.clone().unwrap_or(Personality {
|
||||
traits: vec![],
|
||||
values: vec![],
|
||||
fears: vec![],
|
||||
goals: vec![],
|
||||
});
|
||||
let knowledge: Knowledge = character.knowledge.clone().unwrap_or(Knowledge {
|
||||
areas: vec![],
|
||||
secrets: vec![],
|
||||
skills: vec![],
|
||||
});
|
||||
|
||||
// Build the system prompt
|
||||
let mut system_prompt = String::new();
|
||||
|
||||
// 1. Character Identity and Role
|
||||
system_prompt.push_str(&format!(
|
||||
"You are {name}, a {profession} in a text-based game. Respond naturally and stay in character at all times. \n Your location: {location}\n",
|
||||
));
|
||||
|
||||
// 2. Personality and Speech Style
|
||||
system_prompt.push_str("## CHARACTER TRAITS\n");
|
||||
if !personality.traits.is_empty() {
|
||||
system_prompt.push_str(&format!(
|
||||
"Personality: {traits}\n",
|
||||
traits = personality.traits.join(", ")
|
||||
));
|
||||
}
|
||||
if !personality.values.is_empty() {
|
||||
system_prompt.push_str(&format!(
|
||||
"Values: {values}\n",
|
||||
values = personality.values.join(", ")
|
||||
));
|
||||
}
|
||||
if !personality.goals.is_empty() {
|
||||
system_prompt.push_str(&format!(
|
||||
"Goals: {goals}\n",
|
||||
goals = personality.goals.join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
// 3. Speech Patterns
|
||||
system_prompt.push_str("\n## SPEECH STYLE\n");
|
||||
if let Some(style) = &self.style {
|
||||
system_prompt.push_str(&format!("Style: {style}\n"));
|
||||
}
|
||||
if let Some(accent) = &self.accent {
|
||||
system_prompt.push_str(&format!("Accent: {accent}\n"));
|
||||
}
|
||||
if let Some(vocab) = &self.vocabulary_level {
|
||||
system_prompt.push_str(&format!("Vocabulary: {vocab}\n"));
|
||||
}
|
||||
if !self.common_phrases.is_empty() {
|
||||
system_prompt.push_str(&format!(
|
||||
"Common phrases: {}\n",
|
||||
self.common_phrases.join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
// 4. Knowledge Base
|
||||
system_prompt.push_str("\n## CHARACTER KNOWLEDGE\n");
|
||||
system_prompt.push_str(
|
||||
"You know about the following topics. Only reference information from these areas:\n",
|
||||
);
|
||||
|
||||
if !knowledge.areas.is_empty() {
|
||||
system_prompt.push_str(&format!("- Locations: {}\n", knowledge.areas.join(", ")));
|
||||
}
|
||||
if !knowledge.skills.is_empty() {
|
||||
system_prompt.push_str(&format!("- Skills: {}\n", knowledge.skills.join(", ")));
|
||||
}
|
||||
if !knowledge.secrets.is_empty() {
|
||||
system_prompt.push_str("- Secrets: [REDACTED - only reveal when appropriate]\n");
|
||||
}
|
||||
|
||||
// 5. Conversation History
|
||||
if !history.is_empty() {
|
||||
system_prompt.push_str("\n## CONVERSATION HISTORY\n");
|
||||
for (speaker, message) in history.iter() {
|
||||
system_prompt.push_str(&format!("{speaker}: {message}\n"));
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Response Guidelines
|
||||
system_prompt.push_str(
|
||||
r#"
|
||||
## RESPONSE INSTRUCTIONS
|
||||
1. Stay in character at all times
|
||||
2. Only reference information from your knowledge base
|
||||
3. If asked about something outside your knowledge, say so
|
||||
4. Keep responses concise (1-3 sentences)
|
||||
5. Use natural speech patterns and contractions
|
||||
6. Show personality through word choice and tone
|
||||
7. If appropriate, ask questions to continue the conversation
|
||||
"#,
|
||||
);
|
||||
|
||||
// 6. Add the message to the history
|
||||
let user_prompt = format!("{user_name}: {message}");
|
||||
history.insert(user_name.to_string(), user_prompt.clone());
|
||||
|
||||
println!("System prompt: {system_prompt}");
|
||||
println!("User prompt: {user_prompt}");
|
||||
|
||||
// 7. Generate response
|
||||
chat_with_lm_studio(&user_prompt, &system_prompt).unwrap()
|
||||
// String::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Knowledge {
|
||||
areas: Vec<String>,
|
||||
secrets: Vec<String>,
|
||||
skills: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Relationship {
|
||||
stages: Vec<String>,
|
||||
default: String,
|
||||
}
|
||||
Reference in New Issue
Block a user