# frozen_string_literal: truemoduleSashitemoduleSnn# Represents a style in SNN (Style Name Notation) format.## A style consists of a single ASCII letter with case-based side encoding:# - Uppercase letter: first player (A, B, C, ..., Z)# - Lowercase letter: second player (a, b, c, ..., z)## All instances are immutable - transformation methods return new instances.# This follows the SNN Specification v1.0.0 with Letter and Side attributes.classStyle# SNN validation pattern matching the specificationSNN_PATTERN=/\A[A-Za-z]\z/# Player side constantsFIRST_PLAYER=:firstSECOND_PLAYER=:second# Valid sidesVALID_SIDES=[FIRST_PLAYER,SECOND_PLAYER].freeze# Error messagesERROR_INVALID_SNN="Invalid SNN string: %s"ERROR_INVALID_LETTER="Letter must be a single ASCII letter symbol (A-Z, a-z), got: %s"ERROR_INVALID_SIDE="Side must be :first or :second, got: %s"# @return [Symbol] the style letter (single ASCII letter as symbol)attr_reader:letter# @return [Symbol] the player side (:first or :second)attr_reader:side# Create a new style instance## @param letter [Symbol] style letter (single ASCII letter as symbol)# @param side [Symbol] player side (:first or :second)# @raise [ArgumentError] if parameters are invaliddefinitialize(letter,side)self.class.validate_letter(letter)self.class.validate_side(side)@letter=letter@side=sidefreezeend# Parse an SNN string into a Style object## @param snn_string [String] SNN notation string (single ASCII letter)# @return [Style] parsed style object with letter and inferred side# @raise [ArgumentError] if the SNN string is invalid# @example Parse SNN strings with case-based side inference# Sashite::Snn::Style.parse("C") # => #<Snn::Style letter=:C side=:first># Sashite::Snn::Style.parse("c") # => #<Snn::Style letter=:c side=:second># Sashite::Snn::Style.parse("S") # => #<Snn::Style letter=:S side=:first>defself.parse(snn_string)string_value=String(snn_string)validate_snn_string(string_value)# Determine side from casestyle_side=string_value==string_value.upcase?FIRST_PLAYER:SECOND_PLAYER# Use the letter directly as symbolstyle_letter=string_value.to_symnew(style_letter,style_side)end# Check if a string is a valid SNN notation## @param snn_string [String] the string to validate# @return [Boolean] true if valid SNN, false otherwise## @example Validate SNN strings# Sashite::Snn::Style.valid?("C") # => true# Sashite::Snn::Style.valid?("c") # => true# Sashite::Snn::Style.valid?("CHESS") # => false (multi-character)defself.valid?(snn_string)returnfalseunlesssnn_string.is_a?(::String)snn_string.match?(SNN_PATTERN)end# Convert the style to its SNN string representation## @return [String] SNN notation string (single ASCII letter)# @example Display styles# style.to_s # => "C" (first player, C family)# style.to_s # => "c" (second player, C family)# style.to_s # => "S" (first player, S family)defto_sletter.to_send# Create a new style with opposite ownership (side)## @return [Style] new immutable style instance with flipped side# @example Flip player sides# style.flip # (:C, :first) => (:c, :second)defflipnew_letter=first_player??letter.to_s.downcase.to_sym:letter.to_s.upcase.to_symself.class.new(new_letter,opposite_side)end# Create a new style with a different letter (keeping same side)## @param new_letter [Symbol] new letter (single ASCII letter as symbol)# @return [Style] new immutable style instance with different letter# @example Change style letter# style.with_letter(:S) # (:C, :first) => (:S, :first)defwith_letter(new_letter)self.class.validate_letter(new_letter)returnselfifletter==new_letter# Ensure the new letter has the correct case for the current sideadjusted_letter=first_player??new_letter.to_s.upcase.to_sym:new_letter.to_s.downcase.to_symself.class.new(adjusted_letter,side)end# Create a new style with a different side (keeping same letter family)## @param new_side [Symbol] :first or :second# @return [Style] new immutable style instance with different side# @example Change player side# style.with_side(:second) # (:C, :first) => (:c, :second)defwith_side(new_side)self.class.validate_side(new_side)returnselfifside==new_side# Adjust letter case for the new sidenew_letter=new_side==FIRST_PLAYER?letter.to_s.upcase.to_sym:letter.to_s.downcase.to_symself.class.new(new_letter,new_side)end# Check if the style belongs to the first player## @return [Boolean] true if first playerdeffirst_player?side==FIRST_PLAYERend# Check if the style belongs to the second player## @return [Boolean] true if second playerdefsecond_player?side==SECOND_PLAYERend# Check if this style has the same letter family as another## @param other [Style] style to compare with# @return [Boolean] true if both styles use the same letter family (case-insensitive)# @example Compare style letter families# c_style.same_letter?(C_style) # (:c, :second) and (:C, :first) => truedefsame_letter?(other)returnfalseunlessother.is_a?(self.class)letter.to_s.upcase==other.letter.to_s.upcaseend# Check if this style belongs to the same side as another## @param other [Style] style to compare with# @return [Boolean] true if both styles belong to the same sidedefsame_side?(other)returnfalseunlessother.is_a?(self.class)side==other.sideend# Custom equality comparison## @param other [Object] object to compare with# @return [Boolean] true if both objects are styles with identical letter and sidedef==(other)returnfalseunlessother.is_a?(self.class)letter==other.letter&&side==other.sideend# Alias for == to ensure Set functionality works correctlyaliaseql?==# Custom hash implementation for use in collections## @return [Integer] hash value based on class, letter, and sidedefhash[self.class,letter,side].hashend# Validate that the letter is a valid single ASCII letter symbol## @param letter [Symbol] the letter to validate# @raise [ArgumentError] if invaliddefself.validate_letter(letter)returnifvalid_letter?(letter)raise::ArgumentError,format(ERROR_INVALID_LETTER,letter.inspect)end# Validate that the side is a valid symbol## @param side [Symbol] the side to validate# @raise [ArgumentError] if invaliddefself.validate_side(side)returnifVALID_SIDES.include?(side)raise::ArgumentError,format(ERROR_INVALID_SIDE,side.inspect)end# Check if a letter is valid (single ASCII letter symbol)## @param letter [Object] the letter to check# @return [Boolean] true if validdefself.valid_letter?(letter)returnfalseunlessletter.is_a?(::Symbol)letter_string=letter.to_sreturnfalseifletter_string.empty?# Must be exactly one ASCII letterletter_string.match?(SNN_PATTERN)end# Validate SNN string format## @param string [String] string to validate# @raise [ArgumentError] if string doesn't match SNN patterndefself.validate_snn_string(string)returnifstring.match?(SNN_PATTERN)raise::ArgumentError,format(ERROR_INVALID_SNN,string)endprivate_class_method:valid_letter?,:validate_snn_stringprivate# Get the opposite side## @return [Symbol] the opposite sidedefopposite_sidefirst_player??SECOND_PLAYER:FIRST_PLAYERendendendend