The rules for this program are to:
- Generate n X n grid.
- Arbitrarily pick a point and grow a 'shape' based off that initial point.
- Must have at least 3 points.
- Strongly biased to not completely fill grid.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
namespace ShapeTestInConsole
{
class Program
{
static void Main(string[] args)
{
Shape.Initialize(4, 4);
Shape shape = new Shape();
Draw(shape);
Console.ReadLine();
}
private static void Draw(Shape shape)
{
for (var x = 0; x < Shape.Width; x++)
{
for (var y = 0; y < Shape.Height; y++)
{
Console.Write("|");
Console.Write(" " + shape.ShapeDefinition[x, y].ToString() + " ");
Console.Write("|");
}
Console.Write("\n");
for (var i = 0; i < Shape.Width * 5; i++)
Console.Write("_");
Console.Write("\n");
}
}
}
public class Shape
{
private static readonly Random _random;
private static readonly Tuple<int, int>[] _adjacent = new[]
{
Tuple.Create(0,-1),
Tuple.Create(1,0),
Tuple.Create(0,1),
Tuple.Create(-1,0)
};
public byte[,] ShapeDefinition { get; private set; }
public static int Width { get; private set; }
public static int Height { get; private set; }
static Shape()
{
_random = new Random();
}
public Shape()
{
ShapeDefinition = new byte[Width, Height];
//Place seed point
ShapeDefinition[_random.Next(0, Width), _random.Next(0, Height)] = 1;
var activePoints = 1;
var starts = new List<Point>();
var neighbors = new List<Point>();
var neighborsToActivate = new List<Point>();
Build(starts, neighbors, neighborsToActivate, activePoints, 0, 1);
}
public static void Initialize(int width, int height)
{
Width = width;
Height = height;
}
private void Build(List<Point> starts, List<Point> neighbors, List<Point> neighborsToActivate,
int activePoints, int runCount, int currentLevel)
{
// Ditch if... Has bias for 4 X 4 grid
if (activePoints >= (Width * Height) || runCount > 5 || currentLevel >= 5)
return;
// minimum 3 points desired
if (activePoints >= 3 && activePoints < 6)
{
if (_random.Next(0, 9) == 2)
return;
}
else if (activePoints >= 6 && activePoints < 8)
{
if (_random.Next(0, 6) == 2)
return;
}
else if (activePoints >= 8)
{
if (_random.Next(0, 5) == 2)
return;
}
starts.Clear();
// Gather points
for (var x = 0; x < ShapeDefinition.GetLength(0); x++)
for (var y = 0; y < ShapeDefinition.GetLength(1); y++)
{
if (ShapeDefinition[x,y] == currentLevel)
starts.Add(new Point(x,y));
}
neighbors.Clear();
// with each start get adjacent usable point
foreach (var currentStartPoint in starts)
{
for (var i = 0; i < _adjacent.Length; i++)
{
var neighborX = currentStartPoint.X + _adjacent[i].Item1;
var neighborY = currentStartPoint.Y + _adjacent[i].Item2;
if (InBounds(neighborX, neighborY))
{
if (ShapeDefinition[neighborX, neighborY] == 0)
neighbors.Add(new Point(neighborX, neighborY));
}
}
neighborsToActivate.Clear();
// randomly pick which usable point(s) will be include with shape
neighborsToActivate = neighbors.OrderBy(n => _random.Next())
.Take(_random.Next(1, neighbors.Count + 1))
.ToList();
for (var j = 0; j < neighborsToActivate.Count; j++)
{
ShapeDefinition[neighborsToActivate[j].X, neighborsToActivate[j].Y] = (byte)(currentLevel + 1);
activePoints++;
}
}
runCount++;
currentLevel++;
Build(starts, neighbors, neighborsToActivate, activePoints, runCount, currentLevel);
}
private static bool InBounds(int x, int y)
{
return x >= 0 && x < Width &&
y >= 0 && y < Height;
}
}
}
This is a good facsimile of what I trying to accomplish now. My own thoughts on improvements would be that Shape, for how I would like to use it, should not have methods to create itself and should instead be created by a ShapeFactory or ShapeBuilder class. A Shape's only job is to be a shape.