sashite-pnn 2.0.0 → 3.1.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.
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sashite
4
+ module Pnn
5
+ # Represents a piece name in PNN (Piece Name Notation) format.
6
+ #
7
+ # PNN provides a canonical naming system for abstract strategy game pieces.
8
+ # Each name consists of an optional state modifier (+ or -) followed by a
9
+ # case-consistent alphabetic name and an optional terminal marker (^),
10
+ # encoding piece identity, player assignment, state, and terminal status
11
+ # in a human-readable format.
12
+ #
13
+ # All instances are immutable.
14
+ class Name
15
+ # PNN validation pattern matching the specification
16
+ PNN_PATTERN = /\A([+-]?)([A-Z]+|[a-z]+)(\^?)\z/
17
+
18
+ # Error messages
19
+ ERROR_INVALID_NAME = "Invalid PNN string: %s"
20
+
21
+ # @return [String] the canonical piece name
22
+ attr_reader :value
23
+
24
+ # Create a new piece name instance
25
+ #
26
+ # @param name [String, Symbol] the piece name (e.g., "KING", :queen, "+ROOK", "-pawn", "KING^")
27
+ # @raise [ArgumentError] if the name does not match PNN pattern
28
+ def initialize(name)
29
+ string_value = name.to_s
30
+ self.class.validate_format(string_value)
31
+
32
+ @value = string_value.freeze
33
+ @parsed = parse_components(string_value)
34
+ freeze
35
+ end
36
+
37
+ # Parse a PNN string into a Name object
38
+ #
39
+ # @param string [String] the PNN-formatted piece name
40
+ # @return [Name] a new Name instance
41
+ # @raise [ArgumentError] if the string is invalid
42
+ #
43
+ # @example
44
+ # Sashite::Pnn::Name.parse("KING") # => #<Pnn::Name value="KING">
45
+ # Sashite::Pnn::Name.parse("+queen") # => #<Pnn::Name value="+queen">
46
+ # Sashite::Pnn::Name.parse("KING^") # => #<Pnn::Name value="KING^">
47
+ # Sashite::Pnn::Name.parse("+ROOK^") # => #<Pnn::Name value="+ROOK^">
48
+ def self.parse(string)
49
+ new(string)
50
+ end
51
+
52
+ # Check whether the given string is a valid PNN name
53
+ #
54
+ # @param string [String] input string to validate
55
+ # @return [Boolean] true if valid, false otherwise
56
+ #
57
+ # @example
58
+ # Sashite::Pnn::Name.valid?("KING") # => true
59
+ # Sashite::Pnn::Name.valid?("King") # => false (mixed case)
60
+ # Sashite::Pnn::Name.valid?("+queen") # => true
61
+ # Sashite::Pnn::Name.valid?("KING^") # => true
62
+ # Sashite::Pnn::Name.valid?("KING1") # => false (contains digit)
63
+ def self.valid?(string)
64
+ string.is_a?(::String) && string.match?(PNN_PATTERN)
65
+ end
66
+
67
+ # Validate that the string is in proper PNN format
68
+ #
69
+ # @param str [String]
70
+ # @raise [ArgumentError] if invalid
71
+ def self.validate_format(str)
72
+ raise ::ArgumentError, format(ERROR_INVALID_NAME, str.inspect) unless str.match?(PNN_PATTERN)
73
+ end
74
+
75
+ # Returns the string representation of the name
76
+ #
77
+ # @return [String]
78
+ def to_s
79
+ value
80
+ end
81
+
82
+ # Returns the base name without state modifier or terminal marker
83
+ #
84
+ # @return [String] the piece name without + or - prefix and without ^ suffix
85
+ #
86
+ # @example
87
+ # Sashite::Pnn::Name.parse("KING").base_name # => "KING"
88
+ # Sashite::Pnn::Name.parse("+queen").base_name # => "queen"
89
+ # Sashite::Pnn::Name.parse("-ROOK").base_name # => "ROOK"
90
+ # Sashite::Pnn::Name.parse("KING^").base_name # => "KING"
91
+ # Sashite::Pnn::Name.parse("+ROOK^").base_name # => "ROOK"
92
+ def base_name
93
+ @parsed[:base_name]
94
+ end
95
+
96
+ # Check if the piece has enhanced state (+)
97
+ #
98
+ # @return [Boolean] true if enhanced, false otherwise
99
+ #
100
+ # @example
101
+ # Sashite::Pnn::Name.parse("+KING").enhanced? # => true
102
+ # Sashite::Pnn::Name.parse("KING").enhanced? # => false
103
+ # Sashite::Pnn::Name.parse("+KING^").enhanced? # => true
104
+ def enhanced?
105
+ @parsed[:state_modifier] == "+"
106
+ end
107
+
108
+ # Check if the piece has diminished state (-)
109
+ #
110
+ # @return [Boolean] true if diminished, false otherwise
111
+ #
112
+ # @example
113
+ # Sashite::Pnn::Name.parse("-pawn").diminished? # => true
114
+ # Sashite::Pnn::Name.parse("pawn").diminished? # => false
115
+ # Sashite::Pnn::Name.parse("-pawn^").diminished? # => true
116
+ def diminished?
117
+ @parsed[:state_modifier] == "-"
118
+ end
119
+
120
+ # Check if the piece has normal state (no modifier)
121
+ #
122
+ # @return [Boolean] true if normal, false otherwise
123
+ #
124
+ # @example
125
+ # Sashite::Pnn::Name.parse("KING").normal? # => true
126
+ # Sashite::Pnn::Name.parse("+KING").normal? # => false
127
+ # Sashite::Pnn::Name.parse("KING^").normal? # => true
128
+ def normal?
129
+ @parsed[:state_modifier].empty?
130
+ end
131
+
132
+ # Check if the piece is a terminal piece (has ^ marker)
133
+ #
134
+ # @return [Boolean] true if terminal, false otherwise
135
+ #
136
+ # @example
137
+ # Sashite::Pnn::Name.parse("KING^").terminal? # => true
138
+ # Sashite::Pnn::Name.parse("KING").terminal? # => false
139
+ # Sashite::Pnn::Name.parse("+KING^").terminal? # => true
140
+ # Sashite::Pnn::Name.parse("-pawn^").terminal? # => true
141
+ def terminal?
142
+ @parsed[:terminal_marker] == "^"
143
+ end
144
+
145
+ # Check if the piece belongs to the first player (uppercase)
146
+ #
147
+ # @return [Boolean] true if first player, false otherwise
148
+ #
149
+ # @example
150
+ # Sashite::Pnn::Name.parse("KING").first_player? # => true
151
+ # Sashite::Pnn::Name.parse("queen").first_player? # => false
152
+ # Sashite::Pnn::Name.parse("KING^").first_player? # => true
153
+ def first_player?
154
+ @parsed[:base_name] == @parsed[:base_name].upcase
155
+ end
156
+
157
+ # Check if the piece belongs to the second player (lowercase)
158
+ #
159
+ # @return [Boolean] true if second player, false otherwise
160
+ #
161
+ # @example
162
+ # Sashite::Pnn::Name.parse("king").second_player? # => true
163
+ # Sashite::Pnn::Name.parse("QUEEN").second_player? # => false
164
+ # Sashite::Pnn::Name.parse("king^").second_player? # => true
165
+ def second_player?
166
+ @parsed[:base_name] == @parsed[:base_name].downcase
167
+ end
168
+
169
+ # Check if another piece has the same base name (ignoring case, state, and terminal marker)
170
+ #
171
+ # @param other [Name] another piece name to compare
172
+ # @return [Boolean] true if same base name, false otherwise
173
+ #
174
+ # @example
175
+ # king = Sashite::Pnn::Name.parse("KING")
176
+ # queen = Sashite::Pnn::Name.parse("king")
177
+ # enhanced = Sashite::Pnn::Name.parse("+KING")
178
+ # terminal = Sashite::Pnn::Name.parse("KING^")
179
+ #
180
+ # king.same_base_name?(queen) # => true (same piece, different player)
181
+ # king.same_base_name?(enhanced) # => true (same piece, different state)
182
+ # king.same_base_name?(terminal) # => true (same piece, terminal marker)
183
+ def same_base_name?(other)
184
+ return false unless other.is_a?(self.class)
185
+
186
+ base_name.downcase == other.base_name.downcase
187
+ end
188
+
189
+ # Equality based on normalized string value
190
+ #
191
+ # @param other [Object]
192
+ # @return [Boolean]
193
+ def ==(other)
194
+ other.is_a?(self.class) && value == other.value
195
+ end
196
+
197
+ # Required for correct Set/hash behavior
198
+ alias eql? ==
199
+
200
+ # Hash based on class and value
201
+ #
202
+ # @return [Integer]
203
+ def hash
204
+ [self.class, value].hash
205
+ end
206
+
207
+ private
208
+
209
+ # Parse the components of a PNN string
210
+ #
211
+ # @param str [String] the PNN string
212
+ # @return [Hash] parsed components
213
+ def parse_components(str)
214
+ match = str.match(PNN_PATTERN)
215
+ {
216
+ state_modifier: match[1],
217
+ base_name: match[2],
218
+ terminal_marker: match[3]
219
+ }
220
+ end
221
+ end
222
+ end
223
+ end
data/lib/sashite/pnn.rb CHANGED
@@ -1,69 +1,66 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "pnn/piece"
3
+ require_relative "pnn/name"
4
4
 
5
5
  module Sashite
6
6
  # PNN (Piece Name Notation) implementation for Ruby
7
7
  #
8
- # Provides style-aware ASCII-based format for representing pieces in abstract strategy board games.
9
- # PNN extends PIN by adding derivation markers that distinguish pieces by their style origin,
10
- # enabling cross-style game scenarios and piece origin tracking.
8
+ # Provides a formal naming system for identifying pieces in abstract strategy board games.
9
+ # PNN uses canonical, human-readable ASCII names with optional state modifiers and case
10
+ # encoding for player assignment. It supports unlimited unique piece identifiers with
11
+ # consistent, rule-agnostic semantics.
11
12
  #
12
- # Format: [<state>]<letter>[<derivation>]
13
- # - State modifier: "+" (enhanced), "-" (diminished), or none (normal)
14
- # - Letter: A-Z (first player), a-z (second player)
15
- # - Derivation marker: "'" (foreign style), or none (native style)
13
+ # Format: [<state-modifier>]<case-consistent-name>
16
14
  #
17
15
  # Examples:
18
- # "K" - First player king (native style, normal state)
19
- # "k'" - Second player king (foreign style, normal state)
20
- # "+R'" - First player rook (foreign style, enhanced state)
21
- # "-p" - Second player pawn (native style, diminished state)
16
+ # "KING" - First player king (normal state)
17
+ # "queen" - Second player queen (normal state)
18
+ # "+ROOK" - First player rook (enhanced state)
19
+ # "-pawn" - Second player pawn (diminished state)
20
+ # "BISHOP" - First player bishop (normal state)
22
21
  #
23
22
  # See: https://sashite.dev/specs/pnn/1.0.0/
24
23
  module Pnn
25
- # Check if a string is a valid PNN notation
24
+ # Check if a string is valid PNN notation
26
25
  #
27
- # @param pnn_string [String] The string to validate
26
+ # @param pnn_string [String] the string to validate
28
27
  # @return [Boolean] true if valid PNN, false otherwise
29
28
  #
30
- # @example
31
- # Sashite::Pnn.valid?("K") # => true
32
- # Sashite::Pnn.valid?("+R'") # => true
33
- # Sashite::Pnn.valid?("-p") # => true
34
- # Sashite::Pnn.valid?("KK") # => false
35
- # Sashite::Pnn.valid?("++K") # => false
29
+ # @example Validate PNN strings
30
+ # Sashite::Pnn.valid?("KING") # => true
31
+ # Sashite::Pnn.valid?("queen") # => true
32
+ # Sashite::Pnn.valid?("+ROOK") # => true
33
+ # Sashite::Pnn.valid?("-pawn") # => true
34
+ # Sashite::Pnn.valid?("King") # => false (mixed case)
35
+ # Sashite::Pnn.valid?("KING1") # => false (contains digit)
36
36
  def self.valid?(pnn_string)
37
- Piece.valid?(pnn_string)
37
+ Name.valid?(pnn_string)
38
38
  end
39
39
 
40
- # Parse a PNN string into a Piece object
40
+ # Parse a PNN string into a Name object
41
41
  #
42
- # @param pnn_string [String] PNN notation string
43
- # @return [Pnn::Piece] new piece instance
44
- # @raise [ArgumentError] if the PNN string is invalid
45
- # @example
46
- # Sashite::Pnn.parse("K") # => #<Pnn::Piece type=:K side=:first state=:normal native=true>
47
- # Sashite::Pnn.parse("+R'") # => #<Pnn::Piece type=:R side=:first state=:enhanced native=false>
48
- # Sashite::Pnn.parse("-p") # => #<Pnn::Piece type=:P side=:second state=:diminished native=true>
42
+ # @param pnn_string [String] the piece name string
43
+ # @return [Pnn::Name] a parsed name object
44
+ # @raise [ArgumentError] if the name is invalid
45
+ #
46
+ # @example Parse valid PNN names
47
+ # Sashite::Pnn.parse("KING") # => #<Pnn::Name value="KING">
48
+ # Sashite::Pnn.parse("+queen") # => #<Pnn::Name value="+queen">
49
49
  def self.parse(pnn_string)
50
- Piece.parse(pnn_string)
50
+ Name.parse(pnn_string)
51
51
  end
52
52
 
53
- # Create a new piece instance
53
+ # Create a new Name instance directly
54
+ #
55
+ # @param value [String, Symbol] piece name to construct
56
+ # @return [Pnn::Name] new name instance
57
+ # @raise [ArgumentError] if name format is invalid
54
58
  #
55
- # @param type [Symbol] piece type (:A to :Z)
56
- # @param side [Symbol] player side (:first or :second)
57
- # @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
58
- # @param native [Boolean] style derivation (true for native, false for foreign)
59
- # @return [Pnn::Piece] new piece instance
60
- # @raise [ArgumentError] if parameters are invalid
61
59
  # @example
62
- # Sashite::Pnn.piece(:K, :first, :normal, true) # => #<Pnn::Piece type=:K side=:first state=:normal native=true>
63
- # Sashite::Pnn.piece(:R, :first, :enhanced, false) # => #<Pnn::Piece type=:R side=:first state=:enhanced native=false>
64
- # Sashite::Pnn.piece(:P, :second, :diminished, true) # => #<Pnn::Piece type=:P side=:second state=:diminished native=true>
65
- def self.piece(type, side, state = Sashite::Pin::Piece::NORMAL_STATE, native = Piece::NATIVE)
66
- Piece.new(type, side, state, native)
60
+ # Sashite::Pnn.name("BISHOP") # => #<Pnn::Name value="BISHOP">
61
+ # Sashite::Pnn.name(:queen) # => #<Pnn::Name value="queen">
62
+ def self.name(value)
63
+ Name.new(value)
67
64
  end
68
65
  end
69
66
  end
data/lib/sashite-pnn.rb CHANGED
@@ -5,9 +5,9 @@ require_relative "sashite/pnn"
5
5
  # Sashité namespace for board game notation libraries
6
6
  #
7
7
  # Sashité provides a collection of libraries for representing and manipulating
8
- # board game concepts according to the Game Protocol specifications.
8
+ # board game concepts according to the Sashité Protocol specifications.
9
9
  #
10
- # @see https://sashite.dev/game-protocol/ Game Protocol Foundation
10
+ # @see https://sashite.dev/protocol/ Sashité Protocol
11
11
  # @see https://sashite.dev/specs/ Sashité Specifications
12
12
  # @author Sashité
13
13
  module Sashite
metadata CHANGED
@@ -1,36 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sashite-pnn
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: sashite-pin
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: 2.0.2
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: 2.0.2
11
+ dependencies: []
26
12
  description: |
27
- PNN (Piece Name Notation) extends PIN to provide style-aware piece representation
28
- in abstract strategy board games. This gem implements the PNN Specification v1.0.0 with
29
- a modern Ruby interface featuring immutable piece objects and functional programming
30
- principles. PNN adds derivation markers to PIN that distinguish pieces by their style
31
- origin, enabling cross-style game scenarios and piece origin tracking. Represents all
32
- four Game Protocol piece attributes with full PIN backward compatibility. Perfect for
33
- game engines, cross-tradition tournaments, and hybrid board game environments.
13
+ PNN (Piece Name Notation) provides a rule-agnostic, scalable naming system for identifying
14
+ abstract strategy board game pieces. This gem implements the PNN Specification v1.0.0 with
15
+ a modern Ruby interface featuring immutable piece name objects and functional programming
16
+ principles. PNN uses canonical ASCII names with optional state modifiers and optional terminal
17
+ markers (e.g., "KING", "queen", "+ROOK", "-pawn", "KING^", "+GENERAL^") to unambiguously
18
+ refer to game pieces across variants and traditions. Ideal for engines, protocols, and tools
19
+ that need clear and extensible piece identifiers.
34
20
  email: contact@cyril.email
35
21
  executables: []
36
22
  extensions: []
@@ -40,7 +26,7 @@ files:
40
26
  - README.md
41
27
  - lib/sashite-pnn.rb
42
28
  - lib/sashite/pnn.rb
43
- - lib/sashite/pnn/piece.rb
29
+ - lib/sashite/pnn/name.rb
44
30
  homepage: https://github.com/sashite/pnn.rb
45
31
  licenses:
46
32
  - MIT
@@ -65,8 +51,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
51
  - !ruby/object:Gem::Version
66
52
  version: '0'
67
53
  requirements: []
68
- rubygems_version: 3.6.9
54
+ rubygems_version: 3.7.2
69
55
  specification_version: 4
70
- summary: PNN (Piece Name Notation) implementation for Ruby extending PIN with style
71
- derivation markers
56
+ summary: PNN (Piece Name Notation) implementation for Ruby with immutable piece name
57
+ objects
72
58
  test_files: []