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:


511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
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
# File 'lib/deckstrings/deckstrings.rb', line 511

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:


456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/deckstrings/deckstrings.rb', line 456

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