Play cards.
This commit is contained in:
		| @@ -2,11 +2,16 @@ package cardsim | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrUncooperativeCards = errors.New("a milion cards refused to join the hand") | ||||
| 	ErrInvalidCard        = errors.New("invalid card specified") | ||||
| 	ErrInvalidChoice      = errors.New("invalid choice specified") | ||||
| 	ErrNotUrgent          = errors.New("action not urgent when urgent card is available") | ||||
| 	ErrNoActions          = errors.New("no actions remaining") | ||||
|  | ||||
| 	WarningStalemate = errors.New("no actions can be taken") | ||||
| ) | ||||
| @@ -92,7 +97,10 @@ type Player[C StatsCollection] struct { | ||||
| 	ActionsPerTurn   int | ||||
| 	ActionsRemaining int | ||||
|  | ||||
| 	// PermanentActions are an "extra hand" of cards that are not discarded when used. | ||||
| 	// PermanentActions are an "extra hand" of cards that are not discarded when | ||||
| 	// used. An Urgent PermanentAction does not block non-urgent actions and | ||||
| 	// cards in hand from being used, but it can be used even when an urgent | ||||
| 	// card is in the hand. | ||||
| 	PermanentActions []Card[C] | ||||
|  | ||||
| 	// InfoPanels lists informational views available to the player. The Prompt | ||||
| @@ -197,7 +205,7 @@ func (p *Player[C]) Simulate() error { | ||||
|  | ||||
| 	if p.DebugLevel > 0 && !errs.IsEmpty() { | ||||
| 		p.ChapterBreak() | ||||
| 		p.TemporaryMessages = append(p.TemporaryMessages, Msgf("%d errors and warnings:", len(errs.Errs))) | ||||
| 		p.TemporaryMessages = append(p.TemporaryMessages, Msgf("%d ERRORS AND WARNINGS:", len(errs.Errs))) | ||||
| 		for i, e := range errs.Errs { | ||||
| 			yikes := " " | ||||
| 			if IsSeriousError(e) { | ||||
| @@ -255,3 +263,128 @@ func (p *Player[C]) FillHand() error { | ||||
|  | ||||
| 	return WarningTooFewCards | ||||
| } | ||||
|  | ||||
| // HasUrgentCards returns whether any cards in the Hand think they are Urgent. | ||||
| func (p *Player[C]) HasUrgentCards() bool { | ||||
| 	for _, c := range p.Hand { | ||||
| 		if c.Urgent(p) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // EnactCardUnchecked executes a card choice, removes it from the hand, and | ||||
| // decrements the ActionsRemaining. It does not check for conflicting Urgent | ||||
| // cards or already being out of actions. If no such card or card choice | ||||
| // exists, this returns nil and ErrInvalidCard/ErrInvalidChoice. Otherwise, this returns | ||||
| // the result of enacting the card. If enacting the card causes an error, | ||||
| // the State becomes GameCrashed. | ||||
| func (p *Player[C]) EnactCardUnchecked(cardIdx, choiceIdx int) (Message, error) { | ||||
| 	if cardIdx < 0 || cardIdx >= len(p.Hand) { | ||||
| 		return nil, fmt.Errorf("%w: no card #%d when %d cards in hand", ErrInvalidCard, cardIdx, len(p.Hand)) | ||||
| 	} | ||||
| 	card := p.Hand[cardIdx] | ||||
| 	var errs ErrorCollector | ||||
| 	options, err := card.Options(p) | ||||
| 	if IsSeriousError(err) { | ||||
| 		p.State = GameCrashed | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	errs.Add(err) | ||||
| 	if choiceIdx < 0 || choiceIdx > len(options) { | ||||
| 		errs.Add(fmt.Errorf("%w: no option #%d on card #%d with %d options", ErrInvalidCard, choiceIdx, cardIdx, len(options))) | ||||
| 		return nil, errs.Emit() | ||||
| 	} | ||||
|  | ||||
| 	p.Hand = DeleteFrom(p.Hand, cardIdx) | ||||
| 	p.ActionsRemaining-- | ||||
|  | ||||
| 	ret, err := options[choiceIdx].Enact(p) | ||||
| 	errs.Add(err) | ||||
| 	if IsSeriousError(err) { | ||||
| 		p.State = GameCrashed | ||||
| 		return ret, errs.Emit() | ||||
| 	} | ||||
|  | ||||
| 	err = card.Then(p, options[choiceIdx]) | ||||
| 	errs.Add(err) | ||||
| 	if IsSeriousError(err) { | ||||
| 		p.State = GameCrashed | ||||
| 	} | ||||
| 	return ret, errs.Emit() | ||||
| } | ||||
|  | ||||
| // EnactCard executes a card choice, removes it from the hand, and decrements | ||||
| // the ActionsRemaining. If the card is not Urgent but urgent cards are | ||||
| // available, or the player is out of actions, this returns ErrNotUrgent or | ||||
| // ErrNoActions. Otherwise, this acts like EnactCardUnchecked. | ||||
| func (p *Player[C]) EnactCard(cardIdx, choiceIdx int) (Message, error) { | ||||
| 	if p.ActionsRemaining <= 0 { | ||||
| 		return nil, ErrNoActions | ||||
| 	} | ||||
| 	if cardIdx < 0 || cardIdx >= len(p.Hand) { | ||||
| 		return nil, fmt.Errorf("%w: no card #%d when %d cards in hand", ErrInvalidCard, cardIdx, len(p.Hand)) | ||||
| 	} | ||||
| 	if !p.Hand[cardIdx].Urgent(p) && p.HasUrgentCards() { | ||||
| 		return nil, ErrNotUrgent | ||||
| 	} | ||||
| 	return p.EnactCardUnchecked(cardIdx, choiceIdx) | ||||
| } | ||||
|  | ||||
| // EnactPermanentActionUnchecked executes a permanently-available action and | ||||
| // decrements the ActionsRemaining. It does not check for conflicting Urgent | ||||
| // cards or already being out of actions. If no such action or card option | ||||
| // exists, this returns nil and ErrInvalidCard/ErrInvalidChoice. Otherwise, this returns | ||||
| // the result of enacting the permanent action. If enacting the card causes an error, | ||||
| // the State becomes GameCrashed. | ||||
| func (p *Player[C]) EnactPermanentActionUnchecked(actionIdx, choiceIdx int) (Message, error) { | ||||
| 	if actionIdx < 0 || actionIdx >= len(p.PermanentActions) { | ||||
| 		return nil, fmt.Errorf("%w: no action #%d when %d permanent actions exist", ErrInvalidCard, actionIdx, len(p.PermanentActions)) | ||||
| 	} | ||||
| 	card := p.PermanentActions[actionIdx] | ||||
| 	var errs ErrorCollector | ||||
| 	options, err := card.Options(p) | ||||
| 	if IsSeriousError(err) { | ||||
| 		p.State = GameCrashed | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	errs.Add(err) | ||||
| 	if choiceIdx < 0 || choiceIdx > len(options) { | ||||
| 		errs.Add(fmt.Errorf("%w: no option #%d on permanent action #%d with %d options", ErrInvalidChoice, choiceIdx, actionIdx, len(options))) | ||||
| 		return nil, errs.Emit() | ||||
| 	} | ||||
|  | ||||
| 	p.ActionsRemaining-- | ||||
|  | ||||
| 	ret, err := options[choiceIdx].Enact(p) | ||||
| 	errs.Add(err) | ||||
| 	if IsSeriousError(err) { | ||||
| 		p.State = GameCrashed | ||||
| 		return ret, errs.Emit() | ||||
| 	} | ||||
|  | ||||
| 	err = card.Then(p, options[choiceIdx]) | ||||
| 	errs.Add(err) | ||||
| 	if IsSeriousError(err) { | ||||
| 		p.State = GameCrashed | ||||
| 	} | ||||
| 	return ret, errs.Emit() | ||||
| } | ||||
|  | ||||
| // EnactPermanentAction executes a permanently-available card and decrements | ||||
| // the ActionsRemaining. If the action is not Urgent but urgent cards are | ||||
| // available, or the player is out of actions, this returns ErrNotUrgent or | ||||
| // ErrNoActions. Otherwise, this acts like EnactPermanentActionUnchecked. | ||||
| func (p *Player[C]) EnactPermanentAction(actionIdx, choiceIdx int) (Message, error) { | ||||
| 	if p.ActionsRemaining <= 0 { | ||||
| 		return nil, ErrNoActions | ||||
| 	} | ||||
| 	if actionIdx < 0 || actionIdx >= len(p.PermanentActions) { | ||||
| 		return nil, fmt.Errorf("%w: no action #%d when %d permanent actions available", ErrInvalidCard, actionIdx, len(p.PermanentActions)) | ||||
| 	} | ||||
| 	if !p.PermanentActions[actionIdx].Urgent(p) && p.HasUrgentCards() { | ||||
| 		return nil, ErrNotUrgent | ||||
| 	} | ||||
| 	return p.EnactPermanentActionUnchecked(actionIdx, choiceIdx) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user