|
| 1 | +# Sashite.Sin |
| 2 | + |
| 3 | +[](https://hex.pm/packages/sashite_sin) |
| 4 | +[](https://hexdocs.pm/sashite_sin) |
| 5 | +[](https://github.com/sashite/sin.ex/blob/main/LICENSE.md) |
| 6 | + |
| 7 | +> **SIN** (Style Identifier Notation) implementation for Elixir. |
| 8 | +
|
| 9 | +## What is SIN? |
| 10 | + |
| 11 | +SIN (Style Identifier Notation) provides a compact, ASCII-based format for encoding **Piece Style** with an associated **Side** in abstract strategy board games. It serves as a minimal building block that can be embedded in higher-level notations. |
| 12 | + |
| 13 | +This library implements the [SIN Specification v1.0.0](https://sashite.dev/specs/sin/1.0.0/). |
| 14 | + |
| 15 | +## Installation |
| 16 | + |
| 17 | +Add `sashite_sin` to your list of dependencies in `mix.exs`: |
| 18 | + |
| 19 | +```elixir |
| 20 | +def deps do |
| 21 | + [ |
| 22 | + {:sashite_sin, "~> 1.0"} |
| 23 | + ] |
| 24 | +end |
| 25 | +``` |
| 26 | + |
| 27 | +## Usage |
| 28 | + |
| 29 | +```elixir |
| 30 | +# Parse SIN strings |
| 31 | +{:ok, sin} = Sashite.Sin.parse("C") |
| 32 | +sin.style # => :C |
| 33 | +sin.side # => :first |
| 34 | + |
| 35 | +Sashite.Sin.to_string(sin) # => "C" |
| 36 | + |
| 37 | +# Parse with pattern matching |
| 38 | +{:ok, chess_first} = Sashite.Sin.parse("C") # Chess-style, first player |
| 39 | +{:ok, chess_second} = Sashite.Sin.parse("c") # Chess-style, second player |
| 40 | +{:ok, shogi_first} = Sashite.Sin.parse("S") # Shogi-style, first player |
| 41 | + |
| 42 | +# Bang version for direct access |
| 43 | +sin = Sashite.Sin.parse!("C") |
| 44 | + |
| 45 | +# Create identifiers directly |
| 46 | +sin = Sashite.Sin.new(:C, :first) |
| 47 | +sin = Sashite.Sin.new(:S, :second) |
| 48 | + |
| 49 | +# Validation |
| 50 | +Sashite.Sin.valid?("C") # => true |
| 51 | +Sashite.Sin.valid?("s") # => true |
| 52 | +Sashite.Sin.valid?("CC") # => false (more than one character) |
| 53 | +Sashite.Sin.valid?("1") # => false (digit instead of letter) |
| 54 | +Sashite.Sin.valid?("") # => false (empty string) |
| 55 | + |
| 56 | +# Side transformation |
| 57 | +flipped = Sashite.Sin.flip(sin) |
| 58 | +Sashite.Sin.to_string(flipped) # => "c" |
| 59 | + |
| 60 | +# Attribute changes |
| 61 | +shogi = Sashite.Sin.with_style(sin, :S) |
| 62 | +Sashite.Sin.to_string(shogi) # => "S" |
| 63 | + |
| 64 | +second = Sashite.Sin.with_side(sin, :second) |
| 65 | +Sashite.Sin.to_string(second) # => "c" |
| 66 | + |
| 67 | +# Side queries |
| 68 | +Sashite.Sin.first_player?(sin) # => true |
| 69 | +Sashite.Sin.second_player?(sin) # => false |
| 70 | + |
| 71 | +# Comparison |
| 72 | +chess1 = Sashite.Sin.parse!("C") |
| 73 | +chess2 = Sashite.Sin.parse!("c") |
| 74 | + |
| 75 | +Sashite.Sin.same_style?(chess1, chess2) # => true |
| 76 | +Sashite.Sin.same_side?(chess1, chess2) # => false |
| 77 | +``` |
| 78 | + |
| 79 | +## Format Specification |
| 80 | + |
| 81 | +### Structure |
| 82 | + |
| 83 | +``` |
| 84 | +<letter> |
| 85 | +``` |
| 86 | + |
| 87 | +A SIN token is **exactly one** ASCII letter (`A-Z` or `a-z`). |
| 88 | + |
| 89 | +### Attribute Mapping |
| 90 | + |
| 91 | +| Attribute | Encoding | |
| 92 | +|-----------|----------| |
| 93 | +| Piece Style | Base letter (case-insensitive): `C` and `c` represent the same style | |
| 94 | +| Side | Letter case: uppercase → `first`, lowercase → `second` | |
| 95 | + |
| 96 | +### Side Convention |
| 97 | + |
| 98 | +- **Uppercase** (`A-Z`): First player (Side `first`) |
| 99 | +- **Lowercase** (`a-z`): Second player (Side `second`) |
| 100 | + |
| 101 | +### Common Conventions |
| 102 | + |
| 103 | +| SIN | Side | Typical Piece Style | |
| 104 | +|-----|------|---------------------| |
| 105 | +| `C` | First | Chess-style | |
| 106 | +| `c` | Second | Chess-style | |
| 107 | +| `S` | First | Shogi-style | |
| 108 | +| `s` | Second | Shogi-style | |
| 109 | +| `X` | First | Xiangqi-style | |
| 110 | +| `x` | Second | Xiangqi-style | |
| 111 | +| `M` | First | Makruk-style | |
| 112 | +| `m` | Second | Makruk-style | |
| 113 | + |
| 114 | +### Invalid Token Examples |
| 115 | + |
| 116 | +| String | Reason | |
| 117 | +|--------|--------| |
| 118 | +| `""` | Empty string | |
| 119 | +| `CC` | More than one character | |
| 120 | +| `c1` | Contains a digit | |
| 121 | +| `+C` | Contains a prefix character | |
| 122 | +| ` C` | Leading whitespace | |
| 123 | +| `C ` | Trailing whitespace | |
| 124 | +| `1` | Digit instead of letter | |
| 125 | +| `é` | Non-ASCII character | |
| 126 | + |
| 127 | +## API Reference |
| 128 | + |
| 129 | +### Parsing |
| 130 | + |
| 131 | +```elixir |
| 132 | +Sashite.Sin.parse(sin_string) # => {:ok, %Sashite.Sin{}} | {:error, reason} |
| 133 | +Sashite.Sin.parse!(sin_string) # => %Sashite.Sin{} | raises ArgumentError |
| 134 | +Sashite.Sin.valid?(sin_string) # => boolean |
| 135 | +``` |
| 136 | + |
| 137 | +### Creation |
| 138 | + |
| 139 | +```elixir |
| 140 | +Sashite.Sin.new(style, side) |
| 141 | +``` |
| 142 | + |
| 143 | +### Conversion |
| 144 | + |
| 145 | +```elixir |
| 146 | +Sashite.Sin.to_string(sin) # => String.t() |
| 147 | +Sashite.Sin.letter(sin) # => String.t() (the single character) |
| 148 | +``` |
| 149 | + |
| 150 | +### Transformations |
| 151 | + |
| 152 | +All transformations return new `%Sashite.Sin{}` structs: |
| 153 | + |
| 154 | +```elixir |
| 155 | +# Side |
| 156 | +Sashite.Sin.flip(sin) |
| 157 | + |
| 158 | +# Attribute changes |
| 159 | +Sashite.Sin.with_style(sin, new_style) |
| 160 | +Sashite.Sin.with_side(sin, new_side) |
| 161 | +``` |
| 162 | + |
| 163 | +### Queries |
| 164 | + |
| 165 | +```elixir |
| 166 | +# Side |
| 167 | +Sashite.Sin.first_player?(sin) |
| 168 | +Sashite.Sin.second_player?(sin) |
| 169 | + |
| 170 | +# Comparison |
| 171 | +Sashite.Sin.same_style?(sin1, sin2) |
| 172 | +Sashite.Sin.same_side?(sin1, sin2) |
| 173 | +``` |
| 174 | + |
| 175 | +## Data Structure |
| 176 | + |
| 177 | +```elixir |
| 178 | +%Sashite.Sin{ |
| 179 | + style: :A..:Z, # Piece style (always uppercase atom) |
| 180 | + side: :first | :second # Player side |
| 181 | +} |
| 182 | +``` |
| 183 | + |
| 184 | +## Protocol Mapping |
| 185 | + |
| 186 | +Following the [Game Protocol](https://sashite.dev/game-protocol/): |
| 187 | + |
| 188 | +| Protocol Attribute | SIN Encoding | |
| 189 | +|-------------------|--------------| |
| 190 | +| Piece Style | Base letter (case-insensitive) | |
| 191 | +| Side | Letter case | |
| 192 | + |
| 193 | +## Relationship with SNN |
| 194 | + |
| 195 | +**Style Name Notation (SNN)** and SIN are **independent primitives** that serve complementary roles: |
| 196 | + |
| 197 | +- **SIN** — compact, single-character style identifiers (`C`, `s`, `X`) |
| 198 | +- **SNN** — human-readable style names (`CHESS`, `shogi`, `XIANGQI`) |
| 199 | + |
| 200 | +Converting between SIN and SNN requires external, context-specific mapping. |
| 201 | + |
| 202 | +## Related Specifications |
| 203 | + |
| 204 | +- [Game Protocol](https://sashite.dev/game-protocol/) — Conceptual foundation |
| 205 | +- [PIN](https://sashite.dev/specs/pin/1.0.0/) — Piece Identifier Notation |
| 206 | +- [SIN Specification](https://sashite.dev/specs/sin/1.0.0/) — Official specification |
| 207 | + |
| 208 | +## License |
| 209 | + |
| 210 | +Available as open source under the [MIT License](https://opensource.org/licenses/MIT). |
| 211 | + |
| 212 | +## About |
| 213 | + |
| 214 | +Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures. |
0 commit comments