diff --git a/cs/BowlingGame/BowlingGame.csproj b/cs/BowlingGame/BowlingGame.csproj
index 3316df102..8c7ba1bd8 100644
--- a/cs/BowlingGame/BowlingGame.csproj
+++ b/cs/BowlingGame/BowlingGame.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/cs/Samples/Samples.csproj b/cs/Samples/Samples.csproj
index 113a459cd..3f8baaa9c 100644
--- a/cs/Samples/Samples.csproj
+++ b/cs/Samples/Samples.csproj
@@ -10,6 +10,7 @@
+
diff --git a/cs/TagsCloudVisualization/Program.cs b/cs/TagsCloudVisualization/Program.cs
new file mode 100644
index 000000000..e96d4de27
--- /dev/null
+++ b/cs/TagsCloudVisualization/Program.cs
@@ -0,0 +1,23 @@
+using System.Drawing;
+using TagsCloudVisualization.Task_2;
+
+
+namespace TagsCloudVisualization;
+
+internal class Program
+{
+ private const string Path = "../../../../TagsCloudVisualization/Task 2";
+
+ static void Main()
+ {
+ var colors = new[] { Color.Red, Color.Green, Color.Brown, Color.Yellow, Color.Blue };
+
+ var visual = new VisualizationCloudLayout(800, 600, Color.White, colors);
+
+ visual.CreateImage(new CircularCloudLayouter(new Point(400, 300)), 175, new Size(30, 5), new Size(100, 25))
+ .Save($"{Path}/CentralСloud.png");
+
+ visual.CreateImage(new CircularCloudLayouter(new Point(250, 150)), 50, new Size(30, 5), new Size(80, 25))
+ .Save($"{Path}/SmalСloud.png");
+ }
+}
diff --git a/cs/TagsCloudVisualization/TagsCloudVisualization.csproj b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj
new file mode 100644
index 000000000..9ff6b6488
--- /dev/null
+++ b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Library
+ net8.0
+ enable
+ false
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cs/TagsCloudVisualization/Task 1,3/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/Task 1,3/CircularCloudLayouter.cs
new file mode 100644
index 000000000..348be6e03
--- /dev/null
+++ b/cs/TagsCloudVisualization/Task 1,3/CircularCloudLayouter.cs
@@ -0,0 +1,64 @@
+using System.Drawing;
+
+
+namespace TagsCloudVisualization;
+
+public class CircularCloudLayouter
+{
+ private const double AngleChangeStep = Math.PI / 180;
+ private int DistanceBetweenTurns { get; set; }
+ private int InitialRadiusOfSpiral { get; set; }
+ private double AngleOfRotationInRadians { get; set; }
+
+ private readonly LinkedList cloudOfRectangles;
+
+ public readonly Point Center;
+
+ public CircularCloudLayouter(Point center)
+ {
+ Center = center;
+ cloudOfRectangles = [];
+ DistanceBetweenTurns = 30;
+ }
+
+ public Rectangle PutNextRectangle(Size rectangleSize)
+ {
+ var halfOfMinSide = Math.Min(rectangleSize.Width, rectangleSize.Height) / 2;
+ DistanceBetweenTurns = Math.Min(DistanceBetweenTurns, halfOfMinSide);
+
+ if (cloudOfRectangles.Count == 0) InitialRadiusOfSpiral = halfOfMinSide;
+
+ var rectangle = ChooseLocationForRectangle(rectangleSize);
+ cloudOfRectangles.AddFirst(rectangle);
+
+ return rectangle;
+ }
+
+ private Rectangle ChooseLocationForRectangle(Size rectangleSize)
+ {
+ var currentPoint = GetNewPoint();
+ var rectangle = GetNewRectangle(currentPoint, rectangleSize);
+
+ while (cloudOfRectangles.Any(rect => rect.IntersectsWith(rectangle)))
+ {
+ AngleOfRotationInRadians += AngleChangeStep;
+ currentPoint = GetNewPoint();
+ rectangle = GetNewRectangle(currentPoint, rectangleSize);
+ }
+
+ return rectangle;
+ }
+
+ private Rectangle GetNewRectangle(Point centerPoint, Size rectangleSize) =>
+ new(new(centerPoint.X - rectangleSize.Width / 2,
+ centerPoint.Y - rectangleSize.Height / 2), rectangleSize);
+
+ private Point GetNewPoint()
+ {
+ var coefficient = InitialRadiusOfSpiral + AngleOfRotationInRadians * DistanceBetweenTurns;
+ var x = coefficient * Math.Cos(AngleOfRotationInRadians) + Center.X;
+ var y = coefficient * Math.Sin(AngleOfRotationInRadians) + Center.Y;
+
+ return new((int)x, (int)y);
+ }
+}
diff --git a/cs/TagsCloudVisualization/Task 1,3/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Task 1,3/CircularCloudLayouterTests.cs
new file mode 100644
index 000000000..200a663f5
--- /dev/null
+++ b/cs/TagsCloudVisualization/Task 1,3/CircularCloudLayouterTests.cs
@@ -0,0 +1,82 @@
+using FluentAssertions;
+using NUnit.Framework;
+using NUnit.Framework.Interfaces;
+using System.Drawing;
+using TagsCloudVisualization.Task_2;
+
+
+namespace TagsCloudVisualization;
+
+
+[TestFixture]
+public class CircularCloudLayouterTests
+{
+ private readonly List listRectangles = [];
+
+ [Test]
+ public void CircularCloudLayouter_CorrectInitialization_NoExceptions()
+ {
+ var createAConstructor = () => new CircularCloudLayouter(new Point(960, 540));
+
+ createAConstructor
+ .Should()
+ .NotThrow();
+ }
+
+ [Test]
+ public void PutNextRectangle_RandomSizes_MustBeRightSize()
+ {
+ var cloud = new CircularCloudLayouter(new Point(960, 540));
+ var random = new Random();
+
+ for (var i = 0; i < 50; i++)
+ {
+ var width = random.Next(30, 200);
+ var actualSize = new Size(width, random.Next(width / 6, width / 3));
+
+ var rectangle = cloud.PutNextRectangle(actualSize);
+
+ actualSize
+ .Should()
+ .Be(rectangle.Size);
+ }
+ }
+
+ [Test]
+ public void PutNextRectangle_RandomSizes_ShouldNotIntersect()
+ {
+ var cloudLayouter = new CircularCloudLayouter(new Point(960, 540));
+ var random = new Random();
+
+ for (int i = 0; i < 100; i++)
+ {
+ var width = random.Next(30, 200);
+
+ var rectangle = cloudLayouter.PutNextRectangle(new(width, random.Next(width / 6, width / 3)));
+
+ listRectangles.Any(rect => rect.IntersectsWith(rectangle))
+ .Should()
+ .BeFalse("Прямоугольники не должны пересекаться");
+
+ listRectangles.Add(rectangle);
+ }
+ }
+
+ [TearDown]
+ public void CreateReportInCaseOfAnError()
+ {
+ if (TestContext.CurrentContext.Result.Outcome == ResultState.Failure)
+ {
+ var colors = new[] { Color.Red, Color.Green, Color.Brown, Color.Yellow, Color.Blue };
+ var path = "../../../../TagsCloudVisualization/TestErrorReports/сloud.png";
+ var visual = new VisualizationCloudLayout(1920, 1080, Color.White, colors);
+
+ visual.CreateImage(listRectangles)
+ .Save(path);
+
+ Console.WriteLine($"Tag cloud visualization saved to file {Path.GetFullPath(path)}");
+ }
+
+ listRectangles.Clear();
+ }
+}
diff --git "a/cs/TagsCloudVisualization/Task 2/Central\320\241loud.png" "b/cs/TagsCloudVisualization/Task 2/Central\320\241loud.png"
new file mode 100644
index 000000000..536450333
Binary files /dev/null and "b/cs/TagsCloudVisualization/Task 2/Central\320\241loud.png" differ
diff --git a/cs/TagsCloudVisualization/Task 2/CloudLayout.cs b/cs/TagsCloudVisualization/Task 2/CloudLayout.cs
new file mode 100644
index 000000000..3cd646ef1
--- /dev/null
+++ b/cs/TagsCloudVisualization/Task 2/CloudLayout.cs
@@ -0,0 +1,33 @@
+using System.Drawing;
+
+namespace TagsCloudVisualization.Task_2;
+
+public static class CloudLayout
+{
+ public static IEnumerable GenerateCloudLayout(
+ int amountRectangles,
+ Size minSize,
+ Size maxSize,
+ CircularCloudLayouter cloudLayouter)
+ => GenerateRectangleSizes(amountRectangles, minSize, maxSize)
+ .Select(cloudLayouter.PutNextRectangle);
+
+ private static IEnumerable GenerateRectangleSizes(
+ int amountData,
+ Size minSize,
+ Size maxSize)
+ {
+ if (minSize.Width > maxSize.Width || minSize.Height > maxSize.Width)
+ throw new ArgumentException("Минимальные размеры не могут быть больше максимальных");
+
+ var random = new Random();
+
+ for (var i = 0; i < amountData; i++)
+ {
+ var width = random.Next(minSize.Width, maxSize.Width + 1);
+ var height = random.Next(minSize.Height, maxSize.Height + 1);
+
+ yield return new Size(width, height);
+ }
+ }
+}
diff --git a/cs/TagsCloudVisualization/Task 2/README.md b/cs/TagsCloudVisualization/Task 2/README.md
new file mode 100644
index 000000000..9d0c10a6e
--- /dev/null
+++ b/cs/TagsCloudVisualization/Task 2/README.md
@@ -0,0 +1,4 @@
+Раскладка 1
+![CentralСloud](CentralСloud.png)
+Раскладка 2
+![SmalСloud](SmalСloud.png)
\ No newline at end of file
diff --git "a/cs/TagsCloudVisualization/Task 2/Smal\320\241loud.png" "b/cs/TagsCloudVisualization/Task 2/Smal\320\241loud.png"
new file mode 100644
index 000000000..c5053674f
Binary files /dev/null and "b/cs/TagsCloudVisualization/Task 2/Smal\320\241loud.png" differ
diff --git a/cs/TagsCloudVisualization/Task 2/VisualizationCloudLayout.cs b/cs/TagsCloudVisualization/Task 2/VisualizationCloudLayout.cs
new file mode 100644
index 000000000..990b55e9b
--- /dev/null
+++ b/cs/TagsCloudVisualization/Task 2/VisualizationCloudLayout.cs
@@ -0,0 +1,59 @@
+using Newtonsoft.Json.Linq;
+using System.Drawing;
+
+namespace TagsCloudVisualization.Task_2;
+
+public class VisualizationCloudLayout
+{
+ private readonly int width;
+ private readonly int height;
+ private readonly Color backgroundColor;
+ private readonly Color[] rectanglePalette;
+
+ public VisualizationCloudLayout(int width, int height, Color backgroundColor, IEnumerable rectanglePalette)
+ {
+ this.width = width;
+ this.height = height;
+ this.backgroundColor = backgroundColor;
+ this.rectanglePalette = rectanglePalette.ToArray();
+ }
+
+ public Bitmap CreateImage(CircularCloudLayouter cloudLayouter, int amountRectangles,
+ Size minSize, Size maxSize)
+ {
+ var image = new Bitmap(width, height);
+ var rectangles = CloudLayout.GenerateCloudLayout(
+ amountRectangles,
+ minSize,
+ maxSize,
+ cloudLayouter);
+
+ DrawCloudLayout(Graphics.FromImage(image), rectangles);
+
+ return image;
+ }
+
+ public Bitmap CreateImage(IEnumerable rectangles)
+ {
+ var image = new Bitmap(width, height);
+
+ DrawCloudLayout(Graphics.FromImage(image), rectangles);
+
+ return image;
+ }
+
+ private void DrawCloudLayout(Graphics graphics, IEnumerable rectangles)
+ {
+ var random = new Random();
+
+ graphics.FillRectangle(new SolidBrush(backgroundColor), 0, 0, width, height);
+
+ foreach (var rect in rectangles)
+ {
+ var color = rectanglePalette[random.Next(rectanglePalette.Length)];
+
+ graphics.FillRectangle(new SolidBrush(color), rect);
+ graphics.DrawRectangle(new Pen(Color.Black, 1), rect);
+ }
+ }
+}
diff --git a/cs/tdd.sln b/cs/tdd.sln
index c8f523d63..182027f76 100644
--- a/cs/tdd.sln
+++ b/cs/tdd.sln
@@ -1,11 +1,13 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.25123.0
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35312.102
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BowlingGame", "BowlingGame\BowlingGame.csproj", "{AD0F018A-732E-4074-8527-AB2EEC8D0BF3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BowlingGame", "BowlingGame\BowlingGame.csproj", "{AD0F018A-732E-4074-8527-AB2EEC8D0BF3}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudVisualization", "TagsCloudVisualization\TagsCloudVisualization.csproj", "{1C4D8E05-ABDC-43A6-BFBA-741A8435AF31}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -21,8 +23,15 @@ 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
+ {1C4D8E05-ABDC-43A6-BFBA-741A8435AF31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1C4D8E05-ABDC-43A6-BFBA-741A8435AF31}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1C4D8E05-ABDC-43A6-BFBA-741A8435AF31}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1C4D8E05-ABDC-43A6-BFBA-741A8435AF31}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {460B15EA-8A79-4B9D-AE55-4F1224612C1D}
+ EndGlobalSection
EndGlobal
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