96 lines
4.0 KiB
C#
96 lines
4.0 KiB
C#
using System.Globalization;
|
|
using Minesweeper.Core;
|
|
|
|
namespace Minesweeper.Cli;
|
|
|
|
/// <summary>Turns typed text and raw keystrokes into <see cref="Command"/> instances. This is the
|
|
/// only place that translates the one-based coordinates a player sees into engine coordinates.</summary>
|
|
public static class CommandParser
|
|
{
|
|
/// <summary>Parses a typed line. Coordinates are one-based, e.g. <c>reveal 3 4</c>.</summary>
|
|
public static Command Parse(string input)
|
|
{
|
|
var text = input.Trim();
|
|
if (text.Length == 0)
|
|
{
|
|
return new Command.Noop();
|
|
}
|
|
|
|
var parts = text.Split([' ', ',', ';', '\t'], StringSplitOptions.RemoveEmptyEntries);
|
|
var verb = parts[0].ToLowerInvariant();
|
|
|
|
switch (verb)
|
|
{
|
|
case "q" or "quit" or "exit":
|
|
return new Command.Quit();
|
|
case "n" or "new":
|
|
return new Command.NewGame();
|
|
case "h" or "help" or "?":
|
|
return new Command.ShowHelp();
|
|
case "d" or "difficulty":
|
|
return ParseDifficulty(parts, text);
|
|
case "r" or "reveal" or "open":
|
|
return ParseCell(parts, 1, cell => new Command.Reveal(cell), text);
|
|
case "f" or "flag" or "mark":
|
|
return ParseCell(parts, 1, cell => new Command.Flag(cell), text);
|
|
case "c" or "chord":
|
|
return ParseCell(parts, 1, cell => new Command.Chord(cell), text);
|
|
case "go" or "goto":
|
|
return ParseCell(parts, 1, cell => new Command.SetCursor(cell), text);
|
|
}
|
|
|
|
// A bare "3 4" reveals that cell, since revealing is by far the most common action.
|
|
return ParseCell(parts, 0, cell => new Command.Reveal(cell), text);
|
|
}
|
|
|
|
/// <summary>Maps a keystroke from keyboard-driven mode onto a command.</summary>
|
|
public static Command FromKey(ConsoleKeyInfo key) => key.Key switch
|
|
{
|
|
ConsoleKey.UpArrow => new Command.MoveCursor(-1, 0),
|
|
ConsoleKey.DownArrow => new Command.MoveCursor(1, 0),
|
|
ConsoleKey.LeftArrow => new Command.MoveCursor(0, -1),
|
|
ConsoleKey.RightArrow => new Command.MoveCursor(0, 1),
|
|
ConsoleKey.Enter or ConsoleKey.Spacebar => new Command.RevealAtCursor(),
|
|
ConsoleKey.F => new Command.FlagAtCursor(),
|
|
ConsoleKey.C => new Command.ChordAtCursor(),
|
|
ConsoleKey.N => new Command.NewGame(),
|
|
ConsoleKey.H => new Command.ShowHelp(),
|
|
ConsoleKey.D1 => new Command.ChangeDifficulty(GameDifficulty.Beginner),
|
|
ConsoleKey.D2 => new Command.ChangeDifficulty(GameDifficulty.Intermediate),
|
|
ConsoleKey.D3 => new Command.ChangeDifficulty(GameDifficulty.Expert),
|
|
ConsoleKey.Escape or ConsoleKey.Q => new Command.Quit(),
|
|
_ => new Command.Noop(),
|
|
};
|
|
|
|
private static Command ParseDifficulty(string[] parts, string text)
|
|
{
|
|
if (parts.Length < 2)
|
|
{
|
|
return new Command.ShowHelp();
|
|
}
|
|
|
|
return parts[1].ToLowerInvariant() switch
|
|
{
|
|
"1" or "b" or "beginner" or "easy" => new Command.ChangeDifficulty(GameDifficulty.Beginner),
|
|
"2" or "i" or "intermediate" or "medium" => new Command.ChangeDifficulty(GameDifficulty.Intermediate),
|
|
"3" or "e" or "expert" or "hard" => new Command.ChangeDifficulty(GameDifficulty.Expert),
|
|
_ => new Command.Unknown(text),
|
|
};
|
|
}
|
|
|
|
private static Command ParseCell(string[] parts, int offset, Func<CellLocation, Command> factory, string text)
|
|
{
|
|
if (parts.Length - offset < 2 ||
|
|
!TryParseCoordinate(parts[offset], out var row) ||
|
|
!TryParseCoordinate(parts[offset + 1], out var column))
|
|
{
|
|
return new Command.Unknown(text);
|
|
}
|
|
|
|
return factory(new CellLocation(row - 1, column - 1));
|
|
}
|
|
|
|
private static bool TryParseCoordinate(string token, out int value) =>
|
|
int.TryParse(token, NumberStyles.None, CultureInfo.InvariantCulture, out value) && value > 0;
|
|
}
|