Label bar in the magnifier tray: seller's claims per label, point out, edit with a labeler
Six labels (model, fan, contacts, bracket, back print, sticker). Dealers state claims (honest tell the truth, middlemen hide 70%, scammers hide everything and name the stamped model); the price follows claims until a label is pointed out, then follows the real state. Pointing at a fake's evidence exposes the model label too. A wrong accusation costs reputation. Owned cards can have claims edited freely with a labeler present; a buyer spots each hidden flaw with 35% chance, which exposes it and costs reputation. Dimensions are additive percent adjustments on the model price. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f93dbc6eec
commit
b359a00789
9 files changed
+452
-174
No files matched your search
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>卖家的说法:每个标签一项。型号是文字,其余五项取值与 <see cref="CardLook"/> 的取值一致。</summary>
|
||||
public struct Claim
|
||||
{
|
||||
public string Model;
|
||||
public int Fan, Fingers, Bracket, Print, Sticker;
|
||||
|
||||
public int Get(int row) => row == CardLabels.RowFan ? Fan : row == CardLabels.RowFingers ? Fingers : row == CardLabels.RowBracket ? Bracket : row == CardLabels.RowPrint ? Print : Sticker;
|
||||
|
||||
public Claim With(int row, int value)
|
||||
{
|
||||
var c = this;
|
||||
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;
|
||||
return c;
|
||||
}
|
||||
|
||||
public string Encode() => $"{Model}:{Fan}{Fingers}{Bracket}{Print}{Sticker}";
|
||||
|
||||
public static Claim Decode(string s)
|
||||
{
|
||||
var p = (s ?? "").Split(':');
|
||||
if (p.Length != 2 || p[1].Length != 5 || !Hardware.Specs.ContainsKey(p[0])) return default;
|
||||
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) };
|
||||
}
|
||||
|
||||
/// <summary>按真实情况写出的说法(没有隐瞒)。</summary>
|
||||
public static Claim Truth(Unit u)
|
||||
{
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标签栏的数据:六个标签(型号、风扇、金手指、挡板、丝印、贴纸),每个标签有卖家的说法和真实情况。
|
||||
/// 一个标签被指出(拆穿)之后,说法改回真实情况;价格按「已指出的标签取真实情况,其余取说法」算,
|
||||
/// 所以卖家隐瞒的问题要玩家一项项指出来才能把价格拉回真实价值。
|
||||
/// </summary>
|
||||
public static class CardLabels
|
||||
{
|
||||
public const int RowSpec = 1, RowFan = 2, RowFingers = 4, RowBracket = 8, RowPrint = 16, RowSticker = 32, AllRows = 63;
|
||||
public static readonly int[] Rows = { RowSpec, RowFan, RowFingers, RowBracket, RowPrint, RowSticker };
|
||||
|
||||
/// <summary>各标签各取值对价格的影响(百分比,相加):风扇积灰 −4%、缺角 −12%;金手指发暗 −4%、氧化 −12%;挡板划痕 −2%;封条缺失 −6%。
|
||||
/// 接口数不对、丝印、手写序列号本身不扣价,它们是假货的证据。</summary>
|
||||
public static double Pct(int row, int value)
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case RowFan: 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 RowSticker: return value == 2 ? -6 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>这一项的真实取值是不是假货的证据。</summary>
|
||||
public static bool IsFakeSign(int row, int realValue) =>
|
||||
(row == RowBracket && realValue == 2) || (row == RowPrint && realValue != 0) || (row == RowSticker && realValue == 1);
|
||||
|
||||
/// <summary>
|
||||
/// 价值:claim 为空表示没有人说过什么,一律按真实情况;exposed 里的位表示已指出的标签(取真实情况)。
|
||||
/// 型号取「说法里的型号」,型号标签被拆穿之后取真实芯片。价值 = 型号定价 ×(0.25 + 0.75 × 健康度)×(1 + 各标签折扣之和)。
|
||||
/// </summary>
|
||||
public static long Value(Unit u, Claim? claim, int exposed)
|
||||
{
|
||||
var real = Claim.Truth(u);
|
||||
Claim believed = claim == null ? real : claim.Value;
|
||||
double sum = 0;
|
||||
foreach (int r in Rows)
|
||||
{
|
||||
if (r == RowSpec) continue;
|
||||
int v = claim == null || (exposed & r) != 0 ? real.Get(r) : believed.Get(r);
|
||||
sum += Pct(r, v);
|
||||
}
|
||||
string model = claim == null || (exposed & RowSpec) != 0 ? real.Model : believed.Model;
|
||||
double value = CardValue.Of(model, u.Health) * Math.Max(0, 1 + sum / 100.0);
|
||||
return Math.Max(1, (long)Math.Round(value));
|
||||
}
|
||||
|
||||
/// <summary>指出一个标签:返回 0 没问题(说法属实)、1 隐瞒了对价格不利的情况、2 拆穿了假货。
|
||||
/// 指出假货的证据(接口数、丝印、手写序列号)时,型号标签一并拆穿。</summary>
|
||||
public static int Point(Unit u, Claim claim, int row, ref int exposed)
|
||||
{
|
||||
var real = Claim.Truth(u);
|
||||
bool wasExposed = (exposed & row) != 0;
|
||||
exposed |= row;
|
||||
if (row == RowSpec) return claim.Model != real.Model ? 2 : 0;
|
||||
if (IsFakeSign(row, real.Get(row)) && claim.Get(row) != real.Get(row))
|
||||
{
|
||||
exposed |= RowSpec;
|
||||
return 2;
|
||||
}
|
||||
if (wasExposed) return 0;
|
||||
return claim.Get(row) != real.Get(row) ? 1 : 0;
|
||||
}
|
||||
|
||||
/// <summary>说法里隐瞒了什么:说法比真实情况价值高的标签,以及型号不符。</summary>
|
||||
public static int Lies(Unit u, Claim claim, int exposed)
|
||||
{
|
||||
var real = Claim.Truth(u);
|
||||
int lies = 0;
|
||||
foreach (int r in Rows)
|
||||
{
|
||||
if ((exposed & r) != 0) continue;
|
||||
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;
|
||||
}
|
||||
return lies;
|
||||
}
|
||||
|
||||
/// <summary>卖家怎么说:老实人如实说;中间商每项有三成概率隐瞒;骗子全部说成没问题。假卡的型号一律说成标称型号。</summary>
|
||||
public static Claim DealerClaim(Unit u, DealerKind kind, Rng rng)
|
||||
{
|
||||
var c = Claim.Truth(u);
|
||||
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;
|
||||
foreach (int r in Rows)
|
||||
{
|
||||
if (r == RowSpec) continue;
|
||||
if (rng.Chance(hide)) c = c.With(r, 0);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,20 +62,9 @@ namespace Decentralized.Domain
|
||||
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 => ValueFactorOf(AllDims);
|
||||
|
||||
/// <summary>外观折扣的维度位:1 风扇、2 金手指、4 挡板划痕、8 封条缺失。</summary>
|
||||
public const int DimFan = 1, DimFingers = 2, DimScratch = 4, DimSeal = 8, AllDims = 15;
|
||||
|
||||
/// <summary>只计入 mask 里的维度的折扣:贩子报价时先装作这些问题都没有,玩家指出一项才扣一项。</summary>
|
||||
public double ValueFactorOf(int mask) =>
|
||||
((mask & DimFan) != 0 ? FanValue[Fan] : 1.0) * ((mask & DimFingers) != 0 ? FingerValue[Fingers] : 1.0)
|
||||
* ((mask & DimScratch) != 0 && Bracket == 1 ? 0.98 : 1.0) * ((mask & DimSeal) != 0 && Sticker == 2 ? 0.94 : 1.0);
|
||||
// 外观维度对应卡的真实状态:损耗、暴毙概率随之变化,价值走标签(见 CardLabels)
|
||||
private static readonly double[] FanWear = { 1.00, 1.10, 1.30 }, FanDeath = { 1.00, 1.00, 1.20 };
|
||||
private static readonly double[] FingerDeath = { 1.00, 1.10, 1.30 };
|
||||
|
||||
/// <summary>每晚损耗倍数:风扇积灰散热差 ×1.1,缺角 ×1.3。</summary>
|
||||
public double WearFactor => FanWear[Fan];
|
||||
@@ -131,22 +120,8 @@ namespace Decentralized.Domain
|
||||
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.Appearance.ValueFactor));
|
||||
|
||||
public const double ScamDiscount = 0.90;
|
||||
|
||||
/// <summary>
|
||||
/// 贩子的报价:装作成色没问题(外观折扣只计玩家已指出的维度),在此之上加价 markupPct%。
|
||||
/// 没拆穿的假卡按标称型号的价再打九折;拆穿之后按真实芯片算,和真卡一样。
|
||||
/// </summary>
|
||||
public static long Quote(Unit u, int markupPct, int pointedDims, bool exposed)
|
||||
{
|
||||
bool fakeHidden = u.FakeOf != null && !exposed;
|
||||
double v = Of(fakeHidden ? u.Model : (u.FakeOf ?? u.Model), u.Health) * u.Appearance.ValueFactorOf(pointedDims);
|
||||
v *= fakeHidden ? ScamDiscount : 1 + markupPct / 100.0;
|
||||
return Math.Max(1, (long)Math.Round(v));
|
||||
}
|
||||
/// <summary>真实价值:全部标签都按真实情况算。</summary>
|
||||
public static long True(Unit u) => CardLabels.Value(u, null, 0);
|
||||
|
||||
/// <summary>标称价:贩子报的价,按标称型号算;二手卡也折旧,但只按贩子"自称"的健康度。</summary>
|
||||
public static long Quoted(Unit u, double claimedHealth) => Of(u.Model, claimedHealth);
|
||||
|
||||
@@ -7,20 +7,18 @@ namespace Decentralized.Domain
|
||||
/// <summary>二手显卡贩子的类型:老实人按真实价卖;中间商加价;骗子夹带假卡,按标称型号的价卖。</summary>
|
||||
public enum DealerKind { Honest, Middleman, Scammer }
|
||||
|
||||
/// <summary>贩子带来的一张卡。老实人不报价(按真实价值卖);其他贩子 <see cref="Quoted"/> 为真,按 <see cref="CardValue.Quote"/> 报价,
|
||||
/// <see cref="MarkupPct"/> 是加价百分比,玩家指出外观问题或假货迹象就按维度扣回去。</summary>
|
||||
/// <summary>贩子带来的一张卡:型号、健康度、是不是假卡。怎么说由 <see cref="CardLabels.DealerClaim"/> 决定,中间商另加 <see cref="MarkupPct"/>% 的价。</summary>
|
||||
public sealed class UsedCard
|
||||
{
|
||||
public string Model;
|
||||
public string FakeOf;
|
||||
public double Health;
|
||||
public int MarkupPct;
|
||||
public bool Quoted;
|
||||
}
|
||||
|
||||
public static class UsedDealers
|
||||
{
|
||||
public const double MiddlemanMarkup = 0.08, ScamDiscount = CardValue.ScamDiscount;
|
||||
public const double MiddlemanMarkup = 0.08;
|
||||
public const double FakeShareMiddleman = 0.10, FakeShareScammer = 0.45;
|
||||
|
||||
/// <summary>今天来的贩子的类型:骗子越往后越多。</summary>
|
||||
@@ -52,7 +50,6 @@ namespace Decentralized.Domain
|
||||
var card = new UsedCard { Model = label, Health = 25 + rng.NextDouble() * 60 };
|
||||
var cheaper = Hardware.Specs.Values.Where(s => !s.Asic && s.Price * 2 <= Hardware.Specs[label].Price).Select(s => s.Model).ToArray();
|
||||
if (cheaper.Length > 0 && rng.Chance(share)) card.FakeOf = cheaper[rng.Range(0, cheaper.Length - 1)];
|
||||
card.Quoted = kind != DealerKind.Honest;
|
||||
card.MarkupPct = kind == DealerKind.Middleman ? (int)Math.Round(MiddlemanMarkup * 100) : 0;
|
||||
cards.Add(card);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user