Реализация Negamax для шашек / шашек
Я пытался реализовать хороший ИИ для игры в шашки, созданной в Unity3D, и, выполнив поиск в Интернете, я нашел лучший выбор - MiniMax/Negamax, поэтому я создал этот класс:
public static class NegaMax
{
public static IMiniMaxNode FindBestChoice(IEnumerable<IMiniMaxNode> choices, int depth, int sign)
{
// if I simply use -Calculate(...) I'm obtaining different results based on whether the depth is even or odd
// I suspect this is wrong nonetheless
int inverter = (depth % 2 == 0) ? 1 : -1;
IMiniMaxNode bestNode = null;
float alpha = float.NegativeInfinity;
foreach (var choice in choices)
{
var score = inverter * Calculate(choice, depth - 1, float.NegativeInfinity, -alpha, -sign);
if (score > alpha)
{
alpha = score;
bestNode = choice;
}
}
return bestNode;
}
private static float Calculate(IMiniMaxNode node, int depth, float alpha, float beta, int sign)
{
if (depth == 0)
{
return node.CalculateValue() * sign;
}
node.CreateChildren();
if (node.Children.Length == 0) // if the opponent has no possible move
{
return sign / 0f; // (sign == 1) ? positive infinity : negative infinity
}
// standard negamax
var bestValue = float.NegativeInfinity;
for (int i = 0; i < node.Children.Length; i++)
{
var value = -Calculate(node.Children[i], depth - 1, -beta, -alpha, -sign);
bestValue = Math.Max(bestValue, value);
alpha = Math.Max(alpha, bestValue);
if (alpha >= beta)
{
return bestValue;
}
}
return bestValue;
}
}
где IMiniMaxNode - это следующий интерфейс:
public interface IMiniMaxNode
{
IMiniMaxNode Parent { get; }
IMiniMaxNode[] Children { get; }
float CalculateValue();
void CreateChildren();
}
и фактическая реализация такова:
public class CheckersMove : IMiniMaxNode
{
public int Team { get; private set; }
public IMiniMaxNode Parent { get; private set; }
public IMiniMaxNode[] Children { get; private set; }
public float CalculateValue()
{
// data.state is the current board array after this move has been applied
// the board array is an int[8,8]:
// empty = 0, black pawn = -1, black king = -2, white pawn = 1, white king = 2
//
// and GetAbsoluteValue() simply returns a Sum of each element
return data.state.GetAbsoluteValue() * Team;
}
public void CreateChildren()
{
// calculate every possible move for the opponent and assign them to the Children array
// every child has Team = -Parent.Team
// also, if a move has multiple jumps, they all get included in the same node
}
// ... other methods and properties to calculate the possible moves,
// and to store movement data (starting position, movement deltas, jump[s], promotion)
}
Затем я использую его в своем классе CheckersAI:
private static MovementData CalculateMoveInternal(State state)
{
// - state.team represents the team that has to make a move:
// black = -1, white = +1
// - state.input is the current board state, represented an int[8,8] array:
// empty = 0, black pawn = -1, black king = -2, white pawn = 1, white king = 2
// - state.depth is simply the depth argument for the negamax function
// create an empty root node, corresponding to the opponent's last move (hence -state.team)
var move = CheckersMove.CreateRoot(state.input, -state.team);
// calculate all possible moves for the current team
move.CreateChildren();
// find the best node amongst all of the root node children (shuffled to obtain different results if the best moves have the same value)
var result = NegaMax.FindBestChoice(move.Children.Shuffle(), state.depth, state.team) as CheckersMove;
// cache some values for debugging
_lastRootMove = move;
_lastChosenMove = result;
// convert the result node into a valid move for the engine
return CreateMovementData(result);
}
Похоже, что ИИ работает нормально на протяжении большей части матча, но иногда принимает просто неправильные решения (например, жертвует 2 пешки без видимой причины), а иногда он инвертирует значение случая "Нет возможных ходов" в том смысле, что он присваивает ему положительный результат. бесконечность (победа), хотя она должна была быть отрицательной бесконечностью (потеряна).
Я на 90% уверен, что проблема где-то в неправильном знаке, но я пробовал каждую возможную комбинацию знаков в каждом методе, и ИИ всегда принимает неожиданные решения, я проверяю его как черную команду (-1) и Белая команда (+1).
Может ли кто-нибудь помочь мне, указав, что я могу делать неправильно? Я попытался включить весь соответствующий код и прокомментировать каждый важный отрывок. Благодарю вас!