Compare commits
16 Commits
83edcfed8a
...
1ca65eccf8
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ca65eccf8 | |||
| 4827eefa9b | |||
| 15fb522ac6 | |||
| 5838214b36 | |||
| 5fb4265197 | |||
| 40038302de | |||
| 9c7d661e8c | |||
| c46b6664ed | |||
| 8018ebbabb | |||
| 8ec3c7847c | |||
| 1587395174 | |||
| 637e9f7fbc | |||
| 023d139281 | |||
| 5a261f5fe2 | |||
| 2e813962c9 | |||
| 07d35a49a3 |
614
.editorconfig
Normal file
614
.editorconfig
Normal file
@@ -0,0 +1,614 @@
|
|||||||
|
root = true
|
||||||
|
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||||
|
|
||||||
|
# C# files
|
||||||
|
[*.cs]
|
||||||
|
|
||||||
|
#### Core EditorConfig Options ####
|
||||||
|
|
||||||
|
# Indentation and spacing
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
#### .NET Coding Conventions ####
|
||||||
|
|
||||||
|
# Organize usings
|
||||||
|
dotnet_separate_import_directive_groups = false
|
||||||
|
dotnet_sort_system_directives_first = false
|
||||||
|
file_header_template =
|
||||||
|
|
||||||
|
# this. and Me. preferences
|
||||||
|
dotnet_style_qualification_for_event = false:suggestion
|
||||||
|
dotnet_style_qualification_for_field = false:suggestion
|
||||||
|
dotnet_style_qualification_for_method = false:suggestion
|
||||||
|
dotnet_style_qualification_for_property = 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 = always_for_clarity:none
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
|
||||||
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:none
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
dotnet_style_coalesce_expression = true
|
||||||
|
dotnet_style_collection_initializer = true
|
||||||
|
dotnet_style_explicit_tuple_names = true
|
||||||
|
dotnet_style_null_propagation = true
|
||||||
|
dotnet_style_object_initializer = true
|
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||||
|
dotnet_style_prefer_auto_properties = true
|
||||||
|
dotnet_style_prefer_compound_assignment = true
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = true
|
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||||
|
dotnet_style_prefer_inferred_tuple_names = true
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true
|
||||||
|
|
||||||
|
# Field preferences
|
||||||
|
dotnet_style_readonly_field = true
|
||||||
|
|
||||||
|
# Parameter preferences
|
||||||
|
dotnet_code_quality_unused_parameters = all
|
||||||
|
|
||||||
|
# Suppression preferences
|
||||||
|
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||||
|
|
||||||
|
#### C# Coding Conventions ####
|
||||||
|
|
||||||
|
# var preferences
|
||||||
|
csharp_style_var_elsewhere = true:silent
|
||||||
|
csharp_style_var_for_built_in_types = false:silent
|
||||||
|
csharp_style_var_when_type_is_apparent = true:silent
|
||||||
|
|
||||||
|
# Expression-bodied members
|
||||||
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
|
csharp_style_expression_bodied_constructors = false:silent
|
||||||
|
csharp_style_expression_bodied_indexers = true:silent
|
||||||
|
csharp_style_expression_bodied_lambdas = true:silent
|
||||||
|
csharp_style_expression_bodied_local_functions = false:silent
|
||||||
|
csharp_style_expression_bodied_methods = false:silent
|
||||||
|
csharp_style_expression_bodied_operators = false:silent
|
||||||
|
csharp_style_expression_bodied_properties = true:silent
|
||||||
|
|
||||||
|
# Pattern matching preferences
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||||
|
csharp_style_prefer_not_pattern = true:suggestion
|
||||||
|
csharp_style_prefer_pattern_matching = true:silent
|
||||||
|
csharp_style_prefer_switch_expression = true:silent
|
||||||
|
|
||||||
|
# Null-checking preferences
|
||||||
|
csharp_style_conditional_delegate_call = true:suggestion
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
csharp_prefer_static_local_function = true:suggestion
|
||||||
|
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion
|
||||||
|
|
||||||
|
# Code-block preferences
|
||||||
|
csharp_prefer_braces = when_multiline:silent
|
||||||
|
csharp_prefer_simple_using_statement = true:suggestion
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
|
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||||
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
csharp_style_pattern_local_over_anonymous_function = true
|
||||||
|
csharp_style_prefer_index_operator = true:suggestion
|
||||||
|
csharp_style_prefer_range_operator = true:suggestion
|
||||||
|
csharp_style_throw_expression = true:suggestion
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||||
|
|
||||||
|
# 'using' directive preferences
|
||||||
|
csharp_using_directive_placement = outside_namespace:silent
|
||||||
|
|
||||||
|
#### C# Formatting Rules ####
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
csharp_new_line_before_catch = true
|
||||||
|
csharp_new_line_before_else = true
|
||||||
|
csharp_new_line_before_finally = true
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_open_brace = accessors,anonymous_methods,control_blocks,events,indexers,local_functions,methods,properties,types
|
||||||
|
csharp_new_line_between_query_expression_clauses = true
|
||||||
|
|
||||||
|
# Indentation preferences
|
||||||
|
csharp_indent_block_contents = true
|
||||||
|
csharp_indent_braces = false
|
||||||
|
csharp_indent_case_contents = true
|
||||||
|
csharp_indent_case_contents_when_block = false
|
||||||
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_indent_switch_labels = true
|
||||||
|
|
||||||
|
# Space preferences
|
||||||
|
csharp_space_after_cast = false
|
||||||
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_after_comma = true
|
||||||
|
csharp_space_after_dot = false
|
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_space_around_declaration_statements = false
|
||||||
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_before_comma = false
|
||||||
|
csharp_space_before_dot = false
|
||||||
|
csharp_space_before_open_square_brackets = false
|
||||||
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
|
csharp_space_between_empty_square_brackets = false
|
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||||
|
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_parentheses = false
|
||||||
|
csharp_space_between_square_brackets = false
|
||||||
|
|
||||||
|
# Wrapping preferences
|
||||||
|
csharp_preserve_single_line_blocks = true
|
||||||
|
csharp_preserve_single_line_statements = true
|
||||||
|
|
||||||
|
#### Naming styles ####
|
||||||
|
|
||||||
|
# Naming rules
|
||||||
|
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||||
|
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.severity = warning
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.struct_should_be_begin_with_s.severity = warning
|
||||||
|
dotnet_naming_rule.struct_should_be_begin_with_s.symbols = struct
|
||||||
|
dotnet_naming_rule.struct_should_be_begin_with_s.style = begin_with_s
|
||||||
|
|
||||||
|
dotnet_naming_rule.property_should_be_pascal_case.severity = warning
|
||||||
|
dotnet_naming_rule.property_should_be_pascal_case.symbols = property
|
||||||
|
dotnet_naming_rule.property_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.constant_field_should_be_begins_with_c_.severity = warning
|
||||||
|
dotnet_naming_rule.constant_field_should_be_begins_with_c_.symbols = constant_field
|
||||||
|
dotnet_naming_rule.constant_field_should_be_begins_with_c_.style = begins_with_c_
|
||||||
|
|
||||||
|
dotnet_naming_rule.static_field_should_be_starts_with_s_.severity = warning
|
||||||
|
dotnet_naming_rule.static_field_should_be_starts_with_s_.symbols = static_field
|
||||||
|
dotnet_naming_rule.static_field_should_be_starts_with_s_.style = starts_with_s_
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_or_internal_static_field_should_be_starts_with_s_.severity = warning
|
||||||
|
dotnet_naming_rule.private_or_internal_static_field_should_be_starts_with_s_.symbols = private_or_internal_static_field
|
||||||
|
dotnet_naming_rule.private_or_internal_static_field_should_be_starts_with_s_.style = starts_with_s_
|
||||||
|
|
||||||
|
dotnet_naming_rule.public_or_protected_field_should_be_starts_with_m_.severity = warning
|
||||||
|
dotnet_naming_rule.public_or_protected_field_should_be_starts_with_m_.symbols = public_or_protected_field
|
||||||
|
dotnet_naming_rule.public_or_protected_field_should_be_starts_with_m_.style = starts_with_m_
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_or_internal_field_should_be_starts_with_m_.severity = warning
|
||||||
|
dotnet_naming_rule.private_or_internal_field_should_be_starts_with_m_.symbols = private_or_internal_field
|
||||||
|
dotnet_naming_rule.private_or_internal_field_should_be_starts_with_m_.style = starts_with_m_
|
||||||
|
|
||||||
|
dotnet_naming_rule.event_should_be_end_with_event.severity = warning
|
||||||
|
dotnet_naming_rule.event_should_be_end_with_event.symbols = event
|
||||||
|
dotnet_naming_rule.event_should_be_end_with_event.style = end_with_event
|
||||||
|
|
||||||
|
dotnet_naming_rule.delegate_should_be_end_with_delegate.severity = warning
|
||||||
|
dotnet_naming_rule.delegate_should_be_end_with_delegate.symbols = delegate
|
||||||
|
dotnet_naming_rule.delegate_should_be_end_with_delegate.style = end_with_delegate
|
||||||
|
|
||||||
|
dotnet_naming_rule.parameter_should_be_camelcase.severity = warning
|
||||||
|
dotnet_naming_rule.parameter_should_be_camelcase.symbols = parameter
|
||||||
|
dotnet_naming_rule.parameter_should_be_camelcase.style = camelcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.local_should_be_camelcase.severity = warning
|
||||||
|
dotnet_naming_rule.local_should_be_camelcase.symbols = local
|
||||||
|
dotnet_naming_rule.local_should_be_camelcase.style = camelcase
|
||||||
|
|
||||||
|
# Symbol specifications
|
||||||
|
|
||||||
|
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.interface.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.struct.applicable_kinds = struct
|
||||||
|
dotnet_naming_symbols.struct.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.struct.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.delegate.applicable_kinds = delegate
|
||||||
|
dotnet_naming_symbols.delegate.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.delegate.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.event.applicable_kinds = event
|
||||||
|
dotnet_naming_symbols.event.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.event.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.property.applicable_kinds = property
|
||||||
|
dotnet_naming_symbols.property.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.property.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.public_or_protected_field.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.public_or_protected_field.applicable_accessibilities = public, protected
|
||||||
|
dotnet_naming_symbols.public_or_protected_field.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.static_field.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.static_field.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.static_field.required_modifiers = static
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
|
||||||
|
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private, private_protected
|
||||||
|
dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static
|
||||||
|
|
||||||
|
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.types.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.parameter.applicable_kinds = parameter
|
||||||
|
dotnet_naming_symbols.parameter.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.parameter.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.local.applicable_kinds = local
|
||||||
|
dotnet_naming_symbols.local.applicable_accessibilities = local
|
||||||
|
dotnet_naming_symbols.local.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.constant_field.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.constant_field.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.constant_field.required_modifiers = const
|
||||||
|
|
||||||
|
# Naming styles
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix =
|
||||||
|
dotnet_naming_style.pascal_case.required_suffix =
|
||||||
|
dotnet_naming_style.pascal_case.word_separator =
|
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||||
|
dotnet_naming_style.begins_with_i.required_suffix =
|
||||||
|
dotnet_naming_style.begins_with_i.word_separator =
|
||||||
|
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.begin_with_s.required_prefix = S
|
||||||
|
dotnet_naming_style.begin_with_s.required_suffix =
|
||||||
|
dotnet_naming_style.begin_with_s.word_separator =
|
||||||
|
dotnet_naming_style.begin_with_s.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.starts_with_m_.required_prefix = m_
|
||||||
|
dotnet_naming_style.starts_with_m_.required_suffix =
|
||||||
|
dotnet_naming_style.starts_with_m_.word_separator =
|
||||||
|
dotnet_naming_style.starts_with_m_.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.starts_with_s_.required_prefix = s_
|
||||||
|
dotnet_naming_style.starts_with_s_.required_suffix =
|
||||||
|
dotnet_naming_style.starts_with_s_.word_separator =
|
||||||
|
dotnet_naming_style.starts_with_s_.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.end_with_delegate.required_prefix =
|
||||||
|
dotnet_naming_style.end_with_delegate.required_suffix = Delegate
|
||||||
|
dotnet_naming_style.end_with_delegate.word_separator =
|
||||||
|
dotnet_naming_style.end_with_delegate.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.end_with_event.required_prefix =
|
||||||
|
dotnet_naming_style.end_with_event.required_suffix = Event
|
||||||
|
dotnet_naming_style.end_with_event.word_separator =
|
||||||
|
dotnet_naming_style.end_with_event.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.camelcase.required_prefix =
|
||||||
|
dotnet_naming_style.camelcase.required_suffix =
|
||||||
|
dotnet_naming_style.camelcase.word_separator =
|
||||||
|
dotnet_naming_style.camelcase.capitalization = camel_case
|
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_c_.required_prefix = c_
|
||||||
|
dotnet_naming_style.begins_with_c_.required_suffix =
|
||||||
|
dotnet_naming_style.begins_with_c_.word_separator =
|
||||||
|
dotnet_naming_style.begins_with_c_.capitalization = pascal_case
|
||||||
|
|
||||||
|
# ReSharper properties
|
||||||
|
resharper_align_linq_query = true
|
||||||
|
resharper_align_multiline_argument = true
|
||||||
|
resharper_align_multiline_calls_chain = true
|
||||||
|
resharper_align_multiline_extends_list = true
|
||||||
|
resharper_align_multiline_for_stmt = true
|
||||||
|
resharper_align_multline_type_parameter_constrains = true
|
||||||
|
resharper_align_multline_type_parameter_list = true
|
||||||
|
resharper_align_tuple_components = true
|
||||||
|
resharper_allow_far_alignment = false
|
||||||
|
resharper_apply_auto_detected_rules = false
|
||||||
|
resharper_blank_lines_after_control_transfer_statements = 1
|
||||||
|
resharper_blank_lines_after_file_scoped_namespace_directive = 1
|
||||||
|
resharper_blank_lines_around_single_line_type = 0
|
||||||
|
resharper_blank_lines_before_multiline_statements = 0
|
||||||
|
resharper_braces_for_fixed = required
|
||||||
|
resharper_braces_for_lock = required
|
||||||
|
resharper_braces_for_using = required
|
||||||
|
resharper_csharp_align_multiline_parameter = true
|
||||||
|
resharper_csharp_align_multiple_declaration = true
|
||||||
|
resharper_csharp_allow_far_alignment = true
|
||||||
|
resharper_csharp_blank_lines_around_field = 0
|
||||||
|
resharper_csharp_extra_spaces = remove_all
|
||||||
|
resharper_csharp_insert_final_newline = false
|
||||||
|
resharper_csharp_keep_blank_lines_in_code = 1
|
||||||
|
resharper_csharp_keep_blank_lines_in_declarations = 1
|
||||||
|
resharper_csharp_max_line_length = 500
|
||||||
|
resharper_csharp_naming_rule.constants = c_ + AaBb
|
||||||
|
resharper_csharp_naming_rule.event = AaBb
|
||||||
|
resharper_csharp_naming_rule.interfaces = I + AaBb
|
||||||
|
resharper_csharp_naming_rule.local_constants = aaBb
|
||||||
|
resharper_csharp_naming_rule.method = AaBb
|
||||||
|
resharper_csharp_naming_rule.private_constants = c_ + AaBb
|
||||||
|
resharper_csharp_naming_rule.private_instance_fields = m_ + AaBb
|
||||||
|
resharper_csharp_naming_rule.private_static_fields = s_ + AaBb
|
||||||
|
resharper_csharp_naming_rule.private_static_readonly = s_ + AaBb
|
||||||
|
resharper_csharp_naming_rule.property = AaBb
|
||||||
|
resharper_csharp_naming_rule.public_fields = m_ + AaBb
|
||||||
|
resharper_csharp_naming_rule.static_readonly = s_ + AaBb
|
||||||
|
resharper_csharp_naming_rule.types_and_namespaces = AaBb
|
||||||
|
resharper_csharp_outdent_commas = true
|
||||||
|
resharper_csharp_outdent_dots = false
|
||||||
|
resharper_csharp_stick_comment = false
|
||||||
|
resharper_csharp_use_indent_from_vs = true
|
||||||
|
resharper_csharp_wrap_extends_list_style = chop_if_long
|
||||||
|
resharper_csharp_wrap_lines = false
|
||||||
|
resharper_csharp_wrap_parameters_style = chop_if_long
|
||||||
|
resharper_csharp_wrap_ternary_expr_style = wrap_if_long
|
||||||
|
resharper_csharp_wrap_chained_method_calls = chop_if_long
|
||||||
|
resharper_extra_spaces = remove_all
|
||||||
|
resharper_for_built_in_types = use_var
|
||||||
|
resharper_indent_preprocessor_region = no_indent
|
||||||
|
resharper_instance_members_qualify_declared_in =
|
||||||
|
resharper_int_align_switch_expressions = false
|
||||||
|
resharper_int_align_switch_sections = false
|
||||||
|
resharper_keep_existing_arrangement = true
|
||||||
|
resharper_keep_existing_attribute_arrangement = true
|
||||||
|
resharper_keep_existing_switch_expression_arrangement = true
|
||||||
|
resharper_max_attribute_length_for_same_line = 0
|
||||||
|
resharper_max_initializer_elements_on_line = 1
|
||||||
|
resharper_nested_ternary_style = compact
|
||||||
|
resharper_object_creation_when_type_not_evident = target_typed
|
||||||
|
resharper_outdent_binary_ops = true
|
||||||
|
resharper_place_constructor_initializer_on_same_line = false
|
||||||
|
resharper_place_method_attribute_on_same_line = if_owner_is_single_line
|
||||||
|
resharper_place_simple_case_statement_on_same_line = true
|
||||||
|
resharper_place_simple_embedded_statement_on_same_line = false
|
||||||
|
resharper_place_simple_initializer_on_single_line = true
|
||||||
|
resharper_place_simple_property_pattern_on_single_line = true
|
||||||
|
resharper_place_type_attribute_on_same_line = if_owner_is_single_line
|
||||||
|
resharper_use_heuristics_for_body_style = true
|
||||||
|
resharper_use_indent_from_vs = true
|
||||||
|
resharper_space_before_new_parentheses = false
|
||||||
|
resharper_use_roslyn_logic_for_evident_types = true
|
||||||
|
|
||||||
|
# IDE0090: Use 'new(...)'
|
||||||
|
dotnet_diagnostic.ide0090.severity = silent
|
||||||
|
dotnet_diagnostic.ide0110.severity = suggestion
|
||||||
|
dotnet_diagnostic.ide0058.severity = suggestion
|
||||||
|
dotnet_diagnostic.ide0059.severity = suggestion
|
||||||
|
csharp_style_namespace_declarations = file_scoped:none
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||||
|
csharp_style_prefer_tuple_swap = true:suggestion
|
||||||
|
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
|
||||||
|
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
|
||||||
|
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
|
||||||
|
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||||
|
|
||||||
|
# Microsoft .NET properties
|
||||||
|
dotnet_naming_rule.constants_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.constants_rule.severity = none
|
||||||
|
dotnet_naming_rule.constants_rule.style = begins_with_c_
|
||||||
|
dotnet_naming_rule.constants_rule.symbols = constants_symbols
|
||||||
|
dotnet_naming_rule.event_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.event_rule.severity = none
|
||||||
|
dotnet_naming_rule.event_rule.style = pascal_case
|
||||||
|
dotnet_naming_rule.event_rule.symbols = event_symbols
|
||||||
|
dotnet_naming_rule.interfaces_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.interfaces_rule.severity = none
|
||||||
|
dotnet_naming_rule.interfaces_rule.style = begins_with_i
|
||||||
|
dotnet_naming_rule.interfaces_rule.symbols = interfaces_symbols
|
||||||
|
dotnet_naming_rule.local_constants_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.local_constants_rule.severity = none
|
||||||
|
dotnet_naming_rule.local_constants_rule.style = camelcase
|
||||||
|
dotnet_naming_rule.local_constants_rule.symbols = local_constants_symbols
|
||||||
|
dotnet_naming_rule.local_should_be_camelcase.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.method_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.method_rule.severity = none
|
||||||
|
dotnet_naming_rule.method_rule.style = pascal_case
|
||||||
|
dotnet_naming_rule.method_rule.symbols = method_symbols
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.import_to_resharper = True
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.resharper_description = non_field_members_should_be_pascal_case
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.resharper_guid = 44ab0b2d-34cc-42db-a691-0c646821e2e4
|
||||||
|
dotnet_naming_rule.parameter_should_be_camelcase.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.private_constants_rule.severity = none
|
||||||
|
dotnet_naming_rule.private_constants_rule.style = begins_with_c_
|
||||||
|
dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
|
||||||
|
dotnet_naming_rule.private_instance_fields_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.private_instance_fields_rule.severity = none
|
||||||
|
dotnet_naming_rule.private_instance_fields_rule.style = starts_with_m_
|
||||||
|
dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
|
||||||
|
dotnet_naming_rule.private_static_fields_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.private_static_fields_rule.severity = none
|
||||||
|
dotnet_naming_rule.private_static_fields_rule.style = starts_with_s_
|
||||||
|
dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
|
||||||
|
dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.private_static_readonly_rule.severity = none
|
||||||
|
dotnet_naming_rule.private_static_readonly_rule.style = starts_with_s_
|
||||||
|
dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
|
||||||
|
dotnet_naming_rule.property_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.property_rule.severity = none
|
||||||
|
dotnet_naming_rule.property_rule.style = pascal_case
|
||||||
|
dotnet_naming_rule.property_rule.symbols = property_symbols
|
||||||
|
dotnet_naming_rule.public_fields_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.public_fields_rule.severity = none
|
||||||
|
dotnet_naming_rule.public_fields_rule.style = starts_with_m_
|
||||||
|
dotnet_naming_rule.public_fields_rule.symbols = public_fields_symbols
|
||||||
|
dotnet_naming_rule.static_readonly_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.static_readonly_rule.severity = none
|
||||||
|
dotnet_naming_rule.static_readonly_rule.style = starts_with_s_
|
||||||
|
dotnet_naming_rule.static_readonly_rule.symbols = static_readonly_symbols
|
||||||
|
dotnet_naming_rule.types_and_namespaces_rule.import_to_resharper = as_predefined
|
||||||
|
dotnet_naming_rule.types_and_namespaces_rule.severity = none
|
||||||
|
dotnet_naming_rule.types_and_namespaces_rule.style = pascal_case
|
||||||
|
dotnet_naming_rule.types_and_namespaces_rule.symbols = types_and_namespaces_symbols
|
||||||
|
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||||
|
dotnet_naming_symbols.constants_symbols.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.constants_symbols.required_modifiers = const
|
||||||
|
dotnet_naming_symbols.event_symbols.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.event_symbols.applicable_kinds = event
|
||||||
|
dotnet_naming_symbols.interfaces_symbols.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.interfaces_symbols.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.local_constants_symbols.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.local_constants_symbols.applicable_kinds = local
|
||||||
|
dotnet_naming_symbols.local_constants_symbols.required_modifiers = const
|
||||||
|
dotnet_naming_symbols.method_symbols.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.method_symbols.applicable_kinds = method
|
||||||
|
dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
|
||||||
|
dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
|
||||||
|
dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
|
||||||
|
dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
|
||||||
|
dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
|
||||||
|
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
|
||||||
|
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
|
||||||
|
dotnet_naming_symbols.property_symbols.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.property_symbols.applicable_kinds = property
|
||||||
|
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||||
|
dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||||
|
dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly
|
||||||
|
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace,class,struct,enum,delegate
|
||||||
|
|
||||||
|
# ReSharper inspection severities
|
||||||
|
resharper_arrange_accessor_owner_body_highlighting = suggestion
|
||||||
|
resharper_arrange_default_value_when_type_evident_highlighting = hint
|
||||||
|
resharper_arrange_object_creation_when_type_evident_highlighting = hint
|
||||||
|
resharper_arrange_redundant_parentheses_highlighting = hint
|
||||||
|
resharper_arrange_this_qualifier_highlighting = hint
|
||||||
|
resharper_arrange_type_member_modifiers_highlighting = hint
|
||||||
|
resharper_arrange_type_modifiers_highlighting = hint
|
||||||
|
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
|
||||||
|
resharper_built_in_type_reference_style_highlighting = hint
|
||||||
|
resharper_comment_typo_highlighting = hint
|
||||||
|
resharper_foreach_can_be_partly_converted_to_query_using_another_get_enumerator_highlighting = none
|
||||||
|
resharper_identifier_typo_highlighting = hint
|
||||||
|
resharper_inconsistent_naming_highlighting = none
|
||||||
|
resharper_json_validation_failed_highlighting = warning
|
||||||
|
resharper_member_can_be_private_global_highlighting = hint
|
||||||
|
resharper_member_can_be_private_local_highlighting = hint
|
||||||
|
resharper_member_can_be_protected_global_highlighting = hint
|
||||||
|
resharper_member_can_be_protected_local_highlighting = hint
|
||||||
|
resharper_redundant_base_qualifier_highlighting = warning
|
||||||
|
resharper_remove_redundant_braces_highlighting = suggestion
|
||||||
|
resharper_string_literal_typo_highlighting = hint
|
||||||
|
resharper_suggest_var_or_type_built_in_types_highlighting = hint
|
||||||
|
resharper_suggest_var_or_type_elsewhere_highlighting = hint
|
||||||
|
resharper_suggest_var_or_type_simple_types_highlighting = hint
|
||||||
|
resharper_unused_auto_property_accessor_global_highlighting = hint
|
||||||
|
resharper_unused_auto_property_accessor_local_highlighting = hint
|
||||||
|
resharper_unused_member_global_highlighting = hint
|
||||||
|
csharp_style_prefer_method_group_conversion = true:suggestion
|
||||||
|
csharp_style_prefer_top_level_statements = true:silent
|
||||||
|
dotnet_diagnostic.VSTHRD001.severity = none
|
||||||
|
dotnet_diagnostic.VSTHRD105.severity = silent
|
||||||
|
csharp_style_prefer_primary_constructors = true:suggestion
|
||||||
|
csharp_prefer_system_threading_lock = true:suggestion
|
||||||
|
dotnet_diagnostic.VSTHRD012.severity = none
|
||||||
|
dotnet_diagnostic.VSTHRD003.severity = none
|
||||||
|
|
||||||
|
[*.{cs,vb}]
|
||||||
|
dotnet_diagnostic.ca1822.severity = silent
|
||||||
|
dotnet_diagnostic.ca1305.severity = suggestion
|
||||||
|
dotnet_diagnostic.IDE0079.severity = silent
|
||||||
|
dotnet_diagnostic.IDE0270.severity = silent
|
||||||
|
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
|
||||||
|
dotnet_diagnostic.ca2215.severity = warning
|
||||||
|
dotnet_diagnostic.ca1001.severity = suggestion
|
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||||
|
tab_width = 4
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = crlf
|
||||||
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
|
dotnet_style_null_propagation = true:suggestion
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||||
|
dotnet_style_prefer_auto_properties = true:silent
|
||||||
|
dotnet_style_object_initializer = true:suggestion
|
||||||
|
dotnet_style_collection_initializer = true:suggestion
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||||
|
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_simplified_interpolation = true:suggestion
|
||||||
|
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||||
|
dotnet_style_namespace_match_folder = true:suggestion
|
||||||
|
dotnet_style_readonly_field = true:suggestion
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||||
|
dotnet_style_predefined_type_for_member_access = true:silent
|
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||||
|
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
|
||||||
|
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
|
||||||
|
dotnet_code_quality_unused_parameters = all:suggestion
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||||
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||||
|
|
||||||
|
[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,js,json,jsproj,jsx,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,tpp,ts,tsx,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
|
[*.{appxmanifest,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cshtml,csproj,cu,cuh,cxx,dbml,discomap,dtd,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,jsproj,lsproj,mpp,mq4,mq5,mqh,njsproj,nuspec,proj,props,proto,razor,resw,resx,StyleCop,targets,tasks,tpp,usf,ush,vbproj,xml,xsd}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
|
[*.{asax,ascx,aspx,axaml,cs,css,js,jsx,master,paml,skin,ts,tsx,vb,xaml,xamlx,xoml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
dotnet_diagnostic.CA1816.severity = silent
|
||||||
|
dotnet_diagnostic.HAA0501.severity = silent
|
||||||
|
dotnet_diagnostic.HAA0502.severity = silent
|
||||||
|
dotnet_style_prefer_collection_expression = true:suggestion
|
||||||
|
dotnet_diagnostic.RS2008.severity = none
|
||||||
|
|
||||||
|
[*.{json,resjson}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.vs
|
||||||
|
bin
|
||||||
|
obj
|
||||||
3
AGENTS.linux.md
Normal file
3
AGENTS.linux.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Linux-specific instructions
|
||||||
|
|
||||||
|
- After every iteration, run `dotnet jb cleanupcode --build=False '$file1' '$file2' ...` for every C# file you touched.
|
||||||
30
AGENTS.md
Normal file
30
AGENTS.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Platform and documentation
|
||||||
|
|
||||||
|
If this is a linux environment, read `AGENTS.linux.md`.
|
||||||
|
If this is a windows environment, read `AGENTS.windows.md`.
|
||||||
|
Follow the guidelines laid out in `CODESTYLE.md`.
|
||||||
|
Also see the other related technical documentation in the docs folder.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Prefer extracting code to a shared helper to be reused instead of duplicating code. Always keep high maintainability standards.
|
||||||
|
- If a class is to be used only once, consider nesting it inside of another class. Otherwise place each newly created class into its own file. The file name must match the class name.
|
||||||
|
- When asked to begin working on a task, create a detailed implementation plan first, present the plan to the user, and ask for approval before beginning with the actual implementation.
|
||||||
|
- Don't make assumptions in the plan. If necessary, ask all clarifying questions before presenting the final plan.
|
||||||
|
- When an task is finished, perform a code review to evaluate if the change is clean and maintainable with high software engineering standards. Iterate on the code and repeat the review process until satisfied.
|
||||||
|
- If there's documnentation present, always keep it updated.
|
||||||
|
- After every iteration, evaluate if the test coverage would fall below 100%, and write tests if necessary.
|
||||||
|
|
||||||
|
### Git
|
||||||
|
|
||||||
|
- Never change the .gitignore file without consent.
|
||||||
|
- Keep changes small with minimal churn and commit often. If one iteration encompasses many smaller tasks with more than one commit, create a git branch and do the commits there. Let me review the branch before merging it back to master.
|
||||||
|
- When multiple commits are necessary, pause after every commit and ask the user to give a command to proceed.
|
||||||
|
- After every iteration, do a git commit with a brief summary of the changes as a commit message.
|
||||||
|
- If you find unexpected changes in the code (deletions, changes, diff results that were not communicated), never revert them and never restore the old state. Assume that those changes happened with intent.
|
||||||
|
- Never use `git restore`, `git checkout --`, reset commands, or equivalent rollback actions to discard local changes unless the user explicitly asks for that exact rollback.
|
||||||
|
|
||||||
|
### Dotnet CLI
|
||||||
|
|
||||||
|
- If you need a separate output directory, use a subfolder under `artifacts`, and clean it up afterwards.
|
||||||
|
- Avoid running `dotnet build` and `dotnet test` in parallel in this repo; that can cause file-lock failures in `obj\Debug\net10.0`.
|
||||||
4
AGENTS.windows.md
Normal file
4
AGENTS.windows.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Windows-specific instructions
|
||||||
|
|
||||||
|
- After the implementation is finished, run `python D:\Code\crlf.py $file1 $file2 ...` for changed files you recognize, in order to normalize all line endings of all touched files to CRLF.
|
||||||
|
- After every iteration, run `jb cleanupcode --build=False '$file1' '$file2' ...` for every C# file you touched.
|
||||||
50
CODESTYLE.md
Normal file
50
CODESTYLE.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Code Style
|
||||||
|
|
||||||
|
This repository follows the local `.editorconfig` and the style visible in the current local changes. Use these notes when creating new code.
|
||||||
|
|
||||||
|
## Naming
|
||||||
|
|
||||||
|
- Use PascalCase for namespaces, types, methods, properties, enum members, and non-field members.
|
||||||
|
- Prefix enum type names with `E`, for example `ECellKind`, `EPipeMedium`, `EFailureKind`, and `EEditorTool`.
|
||||||
|
- Prefix struct type names with `S` when creating new structs.
|
||||||
|
- Prefix interfaces with `I`.
|
||||||
|
- Use camelCase for parameters and local variables.
|
||||||
|
- Prefix private instance fields with `m_` and keep the remainder PascalCase, for example `m_Level` and `m_SelectedTool`.
|
||||||
|
- Prefix private static fields and static readonly fields with `s_`.
|
||||||
|
- Prefix constants with `c_`.
|
||||||
|
- Avoid `this.` unless it is needed for clarity or disambiguation.
|
||||||
|
- Always use folder-based namespaces when creating types and refactoring.
|
||||||
|
|
||||||
|
## Files And Types
|
||||||
|
|
||||||
|
- Use file-scoped namespaces.
|
||||||
|
- Keep one reusable top-level class per file, with the file name matching the class name.
|
||||||
|
- If a helper type is used only by one class, prefer nesting it inside that class.
|
||||||
|
- Keep small, cohesive files and extract shared helpers instead of duplicating logic.
|
||||||
|
|
||||||
|
## Braces And Blocks
|
||||||
|
|
||||||
|
- Use braces for multi-line bodies.
|
||||||
|
- Omit braces for simple single-line embedded statements when readability stays clear.
|
||||||
|
- Nested control flow with multi-line bodies should use braces at every multi-line level.
|
||||||
|
- Keep opening braces on the next line for types, methods, properties, accessors, and control blocks.
|
||||||
|
- Compact object initializers, switch expressions, and `with` expressions may keep the opening brace on the same line when cleanup formats them that way.
|
||||||
|
|
||||||
|
## Blank Lines
|
||||||
|
|
||||||
|
- Use a blank line to separate members.
|
||||||
|
- Use a blank line after control-flow transfer clauses such as `return`, `continue`, `break`, and `throw` when more code follows in the same scope.
|
||||||
|
- Avoid extra blank lines inside short methods and between tightly related statements.
|
||||||
|
- Keep at most one blank line in code and declarations.
|
||||||
|
|
||||||
|
## Expressions And Formatting
|
||||||
|
|
||||||
|
- Prefer `var` when the type is apparent or not useful to repeat; use explicit built-in types such as `int`, `bool`, and `string`.
|
||||||
|
- Prefer target-typed `new()` when the type is evident.
|
||||||
|
- Prefer object and collection initializers, including collection expressions such as `[".json"]`.
|
||||||
|
- Prefer pattern matching for combined checks, for example `cell is { HasPipe: true, Pressure: > 7 }`.
|
||||||
|
- Prefer switch expressions for simple value selection.
|
||||||
|
- Prefer expression-bodied properties and accessors when they remain simple.
|
||||||
|
- Keep simple object initializers and property patterns on one line when they are short and readable.
|
||||||
|
- Keep long boolean expressions and interpolated status strings readable without introducing unnecessary blank lines.
|
||||||
|
- Keep using directives outside namespaces.
|
||||||
18
README.md
18
README.md
@@ -1,2 +1,18 @@
|
|||||||
# zfxaction26_2
|
# Reactor Maintenance
|
||||||
|
|
||||||
|
C# WinUI 3 + Win2D level editor for the deterministic grid simulation described in `design.md`.
|
||||||
|
|
||||||
|
## Projects
|
||||||
|
|
||||||
|
- `src/ReactorMaintenance.Simulation`: UI-independent level model, editor operations, forecasts, simulation turns, versioned JSON serialization, and swappable difficulty balancing profiles.
|
||||||
|
- `src/ReactorMaintenance.Win2D`: Win2D editor app for painting floor/wall terrain plus cell props, loading/saving levels, advancing simulation turns, and activating the reactor.
|
||||||
|
- `tests/ReactorMaintenance.Simulation.Tests`: unit tests for deterministic simulation behavior.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet test tests\ReactorMaintenance.Simulation.Tests\ReactorMaintenance.Simulation.Tests.csproj
|
||||||
|
dotnet build src\ReactorMaintenance.Win2D\ReactorMaintenance.Win2D.csproj -p:Platform=x64
|
||||||
|
dotnet run --project src\ReactorMaintenance.Win2D\ReactorMaintenance.Win2D.csproj -p:Platform=x64
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
11
ReactorMaintenance.slnx
Normal file
11
ReactorMaintenance.slnx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<Solution>
|
||||||
|
<Folder Name="/src/">
|
||||||
|
<Project Path="src/ReactorMaintenance.Simulation/ReactorMaintenance.Simulation.csproj" />
|
||||||
|
<Project Path="src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj">
|
||||||
|
<Platform Project="x86" />
|
||||||
|
</Project>
|
||||||
|
</Folder>
|
||||||
|
<Folder Name="/tests/">
|
||||||
|
<Project Path="tests/ReactorMaintenance.Simulation.Tests/ReactorMaintenance.Simulation.Tests.csproj" />
|
||||||
|
</Folder>
|
||||||
|
</Solution>
|
||||||
1280
docs/design.md
Normal file
1280
docs/design.md
Normal file
File diff suppressed because it is too large
Load Diff
78
src/ReactorMaintenance.Simulation/Balancing.cs
Normal file
78
src/ReactorMaintenance.Simulation/Balancing.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using ReactorMaintenance.Simulation.Difficulties;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
public abstract class Balancing
|
||||||
|
{
|
||||||
|
public static Balancing Current { get; set; } = new NormalBalancing();
|
||||||
|
|
||||||
|
public abstract int MinHazardValue { get; }
|
||||||
|
public abstract int MaxHazardValue { get; }
|
||||||
|
public abstract int DefaultHazardStability { get; }
|
||||||
|
public abstract int DefaultCellIntegrity { get; }
|
||||||
|
public abstract int DefaultActionsPerTurn { get; }
|
||||||
|
public abstract int DefaultCoreHeat { get; }
|
||||||
|
public abstract int DefaultFacilityStability { get; }
|
||||||
|
public abstract int DefaultPower { get; }
|
||||||
|
public abstract int DefaultCooling { get; }
|
||||||
|
public abstract int FirstGridCoordinate { get; }
|
||||||
|
public abstract int NeighborDistance { get; }
|
||||||
|
public abstract int CurrentForecastTurn { get; }
|
||||||
|
public abstract int MinimumLevelSize { get; }
|
||||||
|
public abstract int DefaultLevelWidth { get; }
|
||||||
|
public abstract int DefaultLevelHeight { get; }
|
||||||
|
public abstract int DefaultRobotCoordinate { get; }
|
||||||
|
public abstract int DefaultPipeFlow { get; }
|
||||||
|
public abstract int DefaultPipePressure { get; }
|
||||||
|
public abstract int DefaultPressurePipeFlow { get; }
|
||||||
|
public abstract int DefaultPressurePipePressure { get; }
|
||||||
|
public abstract int DefaultEditedPipeIntegrity { get; }
|
||||||
|
public abstract int MinimumLeakRate { get; }
|
||||||
|
public abstract int DamagedPipeIntegrity { get; }
|
||||||
|
public abstract int RepairedLeakRate { get; }
|
||||||
|
public abstract int RepairedElectricalCharge { get; }
|
||||||
|
public abstract int HeatToolIncrease { get; }
|
||||||
|
public abstract int FireToolMinimumHeat { get; }
|
||||||
|
public abstract int FireToolMinimumSmoke { get; }
|
||||||
|
public abstract int MaxForecastStepCount { get; }
|
||||||
|
public abstract int TurnIncrement { get; }
|
||||||
|
public abstract int OverpressureThreshold { get; }
|
||||||
|
public abstract int HeatIntegrityDamageThreshold { get; }
|
||||||
|
public abstract int PipeFireIntegrityDamage { get; }
|
||||||
|
public abstract int FireStabilityDamage { get; }
|
||||||
|
public abstract int BurstLeakRate { get; }
|
||||||
|
public abstract int BrokenPipeFlow { get; }
|
||||||
|
public abstract int ElectrifiedCoolantPoolingThreshold { get; }
|
||||||
|
public abstract int ElectricalChargeIncrease { get; }
|
||||||
|
public abstract int FuelVaporFireThreshold { get; }
|
||||||
|
public abstract int LiquidFuelFireThreshold { get; }
|
||||||
|
public abstract int HeatIgnitionThreshold { get; }
|
||||||
|
public abstract int ElectricalIgnitionThreshold { get; }
|
||||||
|
public abstract int FireHeatIncrease { get; }
|
||||||
|
public abstract int FireSmokeIncrease { get; }
|
||||||
|
public abstract int FireLiquidFuelConsumption { get; }
|
||||||
|
public abstract int FireFuelVaporConsumption { get; }
|
||||||
|
public abstract int SmokeDecay { get; }
|
||||||
|
public abstract int PressurizedFuelLeakPressureThreshold { get; }
|
||||||
|
public abstract int PassiveFuelVaporHeatOffset { get; }
|
||||||
|
public abstract int PassiveFuelVaporDivisor { get; }
|
||||||
|
public abstract int MinimumCoolantHeatReduction { get; }
|
||||||
|
public abstract int CoolantHeatReductionDivisor { get; }
|
||||||
|
public abstract int CoolantSteamHeatThreshold { get; }
|
||||||
|
public abstract int CoolantSteamSmokeIncrease { get; }
|
||||||
|
public abstract int PressureLeakSmokeThreshold { get; }
|
||||||
|
public abstract int PressureLeakSmokeIncrease { get; }
|
||||||
|
public abstract int GeneratorHeatIncrease { get; }
|
||||||
|
public abstract int CoolingPumpHeatReduction { get; }
|
||||||
|
public abstract int ReactorHeatIncrease { get; }
|
||||||
|
public abstract int SmokeSpreadThreshold { get; }
|
||||||
|
public abstract int SmokeSpreadIncrease { get; }
|
||||||
|
public abstract int CriticalCellStabilityThreshold { get; }
|
||||||
|
public abstract int MeltdownCoreHeatThreshold { get; }
|
||||||
|
public abstract int StabilityCollapseThreshold { get; }
|
||||||
|
public abstract int GeneratorPowerOutput { get; }
|
||||||
|
public abstract int CoolingPumpOutput { get; }
|
||||||
|
public abstract int ReactorReadyPowerThreshold { get; }
|
||||||
|
public abstract int ReactorReadyCoolingThreshold { get; }
|
||||||
|
public abstract int ReactorReadyCoreHeatThreshold { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Difficulties;
|
||||||
|
|
||||||
|
public class NormalBalancing : Balancing
|
||||||
|
{
|
||||||
|
public override int MinHazardValue => 0;
|
||||||
|
public override int MaxHazardValue => 10;
|
||||||
|
public override int DefaultHazardStability => 10;
|
||||||
|
public override int DefaultCellIntegrity => 10;
|
||||||
|
public override int DefaultActionsPerTurn => 3;
|
||||||
|
public override int DefaultCoreHeat => 5;
|
||||||
|
public override int DefaultFacilityStability => 10;
|
||||||
|
public override int DefaultPower => 5;
|
||||||
|
public override int DefaultCooling => 0;
|
||||||
|
public override int FirstGridCoordinate => 0;
|
||||||
|
public override int NeighborDistance => 1;
|
||||||
|
public override int CurrentForecastTurn => 0;
|
||||||
|
public override int MinimumLevelSize => 4;
|
||||||
|
public override int DefaultLevelWidth => 16;
|
||||||
|
public override int DefaultLevelHeight => 12;
|
||||||
|
public override int DefaultRobotCoordinate => 1;
|
||||||
|
public override int DefaultPipeFlow => 4;
|
||||||
|
public override int DefaultPipePressure => 4;
|
||||||
|
public override int DefaultPressurePipeFlow => 5;
|
||||||
|
public override int DefaultPressurePipePressure => 6;
|
||||||
|
public override int DefaultEditedPipeIntegrity => 8;
|
||||||
|
public override int MinimumLeakRate => 1;
|
||||||
|
public override int DamagedPipeIntegrity => 4;
|
||||||
|
public override int RepairedLeakRate => 0;
|
||||||
|
public override int RepairedElectricalCharge => 0;
|
||||||
|
public override int HeatToolIncrease => 2;
|
||||||
|
public override int FireToolMinimumHeat => 7;
|
||||||
|
public override int FireToolMinimumSmoke => 3;
|
||||||
|
public override int MaxForecastStepCount => 12;
|
||||||
|
public override int TurnIncrement => 1;
|
||||||
|
public override int OverpressureThreshold => 7;
|
||||||
|
public override int HeatIntegrityDamageThreshold => 10;
|
||||||
|
public override int PipeFireIntegrityDamage => 1;
|
||||||
|
public override int FireStabilityDamage => 1;
|
||||||
|
public override int BurstLeakRate => 3;
|
||||||
|
public override int BrokenPipeFlow => 0;
|
||||||
|
public override int ElectrifiedCoolantPoolingThreshold => 3;
|
||||||
|
public override int ElectricalChargeIncrease => 2;
|
||||||
|
public override int FuelVaporFireThreshold => 4;
|
||||||
|
public override int LiquidFuelFireThreshold => 6;
|
||||||
|
public override int HeatIgnitionThreshold => 8;
|
||||||
|
public override int ElectricalIgnitionThreshold => 4;
|
||||||
|
public override int FireHeatIncrease => 2;
|
||||||
|
public override int FireSmokeIncrease => 2;
|
||||||
|
public override int FireLiquidFuelConsumption => 1;
|
||||||
|
public override int FireFuelVaporConsumption => 1;
|
||||||
|
public override int SmokeDecay => 1;
|
||||||
|
public override int PressurizedFuelLeakPressureThreshold => 7;
|
||||||
|
public override int PassiveFuelVaporHeatOffset => 3;
|
||||||
|
public override int PassiveFuelVaporDivisor => 3;
|
||||||
|
public override int MinimumCoolantHeatReduction => 1;
|
||||||
|
public override int CoolantHeatReductionDivisor => 2;
|
||||||
|
public override int CoolantSteamHeatThreshold => 7;
|
||||||
|
public override int CoolantSteamSmokeIncrease => 2;
|
||||||
|
public override int PressureLeakSmokeThreshold => 8;
|
||||||
|
public override int PressureLeakSmokeIncrease => 1;
|
||||||
|
public override int GeneratorHeatIncrease => 1;
|
||||||
|
public override int CoolingPumpHeatReduction => 2;
|
||||||
|
public override int ReactorHeatIncrease => 1;
|
||||||
|
public override int SmokeSpreadThreshold => 6;
|
||||||
|
public override int SmokeSpreadIncrease => 1;
|
||||||
|
public override int CriticalCellStabilityThreshold => 3;
|
||||||
|
public override int MeltdownCoreHeatThreshold => 10;
|
||||||
|
public override int StabilityCollapseThreshold => 0;
|
||||||
|
public override int GeneratorPowerOutput => 3;
|
||||||
|
public override int CoolingPumpOutput => 3;
|
||||||
|
public override int ReactorReadyPowerThreshold => 3;
|
||||||
|
public override int ReactorReadyCoolingThreshold => 3;
|
||||||
|
public override int ReactorReadyCoreHeatThreshold => 8;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
|
public sealed class CellIntegrityEffect : ISimulationEffect
|
||||||
|
{
|
||||||
|
public CellState Apply(CellState cell)
|
||||||
|
{
|
||||||
|
var integrity = cell.Integrity;
|
||||||
|
var hazards = cell.Hazards;
|
||||||
|
|
||||||
|
if (cell is { HasPipe: true } && cell.Pressure > Balancing.Current.OverpressureThreshold)
|
||||||
|
integrity -= cell.Pressure - Balancing.Current.OverpressureThreshold;
|
||||||
|
|
||||||
|
if (hazards.Heat >= Balancing.Current.HeatIntegrityDamageThreshold || hazards.Fire)
|
||||||
|
{
|
||||||
|
integrity -= cell.HasPipe ? Balancing.Current.PipeFireIntegrityDamage : Balancing.Current.MinHazardValue;
|
||||||
|
hazards = hazards with { Stability = hazards.Stability - Balancing.Current.FireStabilityDamage };
|
||||||
|
}
|
||||||
|
|
||||||
|
cell = cell with {
|
||||||
|
Integrity = Rules.Clamp(integrity),
|
||||||
|
Hazards = hazards.Clamp()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (integrity > Balancing.Current.MinHazardValue || !cell.HasPipe)
|
||||||
|
return cell;
|
||||||
|
|
||||||
|
return cell with {
|
||||||
|
LeakRate = Math.Max(cell.LeakRate, Balancing.Current.BurstLeakRate),
|
||||||
|
Flow = Balancing.Current.BrokenPipeFlow,
|
||||||
|
PipeOpen = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
|
public sealed class FireAndElectricalHazardEffect : ISimulationEffect
|
||||||
|
{
|
||||||
|
public CellState Apply(CellState cell)
|
||||||
|
{
|
||||||
|
var hazards = cell.Hazards;
|
||||||
|
if (hazards.CoolantPooling >= Balancing.Current.ElectrifiedCoolantPoolingThreshold && cell.Powered)
|
||||||
|
hazards = hazards with { ElectricalCharge = hazards.ElectricalCharge + Balancing.Current.ElectricalChargeIncrease };
|
||||||
|
|
||||||
|
var hasFuel = hazards.FuelVapor >= Balancing.Current.FuelVaporFireThreshold || hazards.LiquidFuel >= Balancing.Current.LiquidFuelFireThreshold;
|
||||||
|
var hasIgnition = hazards.Heat >= Balancing.Current.HeatIgnitionThreshold || hazards.ElectricalCharge >= Balancing.Current.ElectricalIgnitionThreshold || cell is { Prop: ECellProp.Generator, Powered: true };
|
||||||
|
if ((hasFuel && hasIgnition) || hazards.Fire)
|
||||||
|
{
|
||||||
|
hazards = hazards with {
|
||||||
|
Fire = hasFuel || hazards.Fire,
|
||||||
|
Heat = hazards.Heat + Balancing.Current.FireHeatIncrease,
|
||||||
|
Smoke = hazards.Smoke + Balancing.Current.FireSmokeIncrease,
|
||||||
|
LiquidFuel = Math.Max(Balancing.Current.MinHazardValue, hazards.LiquidFuel - Balancing.Current.FireLiquidFuelConsumption),
|
||||||
|
FuelVapor = Math.Max(Balancing.Current.MinHazardValue, hazards.FuelVapor - Balancing.Current.FireFuelVaporConsumption)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (hazards.Smoke > Balancing.Current.MinHazardValue)
|
||||||
|
hazards = hazards with { Smoke = hazards.Smoke - Balancing.Current.SmokeDecay };
|
||||||
|
|
||||||
|
return cell with { Hazards = hazards.Clamp() };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
|
public interface IAreaSimulationEffect
|
||||||
|
{
|
||||||
|
CellState[] Apply(LevelState level, CellState[] cells);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
|
public interface ISimulationEffect
|
||||||
|
{
|
||||||
|
CellState Apply(CellState cell);
|
||||||
|
}
|
||||||
18
src/ReactorMaintenance.Simulation/Effects/MachineEffect.cs
Normal file
18
src/ReactorMaintenance.Simulation/Effects/MachineEffect.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
|
public sealed class MachineEffect : ISimulationEffect
|
||||||
|
{
|
||||||
|
public CellState Apply(CellState cell)
|
||||||
|
{
|
||||||
|
var hazards = cell.Prop switch {
|
||||||
|
ECellProp.Generator when cell.Powered => cell.Hazards with { Heat = cell.Hazards.Heat + Balancing.Current.GeneratorHeatIncrease },
|
||||||
|
ECellProp.CoolingPump when cell.Powered => cell.Hazards with { Heat = cell.Hazards.Heat - Balancing.Current.CoolingPumpHeatReduction },
|
||||||
|
ECellProp.Reactor => cell.Hazards with { Heat = cell.Hazards.Heat + Balancing.Current.ReactorHeatIncrease },
|
||||||
|
_ => cell.Hazards
|
||||||
|
};
|
||||||
|
|
||||||
|
return cell with { Hazards = hazards.Clamp() };
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/ReactorMaintenance.Simulation/Effects/PipeLeakEffect.cs
Normal file
28
src/ReactorMaintenance.Simulation/Effects/PipeLeakEffect.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
|
public sealed class PipeLeakEffect : ISimulationEffect
|
||||||
|
{
|
||||||
|
public CellState Apply(CellState cell)
|
||||||
|
{
|
||||||
|
if (!cell.HasPipe || cell.LeakRate <= Balancing.Current.MinHazardValue)
|
||||||
|
return cell;
|
||||||
|
|
||||||
|
var hazards = cell.Pipe switch {
|
||||||
|
EPipeMedium.Fuel => cell.Hazards with {
|
||||||
|
LiquidFuel = cell.Hazards.LiquidFuel + cell.LeakRate,
|
||||||
|
FuelVapor = cell.Hazards.FuelVapor + (cell.Pressure >= Balancing.Current.PressurizedFuelLeakPressureThreshold ? cell.LeakRate : Math.Max(Balancing.Current.MinHazardValue, cell.Hazards.Heat - Balancing.Current.PassiveFuelVaporHeatOffset) / Balancing.Current.PassiveFuelVaporDivisor)
|
||||||
|
},
|
||||||
|
EPipeMedium.Coolant => cell.Hazards with {
|
||||||
|
CoolantPooling = cell.Hazards.CoolantPooling + cell.LeakRate,
|
||||||
|
Heat = cell.Hazards.Heat - Math.Max(Balancing.Current.MinimumCoolantHeatReduction, cell.LeakRate / Balancing.Current.CoolantHeatReductionDivisor),
|
||||||
|
Smoke = cell.Hazards.Smoke + (cell.Hazards.Heat >= Balancing.Current.CoolantSteamHeatThreshold ? Balancing.Current.CoolantSteamSmokeIncrease : Balancing.Current.MinHazardValue)
|
||||||
|
},
|
||||||
|
EPipeMedium.Pressure => cell.Hazards with { Smoke = cell.Hazards.Smoke + (cell.Pressure >= Balancing.Current.PressureLeakSmokeThreshold ? Balancing.Current.PressureLeakSmokeIncrease : Balancing.Current.MinHazardValue) },
|
||||||
|
_ => cell.Hazards
|
||||||
|
};
|
||||||
|
|
||||||
|
return cell with { Hazards = hazards.Clamp() };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
|
public sealed class SmokeSpreadEffect : IAreaSimulationEffect
|
||||||
|
{
|
||||||
|
public CellState[] Apply(LevelState level, CellState[] cells)
|
||||||
|
{
|
||||||
|
var next = cells.ToArray();
|
||||||
|
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
||||||
|
{
|
||||||
|
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
||||||
|
{
|
||||||
|
var position = new GridPosition(x, y);
|
||||||
|
var cell = cells[level.Index(position)];
|
||||||
|
if (cell.Hazards.Smoke < Balancing.Current.SmokeSpreadThreshold)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
SpreadToNeighbors(level, next, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SpreadToNeighbors(LevelState level, CellState[] next, GridPosition position)
|
||||||
|
{
|
||||||
|
foreach (var neighbor in position.Neighbors().Where(level.InBounds))
|
||||||
|
{
|
||||||
|
var neighborCell = next[level.Index(neighbor)];
|
||||||
|
if (!neighborCell.IsWalkable || neighborCell.DoorLocked)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
next[level.Index(neighbor)] = neighborCell with { Hazards = neighborCell.Hazards with { Smoke = Rules.Clamp(neighborCell.Hazards.Smoke + Balancing.Current.SmokeSpreadIncrease) } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/ReactorMaintenance.Simulation/Hazards/Hazard.cs
Normal file
8
src/ReactorMaintenance.Simulation/Hazards/Hazard.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
|
public abstract class Hazard
|
||||||
|
{
|
||||||
|
public abstract IEnumerable<Forecast> Predict(LevelState level, int turns);
|
||||||
|
}
|
||||||
20
src/ReactorMaintenance.Simulation/Hazards/IgnitionHazard.cs
Normal file
20
src/ReactorMaintenance.Simulation/Hazards/IgnitionHazard.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
|
public sealed class IgnitionHazard : Hazard
|
||||||
|
{
|
||||||
|
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
||||||
|
{
|
||||||
|
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
||||||
|
{
|
||||||
|
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
||||||
|
{
|
||||||
|
var position = new GridPosition(x, y);
|
||||||
|
var cell = level.GetCell(position);
|
||||||
|
if (cell.Hazards.Fire)
|
||||||
|
yield return new(EFailureKind.Ignition, position, turns, turns == Balancing.Current.TurnIncrement ? $"FUEL IGNITION PREDICTED AT {x},{y} NEXT TURN" : $"FUEL IGNITION PREDICTED AT {x},{y} IN {turns} TURNS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/ReactorMaintenance.Simulation/Hazards/MeltdownHazard.cs
Normal file
12
src/ReactorMaintenance.Simulation/Hazards/MeltdownHazard.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
|
public sealed class MeltdownHazard : Hazard
|
||||||
|
{
|
||||||
|
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
||||||
|
{
|
||||||
|
if (level.Global is { Lost: true, Status: "CORE MELTDOWN" })
|
||||||
|
yield return new(EFailureKind.Meltdown, null, turns, "CORE MELTDOWN APPROACHING");
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/ReactorMaintenance.Simulation/Hazards/PipeBurstHazard.cs
Normal file
20
src/ReactorMaintenance.Simulation/Hazards/PipeBurstHazard.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
|
public sealed class PipeBurstHazard : Hazard
|
||||||
|
{
|
||||||
|
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
||||||
|
{
|
||||||
|
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
||||||
|
{
|
||||||
|
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
||||||
|
{
|
||||||
|
var position = new GridPosition(x, y);
|
||||||
|
var cell = level.GetCell(position);
|
||||||
|
if (cell is { HasPipe: true, PipeOpen: false } && cell.Flow == Balancing.Current.BrokenPipeFlow && cell.LeakRate >= Balancing.Current.BurstLeakRate)
|
||||||
|
yield return new(EFailureKind.PipeBurst, position, turns, $"PIPE BURST PREDICTED AT {x},{y} IN {turns} TURNS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
|
public sealed class StabilityCollapseHazard : Hazard
|
||||||
|
{
|
||||||
|
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
||||||
|
{
|
||||||
|
if (level.Global is { Lost: true, Status: "FACILITY STABILITY COLLAPSE" })
|
||||||
|
yield return new(EFailureKind.StabilityCollapse, null, turns, "FACILITY STABILITY COLLAPSE APPROACHING");
|
||||||
|
}
|
||||||
|
}
|
||||||
117
src/ReactorMaintenance.Simulation/LevelEditor.cs
Normal file
117
src/ReactorMaintenance.Simulation/LevelEditor.cs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
public enum EEditorTool
|
||||||
|
{
|
||||||
|
Floor,
|
||||||
|
Wall,
|
||||||
|
Reactor,
|
||||||
|
CoolingPump,
|
||||||
|
Generator,
|
||||||
|
PressureRegulator,
|
||||||
|
DiagnosticTerminal,
|
||||||
|
ControlTerminal,
|
||||||
|
CoolantPipe,
|
||||||
|
FuelPipe,
|
||||||
|
PressurePipe,
|
||||||
|
Leak,
|
||||||
|
Repair,
|
||||||
|
Heat,
|
||||||
|
Fire,
|
||||||
|
Robot
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LevelEditor
|
||||||
|
{
|
||||||
|
public static LevelState Apply(LevelState level, GridPosition position, EEditorTool tool)
|
||||||
|
{
|
||||||
|
if (!level.InBounds(position))
|
||||||
|
return level;
|
||||||
|
|
||||||
|
if (tool == EEditorTool.Robot)
|
||||||
|
return level.GetCell(position).IsWalkable ? level with { Robot = position } : level;
|
||||||
|
|
||||||
|
var cell = level.GetCell(position);
|
||||||
|
cell = tool switch {
|
||||||
|
EEditorTool.Floor => cell with { Terrain = ECellTerrain.Floor },
|
||||||
|
EEditorTool.Wall => cell with {
|
||||||
|
Terrain = ECellTerrain.Wall,
|
||||||
|
Prop = ECellProp.None,
|
||||||
|
Pipe = EPipeMedium.None,
|
||||||
|
Flow = Balancing.Current.MinHazardValue,
|
||||||
|
Pressure = Balancing.Current.MinHazardValue,
|
||||||
|
LeakRate = Balancing.Current.MinHazardValue,
|
||||||
|
PipeOpen = false,
|
||||||
|
Powered = false
|
||||||
|
},
|
||||||
|
EEditorTool.Reactor => cell with { Terrain = ECellTerrain.Floor, Prop = ECellProp.Reactor },
|
||||||
|
EEditorTool.CoolingPump => cell with {
|
||||||
|
Terrain = ECellTerrain.Floor,
|
||||||
|
Prop = ECellProp.CoolingPump,
|
||||||
|
Powered = true
|
||||||
|
},
|
||||||
|
EEditorTool.Generator => cell with {
|
||||||
|
Terrain = ECellTerrain.Floor,
|
||||||
|
Prop = ECellProp.Generator,
|
||||||
|
Powered = true
|
||||||
|
},
|
||||||
|
EEditorTool.PressureRegulator => cell with { Terrain = ECellTerrain.Floor, Prop = ECellProp.PressureRegulator },
|
||||||
|
EEditorTool.DiagnosticTerminal => cell with {
|
||||||
|
Terrain = ECellTerrain.Floor,
|
||||||
|
Prop = ECellProp.DiagnosticTerminal,
|
||||||
|
Powered = true
|
||||||
|
},
|
||||||
|
EEditorTool.ControlTerminal => cell with {
|
||||||
|
Terrain = ECellTerrain.Floor,
|
||||||
|
Prop = ECellProp.ControlTerminal,
|
||||||
|
Powered = true
|
||||||
|
},
|
||||||
|
EEditorTool.CoolantPipe => cell with {
|
||||||
|
Pipe = EPipeMedium.Coolant,
|
||||||
|
Flow = Balancing.Current.DefaultPipeFlow,
|
||||||
|
Pressure = Balancing.Current.DefaultPipePressure,
|
||||||
|
Integrity = Math.Max(cell.Integrity, Balancing.Current.DefaultEditedPipeIntegrity),
|
||||||
|
PipeOpen = true
|
||||||
|
},
|
||||||
|
EEditorTool.FuelPipe => cell with {
|
||||||
|
Pipe = EPipeMedium.Fuel,
|
||||||
|
Flow = Balancing.Current.DefaultPipeFlow,
|
||||||
|
Pressure = Balancing.Current.DefaultPipePressure,
|
||||||
|
Integrity = Math.Max(cell.Integrity, Balancing.Current.DefaultEditedPipeIntegrity),
|
||||||
|
PipeOpen = true
|
||||||
|
},
|
||||||
|
EEditorTool.PressurePipe => cell with {
|
||||||
|
Pipe = EPipeMedium.Pressure,
|
||||||
|
Flow = Balancing.Current.DefaultPressurePipeFlow,
|
||||||
|
Pressure = Balancing.Current.DefaultPressurePipePressure,
|
||||||
|
Integrity = Math.Max(cell.Integrity, Balancing.Current.DefaultEditedPipeIntegrity),
|
||||||
|
PipeOpen = true
|
||||||
|
},
|
||||||
|
EEditorTool.Leak => cell with {
|
||||||
|
LeakRate = Math.Max(Balancing.Current.MinimumLeakRate, cell.LeakRate),
|
||||||
|
Integrity = Math.Min(cell.Integrity, Balancing.Current.DamagedPipeIntegrity)
|
||||||
|
},
|
||||||
|
EEditorTool.Repair => cell with {
|
||||||
|
LeakRate = Balancing.Current.RepairedLeakRate,
|
||||||
|
Integrity = Balancing.Current.DefaultCellIntegrity,
|
||||||
|
Hazards = cell.Hazards with {
|
||||||
|
Fire = false,
|
||||||
|
ElectricalCharge = Balancing.Current.RepairedElectricalCharge
|
||||||
|
}
|
||||||
|
},
|
||||||
|
EEditorTool.Heat => cell with { Hazards = cell.Hazards with { Heat = Rules.Clamp(cell.Hazards.Heat + Balancing.Current.HeatToolIncrease) } },
|
||||||
|
EEditorTool.Fire => cell with {
|
||||||
|
Hazards = cell.Hazards with {
|
||||||
|
Fire = !cell.Hazards.Fire,
|
||||||
|
Heat = Math.Max(cell.Hazards.Heat, Balancing.Current.FireToolMinimumHeat),
|
||||||
|
Smoke = Math.Max(cell.Hazards.Smoke, Balancing.Current.FireToolMinimumSmoke)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => cell
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cell.Terrain == ECellTerrain.Wall)
|
||||||
|
cell = cell with { Hazards = new() };
|
||||||
|
|
||||||
|
return level.SetCell(position, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/ReactorMaintenance.Simulation/LevelSerializer.cs
Normal file
39
src/ReactorMaintenance.Simulation/LevelSerializer.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
public static class LevelSerializer
|
||||||
|
{
|
||||||
|
private const int c_CurrentVersion = 1;
|
||||||
|
|
||||||
|
public static string Serialize(LevelState level)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(new LevelFile {
|
||||||
|
Version = c_CurrentVersion,
|
||||||
|
Level = level
|
||||||
|
}, Options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LevelState Deserialize(string json)
|
||||||
|
{
|
||||||
|
var file = JsonSerializer.Deserialize<LevelFile>(json, Options) ?? throw new InvalidOperationException("Level file did not contain a level.");
|
||||||
|
var level = file.Version switch {
|
||||||
|
c_CurrentVersion => file.Level,
|
||||||
|
_ => throw new InvalidOperationException($"Unsupported level file version {file.Version}.")
|
||||||
|
};
|
||||||
|
|
||||||
|
return level.Cells.Length != level.Width * level.Height ? throw new InvalidOperationException("Level cell count does not match its dimensions.") : level;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions Options = new() {
|
||||||
|
WriteIndented = true,
|
||||||
|
Converters = { new JsonStringEnumConverter() }
|
||||||
|
};
|
||||||
|
|
||||||
|
private sealed record LevelFile
|
||||||
|
{
|
||||||
|
public int Version { get; init; }
|
||||||
|
public LevelState Level { get; init; } = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
181
src/ReactorMaintenance.Simulation/Models.cs
Normal file
181
src/ReactorMaintenance.Simulation/Models.cs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
public enum ECellTerrain
|
||||||
|
{
|
||||||
|
Floor,
|
||||||
|
Wall
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ECellProp
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Reactor,
|
||||||
|
CoolingPump,
|
||||||
|
Generator,
|
||||||
|
PressureRegulator,
|
||||||
|
DiagnosticTerminal,
|
||||||
|
ControlTerminal
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EPipeMedium
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Pressure,
|
||||||
|
Coolant,
|
||||||
|
Fuel
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EFailureKind
|
||||||
|
{
|
||||||
|
PipeBurst,
|
||||||
|
Ignition,
|
||||||
|
Meltdown,
|
||||||
|
StabilityCollapse,
|
||||||
|
ReactorReady
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record GridPosition(int X, int Y)
|
||||||
|
{
|
||||||
|
public IEnumerable<GridPosition> Neighbors()
|
||||||
|
{
|
||||||
|
yield return new(X - Balancing.Current.NeighborDistance, Y);
|
||||||
|
yield return new(X + Balancing.Current.NeighborDistance, Y);
|
||||||
|
yield return new(X, Y - Balancing.Current.NeighborDistance);
|
||||||
|
yield return new(X, Y + Balancing.Current.NeighborDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record HazardState
|
||||||
|
{
|
||||||
|
public HazardState Clamp()
|
||||||
|
{
|
||||||
|
return this with {
|
||||||
|
Heat = Rules.Clamp(Heat),
|
||||||
|
Smoke = Rules.Clamp(Smoke),
|
||||||
|
FuelVapor = Rules.Clamp(FuelVapor),
|
||||||
|
LiquidFuel = Rules.Clamp(LiquidFuel),
|
||||||
|
CoolantPooling = Rules.Clamp(CoolantPooling),
|
||||||
|
ElectricalCharge = Rules.Clamp(ElectricalCharge),
|
||||||
|
Stability = Rules.Clamp(Stability)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Heat { get; init; }
|
||||||
|
public int Smoke { get; init; }
|
||||||
|
public int FuelVapor { get; init; }
|
||||||
|
public int LiquidFuel { get; init; }
|
||||||
|
public int CoolantPooling { get; init; }
|
||||||
|
public int ElectricalCharge { get; init; }
|
||||||
|
public int Stability { get; init; } = Balancing.Current.DefaultHazardStability;
|
||||||
|
public bool Fire { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record CellState
|
||||||
|
{
|
||||||
|
public ECellTerrain Terrain { get; init; } = ECellTerrain.Floor;
|
||||||
|
public ECellProp Prop { get; init; }
|
||||||
|
public EPipeMedium Pipe { get; init; }
|
||||||
|
public int Flow { get; init; }
|
||||||
|
public int Pressure { get; init; }
|
||||||
|
public int Integrity { get; init; } = Balancing.Current.DefaultCellIntegrity;
|
||||||
|
public int LeakRate { get; init; }
|
||||||
|
public bool PipeOpen { get; init; } = true;
|
||||||
|
public bool Powered { get; init; }
|
||||||
|
public bool DoorLocked { get; init; }
|
||||||
|
public HazardState Hazards { get; init; } = new();
|
||||||
|
public bool IsWalkable => Terrain != ECellTerrain.Wall;
|
||||||
|
public bool HasPipe => Pipe != EPipeMedium.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record GlobalState
|
||||||
|
{
|
||||||
|
public int Turn { get; init; }
|
||||||
|
public int ActionsPerTurn { get; init; } = Balancing.Current.DefaultActionsPerTurn;
|
||||||
|
public int CoreHeat { get; init; } = Balancing.Current.DefaultCoreHeat;
|
||||||
|
public int FacilityStability { get; init; } = Balancing.Current.DefaultFacilityStability;
|
||||||
|
public int Power { get; init; } = Balancing.Current.DefaultPower;
|
||||||
|
public int Cooling { get; init; } = Balancing.Current.DefaultCooling;
|
||||||
|
public bool ReactorActivated { get; init; }
|
||||||
|
public bool Lost { get; init; }
|
||||||
|
public string Status { get; init; } = "STABILIZE SYSTEMS";
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record Forecast(EFailureKind Kind, GridPosition? Position, int Turns, string Message);
|
||||||
|
|
||||||
|
public sealed record LevelState
|
||||||
|
{
|
||||||
|
public static LevelState Create(string name, int width, int height)
|
||||||
|
{
|
||||||
|
if (width < Balancing.Current.MinimumLevelSize || height < Balancing.Current.MinimumLevelSize)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(width), $"Levels must be at least {Balancing.Current.MinimumLevelSize}x{Balancing.Current.MinimumLevelSize}.");
|
||||||
|
|
||||||
|
var cells = CreateCells(width, height);
|
||||||
|
for (var y = Balancing.Current.FirstGridCoordinate; y < height; y++)
|
||||||
|
{
|
||||||
|
for (var x = Balancing.Current.FirstGridCoordinate; x < width; x++)
|
||||||
|
{
|
||||||
|
if (x == Balancing.Current.FirstGridCoordinate || y == Balancing.Current.FirstGridCoordinate || x == width - Balancing.Current.NeighborDistance || y == height - Balancing.Current.NeighborDistance)
|
||||||
|
cells[y * width + x] = cells[y * width + x] with { Terrain = ECellTerrain.Wall };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new() {
|
||||||
|
Name = name,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
Cells = cells,
|
||||||
|
Robot = new(Balancing.Current.DefaultRobotCoordinate, Balancing.Current.DefaultRobotCoordinate)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellState GetCell(GridPosition position)
|
||||||
|
{
|
||||||
|
EnsureInBounds(position);
|
||||||
|
return Cells[Index(position)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelState SetCell(GridPosition position, CellState cell)
|
||||||
|
{
|
||||||
|
EnsureInBounds(position);
|
||||||
|
var cells = Cells.ToArray();
|
||||||
|
cells[Index(position)] = cell;
|
||||||
|
return this with { Cells = cells };
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool InBounds(GridPosition position)
|
||||||
|
{
|
||||||
|
return position.X >= Balancing.Current.FirstGridCoordinate && position.Y >= Balancing.Current.FirstGridCoordinate && position.X < Width && position.Y < Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Index(GridPosition position)
|
||||||
|
{
|
||||||
|
return position.Y * Width + position.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureInBounds(GridPosition position)
|
||||||
|
{
|
||||||
|
if (!InBounds(position))
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(position), $"Position {position.X},{position.Y} is outside {Width}x{Height}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CellState[] CreateCells(int width, int height)
|
||||||
|
{
|
||||||
|
return Enumerable.Range(Balancing.Current.FirstGridCoordinate, width * height).Select(_ => new CellState()).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; init; } = "New Reactor";
|
||||||
|
public int Width { get; init; } = Balancing.Current.DefaultLevelWidth;
|
||||||
|
public int Height { get; init; } = Balancing.Current.DefaultLevelHeight;
|
||||||
|
public CellState[] Cells { get; init; } = CreateCells(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight);
|
||||||
|
public GridPosition Robot { get; init; } = new(Balancing.Current.DefaultRobotCoordinate, Balancing.Current.DefaultRobotCoordinate);
|
||||||
|
public GlobalState Global { get; init; } = new();
|
||||||
|
public IReadOnlyList<Forecast> Forecasts { get; init; } = Array.Empty<Forecast>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class Rules
|
||||||
|
{
|
||||||
|
public static int Clamp(int value)
|
||||||
|
{
|
||||||
|
return Math.Clamp(value, Balancing.Current.MinHazardValue, Balancing.Current.MaxHazardValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
150
src/ReactorMaintenance.Simulation/SimulationEngine.cs
Normal file
150
src/ReactorMaintenance.Simulation/SimulationEngine.cs
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
using ReactorMaintenance.Simulation.Effects;
|
||||||
|
using ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
public sealed class SimulationEngine(IEnumerable<ISimulationEffect> effects, IEnumerable<IAreaSimulationEffect> areaEffects, IEnumerable<Hazard> hazards)
|
||||||
|
{
|
||||||
|
private sealed record ForecastKey(EFailureKind Kind, GridPosition? Position);
|
||||||
|
|
||||||
|
public SimulationEngine()
|
||||||
|
: this(
|
||||||
|
[new PipeLeakEffect(), new MachineEffect(), new FireAndElectricalHazardEffect(), new CellIntegrityEffect()],
|
||||||
|
[new SmokeSpreadEffect()],
|
||||||
|
[new PipeBurstHazard(), new IgnitionHazard(), new MeltdownHazard(), new StabilityCollapseHazard()])
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelState AdvanceTurn(LevelState level)
|
||||||
|
{
|
||||||
|
return AdvanceTurn(level, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<Forecast> Forecast(LevelState level)
|
||||||
|
{
|
||||||
|
var forecasts = new List<Forecast>();
|
||||||
|
var seen = new HashSet<ForecastKey>();
|
||||||
|
var forecastLevel = level with { Cells = level.Cells.ToArray(), Forecasts = Array.Empty<Forecast>() };
|
||||||
|
if (forecastLevel.Global.Lost)
|
||||||
|
AddHazardForecasts(forecasts, seen, forecastLevel, Balancing.Current.CurrentForecastTurn);
|
||||||
|
|
||||||
|
AddReactorReadyForecast(forecasts, seen, forecastLevel, Balancing.Current.CurrentForecastTurn);
|
||||||
|
|
||||||
|
if (IsReactorReady(forecastLevel) || forecastLevel.Global.Lost || forecastLevel.Global.ReactorActivated)
|
||||||
|
return forecasts.OrderBy(f => f.Turns).ThenBy(f => f.Message).ToArray();
|
||||||
|
|
||||||
|
for (var step = Balancing.Current.TurnIncrement; step <= Balancing.Current.MaxForecastStepCount; step++)
|
||||||
|
{
|
||||||
|
forecastLevel = AdvanceTurn(forecastLevel, false);
|
||||||
|
AddHazardForecasts(forecasts, seen, forecastLevel, step);
|
||||||
|
AddReactorReadyForecast(forecasts, seen, forecastLevel, step);
|
||||||
|
|
||||||
|
if (forecastLevel.Global.Lost || IsReactorReady(forecastLevel) || forecastLevel.Global.ReactorActivated)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return forecasts.OrderBy(f => f.Turns).ThenBy(f => f.Message).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelState ActivateReactor(LevelState level)
|
||||||
|
{
|
||||||
|
if (!IsReactorReady(level))
|
||||||
|
return level with { Global = level.Global with { Status = "REACTOR NOT READY" } };
|
||||||
|
|
||||||
|
return level with {
|
||||||
|
Global = level.Global with {
|
||||||
|
ReactorActivated = true,
|
||||||
|
Status = "REACTOR ONLINE"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private LevelState AdvanceTurn(LevelState level, bool updateForecasts)
|
||||||
|
{
|
||||||
|
var cells = level.Cells.ToArray();
|
||||||
|
|
||||||
|
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
||||||
|
{
|
||||||
|
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
||||||
|
{
|
||||||
|
var position = new GridPosition(x, y);
|
||||||
|
var index = level.Index(position);
|
||||||
|
var cell = cells[index];
|
||||||
|
|
||||||
|
if (!cell.IsWalkable)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var effect in m_Effects)
|
||||||
|
cell = effect.Apply(cell);
|
||||||
|
|
||||||
|
cells[index] = cell with { Hazards = cell.Hazards.Clamp() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var areaEffect in m_AreaEffects)
|
||||||
|
cells = areaEffect.Apply(level, cells);
|
||||||
|
|
||||||
|
var global = UpdateGlobal(level, cells);
|
||||||
|
var next = level with {
|
||||||
|
Cells = cells,
|
||||||
|
Global = global with { Turn = level.Global.Turn + Balancing.Current.TurnIncrement }
|
||||||
|
};
|
||||||
|
|
||||||
|
return updateForecasts ? next with { Forecasts = Forecast(next) } : next;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddHazardForecasts(List<Forecast> forecasts, HashSet<ForecastKey> seen, LevelState level, int turns)
|
||||||
|
{
|
||||||
|
foreach (var hazard in m_Hazards)
|
||||||
|
{
|
||||||
|
foreach (var forecast in hazard.Predict(level, turns))
|
||||||
|
AddForecast(forecasts, seen, forecast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddReactorReadyForecast(List<Forecast> forecasts, HashSet<ForecastKey> seen, LevelState level, int turns)
|
||||||
|
{
|
||||||
|
if (IsReactorReady(level))
|
||||||
|
AddForecast(forecasts, seen, new(EFailureKind.ReactorReady, null, turns, "REACTOR READY"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddForecast(List<Forecast> forecasts, HashSet<ForecastKey> seen, Forecast forecast)
|
||||||
|
{
|
||||||
|
if (seen.Add(new(forecast.Kind, forecast.Position)))
|
||||||
|
forecasts.Add(forecast);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GlobalState UpdateGlobal(LevelState level, CellState[] cells)
|
||||||
|
{
|
||||||
|
var reactorHeat = cells.Where(c => c.Prop == ECellProp.Reactor).Select(c => c.Hazards.Heat).DefaultIfEmpty(level.Global.CoreHeat).Max();
|
||||||
|
var poweredGenerators = cells.Count(c => c is { Prop: ECellProp.Generator, Powered: true, Hazards.Fire: false });
|
||||||
|
var poweredPumps = cells.Count(c => c is { Prop: ECellProp.CoolingPump, Powered: true, Hazards.Fire: false });
|
||||||
|
var damagedCriticalCells = cells.Count(c => c.Prop is ECellProp.Reactor or ECellProp.Generator or ECellProp.CoolingPump && c.Hazards.Stability <= Balancing.Current.CriticalCellStabilityThreshold);
|
||||||
|
var stability = Rules.Clamp(level.Global.FacilityStability - damagedCriticalCells);
|
||||||
|
var lost = reactorHeat >= Balancing.Current.MeltdownCoreHeatThreshold || stability <= Balancing.Current.StabilityCollapseThreshold;
|
||||||
|
var status = lost ? reactorHeat >= Balancing.Current.MeltdownCoreHeatThreshold ? "CORE MELTDOWN" : "FACILITY STABILITY COLLAPSE" : "STABILIZE SYSTEMS";
|
||||||
|
var global = level.Global with {
|
||||||
|
CoreHeat = Rules.Clamp(reactorHeat - poweredPumps),
|
||||||
|
Power = Rules.Clamp(poweredGenerators * Balancing.Current.GeneratorPowerOutput),
|
||||||
|
Cooling = Rules.Clamp(poweredPumps * Balancing.Current.CoolingPumpOutput),
|
||||||
|
FacilityStability = stability,
|
||||||
|
Lost = lost,
|
||||||
|
Status = status
|
||||||
|
};
|
||||||
|
|
||||||
|
return IsReactorReady(level with { Cells = cells, Global = global }) ? global with { Status = "REACTOR READY" } : global;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsReactorReady(LevelState level)
|
||||||
|
{
|
||||||
|
var hasReactor = level.Cells.Any(c => c.Prop == ECellProp.Reactor);
|
||||||
|
var hasStablePower = level.Global.Power >= Balancing.Current.ReactorReadyPowerThreshold || level.Cells.Any(c => c is { Prop: ECellProp.Generator, Powered: true, Hazards.Fire: false });
|
||||||
|
var hasCooling = level.Global.Cooling >= Balancing.Current.ReactorReadyCoolingThreshold || level.Cells.Any(c => c is { Prop: ECellProp.CoolingPump, Powered: true, Hazards.Fire: false });
|
||||||
|
var reactorStable = level.Global.CoreHeat < Balancing.Current.ReactorReadyCoreHeatThreshold;
|
||||||
|
return hasReactor && hasStablePower && hasCooling && reactorStable && !level.Global.Lost;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IReadOnlyList<IAreaSimulationEffect> m_AreaEffects = areaEffects.ToArray();
|
||||||
|
private readonly IReadOnlyList<ISimulationEffect> m_Effects = effects.ToArray();
|
||||||
|
private readonly IReadOnlyList<Hazard> m_Hazards = hazards.ToArray();
|
||||||
|
}
|
||||||
5
src/ReactorMaintenance.Win2D/App.xaml
Normal file
5
src/ReactorMaintenance.Win2D/App.xaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<Application
|
||||||
|
x:Class="ReactorMaintenance.Win2D.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
</Application>
|
||||||
19
src/ReactorMaintenance.Win2D/App.xaml.cs
Normal file
19
src/ReactorMaintenance.Win2D/App.xaml.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Win2D;
|
||||||
|
|
||||||
|
public partial class App
|
||||||
|
{
|
||||||
|
public App()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||||
|
{
|
||||||
|
m_Window = new MainWindow();
|
||||||
|
m_Window.Activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Window? m_Window;
|
||||||
|
}
|
||||||
90
src/ReactorMaintenance.Win2D/MainWindow.xaml
Normal file
90
src/ReactorMaintenance.Win2D/MainWindow.xaml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<Window
|
||||||
|
x:Class="ReactorMaintenance.Win2D.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||||
|
Title="Reactor Maintenance">
|
||||||
|
|
||||||
|
<Grid Background="#16191D">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<CommandBar Grid.Row="0" DefaultLabelPosition="Right" Background="#20252A">
|
||||||
|
<AppBarButton Icon="Add" Label="New" Click="New_Click" />
|
||||||
|
<AppBarButton Icon="OpenFile" Label="Open" Click="Open_Click" />
|
||||||
|
<AppBarButton Icon="Save" Label="Save" Click="Save_Click" />
|
||||||
|
<AppBarSeparator />
|
||||||
|
<AppBarButton Icon="Play" Label="Simulate" Click="Simulate_Click" />
|
||||||
|
<AppBarButton Icon="Accept" Label="Activate" Click="Activate_Click" />
|
||||||
|
</CommandBar>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" ColumnSpacing="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="220" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="300" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Column="0" Background="#1C2126">
|
||||||
|
<StackPanel Padding="12" Spacing="10">
|
||||||
|
<TextBlock Text="Tools" FontSize="18" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
||||||
|
<ComboBox x:Name="ToolPicker" SelectionChanged="ToolPicker_SelectionChanged" />
|
||||||
|
<TextBlock Text="Brush applies to the selected cell." Foreground="#9EA7AE" TextWrapping="Wrap" />
|
||||||
|
<TextBlock Text="Left click paints. Use Robot to set the start position." Foreground="#9EA7AE"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Grid Grid.Column="1" Background="#101215">
|
||||||
|
<canvas:CanvasControl
|
||||||
|
x:Name="LevelCanvas"
|
||||||
|
ClearColor="#101215"
|
||||||
|
Draw="LevelCanvas_Draw"
|
||||||
|
PointerPressed="LevelCanvas_PointerPressed"
|
||||||
|
PointerMoved="LevelCanvas_PointerMoved"
|
||||||
|
PointerReleased="LevelCanvas_PointerReleased" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Column="2" Background="#1C2126">
|
||||||
|
<StackPanel Padding="14" Spacing="12">
|
||||||
|
<TextBlock x:Name="LevelNameText" FontSize="20" FontWeight="SemiBold" Foreground="#F4F1E8"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<Grid ColumnSpacing="8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="Turn" Foreground="#9EA7AE" />
|
||||||
|
<TextBlock x:Name="TurnText" FontSize="22" Foreground="#F4F1E8" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="1">
|
||||||
|
<TextBlock Text="Status" Foreground="#9EA7AE" />
|
||||||
|
<TextBlock x:Name="StatusText" FontSize="16" Foreground="#F4F1E8" TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<TextBlock Text="Global Systems" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
||||||
|
<TextBlock x:Name="GlobalText" Foreground="#CCD4DA" TextWrapping="Wrap" />
|
||||||
|
|
||||||
|
<TextBlock Text="Selected Cell" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
||||||
|
<TextBlock x:Name="CellText" Foreground="#CCD4DA" TextWrapping="Wrap" />
|
||||||
|
|
||||||
|
<TextBlock Text="Forecasts" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
||||||
|
<ItemsControl x:Name="ForecastList">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border BorderBrush="#46515A" BorderThickness="1" Padding="8" Margin="0,0,0,8"
|
||||||
|
CornerRadius="3">
|
||||||
|
<TextBlock Text="{Binding Message}" Foreground="#F4F1E8" TextWrapping="Wrap" />
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
499
src/ReactorMaintenance.Win2D/MainWindow.xaml.cs
Normal file
499
src/ReactorMaintenance.Win2D/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,499 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Storage;
|
||||||
|
using Windows.Storage.Pickers;
|
||||||
|
using Windows.UI;
|
||||||
|
using Microsoft.Graphics.Canvas;
|
||||||
|
using Microsoft.Graphics.Canvas.Geometry;
|
||||||
|
using Microsoft.Graphics.Canvas.Text;
|
||||||
|
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||||
|
using Microsoft.UI;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
using System.Globalization;
|
||||||
|
using Windows.UI.Popups;
|
||||||
|
using WinRT.Interop;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Win2D;
|
||||||
|
|
||||||
|
public sealed partial class MainWindow
|
||||||
|
{
|
||||||
|
private sealed record CanvasLayout(double CellSize, double OriginX, double OriginY)
|
||||||
|
{
|
||||||
|
public Rect CellRect(int x, int y)
|
||||||
|
{
|
||||||
|
return new(OriginX + x * CellSize, OriginY + y * CellSize, CellSize, CellSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rect DualTileRect(int x, int y)
|
||||||
|
{
|
||||||
|
return new(OriginX + (x - 0.5) * CellSize, OriginY + (y - 0.5) * CellSize, CellSize, CellSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
m_Level = BuildStarterLevel();
|
||||||
|
ToolPicker.ItemsSource = Enum.GetValues<EEditorTool>();
|
||||||
|
ToolPicker.SelectedItem = m_SelectedTool;
|
||||||
|
RefreshInspector();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToolPicker_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ToolPicker.SelectedItem is EEditorTool tool)
|
||||||
|
m_SelectedTool = tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void New_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
m_Level = BuildStarterLevel();
|
||||||
|
m_CurrentFile = null;
|
||||||
|
m_SelectedCell = null;
|
||||||
|
RefreshInspector();
|
||||||
|
LevelCanvas.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Open_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var picker = new FileOpenPicker();
|
||||||
|
InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
|
||||||
|
picker.FileTypeFilter.Add(".json");
|
||||||
|
|
||||||
|
var file = await picker.PickSingleFileAsync();
|
||||||
|
if (file is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var json = await FileIO.ReadTextAsync(file);
|
||||||
|
m_Level = LevelSerializer.Deserialize(json);
|
||||||
|
m_Level = m_Level with { Forecasts = m_Simulation.Forecast(m_Level) };
|
||||||
|
m_CurrentFile = file;
|
||||||
|
m_SelectedCell = null;
|
||||||
|
RefreshInspector();
|
||||||
|
LevelCanvas.Invalidate();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
var messageDialog = new MessageDialog(e.Message);
|
||||||
|
_ = await messageDialog.ShowAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Save_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var file = m_CurrentFile;
|
||||||
|
if (file is null)
|
||||||
|
{
|
||||||
|
var picker = new FileSavePicker();
|
||||||
|
InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
|
||||||
|
picker.SuggestedFileName = m_Level.Name.Replace(' ', '-').ToLowerInvariant();
|
||||||
|
picker.FileTypeChoices.Add("Reactor level", [".json"]);
|
||||||
|
file = await picker.PickSaveFileAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await FileIO.WriteTextAsync(file, LevelSerializer.Serialize(m_Level));
|
||||||
|
m_CurrentFile = file;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
var messageDialog = new MessageDialog(e.Message);
|
||||||
|
_ = await messageDialog.ShowAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Simulate_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
m_Level = m_Simulation.AdvanceTurn(m_Level);
|
||||||
|
RefreshInspector();
|
||||||
|
LevelCanvas.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Activate_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
m_Level = m_Simulation.ActivateReactor(m_Level);
|
||||||
|
RefreshInspector();
|
||||||
|
LevelCanvas.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LevelCanvas_PointerPressed(object sender, PointerRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
m_Painting = true;
|
||||||
|
_ = LevelCanvas.CapturePointer(e.Pointer);
|
||||||
|
PaintAt(e.GetCurrentPoint(LevelCanvas).Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LevelCanvas_PointerMoved(object sender, PointerRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (m_Painting)
|
||||||
|
PaintAt(e.GetCurrentPoint(LevelCanvas).Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LevelCanvas_PointerReleased(object sender, PointerRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
m_Painting = false;
|
||||||
|
LevelCanvas.ReleasePointerCapture(e.Pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PaintAt(Point point)
|
||||||
|
{
|
||||||
|
if (!TryGetGridPosition(point, out var position))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_SelectedCell = position;
|
||||||
|
m_Level = LevelEditor.Apply(m_Level, position, m_SelectedTool);
|
||||||
|
m_Level = m_Level with { Forecasts = m_Simulation.Forecast(m_Level) };
|
||||||
|
RefreshInspector();
|
||||||
|
LevelCanvas.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LevelCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
|
||||||
|
{
|
||||||
|
var drawing = args.DrawingSession;
|
||||||
|
var layout = GetLayout();
|
||||||
|
|
||||||
|
drawing.Clear(ColorHelper.FromArgb(255, 16, 18, 21));
|
||||||
|
DrawTerrain(drawing, layout);
|
||||||
|
DrawCellOverlays(drawing, layout);
|
||||||
|
DrawGrid(drawing, layout);
|
||||||
|
DrawRobot(drawing, layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTerrain(CanvasDrawingSession drawing, CanvasLayout layout)
|
||||||
|
{
|
||||||
|
for (var y = 0; y <= m_Level.Height; y++)
|
||||||
|
for (var x = 0; x <= m_Level.Width; x++)
|
||||||
|
DrawDualTerrainTile(drawing, layout.DualTileRect(x, y), GetDualTileMask(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCellOverlays(CanvasDrawingSession drawing, CanvasLayout layout)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < m_Level.Height; y++)
|
||||||
|
for (var x = 0; x < m_Level.Width; x++)
|
||||||
|
{
|
||||||
|
var position = new GridPosition(x, y);
|
||||||
|
var cell = m_Level.GetCell(position);
|
||||||
|
var rect = layout.CellRect(x, y);
|
||||||
|
|
||||||
|
if (cell.HasPipe)
|
||||||
|
{
|
||||||
|
var center = new Vector2((float)(rect.X + rect.Width / 2), (float)(rect.Y + rect.Height / 2));
|
||||||
|
var pipeColor = cell.Pipe switch {
|
||||||
|
EPipeMedium.Coolant => Colors.DeepSkyBlue,
|
||||||
|
EPipeMedium.Fuel => Colors.Goldenrod,
|
||||||
|
EPipeMedium.Pressure => Colors.LightSteelBlue,
|
||||||
|
_ => Colors.Transparent
|
||||||
|
};
|
||||||
|
drawing.DrawLine(center with { X = (float)rect.X + 6 }, center with { X = (float)(rect.X + rect.Width - 6) }, pipeColor, Math.Max(3, (float)rect.Width / 7));
|
||||||
|
drawing.DrawLine(center with { Y = (float)rect.Y + 6 }, center with { Y = (float)(rect.Y + rect.Height - 6) }, pipeColor, Math.Max(3, (float)rect.Width / 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell.LeakRate > 0)
|
||||||
|
drawing.DrawCircle(new((float)(rect.X + rect.Width - 10), (float)(rect.Y + 10)), 5, Colors.OrangeRed, 2);
|
||||||
|
|
||||||
|
if (cell.Hazards.Fire)
|
||||||
|
drawing.FillCircle(new((float)(rect.X + rect.Width * 0.5), (float)(rect.Y + rect.Height * 0.5)), (float)rect.Width * 0.24f, Colors.OrangeRed);
|
||||||
|
|
||||||
|
if (m_SelectedCell == position)
|
||||||
|
drawing.DrawRectangle(rect, Colors.White, 3);
|
||||||
|
|
||||||
|
DrawCellProp(drawing, cell, rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDualTerrainTile(CanvasDrawingSession drawing, Rect rect, int floorMask)
|
||||||
|
{
|
||||||
|
var wallColor = ColorHelper.FromArgb(255, 54, 61, 68);
|
||||||
|
var floorColor = ColorHelper.FromArgb(255, 31, 36, 40);
|
||||||
|
var wallMask = c_AllCorners ^ floorMask;
|
||||||
|
|
||||||
|
drawing.FillRectangle(rect, floorColor);
|
||||||
|
DrawDualTerrainTile(drawing, rect, wallColor, floorColor, GetDualTerrainTileId(wallMask));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetDualTerrainTileId(int wallMask)
|
||||||
|
{
|
||||||
|
return wallMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawDualTerrainTile(CanvasDrawingSession drawing, Rect rect, Color wallColor, Color floorColor, int tileId)
|
||||||
|
{
|
||||||
|
if (tileId == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tileId == c_AllCorners)
|
||||||
|
{
|
||||||
|
drawing.FillRectangle(rect, wallColor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (tileId)
|
||||||
|
{
|
||||||
|
case c_TopLeftCorner:
|
||||||
|
case c_TopRightCorner:
|
||||||
|
case c_BottomLeftCorner:
|
||||||
|
case c_BottomRightCorner:
|
||||||
|
DrawTerrainCorner(drawing, rect, wallColor, tileId);
|
||||||
|
break;
|
||||||
|
case c_TopLeftCorner | c_TopRightCorner:
|
||||||
|
drawing.FillRectangle(new(rect.X, rect.Y, rect.Width, rect.Height / 2), wallColor);
|
||||||
|
break;
|
||||||
|
case c_BottomLeftCorner | c_BottomRightCorner:
|
||||||
|
drawing.FillRectangle(new(rect.X, rect.Y + rect.Height / 2, rect.Width, rect.Height / 2), wallColor);
|
||||||
|
break;
|
||||||
|
case c_TopLeftCorner | c_BottomLeftCorner:
|
||||||
|
drawing.FillRectangle(new(rect.X, rect.Y, rect.Width / 2, rect.Height), wallColor);
|
||||||
|
break;
|
||||||
|
case c_TopRightCorner | c_BottomRightCorner:
|
||||||
|
drawing.FillRectangle(new(rect.X + rect.Width / 2, rect.Y, rect.Width / 2, rect.Height), wallColor);
|
||||||
|
break;
|
||||||
|
case c_TopLeftCorner | c_BottomRightCorner:
|
||||||
|
DrawTerrainCorner(drawing, rect, wallColor, c_TopLeftCorner);
|
||||||
|
DrawTerrainCorner(drawing, rect, wallColor, c_BottomRightCorner);
|
||||||
|
break;
|
||||||
|
case c_TopRightCorner | c_BottomLeftCorner:
|
||||||
|
DrawTerrainCorner(drawing, rect, wallColor, c_TopRightCorner);
|
||||||
|
DrawTerrainCorner(drawing, rect, wallColor, c_BottomLeftCorner);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
drawing.FillRectangle(rect, wallColor);
|
||||||
|
DrawTerrainCorner(drawing, rect, floorColor, c_AllCorners ^ tileId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawTerrainCorner(CanvasDrawingSession drawing, Rect rect, Color color, int corner)
|
||||||
|
{
|
||||||
|
var radiusX = (float)rect.Width / 2;
|
||||||
|
var radiusY = (float)rect.Height / 2;
|
||||||
|
var center = corner switch {
|
||||||
|
c_TopLeftCorner => new Vector2((float)rect.X, (float)rect.Y),
|
||||||
|
c_TopRightCorner => new Vector2((float)(rect.X + rect.Width), (float)rect.Y),
|
||||||
|
c_BottomRightCorner => new Vector2((float)(rect.X + rect.Width), (float)(rect.Y + rect.Height)),
|
||||||
|
c_BottomLeftCorner => new Vector2((float)rect.X, (float)(rect.Y + rect.Height)),
|
||||||
|
_ => new Vector2((float)(rect.X + rect.Width / 2), (float)(rect.Y + rect.Height / 2))
|
||||||
|
};
|
||||||
|
var start = corner switch {
|
||||||
|
c_TopLeftCorner => new Vector2(center.X + radiusX, center.Y),
|
||||||
|
c_TopRightCorner => new Vector2(center.X, center.Y + radiusY),
|
||||||
|
c_BottomRightCorner => new Vector2(center.X - radiusX, center.Y),
|
||||||
|
c_BottomLeftCorner => new Vector2(center.X, center.Y - radiusY),
|
||||||
|
_ => center
|
||||||
|
};
|
||||||
|
var end = corner switch {
|
||||||
|
c_TopLeftCorner => new Vector2(center.X, center.Y + radiusY),
|
||||||
|
c_TopRightCorner => new Vector2(center.X - radiusX, center.Y),
|
||||||
|
c_BottomRightCorner => new Vector2(center.X, center.Y - radiusY),
|
||||||
|
c_BottomLeftCorner => new Vector2(center.X + radiusX, center.Y),
|
||||||
|
_ => center
|
||||||
|
};
|
||||||
|
|
||||||
|
using var builder = new CanvasPathBuilder(drawing);
|
||||||
|
builder.BeginFigure(center);
|
||||||
|
builder.AddLine(start);
|
||||||
|
builder.AddArc(end, radiusX, radiusY, 0, CanvasSweepDirection.Clockwise, CanvasArcSize.Small);
|
||||||
|
builder.EndFigure(CanvasFigureLoop.Closed);
|
||||||
|
|
||||||
|
using var geometry = CanvasGeometry.CreatePath(builder);
|
||||||
|
drawing.FillGeometry(geometry, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetDualTileMask(int x, int y)
|
||||||
|
{
|
||||||
|
var mask = 0;
|
||||||
|
if (GetTerrainOrWall(x - 1, y - 1) == ECellTerrain.Floor)
|
||||||
|
mask |= c_TopLeftCorner;
|
||||||
|
|
||||||
|
if (GetTerrainOrWall(x, y - 1) == ECellTerrain.Floor)
|
||||||
|
mask |= c_TopRightCorner;
|
||||||
|
|
||||||
|
if (GetTerrainOrWall(x - 1, y) == ECellTerrain.Floor)
|
||||||
|
mask |= c_BottomLeftCorner;
|
||||||
|
|
||||||
|
if (GetTerrainOrWall(x, y) == ECellTerrain.Floor)
|
||||||
|
mask |= c_BottomRightCorner;
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ECellTerrain GetTerrainOrWall(int x, int y)
|
||||||
|
{
|
||||||
|
var position = new GridPosition(x, y);
|
||||||
|
return m_Level.InBounds(position) ? m_Level.GetCell(position).Terrain : ECellTerrain.Wall;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCellProp(CanvasDrawingSession drawing, CellState cell, Rect rect)
|
||||||
|
{
|
||||||
|
var text = cell.Prop switch {
|
||||||
|
ECellProp.Reactor => "R",
|
||||||
|
ECellProp.CoolingPump => "C",
|
||||||
|
ECellProp.Generator => "G",
|
||||||
|
ECellProp.PressureRegulator => "P",
|
||||||
|
ECellProp.DiagnosticTerminal => "D",
|
||||||
|
ECellProp.ControlTerminal => "T",
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var propRect = new Rect(rect.X + rect.Width * 0.18, rect.Y + rect.Height * 0.18, rect.Width * 0.64, rect.Height * 0.64);
|
||||||
|
drawing.FillRoundedRectangle(propRect, 4, 4, PropColor(cell.Prop));
|
||||||
|
drawing.DrawRoundedRectangle(propRect, 4, 4, ColorHelper.FromArgb(210, 12, 14, 16), 2);
|
||||||
|
|
||||||
|
using var format = new CanvasTextFormat();
|
||||||
|
format.FontSize = Math.Max(14, (float)rect.Width * 0.34f);
|
||||||
|
format.HorizontalAlignment = CanvasHorizontalAlignment.Center;
|
||||||
|
format.VerticalAlignment = CanvasVerticalAlignment.Center;
|
||||||
|
|
||||||
|
drawing.DrawText(text, propRect, Colors.White, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGrid(CanvasDrawingSession drawing, CanvasLayout layout)
|
||||||
|
{
|
||||||
|
for (var x = 0; x <= m_Level.Width; x++)
|
||||||
|
{
|
||||||
|
var xPos = (float)(layout.OriginX + x * layout.CellSize);
|
||||||
|
drawing.DrawLine(xPos, (float)layout.OriginY, xPos, (float)(layout.OriginY + m_Level.Height * layout.CellSize), ColorHelper.FromArgb(120, 91, 104, 115), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var y = 0; y <= m_Level.Height; y++)
|
||||||
|
{
|
||||||
|
var yPos = (float)(layout.OriginY + y * layout.CellSize);
|
||||||
|
drawing.DrawLine((float)layout.OriginX, yPos, (float)(layout.OriginX + m_Level.Width * layout.CellSize), yPos, ColorHelper.FromArgb(120, 91, 104, 115), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRobot(CanvasDrawingSession drawing, CanvasLayout layout)
|
||||||
|
{
|
||||||
|
var rect = layout.CellRect(m_Level.Robot.X, m_Level.Robot.Y);
|
||||||
|
var center = new Vector2((float)(rect.X + rect.Width / 2), (float)(rect.Y + rect.Height / 2));
|
||||||
|
drawing.FillCircle(center, (float)rect.Width * 0.28f, Colors.White);
|
||||||
|
drawing.DrawCircle(center, (float)rect.Width * 0.28f, Colors.Black, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetGridPosition(Point point, out GridPosition position)
|
||||||
|
{
|
||||||
|
var layout = GetLayout();
|
||||||
|
var x = (int)((point.X - layout.OriginX) / layout.CellSize);
|
||||||
|
var y = (int)((point.Y - layout.OriginY) / layout.CellSize);
|
||||||
|
position = new(x, y);
|
||||||
|
return m_Level.InBounds(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CanvasLayout GetLayout()
|
||||||
|
{
|
||||||
|
var availableWidth = Math.Max(1, LevelCanvas.ActualWidth);
|
||||||
|
var availableHeight = Math.Max(1, LevelCanvas.ActualHeight);
|
||||||
|
var size = Math.Floor(Math.Min(availableWidth / m_Level.Width, availableHeight / m_Level.Height));
|
||||||
|
size = Math.Max(20, size);
|
||||||
|
var originX = Math.Max(0, (availableWidth - size * m_Level.Width) / 2);
|
||||||
|
var originY = Math.Max(0, (availableHeight - size * m_Level.Height) / 2);
|
||||||
|
return new(size, originX, originY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color PropColor(ECellProp prop)
|
||||||
|
{
|
||||||
|
return prop switch {
|
||||||
|
ECellProp.Reactor => ColorHelper.FromArgb(255, 61, 76, 82),
|
||||||
|
ECellProp.CoolingPump => ColorHelper.FromArgb(255, 25, 79, 96),
|
||||||
|
ECellProp.Generator => ColorHelper.FromArgb(255, 86, 75, 35),
|
||||||
|
ECellProp.PressureRegulator => ColorHelper.FromArgb(255, 70, 78, 98),
|
||||||
|
ECellProp.DiagnosticTerminal => ColorHelper.FromArgb(255, 39, 84, 62),
|
||||||
|
ECellProp.ControlTerminal => ColorHelper.FromArgb(255, 80, 61, 91),
|
||||||
|
_ => Colors.Transparent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshInspector()
|
||||||
|
{
|
||||||
|
LevelNameText.Text = m_Level.Name;
|
||||||
|
TurnText.Text = m_Level.Global.Turn.ToString(CultureInfo.InvariantCulture);
|
||||||
|
StatusText.Text = m_Level.Global.Status;
|
||||||
|
GlobalText.Text = $"Power: {m_Level.Global.Power}/10\n" + $"Cooling: {m_Level.Global.Cooling}/10\n" + $"Core Heat: {m_Level.Global.CoreHeat}/10\n" + $"Facility Stability: {m_Level.Global.FacilityStability}/10";
|
||||||
|
|
||||||
|
if (m_SelectedCell is { } position && m_Level.InBounds(position))
|
||||||
|
{
|
||||||
|
var cell = m_Level.GetCell(position);
|
||||||
|
CellText.Text = $"Position: {position.X},{position.Y}\n" + $"Terrain: {cell.Terrain}\n" + $"Prop: {cell.Prop}\n" + $"Pipe: {cell.Pipe}\n" + $"Flow: {cell.Flow}, Pressure: {cell.Pressure}\n" + $"Integrity: {cell.Integrity}, Leak: {cell.LeakRate}\n" + $"Heat: {cell.Hazards.Heat}, Smoke: {cell.Hazards.Smoke}\n" + $"Fuel Vapor: {cell.Hazards.FuelVapor}, Fuel: {cell.Hazards.LiquidFuel}\n" + $"Coolant: {cell.Hazards.CoolantPooling}, Charge: {cell.Hazards.ElectricalCharge}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
CellText.Text = "No cell selected.";
|
||||||
|
|
||||||
|
ForecastList.ItemsSource = m_Level.Forecasts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LevelState BuildStarterLevel()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Cooling Sector B", 16, 12);
|
||||||
|
level = level.SetCell(new(3, 5), new() {
|
||||||
|
Prop = ECellProp.CoolingPump,
|
||||||
|
Pipe = EPipeMedium.Coolant,
|
||||||
|
Flow = 5,
|
||||||
|
Pressure = 5,
|
||||||
|
Powered = true
|
||||||
|
});
|
||||||
|
level = level.SetCell(new(4, 5), new() {
|
||||||
|
Pipe = EPipeMedium.Coolant,
|
||||||
|
Flow = 5,
|
||||||
|
Pressure = 7
|
||||||
|
});
|
||||||
|
level = level.SetCell(new(5, 5), new() {
|
||||||
|
Pipe = EPipeMedium.Coolant,
|
||||||
|
Flow = 3,
|
||||||
|
Pressure = 8,
|
||||||
|
LeakRate = 2,
|
||||||
|
Integrity = 4
|
||||||
|
});
|
||||||
|
level = level.SetCell(new(6, 5), new() {
|
||||||
|
Pipe = EPipeMedium.Coolant,
|
||||||
|
Flow = 3,
|
||||||
|
Pressure = 7
|
||||||
|
});
|
||||||
|
level = level.SetCell(new(8, 5), new() {
|
||||||
|
Prop = ECellProp.Reactor,
|
||||||
|
Hazards = new() {
|
||||||
|
Heat = 6,
|
||||||
|
Stability = 8
|
||||||
|
}
|
||||||
|
});
|
||||||
|
level = level.SetCell(new(2, 8), new() {
|
||||||
|
Prop = ECellProp.Generator,
|
||||||
|
Pipe = EPipeMedium.Fuel,
|
||||||
|
Flow = 4,
|
||||||
|
Pressure = 6,
|
||||||
|
Powered = true
|
||||||
|
});
|
||||||
|
level = level.SetCell(new(11, 4), new() {
|
||||||
|
Prop = ECellProp.DiagnosticTerminal,
|
||||||
|
Powered = true
|
||||||
|
});
|
||||||
|
level = level.SetCell(new(12, 8), new() {
|
||||||
|
Prop = ECellProp.ControlTerminal,
|
||||||
|
Powered = true
|
||||||
|
});
|
||||||
|
return level with { Forecasts = new SimulationEngine().Forecast(level) };
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly SimulationEngine m_Simulation = new();
|
||||||
|
private const int c_TopLeftCorner = 1;
|
||||||
|
private const int c_TopRightCorner = 2;
|
||||||
|
private const int c_BottomLeftCorner = 4;
|
||||||
|
private const int c_BottomRightCorner = 8;
|
||||||
|
private const int c_AllCorners = c_TopLeftCorner | c_TopRightCorner | c_BottomLeftCorner | c_BottomRightCorner;
|
||||||
|
private StorageFile? m_CurrentFile;
|
||||||
|
private LevelState m_Level;
|
||||||
|
private bool m_Painting;
|
||||||
|
private GridPosition? m_SelectedCell;
|
||||||
|
private EEditorTool m_SelectedTool = EEditorTool.Floor;
|
||||||
|
}
|
||||||
27
src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj
Normal file
27
src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<RootNamespace>ReactorMaintenance.Win2D</RootNamespace>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<Platforms>x86;x64;arm64</Platforms>
|
||||||
|
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||||
|
<UseWinUI>true</UseWinUI>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<WindowsPackageType>None</WindowsPackageType>
|
||||||
|
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.4.0" />
|
||||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.1839" />
|
||||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.260317003" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ReactorMaintenance.Simulation\ReactorMaintenance.Simulation.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
10
src/ReactorMaintenance.Win2D/app.manifest
Normal file
10
src/ReactorMaintenance.Win2D/app.manifest
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="ReactorMaintenance.Win2D.app"/>
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.5.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\ReactorMaintenance.Simulation\ReactorMaintenance.Simulation.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
using ReactorMaintenance.Simulation.Difficulties;
|
||||||
|
using ReactorMaintenance.Simulation.Effects;
|
||||||
|
using ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Simulation.Tests;
|
||||||
|
|
||||||
|
public sealed class SimulationEngineTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void FuelLeakNearPoweredGeneratorCreatesIgnitionForecast()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Fuel leak", 6, 6)
|
||||||
|
.SetCell(new(2, 2), new() {
|
||||||
|
Prop = ECellProp.Generator,
|
||||||
|
Pipe = EPipeMedium.Fuel,
|
||||||
|
LeakRate = Balancing.Current.FuelVaporFireThreshold,
|
||||||
|
Pressure = Balancing.Current.PressurizedFuelLeakPressureThreshold + Balancing.Current.NeighborDistance,
|
||||||
|
Integrity = Balancing.Current.DefaultEditedPipeIntegrity,
|
||||||
|
Powered = true
|
||||||
|
});
|
||||||
|
|
||||||
|
var forecasts = m_Engine.Forecast(level);
|
||||||
|
|
||||||
|
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.Ignition && forecast.Position == new GridPosition(2, 2) && forecast.Turns == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CoolantLeakOnPoweredCellRaisesElectricalCharge()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Wet cable", 6, 6)
|
||||||
|
.SetCell(new(3, 3), new() {
|
||||||
|
Pipe = EPipeMedium.Coolant,
|
||||||
|
LeakRate = Balancing.Current.ElectrifiedCoolantPoolingThreshold,
|
||||||
|
Powered = true
|
||||||
|
});
|
||||||
|
|
||||||
|
var next = m_Engine.AdvanceTurn(level);
|
||||||
|
|
||||||
|
Assert.True(next.GetCell(new(3, 3)).Hazards.ElectricalCharge >= Balancing.Current.ElectricalChargeIncrease);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ActiveFireSpreadsSmokeToOpenNeighbors()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Smoke", 6, 6)
|
||||||
|
.SetCell(new(2, 2), new() {
|
||||||
|
Hazards = new() {
|
||||||
|
Fire = true,
|
||||||
|
Smoke = Balancing.Current.SmokeSpreadThreshold
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var next = m_Engine.AdvanceTurn(level);
|
||||||
|
|
||||||
|
Assert.True(next.GetCell(new(2, 3)).Hazards.Smoke > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AdvanceTurnRunsConfiguredCellEffects()
|
||||||
|
{
|
||||||
|
var engine = new SimulationEngine([new TestCellEffect()], [], []);
|
||||||
|
var level = LevelState.Create("Custom effect", 6, 6)
|
||||||
|
.SetCell(new(2, 2), new() {
|
||||||
|
Hazards = new() { Heat = 1 }
|
||||||
|
});
|
||||||
|
|
||||||
|
var next = engine.AdvanceTurn(level);
|
||||||
|
|
||||||
|
Assert.Equal(5, next.GetCell(new(2, 2)).Hazards.Heat);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AdvanceTurnRunsConfiguredAreaEffects()
|
||||||
|
{
|
||||||
|
var engine = new SimulationEngine([], [new TestAreaEffect()], []);
|
||||||
|
var level = LevelState.Create("Custom area effect", 6, 6);
|
||||||
|
|
||||||
|
var next = engine.AdvanceTurn(level);
|
||||||
|
|
||||||
|
Assert.Equal(7, next.GetCell(new(2, 2)).Hazards.Smoke);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OverpressurePredictsPipeBurst()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Pressure", 6, 6)
|
||||||
|
.SetCell(new(1, 2), new() {
|
||||||
|
Pipe = EPipeMedium.Pressure,
|
||||||
|
Pressure = 10,
|
||||||
|
Integrity = 6
|
||||||
|
});
|
||||||
|
|
||||||
|
var forecasts = m_Engine.Forecast(level);
|
||||||
|
|
||||||
|
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.PipeBurst && forecast.Turns == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForecastCapsFutureSimulationWhenNoTerminalConditionOccurs()
|
||||||
|
{
|
||||||
|
var engine = new SimulationEngine([], [], [new StepCountingHazard()]);
|
||||||
|
var level = LevelState.Create("Stable", 6, 6);
|
||||||
|
|
||||||
|
var forecasts = engine.Forecast(level);
|
||||||
|
|
||||||
|
Assert.Equal(Balancing.Current.MaxForecastStepCount, forecasts.Count);
|
||||||
|
Assert.Equal(Balancing.Current.MaxForecastStepCount, forecasts.Max(forecast => forecast.Turns));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForecastUsesCurrentBalancingProfile()
|
||||||
|
{
|
||||||
|
var previous = Balancing.Current;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Balancing.Current = new TestBalancing();
|
||||||
|
var engine = new SimulationEngine([], [], [new StepCountingHazard()]);
|
||||||
|
var level = LevelState.Create("Stable", 6, 6);
|
||||||
|
|
||||||
|
var forecasts = engine.Forecast(level);
|
||||||
|
|
||||||
|
Assert.Equal(Balancing.Current.MaxForecastStepCount, forecasts.Count);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Balancing.Current = previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForecastPredictsMeltdownFromFutureSimulation()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Meltdown", 6, 6)
|
||||||
|
.SetCell(new(2, 2), new() {
|
||||||
|
Prop = ECellProp.Reactor,
|
||||||
|
Hazards = new() { Heat = Balancing.Current.MeltdownCoreHeatThreshold - Balancing.Current.ReactorHeatIncrease }
|
||||||
|
});
|
||||||
|
|
||||||
|
var forecasts = m_Engine.Forecast(level);
|
||||||
|
|
||||||
|
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.Meltdown && forecast.Turns == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForecastReportsAlreadyLostLevelAtCurrentTurn()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Lost", 6, 6) with {
|
||||||
|
Global = new() {
|
||||||
|
CoreHeat = Balancing.Current.MeltdownCoreHeatThreshold,
|
||||||
|
Lost = true,
|
||||||
|
Status = "CORE MELTDOWN"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var forecasts = m_Engine.Forecast(level);
|
||||||
|
|
||||||
|
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.Meltdown && forecast.Turns == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForecastPredictsStabilityCollapseFromFutureSimulation()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Collapse", 6, 6)
|
||||||
|
.SetCell(new(2, 2), new() {
|
||||||
|
Prop = ECellProp.Generator,
|
||||||
|
Hazards = new() { Stability = Balancing.Current.CriticalCellStabilityThreshold }
|
||||||
|
}) with {
|
||||||
|
Global = new() { FacilityStability = Balancing.Current.FireStabilityDamage }
|
||||||
|
};
|
||||||
|
|
||||||
|
var forecasts = m_Engine.Forecast(level);
|
||||||
|
|
||||||
|
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.StabilityCollapse && forecast.Turns == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StableReactorWithPowerAndCoolingCanActivate()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Ready", 8, 6)
|
||||||
|
.SetCell(new(2, 2), new() {
|
||||||
|
Prop = ECellProp.Reactor,
|
||||||
|
Hazards = new() { Heat = 3 }
|
||||||
|
})
|
||||||
|
.SetCell(new(3, 2), new() {
|
||||||
|
Prop = ECellProp.Generator,
|
||||||
|
Powered = true
|
||||||
|
})
|
||||||
|
.SetCell(new(4, 2), new() {
|
||||||
|
Prop = ECellProp.CoolingPump,
|
||||||
|
Powered = true
|
||||||
|
});
|
||||||
|
|
||||||
|
var next = m_Engine.AdvanceTurn(level);
|
||||||
|
var activated = m_Engine.ActivateReactor(next);
|
||||||
|
|
||||||
|
Assert.Equal("REACTOR ONLINE", activated.Global.Status);
|
||||||
|
Assert.True(activated.Global.ReactorActivated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LevelSerializationRoundTripsEditableState()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Round trip", 5, 5);
|
||||||
|
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.Reactor);
|
||||||
|
level = LevelEditor.Apply(level, new(1, 2), EEditorTool.CoolantPipe);
|
||||||
|
level = LevelEditor.Apply(level, new(1, 2), EEditorTool.Leak);
|
||||||
|
|
||||||
|
var json = LevelSerializer.Serialize(level);
|
||||||
|
var loaded = LevelSerializer.Deserialize(json);
|
||||||
|
|
||||||
|
Assert.Contains("\"Version\": 1", json);
|
||||||
|
Assert.Equal(level.Name, loaded.Name);
|
||||||
|
Assert.Equal(ECellProp.Reactor, loaded.GetCell(new(2, 2)).Prop);
|
||||||
|
Assert.Equal(EPipeMedium.Coolant, loaded.GetCell(new(1, 2)).Pipe);
|
||||||
|
Assert.Equal(1, loaded.GetCell(new(1, 2)).LeakRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LevelSerializationRejectsUnsupportedVersion()
|
||||||
|
{
|
||||||
|
var json = """
|
||||||
|
{
|
||||||
|
"Version": 999,
|
||||||
|
"Level": {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => LevelSerializer.Deserialize(json));
|
||||||
|
|
||||||
|
Assert.Contains("Unsupported level file version 999", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WallToolClearsCellPropsPipesAndHazards()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Wall", 5, 5);
|
||||||
|
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.Generator);
|
||||||
|
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.CoolantPipe);
|
||||||
|
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.Fire);
|
||||||
|
|
||||||
|
var edited = LevelEditor.Apply(level, new(2, 2), EEditorTool.Wall);
|
||||||
|
var cell = edited.GetCell(new(2, 2));
|
||||||
|
|
||||||
|
Assert.Equal(ECellTerrain.Wall, cell.Terrain);
|
||||||
|
Assert.Equal(ECellProp.None, cell.Prop);
|
||||||
|
Assert.Equal(EPipeMedium.None, cell.Pipe);
|
||||||
|
Assert.False(cell.Powered);
|
||||||
|
Assert.False(cell.Hazards.Fire);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PropToolsKeepFloorTerrain()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Prop", 5, 5);
|
||||||
|
level = LevelEditor.Apply(level, new(1, 1), EEditorTool.Wall);
|
||||||
|
|
||||||
|
var edited = LevelEditor.Apply(level, new(1, 1), EEditorTool.Reactor);
|
||||||
|
var cell = edited.GetCell(new(1, 1));
|
||||||
|
|
||||||
|
Assert.Equal(ECellTerrain.Floor, cell.Terrain);
|
||||||
|
Assert.Equal(ECellProp.Reactor, cell.Prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly SimulationEngine m_Engine = new();
|
||||||
|
|
||||||
|
private sealed class StepCountingHazard : Hazard
|
||||||
|
{
|
||||||
|
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
||||||
|
{
|
||||||
|
yield return new(EFailureKind.PipeBurst, new(turns, 0), turns, $"STEP {turns}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestBalancing : NormalBalancing
|
||||||
|
{
|
||||||
|
public override int MaxForecastStepCount => 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestCellEffect : ISimulationEffect
|
||||||
|
{
|
||||||
|
public CellState Apply(CellState cell)
|
||||||
|
{
|
||||||
|
return cell with { Hazards = cell.Hazards with { Heat = 5 } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestAreaEffect : IAreaSimulationEffect
|
||||||
|
{
|
||||||
|
public CellState[] Apply(LevelState level, CellState[] cells)
|
||||||
|
{
|
||||||
|
var next = cells.ToArray();
|
||||||
|
var position = new GridPosition(2, 2);
|
||||||
|
var cell = next[level.Index(position)];
|
||||||
|
next[level.Index(position)] = cell with { Hazards = cell.Hazards with { Smoke = 7 } };
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user