traveller_rpg 0.0.1.1 → 0.1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,21 @@
1
1
  require 'traveller_rpg'
2
+ require 'traveller_rpg/homeworld'
3
+ require 'traveller_rpg/skill_set'
2
4
 
3
5
  module TravellerRPG
4
6
  class Character
5
7
  Stats = Struct.new(:strength, :dexterity, :endurance,
6
8
  :intelligence, :education, :social_status) do
7
- def self.roll(spec = '2d6')
8
- self.new(*Array.new(6) { TravellerRPG.roll spec })
9
+ def self.sym(str)
10
+ str.is_a?(Symbol) ? str : str.to_s.downcase.gsub(' ', '_').to_sym
11
+ end
12
+
13
+ def self.member?(stat)
14
+ self.members.include? self.sym stat
15
+ end
16
+
17
+ def self.roll
18
+ self.new(*Array.new(6) { TravellerRPG.roll '2d6' })
9
19
  end
10
20
 
11
21
  def self.empty
@@ -16,23 +26,44 @@ module TravellerRPG
16
26
  hsh.each { |k,v| self[k] += v if self[k] }
17
27
  self
18
28
  end
29
+
30
+ def bump(sym, level = nil)
31
+ if level
32
+ self[sym] = level if level > self[sym]
33
+ else
34
+ self[sym] += 1
35
+ end
36
+ end
37
+
38
+ def report
39
+ rpt = []
40
+ w = 'Social Status'.size + 2
41
+ self.members.each { |sym|
42
+ rpt << format("%s: %s", sym.to_s.capitalize.rjust(w, ' '), self[sym])
43
+ }
44
+ rpt.join("\n")
45
+ end
19
46
  end
20
47
 
21
48
  Description = Struct.new(:name, :gender, :age,
22
49
  :appearance, :plot, :temperament) do
23
- def self.new_with_hash(hsh)
24
- self.new(hsh[:name], hsh[:gender], hsh[:age],
25
- hsh[:appearance], hsh[:plot], hsh[:temperament])
50
+ def merge(other)
51
+ self.class.new(other[:name] || self.name,
52
+ other[:gender] || self.gender,
53
+ other[:age] || self.age,
54
+ other[:appearance] || self.appearance,
55
+ other[:plot] || self.plot,
56
+ other[:temperament] || self.temperament)
26
57
  end
27
58
 
28
- def merge(other)
29
- other = self.class.new_with_hash(other) if other.is_a?(Hash)
30
- self.class.new(other.name || self.name,
31
- other.gender || self.gender,
32
- other.age || self.age,
33
- other.appearance || self.appearance,
34
- other.plot || self.plot,
35
- other.temperament || self.temperament)
59
+ def report(short: false)
60
+ rpt = []
61
+ w = (short ? 'Gender' : 'Temperament').size + 2
62
+ self.members.each { |sym|
63
+ next if short and [:appearance, :plot, :temperament].include?(sym)
64
+ rpt << format("%s: %s", sym.to_s.capitalize.rjust(w, ' '), self[sym])
65
+ }
66
+ rpt.join("\n")
36
67
  end
37
68
  end
38
69
 
@@ -50,89 +81,64 @@ module TravellerRPG
50
81
  end
51
82
  end
52
83
 
53
- attr_reader :desc, :stats, :homeworld, :skills,
54
- :stuff, :credits, :cash_rolls
84
+ attr_reader :desc, :stats, :skills, :stuff, :credits, :cash_rolls
55
85
 
56
- def initialize(desc:, stats:, homeworld:,
57
- skills: {}, stuff: {}, log: [], credits: 0, cash_rolls: 0)
86
+ def initialize(desc:, stats:, skills: SkillSet.new, stuff: {},
87
+ log: [], credits: 0, cash_rolls: 0, homeworld: nil)
58
88
  @desc = desc
59
89
  @stats = stats
60
- @homeworld = homeworld
61
90
  @skills = skills
62
91
  @stuff = stuff
63
92
  @log = log
64
- @cash_rolls = cash_rolls # max 3 lifetime
65
93
  @credits = credits
66
- self.birth
94
+ @cash_rolls = cash_rolls # max 3 lifetime
95
+ self.birth(homeworld) if homeworld
67
96
  end
68
97
 
69
98
  def stats_dm(stat_sym)
70
99
  self.class.stats_dm(@stats[stat_sym])
71
100
  end
72
101
 
73
- # gain background skills based on homeworld
74
- def birth
75
- return nil unless @log.empty?
76
- self.log format("%s was born on %s (%s)",
77
- @desc.name,
78
- @homeworld.name,
79
- @homeworld.traits.join(' '))
80
- skill_count = 3 + self.stats_dm(:education)
81
- self.log format("Education %i qualifies for %i skills",
82
- @stats.education, skill_count)
83
- skill_choices = []
84
-
85
- # choose skill_count skills
86
- if @homeworld.skills.size <= skill_count
87
- self.log format("Homeworld %s only has %i skills available",
88
- @homeworld.name, @homeworld.skills.size)
89
- skill_choices = @homeworld.skills
90
- else
91
- skill_count.times { |i|
92
- available = @homeworld.skills - skill_choices
93
- skill_choices << TravellerRPG.choose("Choose a skill:", *available)
94
- }
102
+ # accepts an array of strings or a single string
103
+ # choose from the array; provide a single skill
104
+ # return nil if no skill was trained
105
+ def basic_training(skill)
106
+ unless @skills[skill]
107
+ self.log "Basic Training: #{skill} 0"
108
+ @skills.provide skill
95
109
  end
96
- skill_choices.each { |sym|
97
- if TravellerRPG::SKILLS.key?(sym)
98
- self.log "Acquired background skill: #{sym} 0"
99
- @skills[sym] ||= 0
100
- else
101
- raise(KeyError, sym)
102
- end
103
- }
104
110
  self
105
111
  end
106
112
 
107
- def train(sym, level = nil)
108
- target = TravellerRPG::SKILLS.key?(sym) ? @skills : @stats
109
- target[sym] ||= 0
110
- if level
111
- if target[sym] < level
112
- target[sym] = level
113
+ def benefit(item)
114
+ case item
115
+ when Integer
116
+ if item != 0
117
+ @credits += item
118
+ self.log "Career benefit: #{item} credits"
119
+ end
120
+ when String
121
+ matches = item.match %r{\A(\d)x (.+)}
122
+ if matches
123
+ # e.g. 2x Ship Share
124
+ count = matches[1].to_i
125
+ item = matches[2]
113
126
  else
114
- self.log "#{sym} #{level} is < current #{target[sym]}"
127
+ count = 1
115
128
  end
129
+ qty = count > 1 ? "(#{count})" : ''
130
+ @stuff[item] ||= 0
131
+ @stuff[item] += count
132
+ self.log "Career benefit: #{item} #{qty}"
133
+ when Symbol
134
+ @stats.bump(item)
135
+ self.log "Career benefit: #{item} bump to #{@stats[item]}"
136
+ when Array
137
+ item.each { |i| self.benefit i }
116
138
  else
117
- target[sym] += 1
118
- level = target[sym]
139
+ raise "unexpected benefit: #{item.inspect}"
119
140
  end
120
- self.log "#{sym} is trained to #{level}"
121
- end
122
-
123
- def add_stuff(benefits)
124
- benefits.each { |sym, val|
125
- self.log "Collecting #{sym} #{val}"
126
- case @stuff[sym]
127
- when Numeric, Array
128
- self.log "Adding #{sym} #{val} to #{@stuff[sym]}"
129
- @stuff[sym] += val
130
- when NilClass
131
- @stuff[sym] = val
132
- else
133
- raise("unexpected benefit: #{sym} #{val} (#{val.class})")
134
- end
135
- }
141
+ self
136
142
  end
137
143
 
138
144
  def log(msg = nil)
@@ -142,6 +148,7 @@ module TravellerRPG
142
148
  self
143
149
  end
144
150
 
151
+ # convenience
145
152
  def name
146
153
  @desc.name
147
154
  end
@@ -150,62 +157,23 @@ module TravellerRPG
150
157
  years ? @desc.age += years : @desc.age
151
158
  end
152
159
 
153
- def skill_check?(skill, val = 0)
154
- @skills[skill] and @skills[skill] >= val
155
- end
156
-
157
- def skill_level(sym)
158
- @skills[sym] and @skills[sym].clamp(0, 4)
159
- end
160
-
161
160
  def cash_roll(amount)
162
161
  @cash_rolls += 1
163
162
  if @cash_rolls <= 3
164
163
  @credits += amount
165
- self.log "Acquired #{amount} credits from cash roll ##{@cash_rolls}"
164
+ self.log "Cash roll ##{@cash_rolls}: #{amount} credits"
166
165
  else
167
- self.log "Ignoring cash roll ##{@cash_rolls}"
166
+ self.log "Cash roll ##{@cash_rolls}: Ignored"
168
167
  end
169
168
  self
170
169
  end
171
170
 
172
- def benefit(item)
173
- if item.is_a?(Integer)
174
- if item != 0
175
- self.log "Acquired #{item} credits as a career benefit"
176
- @credits += item
177
- end
178
- else
179
- self.log "Acquired #{item} as a career benefit"
180
- @stuff[item] ||= 0
181
- @stuff[item] += 1
182
- end
183
- end
184
-
185
171
  def report(desc: :short, stats: true, skills: true, stuff: true,
186
172
  log: false, credits: true)
187
173
  hsh = {}
188
- if desc
189
- hsh['Description'] = { 'Name' => @desc.name,
190
- 'Gender' => @desc.gender,
191
- 'Age' => @desc.age }
192
- if desc == :long
193
- hsh['Description'].merge! 'Appearance' => @desc.appearance,
194
- 'Temperament' => @desc.temperament,
195
- 'Plot' => @desc.plot
196
- end
197
- end
198
- if stats
199
- hsh['Characteristics'] = {
200
- 'Strength' => @stats.strength,
201
- 'Dexterity' => @stats.dexterity,
202
- 'Endurance' => @stats.endurance,
203
- 'Intelligence' => @stats.intelligence,
204
- 'Education' => @stats.education,
205
- 'Social Status' => @stats.social_status,
206
- }
207
- end
208
- hsh['Skills'] = @skills if skills
174
+ hsh['Description'] = @desc.report(short: desc == :short) if desc
175
+ hsh['Characteristics'] = @stats.report if stats
176
+ hsh['Skills'] = @skills.report if skills
209
177
  hsh['Stuff'] = @stuff if stuff
210
178
  hsh['Log'] = @log if log
211
179
  if credits
@@ -217,10 +185,12 @@ module TravellerRPG
217
185
  report = []
218
186
  hsh.each { |section, tbl|
219
187
  report << "#{section}\n==="
220
- report << "(none)" if tbl.empty?
221
- if tbl.is_a?(Hash)
222
- tbl.each { |label, val|
223
- report << format("%s: %s", label.to_s.rjust(20, ' '), val.to_s)
188
+ if tbl.empty?
189
+ report << "(none)"
190
+ elsif tbl.is_a?(Hash)
191
+ width = tbl.keys.map(&:size).max + 2
192
+ tbl.each { |k, v|
193
+ report << format("%s: %s", k.to_s.rjust(width, ' '), v)
224
194
  }
225
195
  elsif tbl.is_a?(Array)
226
196
  report += tbl
@@ -231,5 +201,26 @@ module TravellerRPG
231
201
  }
232
202
  report.join("\n")
233
203
  end
204
+
205
+ protected
206
+
207
+ # gain background skills based on homeworld
208
+ def birth(homeworld)
209
+ raise "log should be empty" unless @log.empty?
210
+ skill_count = 3 + self.stats_dm(:education)
211
+ self.log format("%s was born on %s (%s)",
212
+ @desc.name,
213
+ homeworld.name,
214
+ homeworld.traits.join(' '))
215
+ puts self.report(desc: :long, skills: false,
216
+ stuff: false, credits: false)
217
+ self.log format("Education %i provides up to %i background skills",
218
+ @stats.education, skill_count)
219
+ homeworld.choose_skills(skill_count).each { |skill|
220
+ self.log "Background skill: #{skill} 0"
221
+ @skills.provide skill
222
+ }
223
+ self
224
+ end
234
225
  end
235
226
  end
@@ -29,7 +29,7 @@ module TravellerRPG
29
29
  end
30
30
 
31
31
  def self.gender
32
- TravellerRPG.roll(dice: 1) > 3 ? 'M' : 'F'
32
+ TravellerRPG.roll('d6') > 3 ? 'M' : 'F'
33
33
  end
34
34
 
35
35
  def self.hair(tone: nil, body: nil, color: nil, length: nil)
@@ -3,49 +3,68 @@ require 'traveller_rpg'
3
3
  module TravellerRPG
4
4
  class Homeworld
5
5
  TRAITS = {
6
- agricultural: :animals_group,
7
- asteroid: :zero_g,
8
- desert: :survival,
9
- fluid_oceans: :seafarer_group,
10
- garden: [:animals_riding, :animals_veterinary],
11
- high_technology: :computers,
12
- high_population: :streetwise,
13
- ice_capped: :vacc_suit,
14
- industrial: :trade_group,
15
- low_technology: :survival,
16
- poor: :animals_group,
17
- rich: :carouse,
18
- water_world: :seafarer_group,
19
- vacuum: :vacc_suit,
20
- education: [:admin, :advocate, :art_group, :carouse, :comms,
21
- :computers, :drive_group, :engineer_group, :language_group,
22
- :medic, :physical_sciences_group, :life_sciences_group,
23
- :social_sciences_group, :space_sciences_group, :trade_group],
6
+ economy: {
7
+ agricultural: ['Animals'],
8
+ industrial: ['Profession'],
9
+ high_tech: ['Astrogation', 'Electronics'],
10
+ },
11
+ wealth: {
12
+ poor: ['Medic', 'Melee'],
13
+ rich: ['Carouse', 'Gambler', 'Profession'],
14
+ },
15
+ population: {
16
+ low_population: ['Jack Of All Trades', 'Survival'],
17
+ high_population: ['Art', 'Streetwise'],
18
+ },
19
+ environment: {
20
+ desert: ['Flyer', 'Survival'],
21
+ fluid_oceans: ['Navigation', 'Seafarer'],
22
+ garden: ['Animals', 'Art'],
23
+ ice_capped: ['Science', 'Vacc Suit'],
24
+ minerals: ['Drive', 'Explosives'],
25
+ water_world: ['Seafarer', 'Tactics'],
26
+ },
24
27
  }
25
28
  TRAIT_MIN = 3
26
- TRAIT_MAX = 6
29
+ TRAIT_MAX = TRAITS.keys.size
27
30
 
28
31
  attr_reader :name, :traits, :skills
29
32
 
30
- def initialize(name, traits = [])
33
+ def initialize(name, traits = {})
31
34
  @name = name
32
- if traits.size > self.class::TRAIT_MAX
33
- warn "lots of world traits: #{traits}"
34
- elsif traits.empty?
35
- sample_num = rand(TRAIT_MAX - TRAIT_MIN + 1) + TRAIT_MIN
36
- traits = self.class::TRAITS.keys.sample(sample_num)
37
- end
38
- @traits = traits
39
35
  @skills = []
40
- @traits.each { |trait|
41
- skill = self.class::TRAITS.fetch(trait)
42
- if skill.is_a?(Array)
43
- skill.each { |sk| @skills << sk }
44
- else
45
- @skills << skill
46
- end
47
- }
36
+
37
+ if traits.empty?
38
+ trait_count = rand(TRAIT_MAX - TRAIT_MIN + 1) + TRAIT_MIN
39
+ @traits = self.class::TRAITS.keys.sample(trait_count).map { |t|
40
+ k = TRAITS[t].keys.sample
41
+ @skills += TRAITS[t][k]
42
+ k
43
+ }
44
+ else
45
+ @traits = []
46
+ traits.each { |type, trait|
47
+ @skills += TRAITS.fetch(type).fetch(trait)
48
+ @traits << trait
49
+ }
50
+ end
51
+
48
52
  @skills.uniq!
53
+ def @skills.choose(count)
54
+ s = self.dup
55
+ return s if s.size < count
56
+ Array.new(count) {
57
+ s.delete TravellerRPG.choose("Choose background skill:", *s)
58
+ }
59
+ end
60
+ end
61
+
62
+ def choose_skills(count)
63
+ s = @skills.dup
64
+ return s if s.size <= count
65
+ Array.new(count) {
66
+ s.delete TravellerRPG.choose("Choose background skill:", *s)
67
+ }
49
68
  end
50
69
  end
51
70
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: traveller_rpg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.1
4
+ version: 0.1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rick Hull
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-05 00:00:00.000000000 Z
11
+ date: 2018-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: buildar