Lesson 09.1: Creating Traders

Now, we’ll add traders to the game, to let the player buy and sell items.

 

 

Lesson Steps

 

Step 1: Create Engine\Models\Trader.cs

First, we’ll create the Trader class. The Trader will have two properties: Name and Inventory (a List of GameItem objects).

We’ll add two functions: AddItemToInventory and RemoveItemFromInventory, which will be needed for the trader to “buy” and “sell” items.

 

Trader.cs

using System.Collections.ObjectModel;

namespace Engine.Models
{
    public class Trader : BaseNotificationClass
    {
        public string Name { get; set; }

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

        public Trader(string name)
        {
            Name = name;
            Inventory = new ObservableCollection<GameItem>();
        }

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

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

 

Step 2: Create Engine\Factories\TraderFactory.cs

Next, we’ll add our factory class, to instantiate Trader objects.

The constructor will create three Trader objects, and add them to the _traders list. The AddTraderToList function will prevent adding a duplicate trader.

 

TraderFactory.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Engine.Models;

namespace Engine.Factories
{
    public static class TraderFactory
    {
        private static readonly List<Trader> _traders = new List<Trader>();

        static TraderFactory()
        {
            Trader susan = new Trader("Susan");
            susan.AddItemToInventory(ItemFactory.CreateGameItem(1001));

            Trader farmerTed = new Trader("Farmer Ted");
            farmerTed.AddItemToInventory(ItemFactory.CreateGameItem(1001));

            Trader peteTheHerbalist = new Trader("Pete the Herbalist");
            peteTheHerbalist.AddItemToInventory(ItemFactory.CreateGameItem(1001));

            AddTraderToList(susan);
            AddTraderToList(farmerTed);
            AddTraderToList(peteTheHerbalist);
        }

        public static Trader GetTraderByName(string name)
        {
            return _traders.FirstOrDefault(t => t.Name == name);
        }

        private static void AddTraderToList(Trader trader)
        {
            if(_traders.Any(t => t.Name == trader.Name))
            {
               throw new ArgumentException($"There is already a trader named '{trader.Name}'"); 
            }

            _traders.Add(trader);
        }
    }
}

 

Step 3: Modify Engine\Models\Location.cs

The traders will “live” at a location, so we need to add a “TraderHere” property to the Location class. This is on line 19, in the code below.

 

Location.cs

using System.Collections.Generic;
using System.Linq;
using Engine.Factories;

namespace Engine.Models
{
    public class Location
    {
        public int XCoordinate { get; set; }
        public int YCoordinate { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string ImageName { get; set; }
        public List<Quest> QuestsAvailableHere { get; set; } = new List<Quest>();

        public List<MonsterEncounter> MonstersHere { get; set; } =
            new List<MonsterEncounter>();

        public Trader TraderHere { get; set; }

        public void AddMonster(int monsterID, int chanceOfEncountering)
        {
            if(MonstersHere.Exists(m => m.MonsterID == monsterID))
            {
                // This monster has already been added to this location.
                // So, overwrite the ChanceOfEncountering with the new number.
                MonstersHere.First(m => m.MonsterID == monsterID)
                            .ChanceOfEncountering = chanceOfEncountering;
            }
            else
            {
                // This monster is not already at this location, so add it.
                MonstersHere.Add(new MonsterEncounter(monsterID, chanceOfEncountering));
            }
        }

        public Monster GetMonster()
        {
            if(!MonstersHere.Any())
            {
                return null;
            }

            // Total the percentages of all monsters at this location.
            int totalChances = MonstersHere.Sum(m => m.ChanceOfEncountering);

            // Select a random number between 1 and the total (in case the total chances is not 100).
            int randomNumber = RandomNumberGenerator.NumberBetween(1, totalChances);

            // Loop through the monster list, 
            // adding the monster's percentage chance of appearing to the runningTotal variable.
            // When the random number is lower than the runningTotal,
            // that is the monster to return.
            int runningTotal = 0;

            foreach(MonsterEncounter monsterEncounter in MonstersHere)
            {
                runningTotal += monsterEncounter.ChanceOfEncountering;

                if(randomNumber <= runningTotal)
                {
                    return MonsterFactory.GetMonster(monsterEncounter.MonsterID);
                }
            }

            // If there was a problem, return the last monster in the list.
            return MonsterFactory.GetMonster(MonstersHere.Last().MonsterID);
        }
    }
}

 

Step 4: Modify Engine\Factories\WorldFactory.cs

The location’s trader (if it has one) will be populated in the WorldFactory. Do this inside the CreateWorld function, with the other Location creation/population code.

There are three traders. The code for adding them is on lines 20-21, 30-31, and 50-51.

 

WorldFactory.cs

using Engine.Models;

namespace Engine.Factories
{
    internal static class WorldFactory
    {
        internal static World CreateWorld()
        {
            World newWorld = new World();

            newWorld.AddLocation(-2, -1, "Farmer's Field", 
                "There are rows of corn growing here, with giant rats hiding between them.", 
                "FarmFields.png");

            newWorld.LocationAt(-2, -1).AddMonster(2, 100);

            newWorld.AddLocation(-1, -1, "Farmer's House",
                "This is the house of your neighbor, Farmer Ted.",
                "Farmhouse.png");
            newWorld.LocationAt(-1, -1).TraderHere =
                TraderFactory.GetTraderByName("Farmer Ted");

            newWorld.AddLocation(0, -1, "Home", 
                "This is your home", 
                "Home.png");

            newWorld.AddLocation(-1, 0, "Trading Shop",
                "The shop of Susan, the trader.",
                "Trader.png");
            newWorld.LocationAt(-1, 0).TraderHere = 
                TraderFactory.GetTraderByName("Susan");

            newWorld.AddLocation(0, 0, "Town square",
                "You see a fountain here.",
                "TownSquare.png");

            newWorld.AddLocation(1, 0, "Town Gate",
                "There is a gate here, protecting the town from giant spiders.",
                "TownGate.png");

            newWorld.AddLocation(2, 0, "Spider Forest",
                "The trees in this forest are covered with spider webs.",
                "SpiderForest.png");

            newWorld.LocationAt(2, 0).AddMonster(3, 100);

            newWorld.AddLocation(0, 1, "Herbalist's hut",
                "You see a small hut, with plants drying from the roof.",
                "HerbalistsHut.png");
            newWorld.LocationAt(0, 1).TraderHere =
                TraderFactory.GetTraderByName("Pete the Herbalist");

            newWorld.LocationAt(0, 1).QuestsAvailableHere.Add(QuestFactory.GetQuestByID(1));

            newWorld.AddLocation(0, 2, "Herbalist's garden",
                "There are many plants here, with snakes hiding behind them.",
                "HerbalistsGarden.png");

            newWorld.LocationAt(0, 2).AddMonster(1, 100);

            return newWorld;
        }
    }
}

 

Step 5: Modify Engine\ViewModels\GameSession.cs

We’ll update the GameSession ViewModel class to let the UI know if there is a trader at the player’s current location.

Add a new property CurrentTrader (lines 61-71) and its backing variable “_currentTrader” (line 17).

CurrentTrader will be set when the player moves to a new location. Do this inside the CurrentLocation setter (line 39).

Create a Boolean property “HasTrader” (line 89), to use for the UI to decide whether or not to display the “Trade” button – based on whether or not there is a CurrentTrader.

 

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",
                                HitPoints = 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.HitPoints -= damageToMonster;
                RaiseMessage($"You hit the {CurrentMonster.Name} for {damageToMonster} points.");
            }

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

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

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

                foreach(ItemQuantity itemQuantity in CurrentMonster.Inventory)
                {
                    GameItem item = ItemFactory.CreateGameItem(itemQuantity.ItemID);
                    CurrentPlayer.AddItemToInventory(item);
                    RaiseMessage($"You receive {itemQuantity.Quantity} {item.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.HitPoints -= damageToPlayer;
                    RaiseMessage($"The {CurrentMonster.Name} hit you for {damageToPlayer} points.");
                }

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

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

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

 

Step 6: Modify WPFUI\MainWindow.xaml

Finally, add a “Trade” button to the UI (lines 264-267). Its Visibility will be based on the Boolean HasTrader property in GameSession.cs.

 

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.HitPoints}"/>
            <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.HitPoints}" />
                    </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" 
                        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 7: Test the game

Run the game and move to the locations that should have a trader (the Farmer’s House, the Trading Shop, and the Herbalist’s Hut). You should see the “Trade” button at those locations, and not see it at the other locations.

 

Return to main page

8 thoughts on “Lesson 09.1: Creating Traders

  1. I think anyone who’s followed along this far would catch it anyway, but you might want to add an annotation or something to the video to remind people to add “CurrentTrader = CurrentLocation.TraderHere;” to the CurrentLocation setter in the GameSession class since it seems you didn’t mention it or show it off in your explanation. It’s clearly mentioned in the text and is an obvious thing that needs to happen, but might save people a little effort when they follow your video’s instructions and find it still doesn’t work 🙂

    1. Thanks for mentioning that Danielle. Unfortunately, it looks like YouTube removed the ability to add annotations last June. I’ll see if there is any way to correct that – without making a corrected video.

  2. When I first implemented the change in Gamesession, “CurrentTrader = CurrentLocation.TraderHere;” I accidentally did “_currentTrader = CurrentLocation.TraderHere;”. As a result I was unable to see the trade button at the different trade locations. I eventually was able to spot the difference between my code and yours, but I am having a hard time see why CurrentLocation.TraderHere wasn’t able to set the _currentTrader variable directly? I stepped through using the debugging and the _currentTrader variable does get set to a trader at a given location, e.g. susan at the trader shop, pete at the herb hut. Yet, I was unable to see the trade button in the UI until I made the correction I mentioned previously. Any thoughts? FYI, Added the link to my github repo of the commit that had the issue in the website dialog. Thanks!

    1. Hi Robert,

      If I’m reading the code differentials correctly (I’m not a GitHub expert), I think this is the source of the problem.

      Directly setting the “_currentTrader” backing variable, instead of going through CurrentTrader’s “get”, means “OnPropertyChanged(nameof(HasTrader));” is never run (line 70 of GameSession.cs). The button’s Visibility is bound to the HasTrader property in the XAML. And, since it never receives a properychanged notification, it never updates the Visbility.

      Let me know if that isn’t clear, or if I’m misunderstanding something.

  3. Hi Scott.
    Thanks for great lecture.
    I have a question.
    In player class, player() constructor does not have any parameter in ().
    But in trader class, the constructor has name parameter in it.
    Trader(string name)

    Why are they different?

    1. You’re welcome,

      The is no real reason why I made those different. When you instantiate an object, you can set its properties by passing values in the constructor and setting the properties inside the constructor function. Or, you can set the properties by calling their “set” (if the setters are not private). I generally prefer to pass in the initial properties as parameters, and setting them inside the constructor. But, I probably didn’t do that with the Player class because it was one of the first classes and I didn’t want to explain parameters in the early lessons.

  4. Hello! Just wanted to leave a quick note of an issue I ran into and resolved. On line 152 it has “&&” and when I tried to compile I got an error that said “the name ‘amp’ does not exist in the current context.” I ended up just replacing it with “&&” and it worked.

Leave a Reply

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