runeterra_cards 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 275e8ca4e8362b65da7022dc3042e9fbe3b0970f52443e652476757be52125f1
4
+ data.tar.gz: ab803e005acf47f9499aad38aaad76d0e1c59c275dc4457bc8735208f08baace
5
+ SHA512:
6
+ metadata.gz: f406aad97cd4b6b57b356f08ff665661d3edecd608467b0a04b2c76d434927bc406cd01fec7f02aa171701f0a0f89fd2e322de7cbc871d18cb58993c9ae4d9e3
7
+ data.tar.gz: 96023313de8c2868fbf551185224a9441a13e30ddb297f9766b69b07481f4d21c1a56b78052ec09ba3a5aa22878c39f1c94ef196a235838b5cc447bb08255d96
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 James 'zofrex' Sanderson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'runeterra_cards/version'
4
+ require 'runeterra_cards/errors'
5
+ require 'runeterra_cards/factions'
6
+ require 'runeterra_cards/card_and_count'
7
+ require 'runeterra_cards/card_metadata'
8
+ require 'runeterra_cards/metadata'
9
+ require 'runeterra_cards/card_set'
10
+
11
+ module RuneterraCards
12
+ # The version of deck encoding supported
13
+ SUPPORTED_VERSION = 2
14
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuneterraCards
4
+ # Represents a card and how many of that card are in a collection.
5
+ # @deprecated
6
+ class CardAndCount
7
+ # The card code, for example "01DE123"
8
+ # @return [String]
9
+ attr_reader :code
10
+
11
+ # How many of this card are in a collection (between 1-3).
12
+ # @return [Fixnum]
13
+ attr_reader :count
14
+
15
+ # @param set [Fixnum]
16
+ # @param faction_number [Fixnum]
17
+ # @param card_number [Fixnum]
18
+ # @param count [Fixnum]
19
+ def initialize(code: nil, count:, set: nil, faction_number: nil, card_number: nil)
20
+ if code
21
+ raise if set || faction_number || card_number
22
+
23
+ @code = code
24
+ else
25
+ padded_set = format('%<i>02d', i: set)
26
+ faction = FACTION_IDENTIFIERS_FROM_INT.fetch(faction_number)
27
+ padded_card_number = format('%<i>03d', i: card_number)
28
+ @code = "#{padded_set}#{faction}#{padded_card_number}"
29
+ end
30
+ @count = count
31
+ end
32
+
33
+ def eql?(other)
34
+ code.eql?(other.code) && count.equal?(other.count)
35
+ end
36
+
37
+ def hash
38
+ [code, count].hash
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuneterraCards
4
+ # Represents metadata for a single card. Metadata is all meaning the game imbues upon a card code, including things
5
+ # like cost, health, etc and also localised information such as name and description.
6
+ #
7
+ # Currently this class only represents a thin sliver of metadata as required by its downstream consumers.
8
+ #
9
+ # @see CardAndCount
10
+ class CardMetadata
11
+ RARITIES = {
12
+ 'None' => :none,
13
+ 'Common' => :common,
14
+ 'Rare' => :rare,
15
+ 'Epic' => :epic,
16
+ 'Champion' => :champion,
17
+ }.freeze
18
+ private_constant :RARITIES
19
+
20
+ # Returns the name attribute. The name is the localised name that the card would have in game.
21
+ # For example: "House Spider".
22
+ # @return [String]
23
+ attr_reader :name
24
+
25
+ # Returns the card_code attribute. For example: "01NX055".
26
+ # @return [String]
27
+ attr_reader :card_code
28
+
29
+ # Returns the card's rarity as a symbol. Can be one of: +:none+, +:common+, +:rare+, +:epic+, or +:champion+
30
+ # @return [Symbol]
31
+ attr_reader :rarity
32
+
33
+ # Creates a single {CardMetadata} Object from the supplied Hash of data.
34
+ # This is intended for use with the data files from Legends of Runeterra Data Dragon, and it expects a Hash
35
+ # representing a single card from the parsed JSON data files from Data Dragon.
36
+ #
37
+ # @param hash [Hash] Card data from Legends of Runeterra Data Dragon
38
+ # @option hash [String] name
39
+ # @option hash [String] cardCode
40
+ # @option hash [Boolean] collectible
41
+ #
42
+ # @raise [MissingCardDataError] if any of the expected hash parameters are missing, or if +rarityRef+ contains an
43
+ # unexpected value.
44
+ def initialize(hash)
45
+ begin
46
+ @name, @card_code, @collectible, rarity_ref = hash.fetch_values('name', 'cardCode', 'collectible', 'rarityRef')
47
+ rescue KeyError => e
48
+ raise MetadataLoadError.new(hash['name'] || hash['cardCode'], "Missing expected key: #{e.key}")
49
+ end
50
+
51
+ @rarity = RARITIES[rarity_ref]
52
+ return unless rarity.nil?
53
+
54
+ raise MetadataLoadError.new(name, "Invalid value for rarityRef, got: #{rarity_ref}, "\
55
+ "expected one of: #{RARITIES.keys.join ', '}")
56
+ end
57
+
58
+ # Whether or not the card is collectible.
59
+ def collectible?
60
+ @collectible
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base32'
4
+
5
+ module RuneterraCards
6
+ # Represents a collection of cards.
7
+ #
8
+ # @todo The API to this class is very unstable and will change a lot in a coming release.
9
+ class CardSet
10
+ attr_reader :cards
11
+
12
+ def initialize(cards)
13
+ @cards = cards
14
+ end
15
+
16
+ # @deprecated
17
+ def self.from_card_and_counts(set)
18
+ new(Hash[set.map { |cac| [cac.code, cac.count] }])
19
+ end
20
+
21
+ def -(other)
22
+ remaining_cards = cards.each_with_object({}) do |(code, count), result|
23
+ new_count = count - other.count_for_card_code(code)
24
+ result[code] = new_count unless new_count.equal?(0)
25
+ end
26
+
27
+ CardSet.new(remaining_cards)
28
+ end
29
+
30
+ # @return Enumerator<CardAndCount>
31
+ # @deprecated
32
+ def as_card_and_counts
33
+ cards.map { |code, count| CardAndCount.new(code: code, count: count) }
34
+ end
35
+
36
+ def count_for_card_code(code)
37
+ cards[code] || 0
38
+ end
39
+
40
+ # @param deck_code [String]
41
+ # @raise [Base32Error] if the deck code cannot be Base32 decoded.
42
+ # @raise [UnrecognizedVersionError] if the deck code's version is not supported by this library
43
+ # (see {SUPPORTED_VERSION}).
44
+ # @raise [EmptyInputError] if the deck code is an empty string.
45
+ def self.from_deck_code(deck_code)
46
+ binary_data = decode_base32(deck_code)
47
+ format, version = decode_format_and_version(binary_data[0])
48
+
49
+ raise UnrecognizedVersionError.new(SUPPORTED_VERSION, version) unless version <= SUPPORTED_VERSION
50
+
51
+ raise unless format.equal? 1
52
+
53
+ int_array = binary_data[1..].unpack('w*')
54
+ cards = assemble_card_list(int_array)
55
+ from_card_and_counts(cards)
56
+ end
57
+
58
+ # @param string [String] base32-encoded string
59
+ # @return [String] binary data
60
+ def self.decode_base32(string)
61
+ raise EmptyInputError if string.empty?
62
+
63
+ begin
64
+ Base32.decode(string)
65
+ rescue StandardError
66
+ raise Base32Error
67
+ end
68
+ end
69
+
70
+ private_class_method :decode_base32
71
+
72
+ # @param byte [String] a single byte
73
+ # @return [FixNum] format and version, in that order
74
+ def self.decode_format_and_version(byte)
75
+ format_and_version = byte.unpack1('C')
76
+ format = format_and_version >> 4
77
+ version = format_and_version & 0xF
78
+ [format, version]
79
+ end
80
+
81
+ private_class_method :decode_format_and_version
82
+
83
+ def self.assemble_card_list(array)
84
+ 3.downto(1).flat_map do |number_of_copies|
85
+ set_faction_combination_count = array.shift
86
+ set_faction_combination_count.times.flat_map do
87
+ number_of_cards, set, faction = array.shift(3)
88
+
89
+ array.shift(number_of_cards).map do |card_number|
90
+ CardAndCount.new(set: set, faction_number: faction, card_number: card_number, count: number_of_copies)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ private_class_method :assemble_card_list
97
+ end
98
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuneterraCards
4
+ # The base exception for errors that occur when decoding a deck code
5
+ class DeckCodeParseError < StandardError
6
+ end
7
+
8
+ # This exception is raised if the deck code cannot be Base32-decoded. This probably means it isn't a deck code, or
9
+ # got malformed somehow.
10
+ class Base32Error < DeckCodeParseError
11
+ def initialize
12
+ super('Encountered an error while Base32 decoding deck code.' \
13
+ ' Probably an invalid deck code, or possibly a bug in the Base32 handling.')
14
+ end
15
+ end
16
+
17
+ # This exception is raised if the deck code is an empty string.
18
+ class EmptyInputError < DeckCodeParseError
19
+ def initialize
20
+ super('The input was an empty string')
21
+ end
22
+ end
23
+
24
+ # This exception is raised if the deck code version number in the deck code is not one we can handle. This could mean
25
+ # this isn't a deck code (especially if the version number is very different to the one expected), or it could mean
26
+ # Riot has updated the deck code version and you need to update this library.
27
+ #
28
+ # If updating this library fails to resolve the issue, and you are sure this is a deck code, check GitHub for an
29
+ # issue relating to this. If none exists, then file one!
30
+ # @see SUPPORTED_VERSION
31
+ class UnrecognizedVersionError < DeckCodeParseError
32
+ def initialize(expected, got)
33
+ super("Unrecognized deck code version number: #{got}, was expecting: #{expected}. \
34
+ Possibly an invalid deck code, possibly you need to update the deck code library version.")
35
+ end
36
+ end
37
+
38
+ # This exception is raised if you try to parse data from Runeterra Data Dragon that is not in the expected form.
39
+ # The message will tell you what data was not right, and the {#card} attribute will tell you which card had issues,
40
+ # if possible.
41
+ #
42
+ # @see CardMetadata#initialize
43
+ class MetadataLoadError < StandardError
44
+ # Return the name or card code of the card that was missing an expected attribute.
45
+ # @return [String] name if the name was present
46
+ # @return [String] card code if the name was not present
47
+ # @return [nil] if neither name nor card code were present
48
+ attr_reader :card
49
+
50
+ def initialize(card, problem)
51
+ if card.nil?
52
+ super("Error loading data for unknown card (no code or name): #{problem}")
53
+ else
54
+ super("Error loading data for card #{card}: #{problem}")
55
+ end
56
+ @card = card
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuneterraCards
4
+ # An array of the two-letter Faction identifiers, indexed by their integer identifiers
5
+ # @example
6
+ # FACTION_IDENTIFIERS_FROM_INT[2] #=> "IO"
7
+ # @return [Array<String>]
8
+ FACTION_IDENTIFIERS_FROM_INT = %w[DE FR IO NX PZ SI BW].freeze
9
+
10
+ # A map from two-letter Faction identifiers to their integer identifiers
11
+ # @example
12
+ # FACTION_INTS_FROM_IDENTIFIER["IO"] #=> 2
13
+ # @return [Hash<String,Fixnum>]
14
+ FACTION_INTS_FROM_IDENTIFIER = {
15
+ 'DE' => 0,
16
+ 'FR' => 1,
17
+ 'IO' => 2,
18
+ 'NX' => 3,
19
+ 'PZ' => 4,
20
+ 'SI' => 5,
21
+ 'BW' => 6,
22
+ }.freeze
23
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module RuneterraCards
6
+ # Loads, stores, and retrieves data for cards from Legends of Runeterra Data Dragon set files.
7
+ # N.B. this does not ship with any metadata of its own, you must provide set files yourself and load them before
8
+ # this class will do anything.
9
+ #
10
+ # @example Load data and retrieve data for a card
11
+ # m = RuneterraCards::Metadata.new
12
+ # m.add_set_file('./set1-en_us.json')
13
+ # m.lookup_card('01DE031') #=> CardMetadata
14
+ #
15
+ # @example Load data from multiple sets
16
+ # m = RuneterraCards::Metadata.new
17
+ # m.add_set_file('./set1-en_us.json')
18
+ # m.add_set_file('./set2-en_us.json')
19
+ #
20
+ # @note This class cannot yet handle metadata for multiple locales at the same time. You will need multiple instances
21
+ # of this class, one for each locale, if you wish to handle multiple locales at this time.
22
+ class Metadata
23
+ def initialize
24
+ @cards = {}
25
+ end
26
+
27
+ # Load card data from a Legends of Runeterra Data Dragon set file, e.g. "set1-en_us.json"
28
+ # @param file_path [String] The path to the metadata file to read
29
+ # @todo document and test exceptions that can be thrown here
30
+ def add_set_file(file_path)
31
+ file = File.read(file_path)
32
+ data = JSON.parse(file)
33
+ data.each do |card_data|
34
+ card = CardMetadata.new(card_data)
35
+ @cards[card.card_code] = card
36
+ end
37
+ end
38
+
39
+ # Fetch card metadata for a card via its card code
40
+ # @param card_code [String] card code, e.g. 01DE031
41
+ # @return [CardMetadata]
42
+ # @todo document errors if card_code doesn't exist
43
+ def lookup_card(card_code)
44
+ @cards.fetch(card_code)
45
+ end
46
+
47
+ # Returns all cards in the metadata set that are collectible
48
+ # @return [Hash<String,CardMetadata>]
49
+ # @see CardMetadata#collectible?
50
+ def all_collectible
51
+ @cards.select { |_, card| card.collectible? }
52
+ end
53
+
54
+ # Returns a [CardSet] that represents a complete card collection.
55
+ # That is: 3x of every card that is collectible.
56
+ # @return [CardSet]
57
+ def full_set
58
+ CardSet.new(all_collectible.keys.each_with_object({}) { |code, result| result[code] = 3 })
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuneterraCards
4
+ # The version of this library
5
+ VERSION = '0.1.0'
6
+ end
metadata ADDED
@@ -0,0 +1,220 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: runeterra_cards
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - zofrex
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base32
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: deep-cover
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-reporters
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.4.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.4.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: mutant-minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 13.0.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 13.0.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.89.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.89.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop-minitest
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.10.1
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.10.1
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop-packaging
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.3.0
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.3.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop-performance
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1.7'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.7'
167
+ - !ruby/object:Gem::Dependency
168
+ name: yard
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 0.9.25
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 0.9.25
181
+ description: Legends of Runeterra deck encoder/decoder and general purpose card info
182
+ email:
183
+ - zofrex@gmail.com
184
+ executables: []
185
+ extensions: []
186
+ extra_rdoc_files: []
187
+ files:
188
+ - LICENSE.txt
189
+ - lib/runeterra_cards.rb
190
+ - lib/runeterra_cards/card_and_count.rb
191
+ - lib/runeterra_cards/card_metadata.rb
192
+ - lib/runeterra_cards/card_set.rb
193
+ - lib/runeterra_cards/errors.rb
194
+ - lib/runeterra_cards/factions.rb
195
+ - lib/runeterra_cards/metadata.rb
196
+ - lib/runeterra_cards/version.rb
197
+ homepage:
198
+ licenses:
199
+ - MIT
200
+ metadata: {}
201
+ post_install_message:
202
+ rdoc_options: []
203
+ require_paths:
204
+ - lib
205
+ required_ruby_version: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: '2.6'
210
+ required_rubygems_version: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ requirements: []
216
+ rubygems_version: 3.1.4
217
+ signing_key:
218
+ specification_version: 4
219
+ summary: Legends of Runeterra deck encoder/decoder and general purpose card info
220
+ test_files: []