commit 4962ee3d7951be58d1dc5a3b68dc4bcf1ef7663e Author: Rekai Musuka Date: Mon Feb 8 22:33:35 2021 -0600 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..bf8229c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,175 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ba-gacha" +version = "0.1.0" +dependencies = [ + "rand", + "serde", + "serde_json", + "serde_repr", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "libc" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f178060 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ba-gacha" +version = "0.1.0" +authors = ["Rekai Musuka "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "^0.8" +serde = { version = "^1.0", features = ["derive"] } +serde_json = "^1.0" +serde_repr = "^0.1" diff --git a/src/banner.rs b/src/banner.rs new file mode 100644 index 0000000..f7ca8c2 --- /dev/null +++ b/src/banner.rs @@ -0,0 +1,121 @@ +use std::convert::TryInto; + +use rand::{prelude::SliceRandom, Rng}; + +use crate::{ + gacha::Rarity, + student::{self, Student}, +}; +use crate::{ + gacha::{Gacha, Recruitment}, + i18n::{I18nString, Language}, +}; + +#[derive(Debug, Default)] +pub struct BannerBuilder { + name: I18nString, + rates: Option, + sparkable: Option>, +} + +impl BannerBuilder { + pub fn new(jpn_name: &str) -> Self { + Self { + name: I18nString::new(jpn_name), + ..Default::default() + } + } + + pub fn with_name_translation(mut self, language: Language, name: &str) -> Self { + self.name.update(language, name); + self + } + + pub fn with_rates(self, rates: &Gacha) -> Self { + Self { + rates: Some(rates.to_owned()), + ..self + } + } + + pub fn with_sparkable_students(self, students: Vec) -> Self { + Self { + sparkable: Some(students), + ..self + } + } + + pub fn finish(self) -> Option { + Some(Banner { + name: self.name, + rates: self.rates?, + sparkable: self.sparkable?, + }) + } +} + +pub struct Banner { + pub name: I18nString, + rates: Gacha, + sparkable: Vec, // FIXME: Can we safely assume this will only contain 2 studnets? +} + +impl Banner { + fn get_random_student(&self, rarity: Rarity) -> Student { + let mut rng = rand::thread_rng(); + + let mut filtered_students: Vec<&Student> = self + .rates + .pool + .iter() + .filter(|student| student.rarity == rarity) + .collect(); + + filtered_students.shuffle(&mut rng); + let index: usize = rng.gen_range(0..filtered_students.len()); + + filtered_students[index].clone() + } + + fn get_random_rarity(&self) -> Rarity { + let mut rng = rand::thread_rng(); + let random_num: usize = rng.gen_range(0..1000); + + let one_rate = self.rates.get_rate(Rarity::One); + let two_rate = self.rates.get_rate(Rarity::Two); + let three_rate = self.rates.get_rate(Rarity::Three); + + if random_num < three_rate { + Rarity::Three + } else if random_num < three_rate + two_rate { + Rarity::Two + } else { + Rarity::One + } + } +} + +impl Recruitment for Banner { + fn roll(&self) -> Student { + self.get_random_student(self.get_random_rarity()) + } + + fn roll10(&self) -> [Student; 10] { + let mut students: [Student; 10] = vec![Default::default(); 10].try_into().unwrap(); + + // Fill students with 10 random students + for student in students.iter_mut() { + *student = self.get_random_student(self.get_random_rarity()); + } + + let two_star_present = students.iter().any(|student| student.rarity == Rarity::Two); + + if !two_star_present { + if students[students.len() - 1].rarity != Rarity::Three { + students[students.len() - 1] = self.get_random_student(Rarity::Two); + } + } + + students + } +} diff --git a/src/gacha.rs b/src/gacha.rs new file mode 100644 index 0000000..dde1fca --- /dev/null +++ b/src/gacha.rs @@ -0,0 +1,161 @@ +use crate::student::Student; +use serde_repr::{Deserialize_repr, Serialize_repr}; +#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq)] +#[repr(u8)] +/// The Available Rarities in Blue Archive's Gacha System +pub enum Rarity { + One = 1, + Two, + Three, +} + +impl std::fmt::Display for Rarity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + Rarity::One => f.write_str("1★"), + Rarity::Two => f.write_str("2★"), + Rarity::Three => f.write_str("3★"), + } + } +} + +impl Default for Rarity { + fn default() -> Self { + Self::One + } +} + +/// Recruitment is a trait that consists of two methods +/// Representing single and 10-rolls +/// +/// Every Banner is expected to implement the Recruitment trait +pub trait Recruitment { + fn roll(&self) -> Student; + fn roll10(&self) -> [Student; 10]; +} + +/// Gacha Structs are built using this GachaBuilder Class +/// +/// By Default, GachaBuilder assumes the rates: +/// * 1★: 79.0% +/// * 2★: 18.5% +/// * 3★: 2.5% +pub struct GachaBuilder { + rates: Option<(usize, usize, usize)>, + pool: Option>, +} + +impl Default for GachaBuilder { + fn default() -> Self { + Self { + rates: Some((790, 185, 25)), + pool: Default::default(), + } + } +} + +impl GachaBuilder { + /// Creates a new instance of a GachaBuilder. + /// + /// + /// # Arguments + /// * `one` - The percent chance of pulling a 1★ Student + /// * `two` - The percent chance of pulling a 2★ Student + /// * `three` - The percent chance of pulling a 3★ Student + /// + /// # Examples + /// ``` + /// # use ba_gacha::gacha::GachaBuilder; + /// let gacha_builder: GachaBuilder = GachaBuilder::new(79.0, 18.5, 2.5) + /// .with_pool(Vec::new()); + /// ``` + pub fn new(one: f32, two: f32, three: f32) -> Self { + let one = (one * 10.0) as usize; + let two = (two * 10.0) as usize; + let three = (three * 10.0) as usize; + + assert_eq!(one + two + three, 1000); + + Self { + rates: Some((one, two, three)), + pool: Default::default(), + } + } + + /// Attaches a Student Gacha Pool to the GachaBuilder + /// + /// # Arguments + /// * `students` - A Vector containing every single pullable student in the Gacha + /// + /// # Examples + /// ``` + /// # use ba_gacha::gacha::{GachaBuilder, Rarity}; + /// # use ba_gacha::student::Student; + /// + /// let aru = Student::new("アル", Rarity::Three); + /// let hina = Student::new("ヒナ", Rarity::Three); + /// let gacha_builder = GachaBuilder::default().with_pool(vec![aru, hina]); + /// ``` + pub fn with_pool(self, students: Vec) -> Self { + Self { + pool: Some(students), + ..self + } + } + + /// Consumes a GachaBuilder and retuns a Gacha Struct. + /// + /// Will return `None` if the `rates` or `pool` property of + /// GachaBuilder have not been set. + /// + /// # Examples + /// ``` + /// # use ba_gacha::gacha::{GachaBuilder, Gacha, Rarity}; + /// # use ba_gacha::student::Student; + /// let aru = Student::new("アル", Rarity::Three); + /// let hina = Student::new("ヒナ", Rarity::Three); + /// let gacha = GachaBuilder::default() + /// .with_pool(vec![aru, hina]) + /// .finish().unwrap(); + /// ``` + pub fn finish(self) -> Option { + Some(Gacha { + rates: self.rates?, + pool: self.pool?, + }) + } +} + +/// Provides the necessary information to facilitate a "pull", which is +/// to randomly select a Student from the gacha pool +#[derive(Debug, Default, Clone)] +pub struct Gacha { + pub rates: (usize, usize, usize), // One Star, Two Star, Three Star + pub pool: Vec, +} + +impl Gacha { + /// Returns a usize representing the percent chance of pulling a specific rarity + /// (in terms of 1000) + /// + /// # Arguments + /// * `rarity` - The Rarity who's gacha pull rate will be returned + /// + /// # Examples + /// ``` + /// # use ba_gacha::gacha::{GachaBuilder, Rarity}; + /// let gacha = GachaBuilder::new(79.0, 18.5, 2.5) + /// .with_pool(Vec::new()) + /// .finish() + /// .unwrap(); + /// + /// assert_eq!(gacha.get_rate(Rarity::One), 790); + /// ``` + pub fn get_rate(&self, rarity: Rarity) -> usize { + match rarity { + Rarity::One => self.rates.0, + Rarity::Two => self.rates.1, + Rarity::Three => self.rates.2, + } + } +} diff --git a/src/i18n.rs b/src/i18n.rs new file mode 100644 index 0000000..8c46f03 --- /dev/null +++ b/src/i18n.rs @@ -0,0 +1,60 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// A String-like Type which allows for easy Internationalization +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct I18nString { + translations: HashMap, +} + +impl std::fmt::Display for I18nString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.translations.get(&Language::Japanese).unwrap().as_str()) + } +} + +impl Default for I18nString { + fn default() -> Self { + let mut map: HashMap = Default::default(); + let _ = map.insert(Language::Japanese, "".to_string()); + Self { translations: map } + } +} + +impl I18nString { + /// Blue Archive is a Japanese Game, therefore I18nString's constructor + /// expects Japanese text. + pub fn new(jpn_message: &str) -> Self { + let mut map: HashMap = Default::default(); + let _ = map.insert(Language::Japanese, jpn_message.to_string()); + + Self { translations: map } + } + + /// This method wraps HashMap's insert, which means that: + /// * If the Language doesn't already have a translation, one will be added + /// * If a translation for the Language already exists, it will be replaced. + pub fn update(&mut self, language: Language, message: &str) { + let _ = self.translations.insert(language, message.to_string()); + } + + /// Get a Language's translation as an Owned String + /// + /// Will return None if there is no translation for the given language + pub fn get(&self, language: Language) -> Option { + self.translations + .get(&language) + .map(|message| message.clone()) + } +} + +/// This enum represents all Languages this Gacha Simulator **must** support. +/// +/// This enum follows ISO-639-2/T +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] +pub enum Language { + #[serde(rename = "eng")] + English, + #[serde(rename = "jpn")] + Japanese, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f7ab0d8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +pub mod banner; +pub mod gacha; +pub mod i18n; +pub mod student; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2dbb808 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,174 @@ +use std::io::Write; + +use ba_gacha::{ + banner::BannerBuilder, + gacha::{GachaBuilder, Rarity, Recruitment}, + i18n::Language, + student::Student, +}; + +fn main() { + // Create a Struct representing Shiroko + let mut shiroko = Student::new("シロコ", Rarity::Three); + shiroko.add_translation(Language::English, "Shiroko"); // Yes, this is a translation. + + // Create a Struct representing Hoshino + let mut hoshino = Student::new("ホシノ", Rarity::Three); + hoshino.add_translation(Language::English, "Hoshino"); + + // Create a Vector Containing all the Sparkable Students + let sparkable_students = vec![shiroko, hoshino]; + + let rates = GachaBuilder::new(79.0, 18.5, 2.5) + .with_pool(pickup_banner_2021_02_11()) + .finish() + .unwrap(); + + // Create the Banner Itself + let pickup_banner = BannerBuilder::new("ピックアップ募集") + .with_name_translation(Language::English, "Priority") + .with_sparkable_students(sparkable_students) + .with_rates(&rates) + .finish() + .unwrap(); + + // Roll Banner 1 Time + let student = pickup_banner.roll(); + + // Roll Banner 10 Times + let students = pickup_banner.roll10(); + + // Display Results + println!("{} Single: {}", pickup_banner.name, student); + println!(); + + println!("{} 10-roll:", pickup_banner.name); + for student in students.iter() { + println!("{}", student); + } + + // let mut file = std::fs::File::create("students.json").unwrap(); + // let students = all_students(); + // let json = serde_json::to_string(&students).unwrap(); + // file.write_all(json.as_bytes()).unwrap(); +} + +fn create_student(jpn_name: &str, eng_name: &str, rarity: Rarity) -> Student { + let mut student = Student::new(jpn_name, rarity); + student.add_translation(Language::English, eng_name); + + student +} + +fn pickup_banner_2021_02_11() -> Vec { + let mut test: Vec = vec![]; + + // 3 Star + test.push(create_student("ヒナ", "Hina", Rarity::Three)); + test.push(create_student("イオリ", "Iori", Rarity::Three)); + test.push(create_student("ハルナ", "Haruna", Rarity::Three)); + test.push(create_student("イズミ", "Izumi", Rarity::Three)); + test.push(create_student("アル", "Aru", Rarity::Three)); + test.push(create_student("スミレ", "Sumire", Rarity::Three)); + test.push(create_student("エイミ", "Iemi", Rarity::Three)); + test.push(create_student("カリン", "Karin", Rarity::Three)); + test.push(create_student("ネル", "Neru", Rarity::Three)); + test.push(create_student("マキ", "Maki", Rarity::Three)); + test.push(create_student("ヒビキ", "Hibiki", Rarity::Three)); + test.push(create_student("サヤ", "Saya", Rarity::Three)); + test.push(create_student("シュン", "Shun", Rarity::Three)); + test.push(create_student("シロコ", "Shiroko", Rarity::Three)); + test.push(create_student("ホシノ", "Hoshino", Rarity::Three)); + test.push(create_student("ヒフミ", "Hifumi", Rarity::Three)); + test.push(create_student("ツルギ", "Tsurugi", Rarity::Three)); + + // Two Star + test.push(create_student("アカリ", "Akari", Rarity::Two)); + test.push(create_student("ジュンコ", "Junko", Rarity::Two)); + test.push(create_student("ムツキ", "Mutsuki", Rarity::Two)); + test.push(create_student("カヨコ", "Kayoko", Rarity::Two)); + test.push(create_student("フウカ", "Fuuka", Rarity::Two)); + test.push(create_student("ユウカ", "Yuuka", Rarity::Two)); + test.push(create_student("アカネ", "Akane", Rarity::Two)); + test.push(create_student("ハル", "Haru", Rarity::Two)); + test.push(create_student("ウタハ", "Utaha", Rarity::Two)); + test.push(create_student("チセ", "Chise", Rarity::Two)); + test.push(create_student("ツバキ", "Tsubaki", Rarity::Two)); + test.push(create_student("セリカ", "Serika", Rarity::Two)); + test.push(create_student("アヤネ", "Ayane", Rarity::Two)); + test.push(create_student("ハスミ", "Hasumi", Rarity::Two)); + test.push(create_student("ハナエ", "Hanae", Rarity::Two)); + test.push(create_student("アイリ", "Airi", Rarity::Two)); + + // 1 Star: + test.push(create_student("チナツ", "Chinatsu", Rarity::One)); + test.push(create_student("ハルカ", "Haruka", Rarity::One)); + test.push(create_student("ジュリ", "Juri", Rarity::One)); + test.push(create_student("コタマ", "Kotama", Rarity::One)); + test.push(create_student("アスナ", "Asuna", Rarity::One)); + test.push(create_student("コトリ", "Kotori", Rarity::One)); + test.push(create_student("フィーナ", "Pina", Rarity::One)); + test.push(create_student("スズミ", "Suzumi", Rarity::One)); + test.push(create_student("シミコ", "Shimiko", Rarity::One)); + test.push(create_student("セリナ", "Serina", Rarity::One)); + test.push(create_student("ヨシミ", "Yoshimi", Rarity::One)); + + test +} + +fn all_students() -> Vec { + let mut students: Vec = Vec::with_capacity(45); + + // 3 Star + students.push(create_student("ヒナ", "Hina", Rarity::Three)); + students.push(create_student("イオリ", "Iori", Rarity::Three)); + students.push(create_student("ハルナ", "Haruna", Rarity::Three)); + students.push(create_student("イズミ", "Izumi", Rarity::Three)); + students.push(create_student("アル", "Aru", Rarity::Three)); + students.push(create_student("スミレ", "Sumire", Rarity::Three)); + students.push(create_student("エイミ", "Iemi", Rarity::Three)); + students.push(create_student("カリン", "Karin", Rarity::Three)); + students.push(create_student("ネル", "Neru", Rarity::Three)); + students.push(create_student("マキ", "Maki", Rarity::Three)); + students.push(create_student("ヒビキ", "Hibiki", Rarity::Three)); + students.push(create_student("サヤ", "Saya", Rarity::Three)); + students.push(create_student("シュン", "Shun", Rarity::Three)); + students.push(create_student("シロコ", "Shiroko", Rarity::Three)); + students.push(create_student("ホシノ", "Hoshino", Rarity::Three)); + students.push(create_student("ヒフミ", "Hifumi", Rarity::Three)); + students.push(create_student("ツルギ", "Tsurugi", Rarity::Three)); + + // Two Star + students.push(create_student("アカリ", "Akari", Rarity::Two)); + students.push(create_student("ジュンコ", "Junko", Rarity::Two)); + students.push(create_student("ムツキ", "Mutsuki", Rarity::Two)); + students.push(create_student("カヨコ", "Kayoko", Rarity::Two)); + students.push(create_student("フウカ", "Fuuka", Rarity::Two)); + students.push(create_student("ユウカ", "Yuuka", Rarity::Two)); + students.push(create_student("アカネ", "Akane", Rarity::Two)); + students.push(create_student("ハル", "Haru", Rarity::Two)); + students.push(create_student("ウタハ", "Utaha", Rarity::Two)); + students.push(create_student("チセ", "Chise", Rarity::Two)); + students.push(create_student("ツバキ", "Tsubaki", Rarity::Two)); + students.push(create_student("セリカ", "Serika", Rarity::Two)); + students.push(create_student("アヤネ", "Ayane", Rarity::Two)); + students.push(create_student("ハスミ", "Hasumi", Rarity::Two)); + students.push(create_student("ハナエ", "Hanae", Rarity::Two)); + students.push(create_student("アイリ", "Airi", Rarity::Two)); + students.push(create_student("ノゾミ", "Nozomi", Rarity::Two)); // Not a part of Gacha + + // 1 Star: + students.push(create_student("チナツ", "Chinatsu", Rarity::One)); + students.push(create_student("ハルカ", "Haruka", Rarity::One)); + students.push(create_student("ジュリ", "Juri", Rarity::One)); + students.push(create_student("コタマ", "Kotama", Rarity::One)); + students.push(create_student("アスナ", "Asuna", Rarity::One)); + students.push(create_student("コトリ", "Kotori", Rarity::One)); + students.push(create_student("フィーナ", "Pina", Rarity::One)); + students.push(create_student("スズミ", "Suzumi", Rarity::One)); + students.push(create_student("シミコ", "Shimiko", Rarity::One)); + students.push(create_student("セリナ", "Serina", Rarity::One)); + students.push(create_student("ヨシミ", "Yoshimi", Rarity::One)); + + students +} diff --git a/src/student.rs b/src/student.rs new file mode 100644 index 0000000..c2b5251 --- /dev/null +++ b/src/student.rs @@ -0,0 +1,59 @@ +use crate::gacha::Rarity; +use crate::i18n::I18nString; +use crate::i18n::Language; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +/// A Student which has just the information necessary for gacha associated with it +pub struct Student { + /// There is guaranteed to be a Japanese Name available at all times + pub name: I18nString, + pub rarity: Rarity, +} + +impl std::fmt::Display for Student { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}\t{}", self.name, self.rarity) + } +} + +impl Student { + /// Creates a new Instance of a Student + /// + /// # Arguments + /// * `jpn_name` - The name of the Student from the Japanese Version of Blue Archive + /// * `rarity` - The Rarity of the Student + /// + /// # Examples + /// ``` + /// # use ba_gacha::gacha::Rarity; + /// # use ba_gacha::student::Student; + /// + /// let mutsuki = Student::new("ムツキ", Rarity::Two); + /// ``` + pub fn new(jpn_name: &str, rarity: Rarity) -> Self { + Self { + name: I18nString::new(jpn_name), + rarity, + } + } + + /// Adds a new Translation to the internal [`I18nString`]. + /// + /// # Arguments + /// * `language` - The Language of the translation to be added + /// * `name` - The Translated name, in the language of the previous argument. + /// + /// # Examples + /// ``` + /// # use ba_gacha::gacha::Rarity; + /// # use ba_gacha::student::Student; + /// # use ba_gacha::i18n::Language; + /// + /// let mut mutsuki = Student::new("ムツキ", Rarity::Two); + /// mutsuki.add_translation(Language::English, "Mutsuki"); + /// ``` + pub fn add_translation(&mut self, language: Language, name: &str) { + self.name.update(language, name); + } +}