Lesson 10.1: Refactoring Base Class for Player, Monster, and Trader

We have some duplicate code in the Player, Monster, and Trader class. In this lesson, we’ll create a base class to eliminate that duplication.

 

 

 

Lesson Steps

Step 1: Create the class Engine\Models\LivingEntity.cs

This class will hold all the common properties and functions that a living creature has in the game. For example, name, hit points, gold, and inventory.

We currently have some differences with those properties. The Monster class has “CurrentHitPoints” and “MaximumHitPoints”. The Player class only has “HitPoints”. But, the Player class really needs MaximumHitPoints, for when we add healing – so the Player cannot heal to more hit points than their maximum.

 

Notice that LivingEntity inherits from BaseNotificationClass. So, it can use the functions in BaseNotificationClass.

We are going to change Player, Monster, and Trader to inherit from LivingEntity. Because LivingEntity inherits from BaseNotificationClass, that means LivingEntity’s “children” will have the functions and properties from BaseNotificationClass – their “grand-parent”.

 

On line 7, notice that LivingEntity has the scope of “abstract”. That means you cannot instantiate a LivingEntity class. You can instantiate one of its children, which will have all the functions and properties from LivingEntity. But, you cannot create a LivingEntity object on its own.

 

On line 58, the LivingEntity constructor is declared as “protected”. Something that is protected can only been seen/used by the children of this class.

When you instantiate a Player class, the constructor in Player.cs will run, and it will run the constructor in LivingEntity.cs. This is how you can ensure all child classes run any initialization code need for the base class.

 

LivingEntity.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace Engine.Models
{
    public abstract class LivingEntity : BaseNotificationClass
    {
        private string _name;
        private int _currentHitPoints;
        private int _maximumHitPoints;
        private int _gold;

        public string Name
        {
            get { return _name; }
            set
            {
                _name = value; 
                OnPropertyChanged(nameof(Name));
            }
        }

        public int CurrentHitPoints
        {
            get { return _currentHitPoints; }
            set
            {
                _currentHitPoints = value; 
                OnPropertyChanged(nameof(CurrentHitPoints));
            }
        }

        public int MaximumHitPoints
        {
            get { return _maximumHitPoints; }
            set
            {
                _maximumHitPoints = value; 
                OnPropertyChanged(nameof(MaximumHitPoints));
            }
        }

        public int Gold
        {
            get { return _gold; }
            set
            {
                _gold = value; 
                OnPropertyChanged(nameof(Gold));
            }
        }

        public ObservableCollection<GameItem> Inventory { get; set; }

        public List<GameItem> Weapons =>
            Inventory.Where(i => i is Weapon).ToList();

        protected LivingEntity()
        {
            Inventory = new ObservableCollection<GameItem>();
        }

        public void AddItemToInventory(GameItem item)
        {
            Inventory.Add(item);

            OnPropertyChanged(nameof(Weapons));
        }

        public void RemoveItemFromInventory(GameItem item)
        {
            Inventory.Remove(item);

            OnPropertyChanged(nameof(Weapons));
        }
    }
}

 

Step 2: Modify Player.cs, Monster.cs, and Trader.cs

Now that we have a base class, we’ll have these classes derive from it.

They currently inherit from BaseNotificationClass. Change that to LivingEntity (in the code below, line 7 in Player.cs and line 3 in Monster and Trader).

Then, we can remove all the properties and functions from the child classes that are now in the base class.

 

Player.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace Engine.Models
{
    public class Player : LivingEntity
    {
        #region Properties

        private string _characterClass;
        private int _experiencePoints;
        private int _level;

        public string CharacterClass
        {
            get { return _characterClass; }
            set
            {
                _characterClass = value;
                OnPropertyChanged(nameof(CharacterClass));
            }
        }

        public int ExperiencePoints
        {
            get { return _experiencePoints; }
            set
            {
                _experiencePoints = value;
                OnPropertyChanged(nameof(ExperiencePoints));
            }
        }

        public int Level
        {
            get { return _level; }
            set
            {
                _level = value;
                OnPropertyChanged(nameof(Level));
            }
        }

        public ObservableCollection<QuestStatus> Quests { get; set; }

        #endregion

        public Player()
        {
            Quests = new ObservableCollection<QuestStatus>();
        }

        public bool HasAllTheseItems(List<ItemQuantity> items)
        {
            foreach (ItemQuantity item in items)
            {
                if (Inventory.Count(i => i.ItemTypeID == item.ItemID) < item.Quantity)
                {
                    return false;
                }
            }

            return true;
        }
    }
}

 

Monster.cs

namespace Engine.Models
{
    public class Monster : LivingEntity
    {
        public string ImageName { get; set; }
        public int MinimumDamage { get; set; }
        public int MaximumDamage { get; set; }

        public int RewardExperiencePoints { get; private set; }

        public Monster(string name, string imageName,
                       int maximumHitPoints, int hitPoints,
                       int minimumDamage, int maxmumDamage,
                       int rewardExperiencePoints, int rewardGold)
        {
            Name = name;
            ImageName = $"/Engine;component/Images/Monsters/{imageName}";
            MaximumHitPoints = maximumHitPoints;
            CurrentHitPoints = hitPoints;
            MinimumDamage = minimumDamage;
            MaximumDamage = maxmumDamage;
            RewardExperiencePoints = rewardExperiencePoints;
            Gold = rewardGold;
        }
    }
}

 

Trader.cs

namespace Engine.Models
{
    public class Trader : LivingEntity
    {
        public Trader(string name)
        {
            Name = name;
        }
    }
}

 

Step 3: Modify Engine\ViewModels\GameSession.cs

Because the child classes did not have the same names for all the properties, we need to change any places where the old names were used, to use the new name in the base class.

For example, in LivingEntity, we have the property CurrentHitPoints. The Player class had the property “HitPoints”. So now, we need to change GameSession to use “CurrentHitPoints”.

 

On line 99, change “HitPoints” to “CurrentHitPoints”, and add line 100 (in the code below) to set the player object’s MaximumHitPoints value.

 

In the AttackCurrentMonster function (line 228 below) there are a few changes to make.

Change the monster’s “HitPoints” to “CurrentHitPoints” on lines 245 and 250.

Change “RewardGold” to “Gold” on lines 258 and 259.

Change the “foreach” loop on line 261 to the new one shown in the code below. In the old version, the Monster’s Inventory property held ItemQuantity objects. Those objects had two properties: ItemID and Quantity.

The new shared Inventory property in LivingEntity holds GameItem objects. So, we can get the GameItem objects directly from the Monster’s Inventory and add them to the Player’s Inventory.

Change the player’s “HitPoints” to “CurrentHitPoints” on lines 281, 286, and 292.

Hose are all the changes we need to make to GameSession.cs

 

GameSession.cs

using System;
using System.Linq;
using Engine.EventArgs;
using Engine.Factories;
using Engine.Models;

namespace Engine.ViewModels
{
    public class GameSession : BaseNotificationClass
    {
        public event EventHandler<GameMessageEventArgs> OnMessageRaised;

        #region Properties

        private Location _currentLocation;
        private Monster _currentMonster;
        private Trader _currentTrader;

        public World CurrentWorld { get; set; }
        public Player CurrentPlayer { get; set; }

        public Location CurrentLocation
        {
            get { return _currentLocation; }
            set
            {
                _currentLocation = value;

                OnPropertyChanged(nameof(CurrentLocation));
                OnPropertyChanged(nameof(HasLocationToNorth));
                OnPropertyChanged(nameof(HasLocationToEast));
                OnPropertyChanged(nameof(HasLocationToWest));
                OnPropertyChanged(nameof(HasLocationToSouth));

                CompleteQuestsAtLocation();
                GivePlayerQuestsAtLocation();
                GetMonsterAtLocation();

                CurrentTrader = CurrentLocation.TraderHere;
            }
        }

        public Monster CurrentMonster
        {
            get { return _currentMonster; }
            set
            {
                _currentMonster = value;

                OnPropertyChanged(nameof(CurrentMonster));
                OnPropertyChanged(nameof(HasMonster));

                if (CurrentMonster != null)
                {
                    RaiseMessage("");
                    RaiseMessage($"You see a {CurrentMonster.Name} here!");
                }
            }
        }

        public Trader CurrentTrader
        {
            get { return _currentTrader; }
            set
            {
                _currentTrader = value; 
                
                OnPropertyChanged(nameof(CurrentTrader));
                OnPropertyChanged(nameof(HasTrader));
            }
        }

        public Weapon CurrentWeapon { get; set; }

        public bool HasLocationToNorth => 
            CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1) != null;

        public bool HasLocationToEast => 
            CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate) != null;

        public bool HasLocationToSouth => 
            CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1) != null;

        public bool HasLocationToWest => 
            CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate) != null;

        public bool HasMonster => CurrentMonster != null;

        public bool HasTrader => CurrentTrader != null;

        #endregion

        public GameSession()
        {
            CurrentPlayer = new Player
                            {
                                Name = "Scott",
                                CharacterClass = "Fighter",
                                CurrentHitPoints = 10,
                                MaximumHitPoints = 10,
                                Gold = 1000000,
                                ExperiencePoints = 0,
                                Level = 1
                            };

            if (!CurrentPlayer.Weapons.Any())
            {
                CurrentPlayer.AddItemToInventory(ItemFactory.CreateGameItem(1001));
            }

            CurrentWorld = WorldFactory.CreateWorld();

            CurrentLocation = CurrentWorld.LocationAt(0, 0);
        }

        public void MoveNorth()
        {
            if(HasLocationToNorth)
            {
                CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1);
            }
        }

        public void MoveEast()
        {
            if(HasLocationToEast)
            {
                CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate);
            }
        }

        public void MoveSouth()
        {
            if(HasLocationToSouth)
            {
                CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1);
            }
        }

        public void MoveWest()
        {
            if(HasLocationToWest)
            {
                CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate);
            }
        }

        private void CompleteQuestsAtLocation()
        {
            foreach(Quest quest in CurrentLocation.QuestsAvailableHere)
            {
                QuestStatus questToComplete =
                    CurrentPlayer.Quests.FirstOrDefault(q => q.PlayerQuest.ID == quest.ID &&
                                                             !q.IsCompleted);

                if(questToComplete != null)
                {
                    if(CurrentPlayer.HasAllTheseItems(quest.ItemsToComplete))
                    {
                        // Remove the quest completion items from the player's inventory
                        foreach (ItemQuantity itemQuantity in quest.ItemsToComplete)
                        {
                            for(int i = 0; i < itemQuantity.Quantity; i++)
                            {
                                CurrentPlayer.RemoveItemFromInventory(CurrentPlayer.Inventory.First(item => item.ItemTypeID == itemQuantity.ItemID));
                            }
                        }

                        RaiseMessage("");
                        RaiseMessage($"You completed the '{quest.Name}' quest");

                        // Give the player the quest rewards
                        CurrentPlayer.ExperiencePoints += quest.RewardExperiencePoints;
                        RaiseMessage($"You receive {quest.RewardExperiencePoints} experience points");

                        CurrentPlayer.Gold += quest.RewardGold;
                        RaiseMessage($"You receive {quest.RewardGold} gold");

                        foreach(ItemQuantity itemQuantity in quest.RewardItems)
                        {
                            GameItem rewardItem = ItemFactory.CreateGameItem(itemQuantity.ItemID);

                            CurrentPlayer.AddItemToInventory(rewardItem);
                            RaiseMessage($"You receive a {rewardItem.Name}");
                        }

                        // Mark the Quest as completed
                        questToComplete.IsCompleted = true;
                    }
                }
            }
        }

        private void GivePlayerQuestsAtLocation()
        {
            foreach(Quest quest in CurrentLocation.QuestsAvailableHere)
            {
                if(!CurrentPlayer.Quests.Any(q => q.PlayerQuest.ID == quest.ID))
                {
                    CurrentPlayer.Quests.Add(new QuestStatus(quest));

                    RaiseMessage("");
                    RaiseMessage($"You receive the '{quest.Name}' quest");
                    RaiseMessage(quest.Description);

                    RaiseMessage("Return with:");
                    foreach(ItemQuantity itemQuantity in quest.ItemsToComplete)
                    {
                        RaiseMessage($"   {itemQuantity.Quantity} {ItemFactory.CreateGameItem(itemQuantity.ItemID).Name}");
                    }

                    RaiseMessage("And you will receive:");
                    RaiseMessage($"   {quest.RewardExperiencePoints} experience points");
                    RaiseMessage($"   {quest.RewardGold} gold");
                    foreach(ItemQuantity itemQuantity in quest.RewardItems)
                    {
                        RaiseMessage($"   {itemQuantity.Quantity} {ItemFactory.CreateGameItem(itemQuantity.ItemID).Name}");
                    }
                }
            }
        }

        private void GetMonsterAtLocation()
        {
            CurrentMonster = CurrentLocation.GetMonster();
        }

        public void AttackCurrentMonster()
        {
            if (CurrentWeapon == null)
            {
                RaiseMessage("You must select a weapon, to attack.");
                return;
            }

            // Determine damage to monster
            int damageToMonster = RandomNumberGenerator.NumberBetween(CurrentWeapon.MinimumDamage, CurrentWeapon.MaximumDamage);

            if (damageToMonster == 0)
            {
                RaiseMessage($"You missed the {CurrentMonster.Name}.");
            }
            else
            {
                CurrentMonster.CurrentHitPoints -= damageToMonster;
                RaiseMessage($"You hit the {CurrentMonster.Name} for {damageToMonster} points.");
            }

            // If monster if killed, collect rewards and loot
            if(CurrentMonster.CurrentHitPoints <= 0)
            {
                RaiseMessage("");
                RaiseMessage($"You defeated the {CurrentMonster.Name}!");

                CurrentPlayer.ExperiencePoints += CurrentMonster.RewardExperiencePoints;
                RaiseMessage($"You receive {CurrentMonster.RewardExperiencePoints} experience points.");

                CurrentPlayer.Gold += CurrentMonster.Gold;
                RaiseMessage($"You receive {CurrentMonster.Gold} gold.");

                foreach(GameItem gameItem in CurrentMonster.Inventory)
                {
                    CurrentPlayer.AddItemToInventory(gameItem);
                    RaiseMessage($"You receive one {gameItem.Name}.");
                }

                // Get another monster to fight
                GetMonsterAtLocation();
            }
            else
            {
                // If monster is still alive, let the monster attack
                int damageToPlayer = RandomNumberGenerator.NumberBetween(CurrentMonster.MinimumDamage, CurrentMonster.MaximumDamage);

                if (damageToPlayer == 0)
                {
                    RaiseMessage("The monster attacks, but misses you.");
                }
                else
                {
                    CurrentPlayer.CurrentHitPoints -= damageToPlayer;
                    RaiseMessage($"The {CurrentMonster.Name} hit you for {damageToPlayer} points.");
                }

                // If player is killed, move them back to their home.
                if (CurrentPlayer.CurrentHitPoints <= 0)
                {
                    RaiseMessage("");
                    RaiseMessage($"The {CurrentMonster.Name} killed you.");

                    CurrentLocation = CurrentWorld.LocationAt(0, -1); // Player's home
                    CurrentPlayer.CurrentHitPoints = CurrentPlayer.Level * 10; // Completely heal the player
                }
            }
        }

        private void RaiseMessage(string message)
        {
            OnMessageRaised?.Invoke(this, new GameMessageEventArgs(message));
        }
    }
}

 

Step 4: Modify Engine\Factories\MonsterFactory.cs

The MonsterFactory used to add ItemQuantity objects to the Monster’s Inventory. But now, Inventory holds GameItem objects. So, we need to change the MonsterFactory to create GameItems and add them to the Monster’s inventory.

Change line 48 to the line below. This will instantiate a GameItem object and add it to the Monster’s Inventory.

 

MonsterFactory.cs

using System;
using Engine.Models;

namespace Engine.Factories
{
    public static class MonsterFactory
    {
        public static Monster GetMonster(int monsterID)
        {
            switch(monsterID)
            {
                case 1:
                    Monster snake =
                        new Monster("Snake", "Snake.png", 4, 4, 1, 2, 5, 1);

                    AddLootItem(snake, 9001, 25);
                    AddLootItem(snake, 9002, 75);

                    return snake;

                case 2:
                    Monster rat =
                        new Monster("Rat", "Rat.png", 5, 5, 1, 2, 5, 1);

                    AddLootItem(rat, 9003, 25);
                    AddLootItem(rat, 9004, 75);

                    return rat;

                case 3:
                    Monster giantSpider =
                        new Monster("Giant Spider", "GiantSpider.png", 10, 10, 1, 4, 10, 3);

                    AddLootItem(giantSpider, 9005, 25);
                    AddLootItem(giantSpider, 9006, 75);

                    return giantSpider;

                default:
                    throw new ArgumentException(string.Format("MonsterType '{0}' does not exist", monsterID));
            }
        }

        private static void AddLootItem(Monster monster, int itemID, int percentage)
        {
            if(RandomNumberGenerator.NumberBetween(1, 100) <= percentage)
            {
                monster.AddItemToInventory(ItemFactory.CreateGameItem(itemID));
            }
        }
    }
}

 

Step 5: Modify WPFUI\MainWindow.xaml

We also need to fix the changed property names used in the UI.

In this screen, we used “HitPoints” on lines 53 and 153. Change those to “CurrentHitPoints”.

 

MainWindow.xaml

<Window x:Class="WPFUI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:viewModels="clr-namespace:Engine.ViewModels;assembly=Engine"
        d:DataContext="{d:DesignInstance viewModels:GameSession}"
        mc:Ignorable="d"
        FontSize="11pt"
        Title="Scott's Awesome Game" Height="768" Width="1024">

    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="225"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <!-- Menu -->
        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Menu" Background="AliceBlue"/>

        <!-- Player stats -->
        <Grid Grid.Row="1" Grid.Column="0" Background="Aquamarine">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>

            <Label Grid.Row="0" Grid.Column="0" Content="Name:"/>
            <Label Grid.Row="0" Grid.Column="1" Content="{Binding CurrentPlayer.Name}"/>
            <Label Grid.Row="1" Grid.Column="0" Content="Class:"/>
            <Label Grid.Row="1" Grid.Column="1" Content="{Binding CurrentPlayer.CharacterClass}"/>
            <Label Grid.Row="2" Grid.Column="0" Content="Hit points:"/>
            <Label Grid.Row="2" Grid.Column="1" Content="{Binding CurrentPlayer.CurrentHitPoints}"/>
            <Label Grid.Row="3" Grid.Column="0" Content="Gold:"/>
            <Label Grid.Row="3" Grid.Column="1" Content="{Binding CurrentPlayer.Gold}"/>
            <Label Grid.Row="4" Grid.Column="0" Content="XP:"/>
            <Label Grid.Row="4" Grid.Column="1" Content="{Binding CurrentPlayer.ExperiencePoints}"/>
            <Label Grid.Row="5" Grid.Column="0" Content="Level:"/>
            <Label Grid.Row="5" Grid.Column="1" Content="{Binding CurrentPlayer.Level}"/>
        </Grid>

        <!-- Gameplay -->
        <Grid Grid.Row="1" Grid.Column="1"
              Background="Beige">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="3*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>

            <!-- Game Messages -->
            <Border Grid.Row="0" Grid.Column="0"
                    Grid.RowSpan="2"
                    BorderBrush="Gainsboro"
                    BorderThickness="1">

                <RichTextBox x:Name="GameMessages"
                             Background="Beige"
                             VerticalScrollBarVisibility="Auto">
                    <RichTextBox.Resources>
                        <Style TargetType="{x:Type Paragraph}">
                            <Setter Property="Margin" Value="0"/>
                        </Style>
                    </RichTextBox.Resources>
                </RichTextBox>

            </Border>

            <!-- Location information -->
            <Border Grid.Row="0" Grid.Column="1"
                    BorderBrush="Gainsboro"
                    BorderThickness="1">

                <Grid Margin="3">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>

                    <TextBlock Grid.Row="0"
                               HorizontalAlignment="Center"
                               Text="{Binding CurrentLocation.Name}"/>

                    <Image Grid.Row="1"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Height="125"
                           Width="125"
                           Source="{Binding CurrentLocation.ImageName}"/>

                    <TextBlock Grid.Row="2"
                               HorizontalAlignment="Center"
                               Text="{Binding CurrentLocation.Description}"
                               TextWrapping="Wrap"/>
                </Grid>

            </Border>

            <!-- Monster information -->
            <Border Grid.Row="1" Grid.Column="1"
                    BorderBrush="Gainsboro"
                    BorderThickness="1">

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <TextBlock Grid.Row="0"
                               HorizontalAlignment="Center"
                               Height="Auto"
                               Text="{Binding CurrentMonster.Name}" />

                    <Image Grid.Row="1"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Height="125"
                           Width="125"
                           Source="{Binding CurrentMonster.ImageName}" />

                    <StackPanel Grid.Row="2"
                                Visibility="{Binding HasMonster, Converter={StaticResource BooleanToVisibility}}"
                                HorizontalAlignment="Center"
                                Orientation="Horizontal">
                        <TextBlock>Current Hit Points:</TextBlock>
                        <TextBlock Text="{Binding CurrentMonster.CurrentHitPoints}" />
                    </StackPanel>

                </Grid>

            </Border>

        </Grid>

        <!-- Inventory and Quests -->
        <Grid Grid.Row="2" Grid.Column="0"
              Background="BurlyWood">

            <TabControl>
                <TabItem Header="Inventory">
                    <DataGrid ItemsSource="{Binding CurrentPlayer.Inventory}"
                              AutoGenerateColumns="False"
                              HeadersVisibility="Column">
                        <DataGrid.Columns>
                            <DataGridTextColumn Header="Description"
                                                Binding="{Binding Name}"
                                                Width="*"/>
                            <DataGridTextColumn Header="Price"
                                                Binding="{Binding Price}"
                                                Width="Auto"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </TabItem>

                <TabItem Header="Quests">
                    <DataGrid ItemsSource="{Binding CurrentPlayer.Quests}"
                              AutoGenerateColumns="False"
                              HeadersVisibility="Column">
                        <DataGrid.Columns>
                            <DataGridTextColumn Header="Name"
                                                Binding="{Binding PlayerQuest.Name}"
                                                Width="*"/>
                            <DataGridTextColumn Header="Done?"
                                                Binding="{Binding IsCompleted}"
                                                Width="Auto"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </TabItem>
            </TabControl>
        </Grid>

        <!-- Action controls -->
        <Grid Grid.Row="2" Grid.Column="1"
              Background="Lavender">

            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="255" />
            </Grid.ColumnDefinitions>

            <!-- Combat Controls -->
            <Grid Grid.Row="0" Grid.Column="0"
                  Visibility="{Binding HasMonster, Converter={StaticResource BooleanToVisibility}}"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>

                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="10"/>
                    <ColumnDefinition Width="50"/>
                </Grid.ColumnDefinitions>

                <ComboBox Grid.Row="0" Grid.Column="0"
                          ItemsSource="{Binding CurrentPlayer.Weapons}"
                          SelectedItem="{Binding CurrentWeapon}"
                          DisplayMemberPath="Name"
                          SelectedValuePath="ID"/>

                <Button Grid.Row="0" Grid.Column="2"
                        Content="Use"
                        Click="OnClick_AttackMonster"/>
            </Grid>

            <!-- Movement Controls -->
            <Grid Grid.Row="0" Grid.Column="1">

                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="*" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>

                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Button Grid.Row="0" Grid.Column="1" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveNorth"
                        Visibility="{Binding HasLocationToNorth, Converter={StaticResource BooleanToVisibility}}"
                        Content="North"/>
                <Button Grid.Row="1" Grid.Column="0" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveWest"
                        Visibility="{Binding HasLocationToWest, Converter={StaticResource BooleanToVisibility}}"
                        Content="West"/>
                <Button Grid.Row="1" Grid.Column="1" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_DisplayTradeScreen"
                        Visibility="{Binding HasTrader, Converter={StaticResource BooleanToVisibility}}"
                        Content="Trade"/>
                <Button Grid.Row="1" Grid.Column="2" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveEast"
                        Visibility="{Binding HasLocationToEast, Converter={StaticResource BooleanToVisibility}}"
                        Content="East"/>
                <Button Grid.Row="2" Grid.Column="1" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveSouth"
                        Visibility="{Binding HasLocationToSouth, Converter={StaticResource BooleanToVisibility}}"
                        Content="South"/>

            </Grid>

        </Grid>

    </Grid>
</Window>

 

Step 6: Test the game

As always, after making changes, run the program and see if it works.

When refactoring, you aren’t adding new features or fixing bugs. You’re only making the code a little cleaner, and easier to work with. So, the program should run exactly the same as it did before.

It’s best to not combine refactoring with other changes (bug fixes or feature changes). If you do, and the program doesn’t work, it’s often difficult to know what caused the problem.

So, when you refactor, only refactor. When you are done testing the refactoring, check in your code to your source control. Then, do your bug fixes or new features. That way, if there is a problem, you can revert your source code back to the working (post-refactoring) code and retry your changes.

 

 

Return to main page

Leave a Reply

Your email address will not be published. Required fields are marked *