feen 5.0.0.beta1 → 5.0.0.beta3
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/LICENSE.md +1 -1
- data/README.md +170 -44
- data/lib/feen/dumper/games_turn.rb +67 -0
- data/lib/feen/dumper/piece_placement.rb +117 -68
- data/lib/feen/dumper/pieces_in_hand/errors.rb +12 -0
- data/lib/feen/dumper/pieces_in_hand/no_pieces.rb +10 -0
- data/lib/feen/dumper/pieces_in_hand.rb +72 -0
- data/lib/feen/dumper.rb +73 -23
- data/lib/feen/parser/games_turn/errors.rb +14 -0
- data/lib/feen/parser/games_turn/valid_games_turn_pattern.rb +24 -0
- data/lib/feen/parser/games_turn.rb +58 -0
- data/lib/feen/parser/piece_placement.rb +634 -0
- data/lib/feen/parser/pieces_in_hand/errors.rb +14 -0
- data/lib/feen/parser/pieces_in_hand/no_pieces.rb +10 -0
- data/lib/feen/parser/pieces_in_hand/valid_format_pattern.rb +15 -0
- data/lib/feen/parser/pieces_in_hand.rb +84 -0
- data/lib/feen/parser.rb +74 -36
- data/lib/feen.rb +58 -43
- metadata +17 -9
- data/lib/feen/parser/board_shape.rb +0 -39
data/lib/feen/dumper.rb
CHANGED
@@ -1,36 +1,86 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative File.join("dumper", "games_turn")
|
3
4
|
require_relative File.join("dumper", "piece_placement")
|
5
|
+
require_relative File.join("dumper", "pieces_in_hand")
|
4
6
|
|
5
7
|
module Feen
|
6
|
-
#
|
8
|
+
# Module responsible for converting internal data structures to FEEN notation strings.
|
9
|
+
# This implements the serialization part of the FEEN (Format for Encounter & Entertainment Notation) format.
|
7
10
|
module Dumper
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
|
11
|
+
# Field separator used between the three main components of FEEN notation
|
12
|
+
FIELD_SEPARATOR = " "
|
13
|
+
|
14
|
+
# Error messages for validation
|
15
|
+
ERRORS = {
|
16
|
+
invalid_piece_placement_type: "Piece placement must be an Array, got %s",
|
17
|
+
invalid_games_turn_type: "Games turn must be an Array with exactly two elements, got %s",
|
18
|
+
invalid_pieces_in_hand_type: "Pieces in hand must be an Array, got %s"
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# Converts position components to a complete FEEN string.
|
13
22
|
#
|
14
|
-
# @example
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
23
|
+
# @example Creating a FEEN string for chess initial position
|
24
|
+
# Feen::Dumper.dump(
|
25
|
+
# piece_placement: [
|
26
|
+
# ["r", "n", "b", "q", "k=", "b", "n", "r"],
|
27
|
+
# ["p", "p", "p", "p", "p", "p", "p", "p"],
|
28
|
+
# ["", "", "", "", "", "", "", ""],
|
29
|
+
# ["", "", "", "", "", "", "", ""],
|
30
|
+
# ["", "", "", "", "", "", "", ""],
|
31
|
+
# ["", "", "", "", "", "", "", ""],
|
32
|
+
# ["P", "P", "P", "P", "P", "P", "P", "P"],
|
33
|
+
# ["R", "N", "B", "Q", "K=", "B", "N", "R"]
|
34
|
+
# ],
|
35
|
+
# pieces_in_hand: [],
|
36
|
+
# games_turn: ["CHESS", "chess"]
|
25
37
|
# )
|
26
|
-
# # => "
|
38
|
+
# # => "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR - CHESS/chess"
|
27
39
|
#
|
28
|
-
# @
|
29
|
-
|
40
|
+
# @param piece_placement [Array] Board position data structure representing the spatial
|
41
|
+
# distribution of pieces across the board, where each cell
|
42
|
+
# is represented by a String (or empty string for empty cells)
|
43
|
+
# @param pieces_in_hand [Array<String>] Pieces available for dropping onto the board,
|
44
|
+
# each represented as a single character string
|
45
|
+
# @param games_turn [Array<String>] A two-element array where the first element is the
|
46
|
+
# active player's variant and the second is the inactive player's variant
|
47
|
+
# @return [String] Complete FEEN string representation compliant with the specification
|
48
|
+
# @raise [ArgumentError] If any input parameter is invalid
|
49
|
+
# @see https://sashite.dev/documents/feen/1.0.0/ FEEN Specification v1.0.0
|
50
|
+
def self.dump(piece_placement:, pieces_in_hand:, games_turn:)
|
51
|
+
# Validate input types
|
52
|
+
validate_inputs(piece_placement, games_turn, pieces_in_hand)
|
53
|
+
|
54
|
+
# Process each component with appropriate submodule and combine into final FEEN string
|
30
55
|
[
|
31
|
-
PiecePlacement.
|
32
|
-
|
33
|
-
|
56
|
+
PiecePlacement.dump(piece_placement),
|
57
|
+
PiecesInHand.dump(*pieces_in_hand),
|
58
|
+
GamesTurn.dump(*games_turn)
|
59
|
+
].join(FIELD_SEPARATOR)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Validates the input parameters for type and structure
|
63
|
+
#
|
64
|
+
# @param piece_placement [Object] Piece placement parameter to validate
|
65
|
+
# @param games_turn [Object] Games turn parameter to validate
|
66
|
+
# @param pieces_in_hand [Object] Pieces in hand parameter to validate
|
67
|
+
# @raise [ArgumentError] If any parameter is invalid
|
68
|
+
# @return [void]
|
69
|
+
private_class_method def self.validate_inputs(piece_placement, games_turn, pieces_in_hand)
|
70
|
+
# Validate piece_placement is an Array
|
71
|
+
unless piece_placement.is_a?(Array)
|
72
|
+
raise ArgumentError, format(ERRORS[:invalid_piece_placement_type], piece_placement.class)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Validate games_turn is an Array with exactly 2 elements
|
76
|
+
unless games_turn.is_a?(Array) && games_turn.size == 2
|
77
|
+
raise ArgumentError, format(ERRORS[:invalid_games_turn_type], games_turn.inspect)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Validate pieces_in_hand is an Array
|
81
|
+
return if pieces_in_hand.is_a?(Array)
|
82
|
+
|
83
|
+
raise ArgumentError, format(ERRORS[:invalid_pieces_in_hand_type], pieces_in_hand.class)
|
34
84
|
end
|
35
85
|
end
|
36
86
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Feen
|
4
|
+
module Parser
|
5
|
+
module GamesTurn
|
6
|
+
# Error messages for games turn parsing
|
7
|
+
Errors = {
|
8
|
+
invalid_type: "Games turn must be a string, got %s",
|
9
|
+
empty_string: "Games turn string cannot be empty",
|
10
|
+
invalid_format: "Invalid games turn format. Expected format: UPPERCASE/lowercase or lowercase/UPPERCASE"
|
11
|
+
}.freeze
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Feen
|
4
|
+
module Parser
|
5
|
+
module GamesTurn
|
6
|
+
# Complete pattern matching the BNF specification with named groups
|
7
|
+
# <games-turn> ::= <game-id-uppercase> "/" <game-id-lowercase>
|
8
|
+
# | <game-id-lowercase> "/" <game-id-uppercase>
|
9
|
+
ValidGamesTurnPattern = %r{
|
10
|
+
\A # Start of string
|
11
|
+
(?: # Non-capturing group for alternatives
|
12
|
+
(?<uppercase_first>[A-Z]+) # Named group: uppercase identifier first
|
13
|
+
/ # Separator
|
14
|
+
(?<lowercase_second>[a-z]+) # Named group: lowercase identifier second
|
15
|
+
| # OR
|
16
|
+
(?<lowercase_first>[a-z]+) # Named group: lowercase identifier first
|
17
|
+
/ # Separator
|
18
|
+
(?<uppercase_second>[A-Z]+) # Named group: uppercase identifier second
|
19
|
+
)
|
20
|
+
\z # End of string
|
21
|
+
}x
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative File.join("games_turn", "valid_games_turn_pattern")
|
4
|
+
require_relative File.join("games_turn", "errors")
|
5
|
+
|
6
|
+
module Feen
|
7
|
+
module Parser
|
8
|
+
# Handles parsing of the games turn section of a FEEN string
|
9
|
+
module GamesTurn
|
10
|
+
# Parses the games turn section of a FEEN string
|
11
|
+
#
|
12
|
+
# @param games_turn_str [String] FEEN games turn string
|
13
|
+
# @return [Array<String>] Array containing [active_player, inactive_player]
|
14
|
+
# @raise [ArgumentError] If the input string is invalid
|
15
|
+
#
|
16
|
+
# @example Valid games turn string with uppercase first
|
17
|
+
# GamesTurn.parse("CHESS/shogi")
|
18
|
+
# # => ["CHESS", "shogi"]
|
19
|
+
#
|
20
|
+
# @example Valid games turn string with lowercase first
|
21
|
+
# GamesTurn.parse("chess/SHOGI")
|
22
|
+
# # => ["chess", "SHOGI"]
|
23
|
+
def self.parse(games_turn_str)
|
24
|
+
validate_input_type(games_turn_str)
|
25
|
+
|
26
|
+
match = ValidGamesTurnPattern.match(games_turn_str)
|
27
|
+
raise ::ArgumentError, Errors[:invalid_format] unless match
|
28
|
+
|
29
|
+
extract_game_identifiers(**match.named_captures.transform_keys(&:to_sym))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Validates that the input is a non-empty string
|
33
|
+
#
|
34
|
+
# @param str [String] Input string to validate
|
35
|
+
# @raise [ArgumentError] If input is not a string or is empty
|
36
|
+
# @return [void]
|
37
|
+
private_class_method def self.validate_input_type(str)
|
38
|
+
raise ::ArgumentError, format(Errors[:invalid_type], str.class) unless str.is_a?(::String)
|
39
|
+
raise ::ArgumentError, Errors[:empty_string] if str.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
# Extracts game identifiers from regexp match captures
|
43
|
+
#
|
44
|
+
# @param uppercase_first [String, nil] Uppercase identifier if it comes first
|
45
|
+
# @param lowercase_second [String, nil] Lowercase identifier if it comes second
|
46
|
+
# @param lowercase_first [String, nil] Lowercase identifier if it comes first
|
47
|
+
# @param uppercase_second [String, nil] Uppercase identifier if it comes second
|
48
|
+
# @return [Array<String>] Array containing [active_player, inactive_player]
|
49
|
+
private_class_method def self.extract_game_identifiers(uppercase_first: nil, lowercase_second: nil, lowercase_first: nil, uppercase_second: nil)
|
50
|
+
if uppercase_first
|
51
|
+
[uppercase_first, lowercase_second]
|
52
|
+
else
|
53
|
+
[lowercase_first, uppercase_second]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|