Files
probably-decentralized/src/Decentralized/PredictGame.cs
T

130 lines
7.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using Decentralized.Domain;
namespace Decentralized
{
/// <summary>
/// 预测市场的接入层:按游戏当前状态给出候选盘口,夜间读游戏事件、派系行动、水纯度和本 mod 价格来结算,判断下注时玩家是否已经知道答案。
/// </summary>
public static class PredictGame
{
public static readonly string[] Factions = { "FACTION_SECURITY", "FACTION_REVOLUTION", "FACTION_LOWER_LEVEL", "FACTION_UPPER_LEVEL", "FACTION_BLACK_MARKET" };
private static readonly double[] GutterCuts = { 8200, 8400, 8600, 8800 }, RustCuts = { 9030, 9060, 9090, 9120 };
private static readonly double[] PriceMult = { 0.9, 0.95, 1.0, 1.05, 1.1 };
public static bool Available(int day) => Session.Data != null && Unlock.IsUnlocked && day >= Tuning.PredictUnlockDay;
/// <summary>夜间观测用的标识:生效中的事件,以及派系行动的 "F:派系"。</summary>
public static List<string> ActiveIds()
{
var list = new List<string>();
var mgr = StoreStation.Instance?.storeEventManager;
if (mgr == null) return list;
foreach (var e in mgr.GetActiveEvents())
{
list.Add(e.identifier);
if (e.isFactionOperation && !string.IsNullOrEmpty(e.sourceFaction)) list.Add("F:" + e.sourceFaction);
}
return list;
}
public static List<PredictCandidate> Candidates(int day)
{
var d = Session.Data;
var list = new List<PredictCandidate>();
var mgr = StoreStation.Instance?.storeEventManager;
if (mgr == null) return list;
// 常规事件:开门时先过"今天有没有事件"(5 天后才开始,有 0/1/2 件在身上时 50/35/25%),再按权重抽
int counter = mgr.GetEventCounter();
double dayChance = counter == 0 ? 0.50 : counter == 1 ? 0.35 : counter == 2 ? 0.25 : 0;
var pool = StoreEventManager.normalEventBlueprints.Where(e => !mgr.IsEventOnCooldown(e.identifier)).ToList();
double total = pool.Sum(e => (double)e.odd);
foreach (var bp in pool)
{
if (mgr.IsEventActive(bp.identifier)) continue;
string name;
try { name = bp.storeEventFunc().GetDisplayName(); } catch (Exception) { continue; }
double odd = bp.odd;
list.Add(new PredictCandidate { Kind = PredictKind.Event, Key = bp.identifier, Title = name, Prob = (from, to) => PredictMath.EventProb(dayChance, odd, total, to - from + 1) });
}
foreach (var f in Factions)
{
if (mgr.GetActiveEvents().Any(e => e.isFactionOperation && e.sourceFaction == f)) continue;
list.Add(new PredictCandidate { Kind = PredictKind.Faction, Key = f, Title = L.T("predict.faction." + f), Prob = (from, to) => PredictMath.FactionProb(to - from + 1) });
}
foreach (var t in GutterCuts)
{
double thr = t;
list.Add(new PredictCandidate { Kind = PredictKind.Purity, Key = "gutter", Title = L.T("predict.purity.gutter"), Param = thr, Tomorrow = true, Prob = (a, b) => PredictMath.PurityAtLeast(thr, 8000, 8999) });
}
foreach (var t in RustCuts)
{
double thr = t;
list.Add(new PredictCandidate { Kind = PredictKind.Purity, Key = "rust", Title = L.T("predict.purity.rust"), Param = thr, Tomorrow = true, Prob = (a, b) => PredictMath.PurityAtLeast(thr, 9000, 9150) });
}
double price = d.Market.Price, anchor = d.Market.Anchor(d.ActiveEvents);
foreach (var mult in PriceMult)
{
double thr = Math.Round(price * mult, price >= 100 ? 0 : 1);
list.Add(new PredictCandidate { Kind = PredictKind.Price, Key = "PSC", Title = "PSC", Param = thr, Prob = (from, to) => PredictMath.PriceAtLeast(price, anchor, Tuning.PscVol, Tuning.Revert, to - day, thr) });
}
// 价格盘一次只留一个(阈值不同的盘口会互相对冲)
return list;
}
/// <summary>开门后补盘。</summary>
public static void Morning(SaveData d, int day)
{
if (!Available(day)) return;
var made = d.Predict.Refill(day, d.Rng, Candidates(day).Where(c => c.Kind != PredictKind.Price || !d.Predict.Markets.Any(m => m.Outcome < 0 && m.Kind == PredictKind.Price)).ToList());
foreach (var m in made) Log.Info($"预测盘口 #{m.Id}:{m.Kind} {m.Key} {m.Title} 阈值 {m.Param} 窗口 {m.FromDay}-{m.ToDay} 先验 {m.P0:0.000}");
}
/// <summary>夜间结算的结果:事件、派系读观测;纯度读当天的数值;行情读夜间更新之前的价格。</summary>
public static bool? Outcome(PredictMarket m, SaveData d)
{
switch (m.Kind)
{
case PredictKind.Event: case PredictKind.Faction: return d.Predict.Happened(m);
case PredictKind.Purity:
var st = StoreStation.Instance;
return st == null ? (bool?)null : (m.Key == "gutter" ? st.todayGutterflowPurity : st.todayRustwaterPurity) >= m.Param;
case PredictKind.Price: return d.Market.Price >= m.Param;
}
return null;
}
/// <summary>下注时玩家是否已经知道答案:这个事件(或派系行动)已经排进日程、玩家看得到(传闻纸条、派系特权),而且落在窗口里。只有押"会发生"才算内幕单。</summary>
public static bool Known(PredictMarket m, bool yes)
{
if (!yes || (m.Kind != PredictKind.Event && m.Kind != PredictKind.Faction)) return false;
var mgr = StoreStation.Instance?.storeEventManager;
if (mgr == null) return false;
int today = StoreStation.GetDayCounter();
for (int i = 0; i < mgr.futurEvents.Count; i++)
{
var e = mgr.futurEvents[i];
if (!e.IsKnown) continue;
bool match = m.Kind == PredictKind.Event ? e.identifier == m.Key : e.isFactionOperation && e.sourceFaction == m.Key;
int start = today + mgr.futurEventsDay[i];
if (match && start >= m.FromDay && start <= m.ToDay) return true;
}
return false;
}
public static string Question(PredictMarket m)
{
string win = m.FromDay == m.ToDay ? L.T("predict.win.one", m.FromDay) : L.T("predict.win.range", m.FromDay, m.ToDay);
switch (m.Kind)
{
case PredictKind.Event: return L.T("predict.q.event", win, m.Title);
case PredictKind.Faction: return L.T("predict.q.faction", win, m.Title);
case PredictKind.Purity: return L.T("predict.q.purity", win, m.Title, Fmt.Num(m.Param));
default: return L.T("predict.q.price", win, m.Title, Fmt.Num(m.Param));
}
}
}
}