Generate Buildings in City

I am developing a game, its map is based on a 2D city. The city is being generated as string, # are streets, O are buildings.

Here is an example:

#######
#OOOOO#
#OOOOO#
#######
#OOOOO#
#OOOOO#
#######


Now I am replacing the Os with other buildings, by replacing the character with an other. The other buildings are defined in a dictionary, where the identifying character is the key and the value is a number between 1 and 100 which is the propability of the building to replace an O.

Currently I am doing it like this:

// Turn city into charArray
char[] cityChars = renderedCity.ToCharArray();

// Cycle through all KeyValuePairs, ordered by Value ascending
foreach (KeyValuePair<char, byte> kvp in specialBuildings.OrderBy(key => key.Value))
{
Random r = new Random(seed);
// Do the following for all normal buildings in the city
// If a random seeded number is smaller or equal to the propability of the spawn, change the building into the new one
cityChars = cityChars.Select(c => c == 'O' ? (r.Next(0, 100) <= kvp.Value ? kvp.Key : c) : c).ToArray();
}


Is there a more elegant way to do this? In the best case, this would be achieved by using no loops at all, but I am struggling with LINQ a bit.

• No time to answer atm, but some needs to mention that r should be a private readonly static field, not re-created every iteration. Aug 12 '16 at 22:03
• I would like to be able to generate the maps with seeds to keep it testable @Mat'sMug. I guess that would not work out if random is a static readonly field
– Marv
Aug 12 '16 at 22:08
• Right, I missed the seed part. Still shouldn't be within the loop scope. Aug 12 '16 at 22:12
• Alright, hopefully a better solution will be given by an answer :)
– Marv
Aug 12 '16 at 22:16
• make the probability the dictionary key and the building the value. Yes there will be 100 entries. Then use a trivial dictionary lookup using the random number. Aug 13 '16 at 1:03

I think even the simplest solutions can and should be made modular and SOLID. Let's try to refactor your game to give you an idea how to begin...

We start with a city. A city can have different squares where a square is just a simple character/symbol.

class City : IEnumerable<Square>
{
private readonly List<Square> _squares = new List<Square>();
{
}
public IEnumerator<Square> GetEnumerator() => _squares.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}


Each square has a position and a symbol. It's abstract so we can derive more concrete types from it.

abstract class Square
{
protected Square(string symbol, int x, int y)
{
Symbol = symbol;
X = x;
Y = y;
}
public string Symbol { get; }
public int X { get; }
public int Y { get; }
public override string ToString() => Symbol;
}


You seem to have two types currently. A building and a street so here they are:

abstract class Square
{
protected Square(string symbol, int x, int y)
{
Symbol = symbol;
X = x;
Y = y;
}
public string Symbol { get; }
public int X { get; }
public int Y { get; }
public override string ToString() => Symbol;
}

{
}

abstract class Building : Square, IUpgradable
{
protected Building(string symbol, int x, int y, int level)
: base(symbol, x, y)
{
Level = level;
}
public int Level { get; private set; }
{
Level++;
}
public override string ToString() => "O";
}

class School : Building
{
public School(int x, int y, int level) : base("H", x, y, level) {}
}

class Hospital : Building
{
public Hospital(int x, int y, int level) : base("S", x, y, level) {}
}

class Street : Square
{
public Street(int x, int y) : base("#", x, y) { }
public override string ToString() => "#";
}


Buildings can be upgraded thus the Upgrade method which increases its level. Each upgradable type implements the IUpgradable interface then so you can even filer on that.

I don't know how you generate your map but for the sake of this example I did it by hand:

Here's a simple city:

// ###
// #H#
// ###
var city = new City
{
new Street(0, 0), new Street(1, 0),new Street(2, 0),
new Street(0, 1), new Hospital(1, 1),new Street(2, 1),
new Street(0, 2), new Street(1, 2),new Street(2, 2),
}


Next we want to upgrade some buildings (I might have the condition for upgrading wrong but it should just demostrate the general idea)

Random r = new Random(seed);

// helper encapsulating the can-upgrade logic
var canUpgradeBuilding = new Func<Building, bool>(b => b.Level <= r.Next(0, 100));

{
}

• Hey, I am currently generating the empty city as a string like given, it is going to be a template that I construct the actual class with. That gives me the freedom to do changes to a city more easily. So in general your answer definitly helps me to design the city class (actually learnt a ton of stuff which I wondered about before :D) but it does not help me to solve my problem here. Think of a city with only appartments, this shall be filled with schools, hospitals and office-buildings.
– Marv
Aug 13 '16 at 10:04
• @Marv this is really simple, if you have more complex buildings then all you need is to make the building abstract and derive more types from it like a hospital or a school. I edited the example. Aug 13 '16 at 10:10
• @Marv oh, I overlooked one sentence in you question... you are replacing the buildings and not upgrading them.... Aug 13 '16 at 10:35
• A Building is a Square and a Street is a Square. Unintuitive modeling IMHO. I imagine structures occupying squares so a Building/Street has a reference to its location. Also I suspect the OP city grid/map is not fully fleshed out and if it is to evolve it must be decoupled. I'd rather inject a location object than re-write the base class. Aug 13 '16 at 15:47
• @radarbob I'm not saying you're not right but it's just a draft to demonstrate how to start... and what it means to separate or encapsulate functionalities. of course you can create a Point structure, overload the == operator, the GetHashCode, and give each object a location, you can change the list into a hashset and create a custom square/structure-comparer, you can create objects that can be bigger the 1x1 you can do all that stuff... a game cannot be programmed in few minutes especially if all you get are 5 lines of code. You can make all kind of assumptions... Aug 13 '16 at 16:01