Minesweeper Extended

Minesweeper Extended offers a gathered place to play the original classic take on Minesweeper, same rules, same style. Or, you could try some completely different styles of the original!
Play Hex mode, an entirely different grid style.
Experience the game in a new perspective and play it in 3D mode.
You can also challenge yourself in the Endless Mode where you play until you lose or reach the end with each game getting more diffucult than the previous!

Key Features:

- Classic
- Hexagonal
- 3D
- Endless

                    
public class Grid
{
    private CellData[,] _grid;

    public int Width => _grid.GetLength(0);
    public int Height => _grid.GetLength(1);

    public CellData this[int x, int y] => _grid[x, y];
    public Grid(int width, int height)
    {
        _grid = new CellData[width, height];

        for (int x = 0; x < _grid.GetLength(0); x++)
        {
            for (int y = 0; y < _grid.GetLength(1); y++)
            {
                CellData cellData = ScriptableObject.CreateInstance<CellData>();
                _grid[x, y] = cellData;
                cellData.Position = new Vector3Int(x, y, 0);
                cellData.Type = CellData.CellType.Empty;
            }
        }
    }
}
                    
                

MINES

Mines are generated on the first click of a cell.
A list of all valid cells is created, this does not include the first clicked cell and it's adjacent neighbors. The list is then shuffled.
Mines are placed by looping through the list until a set number has been placed.

                    
public void CreateCellNumbers()
{
    int width = Width;
    int height = Height;

    for (int x = 0; x < width; x++)
    {
        for (int y = 0; y < height; y++)
        {
            CellData cellData = _grid[x, y];

            if (cellData.Type == CellData.CellType.Mine) continue;

            cellData.Number = GetAdjacentMines(cellData);
            cellData.Type = cellData.Number > 0 ? CellData.CellType.Number : CellData.CellType.Empty;
        }
    }
}
                    
                

REVEAL

There are three outcomes when revealing a cell.

1. Mine -> Game over.
2. Number -> The cell is revealed.
3. Empty -> The cell and adjacent cells are revealed.


Revealing an area of cells is done by recursively checking if adjacent cells are either empty or a number.

                    
private void Unchord(CellData chord)
{
    chord.IsChorded = false;

    IEnumerable<Vector2Int> adjacentOffsets = GameModeManager.Instance.CurrentGameMode != GameModeManager.GameMode.Hex
            ? _grid.GetAllAdjacent()
            : _grid.GetAdjacentHex(chord);

    foreach (Vector2Int offset in adjacentOffsets)
    {
        int x = chord.Position.x + offset.x;
        int y = chord.Position.y + offset.y;

        if (_grid.TryGetCellData(x, y, out CellData cellData))
        {
            if (cellData.IsRevealed && cellData.Type == CellData.CellType.Number)
            {
                if (_grid.GetAdjacentFlags(cellData) >= cellData.Number)
                {
                    RevealCellData(chord);
                    return;
                }
            }
        }
            
    }
}
                    
                

STEAM ACHIEVEMENTS

The managaer is a singleton to ensure only one instance exists.
I use Steam's Steamworks API.

- SetAchievement() method sets and saves an achievement.
- SetStats() method sets data for a stat and saves it.
For example, if an achievement for 10 wins, would update the amount after each win, using SetStats().