feat: implement rate-up

This commit is contained in:
Rekai Nyangadzayi Musuka 2021-02-09 14:42:09 -06:00
parent 4962ee3d79
commit aa68942d84
7 changed files with 627 additions and 218 deletions

View File

@ -7,7 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
rand = "^0.8" rand = { version = "^0.8", features = ["alloc"] }
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0" serde_json = "^1.0"
serde_repr = "^0.1" serde_repr = "^0.1"

View File

@ -1,24 +1,29 @@
use std::convert::TryInto; use crate::gacha::{Gacha, Rarity, Recruitment};
use crate::i18n::{I18nString, Language};
use rand::{prelude::SliceRandom, Rng}; use crate::student::Student;
use rand::distributions::{Distribution, WeightedIndex};
use crate::{ use rand::Rng;
gacha::Rarity, use std::convert::{TryFrom, TryInto};
student::{self, Student},
};
use crate::{
gacha::{Gacha, Recruitment},
i18n::{I18nString, Language},
};
/// Used to Construct a Banner
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct BannerBuilder { pub struct BannerBuilder {
name: I18nString, name: I18nString,
rates: Option<Gacha>, gacha: Option<Gacha>,
sparkable: Option<Vec<Student>>, sparkable: Option<Vec<Student>>,
} }
impl BannerBuilder { 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 { pub fn new(jpn_name: &str) -> Self {
Self { Self {
name: I18nString::new(jpn_name), 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 { pub fn with_name_translation(mut self, language: Language, name: &str) -> Self {
self.name.update(language, name); self.name.update(language, name);
self 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 { Self {
rates: Some(rates.to_owned()), gacha: Some(gacha.to_owned()),
..self ..self
} }
} }
pub fn with_sparkable_students(self, students: Vec<Student>) -> 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 { Self {
sparkable: Some(students), sparkable: Some(students.to_vec()),
..self ..self
} }
} }
@ -48,56 +90,99 @@ impl BannerBuilder {
pub fn finish(self) -> Option<Banner> { pub fn finish(self) -> Option<Banner> {
Some(Banner { Some(Banner {
name: self.name, name: self.name,
rates: self.rates?, gacha: self.gacha?,
sparkable: self.sparkable?, 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<Rarity> for StudentType {
fn from(rarity: Rarity) -> Self {
match rarity {
Rarity::One => Self::One,
Rarity::Two => Self::Two,
Rarity::Three => Self::Three,
}
}
}
impl TryFrom<StudentType> for Rarity {
type Error = &'static str;
fn try_from(value: StudentType) -> Result<Self, Self::Error> {
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 struct Banner {
pub name: I18nString, pub name: I18nString,
rates: Gacha, gacha: Gacha,
sparkable: Vec<Student>, // FIXME: Can we safely assume this will only contain 2 studnets? sparkable: Option<Vec<Student>>,
} }
impl Banner { impl Banner {
fn get_random_student(&self, rarity: Rarity) -> Student { fn get_random_student(&self) -> Student {
let mut rng = rand::thread_rng(); 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 let items: [(StudentType, usize); 4] = [
.rates (StudentType::One, self.gacha.get_rate(Rarity::One)),
.pool (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() .iter()
.filter(|student| student.rarity == rarity) .filter(|student| student.rarity == rarity)
.collect(); .collect();
filtered_students.shuffle(&mut rng); let index = rand::thread_rng().gen_range(0..two_star_students.len());
let index: usize = rng.gen_range(0..filtered_students.len()); two_star_students[index].clone()
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 { impl Recruitment for Banner {
fn roll(&self) -> Student { fn roll(&self) -> Student {
self.get_random_student(self.get_random_rarity()) self.get_random_student()
} }
fn roll10(&self) -> [Student; 10] { fn roll10(&self) -> [Student; 10] {
@ -105,14 +190,14 @@ impl Recruitment for Banner {
// Fill students with 10 random students // Fill students with 10 random students
for student in students.iter_mut() { 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); let two_star_present = students.iter().any(|student| student.rarity == Rarity::Two);
if !two_star_present { if !two_star_present {
if students[students.len() - 1].rarity != Rarity::Three { 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);
} }
} }

View File

@ -43,6 +43,7 @@ pub trait Recruitment {
pub struct GachaBuilder { pub struct GachaBuilder {
rates: Option<(usize, usize, usize)>, rates: Option<(usize, usize, usize)>,
pool: Option<Vec<Student>>, pool: Option<Vec<Student>>,
priority: Option<(Vec<Student>, usize)>,
} }
impl Default for GachaBuilder { impl Default for GachaBuilder {
@ -50,6 +51,7 @@ impl Default for GachaBuilder {
Self { Self {
rates: Some((790, 185, 25)), rates: Some((790, 185, 25)),
pool: Default::default(), pool: Default::default(),
priority: Default::default(),
} }
} }
} }
@ -78,7 +80,7 @@ impl GachaBuilder {
Self { Self {
rates: Some((one, two, three)), 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. /// Consumes a GachaBuilder and retuns a Gacha Struct.
/// ///
/// Will return `None` if the `rates` or `pool` property of /// Will return `None` if the `rates` or `pool` property of
@ -122,6 +147,7 @@ impl GachaBuilder {
Some(Gacha { Some(Gacha {
rates: self.rates?, rates: self.rates?,
pool: self.pool?, pool: self.pool?,
priority: self.priority,
}) })
} }
} }
@ -130,8 +156,10 @@ impl GachaBuilder {
/// to randomly select a Student from the gacha pool /// to randomly select a Student from the gacha pool
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct Gacha { 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<Student>, pub pool: Vec<Student>,
pub priority: Option<(Vec<Student>, usize)>,
} }
impl Gacha { impl Gacha {

View File

@ -7,6 +7,24 @@ pub struct I18nString {
translations: HashMap<Language, String>, translations: HashMap<Language, String>,
} }
impl PartialEq for I18nString {
fn eq(&self, other: &Self) -> bool {
self.get(Language::Japanese).unwrap() == other.get(Language::Japanese).unwrap()
}
}
impl PartialEq<String> 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 { impl std::fmt::Display for I18nString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.translations.get(&Language::Japanese).unwrap().as_str()) f.write_str(self.translations.get(&Language::Japanese).unwrap().as_str())

View File

@ -1,174 +1,46 @@
use std::io::Write; use ba_gacha::banner::BannerBuilder;
use ba_gacha::gacha::{GachaBuilder, Recruitment};
use ba_gacha::{ use ba_gacha::i18n::Language;
banner::BannerBuilder, use ba_gacha::student::Student;
gacha::{GachaBuilder, Rarity, Recruitment}, use std::{fs::File, io::Read};
i18n::Language,
student::Student,
};
fn main() { fn main() {
// Create a Struct representing Shiroko let mut file = File::open("students.json").unwrap();
let mut shiroko = Student::new("シロコ", Rarity::Three); let mut json = String::new();
shiroko.add_translation(Language::English, "Shiroko"); // Yes, this is a translation. file.read_to_string(&mut json).unwrap();
// Create a Struct representing Hoshino let students: Vec<Student> = serde_json::from_str(&json).unwrap();
let mut hoshino = Student::new("ホシノ", Rarity::Three);
hoshino.add_translation(Language::English, "Hoshino");
// Create a Vector Containing all the Sparkable Students let banner_students: Vec<Student> = students
let sparkable_students = vec![shiroko, hoshino]; .iter()
.filter(|student| student.name != "ノゾミ")
.map(|student| student.clone())
.collect();
let rates = GachaBuilder::new(79.0, 18.5, 2.5) let hoshino = find_student(&students, "ホシノ").unwrap();
.with_pool(pickup_banner_2021_02_11()) 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() .finish()
.unwrap(); .unwrap();
// Create the Banner Itself
let pickup_banner = BannerBuilder::new("ピックアップ募集") let pickup_banner = BannerBuilder::new("ピックアップ募集")
.with_name_translation(Language::English, "Priority") .with_name_translation(Language::English, "Rate-Up Registration")
.with_sparkable_students(sparkable_students) .with_sparkable_students(&rate_up_students)
.with_rates(&rates) .with_gacha(&gacha)
.finish() .finish()
.unwrap(); .unwrap();
// Roll Banner 1 Time // let student = pickup_banner.roll();
let student = pickup_banner.roll(); // let students = pickup_banner.roll10();
// 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 { pub fn find_student(students: &[Student], jpn_name: &str) -> Option<Student> {
let mut student = Student::new(jpn_name, rarity);
student.add_translation(Language::English, eng_name);
student
}
fn pickup_banner_2021_02_11() -> Vec<Student> {
let mut test: Vec<Student> = 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<Student> {
let mut students: Vec<Student> = 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 students
.iter()
.find(|student| student.name == jpn_name)
.map(|student| student.clone())
} }

View File

@ -1,6 +1,5 @@
use crate::gacha::Rarity; use crate::gacha::Rarity;
use crate::i18n::I18nString; use crate::i18n::{I18nString, Language};
use crate::i18n::Language;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default)]

407
students.json Normal file
View File

@ -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
}
]