initial commit!

This commit is contained in:
2025-07-08 20:14:07 +01:00
commit fda08ba458
10 changed files with 4279 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
+11
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
data_dir = "./game"
+2179
View File
File diff suppressed because it is too large Load Diff
View File
+119
View File
@@ -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",
),
},
)
]
)
View File
+281
View File
@@ -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,
}