Module: Sashite::Lcn

Defined in:
lib/sashite/lcn.rb,
lib/sashite/lcn/conditions.rb

Overview

LCN (Location Condition Notation) implementation for Ruby

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

Concept

LCN is a foundational specification designed to be used by other formats that require location state definitions. It expresses environmental constraints as key-value pairs where keys are CELL coordinates and values are state values.

State Value Types

LCN supports two categories of state values:

Reserved Keywords

  • "empty": Location should be unoccupied
  • "enemy": Location should contain a piece from the opposing side (context-dependent)

QPI Piece Identifiers

  • Exact piece requirements using QPI format (e.g., "C:K", "c:p", "S:+P")

Context Dependency

The "enemy" keyword is context-dependent and requires a reference piece for interpretation. The consuming specification must provide:

  1. Reference piece identification (which piece determines the perspective)
  2. Side determination logic (how the reference piece's side is established)
  3. Enemy evaluation rules (how opposing pieces are identified)

Format Structure

LCN uses a simple JSON object structure:

{
  "<cell-coordinate>": "<state-value>"
}

Where:

  • Keys: CELL coordinates (string, conforming to CELL specification)
  • Values: State values (reserved keywords or QPI identifiers)

Examples

Basic Conditions

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

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

Specific Piece Requirements

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

Complex Path Conditions

# Path clearance for movement
path_conditions = Sashite::Lcn.parse({
"b2" => "empty",
"c3" => "empty",
"d4" => "empty"
})

# Castling requirements
castling = Sashite::Lcn.parse({
"f1" => "empty",
"g1" => "empty",
"h1" => "C:+R"
})

Empty Conditions

no_conditions = Sashite::Lcn.parse({})
no_conditions.empty?  # => true

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
  • Minimal syntax: Clean key-value pairs without nesting
  • Functional: Pure functions with no side effects
  • Immutable: All instances are frozen for thread safety

Defined Under Namespace

Classes: Conditions

Constant Summary collapse

EMPTY_KEYWORD =

Reserved keywords for common location states

"empty"
ENEMY_KEYWORD =
"enemy"
RESERVED_KEYWORDS =
[EMPTY_KEYWORD, ENEMY_KEYWORD].freeze

Class Method Summary collapse

Class Method Details

.parse(data) ⇒ Lcn::Conditions

Parse LCN data into a Conditions object

Creates a new LCN Conditions instance by parsing and validating the input data. Accepts both string and symbol keys for compatibility, but internally normalizes to symbol keys for Ruby idiomatic usage.

Examples:

Parse different LCN formats

# String keys (as per specification)
conditions = Sashite::Lcn.parse({ "e4" => "empty", "f5" => "enemy" })
conditions[:e4]  # => "empty"

# Symbol keys (Ruby idiomatic)
conditions = Sashite::Lcn.parse({ e4: "empty", f5: "enemy" })
conditions[:e4]  # => "empty"

# Mixed QPI and keywords
conditions = Sashite::Lcn.parse({
  "f1" => "empty",
  "g1" => "empty",
  "h1" => "C:+R"
})

Error handling

Sashite::Lcn.parse({ "invalid" => "empty" })  # => ArgumentError: Invalid CELL coordinate: invalid
Sashite::Lcn.parse({ "e4" => "unknown" })     # => ArgumentError: Invalid state value: unknown
Sashite::Lcn.parse("not a hash")              # => ArgumentError: LCN data must be a Hash

Parameters:

  • data (Hash) —

    LCN conditions data with CELL coordinates as keys

Returns:

Raises:

  • (ArgumentError) —

    if the data is invalid (not a Hash, invalid keys, or invalid values)



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/sashite/lcn.rb', line 168

def self.parse(data)
  raise ArgumentError, "LCN data must be a Hash" unless data.is_a?(Hash)

  validated_conditions = {}

  data.each do |location, state_value|
    # Convert location to string for validation, then to symbol for storage
    location_str = location.to_s

    raise ArgumentError, "Invalid CELL coordinate: #{location_str}" unless valid_location?(location_str)

    raise ArgumentError, "Invalid state value: #{state_value}" unless valid_state_value?(state_value)

    # Store with symbol key for Ruby idiomatic access
    validated_conditions[location_str.to_sym] = state_value
  end

  Conditions.new(**validated_conditions)
end

.valid?(data) ⇒ Boolean

Check if data represents valid LCN conditions

Validates that the data is a Hash with:

  • Keys: Valid CELL coordinates
  • Values: Reserved keywords ("empty", "enemy") or valid QPI identifiers

Examples:

Validate various LCN formats

Sashite::Lcn.valid?({ "e4" => "empty" })          # => true
Sashite::Lcn.valid?({ "f5" => "enemy" })          # => true
Sashite::Lcn.valid?({ "h1" => "C:+R" })           # => true
Sashite::Lcn.valid?({ "invalid" => "empty" })     # => false (invalid CELL)
Sashite::Lcn.valid?({ "e4" => "unknown" })        # => false (invalid state)
Sashite::Lcn.valid?("not a hash")                 # => false
Sashite::Lcn.valid?({})                           # => true (empty conditions)

Parameters:

  • data (Object) —

    the data to validate

Returns:

  • (Boolean) —

    true if valid LCN data, false otherwise



130
131
132
133
134
135
136
# File 'lib/sashite/lcn.rb', line 130

def self.valid?(data)
  return false unless data.is_a?(Hash)

  data.all? do |location, state_value|
    valid_location?(location) && valid_state_value?(state_value)
  end
end