sashite-ggn 0.5.0 → 0.7.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.
@@ -6,8 +6,11 @@ module Sashite
6
6
  #
7
7
  # This schema defines the structure and constraints for GGN documents,
8
8
  # which describe pseudo-legal moves in abstract strategy board games.
9
- # GGN is rule-agnostic and focuses on basic movement constraints rather
10
- # than game-specific legality (e.g., check, ko, repetition).
9
+ # GGN is rule-agnostic and focuses exclusively on board-to-board transformations:
10
+ # pieces moving, capturing, or transforming on the game board.
11
+ #
12
+ # The schema has been updated to reflect GGN's focus on board transformations only.
13
+ # Hand management, piece drops, and captures-to-hand are outside the scope of GGN.
11
14
  #
12
15
  # @example Basic GGN document structure
13
16
  # {
@@ -23,15 +26,28 @@ module Sashite
23
26
  # }
24
27
  # }
25
28
  #
26
- # @example Complex move with capture and piece gain
29
+ # @example Complex move with multiple conditions
27
30
  # {
28
- # "OGI:P": {
29
- # "e4": {
30
- # "e5": [
31
+ # "CHESS:P": {
32
+ # "d5": {
33
+ # "e6": [
31
34
  # {
32
- # "require": { "e5": "enemy" },
33
- # "perform": { "e4": null, "e5": "OGI:P" },
34
- # "gain": "OGI:P"
35
+ # "require": { "e5": "chess:p", "e6": "empty" },
36
+ # "perform": { "d5": null, "e5": null, "e6": "CHESS:P" }
37
+ # }
38
+ # ]
39
+ # }
40
+ # }
41
+ # }
42
+ #
43
+ # @example Multi-square move (castling)
44
+ # {
45
+ # "CHESS:K": {
46
+ # "e1": {
47
+ # "g1": [
48
+ # {
49
+ # "require": { "f1": "empty", "g1": "empty", "h1": "CHESS:R" },
50
+ # "perform": { "e1": null, "f1": "CHESS:R", "g1": "CHESS:K", "h1": null }
35
51
  # }
36
52
  # ]
37
53
  # }
@@ -45,7 +61,7 @@ module Sashite
45
61
  "$schema": "https://json-schema.org/draft/2020-12/schema",
46
62
  "$id": "https://sashite.dev/schemas/ggn/1.0.0/schema.json",
47
63
  "title": "General Gameplay Notation (GGN)",
48
- "description": "JSON Schema for pseudo-legal moves in abstract board games using the GGN format.",
64
+ "description": "JSON Schema for pseudo-legal moves in abstract board games using the GGN format. GGN focuses exclusively on board-to-board transformations.",
49
65
  "type": "object",
50
66
 
51
67
  # Optional schema reference property
@@ -66,82 +82,85 @@ module Sashite
66
82
  "type": "object",
67
83
  "minProperties": 1,
68
84
 
69
- # Source squares: where the piece starts (or "*" for drops)
70
- "additionalProperties": {
71
- "type": "object",
72
- "minProperties": 1,
85
+ # Source squares: where the piece starts (regular board squares only)
86
+ "patternProperties": {
87
+ ".+": {
88
+ "type": "object",
89
+ "minProperties": 1,
73
90
 
74
- # Destination squares: where the piece can move to
75
- "additionalProperties": {
76
- "type": "array",
77
- "minItems": 0,
78
-
79
- # Array of conditional transitions for this source->destination pair
80
- "items": {
81
- "type": "object",
82
- "properties": {
83
- # Conditions that MUST be satisfied before the move (logical AND)
84
- "require": {
85
- "type": "object",
86
- "minProperties": 1,
87
- "additionalProperties": {
88
- "type": "string",
89
- # Occupation states: "empty", "enemy", or exact GAN identifier
90
- "pattern": "^empty$|^enemy$|([A-Z]+:[-+]?[A-Z][']?|[a-z]+:[-+]?[a-z][']?)$"
91
- }
92
- },
91
+ # Destination squares: where the piece can move to (regular board squares only)
92
+ "patternProperties": {
93
+ ".+": {
94
+ "type": "array",
95
+ "minItems": 1,
93
96
 
94
- # Conditions that MUST NOT be satisfied before the move (logical OR)
95
- "prevent": {
97
+ # Array of conditional transitions for this source->destination pair
98
+ "items": {
96
99
  "type": "object",
97
- "minProperties": 1,
98
- "additionalProperties": {
99
- "type": "string",
100
- # Same occupation states as require
101
- "pattern": "^empty$|^enemy$|([A-Z]+:[-+]?[A-Z][']?|[a-z]+:[-+]?[a-z][']?)$"
102
- }
103
- },
100
+ "properties": {
101
+ # Conditions that MUST be satisfied before the move (logical AND)
102
+ "require": {
103
+ "type": "object",
104
+ "minProperties": 1,
105
+ "patternProperties": {
106
+ ".+": {
107
+ "type": "string",
108
+ # Occupation states: "empty", "enemy", or exact GAN identifier
109
+ "pattern": "^(empty|enemy|[A-Z]+:[-+]?[A-Z][']?|[a-z]+:[-+]?[a-z][']?)$"
110
+ }
111
+ },
112
+ "additionalProperties": false
113
+ },
104
114
 
105
- # Board state changes after the move (REQUIRED field)
106
- "perform": {
107
- "type": "object",
108
- "minProperties": 1,
109
- "additionalProperties": {
110
- "anyOf": [
111
- {
112
- # Square contains a piece (GAN identifier)
113
- "type": "string",
114
- "pattern": "^([A-Z]+:[-+]?[A-Z][']?|[a-z]+:[-+]?[a-z][']?)$"
115
+ # Conditions that MUST NOT be satisfied before the move (logical OR)
116
+ "prevent": {
117
+ "type": "object",
118
+ "minProperties": 1,
119
+ "patternProperties": {
120
+ ".+": {
121
+ "type": "string",
122
+ # Same occupation states as require
123
+ "pattern": "^(empty|enemy|[A-Z]+:[-+]?[A-Z][']?|[a-z]+:[-+]?[a-z][']?)$"
124
+ }
115
125
  },
116
- {
117
- # Square becomes empty (null)
118
- "type": "null"
119
- }
120
- ]
121
- }
122
- },
126
+ "additionalProperties": false
127
+ },
123
128
 
124
- # Piece added to player's hand (base GAN only, no modifiers)
125
- "gain": {
126
- "type": ["string", "null"],
127
- # Base form GAN pattern (no prefixes/suffixes for hand pieces)
128
- "pattern": "^([A-Z]+:[A-Z]|[a-z]+:[a-z])$"
129
- },
129
+ # Board state changes after the move (REQUIRED field)
130
+ # This is the core of GGN: describing board transformations
131
+ "perform": {
132
+ "type": "object",
133
+ "minProperties": 1,
134
+ "patternProperties": {
135
+ ".+": {
136
+ "anyOf": [
137
+ {
138
+ # Square contains a piece (GAN identifier)
139
+ "type": "string",
140
+ "pattern": "^([A-Z]+:[-+]?[A-Z][']?|[a-z]+:[-+]?[a-z][']?)$"
141
+ },
142
+ {
143
+ # Square becomes empty (null)
144
+ "type": "null"
145
+ }
146
+ ]
147
+ }
148
+ },
149
+ "additionalProperties": false
150
+ }
151
+ },
130
152
 
131
- # Piece removed from player's hand (base GAN only, no modifiers)
132
- "drop": {
133
- "type": ["string", "null"],
134
- # Base form GAN pattern (no prefixes/suffixes for hand pieces)
135
- "pattern": "^([A-Z]+:[A-Z]|[a-z]+:[a-z])$"
153
+ # Only "perform" is mandatory; "require" and "prevent" are optional
154
+ # NOTE: "gain" and "drop" fields are no longer supported in GGN
155
+ "required": ["perform"],
156
+ "additionalProperties": false
136
157
  }
137
- },
138
-
139
- # Only "perform" is mandatory; other fields are optional
140
- "required": ["perform"],
141
- "additionalProperties": false
142
- }
158
+ }
159
+ },
160
+ "additionalProperties": false
143
161
  }
144
- }
162
+ },
163
+ "additionalProperties": false
145
164
  }
146
165
  },
147
166
 
@@ -8,22 +8,47 @@ module Sashite
8
8
  # the JSON Schema, contain malformed data, or encounter processing errors
9
9
  # during parsing and evaluation of pseudo-legal moves.
10
10
  #
11
+ # Since GGN focuses exclusively on board-to-board transformations, validation
12
+ # errors typically relate to:
13
+ # - Invalid board position representations
14
+ # - Malformed GAN identifiers or square labels
15
+ # - Logical contradictions in require/prevent conditions
16
+ # - Missing or invalid perform actions
17
+ #
11
18
  # Common scenarios that raise ValidationError:
12
19
  # - Invalid JSON syntax in GGN files
13
20
  # - Schema validation failures (missing required fields, invalid patterns)
14
21
  # - File system errors (file not found, permission denied)
15
22
  # - Malformed GAN identifiers or square labels
16
23
  # - Logical contradictions in require/prevent conditions
24
+ # - Invalid board transformation specifications
17
25
  #
18
26
  # @example Handling validation errors during file loading
19
27
  # begin
20
- # piece = Sashite::Ggn.load_file('invalid_moves.json')
28
+ # piece_data = Sashite::Ggn.load_file('invalid_moves.json')
21
29
  # rescue Sashite::Ggn::ValidationError => e
22
30
  # puts "GGN validation failed: #{e.message}"
23
31
  # # Handle the error appropriately
24
32
  # end
25
33
  #
34
+ # @example Handling validation errors during move evaluation
35
+ # begin
36
+ # transitions = engine.where(board_state, 'CHESS')
37
+ # rescue Sashite::Ggn::ValidationError => e
38
+ # puts "Move evaluation failed: #{e.message}"
39
+ # # Handle invalid board state or parameters
40
+ # end
41
+ #
42
+ # @example Handling schema validation errors
43
+ # begin
44
+ # Sashite::Ggn.validate!(ggn_data)
45
+ # rescue Sashite::Ggn::ValidationError => e
46
+ # puts "Schema validation failed: #{e.message}"
47
+ # # The data doesn't conform to GGN specification
48
+ # end
49
+ #
26
50
  # @see Sashite::Ggn.load_file Main method that can raise this exception
51
+ # @see Sashite::Ggn.validate! Schema validation method
27
52
  # @see Sashite::Ggn::Schema JSON Schema used for validation
28
53
  class ValidationError < ::StandardError
29
54
  end
data/lib/sashite/ggn.rb CHANGED
@@ -12,20 +12,30 @@ module Sashite
12
12
  # General Gameplay Notation (GGN) module for parsing, validating, and working with
13
13
  # JSON documents that describe pseudo-legal moves in abstract strategy board games.
14
14
  #
15
- # GGN is a rule-agnostic format that focuses on basic movement constraints rather
16
- # than game-specific legality rules. It answers the fundamental question: "Can this
17
- # piece, currently on this square, reach that square?" while remaining neutral about
18
- # higher-level game rules like check, ko, repetition, or castling paths.
15
+ # GGN is a rule-agnostic format that focuses exclusively on board-to-board transformations.
16
+ # It answers the fundamental question: "Can this piece, currently on this square, reach
17
+ # that square?" while remaining neutral about higher-level game rules like check, ko,
18
+ # repetition, or castling paths.
19
19
  #
20
20
  # = Key Features
21
21
  #
22
22
  # - **Rule-agnostic**: Works with any abstract strategy board game
23
+ # - **Board-focused**: Describes only board transformations, no hand management
23
24
  # - **Pseudo-legal** focus: Describes basic movement constraints only
24
25
  # - **JSON-based**: Structured, machine-readable format
25
- # - **Validation** support: Built-in schema validation
26
+ # - **Validation** support: Built-in schema validation and logical consistency checks
26
27
  # - **Performance** optimized: Optional validation for large datasets
27
28
  # - **Cross-game** compatible: Supports hybrid games and variants
28
29
  #
30
+ # = Validation Levels
31
+ #
32
+ # When `validate: true` (default), performs:
33
+ # - JSON Schema validation against GGN specification
34
+ # - Logical contradiction detection in require/prevent conditions
35
+ # - Implicit requirement duplication detection
36
+ #
37
+ # When `validate: false`, skips all validations for maximum performance.
38
+ #
29
39
  # = Related Specifications
30
40
  #
31
41
  # GGN works alongside other Sashité specifications:
@@ -45,16 +55,18 @@ module Sashite
45
55
  # 1. Reads the JSON file from the filesystem with proper encoding
46
56
  # 2. Parses the JSON content into a Ruby Hash with error handling
47
57
  # 3. Optionally validates the structure against the GGN JSON Schema
48
- # 4. Creates and returns a Ruleset instance for querying moves
58
+ # 4. Optionally performs logical consistency validation
59
+ # 5. Creates and returns a Ruleset instance for querying moves
49
60
  #
50
61
  # @param filepath [String, Pathname] Path to the GGN JSON file to load.
51
62
  # Supports both relative and absolute paths.
52
- # @param validate [Boolean] Whether to validate against GGN schema (default: true).
53
- # Set to false to skip validation for improved performance on large documents.
63
+ # @param validate [Boolean] Whether to perform all validations (default: true).
64
+ # When false, skips JSON schema validation AND internal logical validations
65
+ # for maximum performance.
54
66
  # @param encoding [String] File encoding to use when reading (default: 'UTF-8').
55
67
  # Most GGN files should use UTF-8 encoding.
56
68
  #
57
- # @return [Ruleset] A Ruleset instance containing the parsed and validated GGN data.
69
+ # @return [Ruleset] A Ruleset instance containing the parsed GGN data.
58
70
  # Use this instance to query pseudo-legal moves for specific pieces and positions.
59
71
  #
60
72
  # @raise [ValidationError] If any of the following conditions occur:
@@ -62,6 +74,7 @@ module Sashite
62
74
  # - File contains invalid JSON syntax
63
75
  # - File permissions prevent reading
64
76
  # - When validation is enabled: data doesn't conform to GGN schema
77
+ # - When validation is enabled: logical contradictions or implicit duplications found
65
78
  #
66
79
  # @example Loading a chess piece definition with full validation
67
80
  # begin
@@ -80,15 +93,15 @@ module Sashite
80
93
  # engine = destinations.to('e2')
81
94
  #
82
95
  # board_state = { 'e1' => 'CHESS:K', 'e2' => nil }
83
- # result = engine.evaluate(board_state, {}, 'CHESS')
84
- # puts "King can move from e1 to e2" if result
96
+ # transitions = engine.where(board_state, 'CHESS')
97
+ # puts "King can move from e1 to e2" if transitions.any?
85
98
  # rescue Sashite::Ggn::ValidationError => e
86
99
  # puts "Failed to process move: #{e.message}"
87
100
  # end
88
101
  #
89
102
  # @example Loading large datasets without validation for performance
90
103
  # begin
91
- # # Skip validation for large files to improve loading performance
104
+ # # Skip all validations for large files to improve loading performance
92
105
  # large_dataset = Sashite::Ggn.load_file('data/all_variants.json', validate: false)
93
106
  # puts "Loaded GGN data without validation"
94
107
  # rescue Sashite::Ggn::ValidationError => e
@@ -122,8 +135,8 @@ module Sashite
122
135
  # Validate against GGN schema if requested
123
136
  validate_schema(data, file_path) if validate
124
137
 
125
- # Create and return Ruleset instance
126
- Ruleset.new(data)
138
+ # Create and return Ruleset instance with validation option
139
+ Ruleset.new(data, validate: validate)
127
140
  end
128
141
 
129
142
  # Loads GGN data directly from a JSON string.
@@ -132,7 +145,8 @@ module Sashite
132
145
  # database, API response, or embedded in your application) rather than a file.
133
146
  #
134
147
  # @param json_string [String] JSON string containing GGN data
135
- # @param validate [Boolean] Whether to validate against GGN schema (default: true)
148
+ # @param validate [Boolean] Whether to perform all validations (default: true).
149
+ # When false, skips JSON schema validation AND internal logical validations.
136
150
  #
137
151
  # @return [Ruleset] A Ruleset instance containing the parsed GGN data
138
152
  #
@@ -163,8 +177,8 @@ module Sashite
163
177
  # Validate against GGN schema if requested
164
178
  validate_schema(data, "<string>") if validate
165
179
 
166
- # Create and return Ruleset instance
167
- Ruleset.new(data)
180
+ # Create and return Ruleset instance with validation option
181
+ Ruleset.new(data, validate: validate)
168
182
  end
169
183
 
170
184
  # Loads GGN data from a Ruby Hash.
@@ -173,7 +187,8 @@ module Sashite
173
187
  # and want to create a GGN Ruleset instance with optional validation.
174
188
  #
175
189
  # @param data [Hash] Ruby Hash containing GGN data structure
176
- # @param validate [Boolean] Whether to validate against GGN schema (default: true)
190
+ # @param validate [Boolean] Whether to perform all validations (default: true).
191
+ # When false, skips JSON schema validation AND internal logical validations.
177
192
  #
178
193
  # @return [Ruleset] A Ruleset instance containing the GGN data
179
194
  #
@@ -181,16 +196,16 @@ module Sashite
181
196
  #
182
197
  # @example Creating from existing Hash data
183
198
  # ggn_data = {
184
- # "SHOGI:K" => {
185
- # "5i" => {
186
- # "4i" => [{ "require" => { "4i" => "empty" }, "perform" => { "5i" => nil, "4i" => "SHOGI:K" } }],
187
- # "6i" => [{ "require" => { "6i" => "empty" }, "perform" => { "5i" => nil, "6i" => "SHOGI:K" } }]
199
+ # "CHESS:K" => {
200
+ # "e1" => {
201
+ # "e2" => [{ "require" => { "e2" => "empty" }, "perform" => { "e1" => nil, "e2" => "CHESS:K" } }],
202
+ # "f1" => [{ "require" => { "f1" => "empty" }, "perform" => { "e1" => nil, "f1" => "CHESS:K" } }]
188
203
  # }
189
204
  # }
190
205
  # }
191
206
  #
192
207
  # piece_data = Sashite::Ggn.load_hash(ggn_data)
193
- # shogi_king = piece_data.select('SHOGI:K')
208
+ # chess_king = piece_data.select('CHESS:K')
194
209
  def load_hash(data, validate: true)
195
210
  unless data.is_a?(Hash)
196
211
  raise ValidationError, "Expected Hash, got #{data.class}"
@@ -199,14 +214,16 @@ module Sashite
199
214
  # Validate against GGN schema if requested
200
215
  validate_schema(data, "<hash>") if validate
201
216
 
202
- # Create and return Ruleset instance
203
- Ruleset.new(data)
217
+ # Create and return Ruleset instance with validation option
218
+ Ruleset.new(data, validate: validate)
204
219
  end
205
220
 
206
221
  # Validates a data structure against the GGN JSON Schema.
207
222
  #
208
223
  # This method can be used independently to validate GGN data without
209
224
  # creating a Ruleset instance. Useful for pre-validation or testing.
225
+ # Note: This only performs JSON Schema validation, not the internal
226
+ # logical consistency checks that Ruleset.new performs.
210
227
  #
211
228
  # @param data [Hash] The data structure to validate
212
229
  # @param context [String] Context information for error messages (default: "<data>")
@@ -229,6 +246,9 @@ module Sashite
229
246
 
230
247
  # Checks if a data structure is valid GGN format.
231
248
  #
249
+ # Note: This only performs JSON Schema validation, not the internal
250
+ # logical consistency checks that Ruleset.new performs.
251
+ #
232
252
  # @param data [Hash] The data structure to validate
233
253
  #
234
254
  # @return [Boolean] true if valid, false otherwise
@@ -246,6 +266,9 @@ module Sashite
246
266
 
247
267
  # Returns detailed validation errors for a data structure.
248
268
  #
269
+ # Note: This only performs JSON Schema validation, not the internal
270
+ # logical consistency checks that Ruleset.new performs.
271
+ #
249
272
  # @param data [Hash] The data structure to validate
250
273
  #
251
274
  # @return [Array<String>] Array of validation error messages (empty if valid)
data/lib/sashite-ggn.rb CHANGED
@@ -6,6 +6,10 @@
6
6
  # specification, which is a rule-agnostic, JSON-based format for describing pseudo-legal
7
7
  # moves in abstract strategy board games.
8
8
  #
9
+ # GGN focuses exclusively on board-to-board transformations: pieces moving, capturing,
10
+ # or transforming on the game board. Hand management, drops, and captures-to-hand are
11
+ # outside the scope of this specification.
12
+ #
9
13
  # GGN works alongside other Sashité specifications:
10
14
  # - GAN (General Actor Notation): Unique piece identifiers
11
15
  # - FEEN (Forsyth-Edwards Enhanced Notation): Board position representation
@@ -30,59 +34,56 @@
30
34
  # "e4" => nil # Empty square
31
35
  # }
32
36
  #
33
- # result = engine.evaluate(board_state, {}, "CHESS")
37
+ # transitions = engine.where(board_state, "CHESS")
34
38
  #
35
- # if result
39
+ # if transitions.any?
40
+ # transition = transitions.first
36
41
  # puts "Move is valid!"
37
- # puts "Board changes: #{result.diff}"
42
+ # puts "Board changes: #{transition.diff}"
38
43
  # # => { "e2" => nil, "e4" => "CHESS:P" }
39
- # puts "Piece gained: #{result.gain}" # => nil (no capture)
40
- # puts "Piece dropped: #{result.drop}" # => nil (not a drop move)
41
44
  # else
42
45
  # puts "Move is not valid under current conditions"
43
46
  # end
44
47
  #
45
- # @example Piece drops in Shogi
46
- # # Shogi allows captured pieces to be dropped back onto the board
47
- # piece_data = Sashite::Ggn.load_file("shogi_moves.json")
48
- # engine = piece_data.select("SHOGI:P").from("*").to("5e")
49
- #
50
- # # Player has captured pawns available
51
- # captures = { "SHOGI:P" => 2 }
48
+ # @example Piece promotion with multiple variants
49
+ # # Chess pawn promotion offers multiple choices
50
+ # piece_data = Sashite::Ggn.load_file("chess_moves.json")
51
+ # engine = piece_data.select("CHESS:P").from("e7").to("e8")
52
52
  #
53
- # # Current board state (5th file is clear of unpromoted pawns)
53
+ # # Board with pawn ready to promote
54
54
  # board_state = {
55
- # "5e" => nil, # Target square is empty
56
- # "5a" => nil, "5b" => nil, "5c" => nil, "5d" => nil,
57
- # "5f" => nil, "5g" => nil, "5h" => nil, "5i" => nil
55
+ # "e7" => "CHESS:P", # White pawn on 7th rank
56
+ # "e8" => nil # Empty promotion square
58
57
  # }
59
58
  #
60
- # result = engine.evaluate(board_state, captures, "SHOGI")
59
+ # transitions = engine.where(board_state, "CHESS")
61
60
  #
62
- # if result
63
- # puts "Pawn drop is valid!"
64
- # puts "Board changes: #{result.diff}" # => { "5e" => "SHOGI:P" }
65
- # puts "Piece dropped from hand: #{result.drop}" # => "SHOGI:P"
61
+ # transitions.each_with_index do |transition, i|
62
+ # promoted_piece = transition.diff["e8"]
63
+ # puts "Promotion choice #{i + 1}: #{promoted_piece}"
66
64
  # end
65
+ # # Output: CHESS:Q, CHESS:R, CHESS:B, CHESS:N
67
66
  #
68
- # @example Captures with piece promotion
69
- # # A chess pawn capturing and promoting to queen
67
+ # @example Complex multi-square moves like castling
68
+ # # Castling involves both king and rook movement
70
69
  # piece_data = Sashite::Ggn.load_file("chess_moves.json")
71
- # engine = piece_data.select("CHESS:P").from("g7").to("h8")
70
+ # engine = piece_data.select("CHESS:K").from("e1").to("g1")
72
71
  #
73
- # # Board with enemy piece on h8
72
+ # # Board state allowing kingside castling
74
73
  # board_state = {
75
- # "g7" => "CHESS:P", # Our pawn ready to promote
76
- # "h8" => "chess:r" # Enemy rook (lowercase = opponent)
74
+ # "e1" => "CHESS:K", # King on starting square
75
+ # "f1" => nil, # Empty square
76
+ # "g1" => nil, # Empty destination
77
+ # "h1" => "CHESS:R" # Rook on starting square
77
78
  # }
78
79
  #
79
- # result = engine.evaluate(board_state, {}, "CHESS")
80
+ # transitions = engine.where(board_state, "CHESS")
80
81
  #
81
- # if result
82
- # puts "Pawn promotes and captures!"
83
- # puts "Final position: #{result.diff}"
84
- # # => { "g7" => nil, "h8" => "CHESS:Q" }
85
- # puts "Captured piece: #{result.gain}" # => nil (no capture _in hand_)
82
+ # if transitions.any?
83
+ # transition = transitions.first
84
+ # puts "Castling is possible!"
85
+ # puts "Final position: #{transition.diff}"
86
+ # # => { "e1" => nil, "f1" => "CHESS:R", "g1" => "CHESS:K", "h1" => nil }
86
87
  # end
87
88
  #
88
89
  # @example Loading GGN data from different sources
@@ -96,6 +97,19 @@
96
97
  # # From Hash
97
98
  # ggn_hash = { "CHESS:K" => { "e1" => { "e2" => [{ "perform" => { "e1" => nil, "e2" => "CHESS:K" } }] } } }
98
99
  # piece_data = Sashite::Ggn.load_hash(ggn_hash)
100
+ #
101
+ # @example Generating all possible moves
102
+ # # Get all pseudo-legal moves for the current position
103
+ # board_state = {
104
+ # "e1" => "CHESS:K", "d1" => "CHESS:Q", "a1" => "CHESS:R",
105
+ # "e2" => "CHESS:P", "d2" => "CHESS:P"
106
+ # }
107
+ #
108
+ # all_moves = piece_data.pseudo_legal_transitions(board_state, "CHESS")
109
+ #
110
+ # all_moves.each do |actor, origin, target, transitions|
111
+ # puts "#{actor}: #{origin} → #{target} (#{transitions.size} variants)"
112
+ # end
99
113
  module Sashite
100
114
  # Base namespace for all Sashité notation libraries.
101
115
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sashite-ggn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
@@ -24,10 +24,12 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: 2.4.0
26
26
  description: A Ruby implementation of the General Gameplay Notation (GGN) specification.
27
- GGN is a rule-agnostic, JSON-based format for describing pseudo-legal moves in abstract
28
- strategy board games. This library provides parsing, validation, and evaluation
29
- capabilities for GGN documents, enabling game engines to work with movement rules
30
- across different board games including Chess, Shogi, Xiangqi, and custom variants.
27
+ GGN is a rule-agnostic, JSON-based format for describing pseudo-legal board-to-board
28
+ transformations in abstract strategy board games. This library provides parsing,
29
+ validation, and evaluation capabilities for GGN documents, focusing exclusively
30
+ on piece movements, captures, and transformations on the game board. Features flexible
31
+ validation system for both safety and performance. Supports Chess, Shogi, Xiangqi,
32
+ and custom variants without hand management or piece drops.
31
33
  email: contact@cyril.email
32
34
  executables: []
33
35
  extensions: []
@@ -55,6 +57,9 @@ metadata:
55
57
  source_code_uri: https://github.com/sashite/ggn.rb
56
58
  specification_uri: https://sashite.dev/documents/ggn/1.0.0/
57
59
  rubygems_mfa_required: 'true'
60
+ keywords: board-game, chess, game, gameplay, json, makruk, notation, performance,
61
+ pseudo-legal-move, rule-agnostic, serialization, shogi, strategy, validation,
62
+ xiangqi
58
63
  rdoc_options: []
59
64
  require_paths:
60
65
  - lib
@@ -71,5 +76,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
76
  requirements: []
72
77
  rubygems_version: 3.6.9
73
78
  specification_version: 4
74
- summary: General Gameplay Notation (GGN) parser and validator for Ruby
79
+ summary: General Gameplay Notation (GGN) library for board-to-board game transformations
75
80
  test_files: []