原神圣遗物不会调?爆肝1天,Typescript 完成简易的圣遗物计算器算法实现!!!

lxf2023-04-02 18:41:01
原神圣遗物不会调?爆肝1天,Typescript 完成简易的圣遗物计算器算法实现!!!

本文正在参加「」

实现简易的原神圣遗物计算器算法

什么是圣遗物计算器?

圣遗物计算器是:可以通过,选择套装类型,选择最低暴击下限,选择最低爆伤下限,选择最低攻击下限,等等种种条件,计算出来匹配满足要求所有套装组合

原生版本调配圣遗物:每个位置都需要去看,每个属性都需要仔细搭配计算

通过圣遗物计算器:只需要选择需要的属性,套装,就可以自动搭配

如何实现?

1. 前提基础

玩原神的知道,圣遗物一共有5个位置,每个角色身上只能装配其中每个位置中的一个圣遗物

原神圣遗物不会调?爆肝1天,Typescript 完成简易的圣遗物计算器算法实现!!!

每个+20圣遗物有1条主属性,和4条副属性

原神圣遗物不会调?爆肝1天,Typescript 完成简易的圣遗物计算器算法实现!!!

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 主函数逻辑以及实现

主函数里,
  1. 我首先进行了简单的一个筛选,根据用户时候选择 '+20' 或者 '追忆之绝缘' 等套装进行初步筛选。
  2. 根据用户传递的更详细的筛选条件,进行更细的筛选,characterBaseAttributes为角色的基础属性,攻击百分比,生命百分比,防御百分比都需要在此基础上进行计算
  3. newRelicsData 简单筛选后剩下的圣遗物,以后将在这个数据上进行遍历,排列组合
  4. 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
}
数据格式化操作
  1. 在这个函数我对统一的数据,进行了一个格式化,便于后期更好的计算,最后的带的数据类型,就是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 入结果数组
  1. 递归函数的两大要素,出口和入口
  2. if(path.length === 5){} 是函数的出口,这块逻辑,我保留了一段代码,当path路径长度为5说明,已经为一套完整的套装,此时对套装的属性进行统计,如果各项条件,均满足condition,这套组合满足要求
  3. for (let key in formateData) {} 是递归的节点,我也称他为递归入口,遍历格式化后的圣遗物数据。
  4. 因为同一个位置会有多次选择的可能,使用hash映射来解决这个麻烦,创建一个同步于path对象,在path改变时,hash同时更改自己的状态为true or false,防止同一个位置的数据多次进入数组
  5. 回溯 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多种组合,并且每种组合详细列出来了

原神圣遗物不会调?爆肝1天,Typescript 完成简易的圣遗物计算器算法实现!!!