|
using Unity.MLAgents.Actuators; |
|
using Debug = UnityEngine.Debug; |
|
|
|
|
|
namespace Unity.MLAgents.Integrations.Match3 |
|
{ |
|
|
|
|
|
|
|
|
|
public class Match3Actuator : IActuator, IBuiltInActuator |
|
{ |
|
AbstractBoard m_Board; |
|
System.Random m_Random; |
|
ActionSpec m_ActionSpec; |
|
bool m_ForceHeuristic; |
|
BoardSize m_MaxBoardSize; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Match3Actuator(AbstractBoard board, |
|
bool forceHeuristic, |
|
int seed, |
|
string name) |
|
{ |
|
m_Board = board; |
|
m_MaxBoardSize = m_Board.GetMaxBoardSize(); |
|
Name = name; |
|
|
|
m_ForceHeuristic = forceHeuristic; |
|
|
|
var numMoves = Move.NumPotentialMoves(m_MaxBoardSize); |
|
m_ActionSpec = ActionSpec.MakeDiscrete(numMoves); |
|
m_Random = new System.Random(seed); |
|
} |
|
|
|
|
|
public ActionSpec ActionSpec => m_ActionSpec; |
|
|
|
|
|
public void OnActionReceived(ActionBuffers actions) |
|
{ |
|
m_Board.CheckBoardSizes(m_MaxBoardSize); |
|
if (m_ForceHeuristic) |
|
{ |
|
Heuristic(actions); |
|
} |
|
var moveIndex = actions.DiscreteActions[0]; |
|
|
|
Move move = Move.FromMoveIndex(moveIndex, m_MaxBoardSize); |
|
m_Board.MakeMove(move); |
|
} |
|
|
|
|
|
public void WriteDiscreteActionMask(IDiscreteActionMask actionMask) |
|
{ |
|
var currentBoardSize = m_Board.GetCurrentBoardSize(); |
|
m_Board.CheckBoardSizes(m_MaxBoardSize); |
|
const int branch = 0; |
|
bool foundValidMove = false; |
|
using (TimerStack.Instance.Scoped("WriteDiscreteActionMask")) |
|
{ |
|
var numMoves = m_Board.NumMoves(); |
|
|
|
var currentMove = Move.FromMoveIndex(0, m_MaxBoardSize); |
|
for (var i = 0; i < numMoves; i++) |
|
{ |
|
|
|
|
|
if (currentMove.InRangeForBoard(currentBoardSize) && m_Board.IsMoveValid(currentMove)) |
|
{ |
|
foundValidMove = true; |
|
} |
|
else |
|
{ |
|
actionMask.SetActionEnabled(branch, i, false); |
|
} |
|
currentMove.Next(m_MaxBoardSize); |
|
} |
|
|
|
if (!foundValidMove) |
|
{ |
|
|
|
|
|
|
|
|
|
if (m_Board.OnNoValidMovesAction != null) |
|
{ |
|
m_Board.OnNoValidMovesAction(); |
|
} |
|
else |
|
{ |
|
Debug.LogWarning( |
|
"No valid moves are available. The last action will be left unmasked, so " + |
|
"an invalid move will be passed to AbstractBoard.MakeMove()." |
|
); |
|
} |
|
actionMask.SetActionEnabled(branch, numMoves - 1, true); |
|
} |
|
} |
|
} |
|
|
|
|
|
public string Name { get; } |
|
|
|
|
|
public void ResetData() |
|
{ |
|
} |
|
|
|
|
|
public BuiltInActuatorType GetBuiltInActuatorType() |
|
{ |
|
return BuiltInActuatorType.Match3Actuator; |
|
} |
|
|
|
|
|
public void Heuristic(in ActionBuffers actionsOut) |
|
{ |
|
var discreteActions = actionsOut.DiscreteActions; |
|
discreteActions[0] = GreedyMove(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal int GreedyMove() |
|
{ |
|
var bestMoveIndex = 0; |
|
var bestMovePoints = -1; |
|
var numMovesAtCurrentScore = 0; |
|
|
|
foreach (var move in m_Board.ValidMoves()) |
|
{ |
|
var movePoints = EvalMovePoints(move); |
|
if (movePoints < bestMovePoints) |
|
{ |
|
|
|
continue; |
|
} |
|
|
|
if (movePoints > bestMovePoints) |
|
{ |
|
|
|
bestMovePoints = movePoints; |
|
bestMoveIndex = move.MoveIndex; |
|
numMovesAtCurrentScore = 1; |
|
} |
|
else |
|
{ |
|
|
|
|
|
numMovesAtCurrentScore++; |
|
var randVal = m_Random.Next(0, numMovesAtCurrentScore); |
|
if (randVal == 0) |
|
{ |
|
|
|
bestMoveIndex = move.MoveIndex; |
|
} |
|
} |
|
} |
|
|
|
return bestMoveIndex; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual int EvalMovePoints(Move move) |
|
{ |
|
return 1; |
|
} |
|
} |
|
} |
|
|