-
Notifications
You must be signed in to change notification settings - Fork 4
/
component.go
516 lines (426 loc) · 17.2 KB
/
component.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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
package boardgame
import (
"crypto/sha1"
"errors"
"fmt"
"strconv"
)
//A Component represents a movable resource in the game. Cards, dice, meeples,
//resource tokens, etc are all components. You don't define these yourself;
//it's an interface because the core engine uses different underlying structs
//in different cases. Values is a struct that stores the specific values for
//the component, as defined in your ConfigureDecks method on GameDelegate.
//Components are the same across all games of this type. Component is a
//generic notion of that type of obejct; see also ComponentInstance and
//ImmutableComponentInstance for a notion of a SPECIFIC instantiation of a
//given type of component within a specific game. Component references should
//not be compared directly for equality, as sometimes different underlying
//objects will represent the same notional component (in order to satisfy both
//the Component and ComponentInstance interfaces simultaneously). Instead, use
//Equivalent() to test that two Components refer to the same conceptual
//Component.
type Component interface {
//Values returns the ComponentValues struct that you associated with this
//component via deck.AddComponent during GameDelegate.ConfigureDecks().
Values() ComponentValues
//Deck returns the deck that this component is part of.
Deck() *Deck
//DeckIndex returns the index into the deck that this component is. This
//is always fixed and never changes in any game.
DeckIndex() int
//Equivalent checks whether two components are equivalent--that is,
//whether they represent the same index in the same deck.
Equivalent(other Component) bool
//Instance returns a mutable ComponentInstance representing the specific
//instantiation of this component in the given state of the given game.
//Will never return nil, even if the component isn't valid in this state
//----although later things like ContainingStack may error later in that
//case. ComponentInstance is where the methods for moving the instance to
//other stacks lie.
Instance(st State) ComponentInstance
//ImmutableInstance returns an ImmutableComponentInstance representing the
//specific instantiation of this component in the given state of the given
//game. Will never return nil, even if the component isn't valid in this
//state--although later things like ContainingStack may error later in
//that case.
ImmutableInstance(st ImmutableState) ImmutableComponentInstance
//Generic returns true if this Component is the generic component for this
//deck. You might get this component if you ask for a component from a
//sanitized stack. This method is a convenience method equivalent to
//checking for equivalency to Deck().GenericComponent().
Generic() bool
//ptr() is used for when the component itself is used as a key into
//an index and equality is important.
ptr() *component
}
//ImmutableComponentInstance is a specific instantiation of a component as it
//exists in the particular State it is associated with. They are like a
//ComponentInstance, but without mutating methods.
//ImmutableComponentInstances also implement all of the Component information,
//as a convenience you often need both bits of inforamation. The downside of
//this is that two Component values can't be compared directly for equality
//because they may be different underlying objects and wrappers. If you want
//to see if two Components that might be from different states refer to the
//same underlying conceptual Component, use Equivalent(). However,
//ImmutableComponentInstances compared with another ImmutableComponentInstance
//for the same component in the same state will be equal. See also
//ComponentInstance, which extends this interface with mutators as well.
type ImmutableComponentInstance interface {
//ImmutableComponentInstances have all of the information of a base Component, as
//often that's the information you most need.
Component
//ID returns a semi-stable ID for this component within this game and the
//current state this component instance is associated with. Within this
//game, it will only change when the shuffleCount for this component
//changes (that is, when the component is in a stack that is Shuffle()'d
//or when the ComponentInstance is in a stach that has a component moved
//to it via SecretMoveTo). Across games the Id for the "same" component
//will be different, in a way that cannot be guessed without access to
//game.SecretSalt. Typically your game logic can ignore IDs and not use
//them; they're provided to enable certain scenarios and animations in the
//core engine and other packages. See the documentation for Policy for
//more on IDs and why they exist.
ID() string
//ImmutableDynamicValues returns the Dynamic Values for this component in the state
//this instance is associated with. A convenience so you don't have to go
//find them within the state.DynamicComponentValues yourself.
ImmutableDynamicValues() ImmutableSubState
//ContainingImmutableStack will return the stack and slot index for the
//associated component, if that location is not sanitized, and the
//componentinstance is legal for the state it's in. If no error is
//returned, stack.ComponentAt(slotIndex) == c will evaluate to true.
ContainingImmutableStack() (stack ImmutableStack, slotIndex int, err error)
//ImmutableState returns the State object that this ComponentInstance is affiliated
//with.
ImmutableState() ImmutableState
secretMoveCount() int
movedSecretly()
}
//Note that a ComponentInstance doesn't actually guarantee that the component
//is in a mutable context at this moment, just that it was at some point on
//thie state (otherwise you couldn't have gotten a reference to it). In
//practice though, if a Component was ever in a mutable context in a given
//state, it must remain that way, because it can't be moved from a Stack to a
//ImmutableStack.
//ComponentInstance is a mutable instantiation of a specific type of component
//in a particular state of a particular game. You generally get these from a
//Stack that contains them. The instance contains many methods to move the
//component to other stacks or locations. See also ImmutableComponentInstance,
//which is similar but lacks mutator methods.
type ComponentInstance interface {
//ComponentInstance can be used anywhere that ImmutableComponentInstance
//can be.
ImmutableComponentInstance
//DynamicValues returns the Dynamic Values for this component in the state
//this instance is associated with, if it exists. A convenience so you
//don't have to go find them within state.DynamicComponentValues
//yourself.
DynamicValues() SubState
//ContainingStack will return the stack and slot index for the associated
//component, if that location is not sanitized, and the componentinstance
//is legal for the state it's in. If no error is returned,
//stack.ComponentAt(slotIndex) == c will evaluate to true.
ContainingStack() (stack Stack, slotIndex int, err error)
//MoveTo moves the specified component in its current stack to the
//specified slot in the destination stack. The destination stack must be
//different than the one the component's currently in--if you're moving
//components within a stack, use SwapComponent. In destination, slotIndex
//must point to a valid "slot" to put a component, such that after
//insertion, using that index on the destination will return that
//component. In default Stacks, slots are any index from 0 up to and
//including stack.Len(), because the stack will grow to insert the
//component between existing components if necessary. For SizedStacks,
//slotIndex must point to a currently empty slot.
//MoveTo{First,Last,Next}Slot methods are useful if you want to move to
//those locations. If you want the precise location of the inserted
//component to not be visible, see SecretMoveTo.
MoveTo(other Stack, slotIndex int) error
//SecretMoveTo is equivalent to MoveTo, but after the move the Ids of all
//components in destination will be scrambled. SecretMoveTo is useful when
//the destination stack will be sanitized with something like PolicyOrder,
//but the precise location of this insertion should not be observable. For
//example, if you're cutting a given card to an unknown location deep in
//the middle of a large stack.
SecretMoveTo(other Stack, slotIndex int) error
//MoveToFirstSlot moves the component to the first valid slot in the other
//stack. For default Stacks, this is always 0. For SizedStacks, this is
//the first empty slot from the left. A convenience wrapper around
//stack.FirstSlot.
MoveToFirstSlot(other Stack) error
//MoveToLastSlot moves the component to the last valid slot in the other
//stack. For default Stacks, this is always Len(). For SizedStacks, this
//is the first empty slot from the right. A convenience wrappar around
//stack.LastSlot().
MoveToLastSlot(other Stack) error
//MoveToNextSlot moves the component to the next valid slot in the other
//stack where the component could be added without splicing. For default
//stacks this is equivalent to MoveToLastSlot. For fixed size stacks this
//is equivalent to MoveToFirstSlot. A convenience wrapper arond
//stack.NextSlot().
MoveToNextSlot(other Stack) error
//SlideToFirstSlot takes the given component and moves it to the start of
//the same stack, moving everything else up. It is equivalent to removing
//the component, moving it to a temporary stack, and then moving it back
//to the original stack with MoveToFirstSlot--but of course without
//needing the extra scratch stack.
SlideToFirstSlot() error
//SlideToLastSlot takes the given component and moves it to the end of the
//same stack, moving everything else down. It is equivalent to removing
//the component, moving it to a temporary stack, and then moving it back
//to the original stack with MoveToLastSlot--but of course without needing
//the extra scratch stack.
SlideToLastSlot() error
}
type component struct {
values ComponentValues
//The deck we're a part of.
deck *Deck
//The index we are in the deck we're in.
deckIndex int
}
//componentInstance has value method receivers so two that are configured the
//same will test as equal even if they were created separately, and because we
//never need to mutate the values within--all of the mutable state is handled
//on the state object.
type componentInstance struct {
*component
statePtr *state
}
func (c *component) Values() ComponentValues {
if c == nil {
return nil
}
return c.values
}
func (c *component) Deck() *Deck {
if c == nil {
return nil
}
return c.deck
}
func (c *component) DeckIndex() int {
if c == nil {
return -1
}
return c.deckIndex
}
func (c *component) Generic() bool {
if c == nil {
return false
}
return c.Equivalent(c.Deck().GenericComponent())
}
func (c *component) Equivalent(other Component) bool {
if c == nil && other == nil {
return true
}
if c.Deck().Name() != other.Deck().Name() {
return false
}
return c.DeckIndex() == other.DeckIndex()
}
func (c *component) ptr() *component {
return c
}
func (c *component) ImmutableInstance(st ImmutableState) ImmutableComponentInstance {
var ptr *state
if st != nil {
ptr = st.(*state)
}
return componentInstance{
c,
ptr,
}
}
func (c *component) Instance(st State) ComponentInstance {
var ptr *state
if st != nil {
ptr = st.(*state)
}
return componentInstance{
c,
ptr,
}
}
//ComponentValues is the interface that the Values property of a Component
//must implement. You define your own ComponentValues to describe the
//immutable properties of the components in your game type. You associate a
//given ComponentValues struct with a given Component in deck.AddComponent.
//base.ComponentValues is designed to be anonymously embedded in your
//component to implement the latter part of the interface. 'boardgame- util
//codegen' can be used to implement Reader.
type ComponentValues interface {
//Reader is the way that the engine will enumerate and extract properties
//on the ComponentValues.
Reader
//ContainingComponent is the component that this ComponentValues is
//embedded in. It should return the component that was passed to
//SetContainingComponent.
ContainingComponent() Component
//SetContainingComponent is called to let the component values know what
//its containing component is.
SetContainingComponent(c Component)
}
func (c componentInstance) ContainingStack() (Stack, int, error) {
if c.statePtr == nil {
return nil, 0, errors.New("State is non-existent")
}
return c.statePtr.containingStack(c)
}
func (c componentInstance) ContainingImmutableStack() (ImmutableStack, int, error) {
return c.ContainingStack()
}
func (c componentInstance) ID() string {
//Shadow components shouldn't get an Id
if c.Equivalent(c.Deck().GenericComponent()) {
return ""
}
//S should never be nil in normal circumstances, but if it is, return an
//obviously-special Id so it doesn't appear to be the actual Id for this
//component.
if c.statePtr == nil {
return ""
}
input := "insecuredefaultinput"
game := c.statePtr.game
input = game.ID() + game.secretSalt
input += c.Deck().Name() + strconv.Itoa(c.DeckIndex())
//The id only ever changes when the item has moved secretly.
input += strconv.Itoa(c.secretMoveCount())
hash := sha1.Sum([]byte(input))
return fmt.Sprintf("%x", hash)
}
//secretMoveCount returns the secret move count for this component in the
//given state.
func (c componentInstance) secretMoveCount() int {
if c == c.Deck().GenericComponent() {
return 0
}
if c.statePtr == nil {
return 0
}
s := c.statePtr
deckMoveCount, ok := s.secretMoveCount[c.Deck().Name()]
//No components in that deck have been moved secretly, I guess.
if !ok {
return 0
}
if c.DeckIndex() >= len(deckMoveCount) {
//TODO: warn?
return 0
}
if c.DeckIndex() < 0 {
//This should never happen
return 0
}
return deckMoveCount[c.DeckIndex()]
}
//movedSecretly increments the secretMoveCount for this component.
func (c componentInstance) movedSecretly() {
if c.Equivalent(c.Deck().GenericComponent()) {
return
}
s := c.statePtr
if s == nil {
return
}
deckMoveCount, ok := s.secretMoveCount[c.Deck().Name()]
//We must be the first component in this deck that has been secretly
//moved. Create the whole int stack for this group.
if !ok {
//The zero-value will be fine
deckMoveCount = make([]int, len(c.Deck().Components()))
s.secretMoveCount[c.Deck().Name()] = deckMoveCount
}
if c.DeckIndex() >= len(deckMoveCount) {
//TODO: warn?
return
}
if c.DeckIndex() < 0 {
//This should never happen
return
}
deckMoveCount[c.DeckIndex()]++
}
func (c componentInstance) MoveTo(other Stack, slotIndex int) error {
if slotIndex < 0 {
return errors.New("Invalid slotIndex")
}
source, sourceIndex, err := c.ContainingStack()
if err != nil {
return errors.New("The source component was not in a mutable stack: " + err.Error())
}
return source.moveComponent(sourceIndex, other, slotIndex)
}
func (c componentInstance) SecretMoveTo(other Stack, slotIndex int) error {
if slotIndex < 0 {
return errors.New("Invalid slotIndex")
}
source, sourceIndex, err := c.ContainingStack()
if err != nil {
return errors.New("The source component was not in a mutable stack: " + err.Error())
}
return source.secretMoveComponent(sourceIndex, other, slotIndex)
}
func (c componentInstance) MoveToFirstSlot(other Stack) error {
source, sourceIndex, err := c.ContainingStack()
if err != nil {
return errors.New("The source component was not in a mutable stack: " + err.Error())
}
return source.moveComponent(sourceIndex, other, other.firstSlot())
}
func (c componentInstance) MoveToLastSlot(other Stack) error {
source, sourceIndex, err := c.ContainingStack()
if err != nil {
return errors.New("The source component was not in a mutable stack: " + err.Error())
}
return source.moveComponent(sourceIndex, other, other.lastSlot())
}
func (c componentInstance) MoveToNextSlot(other Stack) error {
source, sourceIndex, err := c.ContainingStack()
if err != nil {
return errors.New("The source component was not in a mutable stack: " + err.Error())
}
return source.moveComponent(sourceIndex, other, other.nextSlot())
}
func (c componentInstance) SlideToFirstSlot() error {
source, sourceIndex, err := c.ContainingStack()
if err != nil {
return errors.New("The source component was not in a mutable stack: " + err.Error())
}
return source.moveComponentToStart(sourceIndex)
}
func (c componentInstance) SlideToLastSlot() error {
source, sourceIndex, err := c.ContainingStack()
if err != nil {
return errors.New("The source component was not in a mutable stack: " + err.Error())
}
return source.moveComponentToEnd(sourceIndex)
}
func (c componentInstance) ImmutableState() ImmutableState {
return c.statePtr
}
func (c componentInstance) State() State {
return c.statePtr
}
func (c componentInstance) ImmutableDynamicValues() ImmutableSubState {
return c.DynamicValues()
}
func (c componentInstance) DynamicValues() SubState {
if c.statePtr == nil {
return nil
}
dynamic := c.statePtr.DynamicComponentValues()
values := dynamic[c.Deck().Name()]
if values == nil {
return nil
}
if len(values) <= c.DeckIndex() {
return nil
}
if c.DeckIndex() < 0 {
//TODO: is this the right beahvior now that we have auto-inflation?
return c.Deck().Chest().Manager().Delegate().DynamicComponentValuesConstructor(c.Deck())
}
return values[c.DeckIndex()]
}