sashite-pnn 2.0.0 → 3.0.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,196 @@
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, encoding piece identity, player assignment,
10
+ # and state in a human-readable format.
11
+ #
12
+ # All instances are immutable.
13
+ class Name
14
+ # PNN validation pattern matching the specification
15
+ PNN_PATTERN = /\A([+-]?)([A-Z]+|[a-z]+)\z/
16
+
17
+ # Error messages
18
+ ERROR_INVALID_NAME = "Invalid PNN string: %s"
19
+
20
+ # @return [String] the canonical piece name
21
+ attr_reader :value
22
+
23
+ # Create a new piece name instance
24
+ #
25
+ # @param name [String, Symbol] the piece name (e.g., "KING", :queen, "+ROOK", "-pawn")
26
+ # @raise [ArgumentError] if the name does not match PNN pattern
27
+ def initialize(name)
28
+ string_value = name.to_s
29
+ self.class.validate_format(string_value)
30
+
31
+ @value = string_value.freeze
32
+ @parsed = parse_components(string_value)
33
+ freeze
34
+ end
35
+
36
+ # Parse a PNN string into a Name object
37
+ #
38
+ # @param string [String] the PNN-formatted piece name
39
+ # @return [Name] a new Name instance
40
+ # @raise [ArgumentError] if the string is invalid
41
+ #
42
+ # @example
43
+ # Sashite::Pnn::Name.parse("KING") # => #<Pnn::Name value="KING">
44
+ # Sashite::Pnn::Name.parse("+queen") # => #<Pnn::Name value="+queen">
45
+ def self.parse(string)
46
+ new(string)
47
+ end
48
+
49
+ # Check whether the given string is a valid PNN name
50
+ #
51
+ # @param string [String] input string to validate
52
+ # @return [Boolean] true if valid, false otherwise
53
+ #
54
+ # @example
55
+ # Sashite::Pnn::Name.valid?("KING") # => true
56
+ # Sashite::Pnn::Name.valid?("King") # => false (mixed case)
57
+ # Sashite::Pnn::Name.valid?("+queen") # => true
58
+ # Sashite::Pnn::Name.valid?("KING1") # => false (contains digit)
59
+ def self.valid?(string)
60
+ string.is_a?(::String) && string.match?(PNN_PATTERN)
61
+ end
62
+
63
+ # Validate that the string is in proper PNN format
64
+ #
65
+ # @param str [String]
66
+ # @raise [ArgumentError] if invalid
67
+ def self.validate_format(str)
68
+ raise ::ArgumentError, format(ERROR_INVALID_NAME, str.inspect) unless str.match?(PNN_PATTERN)
69
+ end
70
+
71
+ # Returns the string representation of the name
72
+ #
73
+ # @return [String]
74
+ def to_s
75
+ value
76
+ end
77
+
78
+ # Returns the base name without state modifier
79
+ #
80
+ # @return [String] the piece name without + or - prefix
81
+ #
82
+ # @example
83
+ # Sashite::Pnn::Name.parse("KING").base_name # => "KING"
84
+ # Sashite::Pnn::Name.parse("+queen").base_name # => "queen"
85
+ # Sashite::Pnn::Name.parse("-ROOK").base_name # => "ROOK"
86
+ def base_name
87
+ @parsed[:base_name]
88
+ end
89
+
90
+ # Check if the piece has enhanced state (+)
91
+ #
92
+ # @return [Boolean] true if enhanced, false otherwise
93
+ #
94
+ # @example
95
+ # Sashite::Pnn::Name.parse("+KING").enhanced? # => true
96
+ # Sashite::Pnn::Name.parse("KING").enhanced? # => false
97
+ def enhanced?
98
+ @parsed[:state_modifier] == "+"
99
+ end
100
+
101
+ # Check if the piece has diminished state (-)
102
+ #
103
+ # @return [Boolean] true if diminished, false otherwise
104
+ #
105
+ # @example
106
+ # Sashite::Pnn::Name.parse("-pawn").diminished? # => true
107
+ # Sashite::Pnn::Name.parse("pawn").diminished? # => false
108
+ def diminished?
109
+ @parsed[:state_modifier] == "-"
110
+ end
111
+
112
+ # Check if the piece has normal state (no modifier)
113
+ #
114
+ # @return [Boolean] true if normal, false otherwise
115
+ #
116
+ # @example
117
+ # Sashite::Pnn::Name.parse("KING").normal? # => true
118
+ # Sashite::Pnn::Name.parse("+KING").normal? # => false
119
+ def normal?
120
+ @parsed[:state_modifier].empty?
121
+ end
122
+
123
+ # Check if the piece belongs to the first player (uppercase)
124
+ #
125
+ # @return [Boolean] true if first player, false otherwise
126
+ #
127
+ # @example
128
+ # Sashite::Pnn::Name.parse("KING").first_player? # => true
129
+ # Sashite::Pnn::Name.parse("queen").first_player? # => false
130
+ def first_player?
131
+ @parsed[:base_name] == @parsed[:base_name].upcase
132
+ end
133
+
134
+ # Check if the piece belongs to the second player (lowercase)
135
+ #
136
+ # @return [Boolean] true if second player, false otherwise
137
+ #
138
+ # @example
139
+ # Sashite::Pnn::Name.parse("king").second_player? # => true
140
+ # Sashite::Pnn::Name.parse("QUEEN").second_player? # => false
141
+ def second_player?
142
+ @parsed[:base_name] == @parsed[:base_name].downcase
143
+ end
144
+
145
+ # Check if another piece has the same base name (ignoring case and state)
146
+ #
147
+ # @param other [Name] another piece name to compare
148
+ # @return [Boolean] true if same base name, false otherwise
149
+ #
150
+ # @example
151
+ # king = Sashite::Pnn::Name.parse("KING")
152
+ # queen = Sashite::Pnn::Name.parse("king")
153
+ # enhanced = Sashite::Pnn::Name.parse("+KING")
154
+ #
155
+ # king.same_base_name?(queen) # => true (same piece, different player)
156
+ # king.same_base_name?(enhanced) # => true (same piece, different state)
157
+ def same_base_name?(other)
158
+ return false unless other.is_a?(self.class)
159
+
160
+ base_name.downcase == other.base_name.downcase
161
+ end
162
+
163
+ # Equality based on normalized string value
164
+ #
165
+ # @param other [Object]
166
+ # @return [Boolean]
167
+ def ==(other)
168
+ other.is_a?(self.class) && value == other.value
169
+ end
170
+
171
+ # Required for correct Set/hash behavior
172
+ alias eql? ==
173
+
174
+ # Hash based on class and value
175
+ #
176
+ # @return [Integer]
177
+ def hash
178
+ [self.class, value].hash
179
+ end
180
+
181
+ private
182
+
183
+ # Parse the components of a PNN string
184
+ #
185
+ # @param str [String] the PNN string
186
+ # @return [Hash] parsed components
187
+ def parse_components(str)
188
+ match = str.match(PNN_PATTERN)
189
+ {
190
+ state_modifier: match[1],
191
+ base_name: match[2]
192
+ }
193
+ end
194
+ end
195
+ end
196
+ 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,21 @@
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.0.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 (e.g., "KING", "queen",
17
+ "+ROOK", "-pawn") to unambiguously refer to game pieces across variants and traditions.
18
+ Ideal for engines, protocols, and tools that need clear and extensible piece identifiers.
34
19
  email: contact@cyril.email
35
20
  executables: []
36
21
  extensions: []
@@ -40,7 +25,7 @@ files:
40
25
  - README.md
41
26
  - lib/sashite-pnn.rb
42
27
  - lib/sashite/pnn.rb
43
- - lib/sashite/pnn/piece.rb
28
+ - lib/sashite/pnn/name.rb
44
29
  homepage: https://github.com/sashite/pnn.rb
45
30
  licenses:
46
31
  - MIT
@@ -65,8 +50,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
50
  - !ruby/object:Gem::Version
66
51
  version: '0'
67
52
  requirements: []
68
- rubygems_version: 3.6.9
53
+ rubygems_version: 3.7.1
69
54
  specification_version: 4
70
- summary: PNN (Piece Name Notation) implementation for Ruby extending PIN with style
71
- derivation markers
55
+ summary: PNN (Piece Name Notation) implementation for Ruby with immutable piece name
56
+ objects
72
57
  test_files: []