| 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; |
| } |
| } |
| } |
|
|