HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Implementation of a Location object, to be used in building a text-based adventure game

Submitted by: @import:stackexchange-codereview··
0
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

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 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.