pnn 1.0.1 → 1.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.
- checksums.yaml +4 -4
- data/README.md +71 -29
- data/lib/pnn/dumper.rb +47 -10
- data/lib/pnn/parser.rb +36 -12
- data/lib/pnn/validator.rb +13 -3
- data/lib/pnn.rb +9 -9
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b48e598bec9d6aeb11a7212428d279f153bbfd00ae6f1a7dc3d0485f4985695c
|
4
|
+
data.tar.gz: d7d8c7da11021a1056e048ab17605bd5db9abdc4cbbdafc90bd00820e651fedc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3957d2e98391d216817a37cf4c9ebc479172c1aeb3415ecc19c1e8abb2d9b29298277312c5b187cdbf1ede2a6f30414bf11b5b05a35ef324abf6a314fc5e7108
|
7
|
+
data.tar.gz: 10665b2f56ae9869dc68db08066dd2e1520691bc4e649ae48cf22643100427d0d0af9981708e3427aab0b8c0894feef37feda79a8c73bd036c020eb423052730
|
data/README.md
CHANGED
@@ -40,7 +40,7 @@ A PNN record consists of a single ASCII letter that represents a piece, with opt
|
|
40
40
|
Where:
|
41
41
|
- `<letter>` is a single ASCII letter (`a-z` or `A-Z`), with uppercase representing the first player's pieces and lowercase representing the second player's pieces
|
42
42
|
- `<prefix>` is an optional modifier preceding the letter (`+` or `-`)
|
43
|
-
- `<suffix>` is an optional modifier following the letter (
|
43
|
+
- `<suffix>` is an optional modifier following the letter (`'`)
|
44
44
|
|
45
45
|
## Basic Usage
|
46
46
|
|
@@ -60,12 +60,12 @@ result = Pnn.parse("+k")
|
|
60
60
|
# => { letter: "k", prefix: "+" }
|
61
61
|
|
62
62
|
# With suffix
|
63
|
-
result = Pnn.parse("k
|
64
|
-
# => { letter: "k", suffix: "
|
63
|
+
result = Pnn.parse("k'")
|
64
|
+
# => { letter: "k", suffix: "'" }
|
65
65
|
|
66
66
|
# With both prefix and suffix
|
67
|
-
result = Pnn.parse("+k
|
68
|
-
# => { letter: "k", prefix: "+", suffix: "
|
67
|
+
result = Pnn.parse("+k'")
|
68
|
+
# => { letter: "k", prefix: "+", suffix: "'" }
|
69
69
|
```
|
70
70
|
|
71
71
|
### Safe Parsing
|
@@ -76,8 +76,8 @@ Parse a PNN string without raising exceptions:
|
|
76
76
|
require "pnn"
|
77
77
|
|
78
78
|
# Valid PNN string
|
79
|
-
result = Pnn.safe_parse("+k
|
80
|
-
# => { letter: "k", prefix: "+", suffix: "
|
79
|
+
result = Pnn.safe_parse("+k'")
|
80
|
+
# => { letter: "k", prefix: "+", suffix: "'" }
|
81
81
|
|
82
82
|
# Invalid PNN string
|
83
83
|
result = Pnn.safe_parse("invalid pnn string")
|
@@ -100,12 +100,12 @@ Pnn.dump(letter: "p", prefix: "+")
|
|
100
100
|
# => "+p"
|
101
101
|
|
102
102
|
# With suffix
|
103
|
-
Pnn.dump(letter: "k", suffix: "
|
104
|
-
# => "k
|
103
|
+
Pnn.dump(letter: "k", suffix: "'")
|
104
|
+
# => "k'"
|
105
105
|
|
106
106
|
# With both prefix and suffix
|
107
|
-
Pnn.dump(letter: "p", prefix: "+", suffix: "
|
108
|
-
# => "+p
|
107
|
+
Pnn.dump(letter: "p", prefix: "+", suffix: "'")
|
108
|
+
# => "+p'"
|
109
109
|
```
|
110
110
|
|
111
111
|
### Validation
|
@@ -117,37 +117,79 @@ require "pnn"
|
|
117
117
|
|
118
118
|
Pnn.valid?("k") # => true
|
119
119
|
Pnn.valid?("+p") # => true
|
120
|
-
Pnn.valid?("k
|
121
|
-
Pnn.valid?("+p
|
120
|
+
Pnn.valid?("k'") # => true
|
121
|
+
Pnn.valid?("+p'") # => true
|
122
122
|
|
123
123
|
Pnn.valid?("") # => false
|
124
124
|
Pnn.valid?("kp") # => false
|
125
125
|
Pnn.valid?("++k") # => false
|
126
|
-
Pnn.valid?("k
|
126
|
+
Pnn.valid?("k''") # => false
|
127
127
|
```
|
128
128
|
|
129
129
|
### Piece Modifiers
|
130
130
|
|
131
|
-
PNN supports prefixes and suffixes for pieces to denote various states or capabilities:
|
131
|
+
PNN supports prefixes and suffixes for pieces to denote various states or capabilities. It's important to note that these modifiers are rule-agnostic - they provide a framework for representing piece states, but their specific meaning is determined by the game implementation:
|
132
132
|
|
133
|
-
- **Prefix `+`**:
|
134
|
-
- Example in shogi: `+p`
|
133
|
+
- **Prefix `+`**: Enhanced state
|
134
|
+
- Example in shogi: `+p` represents a promoted pawn with enhanced movement capabilities
|
135
|
+
- Example in chess variants: `+Q` might represent a queen with special powers
|
135
136
|
|
136
|
-
- **Prefix `-`**: Diminished
|
137
|
-
- Example: `-
|
137
|
+
- **Prefix `-`**: Diminished state
|
138
|
+
- Example in variants: `-R` might represent a rook with restricted movement abilities
|
139
|
+
- Example in weakened pieces: `-N` could indicate a knight that has been partially immobilized
|
138
140
|
|
139
|
-
- **Suffix
|
140
|
-
- Example in chess: `
|
141
|
+
- **Suffix `'`**: Intermediate state
|
142
|
+
- Example in chess: `R'` represents a rook that can still be used for castling
|
143
|
+
- Example in chess: `P'` represents a pawn that can be captured en passant
|
144
|
+
- Example in variants: `B'` might indicate a bishop with a special one-time ability
|
141
145
|
|
142
|
-
|
143
|
-
- Example in chess: `k<` may represent a king eligible for queenside castling only
|
144
|
-
- Example in chess: `p<` may represent a pawn that may be captured _en passant_ from the left
|
146
|
+
These modifiers have no intrinsic semantics in the PNN specification itself. They merely provide a flexible framework for representing piece-specific conditions or states while maintaining PNN's rule-agnostic nature. Game implementations are responsible for interpreting these modifiers according to their specific rules.
|
145
147
|
|
146
|
-
|
147
|
-
- Example in chess: `k>` may represent a king eligible for kingside castling only
|
148
|
-
- Example in chess: `p>` may represent a pawn that may be captured en passant from the right
|
148
|
+
## Examples of PNN in Common Games
|
149
149
|
|
150
|
-
|
150
|
+
The following examples demonstrate how PNN might be used in familiar games. Remember that PNN itself defines only the notation format, not the game-specific interpretations.
|
151
|
+
|
152
|
+
### Chess Examples
|
153
|
+
|
154
|
+
In the context of chess:
|
155
|
+
|
156
|
+
```
|
157
|
+
K # King (first player)
|
158
|
+
k # King (second player)
|
159
|
+
Q # Queen (first player)
|
160
|
+
R ' # Rook that has not moved yet and can be used for castling
|
161
|
+
P' # Pawn that can be captured en passant
|
162
|
+
```
|
163
|
+
|
164
|
+
### Shogi Examples
|
165
|
+
|
166
|
+
In the context of shogi:
|
167
|
+
|
168
|
+
```
|
169
|
+
K # King (first player)
|
170
|
+
k # King (second player)
|
171
|
+
+P # Promoted pawn (tokin)
|
172
|
+
+L # Promoted lance (narikyou)
|
173
|
+
```
|
174
|
+
|
175
|
+
### Example: A Complete Chess Position with PNN
|
176
|
+
|
177
|
+
A chess position might contain a mix of standard and modified pieces. Here's an example after the moves 1. e4 c5 2. e5 d5:
|
178
|
+
|
179
|
+
```
|
180
|
+
r' n b q k b n r' # Eighth rank with unmoved rooks (castling rights)
|
181
|
+
p p . . p p p p # Seventh rank pawns (d and c pawns have moved)
|
182
|
+
. . . . . . . . # Empty sixth rank
|
183
|
+
. . p p' P . . . # Fifth rank with pawn that can be captured en passant (d5) and other pawns
|
184
|
+
. . . . . . . . # Empty fourth rank
|
185
|
+
. . . . . . . . # Empty third rank
|
186
|
+
P P P P . P P P # Second rank pawns (e pawn has moved)
|
187
|
+
R' N B Q K B N R' # First rank with unmoved rooks (castling rights)
|
188
|
+
```
|
189
|
+
|
190
|
+
In this position, White could capture Black's queen pawn (d5) en passant with the e5 pawn moving to d6.
|
191
|
+
|
192
|
+
Note: The above representation is merely illustrative; PNN itself only defines the notation for individual pieces, not complete board states.
|
151
193
|
|
152
194
|
## Properties of PNN
|
153
195
|
|
@@ -173,4 +215,4 @@ The [gem](https://rubygems.org/gems/pnn) is available as open source under the t
|
|
173
215
|
|
174
216
|
## About Sashité
|
175
217
|
|
176
|
-
This project is maintained by [Sashité](https://sashite.com/)
|
218
|
+
This project is maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of Chinese, Japanese, and Western chess cultures.
|
data/lib/pnn/dumper.rb
CHANGED
@@ -7,7 +7,15 @@ module Pnn
|
|
7
7
|
VALID_PREFIXES = ["+", "-", nil].freeze
|
8
8
|
|
9
9
|
# Valid suffix modifiers
|
10
|
-
VALID_SUFFIXES = ["
|
10
|
+
VALID_SUFFIXES = ["'", nil].freeze
|
11
|
+
|
12
|
+
# Letter validation pattern
|
13
|
+
LETTER_PATTERN = /\A[a-zA-Z]\z/
|
14
|
+
|
15
|
+
# Error messages
|
16
|
+
ERROR_INVALID_LETTER = "Letter must be a single ASCII letter (a-z or A-Z): %s"
|
17
|
+
ERROR_INVALID_PREFIX = "Invalid prefix: %s. Must be '+', '-', or nil."
|
18
|
+
ERROR_INVALID_SUFFIX = "Invalid suffix: %s. Must be ''', or nil."
|
11
19
|
|
12
20
|
# Serialize piece components into a PNN string
|
13
21
|
#
|
@@ -17,19 +25,48 @@ module Pnn
|
|
17
25
|
# @return [String] PNN notation string
|
18
26
|
# @raise [ArgumentError] If any component is invalid
|
19
27
|
def self.dump(letter:, prefix: nil, suffix: nil)
|
20
|
-
|
28
|
+
validate_letter(letter)
|
29
|
+
validate_prefix(prefix)
|
30
|
+
validate_suffix(suffix)
|
21
31
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
32
|
+
"#{prefix}#{letter}#{suffix}"
|
33
|
+
end
|
25
34
|
|
26
|
-
|
35
|
+
# Validates that the letter is a single ASCII letter
|
36
|
+
#
|
37
|
+
# @param letter [Object] The letter to validate
|
38
|
+
# @return [void]
|
39
|
+
# @raise [ArgumentError] If the letter is invalid
|
40
|
+
def self.validate_letter(letter)
|
41
|
+
letter_str = String(letter)
|
27
42
|
|
28
|
-
|
29
|
-
raise ArgumentError, "Invalid suffix: #{suffix}. Must be '=', '<', '>', or nil."
|
30
|
-
end
|
43
|
+
return if letter_str.match?(LETTER_PATTERN)
|
31
44
|
|
32
|
-
|
45
|
+
raise ArgumentError, format(ERROR_INVALID_LETTER, letter_str)
|
33
46
|
end
|
47
|
+
|
48
|
+
# Validates that the prefix is valid
|
49
|
+
#
|
50
|
+
# @param prefix [String, nil] The prefix to validate
|
51
|
+
# @return [void]
|
52
|
+
# @raise [ArgumentError] If the prefix is invalid
|
53
|
+
def self.validate_prefix(prefix)
|
54
|
+
return if VALID_PREFIXES.include?(prefix)
|
55
|
+
|
56
|
+
raise ArgumentError, format(ERROR_INVALID_PREFIX, prefix)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Validates that the suffix is valid
|
60
|
+
#
|
61
|
+
# @param suffix [String, nil] The suffix to validate
|
62
|
+
# @return [void]
|
63
|
+
# @raise [ArgumentError] If the suffix is invalid
|
64
|
+
def self.validate_suffix(suffix)
|
65
|
+
return if VALID_SUFFIXES.include?(suffix)
|
66
|
+
|
67
|
+
raise ArgumentError, format(ERROR_INVALID_SUFFIX, suffix)
|
68
|
+
end
|
69
|
+
|
70
|
+
private_class_method :validate_letter, :validate_prefix, :validate_suffix
|
34
71
|
end
|
35
72
|
end
|
data/lib/pnn/parser.rb
CHANGED
@@ -4,7 +4,13 @@ module Pnn
|
|
4
4
|
# Parses PNN strings into their component parts
|
5
5
|
class Parser
|
6
6
|
# PNN regex capture groups for parsing
|
7
|
-
PATTERN = /\A(?<prefix>[-+])?(?<letter>[a-zA-Z])(?<suffix>[
|
7
|
+
PATTERN = /\A(?<prefix>[-+])?(?<letter>[a-zA-Z])(?<suffix>['])?\z/
|
8
|
+
|
9
|
+
# Error message for invalid PNN string
|
10
|
+
ERROR_INVALID_PNN = "Invalid PNN string: %s"
|
11
|
+
|
12
|
+
# Component keys for the result hash
|
13
|
+
COMPONENT_KEYS = %i[letter prefix suffix].freeze
|
8
14
|
|
9
15
|
# Parse a PNN string into its components
|
10
16
|
#
|
@@ -12,17 +18,9 @@ module Pnn
|
|
12
18
|
# @return [Hash] Hash containing the parsed components
|
13
19
|
# @raise [ArgumentError] If the PNN string is invalid
|
14
20
|
def self.parse(pnn_string)
|
15
|
-
|
16
|
-
|
17
|
-
matches
|
18
|
-
|
19
|
-
raise ArgumentError, "Invalid PNN string: #{pnn_string}" if matches.nil?
|
20
|
-
|
21
|
-
{
|
22
|
-
letter: matches[:letter],
|
23
|
-
prefix: matches[:prefix],
|
24
|
-
suffix: matches[:suffix]
|
25
|
-
}.compact
|
21
|
+
string_value = String(pnn_string)
|
22
|
+
matches = match_pattern(string_value)
|
23
|
+
extract_components(matches)
|
26
24
|
end
|
27
25
|
|
28
26
|
# Safely parse a PNN string without raising exceptions
|
@@ -34,5 +32,31 @@ module Pnn
|
|
34
32
|
rescue ArgumentError
|
35
33
|
nil
|
36
34
|
end
|
35
|
+
|
36
|
+
# Match the PNN pattern against a string
|
37
|
+
#
|
38
|
+
# @param string [String] The string to match
|
39
|
+
# @return [MatchData] The match data
|
40
|
+
# @raise [ArgumentError] If the string doesn't match the pattern
|
41
|
+
def self.match_pattern(string)
|
42
|
+
matches = PATTERN.match(string)
|
43
|
+
|
44
|
+
return matches if matches
|
45
|
+
|
46
|
+
raise ArgumentError, format(ERROR_INVALID_PNN, string)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Extract components from match data
|
50
|
+
#
|
51
|
+
# @param matches [MatchData] The match data
|
52
|
+
# @return [Hash] Hash containing the parsed components
|
53
|
+
def self.extract_components(matches)
|
54
|
+
COMPONENT_KEYS.each_with_object({}) do |key, result|
|
55
|
+
value = matches[key]
|
56
|
+
result[key] = value if value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private_class_method :match_pattern, :extract_components
|
37
61
|
end
|
38
62
|
end
|
data/lib/pnn/validator.rb
CHANGED
@@ -4,14 +4,24 @@ module Pnn
|
|
4
4
|
# Validates PNN strings according to the specification
|
5
5
|
class Validator
|
6
6
|
# PNN validation pattern matching the JSON schema pattern in the spec
|
7
|
-
PATTERN = /\A[-+]?[a-zA-Z][
|
7
|
+
PATTERN = /\A[-+]?[a-zA-Z][']?\z/
|
8
8
|
|
9
9
|
# Class method to validate PNN strings
|
10
10
|
#
|
11
|
-
# @param pnn_string [
|
11
|
+
# @param pnn_string [Object] The PNN string to validate
|
12
12
|
# @return [Boolean] True if the string is valid according to PNN specification
|
13
13
|
def self.valid?(pnn_string)
|
14
|
-
String(pnn_string)
|
14
|
+
validate_string(String(pnn_string))
|
15
15
|
end
|
16
|
+
|
17
|
+
# Validates the given string against the PNN pattern
|
18
|
+
#
|
19
|
+
# @param string [String] The string to validate
|
20
|
+
# @return [Boolean] True if the string matches the PNN pattern
|
21
|
+
def self.validate_string(string)
|
22
|
+
string.match?(PATTERN)
|
23
|
+
end
|
24
|
+
|
25
|
+
private_class_method :validate_string
|
16
26
|
end
|
17
27
|
end
|
data/lib/pnn.rb
CHANGED
@@ -14,14 +14,14 @@ require_relative File.join("pnn", "validator")
|
|
14
14
|
module Pnn
|
15
15
|
# Serializes a piece identifier into a PNN string.
|
16
16
|
#
|
17
|
-
# @param prefix [String, nil] Optional modifier preceding the letter ('+' or '-')
|
18
17
|
# @param letter [String] A single ASCII letter ('a-z' or 'A-Z')
|
19
|
-
# @param
|
18
|
+
# @param prefix [String, nil] Optional modifier preceding the letter ('+' or '-')
|
19
|
+
# @param suffix [String, nil] Optional modifier following the letter (''')
|
20
20
|
# @return [String] PNN notation string
|
21
21
|
# @raise [ArgumentError] If any parameter is invalid
|
22
22
|
# @example
|
23
|
-
# Pnn.dump(letter: "k", suffix: "
|
24
|
-
# # => "k
|
23
|
+
# Pnn.dump(letter: "k", suffix: "'")
|
24
|
+
# # => "k'"
|
25
25
|
def self.dump(letter:, prefix: nil, suffix: nil)
|
26
26
|
Dumper.dump(letter:, prefix:, suffix:)
|
27
27
|
end
|
@@ -35,8 +35,8 @@ module Pnn
|
|
35
35
|
# - :suffix [String, nil] - The suffix modifier if present
|
36
36
|
# @raise [ArgumentError] If the PNN string is invalid
|
37
37
|
# @example
|
38
|
-
# Pnn.parse("+k
|
39
|
-
# # => { letter: "k", prefix: "+", suffix: "
|
38
|
+
# Pnn.parse("+k'")
|
39
|
+
# # => { letter: "k", prefix: "+", suffix: "'" }
|
40
40
|
def self.parse(pnn_string)
|
41
41
|
Parser.parse(pnn_string)
|
42
42
|
end
|
@@ -47,8 +47,8 @@ module Pnn
|
|
47
47
|
# @return [Hash, nil] Hash containing the parsed piece data or nil if parsing fails
|
48
48
|
# @example
|
49
49
|
# # Valid PNN string
|
50
|
-
# Pnn.safe_parse("+k
|
51
|
-
# # => { letter: "k", prefix: "+", suffix: "
|
50
|
+
# Pnn.safe_parse("+k'")
|
51
|
+
# # => { letter: "k", prefix: "+", suffix: "'" }
|
52
52
|
#
|
53
53
|
# # Invalid PNN string
|
54
54
|
# Pnn.safe_parse("invalid")
|
@@ -62,7 +62,7 @@ module Pnn
|
|
62
62
|
# @param pnn_string [String] PNN string to validate
|
63
63
|
# @return [Boolean] True if the string is a valid PNN string
|
64
64
|
# @example
|
65
|
-
# Pnn.valid?("k
|
65
|
+
# Pnn.valid?("k'") # => true
|
66
66
|
# Pnn.valid?("invalid") # => false
|
67
67
|
def self.valid?(pnn_string)
|
68
68
|
Validator.valid?(pnn_string)
|