From aa68942d84f628d80133f3323b13e109fdb1cca2 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Tue, 9 Feb 2021 14:42:09 -0600 Subject: [PATCH] feat: implement rate-up --- Cargo.toml | 2 +- src/banner.rs | 183 ++++++++++++++++------ src/gacha.rs | 32 +++- src/i18n.rs | 18 +++ src/main.rs | 188 ++++------------------- src/student.rs | 15 +- students.json | 407 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 627 insertions(+), 218 deletions(-) create mode 100644 students.json diff --git a/Cargo.toml b/Cargo.toml index f178060..e365117 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rand = "^0.8" +rand = { version = "^0.8", features = ["alloc"] } serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" serde_repr = "^0.1" diff --git a/src/banner.rs b/src/banner.rs index f7ca8c2..4c87d2e 100644 --- a/src/banner.rs +++ b/src/banner.rs @@ -1,24 +1,29 @@ -use std::convert::TryInto; - -use rand::{prelude::SliceRandom, Rng}; - -use crate::{ - gacha::Rarity, - student::{self, Student}, -}; -use crate::{ - gacha::{Gacha, Recruitment}, - i18n::{I18nString, Language}, -}; +use crate::gacha::{Gacha, Rarity, Recruitment}; +use crate::i18n::{I18nString, Language}; +use crate::student::Student; +use rand::distributions::{Distribution, WeightedIndex}; +use rand::Rng; +use std::convert::{TryFrom, TryInto}; +/// Used to Construct a Banner #[derive(Debug, Default)] pub struct BannerBuilder { name: I18nString, - rates: Option, + gacha: Option, sparkable: Option>, } impl BannerBuilder { + /// Creates a new instance of a BannerBuilder + /// + /// # Arguments + /// * `jpn_name` - The name of the Banner as seen in Blue Archive + /// + /// # Examples + /// ``` + /// # use ba_gacha::banner::BannerBuilder; + /// let banner_builder = BannerBuilder::new("ピックアップ募集"); + /// ``` pub fn new(jpn_name: &str) -> Self { Self { name: I18nString::new(jpn_name), @@ -26,21 +31,58 @@ impl BannerBuilder { } } + /// Adds a Translation to the internal I18nString + /// + /// # Arguments + /// * `language` - The target language. + /// * `name` - The name of the Banner in the target language. + /// + /// # Examples + /// ``` + /// # use ba_gacha::banner::BannerBuilder; + /// # use ba_gacha::i18n::Language; + /// let banner_builder = BannerBuilder::new("ピックアップ募集") + /// .with_name_translation(Language::English, "Focus Recruitment"); + /// ``` 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 { + /// Adds an instance of a Gacha struct + /// + /// # Arguments + /// * `gacha` - An instance of a Gacha struct + /// + /// # Examples + /// ``` + /// # use ba_gacha::gacha::GachaBuilder; + /// # use ba_gacha::banner::BannerBuilder; + /// let gacha = GachaBuilder::default() + /// .with_pool(Vec::new()) + /// .finish().unwrap(); + /// + /// let banner_builder = BannerBuilder::new("ピックアップ募集") + /// .with_gacha(&gacha); + /// ``` + pub fn with_gacha(self, gacha: &Gacha) -> Self { Self { - rates: Some(rates.to_owned()), + gacha: Some(gacha.to_owned()), ..self } } - pub fn with_sparkable_students(self, students: Vec) -> Self { + /// Adds a vector containing all sparkable students in the current Banner + /// + /// # Arguments + /// * `students` - A Vector of Students which are sparkable + /// + /// # Examples + /// ``` + /// # use ba_gacha::banner::BannerBuilder; + pub fn with_sparkable_students(self, students: &[Student]) -> Self { Self { - sparkable: Some(students), + sparkable: Some(students.to_vec()), ..self } } @@ -48,56 +90,99 @@ impl BannerBuilder { pub fn finish(self) -> Option { Some(Banner { name: self.name, - rates: self.rates?, - sparkable: self.sparkable?, + gacha: self.gacha?, + sparkable: self.sparkable, + }) + } +} + +#[derive(Debug, Clone, Copy)] +enum StudentType { + One = 1, // One Star + Two, // Two Stars + Three, // Three Stars + Priority, // Rate-up Student (Presumably 3*) +} + +impl From for StudentType { + fn from(rarity: Rarity) -> Self { + match rarity { + Rarity::One => Self::One, + Rarity::Two => Self::Two, + Rarity::Three => Self::Three, + } + } +} + +impl TryFrom for Rarity { + type Error = &'static str; + + fn try_from(value: StudentType) -> Result { + Ok(match value { + StudentType::One => Self::One, + StudentType::Two => Self::Two, + StudentType::Three => Self::Three, + StudentType::Priority => return Err("Can not convert from Priority to Rarity"), }) } } pub struct Banner { pub name: I18nString, - rates: Gacha, - sparkable: Vec, // FIXME: Can we safely assume this will only contain 2 studnets? + gacha: Gacha, + sparkable: Option>, } impl Banner { - fn get_random_student(&self, rarity: Rarity) -> Student { + fn get_random_student(&self) -> Student { let mut rng = rand::thread_rng(); + let priority_rate = self.gacha.priority.as_ref().map_or(0, |tuple| tuple.1); + let three_star_rate = self.gacha.get_rate(Rarity::Three) - priority_rate; - let mut filtered_students: Vec<&Student> = self - .rates - .pool + let items: [(StudentType, usize); 4] = [ + (StudentType::One, self.gacha.get_rate(Rarity::One)), + (StudentType::Two, self.gacha.get_rate(Rarity::Two)), + (StudentType::Three, three_star_rate), + (StudentType::Priority, priority_rate), + ]; + + let dist = WeightedIndex::new(items.iter().map(|item| item.1)).unwrap(); + let students = &self.gacha.pool; + + match items[dist.sample(&mut rng)] { + (StudentType::Priority, _) => { + let priority_students = &self.gacha.priority.as_ref().unwrap().0; + + let index: usize = rng.gen_range(0..priority_students.len()); + priority_students[index].clone() + } + (rarity, _) => { + let students: Vec<&Student> = students + .iter() + .filter(|student| student.rarity == rarity.try_into().unwrap()) + .collect(); + + let index: usize = rng.gen_range(0..students.len()); + students[index].clone() + } + } + } + + fn get_random_student_of_rarity(&self, rarity: Rarity) -> Student { + let students = &self.gacha.pool; + let two_star_students: Vec<&Student> = students .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 - } + let index = rand::thread_rng().gen_range(0..two_star_students.len()); + two_star_students[index].clone() } } impl Recruitment for Banner { fn roll(&self) -> Student { - self.get_random_student(self.get_random_rarity()) + self.get_random_student() } fn roll10(&self) -> [Student; 10] { @@ -105,14 +190,14 @@ impl Recruitment for Banner { // Fill students with 10 random students for student in students.iter_mut() { - *student = self.get_random_student(self.get_random_rarity()); + *student = self.get_random_student() } 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[students.len() - 1] = self.get_random_student_of_rarity(Rarity::Two); } } diff --git a/src/gacha.rs b/src/gacha.rs index dde1fca..2c70e0c 100644 --- a/src/gacha.rs +++ b/src/gacha.rs @@ -43,6 +43,7 @@ pub trait Recruitment { pub struct GachaBuilder { rates: Option<(usize, usize, usize)>, pool: Option>, + priority: Option<(Vec, usize)>, } impl Default for GachaBuilder { @@ -50,6 +51,7 @@ impl Default for GachaBuilder { Self { rates: Some((790, 185, 25)), pool: Default::default(), + priority: Default::default(), } } } @@ -78,7 +80,7 @@ impl GachaBuilder { Self { rates: Some((one, two, three)), - pool: Default::default(), + ..Default::default() } } @@ -103,6 +105,29 @@ impl GachaBuilder { } } + /// Attaches a pool of Students who have increased rates + /// + /// # Arugments + /// * `students` - A Vector of Students who have increased rates + /// * `rate` - The rate of the students in the previous argument + /// + /// # 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::new(79.0, 18.5, 2.5) + /// .with_pool(vec![aru, hina.clone()]) + /// .with_priority(vec![hina], 3.5); + /// ``` + pub fn with_priority(self, students: &[Student], total_rate: f32) -> Self { + Self { + priority: Some((students.to_vec(), (total_rate * 10.0) as usize)), + ..self + } + } + /// Consumes a GachaBuilder and retuns a Gacha Struct. /// /// Will return `None` if the `rates` or `pool` property of @@ -122,6 +147,7 @@ impl GachaBuilder { Some(Gacha { rates: self.rates?, pool: self.pool?, + priority: self.priority, }) } } @@ -130,8 +156,10 @@ impl GachaBuilder { /// 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 + /// (1★, 2★, 3★) + pub rates: (usize, usize, usize), pub pool: Vec, + pub priority: Option<(Vec, usize)>, } impl Gacha { diff --git a/src/i18n.rs b/src/i18n.rs index 8c46f03..ea0ba2f 100644 --- a/src/i18n.rs +++ b/src/i18n.rs @@ -7,6 +7,24 @@ pub struct I18nString { translations: HashMap, } +impl PartialEq for I18nString { + fn eq(&self, other: &Self) -> bool { + self.get(Language::Japanese).unwrap() == other.get(Language::Japanese).unwrap() + } +} + +impl PartialEq for I18nString { + fn eq(&self, other: &String) -> bool { + self.get(Language::Japanese).unwrap() == *other + } +} + +impl PartialEq<&str> for I18nString { + fn eq(&self, other: &&str) -> bool { + self.get(Language::Japanese).unwrap() == *other + } +} + 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()) diff --git a/src/main.rs b/src/main.rs index 2dbb808..6ff69ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,174 +1,46 @@ -use std::io::Write; - -use ba_gacha::{ - banner::BannerBuilder, - gacha::{GachaBuilder, Rarity, Recruitment}, - i18n::Language, - student::Student, -}; +use ba_gacha::banner::BannerBuilder; +use ba_gacha::gacha::{GachaBuilder, Recruitment}; +use ba_gacha::i18n::Language; +use ba_gacha::student::Student; +use std::{fs::File, io::Read}; 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. + let mut file = File::open("students.json").unwrap(); + let mut json = String::new(); + file.read_to_string(&mut json).unwrap(); - // Create a Struct representing Hoshino - let mut hoshino = Student::new("ホシノ", Rarity::Three); - hoshino.add_translation(Language::English, "Hoshino"); + let students: Vec = serde_json::from_str(&json).unwrap(); - // Create a Vector Containing all the Sparkable Students - let sparkable_students = vec![shiroko, hoshino]; + let banner_students: Vec = students + .iter() + .filter(|student| student.name != "ノゾミ") + .map(|student| student.clone()) + .collect(); - let rates = GachaBuilder::new(79.0, 18.5, 2.5) - .with_pool(pickup_banner_2021_02_11()) + let hoshino = find_student(&students, "ホシノ").unwrap(); + let shiroko = find_student(&students, "シロコ").unwrap(); + let rate_up_students = vec![shiroko, hoshino]; + + let gacha = GachaBuilder::new(79.0, 18.5, 2.5) + .with_pool(banner_students) + .with_priority(&rate_up_students, 0.7) .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) + .with_name_translation(Language::English, "Rate-Up Registration") + .with_sparkable_students(&rate_up_students) + .with_gacha(&gacha) .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(); + // let student = pickup_banner.roll(); + // let students = pickup_banner.roll10(); } -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)); - +pub fn find_student(students: &[Student], jpn_name: &str) -> Option { students + .iter() + .find(|student| student.name == jpn_name) + .map(|student| student.clone()) } diff --git a/src/student.rs b/src/student.rs index c2b5251..b1e9b30 100644 --- a/src/student.rs +++ b/src/student.rs @@ -1,6 +1,5 @@ use crate::gacha::Rarity; -use crate::i18n::I18nString; -use crate::i18n::Language; +use crate::i18n::{I18nString, Language}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -19,16 +18,16 @@ impl std::fmt::Display for Student { 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 { @@ -39,17 +38,17 @@ impl Student { } /// 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"); /// ``` diff --git a/students.json b/students.json new file mode 100644 index 0000000..ce7f943 --- /dev/null +++ b/students.json @@ -0,0 +1,407 @@ +[ + { + "name": { + "translations": { + "jpn": "ヒナ", + "eng": "Hina" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "jpn": "イオリ", + "eng": "Iori" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "jpn": "ハルナ", + "eng": "Haruna" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "jpn": "イズミ", + "eng": "Izumi" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "eng": "Aru", + "jpn": "アル" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "jpn": "スミレ", + "eng": "Sumire" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "jpn": "エイミ", + "eng": "Iemi" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "eng": "Karin", + "jpn": "カリン" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "jpn": "ネル", + "eng": "Neru" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "eng": "Maki", + "jpn": "マキ" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "eng": "Hibiki", + "jpn": "ヒビキ" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "eng": "Saya", + "jpn": "サヤ" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "eng": "Shun", + "jpn": "シュン" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "eng": "Shiroko", + "jpn": "シロコ" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "eng": "Hoshino", + "jpn": "ホシノ" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "jpn": "ヒフミ", + "eng": "Hifumi" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "eng": "Tsurugi", + "jpn": "ツルギ" + } + }, + "rarity": 3 + }, + { + "name": { + "translations": { + "jpn": "アカリ", + "eng": "Akari" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "jpn": "ジュンコ", + "eng": "Junko" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "jpn": "ムツキ", + "eng": "Mutsuki" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "eng": "Kayoko", + "jpn": "カヨコ" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "jpn": "フウカ", + "eng": "Fuuka" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "jpn": "ユウカ", + "eng": "Yuuka" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "eng": "Akane", + "jpn": "アカネ" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "jpn": "ハル", + "eng": "Haru" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "eng": "Utaha", + "jpn": "ウタハ" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "jpn": "チセ", + "eng": "Chise" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "eng": "Tsubaki", + "jpn": "ツバキ" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "jpn": "セリカ", + "eng": "Serika" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "jpn": "アヤネ", + "eng": "Ayane" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "eng": "Hasumi", + "jpn": "ハスミ" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "eng": "Hanae", + "jpn": "ハナエ" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "eng": "Airi", + "jpn": "アイリ" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "jpn": "ノゾミ", + "eng": "Nozomi" + } + }, + "rarity": 2 + }, + { + "name": { + "translations": { + "eng": "Chinatsu", + "jpn": "チナツ" + } + }, + "rarity": 1 + }, + { + "name": { + "translations": { + "eng": "Haruka", + "jpn": "ハルカ" + } + }, + "rarity": 1 + }, + { + "name": { + "translations": { + "jpn": "ジュリ", + "eng": "Juri" + } + }, + "rarity": 1 + }, + { + "name": { + "translations": { + "jpn": "コタマ", + "eng": "Kotama" + } + }, + "rarity": 1 + }, + { + "name": { + "translations": { + "jpn": "アスナ", + "eng": "Asuna" + } + }, + "rarity": 1 + }, + { + "name": { + "translations": { + "jpn": "コトリ", + "eng": "Kotori" + } + }, + "rarity": 1 + }, + { + "name": { + "translations": { + "jpn": "フィーナ", + "eng": "Pina" + } + }, + "rarity": 1 + }, + { + "name": { + "translations": { + "jpn": "スズミ", + "eng": "Suzumi" + } + }, + "rarity": 1 + }, + { + "name": { + "translations": { + "eng": "Shimiko", + "jpn": "シミコ" + } + }, + "rarity": 1 + }, + { + "name": { + "translations": { + "eng": "Serina", + "jpn": "セリナ" + } + }, + "rarity": 1 + }, + { + "name": { + "translations": { + "eng": "Yoshimi", + "jpn": "ヨシミ" + } + }, + "rarity": 1 + } +] \ No newline at end of file