feat: 精分独立计分模式(independent_bonus)
引擎原算法: (底分+精分)×倍数 → 精分被庄家/自摸放大 南昌实际: 底分×倍数 + 精分 → 精分不参与倍数 新增DSL字段 wildcard_rules.scoring.independent_bonus: - false(默认): 精分混入底分(现有行为,向后兼容) - true(南昌): 精分独立加在倍数之后 代码: ScoreEngine.Settle → multiplierPart(底分×倍数) + addPart(精分) 自摸: 精分只加给赢家一次(不计入倍数) 点炮: 精分计入pay总额(放炮者全额支付)
This commit is contained in:
@ -319,7 +319,11 @@ public class WildcardConfig
|
||||
public WildcardScoringConfig? Scoring { get; set; }
|
||||
public string FanCalculationPolicy { get; set; } = "optimal";
|
||||
}
|
||||
public class WildcardScoringConfig { public int PerWildcardInWin { get; set; } }
|
||||
public class WildcardScoringConfig
|
||||
{
|
||||
public int PerWildcardInWin { get; set; }
|
||||
public bool IndependentBonus { get; set; } = false; // true=精分不参与庄家/自摸倍数(南昌麻将)
|
||||
}
|
||||
public class WinConditionConfig
|
||||
{
|
||||
[YamlMember(Alias = "pair_must_be_258")]
|
||||
|
||||
@ -49,6 +49,7 @@ public class MahjongRoom
|
||||
MaxCap = _rules.Scoring.MaxCap,
|
||||
FanStacking = _rules.FanStacking,
|
||||
PerWildcardBonus = _rules.WildcardRules?.Scoring?.PerWildcardInWin ?? 0,
|
||||
WildcardIndependent = _rules.WildcardRules?.Scoring?.IndependentBonus ?? false,
|
||||
JiHuSelfDrawOnly = _rules.WinRule?.JiHuSelfDrawOnly ?? false,
|
||||
FlowerNormalScore = _rules.FlowerRules?.Scoring?.Normal ?? 0,
|
||||
FlowerMatchScore = _rules.FlowerRules?.Scoring?.Matching?.Value ?? 0,
|
||||
|
||||
@ -9,6 +9,7 @@ public class ScoringConfig
|
||||
public int MaxCap { get; set; } = int.MaxValue;
|
||||
public string FanStacking { get; set; } = "add";
|
||||
public int PerWildcardBonus { get; set; } = 0;
|
||||
public bool WildcardIndependent { get; set; } = false; // 精分不参与倍数(南昌麻将)
|
||||
public bool JiHuSelfDrawOnly { get; set; } = false;
|
||||
public int FlowerNormalScore { get; set; } = 0;
|
||||
public int FlowerMatchScore { get; set; } = 0;
|
||||
@ -39,6 +40,7 @@ public class MahjongScoreEngine
|
||||
return;
|
||||
|
||||
int baseFan = ComputeTotalFans(result.Fans);
|
||||
int wildcardBonus = 0;
|
||||
|
||||
// Wildcard bonus: count ALL wildcards in winner's hand (not just gap-fill)
|
||||
if (_config.PerWildcardBonus > 0)
|
||||
@ -50,16 +52,15 @@ public class MahjongScoreEngine
|
||||
foreach (var m in exposed)
|
||||
totalWildInHand += state.Wildcards.CountWildcards(m.Tiles);
|
||||
if (totalWildInHand > 0)
|
||||
baseFan += totalWildInHand * _config.PerWildcardBonus;
|
||||
wildcardBonus = totalWildInHand * _config.PerWildcardBonus;
|
||||
}
|
||||
|
||||
// Flower bonus: e.g. Guangdong 每花牌+1分 + 配对花牌额外分
|
||||
// Flower bonus
|
||||
if (_config.FlowerNormalScore > 0 && state.FlowerPool.TryGetValue(winner, out var flowers) && flowers.Count > 0)
|
||||
{
|
||||
baseFan += flowers.Count * _config.FlowerNormalScore;
|
||||
if (_config.FlowerMatchScore > 0 && _config.FlowerMapping != null)
|
||||
{
|
||||
// Count matching position+season pairs
|
||||
var flowerNames = flowers.Select(f => FlowerName(f)).ToHashSet();
|
||||
foreach (var (pos, names) in _config.FlowerMapping)
|
||||
{
|
||||
@ -72,29 +73,34 @@ public class MahjongScoreEngine
|
||||
|
||||
baseFan = Math.Min(baseFan, _config.MaxCap);
|
||||
|
||||
// Apply self-draw/discard multipliers to baseFan (not wildcardBonus when independent)
|
||||
int multiplierPart = baseFan;
|
||||
int addPart = _config.WildcardIndependent ? wildcardBonus : 0;
|
||||
if (!_config.WildcardIndependent)
|
||||
multiplierPart += wildcardBonus; // classic: wildcardBonus included in multiplier
|
||||
|
||||
if (isSelfDraw)
|
||||
{
|
||||
int perPlayerPay = baseFan * _config.SelfDrawMultiplier;
|
||||
int perPlayerPay = multiplierPart * _config.SelfDrawMultiplier;
|
||||
bool dealerWon = (state.Dealer == winner);
|
||||
foreach (var p in state.AlivePlayers)
|
||||
{
|
||||
if (p == winner) continue;
|
||||
// 庄家赢→每家付倍数; 闲家赢→仅庄家付倍数
|
||||
int mul = (dealerWon || p == state.Dealer) ? _config.DealerMultiplier : 1;
|
||||
int pay = perPlayerPay * mul;
|
||||
state.Scores[p] = (state.Scores.GetValueOrDefault(p) - pay);
|
||||
state.Scores[winner] = (state.Scores.GetValueOrDefault(winner) + pay);
|
||||
}
|
||||
state.Scores[winner] = state.Scores.GetValueOrDefault(winner) + addPart;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state.LastDiscardPlayer != null && state.LastDiscardPlayer != winner)
|
||||
{
|
||||
int discardPay = baseFan * _config.DiscardWinMultiplier;
|
||||
// 庄家赢了或庄家放炮→加倍
|
||||
int discardPay = multiplierPart * _config.DiscardWinMultiplier;
|
||||
bool dealerInvolved = (state.Dealer == winner || state.Dealer == state.LastDiscardPlayer);
|
||||
int mul = dealerInvolved ? _config.DealerMultiplier : 1;
|
||||
int pay = discardPay * mul;
|
||||
int pay = discardPay * mul + addPart; // 精分也要付
|
||||
state.Scores[state.LastDiscardPlayer] = (state.Scores.GetValueOrDefault(state.LastDiscardPlayer) - pay);
|
||||
state.Scores[winner] = (state.Scores.GetValueOrDefault(winner) + pay);
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ wildcard_rules:
|
||||
fan_calculation_policy: optimal
|
||||
scoring:
|
||||
per_wildcard_in_win: 2
|
||||
independent_bonus: true
|
||||
|
||||
fan_types:
|
||||
# 基础番型
|
||||
|
||||
Reference in New Issue
Block a user