Initial codes of Lab 8

Page 1 of 17
Lab week 8 Implementing the Command Pattern and NPCs
Codebase
This lab continues from last week. You need to have completed lab 7 to continue with this labsheet and you
are expected to use your own code from last week to work on this week’s tasks.
Assignment 2 Hand up
You need to submit the following from today’s lab sheet for assignment 2:
Completed codebase zipped up as lab8.
Page 2 of 17
Implementing the Command Pattern
The DungeonMaster class is already looking very messy (and is only going to get worse as we continue to
add commands to the game). If we consider the current sequence diagram for processing a turn:
We can see that already I have had to simplify the processCommand logic (actually what is really going on is
more complicated), and we have already started to break things up with dedicated methods within
DungeonMaster to help deal with the logic (like processMove). This is only going to get worse over time.
One way we can clean this up is to introduce dedicated classes to handle each command, lucky for us this
problem has been tackled before and there is a dedicated command pattern which comes to our rescue!
sd Process Turn
:DungeonMaster
«interface»
:IMazeClient
:Parser
:ParsedInput
processMove(ParsedInput)
GetCommand() :String
Parse(String) :ParsedInput
Command() :string
[command not recognised]:PlayerMessage(String)
[command recognised]:processCommand()
Page 3 of 17
Let’s start by specifying an abstract class to represent commands in general. Create a new class called
Command in the Control package as follows:
using Mazegame.Entity;
namespace Mazegame.Control
{
public abstract class Command
{
public abstract CommandResponse Execute(ParsedInput userInput,
Player thePlayer);
}
}
Each command will take the parsed input and a reference to the player and return some kind of response. In
our case looking at the first 2 commands we have implemented (quit and move) the response is either some
kind of flag as to whether we need to quit the game, or some kind of message to display to the user. So let’s
specify our CommandResponse class:
namespace Mazegame.Control
{
public class CommandResponse
{
private bool finishedGame;
private string message;
public CommandResponse(string message)
{
Message = message;
FinishedGame = false;
}
public CommandResponse(string message, bool quitFlag)
{
Message = message;
FinishedGame = quitFlag;
}
public bool FinishedGame
{
get { return finishedGame; }
set { finishedGame = value; }
}
public string Message
{
get { return message; }
set { message = value; }
}
}
}
Page 4 of 17
We are now in a position to write a command class for move and another for quit.
namespace Mazegame.Control
{
public class QuitCommand : Command
{
public override CommandResponse Execute(ParsedInput userInput, Player thePlayer)
{
return new CommandResponse("Thanks for playing -- Goodbye",true);
}
}
}
That’s it for Quit (about the easiest command you could imagine!). Now let’s create a class for Move:
namespace Mazegame.Control
{
public class MoveCommand : Command
{
public override CommandResponse Execute(ParsedInput userInput, Player thePlayer)
{
String exitLabel = (String) userInput.Arguments[0];
Exit desiredExit = thePlayer.CurrentLocation.GetExit(exitLabel);
if (desiredExit == null)
{
return new CommandResponse("There is no exit there.. Trying moving someplace
moveable!!");
}
thePlayer.CurrentLocation = desiredExit.Destination;
return new CommandResponse("You find yourself looking at " +
thePlayer.CurrentLocation.Description);
}
}
}
So now we have defined our command classes, but we haven’t implemented them yet.
Page 5 of 17
Before we change DungeonMaster let’s introduce one more class to keep track of the available commands.
Create a new Control class called CommandHandler as follows:
namespace Mazegame.Control
{
public class CommandHandler
{
private Hashtable availableCommands;
private Parser theParser;
public CommandHandler()
{
availableCommands = new Hashtable();
SetupCommands();
theParser = new Parser(new ArrayList(availableCommands.Keys));
}
private void SetupCommands()
{
availableCommands.Add("go", new MoveCommand());
availableCommands.Add("quit", new QuitCommand());
availableCommands.Add("move", new MoveCommand());
}
public CommandResponse ProcessTurn(String userInput, Player thePlayer)
{
ParsedInput validInput = theParser.Parse(userInput);
try
{
Command theCommand = (Command) availableCommands[validInput.Command];
return theCommand.Execute(validInput, thePlayer);
}
catch (KeyNotFoundException)
{
return new CommandResponse("Not a valid command");
}
}
}
}
So here we maintain a Hashtable for each command class, indexed by a label. The beauty of this is:
1. We can set up aliases for different commands (such as move and go pointing to the same
command).
2. When we develop new command classes we simple define the class and add it to the hashtable with
a label and it works.
OK now we have our CommandHandler it is time to change DungeonMaster:
Page 6 of 17
namespace Mazegame.Control
{
public class DungeonMaster
{
private IMazeClient gameClient;
private IMazeData gameData;
private Player thePlayer;
private CommandHandler playerTurnHandler;
public DungeonMaster(IMazeData gameData, IMazeClient gameClient)
{
this.gameData = gameData;
this.gameClient = gameClient;
playerTurnHandler = new CommandHandler();
}
public void PrintWelcome()
{
gameClient.PlayerMessage(gameData.GetWelcomeMessage());
}
public void SetupPlayer()
{
String playerName = gameClient.GetReply("What name do you choose to be known by?");
thePlayer = new Player(playerName);
thePlayer.CurrentLocation = gameData.GetStartingLocation();
gameClient.PlayerMessage("Welcome " + playerName + "\n\n");
gameClient.PlayerMessage("You find yourself looking at ");
gameClient.PlayerMessage(thePlayer.CurrentLocation.Description);
}
public void RunGame()
{
PrintWelcome();
SetupPlayer();
while (handlePlayerTurn())
{
// handle npc logic later here
}
gameClient.GetReply("\n\n<<Hit enter to exit>>");
}
private bool handlePlayerTurn()
{
CommandResponse playerResponse = playerTurnHandler.ProcessTurn(gameClient.GetCommand(),
thePlayer);
gameClient.PlayerMessage(playerResponse.Message);
return !playerResponse.FinishedGame;
}
} //end DungeonMaster
} //end namespace Control
Page 7 of 17
Our code is now cleaner and we have a flexible, extensible structure to continue adding commands. The
revised process player turn sequence diagram looks like:
Before moving on with anything else, lets write a unit test for both of the move and quit commands. Start by
creating a new Unit Test class called QuitCommandTest and enter the following:
using System;
using System.Collections;
using Mazegame.Control;
using Mazegame.Entity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MazegameTest
{
[TestClass]
public class QuitCommandTest
{
private ParsedInput playerInput;
private Player thePlayer;
private CommandHandler handler;
private QuitCommand quit;
[TestInitialize]
public void Init()
{
playerInput = new ParsedInput("quit", new ArrayList());
thePlayer = new Player("greg");
handler = new CommandHandler();
quit = new QuitCommand();
:DungeonMaster
«interface»
:IMazeClient
:CommandHand...
:CommandResponse
Page 8 of 17
}
[TestMethod]
public void TestQuit()
{
// test quit command no arguments
CommandResponse response = quit.Execute(playerInput, thePlayer);
Assert.IsTrue(response.FinishedGame);
Assert.IsTrue(response.Message.Contains("Goodbye"));
// test quit command >0 arguments
playerInput.Arguments = new ArrayList(new string[] {"this", "game"});
response = quit.Execute(playerInput, thePlayer);
Assert.IsTrue(response.FinishedGame);
Assert.IsTrue(response.Message.Contains("Goodbye"));
}
[TestMethod]
public void TestQuitHandler()
{
// test quit command no arguments
CommandResponse response = handler.ProcessTurn("quit", thePlayer);
Assert.IsTrue(response.FinishedGame);
Assert.IsTrue(response.Message.Contains("Goodbye"));
// test quit command >0 arguments
response = handler.ProcessTurn("quit this game", thePlayer);
Assert.IsTrue(response.FinishedGame);
Assert.IsTrue(response.Message.Contains("Goodbye"));
}
}
}
Run the test to make sure it works. Now create a Unit Test class called MoveCommandTest and enter the
following:
using System;
using System.Collections;
using Mazegame.Control;
using Mazegame.Entity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MazegameTest
{
[TestClass]
public class MoveCommandTest
{
private ParsedInput playerInput;
private Player thePlayer;
private CommandHandler handler;
private MoveCommand move;
private Location t127;
private Location gregsoffice;
[TestInitialize]
public void Init()
{
playerInput = new ParsedInput("move", new ArrayList());
thePlayer = new Player("greg");
Page 9 of 17
t127 = new Location("a lecture theatre", "T127");
gregsoffice = new Location("a spinning vortex of terror", "Greg's Office");
t127.AddExit("south", new Exit("you see a mound of paper to the south", gregsoffice));
gregsoffice.AddExit("north", new Exit("you see a bleak place to the north", t127));
thePlayer.CurrentLocation = t127;
handler = new CommandHandler();
move = new MoveCommand();
}
[TestMethod]
public void TestMoveNowhere()
{
Assert.AreSame(t127, thePlayer.CurrentLocation);
// test move command no arguments
CommandResponse response = move.Execute(playerInput, thePlayer);
Assert.IsFalse(response.FinishedGame);
Assert.IsTrue(response.Message.Contains("no exit there"));
Assert.AreSame(t127, thePlayer.CurrentLocation);
}
[TestMethod]
public void TestMoveNoExit()
{
playerInput.Arguments = new ArrayList(new string[] { "west" });
CommandResponse response = move.Execute(playerInput, thePlayer);
Assert.IsFalse(response.FinishedGame);
Assert.IsTrue(response.Message.Contains("no exit there"));
Assert.AreSame(t127, thePlayer.CurrentLocation);
}
[TestMethod]
public void TestTakeExit()
{
playerInput.Arguments = new ArrayList(new string[] { "south" });
CommandResponse response = move.Execute(playerInput, thePlayer);
Assert.IsFalse(response.FinishedGame);
Assert.IsTrue(response.Message.Contains(gregsoffice.Description));
Assert.AreSame(gregsoffice, thePlayer.CurrentLocation);
}
[TestMethod]
public void TestMoveHandler()
{
// test move command no arguments
CommandResponse response = handler.ProcessTurn("move to the south", thePlayer);
Assert.IsFalse(response.FinishedGame);
Assert.IsTrue(response.Message.Contains(gregsoffice.Description));
Assert.AreSame(gregsoffice, thePlayer.CurrentLocation);
}
}
}
Build your solution and try to run your test. You will see that the TestMoveNowhere test fails. This problem
occurs because we have invoked the move command with no arguments and our arguments ArrayList in
ParsedInput is empty.
We can fix this easily with a bit of defensive coding in MoveCommand as follows:
namespace Mazegame.Control
Page 10 of 17
{
public class MoveCommand : Command
{
public override CommandResponse Execute(ParsedInput userInput, Player thePlayer)
{
if (userInput.Arguments.Count == 0)
{
return new CommandResponse("If you want to move you need to tell me where");
}
String exitLabel = (String) userInput.Arguments[0];
Exit desiredExit = thePlayer.CurrentLocation.GetExit(exitLabel);
if (desiredExit == null)
{
return new CommandResponse("There is no exit there.. Trying moving someplace
moveable!!");
}
thePlayer.CurrentLocation = desiredExit.Destination;
return new CommandResponse("You find yourself looking at " +
thePlayer.CurrentLocation.Description);
}
}
}
Now modify the test method accordingly:
[TestMethod]
public void TestMoveNowhere()
{
Assert.AreSame(t127, thePlayer.CurrentLocation);
// test move command no arguments
CommandResponse response = move.Execute(playerInput, thePlayer);
Assert.IsFalse(response.FinishedGame);
Assert.IsTrue(response.Message.Contains("tell me where"));
Assert.AreSame(t127, thePlayer.CurrentLocation);
}
You should be able to run the test without incident.
Page 11 of 17
Now our tests are running give your game a little play test, to verify it is behaving how you would like:
I’m still not quit happy with the information I get from the game when I move from one location to another. So
I am going to change how the results are presented. Let’s start by developing a ToString method for
Location.
using System;
using System.Collections;
using System.Text;
namespace Mazegame.Entity
{
public class Location
{
private Hashtable exits;
private String description;
private String label;
public Location()
{
}
public Location(String description, String label)
{
Description = description;
Label = label;
exits = new Hashtable();
}
public Boolean AddExit(String exitLabel, Exit theExit)
{
if (exits.ContainsKey(exitLabel))
return false;
exits.Add(exitLabel, theExit);
return true;
}
public Exit GetExit(String exitLabel)
{
return (Exit) exits[exitLabel];
}
Page 12 of 17
public String Description
{
get { return description; }
set { description = value; }
}
public String Label
{
get { return label; }
set { label = value; }
}
public String AvailableExits()
{
StringBuilder returnMsg = new StringBuilder();
foreach (string label in this.exits.Keys)
{
returnMsg.Append("[" + label + "] ");
}
return returnMsg.ToString();
}
public override string ToString()
{
return "**********\n" + this.Label + "\n**********\n"
+ "Exits found :: " + AvailableExits() + "\n**********\n"
+ this.Description + "\n**********\n";
}
}
Now I can change my MoveCommand class accordingly:
namespace Mazegame.Control
{
public class MoveCommand : Command
{
public override CommandResponse Execute(ParsedInput userInput, Player thePlayer)
{
if (userInput.Arguments.Count == 0)
{
return new CommandResponse("If you want to move you need to tell me where");
}
String exitLabel = (String) userInput.Arguments[0];
Exit desiredExit = thePlayer.CurrentLocation.GetExit(exitLabel);
if (desiredExit == null)
{
return new CommandResponse("There is no exit there.. Trying moving someplace
moveable!!");
}
thePlayer.CurrentLocation = desiredExit.Destination;
return new CommandResponse("You successfully move " + exitLabel + " and find yourself
somewhere else\n\n" + thePlayer.CurrentLocation.ToString());
}
}
}
Page 13 of 17
So the results now look as follows:
Rerunning the tests reveals nothing is broken, so now we can move on to developing the next user story.
Look Command
Before we implement the look command we should specify how we think it should behave by specifying its
user story(s). Our initial RAD identified 3 “look” stories
Look Item
Look Character
Look Location
I can think of a third as well “look exit”. As we haven’t introduced non-playing characters or items yet let’s
focus on location and exit for now:
Look Location the player issues the look command without arguments and the system returns a
description of the current player location.
Look Exit the player issues the look command with an argument and if the argument matches an
exit a description of the exit is returned.
Page 14 of 17
Both of these user stories are related and can be completed at once by developing the LookCommand:
namespace Mazegame.Control
{
public class LookCommand : Command
{
private CommandResponse response;
public override CommandResponse Execute(ParsedInput userInput, Player thePlayer)
{
response = new CommandResponse("Can't find that to look at here!");
if (userInput.Arguments.Count == 0)
{
response.Message = thePlayer.CurrentLocation.ToString();
return response;
}
foreach (string argument in userInput.Arguments)
{
if (thePlayer.CurrentLocation.ContainsExit(argument))
{
Exit theExit = thePlayer.CurrentLocation.GetExit(argument);
return new CommandResponse(theExit.Description);
}
}
return response;
}
}
}
To get this to work I need to introduce a new method in Location called ContainsExit:
public bool ContainsExit(String exitLabel)
{
return exits.Contains(exitLabel);
}
And now I need to add the LookCommand to my CommandHandler:
private void SetupCommands()
{
availableCommands.Add("go", new MoveCommand());
availableCommands.Add("quit", new QuitCommand());
availableCommands.Add("move", new MoveCommand());
availableCommands.Add("look", new LookCommand());
}
Compile this and give it a try!
Page 15 of 17
Now we have play tested this we need to finish by writing a unit test for the LookCommand:
using System;
using System.Collections;
using Mazegame.Control;
using Mazegame.Entity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MazegameTest
{
[TestClass]
public class LookCommandTest
{
private ParsedInput playerInput;
private Player thePlayer;
private CommandHandler handler;
private LookCommand look;
private Exit southExit;
private Location t127;
[TestInitialize]
public void Init()
{
playerInput = new ParsedInput("look", new ArrayList());
thePlayer = new Player("greg");
t127 = new Location("a lecture theatre", "T127");
Location gregsoffice = new Location("a spinning vortex of terror", "Greg's Office");
southExit = new Exit("you see a mound of paper to the south", gregsoffice);
t127.AddExit("south", southExit );
thePlayer.CurrentLocation = t127;
handler = new CommandHandler();
look = new LookCommand();
}
[TestMethod]
public void TestLookNowhere()
{
Assert.AreSame(t127, thePlayer.CurrentLocation);
// test move command no arguments
CommandResponse response = look.Execute(playerInput, thePlayer);
Assert.IsFalse(response.FinishedGame);
Assert.IsTrue(response.Message.Contains(t127.Description));
Page 16 of 17
}
[TestMethod]
public void TestLookNoMatch()
{
playerInput.Arguments = new ArrayList(new string[] {"bunyip"});
CommandResponse response = look.Execute(playerInput, thePlayer);
Assert.IsFalse(response.FinishedGame);
Assert.IsTrue(response.Message.Contains("Can't find that"));
}
[TestMethod]
public void TestLookExit()
{
playerInput.Arguments = new ArrayList(new string[] {"south"});
CommandResponse response = look.Execute(playerInput, thePlayer);
Assert.IsFalse(response.FinishedGame);
Assert.IsTrue(response.Message.Contains(southExit.Description));
}
[TestMethod]
public void TestMoveHandler()
{
// test move command no arguments
CommandResponse response = handler.ProcessTurn("look to the south", thePlayer);
Assert.IsFalse(response.FinishedGame);
Assert.IsTrue(response.Message.Contains(southExit.Description));
}
}
}
Sequence Command
The look command is illustrated below as a sequence diagram. Not the use of “fragment” boxes to illustrate
condition messages.
Page 17 of 17
sd Look Command
:CommandHand...
:LookCommand
:ParsedInput
:Player
:Location
:Exit
:CommandResponse
alt
[command arguments = 0]
[command arguments > 0]
alt
[exit found]
Execute(ParsedInput, Player) :CommandResponse
create()
CurrentLocation() :Location
ToString() :location description
Message(location description)
Arguments() :list of arguments
[for each argument]:ContainsExit(argument) :bool
GetExit(String) :Exit
Description() :string
Message(exit description)

Place new order. It's free, fast and safe

-+
550 words

Our customers say

Customer Avatar
Jeff Curtis
USA, Student

"I'm fully satisfied with the essay I've just received. When I read it, I felt like it was exactly what I wanted to say, but couldn’t find the necessary words. Thank you!"

Customer Avatar
Ian McGregor
UK, Student

"I don’t know what I would do without your assistance! With your help, I met my deadline just in time and the work was very professional. I will be back in several days with another assignment!"

Customer Avatar
Shannon Williams
Canada, Student

"It was the perfect experience! I enjoyed working with my writer, he delivered my work on time and followed all the guidelines about the referencing and contents."

  • 5-paragraph Essay
  • Admission Essay
  • Annotated Bibliography
  • Argumentative Essay
  • Article Review
  • Assignment
  • Biography
  • Book/Movie Review
  • Business Plan
  • Case Study
  • Cause and Effect Essay
  • Classification Essay
  • Comparison Essay
  • Coursework
  • Creative Writing
  • Critical Thinking/Review
  • Deductive Essay
  • Definition Essay
  • Essay (Any Type)
  • Exploratory Essay
  • Expository Essay
  • Informal Essay
  • Literature Essay
  • Multiple Choice Question
  • Narrative Essay
  • Personal Essay
  • Persuasive Essay
  • Powerpoint Presentation
  • Reflective Writing
  • Research Essay
  • Response Essay
  • Scholarship Essay
  • Term Paper
We use cookies to provide you with the best possible experience. By using this website you are accepting the use of cookies mentioned in our Privacy Policy.