sashite-sin 2.1.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 +201 -0
- data/README.md +143 -261
- data/lib/sashite/sin/constants.rb +38 -0
- data/lib/sashite/sin/errors/argument/messages.rb +45 -0
- data/lib/sashite/sin/errors/argument.rb +21 -0
- data/lib/sashite/sin/errors.rb +19 -0
- data/lib/sashite/sin/identifier.rb +155 -311
- data/lib/sashite/sin/parser.rb +137 -0
- data/lib/sashite/sin.rb +48 -166
- metadata +14 -12
- data/LICENSE.md +0 -22
|
@@ -1,405 +1,249 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
|
|
3
6
|
module Sashite
|
|
4
7
|
module Sin
|
|
5
|
-
# Represents
|
|
6
|
-
#
|
|
7
|
-
# ## Concept
|
|
8
|
-
#
|
|
9
|
-
# SIN addresses the fundamental need to identify which style system governs piece behavior
|
|
10
|
-
# while simultaneously indicating which player controls pieces of that style. In cross-style
|
|
11
|
-
# scenarios where different players use different game traditions, this dual encoding becomes
|
|
12
|
-
# essential for unambiguous piece identification.
|
|
13
|
-
#
|
|
14
|
-
# ## Dual-Purpose Encoding
|
|
15
|
-
#
|
|
16
|
-
# Each SIN identifier serves two functions:
|
|
17
|
-
# - **Style Family Identification**: The family choice indicates which rule system applies
|
|
18
|
-
# - **Player Assignment**: The side indicates which player uses this style as their native system
|
|
19
|
-
#
|
|
20
|
-
# ## Format Structure
|
|
21
|
-
#
|
|
22
|
-
# An identifier consists of a single ASCII letter with case-based side encoding:
|
|
23
|
-
# - Uppercase letter: first player (A, B, C, ..., Z)
|
|
24
|
-
# - Lowercase letter: second player (a, b, c, ..., z)
|
|
25
|
-
#
|
|
26
|
-
# The letter representation combines two distinct semantic components:
|
|
27
|
-
# - **Style Family**: The underlying ASCII character (A-Z), representing the game tradition or rule system
|
|
28
|
-
# - **Player Assignment**: The case of the character (uppercase/lowercase), representing which player uses this style
|
|
8
|
+
# Represents a parsed SIN (Style Identifier Notation) identifier.
|
|
29
9
|
#
|
|
30
|
-
#
|
|
31
|
-
# -
|
|
32
|
-
# -
|
|
33
|
-
# - Family :S + Side :first → Letter "S" (Shōgi, First player)
|
|
34
|
-
# - Family :S + Side :second → Letter "s" (Shōgi, Second player)
|
|
10
|
+
# An Identifier encodes two attributes:
|
|
11
|
+
# - Style: the piece style (A-Z as uppercase symbol)
|
|
12
|
+
# - Side: the player side (:first or :second)
|
|
35
13
|
#
|
|
36
|
-
#
|
|
14
|
+
# Instances are immutable (frozen after creation).
|
|
37
15
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
16
|
+
# @example Creating identifiers
|
|
17
|
+
# sin = Identifier.new(:C, :first)
|
|
18
|
+
# sin = Identifier.new(:S, :second)
|
|
41
19
|
#
|
|
42
|
-
#
|
|
20
|
+
# @example String conversion
|
|
21
|
+
# Identifier.new(:C, :first).to_s # => "C"
|
|
22
|
+
# Identifier.new(:C, :second).to_s # => "c"
|
|
43
23
|
#
|
|
44
|
-
#
|
|
45
|
-
# This follows the SIN Specification v1.0.0 functional design principles.
|
|
46
|
-
#
|
|
47
|
-
# @example Basic usage with traditional game styles
|
|
48
|
-
# # Chess family identifiers
|
|
49
|
-
# chess_white = Sashite::Sin::Identifier.parse("C") # Family :C, Side :first
|
|
50
|
-
# chess_black = Sashite::Sin::Identifier.parse("c") # Family :C, Side :second
|
|
51
|
-
#
|
|
52
|
-
# # Shōgi family identifiers
|
|
53
|
-
# shogi_sente = Sashite::Sin::Identifier.parse("S") # Family :S, Side :first
|
|
54
|
-
# shogi_gote = Sashite::Sin::Identifier.parse("s") # Family :S, Side :second
|
|
55
|
-
#
|
|
56
|
-
# @example Dual-purpose encoding demonstration
|
|
57
|
-
# identifier = Sashite::Sin::Identifier.parse("C")
|
|
58
|
-
# identifier.family # => :C (Style Family)
|
|
59
|
-
# identifier.side # => :first (Player Assignment)
|
|
60
|
-
# identifier.letter # => "C" (Combined representation)
|
|
61
|
-
#
|
|
62
|
-
# @example Cross-style scenarios
|
|
63
|
-
# # Different families in one match (requires compatible board structures)
|
|
64
|
-
# chess_style = Sashite::Sin::Identifier.parse("C") # First player uses Chess family
|
|
65
|
-
# ogi_style = Sashite::Sin::Identifier.parse("o") # Second player uses Ōgi family
|
|
66
|
-
#
|
|
67
|
-
# @see https://sashite.dev/specs/sin/1.0.0/ SIN Specification v1.0.0
|
|
24
|
+
# @see https://sashite.dev/specs/sin/1.0.0/
|
|
68
25
|
class Identifier
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
SIN_PATTERN = /\A[A-Za-z]\z/
|
|
72
|
-
|
|
73
|
-
# Player side constants following SIN v1.0.0 two-player constraint
|
|
74
|
-
FIRST_PLAYER = :first
|
|
75
|
-
SECOND_PLAYER = :second
|
|
76
|
-
|
|
77
|
-
# Valid families (A-Z)
|
|
78
|
-
VALID_FAMILIES = (:A..:Z).to_a.freeze
|
|
26
|
+
# Valid style symbols (A-Z).
|
|
27
|
+
VALID_STYLES = Constants::VALID_STYLES
|
|
79
28
|
|
|
80
|
-
# Valid
|
|
81
|
-
VALID_SIDES =
|
|
29
|
+
# Valid side symbols.
|
|
30
|
+
VALID_SIDES = Constants::VALID_SIDES
|
|
82
31
|
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
ERROR_INVALID_FAMILY = "Family must be a symbol from :A to :Z representing Style Family, got: %s"
|
|
86
|
-
ERROR_INVALID_SIDE = "Side must be :first or :second following SIN two-player constraint, got: %s"
|
|
32
|
+
# @return [Symbol] Piece style (:A to :Z, always uppercase)
|
|
33
|
+
attr_reader :style
|
|
87
34
|
|
|
88
|
-
# @return [Symbol]
|
|
89
|
-
# This represents the Style Family component - the game tradition or rule system
|
|
90
|
-
attr_reader :family
|
|
91
|
-
|
|
92
|
-
# @return [Symbol] the player side (:first or :second)
|
|
93
|
-
# This represents the Player Assignment component
|
|
35
|
+
# @return [Symbol] Player side (:first or :second)
|
|
94
36
|
attr_reader :side
|
|
95
37
|
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
# @param family [Symbol] style family (:A to :Z representing Style Family)
|
|
99
|
-
# @param side [Symbol] player side (:first or :second representing Player Assignment)
|
|
100
|
-
# @raise [ArgumentError] if parameters are invalid
|
|
38
|
+
# Creates a new Identifier instance.
|
|
101
39
|
#
|
|
102
|
-
# @
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
40
|
+
# @param style [Symbol] Piece style (:A to :Z)
|
|
41
|
+
# @param side [Symbol] Player side (:first or :second)
|
|
42
|
+
# @return [Identifier] A new frozen Identifier instance
|
|
43
|
+
# @raise [Errors::Argument] If any attribute is invalid
|
|
106
44
|
#
|
|
107
|
-
# @example
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
@family = family
|
|
45
|
+
# @example
|
|
46
|
+
# Identifier.new(:C, :first)
|
|
47
|
+
# Identifier.new(:S, :second)
|
|
48
|
+
def initialize(style, side)
|
|
49
|
+
validate_style!(style)
|
|
50
|
+
validate_side!(side)
|
|
51
|
+
|
|
52
|
+
@style = style
|
|
117
53
|
@side = side
|
|
118
54
|
|
|
119
55
|
freeze
|
|
120
56
|
end
|
|
121
57
|
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
# and case (Player Assignment):
|
|
126
|
-
# - Uppercase letter → Style Family + First player
|
|
127
|
-
# - Lowercase letter → Style Family + Second player
|
|
128
|
-
#
|
|
129
|
-
# @param sin_string [String] SIN notation string (single ASCII letter)
|
|
130
|
-
# @return [Identifier] parsed identifier object with Family and Side attributes
|
|
131
|
-
# @raise [ArgumentError] if the SIN string is invalid
|
|
132
|
-
#
|
|
133
|
-
# @example Parse SIN strings with case-based Player Assignment inference
|
|
134
|
-
# Sashite::Sin::Identifier.parse("C") # => Family=:C, Side=:first (Chess, White)
|
|
135
|
-
# Sashite::Sin::Identifier.parse("c") # => Family=:C, Side=:second (Chess, Black)
|
|
136
|
-
# Sashite::Sin::Identifier.parse("S") # => Family=:S, Side=:first (Shōgi, Sente)
|
|
137
|
-
# Sashite::Sin::Identifier.parse("s") # => Family=:S, Side=:second (Shōgi, Gote)
|
|
138
|
-
#
|
|
139
|
-
# @example Traditional game styles from SIN Examples
|
|
140
|
-
# # Chess (8×8 board)
|
|
141
|
-
# chess_white = Sashite::Sin::Identifier.parse("C") # First player (White pieces)
|
|
142
|
-
# chess_black = Sashite::Sin::Identifier.parse("c") # Second player (Black pieces)
|
|
143
|
-
#
|
|
144
|
-
# # Xiangqi (9×10 board)
|
|
145
|
-
# xiangqi_red = Sashite::Sin::Identifier.parse("X") # First player (Red pieces)
|
|
146
|
-
# xiangqi_black = Sashite::Sin::Identifier.parse("x") # Second player (Black pieces)
|
|
147
|
-
def self.parse(sin_string)
|
|
148
|
-
string_value = String(sin_string)
|
|
149
|
-
validate_sin_string(string_value)
|
|
150
|
-
|
|
151
|
-
# Extract Style Family (case-insensitive) and Player Assignment (case-sensitive)
|
|
152
|
-
family_symbol = string_value.upcase.to_sym
|
|
153
|
-
identifier_side = string_value == string_value.upcase ? FIRST_PLAYER : SECOND_PLAYER
|
|
154
|
-
|
|
155
|
-
new(family_symbol, identifier_side)
|
|
156
|
-
end
|
|
58
|
+
# ========================================================================
|
|
59
|
+
# String Conversion
|
|
60
|
+
# ========================================================================
|
|
157
61
|
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
# Validates against the SIN grammar:
|
|
161
|
-
# <sin> ::= <uppercase-letter> | <lowercase-letter>
|
|
162
|
-
#
|
|
163
|
-
# @param sin_string [String] the string to validate
|
|
164
|
-
# @return [Boolean] true if valid SIN, false otherwise
|
|
165
|
-
#
|
|
166
|
-
# @example Validate SIN strings against specification
|
|
167
|
-
# Sashite::Sin::Identifier.valid?("C") # => true (Chess first player)
|
|
168
|
-
# Sashite::Sin::Identifier.valid?("c") # => true (Chess second player)
|
|
169
|
-
# Sashite::Sin::Identifier.valid?("CHESS") # => false (multi-character)
|
|
170
|
-
# Sashite::Sin::Identifier.valid?("1") # => false (not ASCII letter)
|
|
171
|
-
def self.valid?(sin_string)
|
|
172
|
-
return false unless sin_string.is_a?(::String)
|
|
173
|
-
|
|
174
|
-
sin_string.match?(SIN_PATTERN)
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Convert the identifier to its SIN string representation
|
|
178
|
-
#
|
|
179
|
-
# Returns the canonical SIN notation with proper case encoding for Player Assignment.
|
|
62
|
+
# Returns the SIN string representation.
|
|
180
63
|
#
|
|
181
|
-
# @return [String]
|
|
64
|
+
# @return [String] The single-character SIN string
|
|
182
65
|
#
|
|
183
|
-
# @example
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
# shogi_first.to_s # => "S" (Shōgi family, first player)
|
|
66
|
+
# @example
|
|
67
|
+
# Identifier.new(:C, :first).to_s # => "C"
|
|
68
|
+
# Identifier.new(:C, :second).to_s # => "c"
|
|
187
69
|
def to_s
|
|
188
70
|
letter
|
|
189
71
|
end
|
|
190
72
|
|
|
191
|
-
#
|
|
73
|
+
# Returns the letter component of the SIN.
|
|
192
74
|
#
|
|
193
|
-
# @return [String]
|
|
75
|
+
# @return [String] Uppercase for first player, lowercase for second
|
|
194
76
|
#
|
|
195
|
-
# @example
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
# shogi_first.letter # => "S" (Shōgi family, first player)
|
|
77
|
+
# @example
|
|
78
|
+
# Identifier.new(:C, :first).letter # => "C"
|
|
79
|
+
# Identifier.new(:C, :second).letter # => "c"
|
|
199
80
|
def letter
|
|
200
|
-
|
|
81
|
+
base = String(style)
|
|
82
|
+
|
|
83
|
+
case side
|
|
84
|
+
when :first then base.upcase
|
|
85
|
+
when :second then base.downcase
|
|
86
|
+
end
|
|
201
87
|
end
|
|
202
88
|
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
# @return [Identifier] new immutable identifier instance with flipped Player Assignment
|
|
89
|
+
# ========================================================================
|
|
90
|
+
# Side Transformations
|
|
91
|
+
# ========================================================================
|
|
92
|
+
|
|
93
|
+
# Returns a new Identifier with the opposite side.
|
|
210
94
|
#
|
|
211
|
-
# @
|
|
212
|
-
# chess_white = Sashite::Sin::Identifier.parse("C")
|
|
213
|
-
# chess_black = chess_white.flip # => Family=:C, Side=:second
|
|
95
|
+
# @return [Identifier] A new Identifier with flipped side
|
|
214
96
|
#
|
|
215
|
-
#
|
|
216
|
-
#
|
|
97
|
+
# @example
|
|
98
|
+
# sin = Identifier.new(:C, :first)
|
|
99
|
+
# sin.flip.to_s # => "c"
|
|
217
100
|
def flip
|
|
218
|
-
|
|
101
|
+
new_side = first_player? ? :second : :first
|
|
102
|
+
self.class.new(style, new_side)
|
|
219
103
|
end
|
|
220
104
|
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
|
|
225
|
-
#
|
|
226
|
-
# @return [Identifier] new immutable identifier instance with different Style Family
|
|
105
|
+
# ========================================================================
|
|
106
|
+
# Attribute Transformations
|
|
107
|
+
# ========================================================================
|
|
108
|
+
|
|
109
|
+
# Returns a new Identifier with a different style.
|
|
227
110
|
#
|
|
228
|
-
# @
|
|
229
|
-
#
|
|
230
|
-
#
|
|
111
|
+
# @param new_style [Symbol] The new piece style (:A to :Z)
|
|
112
|
+
# @return [Identifier] A new Identifier with the specified style
|
|
113
|
+
# @raise [Errors::Argument] If the style is invalid
|
|
231
114
|
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return self if
|
|
115
|
+
# @example
|
|
116
|
+
# sin = Identifier.new(:C, :first)
|
|
117
|
+
# sin.with_style(:S).to_s # => "S"
|
|
118
|
+
def with_style(new_style)
|
|
119
|
+
return self if style.equal?(new_style)
|
|
237
120
|
|
|
238
|
-
self.class.new(
|
|
121
|
+
self.class.new(new_style, side)
|
|
239
122
|
end
|
|
240
123
|
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
# Changes the Player Assignment component while preserving Style Family.
|
|
124
|
+
# Returns a new Identifier with a different side.
|
|
244
125
|
#
|
|
245
|
-
# @param new_side [Symbol] :first or :second
|
|
246
|
-
# @return [Identifier] new
|
|
126
|
+
# @param new_side [Symbol] The new side (:first or :second)
|
|
127
|
+
# @return [Identifier] A new Identifier with the specified side
|
|
128
|
+
# @raise [Errors::Argument] If the side is invalid
|
|
247
129
|
#
|
|
248
|
-
# @example
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
# shogi_sente = Sashite::Sin::Identifier.parse("S") # Shōgi, first player
|
|
253
|
-
# shogi_gote = shogi_sente.with_side(:second) # Shōgi, second player
|
|
130
|
+
# @example
|
|
131
|
+
# sin = Identifier.new(:C, :first)
|
|
132
|
+
# sin.with_side(:second).to_s # => "c"
|
|
254
133
|
def with_side(new_side)
|
|
255
|
-
self.
|
|
256
|
-
return self if side == new_side
|
|
134
|
+
return self if side.equal?(new_side)
|
|
257
135
|
|
|
258
|
-
self.class.new(
|
|
136
|
+
self.class.new(style, new_side)
|
|
259
137
|
end
|
|
260
138
|
|
|
261
|
-
#
|
|
139
|
+
# ========================================================================
|
|
140
|
+
# Side Queries
|
|
141
|
+
# ========================================================================
|
|
142
|
+
|
|
143
|
+
# Checks if the Identifier belongs to the first player.
|
|
262
144
|
#
|
|
263
|
-
# @return [Boolean] true if first player
|
|
145
|
+
# @return [Boolean] true if first player
|
|
264
146
|
#
|
|
265
|
-
# @example
|
|
266
|
-
#
|
|
267
|
-
# Sashite::Sin::Identifier.parse("c").first_player? # => false
|
|
147
|
+
# @example
|
|
148
|
+
# Identifier.new(:C, :first).first_player? # => true
|
|
268
149
|
def first_player?
|
|
269
|
-
side
|
|
150
|
+
side.equal?(:first)
|
|
270
151
|
end
|
|
271
152
|
|
|
272
|
-
#
|
|
153
|
+
# Checks if the Identifier belongs to the second player.
|
|
273
154
|
#
|
|
274
|
-
# @return [Boolean] true if second player
|
|
155
|
+
# @return [Boolean] true if second player
|
|
275
156
|
#
|
|
276
|
-
# @example
|
|
277
|
-
#
|
|
278
|
-
# Sashite::Sin::Identifier.parse("C").second_player? # => false
|
|
157
|
+
# @example
|
|
158
|
+
# Identifier.new(:C, :second).second_player? # => true
|
|
279
159
|
def second_player?
|
|
280
|
-
side
|
|
160
|
+
side.equal?(:second)
|
|
281
161
|
end
|
|
282
162
|
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
#
|
|
288
|
-
# @param other [Identifier] identifier to compare with
|
|
289
|
-
# @return [Boolean] true if both identifiers use the same Style Family
|
|
163
|
+
# ========================================================================
|
|
164
|
+
# Comparison Queries
|
|
165
|
+
# ========================================================================
|
|
166
|
+
|
|
167
|
+
# Checks if two Identifiers have the same style.
|
|
290
168
|
#
|
|
291
|
-
# @
|
|
292
|
-
#
|
|
293
|
-
# chess_black = Sashite::Sin::Identifier.parse("c") # Chess, second player
|
|
294
|
-
# shogi_white = Sashite::Sin::Identifier.parse("S") # Shōgi, first player
|
|
169
|
+
# @param other [Identifier] The other Identifier to compare
|
|
170
|
+
# @return [Boolean] true if same style
|
|
295
171
|
#
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
172
|
+
# @example
|
|
173
|
+
# sin1 = Identifier.new(:C, :first)
|
|
174
|
+
# sin2 = Identifier.new(:C, :second)
|
|
175
|
+
# sin1.same_style?(sin2) # => true
|
|
176
|
+
def same_style?(other)
|
|
177
|
+
style.equal?(other.style)
|
|
302
178
|
end
|
|
303
179
|
|
|
304
|
-
#
|
|
305
|
-
#
|
|
306
|
-
# Compares the Player Assignment component of identifiers across different Style Families.
|
|
307
|
-
# This is useful for grouping pieces by controlling player in multi-style games.
|
|
180
|
+
# Checks if two Identifiers have the same side.
|
|
308
181
|
#
|
|
309
|
-
# @param other [Identifier]
|
|
310
|
-
# @return [Boolean] true if
|
|
182
|
+
# @param other [Identifier] The other Identifier to compare
|
|
183
|
+
# @return [Boolean] true if same side
|
|
311
184
|
#
|
|
312
|
-
# @example
|
|
313
|
-
#
|
|
314
|
-
#
|
|
315
|
-
#
|
|
316
|
-
#
|
|
317
|
-
# chess_white.same_side?(shogi_white) # => true (both first player)
|
|
318
|
-
# chess_white.same_side?(chess_black) # => false (different players)
|
|
185
|
+
# @example
|
|
186
|
+
# sin1 = Identifier.new(:C, :first)
|
|
187
|
+
# sin2 = Identifier.new(:S, :first)
|
|
188
|
+
# sin1.same_side?(sin2) # => true
|
|
319
189
|
def same_side?(other)
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
side == other.side
|
|
190
|
+
side.equal?(other.side)
|
|
323
191
|
end
|
|
324
192
|
|
|
325
|
-
#
|
|
326
|
-
#
|
|
327
|
-
#
|
|
328
|
-
# @param other [Identifier] identifier to compare with
|
|
329
|
-
# @return [Boolean] true if both identifiers use the same Style Family
|
|
330
|
-
def same_letter?(other)
|
|
331
|
-
same_family?(other)
|
|
332
|
-
end
|
|
193
|
+
# ========================================================================
|
|
194
|
+
# Equality
|
|
195
|
+
# ========================================================================
|
|
333
196
|
|
|
334
|
-
#
|
|
197
|
+
# Checks equality with another Identifier.
|
|
335
198
|
#
|
|
336
|
-
#
|
|
199
|
+
# @param other [Object] The object to compare
|
|
200
|
+
# @return [Boolean] true if equal
|
|
337
201
|
#
|
|
338
|
-
# @
|
|
339
|
-
#
|
|
340
|
-
#
|
|
341
|
-
#
|
|
342
|
-
# id1 = Sashite::Sin::Identifier.parse("C")
|
|
343
|
-
# id2 = Sashite::Sin::Identifier.parse("C")
|
|
344
|
-
# id3 = Sashite::Sin::Identifier.parse("c")
|
|
345
|
-
#
|
|
346
|
-
# id1 == id2 # => true (identical Family and Side)
|
|
347
|
-
# id1 == id3 # => false (different Player Assignment)
|
|
202
|
+
# @example
|
|
203
|
+
# sin1 = Identifier.new(:C, :first)
|
|
204
|
+
# sin2 = Identifier.new(:C, :first)
|
|
205
|
+
# sin1 == sin2 # => true
|
|
348
206
|
def ==(other)
|
|
349
|
-
return false unless
|
|
207
|
+
return false unless self.class === other
|
|
350
208
|
|
|
351
|
-
|
|
209
|
+
style.equal?(other.style) && side.equal?(other.side)
|
|
352
210
|
end
|
|
353
211
|
|
|
354
|
-
# Alias for == to ensure Set functionality works correctly
|
|
355
212
|
alias eql? ==
|
|
356
213
|
|
|
357
|
-
#
|
|
214
|
+
# Returns a hash code for the Identifier.
|
|
358
215
|
#
|
|
359
|
-
# @return [Integer]
|
|
216
|
+
# @return [Integer] Hash code
|
|
360
217
|
def hash
|
|
361
|
-
[
|
|
218
|
+
[style, side].hash
|
|
362
219
|
end
|
|
363
220
|
|
|
364
|
-
#
|
|
221
|
+
# Returns an inspect string for the Identifier.
|
|
365
222
|
#
|
|
366
|
-
# @
|
|
367
|
-
#
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
223
|
+
# @return [String] Inspect representation
|
|
224
|
+
#
|
|
225
|
+
# @example
|
|
226
|
+
# Identifier.new(:C, :first).inspect # => "#<Sashite::Sin::Identifier C>"
|
|
227
|
+
def inspect
|
|
228
|
+
"#<#{self.class} #{self}>"
|
|
372
229
|
end
|
|
373
230
|
|
|
374
|
-
|
|
375
|
-
#
|
|
376
|
-
# @param side [Symbol] the side to validate
|
|
377
|
-
# @raise [ArgumentError] if invalid
|
|
378
|
-
def self.validate_side(side)
|
|
379
|
-
return if VALID_SIDES.include?(side)
|
|
231
|
+
private
|
|
380
232
|
|
|
381
|
-
|
|
382
|
-
|
|
233
|
+
# ========================================================================
|
|
234
|
+
# Private Validation
|
|
235
|
+
# ========================================================================
|
|
383
236
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
# @param string [String] string to validate
|
|
387
|
-
# @raise [ArgumentError] if string doesn't match SIN pattern
|
|
388
|
-
def self.validate_sin_string(string)
|
|
389
|
-
return if string.match?(SIN_PATTERN)
|
|
237
|
+
def validate_style!(style)
|
|
238
|
+
return if ::Symbol === style && Constants::VALID_STYLES.include?(style)
|
|
390
239
|
|
|
391
|
-
raise ::
|
|
240
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_STYLE
|
|
392
241
|
end
|
|
393
242
|
|
|
394
|
-
|
|
243
|
+
def validate_side!(side)
|
|
244
|
+
return if ::Symbol === side && Constants::VALID_SIDES.include?(side)
|
|
395
245
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
# Get the opposite Player Assignment
|
|
399
|
-
#
|
|
400
|
-
# @return [Symbol] the opposite side
|
|
401
|
-
def opposite_side
|
|
402
|
-
first_player? ? SECOND_PLAYER : FIRST_PLAYER
|
|
246
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_SIDE
|
|
403
247
|
end
|
|
404
248
|
end
|
|
405
249
|
end
|