Разработка игры на C# (Mini RPG) Часть 4 из 5. Клиент

Необходимо реализовать некоторое меню для клиента. Клиент будет консольным приложением. В качестве инверсии управления (IoC) используется structuremap. Для начала создадим регистр для инверсии управления.

internal sealed class DefaultRegistry : Registry
{
    public DefaultRegistry()
    {
        For<IGameConfigReader>().Use<ConfigSectionReader>();
    }
}

И класс для работы с контейнером.

internal static class PTResolver
{
    private static IContainer _container;

    public static IContainer Current
    {
        get
        {
            if (_container == null)
                _container = Initialize();
            return _container;
        }
    }

    private static IContainer Initialize()
    {
        return new Container(c => c.AddRegistry<DefaultRegistry>());
    }
}

Реализация меню в консоли

В качестве объекта пункта меню необходима информация о клавише и действии. В дополнение можно сделать выход, тогда нужна информация о том, является ли пункт меню выходом из игры.

internal class GameMenuItem
{
    public GameMenuItem(string title, Action action)
        : this(title, action, false)
    {
    }

    public GameMenuItem(string title, Action action, bool isExit)
    {
        Action = action;
        Title = title;
        IsExit = isExit;
    }

    public string Title { get; set; }
    public Action Action { get; set; }
    public bool IsExit { get; set; }
}

Далее необходимо вывести на экран меню и дать пользователю возможность выбрать пункт.

private void PrintMenu()
{
    DrawStars();
    foreach (KeyValuePair<char, GameMenuItem> pair in _menu)
    {
        Console.WriteLine(" {0}. {1}", pair.Key, pair.Value.Title);
    }
    DrawStars();
    Console.WriteLine();
}

Клиент игры

Начало положено. Начнем по порядку.

private readonly Game _game;
//Пункты меню
private readonly Dictionary<char, GameMenuItem> _menu;
//Сообщения для показа (о статусе игры)
private readonly List<string> _messagesFromActions;

В конструкторе создаем меню и объект игры. Заметим, что реализацию класса читающего настройки мы не указываем явно. Используется слабая связанность. Значит, что настройки могут хранится в другом файле и для этого нужно будет реализовать еще 1 класс для чтения и зарегистрировать в соответствующий интерфейс именно его.

public GameConsoleClient()
{
    _menu = new Dictionary<char, GameMenuItem>();
    _messagesFromActions = new List<string>();

    _game = new Game(PTResolver.Current.GetInstance<IGameConfigReader>());


    _menu.Add('w', new GameMenuItem("Attack", Attack));
    _menu.Add('a', new GameMenuItem("Buy Weapon", BuyWeapon));
    _menu.Add('d', new GameMenuItem("Buy Armor", BuyArmor));
    _menu.Add('s', new GameMenuItem("Heal", Heal));
    _menu.Add('e', new GameMenuItem("Auto", Auto));
    _menu.Add('9', new GameMenuItem("Exit", null, true));
}

Старт игры

В консольном приложении выполняем команды до получения сигнала о выходе. Кнопку выход мы сделали ранее. В случае, если нажатая клавиша является пунктом меню будет выполнено действие определяемое этим пунктом меню. Таким образом обработка пунктов меню не будет разрастаться при увеличении этих пунктов и будет оставаться лаконичной и красивой.

public void Start()
{
    bool inGame = true;

    while (inGame)
    {
        Console.Clear();
        PrintStatus();
        PrintMenu();
        Console.Write(">");      


        ConsoleKeyInfo key = Console.ReadKey();
        _messagesFromActions.Clear();
        char selectedMenu = key.KeyChar;
        if (_menu.ContainsKey(selectedMenu))
        {
            if (_menu[selectedMenu].IsExit)
                return;

            if (_menu[selectedMenu].Action != null)
                _menu[selectedMenu].Action();
        }
        else
        {
            _messagesFromActions.Add("No action with this key");
        }
    }
}

Рассмотрим на примере покупки обработку действия.

private void BuyItem(ItemTypes type)
{
    BuyItemActionResult result = _game.BuyItem(type);
    if (!result.IsSuccessful)
    {
        _messagesFromActions.Add("Fail buy (no money).");
    }
    else
    {
        _messagesFromActions.Add(string.Format("Item Effect: {0}", result.EffectResult));
    }
}

Все работы практически завершены. Теперь введем настройки для игры и попробуем пройти.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="gameConfig" type="Oxozle.MiniRPG.GameConfig.ConfigSection.GameConfigurationSection, Oxozle.MiniRPG.GameConfig" allowLocation="true" allowDefinition="Everywhere" />
  </configSections>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <gameConfig>
    <initialPlayerConfig initialPlayerHealth="100" initialPlayerMaxHealth="100" initialPlayerPower="1" initialPlayerCoins="2"/>
    <battle minWinProbability="50" maxWinProbability="70" increasePowerProbability="5">
      <winResult coinsChange="5" healthChange="-10" healthChangeType="Percent"/>
      <looseResult healthChange="-40"/>
    </battle>
    <shops>
      <armor price="10" effectFrom="1" effectTo="2"/>
      <weapon price="10" effectFrom="1" effectTo="2"/>
      <heal price="3" effectFrom="10" effectTo="10"/>
    </shops>
  </gameConfig>
</configuration>

На удивление игра получилась довольно интересной. С настройками приведенным выше мне удалось достичь 17-го уровня. Что при максимальной вероятности победы 70% довольно хороший результат. Можно изменять настройки и найти оптимальные для себя условия игры.

пример игры на C#

Если у вас после прочтения и изучения исходного кода появились вопросы или комментарии буду очень признателен.

Разработка игры на C# (Mini RPG)

Скачать исходный код игры oxozle.minirpg.zip 46 Кб.

Комментарии

comments powered by Disqus