1use crate::worldgen::Rng;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Meter { Iambic, Trochaic, Anapestic, Dactylic, Free, Fibonacci }
9
10#[derive(Debug, Clone)]
12pub struct VerseLine { pub text: String, pub syllable_count: usize, pub rhyme_sound: String }
13
14#[derive(Debug, Clone)]
16pub struct Poem {
17 pub title: String,
18 pub lines: Vec<VerseLine>,
19 pub meter: Meter,
20 pub rhyme_scheme: String,
21}
22
23pub fn generate_poem(theme: &str, meter: Meter, lines: usize, rng: &mut Rng) -> Poem {
25 let syllable_counts = match meter {
26 Meter::Fibonacci => fibonacci_syllables(lines),
27 Meter::Iambic => vec![10; lines],
28 Meter::Trochaic => vec![8; lines],
29 _ => (0..lines).map(|_| rng.range_usize(5, 12)).collect(),
30 };
31
32 let rhyme_endings = generate_rhyme_pairs(lines, rng);
33 let verse_lines: Vec<VerseLine> = syllable_counts.iter().enumerate().map(|(i, &count)| {
34 let text = generate_line(theme, count, rng);
35 VerseLine { text, syllable_count: count, rhyme_sound: rhyme_endings[i].clone() }
36 }).collect();
37
38 let scheme = if lines == 4 { "ABAB".to_string() }
39 else if lines == 2 { "AA".to_string() }
40 else { "Free".to_string() };
41
42 Poem { title: format!("Ode to {}", capitalize(theme)), lines: verse_lines, meter, rhyme_scheme: scheme }
43}
44
45fn fibonacci_syllables(n: usize) -> Vec<usize> {
46 let mut fibs = Vec::with_capacity(n);
47 let (mut a, mut b) = (1usize, 1usize);
48 for _ in 0..n {
49 fibs.push(a.max(1).min(13));
50 let next = a + b;
51 a = b;
52 b = next;
53 }
54 fibs
55}
56
57fn generate_line(theme: &str, target_syllables: usize, rng: &mut Rng) -> String {
58 let words_by_syl: Vec<(&str, usize)> = vec![
59 ("the", 1), ("of", 1), ("and", 1), ("in", 1), ("a", 1), ("to", 1), ("with", 1),
60 ("is", 1), ("was", 1), ("on", 1), ("by", 1), ("no", 1), ("from", 1),
61 ("dark", 1), ("light", 1), ("wind", 1), ("stone", 1), ("fire", 1), ("rain", 1),
62 ("shadow", 2), ("river", 2), ("mountain", 2), ("silence", 2), ("ancient", 2),
63 ("golden", 2), ("silver", 2), ("fallen", 2), ("rising", 2), ("burning", 2),
64 ("wandering", 3), ("eternal", 3), ("beautiful", 3), ("forgotten", 3), ("awakening", 4),
65 ("remembering", 4), ("everlasting", 4),
66 ];
67
68 let mut line = Vec::new();
69 let mut remaining = target_syllables;
70 while remaining > 0 {
71 let candidates: Vec<_> = words_by_syl.iter().filter(|(_, s)| *s <= remaining).collect();
72 if candidates.is_empty() { break; }
73 let &(word, syls) = candidates[rng.next_u64() as usize % candidates.len()];
74 line.push(word);
75 remaining -= syls;
76 }
77
78 let mut text = line.join(" ");
79 if !text.is_empty() {
80 let first = text.remove(0).to_uppercase().to_string();
81 text = first + &text;
82 }
83 text
84}
85
86fn generate_rhyme_pairs(n: usize, rng: &mut Rng) -> Vec<String> {
87 let endings = ["ight", "ane", "ow", "air", "ound", "ong", "aze", "ear", "ire", "one"];
88 (0..n).map(|i| {
89 let pair_idx = i / 2;
90 endings[pair_idx % endings.len()].to_string()
91 }).collect()
92}
93
94fn capitalize(s: &str) -> String {
95 let mut c = s.chars();
96 match c.next() {
97 None => String::new(),
98 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_fibonacci_poem() {
108 let mut rng = Rng::new(42);
109 let poem = generate_poem("shadow", Meter::Fibonacci, 6, &mut rng);
110 assert_eq!(poem.lines.len(), 6);
111 assert_eq!(poem.lines[0].syllable_count, 1);
113 assert_eq!(poem.lines[4].syllable_count, 5);
114 }
115
116 #[test]
117 fn test_generate_line() {
118 let mut rng = Rng::new(42);
119 let line = generate_line("war", 8, &mut rng);
120 assert!(!line.is_empty());
121 }
122}