Module: Deckstrings

Defined in:
lib/deckstrings/enum.rb,
lib/deckstrings/varint.rb,
lib/deckstrings/deckstrings.rb

Defined Under Namespace

Modules: Enum Classes: Card, Deck, Format, FormatError, Hero, HeroClass

Class Method Summary collapse

Class Method Details

.decode(deckstring) ⇒ { format: Integer, heroes: Array<Integer>, cards: Hash{Integer => Integer} }

Decodes a Hearthstone deckstring into format, hero, and card counts.

This method validates the well-formedness of the deckstring and the embedded version, but does not validate the format, individual hero/card IDs, or card counts. For stricter validation and additional deck info, see Deckstrings::Deck.decode.

All IDs refer to unique Hearthstone DBF IDs which can be used in conjunction with HearthstoneJSON metadata.

Examples:

deck = Deckstrings::decode('AAEBAf0GAA/yAaIC3ALgBPcE+wWKBs4H2QexCMII2Q31DfoN9g4A')
deck = Deckstrings::decode('AAECAZICCPIF+Az5DK6rAuC7ApS9AsnHApnTAgtAX/4BxAbkCLS7Asu8As+8At2+AqDNAofOAgA=')

Parameters:

  • deckstring (String)

    Base64-encoded Hearthstone deckstring.

Returns:

  • ({ format: Integer, heroes: Array<Integer>, cards: Hash{Integer => Integer} })

    Parsed Hearthstone deck details. heroes is an array of hero IDs, but this will usually be just one element. cards is a Hash from card ID to its instance count in the deck.

Raises:

  • (FormatError)

    If the deckstring is malformed or contains invalid deck data.

See Also:



526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
# File 'lib/deckstrings/deckstrings.rb', line 526

def self.decode(deckstring)
  if deckstring.nil? || deckstring.empty?
    raise FormatError, 'Invalid deckstring.'
  end

  stream = begin
    StringIO.new(Base64::strict_decode64(deckstring))
  rescue ArgumentError
    raise FormatError, 'Invalid base64-encoded string.'
  end

  begin
    reserved = stream.read_varint
    if reserved != 0
      raise FormatError, "Unexpected reserved byte: #{reserved}."
    end

    version = stream.read_varint
    if version != 1
      raise FormatError, "Unexpected version: #{version}."
    end

    format = stream.read_varint

    # Heroes
    heroes = []
    length = stream.read_varint
    length.times do
      heroes << stream.read_varint
    end

    # Cards
    cards = {}
    1.upto(3) do |i|
      length = stream.read_varint
      length.times do
        card = stream.read_varint
        cards[card] = i < 3 ? i : stream.read_varint
      end
    end
  rescue EOFError
    raise FormatError, 'Unexpected end of data.'
  end

  return {
    format: format,
    heroes: heroes,
    cards: cards
  }
end

.encode(format:, heroes:, cards:) ⇒ String

Encodes a Hearthstone deck as a compact deckstring.

This method validates card counts, but does not validate the format or individual hero/card IDs. For stricter validation, see Deckstrings::Deck.encode.

All IDs refer to unique Hearthstone DBF IDs which can be seen in HearthstoneJSON metadata.

Examples:

deckstring = Deckstrings::encode(format: 2, heroes: [637], cards: { 1004 => 2, 315 => 2 })
deckstring = Deckstrings::encode(
  format: Deckstrings::Format.standard,
  heroes: [Deckstrings::Hero.mage],
  cards: { 1004 => 2, 315 => 2 }
)

Parameters:

  • format (Integer, Deckstrings::Format)

    Format for this deck: wild or standard.

  • heroes (Array<Integer, Deckstrings::Hero>)

    Heroes for this deck. Multiple heroes are supported, but typically this array will contain one element.

  • cards (Hash{Integer, Deckstrings::Card => Integer})

    Cards in the deck. A Hash from card ID to its instance count in the deck.

Returns:

  • (String)

    Base64-encoded compact byte string representing the deck.

Raises:

  • (FormatError)

    If any card counts are less than 1.

See Also:



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/deckstrings/deckstrings.rb', line 471

def self.encode(format:, heroes:, cards:)
  stream = StringIO.new('')

  format = format.is_a?(Deckstrings::Format) ? format.value : format
  heroes = heroes.map { |hero| hero.is_a?(Deckstrings::Hero) ? hero.id : hero }

  # Reserved slot, version, and format.
  stream.write_varint(0)
  stream.write_varint(1)
  stream.write_varint(format)

  # Heroes.
  stream.write_varint(heroes.length)
  heroes.sort.each do |hero|
    stream.write_varint(hero)
  end

  # Cards.
  by_count = cards.group_by { |id, n| n > 2 ? 3 : n }

  invalid = by_count.keys.select { |count| count < 1 }
  unless invalid.empty?
    raise FormatError, "Invalid card count: #{invalid.join(', ')}."
  end

  1.upto(3) do |count|
    group = by_count[count] || []
    stream.write_varint(group.length)
    group.sort_by { |id, n| id }.each do |id, n|
      stream.write_varint(id)
      stream.write_varint(n) if n > 2
    end
  end

  Base64::strict_encode64(stream.string).strip
end