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.
- checksums.yaml +4 -4
- data/README.md +412 -137
- data/lib/sashite/feen/dumper/piece_placement.rb +144 -64
- data/lib/sashite/feen/dumper/pieces_in_hand.rb +124 -34
- data/lib/sashite/feen/dumper/style_turn.rb +29 -45
- data/lib/sashite/feen/dumper.rb +40 -30
- data/lib/sashite/feen/error.rb +72 -29
- data/lib/sashite/feen/hands.rb +62 -20
- data/lib/sashite/feen/parser/piece_placement.rb +324 -118
- data/lib/sashite/feen/parser/pieces_in_hand.rb +210 -44
- data/lib/sashite/feen/parser/style_turn.rb +81 -41
- data/lib/sashite/feen/parser.rb +60 -15
- data/lib/sashite/feen/placement.rb +295 -19
- data/lib/sashite/feen/position.rb +64 -13
- data/lib/sashite/feen/styles.rb +54 -57
- data/lib/sashite/feen.rb +57 -96
- metadata +1 -2
- data/lib/sashite/feen/ordering.rb +0 -16
data/lib/sashite/feen/error.rb
CHANGED
|
@@ -2,39 +2,82 @@
|
|
|
2
2
|
|
|
3
3
|
module Sashite
|
|
4
4
|
module Feen
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
# Base error class for all FEEN-related errors.
|
|
6
|
+
#
|
|
7
|
+
# All FEEN parsing and validation errors inherit from this class,
|
|
8
|
+
# allowing callers to rescue all FEEN errors with a single rescue clause.
|
|
9
|
+
#
|
|
10
|
+
# @see https://sashite.dev/specs/feen/1.0.0/
|
|
11
|
+
class Error < StandardError
|
|
12
|
+
# Error raised when FEEN structure is malformed.
|
|
13
|
+
#
|
|
14
|
+
# Indicates problems with the overall FEEN format, such as:
|
|
15
|
+
# - Missing or incorrect number of fields
|
|
16
|
+
# - Missing required separators
|
|
17
|
+
# - Empty fields where content is required
|
|
18
|
+
# - Invalid field structure
|
|
19
|
+
#
|
|
20
|
+
# @example Missing field separator
|
|
21
|
+
# raise Error::Syntax, "FEEN must have exactly 3 space-separated fields"
|
|
22
|
+
#
|
|
23
|
+
# @example Empty required field
|
|
24
|
+
# raise Error::Syntax, "active style cannot be empty"
|
|
25
|
+
class Syntax < Error; end
|
|
11
26
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
# Error raised when EPIN (Extended Piece Identifier Notation) is invalid.
|
|
28
|
+
#
|
|
29
|
+
# Indicates problems with piece notation, such as:
|
|
30
|
+
# - Invalid EPIN format
|
|
31
|
+
# - Unrecognized piece characters
|
|
32
|
+
# - Malformed state modifiers or derivation suffixes
|
|
33
|
+
# - EPIN parsing failures
|
|
34
|
+
#
|
|
35
|
+
# @example Invalid EPIN format
|
|
36
|
+
# raise Error::Piece, "invalid EPIN notation: K#"
|
|
37
|
+
#
|
|
38
|
+
# @example Failed EPIN parsing
|
|
39
|
+
# raise Error::Piece, "failed to parse EPIN 'X': unknown piece type"
|
|
40
|
+
class Piece < Error; end
|
|
20
41
|
|
|
21
|
-
#
|
|
22
|
-
|
|
42
|
+
# Error raised when SIN (Style Identifier Notation) is invalid.
|
|
43
|
+
#
|
|
44
|
+
# Indicates problems with style notation, such as:
|
|
45
|
+
# - Invalid SIN format
|
|
46
|
+
# - Non-letter characters in style identifier
|
|
47
|
+
# - Multi-character style identifiers
|
|
48
|
+
# - SIN parsing failures
|
|
49
|
+
#
|
|
50
|
+
# @example Invalid SIN format
|
|
51
|
+
# raise Error::Style, "invalid SIN notation: '1' (must be a single letter)"
|
|
52
|
+
#
|
|
53
|
+
# @example Failed SIN parsing
|
|
54
|
+
# raise Error::Style, "failed to parse SIN 'XY': too long"
|
|
55
|
+
class Style < Error; end
|
|
23
56
|
|
|
24
|
-
#
|
|
25
|
-
|
|
57
|
+
# Error raised when piece counts are invalid.
|
|
58
|
+
#
|
|
59
|
+
# Indicates problems with piece quantity specifications, such as:
|
|
60
|
+
# - Count less than 1
|
|
61
|
+
# - Count exceeding reasonable limits
|
|
62
|
+
# - Invalid count format
|
|
63
|
+
#
|
|
64
|
+
# @example Count too small
|
|
65
|
+
# raise Error::Count, "piece count must be at least 1, got 0"
|
|
66
|
+
#
|
|
67
|
+
# @example Count too large
|
|
68
|
+
# raise Error::Count, "piece count too large: 9999"
|
|
69
|
+
class Count < Error; end
|
|
26
70
|
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class Bounds < Base; end
|
|
71
|
+
# Error raised for other semantic validation failures.
|
|
72
|
+
#
|
|
73
|
+
# Indicates problems that don't fit other error categories, such as:
|
|
74
|
+
# - Inconsistent position state
|
|
75
|
+
# - Rule violations
|
|
76
|
+
# - Other semantic constraints
|
|
77
|
+
#
|
|
78
|
+
# @example Semantic constraint violation
|
|
79
|
+
# raise Error::Validation, "position violates conservation principle"
|
|
80
|
+
class Validation < Error; end
|
|
38
81
|
end
|
|
39
82
|
end
|
|
40
83
|
end
|
data/lib/sashite/feen/hands.rb
CHANGED
|
@@ -2,36 +2,78 @@
|
|
|
2
2
|
|
|
3
3
|
module Sashite
|
|
4
4
|
module Feen
|
|
5
|
-
# Immutable
|
|
5
|
+
# Immutable representation of pieces held in hand by each player.
|
|
6
|
+
#
|
|
7
|
+
# Stores captured pieces that players hold in reserve, available for
|
|
8
|
+
# placement back onto the board in games that support drop mechanics
|
|
9
|
+
# (such as shogi, crazyhouse, etc.).
|
|
10
|
+
#
|
|
11
|
+
# @see https://sashite.dev/specs/feen/1.0.0/
|
|
6
12
|
class Hands
|
|
7
|
-
|
|
13
|
+
# @return [Array] Array of pieces held by first player
|
|
14
|
+
attr_reader :first_player
|
|
8
15
|
|
|
9
|
-
# @
|
|
10
|
-
|
|
11
|
-
raise TypeError, "hands map must be a Hash, got #{map.class}" unless map.is_a?(Hash)
|
|
16
|
+
# @return [Array] Array of pieces held by second player
|
|
17
|
+
attr_reader :second_player
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
# Create a new immutable Hands object.
|
|
20
|
+
#
|
|
21
|
+
# @param first_player [Array] Pieces in first player's hand
|
|
22
|
+
# @param second_player [Array] Pieces in second player's hand
|
|
23
|
+
#
|
|
24
|
+
# @example Empty hands
|
|
25
|
+
# hands = Hands.new([], [])
|
|
26
|
+
#
|
|
27
|
+
# @example First player has captured pieces
|
|
28
|
+
# hands = Hands.new([pawn1, pawn2], [])
|
|
29
|
+
#
|
|
30
|
+
# @example Both players have captured pieces
|
|
31
|
+
# hands = Hands.new([rook, bishop], [pawn1, pawn2, knight])
|
|
32
|
+
def initialize(first_player, second_player)
|
|
33
|
+
@first_player = first_player.sort_by(&:to_s).freeze
|
|
34
|
+
@second_player = second_player.sort_by(&:to_s).freeze
|
|
19
35
|
|
|
20
|
-
coerced[k] = c
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Freeze shallowly (keys may already be complex frozen EPIN values)
|
|
24
|
-
@map = coerced.each_with_object({}) { |(k, v), h| h[k] = v }.freeze
|
|
25
36
|
freeze
|
|
26
37
|
end
|
|
27
38
|
|
|
28
|
-
#
|
|
39
|
+
# Check if both hands are empty.
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean] True if neither player has pieces in hand
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
# hands.empty? # => true
|
|
29
45
|
def empty?
|
|
30
|
-
|
|
46
|
+
first_player.empty? && second_player.empty?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Convert hands to their FEEN string representation.
|
|
50
|
+
#
|
|
51
|
+
# @return [String] FEEN pieces-in-hand field
|
|
52
|
+
#
|
|
53
|
+
# @example
|
|
54
|
+
# hands.to_s
|
|
55
|
+
# # => "2P/p"
|
|
56
|
+
def to_s
|
|
57
|
+
Dumper::PiecesInHand.dump(self)
|
|
31
58
|
end
|
|
32
59
|
|
|
33
|
-
|
|
34
|
-
|
|
60
|
+
# Compare two hands for equality.
|
|
61
|
+
#
|
|
62
|
+
# @param other [Hands] Another hands object
|
|
63
|
+
# @return [Boolean] True if both players' pieces are equal
|
|
64
|
+
def ==(other)
|
|
65
|
+
other.is_a?(Hands) &&
|
|
66
|
+
first_player == other.first_player &&
|
|
67
|
+
second_player == other.second_player
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
alias eql? ==
|
|
71
|
+
|
|
72
|
+
# Generate hash code for hands.
|
|
73
|
+
#
|
|
74
|
+
# @return [Integer] Hash code based on both players' pieces
|
|
75
|
+
def hash
|
|
76
|
+
[first_player, second_player].hash
|
|
35
77
|
end
|
|
36
78
|
end
|
|
37
79
|
end
|