How I Wrote a C# Terrain Map Generator

Last week while I was taking the dog for a walk, an idea came to mind: What if I built a town simulation that told stories about people who lived there? Upon thinking this, I was curious if I could somehow build a random map that these people, or “sims” since I grew up in the golden years of SimCity and other sim games, would tell the user stories as they interacted with the world.

For example, if Bob Dragonsbreath realized there was a shortage of food, he would build a farm. Eventually, he met the future Mrs. Dragonsbreath, and they had kids who also eventually helped build up society. I would be lying to say I wasn’t inspired by Dwarf Fortress. Though, I haven’t played the game as I haven’t taken the time to understand it. What I do know is that many fans of it talk about all the stories that can be created from it. Rather than having a game people interacted with, I wanted to build a simulation of a town interacting with itself.

I thought to myself, “how would these people interact with the world that is created for them?” To accomplish this, I needed to figure out how to build a town layout for them. The idea came suddenly quick: what if I generated terrain via characters, and each character represented a tile on the map?

Initially, I started reading various articles on noise algorithms. I read articles on simplex noise and the more popular Perlin noise, but both seemed overly complex for a 2D-based world. If I was to build a terrain map with various heights to include mountainous ranges, then this would be the route I would take. For the sake of this experiment, I wanted to do something simple.

My first iteration of the code had each letter generate completely random, only looking at the previous character. Specifically, I assigned “g” for grass, “t” for trees, “d” for dirt, and “r” for river. I spit out each of these characters into a textbox so I could see how it would appear. Once it looked at the previous character it would either replicate it 80% of the time, or create a new character.

Code missing

I deleted the code. More importantly, though, I needed to figure out how to transform each of these characters to an image. Before even that, I needed to create these images. I’d be the first person to tell you I’m a terrible artist, so I made these little tiny 12×12 pictures drawn in paint.net (a wonderful and free Adobe Photoshope clone by the way).

I knew the first thing I needed to do was go through each character in the textbox.

foreach (char itm in textBox1.Text) 
{
}

A developer would be lying to you if they said they never went online to websites like stackexchange.com for ideas on solutions to problems. I did not find any solutions to my problem no matter how many different searches I tried. Every solution I found was either building an imagelist, or displaying a single image. However, I did find some clues to a solution. In one situation, someone was trying to connect multiple images together as a bitmap. Basically, a bitmap stores an image often seen with a file extension of *.bmp, although this method wasn’t creating a file. I thought if I created a bitmap with all of these image files, I could create a single image and place it in a picturebox. After several failed attempts, it eventually worked. Then, I split the characters in the textbox by 50 so it could create a new row of images every 50 characters and give it a map type of feeling.

int x = 0, y = 0, img = 0;

Bitmap[] bm;
 bm = new Bitmap[501];

Image[] files;
 files = new Image[501];

List<Bitmap> images = new List<Bitmap>();
 Bitmap bitmap;

Bitmap finalImage = new Bitmap(600, 600);

foreach (char itm in textBox1.Text)
 {

// create the images!
 if (itm == 'g')
 {
 bm[img] = new Bitmap(@"D:\Dropbox\Dropbox\visual basic\Tiny Town\TinyTown\TinyTown\images\grass.png");
 files[img] = new Bitmap(bm[img], 12, 12);
 }

if (itm == 's')
 {
 bm[img] = new Bitmap(@"D:\Dropbox\Dropbox\visual basic\Tiny Town\TinyTown\TinyTown\images\stone.png");
 files[img] = new Bitmap(bm[img], 12, 12);
 }

if (itm == 't')
 {
 bm[img] = new Bitmap(@"D:\Dropbox\Dropbox\visual basic\Tiny Town\TinyTown\TinyTown\images\tree.png");
 files[img] = new Bitmap(bm[img], 12, 12);
 }

if (itm == 'r')
 {
 bm[img] = new Bitmap(@"D:\Dropbox\Dropbox\visual basic\Tiny Town\TinyTown\TinyTown\images\river.png");
 files[img] = new Bitmap(bm[img], 12, 12);
 }

}

I could probably simplify this code, but optimization comes later. Anyways, I made both the bitmap and image files as an array noted by bm[]. The “img” in the code represents which character I’m looking at (i.e. “g”).  Once I’ve determined what the bitmap is, then I create an image using files[]. Within the image I define the bitmap used, and the size of the bitmap. This is all done within each if statement so I can translate a “g” to the grass image file.

After the code combs through the if statements, it adds each image file to the graphics using an x and y axis. I used Graphics because it creates a drawing of the image, which was easier to handle than creating a list of images that simulate a single image. Basically, this is combining all of the images into a single image (or finalImage):

 // now we add the image to the graphics engine
 using (Graphics g = Graphics.FromImage(finalImage))
 {
 g.DrawImage(files[img], new Point(x, y));
 }

Now we move on to the next variables so that we’re going to the next image, and the next x-axis. In this case it goes up by 12 because that is the size of each image.

// next up
 img++;
 x += 12;

//  new row!
 if (img % 50 == 0)
 {
 x = 0;
 y += 12; // 12 because that's how big the image is
 }

The if statement uses % to determine whether or not the number of images is divisible by 50. If it is, then it creates a new row of images to give it that map look.

Then, we draw the image:

// draw the graphics
 pictureBox1.Size = finalImage.Size;
 pictureBox1.Image = finalImage;

Going back to the grouping situation, it wasn’t until this point that I noticed the terrain looked silly. There would be a tiny row of trees, a tiny row of grass, a dot of water, and a tiny row of dirt. It didn’t feel like a believable land to me. I went back to the drawing board the next evening as this was now a determined mission to complete.

I moved the 80% character rate to 90% because I thought it needed a little bit more of a “grouping” rate, then I completely expanded upon what I had. Rather than look at the previous character, I wanted it to look at the surrounding characters. Obviously, since text generated from left-to-right, it couldn’t look at any text to the right or the undetermined “rows” below. Once I established how many characters would be in a row, I set the code to randomly and equally choose either the top-left character, the top character, the top-right character, or the previous character. Below is how that code appears:

Random rnd = new Random();
 string prevter = "g", ulter = "g", upter = "g", urter = "g", ter = "g";
 int index = 0, y = 0;

for(int x = 0; x < 500; x++)
 {
 // first, will we get a previously used one?
 index = rnd.Next(10);

// yes, let's use a past one
 if (index < 9)
 {
 // get northern borders
 if (x > (thesplit + 1))
 {
 ulter = textbox1.Text((x - (50 - 1)), 1);
 upter = textbox1.Text((x - 50), 1);
 urter = textbox1.Text((x - (50 + 1)), 1);
 }

// set the tile as the surrounding one
 y = rnd.Next(0, 4);

switch(y)
 {
 case 0:
 ter = prevter;
 break;
 case 1:
 ter = ulter;
 break;
 case 2:
 ter = upter;
 break;
 case 3:
 ter = urter;
 break;
 case 4:
 ter = ulter;
 break;
 }

}

Since the length of each row was 50 images, I knew i had to look at 50 characters. Thus, if I wanted the pixel to the top-left of where it was currently generating, I used (x – (50+ 1)) – seen as the variable “ulter”. By the way, I used “ulter” twice because I found all the grouping seemed to go down and right as if I was a painter only stroking the brush in one direction. If it wasn’t in that 90% rate, then it randomly generated a new character. Each character had a different ratio of being produced, grass being the most frequent while water produced the least frequently. Also, at this point I switched from dirt to stone.

else // some new terrain

{
 y = rnd.Next(1,100);

if (y < 5)
 {
 ter = "r";
 }
 if (y >= 5 && y < 15)
 {
 ter = "s";
 }
 if (y >= 15 && y < 50)
 {
 ter = "t";
 }
 if (y >= 50)
 {
 ter = "g";
 }
 }

Then we append the text to the textbox.

textbox1.AppendText(ter);

I improved the code in a later version by using StringBuilder. I also added more images and changed the row creation at 100, and also used a defined variable instead of 50 as I kept altering it and referred to it in multiple locations throughout the code. Anyways, below is a screenshot:

As you can see, this solution worked great because not only did it produce a decent-looking map, but the characters enabled me to manipulate them easily. You can see big areas of grass with little forests, a small lake on the far-right, and a weird-looking color that is supposed to be “stone”. If I wanted to build a farm, for example, all I had to do was find water on the map (or look for a “r” in the textbox), and find four spaces of grass nearby.

If you’re curious, this is what some of the string of the above image looks like:

gggggggggggggggggggggggggttggtggggsggttgggggggggggsgtggggggggggggggggggggggggggggggggggggggggggsggggggggggggggggggggggssggggtttggggggsgttttgggggggggggggtgggggggggggggggggggggggggggggggggggggggtggggggggggggggggggggggtgsttssgggtttrggsgggtttttggtggggggggggggggggggggtggggggggggggggggggggggggggggtgggggsssgggggggggggggggsstttgggtggrtgggsgtgttttgggggggggggggggggggggggttgggggtgggtggggtggggggggggggtgggggsssssgggtggtgggggsgssttggggggggggggggttttgggggggggggttgggggtggggttgggggggtgggggttgggggggggggggttggggssgssssggggttgggggsssttgggtggggggggggttttttggtgggggggttgrggtgggttggggggggtggtgtttggggggggggggtgtgggggsgggssgggggttgggggsstrgtsssgggggggggttttttttgggtttgtssrgggttggsggggggggggggggttgtggggggggggggggtgggggsggggggggttgggggtssrgggssgsgsggggggggtgtttgggsgtttttrsgggsgttgsggggtgggggggggttgggggggggsgggggtggggggggggggggttggggsgsssgggggggggsggttttgttsttggggtgttttrrggggsssssggggggggggtgggggggggtgggtsggggttttgggggggggggggggggggsssssgggtggggggggtttttstttggggggggttrrgggggsssgggggggggggttggsggggggggtttgggggg

This got me thinking to a project I started about 10 years ago. I wanted to create a “God simulator” where you pushed the world in certain directions, but did not have direct control. If you got it right, humans (or a species) would start to form a civilization. Eventually, they’d have disagreements and a new nation would form beginning the era of “countries” and the first war. From there, people would explore and expand the world map and you’d see nations come and go. I started and stopped the project on multiple occasions because I struggled with how the maps would work. Oddly enough, working on this project might spark my interest on that old idea again. Until then, I’m sticking with the small town simulation.

Thanks for reading!

Comments are closed.