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.
@@ -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", "board_shape")
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
- # The parser module.
8
+ # Module responsible for parsing FEEN notation strings into internal data structures
7
9
  module Parser
8
- # Parse a FEEN string into position params.
10
+ # Parses a complete FEEN string into a structured representation
9
11
  #
10
- # @param feen [String] The FEEN string representing a position.
11
- #
12
- # @example Parse a classic Tsume Shogi problem
13
- # call("3sks3/9/4+P4/9/7+B1/9/9/9/9 s")
14
- # # => {
15
- # # "board_shape": [9, 9],
16
- # # "side_to_move": "s",
17
- # # "piece_placement": {
18
- # # 3 => "s",
19
- # # 4 => "k",
20
- # # 5 => "s",
21
- # # 22 => "+P",
22
- # # 43 => "+B"
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
- board_shape: BoardShape.new(piece_placement_str, regex:).to_a,
31
- piece_placement: piece_placement(piece_placement_str, regex:),
32
- side_to_move: side_to_move_str
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
- def self.piece_placement(string, regex:)
37
- hash = {}
38
- index = 0
39
- string.scan(/(\d+|#{regex})/) do |match|
40
- if /\d+/.match?(match[0])
41
- index += match[0].to_i
42
- else
43
- hash[index] = match[0]
44
- index += 1
45
- end
46
- end
47
- hash
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://github.com/sashite/specs/blob/main/forsyth-edwards-expanded-notation.md
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 board_shape [Array] The shape of the board.
14
- # @param side_to_move [String] The identifier of the player who must play.
15
- # @param piece_placement [Hash] The index of each piece on the board.
16
- #
17
- # @example Dump a classic Tsume Shogi problem
18
- # dump(
19
- # "board_shape": [9, 9],
20
- # "side_to_move": "s",
21
- # "piece_placement": {
22
- # 3 => "s",
23
- # 4 => "k",
24
- # 5 => "s",
25
- # 22 => "+P",
26
- # 43 => "+B"
27
- # }
28
- # )
29
- # # => "3sks3/9/4+P4/9/7+B1/9/9/9/9 s"
30
- #
31
- # @return [String] The FEEN string representing the position.
32
- def self.dump(board_shape:, side_to_move:, piece_placement:)
33
- Dumper.call(
34
- board_shape:,
35
- side_to_move:,
36
- piece_placement:
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 feen [String] The FEEN string representing a position.
43
- #
44
- # @example Parse a classic Tsume Shogi problem
45
- # parse("3sks3/9/4+P4/9/7+B1/9/9/9/9 s")
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
- # # "board_shape": [9, 9],
48
- # # "side_to_move": "s",
49
- # # "piece_placement": {
50
- # # 3 => "s",
51
- # # 4 => "k",
52
- # # 5 => "s",
53
- # # 22 => "+P",
54
- # # 43 => "+B"
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
- # @return [Hash] The position params representing the position.
58
- def self.parse(feen, regex: /\+?[a-z]/i)
59
- Parser.call(feen, regex:)
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.beta1
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: 2023-04-27 00:00:00.000000000 Z
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/board_shape.rb
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.2.0
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: 1.3.1
50
+ version: '0'
45
51
  requirements: []
46
- rubygems_version: 3.4.6
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