# Sashite.Feen
[](https://hex.pm/packages/sashite_feen)
[](https://hexdocs.pm/sashite_feen)
[](https://github.com/sashite/feen.ex/blob/main/LICENSE.md)
> **FEEN** (Field Expression Encoding Notation) implementation for Elixir.
## What is FEEN?
FEEN (Field Expression Encoding Notation) is a **rule-agnostic position encoding** for two-player, turn-based board games built on the [Sashité Game Protocol](https://sashite.dev/game-protocol/).
A FEEN string encodes exactly:
1. **Board occupancy** (which Pieces are on which Squares)
2. **Hands** (multisets of off-board Pieces held by each Player)
3. **Side styles** and the **Active Player**
This library implements the [FEEN Specification v1.0.0](https://sashite.dev/specs/feen/1.0.0/).
## Installation
Add `sashite_feen` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:sashite_feen, "~> 1.0"}
]
end
```
This will also install `sashite_epin` and `sashite_sin` as transitive dependencies.
## Quick Start
```elixir
# Parse a FEEN string (Western Chess starting position)
feen_string = "+rnbq+k^bn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+K^BN+R / C/c"
{:ok, position} = Sashite.Feen.parse(feen_string)
# Access position components
position.piece_placement # Board state
position.hands # Captured pieces
position.style_turn # Active player and styles
# Serialize back to FEEN string
Sashite.Feen.to_string(position) # => "+rnbq+k^bn+r/..."
# Validate a FEEN string
Sashite.Feen.valid?(feen_string) # => true
```
## Format Overview
A FEEN string consists of **three fields** separated by single ASCII spaces:
```
<PIECE-PLACEMENT> <HANDS> <STYLE-TURN>
```
### Example: Chess Starting Position
```
+rnbq+k^bn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+K^BN+R / C/c
|----------------------------------------------------| |-| |---|
Piece Placement Hands Style-Turn
```
### Example: Shogi Starting Position
```
lnsgk^gsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGK^GSNL / S/s
```
### Example: Position with Captured Pieces
```
r1bqk^b1r/+p+p+p+p1+p+p+p/2n2n2/4p3/2B1P3/5N2/+P+P+P+P1+P+P+P/RNBQK^2R 2P/p C/c
|--| |-|
First Second
hand hand
```
## Usage
### Parsing FEEN Strings
```elixir
# Parse with error handling
case Sashite.Feen.parse(feen_string) do
{:ok, position} -> # use position
{:error, reason} -> # handle error
end
# Bang version (raises on invalid input)
position = Sashite.Feen.parse!(feen_string)
# Validate without parsing
Sashite.Feen.valid?(feen_string) # => true/false
```
### Accessing Position Components
```elixir
position = Sashite.Feen.parse!("+rnbq+k^bn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+K^BN+R / C/c")
# Piece Placement (board state)
position.piece_placement
# => %{squares: [[...], [...], ...], separators: [1, 1, 1, 1, 1, 1, 1]}
# Hands (captured pieces)
position.hands
# => %{first: [], second: []}
# Style-Turn (active player and styles)
position.style_turn
# => %{active: %Sashite.Sin{style: :C, side: :first}, inactive: %Sashite.Sin{style: :C, side: :second}}
```
### Working with Piece Placement
The `piece_placement` field contains:
- `squares` — List of segments, each segment is a list of squares (`nil` for empty, `%Sashite.Epin{}` for piece)
- `separators` — List of separator counts between consecutive segments
```elixir
position = Sashite.Feen.parse!("8/8/8/8/8/8/8/8 / C/c")
# Access segments
position.piece_placement.squares
# => [[nil, nil, nil, nil, nil, nil, nil, nil], ...]
# Access separator structure (for multi-dimensional boards)
position.piece_placement.separators
# => [1, 1, 1, 1, 1, 1, 1]
# Iterate over a segment
Enum.each(hd(position.piece_placement.squares), fn
nil -> IO.puts("Empty square")
%Sashite.Epin{} = epin -> IO.puts("Piece: #{epin}")
end)
```
### Working with Hands
The `hands` field contains:
- `first` — List of `%Sashite.Epin{}` structs held by first player
- `second` — List of `%Sashite.Epin{}` structs held by second player
```elixir
position = Sashite.Feen.parse!("8/8/8/8/8/8/8/8 2P/p C/c")
# Access first player's hand
position.hands.first
# => [%Sashite.Epin{...}, %Sashite.Epin{...}]
length(position.hands.first) # => 2
# Access second player's hand
position.hands.second
# => [%Sashite.Epin{...}]
```
### Working with Style-Turn
The `style_turn` field contains:
- `active` — `%Sashite.Sin{}` of the active player (to move)
- `inactive` — `%Sashite.Sin{}` of the inactive player
```elixir
position = Sashite.Feen.parse!("8/8/8/8/8/8/8/8 / C/c")
# Get active player's style
position.style_turn.active
# => %Sashite.Sin{style: :C, side: :first}
# Get inactive player's style
position.style_turn.inactive
# => %Sashite.Sin{style: :C, side: :second}
# Check who is to move
Sashite.Sin.first_player?(position.style_turn.active) # => true
Sashite.Sin.second_player?(position.style_turn.active) # => false
```
### Serialization
```elixir
# Convert position back to FEEN string
feen_string = Sashite.Feen.to_string(position)
# String.Chars protocol is implemented
"Position: #{position}"
# The output is always canonical
```
## Field Specifications
### Field 1 — Piece Placement
Encodes board occupancy as a stream of tokens organized into segments separated by `/`:
- **Empty-count tokens**: integers representing runs of empty squares (e.g., `8` = 8 empty squares)
- **Piece tokens**: valid EPIN tokens (e.g., `+K^`, `p`, `R'`)
```elixir
# 8x8 board with pieces
"+rnbq+k^bn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+K^BN+R"
# Multi-dimensional board (3D Raumschach uses //)
"+rn+k^n+r/+p+p+p+p+p/5/5/5//buqbu/+p+p+p+p+p/5/5/5//..."
```
### Field 2 — Hands
Encodes pieces held by each player, separated by `/`:
```
<FIRST-HAND>/<SECOND-HAND>
```
- Each hand is a concatenation of `[count]<piece>` items
- Count is optional (absent = 1, present ≥ 2)
- Empty hands are represented as empty strings
```elixir
"/" # Both hands empty
"2P/p" # First has 2 pawns, second has 1 pawn
"3P2B/2p" # First has 3 pawns + 2 bishops, second has 2 pawns
```
### Field 3 — Style-Turn
Encodes native styles and active player:
```
<ACTIVE-STYLE>/<INACTIVE-STYLE>
```
- Each style is a valid SIN token (single ASCII letter)
- Uppercase = Side `first`, lowercase = Side `second`
- Position determines who is active
```elixir
"C/c" # First player (Chess-style) to move
"c/C" # Second player (Chess-style) to move
"S/s" # First player (Shogi-style) to move
"M/c" # First player (Makruk-style) vs second player (Chess-style), first to move
```
## Game Examples
### Western Chess
```elixir
# Starting position
"+rnbq+k^bn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+K^BN+R / C/c"
# After 1.e4
"+rnbq+k^bn+r/+p+p+p+p+p+p+p+p/8/8/4P3/8/+P+P+P+P1+P+P+P/+RNBQ+K^BN+R / c/C"
# After 1.e4 c5 (Sicilian Defense)
"+rnbq+k^bn+r/+p+p1+p+p+p+p+p/8/2p5/4P3/8/+P+P+P+P1+P+P+P/+RNBQ+K^BN+R / C/c"
```
### Japanese Shogi
```elixir
# Starting position
"lnsgk^gsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGK^GSNL / S/s"
# After 1.P-7f
"lnsgk^gsnl/1r5b1/ppppppppp/9/9/2P6/PP1PPPPPP/1B5R1/LNSGK^GSNL / s/S"
```
### Chinese Xiangqi
```elixir
# Starting position
"rheag^aehr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RHEAG^AEHR / X/x"
```
### Thai Makruk
```elixir
# Starting position
"rnsmk^snr/8/pppppppp/8/8/PPPPPPPP/8/RNSK^MSNR / M/m"
```
### Cross-Style Games
```elixir
# Chess vs Makruk hybrid
"rnsmk^snr/8/pppppppp/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+K^BN+R / C/m"
```
## API Reference
### Main Module
```elixir
# Parse FEEN string
Sashite.Feen.parse(feen_string) # => {:ok, %Sashite.Feen{}} | {:error, reason}
Sashite.Feen.parse!(feen_string) # => %Sashite.Feen{} | raises ArgumentError
# Validate string
Sashite.Feen.valid?(feen_string) # => boolean
# Serialize position
Sashite.Feen.to_string(position) # => String.t()
```
### Data Structure
```elixir
%Sashite.Feen{
piece_placement: %{
squares: [[Sashite.Epin.t() | nil]], # List of segments
separators: [pos_integer()] # Separator counts between segments
},
hands: %{
first: [Sashite.Epin.t()], # First player's hand
second: [Sashite.Epin.t()] # Second player's hand
},
style_turn: %{
active: Sashite.Sin.t(), # Active player's style
inactive: Sashite.Sin.t() # Inactive player's style
}
}
```
## Canonical Form
FEEN output is always **canonical**:
- Empty-count tokens use minimal base-10 integers (no leading zeros)
- Hand items are aggregated and sorted deterministically:
1. By multiplicity (descending)
2. By EPIN base letter (case-insensitive alphabetical)
3. By EPIN letter case (uppercase before lowercase)
4. By EPIN state modifier (`-` before `+` before none)
5. By EPIN terminal marker (absent before present)
6. By EPIN derivation marker (absent before present)
```elixir
# Non-canonical input is accepted and normalized on output
{:ok, pos} = Sashite.Feen.parse("8/8/8/8/8/8/8/8 PpP/p C/c")
Sashite.Feen.to_string(pos)
# => "8/8/8/8/8/8/8/8 2Pp/p C/c"
```
## Dependencies
FEEN builds on these Sashité specifications:
- **[EPIN](https://sashite.dev/specs/epin/1.0.0/)** — Extended Piece Identifier Notation for piece tokens
- **[SIN](https://sashite.dev/specs/sin/1.0.0/)** — Style Identifier Notation for style-turn encoding
## Design Properties
FEEN is designed to be:
- **Rule-agnostic** — No game-specific rules embedded
- **Protocol-aligned** — Compatible with the Game Protocol's Position model
- **Compact** — Run-length encoding for empty squares, multiplicities for hands
- **Multi-dimensional friendly** — Separator groups preserve higher-dimensional boundaries
- **Canonical** — Single canonical string for each position
- **Engine-friendly** — Suitable for hashing, caching, and repetition detection
## Related Specifications
- [FEEN Specification v1.0.0](https://sashite.dev/specs/feen/1.0.0/) — Technical specification
- [FEEN Examples](https://sashite.dev/specs/feen/1.0.0/examples/) — Comprehensive examples
- [Game Protocol](https://sashite.dev/game-protocol/) — Conceptual foundation
- [EPIN Specification](https://sashite.dev/specs/epin/1.0.0/) — Piece encoding
- [SIN Specification](https://sashite.dev/specs/sin/1.0.0/) — Style encoding
## License
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
## About
Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.