manasimu 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []