ascension 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.
- data/.document +5 -0
- data/.lre +1 -0
- data/.rspec +1 -0
- data/Gemfile +37 -0
- data/Gemfile.lock +128 -0
- data/Guardfile +27 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +75 -0
- data/VERSION +1 -0
- data/ascension.gemspec +129 -0
- data/lib/ascension.rb +209 -0
- data/lib/ascension/Ascension-statistics_v1.csv +49 -0
- data/lib/ascension/ability.rb +222 -0
- data/lib/ascension/card.rb +133 -0
- data/lib/ascension/cards.csv +49 -0
- data/lib/ascension/cards.rb +206 -0
- data/lib/ascension/cards2.csv +49 -0
- data/lib/ascension/events.rb +73 -0
- data/lib/ascension/parse.rb +283 -0
- data/lib/ascension/pool.rb +49 -0
- data/lib/ascension/run.rb +27 -0
- data/lib/ascension/setup_rchoice.rb +11 -0
- data/lib/ascension/to_json.rb +74 -0
- data/lib/ascension/turn_manager.rb +14 -0
- data/notes.txt +2 -0
- data/spec/card_spec.rb +87 -0
- data/spec/center_spec.rb +25 -0
- data/spec/choice_instance_spec.rb +97 -0
- data/spec/main_spec.rb +413 -0
- data/spec/parse_spec.rb +225 -0
- data/spec/spec_helper.rb +73 -0
- data/vol/dmp.json +556 -0
- data/vol/dmp2.json +1 -0
- data/vol/find_by_id.rb +22 -0
- data/vol/game.json +1 -0
- data/vol/game_pp.json +614 -0
- data/vol/test_persist.rb +44 -0
- data/web/main.rb +83 -0
- metadata +377 -0
@@ -0,0 +1,283 @@
|
|
1
|
+
module Parse
|
2
|
+
def self.reg_word(word,&b)
|
3
|
+
Words.instance.reg_word(word,&b)
|
4
|
+
end
|
5
|
+
def self.reg_ability(word,ability=nil,&b)
|
6
|
+
ability ||= b
|
7
|
+
Words.instance.reg_word(word) { |side| ability.call(side) }
|
8
|
+
end
|
9
|
+
def self.cards
|
10
|
+
@cards ||= InputFile.new.cards
|
11
|
+
end
|
12
|
+
def self.get(name)
|
13
|
+
cards.find { |x| x.name == name }.tap { |x| raise "no card #{name}" unless x }
|
14
|
+
end
|
15
|
+
|
16
|
+
class Words
|
17
|
+
class << self
|
18
|
+
fattr(:instance) { new }
|
19
|
+
end
|
20
|
+
fattr(:words) { {} }
|
21
|
+
fattr(:abilities) { {} }
|
22
|
+
def reg_word(word,&b)
|
23
|
+
words[word.to_s] = b
|
24
|
+
words["first_#{word}"] = lambda do |event|
|
25
|
+
b.call(event) && event.first
|
26
|
+
end
|
27
|
+
end
|
28
|
+
def reg_ability(word,ability)
|
29
|
+
abilities[word.to_s] = ability
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Word
|
34
|
+
include FromHash
|
35
|
+
attr_accessor :raw
|
36
|
+
def self.parsed(ops)
|
37
|
+
new(ops)
|
38
|
+
end
|
39
|
+
fattr(:word_blk) do
|
40
|
+
Words.instance.words[raw.to_s] || (raise "no block for #{raw}")
|
41
|
+
end
|
42
|
+
def occured?(side)
|
43
|
+
if word_blk.arity == 1
|
44
|
+
side.events.cond?(&word_blk)
|
45
|
+
else
|
46
|
+
word_blk[side,nil]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module Phrase
|
52
|
+
def self.phrase_class(raw)
|
53
|
+
a = raw.split(" ")
|
54
|
+
h = {"on" => On, "if" => If, "for" => For}
|
55
|
+
if a.size == 3
|
56
|
+
h[a[1]]
|
57
|
+
else
|
58
|
+
Basic
|
59
|
+
end
|
60
|
+
end
|
61
|
+
def self.parsed(raw)
|
62
|
+
cls = phrase_class(raw)
|
63
|
+
cls.new(:raw => raw)
|
64
|
+
end
|
65
|
+
|
66
|
+
class Base
|
67
|
+
include FromHash
|
68
|
+
attr_accessor :raw, :category
|
69
|
+
fattr(:before_clause_raw) { raw.split(" ").first }
|
70
|
+
fattr(:before_clause) do
|
71
|
+
before_clause_raw[0..0] == 'o' ? before_clause_raw[1..-1] : before_clause_raw
|
72
|
+
end
|
73
|
+
fattr(:optional) do
|
74
|
+
before_clause_raw[0..0] == 'o'
|
75
|
+
end
|
76
|
+
fattr(:after_clause) { raw.split(" ").last }
|
77
|
+
fattr(:after_word) do
|
78
|
+
Word.parsed(:raw => after_clause)
|
79
|
+
end
|
80
|
+
def trigger; nil; end
|
81
|
+
def ability; nil; end
|
82
|
+
def mod_card(card)
|
83
|
+
card.triggers << trigger.tap { |x| x.optional = optional if x.respond_to?('optional=') } if trigger
|
84
|
+
card.abilities << ability.tap { |x| x.optional = optional if x.respond_to?('optional=') } if ability
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_honor(side)
|
88
|
+
side.honor += before_clause.to_i
|
89
|
+
end
|
90
|
+
def add_power(side)
|
91
|
+
side.pool.power += before_clause.to_i
|
92
|
+
end
|
93
|
+
def draw_cards(side)
|
94
|
+
before_clause.to_i.times do
|
95
|
+
side.draw_one!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Basic < Base
|
101
|
+
def mod_card(card)
|
102
|
+
if category == :runes
|
103
|
+
card.runes += before_clause.to_i if before_clause.to_i > 0
|
104
|
+
elsif category == :power || category == :add_power
|
105
|
+
card.power += before_clause.to_i
|
106
|
+
elsif category == :draw_cards
|
107
|
+
card.abilities << lambda do |side|
|
108
|
+
draw_cards(side)
|
109
|
+
end
|
110
|
+
elsif category.kind_of?(Class)
|
111
|
+
card.abilities << category.new(:optional => optional, :parent_card => card)
|
112
|
+
else
|
113
|
+
raise "unknown category #{category}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class On < Base
|
119
|
+
fattr(:trigger) do
|
120
|
+
lambda do |event, side|
|
121
|
+
if after_word.word_blk[event]
|
122
|
+
send(category, side)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class If < Base
|
129
|
+
fattr(:ability) do
|
130
|
+
lambda do |side|
|
131
|
+
if after_word.occured?(side)
|
132
|
+
send(category, side)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class For < Base
|
139
|
+
fattr(:ability) do
|
140
|
+
lambda do |side|
|
141
|
+
meth = "#{after_clause}_runes"
|
142
|
+
val = side.played.pool.send(meth) + before_clause.to_i
|
143
|
+
side.played.pool.send("#{meth}=",val)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Card
|
150
|
+
include FromHash
|
151
|
+
def self.input_field(*args)
|
152
|
+
attr_accessor *args
|
153
|
+
end
|
154
|
+
input_field :rune_cost, :honor_given, :power, :runes, :draw
|
155
|
+
input_field :banish_center, :banish_hand_discard
|
156
|
+
input_field :special_abilities, :realm, :name, :honor, :power_cost
|
157
|
+
fattr(:card_class) do
|
158
|
+
::Card::Hero
|
159
|
+
end
|
160
|
+
def phrase(raw, cat)
|
161
|
+
return nil unless raw
|
162
|
+
Phrase.parsed(raw).tap { |x| x.category = cat }
|
163
|
+
end
|
164
|
+
def mod_for_phrases(raw, cat, card)
|
165
|
+
return unless raw
|
166
|
+
#puts [raw,cat,card_class,name].inspect
|
167
|
+
raw.split(",").each do |r|
|
168
|
+
phrase(r,cat).mod_card(card)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
fattr(:card) do
|
172
|
+
res = card_class.new(:name => name, :realm => realm)
|
173
|
+
|
174
|
+
mod_for_phrases(runes, :runes, res)
|
175
|
+
mod_for_phrases(honor_given,:add_honor,res)
|
176
|
+
mod_for_phrases(power, :add_power, res)
|
177
|
+
mod_for_phrases(draw, :draw_cards, res)
|
178
|
+
|
179
|
+
mod_for_phrases(banish_center, Ability::BanishCenter, res)
|
180
|
+
mod_for_phrases(banish_hand_discard, Ability::BanishHandDiscard, res)
|
181
|
+
|
182
|
+
if special_abilities
|
183
|
+
word = Word.parsed(:raw => special_abilities)
|
184
|
+
res.abilities << word.word_blk
|
185
|
+
end
|
186
|
+
|
187
|
+
res.power_cost = power_cost.to_i if res.monster?
|
188
|
+
res.rune_cost = rune_cost.to_i unless res.monster?
|
189
|
+
|
190
|
+
res
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
class Line
|
195
|
+
include FromHash
|
196
|
+
attr_accessor :raw
|
197
|
+
attr_accessor :realm_short
|
198
|
+
fattr(:card_class) do
|
199
|
+
h = {'H' => ::Card::Hero, 'C' => ::Card::Construct, 'M' => ::Card::Monster}
|
200
|
+
h[raw['card_type']] || (raise 'no class')
|
201
|
+
end
|
202
|
+
fattr(:realm) do
|
203
|
+
h = {'L' => :lifebound, 'M' => :mechana, 'V' => :void, 'E' => :enlightened, 'S' => :monster}
|
204
|
+
h[raw['realm_short']] || (raise 'no realm')
|
205
|
+
end
|
206
|
+
fattr(:parse_card) do
|
207
|
+
card = Card.new
|
208
|
+
%w(card_class realm).each do |f|
|
209
|
+
card.send("#{f}=",send(f))
|
210
|
+
end
|
211
|
+
%w(name rune_cost honor runes power power_cost draw banish_center banish_hand_discard special_abilities).each do |f|
|
212
|
+
card.send("#{f}=",raw[f])
|
213
|
+
end
|
214
|
+
card
|
215
|
+
end
|
216
|
+
fattr(:cards) do
|
217
|
+
raw['count'].to_i.of { parse_card.card! }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class InputFile
|
222
|
+
fattr(:raw_lines) do
|
223
|
+
require 'csv'
|
224
|
+
res = []
|
225
|
+
f = File.expand_path(File.dirname(__FILE__)) + "/cards.csv"
|
226
|
+
CSV.foreach(f,:headers => true, :row_sep => "\n", :quote_char => '"') do |row|
|
227
|
+
h = {}
|
228
|
+
row.each do |k,v|
|
229
|
+
#puts [k,v].inspect
|
230
|
+
k = k.downcase.gsub(' ','_')
|
231
|
+
h[k] = v
|
232
|
+
end
|
233
|
+
res << h if h['name']
|
234
|
+
end
|
235
|
+
res
|
236
|
+
end
|
237
|
+
fattr(:lines) do
|
238
|
+
raw_lines.map { |x| Line.new(:raw => x) }
|
239
|
+
end
|
240
|
+
fattr(:cards) do
|
241
|
+
lines.map { |x| x.cards }.flatten
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
Parse.reg_word :lifebound_hero_played do |event|
|
249
|
+
event.kind_of?(Event::CardPlayed) && event.card.realm.to_s == 'lifebound' && event.card.kind_of?(Card::Hero)
|
250
|
+
end
|
251
|
+
|
252
|
+
Parse.reg_word :mechana_construct_played do |event|
|
253
|
+
event.kind_of?(Event::CardPlayed) && event.card.realm.to_s == 'mechana' && event.card.kind_of?(Card::Construct)
|
254
|
+
end
|
255
|
+
|
256
|
+
Parse.reg_word :center_monster_killed do |event|
|
257
|
+
event.kind_of?(Event::MonsterKilled) && event.center
|
258
|
+
end
|
259
|
+
|
260
|
+
Parse.reg_word :two_or_more_constructs do |side,junk|
|
261
|
+
side.constructs.size >= 2
|
262
|
+
end
|
263
|
+
|
264
|
+
(2..6).each do |i|
|
265
|
+
Parse.reg_ability "kill_monster_#{i}", Ability::KillMonster.new(:max_power => i)
|
266
|
+
end
|
267
|
+
|
268
|
+
(1..10).each do |i|
|
269
|
+
Parse.reg_ability "acquire_hero_#{i}", Ability::AcquireHero.new(:max_rune_cost => i)
|
270
|
+
end
|
271
|
+
|
272
|
+
Parse.reg_ability :discard_construct, Ability::DiscardConstruct
|
273
|
+
Parse.reg_ability :discard_all_but_one_construct, Ability::KeepOneConstruct
|
274
|
+
|
275
|
+
Parse.reg_ability :copy_hero, Ability::CopyHero.new
|
276
|
+
|
277
|
+
%w(power_or_rune_1 take_opponents_card acquire_center).each do |f|
|
278
|
+
Parse.reg_ability f do |*args|
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Pool
|
2
|
+
include FromHash
|
3
|
+
setup_mongo_persist :runes, :mechana_runes, :construct_runes, :power
|
4
|
+
fattr(:runes) { 0 }
|
5
|
+
fattr(:mechana_runes) { 0 }
|
6
|
+
fattr(:construct_runes) { 0 }
|
7
|
+
fattr(:power) { 0 }
|
8
|
+
def use_rune_type(type, max, modify=true)
|
9
|
+
raise "bad max" unless max
|
10
|
+
pool = send(type)
|
11
|
+
if max >= pool
|
12
|
+
send("#{type}=",0) if modify
|
13
|
+
max - pool
|
14
|
+
else
|
15
|
+
send("#{type}=",pool - max) if modify
|
16
|
+
0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def can_purchase?(card)
|
20
|
+
remaining = card.rune_cost
|
21
|
+
raise "bad rune cost #{card.name}" unless remaining
|
22
|
+
if card.mechana? && card.construct?
|
23
|
+
remaining = use_rune_type(:mechana_runes,remaining,false)
|
24
|
+
end
|
25
|
+
if card.construct?
|
26
|
+
remaining = use_rune_type(:construct_runes,remaining,false)
|
27
|
+
end
|
28
|
+
if remaining > 0
|
29
|
+
remaining = use_rune_type(:runes,remaining,false)
|
30
|
+
end
|
31
|
+
remaining == 0
|
32
|
+
end
|
33
|
+
def deplete_runes(card)
|
34
|
+
remaining = card.rune_cost
|
35
|
+
if card.mechana? && card.construct?
|
36
|
+
remaining = use_rune_type(:mechana_runes,remaining)
|
37
|
+
end
|
38
|
+
if card.construct?
|
39
|
+
remaining = use_rune_type(:construct_runes,remaining)
|
40
|
+
end
|
41
|
+
if remaining > 0
|
42
|
+
remaining = use_rune_type(:runes,remaining)
|
43
|
+
end
|
44
|
+
raise "not enough runes" if remaining > 0
|
45
|
+
end
|
46
|
+
def to_s
|
47
|
+
"#{runes} (#{mechana_runes}) / #{power}"
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
load File.dirname(__FILE__) + '/../ascension.rb'
|
3
|
+
|
4
|
+
Ability::CardChoice.chooser = RChoice::CommandLineChooser.new
|
5
|
+
|
6
|
+
game = Game.new
|
7
|
+
side = Side.new(:game => game)
|
8
|
+
game.sides << side
|
9
|
+
|
10
|
+
game.deck = CenterDeck.starting
|
11
|
+
game.center.fill!
|
12
|
+
side.deck << game.deck.get_one('Temple Librarian')
|
13
|
+
#side.deck[-1] = Card::Hero.arha
|
14
|
+
side.draw_hand!
|
15
|
+
side.hand << game.deck.get_one('Void Thirster')
|
16
|
+
side.deck << game.deck.get_one('Void Initiate')
|
17
|
+
|
18
|
+
while true
|
19
|
+
|
20
|
+
side.hand.play_all!
|
21
|
+
|
22
|
+
side.print_status!
|
23
|
+
|
24
|
+
Ability::DoCenterAction.new.call_until_nil(side) { side.print_status! }
|
25
|
+
|
26
|
+
side.end_turn!
|
27
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
RChoice::Choice.setup_mongo_persist :optional, :name, :options
|
2
|
+
RChoice::Option.setup_mongo_persist :base_obj
|
3
|
+
|
4
|
+
class Choices
|
5
|
+
class << self
|
6
|
+
fattr(:list) { [] }
|
7
|
+
def setup_chooser!
|
8
|
+
Ability::CardChoice.chooser = lambda { |choice| self.list << choice }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'json'
|
2
|
+
module JsonPersist
|
3
|
+
def as_json(*args)
|
4
|
+
to_json_hash
|
5
|
+
end
|
6
|
+
def new_hash_json(attr,h,obj)
|
7
|
+
if obj.can_mongo_convert?
|
8
|
+
if obj.respond_to?(:select) && false
|
9
|
+
h.merge(attr => obj.to_mongo_hash)
|
10
|
+
elsif [Numeric,String].any? { |c| obj.kind_of?(c) }
|
11
|
+
h.merge(attr => obj)
|
12
|
+
else
|
13
|
+
h.merge(attr => obj.as_json)
|
14
|
+
end
|
15
|
+
else
|
16
|
+
h
|
17
|
+
end
|
18
|
+
rescue
|
19
|
+
return h
|
20
|
+
end
|
21
|
+
def to_json_hash
|
22
|
+
res = mongo_child_attributes.inject({}) do |h,attr|
|
23
|
+
obj = send(attr)
|
24
|
+
#raise "#{attr} is nil" unless obj
|
25
|
+
new_hash_json(attr,h,obj)
|
26
|
+
end.merge("_mongo_class" => self.class.to_s)
|
27
|
+
klass.mongo_reference_attributes.each do |attr|
|
28
|
+
val = send(attr)
|
29
|
+
res[attr] = val.to_mongo_ref_hash if val
|
30
|
+
end
|
31
|
+
|
32
|
+
if respond_to?(:addl_json_attributes) && true
|
33
|
+
puts "in addl_json_attributes part"
|
34
|
+
addl = [addl_json_attributes].flatten.select { |x| x }
|
35
|
+
addl.each do |attr|
|
36
|
+
puts "addl attr #{attr}"
|
37
|
+
res = new_hash_json(attr,res,send(attr))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
res
|
41
|
+
end
|
42
|
+
def to_json(*args)
|
43
|
+
as_json(*args).to_json
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module MongoHash
|
48
|
+
def as_json(*args)
|
49
|
+
res = {}
|
50
|
+
each do |k,v|
|
51
|
+
v = v.as_json(*args) if v.respond_to?(:as_json)
|
52
|
+
res[k.safe_to_mongo_hash.to_mongo_key] = v
|
53
|
+
end
|
54
|
+
res
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Array
|
59
|
+
def as_json(*args)
|
60
|
+
map do |obj|
|
61
|
+
obj.respond_to?(:as_json) ? obj.as_json(*args) : obj
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Class
|
67
|
+
def setup_mongo_persist(*attrs)
|
68
|
+
include MongoPersist
|
69
|
+
include JsonPersist
|
70
|
+
define_method(:mongo_attributes) do
|
71
|
+
attrs.flatten.map { |x| x.to_s }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class TurnManager
|
2
|
+
include FromHash
|
3
|
+
setup_mongo_persist :current_side_index
|
4
|
+
attr_accessor :game
|
5
|
+
fattr(:current_side_index) { 0 }
|
6
|
+
def current_side
|
7
|
+
game.sides[current_side_index]
|
8
|
+
end
|
9
|
+
def advance!
|
10
|
+
current_side.end_turn!
|
11
|
+
self.current_side_index = current_side_index + 1
|
12
|
+
self.current_side_index = 0 if current_side_index >= game.sides.size
|
13
|
+
end
|
14
|
+
end
|