sashite-pin 3.1.0 → 3.2.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 +81 -18
- data/lib/sashite/pin/identifier.rb +84 -18
- data/lib/sashite/pin.rb +7 -5
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5f278c657907ec277b42f6c5f7b22b5df56d6cb57bd6438eeda33c3c847294f9
|
|
4
|
+
data.tar.gz: 96e8c58b731ea334214aa5803e27a7fdd629f17df38b1eb55ddf8654397bd5f5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d451a383989a72da646522113f4ece370b88d57edc940c932cc238311dde1d3f52e477858a1dabba6ff7ee2b9d1a2ee561c9827230ecc3986c421ded53814634
|
|
7
|
+
data.tar.gz: 74cf7b9dd82202b8999951d42ae44bac1cd84cc286819268732070cc8b0bde769984de64a374bcd72ab1e5eaa803dd84e452e85f657163d5bd8d8512c978318b
|
data/README.md
CHANGED
|
@@ -32,19 +32,28 @@ gem install sashite-pin
|
|
|
32
32
|
require "sashite/pin"
|
|
33
33
|
|
|
34
34
|
# Parse PIN strings into identifier objects
|
|
35
|
-
identifier = Sashite::Pin.parse("K") # => #<Pin::Identifier type=:K side=:first state=:normal>
|
|
35
|
+
identifier = Sashite::Pin.parse("K") # => #<Pin::Identifier type=:K side=:first state=:normal terminal=false>
|
|
36
36
|
identifier.to_s # => "K"
|
|
37
37
|
identifier.type # => :K
|
|
38
38
|
identifier.side # => :first
|
|
39
39
|
identifier.state # => :normal
|
|
40
|
+
identifier.terminal? # => false
|
|
41
|
+
|
|
42
|
+
# Parse terminal pieces (e.g., kings in chess)
|
|
43
|
+
terminal_king = Sashite::Pin.parse("K^") # => #<Pin::Identifier type=:K side=:first state=:normal terminal=true>
|
|
44
|
+
terminal_king.to_s # => "K^"
|
|
45
|
+
terminal_king.terminal? # => true
|
|
40
46
|
|
|
41
47
|
# Create identifiers directly
|
|
42
|
-
identifier = Sashite::Pin.identifier(:K, :first, :normal)
|
|
48
|
+
identifier = Sashite::Pin.identifier(:K, :first, :normal) # => #<Pin::Identifier type=:K side=:first state=:normal>
|
|
49
|
+
terminal_king = Sashite::Pin.identifier(:K, :first, :normal, terminal: true) # => #<Pin::Identifier type=:K side=:first state=:normal terminal=true>
|
|
43
50
|
identifier = Sashite::Pin::Identifier.new(:R, :second, :enhanced) # => #<Pin::Identifier type=:R side=:second state=:enhanced>
|
|
44
51
|
|
|
45
52
|
# Validate PIN strings
|
|
46
53
|
Sashite::Pin.valid?("K") # => true
|
|
47
54
|
Sashite::Pin.valid?("+R") # => true
|
|
55
|
+
Sashite::Pin.valid?("K^") # => true
|
|
56
|
+
Sashite::Pin.valid?("+K^") # => true
|
|
48
57
|
Sashite::Pin.valid?("invalid") # => false
|
|
49
58
|
|
|
50
59
|
# State manipulation (returns new immutable instances)
|
|
@@ -53,6 +62,11 @@ enhanced.to_s # => "+K"
|
|
|
53
62
|
diminished = identifier.diminish # => #<Pin::Identifier type=:K side=:first state=:diminished>
|
|
54
63
|
diminished.to_s # => "-K"
|
|
55
64
|
|
|
65
|
+
# Terminal marker manipulation
|
|
66
|
+
normal_king = Sashite::Pin.parse("K")
|
|
67
|
+
terminal_king = normal_king.mark_terminal # => "K^"
|
|
68
|
+
back_to_normal = terminal_king.unmark_terminal # => "K"
|
|
69
|
+
|
|
56
70
|
# Side manipulation
|
|
57
71
|
flipped = identifier.flip # => #<Pin::Identifier type=:K side=:second state=:normal>
|
|
58
72
|
flipped.to_s # => "k"
|
|
@@ -74,6 +88,7 @@ flipped.second_player? # => true
|
|
|
74
88
|
identifier.letter # => "K"
|
|
75
89
|
enhanced.prefix # => "+"
|
|
76
90
|
identifier.prefix # => ""
|
|
91
|
+
terminal_king.suffix # => "^"
|
|
77
92
|
|
|
78
93
|
# Type and side comparison
|
|
79
94
|
king1 = Sashite::Pin.parse("K")
|
|
@@ -87,6 +102,10 @@ king1.same_type?(queen) # => false (different types)
|
|
|
87
102
|
# Functional transformations can be chained
|
|
88
103
|
pawn = Sashite::Pin.parse("P")
|
|
89
104
|
enemy_promoted = pawn.flip.enhance # => "+p" (second player promoted pawn)
|
|
105
|
+
|
|
106
|
+
# Transformations preserve terminal status
|
|
107
|
+
terminal_piece = Sashite::Pin.parse("K^")
|
|
108
|
+
enhanced_terminal = terminal_piece.enhance # => "+K^"
|
|
90
109
|
```
|
|
91
110
|
|
|
92
111
|
## Format Specification
|
|
@@ -94,7 +113,7 @@ enemy_promoted = pawn.flip.enhance # => "+p" (second player promoted
|
|
|
94
113
|
### Structure
|
|
95
114
|
|
|
96
115
|
```
|
|
97
|
-
[<state>]<letter>
|
|
116
|
+
[<state-modifier>]<letter>[<terminal-marker>]
|
|
98
117
|
```
|
|
99
118
|
|
|
100
119
|
### Components
|
|
@@ -103,23 +122,30 @@ enemy_promoted = pawn.flip.enhance # => "+p" (second player promoted
|
|
|
103
122
|
|
|
104
123
|
* Uppercase: First player pieces
|
|
105
124
|
* Lowercase: Second player pieces
|
|
106
|
-
* **State** (optional prefix):
|
|
125
|
+
* **State Modifier** (optional prefix):
|
|
107
126
|
|
|
108
127
|
* `+`: Enhanced state (promoted, upgraded, empowered)
|
|
109
128
|
* `-`: Diminished state (weakened, restricted, temporary)
|
|
110
129
|
* No prefix: Normal state
|
|
130
|
+
* **Terminal Marker** (optional suffix):
|
|
131
|
+
|
|
132
|
+
* `^`: Terminal piece (critical to match continuation)
|
|
133
|
+
* No suffix: Non-terminal piece
|
|
111
134
|
|
|
112
135
|
### Regular Expression
|
|
113
136
|
|
|
114
137
|
```ruby
|
|
115
|
-
/\A[-+]?[A-Za-z]
|
|
138
|
+
/\A[-+]?[A-Za-z]\^?\z/
|
|
116
139
|
```
|
|
117
140
|
|
|
118
141
|
### Examples
|
|
119
142
|
|
|
120
143
|
* `K` - First player king (normal state)
|
|
144
|
+
* `K^` - First player king (normal state, terminal)
|
|
121
145
|
* `k` - Second player king (normal state)
|
|
146
|
+
* `k^` - Second player king (normal state, terminal)
|
|
122
147
|
* `+R` - First player rook (enhanced state)
|
|
148
|
+
* `+R^` - First player rook (enhanced state, terminal)
|
|
123
149
|
* `-p` - Second player pawn (diminished state)
|
|
124
150
|
|
|
125
151
|
## API Reference
|
|
@@ -128,13 +154,13 @@ enemy_promoted = pawn.flip.enhance # => "+p" (second player promoted
|
|
|
128
154
|
|
|
129
155
|
* `Sashite::Pin.valid?(pin_string)` - Check if string is valid PIN notation
|
|
130
156
|
* `Sashite::Pin.parse(pin_string)` - Parse PIN string into Identifier object
|
|
131
|
-
* `Sashite::Pin.identifier(type, side, state = :normal)` - Create identifier instance directly
|
|
157
|
+
* `Sashite::Pin.identifier(type, side, state = :normal, terminal: false)` - Create identifier instance directly
|
|
132
158
|
|
|
133
159
|
### Identifier Class
|
|
134
160
|
|
|
135
161
|
#### Creation and Parsing
|
|
136
162
|
|
|
137
|
-
* `Sashite::Pin::Identifier.new(type, side, state = :normal)` - Create identifier instance
|
|
163
|
+
* `Sashite::Pin::Identifier.new(type, side, state = :normal, terminal: false)` - Create identifier instance
|
|
138
164
|
* `Sashite::Pin::Identifier.parse(pin_string)` - Parse PIN string (same as module method)
|
|
139
165
|
* `Sashite::Pin::Identifier.valid?(pin_string)` - Validate PIN string (class method)
|
|
140
166
|
|
|
@@ -143,8 +169,10 @@ enemy_promoted = pawn.flip.enhance # => "+p" (second player promoted
|
|
|
143
169
|
* `#type` - Get piece type (symbol \:A to \:Z, always uppercase)
|
|
144
170
|
* `#side` - Get player side (\:first or \:second)
|
|
145
171
|
* `#state` - Get state (\:normal, \:enhanced, or \:diminished)
|
|
172
|
+
* `#terminal` - Get terminal status (Boolean)
|
|
146
173
|
* `#letter` - Get letter representation (string, case determined by side)
|
|
147
174
|
* `#prefix` - Get state prefix (string: "+", "-", or "")
|
|
175
|
+
* `#suffix` - Get terminal marker (string: "^" or "")
|
|
148
176
|
* `#to_s` - Convert to PIN string representation
|
|
149
177
|
|
|
150
178
|
#### Type and Case Handling
|
|
@@ -174,6 +202,10 @@ identifier2.letter # => "k" (lowercase display)
|
|
|
174
202
|
* `#first_player?` - Check if first player identifier
|
|
175
203
|
* `#second_player?` - Check if second player identifier
|
|
176
204
|
|
|
205
|
+
#### Terminal Queries
|
|
206
|
+
|
|
207
|
+
* `#terminal?` - Check if terminal piece
|
|
208
|
+
|
|
177
209
|
#### State Transformations (immutable - return new instances)
|
|
178
210
|
|
|
179
211
|
* `#enhance` - Create enhanced version
|
|
@@ -189,16 +221,24 @@ identifier2.letter # => "k" (lowercase display)
|
|
|
189
221
|
* `#with_side(new_side)` - Create identifier with different side
|
|
190
222
|
* `#with_state(new_state)` - Create identifier with different state
|
|
191
223
|
|
|
224
|
+
#### Terminal Transformations (immutable - return new instances)
|
|
225
|
+
|
|
226
|
+
* `#mark_terminal` - Create terminal version
|
|
227
|
+
* `#unmark_terminal` - Create non-terminal version
|
|
228
|
+
* `#with_terminal(boolean)` - Create identifier with specified terminal status
|
|
229
|
+
|
|
192
230
|
#### Comparison Methods
|
|
193
231
|
|
|
194
232
|
* `#same_type?(other)` - Check if same piece type
|
|
195
233
|
* `#same_side?(other)` - Check if same side
|
|
196
234
|
* `#same_state?(other)` - Check if same state
|
|
235
|
+
* `#same_terminal?(other)` - Check if same terminal status
|
|
197
236
|
* `#==(other)` - Full equality comparison
|
|
198
237
|
|
|
199
238
|
### Constants
|
|
200
239
|
|
|
201
240
|
* `Sashite::Pin::Identifier::PIN_PATTERN` - Regular expression for PIN validation (internal use)
|
|
241
|
+
* `Sashite::Pin::Identifier::TERMINAL_MARKER` - Terminal marker character (`"^"`)
|
|
202
242
|
|
|
203
243
|
## Advanced Usage
|
|
204
244
|
|
|
@@ -229,7 +269,7 @@ white_king.same_side?(black_king) # => false
|
|
|
229
269
|
### Immutable Transformations
|
|
230
270
|
```ruby
|
|
231
271
|
# All transformations return new instances
|
|
232
|
-
original = Sashite::Pin.
|
|
272
|
+
original = Sashite::Pin.identifier(:K, :first, :normal)
|
|
233
273
|
enhanced = original.enhance
|
|
234
274
|
diminished = original.diminish
|
|
235
275
|
|
|
@@ -241,6 +281,11 @@ puts diminished.to_s # => "-K"
|
|
|
241
281
|
# Transformations can be chained
|
|
242
282
|
result = original.flip.enhance.with_type(:Q)
|
|
243
283
|
puts result.to_s # => "+q"
|
|
284
|
+
|
|
285
|
+
# Terminal status is preserved through transformations
|
|
286
|
+
terminal_king = Sashite::Pin.parse("K^")
|
|
287
|
+
enhanced_terminal = terminal_king.enhance
|
|
288
|
+
puts enhanced_terminal.to_s # => "+K^"
|
|
244
289
|
```
|
|
245
290
|
|
|
246
291
|
### Game State Management
|
|
@@ -274,13 +319,17 @@ class GameBoard
|
|
|
274
319
|
def promoted_pieces
|
|
275
320
|
@pieces.select { |_, piece| piece.enhanced? }
|
|
276
321
|
end
|
|
322
|
+
|
|
323
|
+
def terminal_pieces
|
|
324
|
+
@pieces.select { |_, piece| piece.terminal? }
|
|
325
|
+
end
|
|
277
326
|
end
|
|
278
327
|
|
|
279
328
|
# Usage
|
|
280
329
|
board = GameBoard.new
|
|
281
|
-
board.place("e1", Sashite::Pin.
|
|
282
|
-
board.place("e8", Sashite::Pin.
|
|
283
|
-
board.place("a7", Sashite::Pin.
|
|
330
|
+
board.place("e1", Sashite::Pin.identifier(:K, :first, :normal, terminal: true))
|
|
331
|
+
board.place("e8", Sashite::Pin.identifier(:K, :second, :normal, terminal: true))
|
|
332
|
+
board.place("a7", Sashite::Pin.identifier(:P, :first, :normal))
|
|
284
333
|
|
|
285
334
|
# Promote pawn
|
|
286
335
|
board.promote("a7", :Q)
|
|
@@ -299,14 +348,16 @@ def analyze_pieces(pins)
|
|
|
299
348
|
by_type: pieces.group_by(&:type),
|
|
300
349
|
by_state: pieces.group_by(&:state),
|
|
301
350
|
promoted: pieces.count(&:enhanced?),
|
|
302
|
-
weakened: pieces.count(&:diminished?)
|
|
351
|
+
weakened: pieces.count(&:diminished?),
|
|
352
|
+
terminal: pieces.count(&:terminal?)
|
|
303
353
|
}
|
|
304
354
|
end
|
|
305
355
|
|
|
306
|
-
pins = %w[K Q +R B N P k q r +b n -p]
|
|
356
|
+
pins = %w[K^ Q +R B N P k^ q r +b n -p]
|
|
307
357
|
analysis = analyze_pieces(pins)
|
|
308
358
|
puts analysis[:by_side][:first].size # => 6
|
|
309
359
|
puts analysis[:promoted] # => 2
|
|
360
|
+
puts analysis[:terminal] # => 2
|
|
310
361
|
```
|
|
311
362
|
|
|
312
363
|
### Move Validation Example
|
|
@@ -325,7 +376,7 @@ def can_promote?(piece, target_rank)
|
|
|
325
376
|
end
|
|
326
377
|
end
|
|
327
378
|
|
|
328
|
-
pawn = Sashite::Pin.
|
|
379
|
+
pawn = Sashite::Pin.identifier(:P, :first, :normal)
|
|
329
380
|
puts can_promote?(pawn, 8) # => true
|
|
330
381
|
|
|
331
382
|
promoted_pawn = pawn.enhance
|
|
@@ -338,9 +389,10 @@ Following the [Game Protocol](https://sashite.dev/game-protocol/):
|
|
|
338
389
|
|
|
339
390
|
| Protocol Attribute | PIN Encoding | Examples | Notes |
|
|
340
391
|
|-------------------|--------------|----------|-------|
|
|
341
|
-
| **
|
|
342
|
-
| **Side** | Letter case in display | `K` = First player, `k` = Second player | Case is determined by side during rendering |
|
|
343
|
-
| **State** | Optional prefix | `+K` = Enhanced, `-K` = Diminished, `K` = Normal | |
|
|
392
|
+
| **Piece Name** | ASCII letter choice | `K`/`k` = King, `P`/`p` = Pawn | Type is always stored as uppercase symbol (`:K`, `:P`) |
|
|
393
|
+
| **Piece Side** | Letter case in display | `K` = First player, `k` = Second player | Case is determined by side during rendering |
|
|
394
|
+
| **Piece State** | Optional prefix | `+K` = Enhanced, `-K` = Diminished, `K` = Normal | |
|
|
395
|
+
| **Terminal Status** | Optional suffix | `K^` = Terminal, `K` = Non-terminal | Identifies pieces critical to match continuation |
|
|
344
396
|
|
|
345
397
|
**Type Convention**: All piece types are internally represented as uppercase symbols (`:A` to `:Z`). The display case is determined by the `side` attribute: first player pieces display as uppercase, second player pieces as lowercase.
|
|
346
398
|
|
|
@@ -352,9 +404,10 @@ Following the [Game Protocol](https://sashite.dev/game-protocol/):
|
|
|
352
404
|
|
|
353
405
|
* **ASCII Compatible**: Maximum portability across systems
|
|
354
406
|
* **Rule-Agnostic**: Independent of specific game mechanics
|
|
355
|
-
* **Compact Format**: 1-
|
|
407
|
+
* **Compact Format**: 1-3 characters per piece
|
|
356
408
|
* **Visual Distinction**: Clear player differentiation through case
|
|
357
409
|
* **Type Normalization**: Consistent uppercase type representation internally
|
|
410
|
+
* **Terminal Marker**: Explicit identification of pieces critical to match continuation
|
|
358
411
|
* **Protocol Compliant**: Direct implementation of Sashité piece attributes
|
|
359
412
|
* **Immutable**: All piece instances are frozen and transformations return new objects
|
|
360
413
|
* **Functional**: Pure functions with no side effects
|
|
@@ -375,6 +428,15 @@ This design ensures:
|
|
|
375
428
|
- Clear separation between piece identity (type) and ownership (side)
|
|
376
429
|
- Predictable behavior when comparing pieces of the same type
|
|
377
430
|
|
|
431
|
+
### Terminal Marker Convention
|
|
432
|
+
|
|
433
|
+
The terminal marker (`^`) identifies pieces whose presence, condition, or capacity for action determines whether the match can continue:
|
|
434
|
+
|
|
435
|
+
1. **Suffix Position**: Always appears as the last character (`K^`, `+K^`, `-k^`)
|
|
436
|
+
2. **Preservation**: Terminal status is preserved through all transformations
|
|
437
|
+
3. **Equality**: Two pieces are equal only if they have the same terminal status
|
|
438
|
+
4. **Independence**: Terminal status is independent of state (normal/enhanced/diminished)
|
|
439
|
+
|
|
378
440
|
### Example Flow
|
|
379
441
|
|
|
380
442
|
```ruby
|
|
@@ -394,6 +456,7 @@ This ensures that `parse(pin).to_s == pin` for all valid PIN strings while maint
|
|
|
394
456
|
- **Maximum 26 piece types** per game system (one per ASCII letter)
|
|
395
457
|
- **Exactly 2 players** (uppercase/lowercase distinction)
|
|
396
458
|
- **3 state levels** (enhanced, normal, diminished)
|
|
459
|
+
- **2 terminal levels** (terminal, non-terminal)
|
|
397
460
|
|
|
398
461
|
## Related Specifications
|
|
399
462
|
|
|
@@ -17,13 +17,16 @@ module Sashite
|
|
|
17
17
|
# This follows the Game Protocol's piece model with Type, Side, and State attributes.
|
|
18
18
|
class Identifier
|
|
19
19
|
# PIN validation pattern matching the specification
|
|
20
|
-
PIN_PATTERN = /\A(?<prefix>[-+])?(?<letter>[a-zA-Z])
|
|
20
|
+
PIN_PATTERN = /\A(?<prefix>[-+])?(?<letter>[a-zA-Z])(?<terminal>\^)?\z/
|
|
21
21
|
|
|
22
22
|
# Valid state modifiers
|
|
23
23
|
ENHANCED_PREFIX = "+"
|
|
24
24
|
DIMINISHED_PREFIX = "-"
|
|
25
25
|
NORMAL_PREFIX = ""
|
|
26
26
|
|
|
27
|
+
# Terminal marker
|
|
28
|
+
TERMINAL_MARKER = "^"
|
|
29
|
+
|
|
27
30
|
# State constants
|
|
28
31
|
ENHANCED_STATE = :enhanced
|
|
29
32
|
DIMINISHED_STATE = :diminished
|
|
@@ -57,13 +60,17 @@ module Sashite
|
|
|
57
60
|
# @return [Symbol] the piece state (:normal, :enhanced, or :diminished)
|
|
58
61
|
attr_reader :state
|
|
59
62
|
|
|
63
|
+
# @return [Boolean] whether the piece is a terminal piece
|
|
64
|
+
attr_reader :terminal
|
|
65
|
+
|
|
60
66
|
# Create a new identifier instance
|
|
61
67
|
#
|
|
62
68
|
# @param type [Symbol] piece type (:A to :Z)
|
|
63
69
|
# @param side [Symbol] player side (:first or :second)
|
|
64
70
|
# @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
|
|
71
|
+
# @param terminal [Boolean] whether the piece is a terminal piece
|
|
65
72
|
# @raise [ArgumentError] if parameters are invalid
|
|
66
|
-
def initialize(type, side, state = NORMAL_STATE)
|
|
73
|
+
def initialize(type, side, state = NORMAL_STATE, terminal: false)
|
|
67
74
|
self.class.validate_type(type)
|
|
68
75
|
self.class.validate_side(side)
|
|
69
76
|
self.class.validate_state(state)
|
|
@@ -71,6 +78,7 @@ module Sashite
|
|
|
71
78
|
@type = type
|
|
72
79
|
@side = side
|
|
73
80
|
@state = state
|
|
81
|
+
@terminal = !!terminal
|
|
74
82
|
|
|
75
83
|
freeze
|
|
76
84
|
end
|
|
@@ -81,9 +89,11 @@ module Sashite
|
|
|
81
89
|
# @return [Identifier] new identifier instance
|
|
82
90
|
# @raise [ArgumentError] if the PIN string is invalid
|
|
83
91
|
# @example
|
|
84
|
-
# Pin::Identifier.parse("k") # => #<Pin::Identifier type=:K side=:second state=:normal>
|
|
85
|
-
# Pin::Identifier.parse("+R") # => #<Pin::Identifier type=:R side=:first state=:enhanced>
|
|
86
|
-
# Pin::Identifier.parse("-p") # => #<Pin::Identifier type=:P side=:second state=:diminished>
|
|
92
|
+
# Pin::Identifier.parse("k") # => #<Pin::Identifier type=:K side=:second state=:normal terminal=false>
|
|
93
|
+
# Pin::Identifier.parse("+R") # => #<Pin::Identifier type=:R side=:first state=:enhanced terminal=false>
|
|
94
|
+
# Pin::Identifier.parse("-p") # => #<Pin::Identifier type=:P side=:second state=:diminished terminal=false>
|
|
95
|
+
# Pin::Identifier.parse("K^") # => #<Pin::Identifier type=:K side=:first state=:normal terminal=true>
|
|
96
|
+
# Pin::Identifier.parse("+K^") # => #<Pin::Identifier type=:K side=:first state=:enhanced terminal=true>
|
|
87
97
|
def self.parse(pin_string)
|
|
88
98
|
string_value = String(pin_string)
|
|
89
99
|
matches = match_pattern(string_value)
|
|
@@ -91,6 +101,7 @@ module Sashite
|
|
|
91
101
|
letter = matches[:letter]
|
|
92
102
|
enhanced = matches[:prefix] == ENHANCED_PREFIX
|
|
93
103
|
diminished = matches[:prefix] == DIMINISHED_PREFIX
|
|
104
|
+
is_terminal = matches[:terminal] == TERMINAL_MARKER
|
|
94
105
|
|
|
95
106
|
type = letter.upcase.to_sym
|
|
96
107
|
side = letter == letter.upcase ? FIRST_PLAYER : SECOND_PLAYER
|
|
@@ -102,7 +113,7 @@ module Sashite
|
|
|
102
113
|
NORMAL_STATE
|
|
103
114
|
end
|
|
104
115
|
|
|
105
|
-
new(type, side, state)
|
|
116
|
+
new(type, side, state, terminal: is_terminal)
|
|
106
117
|
end
|
|
107
118
|
|
|
108
119
|
# Check if a string is a valid PIN notation
|
|
@@ -127,8 +138,10 @@ module Sashite
|
|
|
127
138
|
# @return [String] PIN notation string
|
|
128
139
|
# @example
|
|
129
140
|
# identifier.to_s # => "+R"
|
|
141
|
+
# terminal_king.to_s # => "K^"
|
|
142
|
+
# enhanced_terminal.to_s # => "+K^"
|
|
130
143
|
def to_s
|
|
131
|
-
"#{prefix}#{letter}"
|
|
144
|
+
"#{prefix}#{letter}#{suffix}"
|
|
132
145
|
end
|
|
133
146
|
|
|
134
147
|
# Get the letter representation
|
|
@@ -149,13 +162,20 @@ module Sashite
|
|
|
149
162
|
end
|
|
150
163
|
end
|
|
151
164
|
|
|
165
|
+
# Get the suffix representation
|
|
166
|
+
#
|
|
167
|
+
# @return [String] suffix representing terminal status
|
|
168
|
+
def suffix
|
|
169
|
+
terminal? ? TERMINAL_MARKER : ""
|
|
170
|
+
end
|
|
171
|
+
|
|
152
172
|
# Create a new identifier with enhanced state
|
|
153
173
|
#
|
|
154
174
|
# @return [Identifier] new identifier instance with enhanced state
|
|
155
175
|
def enhance
|
|
156
176
|
return self if enhanced?
|
|
157
177
|
|
|
158
|
-
self.class.new(type, side, ENHANCED_STATE)
|
|
178
|
+
self.class.new(type, side, ENHANCED_STATE, terminal: terminal)
|
|
159
179
|
end
|
|
160
180
|
|
|
161
181
|
# Create a new identifier without enhanced state
|
|
@@ -164,7 +184,7 @@ module Sashite
|
|
|
164
184
|
def unenhance
|
|
165
185
|
return self unless enhanced?
|
|
166
186
|
|
|
167
|
-
self.class.new(type, side, NORMAL_STATE)
|
|
187
|
+
self.class.new(type, side, NORMAL_STATE, terminal: terminal)
|
|
168
188
|
end
|
|
169
189
|
|
|
170
190
|
# Create a new identifier with diminished state
|
|
@@ -173,7 +193,7 @@ module Sashite
|
|
|
173
193
|
def diminish
|
|
174
194
|
return self if diminished?
|
|
175
195
|
|
|
176
|
-
self.class.new(type, side, DIMINISHED_STATE)
|
|
196
|
+
self.class.new(type, side, DIMINISHED_STATE, terminal: terminal)
|
|
177
197
|
end
|
|
178
198
|
|
|
179
199
|
# Create a new identifier without diminished state
|
|
@@ -182,7 +202,7 @@ module Sashite
|
|
|
182
202
|
def undiminish
|
|
183
203
|
return self unless diminished?
|
|
184
204
|
|
|
185
|
-
self.class.new(type, side, NORMAL_STATE)
|
|
205
|
+
self.class.new(type, side, NORMAL_STATE, terminal: terminal)
|
|
186
206
|
end
|
|
187
207
|
|
|
188
208
|
# Create a new identifier with normal state (no modifiers)
|
|
@@ -191,14 +211,32 @@ module Sashite
|
|
|
191
211
|
def normalize
|
|
192
212
|
return self if normal?
|
|
193
213
|
|
|
194
|
-
self.class.new(type, side, NORMAL_STATE)
|
|
214
|
+
self.class.new(type, side, NORMAL_STATE, terminal: terminal)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Create a new identifier marked as terminal
|
|
218
|
+
#
|
|
219
|
+
# @return [Identifier] new identifier instance marked as terminal
|
|
220
|
+
def mark_terminal
|
|
221
|
+
return self if terminal?
|
|
222
|
+
|
|
223
|
+
self.class.new(type, side, state, terminal: true)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Create a new identifier unmarked as terminal
|
|
227
|
+
#
|
|
228
|
+
# @return [Identifier] new identifier instance unmarked as terminal
|
|
229
|
+
def unmark_terminal
|
|
230
|
+
return self unless terminal?
|
|
231
|
+
|
|
232
|
+
self.class.new(type, side, state, terminal: false)
|
|
195
233
|
end
|
|
196
234
|
|
|
197
235
|
# Create a new identifier with opposite side
|
|
198
236
|
#
|
|
199
237
|
# @return [Identifier] new identifier instance with opposite side
|
|
200
238
|
def flip
|
|
201
|
-
self.class.new(type, opposite_side, state)
|
|
239
|
+
self.class.new(type, opposite_side, state, terminal: terminal)
|
|
202
240
|
end
|
|
203
241
|
|
|
204
242
|
# Create a new identifier with a different type
|
|
@@ -209,7 +247,7 @@ module Sashite
|
|
|
209
247
|
self.class.validate_type(new_type)
|
|
210
248
|
return self if type == new_type
|
|
211
249
|
|
|
212
|
-
self.class.new(new_type, side, state)
|
|
250
|
+
self.class.new(new_type, side, state, terminal: terminal)
|
|
213
251
|
end
|
|
214
252
|
|
|
215
253
|
# Create a new identifier with a different side
|
|
@@ -220,7 +258,7 @@ module Sashite
|
|
|
220
258
|
self.class.validate_side(new_side)
|
|
221
259
|
return self if side == new_side
|
|
222
260
|
|
|
223
|
-
self.class.new(type, new_side, state)
|
|
261
|
+
self.class.new(type, new_side, state, terminal: terminal)
|
|
224
262
|
end
|
|
225
263
|
|
|
226
264
|
# Create a new identifier with a different state
|
|
@@ -231,7 +269,18 @@ module Sashite
|
|
|
231
269
|
self.class.validate_state(new_state)
|
|
232
270
|
return self if state == new_state
|
|
233
271
|
|
|
234
|
-
self.class.new(type, side, new_state)
|
|
272
|
+
self.class.new(type, side, new_state, terminal: terminal)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Create a new identifier with a different terminal status
|
|
276
|
+
#
|
|
277
|
+
# @param new_terminal [Boolean] new terminal status
|
|
278
|
+
# @return [Identifier] new identifier instance with new terminal status
|
|
279
|
+
def with_terminal(new_terminal)
|
|
280
|
+
new_terminal_bool = !!new_terminal
|
|
281
|
+
return self if terminal? == new_terminal_bool
|
|
282
|
+
|
|
283
|
+
self.class.new(type, side, state, terminal: new_terminal_bool)
|
|
235
284
|
end
|
|
236
285
|
|
|
237
286
|
# Check if the identifier has enhanced state
|
|
@@ -269,6 +318,13 @@ module Sashite
|
|
|
269
318
|
side == SECOND_PLAYER
|
|
270
319
|
end
|
|
271
320
|
|
|
321
|
+
# Check if the identifier is a terminal piece
|
|
322
|
+
#
|
|
323
|
+
# @return [Boolean] true if terminal
|
|
324
|
+
def terminal?
|
|
325
|
+
terminal
|
|
326
|
+
end
|
|
327
|
+
|
|
272
328
|
# Check if this identifier is the same type as another
|
|
273
329
|
#
|
|
274
330
|
# @param other [Identifier] identifier to compare with
|
|
@@ -299,6 +355,16 @@ module Sashite
|
|
|
299
355
|
state == other.state
|
|
300
356
|
end
|
|
301
357
|
|
|
358
|
+
# Check if this identifier has the same terminal status as another
|
|
359
|
+
#
|
|
360
|
+
# @param other [Identifier] identifier to compare with
|
|
361
|
+
# @return [Boolean] true if same terminal status
|
|
362
|
+
def same_terminal?(other)
|
|
363
|
+
return false unless other.is_a?(self.class)
|
|
364
|
+
|
|
365
|
+
terminal? == other.terminal?
|
|
366
|
+
end
|
|
367
|
+
|
|
302
368
|
# Custom equality comparison
|
|
303
369
|
#
|
|
304
370
|
# @param other [Object] object to compare with
|
|
@@ -306,7 +372,7 @@ module Sashite
|
|
|
306
372
|
def ==(other)
|
|
307
373
|
return false unless other.is_a?(self.class)
|
|
308
374
|
|
|
309
|
-
type == other.type && side == other.side && state == other.state
|
|
375
|
+
type == other.type && side == other.side && state == other.state && terminal? == other.terminal?
|
|
310
376
|
end
|
|
311
377
|
|
|
312
378
|
# Alias for == to ensure Set functionality works correctly
|
|
@@ -316,7 +382,7 @@ module Sashite
|
|
|
316
382
|
#
|
|
317
383
|
# @return [Integer] hash value
|
|
318
384
|
def hash
|
|
319
|
-
[self.class, type, side, state].hash
|
|
385
|
+
[self.class, type, side, state, terminal?].hash
|
|
320
386
|
end
|
|
321
387
|
|
|
322
388
|
# Validate that the type is a valid symbol
|
data/lib/sashite/pin.rb
CHANGED
|
@@ -53,14 +53,16 @@ module Sashite
|
|
|
53
53
|
# @param type [Symbol] piece type (:A to :Z)
|
|
54
54
|
# @param side [Symbol] player side (:first or :second)
|
|
55
55
|
# @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
|
|
56
|
+
# @param terminal [Boolean] whether the piece is a terminal piece
|
|
56
57
|
# @return [Pin::Identifier] new identifier instance
|
|
57
58
|
# @raise [ArgumentError] if parameters are invalid
|
|
58
59
|
# @example
|
|
59
|
-
# Sashite::Pin.identifier(:K, :first, :normal) # => #<Pin::Identifier type=:K side=:first state=:normal>
|
|
60
|
-
# Sashite::Pin.identifier(:R, :first, :enhanced) # => #<Pin::Identifier type=:R side=:first state=:enhanced>
|
|
61
|
-
# Sashite::Pin.identifier(:P, :second, :diminished) # => #<Pin::Identifier type=:P side=:second state=:diminished>
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
# Sashite::Pin.identifier(:K, :first, :normal) # => #<Pin::Identifier type=:K side=:first state=:normal terminal=false>
|
|
61
|
+
# Sashite::Pin.identifier(:R, :first, :enhanced) # => #<Pin::Identifier type=:R side=:first state=:enhanced terminal=false>
|
|
62
|
+
# Sashite::Pin.identifier(:P, :second, :diminished) # => #<Pin::Identifier type=:P side=:second state=:diminished terminal=false>
|
|
63
|
+
# Sashite::Pin.identifier(:K, :first, :normal, terminal: true) # => #<Pin::Identifier type=:K side=:first state=:normal terminal=true>
|
|
64
|
+
def self.identifier(type, side, state, terminal: false)
|
|
65
|
+
Identifier.new(type, side, state, terminal: terminal)
|
|
64
66
|
end
|
|
65
67
|
end
|
|
66
68
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sashite-pin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cyril Kato
|
|
@@ -13,10 +13,11 @@ description: |
|
|
|
13
13
|
PIN (Piece Identifier Notation) provides a rule-agnostic format for identifying pieces
|
|
14
14
|
in abstract strategy board games. This gem implements the PIN Specification v1.0.0 with
|
|
15
15
|
a modern Ruby interface featuring immutable identifier objects and functional programming
|
|
16
|
-
principles. PIN uses single ASCII letters with optional state modifiers
|
|
17
|
-
side encoding (A-Z for first player, a-z for second player), enabling
|
|
18
|
-
identification of pieces across multiple games. Perfect for game
|
|
19
|
-
systems, and hybrid gaming platforms requiring compact,
|
|
16
|
+
principles. PIN uses single ASCII letters with optional state modifiers, terminal markers,
|
|
17
|
+
and case-based side encoding (A-Z for first player, a-z for second player), enabling
|
|
18
|
+
precise and portable identification of pieces across multiple games. Perfect for game
|
|
19
|
+
engines, board game notation systems, and hybrid gaming platforms requiring compact,
|
|
20
|
+
stateful piece representation.
|
|
20
21
|
email: contact@cyril.email
|
|
21
22
|
executables: []
|
|
22
23
|
extensions: []
|
|
@@ -51,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
51
52
|
- !ruby/object:Gem::Version
|
|
52
53
|
version: '0'
|
|
53
54
|
requirements: []
|
|
54
|
-
rubygems_version: 3.
|
|
55
|
+
rubygems_version: 3.7.2
|
|
55
56
|
specification_version: 4
|
|
56
57
|
summary: PIN (Piece Identifier Notation) implementation for Ruby with immutable identifier
|
|
57
58
|
objects
|