manasimu 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3f5c5bf36d69150a34c30b6d1bd1304499bb5b7ffe36b6ef37640907803e0926
4
+ data.tar.gz: 24aad739622210bd7b24cf59d1c7db77c98db3da7cfa2c34f219598aaad6d4d6
5
+ SHA512:
6
+ metadata.gz: e796dbaf5cec8317086d4e72e6ce3b521996f39bdee0ee3261c97f6f2590ada27c6eb211227f54b49eaa1226244d94707e903a85abaa847f8f4253f1764e5812
7
+ data.tar.gz: 58f72bbf05f2842e2a70a14b21bdddf87a3ddf59da921c0db460fcb5d7c7720bb15f61102c31f72c94066aecdf128197f51d6db48ef5a709850d6fe2610227df
Binary file
Binary file
@@ -0,0 +1,297 @@
1
+ class Card
2
+ attr_accessor :id, :card_type
3
+
4
+ def initialize(card_type)
5
+ @card_type = card_type
6
+ end
7
+
8
+ def step(turn)
9
+ end
10
+
11
+ def drawed(turn)
12
+ @drawed = turn
13
+ @card_type.drawed(turn)
14
+ end
15
+
16
+ def played(turn)
17
+ @played = turn
18
+ @card_type.played(turn)
19
+ end
20
+ def played?
21
+ @played.nil?
22
+ end
23
+
24
+ def tapped?
25
+ false
26
+ end
27
+
28
+ def mana_source?
29
+ @card_type.mana_source?
30
+ end
31
+
32
+ def playable?(lands)
33
+ @card_type.playable?(lands)
34
+ end
35
+
36
+ def types
37
+ @card_type.types
38
+ end
39
+
40
+ def mana
41
+ @card_type.mana
42
+ end
43
+
44
+ def color_identity
45
+ @card_type.color_identity
46
+ end
47
+
48
+ def converted_mana_cost
49
+ @card_type.converted_mana_cost
50
+ end
51
+
52
+ def color_identity_size
53
+ @card_type.color_identity_size
54
+ end
55
+
56
+ def mana_cost
57
+ @card_type.mana_cost
58
+ end
59
+
60
+ def price
61
+ @card_type.price
62
+ end
63
+
64
+ def max_flow(lands)
65
+ @card_type.max_flow(lands)
66
+ end
67
+
68
+ def edges(lands)
69
+ @card_type.edges(lands)
70
+ end
71
+
72
+ def to_s
73
+ @card_type.to_s
74
+ end
75
+ end
76
+
77
+ class CardType
78
+ attr_accessor :contents
79
+
80
+ def initialize(contents)
81
+ return if not contents
82
+ @contents = contents.map {|c| Content.new(c)}
83
+ end
84
+
85
+ def name
86
+ @name ||= @contents[0].name
87
+ end
88
+
89
+ def played(turn)
90
+ @played ||= {}
91
+ @played[turn] ||= 0
92
+ @played[turn] += 1
93
+ end
94
+
95
+ def drawed(turn)
96
+ @drawed ||= {}
97
+ @drawed[turn] ||= 0
98
+ @drawed[turn] += 1
99
+ end
100
+
101
+ def mana_source?
102
+ @mana_source ||= @contents.any? {|content| content.mana_source?}
103
+ end
104
+
105
+ def types
106
+ @types ||= @contents.map {|c| c.types}
107
+ end
108
+
109
+ def mana
110
+ @mana ||= @contents.map {|content| content.color_identity }.flatten
111
+ end
112
+
113
+ def color_identity
114
+ return @memo_colors if @memo_colors
115
+ @memo_colors ||= []
116
+ @contents.each do |c|
117
+ c.color_identity.split(",").each do |color|
118
+ @memo_colors << color if not @memo_colors.include? color
119
+ end
120
+ end
121
+ @memo_colors
122
+ end
123
+
124
+ def converted_mana_cost
125
+ @converted_mana_cost ||= @contents.map {|c| c.converted_mana_cost}.min
126
+ end
127
+
128
+ def color_identity_size
129
+ color_identity.length
130
+ end
131
+
132
+ def mana_cost
133
+ @mana_cost ||= @contents.select {|c| c.types != "Land"}.first.mana_cost
134
+ end
135
+
136
+ def price
137
+ converted_mana_cost
138
+ end
139
+
140
+ def playable?(lands)
141
+ return [false, []] if lands.empty?
142
+ return [false, []] if converted_mana_cost > lands.length
143
+ mf, used = max_flow(lands)
144
+ [mf == converted_mana_cost, used.to_a[1..lands.length]]
145
+ end
146
+
147
+ def max_flow(lands)
148
+ obj = FordFulkersonSingleton.instance.obj
149
+ # Graph has x+y+2 nodes
150
+ # source : 0
151
+ # lands : 1 - x
152
+ # mana_cost : x+1 - x+y+1
153
+ # destination : x+y+2
154
+ #
155
+ # image
156
+ # - land1 - mana4
157
+ # source0 - land2 - mana5 - destination6
158
+ # - land3
159
+ #
160
+
161
+ # create edge
162
+ x, y, e = edges(lands)
163
+ g = Graph.new(x + y + 2)
164
+ e.each do |s, d|
165
+ g.add_edge(s, d, 1)
166
+ end
167
+
168
+ ret = obj.max_flow(g, 0, x + y + 1)
169
+ [ret, obj.used]
170
+ end
171
+
172
+ def edges(lands)
173
+ result = []
174
+ x = lands.length
175
+ i_src = 0
176
+ # source connect to lands
177
+ x.times do |i|
178
+ result << [i_src, i + 1]
179
+ end
180
+
181
+ # create symbol
182
+ symbols = []
183
+ mana_cost[1..-2].split('}{').each_with_index do |mana, j|
184
+ spell_colors = mana.split('/')
185
+ if spell_colors.length == 1
186
+ spell_color = spell_colors[0]
187
+ if spell_color.to_i.to_s == spell_color
188
+ # numeric symbol
189
+ spell_color.to_i.times do |k|
190
+ symbols << "1"
191
+ end
192
+ else
193
+ # color symbol
194
+ symbols << spell_color
195
+ end
196
+ else
197
+ # multi symbol
198
+ throw Exception.new('unprogramed exception')
199
+ end
200
+ end
201
+
202
+ # lands and mana_cost connect to each symbols
203
+ lands.each_with_index do |land, i|
204
+ land_colors = land.color_identity
205
+ symbols.each_with_index do |symbol, j|
206
+ if symbol == "1" or land_colors.include? symbol
207
+ result << [i + 1, x + 1 + j]
208
+ end
209
+ end
210
+ end
211
+
212
+ y = symbols.length
213
+ i_dst = x + y + 1
214
+
215
+ # mana_cost connect to destination
216
+ y.times do |i|
217
+ result << [x + 1 + i, i_dst]
218
+ end
219
+
220
+ [x, y, result]
221
+ end
222
+
223
+ def count(turn = nil)
224
+ turn ||= converted_mana_cost
225
+ played = @played ? @played [turn] : 0
226
+ drawed = 0
227
+ if (@drawed)
228
+ (turn+1).times do |i|
229
+ next if not @drawed[i]
230
+ drawed += @drawed[i]
231
+ end
232
+ end
233
+ [played, drawed]
234
+ end
235
+
236
+ def to_s
237
+ @contents.map {|c| c.to_s}.join(",")
238
+ end
239
+ end
240
+
241
+ class CardTypeAggregate
242
+
243
+ def find(card_type)
244
+ @memo ||= []
245
+ return nil if not card_type
246
+ singleton = @memo.find do |c|
247
+ a = c.contents[0]
248
+ b = card_type.contents[0]
249
+ a and b and a.name == b.name
250
+ end
251
+ if singleton
252
+ singleton
253
+ else
254
+ @memo << card_type
255
+ card_type
256
+ end
257
+ end
258
+
259
+ def each
260
+ return if not @memo
261
+ @memo.each do |item|
262
+ yield item
263
+ end
264
+ end
265
+
266
+ end
267
+
268
+ class Content
269
+ attr_accessor :name, :number, :side, :set_code, :mana_cost, :types, :color_identity, :converted_mana_cost, :text
270
+
271
+ def initialize(hash)
272
+ @name = hash[:name]
273
+ @number = hash[:number]
274
+ @side = hash[:side]
275
+ @set_code = hash[:set_code]
276
+ @mana_cost = hash[:mana_cost]
277
+ @types = hash[:types]
278
+ @text = hash[:text]
279
+ @color_identity = hash[:color_identity]
280
+ @converted_mana_cost = hash[:converted_mana_cost].to_i
281
+ end
282
+
283
+ def mana_source?
284
+ return @types == "Land"
285
+ end
286
+
287
+ def to_s
288
+ "[#{@name}] [#{@types}] [#{@color_identity}] [#{@mana_cost}]"
289
+ end
290
+ end
291
+
292
+ class FordFulkersonSingleton
293
+ include Singleton
294
+ def obj
295
+ @memo_obj ||= FordFulkerson.new
296
+ end
297
+ end
@@ -0,0 +1,117 @@
1
+ class Deck
2
+
3
+ def self.create(lines)
4
+ items = Deck.input_to_card_hash(lines)
5
+ Deck.get_card_details(items)
6
+ end
7
+
8
+ def self.input_to_card_hash(lines)
9
+ result = []
10
+ looking_for_deck_line = false
11
+ for line in lines do
12
+ trimmed = line.chomp
13
+ trimmed_lower = trimmed.downcase
14
+
15
+ # Ignore reserved words
16
+ if trimmed_lower == "deck"
17
+ looking_for_deck_line = false
18
+ next
19
+ end
20
+
21
+ if trimmed_lower == "commander"
22
+ looking_for_deck_line = true
23
+ next
24
+ end
25
+ if trimmed_lower == "companion"
26
+ looking_for_deck_line = true
27
+ next
28
+ end
29
+ #Assumes sideboard comes after deck
30
+ if trimmed_lower == "sideboard"
31
+ break
32
+ end
33
+ if trimmed_lower == "maybeboard"
34
+ # Assumes maybeboard comes after deck
35
+ break
36
+ end
37
+ # Ignore line comments
38
+ if trimmed.start_with? ('#')
39
+ next
40
+ end
41
+ if looking_for_deck_line
42
+ next
43
+ end
44
+ # An empty line divides the main board cards from the side board cards
45
+ if trimmed.empty?
46
+ break
47
+ end
48
+
49
+ if !(trimmed =~ /\s*(\d+)\s+([^\(#\n\r]+)(?:\s*\((\w+)\)\s+(\d+))?\s*/)
50
+ next
51
+ end
52
+
53
+ deck_item = {}
54
+ deck_item[:amount] = $1.strip
55
+ deck_item[:name] = $2.strip
56
+ deck_item[:set] = $3.strip
57
+ deck_item[:setnum] = $4.strip
58
+ result << deck_item
59
+ end
60
+ result
61
+ end
62
+
63
+ def self.get_card_details(deck_items)
64
+ path = File.expand_path( '../../../db/AllPrintings.sqlite', __FILE__ )
65
+ db = SQLite3::Database.new(path)
66
+ sql = <<DOC
67
+ select distinct
68
+ name
69
+ ,number
70
+ ,colorIdentity
71
+ ,side
72
+ ,setCode
73
+ ,manaCost
74
+ ,types
75
+ ,text
76
+ ,convertedManaCost
77
+ from cards
78
+ where
79
+ number = ? and
80
+ setCode = ?
81
+ DOC
82
+ cards = []
83
+ card_id = 0
84
+ card_types = CardTypeAggregate.new
85
+ deck_items.each do |deck_item|
86
+ rows = db.execute(sql, deck_item[:setnum], deck_item[:set])
87
+ if rows.empty?
88
+ puts deck_item[:name]
89
+ end
90
+
91
+ card_type = card_types.find(
92
+ CardType.new(rows.map { |row|
93
+ {
94
+ name: row[0],
95
+ number: row[1],
96
+ color_identity: row[2],
97
+ side: row[3],
98
+ set_code: row[4],
99
+ mana_cost: row[5],
100
+ types: row[6],
101
+ text: row[7],
102
+ converted_mana_cost: row[8]
103
+ }
104
+ })
105
+ )
106
+ card = Card.new(card_type)
107
+
108
+ deck_item[:amount].to_i.times do
109
+ card_clone = card.dup
110
+ card_clone.id = card_id
111
+ card_id += 1
112
+ cards << card_clone
113
+ end
114
+ end
115
+ [cards, card_types]
116
+ end
117
+ end
@@ -0,0 +1,47 @@
1
+ class Game
2
+ attr_accessor :hands, :plays, :deck
3
+
4
+ def initialize(deck)
5
+ @deck = deck.shuffle(random: Random.new)
6
+ @hands = []
7
+ @plays = []
8
+ @planner = Planner.new
9
+ 7.times { draw(0) }
10
+ end
11
+
12
+ def step(turn)
13
+ # puts "turn #{turn}"
14
+ # puts "played"
15
+ # @plays.each do |card| puts " #{card}" end
16
+ # puts "hands"
17
+ # @hands.each do |card| puts " #{card}" end
18
+
19
+ upkeep(turn)
20
+ draw(turn)
21
+ plan.each do |card|
22
+ play(card, turn)
23
+ end
24
+ end
25
+
26
+ def upkeep(turn)
27
+ @plays.each { |card| card.step(turn) }
28
+ end
29
+
30
+ def draw(turn)
31
+ card = @deck.pop
32
+ # puts "draw #{card}"
33
+ card.drawed(turn)
34
+ @hands << card
35
+ end
36
+
37
+ def plan
38
+ @planner.plan(@hands, @plays)
39
+ end
40
+
41
+ def play(card, turn)
42
+ # puts "play #{card}"
43
+ card.played(turn)
44
+ @plays << card
45
+ @hands.delete card
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ require './ext/hello.so'
2
+
3
+ Hello.new.say
@@ -0,0 +1,7 @@
1
+ class ManaType
2
+ class Red end;
3
+ class Blue end;
4
+ class Green end;
5
+ class White end;
6
+ class Black end;
7
+ end
@@ -0,0 +1,162 @@
1
+ class Planner
2
+
3
+ def plan(hands, fields)
4
+ lands_in_hand = lands(hands)
5
+
6
+ max_price = 0
7
+ max_spells = nil
8
+ max_land = nil
9
+
10
+ if not lands_in_hand.empty?
11
+ lands_in_hand.each do |play_land|
12
+ # dup
13
+ current_hands = hands.dup
14
+ current_fields = fields.dup
15
+
16
+ # play the land
17
+ current_hands.delete play_land
18
+ current_fields << play_land
19
+
20
+ # search_opt_spells
21
+ price, spells =
22
+ search_opt_spells(current_hands, current_fields)
23
+ if price >= max_price and not spells.empty?
24
+ max_price = price
25
+ max_spells = spells
26
+ max_land = play_land
27
+ end
28
+ end
29
+ else
30
+ # search_opt_spells
31
+ max_price, max_spells = search_opt_spells(hands, fields)
32
+ end
33
+
34
+ if not max_spells and not lands_in_hand.empty?
35
+ max_land = lands_in_hand[0]
36
+ end
37
+
38
+ [max_land, max_spells].select {|a| a}.flatten
39
+ end
40
+
41
+ #
42
+ # on conditional playing land, search most
43
+ # high price spells combinations
44
+ # return price, spells
45
+ #
46
+ def search_opt_spells(hands, fields)
47
+ spells = spells(hands)
48
+ lands = lands(fields)
49
+
50
+ # sort spells desc converted_mana_cost
51
+ spells.sort! do |a, b|
52
+ b.converted_mana_cost <=> a.converted_mana_cost
53
+ end
54
+
55
+ lands.sort! do |a, b|
56
+ b.color_identity_size <=> a.color_identity_size
57
+ end
58
+
59
+ price = 0
60
+ bit_lands = 0
61
+ bit_spells = 0
62
+ # search playable spell comibantion
63
+ cost, bit_spells, bit_lands =
64
+ dfs(1, spells, lands, bit_spells, bit_lands, price)
65
+ [price, bit_select(spells, bit_spells)]
66
+ end
67
+
68
+ def dfs(n, spells, lands, bit_spells, bit_lands, price)
69
+ index = n - 1
70
+
71
+ # exit
72
+ return [price, bit_spells, bit_lands] if n > spells.length
73
+
74
+ spell = spells[index]
75
+ # ex) lands [a,b,c,d]
76
+ # bit_lands is 3 ( = 0011)
77
+ # then left_lands to be [c, d]
78
+ left_lands = bit_select(lands, reverse_bit(bit_lands, lands.length))
79
+
80
+ # cast case
81
+ is_playable, used_lands = spell.playable?(left_lands)
82
+ a_price, a_bit_spells, a_bit_lands =
83
+ if is_playable
84
+ bit_spells = bit_spells | 1 << ( n - 1 )
85
+ # ex) lands [a,b,c,d]
86
+ # bit_lands 3 ( = 0011)
87
+ # used_lands [d]
88
+ # then used_lands to be [0,0,0,d]
89
+ used_lands = fill_used_lands(used_lands, bit_lands, lands)
90
+ # ex) used_lands [0,0,0,d]
91
+ # bit_lands 3 ( = 0011)
92
+ # then bit_lands to be 11 ( = 1011)
93
+ bit_lands = update_bit(used_lands, bit_lands)
94
+ # dfs
95
+ dfs(n + 1 , spells, lands, bit_spells, bit_lands, price + spell.price)
96
+ else
97
+ [nil, nil, nil]
98
+ end
99
+
100
+ # not cast case
101
+ b_price, b_bit_spells, b_bit_lands =
102
+ dfs(n + 1 , spells, lands, bit_spells, bit_lands, price)
103
+
104
+ if (a_price and a_price >= b_price)
105
+ [a_price, a_bit_spells, a_bit_lands]
106
+ else
107
+ [b_price, b_bit_spells, b_bit_lands]
108
+ end
109
+ end
110
+
111
+ def reverse_bit(bit, length)
112
+ s = bit.to_s(2)
113
+ length.times.to_a.map do |i|
114
+ if s[i] and s[i] == "1"
115
+ "0"
116
+ else
117
+ "1"
118
+ end
119
+ end.join.to_i(2)
120
+ end
121
+
122
+ def bit_select(cards, bit)
123
+ cards.length.times
124
+ .map { |i| cards[i] if (bit & (1 << i) > 0) }
125
+ .select { |o| o }
126
+ end
127
+
128
+ def update_bit(used_lands, bit_lands)
129
+ used_lands.each_with_index do |flg, i|
130
+ bit_lands = bit_lands | ( 1 << i ) if flg == 1
131
+ end
132
+ bit_lands
133
+ end
134
+
135
+ def fill_used_lands(used_lands, bit_lands, lands)
136
+ result = []
137
+ j = 0
138
+ lands.length.times do |i|
139
+ if (bit_lands & 1 << i) == 1
140
+ # used before dfs
141
+ result << 1
142
+ else
143
+ # used after dfs
144
+ result << used_lands[j]
145
+ j += 1
146
+ end
147
+ end
148
+ result
149
+ end
150
+
151
+ def lands(list)
152
+ list.select do |card|
153
+ card.types.include? "Land"
154
+ end
155
+ end
156
+
157
+ def spells(list)
158
+ list.select do |card|
159
+ not card.types.include? "Land"
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,20 @@
1
+ class Simulator
2
+
3
+ def initialize(config)
4
+ @config = config
5
+ end
6
+
7
+ def run
8
+ @config.simulations.times do
9
+ game = Game.new(@config.deck)
10
+ @config.turns.times do |i|
11
+ turn = i + 1
12
+ game.step turn
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ class SimulatorConfig
19
+ attr_accessor :simulations, :turns, :deck
20
+ end
data/lib/manasimu.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ require_relative './manasimu/card.rb'
5
+ require_relative './manasimu/planner.rb'
6
+ require_relative './manasimu/game.rb'
7
+ require_relative './manasimu/simulator.rb'
8
+ require_relative './manasimu/data.rb'
9
+ require_relative '../ext/ford_fulkerson.so'
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: manasimu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - so1itaryrove
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-05-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: mtg arrena mana curve simulator
14
+ email: so1itaryrove@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - db/AllPrintings.sqlite
20
+ - ext/ford_fulkerson.so
21
+ - lib/manasimu.rb
22
+ - lib/manasimu/card.rb
23
+ - lib/manasimu/data.rb
24
+ - lib/manasimu/game.rb
25
+ - lib/manasimu/hello.rb
26
+ - lib/manasimu/mana_type.rb
27
+ - lib/manasimu/planner.rb
28
+ - lib/manasimu/simulator.rb
29
+ homepage:
30
+ licenses:
31
+ - MIT
32
+ metadata: {}
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubygems_version: 3.1.2
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: mtg arrena mana curve simulator
52
+ test_files: []