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.
- checksums.yaml +4 -4
- data/LICENSE.md +18 -17
- data/README.md +122 -428
- data/lib/sashite/pnn/name.rb +196 -0
- data/lib/sashite/pnn.rb +39 -42
- data/lib/sashite-pnn.rb +2 -2
- metadata +12 -27
- data/lib/sashite/pnn/piece.rb +0 -441
|
@@ -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/
|
|
3
|
+
require_relative "pnn/name"
|
|
4
4
|
|
|
5
5
|
module Sashite
|
|
6
6
|
# PNN (Piece Name Notation) implementation for Ruby
|
|
7
7
|
#
|
|
8
|
-
# Provides
|
|
9
|
-
# PNN
|
|
10
|
-
#
|
|
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>]<
|
|
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
|
-
# "
|
|
19
|
-
# "
|
|
20
|
-
# "+
|
|
21
|
-
# "-
|
|
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
|
|
24
|
+
# Check if a string is valid PNN notation
|
|
26
25
|
#
|
|
27
|
-
# @param pnn_string [String]
|
|
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?("
|
|
32
|
-
# Sashite::Pnn.valid?("
|
|
33
|
-
# Sashite::Pnn.valid?("
|
|
34
|
-
# Sashite::Pnn.valid?("
|
|
35
|
-
# Sashite::Pnn.valid?("
|
|
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
|
-
|
|
37
|
+
Name.valid?(pnn_string)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
# Parse a PNN string into a
|
|
40
|
+
# Parse a PNN string into a Name object
|
|
41
41
|
#
|
|
42
|
-
# @param pnn_string [String]
|
|
43
|
-
# @return [Pnn::
|
|
44
|
-
# @raise [ArgumentError] if the
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# Sashite::Pnn.parse("
|
|
48
|
-
# Sashite::Pnn.parse("
|
|
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
|
-
|
|
50
|
+
Name.parse(pnn_string)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
# Create a new
|
|
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.
|
|
63
|
-
# Sashite::Pnn.
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
8
|
+
# board game concepts according to the Sashité Protocol specifications.
|
|
9
9
|
#
|
|
10
|
-
# @see https://sashite.dev/
|
|
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:
|
|
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)
|
|
28
|
-
|
|
29
|
-
a modern Ruby interface featuring immutable piece objects and functional programming
|
|
30
|
-
principles. PNN
|
|
31
|
-
|
|
32
|
-
|
|
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/
|
|
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.
|
|
53
|
+
rubygems_version: 3.7.1
|
|
69
54
|
specification_version: 4
|
|
70
|
-
summary: PNN (Piece Name Notation) implementation for Ruby
|
|
71
|
-
|
|
55
|
+
summary: PNN (Piece Name Notation) implementation for Ruby with immutable piece name
|
|
56
|
+
objects
|
|
72
57
|
test_files: []
|