| using System; |
| using Unity.MLAgents.Integrations.Match3; |
| using UnityEngine; |
| using UnityEngine.Serialization; |
|
|
| namespace Unity.MLAgentsExamples |
| { |
|
|
|
|
| public class Match3Board : AbstractBoard |
| { |
| public int MinRows; |
| [FormerlySerializedAs("Rows")] |
| public int MaxRows; |
|
|
| public int MinColumns; |
| [FormerlySerializedAs("Columns")] |
| public int MaxColumns; |
|
|
| public int NumCellTypes; |
| public int NumSpecialTypes; |
|
|
| public const int k_EmptyCell = -1; |
| [Tooltip("Points earned for clearing a basic cell (cube)")] |
| public int BasicCellPoints = 1; |
|
|
| [Tooltip("Points earned for clearing a special cell (sphere)")] |
| public int SpecialCell1Points = 2; |
|
|
| [Tooltip("Points earned for clearing an extra special cell (plus)")] |
| public int SpecialCell2Points = 3; |
|
|
| |
| |
| |
| public int RandomSeed; |
|
|
| (int CellType, int SpecialType)[,] m_Cells; |
| bool[,] m_Matched; |
|
|
| private BoardSize m_CurrentBoardSize; |
|
|
| System.Random m_Random; |
|
|
| void Awake() |
| { |
| m_Cells = new (int, int)[MaxColumns, MaxRows]; |
| m_Matched = new bool[MaxColumns, MaxRows]; |
|
|
| |
| m_CurrentBoardSize = new BoardSize |
| { |
| Rows = MaxRows, |
| Columns = MaxColumns, |
| NumCellTypes = NumCellTypes, |
| NumSpecialTypes = NumSpecialTypes |
| }; |
| } |
|
|
| int CreateNewSeed() |
| { |
| #if UNITY_6000_3_OR_NEWER |
| return gameObject.GetEntityId().GetHashCode(); |
| #else |
| return gameObject.GetInstanceID(); |
| #endif |
| } |
|
|
| void Start() |
| { |
| m_Random = new System.Random(RandomSeed == -1 ? CreateNewSeed() : RandomSeed); |
| InitRandom(); |
| } |
|
|
| public override BoardSize GetMaxBoardSize() |
| { |
| return new BoardSize |
| { |
| Rows = MaxRows, |
| Columns = MaxColumns, |
| NumCellTypes = NumCellTypes, |
| NumSpecialTypes = NumSpecialTypes |
| }; |
| } |
|
|
| public override BoardSize GetCurrentBoardSize() |
| { |
| return m_CurrentBoardSize; |
| } |
|
|
| |
| |
| |
| |
| |
| public void UpdateCurrentBoardSize() |
| { |
| var newRows = m_Random.Next(MinRows, MaxRows + 1); |
| var newCols = m_Random.Next(MinColumns, MaxColumns + 1); |
| m_CurrentBoardSize.Rows = newRows; |
| m_CurrentBoardSize.Columns = newCols; |
| } |
|
|
| public override bool MakeMove(Move move) |
| { |
| if (!IsMoveValid(move)) |
| { |
| return false; |
| } |
| var originalValue = m_Cells[move.Column, move.Row]; |
| var (otherRow, otherCol) = move.OtherCell(); |
| var destinationValue = m_Cells[otherCol, otherRow]; |
|
|
| m_Cells[move.Column, move.Row] = destinationValue; |
| m_Cells[otherCol, otherRow] = originalValue; |
| return true; |
| } |
|
|
| public override int GetCellType(int row, int col) |
| { |
| if (row >= m_CurrentBoardSize.Rows || col >= m_CurrentBoardSize.Columns) |
| { |
| throw new IndexOutOfRangeException(); |
| } |
| return m_Cells[col, row].CellType; |
| } |
|
|
| public override int GetSpecialType(int row, int col) |
| { |
| if (row >= m_CurrentBoardSize.Rows || col >= m_CurrentBoardSize.Columns) |
| { |
| throw new IndexOutOfRangeException(); |
| } |
| return m_Cells[col, row].SpecialType; |
| } |
|
|
| public override bool IsMoveValid(Move m) |
| { |
| if (m_Cells == null) |
| { |
| return false; |
| } |
|
|
| return SimpleIsMoveValid(m); |
| } |
|
|
| public bool MarkMatchedCells(int[,] cells = null) |
| { |
| ClearMarked(); |
| bool madeMatch = false; |
| for (var i = 0; i < m_CurrentBoardSize.Rows; i++) |
| { |
| for (var j = 0; j < m_CurrentBoardSize.Columns; j++) |
| { |
| |
| var matchedRows = 0; |
| for (var iOffset = i; iOffset < m_CurrentBoardSize.Rows; iOffset++) |
| { |
| if (m_Cells[j, i].CellType != m_Cells[j, iOffset].CellType) |
| { |
| break; |
| } |
|
|
| matchedRows++; |
| } |
|
|
| if (matchedRows >= 3) |
| { |
| madeMatch = true; |
| for (var k = 0; k < matchedRows; k++) |
| { |
| m_Matched[j, i + k] = true; |
| } |
| } |
|
|
| |
| var matchedCols = 0; |
| for (var jOffset = j; jOffset < m_CurrentBoardSize.Columns; jOffset++) |
| { |
| if (m_Cells[j, i].CellType != m_Cells[jOffset, i].CellType) |
| { |
| break; |
| } |
|
|
| matchedCols++; |
| } |
|
|
| if (matchedCols >= 3) |
| { |
| madeMatch = true; |
| for (var k = 0; k < matchedCols; k++) |
| { |
| m_Matched[j + k, i] = true; |
| } |
| } |
| } |
| } |
|
|
| return madeMatch; |
| } |
|
|
| |
| |
| |
| |
| public int ClearMatchedCells() |
| { |
| var pointsByType = new[] { BasicCellPoints, SpecialCell1Points, SpecialCell2Points }; |
| int pointsEarned = 0; |
| for (var i = 0; i < m_CurrentBoardSize.Rows; i++) |
| { |
| for (var j = 0; j < m_CurrentBoardSize.Columns; j++) |
| { |
| if (m_Matched[j, i]) |
| { |
| var speciaType = GetSpecialType(i, j); |
| pointsEarned += pointsByType[speciaType]; |
| m_Cells[j, i] = (k_EmptyCell, 0); |
| } |
| } |
| } |
|
|
| ClearMarked(); |
| return pointsEarned; |
| } |
|
|
| public bool DropCells() |
| { |
| var madeChanges = false; |
| |
| for (var j = 0; j < m_CurrentBoardSize.Columns; j++) |
| { |
| var writeIndex = 0; |
| for (var readIndex = 0; readIndex < m_CurrentBoardSize.Rows; readIndex++) |
| { |
| m_Cells[j, writeIndex] = m_Cells[j, readIndex]; |
| if (m_Cells[j, readIndex].CellType != k_EmptyCell) |
| { |
| writeIndex++; |
| } |
| } |
|
|
| |
| for (; writeIndex < m_CurrentBoardSize.Rows; writeIndex++) |
| { |
| madeChanges = true; |
| m_Cells[j, writeIndex] = (k_EmptyCell, 0); |
| } |
| } |
|
|
| return madeChanges; |
| } |
|
|
| public bool FillFromAbove() |
| { |
| bool madeChanges = false; |
| for (var i = 0; i < m_CurrentBoardSize.Rows; i++) |
| { |
| for (var j = 0; j < m_CurrentBoardSize.Columns; j++) |
| { |
| if (m_Cells[j, i].CellType == k_EmptyCell) |
| { |
| madeChanges = true; |
| m_Cells[j, i] = (GetRandomCellType(), GetRandomSpecialType()); |
| } |
| } |
| } |
|
|
| return madeChanges; |
| } |
|
|
| public (int, int)[,] Cells |
| { |
| get { return m_Cells; } |
| } |
|
|
| public bool[,] Matched |
| { |
| get { return m_Matched; } |
| } |
|
|
| |
| public void InitRandom() |
| { |
| for (var i = 0; i < MaxRows; i++) |
| { |
| for (var j = 0; j < MaxColumns; j++) |
| { |
| m_Cells[j, i] = (GetRandomCellType(), GetRandomSpecialType()); |
| } |
| } |
| } |
|
|
| public void InitSettled() |
| { |
| InitRandom(); |
| while (true) |
| { |
| var anyMatched = MarkMatchedCells(); |
| if (!anyMatched) |
| { |
| return; |
| } |
| ClearMatchedCells(); |
| DropCells(); |
| FillFromAbove(); |
| } |
| } |
|
|
| void ClearMarked() |
| { |
| for (var i = 0; i < MaxRows; i++) |
| { |
| for (var j = 0; j < MaxColumns; j++) |
| { |
| m_Matched[j, i] = false; |
| } |
| } |
| } |
|
|
| int GetRandomCellType() |
| { |
| return m_Random.Next(0, NumCellTypes); |
| } |
|
|
| int GetRandomSpecialType() |
| { |
| |
| |
| |
| var N = 10; |
| var val = m_Random.Next(0, N); |
| if (val == 0) |
| { |
| return 2; |
| } |
|
|
| if (val <= 2) |
| { |
| return 1; |
| } |
|
|
| return 0; |
| } |
|
|
| } |
| } |
|
|