diff --git a/examples/hyperspace-roll/src/battle.rs b/examples/hyperspace-roll/src/battle.rs index bbde2c45..fa8d2fad 100644 --- a/examples/hyperspace-roll/src/battle.rs +++ b/examples/hyperspace-roll/src/battle.rs @@ -1,3 +1,4 @@ +use crate::level_generation::generate_enemy_health; use crate::sfx::Sfx; use crate::{ graphics::SELECT_BOX, level_generation::generate_attack, Agb, EnemyAttackType, Face, PlayerDice, @@ -499,7 +500,7 @@ pub(crate) fn battle_screen( let num_dice = player_dice.dice.len(); - let enemy_health = 5 + current_level * agb::rng::gen().rem_euclid(4) as u32; + let enemy_health = generate_enemy_health(current_level); let mut current_battle_state = CurrentBattleState { player: PlayerState { diff --git a/examples/hyperspace-roll/src/level_generation.rs b/examples/hyperspace-roll/src/level_generation.rs index 04c45b5f..523332d6 100644 --- a/examples/hyperspace-roll/src/level_generation.rs +++ b/examples/hyperspace-roll/src/level_generation.rs @@ -8,6 +8,57 @@ pub struct GeneratedAttack { pub cooldown: u32, } +fn roll_dice(number_of_dice: u32, bits_per_dice: u32) -> u32 { + assert!( + 32 % bits_per_dice == 0, + "the number of bits per dice should be a multiple of 32" + ); + + assert!( + number_of_dice % (32 / bits_per_dice) == 0, + "number of dice should be a multiple of 32 / bits per dice" + ); + + fn roll_dice_inner(number_of_random_values: u32, bits_per_dice: u32) -> u32 { + let mut count = 0; + let bit_mask = 1u32.wrapping_shl(bits_per_dice).wrapping_sub(1); + + for _ in 0..number_of_random_values { + let n = agb::rng::gen() as u32; + for idx in 0..(32 / bits_per_dice) { + count += (n >> (bits_per_dice * idx)) & bit_mask; + } + } + + count + } + + roll_dice_inner(number_of_dice / (32 / bits_per_dice), bits_per_dice) +} + +// uses multiple dice rolls to generate a random value with a specified mean and width +fn roll_dice_scaled(number_of_dice: u32, bits_per_dice: u32, width: u32) -> i32 { + let dice = roll_dice(number_of_dice, bits_per_dice) as i32; + + let current_width = (number_of_dice * ((1 << bits_per_dice) - 1)) as i32; + let current_mean = current_width / 2; + + let dice_around_zero = dice - current_mean; + + fn divide_nearest(numerator: i32, denominator: i32) -> i32 { + if (numerator < 0) ^ (denominator < 0) { + (numerator - denominator / 2) / denominator + } else { + (numerator + denominator / 2) / denominator + } + } + divide_nearest(dice_around_zero * width as i32, current_width) +} + +fn default_roll(width: u32) -> i32 { + roll_dice_scaled(2, 16, width) +} + pub fn generate_attack(current_level: u32) -> Option { if (rng::gen().rem_euclid(1024) as u32) < current_level * 2 { Some(GeneratedAttack { @@ -19,6 +70,10 @@ pub fn generate_attack(current_level: u32) -> Option { } } +pub fn generate_enemy_health(current_level: u32) -> u32 { + (5 + current_level as i32 * 2 + default_roll(current_level * 4)) as u32 +} + fn generate_enemy_attack(current_level: u32) -> EnemyAttack { let attack_id = rng::gen().rem_euclid(10) as u32;