sashite-pin 3.2.0 → 4.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 +201 -0
- data/README.md +197 -425
- data/lib/sashite/pin/constants.rb +35 -0
- data/lib/sashite/pin/errors/argument/messages.rb +28 -0
- data/lib/sashite/pin/errors/argument.rb +16 -0
- data/lib/sashite/pin/errors.rb +3 -0
- data/lib/sashite/pin/identifier.rb +294 -271
- data/lib/sashite/pin/parser.rb +170 -0
- data/lib/sashite/pin.rb +72 -46
- metadata +14 -13
- data/LICENSE.md +0 -22
|
@@ -1,441 +1,464 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
|
|
3
6
|
module Sashite
|
|
4
7
|
module Pin
|
|
5
|
-
# Represents
|
|
8
|
+
# Represents a parsed PIN (Piece Identifier Notation) identifier.
|
|
9
|
+
#
|
|
10
|
+
# An Identifier encodes four attributes of a piece:
|
|
11
|
+
# - Type: the piece type (A-Z as uppercase symbol)
|
|
12
|
+
# - Side: the player side (:first or :second)
|
|
13
|
+
# - State: the piece state (:normal, :enhanced, or :diminished)
|
|
14
|
+
# - Terminal: whether the piece is terminal (true or false)
|
|
6
15
|
#
|
|
7
|
-
#
|
|
8
|
-
# - Enhanced state: prefix '+'
|
|
9
|
-
# - Diminished state: prefix '-'
|
|
10
|
-
# - Normal state: no modifier
|
|
16
|
+
# Instances are immutable (frozen after creation).
|
|
11
17
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
18
|
+
# @example Creating identifiers
|
|
19
|
+
# pin = Identifier.new(:K, :first)
|
|
20
|
+
# pin = Identifier.new(:R, :second, :enhanced)
|
|
21
|
+
# pin = Identifier.new(:K, :first, :normal, terminal: true)
|
|
15
22
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
23
|
+
# @example String conversion
|
|
24
|
+
# Identifier.new(:K, :first).to_s # => "K"
|
|
25
|
+
# Identifier.new(:R, :second, :enhanced).to_s # => "+r"
|
|
26
|
+
# Identifier.new(:K, :first, :normal, terminal: true).to_s # => "K^"
|
|
27
|
+
#
|
|
28
|
+
# @see https://sashite.dev/specs/pin/1.0.0/
|
|
18
29
|
class Identifier
|
|
19
|
-
#
|
|
20
|
-
PIN_PATTERN = /\A(?<prefix>[-+])?(?<letter>[a-zA-Z])(?<terminal>\^)?\z/
|
|
21
|
-
|
|
22
|
-
# Valid state modifiers
|
|
23
|
-
ENHANCED_PREFIX = "+"
|
|
24
|
-
DIMINISHED_PREFIX = "-"
|
|
25
|
-
NORMAL_PREFIX = ""
|
|
26
|
-
|
|
27
|
-
# Terminal marker
|
|
28
|
-
TERMINAL_MARKER = "^"
|
|
29
|
-
|
|
30
|
-
# State constants
|
|
31
|
-
ENHANCED_STATE = :enhanced
|
|
32
|
-
DIMINISHED_STATE = :diminished
|
|
33
|
-
NORMAL_STATE = :normal
|
|
34
|
-
|
|
35
|
-
# Player side constants
|
|
36
|
-
FIRST_PLAYER = :first
|
|
37
|
-
SECOND_PLAYER = :second
|
|
38
|
-
|
|
39
|
-
# Valid types (A-Z)
|
|
40
|
-
VALID_TYPES = (:A..:Z).to_a.freeze
|
|
41
|
-
|
|
42
|
-
# Valid sides
|
|
43
|
-
VALID_SIDES = [FIRST_PLAYER, SECOND_PLAYER].freeze
|
|
44
|
-
|
|
45
|
-
# Valid states
|
|
46
|
-
VALID_STATES = [NORMAL_STATE, ENHANCED_STATE, DIMINISHED_STATE].freeze
|
|
47
|
-
|
|
48
|
-
# Error messages
|
|
49
|
-
ERROR_INVALID_PIN = "Invalid PIN string: %s"
|
|
50
|
-
ERROR_INVALID_TYPE = "Type must be a symbol from :A to :Z, got: %s"
|
|
51
|
-
ERROR_INVALID_SIDE = "Side must be :first or :second, got: %s"
|
|
52
|
-
ERROR_INVALID_STATE = "State must be :normal, :enhanced, or :diminished, got: %s"
|
|
53
|
-
|
|
54
|
-
# @return [Symbol] the piece type (:A to :Z)
|
|
30
|
+
# @return [Symbol] Piece type (:A to :Z, always uppercase)
|
|
55
31
|
attr_reader :type
|
|
56
32
|
|
|
57
|
-
# @return [Symbol]
|
|
33
|
+
# @return [Symbol] Player side (:first or :second)
|
|
58
34
|
attr_reader :side
|
|
59
35
|
|
|
60
|
-
# @return [Symbol]
|
|
36
|
+
# @return [Symbol] Piece state (:normal, :enhanced, or :diminished)
|
|
61
37
|
attr_reader :state
|
|
62
38
|
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
#
|
|
39
|
+
# Creates a new Identifier instance.
|
|
40
|
+
#
|
|
41
|
+
# @param type [Symbol] Piece type (:A to :Z)
|
|
42
|
+
# @param side [Symbol] Player side (:first or :second)
|
|
43
|
+
# @param state [Symbol] Piece state (:normal, :enhanced, or :diminished)
|
|
44
|
+
# @param terminal [Boolean] Terminal status
|
|
45
|
+
# @return [Identifier] A new frozen Identifier instance
|
|
46
|
+
# @raise [Errors::Argument] If any attribute is invalid
|
|
67
47
|
#
|
|
68
|
-
# @
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
48
|
+
# @example
|
|
49
|
+
# Identifier.new(:K, :first)
|
|
50
|
+
# Identifier.new(:R, :second, :enhanced)
|
|
51
|
+
# Identifier.new(:K, :first, :normal, terminal: true)
|
|
52
|
+
def initialize(type, side, state = :normal, terminal: false)
|
|
53
|
+
validate_type!(type)
|
|
54
|
+
validate_side!(side)
|
|
55
|
+
validate_state!(state)
|
|
56
|
+
validate_terminal!(terminal)
|
|
77
57
|
|
|
78
58
|
@type = type
|
|
79
59
|
@side = side
|
|
80
60
|
@state = state
|
|
81
|
-
@terminal =
|
|
61
|
+
@terminal = terminal
|
|
82
62
|
|
|
83
63
|
freeze
|
|
84
64
|
end
|
|
85
65
|
|
|
86
|
-
#
|
|
66
|
+
# Returns the terminal status.
|
|
87
67
|
#
|
|
88
|
-
# @
|
|
89
|
-
# @return [Identifier] new identifier instance
|
|
90
|
-
# @raise [ArgumentError] if the PIN string is invalid
|
|
91
|
-
# @example
|
|
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>
|
|
97
|
-
def self.parse(pin_string)
|
|
98
|
-
string_value = String(pin_string)
|
|
99
|
-
matches = match_pattern(string_value)
|
|
100
|
-
|
|
101
|
-
letter = matches[:letter]
|
|
102
|
-
enhanced = matches[:prefix] == ENHANCED_PREFIX
|
|
103
|
-
diminished = matches[:prefix] == DIMINISHED_PREFIX
|
|
104
|
-
is_terminal = matches[:terminal] == TERMINAL_MARKER
|
|
105
|
-
|
|
106
|
-
type = letter.upcase.to_sym
|
|
107
|
-
side = letter == letter.upcase ? FIRST_PLAYER : SECOND_PLAYER
|
|
108
|
-
state = if enhanced
|
|
109
|
-
ENHANCED_STATE
|
|
110
|
-
elsif diminished
|
|
111
|
-
DIMINISHED_STATE
|
|
112
|
-
else
|
|
113
|
-
NORMAL_STATE
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
new(type, side, state, terminal: is_terminal)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Check if a string is a valid PIN notation
|
|
120
|
-
#
|
|
121
|
-
# @param pin_string [String] The string to validate
|
|
122
|
-
# @return [Boolean] true if valid PIN, false otherwise
|
|
68
|
+
# @return [Boolean] true if terminal piece, false otherwise
|
|
123
69
|
#
|
|
124
70
|
# @example
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# Sashite::Pin::Identifier.valid?("++K") # => false
|
|
130
|
-
def self.valid?(pin_string)
|
|
131
|
-
return false unless pin_string.is_a?(::String)
|
|
132
|
-
|
|
133
|
-
pin_string.match?(PIN_PATTERN)
|
|
71
|
+
# Identifier.new(:K, :first).terminal? # => false
|
|
72
|
+
# Identifier.new(:K, :first, :normal, terminal: true).terminal? # => true
|
|
73
|
+
def terminal?
|
|
74
|
+
@terminal
|
|
134
75
|
end
|
|
135
76
|
|
|
136
|
-
#
|
|
77
|
+
# ========================================================================
|
|
78
|
+
# String Conversion
|
|
79
|
+
# ========================================================================
|
|
80
|
+
|
|
81
|
+
# Returns the PIN string representation.
|
|
82
|
+
#
|
|
83
|
+
# @return [String] The PIN string
|
|
137
84
|
#
|
|
138
|
-
# @return [String] PIN notation string
|
|
139
85
|
# @example
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
86
|
+
# Identifier.new(:K, :first).to_s # => "K"
|
|
87
|
+
# Identifier.new(:R, :second, :enhanced).to_s # => "+r"
|
|
88
|
+
# Identifier.new(:K, :first, :normal, terminal: true).to_s # => "K^"
|
|
143
89
|
def to_s
|
|
144
90
|
"#{prefix}#{letter}#{suffix}"
|
|
145
91
|
end
|
|
146
92
|
|
|
147
|
-
#
|
|
93
|
+
# Returns the letter component of the PIN.
|
|
94
|
+
#
|
|
95
|
+
# @return [String] Uppercase for first player, lowercase for second
|
|
148
96
|
#
|
|
149
|
-
# @
|
|
97
|
+
# @example
|
|
98
|
+
# Identifier.new(:K, :first).letter # => "K"
|
|
99
|
+
# Identifier.new(:K, :second).letter # => "k"
|
|
150
100
|
def letter
|
|
151
|
-
|
|
101
|
+
case side
|
|
102
|
+
when :first then String(type.upcase)
|
|
103
|
+
when :second then String(type.downcase)
|
|
104
|
+
end
|
|
152
105
|
end
|
|
153
106
|
|
|
154
|
-
#
|
|
107
|
+
# Returns the state prefix of the PIN.
|
|
108
|
+
#
|
|
109
|
+
# @return [String] "+" for enhanced, "-" for diminished, "" for normal
|
|
155
110
|
#
|
|
156
|
-
# @
|
|
111
|
+
# @example
|
|
112
|
+
# Identifier.new(:K, :first, :enhanced).prefix # => "+"
|
|
113
|
+
# Identifier.new(:K, :first, :diminished).prefix # => "-"
|
|
114
|
+
# Identifier.new(:K, :first, :normal).prefix # => ""
|
|
157
115
|
def prefix
|
|
158
116
|
case state
|
|
159
|
-
when
|
|
160
|
-
when
|
|
161
|
-
else
|
|
117
|
+
when :enhanced then Constants::ENHANCED_PREFIX
|
|
118
|
+
when :diminished then Constants::DIMINISHED_PREFIX
|
|
119
|
+
else Constants::EMPTY_STRING
|
|
162
120
|
end
|
|
163
121
|
end
|
|
164
122
|
|
|
165
|
-
#
|
|
123
|
+
# Returns the terminal suffix of the PIN.
|
|
124
|
+
#
|
|
125
|
+
# @return [String] "^" if terminal, "" otherwise
|
|
166
126
|
#
|
|
167
|
-
# @
|
|
127
|
+
# @example
|
|
128
|
+
# Identifier.new(:K, :first, :normal, terminal: true).suffix # => "^"
|
|
129
|
+
# Identifier.new(:K, :first).suffix # => ""
|
|
168
130
|
def suffix
|
|
169
|
-
terminal? ?
|
|
131
|
+
terminal? ? Constants::TERMINAL_SUFFIX : Constants::EMPTY_STRING
|
|
170
132
|
end
|
|
171
133
|
|
|
172
|
-
#
|
|
134
|
+
# ========================================================================
|
|
135
|
+
# State Transformations
|
|
136
|
+
# ========================================================================
|
|
137
|
+
|
|
138
|
+
# Returns a new Identifier with enhanced state.
|
|
139
|
+
#
|
|
140
|
+
# @return [Identifier] A new Identifier with :enhanced state
|
|
173
141
|
#
|
|
174
|
-
# @
|
|
142
|
+
# @example
|
|
143
|
+
# pin = Identifier.new(:K, :first)
|
|
144
|
+
# pin.enhance.to_s # => "+K"
|
|
175
145
|
def enhance
|
|
176
146
|
return self if enhanced?
|
|
177
147
|
|
|
178
|
-
self.class.new(type, side,
|
|
148
|
+
self.class.new(type, side, :enhanced, terminal: terminal?)
|
|
179
149
|
end
|
|
180
150
|
|
|
181
|
-
#
|
|
151
|
+
# Returns a new Identifier with diminished state.
|
|
182
152
|
#
|
|
183
|
-
# @return [Identifier] new
|
|
184
|
-
def unenhance
|
|
185
|
-
return self unless enhanced?
|
|
186
|
-
|
|
187
|
-
self.class.new(type, side, NORMAL_STATE, terminal: terminal)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
# Create a new identifier with diminished state
|
|
153
|
+
# @return [Identifier] A new Identifier with :diminished state
|
|
191
154
|
#
|
|
192
|
-
# @
|
|
155
|
+
# @example
|
|
156
|
+
# pin = Identifier.new(:K, :first)
|
|
157
|
+
# pin.diminish.to_s # => "-K"
|
|
193
158
|
def diminish
|
|
194
159
|
return self if diminished?
|
|
195
160
|
|
|
196
|
-
self.class.new(type, side,
|
|
161
|
+
self.class.new(type, side, :diminished, terminal: terminal?)
|
|
197
162
|
end
|
|
198
163
|
|
|
199
|
-
#
|
|
164
|
+
# Returns a new Identifier with normal state.
|
|
200
165
|
#
|
|
201
|
-
# @return [Identifier] new
|
|
202
|
-
def undiminish
|
|
203
|
-
return self unless diminished?
|
|
204
|
-
|
|
205
|
-
self.class.new(type, side, NORMAL_STATE, terminal: terminal)
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
# Create a new identifier with normal state (no modifiers)
|
|
166
|
+
# @return [Identifier] A new Identifier with :normal state
|
|
209
167
|
#
|
|
210
|
-
# @
|
|
168
|
+
# @example
|
|
169
|
+
# pin = Identifier.new(:K, :first, :enhanced)
|
|
170
|
+
# pin.normalize.to_s # => "K"
|
|
211
171
|
def normalize
|
|
212
172
|
return self if normal?
|
|
213
173
|
|
|
214
|
-
self.class.new(type, side,
|
|
174
|
+
self.class.new(type, side, :normal, terminal: terminal?)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# ========================================================================
|
|
178
|
+
# Side Transformations
|
|
179
|
+
# ========================================================================
|
|
180
|
+
|
|
181
|
+
# Returns a new Identifier with the opposite side.
|
|
182
|
+
#
|
|
183
|
+
# @return [Identifier] A new Identifier with flipped side
|
|
184
|
+
#
|
|
185
|
+
# @example
|
|
186
|
+
# pin = Identifier.new(:K, :first)
|
|
187
|
+
# pin.flip.to_s # => "k"
|
|
188
|
+
def flip
|
|
189
|
+
new_side = first_player? ? :second : :first
|
|
190
|
+
self.class.new(type, new_side, state, terminal: terminal?)
|
|
215
191
|
end
|
|
216
192
|
|
|
217
|
-
#
|
|
193
|
+
# ========================================================================
|
|
194
|
+
# Terminal Transformations
|
|
195
|
+
# ========================================================================
|
|
196
|
+
|
|
197
|
+
# Returns a new Identifier marked as terminal.
|
|
198
|
+
#
|
|
199
|
+
# @return [Identifier] A new Identifier with terminal: true
|
|
218
200
|
#
|
|
219
|
-
# @
|
|
201
|
+
# @example
|
|
202
|
+
# pin = Identifier.new(:K, :first)
|
|
203
|
+
# pin.mark_terminal.to_s # => "K^"
|
|
220
204
|
def mark_terminal
|
|
221
205
|
return self if terminal?
|
|
222
206
|
|
|
223
207
|
self.class.new(type, side, state, terminal: true)
|
|
224
208
|
end
|
|
225
209
|
|
|
226
|
-
#
|
|
210
|
+
# Returns a new Identifier unmarked as terminal.
|
|
211
|
+
#
|
|
212
|
+
# @return [Identifier] A new Identifier with terminal: false
|
|
227
213
|
#
|
|
228
|
-
# @
|
|
214
|
+
# @example
|
|
215
|
+
# pin = Identifier.new(:K, :first, :normal, terminal: true)
|
|
216
|
+
# pin.unmark_terminal.to_s # => "K"
|
|
229
217
|
def unmark_terminal
|
|
230
218
|
return self unless terminal?
|
|
231
219
|
|
|
232
220
|
self.class.new(type, side, state, terminal: false)
|
|
233
221
|
end
|
|
234
222
|
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
def flip
|
|
239
|
-
self.class.new(type, opposite_side, state, terminal: terminal)
|
|
240
|
-
end
|
|
223
|
+
# ========================================================================
|
|
224
|
+
# Attribute Transformations
|
|
225
|
+
# ========================================================================
|
|
241
226
|
|
|
242
|
-
#
|
|
227
|
+
# Returns a new Identifier with a different type.
|
|
243
228
|
#
|
|
244
|
-
# @param new_type [Symbol] new type (:A to :Z)
|
|
245
|
-
# @return [Identifier] new
|
|
229
|
+
# @param new_type [Symbol] The new piece type (:A to :Z)
|
|
230
|
+
# @return [Identifier] A new Identifier with the specified type
|
|
231
|
+
# @raise [Errors::Argument] If the type is invalid
|
|
232
|
+
#
|
|
233
|
+
# @example
|
|
234
|
+
# pin = Identifier.new(:K, :first)
|
|
235
|
+
# pin.with_type(:Q).to_s # => "Q"
|
|
246
236
|
def with_type(new_type)
|
|
247
|
-
self.
|
|
248
|
-
return self if type == new_type
|
|
237
|
+
return self if type.equal?(new_type)
|
|
249
238
|
|
|
250
|
-
self.class.new(new_type, side, state, terminal: terminal)
|
|
239
|
+
self.class.new(new_type, side, state, terminal: terminal?)
|
|
251
240
|
end
|
|
252
241
|
|
|
253
|
-
#
|
|
242
|
+
# Returns a new Identifier with a different side.
|
|
254
243
|
#
|
|
255
|
-
# @param new_side [Symbol] new side (:first or :second)
|
|
256
|
-
# @return [Identifier] new
|
|
244
|
+
# @param new_side [Symbol] The new side (:first or :second)
|
|
245
|
+
# @return [Identifier] A new Identifier with the specified side
|
|
246
|
+
# @raise [Errors::Argument] If the side is invalid
|
|
247
|
+
#
|
|
248
|
+
# @example
|
|
249
|
+
# pin = Identifier.new(:K, :first)
|
|
250
|
+
# pin.with_side(:second).to_s # => "k"
|
|
257
251
|
def with_side(new_side)
|
|
258
|
-
self.
|
|
259
|
-
return self if side == new_side
|
|
252
|
+
return self if side.equal?(new_side)
|
|
260
253
|
|
|
261
|
-
self.class.new(type, new_side, state, terminal: terminal)
|
|
254
|
+
self.class.new(type, new_side, state, terminal: terminal?)
|
|
262
255
|
end
|
|
263
256
|
|
|
264
|
-
#
|
|
257
|
+
# Returns a new Identifier with a different state.
|
|
265
258
|
#
|
|
266
|
-
# @param new_state [Symbol] new state (:normal, :enhanced, or :diminished)
|
|
267
|
-
# @return [Identifier] new
|
|
259
|
+
# @param new_state [Symbol] The new state (:normal, :enhanced, or :diminished)
|
|
260
|
+
# @return [Identifier] A new Identifier with the specified state
|
|
261
|
+
# @raise [Errors::Argument] If the state is invalid
|
|
262
|
+
#
|
|
263
|
+
# @example
|
|
264
|
+
# pin = Identifier.new(:K, :first)
|
|
265
|
+
# pin.with_state(:enhanced).to_s # => "+K"
|
|
268
266
|
def with_state(new_state)
|
|
269
|
-
self.
|
|
270
|
-
return self if state == new_state
|
|
267
|
+
return self if state.equal?(new_state)
|
|
271
268
|
|
|
272
|
-
self.class.new(type, side, new_state, terminal: terminal)
|
|
269
|
+
self.class.new(type, side, new_state, terminal: terminal?)
|
|
273
270
|
end
|
|
274
271
|
|
|
275
|
-
#
|
|
272
|
+
# Returns a new Identifier with a different terminal status.
|
|
273
|
+
#
|
|
274
|
+
# @param new_terminal [Boolean] The new terminal status
|
|
275
|
+
# @return [Identifier] A new Identifier with the specified terminal status
|
|
276
|
+
# @raise [Errors::Argument] If the terminal is not a boolean
|
|
276
277
|
#
|
|
277
|
-
# @
|
|
278
|
-
#
|
|
278
|
+
# @example
|
|
279
|
+
# pin = Identifier.new(:K, :first)
|
|
280
|
+
# pin.with_terminal(true).to_s # => "K^"
|
|
279
281
|
def with_terminal(new_terminal)
|
|
280
|
-
|
|
281
|
-
return self if terminal? == new_terminal_bool
|
|
282
|
+
return self if terminal?.equal?(new_terminal)
|
|
282
283
|
|
|
283
|
-
self.class.new(type, side, state, terminal:
|
|
284
|
+
self.class.new(type, side, state, terminal: new_terminal)
|
|
284
285
|
end
|
|
285
286
|
|
|
286
|
-
#
|
|
287
|
+
# ========================================================================
|
|
288
|
+
# State Queries
|
|
289
|
+
# ========================================================================
|
|
290
|
+
|
|
291
|
+
# Checks if the Identifier has normal state.
|
|
287
292
|
#
|
|
288
|
-
# @return [Boolean] true if
|
|
289
|
-
|
|
290
|
-
|
|
293
|
+
# @return [Boolean] true if normal state
|
|
294
|
+
#
|
|
295
|
+
# @example
|
|
296
|
+
# Identifier.new(:K, :first).normal? # => true
|
|
297
|
+
def normal?
|
|
298
|
+
state.equal?(:normal)
|
|
291
299
|
end
|
|
292
300
|
|
|
293
|
-
#
|
|
301
|
+
# Checks if the Identifier has enhanced state.
|
|
294
302
|
#
|
|
295
|
-
# @return [Boolean] true if
|
|
296
|
-
|
|
297
|
-
|
|
303
|
+
# @return [Boolean] true if enhanced state
|
|
304
|
+
#
|
|
305
|
+
# @example
|
|
306
|
+
# Identifier.new(:K, :first, :enhanced).enhanced? # => true
|
|
307
|
+
def enhanced?
|
|
308
|
+
state.equal?(:enhanced)
|
|
298
309
|
end
|
|
299
310
|
|
|
300
|
-
#
|
|
311
|
+
# Checks if the Identifier has diminished state.
|
|
301
312
|
#
|
|
302
|
-
# @return [Boolean] true if
|
|
303
|
-
|
|
304
|
-
|
|
313
|
+
# @return [Boolean] true if diminished state
|
|
314
|
+
#
|
|
315
|
+
# @example
|
|
316
|
+
# Identifier.new(:K, :first, :diminished).diminished? # => true
|
|
317
|
+
def diminished?
|
|
318
|
+
state.equal?(:diminished)
|
|
305
319
|
end
|
|
306
320
|
|
|
307
|
-
#
|
|
321
|
+
# ========================================================================
|
|
322
|
+
# Side Queries
|
|
323
|
+
# ========================================================================
|
|
324
|
+
|
|
325
|
+
# Checks if the Identifier belongs to the first player.
|
|
308
326
|
#
|
|
309
327
|
# @return [Boolean] true if first player
|
|
328
|
+
#
|
|
329
|
+
# @example
|
|
330
|
+
# Identifier.new(:K, :first).first_player? # => true
|
|
310
331
|
def first_player?
|
|
311
|
-
side
|
|
332
|
+
side.equal?(:first)
|
|
312
333
|
end
|
|
313
334
|
|
|
314
|
-
#
|
|
335
|
+
# Checks if the Identifier belongs to the second player.
|
|
315
336
|
#
|
|
316
337
|
# @return [Boolean] true if second player
|
|
338
|
+
#
|
|
339
|
+
# @example
|
|
340
|
+
# Identifier.new(:K, :second).second_player? # => true
|
|
317
341
|
def second_player?
|
|
318
|
-
side
|
|
342
|
+
side.equal?(:second)
|
|
319
343
|
end
|
|
320
344
|
|
|
321
|
-
#
|
|
322
|
-
#
|
|
323
|
-
#
|
|
324
|
-
def terminal?
|
|
325
|
-
terminal
|
|
326
|
-
end
|
|
345
|
+
# ========================================================================
|
|
346
|
+
# Comparison Queries
|
|
347
|
+
# ========================================================================
|
|
327
348
|
|
|
328
|
-
#
|
|
349
|
+
# Checks if two Identifiers have the same type.
|
|
329
350
|
#
|
|
330
|
-
# @param other [Identifier]
|
|
351
|
+
# @param other [Identifier] The other Identifier to compare
|
|
331
352
|
# @return [Boolean] true if same type
|
|
353
|
+
#
|
|
354
|
+
# @example
|
|
355
|
+
# pin1 = Identifier.new(:K, :first)
|
|
356
|
+
# pin2 = Identifier.new(:K, :second)
|
|
357
|
+
# pin1.same_type?(pin2) # => true
|
|
332
358
|
def same_type?(other)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
type == other.type
|
|
359
|
+
type.equal?(other.type)
|
|
336
360
|
end
|
|
337
361
|
|
|
338
|
-
#
|
|
362
|
+
# Checks if two Identifiers have the same side.
|
|
339
363
|
#
|
|
340
|
-
# @param other [Identifier]
|
|
364
|
+
# @param other [Identifier] The other Identifier to compare
|
|
341
365
|
# @return [Boolean] true if same side
|
|
366
|
+
#
|
|
367
|
+
# @example
|
|
368
|
+
# pin1 = Identifier.new(:K, :first)
|
|
369
|
+
# pin2 = Identifier.new(:Q, :first)
|
|
370
|
+
# pin1.same_side?(pin2) # => true
|
|
342
371
|
def same_side?(other)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
side == other.side
|
|
372
|
+
side.equal?(other.side)
|
|
346
373
|
end
|
|
347
374
|
|
|
348
|
-
#
|
|
375
|
+
# Checks if two Identifiers have the same state.
|
|
349
376
|
#
|
|
350
|
-
# @param other [Identifier]
|
|
377
|
+
# @param other [Identifier] The other Identifier to compare
|
|
351
378
|
# @return [Boolean] true if same state
|
|
379
|
+
#
|
|
380
|
+
# @example
|
|
381
|
+
# pin1 = Identifier.new(:K, :first, :enhanced)
|
|
382
|
+
# pin2 = Identifier.new(:Q, :second, :enhanced)
|
|
383
|
+
# pin1.same_state?(pin2) # => true
|
|
352
384
|
def same_state?(other)
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
state == other.state
|
|
385
|
+
state.equal?(other.state)
|
|
356
386
|
end
|
|
357
387
|
|
|
358
|
-
#
|
|
388
|
+
# Checks if two Identifiers have the same terminal status.
|
|
359
389
|
#
|
|
360
|
-
# @param other [Identifier]
|
|
390
|
+
# @param other [Identifier] The other Identifier to compare
|
|
361
391
|
# @return [Boolean] true if same terminal status
|
|
392
|
+
#
|
|
393
|
+
# @example
|
|
394
|
+
# pin1 = Identifier.new(:K, :first, :normal, terminal: true)
|
|
395
|
+
# pin2 = Identifier.new(:Q, :second, :normal, terminal: true)
|
|
396
|
+
# pin1.same_terminal?(pin2) # => true
|
|
362
397
|
def same_terminal?(other)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
terminal? == other.terminal?
|
|
398
|
+
terminal?.equal?(other.terminal?)
|
|
366
399
|
end
|
|
367
400
|
|
|
368
|
-
#
|
|
401
|
+
# ========================================================================
|
|
402
|
+
# Equality
|
|
403
|
+
# ========================================================================
|
|
404
|
+
|
|
405
|
+
# Checks equality with another Identifier.
|
|
369
406
|
#
|
|
370
|
-
# @param other [Object] object to compare
|
|
371
|
-
# @return [Boolean] true if
|
|
407
|
+
# @param other [Object] The object to compare
|
|
408
|
+
# @return [Boolean] true if equal
|
|
372
409
|
def ==(other)
|
|
373
|
-
return false unless
|
|
410
|
+
return false unless self.class === other
|
|
374
411
|
|
|
375
|
-
type
|
|
412
|
+
type.equal?(other.type) &&
|
|
413
|
+
side.equal?(other.side) &&
|
|
414
|
+
state.equal?(other.state) &&
|
|
415
|
+
terminal?.equal?(other.terminal?)
|
|
376
416
|
end
|
|
377
417
|
|
|
378
|
-
# Alias for == to ensure Set functionality works correctly
|
|
379
418
|
alias eql? ==
|
|
380
419
|
|
|
381
|
-
#
|
|
420
|
+
# Returns a hash code for the Identifier.
|
|
382
421
|
#
|
|
383
|
-
# @return [Integer]
|
|
422
|
+
# @return [Integer] Hash code
|
|
384
423
|
def hash
|
|
385
|
-
[
|
|
424
|
+
[type, side, state, terminal?].hash
|
|
386
425
|
end
|
|
387
426
|
|
|
388
|
-
#
|
|
427
|
+
# Returns an inspect string for the Identifier.
|
|
389
428
|
#
|
|
390
|
-
# @
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return if VALID_TYPES.include?(type)
|
|
394
|
-
|
|
395
|
-
raise ::ArgumentError, format(ERROR_INVALID_TYPE, type.inspect)
|
|
429
|
+
# @return [String] Inspect representation
|
|
430
|
+
def inspect
|
|
431
|
+
"#<#{self.class} #{self}>"
|
|
396
432
|
end
|
|
397
433
|
|
|
398
|
-
|
|
399
|
-
#
|
|
400
|
-
# @param side [Symbol] the side to validate
|
|
401
|
-
# @raise [ArgumentError] if invalid
|
|
402
|
-
def self.validate_side(side)
|
|
403
|
-
return if VALID_SIDES.include?(side)
|
|
434
|
+
private
|
|
404
435
|
|
|
405
|
-
|
|
406
|
-
|
|
436
|
+
# ========================================================================
|
|
437
|
+
# Private Validation
|
|
438
|
+
# ========================================================================
|
|
407
439
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
# @param state [Symbol] the state to validate
|
|
411
|
-
# @raise [ArgumentError] if invalid
|
|
412
|
-
def self.validate_state(state)
|
|
413
|
-
return if VALID_STATES.include?(state)
|
|
440
|
+
def validate_type!(type)
|
|
441
|
+
return if Constants::VALID_TYPES.include?(type)
|
|
414
442
|
|
|
415
|
-
raise ::
|
|
443
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_TYPE
|
|
416
444
|
end
|
|
417
445
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
# @param string [String] string to match
|
|
421
|
-
# @return [MatchData] match data
|
|
422
|
-
# @raise [ArgumentError] if string doesn't match
|
|
423
|
-
def self.match_pattern(string)
|
|
424
|
-
matches = PIN_PATTERN.match(string)
|
|
425
|
-
return matches if matches
|
|
446
|
+
def validate_side!(side)
|
|
447
|
+
return if Constants::VALID_SIDES.include?(side)
|
|
426
448
|
|
|
427
|
-
raise ::
|
|
449
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_SIDE
|
|
428
450
|
end
|
|
429
451
|
|
|
430
|
-
|
|
452
|
+
def validate_state!(state)
|
|
453
|
+
return if Constants::VALID_STATES.include?(state)
|
|
431
454
|
|
|
432
|
-
|
|
455
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_STATE
|
|
456
|
+
end
|
|
433
457
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
first_player? ? SECOND_PLAYER : FIRST_PLAYER
|
|
458
|
+
def validate_terminal!(terminal)
|
|
459
|
+
return if ::TrueClass === terminal || ::FalseClass === terminal
|
|
460
|
+
|
|
461
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_TERMINAL
|
|
439
462
|
end
|
|
440
463
|
end
|
|
441
464
|
end
|