diff --git a/App/CommandLineOptions.cs b/App/CommandLineOptions.cs new file mode 100644 index 0000000..0659208 --- /dev/null +++ b/App/CommandLineOptions.cs @@ -0,0 +1,7 @@ +namespace Game { + public struct CommandLineOptions { + public static CommandLineOptions Parse(string[] args) { + return new CommandLineOptions(); + } + } +} \ No newline at end of file diff --git a/App/I18n.cs b/App/I18n.cs new file mode 100644 index 0000000..be429e9 --- /dev/null +++ b/App/I18n.cs @@ -0,0 +1,46 @@ +namespace Game { + public class I18n { + public static readonly string LANGUAGE = "ru-RU"; + + static readonly Dictionary LANGUAGES = new Dictionary() { + {"en-US", new string[] { + "No answers added for question", + "Expected `=>`", + "Expected `True` or `False`", + "Expected `||` between answers", + "The output is not a terminal", + "Syntax error in the question database: {0} ({1}:{2}:{3})", + "Syntax error in the question database: {0} (line {1} column {2})", + "Cannot get console mode", + "Cannot set console mode", + }}, + {"ru-RU", new string[] { + "Не указано ни одного варианта ответа на вопрос", + "Ожидалось `=>`", + "Ожидалось `True` или `False`", + "Ожидалось `||` между вариантами ответа", + "Поток вывода не идёт в терминал", + "Ошибка синтаксиса в банке вопросов: {0} ({1}:{2}:{3})", + "Ошибка синтаксиса в банке вопросов: {0} (строка {1} символ {2})", + "Не удалось узнать режим консоли", + "Не удалось установить режим консоли", + }} + }; + + public static string? GetMessage(Message id) { + return LANGUAGES[LANGUAGE][(int)id]; + } + + public enum Message { + ERROR_NO_ANSWER_OPTIONS = 0, + ERROR_EXPECTED_ARROW_TOKEN, + ERROR_EXPECTED_TRUE_OR_FALSE, + ERROR_EXPECTED_ANSWER_SEPARATOR, + OUTPUT_IS_NOT_TERMINAL, + SYNTAX_ERROR_WITH_FILENAME, + SYNTAX_ERROR, + ERROR_GETCONSOLEMODE, + ERROR_SETCONSOLEMODE, + } + } +} \ No newline at end of file diff --git a/App/Program.cs b/App/Program.cs index f346be5..c461e25 100644 --- a/App/Program.cs +++ b/App/Program.cs @@ -1,7 +1,23 @@ -namespace Game { +using Game.UI; + +namespace Game { internal class Program { static int Main(string[] args) { + CommandLineOptions opts = CommandLineOptions.Parse(args); + + TextDisplay display = new TextDisplay(); + if (!display.InitWindow()) { + return 1; + } + display.PrintSimpleWelcome(); + Thread.Sleep(1200); + display.ShowSplash(); + Thread.Sleep(2500); + + + display.ResetWindow(); + return 0; } } -} +} \ No newline at end of file diff --git a/App/UI/Colors.cs b/App/UI/Colors.cs new file mode 100644 index 0000000..11bb6da --- /dev/null +++ b/App/UI/Colors.cs @@ -0,0 +1,4 @@ +namespace App.UI { + internal class Colors { + } +} diff --git a/App/UI/TextDisplay.cs b/App/UI/TextDisplay.cs new file mode 100644 index 0000000..f1a2a87 --- /dev/null +++ b/App/UI/TextDisplay.cs @@ -0,0 +1,172 @@ +using static Game.I18n; +using System.Runtime.InteropServices; +using System.Text; + +namespace Game.UI { + public class TextDisplay { + private const string RGB_FG_SEQ_START = "\e[38;2;"; + private const string RGB_BG_SEQ_START = "\e[48;2;"; + private const string ESC_SEQ_START = "\e["; + private const string SETCURSOR_END = "H"; + private const string GRAPHICS_SEQ_END = "m"; + private const string HIDECURSOR_SEQ = "\e[?25l"; + private const string SHOWCURSOR_SEQ = "\e[?25h"; + + private int currentFgColor = 0xffffff, currentBgColor = 0; + private int cursorRow = 0, cursorCol = 0; + private int windowWidth = 0, windowHeight = 0; + + public bool InitWindow() { + if (Console.IsOutputRedirected) { + Console.WriteLine(GetMessage(Message.OUTPUT_IS_NOT_TERMINAL)); + return false; + } + Console.InputEncoding = Encoding.UTF8; + Console.OutputEncoding = Encoding.UTF8; + + IntPtr consoleInHandle = Win32Imports.GetStdHandle(Win32Imports.STD_INPUT_HANDLE); + IntPtr consoleOutHandle = Win32Imports.GetStdHandle(Win32Imports.STD_OUTPUT_HANDLE); + + uint inConsoleMode, outConsoleMode; + if (!Win32Imports.GetConsoleMode(consoleInHandle, out inConsoleMode)) { + Console.WriteLine(GetMessage(Message.ERROR_GETCONSOLEMODE)); + return false; + } + if (!Win32Imports.GetConsoleMode(consoleOutHandle, out outConsoleMode)) { + Console.WriteLine(GetMessage(Message.ERROR_GETCONSOLEMODE)); + return false; + } + inConsoleMode |= Win32Imports.ENABLE_VIRTUAL_TERMINAL_INPUT; + //inConsoleMode &= ~Win32Imports.ENABLE_LINE_INPUT; + outConsoleMode |= Win32Imports.ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (!Win32Imports.SetConsoleMode(consoleInHandle, inConsoleMode)) { + Console.WriteLine(GetMessage(Message.ERROR_SETCONSOLEMODE)); + return false; + } + if (!Win32Imports.SetConsoleMode(consoleOutHandle, outConsoleMode)) { + Console.WriteLine(GetMessage(Message.ERROR_SETCONSOLEMODE)); + return false; + } + SetColors(currentFgColor, currentBgColor); + HideCursor(); + SetCursor(cursorRow, cursorCol); + windowWidth = Console.BufferWidth; + windowHeight = Console.BufferHeight; + return true; + } + + public void ResetWindow() { + SetColors(0xffffff, 0); + SetCursor(1, 1); + ShowCursor(); + } + + public void PrintSimpleWelcome() { + WriteColored("Welcome to КТО ХОЧЕТ СТАТЬ СЕКУНДОМЕРОМ", 0x5e61ed); + } + + public void ShowSplash() { + ClearWithColor(0x5e61ed); + + DrawTitle(windowHeight / 2 + 1 - 3, 0x5e61ed); + DrawCopyright(0x5e61ed); + } + + void DrawTitle(int y, int bgColor) { + string text1 = "КТО ХОЧЕТ СТАТЬ"; + string text2 = "СЕКУНДОМЕРОМ"; + SetColors(0xffffff, 0); + + int text1Start = (windowWidth - text1.Length) / 2 + 1; + SetCursor(y, text1Start - 6); + for (int i = 0; i < 6; ++i) { + WriteColored(" ", -1, ColorBlend(0, bgColor, (float)i / 6)); + } + WriteColored(text1, 0xffffff, 0); + for (int i = 6; i >= 0; --i) { + WriteColored(" ", -1, ColorBlend(0, bgColor, (float)i / 6)); + } + + int text2Start = (windowWidth - text2.Length) / 2 + 1; + SetCursor(y + 2, text2Start - 6); + for (int i = 0; i < 6; ++i) { + WriteColored(" ", -1, ColorBlend(0, bgColor, (float)i / 6)); + } + WriteColored(text2, 0xffffff, 0); + for (int i = 6; i >= 0; --i) { + WriteColored(" ", -1, ColorBlend(0, bgColor, (float)i / 6)); + } + } + + void DrawCopyright(int bgColor) { + string bottomText = "(c) subvia, 2025"; + SetCursor(windowHeight, (windowWidth - bottomText.Length) / 2 + 1); + WriteColored(bottomText, 0x4d4dc0, bgColor); + } + + public void WriteColored(string s, int color, int bgColor = -1) { + if (color == -1) color = currentFgColor; + (int r, int g, int b) = ((color >> 16) & 255, (color >> 8) & 255, color & 255); + currentFgColor = color; + if (bgColor == -1) { + Console.Write(RGB_FG_SEQ_START + r + ';' + g + ';' + b + GRAPHICS_SEQ_END + s); + } else { + (int br, int bg, int bb) = ((bgColor >> 16) & 255, (bgColor >> 8) & 255, bgColor & 255); + Console.Write(RGB_FG_SEQ_START + r + ';' + g + ';' + b + GRAPHICS_SEQ_END + RGB_BG_SEQ_START + br + ';' + bg + ';' + bb + GRAPHICS_SEQ_END + s); + currentBgColor = bgColor; + } + } + + public void SetColors(int fg, int bg) { + StringBuilder command = new StringBuilder(); + if (fg != -1) { + (int r, int g, int b) = ((fg >> 16) & 255, (fg >> 8) & 255, fg & 255); + command.Append(RGB_FG_SEQ_START + r + ';' + g + ';' + b + GRAPHICS_SEQ_END); + } + if (bg != -1) { + (int r, int g, int b) = ((bg >> 16) & 255, (bg >> 8) & 255, bg & 255); + command.Append(RGB_BG_SEQ_START + r + ';' + g + ';' + b + GRAPHICS_SEQ_END); + } + Console.Write(command.ToString()); + } + + public void ClearWithColor(int color) { + (int r, int g, int b) = ((color >> 16) & 255, (color >> 8) & 255, color & 255); + SetColors(-1, color); + Console.Write("\e[2J"); + } + + public void SetCursor(int row, int column) { + Console.Write(ESC_SEQ_START + row + ';' + column + SETCURSOR_END); + } + + public void HideCursor() { + Console.Write(HIDECURSOR_SEQ); + } + + public void ShowCursor() { + Console.Write(SHOWCURSOR_SEQ); + } + + public int ColorBlend(int fg, int bg, float alpha) { + //return (int)(fg * alpha + bg * (1f - alpha)); + return ((int)(((fg >> 16) & 255) * alpha + ((bg >> 16) & 255) * (1 - alpha)) << 16) + ((int)(((fg >> 8) & 255) * alpha + ((bg >> 8) & 255) * (1 - alpha)) << 8) + (int)((fg & 255) * alpha + (bg & 255) * (1 - alpha)); + } + } + + static class Win32Imports + { + public const int STD_INPUT_HANDLE = -10; + public const int STD_OUTPUT_HANDLE = -11; + public const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; + public const uint ENABLE_LINE_INPUT = 2; + public const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512; + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GetStdHandle(int stdHandle); + [DllImport("kernel32.dll")] + public static extern bool SetConsoleMode(IntPtr consoleHandle, uint mode); + [DllImport("kernel32.dll")] + public static extern bool GetConsoleMode(IntPtr consoleHandle, out uint mode); + } +} \ No newline at end of file