Class: Sashite::Gan::Actor

Inherits:
Object
  • Object
show all
Defined in:
lib/sashite/qpi/actor.rb

Overview

Represents a game actor in GAN (General Actor Notation) format.

An actor combines a style identifier (SNN format) with a piece identifier (PIN format) using a colon separator and consistent case encoding to create an unambiguous representation of a game piece within its style context.

GAN represents all four fundamental piece attributes from the Game Protocol:

  • Type → PIN component (ASCII letter choice)

  • Side → Consistent case encoding across both SNN and PIN components

  • State → PIN component (optional prefix modifier)

  • Style → SNN component (explicit style identifier)

All instances are immutable - transformation methods return new instances. This follows the Game Protocol’s actor model with complete attribute representation.

Constant Summary collapse

SEPARATOR =

Colon separator character

":"
FIRST_PLAYER =

Player side constants

:first
SECOND_PLAYER =
:second
NORMAL_STATE =

State constants

:normal
ENHANCED_STATE =
:enhanced
DIMINISHED_STATE =
:diminished
VALID_SIDES =

Valid sides

[FIRST_PLAYER, SECOND_PLAYER].freeze
VALID_STATES =

Valid states

[NORMAL_STATE, ENHANCED_STATE, DIMINISHED_STATE].freeze
VALID_TYPES =

Valid types (A-Z)

(:A..:Z).to_a.freeze
ERROR_INVALID_GAN =

Error messages

"Invalid GAN format: %s"
ERROR_CASE_MISMATCH =
"Case mismatch between SNN and PIN components in GAN string: %s"
ERROR_INVALID_NAME =
"Name must be a symbol with proper capitalization, got: %s"
ERROR_INVALID_TYPE =
"Type must be a symbol from :A to :Z, got: %s"
ERROR_INVALID_SIDE =
"Side must be :first or :second, got: %s"
ERROR_INVALID_STATE =
"State must be :normal, :enhanced, or :diminished, got: %s"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, type, side, state = NORMAL_STATE) ⇒ Actor

Create a new actor instance

Examples:

Actor.new(:Chess, :K, :first, :normal)
Actor.new(:Shogi, :P, :second, :enhanced)

Parameters:

  • name (Symbol)

    style name (with proper capitalization)

  • type (Symbol)

    piece type (:A to :Z)

  • side (Symbol)

    player side (:first or :second)

  • state (Symbol) (defaults to: NORMAL_STATE)

    piece state (:normal, :enhanced, or :diminished)

Raises:

  • (ArgumentError)

    if parameters are invalid



62
63
64
65
66
67
68
69
70
71
72
# File 'lib/sashite/qpi/actor.rb', line 62

def initialize(name, type, side, state = NORMAL_STATE)
  self.class.validate_name(name)
  self.class.validate_type(type)
  self.class.validate_side(side)
  self.class.validate_state(state)

  @style = Snn::Style.new(name, side)
  @piece = Pin::Piece.new(type, side, state)

  freeze
end

Class Method Details

.parse(gan_string) ⇒ Actor

Parse a GAN string into an Actor object

Examples:

Actor.parse("CHESS:K")     # => #<Actor name=:Chess type=:K side=:first state=:normal>
Actor.parse("shogi:+p")    # => #<Actor name=:Shogi type=:P side=:second state=:enhanced>
Actor.parse("XIANGQI:-G")  # => #<Actor name=:Xiangqi type=:G side=:first state=:diminished>

Parameters:

  • gan_string (String)

    GAN notation string

Returns:

  • (Actor)

    new actor instance

Raises:

  • (ArgumentError)

    if the GAN string is invalid or has case mismatch



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/sashite/qpi/actor.rb', line 83

def self.parse(gan_string)
  string_value = String(gan_string)

  # Split into SNN and PIN components
  snn_part, pin_part = string_value.split(SEPARATOR, 2)

  # Validate basic format
  unless snn_part && pin_part && string_value.count(SEPARATOR) == 1
    raise ::ArgumentError, format(ERROR_INVALID_GAN, string_value)
  end

  # Validate case consistency
  validate_case_consistency(snn_part, pin_part, string_value)

  # Parse components - let SNN and PIN handle their own validation
  parsed_style = Snn::Style.parse(snn_part)
  parsed_piece = Pin::Piece.parse(pin_part)

  # Create actor with parsed components
  new(parsed_style.name, parsed_piece.type, parsed_style.side, parsed_piece.state)
end

.valid?(gan_string) ⇒ Boolean

Check if a string is a valid GAN notation

Examples:

Sashite::Gan::Actor.valid?("CHESS:K")      # => true
Sashite::Gan::Actor.valid?("shogi:+p")     # => true
Sashite::Gan::Actor.valid?("Chess:K")      # => false (mixed case in style)
Sashite::Gan::Actor.valid?("CHESS:k")      # => false (case mismatch)
Sashite::Gan::Actor.valid?("CHESS")        # => false (missing piece)
Sashite::Gan::Actor.valid?("")             # => false (empty string)

Parameters:

  • gan_string (String)

    The string to validate

Returns:

  • (Boolean)

    true if valid GAN, false otherwise



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/sashite/qpi/actor.rb', line 117

def self.valid?(gan_string)
  return false unless gan_string.is_a?(::String)
  return false if gan_string.empty?

  # Split into SNN and PIN components
  parts = gan_string.split(SEPARATOR, 2)
  return false unless parts.length == 2

  snn_part, pin_part = parts

  # Validate each component with its specific regex
  return false unless snn_part.match?(Snn::Style::SNN_PATTERN)
  return false unless pin_part.match?(Pin::Piece::PIN_PATTERN)

  # Check case consistency between components
  case_consistent?(snn_part, pin_part)
end

.validate_name(name) ⇒ Object

Validate that the name is a valid symbol with proper capitalization

Parameters:

  • name (Symbol)

    the name to validate

Raises:

  • (ArgumentError)

    if invalid



405
406
407
408
409
# File 'lib/sashite/qpi/actor.rb', line 405

def self.validate_name(name)
  return if valid_name?(name)

  raise ::ArgumentError, format(ERROR_INVALID_NAME, name.inspect)
end

.validate_side(side) ⇒ Object

Validate that the side is a valid symbol

Parameters:

  • side (Symbol)

    the side to validate

Raises:

  • (ArgumentError)

    if invalid



425
426
427
428
429
# File 'lib/sashite/qpi/actor.rb', line 425

def self.validate_side(side)
  return if VALID_SIDES.include?(side)

  raise ::ArgumentError, format(ERROR_INVALID_SIDE, side.inspect)
end

.validate_state(state) ⇒ Object

Validate that the state is a valid symbol

Parameters:

  • state (Symbol)

    the state to validate

Raises:

  • (ArgumentError)

    if invalid



435
436
437
438
439
# File 'lib/sashite/qpi/actor.rb', line 435

def self.validate_state(state)
  return if VALID_STATES.include?(state)

  raise ::ArgumentError, format(ERROR_INVALID_STATE, state.inspect)
end

.validate_type(type) ⇒ Object

Validate that the type is a valid symbol

Parameters:

  • type (Symbol)

    the type to validate

Raises:

  • (ArgumentError)

    if invalid



415
416
417
418
419
# File 'lib/sashite/qpi/actor.rb', line 415

def self.validate_type(type)
  return if VALID_TYPES.include?(type)

  raise ::ArgumentError, format(ERROR_INVALID_TYPE, type.inspect)
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Custom equality comparison

Parameters:

  • other (Object)

    object to compare with

Returns:

  • (Boolean)

    true if actors are equal



385
386
387
388
389
# File 'lib/sashite/qpi/actor.rb', line 385

def ==(other)
  return false unless other.is_a?(self.class)

  name == other.name && type == other.type && side == other.side && state == other.state
end

#diminishActor

Create a new actor with diminished piece state

Examples:

actor.diminish  # CHESS:K => CHESS:-K

Returns:

  • (Actor)

    new actor instance with diminished piece



220
221
222
223
224
# File 'lib/sashite/qpi/actor.rb', line 220

def diminish
  return self if diminished?

  self.class.new(name, type, side, DIMINISHED_STATE)
end

#diminished?Boolean

Check if the actor has diminished state

Returns:

  • (Boolean)

    true if diminished



312
313
314
# File 'lib/sashite/qpi/actor.rb', line 312

def diminished?
  piece.diminished?
end

#enhanceActor

Create a new actor with enhanced piece state

Examples:

actor.enhance  # CHESS:K => CHESS:+K

Returns:

  • (Actor)

    new actor instance with enhanced piece



209
210
211
212
213
# File 'lib/sashite/qpi/actor.rb', line 209

def enhance
  return self if enhanced?

  self.class.new(name, type, side, ENHANCED_STATE)
end

#enhanced?Boolean

Check if the actor has enhanced state

Returns:

  • (Boolean)

    true if enhanced



305
306
307
# File 'lib/sashite/qpi/actor.rb', line 305

def enhanced?
  piece.enhanced?
end

#first_player?Boolean

Check if the actor belongs to the first player

Returns:

  • (Boolean)

    true if first player



326
327
328
# File 'lib/sashite/qpi/actor.rb', line 326

def first_player?
  style.first_player?
end

#flipActor

Create a new actor with opposite ownership (side)

Changes both the style and piece sides consistently. This method is rule-agnostic and preserves all piece modifiers.

Examples:

actor.flip  # CHESS:K => chess:k
enhanced.flip  # CHESS:+K => chess:+k (modifiers preserved)

Returns:

  • (Actor)

    new actor instance with flipped side



246
247
248
# File 'lib/sashite/qpi/actor.rb', line 246

def flip
  self.class.new(name, type, opposite_side, state)
end

#hashInteger

Custom hash implementation for use in collections

Returns:

  • (Integer)

    hash value



397
398
399
# File 'lib/sashite/qpi/actor.rb', line 397

def hash
  [self.class, name, type, side, state].hash
end

#nameSymbol

Get the style name

Examples:

actor.name  # => :Chess

Returns:

  • (Symbol)

    style name (with proper capitalization)



173
174
175
# File 'lib/sashite/qpi/actor.rb', line 173

def name
  style.name
end

#normal?Boolean

Check if the actor has normal state (no modifiers)

Returns:

  • (Boolean)

    true if no modifiers are present



319
320
321
# File 'lib/sashite/qpi/actor.rb', line 319

def normal?
  piece.normal?
end

#normalizeActor

Create a new actor with normal piece state (no modifiers)

Examples:

actor.normalize  # CHESS:+K => CHESS:K

Returns:

  • (Actor)

    new actor instance with normalized piece



231
232
233
234
235
# File 'lib/sashite/qpi/actor.rb', line 231

def normalize
  return self if normal?

  self.class.new(name, type, side, NORMAL_STATE)
end

#same_name?(other) ⇒ Boolean

Check if this actor has the same style name as another

Examples:

chess1.same_name?(chess2)  # (CHESS:K) and (chess:Q) => true

Parameters:

  • other (Actor)

    actor to compare with

Returns:

  • (Boolean)

    true if same style name



343
344
345
346
347
# File 'lib/sashite/qpi/actor.rb', line 343

def same_name?(other)
  return false unless other.is_a?(self.class)

  name == other.name
end

#same_side?(other) ⇒ Boolean

Check if this actor belongs to the same side as another

Parameters:

  • other (Actor)

    actor to compare with

Returns:

  • (Boolean)

    true if same side



365
366
367
368
369
# File 'lib/sashite/qpi/actor.rb', line 365

def same_side?(other)
  return false unless other.is_a?(self.class)

  side == other.side
end

#same_state?(other) ⇒ Boolean

Check if this actor has the same state as another

Parameters:

  • other (Actor)

    actor to compare with

Returns:

  • (Boolean)

    true if same piece state



375
376
377
378
379
# File 'lib/sashite/qpi/actor.rb', line 375

def same_state?(other)
  return false unless other.is_a?(self.class)

  state == other.state
end

#same_type?(other) ⇒ Boolean

Check if this actor is the same type as another (ignoring name, side, and state)

Examples:

king1.same_type?(king2)  # (CHESS:K) and (SHOGI:k) => true

Parameters:

  • other (Actor)

    actor to compare with

Returns:

  • (Boolean)

    true if same piece type



355
356
357
358
359
# File 'lib/sashite/qpi/actor.rb', line 355

def same_type?(other)
  return false unless other.is_a?(self.class)

  type == other.type
end

#second_player?Boolean

Check if the actor belongs to the second player

Returns:

  • (Boolean)

    true if second player



333
334
335
# File 'lib/sashite/qpi/actor.rb', line 333

def second_player?
  style.second_player?
end

#sideSymbol

Get the player side

Examples:

actor.side  # => :first

Returns:

  • (Symbol)

    player side (:first or :second)



191
192
193
# File 'lib/sashite/qpi/actor.rb', line 191

def side
  style.side
end

#stateSymbol

Get the piece state

Examples:

actor.state  # => :normal

Returns:

  • (Symbol)

    piece state (:normal, :enhanced, or :diminished)



200
201
202
# File 'lib/sashite/qpi/actor.rb', line 200

def state
  piece.state
end

#to_pinString

Convert the actor to its PIN representation (piece component only)

Examples:

actor.to_pin  # => "K"
promoted_actor.to_pin  # => "+p"
diminished_actor.to_pin  # => "-G"

Returns:

  • (String)

    PIN notation string for the piece component



153
154
155
# File 'lib/sashite/qpi/actor.rb', line 153

def to_pin
  piece.to_s
end

#to_sString

Convert the actor to its GAN string representation

Examples:

actor.to_s  # => "CHESS:K"
actor.to_s  # => "shogi:+p"
actor.to_s  # => "XIANGQI:-G"

Returns:

  • (String)

    GAN notation string



142
143
144
# File 'lib/sashite/qpi/actor.rb', line 142

def to_s
  "#{style}#{SEPARATOR}#{piece}"
end

#to_snnString

Convert the actor to its SNN representation (style component only)

Examples:

actor.to_snn  # => "CHESS"
black_actor.to_snn  # => "chess"
xiangqi_actor.to_snn  # => "XIANGQI"

Returns:

  • (String)

    SNN notation string for the style component



164
165
166
# File 'lib/sashite/qpi/actor.rb', line 164

def to_snn
  style.to_s
end

#typeSymbol

Get the piece type

Examples:

actor.type  # => :K

Returns:

  • (Symbol)

    piece type (:A to :Z, always uppercase)



182
183
184
# File 'lib/sashite/qpi/actor.rb', line 182

def type
  piece.type
end

#with_name(new_name) ⇒ Actor

Create a new actor with a different style name (keeping same type, side, and state)

Examples:

actor.with_name(:Shogi)  # CHESS:K => SHOGI:K

Parameters:

  • new_name (Symbol)

    new style name (with proper capitalization)

Returns:

  • (Actor)

    new actor instance with different style name



256
257
258
259
260
261
# File 'lib/sashite/qpi/actor.rb', line 256

def with_name(new_name)
  self.class.validate_name(new_name)
  return self if name == new_name

  self.class.new(new_name, type, side, state)
end

#with_side(new_side) ⇒ Actor

Create a new actor with a different side (keeping same name, type, and state)

Examples:

actor.with_side(:second)  # CHESS:K => chess:k

Parameters:

  • new_side (Symbol)

    :first or :second

Returns:

  • (Actor)

    new actor instance with different side



282
283
284
285
286
287
# File 'lib/sashite/qpi/actor.rb', line 282

def with_side(new_side)
  self.class.validate_side(new_side)
  return self if side == new_side

  self.class.new(name, type, new_side, state)
end

#with_state(new_state) ⇒ Actor

Create a new actor with a different piece state (keeping same name, type, and side)

Examples:

actor.with_state(:enhanced)  # CHESS:K => CHESS:+K

Parameters:

  • new_state (Symbol)

    :normal, :enhanced, or :diminished

Returns:

  • (Actor)

    new actor instance with different piece state



295
296
297
298
299
300
# File 'lib/sashite/qpi/actor.rb', line 295

def with_state(new_state)
  self.class.validate_state(new_state)
  return self if state == new_state

  self.class.new(name, type, side, new_state)
end

#with_type(new_type) ⇒ Actor

Create a new actor with a different piece type (keeping same name, side, and state)

Examples:

actor.with_type(:Q)  # CHESS:K => CHESS:Q

Parameters:

  • new_type (Symbol)

    new piece type (:A to :Z)

Returns:

  • (Actor)

    new actor instance with different piece type



269
270
271
272
273
274
# File 'lib/sashite/qpi/actor.rb', line 269

def with_type(new_type)
  self.class.validate_type(new_type)
  return self if type == new_type

  self.class.new(name, new_type, side, state)
end