EPIN (Extended Piece Identifier Notation) implementation for Elixir.
EPIN (Extended Piece Identifier Notation) extends PIN by adding a derivation marker to track piece style in cross-style games.
EPIN is simply: PIN + optional style derivation marker (')
This library implements the EPIN Specification v1.0.0 with a minimal compositional API.
# EPIN is just PIN + derived flag
pin = Sashite.Pin.parse!("K^")
epin = Sashite.Epin.new(pin)
Sashite.Epin.to_string(epin) # => "K^" (native)
epin.pin # => %Sashite.Pin{} instance
epin.derived # => false
# Mark as derived
derived_epin = Sashite.Epin.mark_derived(epin)
Sashite.Epin.to_string(derived_epin) # => "K^'" (derived from opposite side's style)That's it. All piece attributes come from the PIN component.
def deps do
[
{:sashite_epin, "~> 1.0"}
]
endThis will also install sashite_pin as a transitive dependency.
# Parse an EPIN string
{:ok, epin} = Sashite.Epin.parse("K^'")
Sashite.Epin.to_string(epin) # => "K^'"
# Access five fundamental attributes through PIN component + derived flag
epin.pin.type # => :K (Piece Name)
epin.pin.side # => :first (Piece Side)
epin.pin.state # => :normal (Piece State)
epin.pin.terminal # => true (Terminal Status)
epin.derived # => true (Piece Style: derived vs native)
# PIN component is a full %Sashite.Pin{} struct
Sashite.Pin.enhanced?(epin.pin) # => false
Sashite.Pin.letter(epin.pin) # => "K"# Parse from string
{:ok, epin} = Sashite.Epin.parse("K^") # Native
{:ok, epin} = Sashite.Epin.parse("K^'") # Derived
# Bang version
epin = Sashite.Epin.parse!("K^'")
# Create from PIN component
pin = Sashite.Pin.parse!("K^")
epin = Sashite.Epin.new(pin) # Native (default)
epin = Sashite.Epin.new(pin, derived: true) # Derived
# Validate
Sashite.Epin.valid?("K^") # => true
Sashite.Epin.valid?("K^'") # => true
Sashite.Epin.valid?("K^''") # => false (multiple markers)
Sashite.Epin.valid?("K'^") # => false (wrong order)epin = Sashite.Epin.parse!("+R^'")
# Get PIN component
epin.pin # => %Sashite.Pin{}
Sashite.Pin.to_string(epin.pin) # => "+R^"
# Check derivation
epin.derived # => true
Sashite.Epin.derived?(epin) # => true
# Serialize
Sashite.Epin.to_string(epin) # => "+R^'"All attributes accessible via PIN component + derived flag:
epin = Sashite.Epin.parse!("+R^'")
# From PIN component (4 attributes)
epin.pin.type # => :R (Piece Name)
epin.pin.side # => :first (Piece Side)
epin.pin.state # => :enhanced (Piece State)
epin.pin.terminal # => true (Terminal Status)
# From EPIN (5th attribute)
epin.derived # => true (Piece Style: native vs derived)All transformations return new immutable structs.
epin = Sashite.Epin.parse!("K^")
# Mark as derived
derived = Sashite.Epin.mark_derived(epin)
Sashite.Epin.to_string(derived) # => "K^'"
# Mark as native
native = Sashite.Epin.unmark_derived(derived)
Sashite.Epin.to_string(native) # => "K^"
# Set explicitly
toggled = Sashite.Epin.with_derived(epin, true)
Sashite.Epin.to_string(toggled) # => "K^'"epin = Sashite.Epin.parse!("K^'")
# Replace PIN component
new_pin = Sashite.Pin.with_type(epin.pin, :Q)
Sashite.Epin.to_string(Sashite.Epin.with_pin(epin, new_pin)) # => "Q^'"
# Change state
new_pin = Sashite.Pin.with_state(epin.pin, :enhanced)
Sashite.Epin.to_string(Sashite.Epin.with_pin(epin, new_pin)) # => "+K^'"
# Remove terminal marker
new_pin = Sashite.Pin.with_terminal(epin.pin, false)
Sashite.Epin.to_string(Sashite.Epin.with_pin(epin, new_pin)) # => "K'"
# Change side
new_pin = Sashite.Pin.flip(epin.pin)
Sashite.Epin.to_string(Sashite.Epin.with_pin(epin, new_pin)) # => "k^'"epin = Sashite.Epin.parse!("K^")
# Transform PIN and derivation
new_pin = epin.pin
|> Sashite.Pin.with_type(:Q)
|> Sashite.Pin.with_state(:enhanced)
transformed = epin
|> Sashite.Epin.with_pin(new_pin)
|> Sashite.Epin.mark_derived()
Sashite.Epin.to_string(transformed) # => "+Q^'"Use the PIN module API directly:
epin = Sashite.Epin.parse!("+P^'")
# PIN queries (name, side, state, terminal)
epin.pin.type # => :P
epin.pin.side # => :first
epin.pin.state # => :enhanced
epin.pin.terminal # => true
Sashite.Pin.first_player?(epin.pin) # => true
Sashite.Pin.enhanced?(epin.pin) # => true
Sashite.Pin.letter(epin.pin) # => "P"
Sashite.Pin.prefix(epin.pin) # => "+"
Sashite.Pin.suffix(epin.pin) # => "^"
# EPIN queries (style)
Sashite.Epin.derived?(epin) # => true
Sashite.Epin.native?(epin) # => false
# Compare EPINs
other = Sashite.Epin.parse!("+P^")
Sashite.Pin.same_type?(epin.pin, other.pin) # => true (both P)
Sashite.Pin.same_state?(epin.pin, other.pin) # => true (both enhanced)
Sashite.Epin.same_derived?(epin, other) # => false (different derivation)# Parse EPIN string
Sashite.Epin.parse(epin_string) # => {:ok, %Sashite.Epin{}} | {:error, reason}
Sashite.Epin.parse!(epin_string) # => %Sashite.Epin{} | raises ArgumentError
# Create from PIN component
Sashite.Epin.new(pin) # => %Sashite.Epin{} (native)
Sashite.Epin.new(pin, derived: true) # => %Sashite.Epin{} (derived)
# Validate string
Sashite.Epin.valid?(epin_string) # => boolean# Creation
Sashite.Epin.new(pin, opts \\ []) # Create from PIN + derivation flag
# Component access
epin.pin # => %Sashite.Pin{}
epin.derived # => boolean
# Serialization
Sashite.Epin.to_string(epin) # => "K^'" or "K^"
# PIN replacement
Sashite.Epin.with_pin(epin, new_pin) # New EPIN with different PIN
# Derivation transformation
Sashite.Epin.mark_derived(epin) # Mark as derived (add ')
Sashite.Epin.unmark_derived(epin) # Mark as native (remove ')
Sashite.Epin.with_derived(epin, bool) # Set derivation explicitlySashite.Epin.derived?(epin) # epin.derived == true
Sashite.Epin.native?(epin) # epin.derived == false
Sashite.Epin.same_derived?(e1, e2) # Compare derivation statusThat's the entire API. Everything else uses the PIN module API directly.
%Sashite.Epin{
pin: %Sashite.Pin{}, # Underlying PIN struct
derived: boolean() # Derivation status (default: false)
}<pin>[']
Where:
<pin>is any valid PIN token'is the optional derivation marker
epin ::= pin | pin "'"
pin ::= ["+" | "-"] letter ["^"]
letter ::= "A" | ... | "Z" | "a" | ... | "z"~r/\A[-+]?[A-Za-z]\^?'?\z/In a chess-vs-makruk cross-style match where:
- First side native style = chess
- Second side native style = makruk
# First player pieces
chess_king = Sashite.Epin.parse!("K^") # Native Chess king
makruk_pawn = Sashite.Epin.parse!("P'") # Derived Makruk pawn (foreign)
Sashite.Epin.native?(chess_king) # => true (uses own style)
Sashite.Epin.derived?(makruk_pawn) # => true (uses opponent's style)
# Second player pieces
makruk_king = Sashite.Epin.parse!("k^") # Native Makruk king
chess_pawn = Sashite.Epin.parse!("p'") # Derived Chess pawn (foreign)
Sashite.Epin.native?(makruk_king) # => true
Sashite.Epin.derived?(chess_pawn) # => trueEPIN doesn't reimplement PIN features — it extends PIN minimally:
defstruct [:pin, :derived]
def new(%Sashite.Pin{} = pin, opts \\ []) do
%__MODULE__{
pin: pin,
derived: Keyword.get(opts, :derived, false)
}
end6 core methods only:
new/2— create from PINpinfield — get PIN componentderivedfield /derived?/1— check derivationto_string/1— serializewith_pin/2— replace PINwith_derived/2/mark_derived/1/unmark_derived/1— change derivation
Everything else uses the PIN module API directly.
Access PIN directly — no wrappers:
# Use PIN API directly
epin.pin.type
Sashite.Pin.with_type(epin.pin, :Q)
Sashite.Pin.enhanced?(epin.pin)
Sashite.Pin.flip(epin.pin)
# No need for wrapper functions like:
# Sashite.Epin.type(epin)
# Sashite.Epin.with_type(epin, :Q)
# Sashite.Epin.enhanced?(epin)
# Sashite.Epin.flip(epin)Every valid PIN is a valid EPIN (without derivation marker):
# All PIN identifiers work as EPIN
~w(K +R -p K^ +R^)
|> Enum.each(fn token ->
{:ok, epin} = Sashite.Epin.parse(token)
true = Sashite.Epin.native?(epin)
^token = Sashite.Epin.to_string(epin)
end)# PIN: 4 attributes
pin = Sashite.Pin.parse!("K^")
pin.type # Piece Name
pin.side # Piece Side
pin.state # Piece State
pin.terminal # Terminal Status
# EPIN: 5 attributes (PIN + style)
epin = Sashite.Epin.parse!("K^'")
epin.pin.type # Piece Name
epin.pin.side # Piece Side
epin.pin.state # Piece State
epin.pin.terminal # Terminal Status
epin.derived # Piece Style (5th attribute)Use PIN when:
- Single-style games (both players use same style)
- Style information not needed
- Maximum compatibility required
Use EPIN when:
- Cross-style games (different styles per player)
- Pieces can change style (promotion to foreign piece)
- Need to track native vs derived pieces
EPIN exposes all five fundamental attributes from the Sashité Game Protocol:
| Protocol Attribute | EPIN Access | Example |
|---|---|---|
| Piece Name | epin.pin.type |
:K (King), :R (Rook) |
| Piece Side | epin.pin.side |
:first, :second |
| Piece State | epin.pin.state |
:normal, :enhanced, :diminished |
| Terminal Status | epin.pin.terminal |
true, false |
| Piece Style | epin.derived |
false (native), true (derived) |
- EPIN Specification v1.0.0 — Technical specification
- EPIN Examples — Usage examples
- PIN Specification v1.0.0 — Base component
- Sashité Game Protocol — Foundation
Available as open source under the MIT License.
Maintained by Sashité — promoting chess variants and sharing the beauty of board game cultures.