Files
bl/logic/service/player/color.go

232 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package player
import (
"math"
grand "math/rand"
)
// RandomMatrixNoSingleColorBright 是在 RandomMatrixNoSingleColorBias 基础上的变体,
// 增加 brightnessScale 参数用于控制允许的 offset亮度提升的比例。
// 参数说明:
// - seed: 随机种子
// - alphaRow: 最后 5 字节alpha 行)固定值
// - brightChance: 原有决定 ModeB 的概率(常量行概率)
// - redBias: 保持原有 R 偏好0..1
// - brightnessScale: 0..10 表示不额外提升亮度行为近似原函数1 表示允许最大可用 offset受每行 sum 限制)
// 返回20 字节矩阵(直接 uint8 原样使用)
func RandomMatrixNoSingleColorBright(seed int64, alphaRow [5]float32, brightChance, redBias, brightnessScale float64) [20]float32 {
var out [20]float32
// clamp params
if brightChance < 0 {
brightChance = 0
}
if brightChance > 1 {
brightChance = 1
}
if redBias < 0 {
redBias = 0
}
if redBias > 1 {
redBias = 1
}
if brightnessScale < 0 {
brightnessScale = 0
}
if brightnessScale > 1 {
brightnessScale = 1
}
const minOffsetBase = 24 // 最小偏移基线(你可以调大以更亮)
const maxOffsetBase = 180 // 最大偏移基线(经验上不宜太接近 255
grand.Seed(seed)
// 1) 先按原逻辑决定 ModeA / ModeB 与 picks保留 redBias 偏好)
modeB := make([]bool, 3)
modeBCount := 0
for r := 0; r < 3; r++ {
if grand.Float64() < brightChance {
modeB[r] = true
modeBCount++
} else {
modeB[r] = false
}
}
if modeBCount == 3 {
which := int(grand.Intn(3))
modeB[which] = false
modeBCount--
}
// assign ModeA picks with red bias (similar于之前实现)
available := []int{0, 1, 2, 3}
picks := make(map[int]int)
used := make(map[int]bool)
modeARows := []int{}
for r := 0; r < 3; r++ {
if !modeB[r] {
modeARows = append(modeARows, r)
}
}
weightedPick := func(avoidUsed bool) int {
cands := []int{}
ws := []float64{}
for _, c := range available {
if avoidUsed && used[c] {
continue
}
w := 1.0
if c == 0 {
w += redBias * 3.0
} // 红色加权
cands = append(cands, c)
ws = append(ws, w)
}
if len(cands) == 0 {
for _, c := range available {
w := 1.0
if c == 0 {
w += redBias * 3.0
}
cands = append(cands, c)
ws = append(ws, w)
}
}
sum := 0.0
for _, w := range ws {
sum += w
}
rv := grand.Float64() * sum
acc := 0.0
for i, w := range ws {
acc += w
if rv <= acc {
return cands[i]
}
}
return cands[len(cands)-1]
}
for _, row := range modeARows {
p := weightedPick(true)
picks[row] = p
used[p] = true
}
// 2) 先把 multipliers 写入 outModeA one-hotModeB 暂置 multipliers=0
for r := 0; r < 3; r++ {
base := r * 5
if !modeB[r] {
p := picks[r]
for c := 0; c < 4; c++ {
if c == p {
out[base+c] = 1
} else {
out[base+c] = 0
}
}
out[base+4] = 0 // 暂时保持 0后面可能根据 sum 注入 offset
} else {
for c := 0; c < 4; c++ {
out[base+c] = 0
}
out[base+4] = 0 // 将在下一步设置合法 offset
}
}
// 3) 基于每行 multipliers 的 sum 计算允许的 offset 上限,并在允许范围内按 brightnessScale 随机选择 offset
// allowedMaxOffset = min(maxOffsetBase, floor(255*(1 - sum))) 然后乘以 brightnessScale线性缩放
hadOffsetRow := false
for r := 0; r < 3; r++ {
base := r * 5
// 计算 multipliers sum目前为 0/1
sum := 0
for c := 0; c < 4; c++ {
sum += int(out[base+c])
}
// 允许的最大 offset绝对上限由 sum 决定)
allowedBySum := int(math.Floor(255.0 * (1.0 - float64(sum))))
if allowedBySum < 0 {
allowedBySum = 0
}
// 想要的最大基线由 maxOffsetBase 决定,再乘以 brightnessScale
desiredMax := int(float64(maxOffsetBase) * brightnessScale)
// 最终可用最大 offset至少要大于等于 minOffsetBase 才能选择)
allowedMax := allowedBySum
if desiredMax < allowedMax {
allowedMax = desiredMax
}
if allowedMax < minOffsetBase {
// 不足最小基线,设为 0不可用 offset
out[base+4] = 0
} else {
// 在 [minOffsetBase, allowedMax] 随机取一个 offset
off := minOffsetBase + int(grand.Intn(allowedMax-minOffsetBase+1))
out[base+4] = float32(off)
hadOffsetRow = true
}
}
// 4) 保证至少有一行有 offset避免整个图都只靠拷贝导致暗
if !hadOffsetRow {
// 尝试找到 sum < 1 的行 preferentially Rout (row 0) then others
rowChosen := -1
for r := 0; r < 3; r++ {
base := r * 5
sum := 0
for c := 0; c < 4; c++ {
sum += int(out[base+c])
}
if sum < 1 {
rowChosen = r
break
}
}
if rowChosen == -1 {
// 所有行 sum==1很可能全部 one-hot我们需要强制把其中一行的 multiplier 置为 0 并赋 offset
rowChosen = int(grand.Intn(3))
base := rowChosen * 5
for c := 0; c < 4; c++ {
out[base+c] = 0
}
}
// 计算 allowedBySum 并设置 offset 为 min(allowedBySum, desired),至少设为 minOffsetBase
base := rowChosen * 5
sum := 0
for c := 0; c < 4; c++ {
sum += int(out[base+c])
}
allowedBySum := int(math.Floor(255.0 * (1.0 - float64(sum))))
if allowedBySum < minOffsetBase {
// 若仍不足则设为 min(minOffsetBase, allowedBySum) 或强制给一个较小的值
if allowedBySum > 0 {
out[base+4] = float32(allowedBySum)
} else {
out[base+4] = float32(minOffsetBase) // 最后手段(风险低,因为我们可能已把 sum=0
}
} else {
desiredMax := int(float64(maxOffsetBase) * brightnessScale)
if desiredMax > allowedBySum {
desiredMax = allowedBySum
}
if desiredMax < minOffsetBase {
desiredMax = minOffsetBase
}
off := minOffsetBase + int(grand.Intn(desiredMax-minOffsetBase+1))
out[base+4] = float32(off)
}
}
// 5) 最后 5 个字节alpha 行)由 caller 指定并固定
for i := 0; i < 5; i++ {
out[15+i] = alphaRow[i]
}
return out
}
// 便利 wrapper默认 redBias=0.6brightnessScale=0.5
func RandomMatrixNoSingleColorBrightDefault(seed int64, alphaRow [5]float32, brightChance float64) [20]float32 {
return RandomMatrixNoSingleColorBright(seed, alphaRow, brightChance, 0.6, 0.5)
}