本文正在参加「」
实现简易的原神圣遗物计算器算法
什么是圣遗物计算器?
圣遗物计算器是:可以通过,选择套装类型,选择最低暴击下限,选择最低爆伤下限,选择最低攻击下限,等等种种条件,计算出来匹配满足要求所有套装组合
原生版本调配圣遗物:每个位置都需要去看,每个属性都需要仔细搭配计算
通过圣遗物计算器:只需要选择需要的属性,套装,就可以自动搭配
如何实现?
1. 前提基础
玩原神的知道,圣遗物一共有5个位置,每个角色身上只能装配其中每个位置中的一个圣遗物
每个+20圣遗物有1条主属性,和4条副属性
2. 代码实现
types.ts 中,定义了一些类型,为了后期更强壮的代码
// 圣遗物套装
export type GroupType = '绝缘之旗印' | '追忆之注连' | '流浪大地的乐团' | '昔日宗室之仪' | '如雷的圣物' | '平息鸣雷的尊者' | '翠绿之影' | '被怜爱的少女' | '悠古的磐岩' | '逆飞的流星' | '炽烈的炎之魔女' | '渡过烈火的贤人'
// 圣遗物位置
export type Type = '生之花' | '死之羽' | '理之冠' | '时之沙' | '空之杯'
// 圣遗物主词条属性
export type MainType = 'fireDamage' | 'waterDamage' | 'rockDamage' | 'grassDamage' | 'windDamage' | 'thunderboltDamage' | 'iceDamage' | 'maxHealthPoint' | 'minHealthPoint' | 'maxAttack' | 'minAttack' | 'maxDefense' | 'minDefense' | 'criticalStrikeRate' | 'criticalStrikeDamage' | 'proficients' | 'chargingRate'
// 圣遗物综合
export interface RelicsType {
type: Type
group: GroupType
level: number
attributes: {
main:MainType
mainValue: number
maxHealthPoint?: number
minHealthPoint?: number
maxAttack?: number
minAttack?: number
maxDefense?: number
minDefense?: number
criticalStrikeRate?: number
criticalStrikeDamage?: number
proficients?: number
chargingRate?: number
}
}
// 基础筛选类型
export type BaseType = {
group?: GroupType
level?: number
}
// 进阶属性筛选类型
export type AdvanceType = {
healthPoint?: number
defense?: number
attack?: number
proficients?: number
criticalStrikeRate?: number
criticalStrikeDamage?: number
chargingRate?: number
fireDamage?: number
waterDamage?: number
rockDamage?: number
grassDamage?: number
windDamage?: number
thunderboltDamage?: number
iceDamage?: number
}
// 筛选条件
export interface ScreeningConditionsType {
base: BaseType
advance: AdvanceType
}
// 角色基础属性
export interface BaseAttributes {
healthPoint: number
defense: number
attack: number
proficients?: number
criticalStrikeRate?: number
criticalStrikeDamage?: number
chargingRate?: number
fireDamage?: number
waterDamage?: number
rockDamage?: number
grassDamage?: number
windDamage?: number
thunderboltDamage?: number
iceDamage?: number
}
//
export interface HashType {
'生之花': boolean
'死之羽': boolean
'理之冠': boolean
'时之沙': boolean
'空之杯': boolean
}
export interface RelicsFormateType {
'生之花': RelicsType[]
'死之羽': RelicsType[]
'理之冠': RelicsType[]
'时之沙': RelicsType[]
'空之杯': RelicsType[]
}
data.ts 目前数据来源于,手动把背包数据敲下来
// type:圣遗物位置
// group:圣遗物套装
// attributes:圣遗物属性
// main:圣遗物主词条
// mainValue:圣遗物主词条属性
// maxHealthPoint:大生命
// minHealthPoint:小生命
// maxAttack:大攻击
// minAttack:小攻击
// maxDefense:大防御
// minDefense:小防御
// criticalStrikeRate:暴击率
// criticalStrikeDamage:暴击伤害
// proficients:元素精通
// chargingRate:元素充能
import { RelicsType, ScreeningConditionsType, BaseAttributes } from "./types"
// 从背包一个一个敲出来的圣遗物数据
export const RelicsData: RelicsType[] = [
{
type: '理之冠',
group: '昔日宗室之仪',
level: 20,
attributes: {
main: 'criticalStrikeDamage',
mainValue: 0.622,
minHealthPoint: 478,
maxAttack: 0.105,
chargingRate: 0.104,
minDefense: 60,
maxDefense:0.066
}
}
* n ........
]
// 筛选条件
export const screeningConditions: ScreeningConditionsType = {
base: {
group: '绝缘之旗印',
level: 20,
},
advance: {
criticalStrikeRate: 0.44,
criticalStrikeDamage: 0.42,
proficients: 0,
chargingRate: 0.11,
}
}
// 不同的角色不同的基础属性
export const characterBaseAttributes: BaseAttributes = {
healthPoint: 14450,
defense: 548,
attack: 786,
proficients: 23
}
index.ts 主函数逻辑以及实现
主函数里,
- 我首先进行了简单的一个筛选,根据用户时候选择 '+20' 或者 '追忆之绝缘' 等套装进行初步筛选。
- 根据用户传递的更详细的筛选条件,进行更细的筛选,
characterBaseAttributes
为角色的基础属性,攻击百分比,生命百分比,防御百分比都需要在此基础上进行计算 newRelicsData
简单筛选后剩下的圣遗物,以后将在这个数据上进行遍历,排列组合screeningConditions
详细的筛选条件,其中可能包括,具体的攻击数值,暴击数值,爆伤数值 等等所有属性
export function relicsCalculator(characterBaseAttributes: BaseAttributes, relicsData: RelicsType[], screeningConditions: ScreeningConditionsType): RelicsType[][] {
// 根据圣遗物等级和圣遗物套装组合初次筛选后的所有圣遗物
let newRelicsData = baseScreening(relicsData, screeningConditions.base)
// 根据角色基础属性和圣遗物属性组合筛选后的圣遗物
let resultRelicsData = advanceScreening(characterBaseAttributes, newRelicsData, screeningConditions.advance)
return resultRelicsData
}
数据格式化操作
- 在这个函数我对统一的数据,进行了一个格式化,便于后期更好的计算,最后的带的数据类型,就是
RelicsFormateType
类型
let formateData = formateRelics(relicsData)
// 对圣遗物数据进行格式化
function formateRelics(relicsData: RelicsType[]) {
let hashMap: RelicsFormateType = {
'生之花': [],
'死之羽': [],
'理之冠': [],
'时之沙': [],
'空之杯': [],
}
for (let item of relicsData) {
hashMap[item.type].push(item)
}
return hashMap
}
回溯算法,对所有圣遗物套装进行排列组合,将满足要求的5件套,push 入结果数组
- 递归函数的两大要素,出口和入口
if(path.length === 5){}
是函数的出口,这块逻辑,我保留了一段代码,当path路径长度为5说明,已经为一套完整的套装,此时对套装的属性进行统计,如果各项条件,均满足condition,这套组合满足要求for (let key in formateData) {}
是递归的节点,我也称他为递归入口,遍历格式化后的圣遗物数据。- 因为同一个位置会有多次选择的可能,使用hash映射来解决这个麻烦,创建一个同步于path的对象,在path改变时,hash同时更改自己的状态为true or false,防止同一个位置的数据多次进入数组
- 回溯
path.push(item)
=>hash[item.type] = true
=>dfs(hash, path) path.pop()
dfs(hash, path)
function dfs(hash: HashType, path: RelicsType[]) {
// 递归出口
if (path.length === 5) {
// 遍历筛选条件的每一项,取得目标值,
for (let key in condition) {
let target = condition[key]
let value = 0
// 生命
if (key === 'healthPoint') {
// 遍历圣遗物组合,取得每个圣遗物 item
for (let item of path) {
// 如果圣遗物主属性为百分比生命值
if (item.attributes.main === 'maxHealthPoint') {
value += baseAttributes.healthPoint * item.attributes.mainValue
}
// 如果圣遗物主属性为固定生命值
if (item.attributes.main === 'minHealthPoint') {
value += item.attributes.mainValue
}
// 圣遗物副属性
const attribute = item.attributes
// 如果副属性有大生命
value += attribute['maxHealthPoint'] ? baseAttributes.healthPoint * attribute['maxHealthPoint'] : 0
// 如果副属性有小生命
value += attribute['minHealthPoint'] ? attribute['minHealthPoint'] : 0
}
if (value < target) return
}
}
res.push([...path])
}
// 递归入口,这个递归入口比较复杂,5种圣遗物,每种只能进去一个,所以我这里用hash来记录,是否该位置进入过数组
for (let key in formateData) {
// 其中一种圣遗物集合
let data = formateData[key]
// 如果这个key值为真,说明path已经进入了一个同类型圣遗物,跳过
if (hash[key]) {
continue
}
// 否则遍历这种圣遗物集合
for (let item of data) {
path.push(item)
hash[item.type] = true
dfs(hash, path)
path.pop()
hash[item.type] = false
}
}
}
return res
最后效果,肝文不易,各位客官攒攒,比心,后期会继续完善
根据我手动录入的数据,可以计算到,满足要求的套装组合一共有20多种组合,并且每种组合详细列出来了