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