~jacqueline/pokemon

25bf8e56ed07cbd61c69408a696eb2ff463ea16f — jacqueline leykam 1 year, 12 days ago 7264411
Implement generating pokemon!
3 files changed, 143 insertions(+), 26 deletions(-)

M src/bin/pack-resources.rs
M src/lib.rs
M src/main.rs
M src/bin/pack-resources.rs => src/bin/pack-resources.rs +16 -8
@@ 1,7 1,7 @@
use minicbor::bytes::ByteArray;
use minicbor_io::Writer;
use pokemon::pokedex::{
    self, LearnCondition, LearnableMove, Move, MoveListChunk, Pokemon, StatData, Type,
    self, LearnCondition, LearnableMove, Move, MoveListChunk, PokemonSpecies, SpeciesStatData, Type,
};
use serde_json::Value;
use std::ascii::AsciiExt;


@@ 150,8 150,8 @@ fn parse_learnable_move(json: &Value) -> Result<Vec<LearnableMove>, ParseError> 
        moves.push(LearnableMove {
            id,
            condition: match method["move_learn_method"].as_str() {
                Some("level-up") => LearnCondition::LevelUp {
                    level: match method["level_learned_at"]
                Some("level-up") => LearnCondition::LevelUp(
                    match method["level_learned_at"]
                        .as_u64()
                        .map(|i| u8::try_from(i).ok())
                        .flatten()


@@ 163,13 163,14 @@ fn parse_learnable_move(json: &Value) -> Result<Vec<LearnableMove>, ParseError> 
                            ))
                        }
                    },
                },
                ),
                Some("machine") => LearnCondition::Machine,
                Some(m) => return Err(ParseError(format!("unknown learn method {}", m))),
                None => return Err(ParseError("missing learn method".to_string())),
            },
        })
    }
    // TODO: sort moves by level before returning.
    return match moves.len() {
        0 => Err(ParseError("move had no learn methods".to_string())),
        _ => Ok(moves),


@@ 180,7 181,7 @@ fn parse_move(json: Value) -> Result<Move, &'static str> {
    return Err("uh oh");
}

fn parse_pokemon(json: &Value, bitmap: &[u8; 578]) -> Result<Pokemon, &'static str> {
fn parse_pokemon(json: &Value, bitmap: &[u8; 578]) -> Result<PokemonSpecies, &'static str> {
    let name = match json["name"].as_str() {
        Some(n) => {
            let mut name = [08; 12];


@@ 223,7 224,7 @@ fn parse_pokemon(json: &Value, bitmap: &[u8; 578]) -> Result<Pokemon, &'static s
        None => return Err("missing stats"),
    };

    Ok(pokedex::Pokemon {
    Ok(pokedex::PokemonSpecies {
        id: match parse_as_u8(&json["id"]) {
            Some(i) => i,
            None => return Err("missing id"),


@@ 262,13 263,13 @@ fn parse_as_u8(json: &Value) -> Option<u8> {
    };
}

fn parse_as_stat_data(json: &Value) -> Option<(&str, StatData)> {
fn parse_as_stat_data(json: &Value) -> Option<(&str, SpeciesStatData)> {
    Some((
        match json["stat"].as_str() {
            Some(i) => i,
            None => return None,
        },
        StatData {
        SpeciesStatData {
            base_value: match json["base_stat"].as_u64().map(|i| u16::try_from(i)) {
                Some(i) => match i {
                    Ok(i) => i,


@@ 276,6 277,13 @@ fn parse_as_stat_data(json: &Value) -> Option<(&str, StatData)> {
                },
                None => return None,
            },
            effort_value_yield: match json["effort"].as_u64().map(|i| u8::try_from(i)) {
                Some(i) => match i {
                    Ok(i) => i,
                    Err(_) => return None,
                },
                None => return None,
            },
        },
    ))
}

M src/lib.rs => src/lib.rs +124 -15
@@ 2,30 2,57 @@

pub mod pokedex {
    use minicbor::{Encode, Decode, bytes::ByteArray};

    
    #[derive(Encode, Decode, Debug)]
    pub struct Pokemon {
        #[n(0)] pub species_id: u8,
        #[n(1)] pub nickname: Option<[u8; 12]>,

        #[n(2)] pub level: u8,
        #[n(3)] pub xp: u16,
        #[n(5)] pub current_hp: u16,

        #[n(6)] pub hp: StatData,
        #[n(7)] pub attack: StatData,
        #[n(8)] pub defense: StatData,
        #[n(9)] pub special_attack: StatData,
        #[n(10)] pub special_defense: StatData,
        #[n(11)] pub speed: StatData,

        #[n(12)] pub moves: [Option<u16>; 4],
    }

    #[derive(Encode, Decode, Debug, Clone, Copy)]
    pub struct StatData {
        #[n(0)] pub value: u16,
        #[n(2)] pub effort_value: u16,
        #[n(3)] pub individual_value: u8,
    }

    #[derive(Encode, Decode, Debug)]
    pub struct PokemonSpecies {
        #[n(0)] pub id: u8,
        #[n(1)] pub name: [u8; 12],
        #[n(2)] pub type_primary: Type,
        #[n(3)] pub type_secondary: Option<Type>,
        #[n(14)] pub growth_rate: GrowthRate,

        #[n(4)] pub capture_rate: u8,
        #[n(5)] pub base_experience: u8,

        #[n(7)] pub hp: StatData,
        #[n(8)] pub attack: StatData,
        #[n(9)] pub defense: StatData,
        #[n(10)] pub special_attack: StatData,
        #[n(11)] pub special_defense: StatData,
        #[n(12)] pub speed: StatData,
        #[n(7)] pub hp: SpeciesStatData,
        #[n(8)] pub attack: SpeciesStatData,
        #[n(9)] pub defense: SpeciesStatData,
        #[n(10)] pub special_attack: SpeciesStatData,
        #[n(11)] pub special_defense: SpeciesStatData,
        #[n(12)] pub speed: SpeciesStatData,

        #[n(13)] pub sprite: ByteArray<578>,
    }

    #[derive(Encode, Decode, Debug)]
    #[cbor(index_only)]
    pub enum Stat {
    pub enum Stats {
        #[n(0)] Hp,
        #[n(1)] Attack,
        #[n(2)] Defense,


@@ 35,8 62,32 @@ pub mod pokedex {
    }

    #[derive(Encode, Decode, Debug, Clone, Copy)]
    pub struct StatData {
        #[n(1)] pub base_value: u16,
    pub struct SpeciesStatData {
        #[n(0)] pub base_value: u16,
        #[n(1)] pub effort_value_yield: u8,
    }

    #[derive(Encode, Decode, Debug, Clone, Copy)]
    pub enum GrowthRate {
        #[n(0)] ERRATIC,
        #[n(1)] FAST,
        #[n(2)] MEDIUM_FAST,
        #[n(3)] MEDIUM_SLOW,
        #[n(4)] SLOW,
        #[n(5)] FLUCTUATING
    }

    impl GrowthRate {
        fn xp_for_level(&self, level: u8) {
            match self {
                GrowthRate::ERRATIC => todo!(),
                GrowthRate::FAST => todo!(),
                GrowthRate::MEDIUM_FAST => todo!(),
                GrowthRate::MEDIUM_SLOW => todo!(),
                GrowthRate::SLOW => todo!(),
                GrowthRate::FLUCTUATING => todo!(),
            }
        }
    }

    // These could be created dynamically from the JSON data. This isn't so bad


@@ 103,16 154,14 @@ pub mod pokedex {

    #[derive(Encode, Decode, Debug, Copy, Clone)]
    pub enum LearnCondition {
        #[n(0)] LevelUp {
            #[n(0)] level: u8
        },
        #[n(0)] LevelUp(#[n(0)] u8),
        #[n(1)] Machine,
    }

    #[derive(Encode, Decode, Debug)]
    pub struct Move {
        #[n(0)] pub id: u16,
        #[n(1)] pub name: [char; 12],
        #[n(1)] pub name: [u8; 12],

        #[n(2)] pub type_: Type,
        #[n(3)] pub damage_class: DamageClass,


@@ 190,6 239,66 @@ pub mod pokedex {
    #[derive(Encode, Decode, Debug)]
    pub struct StatChange {
        #[n(0)] pub amount: u8,
        #[n(1)] pub stat: Stat,
        #[n(1)] pub stat: Stats,
    }
}

pub mod generation {
    use crate::pokedex::{Pokemon, PokemonSpecies, StatData, SpeciesStatData, StatChange, LearnableMove, LearnCondition};

    trait Random {
        fn random(&self) -> u8;

        fn generate_iv(&self) -> u8 {
            return self.random() & 0b00011111;
        }
    }

    pub fn generate_pokemon(species: PokemonSpecies, move_list: &mut dyn Iterator<Item = &LearnableMove>, level: u8, rng: &dyn Random) -> Pokemon {
        let mut moves = [None; 4];
        let mut i = 0;
        for m in move_list {
            if let LearnCondition::LevelUp(lvl) = m.condition {
                if lvl > level {
                    break;
                }
                moves[i] = Some(m.id);
                i = (i + 1) % 4;
            }
        }

        let hp_iv = rng.generate_iv();
        let hp = StatData {
            value: calculate_hp_stat(species.hp.base_value, 0, hp_iv, level),
            effort_value: 0,
            individual_value: hp_iv
        };
        return Pokemon{
            species_id: species.id,
            nickname: None,
            level,
            xp: species.growth_rate.xp_for_level(level),
            current_hp: hp.value,
            hp,
            attack: new_stat(species.attack, level, rng),
            defense: new_stat(species.defense, level, rng),
            special_attack: new_stat(species.special_attack, level, rng),
            special_defense: new_stat(species.special_defense, level, rng),
            speed: new_stat(species.speed, level, rng),
            moves
        }
    }

    fn new_stat(stat: SpeciesStatData, level: u8, rng: &dyn Random) -> StatData {
        let iv = rng.generate_iv();
        return StatData { value: calculate_stat(stat.base_value, 0, iv, level), effort_value: 0, individual_value: iv }
    }

    fn calculate_stat(base: u16, ev: u16, iv: u8, level: u8) -> u16 {
        return (((2 * base) + (iv as u16) + ev) * (level as u16) / 100) + 5
    }

    fn calculate_hp_stat(base: u16, ev: u16, iv: u8, level: u8) -> u16 {
        return (((2 * base) + (iv as u16) + ev) * (level as u16) / 100) + (level as u16) + 10
    }
}

M src/main.rs => src/main.rs +3 -3
@@ 22,7 22,7 @@ use esp8266_hal::time::{KiloHertz, Nanoseconds};
use esp8266_hal::timer::Timer1;
use minicbor::decode;
use panic_halt as _;
use pokemon::pokedex::Pokemon;
use pokemon::pokedex::PokemonSpecies;
use sh1106::{prelude::*, Builder};
use tinybmp::Bmp;



@@ 164,7 164,7 @@ fn main() -> ! {
    display.flush().ok();

    let mut pokemon_id = 1;
    let mut pokemon: Pokemon;
    let mut pokemon: PokemonSpecies;
    let mut has_drawn = false;
    loop {
        btn_left = update_buttons(


@@ 277,7 277,7 @@ const DATA_BUFFER_SIZE: usize = 2048;
#[derive(Debug)]
struct ReadError(&'static str);

fn read_pokemon(id: u8, flash: &mut ESPFlash) -> Result<Pokemon, ReadError> {
fn read_pokemon(id: u8, flash: &mut ESPFlash) -> Result<PokemonSpecies, ReadError> {
    // First read this id's row in the lookup table.
    let mut table_entry = [0u8; 8];
    let row_offset = match DATA_OFFSET.checked_add(u32::from(id) * 8) {