/// <summary> /// Gives general information about the game and how to play it. /// </summary> /// <param name="funConfiguration"></param> /// <returns></returns> public EmbedBuilder Info(FunConfiguration funConfiguration) { return(new EmbedBuilder() .WithColor(Color.Magenta) .WithTitle("How To Play: Hangman") .WithDescription("**Step 1**: Set a TERM! The master can choose a term in DMs with the `game SET TERM [Term]` command.\n" + "**Step 2**: Any player can say `guess` at any moment to see the current status of the guess.\n" + "**Step 3**: Start guessing letters! type `guess [letter]` to guess a letter (you can't guess twice in a row!).\n" + "Feeling brave? Guess the whole term with `guess [term]`, it doesn't cost lives, but it does cost a turn.\n" + "If you'd like to forego your turn, type `pass`. To check misguessed letters, type `mistakes`.\n" + "Keep guessing until you run out of lives or you complete the word! (Type `lives` to see how many lives you have left)")); }
public void Reset(FunConfiguration funConfiguration, GamesDB gamesDB) { game.Data = EmptyData; game.LastUserInteracted = game.Master; if (gamesDB is null) { return; } Player[] players = gamesDB.GetPlayersFromInstance(game.GameID); foreach (Player p in players) { p.Score = 0; p.Lives = 0; } }
public EmbedBuilder Info(FunConfiguration funConfiguration) { return(new EmbedBuilder() .WithColor(Discord.Color.Magenta) .WithTitle("How to Play: Minesweeper") .WithDescription("**Step 1**: Set your desired parameters for the board using the `~game set [field] [value]` command. These fields are `width`, `height` and `mines`.\n" + "Here are some defaults, if you'd like to try them:\n" + "Beginner: [10x10] (10💣)\n" + "Intermediate: [16x16] (40💣)\n" + "Advanced: [26x18] (99💣)\n" + "**Step 2**: Create the board view by typing `board` into the game chat.\n" + "**Step 3**: Begin probing cells! Type the name of one or more cells (e.g. `C3` or `G12 D4 H3`) to probe them. Or type `flag [Cell] (Cells...)` to toggle a flag in one or more cells.)\n" + "Since this game is singleplayer, only the game master can interact with it. The number on each cell tells you how many mines are on its immediate surroundings, anywhere from 0 to 8. If you probe a mine, it's game over!\n" + "You win once there are no more cells to probe that do not contain a mine.\n" + "PRO TIP! Type `auto` to automatically probe any cells deemed safe by surrounding flags and danger levels.")); }
/// <summary> /// Resets the game state to its initial default value. /// </summary> /// <param name="FunConfiguration">Settings related to the fun module, which contain the default lives parameter.</param> /// <param name="GamesDB">The database containing player information, set to <see langword="null"/> to avoid resetting scores.</param> public void Reset(FunConfiguration FunConfiguration, GamesDB GamesDB) { game.Data = EmptyData; game.LastUserInteracted = game.Master; Lives = MaxLives = FunConfiguration.HangmanDefaultLives; if (GamesDB is null) { return; } Player[] Players = GamesDB.GetPlayersFromInstance(game.GameID); foreach (Player p in Players) { p.Score = 0; p.Lives = 0; } }
public bool Set(string field, string value, FunConfiguration funConfiguration, out string feedback) { bool nonNumeric = false; string nonNumericFeedback = $"This field is numeric, unable to parse \"{value}\" into an integer value.\n" + $"Did you mean to use a default field instead?"; if (!int.TryParse(value, out int number)) { nonNumeric = true; } int mines = Mines; switch (field.ToLower()) { case "width": if (nonNumeric) { feedback = nonNumericFeedback; return(false); } if (number > MaxWidth || number < MinWidth) { feedback = $"Invalid width! The width must be between {MinWidth} and {MaxWidth}."; return(false); } Width = number; if (mines > MaxMines) { mines = MaxMines; } Board = GenerateBoard(Height, Width, mines, new Random()); State = GenerateNewState(Height, Width); feedback = $"Set \"width\" to {number} and regenerated game board [{Width}x{Height}]. Maximum mine count for this size is {MaxMines}."; return(true); case "height": if (nonNumeric) { feedback = nonNumericFeedback; return(false); } if (number > MaxHeight || number < MinHeight) { feedback = $"Invalid height! The height must be between {MinHeight} and {MaxHeight}."; return(false); } Height = number; if (mines > MaxMines) { mines = MaxMines; } Board = GenerateBoard(Height, Width, mines, new Random()); State = GenerateNewState(Height, Width); feedback = $"Set \"height\" to {number} and regenerated game board [{Width}x{Height}]. Maximum mine count for this size is {MaxMines}."; return(true); case "mines": if (nonNumeric) { feedback = nonNumericFeedback; return(false); } if (number < 0) { feedback = $"Invalid value! Number of mines can't be a negative number."; return(false); } feedback = $"Set \"mines\" to {number} and regenerated game board [{Width}x{Height}]. Maximum mine count for this size is {MaxMines}."; Mines = mines = number; if (mines > MaxMines) { mines = MaxMines; } Board = GenerateBoard(Height, Width, mines, new Random()); State = GenerateNewState(Height, Width); return(true); case "size": string[] toParse = value.Split(" "); List <int> numbers = new List <int>(); foreach (string s in toParse) { if (int.TryParse(s, out int n)) { numbers.Add(n); } } if (numbers.Count < 2) { feedback = $"You didn't provide enough numbers, please use the field-value syntax `size [WIDTH] [HEIGHT] (MINES)`."; return(false); } if (numbers[0] > MaxWidth || numbers[0] < MinWidth) { feedback = $"Invalid width! The width must be between {MinWidth} and {MaxWidth}."; return(false); } if (numbers[1] > MaxHeight || numbers[1] < MinHeight) { feedback = $"Invalid height! The height must be between {MinHeight} and {MaxHeight}."; return(false); } Width = numbers[0]; Height = numbers[1]; if (numbers.Count > 2) { Mines = mines = numbers[2]; } else { mines = Mines; } if (mines > MaxMines) { mines = MaxMines; } feedback = $"Set board size to [{Width}x{Height}] with a maximum mine count of {Mines}, current size can hold up to {MaxMines} mines."; Board = GenerateBoard(Height, Width, mines, new Random()); State = GenerateNewState(Height, Width); return(true); case "difficulty": value = value.ToLower(); if (!Difficulties.ContainsKey(value)) { feedback = $"\"{value}\" is not a valid difficulty! Available difficulties are: {string.Join(", ", Difficulties.Keys)}"; return(false); } Set("size", Difficulties[value], funConfiguration, out feedback); return(true); } feedback = $"Invalid field: \"{field}\" is not a default field nor \"width\", \"height\", \"mines\", \"size\", or \"difficulty\"."; return(false); }
/// <summary> /// Handles a message sent by a player in the appropriate channel. /// </summary> /// <param name="message">The message context from which the author and content can be obtained.</param> /// <param name="gamesDB">The games database in case any player data has to be modified.</param> /// <param name="client">The Discord client used to parse users.</param> /// <param name="funConfiguration">The configuration file containing relevant game information.</param> /// <returns>A <c>Task</c> object, which can be awaited until the method completes successfully.</returns> public async Task HandleMessage(IMessage message, GamesDB gamesDB, DiscordSocketClient client, FunConfiguration funConfiguration) { if (message.Channel is IDMChannel) { return; } Player player = gamesDB.GetOrCreatePlayer(message.Author.Id); string msg = message.Content.Replace("@", "@-"); if (msg.ToLower() == "pass") { if (game.LastUserInteracted == message.Author.Id) { await message.Channel.SendMessageAsync($"It's not your turn, {message.Author.Mention}!"); return; } game.LastUserInteracted = message.Author.Id; await message.Channel.SendMessageAsync($"{message.Author.Mention} passed their turn!"); await message.DeleteAsync(); return; } if (msg.ToLower() == "mistakes") { await message.Channel.SendMessageAsync($"Mistakes: {MistakesExpression()}"); return; } if (msg.ToLower() == "lives") { await message.Channel.SendMessageAsync($"Lives: {LivesExpression()}"); return; } if (msg.ToLower().StartsWith("guess")) { string[] args = msg.Split(" "); if (args.Length == 1) { await message.Channel.SendMessageAsync(DiscordifyGuess()); return; } if (message.Author.Id == game.Master) { await message.Channel.SendMessageAsync("The game master isn't allowed to guess their own word."); return; } if (game.LastUserInteracted == message.Author.Id) { await message.Channel.SendMessageAsync("You've already guessed! Let someone else play, if it's only you, have the master pass their turn."); return; } if (!Guess.Contains('_')) { await message.Channel.SendMessageAsync($"The term was already guessed! You're late to the party. Waiting for the Game Master to change it."); return; } if (Lives < 1) { await message.Channel.SendMessageAsync($"You're out of lives! Choose a new term before continuing."); return; } string newGuess = string.Join(' ', args[1..]);
/// <summary> /// Sets a local <paramref name="field"/> to a given <paramref name="value"/>. /// </summary> /// <remarks>Valid <paramref name="field"/> values are: TERM, LIVES, MAXLIVES, and MISTAKES.</remarks> /// <param name="field">The name of the field to modify.</param> /// <param name="value">The value to set the field to.</param> /// <param name="funConfiguration">The Fun Configuration settings file, which holds relevant data such as default lives.</param> /// <param name="feedback">In case this operation wasn't possible, its reason, or useful feedback even if the operation was successful.</param> /// <returns><see langword="true"/> if the operation was successful, otherwise <see langword="false"/>.</returns> public bool Set(string field, string value, FunConfiguration funConfiguration, out string feedback) { feedback = ""; int n; switch (field.ToLower()) { case "word": case "term": if (value.Contains('_')) { feedback = "The term cannot contain underscores!"; return(false); } else if (value.Contains('@')) { feedback = "The term cannot contain the at symbol (@)!"; return(false); } else if (value.Length > 256) { feedback = "The term is too long!"; return(false); } Reset(funConfiguration, null); Term = value; Guess = ObscureTerm(value); feedback = $"Success! Term = {value}, Guess = \"{DiscordifyGuess()}\""; return(true); case "lives": if (!int.TryParse(value, out n)) { feedback = $"Unable to parse {value} into an integer value."; return(false); } if (n > MAX_LIVES_ALLOWED) { feedback = $"Too many lives! Please keep it below {MAX_LIVES_ALLOWED}"; return(false); } Lives = n; feedback = $"Lives set to {Lives}/{MaxLives}"; return(true); case "maxlives": if (!int.TryParse(value, out n)) { feedback = $"Unable to parse {value} into an integer value."; return(false); } if (n > MAX_LIVES_ALLOWED) { feedback = $"Too many lives! Please keep it below {MAX_LIVES_ALLOWED}"; return(false); } MaxLives = n; feedback = $"Lives set to {Lives}/{MaxLives}"; return(true); case "missed": case "mistakes": if (value.ToLower() == "none") { LettersMissed = ""; feedback = "Reset mistakes."; return(true); } HashSet <char> missed = new HashSet <char>(); foreach (char c in value.ToCharArray()) { if (!char.IsLetter(c)) { missed.Add(char.ToUpper('c')); } } LettersMissed = string.Join("", missed); feedback = $"Missed letters set to {{{string.Join(", ", missed)}}}."; return(true); default: feedback = $"The given field ({field}) was not found, game-specific fields are `term`, `lives`, `maxlives`, and `mistakes`"; return(false); } }