sashite-feen 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/sashite/feen.rb CHANGED
@@ -1,106 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Public API for FEEN (Forsyth–Edwards Enhanced Notation)
4
- # - Pure functions, no global state
5
- # - Immutable value objects
6
- # - Delegates parsing/dumping to dedicated components
7
-
8
- require "sashite/epin"
9
- require "sashite/sin"
10
-
11
- require_relative "feen/error"
12
- require_relative "feen/position"
13
- require_relative "feen/placement"
14
- require_relative "feen/hands"
15
- require_relative "feen/styles"
16
- require_relative "feen/ordering"
17
- require_relative "feen/parser"
18
3
  require_relative "feen/dumper"
4
+ require_relative "feen/parser"
19
5
 
20
6
  module Sashite
7
+ # FEEN (Forsyth–Edwards Enhanced Notation) module provides parsing and dumping
8
+ # functionality for board game positions.
9
+ #
10
+ # FEEN is a universal, rule-agnostic notation for representing board game positions.
11
+ # It extends traditional FEN to support multiple game systems, cross-style games,
12
+ # multi-dimensional boards, and captured pieces.
13
+ #
14
+ # A FEEN string consists of three space-separated fields:
15
+ # 1. Piece placement: Board configuration using EPIN notation
16
+ # 2. Pieces in hand: Captured pieces held by each player
17
+ # 3. Style-turn: Game styles and active player
18
+ #
19
+ # @see https://sashite.dev/specs/feen/1.0.0/
21
20
  module Feen
22
- class << self
23
- # Parse a FEEN string into an immutable Position object.
24
- #
25
- # @param feen [String]
26
- # @return [Sashite::Feen::Position]
27
- def parse(feen)
28
- s = String(feen).strip
29
- raise Error::Syntax, "empty FEEN input" if s.empty?
30
-
31
- Parser.parse(s).freeze
32
- rescue ArgumentError => e
33
- # Normalise en Syntax pour surface d'erreurs plus propre
34
- raise Error::Syntax, e.message
35
- end
36
-
37
- # Validate a FEEN string.
38
- #
39
- # @param feen [String]
40
- # @return [Boolean]
41
- def valid?(feen)
42
- parse(feen)
43
- true
44
- rescue Error::Validation, Error::Syntax, Error::Piece, Error::Style, Error::Count, Error::Bounds
45
- false
46
- end
47
-
48
- # Dump a Position to its canonical FEEN string.
49
- #
50
- # @param position [Sashite::Feen::Position, String] # String => parse puis dump
51
- # @return [String] canonical FEEN
52
- def dump(position)
53
- pos = _coerce_position(position)
54
- Dumper.dump(pos).dup.freeze
55
- end
56
-
57
- # Canonicalize a FEEN string (parse → dump).
58
- #
59
- # @param feen [String]
60
- # @return [String] canonical FEEN
61
- def normalize(feen)
62
- dump(parse(feen))
63
- end
64
-
65
- # Build a Position from its three FEEN fields.
66
- #
67
- # Each argument accepts either a String (parsed by its field parser)
68
- # or the corresponding value-object (Placement/Hands/Styles).
69
- #
70
- # @param piece_placement [String, Sashite::Feen::Placement]
71
- # @param pieces_in_hand [String, Sashite::Feen::Hands]
72
- # @param style_turn [String, Sashite::Feen::Styles]
73
- # @return [Sashite::Feen::Position]
74
- def build(piece_placement:, pieces_in_hand:, style_turn:)
75
- placement = _coerce_component(Placement, Parser::PiecePlacement, piece_placement)
76
- hands = _coerce_component(Hands, Parser::PiecesInHand, pieces_in_hand)
77
- styles = _coerce_component(Styles, Parser::StyleTurn, style_turn)
78
-
79
- Position.new(placement, hands, styles).freeze
80
- end
81
-
82
- private
83
-
84
- # -- helpers -------------------------------------------------------------
85
-
86
- def _coerce_position(obj)
87
- return obj if obj.is_a?(Position)
88
- return parse(obj) if obj.is_a?(String)
89
-
90
- raise TypeError, "expected Sashite::Feen::Position or FEEN String, got #{obj.class}"
91
- end
21
+ # Dump a Position object into its canonical FEEN string representation.
22
+ #
23
+ # Generates a deterministic FEEN string from a position object. The same
24
+ # position will always produce the same canonical string, ensuring
25
+ # position equality can be tested via string comparison.
26
+ #
27
+ # @param position [Position] A position object with placement, hands, and styles
28
+ # @return [String] Canonical FEEN notation string
29
+ #
30
+ # @example Dump a position to FEEN
31
+ # feen_string = Sashite::Feen.dump(position)
32
+ # # => "+rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/c"
33
+ #
34
+ # @example Round-trip parsing and dumping
35
+ # original = "+rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/c"
36
+ # position = Sashite::Feen.parse(original)
37
+ # Sashite::Feen.dump(position) == original # => true
38
+ def self.dump(position)
39
+ Dumper.dump(position)
40
+ end
92
41
 
93
- def _coerce_component(klass, field_parser_mod, value)
94
- case value
95
- when klass
96
- value
97
- when String
98
- field_parser_mod.parse(value)
99
- else
100
- raise TypeError,
101
- "expected #{klass} or String for #{klass.name.split('::').last.downcase}, got #{value.class}"
102
- end
103
- end
42
+ # Parse a FEEN string into an immutable Position object.
43
+ #
44
+ # This method parses the three FEEN fields and constructs an immutable position
45
+ # object with placement, hands, and styles components.
46
+ #
47
+ # @param string [String] A FEEN notation string with three space-separated fields
48
+ # @return [Position] Immutable position object
49
+ # @raise [Error::Syntax] If the FEEN structure is malformed
50
+ # @raise [Error::Piece] If EPIN notation is invalid
51
+ # @raise [Error::Style] If SIN notation is invalid
52
+ # @raise [Error::Count] If piece counts are invalid
53
+ # @raise [Error::Validation] For other semantic violations
54
+ #
55
+ # @example Parse a chess starting position
56
+ # position = Sashite::Feen.parse("+rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/c")
57
+ # position.placement # => Placement object (board configuration)
58
+ # position.hands # => Hands object (pieces in hand)
59
+ # position.styles # => Styles object (style-turn information)
60
+ #
61
+ # @example Parse a shogi position with captured pieces
62
+ # position = Sashite::Feen.parse("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL P/p S/s")
63
+ def self.parse(string)
64
+ Parser.parse(string)
104
65
  end
105
66
  end
106
67
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sashite-feen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
@@ -62,7 +62,6 @@ files:
62
62
  - lib/sashite/feen/dumper/style_turn.rb
63
63
  - lib/sashite/feen/error.rb
64
64
  - lib/sashite/feen/hands.rb
65
- - lib/sashite/feen/ordering.rb
66
65
  - lib/sashite/feen/parser.rb
67
66
  - lib/sashite/feen/parser/piece_placement.rb
68
67
  - lib/sashite/feen/parser/pieces_in_hand.rb
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sashite
4
- module Feen
5
- # Deterministic ordering helpers (kept minimal for now).
6
- # If you later need domain-specific sort (e.g., EPIN-aware), centralize it here.
7
- module Ordering
8
- module_function
9
-
10
- # Default lexicographic sort key for serialized EPIN tokens (String)
11
- def hand_token_key(token_str)
12
- String(token_str)
13
- end
14
- end
15
- end
16
- end