Sin.rb
SIN (Style Identifier Notation) implementation for the Ruby language.
What is SIN?
SIN (Style Identifier Notation) provides a compact, ASCII-based format for identifying styles in abstract strategy board games. SIN uses single-character identifiers with case encoding to represent both style identity and player assignment simultaneously.
This gem implements the SIN Specification v1.0.0 exactly, providing a rule-agnostic notation system for style identification in board games.
Installation
# In your Gemfile
gem "sashite-sin"
Or install manually:
gem install sashite-sin
Usage
Basic Operations
require "sashite/sin"
# Parse SIN strings into identifier objects
identifier = Sashite::Sin.parse("C") # Family=:C, Side=:first
identifier.to_s # => "C"
identifier.family # => :C
identifier.side # => :first
identifier.letter # => "C" (combined representation)
# Create identifiers directly
identifier = Sashite::Sin.identifier(:C, :first) # Family=:C, Side=:first
identifier = Sashite::Sin::Identifier.new(:C, :second) # Family=:C, Side=:second
# Validate SIN strings
Sashite::Sin.valid?("C") # => true
Sashite::Sin.valid?("c") # => true
Sashite::Sin.valid?("1") # => false (not a letter)
Sashite::Sin.valid?("CC") # => false (not single character)
Identifier Transformations
# All transformations return new immutable instances
identifier = Sashite::Sin.parse("C")
# Flip player assignment
flipped = identifier.flip # Family=:C, Side=:second
flipped.to_s # => "c"
# Change family
changed = identifier.with_family(:S) # Family=:S, Side=:first
changed.to_s # => "S"
# Change side
other_side = identifier.with_side(:second) # Family=:C, Side=:second
other_side.to_s # => "c"
# Chain transformations
result = identifier.flip.with_family(:M) # Family=:M, Side=:second
result.to_s # => "m"
Player and Style Queries
identifier = Sashite::Sin.parse("C")
opposite = Sashite::Sin.parse("s")
# Player identification
identifier.first_player? # => true
identifier.second_player? # => false
opposite.first_player? # => false
opposite.second_player? # => true
# Family and side comparison
chess1 = Sashite::Sin.parse("C")
chess2 = Sashite::Sin.parse("c")
shogi = Sashite::Sin.parse("S")
chess1.same_family?(chess2) # => true (both Chess family)
chess1.same_side?(shogi) # => true (both first player)
chess1.same_family?(shogi) # => false (different families)
Identifier Collections
# Working with multiple identifiers
identifiers = %w[C c S s M m].map { |sin| Sashite::Sin.parse(sin) }
# Filter by player
first_player_identifiers = identifiers.select(&:first_player?)
first_player_identifiers.map(&:to_s) # => ["C", "S", "M"]
# Group by family
by_family = identifiers.group_by(&:family)
by_family[:C].size # => 2 (both C and c)
# Find specific families
chess_identifiers = identifiers.select { |i| i.family == :C }
chess_identifiers.map(&:to_s) # => ["C", "c"]
Format Specification
Structure
<style-letter>
Grammar (BNF)
<sin> ::= <uppercase-letter> | <lowercase-letter>
<uppercase-letter> ::= "A" | "B" | "C" | ... | "Z"
<lowercase-letter> ::= "a" | "b" | "c" | ... | "z"
Regular Expression
/\A[A-Za-z]\z/
Style Attribute Mapping
SIN encodes style attributes using the following correspondence:
Style Attribute | SIN Encoding | Examples |
---|---|---|
Family | Style family symbol |
:C , :S , :X
|
Side | Player assignment |
:first , :second
|
Letter | Combined representation |
"C" , "c" , "S" , "s"
|
Dual-Purpose Encoding
The Letter combines two distinct semantic components:
- Style Family: The underlying family symbol (:A-:Z), representing the game tradition or rule system
- Player Assignment: The side (:first or :second), encoded as case in the letter representation
Examples:
- Family
:C
+ Side:first
→ Letter"C"
(Chess, First player) - Family
:C
+ Side:second
→ Letter"c"
(Chess, Second player) - Family
:S
+ Side:first
→ Letter"S"
(Shōgi, First player) - Family
:S
+ Side:second
→ Letter"s"
(Shōgi, Second player)
Traditional Game Style Examples
The SIN specification is rule-agnostic and does not define specific letter assignments. However, here are common usage patterns following SIN Examples:
# Chess (8×8 board)
chess_white = Sashite::Sin.parse("C") # First player (White pieces)
chess_black = Sashite::Sin.parse("c") # Second player (Black pieces)
# Shōgi (9×9 board)
shogi_sente = Sashite::Sin.parse("S") # First player (Sente 先手)
shogi_gote = Sashite::Sin.parse("s") # Second player (Gote 後手)
# Xiangqi (9×10 board)
xiangqi_red = Sashite::Sin.parse("X") # First player (Red pieces)
xiangqi_black = Sashite::Sin.parse("x") # Second player (Black pieces)
# Makruk (8×8 board)
makruk_white = Sashite::Sin.parse("M") # First player (White pieces)
makruk_black = Sashite::Sin.parse("m") # Second player (Black pieces)
# Janggi (9×10 board)
janggi_cho = Sashite::Sin.parse("J") # First player (Cho 초)
janggi_han = Sashite::Sin.parse("j") # Second player (Han 한)
Cross-Style Scenarios
# Chess vs. Ōgi Match (both 8×8 compatible)
chess_white = Sashite::Sin.parse("C") # Chess style, first player
ogi_black = Sashite::Sin.parse("o") # Ōgi style, second player
# Cross-Style Match Setup
def create_hybrid_match
[
Sashite::Sin.parse("C"), # First player uses Chess family
Sashite::Sin.parse("s") # Second player uses Shōgi family
]
end
identifiers = create_hybrid_match
identifiers[0].same_side?(identifiers[1]) # => false (different players)
identifiers[0].same_family?(identifiers[1]) # => false (different families)
System Constraints
Character Limitation
SIN provides 26 possible identifiers per player using ASCII letters (A-Z, a-z).
Player Limitation
SIN supports exactly two players through case distinction:
-
First player: Uppercase letters (A-Z) →
:first
-
Second player: Lowercase letters (a-z) →
:second
Context Dependency
The specific SIN assignment for a style may vary between different game contexts based on collision avoidance, historical precedence, and community conventions.
API Reference
Main Module Methods
-
Sashite::Sin.valid?(sin_string)
- Check if string is valid SIN notation -
Sashite::Sin.parse(sin_string)
- Parse SIN string into Identifier object -
Sashite::Sin.identifier(family, side)
- Create identifier instance directly
Identifier Class
Creation and Parsing
-
Sashite::Sin::Identifier.new(family, side)
- Create identifier instance -
Sashite::Sin::Identifier.parse(sin_string)
- Parse SIN string
Attribute Access
-
#family
- Get style family (symbol :A through :Z) -
#side
- Get player side (:first or :second) -
#letter
- Get combined letter representation (string) -
#to_s
- Convert to SIN string representation
Player Queries
-
#first_player?
- Check if first player identifier -
#second_player?
- Check if second player identifier
Transformations (immutable - return new instances)
-
#flip
- Switch player assignment -
#with_family(new_family)
- Create identifier with different family -
#with_side(new_side)
- Create identifier with different side
Comparison Methods
-
#same_family?(other)
- Check if same style family -
#same_side?(other)
- Check if same player side -
#==(other)
- Full equality comparison -
#same_letter?(other)
- Alias forsame_family?
(deprecated)
Constants
-
Sashite::Sin::Identifier::FIRST_PLAYER
- Symbol for first player (:first
) -
Sashite::Sin::Identifier::SECOND_PLAYER
- Symbol for second player (:second
) -
Sashite::Sin::Identifier::VALID_FAMILIES
- Array of valid families (:A
to:Z
) -
Sashite::Sin::Identifier::VALID_SIDES
- Array of valid sides -
Sashite::Sin::Identifier::SIN_PATTERN
- Regular expression for SIN validation
Advanced Usage
Family and Side Separation
# Clear separation of concerns
identifier = Sashite::Sin.parse("C")
identifier.family # => :C (Style Family - invariant)
identifier.side # => :first (Player Assignment)
identifier.letter # => "C" (Combined representation)
# Transformations are explicit
chess_white = Sashite::Sin.identifier(:C, :first)
shogi_white = chess_white.with_family(:S) # Change family, keep side
chess_black = chess_white.with_side(:second) # Change side, keep family
Immutable Transformations
# All transformations return new instances
original = Sashite::Sin.identifier(:C, :first)
flipped = original.flip
changed_family = original.with_family(:S)
# Original identifier is never modified
original.to_s # => "C" (unchanged)
flipped.to_s # => "c"
changed_family.to_s # => "S"
# Transformations can be chained
result = original.flip.with_family(:M).flip
result.to_s # => "M"
Design Properties
Following the SIN v1.0.0 specification, this implementation provides:
- ASCII compatibility: Maximum portability across systems
- Rule-agnostic: Independent of specific game mechanics
- Minimal overhead: Single character per style-player combination
- Flexible collision resolution: Systematic approaches for identifier conflicts
- Semantic clarity: Distinct concepts for Family, Side, and Letter
- SNN coordination: Works harmoniously with formal style naming
- Context-aware: Adapts to avoid conflicts within specific game scenarios
- Canonical representation: Each style-player combination has exactly one SIN identifier
- Immutable: All identifier instances are frozen and transformations return new objects
- Functional: Pure functions with no side effects
Related Specifications
- SIN Specification v1.0.0 - Complete technical specification
- SIN Examples - Practical implementation examples
- Style Name Notation (SNN) - Formal naming for game styles
- Sashité Protocol - Conceptual foundation for abstract strategy board games
Documentation
Development
# Clone the repository
git clone https://github.com/sashite/sin.rb.git
cd sin.rb
# Install dependencies
bundle install
# Run tests
ruby test.rb
# Generate documentation
yard doc
License
Available as open source under the MIT License.
About
Maintained by Sashité — promoting chess variants and sharing the beauty of board game cultures.