mirror of
https://github.com/Paoda/blue-gacha.git
synced 2025-11-06 22:52:10 +00:00
feat: reimplement priority gacha system
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use crate::gacha::{Gacha, Rarity, Recruitment};
|
||||
use crate::i18n::{I18nString, Language};
|
||||
use crate::student::Student;
|
||||
use crate::student::{PriorityStudent, Student};
|
||||
use rand::distributions::{Distribution, WeightedIndex};
|
||||
use rand::Rng;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
@@ -101,15 +101,15 @@ impl BannerBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum StudentType {
|
||||
One = 1, // One Star
|
||||
Two, // Two Stars
|
||||
Three, // Three Stars
|
||||
Priority, // Rate-up Student (Presumably 3*)
|
||||
#[derive(Debug, Clone)]
|
||||
enum StudentType<'a> {
|
||||
One, // One Star
|
||||
Two, // Two Stars
|
||||
Three, // Three Stars
|
||||
Priority(&'a PriorityStudent),
|
||||
}
|
||||
|
||||
impl From<Rarity> for StudentType {
|
||||
impl<'a> From<Rarity> for StudentType<'a> {
|
||||
fn from(rarity: Rarity) -> Self {
|
||||
match rarity {
|
||||
Rarity::One => Self::One,
|
||||
@@ -119,15 +119,23 @@ impl From<Rarity> for StudentType {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StudentType> for Rarity {
|
||||
impl<'a> TryFrom<StudentType<'a>> for Rarity {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: StudentType) -> Result<Self, Self::Error> {
|
||||
Rarity::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&StudentType<'a>> for Rarity {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: &StudentType<'a>) -> 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"),
|
||||
StudentType::Priority(_) => return Err("Can not convert from Priority to Rarity"),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -141,39 +149,59 @@ pub struct Banner {
|
||||
impl Banner {
|
||||
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 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 empty_vec = Vec::new();
|
||||
let priority_students = self.gacha.priority.as_ref().unwrap_or(&empty_vec);
|
||||
let mut rates = (
|
||||
self.gacha.get_rate(Rarity::One),
|
||||
self.gacha.get_rate(Rarity::Two),
|
||||
self.gacha.get_rate(Rarity::Three),
|
||||
);
|
||||
|
||||
for priority_student in priority_students {
|
||||
match priority_student.student().rarity {
|
||||
Rarity::One => rates.0 -= priority_student.rate,
|
||||
Rarity::Two => rates.1 -= priority_student.rate,
|
||||
Rarity::Three => rates.2 -= priority_student.rate,
|
||||
};
|
||||
}
|
||||
|
||||
let mut items: Vec<(StudentType, usize)> = Vec::with_capacity(3 + priority_students.len());
|
||||
items.push((StudentType::One, rates.0));
|
||||
items.push((StudentType::Two, rates.1));
|
||||
items.push((StudentType::Three, rates.2));
|
||||
|
||||
items.extend(
|
||||
priority_students
|
||||
.iter()
|
||||
.map(|student| (StudentType::Priority(student), student.rate)),
|
||||
);
|
||||
|
||||
let dist = WeightedIndex::new(items.iter().map(|item| item.1)).unwrap();
|
||||
let students = &self.gacha.pool;
|
||||
let student_pool = &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
|
||||
match &items[dist.sample(&mut rng)] {
|
||||
(StudentType::Priority(priority_student), _) => priority_student.student().clone(),
|
||||
(student_type, _) => {
|
||||
let students: Vec<&Student> = student_pool
|
||||
.iter()
|
||||
.filter(|student| student.rarity == rarity.try_into().unwrap())
|
||||
.filter(|student| student.rarity == student_type.try_into().unwrap())
|
||||
.filter(|student| {
|
||||
// Remove any Rate-Up Units
|
||||
// TODO: Determine whether this is the right way of implementing priority gacha
|
||||
!priority_students
|
||||
.iter()
|
||||
.any(|priority_student| student.name == priority_student.student().name)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let index: usize = rng.gen_range(0..students.len());
|
||||
let index = rng.gen_range(0..students.len());
|
||||
students[index].clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_random_student_of_rarity(&self, rarity: Rarity) -> Student {
|
||||
// NOTE: This does not actually follow the rules of any given banner. Only get_random_student() does.
|
||||
let students = &self.gacha.pool;
|
||||
let two_star_students: Vec<&Student> = students
|
||||
.iter()
|
||||
@@ -200,10 +228,8 @@ impl Recruitment for Banner {
|
||||
|
||||
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_of_rarity(Rarity::Two);
|
||||
}
|
||||
if !two_star_present && students[students.len() - 1].rarity != Rarity::Three {
|
||||
students[students.len() - 1] = self.get_random_student_of_rarity(Rarity::Two);
|
||||
}
|
||||
|
||||
students
|
||||
|
||||
24
src/gacha.rs
24
src/gacha.rs
@@ -1,4 +1,4 @@
|
||||
use crate::student::Student;
|
||||
use crate::student::{PriorityStudent, Student};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::cmp::Ordering;
|
||||
#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
|
||||
@@ -66,7 +66,7 @@ pub trait Recruitment {
|
||||
pub struct GachaBuilder {
|
||||
rates: Option<(usize, usize, usize)>,
|
||||
pool: Option<Vec<Student>>,
|
||||
priority: Option<(Vec<Student>, usize)>,
|
||||
priority: Option<Vec<PriorityStudent>>,
|
||||
}
|
||||
|
||||
impl Default for GachaBuilder {
|
||||
@@ -137,18 +137,18 @@ impl GachaBuilder {
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use bluearch_recruitment::gacha::{GachaBuilder, Rarity};
|
||||
/// # use bluearch_recruitment::student::Student;
|
||||
/// let aru = Student::new("アル", Rarity::Three);
|
||||
/// let hina = Student::new("ヒナ", Rarity::Three);
|
||||
/// let rate_up = vec![aru, hina.clone()];
|
||||
/// let priority = vec![hina];
|
||||
/// # use bluearch_recruitment::student::{Student, PriorityStudent};
|
||||
/// let aru = Student::new("アル", Rarity::Three).into_priority_student(3.5 / 2.0);
|
||||
/// let hina = Student::new("ヒナ", Rarity::Three).into_priority_student(3.5 / 2.0);
|
||||
/// let pool = vec![aru.student().clone(), hina.student().clone()];
|
||||
/// let priority = vec![aru, hina];
|
||||
/// let gacha_builder = GachaBuilder::new(79.0, 18.5, 2.5)
|
||||
/// .with_pool(rate_up)
|
||||
/// .with_priority(&priority, 3.5);
|
||||
/// .with_pool(pool)
|
||||
/// .with_priority(&priority);
|
||||
/// ```
|
||||
pub fn with_priority(self, students: &[Student], total_rate: f32) -> Self {
|
||||
pub fn with_priority(self, students: &[PriorityStudent]) -> Self {
|
||||
Self {
|
||||
priority: Some((students.to_vec(), (total_rate * 10.0) as usize)),
|
||||
priority: Some(students.to_vec()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,7 @@ pub struct Gacha {
|
||||
/// (1★, 2★, 3★)
|
||||
pub rates: (usize, usize, usize),
|
||||
pub pool: Vec<Student>,
|
||||
pub priority: Option<(Vec<Student>, usize)>,
|
||||
pub priority: Option<Vec<PriorityStudent>>,
|
||||
}
|
||||
|
||||
impl Gacha {
|
||||
|
||||
@@ -60,9 +60,7 @@ impl I18nString {
|
||||
///
|
||||
/// Will return None if there is no translation for the given language
|
||||
pub fn get(&self, language: Language) -> Option<String> {
|
||||
self.translations
|
||||
.get(&language)
|
||||
.map(|message| message.clone())
|
||||
self.translations.get(&language).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,4 +55,32 @@ impl Student {
|
||||
pub fn add_translation(&mut self, language: Language, name: &str) {
|
||||
self.name.update(language, name);
|
||||
}
|
||||
|
||||
pub fn into_priority_student(self, rate: f32) -> PriorityStudent {
|
||||
PriorityStudent {
|
||||
inner: self,
|
||||
rate: (rate * 10.0) as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Priority Student is a student who has a pull-rate that is unique from
|
||||
/// the rest of the rest of their peers in their star rating
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PriorityStudent {
|
||||
inner: Student,
|
||||
pub rate: usize,
|
||||
}
|
||||
|
||||
impl PriorityStudent {
|
||||
pub fn new(student: Student, rate: f32) -> Self {
|
||||
Self {
|
||||
inner: student,
|
||||
rate: (rate * 10.0) as usize,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn student(&self) -> &Student {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user