C# - How to create appealing CLI applications with Spectre.Console
Introduction to Spectre Console
Spectre.Console is a super cool library for C# developers that love command line interfaces. Developed by the Spectresystems, this library empowers developers to create rich, interactive, and visually stunning console applications with ease.
Traditionally, building console applications in C# has been a tedious task, often requiring developers to write extensive code for handling input, rendering output, and managing the overall user experience. However, Spectre Console simplifies this process by providing a comprehensive set of tools and abstractions that streamline the development workflow.
One of the key features of Spectre Console is its powerful and flexible rendering engine. This engine allows developers to create complex user interfaces with various elements such as menus, grids, tables, and custom UI components. The library also supports advanced features like text formatting, colors, and Unicode character rendering, enabling developers to create visually appealing and accessible console applications. It provides built-in support for keyboard and mouse events, allowing developers to create interactive applications with responsive user interfaces. Whether you're building a console-based game, a utility tool, or a command-line application, Spectre Console has got you covered.
Another notable aspect of Spectre Console is its extensibility. The library is designed with a modular architecture, allowing developers to create custom components and extensions tailored to their specific needs.
In the following sections, we will see how to use Spectre.Console with a code-first approach. Two well-commented application examples will showcase some of the most useful features of this library.
How to create a console menu with Spectre Console
using Spectre.Console; class Program{ static void Main(string[] args){ bool exit = false; while (!exit){ // Clear the console AnsiConsole.Clear(); // Create a title AnsiConsole.Write( new FigletText("My CLI Menu") .LeftJustified() .Color(Color.Cyan1)); // Menu instruction AnsiConsole.MarkupLine("\nPlease select an option:"); // Create 3 menu options + exit var options = AnsiConsole.Prompt( new SelectionPrompt<string>() .Title("Options") .PageSize(10) .HighlightStyle(new Style( foreground: Color.Black, background: Color.Cyan1)) .AddChoices(new[]{ "Option 1", "Option 2", "Option 3", "Exit" })); // Manage menu options switch (options){ case "Option 1": HandleOption1(); break; case "Option 2": HandleOption2(); break; case "Option 3": HandleOption3(); break; case "Exit": exit = true; break; } AnsiConsole.MarkupLine("\nPress any key to continue..."); Console.ReadKey(); } } static void HandleOption1(){ AnsiConsole.MarkupLine("You selected [green]Option 1[/]"); // Add your logic here } static void HandleOption2(){ AnsiConsole.MarkupLine("You selected [green]Option 2[/]"); // Add your logic here } static void HandleOption3(){ AnsiConsole.MarkupLine("You selected [green]Option 3[/]"); // Add your logic here } }
Building an Interactive Console Game
using System; using Spectre.Console; // // :#/ GSLF - www.gslf.it // PROMEZIO ENGINEERING - www.promezio.it // // 4-in-a-row CLI game // class Program { // Game configuration const int Rows = 6; const int Columns = 7; const char Empty = ' '; const char Player1 = 'X'; const char Player2 = 'O'; static char[,] board = new char[Rows, Columns]; static void Main(string[] args) { InitBoard(); PlayGame(); } // Init the game with an empty board static void InitBoard() { for (int i = 0; i < Rows; i++) { for (int j = 0; j < Columns; j++) { board[i,j] = Empty; } } } // Gameplay loop static void PlayGame() { bool player1Turn = true; bool gameWon = false; while (!gameWon && !BoardFull()) { AnsiConsole.Clear(); DisplayBoard(); int column = GetPlayerMove(player1Turn ? Player1 : Player2); if (MakeMove(column, player1Turn ? Player1 : Player2)) { gameWon = CheckWin(player1Turn ? Player1 : Player2); player1Turn = !player1Turn; } else { AnsiConsole.Markup("[red]Column is full! Try another column.[/]"); AnsiConsole.Console.Input.ReadKey(true); } } AnsiConsole.Clear(); DisplayBoard(); if (gameWon) { AnsiConsole.Markup($"[green]Player {(player1Turn ? 2 : 1)} wins![/]"); } else { AnsiConsole.Markup("[yellow]It's a draw![/]"); } } // Draw the game board static void DisplayBoard() { // Displya title AnsiConsole.Write( new FigletText("4-in-a-row") .LeftJustified() .Color(Color.Cyan1)); // Draw rows for (int i = 0; i < Rows; i++) { for (int j = 0; j < Columns; j++) { AnsiConsole.Write($"| {board[i, j]} "); } AnsiConsole.WriteLine("|"); } // Draw bottom line for (int j = 0; j < Columns; j++) { AnsiConsole.Write("----"); } AnsiConsole.WriteLine("-"); // Draw row number for (int j = 0; j < Columns; j++) { AnsiConsole.Write($" {j + 1} "); } AnsiConsole.WriteLine(); } // Manage players moves static int GetPlayerMove(char player) { int column; do { column = AnsiConsole.Ask<int>($"Player {player}, choose a column (1-{Columns}): ") - 1; } while (column < 0 || column >= Columns); return column; } // Make the move static bool MakeMove(int column, char player) { for (int i = Rows - 1; i >= 0; i--) { if (board[i, column] == Empty) { board[i, column] = player; return true; } } return false; } // Check for the full board static bool BoardFull() { for (int i = 0; i < Columns; i++) { if (board[0, i] == Empty) { return false; } } return true; } // Check for 4-in-a-row static bool CheckWin(char player) { for (int i = 0; i < Rows; i++) { for (int j = 0; j < Columns; j++) { if (CheckDirection(player, i, j, 1, 0) || // Horizontal CheckDirection(player, i, j, 0, 1) || // Vertical CheckDirection(player, i, j, 1, 1) || // Diagonal / CheckDirection(player, i, j, 1, -1)) // Diagonal \ { return true; } } } return false; } static bool CheckDirection(char player, int row, int col, int dRow, int dCol) { int count = 0; for (int i = 0; i < 4; i++) { int r = row + i * dRow; int c = col + i * dCol; if (r >= 0 && r < Rows && c >= 0 && c < Columns && board[r, c] == player) { count++; } else { break; } } return count == 4; } }