Compare commits

..

10 Commits

Author SHA1 Message Date
Masahiko AMANO
21af99f8ae Row and column titles on right and bottom 2022-03-12 10:11:16 +03:00
Masahiko AMANO
b3e3b50d64 Auto marking unmarked mines when win 2022-03-11 19:04:45 +03:00
Masahiko AMANO
2445155e93 Show number of remaining mines 2022-03-11 18:52:15 +03:00
Masahiko AMANO
548b29ea22 Block opening marked cells 2022-03-11 18:43:20 +03:00
Masahiko AMANO
b953371536 Add favicon and window title 2022-03-11 18:22:56 +03:00
Masahiko AMANO
2c477ef637 Marking/unmarking multiple cells
in one move
2022-03-09 12:10:13 +03:00
Masahiko AMANO
ebd536897c Minor fixes 2022-03-09 12:01:38 +03:00
Masahiko AMANO
542667699e Refactoring 2022-03-03 18:04:49 +03:00
Masahiko AMANO
4f2b5cd940 Update MineSweeper.csproj 2022-03-03 17:58:59 +03:00
Masahiko AMANO
d565970903 Add user database with stats 2022-03-03 17:52:34 +03:00
8 changed files with 143 additions and 37 deletions

View File

@ -5,7 +5,6 @@
public string Value = " "; public string Value = " ";
public bool IsMine; public bool IsMine;
public bool IsMarked; public bool IsMarked;
public Cell(bool mine) public Cell(bool mine)
{ {
if (mine) if (mine)

44
Database.cs Normal file
View File

@ -0,0 +1,44 @@
using System.Text.Json;
namespace MineSweeper
{
internal class Database
{
private readonly string path;
private class PlayerData
{
public string name { get; set; }
public int wins { get; set; } = 0;
public int loses { get; set; } = 0;
}
private record Root(List<PlayerData> players);
private readonly Root root;
public Database()
{
path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MineSweeper by H1K0");
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
path = Path.Combine(path, "db.json");
if (!File.Exists(path))
File.WriteAllText(path, "{\"players\":[]}");
root = JsonSerializer.Deserialize<Root>(File.ReadAllText(path));
}
public int PlayersCount() { return root.players.Count; }
public int FindPlayer(string name) { return root.players.FindIndex(p => p.name == name); }
public void AddPlayer(string name)
{
PlayerData newplayer = new();
newplayer.name = name;
root.players.Add(newplayer);
}
public int[] Stats(int playerId) { return new int[] { root.players[playerId].wins, root.players[playerId].loses }; }
public void AddGame(int playerId, bool win)
{
if (win)
root.players[playerId].wins++;
else
root.players[playerId].loses++;
}
public void Update() { File.WriteAllText(path, JsonSerializer.Serialize(root)); }
}
}

View File

@ -5,9 +5,10 @@
private readonly int width = 0; private readonly int width = 0;
private readonly int height = 0; private readonly int height = 0;
private readonly int size = 0; private readonly int size = 0;
private int nmines = 0;
private readonly List<Cell> cells = new(); private readonly List<Cell> cells = new();
private readonly List<Cell> opened = new(); private readonly List<Cell> opened = new();
public Field(int input_width, int input_height, int nmines) public Field(int input_width, int input_height, int input_nmines)
{ {
if (input_width < 0 || input_height < 0) if (input_width < 0 || input_height < 0)
throw new ArgumentException("Field dimensions must be natural numbers."); throw new ArgumentException("Field dimensions must be natural numbers.");
@ -19,12 +20,11 @@
throw new ArgumentException("The number of mines can not be negative."); throw new ArgumentException("The number of mines can not be negative.");
if (nmines > input_width * input_height) if (nmines > input_width * input_height)
throw new ArgumentException("The number of mines can not be greater than the number of all cells."); throw new ArgumentException("The number of mines can not be greater than the number of all cells.");
width = input_width; (width, height, nmines) = (input_width, input_height, input_nmines);
height = input_height;
size = width * height; size = width * height;
cells = new List<Cell>(); cells = new List<Cell>();
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
cells.Add(new Cell(nmines-- > 0)); cells.Add(new Cell(input_nmines-- > 0));
Random rnd = new(); Random rnd = new();
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
@ -78,7 +78,7 @@
{ {
Console.Write(" " + ((opened.Contains(cells[y * width + x]) || cells[y * width + x].IsMarked) ? cells[y * width + x].Show() : "■")); Console.Write(" " + ((opened.Contains(cells[y * width + x]) || cells[y * width + x].IsMarked) ? cells[y * width + x].Show() : "■"));
} }
Console.WriteLine(" ║"); Console.WriteLine(" ║ " + (char)('A' + y));
} }
Console.Write(" ╚"); Console.Write(" ╚");
for (int i = 0; i < 2 * width + 1; i++) for (int i = 0; i < 2 * width + 1; i++)
@ -86,15 +86,21 @@
Console.Write("═"); Console.Write("═");
} }
Console.WriteLine("╝"); Console.WriteLine("╝");
Console.Write(" ");
for (int i = 0; i < width;)
{
Console.Write(" " + ++i);
}
Console.WriteLine($"\n\nRemaining mines: {nmines}.");
} }
public bool Open(int y, int x) public bool Open(int y, int x)
{ {
if (y < 0 || y >= height || x < 0 || x >= width) if (y < 0 || y >= height || x < 0 || x >= width)
throw new Exception("Coordinates out of the field!"); throw new Exception("Coordinates out of the field!");
if (cells[y * width + x].IsMine)
return true;
if (cells[y * width + x].IsMarked) if (cells[y * width + x].IsMarked)
return false; return false;
if (cells[y * width + x].IsMine)
return true;
if (!opened.Contains(cells[y * width + x])) if (!opened.Contains(cells[y * width + x]))
{ {
opened.Add(cells[y * width + x]); opened.Add(cells[y * width + x]);
@ -150,7 +156,7 @@
} }
return false; return false;
} }
public void OpenAll() public void OpenAll(bool automark)
{ {
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
@ -158,14 +164,27 @@
opened.Add(cells[i]); opened.Add(cells[i]);
if (cells[i].IsMarked && !cells[i].IsMine) if (cells[i].IsMarked && !cells[i].IsMine)
cells[i].SetWrong(); cells[i].SetWrong();
if (automark && cells[i].IsMine)
{
cells[i].Mark();
nmines = 0;
} }
} }
this.Draw();
}
public void Mark(int y, int x) public void Mark(int y, int x)
{ {
if (!opened.Contains(cells[y * width + x])) if (!opened.Contains(cells[y * width + x]))
{
cells[y * width + x].Mark(); cells[y * width + x].Mark();
nmines--;
}
}
public void Unmark(int y, int x)
{
cells[y * width + x].Unmark();
nmines++;
} }
public void Unmark(int y, int x) { cells[y * width + x].Unmark(); }
public bool Check() public bool Check()
{ {
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)

23
Game.cs
View File

@ -14,37 +14,42 @@
while (true) while (true)
{ {
Console.Clear(); Console.Clear();
field.Draw();
if (field.Check()) if (field.Check())
{ {
field.OpenAll(true);
return true; return true;
} }
field.Draw();
Console.Write("Enter your command: "); Console.Write("Enter your command: ");
try try
{ {
command = Console.ReadLine().ToUpper().Split(); command = Console.ReadLine().ToUpper().Split();
if (command.Length == 1) if (command.Length == 1)
{ {
int x = Convert.ToInt16(command[0].Substring(1)) - 1; int x = Convert.ToInt16(command[0][1..]) - 1;
int y = command[0].First() - 'A'; int y = command[0].First() - 'A';
if (field.Open(y, x)) if (field.Open(y, x))
{ {
this.Finish(); Console.Clear();
field.OpenAll(false);
return false; return false;
} }
} }
else if (command.Length == 2) else if (command.Length > 1)
{ {
if (command[0] != "M" && command[0] != "U") if (command[0] != "M" && command[0] != "U")
throw new Exception("Invalid command."); throw new Exception("Invalid command.");
int x = Convert.ToInt16(command[1].Substring(1)) - 1; for (int i = 1; i < command.Length; i++)
int y = command[1].First() - 'A'; {
int x = Convert.ToInt16(command[i][1..]) - 1;
int y = command[i].First() - 'A';
if (command[0] == "M") if (command[0] == "M")
field.Mark(y, x); field.Mark(y, x);
else else
field.Unmark(y, x); field.Unmark(y, x);
} }
} }
}
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.Message + "\nTry again."); Console.WriteLine(ex.Message + "\nTry again.");
@ -52,11 +57,5 @@
} }
} }
} }
private void Finish()
{
field.OpenAll();
Console.Clear();
field.Draw();
}
} }
} }

40
Main.cs
View File

@ -5,17 +5,41 @@
public static void Main(string[] args) public static void Main(string[] args)
{ {
Game game; Game game;
while (true) Database database = new();
{ int PlayerId;
string PlayerName = "";
Console.Title = "MineSweeper by H1K0";
Console.WriteLine("(C) Masahiko AMANO a.k.a. H1K0, 2022\n\n" + Console.WriteLine("(C) Masahiko AMANO a.k.a. H1K0, 2022\n\n" +
"Hey! Let's play the MineSweeper game!\nType anything for help or just press Enter to start the game."); "Hey! Let's play the MineSweeper game!\nPress any button for help or Enter to log in.");
while (Console.ReadLine().Length != 0) while (Console.ReadKey(true).Key != ConsoleKey.Enter)
Console.WriteLine("\nThe game follows the classic rules.\n" + Console.WriteLine("\nThe game follows the classic rules.\n" +
"Target cell coordinates are represented this way: A1, F5, I8. Case insensitive.\n" + "Target cell coordinates are represented this way: A1, F5, I8. Case insensitive.\n" +
"Type coordinates of the cell to open it or use prefixes: \"M\" to mark or \"U\" to unmark the cell.\n" + "Type coordinates of the cell to open it or use prefixes: \"M\" to mark or \"U\" to unmark the cell.\n" +
"For example: \"B9\", \"M D7\".\n" + "For example: \"B9\", \"M D7\".\n" +
"Type anything to see help again or just press Enter to start the game.\n"); "Press any button to see help again or just press Enter to start the game.");
Console.Write("Okay, let's go!\nEnter field width, height and number of mines separated with a space: "); while (true)
{
Console.Write("\nYour name: ");
PlayerName = Console.ReadLine();
PlayerId = database.FindPlayer(PlayerName);
if (PlayerId == -1 || PlayerName == "")
{
Console.WriteLine("Could not find a player with the name \"" + PlayerName + "\". Press Enter to add new or any other button to re-enter.");
if (Console.ReadKey(true).Key == ConsoleKey.Enter)
{
database.AddPlayer(PlayerName);
PlayerId = database.PlayersCount() - 1;
break;
}
}
else
break;
}
Console.WriteLine("You logged in as \"" + PlayerName + "\".\nYour stats:");
Console.WriteLine($"Wins: {database.Stats(PlayerId)[0]}\nLoses: {database.Stats(PlayerId)[1]}");
while (true)
{
Console.Write("\nOkay, let's play!\nEnter field width, height and number of mines separated with a space: ");
while (true) while (true)
{ {
try try
@ -35,12 +59,14 @@
Console.WriteLine("You win! Congratulations!"); Console.WriteLine("You win! Congratulations!");
else else
Console.WriteLine("You lose!"); Console.WriteLine("You lose!");
database.AddGame(PlayerId, result);
database.Update();
Console.WriteLine("Press Enter to play again or Escape to exit."); Console.WriteLine("Press Enter to play again or Escape to exit.");
ConsoleKey key; ConsoleKey key;
bool exit = false; bool exit = false;
while (true) while (true)
{ {
key = Console.ReadKey().Key; key = Console.ReadKey(true).Key;
if (key == ConsoleKey.Enter) if (key == ConsoleKey.Enter)
{ {
Console.Clear(); Console.Clear();

View File

@ -1,10 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RepositoryUrl>https://github.com/H1K0/MineSweeper.git</RepositoryUrl>
<Copyright>Masahiko AMANO a.k.a. H1K0</Copyright>
<Authors>H1K0</Authors>
<StartupObject>MineSweeper.Program</StartupObject>
<PackageProjectUrl>https://github.com/H1K0/MineSweeper</PackageProjectUrl>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<Description>Console MineSweeper game on C#</Description>
<PackageIcon>favicon.png</PackageIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Content Include="favicon.ico" />
</ItemGroup>
<ItemGroup>
<None Update="favicon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project> </Project>

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB