demo
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ReplacedPerson.Core
|
||||
{
|
||||
public sealed class ReplacedAi
|
||||
{
|
||||
private readonly ReplacedContentCatalog _content;
|
||||
|
||||
public ReplacedAi(ReplacedContentCatalog content) => _content = content ?? throw new ArgumentNullException(nameof(content));
|
||||
|
||||
/// <summary>Uses only own hand plus the already-public lead submission.</summary>
|
||||
public ReplacedTurnSubmission Choose(ReplacedMatchState visibleState, int playerIndex)
|
||||
{
|
||||
var self = visibleState.Players[playerIndex];
|
||||
var targetCount = visibleState.IsLastStand ? 4 : Math.Min(3, self.NormalHand.Count);
|
||||
var publicOpponent = visibleState.Submissions[1 - playerIndex];
|
||||
var opponentTypes = publicOpponent == null
|
||||
? Array.Empty<ReplacedEffectType>()
|
||||
: publicOpponent.NormalCardIds.Where(_content.Cards.ContainsKey).Select(id => _content.Cards[id].EffectType).ToArray();
|
||||
|
||||
var scored = new List<(string id, double score)>();
|
||||
foreach (var id in self.NormalHand.Distinct(StringComparer.Ordinal))
|
||||
{
|
||||
var card = _content.Cards[id];
|
||||
var expected = card.BaseValue + card.Factor * 3.5;
|
||||
var efficiency = expected / Math.Max(1, card.Cost);
|
||||
var survival = self.Health <= 10 && card.EffectType != ReplacedEffectType.Attack ? 4.0 : 0.0;
|
||||
var counterScore = opponentTypes.Sum(type => MatchupScore(card.EffectType, type));
|
||||
scored.Add((id, expected + efficiency * 2.0 + survival + counterScore));
|
||||
}
|
||||
|
||||
var selected = new List<string>();
|
||||
var remainingCost = self.Cost;
|
||||
foreach (var candidate in scored.OrderByDescending(value => value.score).ThenBy(value => value.id, StringComparer.Ordinal))
|
||||
{
|
||||
var available = self.NormalHand.Count(id => id == candidate.id);
|
||||
for (var copy = 0; copy < available && selected.Count < targetCount; copy++)
|
||||
{
|
||||
var cost = _content.Cards[candidate.id].Cost;
|
||||
if (cost > remainingCost) continue;
|
||||
selected.Add(candidate.id);
|
||||
remainingCost -= cost;
|
||||
}
|
||||
}
|
||||
|
||||
var endCount = visibleState.IsLastStand ? 2 : 1;
|
||||
var ends = self.EndHand.Where(id => _content.EndCards[id].Cost <= remainingCost)
|
||||
.OrderByDescending(id => _content.EndCards[id].SlotBase.Sum() + _content.EndCards[id].SlotFactor.Sum() * 3.5)
|
||||
.ThenBy(id => id, StringComparer.Ordinal).Take(endCount).ToList();
|
||||
if (selected.Count < (visibleState.IsLastStand ? 4 : 1) || ends.Count < endCount)
|
||||
return new ReplacedTurnSubmission { Pass = true, DiscardEndOnPass = false };
|
||||
|
||||
return new ReplacedTurnSubmission
|
||||
{
|
||||
NormalCardIds = selected,
|
||||
EndCardIds = ends,
|
||||
RerollSlot = ChooseRerollSlot(selected)
|
||||
};
|
||||
}
|
||||
|
||||
private int ChooseRerollSlot(IReadOnlyList<string> selected)
|
||||
{
|
||||
if (selected.Count == 0) return -1;
|
||||
var weakest = double.MaxValue;
|
||||
var result = -1;
|
||||
for (var i = 0; i < Math.Min(3, selected.Count); i++)
|
||||
{
|
||||
var card = _content.Cards[selected[i]];
|
||||
var expected = card.BaseValue + card.Factor * 3.5;
|
||||
if (expected < weakest) { weakest = expected; result = i; }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double MatchupScore(ReplacedEffectType own, ReplacedEffectType opponent) =>
|
||||
own == ReplacedEffectType.Counter && opponent == ReplacedEffectType.Attack ? 4.0 :
|
||||
own == ReplacedEffectType.Dodge && opponent == ReplacedEffectType.Attack ? 3.0 :
|
||||
own == ReplacedEffectType.Attack && opponent == ReplacedEffectType.Dodge ? 2.0 : 0.0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user