Parse, validate and emit PMN v1.0.0 — the rule‑agnostic Portable Move Notation — in pure Ruby.
PMN expresses state‑changing actions using a simple, deterministic array format without embedding game rules. Whether you are writing a Chess engine, a Shogi server, or a hybrid variant, PMN gives you a compact, game‑neutral core that travels well across languages and databases.
Each action is represented as a 4-element array: [source_square, destination_square, piece_name, captured_piece]
.
Add to your Gemfile:
# Gemfile
gem "portable_move_notation"
then:
bundle install
Or grab it directly:
gem install portable_move_notation
Require it in your code:
require "portable_move_notation" # provides the PortableMoveNotation namespace
Create a simple pawn move (e2 to e4):
require "portable_move_notation"
# Create an action using the array format: [source, destination, piece, captured]
action = PortableMoveNotation::Action.new("e2", "e4", "P", nil)
move = PortableMoveNotation::Move.new(action)
puts move.to_json
# Output: [["e2","e4","P",null]]
Parse it back:
restored = PortableMoveNotation::Move.from_json(move.to_json)
restored.actions.first.dst_square # => "e4"
require "portable_move_notation"
# Two separate actions for castling
king_move = PortableMoveNotation::Action.new("e1", "g1", "K", nil)
rook_move = PortableMoveNotation::Action.new("h1", "f1", "R", nil)
castling = PortableMoveNotation::Move.new(king_move, rook_move)
puts castling.to_json
# Output: [["e1","g1","K",null],["h1","f1","R",null]]
# Drop a pawn onto square 27 (source is nil for drops)
drop = PortableMoveNotation::Action.new(nil, "27", "p", nil)
move = PortableMoveNotation::Move.new(drop)
puts move.to_json
# Output: [[null,"27","p",null]]
# En passant involves removing the captured pawn from its square
capture_move = PortableMoveNotation::Action.new("d4", "e3", "p", nil)
remove_pawn = PortableMoveNotation::Action.new("e4", "e4", nil, "P")
en_passant = PortableMoveNotation::Move.new(capture_move, remove_pawn)
puts en_passant.to_json
# Output: [["d4","e3","p",null],["e4","e4",null,"P"]]
# Bishop captures a promoted pawn and promotes itself
promote_capture = PortableMoveNotation::Action.new("36", "27", "+B", "P")
move = PortableMoveNotation::Move.new(promote_capture)
puts move.to_json
# Output: [["36","27","+B","P"]]
Each action is a 4-element array representing:
- Source square (
String
ornil
) - Where the piece comes from (nil
for drops) - Destination square (
String
) - Where the piece ends up (required) - Piece name (
String
) - What sits on the destination after the action - Captured piece (
String
ornil
) - What enters the mover's reserve
Every action produces deterministic changes:
- Board Removal: Source square becomes empty (if not
nil
) - Board Placement: Destination square contains the piece
- Hand Addition: Captured piece enters the mover's reserve (if not
nil
) - Hand Removal: For drops (
source
isnil
), remove piece from hand
The library validates PMN structure but not game legality:
require "portable_move_notation"
require "json"
# Valid PMN structure
pmn_data = [["e2", "e4", "P", nil]]
puts PortableMoveNotation::Move.valid?(pmn_data) # => true
# Parse and validate
move = PortableMoveNotation::Move.from_pmn(pmn_data)
puts move.actions.size # => 1
Individual action validation:
# Check array format compliance
action_array = ["e2", "e4", "P", nil]
puts PortableMoveNotation::Action.valid?(action_array) # => true
PMN is agnostic to piece notation systems. This implementation supports any UTF-8 string for piece identifiers:
- Traditional:
"K"
,"Q"
,"R"
,"B"
,"N"
,"P"
- Descriptive:
"WhiteKing"
,"BlackQueen"
- Shogi:
"p"
,"+P"
,"B'"
- Custom:
"MagicDragon_powered"
,"42"
All output conforms to the official PMN JSON Schema:
- Schema URL:
https://sashite.dev/schemas/pmn/1.0.0/schema.json
- Format: Array of 4-element arrays
- Types:
[string|null, string, string, string|null]
- Actions are immutable (frozen) after creation
- Moves are lightweight containers for action arrays
- JSON serialization follows the official schema exactly
All objects are immutable after construction, making them thread-safe by design.
ArgumentError
for malformed data during constructionJSON::ParserError
for invalid JSON inputKeyError
for missing required elements
- Portable Move Notation (PMN) — This specification
- Piece Name Notation (PNN) — Piece identifier format
- General Actor Notation (GAN) — Game-qualified pieces
- Forsyth‑Edwards Enhanced Notation (FEEN) — Static positions
The gem is released under the MIT License.
This project is maintained by Sashité — promoting chess variants and sharing the beauty of Chinese, Japanese, and Western chess cultures.