/
err404
/
GuessTheNumber
Обзор
Документация
Войти
/
err404
/
GuessTheNumber
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
Program.cs
1 171 строка
50 KB
err404
fix: change GameHistoryEntry.Secret from object to string 🎯
17 ноя 2025, 16:21
17 ноя 2025, 16:21
5288e75
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; class Program { static void Main(string[] args) { var lang = "ru"; // по умолчанию var langArgIndex = Array.IndexOf(args, "--lang"); if (langArgIndex != -1 && langArgIndex + 1 < args.Length) lang = args[langArgIndex + 1]; var ui = new UI(lang); var scores = ScoreManager.LoadOrCreate(); var random = new Random(); Console.WriteLine(ui.WelcomeMessage); Console.Write(ui.AskName); var playerName = Console.ReadLine()?.Trim() ?? "Аноним"; Console.WriteLine(ui.Hello(playerName)); Console.WriteLine(ui.SecretMessage); while (true) { Console.WriteLine(); Console.WriteLine(ui.MenuTitle); Console.WriteLine("1. " + ui.EasyLevel); Console.WriteLine("2. " + ui.MediumLevel); Console.WriteLine("3. " + ui.HardLevel); Console.WriteLine("4. 🍞 " + ui.BulkinDream); Console.WriteLine("5. 🌐 " + ui.NetworkDuel); Console.WriteLine("6. 🧠 " + ui.BulkinMaster); Console.WriteLine("7. 📊 " + ui.HighScores); Console.WriteLine("8. ❌ " + ui.Exit); Console.WriteLine("9. 🧪 Парсер-испытание"); Console.WriteLine("10. 📜 История игр"); Console.WriteLine("11. 🗄️ Рецепт-архив"); Console.Write(ui.ChooseOption); var input = Console.ReadLine()?.Trim(); if (string.Equals(input, "булка", StringComparison.OrdinalIgnoreCase)) { PlayBulkinDream(ref scores, ui); continue; } if (int.TryParse(input, out var choice)) { var level = ""; var range = 0; switch (choice) { case 1: range = 10; level = ui.EasyLevel; break; case 2: range = 100; level = ui.MediumLevel; break; case 3: range = 1000; level = ui.HardLevel; break; case 4: PlayBulkinDream(ref scores, ui); continue; case 5: ShowDuelMenu(ref scores, ui); continue; case 6: PlayBulkinMasterMode(ref scores, ui); continue; case 7: ShowScores(scores, ui); continue; case 8: ScoreManager.Save(scores); Console.WriteLine(ui.Goodbye); return; default: continue; case 9: PlayParserChallenge(ui); continue; case 10: ShowGameHistory(ui); continue; case 11: PlayRecipeArchive(ui); continue; } if (range > 0) PlayGame(random, range, level, ref scores, ui); } } } static void PlayGame(Random random, int max, string level, ref Dictionary<string, int> scores, UI ui) { var secret = random.Next(1, max + 1); var attempts = 0; var maxAttempts = GameLogic.GetMaxAttempts(max); Console.WriteLine(); Console.WriteLine(ui.LevelInfo(level, max, maxAttempts)); while (true) { Console.Write(ui.YourGuess); var input = Console.ReadLine()?.Trim(); if (string.Equals(input, "q", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine(ui.GiveUp(secret)); return; } if (int.TryParse(input, out var guess)) { attempts++; Console.WriteLine(ui.AttemptsBar(attempts, maxAttempts)); var (isCorrect, baseHint) = GameLogic.CheckGuess(guess, secret); if (isCorrect) { Console.WriteLine(Color.Green + ui.Congrats(attempts) + Color.Reset); if (secret == 42) { Console.WriteLine(Color.Yellow + ui.BulkinEasterEgg + Color.Reset); } if (!scores.TryGetValue(level, out var best) || attempts < best) { scores[level] = attempts; ScoreManager.Save(scores); Console.WriteLine(Color.Gold + ui.NewRecord + Color.Reset); } Console.WriteLine(ui.PlayAgain); Console.ReadLine(); return; } else { var madHint = baseHint switch { "Точно!" => ui.HintExact, "Датчики визжат!" => ui.HintClose, "Реакция близка к критической!" => ui.HintCritical, "Слишком мало!" => max > 100 ? ui.HintGalaxy : ui.HintWarmer, "Слишком много!" => ui.HintEntropy, _ => ui.HintQuantum }; Console.WriteLine(Color.Magenta + ui.ProfessorHint(madHint) + Color.Reset); } } else { Console.WriteLine(Color.Red + ui.InvalidNumber + Color.Reset); } } } static void PlayBulkinDream(ref Dictionary<string, int> scores, UI ui) { Console.WriteLine(); Console.WriteLine(Color.Yellow + ui.BulkinDreamTitle + Color.Reset); Console.WriteLine(ui.BulkinDreamIntro); var ingredients = new[] { "мука", "сахар", "масло", "изюм", "корица" }; var secret = ingredients.OrderBy(_ => new Random().Next()).Take(3).ToArray(); var guessed = new HashSet<string>(); var attempts = 0; while (guessed.Count < 3 && attempts < 10) { attempts++; Console.Write(ui.BulkinGuess(attempts)); var input = Console.ReadLine()?.Trim(); var tokens = FieldsParserTask.ParseLine(input); var parsedIngredients = new List<string>(); var recipeFound = false; for (var i = 0; i < tokens.Length; i++) { if (tokens[i].Value == "рецепт") { recipeFound = true; var nextIndex = tokens[i].GetIndexNextToToken(); if (nextIndex < tokens.Length && tokens[nextIndex].Value == ":") { i = nextIndex; i++; // после ':' while (i < tokens.Length) { if (tokens[i].Value == ",") { i++; continue; } if (tokens[i].Value == " " || tokens[i].Value == "\t") { i++; continue; } parsedIngredients.Add(tokens[i].Value.ToLower()); i++; } } break; } } if (!recipeFound) { // если не ввели "рецепт:...", то просто добавляем как ингредиент parsedIngredients.Add(input.ToLower()); } foreach (var ingredient in parsedIngredients) { if (secret.Contains(ingredient)) { if (guessed.Add(ingredient)) { Console.WriteLine(Color.Green + ui.BulkinCorrect(ingredient) + Color.Reset); if (guessed.Count == 3) { Console.WriteLine(Color.Gold + ui.BulkinSuccess + Color.Reset); if (!scores.TryGetValue("булочный мастер", out var best) || attempts < best) { scores["булочный мастер"] = attempts; ScoreManager.Save(scores); Console.WriteLine(Color.Gold + ui.BulkinNewTitle + Color.Reset); } return; } } else { Console.WriteLine(Color.Yellow + ui.BulkinAlreadyGuessed + Color.Reset); } } else { Console.WriteLine(Color.Red + ui.BulkinWrong(ingredient) + Color.Reset); } } if (guessed.Count == 3) { Console.WriteLine(Color.Gold + ui.BulkinSuccess + Color.Reset); if (!scores.TryGetValue("булочный мастер", out var best) || attempts < best) { scores["булочный мастер"] = attempts; ScoreManager.Save(scores); Console.WriteLine(Color.Gold + ui.BulkinNewTitle + Color.Reset); } return; } } if (guessed.Count < 3) { Console.WriteLine(Color.Red + ui.BulkinTimeUp(string.Join(", ", secret)) + Color.Reset); } Console.WriteLine(ui.BulkinReturn); Console.ReadLine(); } static void PlayParserChallenge(UI ui) { Console.WriteLine(); Console.WriteLine(Color.Yellow + ui.ParserChallengeTitle + Color.Reset); Console.WriteLine(ui.ParserChallengeIntro); while (true) { Console.Write(ui.ParserChallengePrompt); var input = Console.ReadLine()?.Trim(); if (string.IsNullOrEmpty(input)) { Console.WriteLine(ui.ParserChallengeReturn); Console.ReadLine(); return; } var tokens = FieldsParserTask.ParseLine(input); Console.WriteLine(ui.ParserChallengeTokensCount(tokens.Length)); foreach (var token in tokens) { var color = token.Value switch { ":" => Color.Blue, "," => Color.Blue, _ => Color.White }; Console.WriteLine(color + $" [{token.StartIndex}..{token.GetIndexNextToToken()}] '{token.Value}'" + Color.Reset); } // Проверяем, является ли строка рецептом if (tokens.Length > 0 && tokens[0].Value == "рецепт") { var recipeTokens = new List<string>(); var recipeFound = false; for (var i = 0; i < tokens.Length; i++) { if (tokens[i].Value == "рецепт") { recipeFound = true; var nextIndex = tokens[i].GetIndexNextToToken(); if (nextIndex < tokens.Length && tokens[nextIndex].Value == ":") { i = nextIndex; i++; // после ':' while (i < tokens.Length) { if (tokens[i].Value == ",") { i++; continue; } if (tokens[i].Value == " " || tokens[i].Value == "\t") { i++; continue; } recipeTokens.Add(tokens[i].Value.ToLower()); i++; } } break; } } if (recipeFound) { var isValid = ValidateRecipe(recipeTokens, out var errors); if (isValid) { Console.WriteLine(Color.Green + ui.ParserChallengeValid + Color.Reset); } else { Console.WriteLine(Color.Red + ui.ParserChallengeInvalid + Color.Reset); foreach (var error in errors) { Console.WriteLine(Color.Red + $" - {error}" + Color.Reset); } } } } else { Console.WriteLine(ui.ParserChallengeNotRecipe); } Console.WriteLine(); } } static bool ValidateRecipe(List<string> ingredients, out List<string> errors) { errors = new List<string>(); if (ingredients.Count != 3) { errors.Add("Должно быть ровно 3 ингредиента."); } var unique = ingredients.Distinct().ToList(); if (unique.Count != ingredients.Count) { errors.Add("Есть дубликаты ингредиентов."); } var allIngredients = new[] { "мука", "сахар", "масло", "изюм", "корица" }; var invalid = ingredients.Where(x => !allIngredients.Contains(x)).ToList(); if (invalid.Count > 0) { errors.Add($"Недопустимые ингредиенты: {string.Join(", ", invalid)}."); } return errors.Count == 0; } static void PlayBulkinMasterMode(ref Dictionary<string, int> scores, UI ui) { Console.WriteLine(); Console.WriteLine(Color.Yellow + ui.BulkinMasterTitle + Color.Reset); Console.WriteLine(ui.BulkinMasterIntro); var allIngredients = new[] { "мука", "сахар", "масло", "изюм", "корица" }; Console.Write(ui.BulkinAskYourRecipe); var input = Console.ReadLine()?.Trim(); var tokens = FieldsParserTask.ParseLine(input); var parsedIngredients = new List<string>(); var recipeFound = false; for (var i = 0; i < tokens.Length; i++) { if (tokens[i].Value == "рецепт") { recipeFound = true; var nextIndex = tokens[i].GetIndexNextToToken(); if (nextIndex < tokens.Length && tokens[nextIndex].Value == ":") { i = nextIndex; i++; // после ':' while (i < tokens.Length) { if (tokens[i].Value == ",") { i++; continue; } if (tokens[i].Value == " " || tokens[i].Value == "\t") { i++; continue; } parsedIngredients.Add(tokens[i].Value.ToLower()); i++; } } break; } } if (!recipeFound || parsedIngredients.Count != 3) { Console.WriteLine(Color.Red + ui.BulkinInvalidRecipe + Color.Reset); Console.WriteLine(ui.BulkinReturn); Console.ReadLine(); return; } var secret = parsedIngredients.ToArray(); var possibleIngredients = new HashSet<string>(allIngredients); var knownYes = new HashSet<string>(); var knownNo = new HashSet<string>(); var attempts = 0; Console.WriteLine(ui.BulkinMasterStart); while (knownYes.Count < 3) { attempts++; var remaining = possibleIngredients.Where(x => !knownYes.Contains(x) && !knownNo.Contains(x)).ToList(); if (remaining.Count == 0) break; var guess = remaining[new Random().Next(remaining.Count)]; Console.Write(ui.BulkinMasterAskIngredient(guess)); var answer = Console.ReadLine()?.Trim().ToLower(); if (answer == "да" || answer == "yes") { knownYes.Add(guess); Console.WriteLine(Color.Green + ui.BulkinMasterYes + Color.Reset); } else { knownNo.Add(guess); Console.WriteLine(Color.Red + ui.BulkinMasterNo + Color.Reset); } if (knownYes.Count == 3) { Console.WriteLine(Color.Gold + ui.BulkinMasterSuccess(attempts) + Color.Reset); if (!scores.TryGetValue("мастер-пекарь", out var best) || attempts < best) { scores["мастер-пекарь"] = attempts; ScoreManager.Save(scores); Console.WriteLine(Color.Gold + ui.BulkinMasterNewTitle + Color.Reset); } break; } } if (knownYes.Count < 3) { Console.WriteLine(Color.Red + ui.BulkinMasterFailed + Color.Reset); } Console.WriteLine(ui.BulkinReturn); Console.ReadLine(); } static void ShowDuelMenu(ref Dictionary<string, int> scores, UI ui) { Console.WriteLine(); Console.WriteLine(ui.DuelMenuTitle); Console.WriteLine("1. " + ui.DuelHost); Console.WriteLine("2. " + ui.DuelGuest); Console.Write(ui.ChooseOption); var input = Console.ReadLine()?.Trim(); if (input == "1") StartNetworkDuelAsHost(ref scores, ui); else if (input == "2") JoinNetworkDuelAsClient(ui); } static void ShowGameHistory(UI ui) { var history = GameHistoryManager.Load(); Console.WriteLine(); Console.WriteLine(Color.Yellow + ui.HistoryTitle + Color.Reset); if (history.Count == 0) { Console.WriteLine(ui.HistoryEmpty); Console.WriteLine(ui.PressEnter); Console.ReadLine(); return; } foreach (var game in history) { var color = game.IsWin ? Color.Green : Color.Red; Console.WriteLine(color + ui.HistoryEntry( game.Timestamp.ToString("yyyy-MM-dd HH:mm"), game.Mode, game.Attempts, game.Secret ) + Color.Reset); } Console.WriteLine(ui.PressEnter); Console.ReadLine(); } static void ShowScores(Dictionary<string, int> scores, UI ui) { Console.WriteLine(); Console.WriteLine(Color.Gold + ui.ScoresTitle + Color.Reset); if (scores.Count == 0) { Console.WriteLine(ui.NoScores); } else { foreach (var (level, best) in scores) Console.WriteLine(ui.ScoreEntry(level, best)); } Console.WriteLine(ui.PressEnter); Console.ReadLine(); } static void StartNetworkDuelAsHost(ref Dictionary<string, int> scores, UI ui) { try { Console.WriteLine(); Console.WriteLine(ui.DuelHostWait); using var listener = new TcpListener(System.Net.IPAddress.Any, 54321); listener.Start(); var client = listener.AcceptTcpClient(); using var stream = client.GetStream(); var secret = new Random().Next(1, 101); var attempts = 0; Console.WriteLine(Color.Green + ui.DuelConnected(client.Client.RemoteEndPoint.ToString()) + Color.Reset); var buffer = new byte[1024]; while (true) { var count = stream.Read(buffer); if (count == 0) break; var guess = int.Parse(System.Text.Encoding.UTF8.GetString(buffer, 0, count)); attempts++; var isCorrect = guess == secret; var response = $"{isCorrect},{attempts},{(isCorrect ? "" : guess < secret ? "меньше" : "больше")}"; stream.Write(System.Text.Encoding.UTF8.GetBytes(response)); Console.WriteLine(Color.Blue + ui.DuelFeedback(guess, isCorrect) + Color.Reset); if (isCorrect) { if (!scores.TryGetValue("сетевая дуэль", out var best) || attempts < best) { scores["сетевая дуэль"] = attempts; ScoreManager.Save(scores); } break; } } client.Close(); listener.Stop(); } catch (Exception ex) { Console.WriteLine(Color.Red + ui.DuelError(ex.Message) + Color.Reset); } Console.WriteLine(ui.PressEnter); Console.ReadLine(); } static void JoinNetworkDuelAsClient(UI ui) { Console.Write(ui.DuelAskIP); var ip = Console.ReadLine()?.Trim(); try { using var client = new TcpClient(); client.Connect(ip, 54321); using var stream = client.GetStream(); while (true) { Console.Write(ui.YourGuess); var input = Console.ReadLine()?.Trim(); if (int.TryParse(input, out var guess)) { var req = System.Text.Encoding.UTF8.GetBytes(guess.ToString()); stream.Write(req); var buffer = new byte[1024]; var count = stream.Read(buffer); var resp = System.Text.Encoding.UTF8.GetString(buffer, 0, count).Split(','); var isCorrect = bool.Parse(resp[0]); var attempts = int.Parse(resp[1]); if (isCorrect) { Console.WriteLine(Color.Green + ui.Congrats(attempts) + Color.Reset); break; } else { Console.WriteLine(Color.Yellow + ui.DuelHint(resp[2], attempts) + Color.Reset); } } } client.Close(); } catch (Exception ex) { Console.WriteLine(Color.Red + ui.DuelError(ex.Message) + Color.Reset); } Console.WriteLine(ui.PressEnter); Console.ReadLine(); } static void PlayRecipeArchive(UI ui) { Console.WriteLine(); Console.WriteLine(Color.Yellow + ui.RecipeArchiveTitle + Color.Reset); Console.WriteLine(ui.RecipeArchiveIntro); while (true) { Console.WriteLine(); Console.WriteLine("1. " + ui.RecipeArchiveList); Console.WriteLine("2. " + ui.RecipeArchiveExport); Console.WriteLine("3. " + ui.RecipeArchiveAdd); Console.WriteLine("4. " + ui.RecipeArchiveDelete); Console.WriteLine("5. " + ui.RecipeArchiveReturn); Console.Write(ui.ChooseOption); var input = Console.ReadLine()?.Trim(); if (input == "1") ListRecipes(ui); else if (input == "2") ExportRecipes(ui); else if (input == "3") AddRecipe(ui); else if (input == "4") DeleteRecipe(ui); else if (input == "5") break; } } static void ListRecipes(UI ui) { var history = GameHistoryManager.Load(); var recipes = history.Where(x => x.Mode.Contains("Булкина") || x.Mode.Contains("Recipe")).ToList(); if (recipes.Count == 0) { Console.WriteLine(ui.RecipeArchiveEmpty); return; } Console.WriteLine(Color.Yellow + ui.RecipeArchiveListTitle + Color.Reset); for (int i = 0; i < recipes.Count; i++) { var recipe = recipes[i]; var color = recipe.IsWin ? Color.Green : Color.Red; Console.WriteLine(color + $"{i + 1}. {recipe.Timestamp:yyyy-MM-dd HH:mm} | {recipe.Secret}" + Color.Reset); } } static void ExportRecipes(UI ui) { var history = GameHistoryManager.Load(); var recipes = history.Where(x => x.Mode.Contains("Булкина") || x.Mode.Contains("Recipe")).ToList(); if (recipes.Count == 0) { Console.WriteLine(ui.RecipeArchiveEmpty); return; } var path = "recipes_export.txt"; var lines = recipes.Select(x => $"{x.Timestamp:yyyy-MM-dd HH:mm} | {x.Secret} | Win: {x.IsWin}").ToList(); File.WriteAllLines(path, lines); Console.WriteLine(Color.Green + ui.RecipeArchiveExported(path) + Color.Reset); } static void AddRecipe(UI ui) { Console.Write(ui.RecipeArchiveAddPrompt); var input = Console.ReadLine()?.Trim(); var tokens = FieldsParserTask.ParseLine(input); var parsedIngredients = new List<string>(); var recipeFound = false; for (var i = 0; i < tokens.Length; i++) { if (tokens[i].Value == "рецепт") { recipeFound = true; var nextIndex = tokens[i].GetIndexNextToToken(); if (nextIndex < tokens.Length && tokens[nextIndex].Value == ":") { i = nextIndex; i++; // после ':' while (i < tokens.Length) { if (tokens[i].Value == ",") { i++; continue; } if (tokens[i].Value == " " || tokens[i].Value == "\t") { i++; continue; } parsedIngredients.Add(tokens[i].Value.ToLower()); i++; } } break; } } if (!recipeFound || parsedIngredients.Count != 3) { Console.WriteLine(Color.Red + ui.BulkinInvalidRecipe + Color.Reset); return; } var isValid = ValidateRecipe(parsedIngredients, out var errors); if (!isValid) { foreach (var error in errors) { Console.WriteLine(Color.Red + $" - {error}" + Color.Reset); } return; } var history = GameHistoryManager.Load(); history.Add(new GameHistoryEntry { Timestamp = DateTime.Now, Mode = "Архивный рецепт", Attempts = 0, Secret = string.Join(",", parsedIngredients), IsWin = true }); GameHistoryManager.Save(history); Console.WriteLine(Color.Green + ui.RecipeArchiveAdded + Color.Reset); } static void DeleteRecipe(UI ui) { var history = GameHistoryManager.Load(); var recipes = history.Where(x => x.Mode.Contains("Булкина") || x.Mode.Contains("Recipe")).ToList(); if (recipes.Count == 0) { Console.WriteLine(ui.RecipeArchiveEmpty); return; } ListRecipes(ui); Console.Write(ui.RecipeArchiveDeletePrompt); var input = Console.ReadLine()?.Trim(); if (int.TryParse(input, out var index) && index > 0 && index <= recipes.Count) { var recipe = recipes[index - 1]; history.Remove(recipe); GameHistoryManager.Save(history); Console.WriteLine(Color.Green + ui.RecipeArchiveDeleted + Color.Reset); } else { Console.WriteLine(Color.Red + ui.InvalidNumber + Color.Reset); } } } class UI { private readonly Dictionary<string, Dictionary<string, string>> _strings = new() { ["ru"] = new() { ["welcome"] = "🔢 Консольная игра с душой и булкой", ["askName"] = "👤 Ваше имя (Enter для анонима): ", ["hello"] = "Привет, {0}!", ["secret"] = "🤫 Секреты: введите 'булка', '42', или 'duel'", ["menuTitle"] = "🎯 Выберите режим", ["easy"] = "Лёгкий (1–10)", ["medium"] = "Средний (1–100)", ["hard"] = "Сложный (1–1000)", ["bulkin"] = "Булкина мечта", ["duel"] = "Сетевая дуэль", ["bulkinMaster"] = "Рецепт-шахматы", ["scores"] = "Рекорды", ["exit"] = "Выйти", ["choose"] = "Выберите: ", ["goodbye"] = "До встречи! 🍞", ["levelInfo"] = "Уровень: {0} (1–{1}) | максимум попыток: {2}", ["yourGuess"] = "🔢 Ваш вариант: ", ["giveUp"] = "Сдался? Число было: {0}", ["congrats"] = "🎉 Угадано за {0} попыток!", ["newRecord"] = "🔥 НОВЫЙ РЕКОРД!", ["playAgain"] = "Нажмите Enter, чтобы продолжить...", ["invalidNumber"] = "Введите целое число или 'q'.", ["hintExact"] = "🎯 Ядро события найдено!", ["hintClose"] = "🔬 Датчики визжат! Ты *почти* в ядре события!", ["hintCritical"] = "🧪 Реакция близка к критической!", ["hintGalaxy"] = "🔭 По шкале Хаббла — ты в пределах одной галактики!", ["hintWarmer"] = "🧫 Культура бактерий шепчет: «Теплее»!", ["hintEntropy"] = "📉 Энтропия растёт — уменьшай!", ["hintQuantum"] = "🌀 Квантовая неопределённость достигла пика!", ["professorHint"] = "🧙♂️ Профессор: {0}", ["bulkinTitle"] = "🍞 ДОБРО ПОЖАЛОВАТЬ В «БУЛКИНУ МЕЧТУ»!", ["bulkinIntro"] = "Профессор испёк булку по секретному рецепту. В ней ровно 3 ингредиента из 5 возможных.", ["bulkinGuess"] = "Ингредиент #{0} (попытка {1}/10): ", ["bulkinCorrect"] = "✅ {0} — в булке есть!", ["bulkinAlreadyGuessed"] = "⚠️ Этот ингредиент уже есть!", ["bulkinWrong"] = "❌ {0} — не входит в рецепт.", ["bulkinSuccess"] = "🎉 РЕЦЕПТ РАСКРЫТ! Булка готова!", ["bulkinNewTitle"] = "🏆 Новый титул: Булочный Мастер!", ["bulkinTimeUp"] = "😔 Время вышло. Рецепт: {0}", ["bulkinReturn"] = "Нажмите Enter, чтобы вернуться в меню...", ["bulkinEasterEgg"] = "🍞 ВНИМАНИЕ! Активирована «Булкина мечта»! Поздравляем. Вы нашли Ответ на Главный Вопрос Жизни, Вселенной и Всего Такого.", ["scoresTitle"] = "🏆 Таблица рекордов:", ["noScores"] = "Пока рекордов нет. Пора играть!", ["scoreEntry"] = "{0}: {1} попыток", ["pressEnter"] = "Нажмите Enter...", ["duelMenuTitle"] = "🌐 Сетевая дуэль", ["duelHost"] = "Хост (ждать подключения)", ["duelGuest"] = "Гость (подключиться)", ["duelWait"] = "Ожидание подключения игрока...", ["duelConnected"] = "✅ Подключено: {0}", ["duelFeedback"] = "📡 {0} → {1}", ["duelHint"] = "📉 {0} (попытка {1})", ["duelAskIP"] = "🌐 IP хоста: ", ["duelError"] = "❌ Ошибка: {0}", ["attemptsBar"] = "[{0}] {1}/{2}", ["bulkinMasterTitle"] = "🧠 ДОБРО ПОЖАЛОВАТЬ В 'РЕЦЕПТ-ШАХМАТЫ'!", ["bulkinMasterIntro"] = "Загадайте 3 ингредиента. Компьютер будет их угадывать.", ["bulkinAskYourRecipe"] = "Введите ваш рецепт: ", ["bulkinInvalidRecipe"] = "Введите ровно 3 ингредиента в формате: рецепт:мука,сахар,масло", ["bulkinMasterStart"] = "Компьютер начинает угадывать...", ["bulkinMasterAsk"] = "Есть ли в рецепте '{0}'? (да/нет): ", ["bulkinMasterYes"] = "✅ Запомнил: есть.", ["bulkinMasterNo"] = "❌ Запомнил: нет.", ["bulkinMasterSuccess"] = "🎉 Компьютер угадал ваш рецепт за {0} ходов!", ["bulkinMasterNewTitle"] = "🏆 Новый рекорд: Мастер-Пекарь!", ["bulkinMasterFailed"] = "😔 Компьютер не смог угадать.", }, ["en"] = new() { ["welcome"] = "🔢 Console game with soul and a bun", ["askName"] = "👤 Your name (Enter for anonymous): ", ["hello"] = "Hello, {0}!", ["secret"] = "🤫 Secrets: type 'bulka', '42', or 'duel'", ["menuTitle"] = "🎯 Choose mode", ["easy"] = "Easy (1–10)", ["medium"] = "Medium (1–100)", ["hard"] = "Hard (1–1000)", ["bulkin"] = "Bulkin's Dream", ["duel"] = "Network Duel", ["bulkinMaster"] = "Recipe Chess", ["scores"] = "High Scores", ["exit"] = "Exit", ["choose"] = "Choose: ", ["goodbye"] = "See you! 🍞", ["levelInfo"] = "Level: {0} (1–{1}) | max attempts: {2}", ["yourGuess"] = "🔢 Your guess: ", ["giveUp"] = "Gave up? The number was: {0}", ["congrats"] = "🎉 Guessed in {0} attempts!", ["newRecord"] = "🔥 NEW RECORD!", ["playAgain"] = "Press Enter to continue...", ["invalidNumber"] = "Enter an integer or 'q'.", ["hintExact"] = "🎯 Core event found!", ["hintClose"] = "🔬 Sensors are squealing! You're *almost* in the core!", ["hintCritical"] = "🧪 Reaction is close to critical!", ["hintGalaxy"] = "🔭 On the Hubble scale — you're within one galaxy!", ["hintWarmer"] = "🧫 Bacteria culture whispers: «Warmer»!", ["hintEntropy"] = "📉 Entropy is rising — decrease!", ["hintQuantum"] = "🌀 Quantum uncertainty has peaked!", ["professorHint"] = "🧙♂️ Professor: {0}", ["bulkinTitle"] = "🍞 WELCOME TO 'BULKIN'S DREAM'!", ["bulkinIntro"] = "Professor baked a bun with a secret recipe. It has exactly 3 ingredients out of 5.", ["bulkinGuess"] = "Ingredient #{0} (attempt {1}/10): ", ["bulkinCorrect"] = "✅ {0} — in the bun!", ["bulkinAlreadyGuessed"] = "⚠️ This ingredient is already there!", ["bulkinWrong"] = "❌ {0} — not in the recipe.", ["bulkinSuccess"] = "🎉 RECIPE REVEALED! Bun is ready!", ["bulkinNewTitle"] = "🏆 New title: Bun Master!", ["bulkinTimeUp"] = "😔 Time is up. Recipe: {0}", ["bulkinReturn"] = "Press Enter to return to menu...", ["bulkinEasterEgg"] = "🍞 ATTENTION! 'Bulkin's Dream' activated! Congratulations. You found the Answer to the Ultimate Question of Life, the Universe, and Everything.", ["scoresTitle"] = "🏆 High Scores:", ["noScores"] = "No scores yet. Time to play!", ["scoreEntry"] = "{0}: {1} attempts", ["pressEnter"] = "Press Enter...", ["duelMenuTitle"] = "🌐 Network Duel", ["duelHost"] = "Host (wait for connection)", ["duelGuest"] = "Guest (connect)", ["duelWait"] = "Waiting for player to connect...", ["duelConnected"] = "✅ Connected: {0}", ["duelFeedback"] = "📡 {0} → {1}", ["duelHint"] = "📉 {0} (attempt {1})", ["duelAskIP"] = "🌐 Host IP: ", ["duelError"] = "❌ Error: {0}", ["attemptsBar"] = "[{0}] {1}/{2}", ["bulkinMasterTitle"] = "🧠 WELCOME TO 'RECIPE CHESS'!", ["bulkinMasterIntro"] = "Choose 3 ingredients. Computer will guess them.", ["bulkinAskYourRecipe"] = "Enter your recipe: ", ["bulkinInvalidRecipe"] = "Enter exactly 3 ingredients in format: recipe:flour,sugar,butter", ["bulkinMasterStart"] = "Computer starts guessing...", ["bulkinMasterAsk"] = "Is '{0}' in the recipe? (yes/no): ", ["bulkinMasterYes"] = "✅ Noted: yes.", ["bulkinMasterNo"] = "❌ Noted: no.", ["bulkinMasterSuccess"] = "🎉 Computer guessed your recipe in {0} moves!", ["bulkinMasterNewTitle"] = "🏆 New record: Master Baker!", ["bulkinMasterFailed"] = "😔 Computer failed to guess.", ["parserChallengeTitle"] = "🧪 ДОБРО ПОЖАЛОВАТЬ В 'ПАРСЕР-ИСПЫТАНИЕ'!", ["parserChallengeIntro"] = "Введите строку — и посмотрите, как она парсится.", ["parserChallengePrompt"] = "Введите строку (или Enter для возврата): ", ["parserChallengeTokensCount"] = "Найдено токенов: {0}", ["parserChallengeValid"] = "✅ Это валидный рецепт!", ["parserChallengeInvalid"] = "❌ Рецепт не прошёл проверку:", ["parserChallengeNotRecipe"] = "Это не похоже на рецепт.", ["parserChallengeReturn"] = "Нажмите Enter, чтобы вернуться...", ["historyTitle"] = "📜 ИСТОРИЯ ИГР", ["historyEmpty"] = "История пока пуста. Пора играть!", ["historyEntry"] = "{0} | {1} | {2} попыток | загадано: {3}", ["recipeArchiveTitle"] = "🗄️ ДОБРО ПОЖАЛОВАТЬ В 'РЕЦЕПТ-АРХИВ'!", ["recipeArchiveIntro"] = "Управляйте сохранёнными рецептами.", ["recipeArchiveList"] = "Просмотреть рецепты", ["recipeArchiveExport"] = "Экспортировать в файл", ["recipeArchiveAdd"] = "Добавить новый рецепт", ["recipeArchiveDelete"] = "Удалить рецепт", ["recipeArchiveReturn"] = "Вернуться в меню", ["recipeArchiveEmpty"] = "Рецептов пока нет.", ["recipeArchiveListTitle"] = "Сохранённые рецепты:", ["recipeArchiveExported"] = "Рецепты экспортированы в {0}", ["recipeArchiveAddPrompt"] = "Введите рецепт для добавления: ", ["recipeArchiveAdded"] = "Рецепт добавлен в архив.", ["recipeArchiveDeletePrompt"] = "Введите номер рецепта для удаления: ", ["recipeArchiveDeleted"] = "Рецепт удалён из архива.", } }; private readonly string _lang; public UI(string lang) => _lang = lang switch { "en" => "en", _ => "ru" }; public string WelcomeMessage => _strings[_lang]["welcome"]; public string AskName => _strings[_lang]["askName"]; public string Hello(string name) => _strings[_lang]["hello"].Replace("{0}", name); public string SecretMessage => _strings[_lang]["secret"]; public string MenuTitle => _strings[_lang]["menuTitle"]; public string EasyLevel => _strings[_lang]["easy"]; public string MediumLevel => _strings[_lang]["medium"]; public string HardLevel => _strings[_lang]["hard"]; public string BulkinDream => _strings[_lang]["bulkin"]; public string NetworkDuel => _strings[_lang]["duel"]; public string BulkinMaster => _strings[_lang]["bulkinMaster"]; public string HighScores => _strings[_lang]["scores"]; public string Exit => _strings[_lang]["exit"]; public string ChooseOption => _strings[_lang]["choose"]; public string Goodbye => _strings[_lang]["goodbye"]; public string LevelInfo(string level, int max, int maxAttempts) => _strings[_lang]["levelInfo"].Replace("{0}", level).Replace("{1}", max.ToString()).Replace("{2}", maxAttempts.ToString()); public string YourGuess => _strings[_lang]["yourGuess"]; public string GiveUp(int number) => _strings[_lang]["giveUp"].Replace("{0}", number.ToString()); public string Congrats(int attempts) => _strings[_lang]["congrats"].Replace("{0}", attempts.ToString()); public string NewRecord => _strings[_lang]["newRecord"]; public string PlayAgain => _strings[_lang]["playAgain"]; public string InvalidNumber => _strings[_lang]["invalidNumber"]; public string HintExact => _strings[_lang]["hintExact"]; public string HintClose => _strings[_lang]["hintClose"]; public string HintCritical => _strings[_lang]["hintCritical"]; public string HintGalaxy => _strings[_lang]["hintGalaxy"]; public string HintWarmer => _strings[_lang]["hintWarmer"]; public string HintEntropy => _strings[_lang]["hintEntropy"]; public string HintQuantum => _strings[_lang]["hintQuantum"]; public string ProfessorHint(string hint) => _strings[_lang]["professorHint"].Replace("{0}", hint); public string BulkinDreamTitle => _strings[_lang]["bulkinTitle"]; public string BulkinDreamIntro => _strings[_lang]["bulkinIntro"]; public string BulkinGuess(int attempt) => _strings[_lang]["bulkinGuess"].Replace("{0}", (attempt).ToString()).Replace("{1}", attempt.ToString()); public string BulkinCorrect(string ingredient) => _strings[_lang]["bulkinCorrect"].Replace("{0}", ingredient); public string BulkinAlreadyGuessed => _strings[_lang]["bulkinAlreadyGuessed"]; public string BulkinWrong(string ingredient) => _strings[_lang]["bulkinWrong"].Replace("{0}", ingredient); public string BulkinSuccess => _strings[_lang]["bulkinSuccess"]; public string BulkinNewTitle => _strings[_lang]["bulkinNewTitle"]; public string BulkinTimeUp(string recipe) => _strings[_lang]["bulkinTimeUp"].Replace("{0}", recipe); public string BulkinReturn => _strings[_lang]["bulkinReturn"]; public string BulkinEasterEgg => _strings[_lang]["bulkinEasterEgg"]; public string ScoresTitle => _strings[_lang]["scoresTitle"]; public string NoScores => _strings[_lang]["noScores"]; public string ScoreEntry(string level, int score) => _strings[_lang]["scoreEntry"].Replace("{0}", level).Replace("{1}", score.ToString()); public string PressEnter => _strings[_lang]["pressEnter"]; public string DuelMenuTitle => _strings[_lang]["duelMenuTitle"]; public string DuelHost => _strings[_lang]["duelHost"]; public string DuelGuest => _strings[_lang]["duelGuest"]; public string DuelHostWait => _strings[_lang]["duelWait"]; public string DuelConnected(string endpoint) => _strings[_lang]["duelConnected"].Replace("{0}", endpoint); public string DuelFeedback(int guess, bool correct) => _strings[_lang]["duelFeedback"].Replace("{0}", guess.ToString()).Replace("{1}", correct ? "✅" : "📉"); public string DuelHint(string hint, int attempt) => _strings[_lang]["duelHint"].Replace("{0}", hint).Replace("{1}", attempt.ToString()); public string DuelAskIP => _strings[_lang]["duelAskIP"]; public string DuelError(string msg) => _strings[_lang]["duelError"].Replace("{0}", msg); public string AttemptsBar(int current, int max) { var filled = new string('█', current); var empty = new string('░', max - current); return _strings[_lang]["attemptsBar"].Replace("{0}", filled + empty).Replace("{1}", current.ToString()).Replace("{2}", max.ToString()); } public string BulkinMasterTitle => _strings[_lang]["bulkinMasterTitle"]; public string BulkinMasterIntro => _strings[_lang]["bulkinMasterIntro"]; public string BulkinAskYourRecipe => _strings[_lang]["bulkinAskYourRecipe"]; public string BulkinInvalidRecipe => _strings[_lang]["bulkinInvalidRecipe"]; public string BulkinMasterStart => _strings[_lang]["bulkinMasterStart"]; public string BulkinMasterAskIngredient(string ingredient) => _strings[_lang]["bulkinMasterAsk"].Replace("{0}", ingredient); public string BulkinMasterYes => _strings[_lang]["bulkinMasterYes"]; public string BulkinMasterNo => _strings[_lang]["bulkinMasterNo"]; public string BulkinMasterSuccess(int attempts) => _strings[_lang]["bulkinMasterSuccess"].Replace("{0}", attempts.ToString()); public string BulkinMasterNewTitle => _strings[_lang]["bulkinMasterNewTitle"]; public string BulkinMasterFailed => _strings[_lang]["bulkinMasterFailed"]; public string ParserChallengeTitle => _strings[_lang]["parserChallengeTitle"]; public string ParserChallengeIntro => _strings[_lang]["parserChallengeIntro"]; public string ParserChallengePrompt => _strings[_lang]["parserChallengePrompt"]; public string ParserChallengeTokensCount(int count) => _strings[_lang]["parserChallengeTokensCount"].Replace("{0}", count.ToString()); public string ParserChallengeValid => _strings[_lang]["parserChallengeValid"]; public string ParserChallengeInvalid => _strings[_lang]["parserChallengeInvalid"]; public string ParserChallengeNotRecipe => _strings[_lang]["parserChallengeNotRecipe"]; public string ParserChallengeReturn => _strings[_lang]["parserChallengeReturn"]; public string HistoryTitle => _strings[_lang]["historyTitle"]; public string HistoryEmpty => _strings[_lang]["historyEmpty"]; public string HistoryEntry(string time, string mode, int attempts, string secret) => _strings[_lang]["historyEntry"] .Replace("{0}", time) .Replace("{1}", mode) .Replace("{2}", attempts.ToString()) .Replace("{3}", secret.ToString()); public string RecipeArchiveTitle => _strings[_lang]["recipeArchiveTitle"]; public string RecipeArchiveIntro => _strings[_lang]["recipeArchiveIntro"]; public string RecipeArchiveList => _strings[_lang]["recipeArchiveList"]; public string RecipeArchiveExport => _strings[_lang]["recipeArchiveExport"]; public string RecipeArchiveAdd => _strings[_lang]["recipeArchiveAdd"]; public string RecipeArchiveDelete => _strings[_lang]["recipeArchiveDelete"]; public string RecipeArchiveReturn => _strings[_lang]["recipeArchiveReturn"]; public string RecipeArchiveEmpty => _strings[_lang]["recipeArchiveEmpty"]; public string RecipeArchiveListTitle => _strings[_lang]["recipeArchiveListTitle"]; public string RecipeArchiveExported(string path) => _strings[_lang]["recipeArchiveExported"].Replace("{0}", path); public string RecipeArchiveAddPrompt => _strings[_lang]["recipeArchiveAddPrompt"]; public string RecipeArchiveAdded => _strings[_lang]["recipeArchiveAdded"]; public string RecipeArchiveDeletePrompt => _strings[_lang]["recipeArchiveDeletePrompt"]; public string RecipeArchiveDeleted => _strings[_lang]["recipeArchiveDeleted"]; } public static class FieldsParserTask { public static Token[] ParseLine(string line) { var tokens = new List<Token>(); var i = 0; while (i < line.Length) { if (char.IsWhiteSpace(line[i])) { i++; continue; } if (line[i] == ':') { tokens.Add(new Token(":", i, 1)); i++; continue; } if (line[i] == ',') { tokens.Add(new Token(",", i, 1)); i++; continue; } var start = i; while (i < line.Length && !char.IsWhiteSpace(line[i]) && line[i] != ':' && line[i] != ',') i++; tokens.Add(new Token(line.Substring(start, i - start), start, i - start)); } return tokens.ToArray(); } } public class Token { public string Value { get; } public int StartIndex { get; } public int Length { get; } public Token(string value, int startIndex, int length) { Value = value; StartIndex = startIndex; Length = length; } public int GetIndexNextToToken() { return StartIndex + Length; } } static class Color { public const string Reset = "\x1b[0m"; public const string Red = "\x1b[31m"; public const string Green = "\x1b[32m"; public const string Yellow = "\x1b[33m"; public const string Blue = "\x1b[34m"; public const string Magenta = "\x1b[35m"; public const string White = "\x1b[37m"; public const string Gold = "\x1b[33m"; // same as yellow } public class GameHistoryEntry { public DateTime Timestamp { get; set; } public string Mode { get; set; } = ""; public int Attempts { get; set; } public string Secret { get; set; } = ""; public bool IsWin { get; set; } } public static class GameHistoryManager { private const string FilePath = "games.json"; public static List<GameHistoryEntry> Load() { if (!File.Exists(FilePath)) return new List<GameHistoryEntry>(); try { var json = File.ReadAllText(FilePath); return System.Text.Json.JsonSerializer.Deserialize<List<GameHistoryEntry>>(json) ?? new List<GameHistoryEntry>(); } catch { return new List<GameHistoryEntry>(); } } public static void Save(List<GameHistoryEntry> history) { try { var json = System.Text.Json.JsonSerializer.Serialize(history, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(FilePath, json); } catch { /* ignore */ } } }