Class: Sashite::Sin::Identifier

Inherits:
Object
  • Object
show all
Defined in:
lib/sashite/sin/identifier.rb

Overview

Represents an identifier in SIN (Style Identifier Notation) format.

An identifier 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 SIN Specification v1.0.0 with Letter and Side attributes.

Constant Summary collapse

SIN_PATTERN =

SIN validation pattern matching the specification

/\A[A-Za-z]\z/
FIRST_PLAYER =

Player side constants

:first
SECOND_PLAYER =
:second
VALID_SIDES =

Valid sides

[FIRST_PLAYER, SECOND_PLAYER].freeze
ERROR_INVALID_SIN =

Error messages

"Invalid SIN 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"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(letter, side) ⇒ Identifier

Create a new identifier instance

Parameters:

  • letter (Symbol)

    style letter (single ASCII letter as symbol)

  • side (Symbol)

    player side (:first or :second)

Raises:

  • (ArgumentError)

    if parameters are invalid



40
41
42
43
44
45
46
47
48
# File 'lib/sashite/sin/identifier.rb', line 40

def initialize(letter, side)
  self.class.validate_letter(letter)
  self.class.validate_side(side)

  @letter = letter
  @side = side

  freeze
end

Instance Attribute Details

#letterSymbol (readonly)

Returns the style letter (single ASCII letter as symbol).

Returns:

  • (Symbol)

    the style letter (single ASCII letter as symbol)



30
31
32
# File 'lib/sashite/sin/identifier.rb', line 30

def letter
  @letter
end

#sideSymbol (readonly)

Returns the player side (:first or :second).

Returns:

  • (Symbol)

    the player side (:first or :second)



33
34
35
# File 'lib/sashite/sin/identifier.rb', line 33

def side
  @side
end

Class Method Details

.parse(sin_string) ⇒ Identifier

Parse an SIN string into an Identifier object

Examples:

Parse SIN strings with case-based side inference

Sashite::Sin::Identifier.parse("C") # => #<Sin::Identifier letter=:C side=:first>
Sashite::Sin::Identifier.parse("c") # => #<Sin::Identifier letter=:c side=:second>
Sashite::Sin::Identifier.parse("S") # => #<Sin::Identifier letter=:S side=:first>

Parameters:

  • sin_string (String)

    SIN notation string (single ASCII letter)

Returns:

  • (Identifier)

    parsed identifier object with letter and inferred side

Raises:

  • (ArgumentError)

    if the SIN string is invalid



59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/sashite/sin/identifier.rb', line 59

def self.parse(sin_string)
  string_value = String(sin_string)
  validate_sin_string(string_value)

  # Determine side from case
  identifier_side = string_value == string_value.upcase ? FIRST_PLAYER : SECOND_PLAYER

  # Use the letter directly as symbol
  identifier_letter = string_value.to_sym

  new(identifier_letter, identifier_side)
end

.valid?(sin_string) ⇒ Boolean

Check if a string is a valid SIN notation

Examples:

Validate SIN strings

Sashite::Sin::Identifier.valid?("C") # => true
Sashite::Sin::Identifier.valid?("c") # => true
Sashite::Sin::Identifier.valid?("CHESS") # => false (multi-character)

Parameters:

  • sin_string (String)

    the string to validate

Returns:

  • (Boolean)

    true if valid SIN, false otherwise



81
82
83
84
85
# File 'lib/sashite/sin/identifier.rb', line 81

def self.valid?(sin_string)
  return false unless sin_string.is_a?(::String)

  sin_string.match?(SIN_PATTERN)
end

.validate_letter(letter) ⇒ Object

Validate that the letter is a valid single ASCII letter symbol

Parameters:

  • letter (Symbol)

    the letter to validate

Raises:

  • (ArgumentError)

    if invalid



198
199
200
201
202
# File 'lib/sashite/sin/identifier.rb', line 198

def self.validate_letter(letter)
  return if valid_letter?(letter)

  raise ::ArgumentError, format(ERROR_INVALID_LETTER, letter.inspect)
end

.validate_side(side) ⇒ Object

Validate that the side is a valid symbol

Parameters:

  • side (Symbol)

    the side to validate

Raises:

  • (ArgumentError)

    if invalid



208
209
210
211
212
# File 'lib/sashite/sin/identifier.rb', line 208

def self.validate_side(side)
  return if VALID_SIDES.include?(side)

  raise ::ArgumentError, format(ERROR_INVALID_SIDE, side.inspect)
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Custom equality comparison

Parameters:

  • other (Object)

    object to compare with

Returns:

  • (Boolean)

    true if both objects are identifiers with identical letter and side



178
179
180
181
182
# File 'lib/sashite/sin/identifier.rb', line 178

def ==(other)
  return false unless other.is_a?(self.class)

  letter == other.letter && side == other.side
end

#first_player?Boolean

Check if the identifier belongs to the first player

Returns:

  • (Boolean)

    true if first player



141
142
143
# File 'lib/sashite/sin/identifier.rb', line 141

def first_player?
  side == FIRST_PLAYER
end

#flipIdentifier

Create a new identifier with opposite ownership (side)

Examples:

Flip player sides

identifier.flip  # (:C, :first) => (:c, :second)

Returns:

  • (Identifier)

    new immutable identifier instance with flipped side



103
104
105
106
# File 'lib/sashite/sin/identifier.rb', line 103

def flip
  new_letter = first_player? ? letter.to_s.downcase.to_sym : letter.to_s.upcase.to_sym
  self.class.new(new_letter, opposite_side)
end

#hashInteger

Custom hash implementation for use in collections

Returns:

  • (Integer)

    hash value based on class, letter, and side



190
191
192
# File 'lib/sashite/sin/identifier.rb', line 190

def hash
  [self.class, letter, side].hash
end

#same_letter?(other) ⇒ Boolean

Check if this identifier has the same letter family as another

Examples:

Compare identifier letter families

c_identifier.same_letter?(C_identifier)  # (:c, :second) and (:C, :first) => true

Parameters:

  • other (Identifier)

    identifier to compare with

Returns:

  • (Boolean)

    true if both identifiers use the same letter family (case-insensitive)



158
159
160
161
162
# File 'lib/sashite/sin/identifier.rb', line 158

def same_letter?(other)
  return false unless other.is_a?(self.class)

  letter.to_s.upcase == other.letter.to_s.upcase
end

#same_side?(other) ⇒ Boolean

Check if this identifier belongs to the same side as another

Parameters:

  • other (Identifier)

    identifier to compare with

Returns:

  • (Boolean)

    true if both identifiers belong to the same side



168
169
170
171
172
# File 'lib/sashite/sin/identifier.rb', line 168

def same_side?(other)
  return false unless other.is_a?(self.class)

  side == other.side
end

#second_player?Boolean

Check if the identifier belongs to the second player

Returns:

  • (Boolean)

    true if second player



148
149
150
# File 'lib/sashite/sin/identifier.rb', line 148

def second_player?
  side == SECOND_PLAYER
end

#to_sString

Convert the identifier to its SIN string representation

Examples:

Display identifiers

identifier.to_s  # => "C" (first player, C family)
identifier.to_s  # => "c" (second player, C family)
identifier.to_s  # => "S" (first player, S family)

Returns:

  • (String)

    SIN notation string (single ASCII letter)



94
95
96
# File 'lib/sashite/sin/identifier.rb', line 94

def to_s
  letter.to_s
end

#with_letter(new_letter) ⇒ Identifier

Create a new identifier with a different letter (keeping same side)

Examples:

Change identifier letter

identifier.with_letter(:S)  # (:C, :first) => (:S, :first)

Parameters:

  • new_letter (Symbol)

    new letter (single ASCII letter as symbol)

Returns:

  • (Identifier)

    new immutable identifier instance with different letter



114
115
116
117
118
119
120
121
# File 'lib/sashite/sin/identifier.rb', line 114

def with_letter(new_letter)
  self.class.validate_letter(new_letter)
  return self if letter == new_letter

  # Ensure the new letter has the correct case for the current side
  adjusted_letter = first_player? ? new_letter.to_s.upcase.to_sym : new_letter.to_s.downcase.to_sym
  self.class.new(adjusted_letter, side)
end

#with_side(new_side) ⇒ Identifier

Create a new identifier with a different side (keeping same letter family)

Examples:

Change player side

identifier.with_side(:second)  # (:C, :first) => (:c, :second)

Parameters:

  • new_side (Symbol)

    :first or :second

Returns:

  • (Identifier)

    new immutable identifier instance with different side



129
130
131
132
133
134
135
136
# File 'lib/sashite/sin/identifier.rb', line 129

def with_side(new_side)
  self.class.validate_side(new_side)
  return self if side == new_side

  # Adjust letter case for the new side
  new_letter = new_side == FIRST_PLAYER ? letter.to_s.upcase.to_sym : letter.to_s.downcase.to_sym
  self.class.new(new_letter, new_side)
end