traveller_rpg 0.0.1.1 → 0.1.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.
@@ -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