patterncsharpMinor
Implementation of a Location object, to be used in building a text-based adventure game
Viewed 0 times
usedtextadventurebuildinggamebasedimplementationobjectlocation
Problem
Based on responses to my question posted here, I've built a full implementation of a Location object. Such an object can be used to build a map for a text-based adventure game.
Feel free to review the code, leave some comments about the good and the bad of what you see.
Things to notice
Objects of this class can only be constructed via its static factory method.
This class is immutable, yet has circular references to other objects of the same type (causing a 'chicken or the egg' conundrum). The circular references are made possible via lazy evaluation: the factory method accepts a Func{Location} delegate to map those references.
Each circular reference is hidden behind a public property (North, South, East, West). Each call to the delegate is tucked within a try-catch block, just in case each delegate contains an error condition, so that a meaningful exception message can be thrown.
```
using System;
using System.Diagnostics.Contracts;
///
/// Represents a location within the game. Instances of this class are immutable.
///
public sealed class Location
{
///
/// The description of this location.
///
private readonly string description;
///
/// The delegate that returns the location that is to the east of this location. -or- The delegate that returns null if there is no location to the
/// east.
///
private readonly Func east;
///
/// The name of this location.
///
private readonly string name;
///
/// The description of the location that is displayed to the game player.
///
private readonly string narration;
///
/// The delegate that returns the location that is to the north of this location. -or- The delegate that returns null if there is no location to the
/// north.
///
private readonly Func north;
///
/// The delegate that returns the location that is to the south of this location. -or- The delegate that returns null if there is no location to
Feel free to review the code, leave some comments about the good and the bad of what you see.
Things to notice
Objects of this class can only be constructed via its static factory method.
This class is immutable, yet has circular references to other objects of the same type (causing a 'chicken or the egg' conundrum). The circular references are made possible via lazy evaluation: the factory method accepts a Func{Location} delegate to map those references.
Each circular reference is hidden behind a public property (North, South, East, West). Each call to the delegate is tucked within a try-catch block, just in case each delegate contains an error condition, so that a meaningful exception message can be thrown.
```
using System;
using System.Diagnostics.Contracts;
///
/// Represents a location within the game. Instances of this class are immutable.
///
public sealed class Location
{
///
/// The description of this location.
///
private readonly string description;
///
/// The delegate that returns the location that is to the east of this location. -or- The delegate that returns null if there is no location to the
/// east.
///
private readonly Func east;
///
/// The name of this location.
///
private readonly string name;
///
/// The description of the location that is displayed to the game player.
///
private readonly string narration;
///
/// The delegate that returns the location that is to the north of this location. -or- The delegate that returns null if there is no location to the
/// north.
///
private readonly Func north;
///
/// The delegate that returns the location that is to the south of this location. -or- The delegate that returns null if there is no location to
Solution
The circular references are made possible via lazy evaluation
The evaluation is not really lazy yet.
To make it truly lazy you need to use the
Use lazy initialization to defer the creation of a large or resource-intensive object, or the execution of a resource-intensive task, particularly when such creation or execution might not occur during the lifetime of the program.
This will give you a much better performance for large maps because it will create the nested objects only once unlike in your current solution where it creates each object everytime you call any of the factory
If you have any loops that traverse the map it will be created over and over again.
Here's shortened example:
You would use it the same way you do now:
Produces:
Other issues have already been mentioned by @jsuth.
The evaluation is not really lazy yet.
To make it truly lazy you need to use the
Lazy Class.Use lazy initialization to defer the creation of a large or resource-intensive object, or the execution of a resource-intensive task, particularly when such creation or execution might not occur during the lifetime of the program.
This will give you a much better performance for large maps because it will create the nested objects only once unlike in your current solution where it creates each object everytime you call any of the factory
Funcs via the East/West etc. properties.If you have any loops that traverse the map it will be created over and over again.
Here's shortened example:
public sealed class Location
{
private readonly Lazy _lazyEast;
private readonly Lazy _lazyWest;
private Location(string name, Func eastFactory, Func westFactory)
{
Name = name;
_lazyEast = eastFactory == null ? null : new Lazy(eastFactory);
_lazyWest = westFactory == null ? null : new Lazy(westFactory);
}
public string Name { get; }
public Location East => _lazyEast?.Value;
public Location West => _lazyWest?.Value;
public static Location Create(string name, Func eastFactory = null, Func westFactory = null)
{
return new Location(name, eastFactory, westFactory);
}
public override string ToString() => Name;
}You would use it the same way you do now:
Location kitchen = null;
Location library = null;
Location office = null;
kitchen = Location.Create(
name: "The kitchen",
eastFactory: () => library
);
library = Location.Create(
name: "The old library",
eastFactory: () => office,
westFactory: () => kitchen
);
office = Location.Create(
name: "The office",
westFactory: () => library
);Produces:
kitchen.Dump();
library.Dump();
office.Dump();Other issues have already been mentioned by @jsuth.
Code Snippets
public sealed class Location
{
private readonly Lazy<Location> _lazyEast;
private readonly Lazy<Location> _lazyWest;
private Location(string name, Func<Location> eastFactory, Func<Location> westFactory)
{
Name = name;
_lazyEast = eastFactory == null ? null : new Lazy<Location>(eastFactory);
_lazyWest = westFactory == null ? null : new Lazy<Location>(westFactory);
}
public string Name { get; }
public Location East => _lazyEast?.Value;
public Location West => _lazyWest?.Value;
public static Location Create(string name, Func<Location> eastFactory = null, Func<Location> westFactory = null)
{
return new Location(name, eastFactory, westFactory);
}
public override string ToString() => Name;
}Location kitchen = null;
Location library = null;
Location office = null;
kitchen = Location.Create(
name: "The kitchen",
eastFactory: () => library
);
library = Location.Create(
name: "The old library",
eastFactory: () => office,
westFactory: () => kitchen
);
office = Location.Create(
name: "The office",
westFactory: () => library
);kitchen.Dump();
library.Dump();
office.Dump();Context
StackExchange Code Review Q#145039, answer score: 4
Revisions (0)
No revisions yet.