| | 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 |
| | }; |
| | } |
| |
|
| | void Start() |
| | { |
| | m_Random = new System.Random(RandomSeed == -1 ? gameObject.GetInstanceID() : 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; |
| | } |
| |
|
| | } |
| | } |
| |
|