-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathMinesweeper.cs
138 lines (122 loc) · 4.09 KB
/
Minesweeper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CompatBot.Commands.Attributes;
using CompatBot.Utils;
using DSharpPlus.CommandsNext;
using DSharpPlus.CommandsNext.Attributes;
namespace CompatBot.Commands;
[Group("minesweeper"), Aliases("msgen")]
[LimitedToOfftopicChannel, Cooldown(1, 30, CooldownBucketType.Channel)]
[Description("Generates a minesweeper field with specified parameters")]
internal sealed class Minesweeper : BaseCommandModuleCustom
{
//private static readonly string[] Numbers = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"];
private static readonly string[] Numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
private static readonly string[] Bombs = ["*", "◎"];
private static readonly int MaxBombLength;
static Minesweeper()
{
MaxBombLength = Bombs.Select(b => b.Length).Max();
}
private enum CellVal: byte
{
Zero = 0,
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
OpenZero = 100,
Mine = 255,
}
[GroupCommand]
public async Task Generate(CommandContext ctx,
[Description("Width of the field")] int width = 14,
[Description("Height of the field")] int height = 14,
[Description("Number of mines")] int mineCount = 30)
{
if (width < 3 || height < 3 || mineCount < 1)
{
await ctx.Channel.SendMessageAsync("Invalid generation parameters").ConfigureAwait(false);
return;
}
var header = $"{mineCount}x💣\n";
var footer = "If something is cut off, blame Discord";
var maxMineCount = (width - 1) * (height - 1) * 2 / 3;
if (mineCount > maxMineCount)
{
await ctx.Channel.SendMessageAsync("Isn't this a bit too many mines 🤔").ConfigureAwait(false);
return;
}
if (height > 98)
{
await ctx.Channel.SendMessageAsync("Too many lines for one message, Discord would truncate the result randomly").ConfigureAwait(false);
return;
}
var msgLen = (4 * width * height - 4) + (height - 1) + mineCount * MaxBombLength + (width * height - mineCount) * "0️⃣".Length + header.Length;
if (width * height > 198 || msgLen > EmbedPager.MaxMessageLength) // for some reason discord would cut everything beyond 198 cells even if the content length is well within the limits
{
await ctx.Channel.SendMessageAsync("Requested field size is too large for one message").ConfigureAwait(false);
return;
}
var rng = new Random();
var field = GenerateField(width, height, mineCount, rng);
var result = new StringBuilder(msgLen).Append(header);
var bomb = rng.NextDouble() > 0.9 ? Bombs[rng.Next(Bombs.Length)] : Bombs[0];
var needOneOpenCell = true;
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
var c = (CellVal)field[y, x] == CellVal.Mine ? bomb : Numbers[field[y, x]];
if (needOneOpenCell && field[y, x] == 0)
{
result.Append(c);
needOneOpenCell = false;
}
else
result.Append("||").Append(c).Append("||");
}
result.Append('\n');
}
result.Append(footer);
await ctx.Channel.SendMessageAsync(result.ToString()).ConfigureAwait(false);
}
private static byte[,] GenerateField(int width, int height, in int mineCount, in Random rng)
{
var len = width * height;
var cells = new byte[len];
// put mines
for (var i = 0; i < mineCount; i++)
cells[i] = (byte)CellVal.Mine;
//shuffle the board
for (var i = 0; i < len - 1; i++)
{
var j = rng.Next(i, len);
(cells[i], cells[j]) = (cells[j], cells[i]);
}
var result = new byte[height, width];
Buffer.BlockCopy(cells, 0, result, 0, len);
//update mine indicators
byte get(int x, int y) => x < 0 || x >= width || y < 0 || y >= height ? (byte)0 : result[y, x];
byte countMines(int x, int y)
{
byte c = 0;
for (var yy = y - 1; yy <= y + 1; yy++)
for (var xx = x - 1; xx <= x + 1; xx++)
if ((CellVal)get(xx, yy) == CellVal.Mine)
c++;
return c;
}
for (var y = 0; y < height; y++)
for (var x = 0; x < width; x++)
if ((CellVal)result[y, x] != CellVal.Mine)
result[y, x] = countMines(x, y);
return result;
}
}