Редкий PvE-торговец: синий кристалл за 30 000 / 60 000 золота, не больше 2 за 7 суток (регламент валют, разд. 3)

- CrystalTrader (без Unity) + 5 тестов из раздела 12
- Входит в недельный PvE-предел; нет квоты, места или покупок -- блок, золото не списывается
- Магазин: карточка «Редкий торговец»; DEV «Золото 100 000» для проверки

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-06 16:27:27 +01:00
co-authored by Claude Opus 5.5
parent 277afdf84e
commit b51ad41925
9 changed files with 288 additions and 3 deletions
@@ -584,3 +584,87 @@ public class CrystalQuotaTests
Assert.AreEqual(2, q.Used(T0 + 10), "квота потрачена один раз: 2, а не 4");
}
}
// Редкий PvE-торговец: синий кристалл за золото (регламент валют 05.11, разд. 3, 10, 12)
public class CrystalTraderTests
{
const long T0 = 1_800_000_000_000L;
const long Day = CrystalQuota.Day;
static System.Func<int, bool> Wallet(float[] gold) => price =>
{
if (gold[0] < price) return false;
gold[0] -= price;
return true;
};
[Test]
public void Trader_Quota4of6_Buys_ChargesGold_CountsQuota_Max2()
{
var quota = new CrystalQuota(6, 7 * Day);
quota.Take("old", 4, T0);
var t = new CrystalTrader(new[] { 30000, 60000 }, 7 * Day);
var gold = new[] { 100000f };
Assert.AreEqual(CrystalTrader.Result.Bought, t.TryBuy("b1", T0 + 1, quota, 10, Wallet(gold)));
Assert.AreEqual(70000f, gold[0], 0.01, "первая -- 30 000");
Assert.AreEqual(5, quota.Used(T0 + 1), "квота +1");
Assert.AreEqual(CrystalTrader.Result.Bought, t.TryBuy("b2", T0 + 2, quota, 10, Wallet(gold)));
Assert.AreEqual(10000f, gold[0], 0.01, "вторая -- 60 000");
Assert.AreEqual(CrystalTrader.Result.NoSlots, t.TryBuy("b3", T0 + 3, quota, 10, Wallet(gold)), "третьей нет");
Assert.AreEqual(10000f, gold[0], 0.01, "золото не списано");
}
[Test]
public void Trader_Quota6of6_Blocked_NoGoldSpent()
{
var quota = new CrystalQuota(6, 7 * Day);
quota.Take("old", 6, T0);
var t = new CrystalTrader(null, 7 * Day);
var gold = new[] { 50000f };
Assert.AreEqual(CrystalTrader.Result.NoQuota, t.TryBuy("b1", T0 + 1, quota, 10, Wallet(gold)));
Assert.AreEqual(50000f, gold[0], 0.01, "6/6 + покупка -> блок без расхода золота");
Assert.AreEqual(0, t.BoughtInWindow(T0 + 1));
}
[Test]
public void Trader_NoSpace_Or_NoGold_NothingChanges()
{
var quota = new CrystalQuota(6, 7 * Day);
var t = new CrystalTrader(null, 7 * Day);
var gold = new[] { 50000f };
Assert.AreEqual(CrystalTrader.Result.NoSpace, t.TryBuy("b1", T0, quota, 0, Wallet(gold)), "хранилище полное");
Assert.AreEqual(50000f, gold[0], 0.01);
var poor = new[] { 1000f };
Assert.AreEqual(CrystalTrader.Result.NoGold, t.TryBuy("b2", T0, quota, 10, Wallet(poor)), "не хватает золота");
Assert.AreEqual(0, quota.Used(T0), "квота не тронута");
Assert.AreEqual(30000, t.PriceNow(T0), "цена по-прежнему первая");
}
[Test]
public void Trader_SameOperationTwice_OnePurchase()
{
var quota = new CrystalQuota(6, 7 * Day);
var t = new CrystalTrader(null, 7 * Day);
var gold = new[] { 100000f };
t.TryBuy("b1", T0, quota, 10, Wallet(gold));
Assert.AreEqual(CrystalTrader.Result.Repeated, t.TryBuy("b1", T0 + 1, quota, 10, Wallet(gold)));
Assert.AreEqual(70000f, gold[0], 0.01, "повтор той же покупки -- ничего не списано");
Assert.AreEqual(1, quota.Used(T0 + 1));
}
[Test]
public void Trader_WindowRolls_AndSurvivesSaveLoad()
{
var quota = new CrystalQuota(6, 7 * Day);
var t = new CrystalTrader(null, 7 * Day);
var gold = new[] { 1000000f };
t.TryBuy("b1", T0, quota, 10, Wallet(gold));
t.TryBuy("b2", T0 + Day, quota, 10, Wallet(gold));
var copy = new CrystalTrader(null, 7 * Day);
copy.Load(t.ToLines());
Assert.AreEqual(-1, copy.PriceNow(T0 + 2 * Day), "после перезапуска -- те же 2 покупки");
Assert.AreEqual(T0 + 7 * Day, copy.NextSlotMs(T0 + 2 * Day));
Assert.AreEqual(60000, copy.PriceNow(T0 + 7 * Day), "первая покупка вышла из окна -- следующая снова вторая по счёту");
Assert.AreEqual(30000, copy.PriceNow(T0 + 8 * Day), "обе вышли из окна -- снова 30 000");
}
}