跳到主要内容

权重随机工具

权重随机工具(WeightRandom)提供基于权重的随机选择功能,适用于抽奖、随机事件触发等需要按指定概率分配的场景。

核心特性

  • 权重配置管理
  • 动态权重计算
  • 集合扩展函数支持
  • 类型安全的泛型支持
  • 线程安全的 Random

基础用法

使用 WeightCategory

import top.maplex.arim.Arim
import top.maplex.arim.tools.weightrandom.WeightRandom.WeightCategory

// 创建权重列表
val items = listOf(
WeightCategory("普通道具", 70),
WeightCategory("稀有道具", 25),
WeightCategory("史诗道具", 5)
)

// 进行随机选择
val selected = Arim.weightRandom.getWeightRandom(items)
println("获得:${selected ?: "无"}")

动态权重计算

data class Enemy(val name: String, val baseRate: Int)

val enemies = listOf(
Enemy("史莱姆", 5),
Enemy("狼人", 3),
Enemy("巨龙", 1)
)

// 根据环境因素动态计算权重
val selectedEnemy = Arim.weightRandom.getRandom(enemies) { enemy ->
enemy.baseRate * weatherMultiplier() // 天气影响因子
}

println("遭遇:${selectedEnemy?.category?.name}")

API 方法

getRandom - 基础权重选择

fun <T> getRandom(categories: Collection<WeightCategory<T>>): WeightCategory<T>?

参数: 预包装的权重类别集合

返回: 随机选中的权重类别对象(包含原始对象和权重),权重总和 ≤ 0 时返回 null

getRandom - 动态权重计算

fun <T> getRandom(categories: Collection<T>, processing: (T) -> Int): WeightCategory<T>?

参数:

  • categories: 原始对象集合
  • processing: 权重计算函数(从对象中提取权重值)

返回: 包含原始对象和计算权重的结果对象

getWeightRandom - 快速获取结果

fun <T> getWeightRandom(categories: Collection<WeightCategory<T>>): T?

返回: 直接返回选中对象的原始值(不包含权重信息)

扩展函数

randomWeight - 集合直接调用

fun <T> Collection<T>.randomWeight(processing: (T) -> Int): WeightCategory<T>?

示例:

// 快速创建权重列表
val result = listOf("A", "B", "C").randomWeight {
when(it) {
"A" -> 50
"B" -> 30
else -> 20
}
}

randomWeightValue - 快速获取值

fun <T> Collection<T>.randomWeightValue(processing: (T) -> Int): T?

示例:

// 直接获取结果值
val result = listOf(1, 2, 3).randomWeightValue { it * 10 } // 权重分别为 10, 20, 30

WeightCategory 数据类

data class WeightCategory<T>(
var category: T, // 原始对象
var weight: Int // 权重值
)

实际应用示例

战利品掉落系统

data class Loot(val name: String, val rarity: Rarity)

enum class Rarity { COMMON, UNCOMMON, RARE, EPIC }

object LootSystem {
private val lootTable = listOf(
Loot("铜币", Rarity.COMMON),
Loot("银币", Rarity.UNCOMMON),
Loot("金币", Rarity.RARE),
Loot("钻石", Rarity.EPIC)
)

fun dropLoot(): Loot? {
return lootTable.randomWeightValue { loot ->
when(loot.rarity) {
Rarity.COMMON -> 1000
Rarity.UNCOMMON -> 100
Rarity.RARE -> 10
Rarity.EPIC -> 1
}
}
}
}

随机事件系统

object RandomEventSystem {
private val events = listOf(
WeightCategory("流星雨", 5),
WeightCategory("血月", 10),
WeightCategory("商人来访", 30),
WeightCategory("无事件", 55)
)

fun triggerRandomEvent(): String? {
return Arim.weightRandom.getWeightRandom(events)
}
}

动态难度调整

data class Monster(val name: String, val level: Int)

object SpawnSystem {
fun spawnMonster(playerLevel: Int): Monster? {
val monsters = listOf(
Monster("史莱姆", 1),
Monster("骷髅", 5),
Monster("僵尸", 10),
Monster("精英怪", 20)
)

return monsters.randomWeightValue { monster ->
// 根据玩家等级调整权重
val levelDiff = (playerLevel - monster.level).coerceAtLeast(0)
100 - (levelDiff * 10).coerceAtMost(90)
}
}
}

抽奖系统

object GachaSystem {
private val rewards = listOf(
WeightCategory("谢谢参与", 60),
WeightCategory("10 金币", 25),
WeightCategory("50 金币", 10),
WeightCategory("稀有道具", 4),
WeightCategory("传说武器", 1)
)

fun draw(player: Player): String {
val result = Arim.weightRandom.getWeightRandom(rewards)
return result ?: "抽奖失败"
}

fun multiDraw(count: Int): List<String> {
return (1..count).map { draw(player) }
}
}

权重计算规则

概率计算公式

实际概率 = 项目权重 / 总权重

示例:

val items = listOf(
WeightCategory("A", 50), // 50 / 100 = 50%
WeightCategory("B", 30), // 30 / 100 = 30%
WeightCategory("C", 20) // 20 / 100 = 20%
)

权重总和为 0 或负数

当所有项目的权重总和 ≤ 0 时,返回 null

val items = listOf(
WeightCategory("A", 0),
WeightCategory("B", -10)
)

val result = Arim.weightRandom.getWeightRandom(items) // null

常见问题

如何确保某个项目必定出现?

将该项目的权重设置得足够大:

val guaranteed = listOf(
WeightCategory("保底奖励", 9999),
WeightCategory("其他", 1)
)

扩展函数和直接调用的区别?

// 扩展函数(更简洁)
val result1 = items.randomWeightValue { it.rarity.weight }

// 直接调用(更明确)
val result2 = Arim.weightRandom.getRandom(items) { it.rarity.weight }?.category

// 两者等价

是否线程安全?

是的,WeightRandom 使用 TabooLib 的线程安全 random() 函数,可以在多线程环境下安全使用。

如何实现保底机制?

object GachaWithPity {
private var pityCounter = 0

fun draw(): String {
pityCounter++

// 90 抽保底
if (pityCounter >= 90) {
pityCounter = 0
return "★★★★★ 传说武器"
}

val result = normalDraw()
if (result.startsWith("★★★★★")) {
pityCounter = 0
}
return result
}

private fun normalDraw(): String {
return Arim.weightRandom.getWeightRandom(rewards) ?: "抽奖失败"
}
}