Support for basic AIs

This commit is contained in:
Deukhoofd 2024-10-01 11:00:01 +02:00
parent dbd07564c4
commit b43e1dc15e
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
3 changed files with 161 additions and 0 deletions

View File

@ -0,0 +1,22 @@
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.AI;
/// <summary>
/// An extremely simple AI that always passes its turn.
/// </summary>
public class PassTurnAI : PokemonAI
{
/// <inheritdoc />
public PassTurnAI() : base("pass_turn")
{
}
/// <inheritdoc />
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon)
{
return new PassChoice(pokemon);
}
}

View File

@ -0,0 +1,94 @@
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>
public abstract class PokemonAI
{
/// <summary>
/// The name of the AI.
/// </summary>
public StringKey Name { get; set; }
/// <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);
public IEnumerable<(byte side, byte position)> GetValidTargetsForMove(IPokemon user, ILearnedMove move)
{
byte GetOppositeSide(byte side) => side == 0 ? (byte)1 : (byte)0;
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();
}
}
}

View File

@ -0,0 +1,45 @@
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.AI;
/// <summary>
/// An AI that makes random choices.
/// </summary>
public class RandomAI : PokemonAI
{
private IRandom _random;
/// <inheritdoc />
public RandomAI() : base("Random")
{
_random = new RandomImpl();
}
/// <inheritdoc />
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon)
{
var moves = pokemon.Moves.Where(x => x?.CurrentPp > 0).ToList();
while (moves.Count > 0)
{
var move = moves[_random.GetInt(moves.Count)];
var targets = GetValidTargetsForMove(pokemon, move!).ToArray();
if (targets.Length == 0)
{
moves.Remove(move);
continue;
}
var target = targets[_random.GetInt(targets.Length)];
var moveTurnChoice = new MoveChoice(pokemon, move!, target.side, target.position);
if (battle.CanUse(moveTurnChoice))
{
return moveTurnChoice;
}
moves.Remove(move);
}
return new PassChoice(pokemon);
}
}