feen 5.0.0.beta1 → 5.0.0.beta2
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 +144 -39
- data/lib/feen/converter/from_fen.rb +170 -0
- data/lib/feen/converter/to_fen.rb +153 -0
- data/lib/feen/converter.rb +16 -0
- data/lib/feen/dumper/games_turn.rb +92 -0
- data/lib/feen/dumper/piece_placement.rb +104 -68
- data/lib/feen/dumper/pieces_in_hand.rb +56 -0
- data/lib/feen/dumper.rb +43 -24
- data/lib/feen/parser/games_turn.rb +136 -0
- data/lib/feen/parser/piece_placement.rb +221 -0
- data/lib/feen/parser/pieces_in_hand.rb +75 -0
- data/lib/feen/parser.rb +38 -37
- data/lib/feen/sanitizer.rb +119 -0
- data/lib/feen.rb +91 -42
- metadata +15 -10
- data/lib/feen/parser/board_shape.rb +0 -39
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Feen
|
4
|
+
module Parser
|
5
|
+
# Handles parsing of the pieces in hand section of a FEEN string
|
6
|
+
module PiecesInHand
|
7
|
+
NO_PIECES = "-"
|
8
|
+
ERRORS = {
|
9
|
+
invalid_type: "Pieces in hand must be a string, got %s",
|
10
|
+
empty_string: "Pieces in hand string cannot be empty",
|
11
|
+
invalid_chars: "Invalid characters in pieces in hand: %s",
|
12
|
+
invalid_identifier: "Invalid piece identifier at position %d"
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
# Parses the pieces in hand section of a FEEN string
|
16
|
+
#
|
17
|
+
# @param pieces_in_hand_str [String] FEEN pieces in hand string
|
18
|
+
# @return [Array<Hash>] Array of pieces in hand
|
19
|
+
# @raise [ArgumentError] If the input string is invalid
|
20
|
+
def self.parse(pieces_in_hand_str)
|
21
|
+
validate_pieces_in_hand_string(pieces_in_hand_str)
|
22
|
+
|
23
|
+
# Handle the special case of no pieces in hand
|
24
|
+
return [] if pieces_in_hand_str == NO_PIECES
|
25
|
+
|
26
|
+
pieces = []
|
27
|
+
i = 0
|
28
|
+
|
29
|
+
while i < pieces_in_hand_str.length
|
30
|
+
# Vérifier que le caractère est une lettre
|
31
|
+
raise ArgumentError, format(ERRORS[:invalid_identifier], i) unless pieces_in_hand_str[i].match?(/[a-zA-Z]/)
|
32
|
+
|
33
|
+
pieces << { id: pieces_in_hand_str[i] }
|
34
|
+
i += 1
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
# Vérifier que les pièces sont triées par ordre lexicographique
|
39
|
+
raise ArgumentError, "Pieces in hand must be in ASCII lexicographic order" unless pieces_sorted?(pieces)
|
40
|
+
|
41
|
+
pieces
|
42
|
+
end
|
43
|
+
|
44
|
+
# Validates the pieces in hand string for syntax
|
45
|
+
#
|
46
|
+
# @param str [String] FEEN pieces in hand string
|
47
|
+
# @raise [ArgumentError] If the string is invalid
|
48
|
+
# @return [void]
|
49
|
+
def self.validate_pieces_in_hand_string(str)
|
50
|
+
raise ArgumentError, format(ERRORS[:invalid_type], str.class) unless str.is_a?(String)
|
51
|
+
|
52
|
+
raise ArgumentError, ERRORS[:empty_string] if str.empty?
|
53
|
+
|
54
|
+
# Check for the special case of no pieces in hand
|
55
|
+
return if str == NO_PIECES
|
56
|
+
|
57
|
+
# Check for valid characters (only letters)
|
58
|
+
valid_chars = /\A[a-zA-Z]+\z/
|
59
|
+
return if str.match?(valid_chars)
|
60
|
+
|
61
|
+
invalid_chars = str.scan(/[^a-zA-Z]/).uniq.join(", ")
|
62
|
+
raise ArgumentError, format(ERRORS[:invalid_chars], invalid_chars)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Checks if pieces are sorted in ASCII lexicographic order
|
66
|
+
#
|
67
|
+
# @param pieces [Array<Hash>] Array of piece hashes
|
68
|
+
# @return [Boolean] True if pieces are sorted
|
69
|
+
def self.pieces_sorted?(pieces)
|
70
|
+
piece_ids = pieces.map { |piece| piece[:id] }
|
71
|
+
piece_ids == piece_ids.sort
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/feen/parser.rb
CHANGED
@@ -1,51 +1,52 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative File.join("parser", "
|
3
|
+
require_relative File.join("parser", "games_turn")
|
4
|
+
require_relative File.join("parser", "piece_placement")
|
5
|
+
require_relative File.join("parser", "pieces_in_hand")
|
4
6
|
|
5
7
|
module Feen
|
6
|
-
#
|
8
|
+
# Module responsible for parsing FEEN notation strings into internal data structures
|
7
9
|
module Parser
|
8
|
-
#
|
10
|
+
# Parses a complete FEEN string into a structured representation
|
9
11
|
#
|
10
|
-
# @param
|
11
|
-
#
|
12
|
-
# @
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# @return [Hash] The position params representing the position.
|
26
|
-
def self.call(feen, regex: /\+?[a-z]/i)
|
27
|
-
piece_placement_str, side_to_move_str = feen.split
|
12
|
+
# @param feen_string [String] Complete FEEN notation string
|
13
|
+
# @return [Hash] Hash containing the parsed position data
|
14
|
+
# @raise [ArgumentError] If the FEEN string is invalid
|
15
|
+
def self.parse(feen_string)
|
16
|
+
validate_feen_string(feen_string)
|
17
|
+
|
18
|
+
# Split the FEEN string into its three fields
|
19
|
+
fields = feen_string.strip.split(/\s+/)
|
20
|
+
|
21
|
+
raise ArgumentError, "Invalid FEEN format: expected 3 fields, got #{fields.size}" unless fields.size == 3
|
22
|
+
|
23
|
+
# Parse each field using the appropriate submodule
|
24
|
+
piece_placement = PiecePlacement.parse(fields[0])
|
25
|
+
games_turn = GamesTurn.parse(fields[1])
|
26
|
+
pieces_in_hand = PiecesInHand.parse(fields[2])
|
28
27
|
|
28
|
+
# Return a structured representation of the position
|
29
29
|
{
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
piece_placement: piece_placement,
|
31
|
+
games_turn: games_turn,
|
32
|
+
pieces_in_hand: pieces_in_hand
|
33
33
|
}
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
36
|
+
# Validates the FEEN string for basic format
|
37
|
+
#
|
38
|
+
# @param feen_string [String] FEEN string to validate
|
39
|
+
# @raise [ArgumentError] If the FEEN string is fundamentally invalid
|
40
|
+
# @return [void]
|
41
|
+
def self.validate_feen_string(feen_string)
|
42
|
+
raise ArgumentError, "FEEN must be a string, got #{feen_string.class}" unless feen_string.is_a?(String)
|
43
|
+
|
44
|
+
raise ArgumentError, "FEEN string cannot be empty" if feen_string.empty?
|
45
|
+
|
46
|
+
# Check for at least two spaces (three fields)
|
47
|
+
return unless feen_string.count(" ") < 2
|
48
|
+
|
49
|
+
raise ArgumentError, "Invalid FEEN format: must contain at least two spaces separating three fields"
|
48
50
|
end
|
49
|
-
private_class_method :piece_placement
|
50
51
|
end
|
51
52
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Feen
|
4
|
+
# Provides methods for sanitizing and validating chess-related notation strings
|
5
|
+
module Sanitizer
|
6
|
+
# Cleans a FEN (Forsyth-Edwards Notation) string by removing invalid castling rights
|
7
|
+
# and en passant targets based on the current position.
|
8
|
+
#
|
9
|
+
# The method performs the following validations:
|
10
|
+
# - Verifies that kings and rooks are in correct positions for castling rights
|
11
|
+
# - Verifies that en passant captures are actually possible
|
12
|
+
#
|
13
|
+
# @param fen_string [String] The FEN string to clean
|
14
|
+
# @return [String] A sanitized FEN string with invalid castling rights and en passant targets removed
|
15
|
+
# @raise [ArgumentError] If the FEN string is malformed (less than 4 parts)
|
16
|
+
#
|
17
|
+
# @example Clean a valid FEN string (unchanged)
|
18
|
+
# fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
19
|
+
# Feen::Sanitizer.clean_fen(fen) # => "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
20
|
+
#
|
21
|
+
# @example Remove invalid castling rights when king has moved
|
22
|
+
# fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQ1BNR w KQkq - 0 1"
|
23
|
+
# Feen::Sanitizer.clean_fen(fen) # => "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQ1BNR w kq - 0 1"
|
24
|
+
#
|
25
|
+
# @example Remove invalid castling rights when rook has moved
|
26
|
+
# fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/1NBQKBNR w KQkq - 0 1"
|
27
|
+
# Feen::Sanitizer.clean_fen(fen) # => "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/1NBQKBNR w Kkq - 0 1"
|
28
|
+
#
|
29
|
+
# @example Remove invalid en passant target when no capturing pawn exists
|
30
|
+
# fen = "rnbqkbnr/pppp1ppp/8/4p3/8/8/PPPPPPPP/RNBQKBNR w KQkq e6 0 2"
|
31
|
+
# Feen::Sanitizer.clean_fen(fen) # => "rnbqkbnr/pppp1ppp/8/4p3/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 2"
|
32
|
+
#
|
33
|
+
# @example Keep valid en passant target when capturing is possible
|
34
|
+
# fen = "rnbqkbnr/pppp1ppp/8/3Pp3/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 2"
|
35
|
+
# Feen::Sanitizer.clean_fen(fen) # => "rnbqkbnr/pppp1ppp/8/3Pp3/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 2"
|
36
|
+
def self.clean_fen(fen_string)
|
37
|
+
parts = fen_string.strip.split
|
38
|
+
return fen_string unless parts.size >= 4
|
39
|
+
|
40
|
+
board, active_color, castling, en_passant, *rest = parts
|
41
|
+
|
42
|
+
# Parse board into a 2D array for easier access
|
43
|
+
board_matrix = []
|
44
|
+
board.split("/").each do |row|
|
45
|
+
current_row = []
|
46
|
+
row.each_char do |c|
|
47
|
+
if /[1-8]/.match?(c)
|
48
|
+
c.to_i.times { current_row << nil }
|
49
|
+
else
|
50
|
+
current_row << c
|
51
|
+
end
|
52
|
+
end
|
53
|
+
board_matrix << current_row
|
54
|
+
end
|
55
|
+
|
56
|
+
# Clean castling rights
|
57
|
+
new_castling = castling.dup
|
58
|
+
new_castling = clean_castling_rights(new_castling, board_matrix)
|
59
|
+
|
60
|
+
# Clean en passant target
|
61
|
+
new_en_passant = clean_en_passant_target(en_passant, board_matrix, active_color)
|
62
|
+
|
63
|
+
([board, active_color, new_castling, new_en_passant] + rest).join(" ")
|
64
|
+
end
|
65
|
+
|
66
|
+
# Validates and cleans castling rights based on the position of kings and rooks
|
67
|
+
#
|
68
|
+
# @param castling [String] The castling rights string from FEN
|
69
|
+
# @param board [Array<Array<String, nil>>] 2D array representing the board
|
70
|
+
# @return [String] Cleaned castling rights or "-" if none are valid
|
71
|
+
# @api private
|
72
|
+
private_class_method def self.clean_castling_rights(castling, board)
|
73
|
+
return "-" if castling == "-"
|
74
|
+
|
75
|
+
new_castling = castling.dup
|
76
|
+
|
77
|
+
# White castling rights
|
78
|
+
new_castling.gsub!(/[KQ]/, "") unless board[7][4] == "K"
|
79
|
+
new_castling.delete!("K") unless board[7][7] == "R"
|
80
|
+
new_castling.delete!("Q") unless board[7][0] == "R"
|
81
|
+
|
82
|
+
# Black castling rights
|
83
|
+
new_castling.gsub!(/[kq]/, "") unless board[0][4] == "k"
|
84
|
+
new_castling.delete!("k") unless board[0][7] == "r"
|
85
|
+
new_castling.delete!("q") unless board[0][0] == "r"
|
86
|
+
|
87
|
+
new_castling.empty? ? "-" : new_castling
|
88
|
+
end
|
89
|
+
|
90
|
+
# Validates and cleans en passant target based on the position of pawns
|
91
|
+
#
|
92
|
+
# @param en_passant [String] The en passant target square from FEN
|
93
|
+
# @param board [Array<Array<String, nil>>] 2D array representing the board
|
94
|
+
# @param active_color [String] The active color ("w" or "b")
|
95
|
+
# @return [String] Cleaned en passant target or "-" if invalid
|
96
|
+
# @api private
|
97
|
+
private_class_method def self.clean_en_passant_target(en_passant, board, active_color)
|
98
|
+
return "-" if en_passant == "-"
|
99
|
+
|
100
|
+
file = en_passant[0].ord - "a".ord
|
101
|
+
rank = en_passant[1].to_i
|
102
|
+
|
103
|
+
# Validate en passant square coordinates
|
104
|
+
return "-" unless file.between?(0, 7) && [3, 6].include?(rank)
|
105
|
+
|
106
|
+
# For white's move (after black pawn double advance)
|
107
|
+
if active_color == "w" && rank == 6
|
108
|
+
# Check for white pawns on the 5th rank (index 3) that can capture
|
109
|
+
return en_passant if [file - 1, file + 1].any? { |f| f.between?(0, 7) && board[3][f] == "P" }
|
110
|
+
# For black's move (after white pawn double advance)
|
111
|
+
elsif active_color == "b" && rank == 3
|
112
|
+
# Check for black pawns on the 4th rank (index 4) that can capture
|
113
|
+
return en_passant if [file - 1, file + 1].any? { |f| f.between?(0, 7) && board[4][f] == "p" }
|
114
|
+
end
|
115
|
+
|
116
|
+
"-" # Invalid en passant square
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/feen.rb
CHANGED
@@ -1,61 +1,110 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative File.join("feen", "converter")
|
3
4
|
require_relative File.join("feen", "dumper")
|
4
5
|
require_relative File.join("feen", "parser")
|
5
6
|
|
6
7
|
# This module provides a Ruby interface for data serialization and
|
7
8
|
# deserialization in FEEN format.
|
8
9
|
#
|
9
|
-
# @see https://
|
10
|
+
# @see https://sashite.dev/documents/feen/1.0.0/
|
10
11
|
module Feen
|
11
12
|
# Dumps position params into a FEEN string.
|
12
13
|
#
|
13
|
-
# @param
|
14
|
-
# @
|
15
|
-
# @
|
16
|
-
#
|
17
|
-
# @
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
14
|
+
# @param position [Hash] Hash containing the position data
|
15
|
+
# @option position [Array] :piece_placement Board position data
|
16
|
+
# @option position [Hash] :games_turn Games and turn data
|
17
|
+
# @option position [Array<Hash>] :pieces_in_hand Pieces in hand data
|
18
|
+
# @return [String] FEEN notation string
|
19
|
+
# @raise [ArgumentError] If the position data is invalid
|
20
|
+
# @example
|
21
|
+
# position = {
|
22
|
+
# piece_placement: [[{id: 'r'}, {id: 'n'}, {id: 'b'}, {id: 'q'}, {id: 'k'}, {id: 'b'}, {id: 'n'}, {id: 'r'}],
|
23
|
+
# [{id: 'p'}, {id: 'p'}, {id: 'p'}, {id: 'p'}, {id: 'p'}, {id: 'p'}, {id: 'p'}, {id: 'p'}],
|
24
|
+
# [nil, nil, nil, nil, nil, nil, nil, nil],
|
25
|
+
# [nil, nil, nil, nil, nil, nil, nil, nil],
|
26
|
+
# [nil, nil, nil, nil, nil, nil, nil, nil],
|
27
|
+
# [nil, nil, nil, nil, nil, nil, nil, nil],
|
28
|
+
# [{id: 'P'}, {id: 'P'}, {id: 'P'}, {id: 'P'}, {id: 'P'}, {id: 'P'}, {id: 'P'}, {id: 'P'}],
|
29
|
+
# [{id: 'R'}, {id: 'N'}, {id: 'B'}, {id: 'Q'}, {id: 'K'}, {id: 'B'}, {id: 'N'}, {id: 'R'}]],
|
30
|
+
# games_turn: {
|
31
|
+
# active_player: 'CHESS',
|
32
|
+
# inactive_player: 'chess',
|
33
|
+
# uppercase_game: 'CHESS',
|
34
|
+
# lowercase_game: 'chess'
|
35
|
+
# },
|
36
|
+
# pieces_in_hand: []
|
37
|
+
# }
|
38
|
+
# Feen.dump(position) # => "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR CHESS/chess -"
|
39
|
+
def self.dump(position)
|
40
|
+
Dumper.dump(position)
|
38
41
|
end
|
39
42
|
|
40
43
|
# Parses a FEEN string into position params.
|
41
44
|
#
|
42
|
-
# @param
|
43
|
-
#
|
44
|
-
# @
|
45
|
-
#
|
45
|
+
# @param feen_string [String] FEEN notation string
|
46
|
+
# @return [Hash] Hash containing the parsed position data
|
47
|
+
# @raise [ArgumentError] If the FEEN string is invalid
|
48
|
+
# @example
|
49
|
+
# feen_string = "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR CHESS/chess -"
|
50
|
+
# Feen.parse(feen_string)
|
46
51
|
# # => {
|
47
|
-
# #
|
48
|
-
# #
|
49
|
-
# #
|
50
|
-
# #
|
51
|
-
# #
|
52
|
-
# #
|
53
|
-
# #
|
54
|
-
# #
|
55
|
-
# #
|
52
|
+
# # piece_placement: [[{id: 'r'}, {id: 'n'}, {id: 'b'}, {id: 'q'}, {id: 'k', suffix: '='}, {id: 'b'}, {id: 'n'}, {id: 'r'}],
|
53
|
+
# # [{id: 'p'}, {id: 'p'}, {id: 'p'}, {id: 'p'}, {id: 'p'}, {id: 'p'}, {id: 'p'}, {id: 'p'}],
|
54
|
+
# # [nil, nil, nil, nil, nil, nil, nil, nil],
|
55
|
+
# # [nil, nil, nil, nil, nil, nil, nil, nil],
|
56
|
+
# # [nil, nil, nil, nil, nil, nil, nil, nil],
|
57
|
+
# # [nil, nil, nil, nil, nil, nil, nil, nil],
|
58
|
+
# # [{id: 'P'}, {id: 'P'}, {id: 'P'}, {id: 'P'}, {id: 'P'}, {id: 'P'}, {id: 'P'}, {id: 'P'}],
|
59
|
+
# # [{id: 'R'}, {id: 'N'}, {id: 'B'}, {id: 'Q'}, {id: 'K', suffix: '='}, {id: 'B'}, {id: 'N'}, {id: 'R'}]],
|
60
|
+
# # games_turn: {
|
61
|
+
# # active_player: 'CHESS',
|
62
|
+
# # inactive_player: 'chess',
|
63
|
+
# # uppercase_game: 'CHESS',
|
64
|
+
# # lowercase_game: 'chess',
|
65
|
+
# # active_player_casing: :uppercase
|
66
|
+
# # },
|
67
|
+
# # pieces_in_hand: []
|
68
|
+
# # }
|
69
|
+
def self.parse(feen_string)
|
70
|
+
Parser.parse(feen_string)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Validates if the given string is a valid FEEN string
|
74
|
+
#
|
75
|
+
# @param feen_string [String] FEEN string to validate
|
76
|
+
# @return [Boolean] True if the string is a valid FEEN string, false otherwise
|
77
|
+
# @example
|
78
|
+
# Feen.valid?("rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR CHESS/chess -") # => true
|
79
|
+
# Feen.valid?("invalid feen string") # => false
|
80
|
+
def self.valid?(feen_string)
|
81
|
+
parse(feen_string)
|
82
|
+
true
|
83
|
+
rescue ::ArgumentError
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
87
|
+
# Converts a FEN string to a FEEN string for chess positions
|
88
|
+
#
|
89
|
+
# @param fen_string [String] Standard FEN notation string for chess
|
90
|
+
# @return [String] Equivalent FEEN notation string
|
91
|
+
# @raise [ArgumentError] If the FEN string is invalid
|
92
|
+
# @example
|
93
|
+
# Feen.from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
94
|
+
# # => "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR CHESS/chess -"
|
95
|
+
def self.from_fen(fen_string)
|
96
|
+
Converter.from_fen(fen_string)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Converts a FEEN string to a FEN string for chess positions
|
56
100
|
#
|
57
|
-
# @
|
58
|
-
|
59
|
-
|
101
|
+
# @param feen_string [String] FEEN notation string
|
102
|
+
# @return [String] Equivalent FEN notation string
|
103
|
+
# @raise [ArgumentError] If the FEEN string is invalid
|
104
|
+
# @example
|
105
|
+
# Feen.to_fen("rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR CHESS/chess -")
|
106
|
+
# # => "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
107
|
+
def self.to_fen(feen_string)
|
108
|
+
Converter.to_fen(feen_string)
|
60
109
|
end
|
61
110
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: feen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.0.
|
4
|
+
version: 5.0.0.beta2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: A Ruby interface for data serialization and deserialization in FEEN format.
|
14
13
|
email: contact@cyril.email
|
@@ -19,16 +18,23 @@ files:
|
|
19
18
|
- LICENSE.md
|
20
19
|
- README.md
|
21
20
|
- lib/feen.rb
|
21
|
+
- lib/feen/converter.rb
|
22
|
+
- lib/feen/converter/from_fen.rb
|
23
|
+
- lib/feen/converter/to_fen.rb
|
22
24
|
- lib/feen/dumper.rb
|
25
|
+
- lib/feen/dumper/games_turn.rb
|
23
26
|
- lib/feen/dumper/piece_placement.rb
|
27
|
+
- lib/feen/dumper/pieces_in_hand.rb
|
24
28
|
- lib/feen/parser.rb
|
25
|
-
- lib/feen/parser/
|
29
|
+
- lib/feen/parser/games_turn.rb
|
30
|
+
- lib/feen/parser/piece_placement.rb
|
31
|
+
- lib/feen/parser/pieces_in_hand.rb
|
32
|
+
- lib/feen/sanitizer.rb
|
26
33
|
homepage: https://github.com/sashite/feen.rb
|
27
34
|
licenses:
|
28
35
|
- MIT
|
29
36
|
metadata:
|
30
37
|
rubygems_mfa_required: 'true'
|
31
|
-
post_install_message:
|
32
38
|
rdoc_options: []
|
33
39
|
require_paths:
|
34
40
|
- lib
|
@@ -36,15 +42,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
36
42
|
requirements:
|
37
43
|
- - ">="
|
38
44
|
- !ruby/object:Gem::Version
|
39
|
-
version: 3.
|
45
|
+
version: 3.4.0
|
40
46
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
47
|
requirements:
|
42
|
-
- - "
|
48
|
+
- - ">="
|
43
49
|
- !ruby/object:Gem::Version
|
44
|
-
version:
|
50
|
+
version: '0'
|
45
51
|
requirements: []
|
46
|
-
rubygems_version: 3.
|
47
|
-
signing_key:
|
52
|
+
rubygems_version: 3.6.7
|
48
53
|
specification_version: 4
|
49
54
|
summary: FEEN support for the Ruby language.
|
50
55
|
test_files: []
|
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Feen
|
4
|
-
module Parser
|
5
|
-
# The BoardShape class.
|
6
|
-
#
|
7
|
-
# @example Parse the shape of a shogiban
|
8
|
-
# BoardShape.new("3sks3/9/4+P4/9/7+B1/9/9/9/9").to_a # => [9, 9]
|
9
|
-
class BoardShape
|
10
|
-
# @param board_str [String] The flatten board.
|
11
|
-
def initialize(board_str, regex: /\+?[a-z]/i)
|
12
|
-
@board_str = board_str
|
13
|
-
@regex = regex
|
14
|
-
end
|
15
|
-
|
16
|
-
# @return [Array] The size of each dimension of the board.
|
17
|
-
def to_a
|
18
|
-
indexes(@board_str, @board_str.scan(%r{/+}).sort.fetch(-1))
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def indexes(string, separator)
|
24
|
-
if separator.empty?
|
25
|
-
last_index = string.scan(/(\d+|#{@regex})/).inject(0) do |counter, match|
|
26
|
-
sub_string = match[0]
|
27
|
-
number = sub_string.match?(/\d+/) ? Integer(sub_string) : 1
|
28
|
-
counter + number
|
29
|
-
end
|
30
|
-
|
31
|
-
return [last_index]
|
32
|
-
end
|
33
|
-
|
34
|
-
sub_strings = string.split(separator)
|
35
|
-
[sub_strings.length] + indexes(sub_strings.fetch(0), separator[1..])
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|