# pin.ex
[](https://hex.pm/packages/sashite_pin)
[](https://hexdocs.pm/sashite_pin)
[](https://github.com/sashite/pin.ex/blob/main/LICENSE)
> **PIN** (Piece Identifier Notation) implementation for Elixir.
## Overview
This library implements the [PIN Specification v1.0.0](https://sashite.dev/specs/pin/1.0.0/).
## Installation
Add `sashite_pin` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:sashite_pin, "~> 2.0"}
]
end
```
## Usage
### Parsing (String → Identifier)
Convert a PIN string into an `Identifier` struct.
```elixir
# Standard parsing (returns {:ok, identifier} or {:error, reason})
{:ok, pin} = Sashite.Pin.parse("K")
pin.type # => :K
pin.side # => :first
pin.state # => :normal
pin.terminal # => false
# With state modifier
{:ok, pin} = Sashite.Pin.parse("+R")
pin.state # => :enhanced
# With terminal marker
{:ok, pin} = Sashite.Pin.parse("K^")
pin.terminal # => true
# Combined
{:ok, pin} = Sashite.Pin.parse("+K^")
pin.state # => :enhanced
pin.terminal # => true
# Bang version (raises on error)
pin = Sashite.Pin.parse!("K")
# Invalid input
{:error, :empty_input} = Sashite.Pin.parse("")
Sashite.Pin.parse!("invalid") # => raises ArgumentError
```
### Formatting (Identifier → String)
Convert an `Identifier` back to a PIN string.
```elixir
# From Identifier struct
pin = Sashite.Pin.Identifier.new(:K, :first)
Sashite.Pin.Identifier.to_string(pin) # => "K"
# With attributes
pin = Sashite.Pin.Identifier.new(:R, :second, :enhanced)
Sashite.Pin.Identifier.to_string(pin) # => "+r"
pin = Sashite.Pin.Identifier.new(:K, :first, :normal, terminal: true)
Sashite.Pin.Identifier.to_string(pin) # => "K^"
```
### Validation
```elixir
# Boolean check
Sashite.Pin.valid?("K") # => true
Sashite.Pin.valid?("+R") # => true
Sashite.Pin.valid?("K^") # => true
Sashite.Pin.valid?("invalid") # => false
```
### Accessing Identifier Data
```elixir
{:ok, pin} = Sashite.Pin.parse("+K^")
# Get attributes (direct struct access)
pin.type # => :K
pin.side # => :first
pin.state # => :enhanced
pin.terminal # => true
# Get string components
Sashite.Pin.Identifier.letter(pin) # => "K"
Sashite.Pin.Identifier.prefix(pin) # => "+"
Sashite.Pin.Identifier.suffix(pin) # => "^"
```
### Transformations
All transformations return new immutable structs.
```elixir
{:ok, pin} = Sashite.Pin.parse("K")
# State transformations
Sashite.Pin.Identifier.enhance(pin) |> Sashite.Pin.Identifier.to_string() # => "+K"
Sashite.Pin.Identifier.diminish(pin) |> Sashite.Pin.Identifier.to_string() # => "-K"
Sashite.Pin.Identifier.normalize(pin) |> Sashite.Pin.Identifier.to_string() # => "K"
# Side transformation
Sashite.Pin.Identifier.flip(pin) |> Sashite.Pin.Identifier.to_string() # => "k"
# Terminal transformations
Sashite.Pin.Identifier.mark_terminal(pin) |> Sashite.Pin.Identifier.to_string() # => "K^"
Sashite.Pin.Identifier.unmark_terminal(pin) |> Sashite.Pin.Identifier.to_string() # => "K"
# Attribute changes
Sashite.Pin.Identifier.with_type(pin, :Q) |> Sashite.Pin.Identifier.to_string() # => "Q"
Sashite.Pin.Identifier.with_side(pin, :second) |> Sashite.Pin.Identifier.to_string() # => "k"
Sashite.Pin.Identifier.with_state(pin, :enhanced) |> Sashite.Pin.Identifier.to_string() # => "+K"
Sashite.Pin.Identifier.with_terminal(pin, true) |> Sashite.Pin.Identifier.to_string() # => "K^"
```
### Queries
```elixir
{:ok, pin} = Sashite.Pin.parse("+K^")
# State queries
Sashite.Pin.Identifier.normal?(pin) # => false
Sashite.Pin.Identifier.enhanced?(pin) # => true
Sashite.Pin.Identifier.diminished?(pin) # => false
# Side queries
Sashite.Pin.Identifier.first_player?(pin) # => true
Sashite.Pin.Identifier.second_player?(pin) # => false
# Terminal query
pin.terminal # => true
# Comparison queries
{:ok, other} = Sashite.Pin.parse("k")
Sashite.Pin.Identifier.same_type?(pin, other) # => true
Sashite.Pin.Identifier.same_side?(pin, other) # => false
Sashite.Pin.Identifier.same_state?(pin, other) # => false
Sashite.Pin.Identifier.same_terminal?(pin, other) # => false
```
## API Reference
### Types
```elixir
# Identifier represents a parsed PIN with all attributes.
defmodule Sashite.Pin.Identifier do
@type t :: %__MODULE__{
type: atom(), # :A to :Z
side: :first | :second,
state: :normal | :enhanced | :diminished,
terminal: boolean()
}
# Creates an Identifier from attributes.
# Raises ArgumentError if attributes are invalid.
@spec new(atom(), atom(), atom(), keyword()) :: t()
def new(type, side, state \\ :normal, opts \\ [])
# Returns the PIN string representation.
@spec to_string(t()) :: String.t()
def to_string(identifier)
end
```
### Constants
```elixir
Sashite.Pin.Constants.valid_types() # => [:A, :B, ..., :Z]
Sashite.Pin.Constants.valid_sides() # => [:first, :second]
Sashite.Pin.Constants.valid_states() # => [:normal, :enhanced, :diminished]
Sashite.Pin.Constants.max_string_length() # => 3
```
### Parsing
```elixir
# Parses a PIN string into an Identifier.
# Returns {:ok, identifier} or {:error, reason}.
@spec Sashite.Pin.parse(String.t()) :: {:ok, Sashite.Pin.Identifier.t()} | {:error, atom()}
# Parses a PIN string into an Identifier.
# Raises ArgumentError if the string is not valid.
@spec Sashite.Pin.parse!(String.t()) :: Sashite.Pin.Identifier.t()
```
### Validation
```elixir
# Reports whether string is a valid PIN.
@spec Sashite.Pin.valid?(String.t()) :: boolean()
```
### Transformations
All transformations return new `%Sashite.Pin.Identifier{}` structs:
```elixir
# State transformations
@spec enhance(t()) :: t()
@spec diminish(t()) :: t()
@spec normalize(t()) :: t()
# Side transformation
@spec flip(t()) :: t()
# Terminal transformations
@spec mark_terminal(t()) :: t()
@spec unmark_terminal(t()) :: t()
# Attribute changes
@spec with_type(t(), atom()) :: t()
@spec with_side(t(), atom()) :: t()
@spec with_state(t(), atom()) :: t()
@spec with_terminal(t(), boolean()) :: t()
```
### Errors
Parsing returns `{:error, reason}` tuples with these atoms:
| Reason | Cause |
|--------|-------|
| `:empty_input` | String length is 0 |
| `:input_too_long` | String exceeds 3 characters |
| `:must_contain_one_letter` | Missing or multiple letters |
| `:invalid_state_modifier` | Invalid prefix character |
| `:invalid_terminal_marker` | Invalid suffix character |
## Design Principles
- **Bounded values**: Explicit validation of types, sides, states
- **Functional style**: Pure functions, immutable structs
- **Elixir idioms**: `{:ok, result}` / `{:error, reason}` tuples, bang functions
- **Pattern matching**: Structs enable pattern matching in function heads
- **Pipe-friendly**: Transformations designed for `|>` operator
- **No dependencies**: Pure Elixir standard library only
## Related Specifications
- [Game Protocol](https://sashite.dev/game-protocol/) — Conceptual foundation
- [PIN Specification](https://sashite.dev/specs/pin/1.0.0/) — Official specification
- [PIN Examples](https://sashite.dev/specs/pin/1.0.0/examples/) — Usage examples
## License
Available as open source under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).