Labels: eight rows (model, build, fan blades, contacts, bracket, print, serial, seal); serial text rules by brand and tier

This commit is contained in:
ZHU MINCHAO committed 2026-11-03 14:07:40 +08:00
1 parent 202bdbbfbc
commit 9d19df407a
3 files changed
+113 -63

No files matched your search

+76 -32
View File
@@ -3,52 +3,56 @@ using System.Linq;
namespace Decentralized.Domain namespace Decentralized.Domain
{ {
/// <summary>卖家的说法:每个标签一项。型号是文字,其余五项取值与 <see cref="CardLook"/> 的取值一致。</summary> /// <summary>卖家的说法:每个标签一项。型号是文字,其余取值与 <see cref="CardLook"/> 一致;「供电与风扇」由说法里的型号决定档次。</summary>
public struct Claim public struct Claim
{ {
public string Model; public string Model;
public int Fan, Fingers, Bracket, Print, Sticker; public int Fan, Fingers, Bracket, Print, Sticker, Serial;
public int Get(int row) => row == CardLabels.RowFan ? Fan : row == CardLabels.RowFingers ? Fingers : row == CardLabels.RowBracket ? Bracket : row == CardLabels.RowPrint ? Print : Sticker; public int Tier => Hardware.Specs[Model].Tier;
public int Get(int row) => row == CardLabels.RowBuild ? Tier : row == CardLabels.RowFan ? Fan : row == CardLabels.RowFingers ? Fingers
: row == CardLabels.RowBracket ? Bracket : row == CardLabels.RowPrint ? Print : row == CardLabels.RowSerial ? Serial : Sticker;
public Claim With(int row, int value) public Claim With(int row, int value)
{ {
var c = this; var c = this;
if (row == CardLabels.RowFan) c.Fan = value; else if (row == CardLabels.RowFingers) c.Fingers = value; if (row == CardLabels.RowFan) c.Fan = value; else if (row == CardLabels.RowFingers) c.Fingers = value;
else if (row == CardLabels.RowBracket) c.Bracket = value; else if (row == CardLabels.RowPrint) c.Print = value; else c.Sticker = value; else if (row == CardLabels.RowBracket) c.Bracket = value; else if (row == CardLabels.RowPrint) c.Print = value;
else if (row == CardLabels.RowSerial) c.Serial = value; else if (row == CardLabels.RowSeal) c.Sticker = value;
return c; return c;
} }
public string Encode() => $"{Model}:{Fan}{Fingers}{Bracket}{Print}{Sticker}"; public string Encode() => $"{Model}:{Fan}{Fingers}{Bracket}{Print}{Sticker}{Serial}";
public static Claim Decode(string s) public static Claim Decode(string s)
{ {
var p = (s ?? "").Split(':'); var p = (s ?? "").Split(':');
if (p.Length != 2 || p[1].Length != 5 || !Hardware.Specs.ContainsKey(p[0])) return default; if (p.Length != 2 || p[1].Length != 6 || !Hardware.Specs.ContainsKey(p[0])) return default;
int D(int i) => Math.Max(0, Math.Min(2, p[1][i] - '0')); int D(int i) => Math.Max(0, Math.Min(2, p[1][i] - '0'));
return new Claim { Model = p[0], Fan = D(0), Fingers = D(1), Bracket = D(2), Print = D(3), Sticker = D(4) }; return new Claim { Model = p[0], Fan = D(0), Fingers = D(1), Bracket = D(2), Print = D(3), Sticker = D(4), Serial = D(5) };
} }
/// <summary>按真实情况写出的说法(没有隐瞒)。</summary> /// <summary>按真实情况写出的说法(没有隐瞒)。</summary>
public static Claim Truth(Unit u) public static Claim Truth(Unit u)
{ {
var l = u.Appearance; var l = u.Appearance;
return new Claim { Model = u.FakeOf ?? u.Model, Fan = l.Fan, Fingers = l.Fingers, Bracket = l.Bracket, Print = l.Print, Sticker = l.Sticker }; return new Claim { Model = u.FakeOf ?? u.Model, Fan = l.Fan, Fingers = l.Fingers, Bracket = l.Bracket, Print = l.Print, Sticker = l.Sticker, Serial = l.Serial };
} }
} }
/// <summary> /// <summary>
/// 标签栏的数据:六个标签(型号、风扇、金手指、挡板、丝印、贴纸),每个标签有卖家的说法和真实情况。 /// 标签栏的数据:八个标签(型号、供电与风扇、风扇叶片、金手指、挡板、背面丝印、序列号、封条),每个标签有卖家的说法和真实情况。
/// 一个标签被指出(拆穿)之后,说法改回真实情况;价格按「已指出的标签取真实情况,其余取说法」算, /// 一个标签被指出(拆穿)之后,说法改回真实情况;价格按「已指出的标签取真实情况,其余取说法」算,
/// 所以卖家隐瞒的问题要玩家一项项指出来才能把价格拉回真实价值。 /// 所以卖家隐瞒的问题要玩家一项项指出来才能把价格拉回真实价值。指出假货的证据(供电与风扇、丝印、序列号)会连带把型号改回真实型号。
/// </summary> /// </summary>
public static class CardLabels public static class CardLabels
{ {
public const int RowSpec = 1, RowFan = 2, RowFingers = 4, RowBracket = 8, RowPrint = 16, RowSticker = 32, AllRows = 63; public const int RowSpec = 1, RowBuild = 2, RowFan = 4, RowFingers = 8, RowBracket = 16, RowPrint = 32, RowSerial = 64, RowSeal = 128, AllRows = 255;
public static readonly int[] Rows = { RowSpec, RowFan, RowFingers, RowBracket, RowPrint, RowSticker }; public static readonly int[] Rows = { RowSpec, RowBuild, RowFan, RowFingers, RowBracket, RowPrint, RowSerial, RowSeal };
/// <summary>各标签各取值对价格的影响(百分比,相加):风扇积灰 −4%、缺角 −12%;金手指发暗 −4%、氧化 −12%;挡板划痕 −2%;封条缺失 −6%。 /// <summary>各标签各取值对价格的影响(百分比,相加):风扇叶片积灰 −4%、缺角 −12%;金手指发暗 −4%、氧化 −12%;挡板划痕 −2%;封条缺失 −6%。
/// 接口数不对、丝印、手写序列号本身不扣价,它们是假货的证据。</summary> /// 型号、供电与风扇、丝印、序列号本身不扣价,它们决定的是这块卡是什么芯片。</summary>
public static double Pct(int row, int value) public static double Pct(int row, int value)
{ {
switch (row) switch (row)
@@ -56,14 +60,22 @@ namespace Decentralized.Domain
case RowFan: return value == 1 ? -4 : value == 2 ? -12 : 0; case RowFan: return value == 1 ? -4 : value == 2 ? -12 : 0;
case RowFingers: return value == 1 ? -4 : value == 2 ? -12 : 0; case RowFingers: return value == 1 ? -4 : value == 2 ? -12 : 0;
case RowBracket: return value == 1 ? -2 : 0; case RowBracket: return value == 1 ? -2 : 0;
case RowSticker: return value == 2 ? -6 : 0; case RowSeal: return value == 2 ? -6 : 0;
} }
return 0; return 0;
} }
/// <summary>这一项的真实取值是不是假货的证据。</summary> /// <summary>这一行是不是假货的证据:说法与真实情况不同,并且真实情况本身不该出现在真卡上。</summary>
public static bool IsFakeSign(int row, int realValue) => public static bool IsEvidence(int row, Claim claim, Claim real)
(row == RowBracket && realValue == 2) || (row == RowPrint && realValue != 0) || (row == RowSticker && realValue == 1); {
switch (row)
{
case RowBuild: return claim.Tier != real.Tier;
case RowPrint: return real.Print != 0 && claim.Print != real.Print;
case RowSerial: return real.Serial != 0 && claim.Serial != real.Serial;
}
return false;
}
/// <summary> /// <summary>
/// 价值:claim 为空表示没有人说过什么,一律按真实情况;exposed 里的位表示已指出的标签(取真实情况)。 /// 价值:claim 为空表示没有人说过什么,一律按真实情况;exposed 里的位表示已指出的标签(取真实情况)。
@@ -74,9 +86,8 @@ namespace Decentralized.Domain
var real = Claim.Truth(u); var real = Claim.Truth(u);
Claim believed = claim == null ? real : claim.Value; Claim believed = claim == null ? real : claim.Value;
double sum = 0; double sum = 0;
foreach (int r in Rows) foreach (int r in new[] { RowFan, RowFingers, RowBracket, RowSeal })
{ {
if (r == RowSpec) continue;
int v = claim == null || (exposed & r) != 0 ? real.Get(r) : believed.Get(r); int v = claim == null || (exposed & r) != 0 ? real.Get(r) : believed.Get(r);
sum += Pct(r, v); sum += Pct(r, v);
} }
@@ -85,24 +96,23 @@ namespace Decentralized.Domain
return Math.Max(1, (long)Math.Round(value)); return Math.Max(1, (long)Math.Round(value));
} }
/// <summary>指出一个标签:返回 0 没问题(说法属实)、1 隐瞒了对价格不利的情况、2 拆穿了假货。 /// <summary>指出一个标签:返回 0 没问题(说法属实)、1 隐瞒了对价格不利的情况、2 拆穿了假货(型号一并改回真实型号)。</summary>
/// 指出假货的证据(接口数、丝印、手写序列号)时,型号标签一并拆穿。</summary>
public static int Point(Unit u, Claim claim, int row, ref int exposed) public static int Point(Unit u, Claim claim, int row, ref int exposed)
{ {
var real = Claim.Truth(u); var real = Claim.Truth(u);
bool wasExposed = (exposed & row) != 0; bool wasExposed = (exposed & row) != 0;
exposed |= row; exposed |= row;
if (row == RowSpec) return claim.Model != real.Model ? 2 : 0; if (row == RowSpec) return claim.Model != real.Model ? 2 : 0;
if (IsFakeSign(row, real.Get(row)) && claim.Get(row) != real.Get(row)) if (IsEvidence(row, claim, real))
{ {
exposed |= RowSpec; exposed |= RowSpec | RowBuild;
return 2; return 2;
} }
if (wasExposed) return 0; if (wasExposed) return 0;
return claim.Get(row) != real.Get(row) ? 1 : 0; return claim.Get(row) != real.Get(row) ? 1 : 0;
} }
/// <summary>说法里隐瞒了什么:说法比真实情况价值高的标签,以及型号不符。</summary> /// <summary>说法里隐瞒了什么:说法比真实情况价值高的标签,型号不符,以及假货的证据。</summary>
public static int Lies(Unit u, Claim claim, int exposed) public static int Lies(Unit u, Claim claim, int exposed)
{ {
var real = Claim.Truth(u); var real = Claim.Truth(u);
@@ -111,24 +121,58 @@ namespace Decentralized.Domain
{ {
if ((exposed & r) != 0) continue; if ((exposed & r) != 0) continue;
if (r == RowSpec) { if (claim.Model != real.Model) lies |= r; } if (r == RowSpec) { if (claim.Model != real.Model) lies |= r; }
else if (claim.Get(r) != real.Get(r) && (Pct(r, claim.Get(r)) > Pct(r, real.Get(r)) || IsFakeSign(r, real.Get(r)))) lies |= r; else if (IsEvidence(r, claim, real)) lies |= r;
else if (claim.Get(r) != real.Get(r) && Pct(r, claim.Get(r)) > Pct(r, real.Get(r))) lies |= r;
} }
return lies; return lies;
} }
/// <summary>卖家怎么说:老实人如实说;中间商每项有三成概率隐瞒;骗子全部说成没问题。假卡的型号一律说成标称型号。</summary> /// <summary>卖家怎么说:老实人如实说;中间商每项有七成概率说成没问题;骗子全部说成没问题。假卡的型号一律说成标称型号。</summary>
public static Claim DealerClaim(Unit u, DealerKind kind, Rng rng) public static Claim DealerClaim(Unit u, DealerKind kind, Rng rng)
{ {
var c = Claim.Truth(u); var c = Claim.Truth(u);
if (kind == DealerKind.Honest) return c;
c.Model = u.Model; c.Model = u.Model;
if (kind == DealerKind.Honest) { c.Model = u.FakeOf ?? u.Model; return c; }
double hide = kind == DealerKind.Scammer ? 1.0 : 0.7; double hide = kind == DealerKind.Scammer ? 1.0 : 0.7;
foreach (int r in Rows) foreach (int r in new[] { RowFan, RowFingers, RowBracket, RowPrint, RowSerial, RowSeal })
{
if (r == RowSpec) continue;
if (rng.Chance(hide)) c = c.With(r, 0); if (rng.Chance(hide)) c = c.With(r, 0);
}
return c; return c;
} }
// ---------- 序列号 ----------
/// <summary>各牌子的序列号前缀与合法工厂码。序列号格式:前缀 + 档次码(1 到 4)+ "-" + 工厂码(两位)+ "-" + 四位流水。</summary>
public static string BrandCode(string brand) => brand == Hardware.Probidia ? "PB" : "MD";
public static readonly int[] ProbidiaFactories = { 11, 12, 21, 34, 47 }, PmdFactories = { 13, 22, 35, 41, 58 };
public static int[] Factories(string brand) => brand == Hardware.Probidia ? ProbidiaFactories : PmdFactories;
private static uint Mix(uint seed, int salt)
{
uint x = seed * 2654435761u + (uint)salt * 40503u + 777u;
x ^= x >> 15; x *= 2246822519u; x ^= x >> 13; x *= 3266489917u; x ^= x >> 16;
return x;
}
/// <summary>
/// 卡上贴纸的序列号。合规:前缀(牌子)+ 标称型号的档次码 + 合法工厂码 + 四位流水。
/// 不合规(贴纸是后补的):工厂码不在合法范围,或流水只有三位。档次码不符:贴的是原芯片的序列号,档次码是真实芯片的档次。
/// </summary>
public static string SerialText(Unit u)
{
var label = Hardware.Specs[u.Model];
var chip = Hardware.Specs[u.FakeOf ?? u.Model];
uint seed = u.Look.Seed == 0 ? 12345u : u.Look.Seed;
int kind = u.Appearance.Serial;
int[] ok = Factories(label.Brand);
int tier = (kind == 2 ? chip.Tier : label.Tier) + 1;
int factory = ok[(int)(Mix(seed, 1) % (uint)ok.Length)];
int number = 1000 + (int)(Mix(seed, 2) % 9000);
if (kind == 1)
{
if (Mix(seed, 3) % 2 == 0) { int[] bad = { 0, 9, 66, 77, 88, 99 }; factory = bad[(int)(Mix(seed, 4) % (uint)bad.Length)]; }
else number = 100 + (int)(Mix(seed, 5) % 900);
}
return $"{BrandCode(label.Brand)}{tier}-{factory:00}-{number}";
}
} }
} }
+14 -25
View File
@@ -13,12 +13,15 @@ namespace Decentralized.Domain
public int Fan; public int Fan;
/// <summary>金手指:0 光亮,1 发暗,2 氧化发绿(老化)。</summary> /// <summary>金手指:0 光亮,1 发暗,2 氧化发绿(老化)。</summary>
public int Fingers; public int Fingers;
/// <summary>挡板:0 完好,1 划痕(老化),2 接口数量不对(假卡)。</summary> /// <summary>挡板:0 完好,1 划痕(老化)。</summary>
public int Bracket; public int Bracket;
/// <summary>背面丝印:0 与标称型号一致,1 印的是真实芯片的型号(假卡),2 重新印过、字歪(假卡)。</summary> /// <summary>背面丝印:0 与标称型号一致,1 印的是真实芯片的型号(假卡),2 重新印过、字歪(假卡)。</summary>
public int Print; public int Print;
/// <summary>背面贴纸与封条:0 印刷序列号、封条完好,1 手写序列号(假卡),2 封条缺失(旧卡或翻新卡)。</summary> /// <summary>封条:0 完好,2 缺失(开过封的旧卡或翻新卡)。</summary>
public int Sticker; public int Sticker;
/// <summary>序列号:0 合规(格式对、工厂码在合法范围、档次码与型号一致),1 不合规(工厂码不在合法范围或位数不对,贴纸是后补的),
/// 2 档次码与标称型号不符(贴的是原芯片的序列号)。后两种只出现在假卡上。</summary>
public int Serial;
public const int Variants = 3; public const int Variants = 3;
@@ -43,7 +46,7 @@ namespace Decentralized.Domain
double fan = worn + (U(Seed, 1) - 0.5) * 0.3, fin = worn + (U(Seed, 2) - 0.5) * 0.3, brk = worn + (U(Seed, 3) - 0.5) * 0.3; double fan = worn + (U(Seed, 1) - 0.5) * 0.3, fin = worn + (U(Seed, 2) - 0.5) * 0.3, brk = worn + (U(Seed, 3) - 0.5) * 0.3;
l.Fan = fan >= 0.55 ? 2 : fan >= 0.25 ? 1 : 0; l.Fan = fan >= 0.55 ? 2 : fan >= 0.25 ? 1 : 0;
l.Fingers = fin >= 0.60 ? 2 : fin >= 0.30 ? 1 : 0; l.Fingers = fin >= 0.60 ? 2 : fin >= 0.30 ? 1 : 0;
if (Bracket != 2) l.Bracket = brk >= 0.45 ? 1 : 0; l.Bracket = brk >= 0.45 ? 1 : 0;
return l; return l;
} }
@@ -52,14 +55,14 @@ namespace Decentralized.Domain
/// <summary>算力倍数:风扇积灰 ×0.97、缺角 ×0.92(降频);金手指发暗 ×0.98、氧化 ×0.94(接触不良掉速)。</summary> /// <summary>算力倍数:风扇积灰 ×0.97、缺角 ×0.92(降频);金手指发暗 ×0.98、氧化 ×0.94(接触不良掉速)。</summary>
public double HashFactor => FanHash[Fan] * FingerHash[Fingers]; public double HashFactor => FanHash[Fan] * FingerHash[Fingers];
/// <summary>五位数字,存在物品标签上:风扇、金手指、挡板、丝印、贴纸。</summary> /// <summary>六位数字,存在物品标签上:风扇、金手指、挡板、丝印、封条、序列号。</summary>
public string Encode() => $"{Fan}{Fingers}{Bracket}{Print}{Sticker}"; public string Encode() => $"{Fan}{Fingers}{Bracket}{Print}{Sticker}{Serial}";
public static CardLook Decode(string s) public static CardLook Decode(string s)
{ {
if (s == null || s.Length != 5) return default; if (s == null || (s.Length != 5 && s.Length != 6)) return default;
int D(int i) => Math.Max(0, Math.Min(Variants - 1, s[i] - '0')); 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) }; return new CardLook { Fan = D(0), Fingers = D(1), Bracket = D(2), Print = D(3), Sticker = D(4), Serial = s.Length > 5 ? D(5) : 0 };
} }
// 外观维度对应卡的真实状态:损耗、暴毙概率随之变化,价值走标签(见 CardLabels) // 外观维度对应卡的真实状态:损耗、暴毙概率随之变化,价值走标签(见 CardLabels)
@@ -73,7 +76,7 @@ namespace Decentralized.Domain
public double DeathFactor => FanDeath[Fan] * FingerDeath[Fingers]; public double DeathFactor => FanDeath[Fan] * FingerDeath[Fingers];
/// <summary>只在假卡上出现的取值:玩家一旦看到,就能肯定是假的。</summary> /// <summary>只在假卡上出现的取值:玩家一旦看到,就能肯定是假的。</summary>
public bool HasFakeSign => Bracket == 2 || Print != 0 || Sticker == 1; public bool HasFakeSign => Print != 0 || Serial != 0;
/// <summary> /// <summary>
/// 生成外观。真卡:老化维度按健康度出现,封条缺失在二手卡上很常见,不会出现破绽类取值。 /// 生成外观。真卡:老化维度按健康度出现,封条缺失在二手卡上很常见,不会出现破绽类取值。
@@ -84,26 +87,12 @@ namespace Decentralized.Domain
var look = new CardLook { Seed = rng.NextUInt() | 1, Sticker = rng.Chance(fake ? 0.35 : 0.15 + 0.30 * (1 - Math.Max(0, Math.Min(100, health)) / 100.0)) ? 2 : 0 }; var look = new CardLook { Seed = rng.NextUInt() | 1, Sticker = rng.Chance(fake ? 0.35 : 0.15 + 0.30 * (1 - Math.Max(0, Math.Min(100, health)) / 100.0)) ? 2 : 0 };
if (fake) if (fake)
{ {
int signs = rng.Chance(0.55) ? 1 : rng.Chance(0.6) ? 2 : 3; // 假卡本身就带着实物上的破绽(低档次的板子:供电口和风扇数对不上),此外骗子还可能重印丝印、补贴序列号
var order = new[] { 0, 1, 2 }; if (rng.Chance(0.55)) look.Print = rng.Chance(0.6) ? 1 : 2;
for (int i = order.Length - 1; i > 0; i--) if (rng.Chance(0.65)) look.Serial = rng.Chance(0.55) ? 2 : 1;
{
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.Appearance(health); return look.Appearance(health);
} }
} }
/// <summary> /// <summary>
+23 -6
View File
@@ -141,17 +141,17 @@ internal static class Program
var fake = CardLook.Generate(rng, 20 + (i % 80), true); var fake = CardLook.Generate(rng, 20 + (i % 80), true);
if (real.HasFakeSign || oldReal.HasFakeSign) realSign++; if (real.HasFakeSign || oldReal.HasFakeSign) realSign++;
if (!fake.HasFakeSign) fakeNoSign++; if (!fake.HasFakeSign) fakeNoSign++;
fakeSigns += (fake.Bracket == 2 ? 1 : 0) + (fake.Print != 0 ? 1 : 0) + (fake.Sticker == 1 ? 1 : 0); fakeSigns += (fake.Print != 0 ? 1 : 0) + (fake.Serial != 0 ? 1 : 0);
if (real.Fan > 0 || real.Fingers > 0) newWorn++; if (real.Fan > 0 || real.Fingers > 0) newWorn++;
if (oldReal.Fan > 0 || oldReal.Fingers > 0) oldWorn++; if (oldReal.Fan > 0 || oldReal.Fingers > 0) oldWorn++;
seen.Add($"{fake.Fan}{fake.Fingers}{fake.Bracket}{fake.Print}{fake.Sticker}"); seen.Add($"{fake.Fan}{fake.Fingers}{fake.Bracket}{fake.Print}{fake.Sticker}");
seen.Add($"{oldReal.Fan}{oldReal.Fingers}{oldReal.Bracket}{oldReal.Print}{oldReal.Sticker}"); seen.Add($"{oldReal.Fan}{oldReal.Fingers}{oldReal.Bracket}{oldReal.Print}{oldReal.Sticker}");
} }
Check(realSign == 0, "真卡从不出现破绽类取值(接口数不对、丝印不对、手写序列号)"); Check(realSign == 0, "真卡从不出现破绽类取值(丝印不对、序列号不合规)");
Check(fakeNoSign == 0, "假卡至少有一处破绽"); Check(fakeNoSign > n * 0.1 && fakeNoSign < n * 0.3, $"假卡里一成多的骗子没动丝印和序列号(只剩供电与风扇这一处实物破绽):{fakeNoSign / (double)n:P0}");
Check(Near(fakeSigns / (double)n, 1.65, 0.12), $"假卡平均 1.65 处破绽:{fakeSigns / (double)n:0.00}"); Check(Near(fakeSigns / (double)n, 0.55 * 1 + 0.65 * 1, 0.1), $"假卡平均 1.2 处额外破绽(重印丝印、补贴序列号):{fakeSigns / (double)n:0.00}");
Check(oldWorn > newWorn * 1.8, $"低健康度的真卡明显更旧:{oldWorn / (double)n:P0} 对 {newWorn / (double)n:P0}"); Check(oldWorn > newWorn * 1.8, $"低健康度的真卡明显更旧:{oldWorn / (double)n:P0} 对 {newWorn / (double)n:P0}");
Check(seen.Count >= 120, $"抽样见到 {seen.Count} 种不同组合(上限 243)"); Check(seen.Count >= 60, $"抽样见到 {seen.Count} 种不同组合(上限 243)");
Check(Hardware.Specs["RZX60"].Price == 220 && Hardware.Specs["RZX90"].Price == 5000 && Hardware.Specs["RZ900"].Price == 4000, "价格已抬高到约 1.8 倍"); Check(Hardware.Specs["RZX60"].Price == 220 && Hardware.Specs["RZX90"].Price == 5000 && Hardware.Specs["RZ900"].Price == 4000, "价格已抬高到约 1.8 倍");
var u = new Unit { Model = "RZX90", FakeOf = "RZX60", Health = 80 }; var u = new Unit { Model = "RZX90", FakeOf = "RZX60", Health = 80 };
Check(CardValue.True(u) == CardValue.Of("RZX60", 80) && CardValue.Quoted(u, 80) == CardValue.Of("RZX90", 80), "真实价值按芯片型号,标称价按标称型号"); Check(CardValue.True(u) == CardValue.Of("RZX60", 80) && CardValue.Quoted(u, 80) == CardValue.Of("RZX90", 80), "真实价值按芯片型号,标称价按标称型号");
@@ -370,11 +370,28 @@ internal static class Program
int ex2 = 0; int ex2 = 0;
var honestUnit = new Unit { Model = "RZX70", Health = 60, Look = new CardLook { Fan = 1 } }; var honestUnit = new Unit { Model = "RZX70", Health = 60, Look = new CardLook { Fan = 1 } };
Check(CardLabels.Point(honestUnit, CardLabels.DealerClaim(honestUnit, DealerKind.Honest, new Rng(1)), CardLabels.RowFan, ref ex2) == 0, "老实人说的是实话,指出去没用"); Check(CardLabels.Point(honestUnit, CardLabels.DealerClaim(honestUnit, DealerKind.Honest, new Rng(1)), CardLabels.RowFan, ref ex2) == 0, "老实人说的是实话,指出去没用");
Check(CardLabels.Lies(u, claim, 0) == (CardLabels.RowSpec | CardLabels.RowFan | CardLabels.RowFingers | CardLabels.RowPrint), "骗子隐瞒了型号、风扇、金手指、丝印"); Check(CardLabels.Lies(u, claim, 0) == (CardLabels.RowSpec | CardLabels.RowBuild | CardLabels.RowFan | CardLabels.RowFingers | CardLabels.RowPrint), "骗子隐瞒了型号、供电与风扇、风扇叶片、金手指、丝印");
Check(CardLabels.Lies(u, claim, ex) == 0, "指出之后不再算隐瞒"); Check(CardLabels.Lies(u, claim, ex) == 0, "指出之后不再算隐瞒");
Check(Claim.Decode(claim.Encode()).Encode() == claim.Encode(), "说法能存取"); Check(Claim.Decode(claim.Encode()).Encode() == claim.Encode(), "说法能存取");
}); });
Test("序列号与供电风扇:四档实物特征;序列号按牌子和档次鉴定;指出供电风扇自动改回真实型号", () =>
{
Check(Hardware.FanCount.SequenceEqual(new[] { 1, 2, 3, 3 }) && Hardware.PowerPins.SequenceEqual(new[] { "6", "8", "8", "8+8" }), "低端 6 针单风扇、中端 8 针双风扇、高端 8 针三风扇、旗舰双 8 针三风扇");
Check(Hardware.Specs.Values.Where(x => !x.Asic).All(x => Hardware.Specs.Values.Count(y => !y.Asic && y.Brand == x.Brand && y.Tier == x.Tier) == 1), "每个牌子每个档次只有一个型号");
var real = new Unit { Model = "RZ80", Health = 100, Look = new CardLook { Seed = 7 } };
var t = CardLabels.SerialText(real);
Check(t.StartsWith("MD2-") && CardLabels.Factories("PMD").Contains(int.Parse(t.Substring(4, 2))) && t.Length == 11, $"真卡的序列号:牌子 + 档次码 + 合法工厂码 + 四位流水:{t}");
var forged = new Unit { Model = "RZX90", FakeOf = "RZX60", Health = 100, Look = new CardLook { Seed = 7, Serial = 1 } };
var f = CardLabels.SerialText(forged);
Check(f.StartsWith("PB4-") && (!CardLabels.Factories("Probidia").Contains(int.Parse(f.Substring(4, 2))) || f.Length == 10), $"补贴的序列号:档次码对,工厂码不合法或位数不对:{f}");
var original = new Unit { Model = "RZX90", FakeOf = "RZX60", Health = 100, Look = new CardLook { Seed = 7, Serial = 2 } };
Check(CardLabels.SerialText(original).StartsWith("PB1-"), "贴的是原芯片的序列号:档次码是真实芯片的档次");
var claim = CardLabels.DealerClaim(original, DealerKind.Scammer, new Rng(1));
int ex = 0;
Check(CardLabels.Point(original, claim, CardLabels.RowBuild, ref ex) == 2 && (ex & CardLabels.RowSpec) != 0 && CardLabels.Value(original, claim, ex) == CardValue.True(original), "指出供电与风扇:型号自动改回真实型号,价格回到真实价值");
});
Test("外观随损耗重算:越旧外观越差;种子固定;算力受风扇与金手指影响", () => Test("外观随损耗重算:越旧外观越差;种子固定;算力受风扇与金手指影响", () =>
{ {
var rng = new Rng(5); var rng = new Rng(5);