116 lines
5.2 KiB
C#
116 lines
5.2 KiB
C#
using PkmnLib.Dynamic.Libraries;
|
|
using PkmnLib.Dynamic.Models;
|
|
using PkmnLib.Dynamic.Models.Choices;
|
|
using PkmnLib.Static.Moves;
|
|
using PkmnLib.Static.Utils;
|
|
|
|
namespace PkmnLib.Dynamic.AI;
|
|
|
|
/// <summary>
|
|
/// The base class for implementing an AI for Pokémon.
|
|
/// </summary>
|
|
[PublicAPI, UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
|
public abstract class PokemonAI
|
|
{
|
|
/// <summary>
|
|
/// The name of the AI.
|
|
/// </summary>
|
|
public StringKey Name { get; }
|
|
|
|
/// <inheritdoc cref="PokemonAI" />
|
|
protected PokemonAI(StringKey name)
|
|
{
|
|
Name = name;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the choice for the Pokémon.
|
|
/// </summary>
|
|
public abstract ITurnChoice GetChoice(IBattle battle, IPokemon pokemon);
|
|
|
|
/// <summary>
|
|
/// For a given user and move, returns the valid targets for that move.
|
|
/// </summary>
|
|
public IEnumerable<(byte side, byte position)> GetValidTargetsForMove(IPokemon user, ILearnedMove move)
|
|
{
|
|
var userBattleData = user.BattleData!;
|
|
switch (move.MoveData.Target)
|
|
{
|
|
case MoveTarget.Adjacent:
|
|
yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
|
|
break;
|
|
case MoveTarget.AdjacentAlly:
|
|
if (userBattleData.Position > 0)
|
|
yield return (userBattleData.SideIndex, (byte)(userBattleData.Position - 1));
|
|
if (userBattleData.Battle.PositionsPerSide > userBattleData.Position + 1)
|
|
yield return (userBattleData.SideIndex, (byte)(userBattleData.Position + 1));
|
|
break;
|
|
case MoveTarget.AdjacentAllySelf:
|
|
if (userBattleData.Position > 0)
|
|
yield return (userBattleData.SideIndex, (byte)(userBattleData.Position - 1));
|
|
if (userBattleData.Battle.PositionsPerSide > userBattleData.Position + 1)
|
|
yield return (userBattleData.SideIndex, (byte)(userBattleData.Position + 1));
|
|
yield return (userBattleData.SideIndex, userBattleData.Position);
|
|
break;
|
|
case MoveTarget.AdjacentOpponent:
|
|
yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
|
|
if (userBattleData.Position > 0)
|
|
yield return (GetOppositeSide(userBattleData.SideIndex), (byte)(userBattleData.Position - 1));
|
|
if (userBattleData.Battle.PositionsPerSide > userBattleData.Position + 1)
|
|
yield return (GetOppositeSide(userBattleData.SideIndex), (byte)(userBattleData.Position + 1));
|
|
break;
|
|
case MoveTarget.All:
|
|
yield return (userBattleData.SideIndex, userBattleData.Position);
|
|
break;
|
|
case MoveTarget.AllAdjacent:
|
|
yield return (userBattleData.SideIndex, userBattleData.Position);
|
|
break;
|
|
case MoveTarget.AllAdjacentOpponent:
|
|
yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
|
|
break;
|
|
case MoveTarget.AllAlly:
|
|
yield return (userBattleData.SideIndex, userBattleData.Position);
|
|
break;
|
|
case MoveTarget.AllOpponent:
|
|
yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
|
|
break;
|
|
case MoveTarget.Any:
|
|
foreach (var side in userBattleData.Battle.Sides)
|
|
{
|
|
foreach (var pokemon in side.Pokemon)
|
|
{
|
|
if (pokemon?.BattleData == null)
|
|
continue;
|
|
yield return (side.Index, pokemon.BattleData!.Position);
|
|
}
|
|
}
|
|
break;
|
|
case MoveTarget.RandomOpponent:
|
|
yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
|
|
break;
|
|
case MoveTarget.SelfUse:
|
|
yield return (userBattleData.SideIndex, userBattleData.Position);
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
yield break;
|
|
byte GetOppositeSide(byte side) => side == 0 ? (byte)1 : (byte)0;
|
|
}
|
|
|
|
public static List<PokemonAI> InstantiateAis(IDynamicLibrary library)
|
|
{
|
|
return AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes())
|
|
.Where(type => type.IsSubclassOf(typeof(PokemonAI)) && !type.IsAbstract).Select(x =>
|
|
{
|
|
var ctorWithLibrary = x.GetConstructor([typeof(IDynamicLibrary)]);
|
|
if (ctorWithLibrary != null)
|
|
return Activator.CreateInstance(x, library);
|
|
var defaultCtor = x.GetConstructor(Type.EmptyTypes);
|
|
if (defaultCtor != null)
|
|
return Activator.CreateInstance(x);
|
|
throw new InvalidOperationException($"No suitable constructor found for {x.Name}. " +
|
|
"Ensure it has a constructor with IDynamicLibrary parameter or a default constructor.");
|
|
}).Cast<PokemonAI>().ToList();
|
|
}
|
|
} |