Skip to content

Commit 6fcb046

Browse files
authored
Merge pull request #36 from Menjay7/men
Add adaptive difficulty scaling based on player performance
2 parents 29c554f + c3b0fb8 commit 6fcb046

2 files changed

Lines changed: 297 additions & 2 deletions

File tree

contracts/registry/src/lib.rs

Lines changed: 296 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ pub struct WaveMeta {
3131
pub closed_at: u64,
3232
pub total_points: u32,
3333
pub status: WaveStatus,
34+
pub difficulty_level: u32,
35+
}
36+
37+
#[contracttype]
38+
#[derive(Clone, Debug, Eq, PartialEq)]
39+
pub struct ContributorPerformance {
40+
pub total_waves_participated: u32,
41+
pub total_points_earned: u32,
42+
pub average_points_per_wave: u32,
43+
pub success_rate: u32,
44+
pub last_updated: u64,
3445
}
3546

3647
/// Storage keys for the registry contract state.
@@ -46,6 +57,8 @@ pub enum DataKey {
4657
WaveCounter,
4758
Contributions(Address, u32), // contributor, wave_id -> contribution
4859
History(Address), // contributor -> Vec<wave_id>
60+
ContributorPerformance(Address), // contributor -> performance metrics
61+
ProgramDifficulty(u32), // program_id -> current difficulty level
4962
}
5063

5164
#[contract]
@@ -118,6 +131,9 @@ impl RegistryContract {
118131
panic!("program doesn't exist");
119132
}
120133

134+
// Get or initialize difficulty level for the program
135+
let difficulty_level: u32 = env.storage().persistent().get(&DataKey::ProgramDifficulty(program_id)).unwrap_or(1);
136+
121137
// Increment global wave ID
122138
let mut counter: u32 = env.storage().instance().get(&DataKey::WaveCounter).unwrap_or(0);
123139
counter += 1;
@@ -131,13 +147,14 @@ impl RegistryContract {
131147
closed_at: 0,
132148
total_points: 0,
133149
status: WaveStatus::Open,
150+
difficulty_level,
134151
};
135152

136153
env.storage().persistent().set(&DataKey::Waves(wave_id), &wave);
137154

138155
// Emit WaveOpened event
139156
env.events().publish(
140-
(symbol_short!("wave_open"), program_id, wave_id),
157+
(symbol_short!("wave_open"), program_id, wave_id, difficulty_level),
141158
env.ledger().timestamp(),
142159
);
143160

@@ -162,6 +179,9 @@ impl RegistryContract {
162179

163180
env.storage().persistent().set(&DataKey::Waves(wave_id), &wave);
164181

182+
// Adjust difficulty based on overall performance
183+
Self::adjust_program_difficulty(&env, wave.program_id, total_points, wave.difficulty_level);
184+
165185
// Emit WaveClosed event
166186
env.events().publish(
167187
(symbol_short!("wave_cls"), wave_id, total_points),
@@ -203,8 +223,11 @@ impl RegistryContract {
203223

204224
if !history.contains(wave_id) {
205225
history.push_back(wave_id);
206-
env.storage().persistent().set(&DataKey::History(address), &history);
226+
env.storage().persistent().set(&DataKey::History(address.clone()), &history);
207227
}
228+
229+
// Update contributor performance metrics
230+
Self::update_contributor_performance(&env, address.clone(), points);
208231
}
209232

210233
/// Returns the full contribution history for a contributor.
@@ -245,6 +268,86 @@ impl RegistryContract {
245268
admin.require_auth();
246269
env.storage().instance().set(&DataKey::SettlementContract, &new_settlement);
247270
}
271+
272+
/// Update contributor performance metrics after recording a contribution
273+
fn update_contributor_performance(env: &Env, address: Address, points: u32) {
274+
let mut perf: ContributorPerformance = env
275+
.storage()
276+
.persistent()
277+
.get(&DataKey::ContributorPerformance(address.clone()))
278+
.unwrap_or_else(|| ContributorPerformance {
279+
total_waves_participated: 0,
280+
total_points_earned: 0,
281+
average_points_per_wave: 0,
282+
success_rate: 100,
283+
last_updated: 0,
284+
});
285+
286+
perf.total_waves_participated += 1;
287+
perf.total_points_earned += points;
288+
perf.average_points_per_wave = perf.total_points_earned / perf.total_waves_participated;
289+
perf.last_updated = env.ledger().timestamp();
290+
291+
// Simple success rate calculation: if points > 0, it's a success
292+
if points > 0 {
293+
let successful_waves = (perf.success_rate as u128 * perf.total_waves_participated as u128 / 100) + 1;
294+
perf.success_rate = (successful_waves * 100 / perf.total_waves_participated as u128) as u32;
295+
}
296+
297+
env.storage().persistent().set(&DataKey::ContributorPerformance(address), &perf);
298+
}
299+
300+
/// Adjust program difficulty based on wave performance
301+
fn adjust_program_difficulty(env: &Env, program_id: u32, total_points: u32, current_difficulty: u32) {
302+
// Performance thresholds for difficulty adjustment
303+
const HIGH_PERFORMANCE_THRESHOLD: u32 = 1000;
304+
const LOW_PERFORMANCE_THRESHOLD: u32 = 100;
305+
const MAX_DIFFICULTY: u32 = 10;
306+
const MIN_DIFFICULTY: u32 = 1;
307+
308+
let new_difficulty = if total_points > HIGH_PERFORMANCE_THRESHOLD {
309+
// High performance - increase difficulty
310+
(current_difficulty + 1).min(MAX_DIFFICULTY)
311+
} else if total_points < LOW_PERFORMANCE_THRESHOLD && current_difficulty > MIN_DIFFICULTY {
312+
// Low performance - decrease difficulty
313+
current_difficulty - 1
314+
} else {
315+
// Maintain current difficulty
316+
current_difficulty
317+
};
318+
319+
if new_difficulty != current_difficulty {
320+
env.storage().persistent().set(&DataKey::ProgramDifficulty(program_id), &new_difficulty);
321+
322+
// Emit DifficultyAdjusted event
323+
env.events().publish(
324+
(symbol_short!("diff_adj"), program_id, current_difficulty, new_difficulty),
325+
total_points,
326+
);
327+
}
328+
}
329+
330+
/// Get contributor performance metrics
331+
pub fn get_contributor_performance(env: Env, address: Address) -> Option<ContributorPerformance> {
332+
env.storage().persistent().get(&DataKey::ContributorPerformance(address))
333+
}
334+
335+
/// Get current difficulty level for a program
336+
pub fn get_program_difficulty(env: Env, program_id: u32) -> u32 {
337+
env.storage().persistent().get(&DataKey::ProgramDifficulty(program_id)).unwrap_or(1)
338+
}
339+
340+
/// Manually set difficulty level for a program (admin only)
341+
pub fn set_program_difficulty(env: Env, program_id: u32, difficulty: u32) {
342+
let admin: Address = env.storage().instance().get(&DataKey::Admin).expect("not initialized");
343+
admin.require_auth();
344+
345+
if difficulty < 1 || difficulty > 10 {
346+
panic!("difficulty must be between 1 and 10");
347+
}
348+
349+
env.storage().persistent().set(&DataKey::ProgramDifficulty(program_id), &difficulty);
350+
}
248351
}
249352

250353
// ─── Tests ────────────────────────────────────────────────────────────────────
@@ -370,6 +473,197 @@ mod tests {
370473
assert_eq!(records.len(), 1);
371474
assert_eq!(records.get(0).unwrap().points, 50);
372475
}
476+
477+
#[test]
478+
fn test_difficulty_initialization() {
479+
let env = Env::default();
480+
env.mock_all_auths();
481+
let (client, _, _) = setup(&env);
482+
483+
let config = ProgramConfig {
484+
name: String::from_str(&env, "prog1"),
485+
organizer: Address::generate(&env),
486+
metadata: String::from_str(&env, "meta"),
487+
funding_target: 1000,
488+
};
489+
let admin: Address = client.get_admin();
490+
let program_id = client.register_program(&admin, &config);
491+
492+
// Initial difficulty should be 1
493+
assert_eq!(client.get_program_difficulty(&program_id), 1);
494+
495+
// Open wave should use initial difficulty
496+
let wave_id = client.open_wave(&program_id);
497+
let wave = client.get_wave(&wave_id).unwrap();
498+
assert_eq!(wave.difficulty_level, 1);
499+
}
500+
501+
#[test]
502+
fn test_difficulty_increase_on_high_performance() {
503+
let env = Env::default();
504+
env.mock_all_auths();
505+
let (client, _, _) = setup(&env);
506+
507+
let config = ProgramConfig {
508+
name: String::from_str(&env, "prog1"),
509+
organizer: Address::generate(&env),
510+
metadata: String::from_str(&env, "meta"),
511+
funding_target: 1000,
512+
};
513+
let admin: Address = client.get_admin();
514+
let program_id = client.register_program(&admin, &config);
515+
let wave_id = client.open_wave(&program_id);
516+
517+
// Close wave with high performance (> 1000 points)
518+
client.close_wave(&wave_id, &1500);
519+
520+
// Difficulty should increase to 2
521+
assert_eq!(client.get_program_difficulty(&program_id), 2);
522+
523+
// Next wave should use new difficulty
524+
let wave_id2 = client.open_wave(&program_id);
525+
let wave2 = client.get_wave(&wave_id2).unwrap();
526+
assert_eq!(wave2.difficulty_level, 2);
527+
}
528+
529+
#[test]
530+
fn test_difficulty_decrease_on_low_performance() {
531+
let env = Env::default();
532+
env.mock_all_auths();
533+
let (client, _, _) = setup(&env);
534+
535+
let config = ProgramConfig {
536+
name: String::from_str(&env, "prog1"),
537+
organizer: Address::generate(&env),
538+
metadata: String::from_str(&env, "meta"),
539+
funding_target: 1000,
540+
};
541+
let admin: Address = client.get_admin();
542+
let program_id = client.register_program(&admin, &config);
543+
544+
// Set initial difficulty to 3
545+
client.set_program_difficulty(&program_id, &3);
546+
assert_eq!(client.get_program_difficulty(&program_id), 3);
547+
548+
let wave_id = client.open_wave(&program_id);
549+
550+
// Close wave with low performance (< 100 points)
551+
client.close_wave(&wave_id, &50);
552+
553+
// Difficulty should decrease to 2
554+
assert_eq!(client.get_program_difficulty(&program_id), 2);
555+
}
556+
557+
#[test]
558+
fn test_difficulty_max_cap() {
559+
let env = Env::default();
560+
env.mock_all_auths();
561+
let (client, _, _) = setup(&env);
562+
563+
let config = ProgramConfig {
564+
name: String::from_str(&env, "prog1"),
565+
organizer: Address::generate(&env),
566+
metadata: String::from_str(&env, "meta"),
567+
funding_target: 1000,
568+
};
569+
let admin: Address = client.get_admin();
570+
let program_id = client.register_program(&admin, &config);
571+
572+
// Set difficulty to max (10)
573+
client.set_program_difficulty(&program_id, &10);
574+
assert_eq!(client.get_program_difficulty(&program_id), 10);
575+
576+
let wave_id = client.open_wave(&program_id);
577+
578+
// Close wave with extremely high performance
579+
client.close_wave(&wave_id, &10000);
580+
581+
// Difficulty should stay at max (10)
582+
assert_eq!(client.get_program_difficulty(&program_id), 10);
583+
}
584+
585+
#[test]
586+
fn test_contributor_performance_tracking() {
587+
let env = Env::default();
588+
env.mock_all_auths();
589+
let (client, _, _) = setup(&env);
590+
591+
let config = ProgramConfig {
592+
name: String::from_str(&env, "prog1"),
593+
organizer: Address::generate(&env),
594+
metadata: String::from_str(&env, "meta"),
595+
funding_target: 1000,
596+
};
597+
let admin: Address = client.get_admin();
598+
let program_id = client.register_program(&admin, &config);
599+
let wave_id = client.open_wave(&program_id);
600+
601+
let contributor = Address::generate(&env);
602+
603+
// Record first contribution
604+
client.record_contribution(&wave_id, &contributor, &100);
605+
606+
let perf = client.get_contributor_performance(&contributor).unwrap();
607+
assert_eq!(perf.total_waves_participated, 1);
608+
assert_eq!(perf.total_points_earned, 100);
609+
assert_eq!(perf.average_points_per_wave, 100);
610+
611+
// Record second contribution
612+
client.record_contribution(&wave_id, &contributor, &200);
613+
614+
let perf = client.get_contributor_performance(&contributor).unwrap();
615+
assert_eq!(perf.total_waves_participated, 2);
616+
assert_eq!(perf.total_points_earned, 300);
617+
assert_eq!(perf.average_points_per_wave, 150);
618+
}
619+
620+
#[test]
621+
#[should_panic(expected = "difficulty must be between 1 and 10")]
622+
fn test_set_invalid_difficulty() {
623+
let env = Env::default();
624+
env.mock_all_auths();
625+
let (client, _, _) = setup(&env);
626+
627+
let config = ProgramConfig {
628+
name: String::from_str(&env, "prog1"),
629+
organizer: Address::generate(&env),
630+
metadata: String::from_str(&env, "meta"),
631+
funding_target: 1000,
632+
};
633+
let admin: Address = client.get_admin();
634+
let program_id = client.register_program(&admin, &config);
635+
636+
// Try to set invalid difficulty (0)
637+
client.set_program_difficulty(&program_id, &0);
638+
}
639+
640+
#[test]
641+
fn test_difficulty_maintained_on_moderate_performance() {
642+
let env = Env::default();
643+
env.mock_all_auths();
644+
let (client, _, _) = setup(&env);
645+
646+
let config = ProgramConfig {
647+
name: String::from_str(&env, "prog1"),
648+
organizer: Address::generate(&env),
649+
metadata: String::from_str(&env, "meta"),
650+
funding_target: 1000,
651+
};
652+
let admin: Address = client.get_admin();
653+
let program_id = client.register_program(&admin, &config);
654+
655+
// Set difficulty to 5
656+
client.set_program_difficulty(&program_id, &5);
657+
assert_eq!(client.get_program_difficulty(&program_id), 5);
658+
659+
let wave_id = client.open_wave(&program_id);
660+
661+
// Close wave with moderate performance (between thresholds)
662+
client.close_wave(&wave_id, &500);
663+
664+
// Difficulty should remain at 5
665+
assert_eq!(client.get_program_difficulty(&program_id), 5);
666+
}
373667
}
374668

375669
#[cfg(test)]

contracts/registry/src/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ fn test_full_wave_lifecycle() {
133133
let wave = client.get_wave(&wave_id).expect("Wave should exist");
134134
assert_eq!(wave.status, WaveStatus::Open);
135135
assert_eq!(wave.program_id, program_id);
136+
assert_eq!(wave.difficulty_level, 1); // Default initial difficulty
136137

137138
// 3. Close Wave
138139
let close_ts = 300000;

0 commit comments

Comments
 (0)