Skip to content

EPIN (Extended Piece Identifier Notation) implementation for Elixir extending PIN with style derivation markers.

License

Notifications You must be signed in to change notification settings

sashite/epin.ex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sashite.Epin

Hex.pm Docs License

EPIN (Extended Piece Identifier Notation) implementation for Elixir.

What is EPIN?

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.

Core Concept

# 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.

Installation

def deps do
  [
    {:sashite_epin, "~> 1.0"}
  ]
end

This will also install sashite_pin as a transitive dependency.

Quick Start

# 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"

Basic Usage

Creating Identifiers

# 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)

Accessing Components

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^'"

Five Fundamental Attributes

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)

Transformations

All transformations return new immutable structs.

Change Derivation Status

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^'"

Transform via PIN Component

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^'"

Multiple Transformations

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^'"

Component Queries

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)

API Reference

Main Module

# 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

Core Methods (6 total)

# 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 explicitly

Convenience Queries

Sashite.Epin.derived?(epin)            # epin.derived == true
Sashite.Epin.native?(epin)             # epin.derived == false
Sashite.Epin.same_derived?(e1, e2)     # Compare derivation status

That's the entire API. Everything else uses the PIN module API directly.

Data Structure

%Sashite.Epin{
  pin: %Sashite.Pin{},   # Underlying PIN struct
  derived: boolean()      # Derivation status (default: false)
}

Format Specification

Structure

<pin>[']

Where:

  • <pin> is any valid PIN token
  • ' is the optional derivation marker

Grammar (EBNF)

epin ::= pin | pin "'"
pin  ::= ["+" | "-"] letter ["^"]
letter ::= "A" | ... | "Z" | "a" | ... | "z"

Regular Expression

~r/\A[-+]?[A-Za-z]\^?'?\z/

Cross-Style Game Example

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)   # => true

Design Principles

1. Pure Composition

EPIN 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)
  }
end

2. Minimal API

6 core methods only:

  1. new/2 — create from PIN
  2. pin field — get PIN component
  3. derived field / derived?/1 — check derivation
  4. to_string/1 — serialize
  5. with_pin/2 — replace PIN
  6. with_derived/2 / mark_derived/1 / unmark_derived/1 — change derivation

Everything else uses the PIN module API directly.

3. Component Transparency

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)

4. Backward Compatibility

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)

Comparison with PIN

What EPIN Adds

# 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)

When to Use EPIN vs PIN

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

Attribute Mapping

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)

Related Specifications

License

Available as open source under the MIT License.

About

Maintained by Sashité — promoting chess variants and sharing the beauty of board game cultures.

About

EPIN (Extended Piece Identifier Notation) implementation for Elixir extending PIN with style derivation markers.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages