-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathvariants.go
312 lines (259 loc) · 10.6 KB
/
variants.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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
package boardgame
import (
"errors"
"strings"
)
//Variant represents a specific configuration of options for a specific game.
//It is just a map of keys to values that are passed to your game so it can
//configure different alternate rulesets, for example using a Short variant
//that uses fewer cards and should play faster, or using a different deck of
//cards than normal. The variant configuration will be considered legal if it
//passes GameDelegate.Variants().LegalVariant(), and will be passed to
//GameDelegate.BeginSetup so that you can set up your game in whatever way
//makes sense for a given Variant. Your GameDelegate defines what valid keys
//and values are, and how they should be displayed to end-users, with its
//return value for GameDelegate.Variants(). See VariantConfig for more on that
//object type.
type Variant map[string]string
/*
VariantConfig defines the legal keys, and their legal values, that a Variant
may have in this game--that is, the set of alternate rule-sets that can be
provided for this game type. You return one of these from your GameDelegate's
Variants() method. Your VariantConfig also defines the display name and
description for each key and each value that will be displayed to the end-
user.
At a high level, a VariantConfig is just a collection of config keys and the
values they may take on.
Those are complemented via DisplayNames and Descriptions to show to end-users
to help them understand what each one does.
Finally, for a given key, it's possible to provide a Default value that will
be used if no value is provided.
Technically you should provide Name and DisplayName for each key and value;
however VariantConfig.Initialize() coes through and sets those to reasonable
defaults, so in practice you can omit a lot of duplication and boilerplate.
For example, Initialize() sets each VariantKey to have the name that it was
given in the map it is a part of, and DisplayNames are set by splitting the
name field and title casing it.
The value you return from your GameDelegate.Variatns() doesn't have to call
Initialize(); the GameManager will automatically do that so as long as you
fetch your variant config from GameManager.Variants(), the config will be
initialized.
See also VariantKey and VariantDisplayInfo.
Here's an example showing a lot of the defaulting in action:
func (g *gameDelegate) Variants() boardgame.VariantConfig {
//As long as you access this via gameManager.Variants() instead of
//directly from the delegate, Initialize will have already been called
//for us, so we can just return the object directly.
return boardgame.VariantConfig{
"color": {
//You can skip setting the VariantDiplayInfo.Name,
//.DisplayName here because initialize (which we call at the
//end of this method) will automatically use the name of the
//entry in the map, and then the displayname will be set to a
//reasonable title-casing.
Values: map[string]*boardgame.VariantDisplayInfo{
"red": {
//Name can be omitted because Initialize() will
//automatically set it bassed on this value's name in
//the map.
//Because DisplayName has been set expclitily it will
//not be overriden in Initialize.
DisplayName: "Very Red",
Description: "The color red",
},
//You can leave the value empty, which will automatically
//create a new value during Initalize with the Name coming
//from the map, and DisplayName set automatically.
"blue": nil,
},
//By setting this, any new Variant created from our NewVariant
//will always have the "color" key to either the value
//provided, or "blue".
Default: "blue",
},
"hand-size": {
VariantDisplayInfo: boardgame.VariantDisplayInfo{
//DisplayName will be "Hand Size" automatically
Description: "How big of a hand players get on initial deal",
},
Default: "normal",
Values: map[string]*boardgame.VariantDisplayInfo{
"small": {
Description: "A small hand",
},
"normal": {
Description: "A normal-sized hand",
},
"large": {
Description: "A large hand",
},
},
},
}
}
*/
type VariantConfig map[string]*VariantKey
//VariantKey represents a specific key in your VariantConfig that has a
//particular meaning for this game type. For example, "color". See
//VariantConfig for more.
type VariantKey struct {
//VariantKey has a DisplayInfo embedded in it the defines the display name
//and description for this configuration key.
VariantDisplayInfo
//The name of the value, in Values, that is default if none provided. Must
//exist in the Values map or Valid() will error.
Default string
//The specific values this key may take, along with their display
//information. For example, "blue", "red".
Values map[string]*VariantDisplayInfo
}
//VariantDisplayInfo is information about a given value and how to display it
//to end- users, with a DisplayName and Description. It is used as part of
//VariantKey both to describe the Key itself as well as to give information
//about the values within the key for each value. See VariantConfig for more.
type VariantDisplayInfo struct {
//The string to actually display to the user
DisplayName string
//An optional description to be shown to the user to describe what the
//setting does.
Description string
//The string that is actually used in the game engine. Name can often be
//skipped because it is often set implicitly during initialization of the
//containing object.
Name string
}
const whitespaceChars = " \n\t"
//Valid returns an error if there is any misconfiguration in this
//VariantConfig in general. In particular, it verifies that the implied name
//for each key matches its explicit Name property, and the same for values. It
//also verifies that if there's a default it denotes a valid value that was
//explicitly listed. Effectively this checks if Initialize() has been called
//or not. NewGameManager will check this during creation of a new game type.
func (v VariantConfig) Valid() error {
if v == nil {
return nil
}
for name, key := range v {
if name != key.Name {
return errors.New("Key " + name + " does not have its name set the same: " + key.Name)
}
if name != strings.ToLower(name) {
return errors.New("Key " + name + " has upper-case letters but may only have lower-case.")
}
if strings.IndexAny(name, whitespaceChars) != -1 {
return errors.New("Key " + name + " has whitespace chars which is illegal")
}
if len(key.Values) == 0 {
return errors.New("Key " + name + " does not define any values.")
}
for valName, val := range key.Values {
if val == nil {
return errors.New("Key " + name + " value " + valName + " is set to nil")
}
if valName != val.Name {
return errors.New("Key " + name + " value " + valName + " does not have its name set the same: " + val.Name)
}
if valName != strings.ToLower(valName) {
return errors.New("Key " + name + " value " + valName + " has upper-case letters but may only have lower-case.")
}
if strings.IndexAny(valName, whitespaceChars) != -1 {
return errors.New("Key " + name + " value " + valName + " has whitespace chars which is illegal")
}
}
if key.Default != "" && key.Values[key.Default] == nil {
return errors.New("Key " + name + " has a default of " + key.Default + " but that is not valid value")
}
}
return nil
}
//Initialize calls initalize on each Key in config, setting reasonable defaults
//if they weren't provided. Typically your GameDelegate.Variants() doesn't
//have to call this, as the GameManager will. See the documentation for the
//VariantConfig struct for more.
func (v VariantConfig) Initialize() {
for key, val := range v {
val.Initialize(key)
}
}
//Initialize is given the name of this key within its parent's map. The
//provided name will override whatever Name was already set and also sets the
//display name. Called by VariantConfig.Initialize, and also calls all Values'
//Initialize. See VariantConfig for more.
func (v *VariantKey) Initialize(nameInParent string) {
for key, val := range v.Values {
if val == nil {
val = &VariantDisplayInfo{}
v.Values[key] = val
}
val.Initialize(key)
}
v.VariantDisplayInfo.Initialize(nameInParent)
}
//Initialize sets the name to the given name. It also sets the display name
//automatically if one wasn't provided by replacing "_" and "-" with spaces
//and title casing name. It's called automatically by VariantKey.Initalize.
func (d *VariantDisplayInfo) Initialize(nameInParent string) {
d.Name = nameInParent
if d.DisplayName != "" {
return
}
displayName := d.Name
displayName = strings.Replace(displayName, "-", " ", -1)
displayName = strings.Replace(displayName, "_", " ", -1)
//Reduce runs of whitespace to just a single space
displayName = strings.Join(strings.Fields(displayName), " ")
d.DisplayName = strings.Title(displayName)
}
//NewVariant returns a new variant with the given values set. Any extra keys
//that are not in VariantConfig will lead to an error, as well as any values
//that are illegal for their key. Any missing key/value pairs will be set to
//their default, if the key has a default. Typically you don't call this
//directly, but it is called for you implicitly within NewGame.
func (v VariantConfig) NewVariant(variantValues map[string]string) (Variant, error) {
result := make(Variant, len(variantValues))
for key, val := range variantValues {
result[key] = val
}
for key, values := range v {
if result[key] != "" {
continue
}
if values.Default == "" {
continue
}
result[key] = values.Default
}
if err := v.LegalVariant(result); err != nil {
return nil, err
}
//If there's no values just return nil
if len(result) == 0 {
return nil, nil
}
return result, nil
}
//LegalVariant returns whether the given variant has keys and values that are
//enumerated and legal in this config. In paticular, ensures that every key in
//variant is defined in this config, and the value for each key is one of the
//legal values according to the config. Nil configs are OK. The engine calls
//this autoamtically in NewGame to verify the passed variant is legal for this
//game type.
func (v VariantConfig) LegalVariant(variant Variant) error {
if v == nil {
if len(variant) == 0 {
return nil
}
return errors.New("Variant defined values, but the VariantConfig in use didn't define any")
}
for key, val := range variant {
configKey := v[key]
if configKey == nil {
return errors.New("configuration had a property called " + key + " that isn't expected")
}
configValue := configKey.Values[val]
if configValue == nil {
return errors.New("configuration's " + configKey.DisplayName + " property had a value that wasn't allowed: " + val)
}
}
return nil
}