Implement first issue.
This seems like the least complicated way to get the desired reusable behaviors.
This commit is contained in:
		
							
								
								
									
										98
									
								
								koboldsim/cards.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								koboldsim/cards.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| package koboldsim | ||||
|  | ||||
| import "git.chromaticdragon.app/kistaro/CardSimEngine/cardsim" | ||||
|  | ||||
| var cards = []Card{ | ||||
| 	&SwitchingCard{ | ||||
| 		Name: cardsim.MsgStr("Warborn"), | ||||
| 		Desc: cardsim.MsgStr(" A surge of anti-kobold sentiment has been reported by your spies on the surface and your military is concerned that anti-kobold vigilantes will attack the tunnels in the near-future.  They want to build up now while doing so is safe."), | ||||
| 		Policies: []Policy{ | ||||
| 			&BasicPolicy{ | ||||
| 				UnenactedDesc: cardsim.MsgStr(`Your war chief paces irritably.  "We have terrible threats facing us.  It's imperative that we build up our numbers in both the military and the domestic sense.  I want creches under military control and a good hunting crew to supply them with food."`), | ||||
| 				EnactedDesc:   cardsim.MsgStr("[current policy] Your war chief is presntly monitoring the situation, building up your military, and securing your creches."), | ||||
| 				Do: func(p *Player) (cardsim.Message, error) { | ||||
| 					p.Stats.Kobolds.Value += 100 | ||||
| 					p.Stats.SectorScavengingProductivity.Value += 0.01 | ||||
| 					p.Stats.GovWarProductivity.Value += 0.02 | ||||
| 					return cardsim.MsgStr("Kobolds are known to be born warriors."), nil | ||||
| 				}, | ||||
| 				Undo: func(p *Player) error { | ||||
| 					p.Stats.Kobolds.Value -= 100 | ||||
| 					p.Stats.SectorScavengingProductivity.Value -= 0.01 | ||||
| 					p.Stats.GovWarProductivity.Value -= 0.02 | ||||
| 					return nil | ||||
| 				}, | ||||
| 			}, | ||||
| 			&BasicPolicy{ | ||||
| 				UnenactedDesc: cardsim.MsgStr(`Your head miner considers the matter worriedly.  "Creches under military control?  No.  That would invite chaos.  We need to dig deeper; we can have a peaceful, orderly society if we just get far enough away from surfacers."`), | ||||
| 				EnactedDesc:   cardsim.MsgStr("[current policy] Your head miner is presently leading a project to dig as far away from the surface as possible."), | ||||
| 				Do: func(p *Player) (cardsim.Message, error) { | ||||
| 					p.Stats.Kobolds.Value += 40 | ||||
| 					p.Stats.SectorMiningProductivity.Value += 0.02 | ||||
| 					p.Stats.GovBureaucracyProductivity.Value += 0.01 | ||||
| 					return cardsim.MsgStr("Kobolds are known to be cowards hiding in the dark."), nil | ||||
| 				}, | ||||
| 				Undo: func(p *Player) error { | ||||
| 					p.Stats.Kobolds.Value -= 40 | ||||
| 					p.Stats.SectorMiningProductivity.Value -= 0.02 | ||||
| 					p.Stats.GovBureaucracyProductivity.Value -= 0.01 | ||||
| 					return nil | ||||
| 				}, | ||||
| 			}, | ||||
| 			&BasicPolicy{ | ||||
| 				UnenactedDesc: cardsim.MsgStr(`Your nursery director is incensed.  "Creches under military control?  Never!  Let young kobolds play!  In fact, cut the military just for suggesting this.  The threats facing us are completely overstated."`), | ||||
| 				EnactedDesc:   cardsim.MsgStr("[current policy] Military funding has been diverted into early childhood education."), | ||||
| 				Do: func(p *Player) (cardsim.Message, error) { | ||||
| 					p.Stats.Kobolds.Value -= 40 | ||||
| 					p.Stats.SectorScavengingProductivity.Value -= 0.01 | ||||
| 					p.Stats.GovWarProductivity.Value -= 0.02 | ||||
| 					return cardsim.MsgStr("An undefended hunting outpost near the surface was recently wiped out by a raid."), nil | ||||
| 				}, | ||||
| 				Undo: func(p *Player) error { | ||||
| 					p.Stats.Kobolds.Value += 40 | ||||
| 					p.Stats.SectorScavengingProductivity.Value += 0.01 | ||||
| 					p.Stats.GovWarProductivity.Value += 0.02 | ||||
| 					return nil | ||||
| 				}, | ||||
| 			}, | ||||
| 			&VerbosePolicy{ | ||||
| 				BasicPolicy: &BasicPolicy{ | ||||
| 					UnenactedDesc: cardsim.MsgStr("This isn't about a disaster and can probably be safely dismissed."), | ||||
| 					Do: func(p *Player) (cardsim.Message, error) { | ||||
| 						p.Stats.Kobolds.Value += 20 | ||||
| 						return cardsim.MsgStr("Creche control doesn't shift that easily."), nil | ||||
| 					}, | ||||
| 					Undo: func(p *Player) error { | ||||
| 						p.Stats.Kobolds.Value -= 20 | ||||
| 						return nil | ||||
| 					}, | ||||
| 				}, | ||||
| 				Content: []DescResult{ | ||||
| 					{ | ||||
| 						Desc:   cardsim.MsgStr("Rejecting this issue will also reject the military's natalist stance."), | ||||
| 						Result: cardsim.MsgStr("Militant natalism has been reduced by policy."), | ||||
| 					}, | ||||
| 					{ | ||||
| 						Desc:   cardsim.MsgStr(`"Dig deeper" pressures in your nation may be excessive.`), | ||||
| 						Result: cardsim.MsgStr("Some of the lower depths are being abandoned."), | ||||
| 					}, | ||||
| 					{ | ||||
| 						Desc:   cardsim.MsgStr("Near-surface patrols may need to be increased."), | ||||
| 						Result: cardsim.MsgStr("More and better-armed hunting outpsts are being established."), | ||||
| 					}, | ||||
| 					{ | ||||
| 						Desc:   cardsim.MsgStr("This isn't about a disaster and can probably continue to be safely dismissed."), | ||||
| 						Result: cardsim.MsgStr("Creche control doesn't shift that easily."), | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, // end of "Warborn" policies | ||||
| 	}, // end of "Warborn" card | ||||
| } // end of card list | ||||
|  | ||||
| func initDeck(d *cardsim.Deck[*KoboldMine]) { | ||||
| 	for _, c := range cards { | ||||
| 		d.Insert(cardsim.BottomOfDeck, c) | ||||
| 	} | ||||
| 	d.Shuffle() | ||||
| } | ||||
| @@ -16,14 +16,18 @@ type Policy interface { | ||||
| 	// Unenact reverses the previous enactment of this policy. | ||||
| 	Unenact(p *Player) error | ||||
|  | ||||
| 	// LastEnacted informs this Policy which choice was last enacted; it will | ||||
| 	// need to prepare to describe itself differently depending on whether | ||||
| 	// this is itself or not. The policy must return the CardOption that should | ||||
| 	// be used further, which is usually self. | ||||
| 	// LastEnacted informs this Policy which choice was last enacted and | ||||
| 	// which index it is under; this allows it to prepare to describe | ||||
| 	// itself differently depending on the last selected policy. If no | ||||
| 	// option has ever been chosen for this card, the index is -1. | ||||
| 	// | ||||
| 	// The Card that would present this Policy as an option must use this, | ||||
| 	// and provide the active policy that this is a candidate to replace, | ||||
| 	LastEnacted(CardOption) CardOption | ||||
| 	LastEnacted(int, Policy) | ||||
|  | ||||
| 	// Is returns whether this policy is this other policy. This is a strict | ||||
| 	// identity equality check, don't do anything clever here. | ||||
| 	Is(Policy) bool | ||||
| } | ||||
|  | ||||
| // A SwitchingCard is an issue card that remembers which option was selected | ||||
| @@ -35,7 +39,7 @@ type SwitchingCard struct { | ||||
| 	IsUrgent   bool | ||||
| 	After      func(*SwitchingCard, *Player, CardOption) error | ||||
| 	Policies   []Policy | ||||
| 	lastPolicy CardOption | ||||
| 	lastPolicy Policy | ||||
| } | ||||
|  | ||||
| // Title implements Card. | ||||
| @@ -59,24 +63,42 @@ func (s *SwitchingCard) EventText(*Player) (cardsim.Message, error) { | ||||
| } | ||||
|  | ||||
| // Options implements Card. | ||||
| func (s *SwitchingCard) Options(p *Player) ([]CardOption, error) { | ||||
| func (s *SwitchingCard) Options(*Player) ([]CardOption, error) { | ||||
| 	lastIdx := -1 | ||||
| 	for i, p := range s.Policies { | ||||
| 		if p.Is(s.lastPolicy) { | ||||
| 			lastIdx = i | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	ret := make([]CardOption, len(s.Policies)) | ||||
| 	for i, p := range s.Policies { | ||||
| 		ret[i] = p.LastEnacted(s.lastPolicy) | ||||
| 		p.LastEnacted(lastIdx, s.lastPolicy) | ||||
| 		ret[i] = p | ||||
| 	} | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| // Then implements Card. | ||||
| func (s *SwitchingCard) Then(p *Player, o CardOption) error { | ||||
| 	s.lastPolicy = o | ||||
| 	if s.After != nil { | ||||
| 		return s.After(s, p, o) | ||||
| 	newPolicy := o.(Policy) | ||||
| 	var errs cardsim.ErrorCollector | ||||
| 	if !newPolicy.Is(s.lastPolicy) { | ||||
| 		err := s.lastPolicy.Unenact(p) | ||||
| 		if cardsim.IsSeriousError(err) { | ||||
| 			return err | ||||
| 		} | ||||
| 		errs.Add(err) | ||||
| 	} | ||||
| 	return nil | ||||
| 	s.lastPolicy = o.(Policy) | ||||
| 	if s.After != nil { | ||||
| 		errs.Add(s.After(s, p, o)) | ||||
| 	} | ||||
| 	return errs.Emit() | ||||
| } | ||||
|  | ||||
| // BasicPolicy is a straightfoward implementation of Policy. | ||||
| // BasicPolicy is a straightfoward implementation of Policy. If the currently | ||||
| // enacted option is re-enacted, it refunds the player's action point. | ||||
| type BasicPolicy struct { | ||||
| 	UnenactedDesc  cardsim.Message | ||||
| 	EnactedDesc    cardsim.Message | ||||
| @@ -94,18 +116,17 @@ func YesWeCan(*Player) bool { | ||||
| } | ||||
|  | ||||
| // lastEnacted notifies b about the last-enacted policy in its group. It updates | ||||
| // b.currentlyEnacted accordingly and returns itself as a generic CardOption. | ||||
| // b.currentlyEnacted accordingly and returns itself. | ||||
| // | ||||
| // It also installs placeholders if CanDo or CanUndo is unspecified. | ||||
| func (b *BasicPolicy) LastEnacted(c CardOption) CardOption { | ||||
| func (b *BasicPolicy) LastEnacted(_ int, p Policy) { | ||||
| 	b.currentlyEnacted = false | ||||
| 	if o, ok := c.(*BasicPolicy); ok && o == b { | ||||
| 	if o, ok := p.(*BasicPolicy); ok && o == b { | ||||
| 		b.currentlyEnacted = true | ||||
| 	} | ||||
| 	if b.CanDo == nil { | ||||
| 		b.CanDo = YesWeCan | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // OptionText implements CardOption. | ||||
| @@ -119,6 +140,10 @@ func (b *BasicPolicy) OptionText(*Player) (cardsim.Message, error) { | ||||
| // Enact implements CardOption. | ||||
| func (b *BasicPolicy) Enact(p *Player) (cardsim.Message, error) { | ||||
| 	if b.currentlyEnacted { | ||||
| 		p.ActionsRemaining++ | ||||
| 		if b.NothingChanged == nil { | ||||
| 			b.NothingChanged = cardsim.MsgStr("You continue your current approach.") | ||||
| 		} | ||||
| 		return b.NothingChanged, nil | ||||
| 	} | ||||
| 	if !b.CanDo(p) { | ||||
| @@ -145,3 +170,54 @@ func (b *BasicPolicy) Enabled(p *Player) bool { | ||||
| 	} | ||||
| 	return b.CanDo(p) | ||||
| } | ||||
|  | ||||
| func (b *BasicPolicy) Is(p Policy) bool { | ||||
| 	if o, ok := p.(*BasicPolicy); ok { | ||||
| 		return o == b | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // A DescResuilt is descriptive text for an option and the text result of | ||||
| // enacting that option when it was described this way. | ||||
| type DescResult struct { | ||||
| 	Desc   cardsim.Message | ||||
| 	Result cardsim.Message | ||||
| } | ||||
|  | ||||
| // A VerbosePolicy is an extension to a BasicPolicy. It emits the BasicPolicy's | ||||
| // `UnenactedDesc` and the message returned from Do only when no policy has ever | ||||
| // been enacted for this card; otherwise, it looks up the description and result | ||||
| // from a slice using the index of the last policy selected. | ||||
| type VerbosePolicy struct { | ||||
| 	*BasicPolicy | ||||
| 	lastIdx int | ||||
| 	Content []DescResult | ||||
| } | ||||
|  | ||||
| func (v *VerbosePolicy) LastEnacted(i int, p Policy) { | ||||
| 	v.lastIdx = i | ||||
| 	v.BasicPolicy.LastEnacted(i, p) | ||||
| } | ||||
|  | ||||
| func (v *VerbosePolicy) OptionText(*Player) (cardsim.Message, error) { | ||||
| 	if v.lastIdx < 0 { | ||||
| 		return v.BasicPolicy.UnenactedDesc, nil | ||||
| 	} | ||||
| 	return v.Content[v.lastIdx].Desc, nil | ||||
| } | ||||
|  | ||||
| func (v *VerbosePolicy) Enact(p *Player) (cardsim.Message, error) { | ||||
| 	msg, err := v.BasicPolicy.Enact(p) | ||||
| 	if v.lastIdx >= 0 { | ||||
| 		msg = v.Content[v.lastIdx].Result | ||||
| 	} | ||||
| 	return msg, err | ||||
| } | ||||
|  | ||||
| func (v *VerbosePolicy) Is(p Policy) bool { | ||||
| 	if o, ok := p.(*VerbosePolicy); ok { | ||||
| 		return o == p | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user