Files
probably-decentralized/src/Decentralized.Domain/CardLook.cs
T

109 lines
5.9 KiB
C#
Raw Normal View History

using System;
namespace Decentralized.Domain
{
/// <summary>
/// 显卡的外观:5 个维度,每个 3 种取值,共 243 种组合。前三个画在正面,后两个画在背面(右键"切换"翻面)。
/// 老化类的维度(风扇、金手指、挡板划痕)随健康度出现,真卡也会有;破绽类的取值只会出现在假卡上,
/// 玩家要结合名字(标称型号)与背面的丝印、序列号贴纸判断。
/// </summary>
public struct CardLook
{
/// <summary>风扇:0 干净,1 积灰,2 叶片缺角(老化)。</summary>
public int Fan;
/// <summary>金手指:0 光亮,1 发暗,2 氧化发绿(老化)。</summary>
public int Fingers;
/// <summary>挡板:0 完好,1 划痕(老化),2 接口数量不对(假卡)。</summary>
public int Bracket;
/// <summary>背面丝印:0 与标称型号一致,1 印的是真实芯片的型号(假卡),2 重新印过、字歪(假卡)。</summary>
public int Print;
/// <summary>背面贴纸与封条:0 印刷序列号、封条完好,1 手写序列号(假卡),2 封条缺失(旧卡或翻新卡)。</summary>
public int Sticker;
public const int Variants = 3;
/// <summary>五位数字,存在物品标签上:风扇、金手指、挡板、丝印、贴纸。</summary>
public string Encode() => $"{Fan}{Fingers}{Bracket}{Print}{Sticker}";
public static CardLook Decode(string s)
{
if (s == null || s.Length != 5) return default;
int D(int i) => Math.Max(0, Math.Min(Variants - 1, s[i] - '0'));
return new CardLook { Fan = D(0), Fingers = D(1), Bracket = D(2), Print = D(3), Sticker = D(4) };
}
// 外观维度对应卡的真实状态:价格、损耗、暴毙概率都随之变化,所以看外观就是在估价
private static readonly double[] FanValue = { 1.00, 0.96, 0.88 }, FanWear = { 1.00, 1.10, 1.30 }, FanDeath = { 1.00, 1.00, 1.20 };
private static readonly double[] FingerValue = { 1.00, 0.96, 0.88 }, FingerDeath = { 1.00, 1.10, 1.30 };
/// <summary>外观对价值的折扣:风扇积灰 −4%、缺角 −12%;金手指发暗 −4%、氧化 −12%;挡板划痕 −2%;封条缺失(开过封)−6%。</summary>
public double ValueFactor => FanValue[Fan] * FingerValue[Fingers] * (Bracket == 1 ? 0.98 : 1.0) * (Sticker == 2 ? 0.94 : 1.0);
/// <summary>每晚损耗倍数:风扇积灰散热差 ×1.1,缺角 ×1.3。</summary>
public double WearFactor => FanWear[Fan];
/// <summary>暴毙概率倍数:风扇缺角 ×1.2;金手指发暗 ×1.1、氧化 ×1.3(接触不良)。</summary>
public double DeathFactor => FanDeath[Fan] * FingerDeath[Fingers];
/// <summary>只在假卡上出现的取值:玩家一旦看到,就能肯定是假的。</summary>
public bool HasFakeSign => Bracket == 2 || Print != 0 || Sticker == 1;
/// <summary>
/// 生成外观。真卡:老化维度按健康度出现,封条缺失在二手卡上很常见,不会出现破绽类取值。
/// 假卡:在三类破绽(接口数、丝印、手写序列号)里随机挑 1–3 处,再叠加老化与封条缺失。
/// </summary>
public static CardLook Generate(Rng rng, double health, bool fake)
{
double worn = 1 - Math.Max(0, Math.Min(100, health)) / 100.0;
var look = new CardLook
{
Fan = Pick(rng, 0.10 + 0.55 * worn, 0.35 * worn),
Fingers = Pick(rng, 0.08 + 0.55 * worn, 0.40 * worn),
Bracket = rng.Chance(0.10 + 0.50 * worn) ? 1 : 0,
Sticker = rng.Chance(fake ? 0.35 : 0.15 + 0.30 * worn) ? 2 : 0,
};
if (!fake) return look;
int signs = rng.Chance(0.55) ? 1 : rng.Chance(0.6) ? 2 : 3;
var order = new[] { 0, 1, 2 };
for (int i = order.Length - 1; i > 0; i--)
{
int j = rng.Range(0, i);
(order[i], order[j]) = (order[j], order[i]);
}
for (int k = 0; k < signs; k++)
{
switch (order[k])
{
case 0: look.Bracket = 2; break;
case 1: look.Print = rng.Chance(0.6) ? 1 : 2; break;
default: look.Sticker = 1; break;
}
}
return look;
}
/// <summary>p1 的概率取 1,再以 p2 的概率升到 2。</summary>
private static int Pick(Rng rng, double p1, double p2) => rng.Chance(p1) ? (rng.Chance(p2 / Math.Max(p1, 1e-9)) ? 2 : 1) : 0;
}
/// <summary>
/// 显卡的统一价值函数:实际售价和鉴定公会的"正确价值"是同一个数。按真实芯片和健康度算;
/// 假卡的标称价(贩子报的价)用标称型号算,二者之差就是被坑的钱。
/// </summary>
public static class CardValue
{
/// <summary>价值 = 型号定价 × 健康度系数。健康 100 时满价,健康 0 时只剩 25%。</summary>
public static long Of(string model, double health)
{
var spec = Hardware.Specs[model];
double f = 0.25 + 0.75 * Math.Max(0, Math.Min(100, health)) / 100.0;
return Math.Max(1, (long)Math.Round(spec.Price * f));
}
/// <summary>真实价值(芯片型号 + 健康度 + 外观折扣)。</summary>
public static long True(Unit u) => Math.Max(1, (long)Math.Round(Of(u.FakeOf ?? u.Model, u.Health) * u.Look.ValueFactor));
/// <summary>标称价:贩子报的价,按标称型号算;二手卡也折旧,但只按贩子"自称"的健康度。</summary>
public static long Quoted(Unit u, double claimedHealth) => Of(u.Model, claimedHealth);
}
}