Class: Sashite::Pcn::Game::Sides

Inherits:
Object
  • Object
show all
Defined in:
lib/sashite/pcn/game/sides.rb,
lib/sashite/pcn/game/sides/player.rb

Overview

Represents player information for both sides of a game

Manages two Player objects (first and second) with support for player metadata, styles, and time control settings. Both players are optional and default to empty player objects.

Examples:

With both players and time control

sides = Sides.new(
  first: {
    name: "Carlsen",
    elo: 2830,
    style: "CHESS",
    periods: [
      { time: 5400, moves: 40, inc: 0 },
      { time: 1800, moves: nil, inc: 30 }
    ]
  },
  second: {
    name: "Nakamura",
    elo: 2794,
    style: "chess",
    periods: [
      { time: 5400, moves: 40, inc: 0 },
      { time: 1800, moves: nil, inc: 30 }
    ]
  }
)

With Fischer time control (5+3 blitz)

sides = Sides.new(
  first: {
    name: "Alice",
    periods: [{ time: 300, moves: nil, inc: 3 }]
  },
  second: {
    name: "Bob",
    periods: [{ time: 300, moves: nil, inc: 3 }]
  }
)

With only first player

sides = Sides.new(
  first: { name: "Player 1", style: "CHESS" }
)

Empty sides (no player information)

sides = Sides.new  # Both players default to empty

Defined Under Namespace

Classes: Player

Constant Summary collapse

ERROR_INVALID_FIRST =

Error messages

"first must be a hash"
ERROR_INVALID_SECOND =
"second must be a hash"

Instance Method Summary collapse

Constructor Details

#initialize(first: {}, second: {}) ⇒ Sides

Create a new Sides instance

Parameters:

  • first (Hash) (defaults to: {})

    first player information (defaults to {})

  • second (Hash) (defaults to: {})

    second player information (defaults to {})

Raises:

  • (ArgumentError)

    if parameters are not hashes



65
66
67
68
69
70
71
72
73
# File 'lib/sashite/pcn/game/sides.rb', line 65

def initialize(first: {}, second: {})
  raise ::ArgumentError, ERROR_INVALID_FIRST unless first.is_a?(::Hash)
  raise ::ArgumentError, ERROR_INVALID_SECOND unless second.is_a?(::Hash)

  @first = Player.new(**first.transform_keys(&:to_sym))
  @second = Player.new(**second.transform_keys(&:to_sym))

  freeze
end

Instance Method Details

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

Check equality with another Sides object

Parameters:

  • other (Object)

    object to compare

Returns:

  • (Boolean)

    true if equal



348
349
350
351
352
# File 'lib/sashite/pcn/game/sides.rb', line 348

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

  @first == other.first && @second == other.second
end

#[](index) ⇒ Player?

Access player by index

Examples:

sides[0]  # => first player
sides[1]  # => second player
sides[2]  # => nil

Parameters:

  • index (Integer)

    0 for first, 1 for second

Returns:

  • (Player, nil)

    player or nil if index out of bounds



108
109
110
111
112
113
# File 'lib/sashite/pcn/game/sides.rb', line 108

def [](index)
  case index
  when 0 then @first
  when 1 then @second
  end
end

#both_have_time_control?Boolean

Check if both players have time control

Examples:

sides.both_have_time_control?  # => true

Returns:

  • (Boolean)

    true if both have periods defined



166
167
168
# File 'lib/sashite/pcn/game/sides.rb', line 166

def both_have_time_control?
  @first.has_time_control? && @second.has_time_control?
end

#complete?Boolean

Check if both players have information

Examples:

sides.complete?  # => true

Returns:

  • (Boolean)

    true if both players have data



146
147
148
# File 'lib/sashite/pcn/game/sides.rb', line 146

def complete?
  !@first.empty? && !@second.empty?
end

#each {|player| ... } ⇒ Enumerator

Iterate over both players

Examples:

sides.each { |player| puts player.name }
sides.each.with_index { |player, i| puts "Player #{i+1}: #{player.name}" }

Yields:

  • (player)

    yields each player

Returns:

  • (Enumerator)

    if no block given



264
265
266
267
268
269
# File 'lib/sashite/pcn/game/sides.rb', line 264

def each
  return enum_for(:each) unless block_given?

  yield @first
  yield @second
end

#elosArray<Integer>

Get both players’ Elo ratings

Examples:

sides.elos  # => [2830, 2794]

Returns:

  • (Array<Integer>)

    array of [first_elo, second_elo]



226
227
228
# File 'lib/sashite/pcn/game/sides.rb', line 226

def elos
  [@first.elo, @second.elo]
end

#empty?Boolean

Check if no player information is present

Examples:

sides.empty?  # => true

Returns:

  • (Boolean)

    true if both players are empty



136
137
138
# File 'lib/sashite/pcn/game/sides.rb', line 136

def empty?
  @first.empty? && @second.empty?
end

#firstPlayer

Get first player information

Examples:

player = sides.first
player.name     # => "Carlsen"
player.periods  # => [{ time: 5400, moves: 40, inc: 0 }, ...]

Returns:

  • (Player)

    first player (may be empty)



83
84
85
# File 'lib/sashite/pcn/game/sides.rb', line 83

def first
  @first
end

#has_player?(side) ⇒ Boolean

Check if a specific side is present

Examples:

sides.has_player?(:first)   # => true
sides.has_player?("second")  # => false

Parameters:

  • side (Symbol, String)

    :first or :second

Returns:

  • (Boolean)

    true if that player has data



371
372
373
374
# File 'lib/sashite/pcn/game/sides.rb', line 371

def has_player?(side)
  player = player(side)
  player && !player.empty?
end

#hashInteger

Hash code for use in collections

Returns:

  • (Integer)

    hash code



359
360
361
# File 'lib/sashite/pcn/game/sides.rb', line 359

def hash
  [@first, @second].hash
end

#inspectString

String representation for debugging

Returns:

  • (String)

    string representation



336
337
338
339
340
341
342
# File 'lib/sashite/pcn/game/sides.rb', line 336

def inspect
  players = []
  players << "first=#{@first.name || '(empty)'}"
  players << "second=#{@second.name || '(empty)'}"

  "#<#{self.class.name} #{players.join(' ')}>"
end

#map {|player| ... } ⇒ Array

Map over both players

Examples:

sides.map(&:name)  # => ["Carlsen", "Nakamura"]
sides.map(&:elo)   # => [2830, 2794]

Yields:

  • (player)

    yields each player

Returns:

  • (Array)

    results of block for each player



279
280
281
282
283
# File 'lib/sashite/pcn/game/sides.rb', line 279

def map(&)
  return enum_for(:map) unless block_given?

  [@first, @second].map(&)
end

#mixed_time_control?Boolean

Check if players have different time controls

Returns true when the two players have different time control settings. This is the logical opposite of symmetric_time_control?.

Examples:

Different periods

# First: 5+3 blitz, Second: 10 minutes
sides.mixed_time_control?  # => true

One with time control, one without

# First: 5+3 blitz, Second: nil (unlimited)
sides.mixed_time_control?  # => true

Both unlimited (but different representation)

# First: [], Second: nil
sides.mixed_time_control?  # => true

Identical time controls

# Both: 5+3 blitz
sides.mixed_time_control?  # => false

Both unlimited (same representation)

# Both: nil
sides.mixed_time_control?  # => false

Returns:

  • (Boolean)

    true if time controls differ



206
207
208
# File 'lib/sashite/pcn/game/sides.rb', line 206

def mixed_time_control?
  !symmetric_time_control?
end

#namesArray<String>

Get both players’ names

Examples:

sides.names  # => ["Carlsen", "Nakamura"]

Returns:

  • (Array<String>)

    array of [first_name, second_name]



216
217
218
# File 'lib/sashite/pcn/game/sides.rb', line 216

def names
  [@first.name, @second.name]
end

#player(side) ⇒ Player?

Get player by side

Examples:

sides.player(:first)   # => first player
sides.player("second") # => second player

Parameters:

  • side (Symbol, String)

    :first or :second

Returns:

  • (Player, nil)

    player or nil if invalid side



123
124
125
126
127
128
# File 'lib/sashite/pcn/game/sides.rb', line 123

def player(side)
  case side.to_sym
  when :first then @first
  when :second then @second
  end
end

#secondPlayer

Get second player information

Examples:

player = sides.second
player.name     # => "Nakamura"
player.elo      # => 2794

Returns:

  • (Player)

    second player (may be empty)



95
96
97
# File 'lib/sashite/pcn/game/sides.rb', line 95

def second
  @second
end

#stylesArray<String>

Get both players’ styles

Examples:

sides.styles  # => ["CHESS", "chess"]

Returns:

  • (Array<String>)

    array of [first_style, second_style]



236
237
238
239
240
241
# File 'lib/sashite/pcn/game/sides.rb', line 236

def styles
  [
    @first.style&.to_s,
    @second.style&.to_s
  ]
end

#symmetric_time_control?Boolean

Check if both players have same time control

Examples:

sides.symmetric_time_control?  # => true

Returns:

  • (Boolean)

    true if periods match



156
157
158
# File 'lib/sashite/pcn/game/sides.rb', line 156

def symmetric_time_control?
  @first.periods == @second.periods
end

#time_budgetsArray<Integer>

Get both players’ time budgets

Examples:

sides.time_budgets  # => [7200, 7200]

Returns:

  • (Array<Integer>)

    array of [first_time, second_time]



249
250
251
252
253
254
# File 'lib/sashite/pcn/game/sides.rb', line 249

def time_budgets
  [
    @first.initial_time_budget,
    @second.initial_time_budget
  ]
end

#time_control_descriptionString?

Get time control description

Examples:

sides.time_control_description
# => "5+3 blitz (both players)"
# => "Classical 90+30 (symmetric)"
# => "Unlimited time"
# => "Mixed: first has 5+3, second unlimited"

Returns:

  • (String, nil)

    human-readable time control or nil



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/sashite/pcn/game/sides.rb', line 386

def time_control_description
  if unlimited_game?
    "Unlimited time"
  elsif mixed_time_control?
    first_tc = describe_periods(@first.periods)
    second_tc = describe_periods(@second.periods)
    "Mixed: first #{first_tc}, second #{second_tc}"
  elsif symmetric_time_control?
    tc = describe_periods(@first.periods)
    "#{tc} (symmetric)"
  else
    first_tc = describe_periods(@first.periods)
    second_tc = describe_periods(@second.periods)
    "First: #{first_tc}, Second: #{second_tc}"
  end
end

#to_aArray<Player>

Get array of both players

Examples:

sides.to_a  # => [#<Player ...>, #<Player ...>]

Returns:

  • (Array<Player>)
    first, second


291
292
293
# File 'lib/sashite/pcn/game/sides.rb', line 291

def to_a
  [@first, @second]
end

#to_hHash

Convert to hash representation

Returns a hash containing only non-empty player objects. If both players are empty, returns an empty hash.

Examples:

With both players

sides.to_h
# => {
#   first: {
#     name: "Carlsen",
#     elo: 2830,
#     style: "CHESS",
#     periods: [{ time: 5400, moves: 40, inc: 0 }]
#   },
#   second: {
#     name: "Nakamura",
#     elo: 2794,
#     style: "chess",
#     periods: [{ time: 5400, moves: 40, inc: 0 }]
#   }
# }

With only first player

sides.to_h
# => { first: { name: "Alice" } }

With no players

sides.to_h
# => {}

Returns:

  • (Hash)

    hash with :first and/or :second keys, or empty hash



326
327
328
329
330
331
# File 'lib/sashite/pcn/game/sides.rb', line 326

def to_h
  result = {}
  result[:first] = @first.to_h unless @first.empty?
  result[:second] = @second.to_h unless @second.empty?
  result
end

#unlimited_game?Boolean

Check if neither player has time control

Examples:

sides.unlimited_game?  # => false

Returns:

  • (Boolean)

    true if both have unlimited time



176
177
178
# File 'lib/sashite/pcn/game/sides.rb', line 176

def unlimited_game?
  @first.unlimited_time? && @second.unlimited_time?
end