feen 5.0.0.beta2 → 5.0.0.beta4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3dbd219b5b2e7870e60da133faae1f342179e35682590d2f5fbe06c4f0130d39
4
- data.tar.gz: c1042208ec1799d5700e87327bafaa39dc918b5e101304c9b90a67e22be76c25
3
+ metadata.gz: de87bfc9b071e9f1ff14b6f7378ba0a6f9f78cdb9a9bfe32ca00f2494a1be2cd
4
+ data.tar.gz: fd4d19e8a3b7fb845f081b127a859ff0791c8c11c05e1efb4846152044f67c18
5
5
  SHA512:
6
- metadata.gz: e8480101aa872bba2295a2ee74d47f4bde63a7d94c50b6504e8acadbf8347ee5a136bc962b65fdbb75aed3dd887693b6b012b53aed77c4e101313fd2189c59d5
7
- data.tar.gz: fde52d865df00f7602017789aeb9d6520b809be88fad13a14215f289abcc60f61d88ae73624cf01fd3a158aa62a94b98814bb0a1aff510b2e218833fb59094d9
6
+ metadata.gz: e9fe95297b8577a3bf171a865deca0dc9c2eed91a7d039c04f97077d53d3fa912ca553f28f448f0dd987087926d3ca5225624d7f2d8d08a9275a16a11efe26e0
7
+ data.tar.gz: b82b7e00f5ece6f5e5859750226be3c74db23e694c8e53def8c8c656fe2fb3ecba7f0f4166cf3d9a4f6c7b4ab7af3cf6544da87a2684e438a389d96e04b3dc75
data/README.md CHANGED
@@ -1,28 +1,28 @@
1
1
  # Feen.rb
2
2
 
3
- [![Version](https://img.shields.io/github/v/tag/sashite/feen.rb?label=Version&logo=github)](https://github.com/sashite/feen.rb/releases)
3
+ [![Version](https://img.shields.io/github/v/tag/sashite/feen.rb?label=Version&logo=github)](https://github.com/sashite/feen.rb/tags)
4
4
  [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/sashite/feen.rb/main)
5
- [![CI](https://github.com/sashite/feen.rb/workflows/CI/badge.svg?branch=main)](https://github.com/sashite/feen.rb/actions?query=workflow%3Aci+branch%3Amain)
6
- [![RuboCop](https://github.com/sashite/feen.rb/workflows/RuboCop/badge.svg?branch=main)](https://github.com/sashite/feen.rb/actions?query=workflow%3Arubocop+branch%3Amain)
5
+ ![Ruby](https://github.com/sashite/feen.rb/actions/workflows/main.yml/badge.svg?branch=main)
7
6
  [![License](https://img.shields.io/github/license/sashite/feen.rb?label=License&logo=github)](https://github.com/sashite/feen.rb/raw/main/LICENSE.md)
8
7
 
9
- > **FEEN** (Forsyth–Edwards Essential Notation) support for the Ruby language.
8
+ > **FEEN** (Forsyth–Edwards Enhanced Notation) support for the Ruby language.
10
9
 
11
10
  ## What is FEEN?
12
11
 
13
- FEEN (Forsyth–Edwards Essential Notation) is a compact, canonical, and rule-agnostic textual format for representing static board positions in two-player piece-placement games.
12
+ FEEN (Forsyth–Edwards Enhanced Notation) is a compact, canonical, and rule-agnostic textual format for representing static board positions in two-player piece-placement games.
14
13
 
15
14
  This gem implements the [FEEN Specification v1.0.0](https://sashite.dev/documents/feen/1.0.0/), providing a Ruby interface for:
16
- - Multiple game types (chess, shogi, xiangqi, etc.)
17
- - Hybrid or cross-game positions
18
- - Arbitrary-dimensional boards
19
- - Pieces in hand (as used in Shogi)
15
+ - Representing positions from various games without knowledge of specific rules
16
+ - Supporting boards of arbitrary dimensions
17
+ - Encoding pieces in hand (as used in Shogi)
18
+ - Facilitating serialization and deserialization of positions
19
+ - Ensuring canonical representation for consistent data handling
20
20
 
21
21
  ## Installation
22
22
 
23
23
  ```ruby
24
24
  # In your Gemfile
25
- gem "feen", ">= 5.0.0.beta2"
25
+ gem "feen", ">= 5.0.0.beta4"
26
26
  ```
27
27
 
28
28
  Or install manually:
@@ -36,7 +36,7 @@ gem install feen --pre
36
36
  A FEEN record consists of three space-separated fields:
37
37
 
38
38
  ```
39
- <PIECE-PLACEMENT> <GAMES-TURN> <PIECES-IN-HAND>
39
+ <PIECE-PLACEMENT> <PIECES-IN-HAND> <GAMES-TURN>
40
40
  ```
41
41
 
42
42
  ## Basic Usage
@@ -48,81 +48,136 @@ Convert a FEEN string into a structured Ruby object:
48
48
  ```ruby
49
49
  require "feen"
50
50
 
51
- feen_string = "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR CHESS/chess -"
51
+ feen_string = "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR - CHESS/chess"
52
52
  position = Feen.parse(feen_string)
53
53
 
54
- # Result is a hash with structured position data
55
- # position[:piece_placement] # 2D array of board pieces
56
- # position[:games_turn] # Details about active player and game
57
- # position[:pieces_in_hand] # Array of pieces held for dropping
54
+ # Result is a hash:
55
+ # {
56
+ # piece_placement: [
57
+ # ["r", "n", "b", "q", "k=", "b", "n", "r"],
58
+ # ["p", "p", "p", "p", "p", "p", "p", "p"],
59
+ # ["", "", "", "", "", "", "", ""],
60
+ # ["", "", "", "", "", "", "", ""],
61
+ # ["", "", "", "", "", "", "", ""],
62
+ # ["", "", "", "", "", "", "", ""],
63
+ # ["P", "P", "P", "P", "P", "P", "P", "P"],
64
+ # ["R", "N", "B", "Q", "K=", "B", "N", "R"]
65
+ # ],
66
+ # pieces_in_hand: [],
67
+ # games_turn: ["CHESS", "chess"]
68
+ # }
69
+ ```
70
+
71
+ ### Safe Parsing
72
+
73
+ Parse a FEEN string without raising exceptions:
74
+
75
+ ```ruby
76
+ require "feen"
77
+
78
+ # Valid FEEN string
79
+ result = Feen.safe_parse("rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR - CHESS/chess")
80
+ # => {piece_placement: [...], pieces_in_hand: [...], games_turn: [...]}
81
+
82
+ # Invalid FEEN string
83
+ result = Feen.safe_parse("invalid feen string")
84
+ # => nil
58
85
  ```
59
86
 
60
87
  ### Creating FEEN Strings
61
88
 
62
- Convert a position structure to a FEEN string:
89
+ Convert position components to a FEEN string using named arguments:
63
90
 
64
91
  ```ruby
65
92
  require "feen"
66
93
 
67
- position = {
68
- piece_placement: [
69
- [{ id: "r" }, { id: "n" }, { id: "b" }, { id: "q" }, { id: "k", suffix: "=" }, { id: "b" }, { id: "n" }, { id: "r" }],
70
- [{ id: "p" }, { id: "p" }, { id: "p" }, { id: "p" }, { id: "p" }, { id: "p" }, { id: "p" }, { id: "p" }],
71
- [nil, nil, nil, nil, nil, nil, nil, nil],
72
- [nil, nil, nil, nil, nil, nil, nil, nil],
73
- [nil, nil, nil, nil, nil, nil, nil, nil],
74
- [nil, nil, nil, nil, nil, nil, nil, nil],
75
- [{ id: "P" }, { id: "P" }, { id: "P" }, { id: "P" }, { id: "P" }, { id: "P" }, { id: "P" }, { id: "P" }],
76
- [{ id: "R" }, { id: "N" }, { id: "B" }, { id: "Q" }, { id: "K", suffix: "=" }, { id: "B" }, { id: "N" }, { id: "R" }]
77
- ],
78
- games_turn: {
79
- active_player: "CHESS",
80
- inactive_player: "chess"
81
- },
94
+ # Representation of a chess board in initial position
95
+ piece_placement = [
96
+ ["r", "n", "b", "q", "k=", "b", "n", "r"],
97
+ ["p", "p", "p", "p", "p", "p", "p", "p"],
98
+ ["", "", "", "", "", "", "", ""],
99
+ ["", "", "", "", "", "", "", ""],
100
+ ["", "", "", "", "", "", "", ""],
101
+ ["", "", "", "", "", "", "", ""],
102
+ ["P", "P", "P", "P", "P", "P", "P", "P"],
103
+ ["R", "N", "B", "Q", "K=", "B", "N", "R"]
104
+ ]
105
+
106
+ result = Feen.dump(
107
+ piece_placement: piece_placement,
108
+ games_turn: %w[CHESS chess],
82
109
  pieces_in_hand: []
83
- }
84
-
85
- Feen.dump(position)
86
- # => "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR CHESS/chess -"
110
+ )
111
+ # => "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR - CHESS/chess"
87
112
  ```
88
113
 
89
114
  ### Validation
90
115
 
91
- Check if a string is valid FEEN notation:
116
+ Check if a string is valid FEEN notation and in canonical form:
92
117
 
93
118
  ```ruby
94
119
  require "feen"
95
120
 
96
- Feen.valid?("rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR CHESS/chess -")
121
+ # Canonical form
122
+ Feen.valid?("lnsgk3l/5g3/p1ppB2pp/9/8B/2P6/P2PPPPPP/3K3R1/5rSNL N5P2gln2s SHOGI/shogi")
97
123
  # => true
98
124
 
125
+ # Invalid syntax
99
126
  Feen.valid?("invalid feen string")
100
127
  # => false
128
+
129
+ # Valid syntax but non-canonical form (pieces in hand not in lexicographic order)
130
+ Feen.valid?("lnsgk3l/5g3/p1ppB2pp/9/8B/2P6/P2PPPPPP/3K3R1/5rSNL N5P2gn2sl SHOGI/shogi")
131
+ # => false
101
132
  ```
102
133
 
103
- ## FEN Compatibility
134
+ The `valid?` method performs two levels of validation:
135
+ 1. **Syntax check**: Verifies the string can be parsed as FEEN
136
+ 2. **Canonicity check**: Ensures the string is in its canonical form through round-trip conversion
104
137
 
105
- ### Converting FEN to FEEN
138
+ ## Game Examples
139
+
140
+ As FEEN is rule-agnostic, it can represent positions from various board games. Here are some examples:
141
+
142
+ ### International Chess
106
143
 
107
144
  ```ruby
108
- require "feen"
145
+ feen_string = "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR - CHESS/chess"
146
+ ```
147
+
148
+ In this initial chess position:
149
+ - The `=` suffixes on kings indicate castling rights on both sides (though FEEN doesn't define this semantics)
150
+ - The third field `CHESS/chess` indicates it's the player with uppercase pieces' turn to move
109
151
 
110
- fen_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
111
- feen_string = Feen.from_fen(fen_string)
112
- # => "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR CHESS/chess -"
152
+ ### Shogi (Japanese Chess)
153
+
154
+ ```ruby
155
+ feen_string = "lnsgk3l/5g3/p1ppB2pp/9/8B/2P6/P2PPPPPP/3K3R1/5rSNL N5P2gln2s SHOGI/shogi"
113
156
  ```
114
157
 
115
- ### Converting FEEN to FEN
158
+ In this shogi position:
159
+ - The format supports promotions with the `+` prefix (e.g., `+P` for a promoted pawn)
160
+ - The notation allows for pieces in hand, indicated in the second field
161
+ - `SHOGI/shogi` indicates it's Sente's (Black's, uppercase) turn to move
162
+ - `N5P2gln2s` shows the pieces in hand: Sente has a Knight (N) and 5 Pawns (5P), while Gote has 2 Golds (2g), a Lance (l), a Knight (n), and 2 Silvers (2s), all properly sorted in ASCII lexicographic order
163
+
164
+ ### Makruk (Thai Chess)
116
165
 
117
166
  ```ruby
118
- require "feen"
167
+ feen_string = "rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR - MAKRUK/makruk"
168
+ ```
169
+
170
+ This initial Makruk position is easily represented in FEEN without needing to know the specific rules of the game.
119
171
 
120
- feen_string = "rnbqk=bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK=BNR CHESS/chess -"
121
- fen_string = Feen.to_fen(feen_string)
122
- # => "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
172
+ ### Xiangqi (Chinese Chess)
173
+
174
+ ```ruby
175
+ feen_string = "rheagaehr/9/1c5c1/s1s1s1s1s/9/9/S1S1S1S1S/1C5C1/9/RHEAGAEHR - XIANGQI/xiangqi"
123
176
  ```
124
177
 
125
- > ⚠️ `Feen.to_fen` only supports FEEN positions where `games_turn` is `CHESS/chess` or `chess/CHESS`.
178
+ In this Xiangqi position:
179
+ - The representation uses single letters for the different pieces
180
+ - The format naturally adapts to the presence of a "river" (empty space in the middle)
126
181
 
127
182
  ## Advanced Features
128
183
 
@@ -134,61 +189,54 @@ FEEN supports arbitrary-dimensional board configurations:
134
189
  require "feen"
135
190
 
136
191
  # 3D board
137
- position = {
138
- piece_placement: [
139
- [
140
- [{ id: "r" }, { id: "n" }, { id: "b" }],
141
- [{ id: "q" }, { id: "k" }, { id: "p" }]
142
- ],
143
- [
144
- [{ id: "P" }, { id: "R" }, nil],
145
- [nil, { id: "K" }, { id: "Q" }]
146
- ]
192
+ piece_placement = [
193
+ [
194
+ %w[r n b],
195
+ %w[q k p]
147
196
  ],
148
- games_turn: {
149
- active_player: "CHESS",
150
- inactive_player: "chess"
151
- },
197
+ [
198
+ ["P", "R", ""],
199
+ ["", "K", "Q"]
200
+ ]
201
+ ]
202
+
203
+ result = Feen.dump(
204
+ piece_placement: piece_placement,
205
+ games_turn: %w[FOO bar],
152
206
  pieces_in_hand: []
153
- }
154
-
155
- Feen.dump(position)
156
- # => "rnb/qkp//PR1/1KQ CHESS/chess -"
207
+ )
208
+ # => "rnb/qkp//PR1/1KQ - FOO/bar"
157
209
  ```
158
210
 
159
211
  ### Piece Modifiers
160
212
 
161
- FEEN supports prefixes and suffixes for pieces:
213
+ FEEN supports prefixes and suffixes for pieces to denote various states or capabilities:
162
214
 
163
- - Prefix `+`: Often used for promotion (e.g., `+P` for promoted pawn in Shogi)
164
- - Suffix `=`: Dual-option status (e.g., `K=` for king eligible for both castling sides)
165
- - Suffix `<`: Left-side constraint (e.g., `K<` for queenside castling only)
166
- - Suffix `>`: Right-side constraint (e.g., `K>` for kingside castling only)
215
+ - **Prefix `+`**: May indicate promotion or special state
216
+ - Example in shogi: `+P` may represent a promoted pawn
167
217
 
168
- ### Sanitizing FEN Strings
218
+ - **Suffix `=`**: May indicate dual-option status
219
+ - Example in chess: `K=` may represent a king eligible for both kingside and queenside castling
169
220
 
170
- FEEN includes utilities to clean FEN strings by validating and removing invalid castling rights and en passant targets:
221
+ - **Suffix `<`**: May indicate left-side constraint
222
+ - Example in chess: `K<` may represent a king eligible for queenside castling only
223
+ - Example in chess: `P<` may represent a pawn that may be captured _en passant_ from the left
171
224
 
172
- ```ruby
173
- require "feen"
225
+ - **Suffix `>`**: May indicate right-side constraint
226
+ - Example in chess: `K>` may represent a king eligible for kingside castling only
227
+ - Example in chess: `P>` may represent a pawn that may be captured en passant from the right
174
228
 
175
- # FEN with invalid castling rights
176
- fen_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQ1BNR w KQkq - 0 1"
177
- cleaned_fen = Feen::Sanitizer.clean_fen(fen_string)
178
- # => "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQ1BNR w kq - 0 1"
179
- ```
229
+ These modifiers have no defined semantics in the FEEN specification itself but provide a flexible framework for representing piece-specific conditions while maintaining FEEN's rule-agnostic nature.
180
230
 
181
231
  ## Documentation
182
232
 
183
- - [Official FEEN Specification v1.0.0](https://sashite.dev/documents/feen/1.0.0/)
233
+ - [Official FEEN Specification](https://sashite.dev/documents/feen/1.0.0/)
184
234
  - [API Documentation](https://rubydoc.info/github/sashite/feen.rb/main)
185
235
 
186
236
  ## License
187
237
 
188
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
238
+ The [gem](https://rubygems.org/gems/feen) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
189
239
 
190
240
  ## About Sashité
191
241
 
192
- This [gem](https://rubygems.org/gems/feen) is maintained by [Sashité](https://sashite.com/).
193
-
194
- With some [lines of code](https://github.com/sashite/), let's share the beauty of Chinese, Japanese and Western cultures through the game of chess!
242
+ This project is maintained by [Sashité](https://sashite.com/) - a project dedicated to promoting chess variants and sharing the beauty of Chinese, Japanese, and Western chess cultures.
@@ -2,90 +2,65 @@
2
2
 
3
3
  module Feen
4
4
  module Dumper
5
- # Handles conversion of games turn data structure to FEEN notation string
5
+ # Handles conversion of games turn data to FEEN notation string
6
6
  module GamesTurn
7
7
  ERRORS = {
8
- missing_key: "Missing required key in games_turn: %s",
9
- invalid_type: "Invalid type for games_turn[%s]: expected String, got %s",
10
- empty_string: "Empty string for games_turn[%s]",
11
- casing_requirement: "One game must be uppercase and the other lowercase",
12
- invalid_chars: "Game identifiers must contain only alphabetic characters (a-z, A-Z)"
8
+ type: "%s must be a String, got %s",
9
+ empty: "%s cannot be empty",
10
+ mixed: "%s has mixed case: %s",
11
+ casing: "One variant must be uppercase and the other lowercase",
12
+ chars: "Variant identifiers must contain only alphabetic characters (a-z, A-Z)"
13
13
  }.freeze
14
14
 
15
- REQUIRED_KEYS = %i[active_player inactive_player].freeze
16
-
17
- # Converts the internal games turn representation to a FEEN string
15
+ # Converts the active and inactive variant identifiers to a FEEN-formatted games turn string
18
16
  #
19
- # @param games_turn [Hash] Hash containing game turn information
17
+ # @param active_variant [String] Identifier for the player to move and their game variant
18
+ # @param inactive_variant [String] Identifier for the opponent and their game variant
20
19
  # @return [String] FEEN-formatted games turn string
21
- def self.dump(games_turn)
22
- validate_games_turn(games_turn)
23
-
24
- # Format is <active_player>/<inactive_player>
25
- "#{games_turn[:active_player]}/#{games_turn[:inactive_player]}"
26
- end
27
-
28
- # Validates the games turn data structure
29
- #
30
- # @param games_turn [Hash] The games turn data to validate
31
- # @raise [ArgumentError] If the games turn data is invalid
32
- # @return [Boolean] true if the validation passes
33
- def self.validate_games_turn(games_turn)
34
- validate_structure(games_turn)
35
- validate_casing(games_turn)
36
- validate_character_set(games_turn)
37
-
38
- true
20
+ def self.dump(active_variant, inactive_variant)
21
+ validate_variants(active_variant, inactive_variant)
22
+ "#{active_variant}/#{inactive_variant}"
39
23
  end
40
24
 
41
- # Validates the basic structure of games_turn
25
+ # Validates the game variant identifiers
42
26
  #
43
- # @param games_turn [Hash] The games turn data to validate
44
- # @raise [ArgumentError] If the structure is invalid
27
+ # @param active [String] The active player's variant identifier
28
+ # @param inactive [String] The inactive player's variant identifier
29
+ # @raise [ArgumentError] If the variant identifiers are invalid
45
30
  # @return [void]
46
- private_class_method def self.validate_structure(games_turn)
47
- REQUIRED_KEYS.each do |key|
48
- raise ArgumentError, format(ERRORS[:missing_key], key) unless games_turn.key?(key)
49
-
50
- unless games_turn[key].is_a?(String)
51
- raise ArgumentError, format(ERRORS[:invalid_type], key, games_turn[key].class)
52
- end
53
-
54
- raise ArgumentError, format(ERRORS[:empty_string], key) if games_turn[key].empty?
31
+ private_class_method def self.validate_variants(active, inactive)
32
+ # Validate basic type and presence
33
+ [["Active variant", active], ["Inactive variant", inactive]].each do |name, variant|
34
+ raise ArgumentError, format(ERRORS[:type], name, variant.class) unless variant.is_a?(String)
35
+ raise ArgumentError, format(ERRORS[:empty], name) if variant.empty?
36
+ raise ArgumentError, ERRORS[:chars] unless variant.match?(/\A[a-zA-Z]+\z/)
55
37
  end
56
- end
57
38
 
58
- # Validates the casing requirement (one uppercase, one lowercase)
59
- #
60
- # @param games_turn [Hash] The games turn data to validate
61
- # @raise [ArgumentError] If the casing requirement is not met
62
- # @return [void]
63
- private_class_method def self.validate_casing(games_turn)
64
- active_has_uppercase = games_turn[:active_player].match?(/[A-Z]/)
65
- inactive_has_uppercase = games_turn[:inactive_player].match?(/[A-Z]/)
39
+ # Validate casing (one must be uppercase, one must be lowercase)
40
+ active_uppercase = active == active.upcase && active != active.downcase
41
+ inactive_uppercase = inactive == inactive.upcase && inactive != inactive.downcase
66
42
 
67
- # Ensure exactly one has uppercase
68
- raise ArgumentError, ERRORS[:casing_requirement] if active_has_uppercase == inactive_has_uppercase
43
+ # If both have the same casing (both uppercase or both lowercase), raise error
44
+ raise ArgumentError, ERRORS[:casing] if active_uppercase == inactive_uppercase
69
45
 
70
- # Check that uppercase game is all caps and lowercase game has no caps
71
- if active_has_uppercase && games_turn[:active_player].match?(/[a-z]/)
72
- raise ArgumentError, "Active game has mixed case: #{games_turn[:active_player]}"
46
+ # Check for mixed case (must be all uppercase or all lowercase)
47
+ if active_uppercase && active != active.upcase
48
+ raise ArgumentError, format(ERRORS[:mixed], "Active variant", active)
73
49
  end
74
50
 
75
- return unless inactive_has_uppercase && games_turn[:inactive_player].match?(/[a-z]/)
51
+ if inactive_uppercase && inactive != inactive.upcase
52
+ raise ArgumentError, format(ERRORS[:mixed], "Inactive variant", inactive)
53
+ end
76
54
 
77
- raise ArgumentError, "Inactive game has mixed case: #{games_turn[:inactive_player]}"
78
- end
55
+ if !active_uppercase && active != active.downcase
56
+ raise ArgumentError, format(ERRORS[:mixed], "Active variant", active)
57
+ end
79
58
 
80
- # Validates that identifiers only contain allowed characters
81
- #
82
- # @param games_turn [Hash] The games turn data to validate
83
- # @raise [ArgumentError] If invalid characters are present
84
- # @return [void]
85
- private_class_method def self.validate_character_set(games_turn)
86
- REQUIRED_KEYS.each do |key|
87
- raise ArgumentError, ERRORS[:invalid_chars] unless games_turn[key].match?(/\A[a-zA-Z]+\z/)
59
+ if !inactive_uppercase && inactive != inactive.downcase
60
+ raise ArgumentError, format(ERRORS[:mixed], "Inactive variant", inactive)
88
61
  end
62
+
63
+ true
89
64
  end
90
65
  end
91
66
  end