using System.Globalization; using Minesweeper.Core; namespace Minesweeper.Cli; /// Turns typed text and raw keystrokes into instances. This is the /// only place that translates the one-based coordinates a player sees into engine coordinates. public static class CommandParser { /// Parses a typed line. Coordinates are one-based, e.g. reveal 3 4. 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); } /// Maps a keystroke from keyboard-driven mode onto a command. 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 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; }