134 lines
5.5 KiB
C#
134 lines
5.5 KiB
C#
using System;
|
|||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Linq;
|
||
|
|
|
||
|
|
namespace Decentralized.Domain
|
||
|
|
{
|
||
|
|
/// <summary>显卡或矿机的型号。算力内部单位 H(1 H = 10 MH/s),耗电单位 kJ/天。</summary>
|
||
|
|
public sealed class HardwareSpec
|
||
|
|
{
|
||
|
|
public string Model;
|
||
|
|
public string Brand;
|
||
|
|
public double Hash;
|
||
|
|
public double Kj;
|
||
|
|
public int Price;
|
||
|
|
/// <summary>暴毙概率的品牌系数。</summary>
|
||
|
|
public double DeathFactor;
|
||
|
|
/// <summary>每天损耗的系数。</summary>
|
||
|
|
public double WearFactor;
|
||
|
|
/// <summary>矿机(ASIC):独立机器;false 为插在矿架上的显卡。</summary>
|
||
|
|
public bool Asic;
|
||
|
|
|
||
|
|
/// <summary>显示用算力:显卡 MH/s,矿机 GH/s。</summary>
|
||
|
|
public double DisplayHash => Asic ? Hash * 10 / 1000 : Hash * 10;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static class Hardware
|
||
|
|
{
|
||
|
|
public const string Probidia = "Probidia", Pmd = "PMD", RatDigg = "RatDigg";
|
||
|
|
|
||
|
|
public static readonly IReadOnlyDictionary<string, HardwareSpec> Specs = new[]
|
||
|
|
{
|
||
|
|
Gpu("RZX50", Probidia, 1.0, 4, 120), Gpu("RZX60", Probidia, 1.5, 5, 180), Gpu("RZX70", Probidia, 5.0, 12, 450),
|
||
|
|
Gpu("RZX80Ti", Probidia, 8.0, 18, 750), Gpu("RZX90", Probidia, 20.0, 30, 2800),
|
||
|
|
Gpu("RZ470", Pmd, 0.8, 2.5, 90), Gpu("RZ580", Pmd, 1.2, 3, 140), Gpu("RZ6800", Pmd, 4.0, 8, 380),
|
||
|
|
Gpu("RZ7900", Pmd, 15.0, 20, 2200),
|
||
|
|
Asic("M1", 50, 60, 6000), Asic("M2", 120, 110, 15000), Asic("M3", 300, 220, 40000),
|
||
|
|
}.ToDictionary(s => s.Model);
|
||
|
|
|
||
|
|
private static HardwareSpec Gpu(string model, string brand, double h, double kj, int price) => new HardwareSpec
|
||
|
|
{
|
||
|
|
Model = model, Brand = brand, Hash = h, Kj = kj, Price = price,
|
||
|
|
DeathFactor = brand == Probidia ? 0.7 : 1.0, WearFactor = brand == Probidia ? 1.0 : 1.2,
|
||
|
|
};
|
||
|
|
|
||
|
|
private static HardwareSpec Asic(string model, double h, double kj, int price) => new HardwareSpec
|
||
|
|
{
|
||
|
|
Model = model, Brand = RatDigg, Hash = h, Kj = kj, Price = price, DeathFactor = 0.5, WearFactor = 0.8, Asic = true,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>一张显卡或一台矿机的实例状态(存进物品标签)。</summary>
|
||
|
|
public sealed class Unit
|
||
|
|
{
|
||
|
|
public string Model;
|
||
|
|
/// <summary>假卡:标着 Model,芯片实为 FakeOf。</summary>
|
||
|
|
public string FakeOf;
|
||
|
|
public double Health = 100;
|
||
|
|
public bool Dead;
|
||
|
|
public bool Refurbished;
|
||
|
|
|
||
|
|
public HardwareSpec Label => Hardware.Specs[Model];
|
||
|
|
public HardwareSpec Chip => Hardware.Specs[FakeOf ?? Model];
|
||
|
|
|
||
|
|
/// <summary>当前算力:健康 ≥ 50 时满算力,低于 50 线性降到健康 0 时的 60%。</summary>
|
||
|
|
public double Hash(bool overclock)
|
||
|
|
{
|
||
|
|
if (Dead) return 0;
|
||
|
|
double h = Chip.Hash * (Health >= 50 ? 1.0 : 0.6 + 0.4 * Health / 50);
|
||
|
|
return overclock ? h * Tuning.OcHash : h;
|
||
|
|
}
|
||
|
|
|
||
|
|
public double Kj(bool overclock) => Dead ? 0 : Chip.Kj * (overclock ? Tuning.OcKj : 1);
|
||
|
|
|
||
|
|
/// <summary>今晚的暴毙概率。</summary>
|
||
|
|
public double DeathChance(bool overclock)
|
||
|
|
{
|
||
|
|
double p = Tuning.DeathBase + Tuning.DeathWorn * Math.Pow(1 - Health / 100, 2);
|
||
|
|
p *= Label.DeathFactor * (FakeOf != null ? Tuning.FakeDeathFactor : 1) * (overclock ? Tuning.OcDeath : 1);
|
||
|
|
return p;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>一夜的损耗与暴毙判定;返回是否在今晚暴毙。</summary>
|
||
|
|
public bool Night(bool overclock, Rng rng)
|
||
|
|
{
|
||
|
|
if (Dead) return false;
|
||
|
|
Health = Math.Max(0, Health - Label.WearFactor * (overclock ? Tuning.OcWear : 1));
|
||
|
|
if (rng.Chance(DeathChance(overclock)))
|
||
|
|
{
|
||
|
|
Dead = true;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>翻新:每张卡一次,健康 +30。</summary>
|
||
|
|
public bool Refurbish()
|
||
|
|
{
|
||
|
|
if (Dead || Refurbished) return false;
|
||
|
|
Refurbished = true;
|
||
|
|
Health = Math.Min(100, Health + Tuning.RefurbishHealth);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>全网:NPC 背景算力、出块奖励与减半。</summary>
|
||
|
|
public sealed class Network
|
||
|
|
{
|
||
|
|
public double NpcHash = Tuning.NpcHash0;
|
||
|
|
public double Reward = Tuning.BlockReward;
|
||
|
|
/// <summary>停电、动荡等事件对 NPC 算力的可用比例。</summary>
|
||
|
|
public double Availability = 1.0;
|
||
|
|
|
||
|
|
/// <summary>按算力分得今晚的出块(已扣矿池抽成)。</summary>
|
||
|
|
public double Mine(double playerHash)
|
||
|
|
{
|
||
|
|
if (playerHash <= 0) return 0;
|
||
|
|
double total = NpcHash * Availability + playerHash;
|
||
|
|
return Reward * playerHash / total * (1 - Tuning.PoolFee);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 夜间:第 HalvingDays 天的倍数时减半;NPC 矿工按"每 H 收益 / 电费"与目标比的偏离扩张或关机,每晚最多 ±5%。
|
||
|
|
/// </summary>
|
||
|
|
public void Night(int day, double price, double playerHash)
|
||
|
|
{
|
||
|
|
if (day > 0 && day % Tuning.HalvingDays == 0) Reward /= 2;
|
||
|
|
double total = NpcHash * Availability + playerHash;
|
||
|
|
double perH = Reward * price / total;
|
||
|
|
double ratio = perH / (Tuning.NpcKjPerH * Tuning.ElecPrice);
|
||
|
|
double adj = Math.Max(-Tuning.NpcAdj, Math.Min(Tuning.NpcAdj, Tuning.NpcAdj * (ratio / Tuning.NpcTargetRatio - 1)));
|
||
|
|
NpcHash *= 1 + adj;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|