-
Notifications
You must be signed in to change notification settings - Fork 3
/
research.go
231 lines (197 loc) · 7.35 KB
/
research.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package cs
type TechField string
const (
TechFieldNone TechField = ""
Energy TechField = "Energy"
Weapons TechField = "Weapons"
Propulsion TechField = "Propulsion"
Construction TechField = "Construction"
Electronics TechField = "Electronics"
Biotechnology TechField = "Biotechnology"
)
var TechFields []TechField = []TechField{
Energy,
Weapons,
Propulsion,
Construction,
Electronics,
Biotechnology,
}
type NextResearchField string
const (
NextResearchFieldSameField NextResearchField = "SameField"
NextResearchFieldEnergy NextResearchField = "Energy"
NextResearchFieldWeapons NextResearchField = "Weapons"
NextResearchFieldPropulsion NextResearchField = "Propulsion"
NextResearchFieldConstruction NextResearchField = "Construction"
NextResearchFieldElectronics NextResearchField = "Electronics"
NextResearchFieldBiotechnology NextResearchField = "Biotechnology"
NextResearchFieldLowestField NextResearchField = "LowestField"
)
// The researcher interface is used during turn generation to research for a player
// and research with splash damage/stolen resources
type researcher interface {
research(player *Player, resourcesToSpend int, onLevelGained func(player *Player, field TechField)) (spent TechLevel)
researchField(player *Player, field TechField, resourcesToSpend int, onLevelGained func(player *Player, field TechField))
getTotalCost(techLevels TechLevel, field TechField, researchCostLevel ResearchCostLevel, level int) int
}
type research struct {
rules *Rules
}
func NewResearcher(rules *Rules) researcher { return &research{rules} }
// This function will be called repeatedly until no more levels are passed
// From starsfaq
//
// The cost of a tech level depends on four things:
// 1) Your research setting for that field (cheap, normal, or expensive)
// 2) The level you are researching (higher level, higher cost)
// 3) The total number of tech levels you have already have in all fields (you can add it up yourself, or look at 'tech levels' on the 'score' screen).
// 4) whether 'slow tech advance' was selected as a game parameter.
//
// in general,
//
// totalCost=(baseCost + (totalLevels * 10)) * costFactor
//
// where totalLevels=the sum of your current levels in all fields
// costFactor =.5 if your setting for the field is '50% less'
// =1 if your setting for the field is 'normal'
// =1.75 if your setting for the field is '75% more expensive'
//
// If 'slow tech advance' is a game parameter, totalCost should be doubled.
//
// Below is a table showing the base cost of each level.
//
// 1 50 14 18040
// 2 80 15 22440
// 3 130 16 27050
// 4 210 17 31870
// 5 340 18 36900
// 6 550 19 42140
// 7 890 20 47590
// 8 1440 21 53250
// 9 2330 22 59120
// 10 3770 23 65200
// 11 6100 24 71490
// 12 9870 25 77990
// 13 13850 26 84700
func (r *research) research(player *Player, resourcesToSpend int, onLevelGained func(player *Player, field TechField)) (spent TechLevel) {
// keep spending resources until we are done
for {
field := player.Researching
if field == TechFieldNone {
// nothing to research, break out
// this happens if we reach max level
break
}
levelGained, leftoverResources := r.researchFieldOnce(player, player.Researching, resourcesToSpend)
spent.Set(field, spent.Get(field)+resourcesToSpend-leftoverResources)
// we gained a level, switch to a new field
if levelGained {
player.Researching = r.getNextResearchField(player)
resourcesToSpend = leftoverResources
onLevelGained(player, field)
} else {
// break out
break
}
}
return spent
}
// keep researching this field until we run out of resources or it maxes out
func (r *research) researchField(player *Player, field TechField, resourcesToSpend int, onLevelGained func(player *Player, field TechField)) {
for {
// keep researching this field until we max it out or run out of resources to spend
if resourcesToSpend == 0 || r.isAtMaxLevel(player, field) {
break
}
levelGained, leftoverResources := r.researchFieldOnce(player, field, resourcesToSpend)
if levelGained {
onLevelGained(player, field)
}
// keep spending until we're out of resources
resourcesToSpend = leftoverResources
}
}
func (r *research) researchFieldOnce(player *Player, field TechField, resourcesToSpend int) (levelGained bool, resourcesLeftover int) {
if r.isAtMaxLevel(player, field) {
return false, resourcesToSpend
}
// don't research more than the max on this level
level := player.TechLevels.Get(field)
// add the resourcesToSpend to how much we've currently spent
spent := player.TechLevelsSpent.Get(field)
totalCost := r.getTotalCost(player.TechLevels, field, player.Race.ResearchCost.Get(field), level)
if spent+resourcesToSpend >= totalCost {
// increase a level
levelGained = true
// gain a level, set our total cost
player.TechLevels.Set(field, level+1)
player.TechLevelsSpent.Set(field, 0)
spent += resourcesToSpend
// figure out how many leftover points we have
resourcesLeftover = spent - totalCost
} else {
// didn't gain a level, apply resources to field
player.TechLevelsSpent.Set(field, spent+resourcesToSpend)
resourcesLeftover = 0
}
return levelGained, resourcesLeftover
}
func (r *research) getTotalCost(techLevels TechLevel, field TechField, researchCostLevel ResearchCostLevel, level int) int {
maxTechLevel := len(r.rules.TechBaseCost) - 1
// we can't research more than tech level 26
if level >= maxTechLevel {
return 0
}
// figure out our total levels
totalLevels := techLevels.Sum()
// figure out the cost to advance to the next level
baseCost := r.rules.TechBaseCost[level+1]
costFactor := 1.0
switch researchCostLevel {
case ResearchCostExtra:
costFactor = 1.75
case ResearchCostLess:
costFactor = .5
}
// from starsfaq
return int(float64(baseCost+(totalLevels*10)) * costFactor)
}
// check if a player is at max research level for their current field
func (r *research) isAtMaxLevel(player *Player, field TechField) bool {
maxTechLevel := len(r.rules.TechBaseCost) - 1
return player.TechLevels.Get(field) >= maxTechLevel
}
// get the next TechField to research based on the NextResearchField setting
func (r *research) getNextResearchField(player *Player) (nextField TechField) {
// find the next field
nextField = Energy
switch player.NextResearchField {
case NextResearchFieldSameField:
nextField = player.Researching
case NextResearchFieldEnergy:
nextField = Energy
case NextResearchFieldWeapons:
nextField = Weapons
case NextResearchFieldPropulsion:
nextField = Propulsion
case NextResearchFieldConstruction:
nextField = Construction
case NextResearchFieldElectronics:
nextField = Electronics
case NextResearchFieldBiotechnology:
nextField = Biotechnology
case NextResearchFieldLowestField:
nextField = player.TechLevels.Lowest()
}
// if the player is at the max level for this nextField, pick the lowest.
// if they are at the maxLevel for the lowest, return none
if r.isAtMaxLevel(player, nextField) {
nextField = player.TechLevels.Lowest()
// determine the next field to research
if r.isAtMaxLevel(player, nextField) {
return TechFieldNone
}
}
return nextField
}