13
\$\begingroup\$

This week's Code Review Weekend Challenge is about implementing a simple console game.

Here's my game loop:

class Program
{
    static void Main(string[] args)
    {
        var player = new Player();
        var intro = new IntroScreen(player);
        var nextScreen = intro.Run();

        while (nextScreen != null)
        {
            nextScreen = nextScreen.Run();
        }
    }
}

So I have a Player class:

public class Player
{
    public string Name { get; set; }
    public ObservableCollection<InventoryItem> Inventory { get; private set; }

    public Player()
    {
        Inventory = new ObservableCollection<InventoryItem>();
        Inventory.CollectionChanged += Inventory_CollectionChanged;
    }

    private void Inventory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action != NotifyCollectionChangedAction.Add) return;
        foreach (InventoryItem item in e.NewItems)
        {
            Console.WriteLine("Received '{0}'!\n({1})", item.Name, item.Description);
        }

        Console.WriteLine();
        Console.ReadLine();
    }

    public bool HasItem(string name)
    {
        return Inventory.Any(item => item.Name.ToLower() == name.ToLower());
    }
}

The player has an Inventory that's made up of InventoryItem instances:

public struct InventoryItem
{
    public string Name { get; set; }
    public string Description { get; set; }
}

So the role of the Player is really to carry an inventory, whatever that is; whenever an item is added to the player's inventory, there's an output to the console.

The game's mechanics are tucked inside a GameScreen abstract class:

public abstract class GameScreen
{
    protected IDictionary<string, Func<GameScreen>> MenuItems;

    public abstract GameScreen Run();

    protected void Write(string text)
    {
        Console.Write(text);
        Console.WriteLine();
        Console.WriteLine("[ENTER]");
        Console.ReadLine();
    }

    protected string Prompt(string text)
    {
        Console.WriteLine(text);
        var result = Console.ReadLine();
        Console.WriteLine();
        return result;
    }

    protected GameScreen Menu()
    {
        Console.WriteLine("What do you do?");

        var i = 0;
        foreach (var item in MenuItems)
        {
            i++;
            Console.WriteLine("[{0}] {1}", i, item.Key);
        }

        Console.WriteLine();
        Console.WriteLine("Selection?");
        var input = Console.ReadLine();

        int selection;
        if (int.TryParse(input, out selection))
        {
            if (selection > 0 && selection <= MenuItems.Count)
            { 
                return MenuItems.ElementAt(selection - 1).Value(); 
            }
        }

        return null;
    }
}

This is a tiny little game, here's the IntroScreen and KitchenScreen implementations:

public class IntroScreen : GameScreen
{
    private readonly Player _player;

    public IntroScreen(Player player)
    {
        MenuItems = new Dictionary<string,Func<GameScreen>> 
        {
            {"Pick up empty bottles, chips and pretzels", () => 
                                    { 
                                        MenuItems.Remove("Pick up empty bottles, chips and pretzels");
                                        _player.Inventory.Add(new InventoryItem { Name = "Living Room badge", Description = "A badge that certifies the living room has been cleaned up." });
                                        return this; 
                                    } },
            {"Go to [KITCHEN]", () => new KitchenScreen(player) }
        };
        _player = player;
    }

    public override GameScreen Run()
    {
        Console.Clear();
        if (!_player.HasItem("GREEN BAG")) return Intro();
        return Menu();
    }

    private GameScreen Intro()
    {
        Write("You wake up with a headache, confused and\nsurrounded with empty bottles, chips and pretzels.");
        Write("Ah! There you are! Time to clean up this mess!\nHere's a [GREEN BAG], meet you in the [KITCHEN] in 30 minutes!");

        var bag = new InventoryItem { Name = "GREEN BAG", Description = "A general-purpose garbage bag." };
        _player.Inventory.Add(bag);

        var name = string.Empty;
        int attempts = 0;
        while (string.IsNullOrEmpty(name))
        {
            attempts++;
            string prompt;
            if (attempts == 1)
            {
                prompt = "You get up, pick up the bag and remember your name (enter it!):";
                name = Prompt(prompt);
            }
            else if (attempts < 3)
            {
                prompt = "Uh, don't you remember your name?";
                name = Prompt(prompt);
            }
            else
            {
                name = "Rudolph";
                prompt = string.Format("Ok nevermind, we'll call you {0}.", name);
                Write(prompt);
            }
        }
        _player.Name = name;

        return Menu();
    }
}

public class KitchenScreen : GameScreen
{
    private readonly Player _player;

    public KitchenScreen(Player player)
    {
        MenuItems = new Dictionary<string, Func<GameScreen>>
                                    {
                                        { "Go to [LIVING ROOM]", () => new IntroScreen(player) }
                                    };
        _player = player;
    }

    public override GameScreen Run()
    {
        Console.Clear();

        if (_player.HasItem("Living Room badge"))
        {
            Write("You walk into the kitchen, your mom looks too happy.");
            Write(_player.Name + ", finally! So you cleaned up everything! Great! So we're ready for NewYear's party!");
            MenuItems = new Dictionary<string, Func<GameScreen>> { { "Collapse and wake up when the Holidays are over", () => null } };
        }
        else
        {
            Write("You walk into the kitchen, your mom is cleaning up dishes.");
            Write(_player.Name + "! What are you doing here? I told you to clean up! Come back when you're done!");
        }

        return Menu();
    }
}

Output:

You wake up with a headache, confused and surrounded with empty bottles, chips and pretzels. 
[ENTER]

Ah! There you are! Time to clean up this mess! Here's a [GREEN BAG], meet you in the [KITCHEN] in 30 minutes! 
[ENTER]

Received 'GREEN BAG'! 
(A general-purpose garbage bag.)


You get up, pick up the bag and remember your name (enter it!):


Uh, don't you remember your name?


Ok nevermind, we'll call you Rudolph. 
[ENTER]

What do you do? 
[1] Pick up empty bottles, chips and pretzels 
[2] Go to [KITCHEN]

Selection?
2

You walk into the kitchen, your mom is cleaning up dishes.
[ENTER]

Rudolph! What are you doing here? I told you to clean up! Come back when you're
done!
[ENTER]

What do you do?
[1] Go to [LIVING ROOM]

Selection?
1

What do you do?
[1] Pick up empty bottles, chips and pretzels
[2] Go to [KITCHEN]

Selection?
1
Received 'Living Room badge'!
(A badge that certifies the living room has been cleaned up.)

What do you do?
[1] Go to [KITCHEN]

Selection?
1

You walk into the kitchen, your mom looks too happy.
[ENTER]

Rudolph, finally! So you cleaned up everything! Great! So we're ready for NewYear's party!
[ENTER]

What do you do?
[1] Collapse and wake up when the Holidays are over

Selection?
\$\endgroup\$
5
  • \$\begingroup\$ Well that was fast... you only accepted the CRWEC a few minutes ago :P \$\endgroup\$ – syb0rg Dec 27 '13 at 23:46
  • 2
    \$\begingroup\$ @syb0rg I figured there wouldn't be a random new suggestion to beat the leading one, so I wrote this code earlier today - was either that or I couldn't post my entry this week :) \$\endgroup\$ – Mathieu Guindon Dec 27 '13 at 23:48
  • 3
    \$\begingroup\$ Typo: "You walk into the kitchen, you mom is cleaning up dishes." -> your mom. \$\endgroup\$ – Roddy of the Frozen Peas Dec 31 '13 at 19:20
  • 1
    \$\begingroup\$ I like it. Just a few questions though, - Why Expose Inventory as ObservableCollection rather than an Inventory class with some helper functions? - I'd throw the "what do you do" strings n such to consts. - and I am really curious about the foreach with the index, why not a for loop? \$\endgroup\$ – apieceoffruit Jan 13 '14 at 14:35
  • \$\begingroup\$ I had to add the following in order for the program to run. using System.Collections.ObjectModel; using System.Collections.Specialized; \$\endgroup\$ – Dale Dec 8 '16 at 20:52
5
\$\begingroup\$

Well, i am new to this weekend-challenge thing so I am not sure, from which stand point should i review such question. I'll give it a try.

  1. I like your game loop. Its simple and makes sense. It can be simlified further though:

    var activeScreen = new IntroScreen(player);
    while (activeScreen != null) // do-while, if you are not a hater ;)
    {
        activeScreen = activeScreen.Run();
    }
    
  2. I don't like your intro screen. It feels like it consists of two separate screens (1 - where you try to recall your name, 2 where you clean up). As it is - it is somewhat difficult to follow the execution path.

  3. I think instead of being a protected method Menu should be an actual screen class which accepts options as constructor parameter. That would simplify reading.

  4. I dont like the use of an observable collection, i think it is an overkill. And it exposes the methods it should not. A simple wrapper around List which only exposes AddItem and HasItem methods would be much batter. At very least - collection should not be public.

  5. Player should probably be a protected property of your base screen.

  6. return Inventory.Any(item => item.Name.ToLower() == name.ToLower()); I think ToLower() is confusing. Imho you should not manipulate properties which you use as an ID. Its better to make sure that all the green bags are called "GREEN BAG" instead (by saving it to constant field, for example)

  7. In general, i do not like implementation which uses hardcoded strings. I think that proper implementation should use scripts or (if the implementation should be C#-only) XML parsing. But i guess its a bit too much work for a two-hour challenge, right? :)

\$\endgroup\$

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.