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