use crate::challenges::{Challenge, ChallengeOrigin};
use crate::evolve;
pub fn fib(n: u32) -> u64 {
if n == 0 { return 0; }
let mut a: u64 = 0;
let mut b: u64 = 1;
for _ in 1..n {
let tmp = a + b;
a = b;
b = tmp;
}
b
}
#[derive(Clone, Debug)]
pub enum GeneratorKind {
Arithmetic,
Composition,
}
impl GeneratorKind {
pub fn name(&self) -> &str {
match self {
GeneratorKind::Arithmetic => "arithmetic-ladder",
GeneratorKind::Composition => "composition-ladder",
}
}
fn generate_next(
&self,
solved: &Challenge,
solution: &str,
all_solved: &[&Challenge],
rng_seed: u64,
) -> Vec<Challenge> {
match self {
GeneratorKind::Arithmetic => arithmetic_generate(solved, solution),
GeneratorKind::Composition => composition_generate(solved, solution, all_solved, rng_seed),
}
}
fn difficulty_level(&self, challenge: &Challenge) -> u32 {
match self {
GeneratorKind::Arithmetic => {
if let Some(idx) = extract_fib_index(&challenge.name) {
idx
} else {
(challenge.reward / 10) as u32
}
}
GeneratorKind::Composition => {
(challenge.reward / 10) as u32 + 5
}
}
}
}
fn extract_fib_index(name: &str) -> Option<u32> {
let lower = name.to_lowercase();
let digits: String = lower.chars()
.skip_while(|c| !c.is_ascii_digit())
.take_while(|c| c.is_ascii_digit())
.collect();
digits.parse().ok()
}
fn arithmetic_generate(solved: &Challenge, solution: &str) -> Vec<Challenge> {
let mut out = Vec::new();
let lower = solved.name.to_lowercase();
if lower.contains("fib") {
let current_n = extract_fib_index(&solved.name).unwrap_or(10);
let token_count = evolve::tokenize(solution).len();
if token_count > 5 {
let target_tokens = token_count - 2;
out.push(Challenge {
id: 0,
name: format!("fib{}-short{}", current_n, target_tokens),
description: format!(
"compute fib({}) in {} or fewer tokens",
current_n, target_tokens
),
target_output: solved.target_output.clone(),
test_input: None,
max_steps: solved.max_steps,
seed_programs: vec![solution.to_string()],
origin: ChallengeOrigin::BuiltIn,
reward: solved.reward + 20,
solved: false,
solution: None,
solver: None,
attempts: 0,
});
}
let next_n = current_n + 5;
if next_n <= 40 {
let target = fib(next_n);
out.push(Challenge {
id: 0,
name: format!("fib{}", next_n),
description: format!("compute the {}th Fibonacci number ({})", next_n, target),
target_output: format!("{} ", target),
test_input: None,
max_steps: solved.max_steps + 5000,
seed_programs: vec![
solution.to_string(),
{
let mut rng = crate::features::mutation::SimpleRng::new(next_n as u64);
evolve::mutate(solution, &mut rng)
},
],
origin: ChallengeOrigin::BuiltIn,
reward: solved.reward + 50,
solved: false,
solution: None,
solver: None,
attempts: 0,
});
}
let fib_val = fib(current_n);
if fib_val < 10000 {
let square = fib_val * fib_val;
out.push(Challenge {
id: 0,
name: format!("square-{}", fib_val),
description: format!("compute {} * {} = {}", fib_val, fib_val, square),
target_output: format!("{} ", square),
test_input: None,
max_steps: 10000,
seed_programs: vec![
format!("{} DUP * .", fib_val),
format!("{} .", square),
],
origin: ChallengeOrigin::BuiltIn,
reward: 80,
solved: false,
solution: None,
solver: None,
attempts: 0,
});
}
}
out
}
fn composition_generate(
_solved: &Challenge,
_solution: &str,
all_solved: &[&Challenge],
rng_seed: u64,
) -> Vec<Challenge> {
if rng_seed % 3 != 0 { return Vec::new(); }
let numeric_solved: Vec<&&Challenge> = all_solved.iter()
.filter(|c| c.target_output.trim().parse::<i64>().is_ok())
.collect();
if numeric_solved.len() < 2 { return Vec::new(); }
let idx_a = (rng_seed as usize) % numeric_solved.len();
let idx_b = ((rng_seed as usize) / 7 + 1) % numeric_solved.len();
if idx_a == idx_b { return Vec::new(); }
let a = numeric_solved[idx_a];
let b = numeric_solved[idx_b];
let val_a: i64 = a.target_output.trim().parse().unwrap_or(0);
let val_b: i64 = b.target_output.trim().parse().unwrap_or(0);
let composed = val_a + val_b;
let sol_a = a.solution.as_deref().unwrap_or("");
let sol_b = b.solution.as_deref().unwrap_or("");
if sol_a.is_empty() || sol_b.is_empty() { return Vec::new(); }
let prog_a = sol_a.trim_end_matches('.').trim();
let prog_b = sol_b.trim_end_matches('.').trim();
vec![Challenge {
id: 0,
name: format!("compose-{}+{}", a.name, b.name),
description: format!(
"compute {} + {} = {} (compose {} and {})",
val_a, val_b, composed, a.name, b.name
),
target_output: format!("{} ", composed),
test_input: None,
max_steps: 15000,
seed_programs: vec![
format!("{} {} + .", prog_a, prog_b),
format!("{} .", composed),
],
origin: ChallengeOrigin::BuiltIn,
reward: a.reward.max(b.reward) + 30,
solved: false,
solution: None,
solver: None,
attempts: 0,
}]
}
#[derive(Clone, Debug)]
pub struct EnvironmentCycle {
pub current_idx: usize,
pub cycle_length: u64,
pub tick_counter: u64,
pub conditions: Vec<String>,
}
impl EnvironmentCycle {
pub fn new() -> Self {
EnvironmentCycle {
current_idx: 0,
cycle_length: 500,
tick_counter: 0,
conditions: vec![
"normal".into(),
"harsh".into(),
"abundant".into(),
"competitive".into(),
],
}
}
pub fn tick(&mut self) {
self.tick_counter += 1;
if self.tick_counter >= self.cycle_length {
self.tick_counter = 0;
self.current_idx = (self.current_idx + 1) % self.conditions.len();
}
}
pub fn current_condition(&self) -> &str {
&self.conditions[self.current_idx]
}
pub fn apply_to_max_steps(&self, base: usize) -> usize {
match self.current_condition() {
"harsh" => base / 2,
"abundant" => base * 2,
_ => base,
}
}
pub fn apply_to_reward(&self, base: i64, attempts: u32) -> i64 {
match self.current_condition() {
"harsh" => base * 2,
"competitive" => base / (attempts as i64 + 1).max(1),
_ => base,
}
}
}
#[derive(Clone, Debug)]
pub struct LandscapeEngine {
pub generators: Vec<GeneratorKind>,
pub environment: EnvironmentCycle,
pub challenges_generated: u64,
pub depth: u32,
}
impl LandscapeEngine {
pub fn new() -> Self {
LandscapeEngine {
generators: vec![GeneratorKind::Arithmetic, GeneratorKind::Composition],
environment: EnvironmentCycle::new(),
challenges_generated: 0,
depth: 0,
}
}
pub fn on_challenge_solved(
&mut self,
challenge: &Challenge,
solution: &str,
all_solved: &[&Challenge],
) -> Vec<Challenge> {
let mut new_challenges = Vec::new();
for gen in &self.generators {
let parent_difficulty = gen.difficulty_level(challenge);
let generated = gen.generate_next(
challenge, solution, all_solved, self.challenges_generated,
);
for ch in generated {
let child_difficulty = gen.difficulty_level(&ch);
if child_difficulty > parent_difficulty && child_difficulty as u32 > self.depth {
self.depth = child_difficulty as u32;
}
new_challenges.push(ch);
}
}
self.challenges_generated += new_challenges.len() as u64;
new_challenges
}
pub fn current_environment(&self) -> &str {
self.environment.current_condition()
}
pub fn tick(&mut self) {
self.environment.tick();
}
pub fn depth(&self) -> u32 {
self.depth
}
pub fn format_landscape(&self) -> String {
format!(
"--- landscape ---\ndepth: {}\nchallenges generated: {}\nenvironment: {}\n",
self.depth, self.challenges_generated, self.current_environment()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn solved_fib10() -> Challenge {
Challenge {
id: 1,
name: "fib10".into(),
description: "compute 10th fibonacci".into(),
target_output: "55 ".into(),
test_input: None,
max_steps: 10000,
seed_programs: vec![],
origin: ChallengeOrigin::BuiltIn,
reward: 100,
solved: true,
solution: Some("0 1 10 0 DO OVER + SWAP LOOP DROP .".into()),
solver: Some([0; 8]),
attempts: 1,
}
}
#[test]
fn test_fib_helper() {
assert_eq!(fib(0), 0);
assert_eq!(fib(1), 1);
assert_eq!(fib(10), 55);
assert_eq!(fib(15), 610);
assert_eq!(fib(20), 6765);
assert_eq!(fib(30), 832040);
}
#[test]
fn test_arithmetic_generates_harder_fib() {
let ch = solved_fib10();
let solution = "0 1 10 0 DO OVER + SWAP LOOP DROP .";
let generated = arithmetic_generate(&ch, solution);
assert!(generated.len() >= 2);
assert!(generated.iter().any(|c| c.name.contains("short")));
let fib15 = generated.iter().find(|c| c.name == "fib15");
assert!(fib15.is_some());
assert_eq!(fib15.unwrap().target_output, "610 ");
assert!(fib15.unwrap().reward > ch.reward);
}
#[test]
fn test_arithmetic_generates_square() {
let ch = solved_fib10();
let solution = "0 1 10 0 DO OVER + SWAP LOOP DROP .";
let generated = arithmetic_generate(&ch, solution);
let sq = generated.iter().find(|c| c.name.contains("square"));
assert!(sq.is_some());
assert_eq!(sq.unwrap().target_output, "3025 "); }
#[test]
fn test_composition_needs_two_solved() {
let ch = solved_fib10();
let solution = "0 1 10 0 DO OVER + SWAP LOOP DROP .";
let generated = composition_generate(&ch, solution, &[&ch], 0);
assert!(generated.is_empty());
}
#[test]
fn test_composition_from_two_solved() {
let ch1 = solved_fib10();
let mut ch2 = solved_fib10();
ch2.id = 2;
ch2.name = "square-55".into();
ch2.target_output = "3025 ".into();
ch2.solution = Some("55 DUP * .".into());
let all = vec![&ch1, &ch2];
let generated = composition_generate(&ch1, "55 .", &all, 0);
if !generated.is_empty() {
assert!(generated[0].name.contains("compose"));
assert_eq!(generated[0].target_output, "3080 ");
}
}
#[test]
fn test_environment_cycle() {
let mut env = EnvironmentCycle::new();
assert_eq!(env.current_condition(), "normal");
for _ in 0..500 { env.tick(); }
assert_eq!(env.current_condition(), "harsh");
for _ in 0..500 { env.tick(); }
assert_eq!(env.current_condition(), "abundant");
for _ in 0..500 { env.tick(); }
assert_eq!(env.current_condition(), "competitive");
for _ in 0..500 { env.tick(); }
assert_eq!(env.current_condition(), "normal"); }
#[test]
fn test_apply_to_max_steps() {
let mut env = EnvironmentCycle::new();
assert_eq!(env.apply_to_max_steps(10000), 10000); for _ in 0..500 { env.tick(); } assert_eq!(env.apply_to_max_steps(10000), 5000);
for _ in 0..500 { env.tick(); } assert_eq!(env.apply_to_max_steps(10000), 20000);
}
#[test]
fn test_apply_to_reward() {
let mut env = EnvironmentCycle::new();
assert_eq!(env.apply_to_reward(100, 0), 100); for _ in 0..500 { env.tick(); } assert_eq!(env.apply_to_reward(100, 0), 200);
for _ in 0..1000 { env.tick(); } assert_eq!(env.apply_to_reward(100, 3), 25); }
#[test]
fn test_depth_increases() {
let mut engine = LandscapeEngine::new();
assert_eq!(engine.depth(), 0);
let ch = solved_fib10();
let solution = "0 1 10 0 DO OVER + SWAP LOOP DROP .";
let _new = engine.on_challenge_solved(&ch, solution, &[&ch]);
assert!(engine.depth() > 0);
}
#[test]
fn test_on_challenge_solved_non_fib() {
let mut engine = LandscapeEngine::new();
let ch = Challenge {
id: 99,
name: "custom".into(),
description: "non-fib".into(),
target_output: "42 ".into(),
test_input: None,
max_steps: 10000,
seed_programs: vec![],
origin: ChallengeOrigin::BuiltIn,
reward: 50,
solved: true,
solution: Some("42 .".into()),
solver: Some([0; 8]),
attempts: 1,
};
let generated = engine.on_challenge_solved(&ch, "42 .", &[&ch]);
assert!(generated.is_empty());
}
#[test]
fn test_format_landscape() {
let engine = LandscapeEngine::new();
let s = engine.format_landscape();
assert!(s.contains("depth: 0"));
assert!(s.contains("environment: normal"));
}
}