343 lines
12 KiB
C#
343 lines
12 KiB
C#
#if UNITY_EDITOR
|
|
namespace Sirenix.OdinInspector.Demos
|
|
{
|
|
using System;
|
|
using UnityEngine;
|
|
using Sirenix.Utilities;
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
using Sirenix.Utilities.Editor;
|
|
using Sirenix.OdinInspector.Editor;
|
|
using UnityEditor;
|
|
|
|
#endif
|
|
|
|
public class MinesweeperExample : MonoBehaviour
|
|
{
|
|
[Minesweeper]
|
|
public int NumberOfBombs;
|
|
}
|
|
|
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
|
|
public sealed class MinesweeperAttribute : Attribute
|
|
{ }
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
/// <summary>
|
|
/// Minesweeper.
|
|
/// </summary>
|
|
public sealed class MinesweeperAttributeDrawer : OdinAttributeDrawer<MinesweeperAttribute, int>
|
|
{
|
|
private enum Tile
|
|
{
|
|
Empty = 0, // Empty tile.
|
|
|
|
// 1-8.
|
|
|
|
Open = 9,
|
|
Bomb = 10,
|
|
Flag = 11,
|
|
}
|
|
|
|
private readonly Color[] NumberColors = new Color[8]
|
|
{
|
|
new Color32(42, 135, 238, 255), // 1
|
|
new Color32(57, 233, 48, 255), // 2
|
|
new Color32(253, 0, 0, 255), // 3
|
|
new Color32(31, 23, 173, 255), // 4
|
|
new Color32(36, 30, 155, 255), // 5
|
|
new Color32(131, 29, 29, 255), // 6
|
|
new Color32(40, 40, 40, 255), // 7
|
|
new Color32(132, 132, 132, 255), // 8
|
|
};
|
|
|
|
private const float TileSize = 20;
|
|
private const int BoardSize = 25;
|
|
|
|
private readonly object Key = new object();
|
|
|
|
private bool isRunning;
|
|
private bool gameOver;
|
|
private int flaggedBombs;
|
|
private int numberOfBombs;
|
|
private Tile[,] visibleTiles;
|
|
private Tile[,] tiles;
|
|
private double time;
|
|
private double prevTime;
|
|
|
|
protected override void Initialize()
|
|
{
|
|
this.isRunning = false;
|
|
this.visibleTiles = new Tile[BoardSize, BoardSize];
|
|
this.tiles = new Tile[BoardSize, BoardSize];
|
|
}
|
|
|
|
private void StartGame(int bombs)
|
|
{
|
|
this.numberOfBombs = bombs;
|
|
|
|
for (int x = 0; x < BoardSize; x++)
|
|
{
|
|
for (int y = 0; y < BoardSize; y++)
|
|
{
|
|
this.visibleTiles[x, y] = Tile.Empty;
|
|
this.tiles[x, y] = Tile.Empty;
|
|
}
|
|
}
|
|
|
|
// Spawn bombs.
|
|
for (int count = 0; count < this.numberOfBombs;)
|
|
{
|
|
int x = UnityEngine.Random.Range(0, BoardSize);
|
|
int y = UnityEngine.Random.Range(0, BoardSize);
|
|
|
|
if (this.tiles[x, y] != Tile.Bomb)
|
|
{
|
|
this.tiles[x, y] = Tile.Bomb;
|
|
|
|
if (x + 1 < BoardSize && this.tiles[x + 1, y] != Tile.Bomb)
|
|
{
|
|
this.tiles[x + 1, y] = (Tile)((int)this.tiles[x + 1, y] + 1);
|
|
}
|
|
if (x + 1 < BoardSize && y + 1 < BoardSize && this.tiles[x + 1, y + 1] != Tile.Bomb)
|
|
{
|
|
this.tiles[x + 1, y + 1] = (Tile)((int)this.tiles[x + 1, y + 1] + 1);
|
|
}
|
|
if (y + 1 < BoardSize && this.tiles[x, y + 1] != Tile.Bomb)
|
|
{
|
|
this.tiles[x, y + 1] = (Tile)((int)this.tiles[x, y + 1] + 1);
|
|
}
|
|
if (x - 1 >= 0 && y + 1 < BoardSize && this.tiles[x - 1, y + 1] != Tile.Bomb)
|
|
{
|
|
this.tiles[x - 1, y + 1] = (Tile)((int)this.tiles[x - 1, y + 1] + 1);
|
|
}
|
|
|
|
if (x - 1 >= 0 && this.tiles[x - 1, y] != Tile.Bomb)
|
|
{
|
|
this.tiles[x - 1, y] = (Tile)((int)this.tiles[x - 1, y] + 1);
|
|
}
|
|
if (x - 1 >= 0 && y - 1 >= 0 && this.tiles[x - 1, y - 1] != Tile.Bomb)
|
|
{
|
|
this.tiles[x - 1, y - 1] = (Tile)((int)this.tiles[x - 1, y - 1] + 1);
|
|
}
|
|
if (y - 1 >= 0 && this.tiles[x, y - 1] != Tile.Bomb)
|
|
{
|
|
this.tiles[x, y - 1] = (Tile)((int)this.tiles[x, y - 1] + 1);
|
|
}
|
|
if (x + 1 < BoardSize && y - 1 >= 0 && this.tiles[x + 1, y - 1] != Tile.Bomb)
|
|
{
|
|
this.tiles[x + 1, y - 1] = (Tile)((int)this.tiles[x + 1, y - 1] + 1);
|
|
}
|
|
|
|
count++;
|
|
}
|
|
}
|
|
|
|
this.gameOver = false;
|
|
this.isRunning = true;
|
|
this.flaggedBombs = 0;
|
|
this.prevTime = EditorApplication.timeSinceStartup;
|
|
this.time = 0.0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the Minesweeper game.
|
|
/// </summary>
|
|
protected override void DrawPropertyLayout(GUIContent label)
|
|
{
|
|
Rect rect = EditorGUILayout.GetControlRect();
|
|
this.ValueEntry.SmartValue = Mathf.Clamp(SirenixEditorFields.IntField(rect.AlignLeft(rect.width - 80 - 4), "Number of Bombs", this.ValueEntry.SmartValue), 1, (BoardSize * BoardSize) / 4);
|
|
|
|
// Start game
|
|
if (GUI.Button(rect.AlignRight(80), "Start"))
|
|
{
|
|
this.StartGame(this.ValueEntry.SmartValue);
|
|
}
|
|
|
|
// Game
|
|
SirenixEditorGUI.BeginShakeableGroup(this.Key);
|
|
if (this.isRunning)
|
|
{
|
|
this.Game();
|
|
}
|
|
SirenixEditorGUI.EndShakeableGroup(this.Key);
|
|
}
|
|
|
|
private void Game()
|
|
{
|
|
Rect rect = EditorGUILayout.GetControlRect(true, TileSize * BoardSize + 20);
|
|
rect = rect.AlignCenter(TileSize * BoardSize);
|
|
|
|
// Toolbar
|
|
{
|
|
SirenixEditorGUI.DrawSolidRect(rect.AlignTop(20), new Color(0.5f, 0.5f, 0.5f, 1f));
|
|
SirenixEditorGUI.DrawBorders(rect.AlignTop(20).SetHeight(21).SetWidth(rect.width + 1), 1);
|
|
|
|
if (Event.current.type == EventType.Repaint && !this.gameOver)
|
|
{
|
|
double t = EditorApplication.timeSinceStartup;
|
|
this.time += t - this.prevTime;
|
|
this.prevTime = t;
|
|
}
|
|
|
|
var time = GUIHelper.TempContent(((int)this.time).ToString());
|
|
GUIHelper.PushContentColor(Color.black);
|
|
GUI.Label(rect.AlignTop(20).HorizontalPadding(4).AlignMiddle(18).AlignRight(EditorStyles.label.CalcSize(time).x), time);
|
|
GUIHelper.PopContentColor();
|
|
|
|
GUIHelper.PushColor(Color.yellow);
|
|
GUI.Label(rect.AlignTop(20).AlignCenter(20), EditorIcons.PacmanGhost.Raw);
|
|
GUIHelper.PopColor();
|
|
|
|
if (this.gameOver)
|
|
{
|
|
GUIHelper.PushContentColor(this.flaggedBombs == this.numberOfBombs ? Color.green : Color.red);
|
|
GUI.Label(rect.AlignTop(20).HorizontalPadding(4).AlignMiddle(18), this.flaggedBombs == this.numberOfBombs ? "You win!" : "Game over!");
|
|
GUIHelper.PopContentColor();
|
|
}
|
|
}
|
|
|
|
rect = rect.AlignBottom(rect.height - 20);
|
|
SirenixEditorGUI.DrawSolidRect(rect, new Color(0.7f, 0.7f, 0.7f, 1f));
|
|
|
|
for (int i = 0; i < BoardSize * BoardSize; i++)
|
|
{
|
|
Rect tileRect = rect.SplitGrid(TileSize, TileSize, i);
|
|
SirenixEditorGUI.DrawBorders(tileRect.SetWidth(tileRect.width + 1).SetHeight(tileRect.height + 1), 1);
|
|
|
|
int x = i % BoardSize;
|
|
int y = i / BoardSize;
|
|
var tile = this.tiles[x, y];
|
|
var visible = this.visibleTiles[x, y];
|
|
|
|
if (this.gameOver || visible == Tile.Open)
|
|
{
|
|
SirenixEditorGUI.DrawSolidRect(new Rect(tileRect.x + 1, tileRect.y + 1, tileRect.width - 1, tileRect.height - 1), new Color(0.3f, 0.3f, 0.3f, 1f));
|
|
}
|
|
|
|
if ((this.gameOver || visible == Tile.Open) && tile == Tile.Bomb)
|
|
{
|
|
GUIHelper.PushColor(visible == Tile.Flag ? Color.black : Color.white);
|
|
GUI.Label(tileRect.AlignCenter(18).AlignMiddle(18), EditorIcons.SettingsCog.ActiveGUIContent);
|
|
GUIHelper.PopColor();
|
|
}
|
|
|
|
if (visible == Tile.Flag)
|
|
{
|
|
GUIHelper.PushColor(Color.red);
|
|
GUI.Label(tileRect.AlignCenter(18).AlignMiddle(18), EditorIcons.Flag.ActiveGUIContent);
|
|
GUIHelper.PopColor();
|
|
}
|
|
|
|
if ((this.gameOver || visible == Tile.Open) && (int)tile >= 1 && (int)tile <= 8)
|
|
{
|
|
GUIHelper.PushColor(this.NumberColors[(int)tile - 1]);
|
|
GUI.Label(tileRect.AlignCenter(18).AlignCenter(18).AddX(2).AddY(2), ((int)tile).ToString(), EditorStyles.boldLabel);
|
|
GUIHelper.PopColor();
|
|
}
|
|
|
|
if (!this.gameOver && tileRect.Contains(Event.current.mousePosition))
|
|
{
|
|
SirenixEditorGUI.DrawSolidRect(new Rect(tileRect.x + 1, tileRect.y + 1, tileRect.width - 1, tileRect.height - 1), new Color(0f, 1f, 0f, 0.3f));
|
|
|
|
// Input
|
|
// Reveal
|
|
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
|
|
{
|
|
if (visible != Tile.Flag)
|
|
{
|
|
if (tile == Tile.Bomb)
|
|
{
|
|
// LOSE
|
|
this.gameOver = true;
|
|
SirenixEditorGUI.StartShakingGroup(this.Key, 3f);
|
|
}
|
|
else
|
|
{
|
|
this.Reveal(x, y);
|
|
}
|
|
}
|
|
|
|
Event.current.Use();
|
|
}
|
|
// Place flag
|
|
else if (Event.current.type == EventType.MouseDown && Event.current.button == 1)
|
|
{
|
|
if (visible == Tile.Empty)
|
|
{
|
|
this.visibleTiles[x, y] = Tile.Flag;
|
|
|
|
if (tile == Tile.Bomb)
|
|
{
|
|
this.flaggedBombs++;
|
|
|
|
if (this.flaggedBombs == this.numberOfBombs)
|
|
{
|
|
this.gameOver = true;
|
|
}
|
|
}
|
|
}
|
|
else if (visible == Tile.Flag)
|
|
{
|
|
this.visibleTiles[x, y] = Tile.Empty;
|
|
|
|
if (tile == Tile.Bomb)
|
|
{
|
|
this.flaggedBombs--;
|
|
}
|
|
}
|
|
|
|
Event.current.Use();
|
|
}
|
|
}
|
|
}
|
|
|
|
GUIHelper.RequestRepaint();
|
|
}
|
|
|
|
private void Reveal(int x, int y)
|
|
{
|
|
if (x < 0 || x >= BoardSize || y < 0 || y >= BoardSize)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.visibleTiles[x, y] == Tile.Open)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.tiles[x, y] == Tile.Bomb)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((int)this.tiles[x, y] <= 8)
|
|
{
|
|
this.visibleTiles[x, y] = Tile.Open;
|
|
|
|
if (this.tiles[x, y] != Tile.Empty)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Recursive reveal.
|
|
this.Reveal(x + 1, y);
|
|
this.Reveal(x + 1, y + 1);
|
|
this.Reveal(x, y + 1);
|
|
this.Reveal(x - 1, y + 1);
|
|
|
|
this.Reveal(x - 1, y);
|
|
this.Reveal(x - 1, y - 1);
|
|
this.Reveal(x, y - 1);
|
|
this.Reveal(x + 1, y - 1);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
#endif
|