From 2e2ace4f23959ade594d1dbc760d90c1568d04b9 Mon Sep 17 00:00:00 2001 From: m1nus0ne Date: Wed, 13 Nov 2024 17:37:18 +0500 Subject: [PATCH 1/3] =?UTF-8?q?=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D0=B0=D1=8F?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D1=8B,=20?= =?UTF-8?q?=D0=B0=D0=BB=D0=B3=D0=BE=D1=80=D0=B8=D1=82=D0=BC=D0=B0=20=D1=80?= =?UTF-8?q?=D0=B0=D1=81=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B8=20=D0=B2=D0=B8=D0=B7=D1=83=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TagCloud/CircularCloudLayouter.cs | 23 +++++++++++ .../CloudDrawer/CircularCloudDrawer.cs | 24 +++++++++++ .../TagCloud/CloudDrawer/ICloudDrawer.cs | 8 ++++ .../PositionCalculator/IPositionCalculator.cs | 9 ++++ .../SpiralPositionCalculator.cs | 41 +++++++++++++++++++ cs/tdd.sln | 12 ++++++ cs/tdd.sln.DotSettings | 3 ++ 7 files changed, 120 insertions(+) create mode 100644 cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs create mode 100644 cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs create mode 100644 cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs create mode 100644 cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs create mode 100644 cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs diff --git a/cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs new file mode 100644 index 000000000..89bda9c7d --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs @@ -0,0 +1,23 @@ +using System.Drawing; + +namespace TagsCloudVisualization.TagCloud; + +public class CircularCloudLayouter(Point center) +{ + private readonly Point center = center; + private readonly IPositionCalculator calculator = new SpiralPositionCalculator(center); + + public List Rectangles { get; private set; } = []; + + public Rectangle PutNextRectangle(Size rectangleSize) + { + if (rectangleSize.Width <= 0) + throw new ArgumentException("Size width must be positive number"); + if (rectangleSize.Height <= 0) + throw new ArgumentException("Size height must be positive number"); + + var temp = calculator.CalculateNextPosition(Rectangles, rectangleSize); //вывести на ленивую реализацию? + Rectangles.Add(temp); + return temp; + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs new file mode 100644 index 000000000..f2e8fe5f2 --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs @@ -0,0 +1,24 @@ +using System.Drawing; +using System.Globalization; + +namespace TagsCloudVisualization.TagCloud; + +public class BaseCloudDrawer : ICloudDrawer +{ + public void DrawCloud(List rectangles, string? fileName = null, string? path = null, + int imageWidth = 500) + { + path ??= Environment.CurrentDirectory; + fileName ??= DateTime.Now.ToOADate().ToString(CultureInfo.InvariantCulture); + using var bitmap = new Bitmap(imageWidth, imageWidth); + using var graphics = Graphics.FromImage(bitmap); + graphics.Clear(Color.White); + + foreach (var rectangle in rectangles) + { + graphics.DrawRectangle(new Pen(Color.Black), rectangle); + } + + bitmap.Save(Path.Combine(path, fileName + ".png")); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs new file mode 100644 index 000000000..f93cc8a90 --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagsCloudVisualization.TagCloud; + +public interface ICloudDrawer +{ + public void DrawCloud(List rectangles, string? fileName, string? path, int imageWidth); +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs new file mode 100644 index 000000000..931b1f6c3 --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs @@ -0,0 +1,9 @@ +using System.Drawing; + +namespace TagsCloudVisualization.TagCloud; + +public interface IPositionCalculator +{ + public Rectangle CalculateNextPosition(List rectangles, Size nextRectangleSize); + public bool ValidateRectanglePosition(List rectangles, Rectangle currentRectangle); +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs new file mode 100644 index 000000000..ea5e7e630 --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs @@ -0,0 +1,41 @@ +using System.Drawing; + +namespace TagsCloudVisualization.TagCloud; + +public class SpiralPositionCalculator(Point center) : IPositionCalculator +{ + private double currentAngle = 0.0; + private double currentOffset = 0.0; + private const double offsetDelta = 2; + private const double angleDelta = 0.1; + + public Rectangle CalculateNextPosition(List rectangles, Size nextRectangleSize) + { + while (true) + { + var newRectangle = RectangleFromParams(nextRectangleSize); + currentAngle += angleDelta; + if (currentAngle >= 2 * Math.PI) + { + currentAngle = 0; + currentOffset += offsetDelta; + } + + if (ValidateRectanglePosition(rectangles, newRectangle)) + return newRectangle; + } + } + + private Rectangle RectangleFromParams(Size nextRectangleSize) + { + var x = (int)(center.X + currentOffset * Math.Cos(currentAngle)); //надо центровать? + var y = (int)(center.Y + currentOffset * Math.Sin(currentAngle)); + var newRectangle = new Rectangle(new Point(x, y), nextRectangleSize); + return newRectangle; + } + + public bool ValidateRectanglePosition(List rectangles, Rectangle currentRectangle) + { + return !rectangles.Any(r => r.IntersectsWith(currentRectangle)); + } +} \ No newline at end of file diff --git a/cs/tdd.sln b/cs/tdd.sln index c8f523d63..d2db0aeeb 100644 --- a/cs/tdd.sln +++ b/cs/tdd.sln @@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BowlingGame", "BowlingGame\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudVisualization", "TagsCloudVisualization\TagsCloudVisualization.csproj", "{D99B90EA-1C05-417E-80C6-B005531D0545}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudVisualizationTest", "TagsCloudVisualizationTest\TagsCloudVisualizationTest.csproj", "{A1FACECA-57BA-4B8F-B804-DC54B8B167C8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +25,14 @@ Global {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.Build.0 = Release|Any CPU + {D99B90EA-1C05-417E-80C6-B005531D0545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D99B90EA-1C05-417E-80C6-B005531D0545}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D99B90EA-1C05-417E-80C6-B005531D0545}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D99B90EA-1C05-417E-80C6-B005531D0545}.Release|Any CPU.Build.0 = Release|Any CPU + {A1FACECA-57BA-4B8F-B804-DC54B8B167C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1FACECA-57BA-4B8F-B804-DC54B8B167C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1FACECA-57BA-4B8F-B804-DC54B8B167C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1FACECA-57BA-4B8F-B804-DC54B8B167C8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/cs/tdd.sln.DotSettings b/cs/tdd.sln.DotSettings index 135b83ecb..229f449d2 100644 --- a/cs/tdd.sln.DotSettings +++ b/cs/tdd.sln.DotSettings @@ -1,6 +1,9 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016 From 88ed57db1fe34ec93a89a4b9f20805456c1ee1b8 Mon Sep 17 00:00:00 2001 From: m1nus0ne Date: Wed, 13 Nov 2024 17:37:18 +0500 Subject: [PATCH 2/3] =?UTF-8?q?=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D0=B0=D1=8F?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D1=8B,=20?= =?UTF-8?q?=D0=B0=D0=BB=D0=B3=D0=BE=D1=80=D0=B8=D1=82=D0=BC=D0=B0=20=D1=80?= =?UTF-8?q?=D0=B0=D1=81=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B8=20=D0=B2=D0=B8=D0=B7=D1=83=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cs/TagsCloudVisualization/Program.cs | 12 +++ .../TagCloud/CircularCloudLayouter.cs | 23 +++++ .../CloudDrawer/CircularCloudDrawer.cs | 24 +++++ .../TagCloud/CloudDrawer/ICloudDrawer.cs | 8 ++ .../PositionCalculator/IPositionCalculator.cs | 9 ++ .../SpiralPositionCalculator.cs | 41 ++++++++ .../TagsCloudVisualization.csproj | 14 +++ .../PngExamples/45608,91380605324.png | Bin 0 -> 10025 bytes .../PngExamples/45609.73230590278.png | Bin 0 -> 17344 bytes .../SpiralPositionCalculator_Tests.cs | 89 ++++++++++++++++++ .../TagsCloudVisualizationTest.csproj | 29 ++++++ cs/tdd.sln | 12 +++ cs/tdd.sln.DotSettings | 3 + 13 files changed, 264 insertions(+) create mode 100644 cs/TagsCloudVisualization/Program.cs create mode 100644 cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs create mode 100644 cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs create mode 100644 cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs create mode 100644 cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs create mode 100644 cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs create mode 100644 cs/TagsCloudVisualization/TagsCloudVisualization.csproj create mode 100644 cs/TagsCloudVisualizationTest/PngExamples/45608,91380605324.png create mode 100644 cs/TagsCloudVisualizationTest/PngExamples/45609.73230590278.png create mode 100644 cs/TagsCloudVisualizationTest/SpiralPositionCalculator_Tests.cs create mode 100644 cs/TagsCloudVisualizationTest/TagsCloudVisualizationTest.csproj diff --git a/cs/TagsCloudVisualization/Program.cs b/cs/TagsCloudVisualization/Program.cs new file mode 100644 index 000000000..abccca8d2 --- /dev/null +++ b/cs/TagsCloudVisualization/Program.cs @@ -0,0 +1,12 @@ +using System.Drawing; +using TagsCloudVisualization.TagCloud; + +var l = new CircularCloudLayouter(new Point(250,250)); +for (int i = 0; i < 100; i++) +{ + var rand = new Random(); + l.PutNextRectangle(new Size(rand.Next(10,70),rand.Next(10,70))); +} + +var drawer = new BaseCloudDrawer(); +drawer.DrawCloud(l.Rectangles); diff --git a/cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs new file mode 100644 index 000000000..89bda9c7d --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs @@ -0,0 +1,23 @@ +using System.Drawing; + +namespace TagsCloudVisualization.TagCloud; + +public class CircularCloudLayouter(Point center) +{ + private readonly Point center = center; + private readonly IPositionCalculator calculator = new SpiralPositionCalculator(center); + + public List Rectangles { get; private set; } = []; + + public Rectangle PutNextRectangle(Size rectangleSize) + { + if (rectangleSize.Width <= 0) + throw new ArgumentException("Size width must be positive number"); + if (rectangleSize.Height <= 0) + throw new ArgumentException("Size height must be positive number"); + + var temp = calculator.CalculateNextPosition(Rectangles, rectangleSize); //вывести на ленивую реализацию? + Rectangles.Add(temp); + return temp; + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs new file mode 100644 index 000000000..f2e8fe5f2 --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs @@ -0,0 +1,24 @@ +using System.Drawing; +using System.Globalization; + +namespace TagsCloudVisualization.TagCloud; + +public class BaseCloudDrawer : ICloudDrawer +{ + public void DrawCloud(List rectangles, string? fileName = null, string? path = null, + int imageWidth = 500) + { + path ??= Environment.CurrentDirectory; + fileName ??= DateTime.Now.ToOADate().ToString(CultureInfo.InvariantCulture); + using var bitmap = new Bitmap(imageWidth, imageWidth); + using var graphics = Graphics.FromImage(bitmap); + graphics.Clear(Color.White); + + foreach (var rectangle in rectangles) + { + graphics.DrawRectangle(new Pen(Color.Black), rectangle); + } + + bitmap.Save(Path.Combine(path, fileName + ".png")); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs new file mode 100644 index 000000000..f93cc8a90 --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagsCloudVisualization.TagCloud; + +public interface ICloudDrawer +{ + public void DrawCloud(List rectangles, string? fileName, string? path, int imageWidth); +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs new file mode 100644 index 000000000..931b1f6c3 --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs @@ -0,0 +1,9 @@ +using System.Drawing; + +namespace TagsCloudVisualization.TagCloud; + +public interface IPositionCalculator +{ + public Rectangle CalculateNextPosition(List rectangles, Size nextRectangleSize); + public bool ValidateRectanglePosition(List rectangles, Rectangle currentRectangle); +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs new file mode 100644 index 000000000..ea5e7e630 --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs @@ -0,0 +1,41 @@ +using System.Drawing; + +namespace TagsCloudVisualization.TagCloud; + +public class SpiralPositionCalculator(Point center) : IPositionCalculator +{ + private double currentAngle = 0.0; + private double currentOffset = 0.0; + private const double offsetDelta = 2; + private const double angleDelta = 0.1; + + public Rectangle CalculateNextPosition(List rectangles, Size nextRectangleSize) + { + while (true) + { + var newRectangle = RectangleFromParams(nextRectangleSize); + currentAngle += angleDelta; + if (currentAngle >= 2 * Math.PI) + { + currentAngle = 0; + currentOffset += offsetDelta; + } + + if (ValidateRectanglePosition(rectangles, newRectangle)) + return newRectangle; + } + } + + private Rectangle RectangleFromParams(Size nextRectangleSize) + { + var x = (int)(center.X + currentOffset * Math.Cos(currentAngle)); //надо центровать? + var y = (int)(center.Y + currentOffset * Math.Sin(currentAngle)); + var newRectangle = new Rectangle(new Point(x, y), nextRectangleSize); + return newRectangle; + } + + public bool ValidateRectanglePosition(List rectangles, Rectangle currentRectangle) + { + return !rectangles.Any(r => r.IntersectsWith(currentRectangle)); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudVisualization.csproj b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj new file mode 100644 index 000000000..d35d934e9 --- /dev/null +++ b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/cs/TagsCloudVisualizationTest/PngExamples/45608,91380605324.png b/cs/TagsCloudVisualizationTest/PngExamples/45608,91380605324.png new file mode 100644 index 0000000000000000000000000000000000000000..90449ad95efeab1dc79d195c227faf6cd6a0f5ba GIT binary patch literal 10025 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 N?WQzj_ZPl^6WVG7d3EBvPVJ;$?ze@NC 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 + + var result = calculator.CalculateNextPosition(rectangles, size); + + result.Location.Should().Be(center); + } + + [Test] + public void CalculateNextPosition_ShouldReturnNonIntersectingRectangle_WhenRectanglesExist() + { + var size = new Size(10, 10); + var rectangles = new List + { + new Rectangle(new Point(0, 0), size) + }; + + var result = calculator.CalculateNextPosition(rectangles, size); + + result.IntersectsWith(rectangles[0]).Should().BeFalse(); + } + + [Test] + public void CalculateNextPosition_ShouldIncreaseOffset_WhenAngleCompletesFullCircle() + { + var size = new Size(10, 10); + var rectangles = new List(); + + for (int i = 0; i < 100; i++) + { + calculator.CalculateNextPosition(rectangles, size); + } + + var result = calculator.CalculateNextPosition(rectangles, size); + + result.Location.Should().NotBe(center); + } + + [Test] + public void ValidateRectanglePosition_ShouldReturnTrue_WhenNoIntersections() + { + var size = new Size(10, 10); + var rectangles = new List(); + var rectangle = new Rectangle(new Point(0, 0), size); + + var result = calculator.ValidateRectanglePosition(rectangles, rectangle); + + result.Should().BeTrue(); + } + + [Test] + public void ValidateRectanglePosition_ShouldReturnFalse_WhenIntersectsWithExistingRectangle() + { + var size = new Size(10, 10); + var rectangles = new List + { + new Rectangle(new Point(0, 0), size) + }; + var rectangle = new Rectangle(new Point(0, 0), size); + + var result = calculator.ValidateRectanglePosition(rectangles, rectangle); + + result.Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTest/TagsCloudVisualizationTest.csproj b/cs/TagsCloudVisualizationTest/TagsCloudVisualizationTest.csproj new file mode 100644 index 000000000..47c063eb8 --- /dev/null +++ b/cs/TagsCloudVisualizationTest/TagsCloudVisualizationTest.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/cs/tdd.sln b/cs/tdd.sln index c8f523d63..d2db0aeeb 100644 --- a/cs/tdd.sln +++ b/cs/tdd.sln @@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BowlingGame", "BowlingGame\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudVisualization", "TagsCloudVisualization\TagsCloudVisualization.csproj", "{D99B90EA-1C05-417E-80C6-B005531D0545}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudVisualizationTest", "TagsCloudVisualizationTest\TagsCloudVisualizationTest.csproj", "{A1FACECA-57BA-4B8F-B804-DC54B8B167C8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +25,14 @@ Global {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.Build.0 = Release|Any CPU + {D99B90EA-1C05-417E-80C6-B005531D0545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D99B90EA-1C05-417E-80C6-B005531D0545}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D99B90EA-1C05-417E-80C6-B005531D0545}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D99B90EA-1C05-417E-80C6-B005531D0545}.Release|Any CPU.Build.0 = Release|Any CPU + {A1FACECA-57BA-4B8F-B804-DC54B8B167C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1FACECA-57BA-4B8F-B804-DC54B8B167C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1FACECA-57BA-4B8F-B804-DC54B8B167C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1FACECA-57BA-4B8F-B804-DC54B8B167C8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/cs/tdd.sln.DotSettings b/cs/tdd.sln.DotSettings index 135b83ecb..229f449d2 100644 --- a/cs/tdd.sln.DotSettings +++ b/cs/tdd.sln.DotSettings @@ -1,6 +1,9 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016 From 6268d51dc525ce3ddb5c5ce4724263458141a0ef Mon Sep 17 00:00:00 2001 From: m1nus0ne Date: Mon, 18 Nov 2024 19:36:42 +0500 Subject: [PATCH 3/3] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=82=D1=80=D0=B8=D1=81=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B8=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0,=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20=D0=B2=20=D0=B0?= =?UTF-8?q?=D1=80=D1=85=D0=B8=D1=82=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cs/TagsCloudVisualization/Program.cs | 5 +- .../TagCloud/CircularCloudLayouter.cs | 8 +- .../TagCloud/CloudDrawer/BaseCloudDrawer.cs | 34 ++++++ .../CloudDrawer/CircularCloudDrawer.cs | 24 ---- .../TagCloud/CloudDrawer/ICloudDrawer.cs | 4 +- ...lator.cs => CircularPositionCalculator.cs} | 20 ++- .../PositionCalculator/IPositionCalculator.cs | 4 +- .../BaseCloudDrawer_Tests.cs | 115 ++++++++++++++++++ .../CircularCloudLayouter_Tests.cs | 61 ++++++++++ cs/TagsCloudVisualizationTest/GlobalUsings.cs | 5 + .../SpiralPositionCalculator_Tests.cs | 52 +++----- 11 files changed, 253 insertions(+), 79 deletions(-) create mode 100644 cs/TagsCloudVisualization/TagCloud/CloudDrawer/BaseCloudDrawer.cs delete mode 100644 cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs rename cs/TagsCloudVisualization/TagCloud/PositionCalculator/{SpiralPositionCalculator.cs => CircularPositionCalculator.cs} (50%) create mode 100644 cs/TagsCloudVisualizationTest/BaseCloudDrawer_Tests.cs create mode 100644 cs/TagsCloudVisualizationTest/CircularCloudLayouter_Tests.cs create mode 100644 cs/TagsCloudVisualizationTest/GlobalUsings.cs diff --git a/cs/TagsCloudVisualization/Program.cs b/cs/TagsCloudVisualization/Program.cs index abccca8d2..68b8acf53 100644 --- a/cs/TagsCloudVisualization/Program.cs +++ b/cs/TagsCloudVisualization/Program.cs @@ -1,7 +1,7 @@ using System.Drawing; using TagsCloudVisualization.TagCloud; -var l = new CircularCloudLayouter(new Point(250,250)); +var l = new CircularCloudLayouter(new Point(250,250),new CircularPositionCalculator(new Point(250,250))); for (int i = 0; i < 100; i++) { var rand = new Random(); @@ -9,4 +9,5 @@ } var drawer = new BaseCloudDrawer(); -drawer.DrawCloud(l.Rectangles); +var bmp = drawer.DrawCloud(l.Rectangles,500,500); +drawer.SaveToFile(bmp); diff --git a/cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs index 89bda9c7d..ae15e78d4 100644 --- a/cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/TagCloud/CircularCloudLayouter.cs @@ -2,11 +2,8 @@ namespace TagsCloudVisualization.TagCloud; -public class CircularCloudLayouter(Point center) +public class CircularCloudLayouter(Point center, IPositionCalculator calculator) { - private readonly Point center = center; - private readonly IPositionCalculator calculator = new SpiralPositionCalculator(center); - public List Rectangles { get; private set; } = []; public Rectangle PutNextRectangle(Size rectangleSize) @@ -16,7 +13,8 @@ public Rectangle PutNextRectangle(Size rectangleSize) if (rectangleSize.Height <= 0) throw new ArgumentException("Size height must be positive number"); - var temp = calculator.CalculateNextPosition(Rectangles, rectangleSize); //вывести на ленивую реализацию? + var temp = calculator.CalculateNextPosition(rectangleSize) + .First(rectangle => calculator.IsRectanglePositionValid(Rectangles, rectangle)); Rectangles.Add(temp); return temp; } diff --git a/cs/TagsCloudVisualization/TagCloud/CloudDrawer/BaseCloudDrawer.cs b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/BaseCloudDrawer.cs new file mode 100644 index 000000000..b5fce6448 --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/BaseCloudDrawer.cs @@ -0,0 +1,34 @@ +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; + +namespace TagsCloudVisualization.TagCloud; + +public class BaseCloudDrawer : ICloudDrawer +{ + public Bitmap DrawCloud(List rectangles, int imageWidth, int imageHeight) + { + if (imageWidth <= 0) + throw new ArgumentException("Width must be positive number"); + if (imageHeight <= 0) + throw new ArgumentException("Height must be positive number"); + var bitmap = new Bitmap(imageWidth, imageHeight); + var graphics = Graphics.FromImage(bitmap); + graphics.Clear(Color.White); + foreach (var rectangle in rectangles) + { + graphics.DrawRectangle(new Pen(Color.Black), rectangle); + } + + return bitmap; + } + + public void SaveToFile(Bitmap bitmap, string? fileName = null, string? path = null, ImageFormat format = null) + { + path ??= Environment.CurrentDirectory; + fileName ??= DateTime.Now.ToOADate().ToString(CultureInfo.InvariantCulture); + format ??= ImageFormat.Png; + var fullPath = Path.Combine(path, fileName + ".png"); + bitmap.Save(fullPath); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs deleted file mode 100644 index f2e8fe5f2..000000000 --- a/cs/TagsCloudVisualization/TagCloud/CloudDrawer/CircularCloudDrawer.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Drawing; -using System.Globalization; - -namespace TagsCloudVisualization.TagCloud; - -public class BaseCloudDrawer : ICloudDrawer -{ - public void DrawCloud(List rectangles, string? fileName = null, string? path = null, - int imageWidth = 500) - { - path ??= Environment.CurrentDirectory; - fileName ??= DateTime.Now.ToOADate().ToString(CultureInfo.InvariantCulture); - using var bitmap = new Bitmap(imageWidth, imageWidth); - using var graphics = Graphics.FromImage(bitmap); - graphics.Clear(Color.White); - - foreach (var rectangle in rectangles) - { - graphics.DrawRectangle(new Pen(Color.Black), rectangle); - } - - bitmap.Save(Path.Combine(path, fileName + ".png")); - } -} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs index f93cc8a90..cc1b9ae15 100644 --- a/cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs +++ b/cs/TagsCloudVisualization/TagCloud/CloudDrawer/ICloudDrawer.cs @@ -1,8 +1,10 @@ using System.Drawing; +using System.Drawing.Imaging; namespace TagsCloudVisualization.TagCloud; public interface ICloudDrawer { - public void DrawCloud(List rectangles, string? fileName, string? path, int imageWidth); + public Bitmap DrawCloud(List rectangles, int imageWidth, int imageHeight); + public void SaveToFile(Bitmap bitmap, string? fileName, string? path, ImageFormat format); } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/CircularPositionCalculator.cs similarity index 50% rename from cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs rename to cs/TagsCloudVisualization/TagCloud/PositionCalculator/CircularPositionCalculator.cs index ea5e7e630..4df93ec26 100644 --- a/cs/TagsCloudVisualization/TagCloud/PositionCalculator/SpiralPositionCalculator.cs +++ b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/CircularPositionCalculator.cs @@ -2,39 +2,37 @@ namespace TagsCloudVisualization.TagCloud; -public class SpiralPositionCalculator(Point center) : IPositionCalculator +public class CircularPositionCalculator(Point center, double offsetDelta = 2.0, double angleDelta = 0.1) : IPositionCalculator { private double currentAngle = 0.0; private double currentOffset = 0.0; - private const double offsetDelta = 2; - private const double angleDelta = 0.1; + private const double fullRoundAngle = Math.PI * 2; - public Rectangle CalculateNextPosition(List rectangles, Size nextRectangleSize) + public IEnumerable CalculateNextPosition (Size nextRectangleSize) { while (true) { - var newRectangle = RectangleFromParams(nextRectangleSize); + var newRectangle = MakeRectangleFromSize(nextRectangleSize); currentAngle += angleDelta; - if (currentAngle >= 2 * Math.PI) + if (currentAngle >= fullRoundAngle) { currentAngle = 0; currentOffset += offsetDelta; } - if (ValidateRectanglePosition(rectangles, newRectangle)) - return newRectangle; + yield return newRectangle; } } - private Rectangle RectangleFromParams(Size nextRectangleSize) + private Rectangle MakeRectangleFromSize(Size nextRectangleSize) { - var x = (int)(center.X + currentOffset * Math.Cos(currentAngle)); //надо центровать? + var x = (int)(center.X + currentOffset * Math.Cos(currentAngle)); var y = (int)(center.Y + currentOffset * Math.Sin(currentAngle)); var newRectangle = new Rectangle(new Point(x, y), nextRectangleSize); return newRectangle; } - public bool ValidateRectanglePosition(List rectangles, Rectangle currentRectangle) + public bool IsRectanglePositionValid(List rectangles, Rectangle currentRectangle) { return !rectangles.Any(r => r.IntersectsWith(currentRectangle)); } diff --git a/cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs index 931b1f6c3..c5cbf40ed 100644 --- a/cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs +++ b/cs/TagsCloudVisualization/TagCloud/PositionCalculator/IPositionCalculator.cs @@ -4,6 +4,6 @@ namespace TagsCloudVisualization.TagCloud; public interface IPositionCalculator { - public Rectangle CalculateNextPosition(List rectangles, Size nextRectangleSize); - public bool ValidateRectanglePosition(List rectangles, Rectangle currentRectangle); + public IEnumerable CalculateNextPosition(Size nextRectangleSize); + public bool IsRectanglePositionValid(List rectangles, Rectangle currentRectangle); } \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTest/BaseCloudDrawer_Tests.cs b/cs/TagsCloudVisualizationTest/BaseCloudDrawer_Tests.cs new file mode 100644 index 000000000..c14751944 --- /dev/null +++ b/cs/TagsCloudVisualizationTest/BaseCloudDrawer_Tests.cs @@ -0,0 +1,115 @@ +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using NUnit.Framework.Interfaces; + +namespace BaseCloudDrawer_Tests; + +[TestFixture] +public class BaseCloudDrawer_Tests +{ + private BaseCloudDrawer drawer; + private string testDirectory; + + [SetUp] + public void SetUp() + { + drawer = new BaseCloudDrawer(); + testDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestImages"); + Directory.CreateDirectory(testDirectory); + } + + [TearDown] + public void TearDown() + { + var status = TestContext.CurrentContext.Result.Outcome.Status; + var files = Directory.GetFiles(testDirectory); + + if (status == TestStatus.Passed && + files.Length > 0) //удаляем в случае успешного прохождения, пишем в консоль при падении теста + { + File.Delete(files.OrderByDescending(f => new FileInfo(f).CreationTime).First()); + } + else if (status == TestStatus.Failed) + { + Console.WriteLine($"Test failed. Image saved to {testDirectory}/{TestContext.CurrentContext.Test.Name}"); + } + } + + [Test] + public void DrawCloud_ThrowsArgumentException_WhenWidthIsNonPositive() + { + var rectangles = new List { new Rectangle(0, 0, 10, 10) }; + + Action act = () => drawer.DrawCloud(rectangles, 0, 100); + + act.Should().Throw().WithMessage("Width must be positive number"); + } + + [Test] + public void DrawCloud_ThrowsArgumentException_WhenHeightIsNonPositive() + { + var rectangles = new List { new Rectangle(0, 0, 10, 10) }; + + Action act = () => drawer.DrawCloud(rectangles, 100, 0); + + act.Should().Throw().WithMessage("Height must be positive number"); + } + + + [Test] + public void DrawCloud_ReturnsBitmapWithCorrectDimensions() + { + var rectangles = new List { new Rectangle(0, 0, 10, 10) }; + var imageWidth = 100; + var imageHeight = 100; + + var bitmap = drawer.DrawCloud(rectangles, imageWidth, imageHeight); + + var fileName = TestContext.CurrentContext.Test.Name; + var format = ImageFormat.Png; + drawer.SaveToFile(bitmap, fileName, testDirectory, format); + bitmap.Width.Should().Be(imageWidth); + bitmap.Height.Should().Be(imageHeight); + } + + [Test] + public void DrawCloud_DrawsAllRectangles() + { + var rectangles = new List + { + new Rectangle(0, 0, 10, 10), + new Rectangle(20, 20, 10, 10) + }; + var imageWidth = 100; + var imageHeight = 100; + + var bitmap = drawer.DrawCloud(rectangles, imageWidth, imageHeight); + + using (var graphics = Graphics.FromImage(bitmap)) + { + foreach (var rectangle in rectangles) + { + var pixelColor = bitmap.GetPixel(rectangle.X + 1, rectangle.Y + 1); + pixelColor.Should().NotBe(Color.White); + } + } + + var fileName = TestContext.CurrentContext.Test.Name; + var format = ImageFormat.Png; + drawer.SaveToFile(bitmap, fileName, testDirectory, format); + } + + [Test] + public void SaveToFile_SavesBitmapToSpecifiedPath() + { + var bitmap = new Bitmap(10, 10); + var fileName = "test_image"; + var format = ImageFormat.Png; + + drawer.SaveToFile(bitmap, fileName, testDirectory, format); + + var fullPath = Path.Combine(testDirectory, fileName + ".png"); + File.Exists(fullPath).Should().BeTrue(); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTest/CircularCloudLayouter_Tests.cs b/cs/TagsCloudVisualizationTest/CircularCloudLayouter_Tests.cs new file mode 100644 index 000000000..7bf1aa703 --- /dev/null +++ b/cs/TagsCloudVisualizationTest/CircularCloudLayouter_Tests.cs @@ -0,0 +1,61 @@ +namespace TagsCloudVisualizationTest; + +public class CircularCloudLayouter_Tests +{ + [TestFixture] + public class CircularCloudLayouterTests + { + private CircularCloudLayouter layouter; + private IPositionCalculator calculator; + private Point center; + + [SetUp] + public void SetUp() + { + center = new Point(0, 0); + calculator = new CircularPositionCalculator(center); + layouter = new CircularCloudLayouter(center, calculator); + } + + [Test] + public void PutNextRectangle_ThrowsArgumentException_WhenWidthIsNonPositive() + { + var size = new Size(0, 10); + + Action act = () => layouter.PutNextRectangle(size); + + act.Should().Throw().WithMessage("Size width must be positive number"); + } + + [Test] + public void PutNextRectangle_ThrowsArgumentException_WhenHeightIsNonPositive() + { + var size = new Size(10, 0); + + Action act = () => layouter.PutNextRectangle(size); + + act.Should().Throw().WithMessage("Size height must be positive number"); + } + + [Test] + public void PutNextRectangle_AddsRectangleToList_WhenValidSize() + { + var size = new Size(10, 10); + + var result = layouter.PutNextRectangle(size); + + layouter.Rectangles.Should().Contain(result); + } + + [Test] + public void PutNextRectangle_ReturnsNonIntersectingRectangle_WhenRectanglesExist() + { + var size = new Size(10, 10); + layouter.PutNextRectangle(size); + + var result = layouter.PutNextRectangle(size); + + result.IntersectsWith(layouter.Rectangles[0]).Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTest/GlobalUsings.cs b/cs/TagsCloudVisualizationTest/GlobalUsings.cs new file mode 100644 index 000000000..e7901fc9b --- /dev/null +++ b/cs/TagsCloudVisualizationTest/GlobalUsings.cs @@ -0,0 +1,5 @@ +// Global using directives + +global using System.Drawing; +global using FluentAssertions; +global using TagsCloudVisualization.TagCloud; \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTest/SpiralPositionCalculator_Tests.cs b/cs/TagsCloudVisualizationTest/SpiralPositionCalculator_Tests.cs index e2389fdfe..1909040d0 100644 --- a/cs/TagsCloudVisualizationTest/SpiralPositionCalculator_Tests.cs +++ b/cs/TagsCloudVisualizationTest/SpiralPositionCalculator_Tests.cs @@ -1,21 +1,18 @@ -using System.Drawing; -using FluentAssertions; using NUnit.Framework; -using TagsCloudVisualization.TagCloud; -namespace TagsCloudVisualization.Tests +namespace TagsCloudVisualizationTests { [TestFixture] - public class SpiralPositionCalculatorTests + public class CircularPositionCalculatorTests { - private SpiralPositionCalculator calculator; + private CircularPositionCalculator calculator; private Point center; [SetUp] public void SetUp() { center = new Point(0, 0); - calculator = new SpiralPositionCalculator(center); + calculator = new CircularPositionCalculator(center); } [Test] @@ -24,39 +21,19 @@ public void CalculateNextPosition_ShouldReturnRectangleAtCenter_WhenNoRectangles var size = new Size(10, 10); var rectangles = new List(); - var result = calculator.CalculateNextPosition(rectangles, size); + var result = calculator.CalculateNextPosition(size).First(); result.Location.Should().Be(center); } - [Test] - public void CalculateNextPosition_ShouldReturnNonIntersectingRectangle_WhenRectanglesExist() - { - var size = new Size(10, 10); - var rectangles = new List - { - new Rectangle(new Point(0, 0), size) - }; - - var result = calculator.CalculateNextPosition(rectangles, size); - - result.IntersectsWith(rectangles[0]).Should().BeFalse(); - } - [Test] public void CalculateNextPosition_ShouldIncreaseOffset_WhenAngleCompletesFullCircle() { var size = new Size(10, 10); - var rectangles = new List(); - - for (int i = 0; i < 100; i++) - { - calculator.CalculateNextPosition(rectangles, size); - } - - var result = calculator.CalculateNextPosition(rectangles, size); - - result.Location.Should().NotBe(center); + var source =calculator.CalculateNextPosition(size) + .Skip(100) + .First(); + GetDistance(source.Location, center).Should().NotBe(0); } [Test] @@ -66,7 +43,7 @@ public void ValidateRectanglePosition_ShouldReturnTrue_WhenNoIntersections() var rectangles = new List(); var rectangle = new Rectangle(new Point(0, 0), size); - var result = calculator.ValidateRectanglePosition(rectangles, rectangle); + var result = calculator.IsRectanglePositionValid(rectangles, rectangle); result.Should().BeTrue(); } @@ -81,9 +58,16 @@ public void ValidateRectanglePosition_ShouldReturnFalse_WhenIntersectsWithExisti }; var rectangle = new Rectangle(new Point(0, 0), size); - var result = calculator.ValidateRectanglePosition(rectangles, rectangle); + var result = calculator.IsRectanglePositionValid(rectangles, rectangle); result.Should().BeFalse(); } + + private static double GetDistance(Point point, Point other) + { + var dx = point.X - other.X; + var dy = point.Y - other.Y; + return Math.Sqrt(dx * dx + dy * dy); + } } } \ No newline at end of file