4
\$\begingroup\$

Main Menu

Category Menu

Category List

Random Name Menu

Pre-Joke

Joke

I've created a command line joke teller which makes Chuck Norris jokes after communicating with the Chuck Norris API.

It also allows you to generate a random person by communicating to the names API and replaces Chuck Norris with that name.

Execution goes like:

1) Asks you if you want a joke

2) Asks you to pick a category

  1. Yes -> calls API/Reads file (acts like cache for me) -> shows list
  2. No -> picks a random category

3) Asks you to pick a random name

  1. Yes -> Calls API
  2. No -> keeps the same chuck norris in joke

4) Calls Joke API and replaces Chuck Norris if user asked for random name

using System;
using System.Linq;
using System.Threading.Tasks;
using System.IO;

namespace ConsoleApp1
{
    class Program
    {

        // key and names allowed to remain as static global variable - not eligible for race condition
        static char key;                                     // monitor keyboard strokes
        static Tuple<string, string, string> names;

        const string CHUCK_NORRIS_API_BASE = "https://api.chucknorris.io";
        const string NAME_API_BASE = "http://uinames.com/api/";


        // create a multithreaded approach to speed up execution time
        static async Task Main(String[] args)
          {
            // list of all categories - used to verify if user entered category is acceptable
            string[] categoriesList = null;
            string[] results = null;
            int lastLoad = -1;

            int calls_count = 0;
            // counter to check API calls from both names API and joke API don't exceed certain number (set at 15)
            while (calls_count < 15)
            {
                // Initial Screen
                PrintScreen("Main");

                validateAnswer("Main");

                // Category Screen
                PrintScreen("Category");

                validateAnswer("CategoryOrName");

                string userCategory = null;

                // if category is selected
                if (key == 'c')
                {
                    Tuple<string[], int> catInfo = Tuple.Create(results, lastLoad);
                    catInfo = await getCategories(lastLoad);

                    lastLoad = (int)catInfo?.Item2;
                    categoriesList = parseCategories(catInfo?.Item1, categoriesList);

                    Console.WriteLine("\nChuckie askes you make a selection. Make sure it's written word for word...:");
                    userCategory = Console.ReadLine().ToLower();
                    validateCategory(userCategory, categoriesList);

                }

                //Randon Name screen
                PrintScreen("Random Name");

                validateAnswer("CategoryOrName");

                // get random name
                if (key == 'r')
                {
                     await GetNames();
                }

                // get the joke and store into results
                String[] joke = await GetRandomJokes(userCategory);
                calls_count++;

                //Pre-Joke Screen
                PrintScreen("Pre-Joke");

                // show joke
                PrintJoke(joke);

            }
            // ends game
            PrintScreen("End");


        }
        // print the joke
        private static void PrintJoke(string[] joke)
        {
            Console.Clear();
            Console.WriteLine(string.Join(",", joke));
            Console.WriteLine("\nChuckie hopes you enjoyed your joke. Press any key to go back to main menu...");
            GetEnteredKey(Console.ReadKey());
        }
        // checks user input
        private static void validateAnswer(string stage)
        {
            // checks for valid answer
            switch (stage)
            {
                case "Main":
                    // checks for valid answer
                    while (key != 'y' && key != 'e')
                    {
                        Console.WriteLine("\nChuckie is not pleased with your invalid answer. Pick 'y' or 'e'");
                        GetEnteredKey(Console.ReadKey());
                    }
                    break;

                case "CategoryOrName":
                    while (key != 'c' && key != 'r' && key != 'e')
                    {
                        Console.WriteLine("\nChuckie is not pleased with your invalid answer. Pick 'c', 'r', or 'e'");
                        GetEnteredKey(Console.ReadKey());
                    }
                    break;

                default:
                    break;
            }


            // check if user wants to exit
            if (key == 'e')
            {
                Console.WriteLine("\nChuckie understands. Good day...");
                Environment.Exit(0);
            }
        }

        // different screen displays
        private static void PrintScreen(string screen)
        {
            switch(screen)
            {
                // Main Menu
                case "Main":
                    Console.Clear();
                    Console.WriteLine("========================================================");
                    Console.WriteLine("|        =)  Chuck Norris Joke Generator  =)           |");
                    Console.WriteLine("| Do you wish to be elightened by the great chuckie... |");
                    Console.WriteLine("|                    Press y for yes                   |");
                    Console.WriteLine("|                    Press e to exit                   |");
                    Console.WriteLine("========================================================");
                    Console.WriteLine("                                 Made by: Joke Company™ ");
                    GetEnteredKey(Console.ReadKey());
                    break;

                // Category Menu
                case "Category":
                    Console.Clear();
                    Console.WriteLine("========================================================");
                    Console.WriteLine("|        =)  Chuck Norris Joke Generator  =)           |");
                    Console.WriteLine("|  Chuckie wonders if you want a category of joke...   |");
                    Console.WriteLine("|              Press c to pick a category              |");
                    Console.WriteLine("|              Press r to get a random category        |");
                    Console.WriteLine("|              Press e to exit                         |");
                    Console.WriteLine("========================================================");
                    Console.WriteLine("                                 Made by: Joke Company™ ");
                    GetEnteredKey(Console.ReadKey());
                    break;

                // Name Menu
                case "Random Name":
                    Console.Clear();
                    Console.WriteLine("========================================================");
                    Console.WriteLine("|        =)  Chuck Norris Joke Generator  =)           |");
                    Console.WriteLine("|     Is the joke about chuckie or someone else...     |");
                    Console.WriteLine("|           Press c to make the joke about chuckie     |");
                    Console.WriteLine("|           Press r to get a random name               |");
                    Console.WriteLine("|           Press e to exit                            |");
                    Console.WriteLine("========================================================");
                    Console.WriteLine("                                 Made by: Joke Company™ ");
                    GetEnteredKey(Console.ReadKey());
                    break;

                // Pre-Joke Screen
                case "Pre-Joke":
                    Console.Clear();
                    Console.WriteLine("========================================================");
                    Console.WriteLine("|        =)  Chuck Norris Joke Generator  =)           |");
                    Console.WriteLine("|     Chuckie has found your perfect joke...           |");
                    Console.WriteLine("|          Press any key to see it                     |");
                    Console.WriteLine("|                                                      |");
                    Console.WriteLine("|                                                      |");
                    Console.WriteLine("========================================================");
                    Console.WriteLine("                                 Made by: Joke Company™ ");
                    GetEnteredKey(Console.ReadKey());
                    break;

                // Closing Screen
                case "End":
                    Console.Clear();
                    Console.Write("\n Chuckie is out of jokes now. Please try again letter :)");
                    break;

                default:
                    break;

            }
        }
        // checks user input for categories
        private static void validateCategory(string category, string[] categories)
        {
            // loop to confirm user enters correct category
            while (!categories.Contains('"' + category + '"'))
            {
                Console.WriteLine("\nThis is not a category chuckie knows about. Please choose a category from the above list");
                category = Console.ReadLine().ToLower();
            }
        }

        //cleans category result into ordered list
        private static string[] parseCategories(string[] results, string[] categories)
        {
            Console.Clear();

            // checks if categories hasn't been previously populated - i.e if program ran before
            if (categories == null) {
                results = results[0].Split('[');
                results = results[1].Split(']');
                categories = results[0].Split(',');
            }
            Console.WriteLine("\nCategories are:");

            //print ordered list of categories
            for (int num_of_cat = 0; num_of_cat < categories.Length; num_of_cat++)
            {
                Console.WriteLine("\n" + num_of_cat.ToString() + "." + categories[num_of_cat]);
            }

            return categories;


        }
        // maps keystroke to standard character - removes error due to case
        private static void GetEnteredKey(ConsoleKeyInfo consoleKeyInfo)
        {
            switch (consoleKeyInfo.Key)
            {
                case ConsoleKey.C:
                    key = 'c';
                    break;
                case ConsoleKey.E:
                    key = 'e';
                    break;
                case ConsoleKey.N:
                    key = 'n';
                    break;
                case ConsoleKey.R:
                    key = 'r';
                    break;
                case ConsoleKey.Y:
                    key = 'y';
                    break;
            }
        }
        // Generates random joke from API
        private static async Task<String[]> GetRandomJokes(string category)
        {
            // creates joke based on first name, and last name and category 
            string[] joke = JsonFeed.GetRandomJokes(CHUCK_NORRIS_API_BASE, names?.Item1, names?.Item2, category);
            // fixes any incorrect pronouns based on random name generated
            return GenderPronounReplace(joke);
        }
        // checks gender pronouns in joke
        private static string[] GenderPronounReplace(string[] joke)
        {
            if (names?.Item3 == "female")
            {
                for (int i = 0; i < joke.Length; i++)
                {
                    switch(joke[i])
                    {
                        case " He ":
                            joke[i] = " She ";
                            break;
                        case " he ":
                            joke[i] = " she ";
                            break;
                        case " His ":
                            joke[i] = " Her ";
                            break;
                        case " his ":
                            joke[i] = " her ";
                            break;
                        case " Him ":
                            joke[i] = " Her ";
                            break;
                        case " him ":
                            joke[i] = " her ";
                            break;
                    }
                } 
            }

            return joke;
        }
        // grabs categories 
        private static async Task <Tuple<String[], int>> getCategories(int lastLoad)
        {
            // save categories to a text file so don't have to call the 
            string[] cat;
            // load categories from file
            if (File.Exists("categories.txt") && lastLoad < 100)
            {
                // read all the categories and last line is count since the number of times text file has been used (in case API updates response to add more categories)
                cat = await File.ReadAllLinesAsync("categories.txt");
                int numLoaded = Int32.Parse(cat[cat.Length - 1]);

                // increment the last updated count and updates the file with new count
                cat[cat.Length - 1] = numLoaded++.ToString();
                await File.WriteAllLinesAsync("categories.txt", cat);
                return Tuple.Create(cat, numLoaded);
            }

            // File has not been created OR > 100 iterations since text file has been updated
            else
            {
                // resets last loaded stat and calls API
                lastLoad = 0;
                cat = JsonFeed.GetCategories(CHUCK_NORRIS_API_BASE);

                // writes list and last Updated from API to file 
                await File.WriteAllLinesAsync("categories.txt", cat);
                await File.AppendAllTextAsync("categories.txt", lastLoad.ToString());
                return Tuple.Create(cat, lastLoad);
            }


           // return  JsonFeed.GetCategories(CHUCK_NORRIS_API_BASE);
        }
        // generates random name
        private static async Task GetNames()
        {
            dynamic result = JsonFeed.Getnames(NAME_API_BASE);
            // stores name, surname, gender
            names = Tuple.Create(result.name.ToString(), result.surname.ToString(), result.gender.ToString());



        }
    }
}

It talks to a helper class which handles all the API calls called JsonFeed

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Text.RegularExpressions;

namespace ConsoleApp1
{
    class JsonFeed
    {

        public static string[] GetRandomJokes(string jokeUrl, string firstName, string lastName, string category)
        {
            StringBuilder joke = new StringBuilder("");
            try
            {
                HttpClient client = new HttpClient();
                client.BaseAddress = new Uri(jokeUrl);
                // TODO: use stringbuilder for this....
                StringBuilder url = new StringBuilder("jokes/random");

                if (category != null)
                {
                    if (url.ToString().Contains('?'))
                        url.Append("&");
                    else url.Append("?");
                    url.Append("category=");
                    url.Append(category);
                }


                //check if word was inside available categories - create array of categories

                 joke.Append(Task.FromResult(client.GetStringAsync(url.ToString()).Result).Result);
            }
            catch (HttpRequestException e)
            {
                Console.WriteLine("\nError");
                Console.WriteLine(e);
                Console.WriteLine("Error Message: {0}", e.Message);
                Environment.Exit(-1);
            }

            if (firstName != null && lastName != null)
            {
                // replace all instances of "chuck" to the first name and "norris" with the last name
                joke.Replace(joke.ToString(), (Regex.Replace(joke.ToString(), "(?i)Chuck", firstName)));
                joke.Replace(joke.ToString(), (Regex.Replace(joke.ToString(), "(?i)Norris", lastName)));
            }

            return new string[] { JsonConvert.DeserializeObject<dynamic>(joke.ToString()).value };
        }

        /// <summary>
        /// returns an object that contains name and surname
        /// </summary>
        /// <param name="client2"></param>
        /// <returns></returns>
        public static dynamic Getnames(string NameAPIUrl)
        {

            try
            {
                HttpClient client = new HttpClient();
                client.BaseAddress = new Uri(NameAPIUrl);
                var result = client.GetStringAsync("").Result;
                return JsonConvert.DeserializeObject<dynamic>(result);
            }
            catch (HttpRequestException e)
            {
                Console.WriteLine("\nError");
                Console.WriteLine(e);
                Console.WriteLine("Error Message: {0}", e.Message);
                Environment.Exit(-1);
            }
            return " ";

        }

        public static string[] GetCategories(string jokeAPI)
        {
            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri(jokeAPI);

            return new string[] { Task.FromResult(client.GetStringAsync("/jokes/categories").Result).Result };
        }
    }
}

I'm still semi-comfortable with best practices and clear documentation so any suggestions in that area or functionality wise, I'm all ears!

\$\endgroup\$
3
\$\begingroup\$

Your code is somewhat messy because almost everything is inside a single Program class. It can use some abstractions. Few things off the bat:

1) Declare and implement a strongly typed api for your service. For example:

interface IChuckService
{
    Joke[] GetRandomJokes(Category category);
    Category[] GetCategories();
    Name[] GetNames();
    //other json-related methods
}

2) Declare and implement api for your screens, and create transitions. For example

interface IChuckScreen
{
    void Show(IChuckService service);
    //this is an example of "active" state machine (you can use "passive" one instead)
    //return "this" to stay on the screen, or new state to transition away
    IChuckScreen ApplyInput(string input);
}

Your program class should probably look like:

var service = new ChuckService();
var currentScreen = new MainScreen();
while(true)
{
    currentScreen.Show(service);
    var input = Console.ReadLine();
    currentScreen = currentScreen.ApplyInput(input);
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Good answer. You basically took the crude state machine that OP had in the one class and broke it out to interfaces. Maybe mention this pattern explicitly in the answer? A question: how do you propose to exit the while loop? Using Environment.Exit? Could the IChuckScreen interface have a method flagging for exit that you could use as a condition in the loop? That way, one can easily do cleanup et cetera after the loop. \$\endgroup\$ – TomG Oct 6 at 9:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.