diff --git a/config.json b/config.json
index dc508066b..1161b97c3 100644
--- a/config.json
+++ b/config.json
@@ -2148,6 +2148,21 @@
],
"difficulty": 4
},
+ {
+ "slug": "prism",
+ "name": "Prism",
+ "uuid": "c52d3d36-4852-48ff-b97c-0b030a5b365d",
+ "practices": [
+ "arrays",
+ "floating-point-numbers",
+ "while-loops"
+ ],
+ "prerequisites": [
+ "arrays",
+ "floating-point-numbers"
+ ],
+ "difficulty": 4
+ },
{
"slug": "twelve-days",
"name": "Twelve Days",
diff --git a/exercises/Exercises.slnx b/exercises/Exercises.slnx
index cdbda49b1..898746ea4 100644
--- a/exercises/Exercises.slnx
+++ b/exercises/Exercises.slnx
@@ -129,6 +129,7 @@
+
diff --git a/exercises/practice/prism/.docs/instructions.md b/exercises/practice/prism/.docs/instructions.md
new file mode 100644
index 000000000..13cefae8c
--- /dev/null
+++ b/exercises/practice/prism/.docs/instructions.md
@@ -0,0 +1,38 @@
+# Instructions
+
+Before activating the laser array, you must predict the exact order in which crystals will be hit, identified by their sample IDs.
+
+## Example Test Case
+
+Consider this crystal array configuration:
+
+```json
+{
+ "start": { "x": 0, "y": 0, "angle": 0 },
+ "prisms": [
+ { "id": 1, "x": 10, "y": 10, "angle": -90 },
+ { "id": 2, "x": 10, "y": 0, "angle": 90 },
+ { "id": 3, "x": 30, "y": 10, "angle": 45 },
+ { "id": 4, "x": 20, "y": 0, "angle": 0 }
+ ]
+}
+```
+
+## What's Happening
+
+The laser starts at the origin `(0, 0)` and fires horizontally to the right at angle 0°.
+Here's the step-by-step beam path:
+
+**Step 1**: The beam travels along the x-axis (y = 0) and first encounters **Crystal #2** at position `(10, 0)`.
+This crystal has a refraction angle of 90°, which means it bends the beam perpendicular to its current path.
+The beam, originally traveling at 0°, is now redirected to 90° (straight up).
+
+**Step 2**: The beam now travels vertically upward from position `(10, 0)` and strikes **Crystal #1** at position `(10, 10)`.
+This crystal has a refraction angle of -90°, bending the beam by -90° relative to its current direction.
+The beam was traveling at 90°, so after refraction it's now at 0° (90° + (-90°) = 0°), traveling horizontally to the right again.
+
+**Step 3**: From position `(10, 10)`, the beam travels horizontally and encounters **Crystal #3** at position `(30, 10)`.
+This crystal refracts the beam by 45°, changing its direction to 45°.
+The beam continues into empty space beyond the array.
+
+
diff --git a/exercises/practice/prism/.docs/introduction.md b/exercises/practice/prism/.docs/introduction.md
new file mode 100644
index 000000000..bfa7ed72e
--- /dev/null
+++ b/exercises/practice/prism/.docs/introduction.md
@@ -0,0 +1,5 @@
+# Introduction
+
+You're a researcher at **PRISM** (Precariously Redirected Illumination Safety Management), working with a precision laser calibration system that tests experimental crystal prisms.
+These crystals are being developed for next-generation optical computers, and each one has unique refractive properties based on its molecular structure.
+The lab's laser system can damage crystals if they receive unexpected illumination, so precise path prediction is critical.
diff --git a/exercises/practice/prism/.editorconfig b/exercises/practice/prism/.editorconfig
new file mode 100644
index 000000000..8e51769a9
--- /dev/null
+++ b/exercises/practice/prism/.editorconfig
@@ -0,0 +1,141 @@
+###############################
+# Core EditorConfig Options #
+###############################
+
+; This file is for unifying the coding style for different editors and IDEs.
+; More information at:
+; https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference?view=vs-2017
+; https://docs.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options?view=vs-2017
+
+root = true
+
+[*]
+indent_style = space
+
+[ProteinTranslation.cs]
+indent_size = 4
+
+###############################
+# .NET Coding Conventions #
+###############################
+
+# Organize usings
+dotnet_sort_system_directives_first = true
+dotnet_separate_import_directive_groups = true
+
+# this. preferences
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = always:suggestion
+dotnet_style_readonly_field = true:suggestion
+
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+
+###############################
+# Naming Conventions #
+###############################
+
+# Style Definitions
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
+# Use PascalCase for constant fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_symbols.constant_fields.applicable_kinds = field
+dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+
+###############################
+# C# Code Style Rules #
+###############################
+
+# var preferences
+csharp_style_var_for_built_in_types = true:none
+csharp_style_var_when_type_is_apparent = true:none
+csharp_style_var_elsewhere = true:none
+
+# Expression-bodied members
+csharp_style_expression_bodied_methods = true:suggestion
+csharp_style_expression_bodied_constructors = true:suggestion
+csharp_style_expression_bodied_operators = true:suggestion
+csharp_style_expression_bodied_properties = true:suggestion
+csharp_style_expression_bodied_indexers = true:suggestion
+csharp_style_expression_bodied_accessors = true:suggestion
+
+# Pattern-matching preferences
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+
+# Null-checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Modifier preferences
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
+
+# Expression-level preferences
+csharp_prefer_braces = true:none
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_pattern_local_over_anonymous_function = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+
+###############################
+# C# Formatting Rules #
+###############################
+
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = false
+csharp_new_line_before_members_in_anonymous_types = false
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = flush_left
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
diff --git a/exercises/practice/prism/.meta/Example.cs b/exercises/practice/prism/.meta/Example.cs
new file mode 100644
index 000000000..b6a837dad
--- /dev/null
+++ b/exercises/practice/prism/.meta/Example.cs
@@ -0,0 +1,54 @@
+public static class Prism
+{
+ public readonly record struct LaserInfo(double X, double Y, double Angle);
+
+ public readonly record struct PrismInfo(int Id, double X, double Y, double Angle);
+
+ public static int[] FindSequence(LaserInfo laser, PrismInfo[] prisms)
+ {
+ var x = laser.X;
+ var y = laser.Y;
+ var angle = laser.Angle;
+ List sequence = new();
+
+ while (true)
+ {
+ var rad = double.DegreesToRadians(angle);
+ var dirX = double.Cos(rad);
+ var dirY = double.Sin(rad);
+
+ PrismInfo? nearest = null;
+ var nearestDist = double.PositiveInfinity;
+
+ foreach (var prism in prisms)
+ {
+ var dx = prism.X - x;
+ var dy = prism.Y - y;
+
+ var dist = dx * dirX + dy * dirY;
+ if (dist <= 1e-6)
+ continue;
+
+ var crossSq = double.Pow(dx - dist * dirX, 2) + double.Pow(dy - dist * dirY, 2);
+ if (crossSq >= 1e-6 * double.Max(1, dist * dist))
+ continue;
+
+ if (dist < nearestDist)
+ {
+ nearestDist = dist;
+ nearest = prism;
+ }
+ }
+
+ if (nearest is not { } hit)
+ break;
+
+ sequence.Add(hit.Id);
+ x = hit.X;
+ y = hit.Y;
+ angle = (angle + hit.Angle) % 360;
+ }
+
+ return sequence.ToArray();
+ }
+}
diff --git a/exercises/practice/prism/.meta/Generator.tpl b/exercises/practice/prism/.meta/Generator.tpl
new file mode 100644
index 000000000..95a33fc48
--- /dev/null
+++ b/exercises/practice/prism/.meta/Generator.tpl
@@ -0,0 +1,22 @@
+public class {{ testClass }}
+{
+ {{- for test in tests }}
+ [Fact{{ if !for.first }}(Skip = "Remove this Skip property to run this test"){{ end }}]
+ public void {{ test.testMethod }}()
+ {
+ Prism.LaserInfo laser = new({{ test.input.start.x }}, {{ test.input.start.y }}, {{ test.input.start.angle }});
+ {{- if test.input.prisms.empty? }}
+ Prism.PrismInfo[] prisms = [];
+ {{- else }}
+ Prism.PrismInfo[] prisms =
+ [
+ {{- for p in test.input.prisms }}
+ new({{ p.id }}, {{ p.x }}, {{ p.y }}, {{ p.angle }}){{ if !for.last }},{{ end }}
+ {{- end }}
+ ];
+ {{- end }}
+ int[] expected = [{{ array.join test.expected.sequence ", " }}];
+ Assert.Equal(expected, {{ testedClass }}.{{ test.testedMethod }}(laser, prisms));
+ }
+ {{ end -}}
+}
diff --git a/exercises/practice/prism/.meta/config.json b/exercises/practice/prism/.meta/config.json
new file mode 100644
index 000000000..42fb82b15
--- /dev/null
+++ b/exercises/practice/prism/.meta/config.json
@@ -0,0 +1,22 @@
+{
+ "authors": [
+ "BNAndras"
+ ],
+ "files": {
+ "solution": [
+ "Prism.cs"
+ ],
+ "test": [
+ "PrismTests.cs"
+ ],
+ "example": [
+ ".meta/Example.cs"
+ ],
+ "invalidator": [
+ "Prism.csproj"
+ ]
+ },
+ "blurb": "Calculate the path of a laser through refractive prisms.",
+ "source": "FraSanga",
+ "source_url": "https://github.com/exercism/problem-specifications/pull/2625"
+}
diff --git a/exercises/practice/prism/.meta/tests.toml b/exercises/practice/prism/.meta/tests.toml
new file mode 100644
index 000000000..b00222383
--- /dev/null
+++ b/exercises/practice/prism/.meta/tests.toml
@@ -0,0 +1,52 @@
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[ec65d3b3-f7bf-4015-8156-0609c141c4c4]
+description = "zero prisms"
+
+[ec0ca17c-0c5f-44fb-89ba-b76395bdaf1c]
+description = "one prism one hit"
+
+[0db955f2-0a27-4c82-ba67-197bd6202069]
+description = "one prism zero hits"
+
+[8d92485b-ebc0-4ee9-9b88-cdddb16b52da]
+description = "going up zero hits"
+
+[78295b3c-7438-492d-8010-9c63f5c223d7]
+description = "going down zero hits"
+
+[acc723ea-597b-4a50-8d1b-b980fe867d4c]
+description = "going left zero hits"
+
+[3f19b9df-9eaa-4f18-a2db-76132f466d17]
+description = "negative angle"
+
+[96dacffb-d821-4cdf-aed8-f152ce063195]
+description = "large angle"
+
+[513a7caa-957f-4c5d-9820-076842de113c]
+description = "upward refraction two hits"
+
+[d452b7c7-9761-4ea9-81a9-2de1d73eb9ef]
+description = "downward refraction two hits"
+
+[be1a2167-bf4c-4834-acc9-e4d68e1a0203]
+description = "same prism twice"
+
+[df5a60dd-7c7d-4937-ac4f-c832dae79e2e]
+description = "simple path"
+
+[8d9a3cc8-e846-4a3b-a137-4bfc4aa70bd1]
+description = "multiple prisms floating point precision"
+
+[e077fc91-4e4a-46b3-a0f5-0ba00321da56]
+description = "complex path with multiple prisms floating point precision"
diff --git a/exercises/practice/prism/Prism.cs b/exercises/practice/prism/Prism.cs
new file mode 100644
index 000000000..6b25c2849
--- /dev/null
+++ b/exercises/practice/prism/Prism.cs
@@ -0,0 +1,11 @@
+public static class Prism
+{
+ public readonly record struct LaserInfo(double X, double Y, double Angle);
+
+ public readonly record struct PrismInfo(int Id, double X, double Y, double Angle);
+
+ public static int[] FindSequence(LaserInfo laser, PrismInfo[] prisms)
+ {
+ throw new NotImplementedException("You need to implement this method.");
+ }
+}
diff --git a/exercises/practice/prism/Prism.csproj b/exercises/practice/prism/Prism.csproj
new file mode 100644
index 000000000..b03c847b8
--- /dev/null
+++ b/exercises/practice/prism/Prism.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net10.0
+ Exe
+ enable
+ enable
+ true
+ Exercism
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/exercises/practice/prism/PrismTests.cs b/exercises/practice/prism/PrismTests.cs
new file mode 100644
index 000000000..b93fd4105
--- /dev/null
+++ b/exercises/practice/prism/PrismTests.cs
@@ -0,0 +1,293 @@
+public class PrismTests
+{
+ [Fact]
+ public void Zero_prisms()
+ {
+ Prism.LaserInfo laser = new(0, 0, 0);
+ Prism.PrismInfo[] prisms = [];
+ int[] expected = [];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void One_prism_one_hit()
+ {
+ Prism.LaserInfo laser = new(0, 0, 0);
+ Prism.PrismInfo[] prisms =
+ [
+ new(1, 10, 0, 0)
+ ];
+ int[] expected = [1];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void One_prism_zero_hits()
+ {
+ Prism.LaserInfo laser = new(0, 0, 0);
+ Prism.PrismInfo[] prisms =
+ [
+ new(1, -10, 0, 0)
+ ];
+ int[] expected = [];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Going_up_zero_hits()
+ {
+ Prism.LaserInfo laser = new(0, 0, 90);
+ Prism.PrismInfo[] prisms =
+ [
+ new(3, 0, -10, 0),
+ new(1, -10, 0, 0),
+ new(2, 10, 0, 0)
+ ];
+ int[] expected = [];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Going_down_zero_hits()
+ {
+ Prism.LaserInfo laser = new(0, 0, -90);
+ Prism.PrismInfo[] prisms =
+ [
+ new(1, 10, 0, 0),
+ new(2, 0, 10, 0),
+ new(3, -10, 0, 0)
+ ];
+ int[] expected = [];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Going_left_zero_hits()
+ {
+ Prism.LaserInfo laser = new(0, 0, 180);
+ Prism.PrismInfo[] prisms =
+ [
+ new(2, 0, 10, 0),
+ new(3, 10, 0, 0),
+ new(1, 0, -10, 0)
+ ];
+ int[] expected = [];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Negative_angle()
+ {
+ Prism.LaserInfo laser = new(0, 0, -180);
+ Prism.PrismInfo[] prisms =
+ [
+ new(1, 0, -10, 0),
+ new(2, 0, 10, 0),
+ new(3, 10, 0, 0)
+ ];
+ int[] expected = [];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Large_angle()
+ {
+ Prism.LaserInfo laser = new(0, 0, 2340);
+ Prism.PrismInfo[] prisms =
+ [
+ new(1, 10, 0, 0)
+ ];
+ int[] expected = [];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Upward_refraction_two_hits()
+ {
+ Prism.LaserInfo laser = new(0, 0, 0);
+ Prism.PrismInfo[] prisms =
+ [
+ new(1, 10, 10, 0),
+ new(2, 10, 0, 90)
+ ];
+ int[] expected = [2, 1];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Downward_refraction_two_hits()
+ {
+ Prism.LaserInfo laser = new(0, 0, 0);
+ Prism.PrismInfo[] prisms =
+ [
+ new(1, 10, 0, -90),
+ new(2, 10, -10, 0)
+ ];
+ int[] expected = [1, 2];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Same_prism_twice()
+ {
+ Prism.LaserInfo laser = new(0, 0, 0);
+ Prism.PrismInfo[] prisms =
+ [
+ new(2, 10, 0, 0),
+ new(1, 20, 0, -180)
+ ];
+ int[] expected = [2, 1, 2];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Simple_path()
+ {
+ Prism.LaserInfo laser = new(0, 0, 0);
+ Prism.PrismInfo[] prisms =
+ [
+ new(3, 30, 10, 45),
+ new(1, 10, 10, -90),
+ new(2, 10, 0, 90),
+ new(4, 20, 0, 0)
+ ];
+ int[] expected = [2, 1, 3];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Multiple_prisms_floating_point_precision()
+ {
+ Prism.LaserInfo laser = new(0, 0, -6.429);
+ Prism.PrismInfo[] prisms =
+ [
+ new(26, 5.8, 73.4, 6.555),
+ new(24, 36.2, 65.2, -0.304),
+ new(20, 20.4, 82.8, 45.17),
+ new(31, -20.2, 48.8, 30.615),
+ new(30, 24.0, 0.6, 28.771),
+ new(29, 31.4, 79.4, 61.327),
+ new(28, 36.4, 31.4, -18.157),
+ new(22, 47.0, 57.8, 54.745),
+ new(38, 36.4, 79.2, 49.05),
+ new(10, 37.8, 55.2, 11.978),
+ new(18, -26.0, 42.6, 22.661),
+ new(25, 38.8, 76.2, 51.958),
+ new(2, 0.0, 42.4, -21.817),
+ new(35, 21.4, 44.8, -171.579),
+ new(7, 14.2, -1.6, 19.081),
+ new(33, 11.2, 44.4, -165.941),
+ new(11, 15.4, 82.6, 66.262),
+ new(16, 30.8, 6.6, 35.852),
+ new(15, -3.0, 79.2, 53.782),
+ new(4, 29.0, 75.4, 17.016),
+ new(23, 41.6, 59.8, 70.763),
+ new(8, -10.0, 15.8, -9.24),
+ new(13, 48.6, 51.8, 45.812),
+ new(1, 13.2, 77.0, 17.937),
+ new(34, -8.8, 36.8, -4.199),
+ new(21, 24.4, 75.8, 20.783),
+ new(17, -4.4, 74.6, 24.709),
+ new(9, 30.8, 41.8, -165.413),
+ new(32, 4.2, 78.6, 40.892),
+ new(37, -15.8, 47.0, 33.29),
+ new(6, 1.0, 80.6, 51.295),
+ new(36, -27.0, 47.8, 92.52),
+ new(14, -2.0, 34.4, -52.001),
+ new(5, 23.2, 80.2, 31.866),
+ new(27, -5.6, 32.8, -75.303),
+ new(12, -1.0, 0.2, 0.0),
+ new(3, -6.6, 3.2, 46.72),
+ new(19, -13.8, 24.2, -9.205)
+ ];
+ int[] expected = [7, 30, 16, 28, 13, 22, 23, 10, 9, 24, 25, 38, 29, 4, 35, 21, 5, 20, 11, 1, 33, 26, 32, 6, 15, 17, 2, 14, 27, 34, 37, 31, 36, 18, 19, 8, 3, 12];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+
+ [Fact(Skip = "Remove this Skip property to run this test")]
+ public void Complex_path_with_multiple_prisms_floating_point_precision()
+ {
+ Prism.LaserInfo laser = new(0, 0, 0.0);
+ Prism.PrismInfo[] prisms =
+ [
+ new(46, 37.4, 20.6, -88.332),
+ new(72, -24.2, 23.4, -90.774),
+ new(25, 78.6, 7.8, 98.562),
+ new(60, -58.8, 31.6, 115.56),
+ new(22, 75.2, 28.0, 63.515),
+ new(2, 89.8, 27.8, 91.176),
+ new(23, 9.8, 30.8, 30.829),
+ new(69, 22.8, 20.6, -88.315),
+ new(44, -0.8, 15.6, -116.565),
+ new(36, -24.2, 8.2, -90.0),
+ new(53, -1.2, 0.0, 0.0),
+ new(52, 14.2, 24.0, -143.896),
+ new(5, -65.2, 21.6, 93.128),
+ new(66, 5.4, 15.6, 31.608),
+ new(51, -72.6, 21.0, -100.976),
+ new(65, 48.0, 10.2, 87.455),
+ new(21, -41.8, 0.0, 68.352),
+ new(18, -46.2, 19.2, -128.362),
+ new(10, 74.4, 0.4, 90.939),
+ new(15, 67.6, 0.4, 84.958),
+ new(35, 14.8, -0.4, 89.176),
+ new(1, 83.0, 0.2, 89.105),
+ new(68, 14.6, 28.0, -29.867),
+ new(67, 79.8, 18.6, -136.643),
+ new(38, 53.0, 14.6, -90.848),
+ new(31, -58.0, 6.6, -61.837),
+ new(74, -30.8, 0.4, 85.966),
+ new(48, -4.6, 10.0, -161.222),
+ new(12, 59.0, 5.0, -91.164),
+ new(33, -16.4, 18.4, 90.734),
+ new(4, 82.6, 27.6, 71.127),
+ new(75, -10.2, 30.6, -1.108),
+ new(28, 38.0, 0.0, 86.863),
+ new(11, 64.4, -0.2, 92.353),
+ new(9, -51.4, 31.6, 67.249),
+ new(26, -39.8, 30.8, 61.113),
+ new(30, -34.2, 0.6, 111.33),
+ new(56, -51.0, 0.2, 70.445),
+ new(41, -12.0, 0.0, 91.219),
+ new(24, 63.8, 14.4, 86.586),
+ new(70, -72.8, 13.4, -87.238),
+ new(3, 22.4, 7.0, -91.685),
+ new(13, 34.4, 7.0, 90.0),
+ new(16, -47.4, 11.4, -136.02),
+ new(6, 90.0, 0.2, 90.415),
+ new(54, 44.0, 27.8, 85.969),
+ new(32, -9.0, 0.0, 91.615),
+ new(8, -31.6, 30.8, 0.535),
+ new(39, -12.0, 8.2, 90.0),
+ new(14, -79.6, 32.4, 92.342),
+ new(42, 65.8, 20.8, -85.867),
+ new(40, -65.0, 14.0, 87.109),
+ new(45, 10.6, 18.8, 23.697),
+ new(71, -24.2, 18.6, -88.531),
+ new(7, -72.6, 6.4, -89.148),
+ new(62, -32.0, 24.8, -140.8),
+ new(49, 34.4, -0.2, 89.415),
+ new(63, 74.2, 12.6, -138.429),
+ new(59, 82.8, 13.0, -140.177),
+ new(34, -9.4, 23.2, -88.238),
+ new(76, -57.6, 0.0, 1.2),
+ new(43, 7.0, 0.0, 116.565),
+ new(20, 45.8, -0.2, 1.469),
+ new(37, -16.6, 13.2, 84.785),
+ new(58, -79.0, -0.2, 89.481),
+ new(50, -24.2, 12.8, -86.987),
+ new(64, 59.2, 10.2, -92.203),
+ new(61, -72.0, 26.4, -83.66),
+ new(47, 45.4, 5.8, -82.992),
+ new(17, -52.2, 17.8, -52.938),
+ new(57, -61.8, 32.0, 84.627),
+ new(29, 47.2, 28.2, 92.954),
+ new(27, -4.6, 0.2, 87.397),
+ new(55, -61.4, 26.4, 94.086),
+ new(73, -40.4, 13.4, -62.229),
+ new(19, 53.2, 20.6, -87.181)
+ ];
+ int[] expected = [43, 44, 66, 45, 52, 35, 49, 13, 3, 69, 46, 28, 20, 11, 24, 38, 19, 42, 15, 10, 63, 25, 59, 1, 6, 2, 4, 67, 22, 29, 65, 64, 12, 47, 54, 68, 23, 75, 8, 26, 18, 9, 60, 17, 31, 7, 70, 40, 5, 51, 61, 55, 57, 14, 58, 76, 56, 16, 21, 30, 73, 62, 74, 41, 39, 36, 50, 37, 33, 71, 72, 34, 32, 27, 48, 53];
+ Assert.Equal(expected, Prism.FindSequence(laser, prisms));
+ }
+}
diff --git a/exercises/practice/prism/packages.lock.json b/exercises/practice/prism/packages.lock.json
new file mode 100644
index 000000000..376ba63d1
--- /dev/null
+++ b/exercises/practice/prism/packages.lock.json
@@ -0,0 +1,177 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0": {
+ "Exercism.Tests.xunit.v3": {
+ "type": "Direct",
+ "requested": "[0.1.0-beta1, )",
+ "resolved": "0.1.0-beta1",
+ "contentHash": "XjVtQWWxmHDDj7UMdkPKpBFFKnsW0tkBhlyJSfFFh+fWwGemyyJwJYhdsvWhiKKCY7zItB+mI/o0OQtOKQxUhA==",
+ "dependencies": {
+ "xunit.v3.extensibility.core": "1.1.0"
+ }
+ },
+ "Microsoft.NET.Test.Sdk": {
+ "type": "Direct",
+ "requested": "[18.3.0, )",
+ "resolved": "18.3.0",
+ "contentHash": "xW3kXuWRQtgoxJp4J+gdhHSQyK+6Wb/AZDSd7lMvuMRYlZ1tnpkojyfZlWilB5G4dmZ0Y0ZxU/M23TlubndNkw==",
+ "dependencies": {
+ "Microsoft.CodeCoverage": "18.3.0",
+ "Microsoft.TestPlatform.TestHost": "18.3.0"
+ }
+ },
+ "xunit.runner.visualstudio": {
+ "type": "Direct",
+ "requested": "[3.1.5, )",
+ "resolved": "3.1.5",
+ "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA=="
+ },
+ "xunit.v3": {
+ "type": "Direct",
+ "requested": "[3.2.2, )",
+ "resolved": "3.2.2",
+ "contentHash": "L+4/4y0Uqcg8/d6hfnxhnwh4j9FaeULvefTwrk30rr1o4n/vdPfyUQ8k0yzH8VJx7bmFEkDdcRfbtbjEHlaYcA==",
+ "dependencies": {
+ "xunit.v3.mtp-v1": "[3.2.2]"
+ }
+ },
+ "Microsoft.ApplicationInsights": {
+ "type": "Transitive",
+ "resolved": "2.23.0",
+ "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw=="
+ },
+ "Microsoft.Bcl.AsyncInterfaces": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg=="
+ },
+ "Microsoft.CodeCoverage": {
+ "type": "Transitive",
+ "resolved": "18.3.0",
+ "contentHash": "23BNy/vziREC20Wwhb50K7+kZe0m07KlLWDQv4qjJ9tt3QjpDpDIqJFrhYHmMEo9xDkuSp55U/8h4bMF7MiB+g=="
+ },
+ "Microsoft.Testing.Extensions.Telemetry": {
+ "type": "Transitive",
+ "resolved": "1.9.1",
+ "contentHash": "No5AudZMmSb+uNXjlgL2y3/stHD2IT4uxqc5yHwkE+/nNux9jbKcaJMvcp9SwgP4DVD8L9/P3OUz8mmmcvEIdQ==",
+ "dependencies": {
+ "Microsoft.ApplicationInsights": "2.23.0",
+ "Microsoft.Testing.Platform": "1.9.1"
+ }
+ },
+ "Microsoft.Testing.Extensions.TrxReport.Abstractions": {
+ "type": "Transitive",
+ "resolved": "1.9.1",
+ "contentHash": "AL46Xe1WBi85Ntd4mNPvat5ZSsZ2uejiVqoKCypr8J3wK0elA5xJ3AN4G/Q4GIwzUFnggZoH/DBjnr9J18IO/g==",
+ "dependencies": {
+ "Microsoft.Testing.Platform": "1.9.1"
+ }
+ },
+ "Microsoft.Testing.Platform": {
+ "type": "Transitive",
+ "resolved": "1.9.1",
+ "contentHash": "QafNtNSmEI0zazdebnsIkDKmFtTSpmx/5PLOjURWwozcPb3tvRxzosQSL8xwYNM1iPhhKiBksXZyRSE2COisrA=="
+ },
+ "Microsoft.Testing.Platform.MSBuild": {
+ "type": "Transitive",
+ "resolved": "1.9.1",
+ "contentHash": "oTUtyR4X/s9ytuiNA29FGsNCCH0rNmY5Wdm14NCKLjTM1cT9edVSlA+rGS/mVmusPqcP0l/x9qOnMXg16v87RQ==",
+ "dependencies": {
+ "Microsoft.Testing.Platform": "1.9.1"
+ }
+ },
+ "Microsoft.TestPlatform.ObjectModel": {
+ "type": "Transitive",
+ "resolved": "18.3.0",
+ "contentHash": "AEIEX2aWdPO9XbtR96eBaJxmXRD9vaI9uQ1T/JbPEKlTAZwYx0ZrMzKyULMdh/HH9Sg03kXCoN7LszQ90o6nPQ=="
+ },
+ "Microsoft.TestPlatform.TestHost": {
+ "type": "Transitive",
+ "resolved": "18.3.0",
+ "contentHash": "twmsoelXnp1uWMU3VGip9f0Jr1mZ0PZqgJdF35CIrdYgYrkHIJMV1m8uKyhcdjLdsQDESHAgkR7KhS9i1qpJag==",
+ "dependencies": {
+ "Microsoft.TestPlatform.ObjectModel": "18.3.0",
+ "Newtonsoft.Json": "13.0.3"
+ }
+ },
+ "Microsoft.Win32.Registry": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg=="
+ },
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.3",
+ "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
+ },
+ "xunit.analyzers": {
+ "type": "Transitive",
+ "resolved": "1.27.0",
+ "contentHash": "y/pxIQaLvk/kxAoDkZW9GnHLCEqzwl5TW0vtX3pweyQpjizB9y3DXhb9pkw2dGeUqhLjsxvvJM1k89JowU6z3g=="
+ },
+ "xunit.v3.assert": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "BPciBghgEEaJN/JG00QfCYDfEfnLgQhfnYEy+j1izoeHVNYd5+3Wm8GJ6JgYysOhpBPYGE+sbf75JtrRc7jrdA=="
+ },
+ "xunit.v3.common": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "Hj775PEH6GTbbg0wfKRvG2hNspDCvTH9irXhH4qIWgdrOSV1sQlqPie+DOvFeigsFg2fxSM3ZAaaCDQs+KreFA==",
+ "dependencies": {
+ "Microsoft.Bcl.AsyncInterfaces": "6.0.0"
+ }
+ },
+ "xunit.v3.core.mtp-v1": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "Ga5aA2Ca9ktz+5k3g5ukzwfexwoqwDUpV6z7atSEUvqtd6JuybU1XopHqg1oFd78QdTfZgZE9h5sHpO4qYIi5w==",
+ "dependencies": {
+ "Microsoft.Testing.Extensions.Telemetry": "1.9.1",
+ "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.1",
+ "Microsoft.Testing.Platform": "1.9.1",
+ "Microsoft.Testing.Platform.MSBuild": "1.9.1",
+ "xunit.v3.extensibility.core": "[3.2.2]",
+ "xunit.v3.runner.inproc.console": "[3.2.2]"
+ }
+ },
+ "xunit.v3.extensibility.core": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "srY8z/oMPvh/t8axtO2DwrHajhFMH7tnqKildvYrVQIfICi8fOn3yIBWkVPAcrKmHMwvXRJ/XsQM3VMR6DOYfQ==",
+ "dependencies": {
+ "xunit.v3.common": "[3.2.2]"
+ }
+ },
+ "xunit.v3.mtp-v1": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "O41aAzYKBT5PWqATa1oEWVNCyEUypFQ4va6K0kz37dduV3EKzXNMaV2UnEhufzU4Cce1I33gg0oldS8tGL5I0A==",
+ "dependencies": {
+ "xunit.analyzers": "1.27.0",
+ "xunit.v3.assert": "[3.2.2]",
+ "xunit.v3.core.mtp-v1": "[3.2.2]"
+ }
+ },
+ "xunit.v3.runner.common": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "/hkHkQCzGrugelOAehprm7RIWdsUFVmIVaD6jDH/8DNGCymTlKKPTbGokD5czbAfqfex47mBP0sb0zbHYwrO/g==",
+ "dependencies": {
+ "Microsoft.Win32.Registry": "[5.0.0]",
+ "xunit.v3.common": "[3.2.2]"
+ }
+ },
+ "xunit.v3.runner.inproc.console": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "ulWOdSvCk+bPXijJZ73bth9NyoOHsAs1ZOvamYbCkD4DNLX/Bd29Ve2ZNUwBbK0MqfIYWXHZViy/HKrdEC/izw==",
+ "dependencies": {
+ "xunit.v3.extensibility.core": "[3.2.2]",
+ "xunit.v3.runner.common": "[3.2.2]"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file