0.0
No release in over 3 years
LCN (Location Condition Notation) provides a rule-agnostic format for describing location conditions in abstract strategy board games. This gem implements the LCN Specification v1.0.0 with a modern Ruby interface featuring immutable condition objects and functional programming principles. LCN enables standardized representation of environmental constraints on board locations using reserved keywords ("empty", "enemy") and QPI piece identifiers with CELL coordinate system integration. Perfect for movement validation, pre-condition checking, constraint evaluation, and rule-agnostic game logic requiring precise location state requirements across multiple game types and traditions.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Runtime

 Project Readme

Lcn.rb

Version Yard documentation Ruby License

LCN (Location Condition Notation) implementation for the Ruby language.

What is LCN?

LCN (Location Condition Notation) is a rule-agnostic format for describing location conditions in abstract strategy board games. LCN provides a standardized way to express constraints on board locations, defining what states specific locations should have.

This gem implements the LCN Specification v1.0.0 exactly, providing environmental constraint validation with CELL coordinates and state values including reserved keywords and QPI piece identifiers.

Installation

# In your Gemfile
gem "sashite-lcn"

Or install manually:

gem install sashite-lcn

Dependencies

LCN builds upon two foundational primitive specifications:

gem "sashite-cell"  # Coordinate Encoding for Layered Locations
gem "sashite-qpi"   # Qualified Piece Identifier

Usage

Basic Operations

require "sashite/lcn"

# Parse location condition data (string keys only)
conditions = Sashite::Lcn.parse({
                                  "e4" => "empty",
                                  "f5" => "enemy"
                                })

# Create directly using symbol keys (constructor)
conditions = Sashite::Lcn::Conditions.new(
  e4: "empty",
  f5: "enemy"
)

# Validate condition data
Sashite::Lcn.valid?({ "e4" => "empty" })          # => true
Sashite::Lcn.valid?({ "invalid" => "empty" })     # => false
Sashite::Lcn.valid?({ "e4" => "unknown" })        # => false

# Access conditions using symbols
conditions[:e4]                                   # => "empty"
conditions[:f5]                                   # => "enemy"
conditions.locations                              # => [:e4, :f5]
conditions.size                                   # => 2
conditions.empty?                                 # => false

# Convert to hash with symbol keys
conditions.to_h # => { e4: "empty", f5: "enemy" }

State Value Types

LCN supports two categories of state values:

Reserved Keywords

# Empty location requirement
empty_condition = Sashite::Lcn.parse({ "e4" => "empty" })
empty_condition[:e4] # => "empty"

# Enemy piece requirement (context-dependent)
enemy_condition = Sashite::Lcn.parse({ "f5" => "enemy" })
enemy_condition[:f5] # => "enemy"

QPI Piece Identifiers

# Specific piece requirements
piece_conditions = Sashite::Lcn.parse({
                                        "h1" => "C:+R", # Chess rook with castling rights
                                        "e1" => "C:+K",    # Chess king with castling rights
                                        "f5" => "c:-p"     # Enemy pawn vulnerable to en passant
                                      })

# Access using symbol keys
piece_conditions[:h1]                             # => "C:+R"
piece_conditions[:e1]                             # => "C:+K"
piece_conditions[:f5]                             # => "c:-p"

Complex Conditions

# Path clearance for movement
path_clear = Sashite::Lcn::Conditions.new(
  b2: "empty",
  c3: "empty",
  d4: "empty"
)

# Castling requirements
castling_conditions = Sashite::Lcn::Conditions.new(
  f1: "empty",
  g1: "empty",
  h1: "C:+R"
)

# File restriction (Shogi pawn drop)
file_restriction = Sashite::Lcn::Conditions.new(
  e1: "S:P",
  e2: "S:P",
  e3: "S:P",
  e4: "S:P",
  e5: "S:P",
  e6: "S:P",
  e7: "S:P",
  e8: "S:P",
  e9: "S:P"
)

Empty Conditions

# No constraints
no_conditions = Sashite::Lcn::Conditions.new
no_conditions.empty?                              # => true
no_conditions.locations                           # => []
no_conditions.to_h                                # => {}

Validation and Analysis

conditions = Sashite::Lcn::Conditions.new(
  e4: "empty",
  f5: "enemy",
  h1: "C:+R"
)

# Basic queries
conditions.size # => 3
conditions.location?(:e4)                     # => true
conditions.location?(:g1)                     # => false

# State value analysis
conditions.keywords                               # => ["empty", "enemy"]
conditions.qpi_identifiers                        # => ["C:+R"]
conditions.keywords? # => true
conditions.qpi_identifiers? # => true

# Specific state checks
conditions.empty_locations                        # => [:e4]
conditions.enemy_locations                        # => [:f5]
conditions.piece_locations                        # => [:h1]

API Reference

Main Module Methods

  • Sashite::Lcn.parse(data) - Parse condition data into LCN object (raises on invalid, string keys only)
  • Sashite::Lcn.valid?(data) - Check if data represents valid LCN conditions

LCN::Conditions Class

Creation and Parsing

  • Sashite::Lcn::Conditions.new(**conditions_hash) - Create from validated hash using keyword arguments (symbol keys)
  • Sashite::Lcn::Conditions.parse(data) - Parse and validate condition data (string keys only, converts to symbols internally)

Data Access

  • #to_h - Convert to hash with symbol keys
  • #[](location) - Get state value for location (symbol key)
  • #locations - Get array of all location symbols
  • #size - Get number of conditions
  • #empty? - Check if no conditions specified

Validation and Queries

  • #location?(location) - Check if location has condition (symbol key)
  • #valid_location?(location) - Check if location uses valid CELL coordinate
  • #valid_state_value?(value) - Check if state value is valid

State Value Analysis

  • #keywords - Get array of keyword state values used
  • #qpi_identifiers - Get array of QPI identifiers used
  • #keywords? - Check if any keywords are used
  • #qpi_identifiers? - Check if any QPI identifiers are used

Categorized Access

  • #empty_locations - Get locations requiring empty state (array of symbols)
  • #enemy_locations - Get locations requiring enemy pieces (array of symbols)
  • #piece_locations - Get locations requiring specific pieces (array of symbols)

Iteration

  • #each - Iterate over location-state pairs (location as symbol)
  • #each_location - Iterate over locations only (symbols)
  • #each_state_value - Iterate over state values only

Format Specification

Internal Ruby Representation

# Symbol keys for Ruby idiomatic usage
{
  e4: "empty",
  f5: "enemy",
  h1: "C:+R"
}

Specification Format (JSON/external)

# String keys as per LCN specification (required for parse method)
{
  "e4" => "empty",
  "f5" => "enemy",
  "h1" => "C:+R"
}

State Value Types

# Reserved keywords
"empty"      # Location should be unoccupied
"enemy"      # Location should contain opposing piece (context-dependent)

# QPI identifiers (examples)
"C:K"        # Chess king, first player
"c:p"        # Chess pawn, second player
"S:+P"       # Shogi promoted pawn, first player
"x:-c"       # Xiangqi diminished cannon, second player

Validation Rules

  • Keys: Must be valid CELL coordinates (required as strings for parse, stored as symbols internally)
  • Values: Must be reserved keywords or valid QPI identifiers
  • Structure: Must be a Hash (after JSON parsing if applicable)
  • Encoding: String values, symbol keys internally

Context Dependency

Reference Piece Requirement

The "enemy" keyword is context-dependent and requires a reference piece for evaluation:

conditions = Sashite::Lcn::Conditions.new(f5: "enemy")

# The consuming specification must provide:
# 1. Reference piece identification (e.g., the moving piece)
# 2. Side determination logic
# 3. Enemy evaluation rules

# LCN validates format but doesn't interpret "enemy" semantics
conditions.enemy_locations                        # => [:f5]
conditions[:f5]                                   # => "enemy"

Integration with Consuming Specifications

LCN is designed to be used by higher-level specifications:

  • GGN: Uses LCN for movement pre-conditions with the moving piece as reference
  • Pattern matching: Could use LCN with designated reference pieces
  • Rule validation: Could combine multiple LCN objects with logical operators

Common Usage Patterns

Movement Validation

# Path must be clear
path_conditions = Sashite::Lcn::Conditions.new(
  b2: "empty",
  c3: "empty",
  d4: "empty"
)

# Destination must contain enemy
capture_condition = Sashite::Lcn::Conditions.new(e5: "enemy")

Special Move Requirements

# Castling conditions
castling = Sashite::Lcn::Conditions.new(
  f1: "empty",      # King's path clear
  g1: "empty",      # King's destination clear
  h1: "C:+R"        # Rook in position with rights
)

# En passant conditions
en_passant = Sashite::Lcn::Conditions.new(
  f6: "empty",      # Capture destination empty
  f5: "c:-p"        # Enemy pawn vulnerable
)

Restriction Patterns

# File cannot contain same piece type (Shogi pawn drop)
file_check = Sashite::Lcn::Conditions.new(
  e1: "S:P",
  e2: "S:P",
  e3: "S:P",
  e4: "S:P",
  e5: "S:P",
  e6: "S:P",
  e7: "S:P",
  e8: "S:P",
  e9: "S:P"
)

Error Handling

# Invalid CELL coordinates
begin
  Sashite::Lcn.parse({ "invalid" => "empty" })
rescue ArgumentError => e
  puts e.message # => "Invalid CELL coordinate: invalid"
end

# Invalid state values
begin
  Sashite::Lcn.parse({ "e4" => "unknown" })
rescue ArgumentError => e
  puts e.message # => "Invalid state value: unknown"
end

# Invalid data structure
begin
  Sashite::Lcn.parse("not a hash")
rescue ArgumentError => e
  puts e.message # => "LCN data must be a Hash"
end

# Direct construction with keyword arguments ensures type safety
conditions_data = { e4: "empty", f5: "enemy" }
conditions = Sashite::Lcn::Conditions.new(**conditions_data)
# Functional approach with clear separation of construction methods

Input Format Specification

Parse Method - String Keys Only

# ✅ Valid input for parse() - string keys required
string_input = { "e4" => "empty", "f5" => "enemy" }
conditions = Sashite::Lcn.parse(string_input)

# ❌ Symbol keys not accepted by parse()
symbol_input = { e4: "empty", f5: "enemy" }
# Sashite::Lcn.parse(symbol_input)  # => ArgumentError

# Internal representation always uses symbol keys
conditions.to_h                                   # => { e4: "empty", f5: "enemy" }
conditions[:e4]                                   # => "empty"

Constructor - Symbol Keys Only

# ✅ Constructor accepts symbol keys via keyword arguments
conditions = Sashite::Lcn::Conditions.new(
  e4: "empty",
  f5: "enemy"
)

# This provides clear separation:
# - parse() for external data (JSON/string keys)
# - new() for internal construction (symbol keys)

Design Rationale

This design provides clear separation between:

  1. External data parsing (parse() with string keys) - matches LCN specification format
  2. Internal construction (new() with symbol keys) - Ruby-idiomatic with keyword arguments
  3. Consistent internal representation (always symbol keys) - optimal performance and developer experience

Benefits:

  • Reduced API complexity - no ambiguity about which key types are accepted where
  • Specification compliance - parse() strictly follows LCN format with string keys
  • Performance - no key conversion logic needed in parse()
  • Clarity - clear distinction between external and internal interfaces

Design Properties

  • Rule-agnostic: Independent of specific game mechanics
  • Context-neutral: Provides format without imposing evaluation logic
  • Composable: Can be combined by consuming specifications
  • Type-safe: Clear distinction between keywords and piece identifiers
  • Ruby-idiomatic: Symbol keys for better performance and developer experience
  • Specification-compliant: String keys for external data parsing
  • Foundational: Designed as building block for higher-level specifications
  • Immutable: All instances frozen, transformations return new objects
  • Functional: Pure functions with no side effects

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.