Make ExplicitAI configurable
All checks were successful
Build / Build (push) Successful in 1m2s

This commit is contained in:
2025-10-21 11:27:04 +02:00
parent 8fd7df5d80
commit 5831df701b
2 changed files with 82 additions and 21 deletions

View File

@@ -60,7 +60,7 @@ public partial class ExplicitAI
private IPokemon? ChooseBestReplacementPokemon(byte position, bool terribleMoves,
IReadOnlyList<IPokemon> usablePokemon, IBattleSide battleSide)
{
var options = usablePokemon.Where((pokemon, index) =>
var options = usablePokemon.Where((_, index) =>
{
if (_skillFlags.ReserveLastPokemon && index == usablePokemon.Count - 1 && usablePokemon.Count > 1)
return false; // Don't switch to the last Pokemon if there are others available.
@@ -132,6 +132,9 @@ public partial class ExplicitAI
return score;
}
/// <summary>
/// Calculates the expected entry hazard damage for a given Pokémon on a given battle side.
/// </summary>
public static uint CalculateEntryHazardDamage(IPokemon pokemon, IBattleSide side)
{
var damage = 0u;

View File

@@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using PkmnLib.Dynamic.BattleFlow;
using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models;
@@ -9,52 +8,111 @@ using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.AI.Explicit;
public interface IExplicitAI
{
public bool TrainerHighSkill { get; }
public bool TrainerMediumSkill { get; }
public IRandom Random { get; }
}
/// <summary>
/// An explicit AI that has explicitly written logic for each Pokémon and move.
/// </summary>
/// <remarks>
/// This is heavily based on the AI used in <a href="https://github.com/Maruno17/pokemon-essentials">Pokémon Essentials</a>
/// </remarks>
public interface IExplicitAI
{
/// <summary>
/// Whether the trainer is of high skill level.
/// </summary>
bool TrainerHighSkill { get; }
/// <summary>
/// Whether the trainer is of medium skill level.
/// </summary>
bool TrainerMediumSkill { get; }
/// <summary>
/// The random number generator used by the AI.
/// </summary>
IRandom Random { get; }
}
/// <inheritdoc cref="IExplicitAI"/>
public partial class ExplicitAI : PokemonAI, IExplicitAI
{
/// <summary>
/// The score assigned to a move that is predicted to fail.
/// </summary>
public const int MoveFailScore = 20;
/// <summary>
/// The score assigned to a move that is considered useless.
/// </summary>
public const int MoveUselessScore = 60;
/// <summary>
/// The base score assigned to a move before modifiers.
/// </summary>
public const int MoveBaseScore = 100;
private const float TrainerSkill = 100; // TODO: This should be configurable
private SkillFlags _skillFlags = new();
private readonly float _trainerSkill = 100;
private readonly SkillFlags _skillFlags = new();
private float MoveScoreThreshold => (float)(0.6f + 0.35f * Math.Sqrt(Math.Min(TrainerSkill, 100) / 100f));
private float MoveScoreThreshold => (float)(0.6f + 0.35f * Math.Sqrt(Math.Min(_trainerSkill, 100) / 100f));
private readonly IReadOnlyExplicitAIHandlers _handlers;
private readonly IRandom _random = new RandomImpl();
/// <summary>
/// Flags that control the skill of the AI.
/// </summary>
public class SkillFlags
{
// TODO: Make these configurable
public bool CanPredictMoveFailure { get; set; } = true;
public bool ScoreMoves { get; set; } = true;
public bool ConsiderSwitching { get; set; } = true;
public bool ReserveLastPokemon { get; set; } = true;
public bool UsePokemonInOrder { get; set; } = true;
/// <summary>
/// Whether the AI can predict if a move will fail.
/// </summary>
public bool CanPredictMoveFailure { get; init; } = true;
/// <summary>
/// Whether the AI will score moves to determine their usefulness.
/// </summary>
public bool ScoreMoves { get; init; } = true;
/// <summary>
/// Whether the AI will consider switching out Pokémon.
/// </summary>
public bool ConsiderSwitching { get; init; } = true;
/// <summary>
/// Whether the AI will reserve the last Pokémon in its party (ace).
/// </summary>
public bool ReserveLastPokemon { get; init; } = false;
/// <summary>
/// Whether the AI will use Pokémon in the order they are in the party, or choose freely.
/// </summary>
public bool UsePokemonInOrder { get; init; } = false;
}
/// <inheritdoc />
/// <inheritdoc cref="ExplicitAI"/>
/// <param name="library">The library for Pokémon data.</param>
public ExplicitAI(IDynamicLibrary library) : base("explicit")
{
_handlers = library.ExplicitAIHandlers;
}
public bool TrainerHighSkill => TrainerSkill >= 45;
public bool TrainerMediumSkill => TrainerSkill >= 32;
/// <inheritdoc cref="ExplicitAI(IDynamicLibrary)"/>
/// <param name="library">The library for Pokémon data.</param>
/// <param name="trainerSkill">The skill level of the trainer (0-100).</param>
/// <param name="skillFlags">The skill flags that control the AI's behavior.</param>
public ExplicitAI(IDynamicLibrary library, float trainerSkill, SkillFlags skillFlags) : base("explicit")
{
_handlers = library.ExplicitAIHandlers;
_trainerSkill = trainerSkill;
_skillFlags = skillFlags;
}
/// <inheritdoc />
public bool TrainerHighSkill => _trainerSkill >= 45;
/// <inheritdoc />
public bool TrainerMediumSkill => _trainerSkill >= 32;
/// <inheritdoc />
public IRandom Random => _random;